diff --git a/.gitignore b/.gitignore index fa1e16d4c..0dae662d7 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ db/zm_create.sql depcomp docs/_build install-sh +install_manifest.txt misc/CMakeFiles/ misc/apache.conf misc/cmake_install.cmake @@ -85,11 +86,40 @@ scripts/zmupdate.pl scripts/zmvideo.pl scripts/zmwatch.pl scripts/zmx10.pl +scripts/zoneminder-zmaudit.pl.8 +scripts/zoneminder-zmaudit.pl.8.gz +scripts/zoneminder-zmcamtool.pl.8 +scripts/zoneminder-zmcamtool.pl.8.gz +scripts/zoneminder-zmcontrol.pl.8 +scripts/zoneminder-zmcontrol.pl.8.gz +scripts/zoneminder-zmdc.pl.8 +scripts/zoneminder-zmdc.pl.8.gz +scripts/zoneminder-zmfilter.pl.8 +scripts/zoneminder-zmfilter.pl.8.gz +scripts/zoneminder-zmpkg.pl.8 +scripts/zoneminder-zmpkg.pl.8.gz +scripts/zoneminder-zmsystemctl.pl.8 +scripts/zoneminder-zmsystemctl.pl.8.gz +scripts/zoneminder-zmtelemetry.pl.8 +scripts/zoneminder-zmtelemetry.pl.8.gz +scripts/zoneminder-zmtrack.pl.8 +scripts/zoneminder-zmtrack.pl.8.gz +scripts/zoneminder-zmtrigger.pl.8 +scripts/zoneminder-zmtrigger.pl.8.gz +scripts/zoneminder-zmupdate.pl.8 +scripts/zoneminder-zmupdate.pl.8.gz +scripts/zoneminder-zmvideo.pl.8 +scripts/zoneminder-zmvideo.pl.8.gz +scripts/zoneminder-zmwatch.pl.8 +scripts/zoneminder-zmwatch.pl.8.gz +scripts/zoneminder-zmx10.pl.8 +scripts/zoneminder-zmx10.pl.8.gz src/*.o src/.deps/ src/CMakeFiles/ src/cmake_install.cmake src/libzm.a +src/nph-zms src/zm_config.h src/zm_config_defines.h src/zma @@ -97,6 +127,16 @@ src/zmc src/zmf src/zms src/zmu +src/zoneminder-zma.8 +src/zoneminder-zma.8.gz +src/zoneminder-zmc.8 +src/zoneminder-zmc.8.gz +src/zoneminder-zmf.8 +src/zoneminder-zmf.8.gz +src/zoneminder-zmstreamer.8 +src/zoneminder-zmstreamer.8.gz +src/zoneminder-zmu.8 +src/zoneminder-zmu.8.gz stamp-h.in stamp-h1 web/CMakeFiles/ @@ -104,12 +144,16 @@ web/api/CMakeFiles/ web/api/app/Config/bootstrap.php web/api/app/Config/core.php web/api/cmake_install.cmake +web/cgi-bin/ web/cmake_install.cmake +web/events/ +web/images/ web/includes/config.php web/tools/mootools/CMakeFiles/ web/tools/mootools/cmake_install.cmake web/tools/mootools/mootools-core.js web/tools/mootools/mootools-more.js +web/undef.log zm.conf zmconfgen.pl zmlinkcontent.sh diff --git a/.travis.yml b/.travis.yml index 65e7bed95..27fa5da49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,4 +48,4 @@ script: - mysql -uzmuser -pzmpass < db/zm_create.sql - mysql -uzmuser -pzmpass zm < db/test.monitor.sql - sudo zmpkg.pl start - - sudo zmfilter.pl -f purgewhenfull + - sudo zmfilter.pl --filter purgewhenfull diff --git a/CMakeLists.txt b/CMakeLists.txt index a70ebe8e8..5f47064d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,24 @@ set(CMAKE_CXX_FLAGS_DEBUG "-Wall -D__STDC_CONSTANT_MACROS -g") set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") +# GCC below 6.0 doesn't support __target__("fpu=neon") attribute, required for compiling ARM Neon code, otherwise compilation fails. +# Must use -mfpu=neon compiler flag instead, but only do that for processors that support neon, otherwise strip the neon code alltogether, +# because passing -fmpu=neon is unsafe to processors that don't support neon +IF(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" AND CMAKE_SYSTEM_NAME MATCHES "Linux") + IF(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) + EXEC_PROGRAM(grep ARGS " neon " "/proc/cpuinfo" OUTPUT_VARIABLE neonoutput RETURN_VALUE neonresult) + IF(neonresult EQUAL 0) + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mfpu=neon") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpu=neon") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -mfpu=neon") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -mfpu=neon") + ELSE(neonresult EQUAL 0) + add_definitions(-DZM_STRIP_NEON=1) + message(STATUS "ARM Neon is not available on this processor. Neon functions will be absent") + ENDIF(neonresult EQUAL 0) + ENDIF(CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) +ENDIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm" AND CMAKE_SYSTEM_NAME MATCHES "Linux") + # Modules that we need: include (GNUInstallDirs) include (CheckIncludeFile) @@ -403,6 +421,59 @@ else(MYSQLCLIENT_LIBRARIES) "ZoneMinder requires mysqlclient but it was not found on your system") endif(MYSQLCLIENT_LIBRARIES) +# x264 (using find_library and find_path) +find_library(X264_LIBRARIES x264) +if(X264_LIBRARIES) + set(HAVE_LIBX264 1) + list(APPEND ZM_BIN_LIBS "${X264_LIBRARIES}") + find_path(X264_INCLUDE_DIR x264.h) + if(X264_INCLUDE_DIR) + include_directories("${X264_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${X264_INCLUDE_DIR}") + endif(X264_INCLUDE_DIR) + mark_as_advanced(FORCE X264_LIBRARIES X264_INCLUDE_DIR) + check_include_files("stdint.h;x264.h" HAVE_X264_H) + set(optlibsfound "${optlibsfound} x264") +else(X264_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} x264") +endif(X264_LIBRARIES) + +# mp4v2 (using find_library and find_path) +find_library(MP4V2_LIBRARIES mp4v2) +if(MP4V2_LIBRARIES) + set(HAVE_LIBMP4V2 1) + list(APPEND ZM_BIN_LIBS "${MP4V2_LIBRARIES}") + + # mp4v2/mp4v2.h + find_path(MP4V2_INCLUDE_DIR mp4v2/mp4v2.h) + if(MP4V2_INCLUDE_DIR) + include_directories("${MP4V2_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") + endif(MP4V2_INCLUDE_DIR) + check_include_file("mp4v2/mp4v2.h" HAVE_MP4V2_MP4V2_H) + + # mp4v2.h + find_path(MP4V2_INCLUDE_DIR mp4v2.h) + if(MP4V2_INCLUDE_DIR) + include_directories("${MP4V2_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") + endif(MP4V2_INCLUDE_DIR) + check_include_file("mp4v2.h" HAVE_MP4V2_H) + + # mp4.h + find_path(MP4V2_INCLUDE_DIR mp4.h) + if(MP4V2_INCLUDE_DIR) + include_directories("${MP4V2_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${MP4V2_INCLUDE_DIR}") + endif(MP4V2_INCLUDE_DIR) + check_include_file("mp4.h" HAVE_MP4_H) + + mark_as_advanced(FORCE MP4V2_LIBRARIES MP4V2_INCLUDE_DIR) + set(optlibsfound "${optlibsfound} mp4v2") +else(MP4V2_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} mp4v2") +endif(MP4V2_LIBRARIES) + set(PATH_FFMPEG "") set(OPT_FFMPEG "no") # Do not check for ffmpeg if ZM_NO_FFMPEG is on @@ -493,6 +564,23 @@ if(NOT ZM_NO_FFMPEG) set(optlibsnotfound "${optlibsnotfound} SWScale") endif(SWSCALE_LIBRARIES) + # rescale (using find_library and find_path) + find_library(SWRESAMPLE_LIBRARIES swresample) + if(SWRESAMPLE_LIBRARIES) + set(HAVE_LIBSWRESAMPLE 1) + list(APPEND ZM_BIN_LIBS "${SWRESAMPLE_LIBRARIES}") + find_path(SWRESAMPLE_INCLUDE_DIR "libswresample/swresample.h" /usr/include/ffmpeg) + if(SWRESAMPLE_INCLUDE_DIR) + include_directories("${SWRESAMPLE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${SWRESAMPLE_INCLUDE_DIR}") + endif(SWRESAMPLE_INCLUDE_DIR) + mark_as_advanced(FORCE SWRESAMPLE_LIBRARIES SWRESAMPLE_INCLUDE_DIR) + check_include_file("libswresample/swresample.h" HAVE_LIBSWRESAMPLE_SWRESAMPLE_H) + set(optlibsfound "${optlibsfound} SWResample") + else(SWRESAMPLE_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} SWResample") + endif(SWRESAMPLE_LIBRARIES) + # Find the path to the ffmpeg executable find_program(FFMPEG_EXECUTABLE NAMES ffmpeg avconv @@ -525,6 +613,13 @@ if(NOT ZM_NO_LIBVLC) endif(LIBVLC_LIBRARIES) endif(NOT ZM_NO_LIBVLC) +find_package(Boost 1.36.0) +if(Boost_FOUND) + include_directories(${Boost_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_INCLUDES "${Boost_INCLUDE_DIRS}") + list(APPEND ZM_BIN_LIBS "${Boost_LIBRARIES}") +endif() + # *** END OF LIBRARY CHECKS *** # Check for gnutls or crypto diff --git a/Dockerfile b/Dockerfile index 837a6d4ce..2c72f0f38 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN apt-get update && \ libdbi-perl libarchive-zip-perl libdate-manip-perl libdevice-serialport-perl libmime-perl libpcre3 \ libwww-perl libdbd-mysql-perl libsys-mmap-perl yasm cmake libjpeg-turbo8-dev \ libjpeg-turbo8 libtheora-dev libvorbis-dev libvpx-dev libx264-dev libmp4v2-dev libav-tools mysql-client \ - apache2 php5 php5-mysql apache2-mpm-prefork libapache2-mod-php5 php5-cli openssh-server \ + apache2 php5 php5-mysql apache2-mpm-prefork libapache2-mod-php5 php5-cli \ mysql-server libvlc-dev libvlc5 libvlccore-dev libvlccore7 vlc-data libcurl4-openssl-dev \ libavformat-dev libswscale-dev libavutil-dev libavcodec-dev libavfilter-dev \ libavresample-dev libavdevice-dev libpostproc-dev libv4l-dev libtool libnetpbm10-dev \ @@ -42,22 +42,12 @@ ADD utils/docker/start.sh /tmp/start.sh # give files in /usr/local/share/zoneminder/ RUN chown -R www-data:www-data /usr/local/share/zoneminder/ -# Creating SSH privilege escalation dir -RUN mkdir /var/run/sshd - # Adding apache virtual hosts file ADD utils/docker/apache-vhost /etc/apache2/sites-available/000-default.conf ADD utils/docker/phpdate.ini /etc/php5/apache2/conf.d/25-phpdate.ini -# Set the root passwd -RUN echo 'root:root' | chpasswd - -# Add a user we can actually login with -RUN useradd -m -s /bin/bash -G sudo zoneminder -RUN echo 'zoneminder:zoneminder' | chpasswd - -# Expose ssh and http ports -EXPOSE 22 80 +# Expose http ports +EXPOSE 80 # Initial database and apache setup: RUN "/ZoneMinder/utils/docker/setup.sh" diff --git a/README.md b/README.md index 460fe62ff..c6ca5e34b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,19 @@ -ZoneMinder -========== +ZoneMinder H264 Patch -[![Build Status](https://travis-ci.org/ZoneMinder/ZoneMinder.png)](https://travis-ci.org/ZoneMinder/ZoneMinder) [![Bountysource](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received) +[![Build Status](https://travis-ci.org/ZoneMinder/ZoneMinder.png?branch=feature-h264-videostorage)](https://travis-ci.org/ZoneMinder/ZoneMinder) [![Bountysource](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received) + +##Feature-h264-videostorage Branch Details +This branch supports direct recording of h264 cameras into MP4 format uisng the h264 Passthrough option, but only with FFMPEG Monitors currently. It also provides h264 encoding for any other monitor type. If you encounter any issues, please open an issue on GitHub and attach it to the h264 milestone. But do remember this is bleeding edge so it will have problems. +Thanks to @chriswiggins and @mastertheknife for their work, @SteveGilvarry is now maintaining this branch and welcomes any assistance. + +**The following SQL changes are required, these will be merged to zmupdate once we are ready to merge this branch to master.** +``` +ALTER TABLE `Monitors` ADD `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' AFTER `Deinterlacing` , +ADD `VideoWriter` TINYINT NOT NULL DEFAULT '0' AFTER `SaveJPEGs` , +ADD `EncoderParameters` TEXT NOT NULL AFTER `VideoWriter` ; + +ALTER TABLE `Events` ADD `DefaultVideo` VARCHAR( 64 ) NOT NULL AFTER `AlarmFrames` ; +``` All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 0d6c853c9..04ed65c85 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -184,6 +184,7 @@ DROP TABLE IF EXISTS `Events`; CREATE TABLE `Events` ( `Id` int(10) unsigned NOT NULL auto_increment, `MonitorId` int(10) unsigned NOT NULL default '0', + `StorageId` smallint(5) unsigned default 0, `Name` varchar(64) NOT NULL default '', `Cause` varchar(32) NOT NULL default '', `StartTime` datetime default NULL, @@ -193,6 +194,7 @@ CREATE TABLE `Events` ( `Length` decimal(10,2) NOT NULL default '0.00', `Frames` int(10) unsigned default NULL, `AlarmFrames` int(10) unsigned default NULL, + `DefaultVideo` VARCHAR( 64 ) DEFAULT '' NOT NULL, `TotScore` int(10) unsigned NOT NULL default '0', `AvgScore` smallint(5) unsigned default '0', `MaxScore` smallint(5) unsigned default '0', @@ -203,6 +205,8 @@ CREATE TABLE `Events` ( `Messaged` tinyint(3) unsigned NOT NULL default '0', `Executed` tinyint(3) unsigned NOT NULL default '0', `Notes` text, + `StateId` int(10) unsigned NOT NULL, + `Orientation` enum('0','90','180','270','hori','vert') NOT NULL default '0', PRIMARY KEY (`Id`,`MonitorId`), KEY `MonitorId` (`MonitorId`), KEY `StartTime` (`StartTime`), @@ -216,6 +220,7 @@ CREATE TABLE `Events` ( DROP TABLE IF EXISTS `Filters`; CREATE TABLE `Filters` ( + `Id` int(10) unsigned NOT NULL auto_increment, `Name` varchar(64) NOT NULL default '', `Query` text NOT NULL, `AutoArchive` tinyint(3) unsigned NOT NULL default '0', @@ -227,7 +232,9 @@ CREATE TABLE `Filters` ( `AutoExecuteCmd` tinytext, `AutoDelete` tinyint(3) unsigned NOT NULL default '0', `Background` tinyint(1) unsigned NOT NULL default '0', - PRIMARY KEY (`Name`) + `Concurrent` tinyint(1) unsigned NOT NULL default '0', + PRIMARY KEY (`Id`), + KEY `Name` (`Name`) ) ENGINE=@ZM_MYSQL_ENGINE@; -- @@ -319,38 +326,43 @@ CREATE TABLE `Monitors` ( `Id` int(10) unsigned NOT NULL auto_increment, `Name` varchar(64) NOT NULL default '', `ServerId` int(10) unsigned, + `StorageId` smallint(5) unsigned default 0, `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local', `Function` enum('None','Monitor','Modect','Record','Mocord','Nodect') NOT NULL default 'Monitor', `Enabled` tinyint(3) unsigned NOT NULL default '1', - `LinkedMonitors` varchar(255) NOT NULL default '', + `LinkedMonitors` varchar(255), `Triggers` set('X10') NOT NULL default '', `Device` tinytext NOT NULL default '', `Channel` tinyint(3) unsigned NOT NULL default '0', `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), `Method` varchar(16) NOT NULL default '', - `Host` varchar(64) NOT NULL default '', + `Host` varchar(64), `Port` varchar(8) NOT NULL default '', `SubPath` varchar(64) NOT NULL default '', - `Path` varchar(255) NOT NULL default '', - `Options` varchar(255) not null default '', - `User` varchar(64) NOT NULL default '', - `Pass` varchar(64) NOT NULL default '', + `Path` varchar(255), + `Options` varchar(255), + `User` varchar(64), + `Pass` varchar(64), `Width` smallint(5) unsigned NOT NULL default '0', `Height` smallint(5) unsigned NOT NULL default '0', `Colours` tinyint(3) unsigned NOT NULL default '1', `Palette` int(10) unsigned NOT NULL default '0', `Orientation` enum('0','90','180','270','hori','vert') NOT NULL default '0', `Deinterlacing` int(10) unsigned NOT NULL default '0', - `RTSPDescribe` tinyint(1) unsigned NOT NULL default '0', + `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' , + `VideoWriter` TINYINT NOT NULL DEFAULT '0', + `EncoderParameters` TEXT NOT NULL, + `RecordAudio` TINYINT NOT NULL DEFAULT '0', + `RTSPDescribe` tinyint(1) unsigned, `Brightness` mediumint(7) NOT NULL default '-1', `Contrast` mediumint(7) NOT NULL default '-1', `Hue` mediumint(7) NOT NULL default '-1', `Colour` mediumint(7) NOT NULL default '-1', `EventPrefix` varchar(32) NOT NULL default 'Event-', - `LabelFormat` varchar(64) NOT NULL default '%N - %y/%m/%d %H:%M:%S', + `LabelFormat` varchar(64) default '%N - %y/%m/%d %H:%M:%S', `LabelX` smallint(5) unsigned NOT NULL default '0', `LabelY` smallint(5) unsigned NOT NULL default '0', `LabelSize` smallint(5) unsigned NOT NULL DEFAULT '1', @@ -371,14 +383,14 @@ CREATE TABLE `Monitors` ( `RefBlendPerc` tinyint(3) unsigned NOT NULL default '6', `AlarmRefBlendPerc` tinyint(3) unsigned NOT NULL default '6', `Controllable` tinyint(3) unsigned NOT NULL default '0', - `ControlId` int(10) unsigned NOT NULL default '0', + `ControlId` int(10) unsigned, `ControlDevice` varchar(255) default NULL, `ControlAddress` varchar(255) default NULL, `AutoStopTimeout` decimal(5,2) default NULL, `TrackMotion` tinyint(3) unsigned NOT NULL default '0', - `TrackDelay` smallint(5) unsigned NOT NULL default '0', + `TrackDelay` smallint(5) unsigned, `ReturnLocation` tinyint(3) NOT NULL default '-1', - `ReturnDelay` smallint(5) unsigned NOT NULL default '0', + `ReturnDelay` smallint(5) unsigned, `DefaultView` enum('Events','Control') NOT NULL default 'Events', `DefaultRate` smallint(5) unsigned NOT NULL default '100', `DefaultScale` smallint(5) unsigned NOT NULL default '100', @@ -389,6 +401,8 @@ CREATE TABLE `Monitors` ( PRIMARY KEY (`Id`) ) ENGINE=@ZM_MYSQL_ENGINE@; +CREATE INDEX `Monitors_ServerId_idx` ON `Monitors` (`ServerId`); + -- -- Table structure for table `States` -- PP - Added IsActive to track custom run states @@ -418,6 +432,9 @@ CREATE TABLE `Servers` ( `State_Id` int(10) unsigned, PRIMARY KEY (`Id`) ) ENGINE=@ZM_MYSQL_ENGINE@; + +CREATE INDEX `Servers_Name_idx` ON `Servers` (`Name`); + -- -- Table structure for table `Stats` -- @@ -467,7 +484,7 @@ CREATE TABLE `Users` ( `Id` int(10) unsigned NOT NULL auto_increment, `Username` varchar(32) character set latin1 collate latin1_bin NOT NULL default '', `Password` varchar(64) NOT NULL default '', - `Language` varchar(8) NOT NULL default '', + `Language` varchar(8), `Enabled` tinyint(3) unsigned NOT NULL default '1', `Stream` enum('None','View') NOT NULL default 'None', `Events` enum('None','View','Edit') NOT NULL default 'None', @@ -476,8 +493,8 @@ CREATE TABLE `Users` ( `Groups` enum('None','View','Edit') NOT NULL default 'None', `Devices` enum('None','View','Edit') NOT NULL default 'None', `System` enum('None','View','Edit') NOT NULL default 'None', - `MaxBandwidth` varchar(16) NOT NULL default '', - `MonitorIds` tinytext NOT NULL, + `MaxBandwidth` varchar(16), + `MonitorIds` tinytext, PRIMARY KEY (`Id`), UNIQUE KEY `UC_Username` (`Username`) ) ENGINE=@ZM_MYSQL_ENGINE@; @@ -544,6 +561,19 @@ CREATE TABLE `Zones` ( KEY `MonitorId` (`MonitorId`) ) ENGINE=@ZM_MYSQL_ENGINE@; +DROP TABLE IF EXISTS `Storage`; +CREATE TABLE `Storage` ( + `Id` smallint(5) unsigned NOT NULL auto_increment, + `Path` varchar(64) NOT NULL default '', + `Name` varchar(64) NOT NULL default '', + PRIMARY KEY (`Id`) +) ENGINE=@ZM_MYSQL_ENGINE@; + +-- +-- Create a default storage location +-- +insert into Storage VALUES (NULL, '/var/cache/zoneminder/events', 'Default' ); + /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; @@ -564,7 +594,7 @@ insert into Users VALUES (NULL,'admin',password('admin'),'',1,'View','Edit','Edi -- -- Add a sample filter to purge the oldest 100 events when the disk is 95% full -- -insert into Filters values ('PurgeWhenFull','{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}',0,0,0,0,0,0,'',1,1); +insert into Filters values (NULL,'PurgeWhenFull','{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}',0,0,0,0,0,0,'',1,1,0); -- -- Add in some sample control protocol definitions @@ -595,6 +625,7 @@ INSERT INTO `Controls` VALUES (NULL,'IPCC 7210W','Libvlc','IPCC7210W', 1, 1, 1, INSERT INTO `Controls` VALUES (NULL,'Vivotek ePTZ','Remote','Vivotek_ePTZ',0,0,1,1,0,0,0,1,0,0,0,0,1,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,1,0,5,0,0,1,0,0,0,0,1,0,5,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Netcat ONVIF','Ffmpeg','Netcat',0,0,1,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,100,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,100,5,5,0,0,0,1,255,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Keekoon','Remote','Keekoon', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); +INSERT INTO `Controls` VALUES (NULL,'HikVision','Local','',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,1,1,1,1,0,0,0,1,1,0,0,0,0,1,1,100,0,0,1,0,0,0,0,1,1,100,1,0,0,0); -- -- Add some monitor preset values @@ -667,6 +698,8 @@ INSERT INTO MonitorPresets VALUES (NULL,'Foscam FI9821W FFMPEG H.264','Ffmpeg',N INSERT INTO MonitorPresets VALUES (NULL,'Loftek Sentinel PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,'','80','/videostream.cgi?user=&pwd=&resolution=32&rate=11',NULL,640,480,4,NULL,1,'13','',':@',100,100); INSERT INTO MonitorPresets VALUES (NULL,'Airlink 777W PTZ, 640x480, mjpeg','Remote','http',0,0,NULL,NULL,':@','80','/cgi/mjpg/mjpg.cgi',NULL,640,480,4,NULL,1,'7','',':@',100,100); INSERT INTO MonitorPresets VALUES (NULL,'SunEyes SP-P1802SWPTZ','Libvlc','/dev/video','0',255,'','rtpMulti','','80','rtsp://:554/11','',1920,1080,0,0.00,1,'16','-speed=64',':',100,33); +INSERT INTO MonitorPresets VALUES (NULL,'Qihan IP, 1280x720, RTP/RTSP','Ffmpeg','rtsp','rtpRtsp',255,'rtsp','rtpRtsp',NULL,554,'rtsp:///tcp_live/ch0_0',NULL,1280,720,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,'Qihan IP, 1920x1080, RTP/RTSP','Ffmpeg','rtsp','rtpRtsp',255,'rtsp','rtpRtsp',NULL,554,'rtsp:///tcp_live/ch0_0',NULL,1920,1080,3,NULL,0,NULL,NULL,NULL,100,100); -- -- Add some zone preset values @@ -685,3 +718,4 @@ INSERT INTO ZonePresets VALUES (7,'Best, high sensitivity','Active','Percent','B -- This section is autogenerated by zmconfgen.pl -- Do not edit this file as any changes will be overwritten -- + diff --git a/db/zm_update-1.28.110.sql b/db/zm_update-1.28.110.sql index 7a573bbf3..388b521aa 100644 --- a/db/zm_update-1.28.110.sql +++ b/db/zm_update-1.28.110.sql @@ -2,10 +2,6 @@ -- This updates a 1.28.109 database to 1.28.110 -- --- --- Update Frame table to have a PrimaryKey of ID, insetad of a Composite Primary Key --- Used primarially for compatibility with CakePHP --- SET @s = (SELECT IF( (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS diff --git a/db/zm_update-1.29.1.sql b/db/zm_update-1.29.1.sql deleted file mode 100644 index 8fd43c318..000000000 --- a/db/zm_update-1.29.1.sql +++ /dev/null @@ -1,7 +0,0 @@ --- --- This updates a 1.29.0 database to 1.29.1 --- --- - --- Increase the size of the Pid field for FreeBSD -ALTER TABLE Logs MODIFY Pid int(10); diff --git a/db/zm_update-1.30.1.sql b/db/zm_update-1.30.1.sql index f3d5698d3..298668e08 100644 --- a/db/zm_update-1.30.1.sql +++ b/db/zm_update-1.30.1.sql @@ -1,7 +1,110 @@ -- --- This updates a 1.30.0 database to 1.30.1 --- --- Alter type of Messages column from VARCHAR(255) to TEXT +-- This updates a 1.28.108 database to 1.28.109 -- -ALTER TABLE Logs MODIFY Message TEXT; +-- +-- Add Controls definition for Vivotek ePTZ +-- +INSERT INTO Controls +SELECT * FROM (SELECT NULL as Id, + 'Vivotek ePTZ' as Name, + 'Remote' as Type, + 'Vivotek_ePTZ' as Protocol, + 0 as CanWake, + 0 as CanSleep, + 1 as CanReset, + 1 as CanZoom, + 0 as CanAutoZoom, + 0 as CanZoomAbs, + 0 as CanZoomRel, + 1 as CanZoomCon, + 0 as MinZoomRange, + 0 as MaxZoomRange, + 0 as MinZoomStep, + 0 as MaxZoomStep, + 1 as HasZoomSpeed, + 0 as MinZoomSpeed, + 5 as MaxZoomSpeed, + 0 as CanFocus, + 0 as CanAutoFocus, + 0 as CanFocusAbs, + 0 as CanFocusRel, + 0 as CanFocusCon, + 0 as MinFocusRange, + 0 as MaxFocusRange, + 0 as MinFocusStep, + 0 as MaxFocusStep, + 0 as HasFocusSpeed, + 0 as MinFocusSpeed, + 0 as MaxFocusSpeed, + 0 as CanIris, + 0 as CanAutoIris, + 0 as CanIrisAbs, + 0 as CanIrisRel, + 0 as CanIrisCon, + 0 as MinIrisRange, + 0 as MaxIrisRange, + 0 as MinIrisStep, + 0 as MaxIrisStep, + 0 as HasIrisSpeed, + 0 as MinIrisSpeed, + 0 as MaxIrisSpeed, + 0 as CanGain, + 0 as CanAutoGain, + 0 as CanGainAbs, + 0 as CanGainRel, + 0 as CanGainCon, + 0 as MinGainRange, + 0 as MaxGainRange, + 0 as MinGainStep, + 0 as MaxGainStep, + 0 as HasGainSpeed, + 0 as MinGainSpeed, + 0 as MaxGainSpeed, + 0 as CanWhite, + 0 as CanAutoWhite, + 0 as CanWhiteAbs, + 0 as CanWhiteRel, + 0 as CanWhiteCon, + 0 as MinWhiteRange, + 0 as MaxWhiteRange, + 0 as MinWhiteStep, + 0 as MaxWhiteStep, + 0 as HasWhiteSpeed, + 0 as MinWhiteSpeed, + 0 as MaxWhiteSpeed, + 0 as HasPresets, + 0 as NumPresets, + 0 as HasHomePreset, + 0 as CanSetPresets, + 1 as CanMove, + 0 as CanMoveDiag, + 0 as CanMoveMap, + 0 as CanMoveAbs, + 0 as CanMoveRel, + 1 as CanMoveCon, + 1 as CanPan, + 0 as MinPanRange, + 0 as MaxPanRange, + 0 as MinPanStep, + 0 as MaxPanStep, + 1 as HasPanSpeed, + 0 as MinPanSpeed, + 5 as MaxPanSpeed, + 0 as HasTurboPan, + 0 as TurboPanSpeed, + 1 as CanTilt, + 0 as MinTiltRange, + 0 as MaxTiltRange, + 0 as MinTiltStep, + 0 as MaxTiltStep, + 1 as HasTiltSpeed, + 0 as MinTiltSpeed, + 5 as MaxTiltSpeed, + 0 as HasTurboTilt, + 0 as TurboTiltSpeed, + 0 as CanAutoScan, + 0 as NumScanPaths) AS tmp +WHERE NOT EXISTS ( + SELECT Name FROM Controls WHERE name = 'Vivotek ePTZ' +) LIMIT 1; diff --git a/db/zm_update-1.30.10.sql b/db/zm_update-1.30.10.sql new file mode 100644 index 000000000..651f52a55 --- /dev/null +++ b/db/zm_update-1.30.10.sql @@ -0,0 +1,9 @@ +-- This updates a 1.30.9 database to 1.30.10 +-- +-- Alter type of Messages column from VARCHAR(255) to TEXT +-- + +-- ALTER TABLE Logs ALTER Message DROP DEFAULT; +ALTER TABLE Logs MODIFY Message TEXT NOT NULL; + +ALTER TABLE Config MODIFY DefaultValue TEXT; diff --git a/db/zm_update-1.30.11.sql b/db/zm_update-1.30.11.sql new file mode 100644 index 000000000..c656ec664 --- /dev/null +++ b/db/zm_update-1.30.11.sql @@ -0,0 +1,19 @@ +-- +-- This updates a 1.30.10 database to 1.30.11 +-- +-- Add StateId Column to Events. +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Events' + AND table_schema = DATABASE() + AND column_name = 'StateId' + ) > 0, +"SELECT 'Column StateId exists in Events'", +"ALTER TABLE Events ADD `StateId` int(10) unsigned default NULL AFTER `Notes`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.30.12.sql b/db/zm_update-1.30.12.sql new file mode 100644 index 000000000..840a9a909 --- /dev/null +++ b/db/zm_update-1.30.12.sql @@ -0,0 +1,7 @@ +-- +-- This updates a 1.30.10 database to 1.30.11 +-- +-- Add StateId Column to Events. +-- + +ALTER TABLE Monitors MODIFY EncoderParameters TEXT; diff --git a/db/zm_update-1.30.13.sql b/db/zm_update-1.30.13.sql new file mode 100644 index 000000000..a01357a41 --- /dev/null +++ b/db/zm_update-1.30.13.sql @@ -0,0 +1,7 @@ +-- +-- This updates a 1.30.10 database to 1.30.11 +-- +-- Add StateId Column to Events. +-- + +ALTER TABLE Monitors MODIFY Path VARCHAR(255); diff --git a/db/zm_update-1.30.2.sql b/db/zm_update-1.30.2.sql new file mode 100644 index 000000000..b86b2903b --- /dev/null +++ b/db/zm_update-1.30.2.sql @@ -0,0 +1,24 @@ +-- +-- This updates a 1.30.1 database to 1.30.2 +-- + +-- +-- Update Filters table to have a Concurrent Column +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Filters' + AND table_schema = DATABASE() + AND column_name = 'Concurrent' + ) > 0, +"SELECT 'Column Concurrent already exists in Filters'", +"ALTER TABLE Filters ADD COLUMN `Concurrent` tinyint(1) unsigned NOT NULL default '0' AFTER Background" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + + +ALTER TABLE Users MODIFY MonitorIds TEXT NOT NULL; diff --git a/db/zm_update-1.30.3.sql b/db/zm_update-1.30.3.sql new file mode 100644 index 000000000..471c4bcc2 --- /dev/null +++ b/db/zm_update-1.30.3.sql @@ -0,0 +1,22 @@ +-- +-- This updates a 1.29.0 database to 1.29.1 +-- +-- + +-- +-- Add an Id column and make it the primary key of the Filters table +-- +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Filters' + AND table_schema = DATABASE() + AND column_name = 'Id' + ) > 0, +"SELECT 'Column Id exists in Filters'", +"ALTER TABLE `Filters` DROP PRIMARY KEY, ADD `Id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY FIRST, ADD KEY `Name` (`Name`);" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + diff --git a/db/zm_update-1.30.4.sql b/db/zm_update-1.30.4.sql new file mode 100644 index 000000000..fe3e083ca --- /dev/null +++ b/db/zm_update-1.30.4.sql @@ -0,0 +1,21 @@ +-- +-- This updates a 1.30.3 database to 1.30.4 +-- +-- No changes required +-- + +ALTER TABLE Monitors MODIFY LabelFormat varchar(64); +ALTER TABLE Monitors MODIFY Host varchar(64); +ALTER TABLE Monitors MODIFY Protocol varchar(16); +ALTER TABLE Monitors MODIFY Options varchar(255); +ALTER TABLE Monitors MODIFY LinkedMonitors varchar(255); +ALTER TABLE Monitors MODIFY User varchar(64); +ALTER TABLE Monitors MODIFY Pass varchar(64); +ALTER TABLE Monitors MODIFY RTSPDescribe tinyint(1) unsigned; +ALTER TABLE Monitors MODIFY ControlId int(10) unsigned; +ALTER TABLE Monitors MODIFY TrackDelay smallint(5) unsigned; +ALTER TABLE Monitors MODIFY ReturnDelay smallint(5) unsigned; + +ALTER TABLE Users MODIFY MonitorIds tinytext; +ALTER TABLE Users MODIFY Language varchar(8); +ALTER TABLE Users MODIFY MaxBandwidth varchar(16); diff --git a/db/zm_update-1.30.5.sql b/db/zm_update-1.30.5.sql new file mode 100644 index 000000000..79a84bfd6 --- /dev/null +++ b/db/zm_update-1.30.5.sql @@ -0,0 +1,60 @@ +-- +-- This updates a 1.29.0 database to 1.29.1 +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.TABLES + WHERE table_name = 'Storage' + AND table_schema = DATABASE() + ) > 0, +"SELECT 'Storage table exists'", +"CREATE TABLE `Storage` ( + `Id` smallint(5) unsigned NOT NULL auto_increment, + `Path` varchar(64) NOT NULL default '', + `Name` varchar(64) NOT NULL default '', + PRIMARY KEY (`Id`) +)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +-- +-- Add StorageId column to Monitors +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'StorageId' + ) > 0, +"SELECT 'Column StorageId exists in Monitors'", +"ALTER TABLE Monitors ADD `StorageId` smallint(5) unsigned AFTER `ServerId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +-- +-- Add StorageId column to Eventss +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Events' + AND table_schema = DATABASE() + AND column_name = 'StorageId' + ) > 0, +"SELECT 'Column StorageId exists in Events'", +"ALTER TABLE Events ADD `StorageId` smallint(5) unsigned NOT NULL default 0 AFTER `MonitorId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +-- Increase the size of the Pid field for FreeBSD +ALTER TABLE Logs MODIFY Pid int(10); diff --git a/db/zm_update-1.30.6.sql b/db/zm_update-1.30.6.sql new file mode 100644 index 000000000..234393ff5 --- /dev/null +++ b/db/zm_update-1.30.6.sql @@ -0,0 +1,73 @@ +-- +-- This updates a 1.29.0 database to 1.30.0 +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'SaveJPEGs' + ) > 0, +"SELECT 'Column SaveJPEGs exists in Monitors'", +"ALTER TABLE `Monitors` ADD `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' AFTER `Deinterlacing`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'VideoWriter' + ) > 0, +"SELECT 'Column VideoWriter exists in Monitors'", +"ALTER TABLE `Monitors` ADD `VideoWriter` TINYINT NOT NULL DEFAULT '0' AFTER `SaveJPEGs`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'EncoderParameters' + ) > 0, +"SELECT 'Column EncoderParameters exists in Monitors'", +"ALTER TABLE `Monitors` ADD `EncoderParameters` TEXT NOT NULL AFTER `VideoWriter`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Events' + AND table_schema = DATABASE() + AND column_name = 'DefaultVideo' + ) > 0, +"SELECT 'Column DefaultVideo exists in Events'", +"ALTER TABLE `Events` ADD `DefaultVideo` VARCHAR( 64 ) NOT NULL DEFAULT '' AFTER `AlarmFrames`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'RecordAudio' + ) > 0, +"SELECT 'Column RecordAudio exists in Monitors'", +"ALTER TABLE `Monitors` ADD `RecordAudio` TINYINT NOT NULL DEFAULT '0' AFTER `EncoderParameters`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.30.7.sql b/db/zm_update-1.30.7.sql new file mode 100644 index 000000000..72641825b --- /dev/null +++ b/db/zm_update-1.30.7.sql @@ -0,0 +1,10 @@ +-- +-- This updates a 1.30.6 database to 1.30.7 +-- +-- Changing StorageId to be NOT NULL and default 0 +-- + +UPDATE Monitors SET StorageId = 0 WHERE StorageId IS NULL; +ALTER TABLE Monitors MODIFY `StorageId` smallint(5) unsigned NOT NULL default 0; +UPDATE Events SET StorageId = 0 WHERE StorageId IS NULL; +ALTER TABLE Events MODIFY `StorageId` smallint(5) unsigned NOT NULL default 0; diff --git a/db/zm_update-1.30.8.sql b/db/zm_update-1.30.8.sql new file mode 100644 index 000000000..5026939e5 --- /dev/null +++ b/db/zm_update-1.30.8.sql @@ -0,0 +1,17 @@ +-- +-- This updates a 1.30.7 database to 1.30.8 +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Events' + AND table_schema = DATABASE() + AND column_name = 'Orientation' + ) > 0, +"SELECT 'Column Orientation exists in Events'", +"ALTER TABLE `Events` ADD `Orientation` enum('0','90','180','270','hori','vert') NOT NULL default '0' AFTER `Notes`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.30.9.sql b/db/zm_update-1.30.9.sql new file mode 100644 index 000000000..b6c523575 --- /dev/null +++ b/db/zm_update-1.30.9.sql @@ -0,0 +1,39 @@ +-- +-- This updates a 1.30.9 database to 1.30.9 +-- + +-- +-- Update Monitors table to have an Index on ServerId +-- +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.STATISTICS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND index_name = 'Monitors_ServerId_idx' + ) > 0, +"SELECT 'Monitors_ServerId Index already exists on Monitors table'", +"CREATE INDEX `Monitors_ServerId_idx` ON `Monitors` (`ServerId`)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + + +-- +-- Update Server table to have an Index on Name +-- +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.STATISTICS + WHERE table_name = 'Servers' + AND table_schema = DATABASE() + AND index_name = 'Servers_Name_idx' + ) > 0, +"SELECT 'Servers_Name Index already exists on Servers table'", +"CREATE INDEX `Servers_Name_idx` ON `Servers` (`Name`)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + diff --git a/distros/debian/apache.conf b/distros/debian/apache.conf index 9a3238807..65399e92f 100644 --- a/distros/debian/apache.conf +++ b/distros/debian/apache.conf @@ -1,8 +1,20 @@ Alias /zm /usr/share/zoneminder/www - - Options -Indexes +FollowSymLinks - - DirectoryIndex index.php - - + + + Options -Indexes +ExecCGI + AllowOverride All + AddHandler fcgid-script .php + FCGIWrapper /usr/bin/php5-cgi + Order allow,deny + Allow from all + + + + + Options -Indexes +FollowSymLinks + + DirectoryIndex index.php + + + diff --git a/distros/debian/control b/distros/debian/control index 9bd3ab88a..ac4c6f4f6 100644 --- a/distros/debian/control +++ b/distros/debian/control @@ -6,14 +6,14 @@ Build-Depends: debhelper (>= 9), cmake , libphp-serialization-perl , libgnutls28-dev | libgnutls-dev , libmysqlclient-dev | libmariadbclient-dev - , libjpeg8-dev + , libjpeg-dev , libpcre3-dev , libavcodec-dev, libavformat-dev (>= 3:0.svn20090204), libswscale-dev (>= 3:0.svn20090204), libavutil-dev + , libavdevice-dev , libv4l-dev (>= 0.8.3) , libbz2-dev , ffmpeg | libav-tools , libnetpbm10-dev - , libavdevice-dev , libvlccore-dev, libvlc-dev , libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev , libgcrypt11-dev, libpolkit-gobject-1-dev @@ -42,11 +42,11 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} , libsys-cpu-perl, libsys-meminfo-perl , libdata-uuid-perl , libpcre3 - , ffmpeg | libav-tools, libavdevice53 + , ffmpeg | libav-tools, libavdevice53 | libavdevice55 | libavdevice57 , rsyslog | system-log-daemon - , netpbm , libjpeg8 + , netpbm , zip - , libvlccore5 | libvlccore7, libvlc5 + , libvlccore5 | libvlccore7 | libvlccore8, libvlc5 , libpolkit-gobject-1-0, php5-gd Recommends: mysql-server | mariadb-server Description: Video camera security and surveillance solution diff --git a/distros/debian/rules b/distros/debian/rules index 473871a49..c4cf6c38c 100755 --- a/distros/debian/rules +++ b/distros/debian/rules @@ -27,9 +27,9 @@ override_dh_auto_configure: override_dh_auto_install: dh_auto_install --buildsystem=cmake install -D -m 0644 debian/apache.conf $(INSTDIR)/etc/zm/apache.conf - rm $(INSTDIR)/usr/share/zoneminder/www/api/lib/Cake/LICENSE.txt - rm $(INSTDIR)/usr/share/zoneminder/www/api/.gitignore - rm -r $(INSTDIR)/usr/share/zoneminder/www/api/lib/Cake/Test + rm -f $(INSTDIR)/usr/share/zoneminder/www/api/lib/Cake/LICENSE.txt + rm -f $(INSTDIR)/usr/share/zoneminder/www/api/.gitignore + rm -rf $(INSTDIR)/usr/share/zoneminder/www/api/lib/Cake/Test override_dh_auto_test: # do not run tests... diff --git a/distros/redhat/CMakeLists.txt b/distros/redhat/CMakeLists.txt index df3edf7d9..f8acef552 100644 --- a/distros/redhat/CMakeLists.txt +++ b/distros/redhat/CMakeLists.txt @@ -49,10 +49,8 @@ endif("${unzip_jsc}" STREQUAL "") file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp) # Install the empty folders -#install(DIRECTORY run DESTINATION /var DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_WRITE GROUP_READ GROUP_EXECUTE WORLD_WRITE WORLD_READ WORLD_EXECUTE) install(DIRECTORY sock swap DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder DESTINATION /var/log DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) -install(DIRECTORY zoneminder DESTINATION /run DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder-upload DESTINATION /var/spool DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY events images temp DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) diff --git a/distros/redhat/nginx/zoneminder.service.in b/distros/redhat/nginx/zoneminder.service.in index da569423d..7e2e36585 100644 --- a/distros/redhat/nginx/zoneminder.service.in +++ b/distros/redhat/nginx/zoneminder.service.in @@ -8,12 +8,15 @@ Requires=mariadb.service nginx.service php-fpm.service fcgiwrap.service [Service] User=@WEB_USER@ +Group=@WEB_GROUP@ Type=forking ExecStart=@BINDIR@/zmpkg.pl start ExecReload=@BINDIR@/zmpkg.pl restart ExecStop=@BINDIR@/zmpkg.pl stop PIDFile=@ZM_RUNDIR@/zm.pid Environment=TZ=/etc/localtime +RuntimeDirectory=zoneminder +RuntimeDirectoryMode=0755 [Install] WantedBy=multi-user.target diff --git a/distros/redhat/nginx/zoneminder.tmpfiles.in b/distros/redhat/nginx/zoneminder.tmpfiles.in index d92ceaafd..8040a7877 100644 --- a/distros/redhat/nginx/zoneminder.tmpfiles.in +++ b/distros/redhat/nginx/zoneminder.tmpfiles.in @@ -1,4 +1,3 @@ -D @ZM_RUNDIR@ 0755 @WEB_USER@ @WEB_GROUP@ D @ZM_TMPDIR@ 0755 @WEB_USER@ @WEB_GROUP@ D @ZM_SOCKDIR@ 0755 @WEB_USER@ @WEB_GROUP@ D /var/lib/php/session 770 root @WEB_GROUP@ diff --git a/distros/redhat/systemd/zoneminder.conf.in b/distros/redhat/systemd/zoneminder.conf.in index 564e4ccbd..005c959a6 100644 --- a/distros/redhat/systemd/zoneminder.conf.in +++ b/distros/redhat/systemd/zoneminder.conf.in @@ -11,6 +11,9 @@ RewriteRule ^/?(zm)(.*) https://%{SERVER_NAME}/$1$2 [R,L] Alias /zm "@ZM_WEBDIR@" + # explicitly set index.php as the only directoryindex + DirectoryIndex disabled + DirectoryIndex index.php SSLRequireSSL Options -Indexes +MultiViews +FollowSymLinks AllowOverride All diff --git a/distros/redhat/systemd/zoneminder.service.in b/distros/redhat/systemd/zoneminder.service.in index 030ca8065..2234af036 100644 --- a/distros/redhat/systemd/zoneminder.service.in +++ b/distros/redhat/systemd/zoneminder.service.in @@ -7,12 +7,15 @@ Requires=mariadb.service httpd.service [Service] User=@WEB_USER@ +Group=@WEB_GROUP@ Type=forking ExecStart=@BINDIR@/zmpkg.pl start ExecReload=@BINDIR@/zmpkg.pl restart ExecStop=@BINDIR@/zmpkg.pl stop PIDFile=@ZM_RUNDIR@/zm.pid Environment=TZ=/etc/localtime +RuntimeDirectory=zoneminder +RuntimeDirectoryMode=0755 [Install] WantedBy=multi-user.target diff --git a/distros/redhat/systemd/zoneminder.tmpfiles.in b/distros/redhat/systemd/zoneminder.tmpfiles.in index f655a9c9f..910c360f1 100644 --- a/distros/redhat/systemd/zoneminder.tmpfiles.in +++ b/distros/redhat/systemd/zoneminder.tmpfiles.in @@ -1,3 +1,7 @@ -D @ZM_RUNDIR@ 0755 @WEB_USER@ @WEB_GROUP@ D @ZM_TMPDIR@ 0755 @WEB_USER@ @WEB_GROUP@ +D @ZM_TMPDIR@/logs 0755 @WEB_USER@ @WEB_GROUP@ +D @ZM_TMPDIR@/cache 0755 @WEB_USER@ @WEB_GROUP@ +D @ZM_TMPDIR@/cache/models 0755 @WEB_USER@ @WEB_GROUP@ +D @ZM_TMPDIR@/cache/persistent 0755 @WEB_USER@ @WEB_GROUP@ +D @ZM_TMPDIR@/cache/views 0755 @WEB_USER@ @WEB_GROUP@ D @ZM_SOCKDIR@ 0755 @WEB_USER@ @WEB_GROUP@ diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 58a5a1a44..0ed6323f0 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -1,14 +1,14 @@ %global zmuid_final apache %global zmgid_final apache -# In some cases older distros do not have this macro defined -%{!?make_build: %global make_build %{__make} %{?_smp_mflags} } +# Crud is configured as a git submodule +%global crud_version 3.0.10 %if "%{zmuid_final}" == "nginx" - %global with_nginx 1 - %global wwwconfdir /etc/nginx/default.d +%global with_nginx 1 +%global wwwconfdir %{_sysconfdir}/nginx/default.d %else - %global wwwconfdir /etc/httpd/conf.d +%global wwwconfdir %{_sysconfdir}/httpd/conf.d %endif %global sslcert %{_sysconfdir}/pki/tls/certs/localhost.crt @@ -24,56 +24,87 @@ %global with_init_sysv 1 %endif -# php-mysql deprecated in f25 -%if 0%{?fedora} >= 25 -%global with_php_mysqlnd 1 -%else -%global with_php_mysql 1 -%endif - %global readme_suffix %{?rhel:Redhat%{?rhel}}%{!?rhel:Fedora} %global _hardened_build 1 Name: zoneminder -Version: 1.30.1 +Version: 1.30.2 Release: 2%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons # jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/ # Mootools is inder the MIT license: http://mootools.net/ # CakePHP is under the MIT license: https://github.com/cakephp/cakephp +# Crud is under the MIT license: https://github.com/FriendsOfCake/crud License: GPLv2+ and LGPLv2+ and MIT URL: http://www.zoneminder.com/ -Source: ZoneMinder-%{version}.tar.gz +Source0: https://github.com/ZoneMinder/ZoneMinder/archive/%{version}.tar.gz#/zoneminder-%{version}.tar.gz +Source1: https://github.com/FriendsOfCake/crud/archive/v%{crud_version}.tar.gz#/crud-%{crud_version}.tar.gz -%{?with_init_systemd:BuildRequires: systemd-devel mariadb-devel perl-podlators} +%{?with_init_systemd:BuildRequires: systemd-devel} +%{?with_init_systemd:BuildRequires: mariadb-devel} +%{?with_init_systemd:BuildRequires: perl-podlators} %{?with_init_sysv:BuildRequires: mysql-devel} +%{?el6:BuildRequires: epel-rpm-macros} BuildRequires: cmake >= 2.8.7 -BuildRequires: gnutls-devel bzip2-devel -BuildRequires: pcre-devel libjpeg-turbo-devel -BuildRequires: perl(Archive::Tar) perl(Archive::Zip) -BuildRequires: perl(Date::Manip) perl(DBD::mysql) -BuildRequires: perl(ExtUtils::MakeMaker) perl(LWP::UserAgent) -BuildRequires: perl(MIME::Entity) perl(MIME::Lite) -BuildRequires: perl(PHP::Serialization) perl(Sys::Mmap) -BuildRequires: perl(Time::HiRes) perl(Net::SFTP::Foreign) -BuildRequires: perl(Expect) perl(Sys::Syslog) -BuildRequires: gcc gcc-c++ vlc-devel libcurl-devel libv4l-devel -BuildRequires: ffmpeg-devel polkit-devel +BuildRequires: gnutls-devel +BuildRequires: bzip2-devel +BuildRequires: pcre-devel +BuildRequires: libjpeg-turbo-devel +BuildRequires: findutils +BuildRequires: coreutils +BuildRequires: perl +BuildRequires: perl-generators +BuildRequires: perl(Archive::Tar) +BuildRequires: perl(Archive::Zip) +BuildRequires: perl(Date::Manip) +BuildRequires: perl(DBD::mysql) +BuildRequires: perl(ExtUtils::MakeMaker) +BuildRequires: perl(LWP::UserAgent) +BuildRequires: perl(MIME::Entity) +BuildRequires: perl(MIME::Lite) +BuildRequires: perl(PHP::Serialization) +BuildRequires: perl(Sys::Mmap) +BuildRequires: perl(Time::HiRes) +BuildRequires: perl(Net::SFTP::Foreign) +BuildRequires: perl(Expect) +BuildRequires: perl(Sys::Syslog) +BuildRequires: gcc +BuildRequires: gcc-c++ +BuildRequires: vlc-devel +BuildRequires: libcurl-devel +BuildRequires: libv4l-devel +BuildRequires: ffmpeg-devel +BuildRequires: polkit-devel -%{?with_nginx:Requires: nginx fcgiwrap php-fpm} +%{?with_nginx:Requires: nginx} +%{?with_nginx:Requires: fcgiwrap} +%{?with_nginx:Requires: php-fpm} %{!?with_nginx:Requires: httpd} -%{?with_php_mysqlnd:Requires: php-mysqlnd} -%{?with_php_mysql:Requires: php-mysql} -Requires: php-common php-gd cambozola polkit net-tools psmisc -Requires: libjpeg-turbo vlc-core libcurl ffmpeg +%{!?with_nginx:Requires: php} +Requires: php-mysqli +Requires: php-common +Requires: php-gd +Requires: cambozola +Requires: net-tools +Requires: psmisc +Requires: polkit +Requires: libjpeg-turbo +Requires: vlc-core +Requires: ffmpeg Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) -Requires: perl(DBD::mysql) perl(Archive::Tar) perl(Archive::Zip) -Requires: perl(MIME::Entity) perl(MIME::Lite) perl(Net::SMTP) perl(Net::FTP) -Requires: perl(LWP::Protocol::https) perl(X10::ActiveHome) perl(Astro::SunTime) +Requires: perl(DBD::mysql) +Requires: perl(Archive::Tar) +Requires: perl(Archive::Zip) +Requires: perl(MIME::Entity) +Requires: perl(MIME::Lite) +Requires: perl(Net::SMTP) +Requires: perl(Net::FTP) +Requires: perl(LWP::Protocol::https) -%{?with_init_systemd:Requires(post): systemd systemd-sysv} +%{?with_init_systemd:Requires(post): systemd} +%{?with_init_systemd:Requires(post): systemd-sysv} %{?with_init_systemd:Requires(preun): systemd} %{?with_init_systemd:Requires(postun): systemd} @@ -100,12 +131,15 @@ too much degradation of performance. %prep %autosetup -n ZoneMinder-%{version} +%autosetup -a 1 -n ZoneMinder-%{version} +rmdir ./web/api/app/Plugin/Crud +mv -f crud-%{crud_version} ./web/api/app/Plugin/Crud # Change the following default values ./utils/zmeditconfigdata.sh ZM_PATH_ZMS /cgi-bin-zm/nph-zms ./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes ./utils/zmeditconfigdata.sh ZM_PATH_SWAP /dev/shm -./utils/zmeditconfigdata.sh ZM_UPLOAD_FTP_LOC_DIR /var/spool/zoneminder-upload +./utils/zmeditconfigdata.sh ZM_UPLOAD_FTP_LOC_DIR %{_localstatedir}/spool/zoneminder-upload ./utils/zmeditconfigdata.sh ZM_OPT_CONTROL yes ./utils/zmeditconfigdata.sh ZM_CHECK_FOR_UPDATES no ./utils/zmeditconfigdata.sh ZM_DYN_SHOW_DONATE_REMINDER no @@ -240,9 +274,9 @@ 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 -%config(noreplace) %attr(640,root,%{zmgid_final}) /etc/zm/zm.conf +%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/zm.conf %config(noreplace) %attr(644,root,root) %{wwwconfdir}/zoneminder.conf -%config(noreplace) /etc/logrotate.d/zoneminder +%config(noreplace) %{_sysconfdir}/logrotate.d/zoneminder %if 0%{?with_nginx} %config(noreplace) %{_sysconfdir}/php-fpm.d/zoneminder.conf @@ -284,11 +318,9 @@ rm -rf %{_docdir}/%{name}-%{version} %{perl_vendorlib}/WSSecurity* %{perl_vendorlib}/WSNotification* %{_mandir}/man*/* -%dir %{_libexecdir}/zoneminder -%{_libexecdir}/zoneminder/cgi-bin -%dir %{_datadir}/zoneminder -%{_datadir}/zoneminder/db -%{_datadir}/zoneminder/www + +%{_libexecdir}/zoneminder/ +%{_datadir}/zoneminder/ %{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy %{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules @@ -299,11 +331,17 @@ rm -rf %{_docdir}/%{name}-%{version} %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/sock %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/swap %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/temp -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/log/zoneminder -%dir %attr(755,%{zmuid_final},%{zmgid_final}) /var/spool/zoneminder-upload -%dir %attr(755,%{zmuid_final},%{zmgid_final}) %ghost /run/zoneminder +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/log/zoneminder +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/spool/zoneminder-upload +%dir %attr(755,%{zmuid_final},%{zmgid_final}) %ghost %{_localstatedir}/run/zoneminder %changelog +* Thu Mar 30 2017 Andrew Bauer - 1.30.2-2 +- 1.30.2 release + +* Wed Feb 08 2017 Andrew Bauer - 1.30.2-1 +- Bump version for 1.30.2 release candidate 1 + * Wed Dec 28 2016 Andrew Bauer - 1.30.1-2 - Changes from rpmfusion #4393 diff --git a/distros/ubuntu1204/changelog b/distros/ubuntu1204/changelog index c7e86f69d..8f4ca90fb 100644 --- a/distros/ubuntu1204/changelog +++ b/distros/ubuntu1204/changelog @@ -1,3 +1,51 @@ +zoneminder (1.30.2-trusty-2016051301) trusty; urgency=medium + + * fixes + + -- Isaac Connor Fri, 13 May 2016 09:45:49 -0400 + +zoneminder (1.30.2-trusty-2016051201) trusty; urgency=medium + + * web ui fixies + + -- Isaac Connor Thu, 12 May 2016 20:01:21 -0400 + +zoneminder (1.30.2-trusty-2016051101) trusty; urgency=medium + + * fix zms + + -- Isaac Connor Wed, 11 May 2016 11:36:08 -0400 + +zoneminder (1.30.2-trusty-2016050901) trusty; urgency=medium + + * + + -- Isaac Connor Mon, 09 May 2016 21:08:56 -0400 + +zoneminder (1.30.2-trusty-2016050501) trusty; urgency=medium + + * add some more debug + + -- Isaac Connor Thu, 05 May 2016 10:20:23 -0400 + +zoneminder (1.30.2-trusty-2016050201) trusty; urgency=medium + + * add Servers support to API + + -- Isaac Connor Mon, 02 May 2016 10:45:24 -0400 + +zoneminder (1.30.2-trusty-2016042901) trusty; urgency=medium + + * zone edit fixes + + -- Isaac Connor Fri, 29 Apr 2016 10:46:18 -0400 + +zoneminder (1.30.2-trusty-2016042801) trusty; urgency=medium + + * Merge video + + -- Isaac Connor Thu, 28 Apr 2016 12:54:08 -0400 + zoneminder (1.28.1+1-vivid-SNAPSHOT2015081701) vivid; urgency=medium * include api, switch to cmake build diff --git a/distros/ubuntu1204/control b/distros/ubuntu1204/control index d7c2232cf..3f360c833 100644 --- a/distros/ubuntu1204/control +++ b/distros/ubuntu1204/control @@ -5,7 +5,9 @@ Maintainer: Dmitry Smirnov Uploaders: Vagrant Cascadian Build-Depends: debhelper (>= 9), python-sphinx | python3-sphinx, apache2-dev, dh-linktree ,cmake + ,libx264-dev, libmp4v2-dev ,libavcodec-dev, libavformat-dev (>= 3:0.svn20090204), libswscale-dev (>= 3:0.svn20090204), libavutil-dev, libavdevice-dev + ,libboost1.55-dev ,libbz2-dev ,libgcrypt-dev ,libcurl4-gnutls-dev @@ -34,6 +36,7 @@ Package: zoneminder Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,javascript-common + ,libmp4v2-2, libx264-142 ,libav-tools|ffmpeg ,libdate-manip-perl ,libdbd-mysql-perl diff --git a/distros/ubuntu1204/rules b/distros/ubuntu1204/rules index b467c870f..2b54a2131 100755 --- a/distros/ubuntu1204/rules +++ b/distros/ubuntu1204/rules @@ -58,8 +58,10 @@ override_dh_auto_install: override_dh_fixperms: dh_fixperms - ## 637685 - chmod -c o-r $(CURDIR)/debian/zoneminder/etc/zm/zm.conf + # + # As requested by the Debian Webapps Policy Manual ยง3.2.1 + chown root:www-data $(CURDIR)/debian/zoneminder/etc/zm/zm.conf + chmod 640 $(CURDIR)/debian/zoneminder/etc/zm/zm.conf override_dh_installinit: dh_installinit --no-start diff --git a/distros/ubuntu1204/zoneminder.logrotate b/distros/ubuntu1204/zoneminder.logrotate index ac7ce0795..846abd4fb 100644 --- a/distros/ubuntu1204/zoneminder.logrotate +++ b/distros/ubuntu1204/zoneminder.logrotate @@ -2,9 +2,11 @@ missingok notifempty sharedscripts + delaycompress + compress postrotate /usr/bin/zmpkg.pl logrot >>/dev/null 2>&1 || : endscript - weekly - rotate 3 + daily + rotate 7 } diff --git a/distros/ubuntu1410/README.Debian b/distros/ubuntu1410/README.Debian new file mode 100644 index 000000000..a49b6be72 --- /dev/null +++ b/distros/ubuntu1410/README.Debian @@ -0,0 +1,51 @@ +zoneminder for Debian +--------------------- + +There is one manual step to get the web interface working. +You need to link /etc/zm/apache.conf to /etc/apache2/conf.d/zoneminder.conf, +then reload the apache config (i.e. /etc/init.d/apache2 reload) + +Changing the location for images and events +------------------------------------------- + +Zoneminder, in its upstream form, stores data in /usr/share/zoneminder/. This +package modifies that by changing /usr/share/zoneminder/images and +/usr/share/zoneminder/events to symlinks to directories under +/var/cache/zoneminder. + +There are numerous places these could be put and ways to do it. But, at the +moment, if you change this, an upgrade will fail with a warning about these +locations having changed (the reason for this was that previously, an upgrade +would silently revert the changes and cause event loss - refer +bug #608793). + +If you do want to change the location, here are a couple of suggestions. + +These lines would mount /dev/sdX1 to /video_storage, and then 'link' /video_storage +to the locations that ZoneMinder expects them to be at. + + /dev/sdX1 /video_storage ext4 defaults 0 2 + /video_storage/zoneminder/images /var/cache/zoneminder/images none bind 0 2 + /video_storage/zoneminder/events /var/cache/zoneminder/events none bind 0 2 + + or if you have a separate partition for each: + + /dev/sdX1 /var/cache/zoneminder/images ext4 defaults 0 2 + /dev/sdX2 /var/cache/zoneminder/events ext4 defaults 0 2 + + + + -- Peter Howard , Sun, 16 Jan 2010 01:35:51 +1100 + +Access to /dev/video* +--------------------- + +For cameras which require access to /dev/video*, zoneminder may need the +www-data user added to the video group in order to see those cameras: + + adduser www-data video + +Note that all web applications running on the zoneminder server will then have +access to all video devices on the system. + + -- Vagrant Cascadian Sun, 27 Mar 2011 13:06:56 -0700 diff --git a/distros/ubuntu1410/apache.conf b/distros/ubuntu1410/apache.conf new file mode 100644 index 000000000..92a2b6414 --- /dev/null +++ b/distros/ubuntu1410/apache.conf @@ -0,0 +1,9 @@ +Alias /zm /usr/share/zoneminder + + + php_flag register_globals off + Options Indexes FollowSymLinks + + DirectoryIndex index.php + + diff --git a/distros/ubuntu1410/changelog b/distros/ubuntu1410/changelog new file mode 100644 index 000000000..55e3d17b0 --- /dev/null +++ b/distros/ubuntu1410/changelog @@ -0,0 +1,323 @@ +zoneminder (1.30.2-trusty-2016033001) trusty; urgency=medium + + * merge master + + -- Isaac Connor Wed, 30 Mar 2016 14:09:48 -0400 + +zoneminder (1.30.2-trusty-2016032901) trusty; urgency=medium + + * filter fixes, merge options rework by Kyle + + -- Isaac Connor Tue, 29 Mar 2016 12:27:57 -0400 + +zoneminder (1.30.2-trusty-2016030702) trusty; urgency=medium + + * + + -- Isaac Connor Mon, 07 Mar 2016 22:14:03 -0500 + +zoneminder (1.30.2-trusty-2016030701) trusty; urgency=medium + + * merge master. with telemetry + + -- Isaac Connor Mon, 07 Mar 2016 21:47:53 -0500 + +zoneminder (1.30.2-trusty-2016022101) trusty; urgency=medium + + * merge zmtrigger fix + + -- Isaac Connor Mon, 22 Feb 2016 09:15:53 -0500 + +zoneminder (1.30.2-trusty-2016021901) trusty; urgency=medium + + * zmtrigger improvements + + -- Isaac Connor Fri, 19 Feb 2016 11:09:57 -0500 + +zoneminder (1.30.2-trusty-2016021701) trusty; urgency=medium + + * printout id, and ip address when failing to connect + + -- Isaac Connor Wed, 17 Feb 2016 09:40:49 -0500 + +zoneminder (1.30.2-trusty-2016021001) trusty; urgency=medium + + * + + -- Isaac Connor Wed, 10 Feb 2016 13:06:09 -0500 + +zoneminder (1.29.111-trusty-2016020101) trusty; urgency=medium + + * Fix video download and use of Storage Areas + + -- Isaac Connor Mon, 01 Feb 2016 13:42:06 -0500 + +zoneminder (1.29.111-trusty-2016011401) trusty; urgency=medium + + * fix timeline view for storageareas + + -- Isaac Connor Thu, 14 Jan 2016 14:03:41 -0500 + +zoneminder (1.29.111-trusty-2016010801) trusty; urgency=medium + + * Add better debug and skip when event links are not just digits. Merge multi-server stuff from master. + + -- Isaac Connor Fri, 08 Jan 2016 10:37:16 -0500 + +zoneminder (1.29.111-trusty-2016010401) trusty; urgency=medium + + * include fix to rotate image dimensions when applying a rotation + + -- Isaac Connor Mon, 04 Jan 2016 13:24:42 -0500 + +zoneminder (1.29.111-trusty-2016010101) trusty; urgency=medium + + * fix logging with multi-server + + -- Isaac Connor Fri, 01 Jan 2016 17:11:09 -0500 + +zoneminder (1.29.111-trusty-2015123101) trusty; urgency=medium + + * Add log filtering from multi-server + + -- Isaac Connor Thu, 31 Dec 2015 10:18:03 -0500 + +zoneminder (1.29.109-trusty-2015122401) trusty; urgency=medium + + * fix delete events not in database in zmaudit.pl + + -- Isaac Connor Thu, 24 Dec 2015 12:38:05 -0500 + +zoneminder (1.29.109-trusty-2015122301) trusty; urgency=medium + + * todays release + + -- Isaac Connor Wed, 23 Dec 2015 09:33:46 -0500 + +zoneminder (1.29.109-trusty-2015122202) trusty; urgency=medium + + * more object work and zmaudit + + -- Isaac Connor Tue, 22 Dec 2015 13:13:56 -0500 + +zoneminder (1.29.109-trusty-2015122201) trusty; urgency=medium + + * merge multi-server, and master stuff, start work on zmaudit storage areas support + + -- Isaac Connor Tue, 22 Dec 2015 11:11:44 -0500 + +zoneminder (1.29.109-trusty-2015122103) trusty; urgency=medium + + * Fixes + + -- Isaac Connor Mon, 21 Dec 2015 15:20:15 -0500 + +zoneminder (1.29.109-trusty-2015122102) trusty; urgency=medium + + * fix deleting events + + -- Isaac Connor Mon, 21 Dec 2015 14:49:12 -0500 + +zoneminder (1.29.109-trusty-2015122101) trusty; urgency=medium + + * Make zmfilter work. + + -- Isaac Connor Mon, 21 Dec 2015 12:32:27 -0500 + +zoneminder (1.29.109-trusty-2015121803) trusty; urgency=medium + + * merge zmvideo improvements + + -- Isaac Connor Fri, 18 Dec 2015 14:17:56 -0500 + +zoneminder (1.29.109-trusty-2015121802) trusty; urgency=medium + + * Add some missing files to the autoconf build + + -- Isaac Connor Fri, 18 Dec 2015 12:14:08 -0500 + +zoneminder (1.29.109-trusty-2015121801) trusty; urgency=medium + + * + + -- Isaac Connor Fri, 18 Dec 2015 11:05:58 -0500 + +zoneminder (1.29.109-trusty-2015121701) trusty; urgency=medium + + * Merge master + better zmvideo + + -- Isaac Connor Thu, 17 Dec 2015 15:11:18 -0500 + +zoneminder (1.29.0-trusty-2015112301) trusty; urgency=medium + + * apply fix for zms crash + + -- Isaac Connor Mon, 23 Nov 2015 10:47:49 -0500 + +zoneminder (1.29.0-trusty-2015110601) trusty; urgency=medium + + * add a FIONREAD test on timeout + + -- Isaac Connor Wed, 22 Jul 2015 09:55:37 -0400 + +zoneminder (1.29.0-trusty-2015072201) trusty; urgency=medium + + * add AnalysisFPS + + -- Isaac Connor Wed, 22 Jul 2015 09:55:37 -0400 + +zoneminder (1.29.0-trusty-2015071601) trusty; urgency=medium + + * Merge master and zmtrigger + + -- Isaac Connor Thu, 16 Jul 2015 13:15:58 -0400 + +zoneminder (1.29.0-trusty-38) trusty; urgency=medium + + * Merge master + + -- Isaac Connor Tue, 14 Jul 2015 10:15:00 -0400 + +zoneminder (1.29.0-trusty-37) trusty; urgency=medium + + * merge master api stuff, set sleep after failure to capture to 5000 instead of 5000000 + + -- Isaac Connor Fri, 19 Jun 2015 09:59:54 -0400 + +zoneminder (1.29.0-trusty-36) trusty; urgency=medium + + * Detect select interuption when no action on our fd + + -- Isaac Connor Thu, 28 May 2015 09:35:59 -0400 + +zoneminder (1.29.0-trusty-35) trusty; urgency=medium + + * better logging + + -- Isaac Connor Fri, 22 May 2015 15:30:42 -0400 + +zoneminder (1.29.0-trusty-34) trusty; urgency=medium + + * Faster shutdown and changes to ReadData + + -- Isaac Connor Thu, 21 May 2015 15:54:47 -0400 + +zoneminder (1.29.0-trusty-33) trusty; urgency=medium + + * update zmaudit some more + + -- Isaac Connor Thu, 14 May 2015 13:44:35 -0400 + +zoneminder (1.29.0-trusty-32) trusty; urgency=medium + + * merge zmaudit_updates1 + + -- Isaac Connor Wed, 13 May 2015 15:51:56 -0400 + +zoneminder (1.29.0-trusty-31) trusty; urgency=medium + + * Merge some fixes and zmaudit improvements + + -- Isaac Connor Wed, 13 May 2015 15:16:19 -0400 + +zoneminder (1.29.0-trusty-27) trusty; urgency=medium + + * fflush logs, merge master. + + -- Isaac Connor Mon, 30 Mar 2015 19:28:05 -0400 + +zoneminder (1.29.0-vivid-26) vivid; urgency=medium + + * logging improvements, merge RedData changes + + -- Isaac Connor Wed, 04 Mar 2015 16:39:19 -0500 + +zoneminder (1.29.0-vivid-25) vivid; urgency=medium + + * some change to ReadData + + -- Isaac Connor Mon, 02 Mar 2015 12:57:16 -0500 + +zoneminder (1.29.0-utopic-24) utopic; urgency=medium + + * merge local_raw + + -- Isaac Connor Mon, 23 Feb 2015 17:49:36 -0500 + +zoneminder (1.29.0-trusty-23) trusty; urgency=medium + + * more onvif merges, fix to zmfilter + + -- Isaac Connor Sat, 21 Feb 2015 16:17:01 -0500 + +zoneminder (1.29.0-trusty-22) trusty; urgency=medium + + * updates from master: merge onvif, default to classic if skin not defined. + + -- Isaac Connor Thu, 19 Feb 2015 18:14:58 -0500 + +zoneminder (1.29.0-utopic-21) utopic; urgency=medium + + * updates from master, improve monitor probing and support TRENDnet cameras + + -- Isaac Connor Tue, 17 Feb 2015 14:18:52 -0500 + +zoneminder (1.29.0-wheezy-20) wheezy; urgency=medium + + * zmaudit.pl improvements - double check db before deleting fs event + + -- Isaac Connor Wed, 04 Feb 2015 11:09:22 -0500 + +zoneminder (1.29.0-utopic-18) utopic; urgency=medium + + * RTSP Timeout fixes + + -- Isaac Connor Wed, 28 Jan 2015 13:49:16 -0500 + +zoneminder (1.29.0-utopic-17) utopic; urgency=medium + + * Merge master, use new split-up debian build + + -- Isaac Connor Mon, 26 Jan 2015 11:21:07 -0500 + +zoneminder (1.28.0+nmu1) testing; urgency=medium + + * Non-maintainer upload + * Split the debian package into several packages + * Switch to native source format + + -- Emmanuel Papin Thu, 15 Jan 2015 20:00:08 +0100 + +zoneminder (1.28.0-0.2) testing; urgency=medium + + * Non-maintainer upload. + * Upstream release for debian jessie + * Package dependencies updated + * debhelper version upgraded + * Standards-Version upgraded + * Use debhelper commands instead of standard commands + * Install man pages in /usr/share/man (patch added) + * Switch to quilt + * Switch to systemd + * Some lintian fixes + + -- Emmanuel Papin Wed, 26 Nov 2014 00:26:01 +0100 + +zoneminder (1.28.0-0.1) stable; urgency=medium + + * Release + + -- Isaac Connor Fri, 17 Oct 2014 09:27:22 -0400 + +zoneminder (1.27.99+1-testing-SNAPSHOT2014072901) testing; urgency=medium + + * improve error messages + * Make zmupdate re-run the most recent patch so that people running the daily builds get their db updates + + -- Isaac Connor Tue, 29 Jul 2014 14:50:20 -0400 + +zoneminder (1.27.0+1-testing-v4ltomonitor-1) testing; urgency=high + + * Snapshot release - + + -- Isaac Connor Wed, 09 Jul 2014 21:35:29 -0400 diff --git a/distros/ubuntu1410/compat b/distros/ubuntu1410/compat new file mode 100644 index 000000000..ec635144f --- /dev/null +++ b/distros/ubuntu1410/compat @@ -0,0 +1 @@ +9 diff --git a/distros/ubuntu1410/control b/distros/ubuntu1410/control new file mode 100644 index 000000000..4f979f8c2 --- /dev/null +++ b/distros/ubuntu1410/control @@ -0,0 +1,122 @@ +Source: zoneminder +Section: net +Priority: optional +Maintainer: Isaac Connor +Build-Depends: debhelper (>= 9), po-debconf (>= 1.0), autoconf, automake, libphp-serialization-perl, libgnutls-dev, libmysqlclient-dev | libmariadbclient-dev, libdbd-mysql-perl, libdate-manip-perl, libwww-perl, libjpeg8-dev | libjpeg9-dev | libjpeg62-turbo-dev, libpcre3-dev, libavcodec-dev, libavformat-dev (>= 3:0.svn20090204), libswscale-dev (>= 3:0.svn20090204), libavutil-dev, libv4l-dev (>= 0.8.3), libbz2-dev, libtool, libsys-mmap-perl, libavdevice-dev, libdevice-serialport-perl, libarchive-zip-perl, libmime-lite-perl, dh-autoreconf, libvlccore-dev, libvlc-dev, libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev, libgcrypt11-dev | libgcrypt20-dev, libpolkit-gobject-1-dev, libdbi-perl, libnet-sftp-foreign-perl, libexpect-perl, libmime-tools-perl, libx264-dev, libmp4v2-dev, libpcre3-dev +Standards-Version: 3.9.6 + +Package: zoneminder +Section: metapackages +Architecture: all +Depends: ${misc:Depends}, + libzoneminder-perl (>= ${source:Version}), + zoneminder-database (>= ${source:Version}), + zoneminder-core (>= ${binary:Version}), + zoneminder-ui-base (>= ${source:Version}), + zoneminder-ui-classic (>= ${source:Version}), + zoneminder-ui-mobile (>= ${source:Version}), + zoneminder-ui-xml (>= ${source:Version}) +Description: Video camera security and surveillance solution (metapackage) + ZoneMinder is intended for use in single or multi-camera video security + applications, including commercial or home CCTV, theft prevention and child + or family member or home monitoring and other care scenarios. It + supports capture, analysis, recording, and monitoring of video data coming + from one or more video or network cameras attached to a Linux system. + ZoneMinder also support web and semi-automatic control of Pan/Tilt/Zoom + cameras using a variety of protocols. It is suitable for use as a home + video security system and for commercial or professional video security + and surveillance. It can also be integrated into a home automation system + via X.10 or other protocols. + +Package: libzoneminder-perl +Section: perl +Architecture: all +Depends: ${misc:Depends}, ${perl:Depends}, libdbi-perl, + libdevice-serialport-perl, libimage-info-perl, libjson-any-perl, + libsys-mmap-perl, liburi-encode-perl, libwww-perl +Description: Perl libraries for ZoneMinder + ZoneMinder is a video camera security and surveillance solution. + . + This package provides the libraries for the perl scripts, it can be used to + write custom interfaces as well. + +Package: zoneminder-database +Section: database +Architecture: all +Depends: ${misc:Depends}, debconf, dbconfig-common, + mysql-client | mariadb-client +Recommends: mysql-server | mariadb-server +Description: Database management package for ZoneMinder + ZoneMinder is a video camera security and surveillance solution. + . + This package provides the sql files and maintenance scripts to perform all the + database operations (installation, upgrade or removal) on a local or a remote + server. + +Package: zoneminder-core +Section: video +Architecture: any +Depends: libzoneminder-perl (= ${source:Version}), + zoneminder-database (= ${source:Version}), ${shlibs:Depends}, ${misc:Depends}, + ${perl:Depends}, libarchive-tar-perl, libarchive-zip-perl, libdate-manip-perl, + libdbi-perl, libmodule-load-conditional-perl, libmime-lite-perl, + libmime-tools-perl, libnet-sftp-foreign-perl, libphp-serialization-perl, + debconf, ffmpeg | libav-tools, rsyslog | system-log-daemon, zip, + policykit-1, apache2, libmp4v2-2, libpcre++0 +Description: Core binaries and perl scripts for ZoneMinder + ZoneMinder is a video camera security and surveillance solution. + . + This package provides the executable compiled binaries which do the main video + processing work and the perl scripts which perform helper and/or external + interface tasks. + +Package: zoneminder-core-dbg +Priority: extra +Section: debug +Architecture: any +Depends: zoneminder-core (= ${binary:Version}), ${misc:Depends} +Description: Debugging symbols for ZoneMinder + ZoneMinder is a video camera security and surveillance solution. + . + This package provides the debugging symbols for the executable compiled + binaries. + +Package: zoneminder-ui-base +Section: web +Architecture: any +Depends: zoneminder-core (= ${binary:Version}), ${shlibs:Depends}, + ${misc:Depends}, debconf, apache2, libapache2-mod-php5 | libapache2-mod-fcgid, + php5, php5-mysql | php5-mysqlnd, php5-gd +Description: Essential files for ZoneMinder's web user interface + ZoneMinder is a video camera security and surveillance solution. + . + This package provides the essential web files and maintenance scripts to set up + a basic web environment. + +Package: zoneminder-ui-classic +Section: web +Architecture: all +Depends: zoneminder-ui-base (>= ${source:Version}), ${misc:Depends} +Description: Classic web user interface for ZoneMinder + ZoneMinder is a video camera security and surveillance solution. + . + This package provides the classic web user interface. + +Package: zoneminder-ui-mobile +Section: web +Architecture: all +Depends: zoneminder-ui-base (>= ${source:Version}), ${misc:Depends} +Description: Mobile web user interface for ZoneMinder + ZoneMinder is a video camera security and surveillance solution. + . + This package provides the web user interface for mobile devices. + +Package: zoneminder-ui-xml +Section: web +Architecture: all +Depends: zoneminder-ui-base (>= ${source:Version}), ${misc:Depends} +Description: XML interface for ZoneMinder + ZoneMinder is a video camera security and surveillance solution. + . + This package provides a XML interface mainly intended for use with the eyeZm + iPhone Application, but can be used with any other custom programs as well. diff --git a/distros/ubuntu1410/copyright b/distros/ubuntu1410/copyright new file mode 100644 index 000000000..a177502a0 --- /dev/null +++ b/distros/ubuntu1410/copyright @@ -0,0 +1,22 @@ +Copyright: + +Copyright 2002 Philip Coombes + +License: + +This package 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 package 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 package; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +On Debian GNU/Linux systems, the text of the GPL can be found in +/usr/share/common-licenses/GPL. diff --git a/distros/ubuntu1410/docs b/distros/ubuntu1410/docs new file mode 100644 index 000000000..b43bf86b5 --- /dev/null +++ b/distros/ubuntu1410/docs @@ -0,0 +1 @@ +README.md diff --git a/distros/ubuntu1410/libzoneminder-perl.install b/distros/ubuntu1410/libzoneminder-perl.install new file mode 100644 index 000000000..792ffc15e --- /dev/null +++ b/distros/ubuntu1410/libzoneminder-perl.install @@ -0,0 +1,4 @@ +usr/share/perl5/ZoneMinder +usr/share/perl5/ZoneMinder.pm +debian/tmp/usr/share/man/man3/ZoneMinder.3pm +debian/tmp/usr/share/man/man3/ZoneMinder::* diff --git a/distros/ubuntu1410/patches/series b/distros/ubuntu1410/patches/series new file mode 100644 index 000000000..e69de29bb diff --git a/distros/ubuntu1410/po/POTFILES.in b/distros/ubuntu1410/po/POTFILES.in new file mode 100644 index 000000000..5b155907e --- /dev/null +++ b/distros/ubuntu1410/po/POTFILES.in @@ -0,0 +1,3 @@ +[type: gettext/rfc822deb] zoneminder-core.templates +[type: gettext/rfc822deb] zoneminder-database.templates +[type: gettext/rfc822deb] zoneminder-ui-base.templates diff --git a/distros/ubuntu1410/po/fr.po b/distros/ubuntu1410/po/fr.po new file mode 100644 index 000000000..85ced7fd2 --- /dev/null +++ b/distros/ubuntu1410/po/fr.po @@ -0,0 +1,252 @@ +# debconf french translation file for ZoneMinder. +# Copyright (C) 2001-2008 Philip Coombes +# This file is distributed under the same license as the zoneminder package. +# First author: Emmanuel Papin , 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: zoneminder\n" +"Report-Msgid-Bugs-To: zoneminder@packages.debian.org\n" +"POT-Creation-Date: 2014-12-16 12:34+0100\n" +"PO-Revision-Date: 2014-12-07 00:40+0100\n" +"Last-Translator: Emmanuel Papin \n" +"Language-Team: French \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../zoneminder-core.templates:1001 +msgid "Delete this non empty directory?" +msgstr "Supprimer ce rรฉpertoire non vide ?" + +#. Type: boolean +#. Description +#: ../zoneminder-core.templates:1001 +msgid "" +"A purge of the ZoneMinder package is performed but the directory '/var/cache/" +"zoneminder' is not empty so it will not be deleted." +msgstr "" +"Une purge du paquet ZoneMinder est en cours mais le rรฉpertoire '/var/cache/" +"zoneminder' n'est pas vide et sera donc conservรฉ." + +#. Type: boolean +#. Description +#: ../zoneminder-core.templates:1001 +msgid "" +"Please consider that this directory is designed to contain data resulting " +"from event detection. Therefore, \"proof of evidence\" could be lost!\"" +msgstr "" +"Veuillez considรฉrer que ce rรฉpertoire est conรงu pour contenir des donnรฉes " +"rรฉsultants de la dรฉtection d'รฉvรฉnements. Par consรฉquent, des preuves " +"pourraient รชtre perdues !" + +#. Type: boolean +#. Description +#: ../zoneminder-core.templates:1001 +msgid "" +"If you are not sure of your decision, please do not delete this directory " +"but perform a manual checkup." +msgstr "" +"Si vous n'รชtes pas sรปr de votre dรฉcision, veuillez conserver ce rรฉpertoire " +"et effectuer une vรฉrification manuelle." + +#. Type: boolean +#. Description +#: ../zoneminder-core.templates:2001 +msgid "Deletion confirmed?" +msgstr "Supression confirmรฉe ?" + +#. Type: boolean +#. Description +#: ../zoneminder-core.templates:2001 +msgid "" +"You have allowed the deletion of directory '/var/cache/zoneminder' although " +"it may contain critical data." +msgstr "" +"Vous avez autorisรฉ la suppression du rรฉpertoire '/var/cache/zoneminder' bien " +"qu'il puisse contenir des donnรฉes critiques." + +#. Type: select +#. Choices +#: ../zoneminder-database.templates:1001 +msgid "local" +msgstr "local" + +#. Type: select +#. Choices +#: ../zoneminder-database.templates:1001 +msgid "remote" +msgstr "distant" + +#. Type: select +#. Description +#: ../zoneminder-database.templates:1002 +msgid "Database location:" +msgstr "Emplacement de la base de donnรฉe :" + +#. Type: select +#. Description +#: ../zoneminder-database.templates:1002 +msgid "" +"A database server is required to run ZoneMinder. The database can be " +"installed either locally or remotely on a machine of your network." +msgstr "" +"Un serveur de base de donnรฉes est requis pour ZoneMinder. La base de donnรฉe " +"peut รชtre installรฉe localement ou ร  distance sur une machine de votre rรฉseau." + +#. Type: select +#. Description +#: ../zoneminder-database.templates:1002 +msgid "" +"If you choose a remote location, you will have to select the 'tcp/ip' " +"connection method and enter the hostname or ip address of the remote machine " +"in the next configuration screens." +msgstr "" +"Si vous choisissez un emplacement distant, vous devrez sรฉlectionner la " +"mรฉthode de connexion 'tcp/ip' et entrer le nom rรฉseau ou l'adresse ip de la " +"machine distante dans les รฉcrans de configuration suivants." + +#. Type: error +#. Description +#: ../zoneminder-database.templates:2001 +msgid "No local database server is available:" +msgstr "Aucun serveur local de base de donnรฉes n'est disponible :" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:2001 +msgid "" +"Currently ZoneMinder supports mysql or mariadb database server but none of " +"them appears to be installed on this machine." +msgstr "" +"Actuellement ZoneMinder supporte les serveurs de base de donnรฉes mysql et " +"mariadb mais aucun d'entre eux n'est installรฉ sur cette machine." + +#. Type: error +#. Description +#: ../zoneminder-database.templates:2001 +msgid "" +"In order to complete ZoneMinder's installation, after ending of this " +"assistant, please install a compatible database server and then restart the " +"assistant by invoking:" +msgstr "" +"Afin de complรฉter l'installation de ZoneMinder, aprรจs la fermeture de cet " +"assitant, veuillez installer un serveur de base de donnรฉes compatible et " +"ensuite redรฉmarrez l'assistant en invoquant :" + +#. Type: error +#. Description +#. Type: error +#. Description +#: ../zoneminder-database.templates:2001 ../zoneminder-database.templates:3001 +msgid "$ sudo dpkg-reconfigure zoneminder" +msgstr "$ sudo dpkg-reconfigure zoneminder" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:3001 +msgid "Remote database servers are not allowed:" +msgstr "Les serveurs de base de donnรฉes distants ne sont pas autorisรฉs :" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:3001 +msgid "" +"The current configuration of dbconfig-common does not allow installation of " +"a database on remote servers." +msgstr "" +"La configuration actuelle de dbconfig-common ne permet pas l'installation de " +"bases de donnรฉes sur des serveurs distants." + +#. Type: error +#. Description +#: ../zoneminder-database.templates:3001 +msgid "" +"In order to reconfigure dbconfig-common, please invoke the following command " +"after ending of this assistant:" +msgstr "" +"Afin de reconfigurer dbconfig-common, veuillez invoquer la commande suivante " +"aprรจs la fermeture de cet assitant :" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:3001 +msgid "$ sudo dpkg-reconfigure dbconfig-common" +msgstr "$ sudo dpkg-reconfigure dbconfig-common" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:3001 +msgid "" +"Then, to complete ZoneMinder's installation, please restart this assistant " +"by invoking:" +msgstr "" +"Ensuite, pour complรฉter l'installation de ZoneMinder, veuillez redรฉmarrer " +"cet assistant en invoquant :" + +#. Type: password +#. Description +#: ../zoneminder-database.templates:4001 +msgid "New password for the ZoneMinder 'admin' user:" +msgstr "Nouveau mot de passe pour le compte 'admin' de ZoneMinder :" + +#. Type: password +#. Description +#: ../zoneminder-database.templates:4001 +msgid "Please enter the password of the default administrative user." +msgstr "Veuillez entrer le mot de passe du compte administrateur par dรฉfaut." + +#. Type: password +#. Description +#: ../zoneminder-database.templates:4001 +msgid "" +"While not mandatory, it is highly recommended that you set a custom password " +"for the administrative 'admin' user." +msgstr "" +"Bien que cela ne soit pas obligatoire, il est fortement recommandรฉ de " +"fournir un mot de passe personnalisรฉ pour le compte administrateur 'admin'." + +#. Type: password +#. Description +#: ../zoneminder-database.templates:4001 +msgid "If this field is left blank, the password will not be changed." +msgstr "Si le champ est laissรฉ vide, le mot de passe ne sera pas changรฉ." + +#. Type: password +#. Description +#: ../zoneminder-database.templates:5001 +msgid "Repeat password for the ZoneMinder 'admin' user:" +msgstr "Rรฉpรฉter le mot de passe pour le compte 'admin' de ZoneMinder :" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:6001 +msgid "Password input error" +msgstr "Erreur de mot de passe" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:6001 +msgid "The two passwords you entered were not the same. Please try again." +msgstr "" +"Les deux mots de passe saisis ne sont pas les mรชmes. Veuillez essayer ร  " +"nouveau." + +#. Type: multiselect +#. Description +#: ../zoneminder-ui-base.templates:1001 +msgid "Web server to reconfigure automatically:" +msgstr "Serveur web ร  reconfigurer automatiquement :" + +#. Type: multiselect +#. Description +#: ../zoneminder-ui-base.templates:1001 +msgid "" +"Please choose the web server that should be automatically configured for " +"ZoneMinder's web portal access." +msgstr "" +"Veuillez choisir le serveur web ร  reconfigurer automatiquement pour l'accรจs " +"au portail web de ZoneMinder." diff --git a/distros/ubuntu1410/po/templates.pot b/distros/ubuntu1410/po/templates.pot new file mode 100644 index 000000000..941a4094e --- /dev/null +++ b/distros/ubuntu1410/po/templates.pot @@ -0,0 +1,222 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: zoneminder\n" +"Report-Msgid-Bugs-To: zoneminder@packages.debian.org\n" +"POT-Creation-Date: 2014-12-16 12:34+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../zoneminder-core.templates:1001 +msgid "Delete this non empty directory?" +msgstr "" + +#. Type: boolean +#. Description +#: ../zoneminder-core.templates:1001 +msgid "" +"A purge of the ZoneMinder package is performed but the directory '/var/cache/" +"zoneminder' is not empty so it will not be deleted." +msgstr "" + +#. Type: boolean +#. Description +#: ../zoneminder-core.templates:1001 +msgid "" +"Please consider that this directory is designed to contain data resulting " +"from event detection. Therefore, \"proof of evidence\" could be lost!\"" +msgstr "" + +#. Type: boolean +#. Description +#: ../zoneminder-core.templates:1001 +msgid "" +"If you are not sure of your decision, please do not delete this directory " +"but perform a manual checkup." +msgstr "" + +#. Type: boolean +#. Description +#: ../zoneminder-core.templates:2001 +msgid "Deletion confirmed?" +msgstr "" + +#. Type: boolean +#. Description +#: ../zoneminder-core.templates:2001 +msgid "" +"You have allowed the deletion of directory '/var/cache/zoneminder' although " +"it may contain critical data." +msgstr "" + +#. Type: select +#. Choices +#: ../zoneminder-database.templates:1001 +msgid "local" +msgstr "" + +#. Type: select +#. Choices +#: ../zoneminder-database.templates:1001 +msgid "remote" +msgstr "" + +#. Type: select +#. Description +#: ../zoneminder-database.templates:1002 +msgid "Database location:" +msgstr "" + +#. Type: select +#. Description +#: ../zoneminder-database.templates:1002 +msgid "" +"A database server is required to run ZoneMinder. The database can be " +"installed either locally or remotely on a machine of your network." +msgstr "" + +#. Type: select +#. Description +#: ../zoneminder-database.templates:1002 +msgid "" +"If you choose a remote location, you will have to select the 'tcp/ip' " +"connection method and enter the hostname or ip address of the remote machine " +"in the next configuration screens." +msgstr "" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:2001 +msgid "No local database server is available:" +msgstr "" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:2001 +msgid "" +"Currently ZoneMinder supports mysql or mariadb database server but none of " +"them appears to be installed on this machine." +msgstr "" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:2001 +msgid "" +"In order to complete ZoneMinder's installation, after ending of this " +"assistant, please install a compatible database server and then restart the " +"assistant by invoking:" +msgstr "" + +#. Type: error +#. Description +#. Type: error +#. Description +#: ../zoneminder-database.templates:2001 ../zoneminder-database.templates:3001 +msgid "$ sudo dpkg-reconfigure zoneminder" +msgstr "" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:3001 +msgid "Remote database servers are not allowed:" +msgstr "" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:3001 +msgid "" +"The current configuration of dbconfig-common does not allow installation of " +"a database on remote servers." +msgstr "" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:3001 +msgid "" +"In order to reconfigure dbconfig-common, please invoke the following command " +"after ending of this assistant:" +msgstr "" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:3001 +msgid "$ sudo dpkg-reconfigure dbconfig-common" +msgstr "" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:3001 +msgid "" +"Then, to complete ZoneMinder's installation, please restart this assistant " +"by invoking:" +msgstr "" + +#. Type: password +#. Description +#: ../zoneminder-database.templates:4001 +msgid "New password for the ZoneMinder 'admin' user:" +msgstr "" + +#. Type: password +#. Description +#: ../zoneminder-database.templates:4001 +msgid "Please enter the password of the default administrative user." +msgstr "" + +#. Type: password +#. Description +#: ../zoneminder-database.templates:4001 +msgid "" +"While not mandatory, it is highly recommended that you set a custom password " +"for the administrative 'admin' user." +msgstr "" + +#. Type: password +#. Description +#: ../zoneminder-database.templates:4001 +msgid "If this field is left blank, the password will not be changed." +msgstr "" + +#. Type: password +#. Description +#: ../zoneminder-database.templates:5001 +msgid "Repeat password for the ZoneMinder 'admin' user:" +msgstr "" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:6001 +msgid "Password input error" +msgstr "" + +#. Type: error +#. Description +#: ../zoneminder-database.templates:6001 +msgid "The two passwords you entered were not the same. Please try again." +msgstr "" + +#. Type: multiselect +#. Description +#: ../zoneminder-ui-base.templates:1001 +msgid "Web server to reconfigure automatically:" +msgstr "" + +#. Type: multiselect +#. Description +#: ../zoneminder-ui-base.templates:1001 +msgid "" +"Please choose the web server that should be automatically configured for " +"ZoneMinder's web portal access." +msgstr "" diff --git a/distros/ubuntu1410/rules b/distros/ubuntu1410/rules new file mode 100755 index 000000000..49d3549f1 --- /dev/null +++ b/distros/ubuntu1410/rules @@ -0,0 +1,154 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +# These are used for cross-compiling and for saving the configure script +# from having to guess our platform (since we know it already) +DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) +DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) + +CFLAGS = -Wall +CPPFLAGS = -D__STDC_CONSTANT_MACROS +CXXFLAGS = -DHAVE_LIBCRYPTO + +ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) +DEBOPT = --enable-debug +CFLAGS += -g +CXXFLAGS += -g +else +DEBOPT = +endif + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) +CFLAGS += -O0 +else +CFLAGS += -O2 +endif + +# These are used to get the most recent version of the original sources from github +UURL = $(shell git config --get remote.origin.url) +BRANCH = $(shell git rev-parse --abbrev-ref HEAD) +HEAD = $(shell git rev-parse HEAD) +PKD = $(abspath $(dir $(MAKEFILE_LIST))) +PKG = $(word 2,$(shell dpkg-parsechangelog -l$(PKD)/changelog | grep ^Source)) +VER ?= $(shell dpkg-parsechangelog -l$(PKD)/changelog | perl -ne 'print $$1 if m{^Version:\s+(?:\d+:)?(\d.*)(?:\-|\+nmu\d+.*)};') +DTYPE = +TARBALL = ../$(PKG)_$(VER)$(DTYPE).orig.tar.xz + +%: + dh $@ --with autoreconf + +override_dh_auto_configure: + CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" dh_auto_configure -- \ + --host=$(DEB_HOST_GNU_TYPE) --build=$(DEB_BUILD_GNU_TYPE) \ + --sysconfdir=/etc/zm --prefix=/usr --mandir=\$${prefix}/share/man \ + --infodir=\$${prefix}/share/info --with-mysql=/usr \ + --with-mariadb=/usr --with-webdir=/usr/share/zoneminder \ + --with-ffmpeg=/usr --with-cgidir=/usr/lib/cgi-bin \ + --with-webuser=www-data --with-webgroup=www-data \ + --enable-mmap=yes $(DEBOPT) + +override_dh_clean: + # Add here commands to clean up after the build process. + [ ! -f Makefile ] || $(MAKE) distclean + dh_clean src/zm_config_defines.h + # + # Delete remaining auto-generated Makefile if Makefile.in exists + find $(CURDIR)/ -type f -name "Makefile" | while read file; do \ + [ -f $$file.in ] && rm -f $$file; \ + done || true + # + # Delete remaining auto-generated Makefile.in if Makefile.am exists + find $(CURDIR)/ -type f -name "Makefile.in" | while read filein; do \ + fileam=`echo $$filein | sed 's/\(.*\)\.in/\1\.am/'`; \ + [ -f $$fileam ] && rm -f $$filein; \ + done || true + +override_dh_install: + dh_install --fail-missing + # + # NOTE: This is a short-term kludge; hopefully changes in the next + # upstream version will render this unnecessary. + rm -rf debian/zoneminder/usr/share/zoneminder/events + rm -rf debian/zoneminder/usr/share/zoneminder/images + rm -rf debian/zoneminder/usr/share/zoneminder/temp + # The link stuff for these folders has been moved to + # zoneminder-core.links file + # + # This is a slightly lesser kludge; moving the cgi stuff to + # /usr/share/zoneminder/cgi-bin breaks one set of behavior, + # having it just in /usr/lib/cgi-bin breaks another bit of + # behavior. + # The link stuff for /usr/share/zoneminder/cgi-bin has been moved to + # zoneminder-ui-base.links file + +override_dh_installinit: + dh_installinit --package=zoneminder-core --name=zoneminder + +override_dh_systemd_start: + dh_systemd_start --package=zoneminder-core --name=zoneminder \ + --restart-after-upgrade + +override_dh_systemd_enable: + dh_systemd_enable --package=zoneminder-core --name=zoneminder + +override_dh_fixperms: + dh_fixperms + # + # As requested by the Debian Webapps Policy Manual ยง3.2.1 + chown root:www-data debian/zoneminder-core/etc/zm/zm.conf + chmod 640 debian/zoneminder-core/etc/zm/zm.conf + +override_dh_auto_test: + # do not run tests... + +.PHONY: override_dh_strip +override_dh_strip: + dh_strip --dbg-package=zoneminder-core-dbg + +# Inspired by https://wiki.debian.org/onlyjob/get-orig-source +.PHONY: get-orig-source +get-orig-source: $(TARBALL) $(info I: $(PKG)_$(VER)$(DTYPE)) + @ + +$(TARBALL): + $(if $(wildcard $(PKG)-$(VER)),$(error folder '$(PKG)-$(VER)' exists, aborting...)) + @echo "# Cloning origin repository..."; \ + if ! git clone $(UURL) $(PKG)-$(VER); then \ + $(RM) -r $(PKG)-$(VER); \ + echo "failed to clone repository, aborting..."; \ + false; \ + fi + @if [ $(BRANCH) != "master" ]; then \ + cd $(PKG)-$(VER); \ + echo "# Not on master branch, fetching origin branch '$(BRANCH)'..."; \ + git fetch origin $(BRANCH):$(BRANCH) || false; \ + echo "# Switching to branch '$(BRANCH)'..."; \ + git checkout $(BRANCH) || false; \ + fi + @echo "# Checking local source..." + @if [ $$(cd $(PKG)-$(VER) && git rev-parse HEAD) = $(HEAD) ]; then \ + echo "even with origin, ok"; \ + true; \ + else \ + echo "not even with origin, aborting..."; \ + false; \ + fi + @echo "# Setting times..." + @cd $(PKG)-$(VER) \ + && for F in $$(git ls-tree -r --name-only HEAD | sed -e "s/\s/\*/g"); do \ + touch --no-dereference -d "$$(git log -1 --format="%ai" -- $$F)" "$$F"; \ + done + @echo "# Cleaning-up..." + cd $(PKG)-$(VER) && $(RM) -r .git + @echo "# Packing file '$(TARBALL)'..." + @find -L "$(PKG)-$(VER)" -xdev -type f -print | sort \ + | XZ_OPT="-6v" tar -caf "$(TARBALL)" -T- --owner=root --group=root --mode=a+rX \ + && $(RM) -r "$(PKG)-$(VER)" diff --git a/distros/ubuntu1410/source/format b/distros/ubuntu1410/source/format new file mode 100644 index 000000000..89ae9db8f --- /dev/null +++ b/distros/ubuntu1410/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/distros/ubuntu1410/source/local-options b/distros/ubuntu1410/source/local-options new file mode 100644 index 000000000..e69de29bb diff --git a/distros/ubuntu1410/source/options b/distros/ubuntu1410/source/options new file mode 100644 index 000000000..8bd61fce6 --- /dev/null +++ b/distros/ubuntu1410/source/options @@ -0,0 +1 @@ +extend-diff-ignore = "(^|/)(config\.sub|config\.guess|Makefile|aclocal.m4|compile|config.h.in|configure|depcomp|install-sh|missing)$" diff --git a/distros/ubuntu1410/zoneminder-core.config b/distros/ubuntu1410/zoneminder-core.config new file mode 100644 index 000000000..2a15a599e --- /dev/null +++ b/distros/ubuntu1410/zoneminder-core.config @@ -0,0 +1,11 @@ +#!/bin/sh +# config maintainer script for zoneminder-core package + +set -e + +# Source the debconf stuff +. /usr/share/debconf/confmodule + +#DEBHELPER# + +exit 0 diff --git a/distros/ubuntu1410/zoneminder-core.dirs b/distros/ubuntu1410/zoneminder-core.dirs new file mode 100644 index 000000000..350c32aff --- /dev/null +++ b/distros/ubuntu1410/zoneminder-core.dirs @@ -0,0 +1,4 @@ +var/log/zm +var/cache/zoneminder/events +var/cache/zoneminder/images +var/cache/zoneminder/temp diff --git a/distros/ubuntu1410/zoneminder-core.install b/distros/ubuntu1410/zoneminder-core.install new file mode 100644 index 000000000..afd9ada95 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-core.install @@ -0,0 +1,4 @@ +etc/zm +usr/bin +usr/share/polkit-1/actions +usr/share/polkit-1/rules.d diff --git a/distros/ubuntu1410/zoneminder-core.links b/distros/ubuntu1410/zoneminder-core.links new file mode 100644 index 000000000..5560a100a --- /dev/null +++ b/distros/ubuntu1410/zoneminder-core.links @@ -0,0 +1,3 @@ +var/cache/zoneminder/events usr/share/zoneminder/events +var/cache/zoneminder/images usr/share/zoneminder/images +var/cache/zoneminder/temp usr/share/zoneminder/temp diff --git a/distros/ubuntu1410/zoneminder-core.postinst b/distros/ubuntu1410/zoneminder-core.postinst new file mode 100644 index 000000000..da2b444fe --- /dev/null +++ b/distros/ubuntu1410/zoneminder-core.postinst @@ -0,0 +1,80 @@ +#! /bin/sh +# postinst maintainer script for zoneminder-core package + +set -e + +# Source the debconf stuff +. /usr/share/debconf/confmodule + +# Source the config file +CONFIGFILE=/etc/zm/zm.conf +. $CONFIGFILE + +# Do this when the package is installed, upgraded or reconfigured +if [ "$1" = "configure" ] || [ "$1" = "reconfigure" ]; then + + # Retrieve data from dbconfig (inputs from user) + . /etc/dbconfig-common/zoneminder.conf + + # ZoneMinder config file handling + # Inspired by: http://manpages.debian.org/cgi-bin/man.cgi?query=debconf-devel&sektion=7 + + # Backup the config file + cp -a -f $CONFIGFILE ${CONFIGFILE}.postinst.bak + + # Redeclare variables if missing in config file + test -z $dbc_dbserver || grep -Eq "^ *ZM_DB_HOST=" $CONFIGFILE \ + || echo "ZM_DB_HOST=" >> ${CONFIGFILE}.postinst.bak + test -z $dbc_dbname || grep -Eq "^ *ZM_DB_NAME=" $CONFIGFILE \ + || echo "ZM_DB_NAME=" >> ${CONFIGFILE}.postinst.bak + test -z $dbc_dbuser || grep -Eq "^ *ZM_DB_USER=" $CONFIGFILE \ + || echo "ZM_DB_USER=" >> ${CONFIGFILE}.postinst.bak + test -z $dbc_dbpass || grep -Eq "^ *ZM_DB_PASS=" $CONFIGFILE \ + || echo "ZM_DB_PASS=" >> ${CONFIGFILE}.postinst.bak + + # Prevent ZM_DB_HOST to be empty if user selected the 'unix socket' method + if test -z $dbc_dbserver; then + dbc_dbserver_override="localhost" + else + dbc_dbserver_override=$dbc_dbserver + fi + + # Update variables in config file + sed -i "s/^ *ZM_DB_HOST=.*/ZM_DB_HOST=$dbc_dbserver_override/" \ + ${CONFIGFILE}.postinst.bak + test -z $dbc_dbname || sed -i "s/^ *ZM_DB_NAME=.*/ZM_DB_NAME=$dbc_dbname/" \ + ${CONFIGFILE}.postinst.bak + test -z $dbc_dbuser || sed -i "s/^ *ZM_DB_USER=.*/ZM_DB_USER=$dbc_dbuser/" \ + ${CONFIGFILE}.postinst.bak + test -z $dbc_dbpass || sed -i "s/^ *ZM_DB_PASS=.*/ZM_DB_PASS=$dbc_dbpass/" \ + ${CONFIGFILE}.postinst.bak + + # Clean-up backup file + mv -f ${CONFIGFILE}.postinst.bak $CONFIGFILE + + + # Set some file permissions + chown $ZM_WEB_USER:$ZM_WEB_GROUP /var/log/zm + if [ -z "$2" ]; then + chown $ZM_WEB_USER:$ZM_WEB_GROUP -R /var/cache/zoneminder + fi + # As requested by the Debian Webapps Policy Manual ยง3.2.1 + chown root:${ZM_WEB_GROUP} $CONFIGFILE + chmod 640 $CONFIGFILE +fi + +# Do this every time the package is installed or upgraded +# Test for database presence to avoid failure of zmupdate.pl +if [ "$dbc_install" = "true" ] && [ "$1" = "configure" ]; then + + # Ensure zoneminder is stopped + deb-systemd-invoke stop zoneminder.service || exit $? + + # Run the ZoneMinder update tool + zmupdate.pl --nointeractive + +fi + +#DEBHELPER# + +exit 0 diff --git a/distros/ubuntu1410/zoneminder-core.postrm b/distros/ubuntu1410/zoneminder-core.postrm new file mode 100644 index 000000000..d75e75e8b --- /dev/null +++ b/distros/ubuntu1410/zoneminder-core.postrm @@ -0,0 +1,37 @@ +#! /bin/sh +# postrm maintainer script for zoneminder-core package + +set -e + +# Source the debconf stuff +if [ -f /usr/share/debconf/confmodule ]; then + . /usr/share/debconf/confmodule +fi + +if [ "$1" = "purge" ]; then + + # Ask the user if we have to remove the cache directory even if not empty + if [ -d /var/cache/zoneminder ] \ + && [ ! $(find /var/cache/zoneminder -maxdepth 0 -type d -empty 2>/dev/null) ]; then + RET="" + db_input high zoneminder/ask_delete || true + db_go || true + db_get zoneminder/ask_delete + if [ "$RET" = "true" ]; then + RET="" + db_input high zoneminder/ask_delete_again || true + db_go || true + db_get zoneminder/ask_delete_again + if [ "$RET" = "true" ]; then + rm -rf /var/cache/zoneminder + fi + fi + fi +fi + +#DEBHELPER# + +# postrm rm may freeze without that +db_stop + +exit 0 diff --git a/distros/ubuntu1410/zoneminder-core.preinst b/distros/ubuntu1410/zoneminder-core.preinst new file mode 100644 index 000000000..3ed1ef661 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-core.preinst @@ -0,0 +1,33 @@ +#!/bin/sh +# preinst maintainer script for zoneminder-core package + +set -e + +abort=false +if [ -L /usr/share/zoneminder/events ]; then + l=$(readlink /usr/share/zoneminder/events) + if [ "$l" != "/var/cache/zoneminder/events" ]; then + abort=true + fi +fi +if [ -L /usr/share/zoneminder/images ]; then + l=$(readlink /usr/share/zoneminder/images ) + if [ "$l" != "/var/cache/zoneminder/images" ]; then + abort=true + fi +fi + +if [ "$abort" = "true" ]; then + cat >&2 << EOF +Aborting installation of zoneminder due to non-default symlinks in +/usr/share/zoneminder for the images and/or events directory, which could +result in loss of data. Please move your data in each of these directories to +/var/cache/zoneminder before installing zoneminder from the package. +EOF + exit 1 + +fi + +#DEBHELPER# + +exit 0 diff --git a/distros/ubuntu1410/zoneminder-core.templates b/distros/ubuntu1410/zoneminder-core.templates new file mode 100644 index 000000000..35fdefd7a --- /dev/null +++ b/distros/ubuntu1410/zoneminder-core.templates @@ -0,0 +1,19 @@ +Template: zoneminder/ask_delete +Type: boolean +Default: false +_Description: Delete this non empty directory? + A purge of the ZoneMinder package is performed but the directory + '/var/cache/zoneminder' is not empty so it will not be deleted. + . + Please consider that this directory is designed to contain data resulting from + event detection. Therefore, "proof of evidence" could be lost!" + . + If you are not sure of your decision, please do not delete this directory but + perform a manual checkup. + +Template: zoneminder/ask_delete_again +Type: boolean +Default: false +_Description: Deletion confirmed? + You have allowed the deletion of directory '/var/cache/zoneminder' although + it may contain critical data. diff --git a/distros/ubuntu1410/zoneminder-core.zoneminder.init b/distros/ubuntu1410/zoneminder-core.zoneminder.init new file mode 100644 index 000000000..d3354c1d8 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-core.zoneminder.init @@ -0,0 +1,90 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: zoneminder +# Required-Start: $network $remote_fs $syslog +# Required-Stop: $network $remote_fs $syslog +# Should-Start: mysql +# Should-Stop: mysql +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Control ZoneMinder as a Service +### END INIT INFO +# description: Control ZoneMinder as a Service +# chkconfig: 2345 20 20 + +# Source function library. +#. /etc/rc.d/init.d/functions + +prog=ZoneMinder +ZM_PATH_BIN="/usr/bin" +RUNDIR=/var/run/zm +TMPDIR=/tmp/zm +command="$ZM_PATH_BIN/zmpkg.pl" + +start() { + echo -n "Starting $prog: " + mkdir -p $RUNDIR && chown www-data:www-data $RUNDIR + mkdir -p $TMPDIR && chown www-data:www-data $TMPDIR + $command start + RETVAL=$? + [ $RETVAL = 0 ] && echo success + [ $RETVAL != 0 ] && echo failure + echo + [ $RETVAL = 0 ] && touch /var/lock/zm + return $RETVAL +} +stop() { + echo -n "Stopping $prog: " + # + # Why is this status check being done? + # as $command stop returns 1 if zoneminder + # is stopped, which will result in + # this returning 1, which will stuff + # dpkg when it tries to stop zoneminder before + # uninstalling . . . + # + result=`$command status` + if [ ! "$result" = "running" ]; then + echo "Zoneminder already stopped" + echo + RETVAL=0 + else + $command stop + RETVAL=$? + [ $RETVAL = 0 ] && echo success + [ $RETVAL != 0 ] && echo failure + echo + [ $RETVAL = 0 ] && rm -f /var/lock/zm + fi +} +status() { + result=`$command status` + if [ "$result" = "running" ]; then + echo "ZoneMinder is running" + RETVAL=0 + else + echo "ZoneMinder is stopped" + RETVAL=1 + fi +} + +case "$1" in +'start') + start + ;; +'stop') + stop + ;; +'restart' | 'force-reload') + stop + start + ;; +'status') + status + ;; +*) + echo "Usage: $0 { start | stop | restart | status }" + RETVAL=1 + ;; +esac +exit $RETVAL diff --git a/distros/ubuntu1410/zoneminder-core.zoneminder.service b/distros/ubuntu1410/zoneminder-core.zoneminder.service new file mode 100644 index 000000000..d82270024 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-core.zoneminder.service @@ -0,0 +1,19 @@ +# ZoneMinder systemd unit file +# This file is intended to work with debian distributions + +[Unit] +Description=ZoneMinder CCTV recording and security system +After=network.target mysql.service apache2.service +Requires=apache2.service +Wants=mysql.service + +[Service] +User=www-data +Type=forking +ExecStart=/usr/bin/zmpkg.pl start +ExecReload=/usr/bin/zmpkg.pl restart +ExecStop=/usr/bin/zmpkg.pl stop +PIDFile=/var/run/zm/zm.pid + +[Install] +WantedBy=multi-user.target diff --git a/distros/ubuntu1410/zoneminder-core.zoneminder.tmpfile b/distros/ubuntu1410/zoneminder-core.zoneminder.tmpfile new file mode 100644 index 000000000..6ea70bf35 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-core.zoneminder.tmpfile @@ -0,0 +1 @@ +d /var/run/zm 0755 www-data www-data diff --git a/distros/ubuntu1410/zoneminder-database.config b/distros/ubuntu1410/zoneminder-database.config new file mode 100644 index 000000000..f6a84d36d --- /dev/null +++ b/distros/ubuntu1410/zoneminder-database.config @@ -0,0 +1,142 @@ +#!/bin/sh +# config maintainer script for zoneminder + +set -e + +# Source the debconf stuff +. /usr/share/debconf/confmodule + +# Set the first version in which dbconfig-common was introduced in the package +dbc_first_version="1.28.0" + +CONFIGFILE=/etc/zm/zm.conf +if [ -e $CONFIGFILE ]; then + # Source the config file if exists + . $CONFIGFILE +elif [ -e ${CONFIGFILE}.dpkg-new ]; then + # If no config file, source the config file which is going to be installed + # by the core package + . ${CONFIGFILE}.dpkg-new +else + # If no config file is going to be installed, set some default values + ZM_DB_HOST= + ZM_DB_NAME="zm" + ZM_DB_USER="zmuser" +fi + +# Set some variables for the dbconfig-common stuff +dbc_dbserver="$ZM_DB_HOST" +dbc_dbname="$ZM_DB_NAME" +dbc_dbuser="$ZM_DB_USER" + +if [ -f /usr/share/dbconfig-common/dpkg/config ]; then + + # Default use dbconfig-common + dbc_install="true" + + # Currently we only support mysql database + dbc_dbtypes="mysql" + + # Set authentication method to password + dbc_authmethod_user="password" + + # Source the dbconfig-common stuff + . /usr/share/dbconfig-common/dpkg/config +fi + +# Do this when the package is installed, upgraded or reconfigured +# Most of answers are cached so the questions will not be asked again +if [ "$1" = "configure" ] || [ "$1" = "reconfigure" ]; then + + # Ask the user if the database shall be installed locally or remotely + db_input high zoneminder/debconf_dblocation || true + db_go || true + db_get zoneminder/debconf_dblocation + + if [ "$RET" = "local" ]; then + if [ ! -e "/usr/sbin/mysqld" ]; then + # Display a message and exit if the user want a local database but + # no database server is available + db_input high zoneminder/debconf_dblocalmissingerror || true + db_go || true + exit 0 + else + # Set the database server to localhost + dbc_dbserver="localhost" + fi + else + # Source the dbconfig main configuration file + if [ -f /etc/dbconfig-common/config ]; then + . /etc/dbconfig-common/config + fi + if [ "$dbc_remote_questions_default" = "false" ]; then + # Display a message and exit if the dbconfig configuration does not + # allow installation of remote databases + # Note: To overcome this issue, we could think to override the + # default setting by using dbc_remote_questions_default='true' in + # maintainer scripts but unfortunately this does not work due to + # current dbconfig design + # More information here: + # https://bugs.launchpad.net/ubuntu/+source/dbconfig-common/+bug/1065331 + db_input high zoneminder/debconf_dbconfigerror || true + db_go || true + exit 0 + fi + fi + + # Ask the user for all database settings + dbc_go zoneminder $@ + + # Ask the user for the password of the database administrator if the user + # has not yet answered to this question. + # This situation may occur if the user skipped the database creation step + # when reconfiguring the package. + RET="" + db_get zoneminder/mysql/admin-pass + if [ -z "$RET" ]; then + db_input high zoneminder/mysql/admin-pass || true + db_go || true + fi + + # Do this only when not upgrading the package (no old version in argument) + if [ -z "$2" ]; then + # Ask for the password of 'admin' user + while :; do + RET="" + db_input high zoneminder/admin_password || true + db_go || true + db_get zoneminder/admin_password + # If password isn't empty we ask for password verification + if [ -z "$RET" ]; then + db_fset zoneminder/admin_password seen false + db_fset zoneminder/admin_password_again seen false + break + fi + ROOT_PW="$RET" + db_input high zoneminder/admin_password_again || true + db_go || true + db_get zoneminder/admin_password_again + if [ "$RET" = "$ROOT_PW" ]; then + ROOT_PW="" + break + fi + db_fset zoneminder/password_mismatch seen false + db_input critical zoneminder/password_mismatch || true + db_set zoneminder/admin_password "" + db_set zoneminder/admin_password_again "" + db_go || true + done + else + # If we are upgrading the package, set an empty password to disable + # password update in ZoneMinder database + db_set zoneminder/admin_password "" + fi + # Set the seen flag to not ask this question again if no password is + # provided + db_fset zoneminder/admin_password seen true + +fi + +#DEBHELPER# + +exit 0 diff --git a/distros/ubuntu1410/zoneminder-database.dirs b/distros/ubuntu1410/zoneminder-database.dirs new file mode 100644 index 000000000..b37463a9e --- /dev/null +++ b/distros/ubuntu1410/zoneminder-database.dirs @@ -0,0 +1,3 @@ +usr/share/zoneminder/db +usr/share/dbconfig-common/data/zoneminder/install +usr/share/dbconfig-common/data/zoneminder/upgrade/mysql diff --git a/distros/ubuntu1410/zoneminder-database.install b/distros/ubuntu1410/zoneminder-database.install new file mode 100644 index 000000000..756c5bbfa --- /dev/null +++ b/distros/ubuntu1410/zoneminder-database.install @@ -0,0 +1 @@ +usr/share/zoneminder/db diff --git a/distros/ubuntu1410/zoneminder-database.postinst b/distros/ubuntu1410/zoneminder-database.postinst new file mode 100644 index 000000000..41d4e5b5b --- /dev/null +++ b/distros/ubuntu1410/zoneminder-database.postinst @@ -0,0 +1,79 @@ +#! /bin/sh +# postinst maintainer script for zoneminder-db package + +set -e + +# Source the debconf stuff +. /usr/share/debconf/confmodule + +mysql_update() { + + # Source the dbconfig stuff + . /usr/share/dbconfig-common/internal/mysql + + # Update the password of the hard-coded default 'admin' account + test -z $ADMIN_PASSWORD || dbc_mysql_exec_command "UPDATE Users SET Password = password('$ADMIN_PASSWORD') WHERE Username = 'admin';" || true + + # Update the database version + dbc_mysql_exec_command "UPDATE Config SET Value = '$DB_VERSION' WHERE Name = 'ZM_DYN_DB_VERSION';" || true +} + +if [ -f /usr/share/dbconfig-common/dpkg/postinst ]; then + + # Set the first version in which dbconfig-common was introduced in the package + dbc_first_version="1.28.0" + + # Set the database type + dbc_dbtypes="mysql" + + # Source the dbconfig-common stuff + . /usr/share/dbconfig-common/dpkg/postinst +fi + +# Do this when the package is installed, upgraded or reconfigured +if [ "$1" = "configure" ] || [ "$1" = "reconfigure" ]; then + + # Install sql database create file for dbconfig + # (needed at first package installation) + if [ ! -f /usr/share/dbconfig-common/data/zoneminder/install/mysql ]; then + install -m 644 /usr/share/zoneminder/db/zm_create.sql \ + /usr/share/dbconfig-common/data/zoneminder/install/mysql + # Remove unneeded sql requests + # dbconfig will create the underlying database + sed -i "/^ *CREATE DATABASE /d" \ + /usr/share/dbconfig-common/data/zoneminder/install/mysql + sed -i "/^ *USE /d" \ + /usr/share/dbconfig-common/data/zoneminder/install/mysql + fi + + # Symlink sql update files for dbconfig (needed when upgrading the package) + for sqlfile in /usr/share/zoneminder/db/zm_update-*.sql; do + lnk=`echo $sqlfile | sed "s/^\/usr\/share\/zoneminder\/db\/zm_update-\(.*\)\.sql/\1/"` + if [ ! -L /usr/share/dbconfig-common/data/zoneminder/upgrade/mysql/$lnk ]; then + ln -sf $sqlfile \ + /usr/share/dbconfig-common/data/zoneminder/upgrade/mysql/$lnk + fi + done || true + + # Create the underlying database and populate it + # dbconfig will take care of applying any updates which are newer than the + # previously installed version + dbc_go zoneminder $@ + + # Get the password of ZoneMinder user 'admin' from debconf + db_get zoneminder/admin_password + ADMIN_PASSWORD=$RET + + # Remove the password from debconf database + test -z $ADMIN_PASSWORD || db_reset zoneminder/admin_password || true + + # Get the lastest database version from dbconfig upgrade folder + DB_VERSION=$(ls -rv /usr/share/dbconfig-common/data/zoneminder/upgrade/$dbc_dbtypes | head -1) + + # Update the default admin account and database version + mysql_update +fi + +#DEBHELPER# + +exit 0 diff --git a/distros/ubuntu1410/zoneminder-database.postrm b/distros/ubuntu1410/zoneminder-database.postrm new file mode 100644 index 000000000..231f01ad7 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-database.postrm @@ -0,0 +1,34 @@ +#! /bin/sh +# postrm maintainer script for zoneminder-db package + +set -e + +# Source the debconf stuff +if [ -f /usr/share/debconf/confmodule ]; then + . /usr/share/debconf/confmodule +fi + +# Source the dbconfig stuff +if [ -f /usr/share/dbconfig-common/dpkg/postrm ]; then + . /usr/share/dbconfig-common/dpkg/postrm + # Ask the user what do to with dbconfig when removing the package + dbc_go zoneminder $@ +fi + +if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then + # Remove dbconfig stuff added in postinst script + rm -rf /usr/share/dbconfig-common/data/zoneminder + # No need to manually remove the zm database, dbconfig take care of this +fi + +if [ "$1" = "purge" ]; then + # Delete a potential remaining file used in postinst script + rm -f /etc/zm/zm.conf.postinst.bak +fi + +#DEBHELPER# + +# postrm rm may freeze without that +db_stop + +exit 0 diff --git a/distros/ubuntu1410/zoneminder-database.prerm b/distros/ubuntu1410/zoneminder-database.prerm new file mode 100644 index 000000000..31786116a --- /dev/null +++ b/distros/ubuntu1410/zoneminder-database.prerm @@ -0,0 +1,22 @@ +#!/bin/sh +# prerm script for zoneminder-db package + +set -e + +# Source the debconf stuff if file exists +if [ -f /usr/share/debconf/confmodule ]; then + . /usr/share/debconf/confmodule +fi + +# If dbconfig-common is installed and has been used by zoneminder +if [ -f /usr/share/dbconfig-common/dpkg/prerm ] \ + && [ -f /etc/dbconfig-common/zoneminder.conf ]; then + # Source the dbconfig stuff + . /usr/share/dbconfig-common/dpkg/prerm + # Ask the user what do to with dbconfig before removing the package + dbc_go zoneminder $@ +fi + +# #DEBHELPER# + +exit 0 diff --git a/distros/ubuntu1410/zoneminder-database.templates b/distros/ubuntu1410/zoneminder-database.templates new file mode 100644 index 000000000..4de4342f6 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-database.templates @@ -0,0 +1,58 @@ +Template: zoneminder/debconf_dblocation +Type: select +__Choices: local, remote +Default: local +_Description: Database location: + A database server is required to run ZoneMinder. The database can be installed + either locally or remotely on a machine of your network. + . + If you choose a remote location, you will have to select the 'tcp/ip' + connection method and enter the hostname or ip address of the remote machine + in the next configuration screens. + +Template: zoneminder/debconf_dblocalmissingerror +Type: error +_Description: No local database server is available: + Currently ZoneMinder supports mysql or mariadb database server but none of them + appears to be installed on this machine. + . + In order to complete ZoneMinder's installation, after ending of this assistant, + please install a compatible database server and then restart the assistant by + invoking: + . + $ sudo dpkg-reconfigure zoneminder + +Template: zoneminder/debconf_dbconfigerror +Type: error +_Description: Remote database servers are not allowed: + The current configuration of dbconfig-common does not allow installation of + a database on remote servers. + . + In order to reconfigure dbconfig-common, please invoke the following command + after ending of this assistant: + . + $ sudo dpkg-reconfigure dbconfig-common + . + Then, to complete ZoneMinder's installation, please restart this assistant by + invoking: + . + $ sudo dpkg-reconfigure zoneminder + +Template: zoneminder/admin_password +Type: password +_Description: New password for the ZoneMinder 'admin' user: + Please enter the password of the default administrative user. + . + While not mandatory, it is highly recommended that you set a custom password + for the administrative 'admin' user. + . + If this field is left blank, the password will not be changed. + +Template: zoneminder/admin_password_again +Type: password +_Description: Repeat password for the ZoneMinder 'admin' user: + +Template: zoneminder/password_mismatch +Type: error +_Description: Password input error + The two passwords you entered were not the same. Please try again. diff --git a/distros/ubuntu1410/zoneminder-ui-base.config b/distros/ubuntu1410/zoneminder-ui-base.config new file mode 100644 index 000000000..2660208a8 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-ui-base.config @@ -0,0 +1,20 @@ +#!/bin/sh +# config maintainer script for zoneminder-ui-base package + +set -e + +# Source the debconf stuff +. /usr/share/debconf/confmodule + +# Do this when the package is installed, upgraded or reconfigured +# Most of answers are cached so the questions will not be asked again +if [ "$1" = "configure" ] || [ "$1" = "reconfigure" ]; then + + # Ask the user for the web server(s) to configure + db_input high zoneminder/webserver || true + db_go || true +fi + +#DEBHELPER# + +exit 0 diff --git a/distros/ubuntu1410/zoneminder-ui-base.install b/distros/ubuntu1410/zoneminder-ui-base.install new file mode 100644 index 000000000..f72b569be --- /dev/null +++ b/distros/ubuntu1410/zoneminder-ui-base.install @@ -0,0 +1,11 @@ +debian/apache.conf etc/zm +usr/lib/cgi-bin +usr/share/zoneminder/ajax +usr/share/zoneminder/css +usr/share/zoneminder/graphics +usr/share/zoneminder/includes +usr/share/zoneminder/index.php +usr/share/zoneminder/js +usr/share/zoneminder/lang +usr/share/zoneminder/tools +usr/share/zoneminder/views diff --git a/distros/ubuntu1410/zoneminder-ui-base.links b/distros/ubuntu1410/zoneminder-ui-base.links new file mode 100644 index 000000000..b00a147d6 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-ui-base.links @@ -0,0 +1 @@ +usr/lib/cgi-bin usr/share/zoneminder/cgi-bin diff --git a/distros/ubuntu1410/zoneminder-ui-base.postinst b/distros/ubuntu1410/zoneminder-ui-base.postinst new file mode 100644 index 000000000..a5bce3c98 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-ui-base.postinst @@ -0,0 +1,48 @@ +#! /bin/sh +# postinst maintainer script for zoneminder-ui-base package + +set -e + +# Source the debconf stuff +. /usr/share/debconf/confmodule + +apache_install() { + + mkdir -p /etc/apache2/conf-available + ln -sf ../../zm/apache.conf /etc/apache2/conf-available/zoneminder.conf + + COMMON_STATE=$(dpkg-query -f '${Status}' -W 'apache2.2-common' 2>/dev/null | awk '{print $3}' || true) + + if [ -e /usr/share/apache2/apache2-maintscript-helper ] ; then + . /usr/share/apache2/apache2-maintscript-helper + apache2_invoke enconf zoneminder + elif [ "$COMMON_STATE" = "installed" ] || [ "$COMMON_STATE" = "unpacked" ] ; then + [ -d /etc/apache2/conf.d/ ] && [ ! -L /etc/apache2/conf.d/zoneminder.conf ] && ln -s ../conf-available/zoneminder.conf /etc/apache2/conf.d/zoneminder.conf + fi + + # Enable CGI script module in apache (not enabled by default on jessie) + a2enmod cgi >/dev/null 2>&1 + + # Reload the web server + deb-systemd-invoke reload apache2.service || true +} + +# Do this when the package is installed, upgraded or reconfigured +if [ "$1" = "configure" ] || [ "$1" = "reconfigure" ]; then + + # Configure the web server + db_get zoneminder/webserver + webservers="$RET" + + for webserver in $webservers; do + webserver=${webserver%,} + # Currently we only support apache2 + if [ "$webserver" = "apache2" ] ; then + apache_install $1 + fi + done +fi + +#DEBHELPER# + +exit 0 diff --git a/distros/ubuntu1410/zoneminder-ui-base.postrm b/distros/ubuntu1410/zoneminder-ui-base.postrm new file mode 100644 index 000000000..441bb5218 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-ui-base.postrm @@ -0,0 +1,41 @@ +#! /bin/sh +# postrm maintainer script for zoneminder-ui-base package + +set -e + +# Source the debconf stuff +if [ -f /usr/share/debconf/confmodule ]; then + . /usr/share/debconf/confmodule +fi + +apache_remove() { + COMMON_STATE=$(dpkg-query -f '${Status}' -W 'apache2.2-common' 2>/dev/null | awk '{print $3}' || true) + if [ -e /usr/share/apache2/apache2-maintscript-helper ] ; then + . /usr/share/apache2/apache2-maintscript-helper + apache2_invoke disconf zoneminder + elif [ "$COMMON_STATE" = "installed" ] || [ "$COMMON_STATE" = "unpacked" ] ; then + rm -f /etc/apache2/conf.d/zoneminder.conf + fi + rm -f /etc/apache2/conf-available/zoneminder.conf + # Reload the web server + deb-systemd-invoke reload apache2.service || true +} + +if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then + # Deconfigure the web server + db_get zoneminder/webserver + for webserver in $RET; do + webserver=${webserver%,} + # Currently we only support apache2 + if [ "$webserver" = "apache2" ] ; then + apache_remove $1 + fi + done +fi + +#DEBHELPER# + +# postrm rm may freeze without that +db_stop + +exit 0 diff --git a/distros/ubuntu1410/zoneminder-ui-base.templates b/distros/ubuntu1410/zoneminder-ui-base.templates new file mode 100644 index 000000000..31e70277f --- /dev/null +++ b/distros/ubuntu1410/zoneminder-ui-base.templates @@ -0,0 +1,7 @@ +Template: zoneminder/webserver +Type: multiselect +Choices: apache2 +Default: apache2 +_Description: Web server to reconfigure automatically: + Please choose the web server that should be automatically configured for + ZoneMinder's web portal access. diff --git a/distros/ubuntu1410/zoneminder-ui-classic.install b/distros/ubuntu1410/zoneminder-ui-classic.install new file mode 100644 index 000000000..9532d9dc9 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-ui-classic.install @@ -0,0 +1 @@ +usr/share/zoneminder/skins/classic diff --git a/distros/ubuntu1410/zoneminder-ui-mobile.install b/distros/ubuntu1410/zoneminder-ui-mobile.install new file mode 100644 index 000000000..464bb74eb --- /dev/null +++ b/distros/ubuntu1410/zoneminder-ui-mobile.install @@ -0,0 +1 @@ +usr/share/zoneminder/skins/mobile diff --git a/distros/ubuntu1410/zoneminder-ui-xml.install b/distros/ubuntu1410/zoneminder-ui-xml.install new file mode 100644 index 000000000..6617707f8 --- /dev/null +++ b/distros/ubuntu1410/zoneminder-ui-xml.install @@ -0,0 +1 @@ +usr/share/zoneminder/skins/xml diff --git a/distros/ubuntu1504_cmake_split_packages/apache.conf b/distros/ubuntu1504_cmake_split_packages/apache.conf index 292581e78..59efc6248 100644 --- a/distros/ubuntu1504_cmake_split_packages/apache.conf +++ b/distros/ubuntu1504_cmake_split_packages/apache.conf @@ -8,7 +8,7 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" Alias /zm /usr/share/zoneminder/www - Options -Indexes +ollowSymLinks + Options -Indexes +FollowSymLinks DirectoryIndex index.php diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control index 9e2ec96e7..2c4fd3b95 100644 --- a/distros/ubuntu1604/control +++ b/distros/ubuntu1604/control @@ -5,11 +5,10 @@ Maintainer: Dmitry Smirnov Uploaders: Vagrant Cascadian Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apache2-dev, dh-linktree ,cmake - ,libavdevice-dev (>= 6:10~) - ,libavcodec-dev (>= 6:10~) - ,libavformat-dev (>= 6:10~) - ,libavutil-dev (>= 6:10~) - ,libswscale-dev (>= 6:10~) + ,libx264-dev, libmp4v2-dev + ,libavcodec-dev, libavformat-dev, libswscale-dev + ,libavutil-dev, libavdevice-dev + ,libboost-dev ,libbz2-dev ,libgcrypt-dev ,libcurl4-gnutls-dev @@ -25,7 +24,7 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa ,libphp-serialization-perl ,libsys-mmap-perl [!hurd-any] ,libwww-perl - ,libdata-uuid-perl + ,libdata-uuid-perl # Unbundled (dh_linktree): ,libjs-jquery ,libjs-mootools @@ -38,11 +37,10 @@ Package: zoneminder Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,javascript-common + ,libmp4v2-2, libx264-142|libx264-148, libswscale-ffmpeg3|libswscale4|libswscale3 ,ffmpeg | libav-tools - ,libdate-manip-perl + ,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl ,libdbd-mysql-perl - ,libmime-lite-perl - ,libmime-tools-perl ,libphp-serialization-perl ,libmodule-load-conditional-perl ,libnet-sftp-foreign-perl @@ -60,7 +58,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libio-socket-multicast-perl ,libdigest-sha-perl ,libsys-cpu-perl, libsys-meminfo-perl - ,libdata-uuid-perl + ,libdata-uuid-perl ,mysql-client | virtual-mysql-client ,perl-modules ,php5-mysql | php-mysql, php5-gd | php-gd diff --git a/distros/ubuntu1604/rules b/distros/ubuntu1604/rules index bf8012aa8..c7fb00198 100755 --- a/distros/ubuntu1604/rules +++ b/distros/ubuntu1604/rules @@ -58,8 +58,10 @@ override_dh_auto_install: override_dh_fixperms: dh_fixperms - ## 637685 - chmod -c o-r $(CURDIR)/debian/zoneminder/etc/zm/zm.conf + # + # As requested by the Debian Webapps Policy Manual ยง3.2.1 + chown root:www-data $(CURDIR)/debian/zoneminder/etc/zm/zm.conf + chmod 640 $(CURDIR)/debian/zoneminder/etc/zm/zm.conf override_dh_installinit: dh_installinit --no-start diff --git a/distros/ubuntu1604/source/format b/distros/ubuntu1604/source/format index 89ae9db8f..d3827e75a 100644 --- a/distros/ubuntu1604/source/format +++ b/distros/ubuntu1604/source/format @@ -1 +1 @@ -3.0 (native) +1.0 diff --git a/distros/ubuntu1604/watch b/distros/ubuntu1604/watch deleted file mode 100644 index 7ee690edb..000000000 --- a/distros/ubuntu1604/watch +++ /dev/null @@ -1,7 +0,0 @@ -version=3 - -opts=\ -repacksuffix=+dfsg,\ -dversionmangle=s{\+dfsg\d*}{},\ - https://github.com/ZoneMinder/ZoneMinder/releases \ - .*/ZoneMinder/archive/v(.*).tar.gz diff --git a/distros/ubuntu1604/zoneminder.links b/distros/ubuntu1604/zoneminder.links index 4299f392a..14a8aee91 100644 --- a/distros/ubuntu1604/zoneminder.links +++ b/distros/ubuntu1604/zoneminder.links @@ -1,3 +1,4 @@ /var/cache/zoneminder/events /usr/share/zoneminder/www/events /var/cache/zoneminder/images /usr/share/zoneminder/www/images /var/cache/zoneminder/temp /usr/share/zoneminder/www/temp +/var/tmp /usr/share/zoneminder/www/api/app/tmp diff --git a/distros/ubuntu1604/zoneminder.logrotate b/distros/ubuntu1604/zoneminder.logrotate index 18fc6b0a0..59238b7fe 100644 --- a/distros/ubuntu1604/zoneminder.logrotate +++ b/distros/ubuntu1604/zoneminder.logrotate @@ -2,6 +2,8 @@ missingok notifempty sharedscripts + delaycompress + compress postrotate /usr/bin/zmpkg.pl logrot >>/dev/null 2>&1 || : endscript diff --git a/distros/ubuntu1604/zoneminder.postinst b/distros/ubuntu1604/zoneminder.postinst index 05f0c0448..a8f93c4d0 100644 --- a/distros/ubuntu1604/zoneminder.postinst +++ b/distros/ubuntu1604/zoneminder.postinst @@ -12,6 +12,10 @@ if [ "$1" = "configure" ]; then if [ -z "$2" ]; then chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/* fi + if [ ! -e "/etc/apache2/mods-enabled/cgi.load" ]; then + echo "The cgi module is not enabled in apache2. I am enabling it using a2enmod cgi." + a2enmod cgi + fi # Do this every time the package is installed or upgraded diff --git a/distros/ubuntu1604/zoneminder.tmpfile b/distros/ubuntu1604/zoneminder.tmpfile index d307c6640..a7333ec19 100644 --- a/distros/ubuntu1604/zoneminder.tmpfile +++ b/distros/ubuntu1604/zoneminder.tmpfile @@ -1,2 +1,6 @@ -d /var/run/zm 0755 www-data www-data -d /tmp/zm 0755 www-data www-data +d /var/run/zm 0755 www-data www-data +d /tmp/zm 0755 www-data www-data +d /var/tmp/zm 0755 www-data www-data +d /var/tmp/cache 0755 www-data www-data +d /var/tmp/cache/models 0755 www-data www-data +d /var/tmp/cache/persistent 0755 www-data www-data diff --git a/docs/userguide/definezone.rst b/docs/userguide/definezone.rst index 24a6bb8b3..cf33f44c9 100644 --- a/docs/userguide/definezone.rst +++ b/docs/userguide/definezone.rst @@ -40,6 +40,8 @@ Type Preset The preset chooser sets sensible default values based on computational needs (fast v. best) and sensitivity (low, medium, high.) It is not required that you select a preset, and you can alter any of the parameters after choosing a preset. For a small number of monitors with ZoneMinder running on modern equipment, Best, high sensitivity can be chosen as a good starting point. + It is important to understand that the available presets are intended merely as a starting point. Since every camera's view is unique, they are not guaranteed to work properly in every case. Presets tend to work acceptably for indoor cameras, where the objects of interest are relatively close and there typically are few or no unwanted objects moving within the cameras view. Presets, on the other hand, tend to not work acceptably for outdoor cameras, where the field of view is typically much wider, objects of interest are farther away, and changing weather patterns can cause false triggers. For outdoor cameras in particular, you will almost certainly have to tune your motion detection zone to get desired results. Please refer to `this guide `__ to learn how to do this. + Units * Pixels - Selecting this option will allow many of the following values to be entered (or viewed) in units of pixels. * Percentage - Selecting this option will allow may of the following values to be entered (or viewed) as a percentage. The sense of the percentage values refers to the area of the zone and not the image as a whole. This makes trying to work out necessary sizes rather easier. diff --git a/docs/userguide/images/README.TXT b/docs/userguide/images/README.TXT new file mode 100644 index 000000000..21f85c8c3 --- /dev/null +++ b/docs/userguide/images/README.TXT @@ -0,0 +1,2 @@ +The XML images present in this folder have been drawn using http://draw.io +To edit images, simple go to draw.io and load the .xml files diff --git a/docs/userguide/images/zm-system-overview.jpg b/docs/userguide/images/zm-system-overview.jpg index 1624e44c6..de02dcf9e 100644 Binary files a/docs/userguide/images/zm-system-overview.jpg and b/docs/userguide/images/zm-system-overview.jpg differ diff --git a/docs/userguide/images/zm-system-overview.xml b/docs/userguide/images/zm-system-overview.xml index d0d55d5d0..e4314666a 100644 --- a/docs/userguide/images/zm-system-overview.xml +++ b/docs/userguide/images/zm-system-overview.xml @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/misc/logcheck b/misc/logcheck index 28940a6cc..e59948e86 100644 --- a/misc/logcheck +++ b/misc/logcheck @@ -27,3 +27,4 @@ zmfilter\[[[:digit:]]+\]: INF \[Deleting event [[:digit:]]+\]$ zmtelemetry\[[[:digit:]]+\]: INF \[Telemetry data uploaded successfully.\]$ zmtelemetry\[[[:digit:]]+\]: INF \[Sending data to ZoneMinder Telemetry server.\]$ zmtelemetry\[[[:digit:]]+\]: INF \[Collec?ting data to send to ZoneMinder Telemetry server.\]$ +web_php\[[[:digit:]]+\]: INF \[Login successful for user "[[:alnum:]]+"\]$ diff --git a/misc/logrotate.conf.in b/misc/logrotate.conf.in index 664b206c3..7c3810f02 100644 --- a/misc/logrotate.conf.in +++ b/misc/logrotate.conf.in @@ -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 diff --git a/misc/zoneminder.service.in b/misc/zoneminder.service.in index d15c479c2..d1cfb36a0 100644 --- a/misc/zoneminder.service.in +++ b/misc/zoneminder.service.in @@ -12,7 +12,7 @@ Type=forking ExecStart=@BINDIR@/zmpkg.pl start ExecReload=@BINDIR@/zmpkg.pl restart ExecStop=@BINDIR@/zmpkg.pl stop -PIDFile="@ZM_RUNDIR@/zm.pid" +PIDFile=@ZM_RUNDIR@/zm.pid Environment=TZ=:/etc/localtime [Install] diff --git a/onvif/modules/lib/WSDiscovery/TransportUDP.pm b/onvif/modules/lib/WSDiscovery/TransportUDP.pm index 5bbf05671..33308db13 100644 --- a/onvif/modules/lib/WSDiscovery/TransportUDP.pm +++ b/onvif/modules/lib/WSDiscovery/TransportUDP.pm @@ -120,8 +120,8 @@ sub send_receive { my ($address,$port) = ($endpoint =~ /([^:\/]+):([0-9]+)/); -# warn "address = ${address}"; -# warn "port = ${port}"; + #warn "address = ${address}"; + #warn "port = ${port}"; $self->send_multi($address, $port, $envelope); diff --git a/scripts/Makefile.am b/scripts/Makefile.am new file mode 100644 index 000000000..2657b8653 --- /dev/null +++ b/scripts/Makefile.am @@ -0,0 +1,83 @@ +AUTOMAKE_OPTIONS = gnu + +# Ack! Nasty hack to get modules Makefile regenerated if wiped with make clean +all-local: ZoneMinder/Makefile + +ZoneMinder/Makefile: ZoneMinder/Makefile.PL + ( cd ZoneMinder; perl Makefile.PL ) + +bin_SCRIPTS = \ + zmdc.pl \ + zmaudit.pl \ + zmfilter.pl \ + zmtrigger.pl \ + zmx10.pl \ + zmwatch.pl \ + zmpkg.pl \ + zmupdate.pl \ + zmvideo.pl \ + zmcontrol.pl \ + zmtrack.pl \ + zmcamtool.pl \ + zmsystemctl.pl \ + zmtelemetry.pl + +SUBDIRS = \ + . \ + ZoneMinder + +EXTRA_DIST = \ + zmdc.pl.in \ + zmaudit.pl.in \ + zmfilter.pl.in \ + zmtrigger.pl.in \ + zmx10.pl.in \ + zmwatch.pl.in \ + zmpkg.pl.in \ + zmupdate.pl.in \ + zmvideo.pl.in \ + zmcontrol.pl.in \ + zmtrack.pl.in \ + zmcamtool.pl.in \ + zmsystemctl.pl.in \ + zmtelemtry.pl.in \ + ZoneMinder/Makefile.PL \ + ZoneMinder/README \ + ZoneMinder/Changes \ + ZoneMinder/META.yml \ + ZoneMinder/MANIFEST \ + ZoneMinder/t/ZoneMinder.t \ + ZoneMinder/lib/ZoneMinder.pm \ + ZoneMinder/lib/ZoneMinder/Base.pm.in \ + ZoneMinder/lib/ZoneMinder/Config.pm.in \ + ZoneMinder/lib/ZoneMinder/Event.pm \ + ZoneMinder/lib/ZoneMinder/Filter.pm \ + ZoneMinder/lib/ZoneMinder/Monitor.pm \ + ZoneMinder/lib/ZoneMinder/Object.pm \ + ZoneMinder/lib/ZoneMinder/Server.pm \ + ZoneMinder/lib/ZoneMinder/Storage.pm \ + ZoneMinder/lib/ZoneMinder/Logger.pm \ + ZoneMinder/lib/ZoneMinder/General.pm \ + ZoneMinder/lib/ZoneMinder/Database.pm \ + ZoneMinder/lib/ZoneMinder/Memory.pm.in \ + ZoneMinder/lib/ZoneMinder/Memory/Shared.pm \ + ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm \ + ZoneMinder/lib/ZoneMinder/ConfigAdmin.pm \ + ZoneMinder/lib/ZoneMinder/ConfigData.pm.in \ + ZoneMinder/lib/ZoneMinder/Control.pm \ + ZoneMinder/lib/ZoneMinder/Control \ + ZoneMinder/lib/ZoneMinder/Trigger/Channel.pm \ + ZoneMinder/lib/ZoneMinder/Trigger/Channel/Handle.pm \ + ZoneMinder/lib/ZoneMinder/Trigger/Channel/Spawning.pm \ + ZoneMinder/lib/ZoneMinder/Trigger/Channel/Inet.pm \ + ZoneMinder/lib/ZoneMinder/Trigger/Channel/Unix.pm \ + ZoneMinder/lib/ZoneMinder/Trigger/Channel/File.pm \ + ZoneMinder/lib/ZoneMinder/Trigger/Channel/Serial.pm \ + ZoneMinder/lib/ZoneMinder/Trigger/Connection.pm \ + ZoneMinder/lib/ZoneMinder/Trigger/Connection/Example.pm \ + zm.in \ + zmdbbackup.in \ + zmdbrestore.in \ + zmeventdump.in \ + zmlogrotate.conf.in + diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index 486468cc8..2ba12dfa2 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -129,7 +129,7 @@ BEGIN { } # end BEGIN sub loadConfigFromDB { - print( "Loading config from DB\n" ); + print( "Loading config from DB" ); my $dbh = ZoneMinder::Database::zmDbConnect(); if ( !$dbh ) { print( "Error: unable to load options from database: $DBI::errstr\n" ); @@ -160,11 +160,12 @@ sub loadConfigFromDB { $option_count++;; } $sth->finish(); + print( " $option_count entries\n" ); return( $option_count ); } # end sub loadConfigFromDB sub saveConfigToDB { - print( "Saving config to DB\n" ); + print( "Saving config to DB " . @options . " entries\n" ); my $dbh = ZoneMinder::Database::zmDbConnect(); if ( !$dbh ) { print( "Error: unable to save options to database: $DBI::errstr\n" ); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 3f80748db..d6ff8b2d9 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -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 = ( - data => [ qw( + 'data' => [ qw( %types @options %options_hash @@ -72,152 +72,146 @@ sub INIT { # Types our %types = ( string => { - db_type => "string", - hint => "string", - pattern => qr|^(.+)$|, - format => q( $1 ) - }, - alphanum => { - db_type => "string", - hint => "alphanumeric", - pattern => qr|^([a-zA-Z0-9-_]+)$|, - format => q( $1 ) - }, - text => { - db_type => "text", - hint => "free text", - pattern => qr|^(.+)$|, - format => q( $1 ) - }, - boolean => { - db_type => "boolean", - hint => "yes|no", - pattern => qr|^([yn])|i, - check => q( $1 ), - format => q( ($1 =~ /^y/) ? "yes" : "no" ) - }, - integer => { - db_type => "integer", - hint => "integer", - pattern => qr|^(\d+)$|, - format => q( $1 ) - }, - decimal => { - db_type => "decimal", - hint => "decimal", - pattern => qr|^(\d+(?:\.\d+)?)$|, - format => q( $1 ) - }, - hexadecimal => { - db_type => "hexadecimal", - hint => "hexadecimal", - pattern => qr|^(?:0x)?([0-9a-f]{1,8})$|, - format => q( "0x".$1 ) - }, - tristate => { - db_type => "string", - hint => "auto|yes|no", - pattern => qr|^([ayn])|i, check=>q( $1 ), - format => q( ($1 =~ /^y/) ? "yes" : ($1 =~ /^n/ ? "no" : "auto" ) ) - }, - abs_path => { - db_type => "string", - hint => "/absolute/path/to/somewhere", - pattern => qr|^((?:/[^/]*)+?)/?$|, - format => q( $1 ) - }, - rel_path => { - db_type => "string", - hint => "relative/path/to/somewhere", - pattern => qr|^((?:[^/].*)?)/?$|, - format => q( $1 ) - }, - directory => { - db_type => "string", - hint => "directory", - pattern => qr|^([a-zA-Z0-9-_.]+)$|, - format => q( $1 ) - }, - file => { - db_type => "string", - hint => "filename", - pattern => qr|^([a-zA-Z0-9-_.]+)$|, - format => q( $1 ) - }, - hostname => { - db_type => "string", - hint => "host.your.domain", - pattern => qr|^([a-zA-Z0-9_.-]+)$|, - format => q( $1 ) - }, - url => { - db_type => "string", - hint => "http://host.your.domain/", - pattern => qr|^(?:http://)?(.+)$|, - format => q( "http://".$1 ) - }, - email => { - db_type => "string", - hint => "your.name\@your.domain", - pattern => qr|^([a-zA-Z0-9_.-]+)\@([a-zA-Z0-9_.-]+)$|, - format => q( $1\@$2 ) - }, - ); - -sub qqq { ## Un-pad paragraph of text. - local $_ = shift; - s{\n?^\s*}{ }mg; - return $_; -} + db_type => 'string', + hint => 'string', + pattern => qr|^(.+)$|, + format => q( $1 ) + }, + alphanum => { + db_type => 'string', + hint => 'alphanumeric', + pattern => qr|^([a-zA-Z0-9-_]+)$|, + format => q( $1 ) + }, + text => { + db_type => 'text', + hint => 'free text', + pattern => qr|^(.+)$|, + format => q( $1 ) + }, + boolean => { + db_type => 'boolean', + hint => 'yes|no', + pattern => qr|^([yn])|i, + check => q( $1 ), + format => q( ($1 =~ /^y/) ? 'yes' : 'no') + }, + integer => { + db_type => 'integer', + hint => 'integer', + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + decimal => { + db_type => 'decimal', + hint => 'decimal', + pattern => qr|^(\d+(?:\.\d+)?)$|, + format => q( $1 ) + }, + hexadecimal => { + db_type => 'hexadecimal', + hint => 'hexadecimal', + pattern => qr|^(?:0x)?([0-9a-f]{1,8})$|, + format => q( '0x'.$1 ) + }, + tristate => { + db_type => 'string', + hint => 'auto|yes|no', + pattern => qr|^([ayn])|i, check=> q( $1 ), + format => q( ($1 =~ /^y/) ? 'yes' : ($1 =~ /^n/ ? 'no' : 'auto' )) + }, + abs_path => { + db_type => 'string', + hint => '/absolute/path/to/somewhere', + pattern => qr|^((?:/[^/]*)+?)/?$|, + format => q( $1 ) + }, + rel_path => { + db_type => 'string', + hint => 'relative/path/to/somewhere', + pattern => qr|^((?:[^/].*)?)/?$|, + format => q( $1 ) + }, + directory => { + db_type => 'string', + hint => 'directory', + pattern => qr|^([a-zA-Z0-9-_.]+)$|, + format => q( $1 ) + }, + file => { + db_type => 'string', + hint => 'filename', + pattern => qr|^([a-zA-Z0-9-_.]+)$|, + format => q( $1 ) + }, + hostname => { + db_type => 'string', + hint => 'host.your.domain', + pattern => qr|^([a-zA-Z0-9_.-]+)$|, + format => q( $1 ) + }, + url => { + db_type => 'string', + hint => 'http://host.your.domain/', + pattern => qr|^(?:http://)?(.+)$|, + format => q( 'http://'.$1 ) + }, + email => { + db_type => 'string', + hint => 'your.name\@your.domain', + pattern => qr|^([a-zA-Z0-9_.-]+)\@([a-zA-Z0-9_.-]+)$|, + format => q( $1\@$2 ) + }, +); our @options = ( { - name => "ZM_SKIN_DEFAULT", - default => "classic", - description => "Default skin used by web interface", + name => 'ZM_SKIN_DEFAULT', + default => 'classic', + description => 'Default skin used by web interface', - help => qqq(" + help => q` ZoneMinder allows the use of many different web interfaces. This option allows you to set the default skin used by the website. Users can change their skin later, this merely sets the default. - "), + `, type => $types{string}, - category => "system", + category => 'system', }, { - name => "ZM_CSS_DEFAULT", - default => "classic", - description => "Default set of css files used by web interface", - help => qqq(" + name => 'ZM_CSS_DEFAULT', + default => 'classic', + description => 'Default set of css files used by web interface', + help => q` ZoneMinder allows the use of many different web interfaces, and some skins allow the use of different set of CSS files to control the appearance. This option allows you to set the default set of css files used by the website. Users can change their css later, this merely sets the default. - "), + `, type => $types{string}, - category => "system", + category => 'system', }, { - name => "ZM_LANG_DEFAULT", - default => "en_gb", - description => "Default language used by web interface", - help => qqq(" + name => 'ZM_LANG_DEFAULT', + default => 'en_gb', + description => 'Default language used by web interface', + help => q` ZoneMinder allows the web interface to use languages other than English if the appropriate language file has been created and is present. This option allows you to change the default language that is used from the shipped language, British English, to another language - "), + `, type => $types{string}, - category => "system", + category => 'system', }, { - name => "ZM_OPT_USE_AUTH", - default => "no", - description => "Authenticate user logins to ZoneMinder", - help => qqq(" + name => 'ZM_OPT_USE_AUTH', + default => 'no', + description => 'Authenticate user logins to ZoneMinder', + help => q` ZoneMinder can run in two modes. The simplest is an entirely unauthenticated mode where anyone can access ZoneMinder and perform all tasks. This is most suitable for installations @@ -225,15 +219,15 @@ our @options = ( mode enables user accounts with varying sets of permissions. Users must login or authenticate to access ZoneMinder and are limited by their defined permissions. - "), + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_AUTH_TYPE", - default => "builtin", - description => "What is used to authenticate ZoneMinder users", - help => qqq(" + name => 'ZM_AUTH_TYPE', + default => 'builtin', + description => 'What is used to authenticate ZoneMinder users', + help => q` ZoneMinder can use two methods to authenticate users when running in authenticated mode. The first is a builtin method where ZoneMinder provides facilities for users to log in and @@ -243,21 +237,21 @@ our @options = ( 'remote' user via http. In this case ZoneMinder would use the supplied user without additional authentication provided such a user is configured ion ZoneMinder. - "), - requires => [ { name=>"ZM_OPT_USE_AUTH", value=>"yes" } ], + `, + requires => [ { name=> 'ZM_OPT_USE_AUTH', value=>'yes' } ], type => { - db_type => "string", - hint => "builtin|remote", + db_type => 'string', + hint => 'builtin|remote', pattern => qr|^([br])|i, - format => q( $1 =~ /^b/ ? "builtin" : "remote" ) + format => q( $1 =~ /^b/ ? 'builtin' : 'remote' ) }, - category => "system", + category => 'system', }, { - name => "ZM_AUTH_RELAY", - default => "hashed", - description => "Method used to relay authentication information", - help => qqq(" + name => 'ZM_AUTH_RELAY', + default => 'hashed', + description => 'Method used to relay authentication information', + help => q` When ZoneMinder is running in authenticated mode it can pass user details between the web pages and the back end processes. There are two methods for doing this. This first is to use a @@ -268,39 +262,39 @@ our @options = ( your system or you have a completely isolated system with no external access. You can also switch off authentication relaying if your system is isolated in other ways. - "), - requires => [ { name=>"ZM_OPT_USE_AUTH", value=>"yes" } ], + `, + requires => [ { name=> 'ZM_OPT_USE_AUTH', value=>'yes' } ], type => { - db_type => "string", - hint => "hashed|plain|none", + db_type => 'string', + hint => 'hashed|plain|none', pattern => qr|^([hpn])|i, - format => q( ($1 =~ /^h/) ? "hashed" : ($1 =~ /^p/ ? "plain" : "none" ) ) + format => q( ($1 =~ /^h/) ? 'hashed' : ($1 =~ /^p/ ? 'plain' : 'none' ) ) }, - category => "system", + category => 'system', }, { - name => "ZM_AUTH_HASH_SECRET", - default => "...Change me to something unique...", - description => "Secret for encoding hashed authentication information", - help => qqq(" + name => 'ZM_AUTH_HASH_SECRET', + default => '...Change me to something unique...', + description => 'Secret for encoding hashed authentication information', + help => q` When ZoneMinder is running in hashed authenticated mode it is necessary to generate hashed strings containing encrypted sensitive information such as usernames and password. Although these string are reasonably secure the addition of a random secret increases security substantially. - "), + `, requires => [ - { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, - { name=>"ZM_AUTH_RELAY", value=>"hashed" } + { name=> 'ZM_OPT_USE_AUTH', value=>'yes' }, + { name=> 'ZM_AUTH_RELAY', value=>'hashed' } ], type => $types{string}, - category => "system", + category => 'system', }, { - name => "ZM_AUTH_HASH_IPS", - default => "yes", - description => "Include IP addresses in the authentication hash", - help => qqq(" + name => 'ZM_AUTH_HASH_IPS', + default => 'yes', + description => 'Include IP addresses in the authentication hash', + help => q` When ZoneMinder is running in hashed authenticated mode it can optionally include the requesting IP address in the resultant hash. This adds an extra level of security as only requests @@ -311,19 +305,34 @@ our @options = ( whether IP addresses are included in the authentication hash on your system. If you experience intermitent problems with authentication, switching this option off may help. - "), + `, requires => [ - { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, - { name=>"ZM_AUTH_RELAY", value=>"hashed" } + { name=> 'ZM_OPT_USE_AUTH', value=>'yes' }, + { name=> 'ZM_AUTH_RELAY', value=>'hashed' } ], type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_AUTH_HASH_LOGINS", - default => "no", - description => "Allow login by authentication hash", - help => qqq(" + name => 'ZM_AUTH_HASH_TTL', + default => '2', + description => 'The number of hours that an authentication hash is valid for.', + help => q` + The default has traditionally been 2 hours. A new hash will + automatically be regenerated at half this value. + `, + requires => [ + { name=>'ZM_OPT_USE_AUTH', value=>'yes' }, + { name=>'ZM_AUTH_RELAY', value=>'hashed' } + ], + type => $types{integer}, + category => 'system', + }, + { + name => 'ZM_AUTH_HASH_LOGINS', + default => 'no', + description => 'Allow login by authentication hash', + help => q` The normal process for logging into ZoneMinder is via the login screen with username and password. In some circumstances it may be desirable to allow access directly to one or more pages, for @@ -337,19 +346,39 @@ our @options = ( authentication hash itself and ensure it is valid. If you use this option you should ensure that you have modified the ZM_AUTH_HASH_SECRET to something unique to your system. - "), + `, requires => [ - { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, - { name=>"ZM_AUTH_RELAY", value=>"hashed" } + { name=> 'ZM_OPT_USE_AUTH', value=>'yes' }, + { name=> 'ZM_AUTH_RELAY', value=>'hashed' } ], type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_OPT_USE_API", - default => "yes", - description => "Enable ZoneMinder APIs", - help => qqq(" + name => 'ZM_ENABLE_CSRF_MAGIC', + default => 'no', + description => 'Enable csrf-magic library', + help => q` + CSRF stands for Cross-Site Request Forgery which, under specific + circumstances, can allow an attacker to perform any task your + ZoneMinder user account has permission to perform. To accomplish + this, the attacker must write a very specific web page and get + you to navigate to it, while you are logged into the ZoneMinder + web console at the same time. Enabling ZM_ENABLE_CSRF_MAGIC will + help mitigate these kinds of attackes. Be warned this feature + is experimental and may cause problems, particularly with the API. + If you find a false positive and can document how to reproduce it, + then please report it. This feature defaults to OFF currently due to + its experimental nature. + `, + type => $types{boolean}, + category => 'system', + }, + { + name => 'ZM_OPT_USE_API', + default => 'yes', + description => 'Enable ZoneMinder APIs', + help => q` ZoneMinder now features a new API using which 3rd party applications can interact with ZoneMinder data. It is STRONGLY recommended that you enable authentication along @@ -357,16 +386,16 @@ our @options = ( Monitor access details which are configured as JSON objects. Which is why we recommend you enabling authentication, especially if you are exposing your ZM instance on the Internet. - "), + `, type => $types{boolean}, - category => "system", + category => 'system', }, - # PP - Google reCaptcha settings +# PP - Google reCaptcha settings { - name => "ZM_OPT_USE_GOOG_RECAPTCHA", - default => "no", - description => "Add Google reCaptcha to login page", - help => qqq(" + name => 'ZM_OPT_USE_GOOG_RECAPTCHA', + default => 'no', + description => 'Add Google reCaptcha to login page', + help => q` This option allows you to include a google reCaptcha validation at login. This means in addition to providing a valid usernane and password, you will also have to @@ -376,51 +405,48 @@ our @options = ( that enabling this option will break 3rd party clients like zmNinja and zmView as they also need to login to ZoneMinder and they will fail the reCaptcha test. - "), - requires => [ - {name=>"ZM_OPT_USE_AUTH", value=>"yes"} - ], - type => $types{boolean}, - category => "system", + `, + requires => [ + {name=> 'ZM_OPT_USE_AUTH', value=>'yes'} + ], + type => $types{boolean}, + category => 'system', }, - { - name => "ZM_OPT_GOOG_RECAPTCHA_SITEKEY", - default => "...Insert your recaptcha site-key here...", - description => "Your recaptcha site-key", - help => qqq("You need to generate your keys from + name => 'ZM_OPT_GOOG_RECAPTCHA_SITEKEY', + default => '...Insert your recaptcha site-key here...', + description => 'Your recaptcha site-key', + help => q`You need to generate your keys from the Google reCaptcha website. Please refer to https://www.google.com/recaptcha/ for more details. - "), - requires => [ - {name=>"ZM_OPT_USE_GOOG_RECAPTCHA", value=>"yes"} - ], - type => $types{string}, - category => "system", + `, + requires => [ + {name=> 'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'} + ], + type => $types{string}, + category => 'system', }, { - name => "ZM_OPT_GOOG_RECAPTCHA_SECRETKEY", - default => "...Insert your recaptcha secret-key here...", - description => "Your recaptcha secret-key", - help => qqq("You need to generate your keys from + name => 'ZM_OPT_GOOG_RECAPTCHA_SECRETKEY', + default => '...Insert your recaptcha secret-key here...', + description => 'Your recaptcha secret-key', + help => q`You need to generate your keys from the Google reCaptcha website. Please refer to https://www.google.com/recaptcha/ for more details. - "), - requires => [ - {name=>"ZM_OPT_USE_GOOG_RECAPTCHA", value=>"yes"} - ], - type => $types{string}, - category => "system", + `, + requires => [ + {name=> 'ZM_OPT_USE_GOOG_RECAPTCHA', value=>'yes'} + ], + type => $types{string}, + category => 'system', }, - - { - name => "ZM_DIR_EVENTS", - default => "events", - description => "Directory where events are stored", - help => qqq(" + name => 'ZM_DIR_EVENTS', + default => 'events', + description => 'Directory where events are stored', + help => q` This is the path to the events directory where all the event images and other miscellaneous files are stored. CAUTION: The directory you specify here cannot be outside the web root. This @@ -429,15 +455,15 @@ our @options = ( share, then you should mount the drive or share directly to the ZoneMinder events folder or follow the instructions in the ZoneMinder Wiki titled Using a dedicated Hard Drive. - "), + `, type => $types{directory}, - category => "paths", + category => 'paths', }, { - name => "ZM_USE_DEEP_STORAGE", - default => "yes", - description => "Use a deep filesystem hierarchy for events", - help => qqq(" + name => 'ZM_USE_DEEP_STORAGE', + default => 'yes', + description => 'Use a deep filesystem hierarchy for events', + help => q` This option is now the default for new ZoneMinder systems and should not be changed. Previous versions of ZoneMinder stored all events for a monitor under one folder. Enabling @@ -447,18 +473,18 @@ our @options = ( than 32k files in a single folder inherent in some filesystems. It is important to note that you cannot simply change this option. You must stop zoneminder, enable USE_DEEP_STORAGE, and - then run \"sudo zmupdate.pl --migrate-events\". FAILURE TO DO + then run 'sudo zmupdate.pl --migrate-events'. FAILURE TO DO SO WILL RESULT IN LOSS OF YOUR DATA! Consult the ZoneMinder WiKi for further details. - "), + `, type => $types{boolean}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_DIR_IMAGES", - default => "images", - description => "Directory where the images that the ZoneMinder client generates are stored", - help => qqq(" + name => 'ZM_DIR_IMAGES', + default => 'images', + description => 'Directory where the images that the ZoneMinder client generates are stored', + help => q` ZoneMinder generates a myriad of images, mostly of which are associated with events. For those that aren't this is where they go. CAUTION: The directory you specify here cannot be @@ -468,28 +494,28 @@ our @options = ( or share directly to the ZoneMinder images folder or follow the instructions in the ZoneMinder Wiki titled Using a dedicated Hard Drive. - "), + `, type => $types{directory}, - category => "paths", + category => 'paths', }, { - name => "ZM_DIR_SOUNDS", - default => "sounds", - description => "Directory to the sounds that the ZoneMinder client can use", - help => qqq(" + name => 'ZM_DIR_SOUNDS', + default => 'sounds', + description => 'Directory to the sounds that the ZoneMinder client can use', + help => q` ZoneMinder can optionally play a sound file when an alarm is detected. This indicates where to look for this file. CAUTION: The directory you specify here cannot be outside the web root. Most users should never change this value. - "), + `, type => $types{directory}, - category => "paths", + category => 'paths', }, { - name => "ZM_PATH_ZMS", - default => "/cgi-bin/nph-zms", - description => "Web path to zms streaming server", - help => qqq(" + name => 'ZM_PATH_ZMS', + default => '/cgi-bin/nph-zms', + description => 'Web path to zms streaming server', + help => q` The ZoneMinder streaming server is required to send streamed images to your browser. It will be installed into the cgi-bin path given at configuration time. This option determines what @@ -498,15 +524,15 @@ our @options = ( parser-header mode however if you experience problems with streaming you can change this to non-parsed-header (nph) mode by changing 'zms' to 'nph-zms'. - "), + `, type => $types{rel_path}, - category => "paths", + category => 'paths', }, { - name => "ZM_COLOUR_JPEG_FILES", - default => "no", - description => "Colourise greyscale JPEG files", - help => qqq(" + name => 'ZM_COLOUR_JPEG_FILES', + default => 'no', + description => 'Colourise greyscale JPEG files', + help => q` Cameras that capture in greyscale can write their captured images to jpeg files with a corresponding greyscale colour space. This saves a small amount of disk space over colour @@ -514,15 +540,15 @@ our @options = ( with this colour space or have to convert it beforehand. Setting this option to yes uses up a little more space but makes creation of MPEG files much faster. - "), + `, type => $types{boolean}, - category => "images", + category => 'images', }, { - name => "ZM_ADD_JPEG_COMMENTS", - default => "no", - description => "Add jpeg timestamp annotations as file header comments", - help => qqq(" + name => 'ZM_ADD_JPEG_COMMENTS', + default => 'no', + description => 'Add jpeg timestamp annotations as file header comments', + help => q` JPEG files may have a number of extra fields added to the file header. The comment field may have any kind of text added. This options allows you to have the same text that is used to @@ -530,15 +556,15 @@ our @options = ( comment. If you archive event images to other locations this may help you locate images for particular events or times if you use software that can read comment headers. - "), + `, type => $types{boolean}, - category => "images", + category => 'images', }, { - name => "ZM_JPEG_FILE_QUALITY", - default => "70", - description => "Set the JPEG quality setting for the saved event files (1-100)", - help => qqq(" + name => 'ZM_JPEG_FILE_QUALITY', + default => '70', + description => 'Set the JPEG quality setting for the saved event files (1-100)', + help => q` When ZoneMinder detects an event it will save the images associated with that event to files. These files are in the JPEG format and can be viewed or streamed later. This option @@ -551,15 +577,15 @@ our @options = ( except if the capture image has caused an alarm and the alarm file quality option is set at a higher value when that is used instead. - "), + `, type => $types{integer}, - category => "images", + category => 'images', }, { - name => "ZM_JPEG_ALARM_FILE_QUALITY", - default => "0", - description => "Set the JPEG quality setting for the saved event files during an alarm (1-100)", - help => qqq(" + name => 'ZM_JPEG_ALARM_FILE_QUALITY', + default => '0', + description => 'Set the JPEG quality setting for the saved event files during an alarm (1-100)', + help => q` This value is equivalent to the regular jpeg file quality setting above except that it only applies to images saved while in an alarm state and then only if this value is set to a @@ -568,16 +594,16 @@ our @options = ( default of 0 effectively means to use the regular file quality setting for all saved images. This is to prevent acccidentally saving important images at a worse quality setting. - "), + `, type => $types{integer}, - category => "images", + category => 'images', }, - # Deprecated, now stream quality +# Deprecated, now stream quality { - name => "ZM_JPEG_IMAGE_QUALITY", - default => "70", - description => "Set the JPEG quality setting for the streamed 'live' images (1-100)", - help => qqq(" + name => 'ZM_JPEG_IMAGE_QUALITY', + default => '70', + description => q`Set the JPEG quality setting for the streamed 'live' images (1-100)`, + help => q` When viewing a 'live' stream for a monitor ZoneMinder will grab an image from the buffer and encode it into JPEG format before sending it. This option specifies what image quality should be @@ -588,15 +614,15 @@ our @options = ( does not apply when viewing events or still images as these are usually just read from disk and so will be encoded at the quality specified by the previous options. - "), + `, type => $types{integer}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_JPEG_STREAM_QUALITY", - default => "70", - description => "Set the JPEG quality setting for the streamed 'live' images (1-100)", - help => qqq(" + name => 'ZM_JPEG_STREAM_QUALITY', + default => '70', + description => q`Set the JPEG quality setting for the streamed 'live' images (1-100)`, + help => q` When viewing a 'live' stream for a monitor ZoneMinder will grab an image from the buffer and encode it into JPEG format before sending it. This option specifies what image quality should be @@ -607,15 +633,15 @@ our @options = ( does not apply when viewing events or still images as these are usually just read from disk and so will be encoded at the quality specified by the previous options. - "), + `, type => $types{integer}, - category => "images", + category => 'images', }, { - name => "ZM_MPEG_TIMED_FRAMES", - default => "yes", - description => "Tag video frames with a timestamp for more realistic streaming", - help => qqq(" + name => 'ZM_MPEG_TIMED_FRAMES', + default => 'yes', + description => 'Tag video frames with a timestamp for more realistic streaming', + help => q` When using streamed MPEG based video, either for live monitor streams or events, ZoneMinder can send the streams in two ways. If this option is selected then the timestamp for each frame, @@ -626,15 +652,15 @@ our @options = ( calculated and that is used to schedule frames instead. This option should be selected unless you encounter problems with your preferred streaming method. - "), + `, type => $types{boolean}, - category => "images", + category => 'images', }, { - name => "ZM_MPEG_LIVE_FORMAT", - default => "swf", - description => "What format 'live' video streams are played in", - help => qqq(" + name => 'ZM_MPEG_LIVE_FORMAT', + default => 'swf', + description => q`What format 'live' video streams are played in`, + help => q` When using MPEG mode ZoneMinder can output live video. However what formats are handled by the browser varies greatly between machines. This option allows you to specify a video format @@ -645,15 +671,15 @@ our @options = ( what, if anything, works on a Linux platform. If you find out please let me know! If this option is left blank then live streams will revert to being in motion jpeg format - "), + `, type => $types{string}, - category => "images", + category => 'images', }, { - name => "ZM_MPEG_REPLAY_FORMAT", - default => "swf", - description => "What format 'replay' video streams are played in", - help => qqq(" + name => 'ZM_MPEG_REPLAY_FORMAT', + default => 'swf', + description => q`What format 'replay' video streams are played in`, + help => q` When using MPEG mode ZoneMinder can replay events in encoded video format. However what formats are handled by the browser varies greatly between machines. This option allows you to @@ -664,27 +690,27 @@ our @options = ( or 'avi' etc should work under Linux. If you know any more then please let me know! If this option is left blank then live streams will revert to being in motion jpeg format - "), + `, type => $types{string}, - category => "images", + category => 'images', }, { - name => "ZM_RAND_STREAM", - default => "yes", - description => "Add a random string to prevent caching of streams", - help => qqq(" + name => 'ZM_RAND_STREAM', + default => 'yes', + description => 'Add a random string to prevent caching of streams', + help => q` Some browsers can cache the streams used by ZoneMinder. In order to prevent his a harmless random string can be appended to the url to make each invocation of the stream appear unique. - "), + `, type => $types{boolean}, - category => "images", + category => 'images', }, { - name => "ZM_OPT_CAMBOZOLA", - default => "no", - description => "Is the (optional) cambozola java streaming client installed", - help => qqq(" + name => 'ZM_OPT_CAMBOZOLA', + default => 'no', + description => 'Is the (optional) cambozola java streaming client installed', + help => q` Cambozola is a handy low fat cheese flavoured Java applet that ZoneMinder uses to view image streams on browsers such as Internet Explorer that don't natively support this format. If @@ -692,15 +718,15 @@ our @options = ( from http://www.charliemouse.com/code/cambozola/ however if it is not installed still images at a lower refresh rate can still be viewed. - "), + `, type => $types{boolean}, - category => "images", + category => 'images', }, { - name => "ZM_PATH_CAMBOZOLA", - default => "cambozola.jar", - description => "Web path to (optional) cambozola java streaming client", - help => qqq(" + name => 'ZM_PATH_CAMBOZOLA', + default => 'cambozola.jar', + description => 'Web path to (optional) cambozola java streaming client', + help => q` Cambozola is a handy low fat cheese flavoured Java applet that ZoneMinder uses to view image streams on browsers such as Internet Explorer that don't natively support this format. If @@ -710,30 +736,30 @@ our @options = ( be viewed. Leave this as 'cambozola.jar' if cambozola is installed in the same directory as the ZoneMinder web client files. - "), - requires => [ { name=>"ZM_OPT_CAMBOZOLA", value=>"yes" } ], + `, + requires => [ { name=> 'ZM_OPT_CAMBOZOLA', value=>'yes' } ], type => $types{rel_path}, - category => "images", + category => 'images', }, { - name => "ZM_RELOAD_CAMBOZOLA", - default => "0", - description => "After how many seconds should Cambozola be reloaded in live view", - help => qqq(" + name => 'ZM_RELOAD_CAMBOZOLA', + default => '0', + description => 'After how many seconds should Cambozola be reloaded in live view', + help => q` Cambozola allows for the viewing of streaming MJPEG however it caches the entire stream into cache space on the computer, setting this to a number > 0 will cause it to automatically reload after that many seconds to avoid filling up a hard drive. - "), + `, type => $types{integer}, - category => "images", + category => 'images', }, { - name => "ZM_TIMESTAMP_ON_CAPTURE", - default => "yes", - description => "Timestamp images as soon as they are captured", - help => qqq(" + name => 'ZM_TIMESTAMP_ON_CAPTURE', + default => 'yes', + description => 'Timestamp images as soon as they are captured', + help => q` ZoneMinder can add a timestamp to images in two ways. The default method, when this option is set, is that each image is timestamped immediately when captured and so the image held in @@ -750,30 +776,30 @@ our @options = ( desirable side effect that the timestamp is always applied at the same resolution so an image that has scaling applied will still have a legible and correctly scaled timestamp. - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_CPU_EXTENSIONS", - default => "yes", - description => "Use advanced CPU extensions to increase performance", - help => qqq(" + name => 'ZM_CPU_EXTENSIONS', + default => 'yes', + description => 'Use advanced CPU extensions to increase performance', + help => q` When advanced processor extensions such as SSE2 or SSSE3 are available, ZoneMinder can use them, which should increase performance and reduce system load. Enabling this option on processors that do not support the advanced processors extensions used by ZoneMinder is harmless and will have no effect. - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_FAST_IMAGE_BLENDS", - default => "yes", - description => "Use a fast algorithm to blend the reference image", - help => qqq(" + name => 'ZM_FAST_IMAGE_BLENDS', + default => 'yes', + description => 'Use a fast algorithm to blend the reference image', + help => q` To detect alarms ZoneMinder needs to blend the captured image with the stored reference image to update it for comparison with the next image. The reference blend percentage specified @@ -786,15 +812,15 @@ our @options = ( Any other blend percentage will be rounded to the nearest possible one. The alternative is to switch this option off and use standard blending instead, which is slower. - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_OPT_ADAPTIVE_SKIP", - default => "yes", - description => "Should frame analysis try and be efficient in skipping frames", - help => qqq(" + name => 'ZM_OPT_ADAPTIVE_SKIP', + default => 'yes', + description => 'Should frame analysis try and be efficient in skipping frames', + help => q` In previous versions of ZoneMinder the analysis daemon would attempt to keep up with the capture daemon by processing the last captured frame on each pass. This would sometimes have the @@ -817,15 +843,15 @@ our @options = ( adaptive algorithm to be overwhelmed and not have time to react to a rapid build up of pending frames and thus for a buffer overrun condition to occur. - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_MAX_SUSPEND_TIME", - default => "30", - description => "Maximum time that a monitor may have motion detection suspended", - help => qqq(" + name => 'ZM_MAX_SUSPEND_TIME', + default => '30', + description => 'Maximum time that a monitor may have motion detection suspended', + help => q` ZoneMinder allows monitors to have motion detection to be suspended, for instance while panning a camera. Ordinarily this relies on the operator resuming motion detection afterwards as @@ -835,29 +861,29 @@ our @options = ( motion detection. This time can be extended by subsequent suspend indications after the first so continuous camera movement will also occur while the monitor is suspended. - "), + `, type => $types{integer}, - category => "config", + category => 'config', }, - # Deprecated, really no longer necessary +# Deprecated, really no longer necessary { - name => "ZM_OPT_REMOTE_CAMERAS", - default => "no", - description => "Are you going to use remote/networked cameras", - help => qqq(" + name => 'ZM_OPT_REMOTE_CAMERAS', + default => 'no', + description => 'Are you going to use remote/networked cameras', + help => q` ZoneMinder can work with both local cameras, ie. those attached physically to your computer and remote or network cameras. If you will be using networked cameras select this option. - "), + `, type => $types{boolean}, - category => "hidden", + category => 'hidden', }, - # Deprecated, now set on a per monitor basis using the Method field +# Deprecated, now set on a per monitor basis using the Method field { - name => "ZM_NETCAM_REGEXPS", - default => "yes", - description => "Use regular expression matching with network cameras", - help => qqq(" + name => 'ZM_NETCAM_REGEXPS', + default => 'yes', + description => 'Use regular expression matching with network cameras', + help => q` Traditionally ZoneMinder has used complex regular regular expressions to handle the multitude of formats that network cameras produce. In versions from 1.21.1 the default is to use @@ -866,64 +892,64 @@ our @options = ( problems you can try the older, but more flexible, regular expression based method by selecting this option. Note, to use this method you must have libpcre installed on your system. - "), - requires => [ { name => "ZM_OPT_REMOTE_CAMERAS", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_REMOTE_CAMERAS', value => 'yes' } ], type => $types{boolean}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_HTTP_VERSION", - default => "1.0", - description => "The version of HTTP that ZoneMinder will use to connect", - help => qqq(" + name => 'ZM_HTTP_VERSION', + default => '1.0', + description => 'The version of HTTP that ZoneMinder will use to connect', + help => q` ZoneMinder can communicate with network cameras using either of the HTTP/1.1 or HTTP/1.0 standard. A server will normally fall back to the version it supports with no problem so this should usually by left at the default. However it can be changed to HTTP/1.0 if necessary to resolve particular issues. - "), + `, type => { - db_type => "string", - hint => "1.1|1.0", + db_type => 'string', + hint => '1.1|1.0', pattern => qr|^(1\.[01])$|, - format => q( $1?$1:"" ) + format => q( $1?$1:'' ) }, - category => "network", + category => 'network', }, { - name => "ZM_HTTP_UA", - default => "ZoneMinder", - description => "The user agent that ZoneMinder uses to identify itself", - help => qqq(" + name => 'ZM_HTTP_UA', + default => 'ZoneMinder', + description => 'The user agent that ZoneMinder uses to identify itself', + help => q` When ZoneMinder communicates with remote cameras it will identify itself using this string and it's version number. This is normally sufficient, however if a particular cameras expects only to communicate with certain browsers then this can be changed to a different string identifying ZoneMinder as Internet Explorer or Netscape etc. - "), + `, type => $types{string}, - category => "network", + category => 'network', }, { - name => "ZM_HTTP_TIMEOUT", - default => "2500", - description => "How long ZoneMinder waits before giving up on images (milliseconds)", - help => qqq(" + name => 'ZM_HTTP_TIMEOUT', + default => '2500', + description => 'How long ZoneMinder waits before giving up on images (milliseconds)', + help => q` When retrieving remote images ZoneMinder will wait for this length of time before deciding that an image is not going to arrive and taking steps to retry. This timeout is in milliseconds (1000 per second) and will apply to each part of an image if it is not sent in one whole chunk. - "), + `, type => $types{integer}, - category => "network", + category => 'network', }, { - name => "ZM_MIN_RTP_PORT", - default => "40200", - description => "Minimum port that ZoneMinder will listen for RTP traffic on", - help => qqq(" + name => 'ZM_MIN_RTP_PORT', + default => '40200', + description => 'Minimum port that ZoneMinder will listen for RTP traffic on', + help => q` When ZoneMinder communicates with MPEG4 capable cameras using RTP with the unicast method it must open ports for the camera to connect back to for control and streaming purposes. This @@ -933,15 +959,15 @@ our @options = ( should be set to an even number, you may also need to open up a hole in your firewall to allow cameras to connect back if you wish to use unicasting. - "), + `, type => $types{integer}, - category => "network", + category => 'network', }, { - name => "ZM_MAX_RTP_PORT", - default => "40499", - description => "Maximum port that ZoneMinder will listen for RTP traffic on", - help => qqq(" + name => 'ZM_MAX_RTP_PORT', + default => '40499', + description => 'Maximum port that ZoneMinder will listen for RTP traffic on', + help => q` When ZoneMinder communicates with MPEG4 capable cameras using RTP with the unicast method it must open ports for the camera to connect back to for control and streaming purposes. This @@ -953,54 +979,54 @@ our @options = ( wish to use unicasting. You should also ensure that you have opened up at least two ports for each monitor that will be connecting to unicasting network cameras. - "), + `, type => $types{integer}, - category => "network", + category => 'network', }, { - name => "ZM_OPT_FFMPEG", - default => "@OPT_FFMPEG@", - description => "Is the ffmpeg video encoder/decoder installed", - help => qqq(" + name => 'ZM_OPT_FFMPEG', + default => '@OPT_FFMPEG@', + description => 'Is the ffmpeg video encoder/decoder installed', + help => q` ZoneMinder can optionally encode a series of video images into an MPEG encoded movie file for viewing, downloading or storage. This option allows you to specify whether you have the ffmpeg tools installed. Note that creating MPEG files can be fairly CPU and disk intensive and is not a required option as events can still be reviewed as video streams without it. - "), + `, type => $types{boolean}, - category => "images", + category => 'images', }, { - name => "ZM_PATH_FFMPEG", - default => "@PATH_FFMPEG@", - description => "Path to (optional) ffmpeg mpeg encoder", - help => "This path should point to where ffmpeg has been installed.", - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + name => 'ZM_PATH_FFMPEG', + default => '@PATH_FFMPEG@', + description => 'Path to (optional) ffmpeg mpeg encoder', + help => 'This path should point to where ffmpeg has been installed.', + requires => [ { name=> 'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{abs_path}, - category => "images", + category => 'images', }, { - name => "ZM_FFMPEG_INPUT_OPTIONS", - default => "", - description => "Additional input options to ffmpeg", - help => qqq(" + name => 'ZM_FFMPEG_INPUT_OPTIONS', + default => '', + description => 'Additional input options to ffmpeg', + help => q` Ffmpeg can take many options on the command line to control the quality of video produced. This option allows you to specify your own set that apply to the input to ffmpeg (options that are given before the -i option). Check the ffmpeg documentation for a full list of options which may be used here. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + `, + requires => [ { name=> 'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{string}, - category => "images", + category => 'images', }, { - name => "ZM_FFMPEG_OUTPUT_OPTIONS", - default => "-r 25", - description => "Additional output options to ffmpeg", - help => qqq(" + name => 'ZM_FFMPEG_OUTPUT_OPTIONS', + default => '-r 25', + description => 'Additional output options to ffmpeg', + help => q` Ffmpeg can take many options on the command line to control the quality of video produced. This option allows you to specify your own set that apply to the output from ffmpeg (options that @@ -1008,566 +1034,567 @@ our @options = ( for a full list of options which may be used here. The most common one will often be to force an output frame rate supported by the video encoder. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + `, + requires => [ { name=> 'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{string}, - category => "images", + category => 'images', }, { - name => "ZM_FFMPEG_FORMATS", - default => "mpg mpeg wmv asf avi* mov swf 3gp**", - description => "Formats to allow for ffmpeg video generation", - help => qqq(" - Ffmpeg can generate video in many different formats. This - option allows you to list the ones you want to be able to - select. As new formats are supported by ffmpeg you can add them - here and be able to use them immediately. Adding a '*' after a - format indicates that this will be the default format used for - web video, adding '**' defines the default format for phone - video. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + name => 'ZM_FFMPEG_FORMATS', + default => 'mpg mpeg wmv asf avi* mov swf 3gp**', + description => 'Formats to allow for ffmpeg video generation', + help => q` + Ffmpeg can generate video in many different formats. This + option allows you to list the ones you want to be able to + select. As new formats are supported by ffmpeg you can add them + here and be able to use them immediately. Adding a '*' after a + format indicates that this will be the default format used for + web video, adding '**' defines the default format for phone + video. + `, + requires => [ { name=> 'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{string}, - category => "images", + category => 'images', }, { - name => "ZM_FFMPEG_OPEN_TIMEOUT", - default => "10", - description => "Timeout in seconds when opening a stream.", - help => qqq(" - When Ffmpeg is opening a stream, it can take a long time before - failing; certain circumstances even seem to be able to lock - indefinitely. This option allows you to set a maximum time in - seconds to pass before closing the stream and trying to reopen - it again. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + name => 'ZM_FFMPEG_OPEN_TIMEOUT', + default => '10', + description => 'Timeout in seconds when opening a stream.', + help => q` + When Ffmpeg is opening a stream, it can take a long time before + failing; certain circumstances even seem to be able to lock + indefinitely. This option allows you to set a maximum time in + seconds to pass before closing the stream and trying to reopen + it again. + `, + requires => [ { name=> 'ZM_OPT_FFMPEG', value=>'yes' } ], type => $types{integer}, - category => "images", + category => 'images', }, { - name => "ZM_LOG_LEVEL_SYSLOG", - default => "0", - description => "Save logging output to the system log", - help => qqq(" - ZoneMinder logging is now more more integrated between - components and allows you to specify the destination for - logging output and the individual levels for each. This option - lets you control the level of logging output that goes to the - system log. ZoneMinder binaries have always logged to the - system log but now scripts and web logging is also included. To - preserve the previous behaviour you should ensure this value is - set to Info or Warning. This option controls the maximum level - of logging that will be written, so Info includes Warnings and - Errors etc. To disable entirely, set this option to None. You - should use caution when setting this option to Debug as it can - affect severely affect system performance. If you want debug - you will also need to set a level and component below - "), + name => 'ZM_LOG_LEVEL_SYSLOG', + default => '0', + description => 'Save logging output to the system log', + help => q` + ZoneMinder logging is now more more integrated between + components and allows you to specify the destination for + logging output and the individual levels for each. This option + lets you control the level of logging output that goes to the + system log. ZoneMinder binaries have always logged to the + system log but now scripts and web logging is also included. To + preserve the previous behaviour you should ensure this value is + set to Info or Warning. This option controls the maximum level + of logging that will be written, so Info includes Warnings and + Errors etc. To disable entirely, set this option to None. You + should use caution when setting this option to Debug as it can + affect severely affect system performance. If you want debug + you will also need to set a level and component below + `, type => { - db_type => "integer", - hint => "None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", + db_type => 'integer', + hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', pattern => qr|^(\d+)$|, format => q( $1 ) }, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_LEVEL_FILE", - default => "-5", - description => "Save logging output to component files", - help => qqq(" - ZoneMinder logging is now more more integrated between - components and allows you to specify the destination for - logging output and the individual levels for each. This option - lets you control the level of logging output that goes to - individual log files written by specific components. This is - how logging worked previously and although useful for tracking - down issues in specific components it also resulted in many - disparate log files. To preserve this behaviour you should - ensure this value is set to Info or Warning. This option - controls the maximum level of logging that will be written, so - Info includes Warnings and Errors etc. To disable entirely, set - this option to None. You should use caution when setting this - option to Debug as it can affect severely affect system - performance though file output has less impact than the other - options. If you want debug you will also need to set a level - and component below - "), + name => 'ZM_LOG_LEVEL_FILE', + default => '-5', + description => 'Save logging output to component files', + help => q` + ZoneMinder logging is now more more integrated between + components and allows you to specify the destination for + logging output and the individual levels for each. This option + lets you control the level of logging output that goes to + individual log files written by specific components. This is + how logging worked previously and although useful for tracking + down issues in specific components it also resulted in many + disparate log files. To preserve this behaviour you should + ensure this value is set to Info or Warning. This option + controls the maximum level of logging that will be written, so + Info includes Warnings and Errors etc. To disable entirely, set + this option to None. You should use caution when setting this + option to Debug as it can affect severely affect system + performance though file output has less impact than the other + options. If you want debug you will also need to set a level + and component below + `, type => { - db_type => "integer", - hint => "None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", + db_type => 'integer', + hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', pattern => qr|^(\d+)$|, format => q( $1 ) }, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_LEVEL_WEBLOG", - default => "-5", - description => "Save logging output to the weblog", - help => qqq(" - ZoneMinder logging is now more more integrated between - components and allows you to specify the destination for - logging output and the individual levels for each. This option - lets you control the level of logging output from the web - interface that goes to the httpd error log. Note that only web - logging from PHP and JavaScript files is included and so this - option is really only useful for investigating specific issues - with those components. This option controls the maximum level - of logging that will be written, so Info includes Warnings and - Errors etc. To disable entirely, set this option to None. You - should use caution when setting this option to Debug as it can - affect severely affect system performance. If you want debug - you will also need to set a level and component below - "), + name => 'ZM_LOG_LEVEL_WEBLOG', + default => '-5', + description => 'Save logging output to the weblog', + help => q` + ZoneMinder logging is now more more integrated between + components and allows you to specify the destination for + logging output and the individual levels for each. This option + lets you control the level of logging output from the web + interface that goes to the httpd error log. Note that only web + logging from PHP and JavaScript files is included and so this + option is really only useful for investigating specific issues + with those components. This option controls the maximum level + of logging that will be written, so Info includes Warnings and + Errors etc. To disable entirely, set this option to None. You + should use caution when setting this option to Debug as it can + affect severely affect system performance. If you want debug + you will also need to set a level and component below + `, type => { - db_type => "integer", - hint => "None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", + db_type => 'integer', + hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', pattern => qr|^(\d+)$|, format => q( $1 ) }, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_LEVEL_DATABASE", - default => "0", - description => "Save logging output to the database", - help => qqq(" - ZoneMinder logging is now more more integrated between - components and allows you to specify the destination for - logging output and the individual levels for each. This option - lets you control the level of logging output that is written to - the database. This is a new option which can make viewing - logging output easier and more intuitive and also makes it - easier to get an overall impression of how the system is - performing. If you have a large or very busy system then it is - possible that use of this option may slow your system down if - the table becomes very large. Ensure you use the - LOG_DATABASE_LIMIT option to keep the table to a manageable - size. This option controls the maximum level of logging that - will be written, so Info includes Warnings and Errors etc. To - disable entirely, set this option to None. You should use - caution when setting this option to Debug as it can affect - severely affect system performance. If you want debug you will - also need to set a level and component below - "), + name => 'ZM_LOG_LEVEL_DATABASE', + default => '0', + description => 'Save logging output to the database', + help => q` + ZoneMinder logging is now more more integrated between + components and allows you to specify the destination for + logging output and the individual levels for each. This option + lets you control the level of logging output that is written to + the database. This is a new option which can make viewing + logging output easier and more intuitive and also makes it + easier to get an overall impression of how the system is + performing. If you have a large or very busy system then it is + possible that use of this option may slow your system down if + the table becomes very large. Ensure you use the + LOG_DATABASE_LIMIT option to keep the table to a manageable + size. This option controls the maximum level of logging that + will be written, so Info includes Warnings and Errors etc. To + disable entirely, set this option to None. You should use + caution when setting this option to Debug as it can affect + severely affect system performance. If you want debug you will + also need to set a level and component below + `, type => { - db_type => "integer", - hint => "None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", + db_type => 'integer', + hint => 'None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1', pattern => qr|^(\d+)$|, format => q( $1 ) }, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_DATABASE_LIMIT", - default => "7 day", - description => "Maximum number of log entries to retain", - help => qqq(" - If you are using database logging then it is possible to - quickly build up a large number of entries in the Logs table. - This option allows you to specify how many of these entries are - kept. If you set this option to a number greater than zero then - that number is used to determine the maximum number of rows, - less than or equal to zero indicates no limit and is not - recommended. You can also set this value to time values such as - ' day' which will limit the log entries to those newer than - that time. You can specify 'hour', 'day', 'week', 'month' and - 'year', note that the values should be singular (no 's' at the - end). The Logs table is pruned periodically so it is possible - for more than the expected number of rows to be present briefly - in the meantime. - "), + name => 'ZM_LOG_DATABASE_LIMIT', + default => '7 day', + description => 'Maximum number of log entries to retain', + help => q` + If you are using database logging then it is possible to + quickly build up a large number of entries in the Logs table. + This option allows you to specify how many of these entries are + kept. If you set this option to a number greater than zero then + that number is used to determine the maximum number of rows, + less than or equal to zero indicates no limit and is not + recommended. You can also set this value to time values such as + ' day' which will limit the log entries to those newer than + that time. You can specify 'hour', 'day', 'week', 'month' and + 'year', note that the values should be singular (no 's' at the + end). The Logs table is pruned periodically so it is possible + for more than the expected number of rows to be present briefly + in the meantime. + `, type => $types{string}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_DEBUG", - default => "no", - description => "Switch debugging on", - help => qqq(" - ZoneMinder components usually support debug logging available - to help with diagnosing problems. Binary components have - several levels of debug whereas more other components have only - one. Normally this is disabled to minimise performance - penalties and avoid filling logs too quickly. This option lets - you switch on other options that allow you to configure - additional debug information to be output. Components will pick - up this instruction when they are restarted. - "), + name => 'ZM_LOG_DEBUG', + default => 'no', + description => 'Switch debugging on', + help => q` + ZoneMinder components usually support debug logging available + to help with diagnosing problems. Binary components have + several levels of debug whereas more other components have only + one. Normally this is disabled to minimise performance + penalties and avoid filling logs too quickly. This option lets + you switch on other options that allow you to configure + additional debug information to be output. Components will pick + up this instruction when they are restarted. + `, type => $types{boolean}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_DEBUG_TARGET", - default => "", - description => "What components should have extra debug enabled", - help => qqq(" - There are three scopes of debug available. Leaving this option - blank means that all components will use extra debug (not - recommended). Setting this option to '_', e.g. _zmc, - will limit extra debug to that component only. Setting this - option to '__', e.g. '_zmc_m1' will limit - extra debug to that instance of the component only. This is - ordinarily what you probably want to do. To debug scripts use - their names without the .pl extension, e.g. '_zmvideo' and to - debug issues with the web interface use '_web'. You can specify - multiple targets by separating them with '|' characters. - "), - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], + name => 'ZM_LOG_DEBUG_TARGET', + default => '', + description => 'What components should have extra debug enabled', + help => q` + There are three scopes of debug available. Leaving this option + blank means that all components will use extra debug (not + recommended). Setting this option to '_', e.g. _zmc, + will limit extra debug to that component only. Setting this + option to '__', e.g. '_zmc_m1' will limit + extra debug to that instance of the component only. This is + ordinarily what you probably want to do. To debug scripts use + their names without the .pl extension, e.g. '_zmvideo' and to + debug issues with the web interface use '_web'. You can specify + multiple targets by separating them with '|' characters. + `, + requires => [ { name => 'ZM_LOG_DEBUG', value => 'yes' } ], type => $types{string}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_DEBUG_LEVEL", + name => 'ZM_LOG_DEBUG_LEVEL', default => 1, - description => "What level of extra debug should be enabled", - help => qqq(" - There are 9 levels of debug available, with higher numbers - being more debug and level 0 being no debug. However not all - levels are used by all components. Also if there is debug at a - high level it is usually likely to be output at such a volume - that it may obstruct normal operation. For this reason you - should set the level carefully and cautiously until the degree - of debug you wish to see is present. Scripts and the web - interface only have one level so this is an on/off type option - for them. - "), - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], + description => 'What level of extra debug should be enabled', + help => q` + There are 9 levels of debug available, with higher numbers + being more debug and level 0 being no debug. However not all + levels are used by all components. Also if there is debug at a + high level it is usually likely to be output at such a volume + that it may obstruct normal operation. For this reason you + should set the level carefully and cautiously until the degree + of debug you wish to see is present. Scripts and the web + interface only have one level so this is an on/off type option + for them. + `, + requires => [ { name => 'ZM_LOG_DEBUG', value => 'yes' } ], type => { - db_type => "integer", - hint => "1|2|3|4|5|6|7|8|9", + db_type => 'integer', + hint => '1|2|3|4|5|6|7|8|9', pattern => qr|^(\d+)$|, format => q( $1 ) }, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_DEBUG_FILE", - default => "@ZM_LOGDIR@/zm_debug.log+", - description => "Where extra debug is output to", - help => qqq(" - This option allows you to specify a different target for debug - output. All components have a default log file which will - norally be in /tmp or /var/log and this is where debug will be - written to if this value is empty. Adding a path here will - temporarily redirect debug, and other logging output, to this - file. This option is a simple filename and you are debugging - several components then they will all try and write to the same - file with undesirable consequences. Appending a '+' to the - filename will cause the file to be created with a '.' - suffix containing your process id. In this way debug from each - run of a component is kept separate. This is the recommended - setting as it will also prevent subsequent runs from - overwriting the same log. You should ensure that permissions - are set up to allow writing to the file and directory specified - here. - "), - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], + name => 'ZM_LOG_DEBUG_FILE', + default => '@ZM_LOGDIR@/zm_debug.log+', + description => 'Where extra debug is output to', + help => q` + This option allows you to specify a different target for debug + output. All components have a default log file which will + norally be in /tmp or /var/log and this is where debug will be + written to if this value is empty. Adding a path here will + temporarily redirect debug, and other logging output, to this + file. This option is a simple filename and you are debugging + several components then they will all try and write to the same + file with undesirable consequences. Appending a '+' to the + filename will cause the file to be created with a '.' + suffix containing your process id. In this way debug from each + run of a component is kept separate. This is the recommended + setting as it will also prevent subsequent runs from + overwriting the same log. You should ensure that permissions + are set up to allow writing to the file and directory specified + here. + `, + requires => [ { name => 'ZM_LOG_DEBUG', value => 'yes' } ], type => $types{string}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_CHECK_PERIOD", - default => "900", - description => "Time period used when calculating overall system health", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to indicate what period of historical - events are used in this calculation. This value is expressed in - seconds and is ignored if LOG_LEVEL_DATABASE is set to None. - "), + name => 'ZM_LOG_CHECK_PERIOD', + default => '900', + description => 'Time period used when calculating overall system health', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to indicate what period of historical + events are used in this calculation. This value is expressed in + seconds and is ignored if LOG_LEVEL_DATABASE is set to None. + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_ALERT_WAR_COUNT", - default => "1", - description => "Number of warnings indicating system alert state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many warnings must have - occurred within the defined time period to generate an overall - system alert state. A value of zero means warnings are not - considered. This value is ignored if LOG_LEVEL_DATABASE is set - to None. - "), + name => 'ZM_LOG_ALERT_WAR_COUNT', + default => '1', + description => 'Number of warnings indicating system alert state', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many warnings must have + occurred within the defined time period to generate an overall + system alert state. A value of zero means warnings are not + considered. This value is ignored if LOG_LEVEL_DATABASE is set + to None. + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_ALERT_ERR_COUNT", - default => "1", - description => "Number of errors indicating system alert state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many errors must have - occurred within the defined time period to generate an overall - system alert state. A value of zero means errors are not - considered. This value is ignored if LOG_LEVEL_DATABASE is set - to None. - "), + name => 'ZM_LOG_ALERT_ERR_COUNT', + default => '1', + description => 'Number of errors indicating system alert state', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many errors must have + occurred within the defined time period to generate an overall + system alert state. A value of zero means errors are not + considered. This value is ignored if LOG_LEVEL_DATABASE is set + to None. + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_ALERT_FAT_COUNT", - default => "0", - description => "Number of fatal error indicating system alert state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many fatal errors - (including panics) must have occurred within the defined time - period to generate an overall system alert state. A value of - zero means fatal errors are not considered. This value is - ignored if LOG_LEVEL_DATABASE is set to None. - "), + name => 'ZM_LOG_ALERT_FAT_COUNT', + default => '0', + description => 'Number of fatal error indicating system alert state', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many fatal errors + (including panics) must have occurred within the defined time + period to generate an overall system alert state. A value of + zero means fatal errors are not considered. This value is + ignored if LOG_LEVEL_DATABASE is set to None. + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_ALARM_WAR_COUNT", - default => "100", - description => "Number of warnings indicating system alarm state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many warnings must have - occurred within the defined time period to generate an overall - system alarm state. A value of zero means warnings are not - considered. This value is ignored if LOG_LEVEL_DATABASE is set - to None. - "), + name => 'ZM_LOG_ALARM_WAR_COUNT', + default => '100', + description => 'Number of warnings indicating system alarm state', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many warnings must have + occurred within the defined time period to generate an overall + system alarm state. A value of zero means warnings are not + considered. This value is ignored if LOG_LEVEL_DATABASE is set + to None. + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_ALARM_ERR_COUNT", - default => "10", - description => "Number of errors indicating system alarm state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many errors must have - occurred within the defined time period to generate an overall - system alarm state. A value of zero means errors are not - considered. This value is ignored if LOG_LEVEL_DATABASE is set - to None. - "), + name => 'ZM_LOG_ALARM_ERR_COUNT', + default => '10', + description => 'Number of errors indicating system alarm state', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many errors must have + occurred within the defined time period to generate an overall + system alarm state. A value of zero means errors are not + considered. This value is ignored if LOG_LEVEL_DATABASE is set + to None. + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_LOG_ALARM_FAT_COUNT", - default => "1", - description => "Number of fatal error indicating system alarm state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many fatal errors - (including panics) must have occurred within the defined time - period to generate an overall system alarm state. A value of - zero means fatal errors are not considered. This value is - ignored if LOG_LEVEL_DATABASE is set to None. - "), + name => 'ZM_LOG_ALARM_FAT_COUNT', + default => '1', + description => 'Number of fatal error indicating system alarm state', + help => q` + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many fatal errors + (including panics) must have occurred within the defined time + period to generate an overall system alarm state. A value of + zero means fatal errors are not considered. This value is + ignored if LOG_LEVEL_DATABASE is set to None. + `, type => $types{integer}, - category => "logging", + category => 'logging', }, { - name => "ZM_RECORD_EVENT_STATS", - default => "yes", - description => "Record event statistical information, switch off if too slow", - help => qqq(" - This version of ZoneMinder records detailed information about - events in the Stats table. This can help in profiling what the - optimum settings are for Zones though this is tricky at - present. However in future releases this will be done more - easily and intuitively, especially with a large sample of - events. The default option of 'yes' allows this information to - be collected now in readiness for this but if you are concerned - about performance you can switch this off in which case no - Stats information will be saved. - "), + name => 'ZM_RECORD_EVENT_STATS', + default => 'yes', + description => 'Record event statistical information, switch off if too slow', + help => q` + This version of ZoneMinder records detailed information about + events in the Stats table. This can help in profiling what the + optimum settings are for Zones though this is tricky at + present. However in future releases this will be done more + easily and intuitively, especially with a large sample of + events. The default option of 'yes' allows this information to + be collected now in readiness for this but if you are concerned + about performance you can switch this off in which case no + Stats information will be saved. + `, type => $types{boolean}, - category => "logging", + category => 'logging', }, { - name => "ZM_RECORD_DIAG_IMAGES", - default => "no", - description => "Record intermediate alarm diagnostic images, can be very slow", - help => qqq(" - In addition to recording event statistics you can also record - the intermediate diagnostic images that display the results of - the various checks and processing that occur when trying to - determine if an alarm event has taken place. There are several - of these images generated for each frame and zone for each - alarm or alert frame so this can have a massive impact on - performance. Only switch this setting on for debug or analysis - purposes and remember to switch it off again once no longer - required. - "), + name => 'ZM_RECORD_DIAG_IMAGES', + default => 'no', + description => 'Record intermediate alarm diagnostic images, can be very slow', + help => q` + In addition to recording event statistics you can also record + the intermediate diagnostic images that display the results of + the various checks and processing that occur when trying to + determine if an alarm event has taken place. There are several + of these images generated for each frame and zone for each + alarm or alert frame so this can have a massive impact on + performance. Only switch this setting on for debug or analysis + purposes and remember to switch it off again once no longer + required. + `, type => $types{boolean}, - category => "logging", + category => 'logging', }, { - name => "ZM_DUMP_CORES", - default => "no", - description => "Create core files on unexpected process failure.", - help => qqq(" - When an unrecoverable error occurs in a ZoneMinder binary - process is has traditionally been trapped and the details - written to logs to aid in remote analysis. However in some - cases it is easier to diagnose the error if a core file, which - is a memory dump of the process at the time of the error, is - created. This can be interactively analysed in the debugger and - may reveal more or better information than that available from - the logs. This option is recommended for advanced users only - otherwise leave at the default. Note using this option to - trigger core files will mean that there will be no indication - in the binary logs that a process has died, they will just - stop, however the zmdc log will still contain an entry. Also - note that you may have to explicitly enable core file creation - on your system via the 'ulimit -c' command or other means - otherwise no file will be created regardless of the value of - this option. - "), + name => 'ZM_DUMP_CORES', + default => 'no', + description => 'Create core files on unexpected process failure.', + help => q` + When an unrecoverable error occurs in a ZoneMinder binary + process is has traditionally been trapped and the details + written to logs to aid in remote analysis. However in some + cases it is easier to diagnose the error if a core file, which + is a memory dump of the process at the time of the error, is + created. This can be interactively analysed in the debugger and + may reveal more or better information than that available from + the logs. This option is recommended for advanced users only + otherwise leave at the default. Note using this option to + trigger core files will mean that there will be no indication + in the binary logs that a process has died, they will just + stop, however the zmdc log will still contain an entry. Also + note that you may have to explicitly enable core file creation + on your system via the 'ulimit -c' command or other means + otherwise no file will be created regardless of the value of + this option. + `, type => $types{boolean}, - category => "logging", + category => 'logging', }, { - name => "ZM_PATH_MAP", - default => "/dev/shm", - description => "Path to the mapped memory files that that ZoneMinder can use", - help => qqq(" - ZoneMinder has historically used IPC shared memory for shared - data between processes. This has it's advantages and - limitations. This version of ZoneMinder can use an alternate - method, mapped memory, instead with can be enabled with the - --enable--mmap directive to configure. This requires less - system configuration and is generally more flexible. However it - requires each shared data segment to map onto a filesystem - file. This option indicates where those mapped files go. You - should ensure that this location has sufficient space for these - files and for the best performance it should be a tmpfs file - system or ramdisk otherwise disk access may render this method - slower than the regular shared memory one. - "), + name => 'ZM_PATH_MAP', + default => '/dev/shm', + description => 'Path to the mapped memory files that that ZoneMinder can use', + help => q` + ZoneMinder has historically used IPC shared memory for shared + data between processes. This has it's advantages and + limitations. This version of ZoneMinder can use an alternate + method, mapped memory, instead with can be enabled with the + --enable--mmap directive to configure. This requires less + system configuration and is generally more flexible. However it + requires each shared data segment to map onto a filesystem + file. This option indicates where those mapped files go. You + should ensure that this location has sufficient space for these + files and for the best performance it should be a tmpfs file + system or ramdisk otherwise disk access may render this method + slower than the regular shared memory one. + `, type => $types{abs_path}, - category => "paths", + category => 'paths', }, { - name => "ZM_PATH_SOCKS", - default => "@ZM_SOCKDIR@", - description => "Path to the various Unix domain socket files that ZoneMinder uses", - help => qqq(" + name => 'ZM_PATH_SOCKS', + default => '@ZM_SOCKDIR@', + description => 'Path to the various Unix domain socket files that ZoneMinder uses', + help => q` ZoneMinder generally uses Unix domain sockets where possible. This reduces the need for port assignments and prevents external applications from possibly compromising the daemons. However each Unix socket requires a .sock file to be created. This option indicates where those socket files go. - "), + `, type => $types{abs_path}, - category => "paths", + category => 'paths', }, { - name => "ZM_PATH_LOGS", - default => "@ZM_LOGDIR@", - description => "Path to the various logs that the ZoneMinder daemons generate", - help => qqq(" + name => 'ZM_PATH_LOGS', + default => '@ZM_LOGDIR@', + description => 'Path to the various logs that the ZoneMinder daemons generate', + help => q` There are various daemons that are used by ZoneMinder to perform various tasks. Most generate helpful log files and this is where they go. They can be deleted if not required for debugging. - "), + `, type => $types{abs_path}, - category => "paths", + category => 'paths', }, { - name => "ZM_PATH_SWAP", - default => "@ZM_TMPDIR@", - description => "Path to location for temporary swap images used in streaming", - help => qqq(" + name => 'ZM_PATH_SWAP', + default => '@ZM_TMPDIR@', + description => 'Path to location for temporary swap images used in streaming', + help => q` Buffered playback requires temporary swap images to be stored for each instance of the streaming daemons. This option determines where these images will be stored. The images will actually be stored in sub directories beneath this location and will be automatically cleaned up after a period of time. - "), + `, type => $types{abs_path}, - category => "paths", + category => 'paths', }, { - name => "ZM_PATH_ARP", - default => "", - description => "Path to a supported ARP tool", - help => qqq(" + name => 'ZM_PATH_ARP', + default => '', + description => 'Path to a supported ARP tool', + help => q` The camera probe function uses Address Resolution Protocol in order to find known devices on the network. Optionally supply - the full path to \"ip neigh\", \"arp -a\", or any other tool on + the full path to 'ip neigh', 'arp -a', or any other tool on your system that returns ip/mac address pairs. If this field is - left empty, ZoneMinder will search for the command \"arp\" and + left empty, ZoneMinder will search for the command 'arp' and attempt to use that. - "), + `, type => $types{abs_path}, - category => "paths", + category => 'paths', }, { - name => "ZM_WEB_TITLE_PREFIX", - default => "ZM", - description => "The title prefix displayed on each window", - help => qqq(" + name => 'ZM_WEB_TITLE_PREFIX', + default => 'ZM', + description => 'The title prefix displayed on each window', + help => q` If you have more than one installation of ZoneMinder it can be helpful to display different titles for each one. Changing this option allows you to customise the window titles to include further information to aid identification. - "), + `, type => $types{string}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_CONSOLE_BANNER", - default => "", - description => "Arbitrary text message near the top of the console", - help => qqq(" + name => 'ZM_WEB_CONSOLE_BANNER', + default => '', + description => 'Arbitrary text message near the top of the console', + help => q` Allows the administrator to place an arbitrary text message near the top of the web console. This is useful for the developers to display a message which indicates the running instance of ZoneMinder is a development snapshot, but it can also be used for any other purpose as well. - "), + `, type => $types{string}, - category => "web", + category => 'web', }, { name => 'ZM_WEB_EVENT_DISK_SPACE', - default => '', - description => "Whether to show disk space used by each event.", - help => qqq("Adds another column to the listing of events + default => 'no', + description => 'Whether to show disk space used by each event.', + help => q` + Adds another column to the listing of events showing the disk space used by the event. This will impart a small overhead as it will call du on the event directory. In practice this overhead is fairly small but may be noticeable on IO-constrained systems. - "), + `, type => $types{string}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_RESIZE_CONSOLE", - default => "yes", - description => "Should the console window resize itself to fit", - help => qqq(" + name => 'ZM_WEB_RESIZE_CONSOLE', + default => 'yes', + description => 'Should the console window resize itself to fit', + help => q` Traditionally the main ZoneMinder web console window has resized itself to shrink to a size small enough to list only the monitors that are actually present. This is intended to @@ -1575,135 +1602,135 @@ our @options = ( tastes, especially if opened in a tab in browsers which support this kind if layout. Switch this option off to have the console window size left to the users preference - "), + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_ID_ON_CONSOLE", - default => "no", - description => "Should the console list the monitor id", - help => qqq("Some find it useful to have the id always visible + name => 'ZM_WEB_ID_ON_CONSOLE', + default => 'no', + description => 'Should the console list the monitor id', + help => q`Some find it useful to have the id always visible on the console. This option will add a column listing it. - "), + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_POPUP_ON_ALARM", - default => "yes", - description => "Should the monitor window jump to the top if an alarm occurs", - help => qqq(" + name => 'ZM_WEB_POPUP_ON_ALARM', + default => 'yes', + description => 'Should the monitor window jump to the top if an alarm occurs', + help => q` When viewing a live monitor stream you can specify whether you want the window to pop to the front if an alarm occurs when the window is minimised or behind another window. This is most useful if your monitors are over doors for example when they can pop up if someone comes to the doorway. - "), + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_OPT_X10", - default => "no", - description => "Support interfacing with X10 devices", - help => qqq(" + name => 'ZM_OPT_X10', + default => 'no', + description => 'Support interfacing with X10 devices', + help => q` If you have an X10 Home Automation setup in your home you can use ZoneMinder to initiate or react to X10 signals if your computer has the appropriate interface controller. This option indicates whether X10 options will be available in the browser client. - "), + `, type => $types{boolean}, - category => "x10", + category => 'x10', }, { - name => "ZM_X10_DEVICE", - default => "/dev/ttyS0", - description => "What device is your X10 controller connected on", - requires => [ { name => "ZM_OPT_X10", value => "yes" } ], - help => qqq(" + name => 'ZM_X10_DEVICE', + default => '/dev/ttyS0', + description => 'What device is your X10 controller connected on', + requires => [ { name => 'ZM_OPT_X10', value => 'yes' } ], + help => q` If you have an X10 controller device (e.g. XM10U) connected to your computer this option details which port it is connected on, the default of /dev/ttyS0 maps to serial or com port 1. - "), + `, type => $types{abs_path}, - category => "x10", + category => 'x10', }, { - name => "ZM_X10_HOUSE_CODE", - default => "A", - description => "What X10 house code should be used", - requires => [ { name => "ZM_OPT_X10", value => "yes" } ], - help => qqq(" + name => 'ZM_X10_HOUSE_CODE', + default => 'A', + description => 'What X10 house code should be used', + requires => [ { name => 'ZM_OPT_X10', value => 'yes' } ], + help => q` X10 devices are grouped together by identifying them as all belonging to one House Code. This option details what that is. It should be a single letter between A and P. - "), - type => { db_type=>"string", hint=>"A-P", pattern=>qr|^([A-P])|i, format=>q( uc($1) ) }, - category => "x10", + `, + type => { db_type=> 'string', hint=>'A-P', pattern=> qr|^([A-P])|i, format=> q( uc($1) ) }, + category => 'x10', }, { - name => "ZM_X10_DB_RELOAD_INTERVAL", - default => "60", - description => "How often (in seconds) the X10 daemon reloads the monitors from the database", - requires => [ { name => "ZM_OPT_X10", value => "yes" } ], - help => qqq(" + name => 'ZM_X10_DB_RELOAD_INTERVAL', + default => '60', + description => 'How often (in seconds) the X10 daemon reloads the monitors from the database', + requires => [ { name => 'ZM_OPT_X10', value => 'yes' } ], + help => q` The zmx10 daemon periodically checks the database to find out what X10 events trigger, or result from, alarms. This option determines how frequently this check occurs, unless you change this area frequently this can be a fairly large value. - "), + `, type => $types{integer}, - category => "x10", + category => 'x10', }, { - name => "ZM_WEB_SOUND_ON_ALARM", - default => "no", - description => "Should the monitor window play a sound if an alarm occurs", - help => qqq(" + name => 'ZM_WEB_SOUND_ON_ALARM', + default => 'no', + description => 'Should the monitor window play a sound if an alarm occurs', + help => q` When viewing a live monitor stream you can specify whether you want the window to play a sound to alert you if an alarm occurs. - "), + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_ALARM_SOUND", - default => "", - description => "The sound to play on alarm, put this in the sounds directory", - help => qqq(" + name => 'ZM_WEB_ALARM_SOUND', + default => '', + description => 'The sound to play on alarm, put this in the sounds directory', + help => q` You can specify a sound file to play if an alarm occurs whilst you are watching a live monitor stream. So long as your browser understands the format it does not need to be any particular type. This file should be placed in the sounds directory defined earlier. - "), + `, type => $types{file}, - requires => [ { name => "ZM_WEB_SOUND_ON_ALARM", value => "yes" } ], - category => "web", + requires => [ { name => 'ZM_WEB_SOUND_ON_ALARM', value => 'yes' } ], + category => 'web', }, { - name => "ZM_WEB_COMPACT_MONTAGE", - default => "no", - description => "Compact the montage view by removing extra detail", - help => qqq(" + name => 'ZM_WEB_COMPACT_MONTAGE', + default => 'no', + description => 'Compact the montage view by removing extra detail', + help => q` The montage view shows the output of all of your active monitors in one window. This include a small menu and status information for each one. This can increase the web traffic and make the window larger than may be desired. Setting this option on removes all this extraneous information and just displays the images. - "), + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_OPT_FAST_DELETE", - default => "no", - description => "Delete only event database records for speed", - help => qqq(" + name => 'ZM_OPT_FAST_DELETE', + default => 'no', + description => 'Delete only event database records for speed', + help => q` Normally an event created as the result of an alarm consists of entries in one or more database tables plus the various files associated with it. When deleting events in the browser it can @@ -1714,15 +1741,15 @@ our @options = ( longer appear in the listing, and leaves the zmaudit daemon to clear up the rest later. Note that this feature is less relevant with modern hardware. Recommend this feature be left off. - "), + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_STRICT_VIDEO_CONFIG", - default => "yes", - description => "Allow errors in setting video config to be fatal", - help => qqq(" + name => 'ZM_STRICT_VIDEO_CONFIG', + default => 'yes', + description => 'Allow errors in setting video config to be fatal', + help => q` With some video devices errors can be reported in setting the various video attributes when in fact the operation was successful. Switching this option off will still allow these @@ -1731,26 +1758,26 @@ our @options = ( errors to be ignored including those which are genuine and which may cause the video capture to not function correctly. Use this option with caution. - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { name => 'ZM_LD_PRELOAD', default => '', - description => "Path to library to preload before launching daemons", - help => qqq("Some older cameras require the use of the v4l1 compat + description => 'Path to library to preload before launching daemons', + help => q`Some older cameras require the use of the v4l1 compat library. This setting allows the setting of the path to the library, so that it can be loaded by zmdc.pl - before launching zmc."), + before launching zmc.`, type => $types{abs_path}, category => 'config', }, { - name => "ZM_SIGNAL_CHECK_POINTS", - default => "10", - description => "How many points in a captured image to check for signal loss", - help => qqq(" + name => 'ZM_SIGNAL_CHECK_POINTS', + default => '10', + description => 'How many points in a captured image to check for signal loss', + help => q` For locally attached video cameras ZoneMinder can check for signal loss by looking at a number of random points on each captured image. If all of these points are set to the same @@ -1762,15 +1789,15 @@ our @options = ( to not have the check colour will abort any further checks so in most cases on a couple of points will actually be checked. Network and file based cameras are never checked. - "), + `, type => $types{integer}, - category => "config", + category => 'config', }, { - name => "ZM_V4L_MULTI_BUFFER", - default => "yes", - description => "Use more than one buffer for Video 4 Linux devices", - help => qqq(" + name => 'ZM_V4L_MULTI_BUFFER', + default => 'yes', + description => 'Use more than one buffer for Video 4 Linux devices', + help => q` Performance when using Video 4 Linux devices is usually best if multiple buffers are used allowing the next image to be captured while the previous one is being processed. If you have @@ -1785,15 +1812,15 @@ our @options = ( value of only one of the options at a time. If you have different capture cards that need different values you can ovveride them in each individual monitor on the source page. - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_CAPTURES_PER_FRAME", - default => "1", - description => "How many images are captured per returned frame, for shared local cameras", - help => qqq(" + name => 'ZM_CAPTURES_PER_FRAME', + default => '1', + description => 'How many images are captured per returned frame, for shared local cameras', + help => q` If you are using cameras attached to a video capture card which forces multiple inputs to share one capture chip, it can sometimes produce images with interlaced frames reversed @@ -1809,15 +1836,15 @@ our @options = ( value of only one of the options at a time. If you have different capture cards that need different values you can ovveride them in each individual monitor on the source page. - "), + `, type => $types{integer}, - category => "config", + category => 'config', }, { - name => "ZM_FILTER_RELOAD_DELAY", - default => "300", - description => "How often (in seconds) filters are reloaded in zmfilter", - help => qqq(" + name => 'ZM_FILTER_RELOAD_DELAY', + default => '300', + description => 'How often (in seconds) filters are reloaded in zmfilter', + help => q` ZoneMinder allows you to save filters to the database which allow events that match certain criteria to be emailed, deleted or uploaded to a remote machine etc. The zmfilter daemon loads @@ -1825,15 +1852,15 @@ our @options = ( often the filters are reloaded from the database to get the latest versions or new filters. If you don't change filters very often this value can be set to a large value. - "), + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_FILTER_EXECUTE_INTERVAL", - default => "60", - description => "How often (in seconds) to run automatic saved filters", - help => qqq(" + name => 'ZM_FILTER_EXECUTE_INTERVAL', + default => '60', + description => 'How often (in seconds) to run automatic saved filters', + help => q` ZoneMinder allows you to save filters to the database which allow events that match certain criteria to be emailed, deleted or uploaded to a remote machine etc. The zmfilter daemon loads @@ -1843,62 +1870,62 @@ our @options = ( should be a smaller value, however this may increase the overall load on the system and affect performance of other elements. - "), + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_OPT_UPLOAD", - default => "no", - description => "Should ZoneMinder support uploading events from filters", - help => qqq(" + name => 'ZM_OPT_UPLOAD', + default => 'no', + description => 'Should ZoneMinder support uploading events from filters', + help => q` In ZoneMinder you can create event filters that specify whether events that match certain criteria should be uploaded to a remote server for archiving. This option specifies whether this functionality should be available - "), + `, type => $types{boolean}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_ARCH_FORMAT", - default => "tar", - description => "What format the uploaded events should be created in.", - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - help => qqq(" + name => 'ZM_UPLOAD_ARCH_FORMAT', + default => 'tar', + description => 'What format the uploaded events should be created in.', + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + help => q` Uploaded events may be stored in either .tar or .zip format, this option specifies which. Note that to use this you will need to have the Archive::Tar and/or Archive::Zip perl modules installed. - "), + `, type => { - db_type =>"string", - hint =>"tar|zip", - pattern =>qr|^([tz])|i, - format =>q( $1 =~ /^t/ ? "tar" : "zip" ) + db_type => 'string', + hint => 'tar|zip', + pattern => qr|^([tz])|i, + format => q( $1 =~ /^t/ ? 'tar' : 'zip' ) }, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_ARCH_COMPRESS", - default => "no", - description => "Should archive files be compressed", - help => qqq(" + name => 'ZM_UPLOAD_ARCH_COMPRESS', + default => 'no', + description => 'Should archive files be compressed', + help => q` When the archive files are created they can be compressed. However in general since the images are compressed already this saves only a minimal amount of space versus utilising more CPU in their creation. Only enable if you have CPU to waste and are limited in disk space on your remote server or bandwidth. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_ARCH_ANALYSE", - default => "no", - description => "Include the analysis files in the archive", - help => qqq(" + name => 'ZM_UPLOAD_ARCH_ANALYSE', + default => 'no', + description => 'Include the analysis files in the archive', + help => q` When the archive files are created they can contain either just the captured frames or both the captured frames and, for frames that caused an alarm, the analysed image with the changed area @@ -1907,17 +1934,17 @@ our @options = ( to the remote server or if you need help in figuring out what caused an alarm in the first place as archives with these files in can be considerably larger. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_PROTOCOL", - default => "ftp", - description => "What protocol to use to upload events", - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - help => qqq(" + name => 'ZM_UPLOAD_PROTOCOL', + default => 'ftp', + description => 'What protocol to use to upload events', + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + help => q` ZoneMinder can upload events to a remote server using either FTP or SFTP. Regular FTP is widely supported but not necessarily very secure whereas SFTP (Secure FTP) runs over an @@ -1925,266 +1952,266 @@ our @options = ( Note that to use this you will need to have the appropriate perl module, either Net::FTP or Net::SFTP installed depending on your choice. - "), + `, type => { - db_type =>"string", - hint =>"ftp|sftp", - pattern =>qr|^([tz])|i, - format =>q( $1 =~ /^f/ ? "ftp" : "sftp" ) + db_type => 'string', + hint => 'ftp|sftp', + pattern => qr|^([tz])|i, + format => q( $1 =~ /^f/ ? 'ftp' : 'sftp' ) }, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_HOST", - default => "", - description => "The remote server to upload to", - help => qqq(" + name => 'ZM_UPLOAD_FTP_HOST', + default => '', + description => 'The remote server to upload to', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the name, or ip address, of the server to use. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{hostname}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_HOST", - default => "", - description => "The remote server to upload events to", - help => qqq(" + name => 'ZM_UPLOAD_HOST', + default => '', + description => 'The remote server to upload events to', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. This option indicates the name, or ip address, of the server to use. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{hostname}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_PORT", - default => "", - description => "The port on the remote upload server, if not the default (SFTP only)", - help => qqq(" + name => 'ZM_UPLOAD_PORT', + default => '', + description => 'The port on the remote upload server, if not the default (SFTP only)', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. If you are using the SFTP protocol then this option allows you to specify a particular port to use for connection. If this option is left blank then the default, port 22, is used. This option is ignored for FTP uploads. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{integer}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_USER", - default => "", - description => "Your ftp username", - help => qqq(" + name => 'ZM_UPLOAD_FTP_USER', + default => '', + description => 'Your ftp username', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the username that ZoneMinder should use to log in for ftp transfer. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{alphanum}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_USER", - default => "", - description => "Remote server username", - help => qqq(" + name => 'ZM_UPLOAD_USER', + default => '', + description => 'Remote server username', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. This option indicates the username that ZoneMinder should use to log in for transfer. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{alphanum}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_PASS", - default => "", - description => "Your ftp password", - help => qqq(" + name => 'ZM_UPLOAD_FTP_PASS', + default => '', + description => 'Your ftp password', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the password that ZoneMinder should use to log in for ftp transfer. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{string}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_PASS", - default => "", - description => "Remote server password", - help => qqq(" + name => 'ZM_UPLOAD_PASS', + default => '', + description => 'Remote server password', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. This option indicates the password that ZoneMinder should use to log in for transfer. If you are using certificate based logins for SFTP servers you can leave this option blank. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{string}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_LOC_DIR", - default => "@ZM_TMPDIR@", - description => "The local directory in which to create upload files", - help => qqq(" + name => 'ZM_UPLOAD_FTP_LOC_DIR', + default => '@ZM_TMPDIR@', + description => 'The local directory in which to create upload files', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the local directory that ZoneMinder should use for temporary upload files. These are files that are created from events, uploaded and then deleted. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{abs_path}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_LOC_DIR", - default => "@ZM_TMPDIR@", - description => "The local directory in which to create upload files", - help => qqq(" + name => 'ZM_UPLOAD_LOC_DIR', + default => '@ZM_TMPDIR@', + description => 'The local directory in which to create upload files', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. This option indicates the local directory that ZoneMinder should use for temporary upload files. These are files that are created from events, uploaded and then deleted. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{abs_path}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_REM_DIR", - default => "", - description => "The remote directory to upload to", - help => qqq(" + name => 'ZM_UPLOAD_FTP_REM_DIR', + default => '', + description => 'The remote directory to upload to', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the remote directory that ZoneMinder should use to upload event files to. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{rel_path}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_REM_DIR", - default => "", - description => "The remote directory to upload to", - help => qqq(" + name => 'ZM_UPLOAD_REM_DIR', + default => '', + description => 'The remote directory to upload to', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. This option indicates the remote directory that ZoneMinder should use to upload event files to. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{rel_path}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_TIMEOUT", - default => "120", - description => "How long to allow the transfer to take for each file", - help => qqq(" + name => 'ZM_UPLOAD_FTP_TIMEOUT', + default => '120', + description => 'How long to allow the transfer to take for each file', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates the maximum ftp inactivity timeout (in seconds) that should be tolerated before ZoneMinder determines that the transfer has failed and closes down the connection. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{integer}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_TIMEOUT", - default => "120", - description => "How long to allow the transfer to take for each file", - help => qqq(" + name => 'ZM_UPLOAD_TIMEOUT', + default => '120', + description => 'How long to allow the transfer to take for each file', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. This option indicates the maximum inactivity timeout (in seconds) that should be tolerated before ZoneMinder determines that the transfer has failed and closes down the connection. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{integer}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_STRICT", - default => "no", - description => "Require strict host key checking for SFTP uploads", - help => qqq(" + name => 'ZM_UPLOAD_STRICT', + default => 'no', + description => 'Require strict host key checking for SFTP uploads', + help => q` You can require SFTP uploads to verify the host key of the remote server for protection against man-in-the-middle attacks. You will need to add the server's key to the known_hosts file. On most systems, this will be ~/.ssh/known_hosts, where ~ is the home directory of the web server running ZoneMinder. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_PASSIVE", - default => "yes", - description => "Use passive ftp when uploading", - help => qqq(" + name => 'ZM_UPLOAD_FTP_PASSIVE', + default => 'yes', + description => 'Use passive ftp when uploading', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. This option indicates that ftp transfers should be done in passive mode. This uses a single connection for all ftp activity and, whilst slower than active transfers, is more robust and likely to work from behind filewalls. This option is ignored for SFTP transfers. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - help => qqq(" + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], + help => q` If your computer is behind a firewall or proxy you may need to set FTP to passive mode. In fact for simple transfers it makes little sense to do otherwise anyway but you can set this to 'No' if you wish. - "), + `, type => $types{boolean}, - category => "upload", + category => 'upload', }, { - name => "ZM_UPLOAD_FTP_DEBUG", - default => "no", - description => "Switch ftp debugging on", - help => qqq(" + name => 'ZM_UPLOAD_FTP_DEBUG', + default => 'no', + description => 'Switch ftp debugging on', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote ftp server. If you are having (or expecting) troubles with uploading events then setting this to 'yes' permits additional information to be included in the zmfilter log file. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_UPLOAD_DEBUG", - default => "no", - description => "Switch upload debugging on", - help => qqq(" + name => 'ZM_UPLOAD_DEBUG', + default => 'no', + description => 'Switch upload debugging on', + help => q` You can use filters to instruct ZoneMinder to upload events to a remote server. If you are having (or expecting) troubles with uploading events then setting this to 'yes' permits additional information to be generated by the underlying transfer modules and included in the logs. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + `, + requires => [ { name => 'ZM_OPT_UPLOAD', value => 'yes' } ], type => $types{boolean}, - category => "upload", + category => 'upload', }, { - name => "ZM_OPT_EMAIL", - default => "no", - description => "Should ZoneMinder email you details of events that match corresponding filters", - help => qqq(" + name => 'ZM_OPT_EMAIL', + default => 'no', + description => 'Should ZoneMinder email you details of events that match corresponding filters', + help => q` In ZoneMinder you can create event filters that specify whether events that match certain criteria should have their details emailed to you at a designated email address. This will allow @@ -2193,24 +2220,24 @@ our @options = ( this functionality should be available. The email created with this option can be any size and is intended to be sent to a regular email reader rather than a mobile device. - "), + `, type => $types{boolean}, - category => "mail", + category => 'mail', }, { - name => "ZM_EMAIL_ADDRESS", - default => "", - description => "The email address to send matching event details to", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" + name => 'ZM_EMAIL_ADDRESS', + default => '', + description => 'The email address to send matching event details to', + requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], + help => q` This option is used to define the email address that any events that match the appropriate filters will be sent to. - "), + `, type => $types{email}, - category => "mail", + category => 'mail', }, { - name => "ZM_EMAIL_TEXT", + name => 'ZM_EMAIL_TEXT', default => 'subject = "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)" body = " Hello, @@ -2228,29 +2255,29 @@ our @options = ( This alarm was matched by the %FN% filter and can be viewed at %EPS% ZoneMinder"', - description => "The text of the email used to send matching event details", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" + description => 'The text of the email used to send matching event details', + requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], + help => q` This option is used to define the content of the email that is sent for any events that match the appropriate filters. - "), + `, type => $types{text}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_EMAIL_SUBJECT", - default => "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)", - description => "The subject of the email used to send matching event details", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" + name => 'ZM_EMAIL_SUBJECT', + default => 'ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)', + description => 'The subject of the email used to send matching event details', + requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], + help => q` This option is used to define the subject of the email that is sent for any events that match the appropriate filters. - "), + `, type => $types{string}, - category => "mail", + category => 'mail', }, { - name => "ZM_EMAIL_BODY", + name => 'ZM_EMAIL_BODY', default => " Hello, @@ -2267,20 +2294,20 @@ our @options = ( This alarm was matched by the %FN% filter and can be viewed at %EPS% ZoneMinder", - description => "The body of the email used to send matching event details", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" + description => 'The body of the email used to send matching event details', + requires => [ { name => 'ZM_OPT_EMAIL', value => 'yes' } ], + help => q` This option is used to define the content of the email that is sent for any events that match the appropriate filters. - "), + `, type => $types{text}, - category => "mail", + category => 'mail', }, { - name => "ZM_OPT_MESSAGE", - default => "no", - description => "Should ZoneMinder message you with details of events that match corresponding filters", - help => qqq(" + name => 'ZM_OPT_MESSAGE', + default => 'no', + description => 'Should ZoneMinder message you with details of events that match corresponding filters', + help => q` In ZoneMinder you can create event filters that specify whether events that match certain criteria should have their details sent to you at a designated short message email address. This @@ -2290,69 +2317,69 @@ our @options = ( is intended to be sent to an SMS gateway or a minimal mail reader such as a mobile device or phone rather than a regular email reader. - "), + `, type => $types{boolean}, - category => "mail", + category => 'mail', }, { - name => "ZM_MESSAGE_ADDRESS", - default => "", - description => "The email address to send matching event details to", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" + name => 'ZM_MESSAGE_ADDRESS', + default => '', + description => 'The email address to send matching event details to', + requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], + help => q` This option is used to define the short message email address that any events that match the appropriate filters will be sent to. - "), + `, type => $types{email}, - category => "mail", + category => 'mail', }, { - name => "ZM_MESSAGE_TEXT", - default => 'subject = "ZoneMinder: Alarm - %MN%-%EI%" - body = "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score."', - description => "The text of the message used to send matching event details", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" + name => 'ZM_MESSAGE_TEXT', + default => q`subject = "ZoneMinder: Alarm - %MN%-%EI%" + body = "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score."`, + description => 'The text of the message used to send matching event details', + requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], + help => q` This option is used to define the content of the message that is sent for any events that match the appropriate filters. - "), + `, type => $types{text}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_MESSAGE_SUBJECT", - default => "ZoneMinder: Alarm - %MN%-%EI%", - description => "The subject of the message used to send matching event details", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" + name => 'ZM_MESSAGE_SUBJECT', + default => 'ZoneMinder: Alarm - %MN%-%EI%', + description => 'The subject of the message used to send matching event details', + requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], + help => q` This option is used to define the subject of the message that is sent for any events that match the appropriate filters. - "), + `, type => $types{string}, - category => "mail", + category => 'mail', }, { - name => "ZM_MESSAGE_BODY", - default => "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.", - description => "The body of the message used to send matching event details", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" + name => 'ZM_MESSAGE_BODY', + default => 'ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.', + description => 'The body of the message used to send matching event details', + requires => [ { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], + help => q` This option is used to define the content of the message that is sent for any events that match the appropriate filters. - "), + `, type => $types{text}, - category => "mail", + category => 'mail', }, { - name => "ZM_NEW_MAIL_MODULES", - default => "no", - description => "Use a newer perl method to send emails", + name => 'ZM_NEW_MAIL_MODULES', + default => 'no', + description => 'Use a newer perl method to send emails', requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], - help => qqq(" + help => q` Traditionally ZoneMinder has used the MIME::Entity perl module to construct and send notification emails and messages. Some people have reported problems with this module not being @@ -2362,69 +2389,69 @@ our @options = ( method was contributed by Ross Melin and should work for everyone but has not been extensively tested so currently is not selected by default. - "), + `, type => $types{boolean}, - category => "mail", + category => 'mail', }, { - name => "ZM_EMAIL_HOST", - default => "localhost", - description => "The host address of your SMTP mail server", + name => 'ZM_EMAIL_HOST', + default => 'localhost', + description => 'The host address of your SMTP mail server', requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], - help => qqq(" + help => q` If you have chosen SMTP as the method by which to send notification emails or messages then this option allows you to choose which SMTP server to use to send them. The default of localhost may work if you have the sendmail, exim or a similar daemon running however you may wish to enter your ISP's SMTP mail server here. - "), + `, type => $types{hostname}, - category => "mail", + category => 'mail', }, { - name => "ZM_FROM_EMAIL", - default => "", - description => "The email address you wish your event notifications to originate from", + name => 'ZM_FROM_EMAIL', + default => '', + description => 'The email address you wish your event notifications to originate from', requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], - help => qqq(" + help => q` The emails or messages that will be sent to you informing you of events can appear to come from a designated email address to help you with mail filtering etc. An address of something like ZoneMinder\@your.domain is recommended. - "), + `, type => $types{email}, - category => "mail", + category => 'mail', }, { - name => "ZM_URL", - default => "", - description => "The URL of your ZoneMinder installation", + name => 'ZM_URL', + default => '', + description => 'The URL of your ZoneMinder installation', requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' } ], - help => qqq(" + help => q` The emails or messages that will be sent to you informing you of events can include a link to the events themselves for easy viewing. If you intend to use this feature then set this option to the url of your installation as it would appear from where you read your email, e.g. http://host.your.domain/zm.php. - "), + `, type => $types{url}, - category => "mail", + category => 'mail', }, { - name => "ZM_MAX_RESTART_DELAY", - default => "600", - description => "Maximum delay (in seconds) for daemon restart attempts.", - help => qqq(" + name => 'ZM_MAX_RESTART_DELAY', + default => '600', + description => 'Maximum delay (in seconds) for daemon restart attempts.', + help => q` The zmdc (zm daemon control) process controls when processeses are started or stopped and will attempt to restart any that fail. If a daemon fails frequently then a delay is introduced @@ -2432,28 +2459,28 @@ our @options = ( this delay is increased to prevent extra load being placed on the system by continual restarts. This option controls what this maximum delay is. - "), + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_WATCH_CHECK_INTERVAL", - default => "10", - description => "How often to check the capture daemons have not locked up", - help => qqq(" + name => 'ZM_WATCH_CHECK_INTERVAL', + default => '10', + description => 'How often to check the capture daemons have not locked up', + help => q` The zmwatch daemon checks the image capture performance of the capture daemons to ensure that they have not locked up (rarely a sync error may occur which blocks indefinitely). This option determines how often the daemons are checked. - "), + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_WATCH_MAX_DELAY", - default => "5", - description => "The maximum delay allowed since the last captured image", - help => qqq(" + name => 'ZM_WATCH_MAX_DELAY', + default => '5', + description => 'The maximum delay allowed since the last captured image', + help => q` The zmwatch daemon checks the image capture performance of the capture daemons to ensure that they have not locked up (rarely a sync error may occur which blocks indefinitely). This option @@ -2462,16 +2489,15 @@ our @options = ( images after this period though the actual restart may take slightly longer in conjunction with the check interval value above. - "), + `, type => $types{decimal}, - category => "system", + category => 'system', }, { - - name => "ZM_RUN_AUDIT", - default => "yes", - description => "Run zmaudit to check data consistency", - help => qqq(" + name => 'ZM_RUN_AUDIT', + default => 'yes', + description => 'Run zmaudit to check data consistency', + help => q` The zmaudit daemon exists to check that the saved information in the database and on the filesystem match and are consistent with each other. If an error occurs or if you are using 'fast @@ -2486,15 +2512,15 @@ our @options = ( case you may prefer to not have zmaudit running unconditionally and schedule occasional checks at other, more convenient, times. - "), + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_AUDIT_CHECK_INTERVAL", - default => "900", - description => "How often to check database and filesystem consistency", - help => qqq(" + name => 'ZM_AUDIT_CHECK_INTERVAL', + default => '900', + description => 'How often to check database and filesystem consistency', + help => q` The zmaudit daemon exists to check that the saved information in the database and on the filesystem match and are consistent with each other. If an error occurs or if you are using 'fast @@ -2508,41 +2534,41 @@ our @options = ( to make this interval much larger to reduce the impact on your system. This option determines how often these checks are performed. - "), + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_AUDIT_MIN_AGE", - default => "86400", - description => "The minimum age in seconds event data must be in order to be deleted.", - help => qqq(" + name => 'ZM_AUDIT_MIN_AGE', + default => '86400', + description => 'The minimum age in seconds event data must be in order to be deleted.', + help => q` The zmaudit daemon exists to check that the saved information in the database and on the filesystem match and are consistent with each other. Event files or db records that are younger than this setting will not be deleted and a warning will be given. - "), + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_FORCED_ALARM_SCORE", - default => "255", - description => "Score to give forced alarms", - help => qqq(" + name => 'ZM_FORCED_ALARM_SCORE', + default => '255', + description => 'Score to give forced alarms', + help => q` The 'zmu' utility can be used to force an alarm on a monitor rather than rely on the motion detection algorithms. This option determines what score to give these alarms to distinguish them from regular ones. It must be 255 or less. - "), + `, type => $types{integer}, - category => "config", + category => 'config', }, { - name => "ZM_BULK_FRAME_INTERVAL", - default => "100", - description => "How often a bulk frame should be written to the database", - help => qqq(" + name => 'ZM_BULK_FRAME_INTERVAL', + default => '100', + description => 'How often a bulk frame should be written to the database', + help => q` Traditionally ZoneMinder writes an entry into the Frames database table for each frame that is captured and saved. This works well in motion detection scenarios but when in a DVR @@ -2556,15 +2582,15 @@ our @options = ( constant frame rate situations this is usually not significant. This setting is ignored in Modect mode and individual frames are still written if an alarm occurs in Mocord mode also. - "), + `, type => $types{integer}, - category => "config", + category => 'config', }, { - name => "ZM_EVENT_CLOSE_MODE", - default => "idle", - description => "When continuous events are closed.", - help => qqq(" + name => 'ZM_EVENT_CLOSE_MODE', + default => 'idle', + description => 'When continuous events are closed.', + help => q` When a monitor is running in a continuous recording mode (Record or Mocord) events are usually closed after a fixed period of time (the section length). However in Mocord mode it @@ -2582,25 +2608,25 @@ our @options = ( effect of limiting the number of alarms to one per event and the events will be shorter than the section length if an alarm has occurred. - "), + `, type => $types{boolean}, type => { - db_type =>"string", - hint =>"time|idle|alarm", - pattern =>qr|^([tia])|i, - format =>q( ($1 =~ /^t/) - ? "time" - : ($1 =~ /^i/ ? "idle" : "time" ) + db_type => 'string', + hint => 'time|idle|alarm', + pattern => qr|^([tia])|i, + format => q( ($1 =~ /^t/) + ? 'time' + : ($1 =~ /^i/ ? 'idle' : 'time' ) ) }, - category => "config", + category => 'config', }, - # Deprecated, superseded by event close mode +# Deprecated, superseded by event close mode { - name => "ZM_FORCE_CLOSE_EVENTS", - default => "no", - description => "Close events at section ends.", - help => qqq(" + name => 'ZM_FORCE_CLOSE_EVENTS', + default => 'no', + description => 'Close events at section ends.', + help => q` When a monitor is running in a continuous recording mode (Record or Mocord) events are usually closed after a fixed period of time (the section length). However in Mocord mode it @@ -2609,15 +2635,15 @@ our @options = ( until the motion has ceased. Switching this option on will force the event closed at the specified time regardless of any motion activity. - "), + `, type => $types{boolean}, - category => "hidden", + category => 'hidden', }, { - name => "ZM_CREATE_ANALYSIS_IMAGES", - default => "yes", - description => "Create analysed alarm images with motion outlined", - help => qqq(" + name => 'ZM_CREATE_ANALYSIS_IMAGES', + default => 'yes', + description => 'Create analysed alarm images with motion outlined', + help => q` By default during an alarm ZoneMinder records both the raw captured image and one that has been analysed and had areas where motion was detected outlined. This can be very useful @@ -2625,15 +2651,15 @@ our @options = ( However it also incurs some overhead and in a stable system may no longer be necessary. This parameter allows you to switch the generation of these images off. - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_WEIGHTED_ALARM_CENTRES", - default => "no", - description => "Use a weighted algorithm to calculate the centre of an alarm", - help => qqq(" + name => 'ZM_WEIGHTED_ALARM_CENTRES', + default => 'no', + description => 'Use a weighted algorithm to calculate the centre of an alarm', + help => q` ZoneMinder will always calculate the centre point of an alarm in a zone to give some indication of where on the screen it is. This can be used by the experimental motion tracking feature or @@ -2643,15 +2669,15 @@ our @options = ( calculated using weighted pixel locations to give more accurate positioning for irregularly shaped blobs. This method, while more precise is also slower and so is turned off by default. - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_EVENT_IMAGE_DIGITS", - default => "5", - description => "How many significant digits are used in event image numbering", - help => qqq(" + name => 'ZM_EVENT_IMAGE_DIGITS', + default => '5', + description => 'How many significant digits are used in event image numbering', + help => q` As event images are captured they are stored to the filesystem with a numerical index. By default this index has three digits so the numbers start 001, 002 etc. This works works for most @@ -2663,15 +2689,15 @@ our @options = ( events unviewable as the event will have been saved with the previous scheme. Decreasing this value should have no ill effects. - "), + `, type => $types{integer}, - category => "config", + category => 'config', }, { - name => "ZM_DEFAULT_ASPECT_RATIO", - default => "4:3", - description => "The default width:height aspect ratio used in monitors", - help => qqq(" + name => 'ZM_DEFAULT_ASPECT_RATIO', + default => '4:3', + description => 'The default width:height aspect ratio used in monitors', + help => q` When specifying the dimensions of monitors you can click a checkbox to ensure that the width stays in the correct ratio to the height, or vice versa. This setting allows you to indicate @@ -2680,28 +2706,28 @@ our @options = ( default of 4:3 normally be acceptable but 11:9 is another common setting. If the checkbox is not clicked when specifying monitor dimensions this setting has no effect. - "), + `, type => $types{string}, - category => "config", + category => 'config', }, { - name => "ZM_USER_SELF_EDIT", - default => "no", - description => "Allow unprivileged users to change their details", - help => qqq(" + name => 'ZM_USER_SELF_EDIT', + default => 'no', + description => 'Allow unprivileged users to change their details', + help => q` Ordinarily only users with system edit privilege are able to change users details. Switching this option on allows ordinary users to change their passwords and their language settings - "), + `, type => $types{boolean}, - category => "config", + category => 'config', }, { - name => "ZM_OPT_FRAME_SERVER", - default => "no", - description => "Should analysis farm out the writing of images to disk", - #requires => [ { name => "ZM_OPT_ADAPTIVE_SKIP", value => "yes" } ], - help => qqq(" + name => 'ZM_OPT_FRAME_SERVER', + default => 'no', + description => 'Should analysis farm out the writing of images to disk', +#requires => [ { name => 'ZM_OPT_ADAPTIVE_SKIP', value => 'yes' } ], + help => q` In some circumstances it is possible for a slow disk to take so long writing images to disk that it causes the analysis daemon to fall behind especially during high frame rate events. @@ -2711,16 +2737,16 @@ our @options = ( daemon to get on with other things. Should this transmission fail or other permanent or transient error occur, this function will fall back to the analysis daemon. - "), + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_FRAME_SOCKET_SIZE", - default => "0", - description => "Specify the frame server socket buffer size if non-standard", - requires => [ { name => "ZM_OPT_FRAME_SERVER", value => "yes" } ], - help => qqq(" + name => 'ZM_FRAME_SOCKET_SIZE', + default => '0', + description => 'Specify the frame server socket buffer size if non-standard', + requires => [ { name => 'ZM_OPT_FRAME_SERVER', value => 'yes' } ], + help => q` For large captured images it is possible for the writes from the analysis daemon to the frame server to fail as the amount to be written exceeds the default buffer size. While the images @@ -2733,43 +2759,43 @@ our @options = ( be set. Alternatively you can change the default buffer size on your system in the same way in which case that will be used with no change necessary in this option - "), + `, type => $types{integer}, - category => "system", + category => 'system', }, { - name => "ZM_OPT_CONTROL", - default => "no", - description => "Support controllable (e.g. PTZ) cameras", - help => qqq(" + name => 'ZM_OPT_CONTROL', + default => 'no', + description => 'Support controllable (e.g. PTZ) cameras', + help => q` ZoneMinder includes limited support for controllable cameras. A number of sample protocols are included and others can easily be added. If you wish to control your cameras via ZoneMinder then select this option otherwise if you only have static cameras or use other control methods then leave this option off. - "), + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_OPT_TRIGGERS", - default => "no", - description => "Interface external event triggers via socket or device files", - help => qqq(" + name => 'ZM_OPT_TRIGGERS', + default => 'no', + description => 'Interface external event triggers via socket or device files', + help => q` ZoneMinder can interact with external systems which prompt or cancel alarms. This is done via the zmtrigger.pl script. This option indicates whether you want to use these external triggers. Most people will say no here. - "), + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_CHECK_FOR_UPDATES", - default => "yes", - description => "Check with zoneminder.com for updated versions", - help => qqq(" + name => 'ZM_CHECK_FOR_UPDATES', + default => 'yes', + description => 'Check with zoneminder.com for updated versions', + help => q` From ZoneMinder version 1.17.0 onwards new versions are expected to be more frequent. To save checking manually for each new version ZoneMinder can check with the zoneminder.com @@ -2779,15 +2805,15 @@ our @options = ( number. If you do not wish these checks to take place or your ZoneMinder system has no internet access you can switch these check off with this configuration variable - "), + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_TELEMETRY_DATA", - default => "yes", - description => "Send usage information to ZoneMinder", - help => qqq(" + name => 'ZM_TELEMETRY_DATA', + default => 'yes', + description => 'Send usage information to ZoneMinder', + help => q` Enable collection of usage information of the local system and send it to the ZoneMinder development team. This data will be used to determine things like who and where our customers are, how big their @@ -2796,49 +2822,65 @@ our @options = ( product for our target audience. This script is intended to be completely transparent to the end user, and can be disabled from the web console under Options. - "), + `, type => $types{boolean}, - category => "system", + category => 'system', }, { - name => "ZM_TELEMETRY_UUID", - default => "", - description => "Unique identifier for ZoneMinder telemetry", - help => qqq(" + name => 'ZM_TELEMETRY_UUID', + default => '', + description => 'Unique identifier for ZoneMinder telemetry', + help => q` This variable is auto-generated once by the system and is used to uniquely identify it among all other ZoneMinder systems in existence. - "), + `, type => $types{string}, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_TELEMETRY_LAST_UPLOAD", - default => "", - description => "When the last ZoneMinder telemetry upload ocurred", - help => "", + name => 'ZM_TELEMETRY_LAST_UPLOAD', + default => '', + description => 'When the last ZoneMinder telemetry upload ocurred', + help => '', type => $types{integer}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_UPDATE_CHECK_PROXY", - default => "", - description => "Proxy url if required to access zoneminder.com", - help => qqq(" + name => 'ZM_TELEMETRY_INTERVAL', + default => '14*24*60*60', + description => 'Interval in seconds between telemetry updates.', + help => 'This value can be expressed as a mathematical expression for ease.', + type => $types{string}, + category => 'system', + }, + { + name => 'ZM_TELEMETRY_SERVER_ENDPOINT', + default => 'https://zmanon:2b2d0b4skps@telemetry.zoneminder.com/zmtelemetry/testing5', + description => 'URL that ZoneMinder will send usage data to', + help => '', + type => $types{url}, + category => 'hidden', + }, + { + name => 'ZM_UPDATE_CHECK_PROXY', + default => '', + description => 'Proxy url if required to access zoneminder.com', + help => q` If you use a proxy to access the internet then ZoneMinder needs to know so it can access zoneminder.com to check for updates. If you do use a proxy enter the full proxy url here in the form of http://:/ - "), + `, type => $types{string}, - category => "system", + category => 'system', }, { - name => "ZM_SHM_KEY", - default => "0x7a6d0000", - description => "Shared memory root key to use", - help => qqq(" + name => 'ZM_SHM_KEY', + default => '0x7a6d0000', + description => 'Shared memory root key to use', + help => q` ZoneMinder uses shared memory to speed up communication between modules. To identify the right area to use shared memory keys are used. This option controls what the base key is, each @@ -2847,16 +2889,16 @@ our @options = ( clashes with another instance of ZoneMinder on the same machine. Only the first four hex digits are used, the lower four will be masked out and ignored. - "), + `, type => $types{hexadecimal}, - category => "system", + category => 'system', }, - # Deprecated, really no longer necessary +# Deprecated, really no longer necessary { - name => "ZM_WEB_REFRESH_METHOD", - default => "javascript", - description => "What method windows should use to refresh themselves", - help => qqq(" + name => 'ZM_WEB_REFRESH_METHOD', + default => 'javascript', + description => 'What method windows should use to refresh themselves', + help => q` Many windows in Javascript need to refresh themselves to keep their information current. This option determines what method they should use to do this. Choosing 'javascript' means that @@ -2867,43 +2909,43 @@ our @options = ( interrupted or cancelled when a link in the window is clicked meaning that the window will no longer refresh and this would have to be done manually. - "), + `, type => { - db_type =>"string", - hint =>"javascript|http", - pattern =>qr|^([jh])|i, - format =>q( $1 =~ /^j/ - ? "javascript" - : "http" + db_type => 'string', + hint => 'javascript|http', + pattern => qr|^([jh])|i, + format => q( $1 =~ /^j/ + ? 'javascript' + : 'http' ) }, - category => "hidden", + category => 'hidden', }, { - name => "ZM_WEB_EVENT_SORT_FIELD", - default => "DateTime", - description => "Default field the event lists are sorted by", - help => qqq(" + name => 'ZM_WEB_EVENT_SORT_FIELD', + default => 'DateTime', + description => 'Default field the event lists are sorted by', + help => q` Events in lists can be initially ordered in any way you want. This option controls what field is used to sort them. You can modify this ordering from filters or by clicking on headings in the lists themselves. Bear in mind however that the 'Prev' and 'Next' links, when scrolling through events, relate to the ordering in the lists and so not always to time based ordering. - "), + `, type => { - db_type =>"string", - hint =>"Id|Name|Cause|MonitorName|DateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore", - pattern =>qr|.|, - format =>q( $1 ) + db_type => 'string', + hint => 'Id|Name|Cause|MonitorName|DateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore', + pattern => qr|.|, + format => q( $1 ) }, - category => "web", + category => 'web', }, { - name => "ZM_WEB_EVENT_SORT_ORDER", - default => "asc", - description => "Default order the event lists are sorted by", - help => qqq(" + name => 'ZM_WEB_EVENT_SORT_ORDER', + default => 'asc', + description => 'Default order the event lists are sorted by', + help => q` Events in lists can be initially ordered in any way you want. This option controls what order (ascending or descending) is used to sort them. You can modify this ordering from filters or @@ -2911,47 +2953,47 @@ our @options = ( however that the 'Prev' and 'Next' links, when scrolling through events, relate to the ordering in the lists and so not always to time based ordering. - "), + `, type => { - db_type =>"string", - hint =>"asc|desc", - pattern =>qr|^([ad])|i, - format =>q( $1 =~ /^a/i ? "asc" : "desc" ) + db_type => 'string', + hint => 'asc|desc', + pattern => qr|^([ad])|i, + format => q( $1 =~ /^a/i ? 'asc' : 'desc' ) }, - category => "web", + category => 'web', }, { - name => "ZM_WEB_EVENTS_PER_PAGE", - default => "25", - description => "How many events to list per page in paged mode", - help => qqq(" + name => 'ZM_WEB_EVENTS_PER_PAGE', + default => '25', + description => 'How many events to list per page in paged mode', + help => q` In the event list view you can either list all events or just a page at a time. This option controls how many events are listed per page in paged mode and how often to repeat the column headers in non-paged mode. - "), + `, type => $types{integer}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_LIST_THUMBS", - default => "no", - description => "Display mini-thumbnails of event images in event lists", - help => qqq(" + name => 'ZM_WEB_LIST_THUMBS', + default => 'no', + description => 'Display mini-thumbnails of event images in event lists', + help => q` Ordinarily the event lists just display text details of the events to save space and time. By switching this option on you can also display small thumbnails to help you identify events of interest. The size of these thumbnails is controlled by the following two options. - "), + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_LIST_THUMB_WIDTH", - default => "48", - description => "The width of the thumbnails that appear in the event lists", - help => qqq(" + name => 'ZM_WEB_LIST_THUMB_WIDTH', + default => '48', + description => 'The width of the thumbnails that appear in the event lists', + help => q` This options controls the width of the thumbnail images that appear in the event lists. It should be fairly small to fit in with the rest of the table. If you prefer you can specify a @@ -2959,16 +3001,16 @@ our @options = ( of the width or height and the other option should be set to zero. If both width and height are specified then width will be used and height ignored. - "), + `, type => $types{integer}, - requires => [ { name => "ZM_WEB_LIST_THUMBS", value => "yes" } ], - category => "web", + requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ], + category => 'web', }, { - name => "ZM_WEB_LIST_THUMB_HEIGHT", - default => "0", - description => "The height of the thumbnails that appear in the event lists", - help => qqq(" + name => 'ZM_WEB_LIST_THUMB_HEIGHT', + default => '0', + description => 'The height of the thumbnails that appear in the event lists', + help => q` This options controls the height of the thumbnail images that appear in the event lists. It should be fairly small to fit in with the rest of the table. If you prefer you can specify a @@ -2976,16 +3018,16 @@ our @options = ( one of the width or height and the other option should be set to zero. If both width and height are specified then width will be used and height ignored. - "), + `, type => $types{integer}, - requires => [ { name => "ZM_WEB_LIST_THUMBS", value => "yes" } ], - category => "web", + requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ], + category => 'web', }, { - name => "ZM_WEB_USE_OBJECT_TAGS", - default => "yes", - description => "Wrap embed in object tags for media content", - help => qqq(" + name => 'ZM_WEB_USE_OBJECT_TAGS', + default => 'yes', + description => 'Wrap embed in object tags for media content', + help => q` There are two methods of including media content in web pages. The most common way is use the EMBED tag which is able to give some indication of the type of content. However this is not a @@ -3000,14 +3042,14 @@ our @options = ( circumstances but they may become more widespread in the future. It is suggested that you leave this option on unless you encounter problems playing some content. - "), + `, type => $types{boolean}, - category => "web", + category => 'web', }, { - name => "ZM_WEB_H_REFRESH_MAIN", - default => "60", - introduction => qqq(" + name => 'ZM_WEB_H_REFRESH_MAIN', + default => '60', + introduction => q` There are now a number of options that are grouped into bandwidth categories, this allows you to configure the ZoneMinder client to work optimally over the various access @@ -3017,71 +3059,71 @@ our @options = ( the ZoneMinder client over a local network or high speed link. In most cases the default values will be suitable as a starting point. - "), - description => "How often (in seconds) the main console window should refresh itself", - help => qqq(" + `, + description => 'How often (in seconds) the main console window should refresh itself', + help => q` The main console window lists a general status and the event totals for all monitors. This is not a trivial task and should not be repeated too frequently or it may affect the performance of the rest of the system. - "), + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_REFRESH_CYCLE", - default => "10", - description => "How often (in seconds) the cycle watch window swaps to the next monitor", - help => qqq(" + name => 'ZM_WEB_H_REFRESH_CYCLE', + default => '10', + description => 'How often (in seconds) the cycle watch window swaps to the next monitor', + help => q` The cycle watch window is a method of continuously cycling between images from all of your monitors. This option determines how often to refresh with a new image. - "), + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_REFRESH_IMAGE", - default => "3", - description => "How often (in seconds) the watched image is refreshed (if not streaming)", - help => qqq(" + name => 'ZM_WEB_H_REFRESH_IMAGE', + default => '3', + description => 'How often (in seconds) the watched image is refreshed (if not streaming)', + help => q` The live images from a monitor can be viewed in either streamed or stills mode. This option determines how often a stills image is refreshed, it has no effect if streaming is selected. - "), + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_REFRESH_STATUS", - default => "1", - description => "How often (in seconds) the status refreshes itself in the watch window", - help => qqq(" + name => 'ZM_WEB_H_REFRESH_STATUS', + default => '1', + description => 'How often (in seconds) the status refreshes itself in the watch window', + help => q` The monitor window is actually made from several frames. The one in the middle merely contains a monitor status which needs to refresh fairly frequently to give a true indication. This option determines that frequency. - "), + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_REFRESH_EVENTS", - default => "5", - description => "How often (in seconds) the event listing is refreshed in the watch window", - help => qqq(" + name => 'ZM_WEB_H_REFRESH_EVENTS', + default => '5', + description => 'How often (in seconds) the event listing is refreshed in the watch window', + help => q` The monitor window is actually made from several frames. The lower framme contains a listing of the last few events for easy access. This option determines how often this is refreshed. - "), + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_CAN_STREAM", - default => "auto", - description => "Override the automatic detection of browser streaming capability", - help => qqq(" + name => 'ZM_WEB_H_CAN_STREAM', + default => 'auto', + description => 'Override the automatic detection of browser streaming capability', + help => q` If you know that your browser can handle image streams of the type 'multipart/x-mixed-replace' but ZoneMinder does not detect this correctly you can set this option to ensure that the @@ -3089,15 +3131,15 @@ our @options = ( plugin. Selecting 'yes' will tell ZoneMinder that your browser can handle the streams natively, 'no' means that it can't and so the plugin will be used while 'auto' lets ZoneMinder decide. - "), + `, type => $types{tristate}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_STREAM_METHOD", - default => "jpeg", - description => "Which method should be used to send video streams to your browser.", - help => qqq(" + name => 'ZM_WEB_H_STREAM_METHOD', + default => 'jpeg', + description => 'Which method should be used to send video streams to your browser.', + help => q` ZoneMinder can be configured to use either mpeg encoded video or a series or still jpeg images when sending video streams. This option defines which is used. If you choose mpeg you @@ -3105,20 +3147,20 @@ our @options = ( on your browser whereas choosing jpeg will work natively on Mozilla and related browsers and with a Java applet on Internet Explorer - "), + `, type => { - db_type =>"string", - hint =>"mpeg|jpeg", - pattern =>qr|^([mj])|i, - format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) + db_type => 'string', + hint => 'mpeg|jpeg', + pattern => qr|^([mj])|i, + format => q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) }, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_DEFAULT_SCALE", - default => "100", - description => "What the default scaling factor applied to 'live' or 'event' views is (%)", - help => qqq(" + name => 'ZM_WEB_H_DEFAULT_SCALE', + default => '100', + description => q`What the default scaling factor applied to 'live' or 'event' views is (%)`, + help => q` Normally ZoneMinder will display 'live' or 'event' streams in their native size. However if you have monitors with large dimensions or a slow link you may prefer to reduce this size, @@ -3126,40 +3168,40 @@ our @options = ( options lets you specify what the default scaling factor will be. It is expressed as a percentage so 100 is normal size, 200 is double size etc. - "), + `, type => { - db_type =>"integer", - hint =>"25|33|50|75|100|150|200|300|400", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) + db_type => 'integer', + hint => '25|33|50|75|100|150|200|300|400', + pattern => qr|^(\d+)$|, + format => q( $1 ) }, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_DEFAULT_RATE", - default => "100", - description => "What the default replay rate factor applied to 'event' views is (%)", - help => qqq(" + name => 'ZM_WEB_H_DEFAULT_RATE', + default => '100', + description => q`What the default replay rate factor applied to 'event' views is (%)`, + help => q` Normally ZoneMinder will display 'event' streams at their native rate, i.e. as close to real-time as possible. However if you have long events it is often convenient to replay them at a faster rate for review. This option lets you specify what the default replay rate will be. It is expressed as a percentage so 100 is normal rate, 200 is double speed etc. - "), + `, type => { - db_type =>"integer", - hint =>"25|50|100|150|200|400|1000|2500|5000|10000", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) + db_type => 'integer', + hint => '25|50|100|150|200|400|1000|2500|5000|10000', + pattern => qr|^(\d+)$|, + format => q( $1 ) }, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_VIDEO_BITRATE", - default => "150000", - description => "What the bitrate of the video encoded stream should be set to", - help => qqq(" + name => 'ZM_WEB_H_VIDEO_BITRATE', + default => '150000', + description => 'What the bitrate of the video encoded stream should be set to', + help => q` When encoding real video via the ffmpeg library a bit rate can be specified which roughly corresponds to the available bandwidth used for the stream. This setting effectively @@ -3171,15 +3213,15 @@ our @options = ( that the video is produced at. A higher frame rate at a particular bit rate result in individual frames being at a lower quality. - "), + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_VIDEO_MAXFPS", - default => "30", - description => "What the maximum frame rate for streamed video should be", - help => qqq(" + name => 'ZM_WEB_H_VIDEO_MAXFPS', + default => '30', + description => 'What the maximum frame rate for streamed video should be', + help => q` When using streamed video the main control is the bitrate which determines how much data can be transmitted. However a lower bitrate at high frame rates results in a lower quality image. @@ -3194,15 +3236,15 @@ our @options = ( option to 10fps then the video is not produced at 10fps, but rather at 7.5fps (15 divided by 2) as the final frame rate must be the original divided by a power of 2. - "), + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_SCALE_THUMBS", - default => "no", - description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", - help => qqq(" + name => 'ZM_WEB_H_SCALE_THUMBS', + default => 'no', + description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', + help => q` If unset, this option sends the whole image to the browser which resizes it in the window. If set the image is scaled down on the server before sending a reduced size image to the @@ -3210,34 +3252,34 @@ our @options = ( Note that ZM can only perform the resizing if the appropriate PHP graphics functionality is installed. This is usually available in the php-gd package. - "), + `, type => $types{boolean}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_EVENTS_VIEW", - default => "events", - description => "What the default view of multiple events should be.", - help => qqq(" + name => 'ZM_WEB_H_EVENTS_VIEW', + default => 'events', + description => 'What the default view of multiple events should be.', + help => q` Stored events can be viewed in either an events list format or in a timeline based one. This option sets the default view that will be used. Choosing one view here does not prevent the other view being used as it will always be selectable from whichever view is currently being used. - "), + `, type => { - db_type =>"string", - hint =>"events|timeline", - pattern =>qr|^([lt])|i, - format =>q( $1 =~ /^e/ ? "events" : "timeline" ) + db_type => 'string', + hint => 'events|timeline', + pattern => qr|^([lt])|i, + format => q( $1 =~ /^e/ ? 'events' : 'timeline' ) }, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_SHOW_PROGRESS", - default => "yes", - description => "Show the progress of replay in event view.", - help => qqq(" + name => 'ZM_WEB_H_SHOW_PROGRESS', + default => 'yes', + description => 'Show the progress of replay in event view.', + help => q` When viewing events an event navigation panel and progress bar is shown below the event itself. This allows you to jump to specific points in the event, but can can also dynamically @@ -3248,15 +3290,15 @@ our @options = ( replay. This option allows you to turn off the progress display, whilst still keeping the navigation aspect, where bandwidth prevents it functioning effectively. - "), + `, type => $types{boolean}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_H_AJAX_TIMEOUT", - default => "3000", - description => "How long to wait for Ajax request responses (ms)", - help => qqq(" + name => 'ZM_WEB_H_AJAX_TIMEOUT', + default => '3000', + description => 'How long to wait for Ajax request responses (ms)', + help => q` The newer versions of the live feed and event views use Ajax to request information from the server and populate the views dynamically. This option allows you to specify a timeout if @@ -3267,84 +3309,84 @@ our @options = ( should timeout so this setting should be set to a value greater than the slowest expected response. This value is in milliseconds but if set to zero then no timeout will be used. - "), + `, type => $types{integer}, - category => "highband", + category => 'highband', }, { - name => "ZM_WEB_M_REFRESH_MAIN", - default => "300", - description => "How often (in seconds) the main console window should refresh itself", - help => qqq(" + name => 'ZM_WEB_M_REFRESH_MAIN', + default => '300', + description => 'How often (in seconds) the main console window should refresh itself', + help => q` The main console window lists a general status and the event totals for all monitors. This is not a trivial task and should not be repeated too frequently or it may affect the performance of the rest of the system. - "), + `, type => $types{integer}, - introduction => qqq(" + introduction => q` The next few options control what happens when the client is running in 'medium' bandwidth mode. You should set these options for when accessing the ZoneMinder client over a slower cable or DSL link. In most cases the default values will be suitable as a starting point. - "), - category => "medband", + `, + category => 'medband', }, { - name => "ZM_WEB_M_REFRESH_CYCLE", - default => "20", - description => "How often (in seconds) the cycle watch window swaps to the next monitor", - help => qqq(" + name => 'ZM_WEB_M_REFRESH_CYCLE', + default => '20', + description => 'How often (in seconds) the cycle watch window swaps to the next monitor', + help => q` The cycle watch window is a method of continuously cycling between images from all of your monitors. This option determines how often to refresh with a new image. - "), + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_REFRESH_IMAGE", - default => "10", - description => "How often (in seconds) the watched image is refreshed (if not streaming)", - help => qqq(" + name => 'ZM_WEB_M_REFRESH_IMAGE', + default => '10', + description => 'How often (in seconds) the watched image is refreshed (if not streaming)', + help => q` The live images from a monitor can be viewed in either streamed or stills mode. This option determines how often a stills image is refreshed, it has no effect if streaming is selected. - "), + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_REFRESH_STATUS", - default => "5", - description => "How often (in seconds) the status refreshes itself in the watch window", - help => qqq(" + name => 'ZM_WEB_M_REFRESH_STATUS', + default => '5', + description => 'How often (in seconds) the status refreshes itself in the watch window', + help => q` The monitor window is actually made from several frames. The one in the middle merely contains a monitor status which needs to refresh fairly frequently to give a true indication. This option determines that frequency. - "), + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_REFRESH_EVENTS", - default => "60", - description => "How often (in seconds) the event listing is refreshed in the watch window", - help => qqq(" + name => 'ZM_WEB_M_REFRESH_EVENTS', + default => '60', + description => 'How often (in seconds) the event listing is refreshed in the watch window', + help => q` The monitor window is actually made from several frames. The lower framme contains a listing of the last few events for easy access. This option determines how often this is refreshed. - "), + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_CAN_STREAM", - default => "auto", - description => "Override the automatic detection of browser streaming capability", - help => qqq(" + name => 'ZM_WEB_M_CAN_STREAM', + default => 'auto', + description => 'Override the automatic detection of browser streaming capability', + help => q` If you know that your browser can handle image streams of the type 'multipart/x-mixed-replace' but ZoneMinder does not detect this correctly you can set this option to ensure that the @@ -3352,15 +3394,15 @@ our @options = ( plugin. Selecting 'yes' will tell ZoneMinder that your browser can handle the streams natively, 'no' means that it can't and so the plugin will be used while 'auto' lets ZoneMinder decide. - "), + `, type => $types{tristate}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_STREAM_METHOD", - default => "jpeg", - description => "Which method should be used to send video streams to your browser.", - help => qqq(" + name => 'ZM_WEB_M_STREAM_METHOD', + default => 'jpeg', + description => 'Which method should be used to send video streams to your browser.', + help => q` ZoneMinder can be configured to use either mpeg encoded video or a series or still jpeg images when sending video streams. This option defines which is used. If you choose mpeg you @@ -3368,20 +3410,20 @@ our @options = ( on your browser whereas choosing jpeg will work natively on Mozilla and related browsers and with a Java applet on Internet Explorer - "), + `, type => { - db_type =>"string", - hint =>"mpeg|jpeg", - pattern =>qr|^([mj])|i, - format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) + db_type => 'string', + hint => 'mpeg|jpeg', + pattern => qr|^([mj])|i, + format => q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) }, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_DEFAULT_SCALE", - default => "100", - description => "What the default scaling factor applied to 'live' or 'event' views is (%)", - help => qqq(" + name => 'ZM_WEB_M_DEFAULT_SCALE', + default => '100', + description => q`What the default scaling factor applied to 'live' or 'event' views is (%)`, + help => q` Normally ZoneMinder will display 'live' or 'event' streams in their native size. However if you have monitors with large dimensions or a slow link you may prefer to reduce this size, @@ -3389,40 +3431,40 @@ our @options = ( options lets you specify what the default scaling factor will be. It is expressed as a percentage so 100 is normal size, 200 is double size etc. - "), + `, type => { - db_type =>"integer", - hint =>"25|33|50|75|100|150|200|300|400", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) + db_type => 'integer', + hint => '25|33|50|75|100|150|200|300|400', + pattern => qr|^(\d+)$|, + format => q( $1 ) }, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_DEFAULT_RATE", - default => "100", - description => "What the default replay rate factor applied to 'event' views is (%)", - help => qqq(" + name => 'ZM_WEB_M_DEFAULT_RATE', + default => '100', + description => q`What the default replay rate factor applied to 'event' views is (%)`, + help => q` Normally ZoneMinder will display 'event' streams at their native rate, i.e. as close to real-time as possible. However if you have long events it is often convenient to replay them at a faster rate for review. This option lets you specify what the default replay rate will be. It is expressed as a percentage so 100 is normal rate, 200 is double speed etc. - "), + `, type => { - db_type =>"integer", - hint =>"25|50|100|150|200|400|1000|2500|5000|10000", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) + db_type => 'integer', + hint => '25|50|100|150|200|400|1000|2500|5000|10000', + pattern => qr|^(\d+)$|, + format => q( $1 ) }, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_VIDEO_BITRATE", - default => "75000", - description => "What the bitrate of the video encoded stream should be set to", - help => qqq(" + name => 'ZM_WEB_M_VIDEO_BITRATE', + default => '75000', + description => 'What the bitrate of the video encoded stream should be set to', + help => q` When encoding real video via the ffmpeg library a bit rate can be specified which roughly corresponds to the available bandwidth used for the stream. This setting effectively @@ -3434,15 +3476,15 @@ our @options = ( that the video is produced at. A higher frame rate at a particular bit rate result in individual frames being at a lower quality. - "), + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_VIDEO_MAXFPS", - default => "10", - description => "What the maximum frame rate for streamed video should be", - help => qqq(" + name => 'ZM_WEB_M_VIDEO_MAXFPS', + default => '10', + description => 'What the maximum frame rate for streamed video should be', + help => q` When using streamed video the main control is the bitrate which determines how much data can be transmitted. However a lower bitrate at high frame rates results in a lower quality image. @@ -3457,15 +3499,15 @@ our @options = ( option to 10fps then the video is not produced at 10fps, but rather at 7.5fps (15 divided by 2) as the final frame rate must be the original divided by a power of 2. - "), + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_SCALE_THUMBS", - default => "yes", - description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", - help => qqq(" + name => 'ZM_WEB_M_SCALE_THUMBS', + default => 'yes', + description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', + help => q` If unset, this option sends the whole image to the browser which resizes it in the window. If set the image is scaled down on the server before sending a reduced size image to the @@ -3473,34 +3515,34 @@ our @options = ( Note that ZM can only perform the resizing if the appropriate PHP graphics functionality is installed. This is usually available in the php-gd package. - "), + `, type => $types{boolean}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_EVENTS_VIEW", - default => "events", - description => "What the default view of multiple events should be.", - help => qqq(" + name => 'ZM_WEB_M_EVENTS_VIEW', + default => 'events', + description => 'What the default view of multiple events should be.', + help => q` Stored events can be viewed in either an events list format or in a timeline based one. This option sets the default view that will be used. Choosing one view here does not prevent the other view being used as it will always be selectable from whichever view is currently being used. - "), + `, type => { - db_type =>"string", - hint =>"events|timeline", - pattern =>qr|^([lt])|i, - format =>q( $1 =~ /^e/ ? "events" : "timeline" ) + db_type => 'string', + hint => 'events|timeline', + pattern => qr|^([lt])|i, + format => q( $1 =~ /^e/ ? 'events' : 'timeline' ) }, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_SHOW_PROGRESS", - default => "yes", - description => "Show the progress of replay in event view.", - help => qqq(" + name => 'ZM_WEB_M_SHOW_PROGRESS', + default => 'yes', + description => 'Show the progress of replay in event view.', + help => q` When viewing events an event navigation panel and progress bar is shown below the event itself. This allows you to jump to specific points in the event, but can can also dynamically @@ -3511,15 +3553,15 @@ our @options = ( replay. This option allows you to turn off the progress display, whilst still keeping the navigation aspect, where bandwidth prevents it functioning effectively. - "), + `, type => $types{boolean}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_M_AJAX_TIMEOUT", - default => "5000", - description => "How long to wait for Ajax request responses (ms)", - help => qqq(" + name => 'ZM_WEB_M_AJAX_TIMEOUT', + default => '5000', + description => 'How long to wait for Ajax request responses (ms)', + help => q` The newer versions of the live feed and event views use Ajax to request information from the server and populate the views dynamically. This option allows you to specify a timeout if @@ -3530,84 +3572,84 @@ our @options = ( should timeout so this setting should be set to a value greater than the slowest expected response. This value is in milliseconds but if set to zero then no timeout will be used. - "), + `, type => $types{integer}, - category => "medband", + category => 'medband', }, { - name => "ZM_WEB_L_REFRESH_MAIN", - default => "300", - description => "How often (in seconds) the main console window should refresh itself", - introduction => qqq(" + name => 'ZM_WEB_L_REFRESH_MAIN', + default => '300', + description => 'How often (in seconds) the main console window should refresh itself', + introduction => q` The next few options control what happens when the client is running in 'low' bandwidth mode. You should set these options for when accessing the ZoneMinder client over a modem or slow link. In most cases the default values will be suitable as a starting point. - "), - help => qqq(" + `, + help => q` The main console window lists a general status and the event totals for all monitors. This is not a trivial task and should not be repeated too frequently or it may affect the performance of the rest of the system. - "), + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_REFRESH_CYCLE", - default => "30", - description => "How often (in seconds) the cycle watch window swaps to the next monitor", - help => qqq(" + name => 'ZM_WEB_L_REFRESH_CYCLE', + default => '30', + description => 'How often (in seconds) the cycle watch window swaps to the next monitor', + help => q` The cycle watch window is a method of continuously cycling between images from all of your monitors. This option determines how often to refresh with a new image. - "), + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_REFRESH_IMAGE", - default => "15", - description => "How often (in seconds) the watched image is refreshed (if not streaming)", - help => qqq(" + name => 'ZM_WEB_L_REFRESH_IMAGE', + default => '15', + description => 'How often (in seconds) the watched image is refreshed (if not streaming)', + help => q` The live images from a monitor can be viewed in either streamed or stills mode. This option determines how often a stills image is refreshed, it has no effect if streaming is selected. - "), + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_REFRESH_STATUS", - default => "10", - description => "How often (in seconds) the status refreshes itself in the watch window", - help => qqq(" + name => 'ZM_WEB_L_REFRESH_STATUS', + default => '10', + description => 'How often (in seconds) the status refreshes itself in the watch window', + help => q` The monitor window is actually made from several frames. The one in the middle merely contains a monitor status which needs to refresh fairly frequently to give a true indication. This option determines that frequency. - "), + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_REFRESH_EVENTS", - default => "180", - description => "How often (in seconds) the event listing is refreshed in the watch window", - help => qqq(" + name => 'ZM_WEB_L_REFRESH_EVENTS', + default => '180', + description => 'How often (in seconds) the event listing is refreshed in the watch window', + help => q` The monitor window is actually made from several frames. The lower framme contains a listing of the last few events for easy access. This option determines how often this is refreshed. - "), + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_CAN_STREAM", - default => "auto", - description => "Override the automatic detection of browser streaming capability", - help => qqq(" + name => 'ZM_WEB_L_CAN_STREAM', + default => 'auto', + description => 'Override the automatic detection of browser streaming capability', + help => q` If you know that your browser can handle image streams of the type 'multipart/x-mixed-replace' but ZoneMinder does not detect this correctly you can set this option to ensure that the @@ -3615,15 +3657,15 @@ our @options = ( plugin. Selecting 'yes' will tell ZoneMinder that your browser can handle the streams natively, 'no' means that it can't and so the plugin will be used while 'auto' lets ZoneMinder decide. - "), + `, type => $types{tristate}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_STREAM_METHOD", - default => "jpeg", - description => "Which method should be used to send video streams to your browser.", - help => qqq(" + name => 'ZM_WEB_L_STREAM_METHOD', + default => 'jpeg', + description => 'Which method should be used to send video streams to your browser.', + help => q` ZoneMinder can be configured to use either mpeg encoded video or a series or still jpeg images when sending video streams. This option defines which is used. If you choose mpeg you @@ -3631,20 +3673,20 @@ our @options = ( on your browser whereas choosing jpeg will work natively on Mozilla and related browsers and with a Java applet on Internet Explorer - "), + `, type => { - db_type =>"string", - hint =>"mpeg|jpeg", - pattern =>qr|^([mj])|i, - format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) + db_type => 'string', + hint => 'mpeg|jpeg', + pattern => qr|^([mj])|i, + format => q( $1 =~ /^m/ ? 'mpeg' : 'jpeg' ) }, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_DEFAULT_SCALE", - default => "100", - description => "What the default scaling factor applied to 'live' or 'event' views is (%)", - help => qqq(" + name => 'ZM_WEB_L_DEFAULT_SCALE', + default => '100', + description => q`What the default scaling factor applied to 'live' or 'event' views is (%)`, + help => q` Normally ZoneMinder will display 'live' or 'event' streams in their native size. However if you have monitors with large dimensions or a slow link you may prefer to reduce this size, @@ -3652,39 +3694,39 @@ our @options = ( options lets you specify what the default scaling factor will be. It is expressed as a percentage so 100 is normal size, 200 is double size etc. - "), + `, type => { - db_type =>"integer", - hint =>"25|33|50|75|100|150|200|300|400", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) + db_type => 'integer', + hint => '25|33|50|75|100|150|200|300|400', + pattern => qr|^(\d+)$|, + format => q( $1 ) }, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_DEFAULT_RATE", - default => "100", - description => "What the default replay rate factor applied to 'event' views is (%)", - help => qqq(" + name => 'ZM_WEB_L_DEFAULT_RATE', + default => '100', + description => q`What the default replay rate factor applied to 'event' views is (%)`, + help => q` Normally ZoneMinder will display 'event' streams at their native rate, i.e. as close to real-time as possible. However if you have long events it is often convenient to replay them at a faster rate for review. This option lets you specify what the default replay rate will be. It is expressed as a percentage so 100 is normal rate, 200 is double speed etc. - "), + `, type => { - db_type =>"integer", - hint =>"25|50|100|150|200|400|1000|2500|5000|10000", - pattern =>qr|^(\d+)$|, format=>q( $1 ) + db_type => 'integer', + hint => '25|50|100|150|200|400|1000|2500|5000|10000', + pattern => qr|^(\d+)$|, format=> q( $1 ) }, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_VIDEO_BITRATE", - default => "25000", - description => "What the bitrate of the video encoded stream should be set to", - help => qqq(" + name => 'ZM_WEB_L_VIDEO_BITRATE', + default => '25000', + description => 'What the bitrate of the video encoded stream should be set to', + help => q` When encoding real video via the ffmpeg library a bit rate can be specified which roughly corresponds to the available bandwidth used for the stream. This setting effectively @@ -3696,15 +3738,15 @@ our @options = ( that the video is produced at. A higher frame rate at a particular bit rate result in individual frames being at a lower quality. - "), + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_VIDEO_MAXFPS", - default => "5", - description => "What the maximum frame rate for streamed video should be", - help => qqq(" + name => 'ZM_WEB_L_VIDEO_MAXFPS', + default => '5', + description => 'What the maximum frame rate for streamed video should be', + help => q` When using streamed video the main control is the bitrate which determines how much data can be transmitted. However a lower bitrate at high frame rates results in a lower quality image. @@ -3719,15 +3761,15 @@ our @options = ( option to 10fps then the video is not produced at 10fps, but rather at 7.5fps (15 divided by 2) as the final frame rate must be the original divided by a power of 2. - "), + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_SCALE_THUMBS", - default => "yes", - description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", - help => qqq(" + name => 'ZM_WEB_L_SCALE_THUMBS', + default => 'yes', + description => 'Scale thumbnails in events, bandwidth versus cpu in rescaling', + help => q` If unset, this option sends the whole image to the browser which resizes it in the window. If set the image is scaled down on the server before sending a reduced size image to the @@ -3735,34 +3777,34 @@ our @options = ( Note that ZM can only perform the resizing if the appropriate PHP graphics functionality is installed. This is usually available in the php-gd package. - "), + `, type => $types{boolean}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_EVENTS_VIEW", - default => "events", - description => "What the default view of multiple events should be.", - help => qqq(" + name => 'ZM_WEB_L_EVENTS_VIEW', + default => 'events', + description => 'What the default view of multiple events should be.', + help => q` Stored events can be viewed in either an events list format or in a timeline based one. This option sets the default view that will be used. Choosing one view here does not prevent the other view being used as it will always be selectable from whichever view is currently being used. - "), + `, type => { - db_type =>"string", - hint =>"events|timeline", - pattern =>qr|^([lt])|i, - format =>q( $1 =~ /^e/ ? "events" : "timeline" ) + db_type => 'string', + hint => 'events|timeline', + pattern => qr|^([lt])|i, + format => q( $1 =~ /^e/ ? 'events' : 'timeline' ) }, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_SHOW_PROGRESS", - default => "no", - description => "Show the progress of replay in event view.", - help => qqq(" + name => 'ZM_WEB_L_SHOW_PROGRESS', + default => 'no', + description => 'Show the progress of replay in event view.', + help => q` When viewing events an event navigation panel and progress bar is shown below the event itself. This allows you to jump to specific points in the event, but can can also dynamically @@ -3773,15 +3815,15 @@ our @options = ( replay. This option allows you to turn off the progress display, whilst still keeping the navigation aspect, where bandwidth prevents it functioning effectively. - "), + `, type => $types{boolean}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_WEB_L_AJAX_TIMEOUT", - default => "10000", - description => "How long to wait for Ajax request responses (ms)", - help => qqq(" + name => 'ZM_WEB_L_AJAX_TIMEOUT', + default => '10000', + description => 'How long to wait for Ajax request responses (ms)', + help => q` The newer versions of the live feed and event views use Ajax to request information from the server and populate the views dynamically. This option allows you to specify a timeout if @@ -3792,112 +3834,112 @@ our @options = ( should timeout so this setting should be set to a value greater than the slowest expected response. This value is in milliseconds but if set to zero then no timeout will be used. - "), + `, type => $types{integer}, - category => "lowband", + category => 'lowband', }, { - name => "ZM_DYN_LAST_VERSION", - default => "", - description => "What the last version of ZoneMinder recorded from zoneminder.com is", - help => "", + name => 'ZM_DYN_LAST_VERSION', + default => '', + description => 'What the last version of ZoneMinder recorded from zoneminder.com is', + help => '', type => $types{string}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_DYN_CURR_VERSION", - default => "@VERSION@", - description => qqq(" + name => 'ZM_DYN_CURR_VERSION', + default => '@VERSION@', + description => q` What the effective current version of ZoneMinder is, might be different from actual if versions ignored - "), - help => "", + `, + help => '', type => $types{string}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_DYN_DB_VERSION", - default => "@VERSION@", - description => "What the version of the database is, from zmupdate", - help => "", + name => 'ZM_DYN_DB_VERSION', + default => '@VERSION@', + description => 'What the version of the database is, from zmupdate', + help => '', type => $types{string}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_DYN_LAST_CHECK", - default => "", - description => "When the last check for version from zoneminder.com was", - help => "", + name => 'ZM_DYN_LAST_CHECK', + default => '', + description => 'When the last check for version from zoneminder.com was', + help => '', type => $types{integer}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_DYN_NEXT_REMINDER", - default => "", - description => "When the earliest time to remind about versions will be", - help => "", + name => 'ZM_DYN_NEXT_REMINDER', + default => '', + description => 'When the earliest time to remind about versions will be', + help => '', type => $types{string}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_DYN_DONATE_REMINDER_TIME", + name => 'ZM_DYN_DONATE_REMINDER_TIME', default => 0, - description => "When the earliest time to remind about donations will be", - help => "", + description => 'When the earliest time to remind about donations will be', + help => '', type => $types{integer}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_DYN_SHOW_DONATE_REMINDER", - default => "yes", - description => "Remind about donations or not", - help => "", + name => 'ZM_DYN_SHOW_DONATE_REMINDER', + default => 'yes', + description => 'Remind about donations or not', + help => '', type => $types{boolean}, readonly => 1, - category => "dynamic", + category => 'dynamic', }, { - name => "ZM_SSMTP_MAIL", - default => "no", - description => qqq(" + name => 'ZM_SSMTP_MAIL', + default => 'no', + description => q` Use a SSMTP mail server if available. NEW_MAIL_MODULES must be enabled - "), + `, requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" }, - { name => "ZM_NEW_MAIL_MODULES", value => "yes" } + { name => 'ZM_OPT_EMAIL', value => 'yes' }, + { name => 'ZM_OPT_MESSAGE', value => 'yes' }, + { name => 'ZM_NEW_MAIL_MODULES', value => 'yes' } ], - help => qqq(" + help => q` SSMTP is a lightweight and efficient method to send email. The SSMTP application is not installed by default. NEW_MAIL_MODULES must also be enabled. Please visit: http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder for setup and configuration help. - "), + `, type => $types{boolean}, - category => "mail", + category => 'mail', }, { - name => "ZM_SSMTP_PATH", - default => "", - description => "SSMTP executable path", - requires => [{ name => "ZM_SSMTP_MAIL", value => "yes" }], - help => qqq(" + name => 'ZM_SSMTP_PATH', + default => '', + description => 'SSMTP executable path', + requires => [{ name => 'ZM_SSMTP_MAIL', value => 'yes' }], + help => q` Recommend setting the path to the SSMTP application. If path is not defined. Zoneminder will try to determine the path via shell command. Example path: /usr/sbin/ssmtp. - "), + `, type => $types{string}, - category => "mail", + category => 'mail', }, - ); +); our %options_hash = map { ( $_->{name}, $_ ) } @options; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm new file mode 100644 index 000000000..f9cabd5fa --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm @@ -0,0 +1,411 @@ +# ========================================================================== +# +# ZoneMinder HikVision Control Protocol Module +# Copyright (C) 2016 Terry Sanders +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ========================================================================== +# +# This module contains an implementation of the HikVision ISAPI camera control +# protocol +# +package ZoneMinder::Control::HikVision; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Control; + +our @ISA = qw(ZoneMinder::Control); + +# ========================================================================== +# +# HiKVision ISAPI Control Protocol +# +# Set the following: +# ControlAddress: username:password@camera_webaddress:port +# ControlDevice: IP Camera Model +# +# ========================================================================== + +use ZoneMinder::Logger qw(:all); + +use Time::HiRes qw( usleep ); + +use LWP::UserAgent; +use HTTP::Cookies; + +my $ChannelID = 1; # Usually... +my $DefaultFocusSpeed = 50; # Should be between 1 and 100 +my $DefaultIrisSpeed = 50; # Should be between 1 and 100 + +sub new { + my $class = shift; + my $id = shift; + my $self = ZoneMinder::Control->new( $id ); + bless( $self, $class ); + srand( time() ); + return $self; +} + +our $AUTOLOAD; + +sub AUTOLOAD { + my $self = shift; + my $class = ref($self) || croak( "$self not object" ); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( exists($self->{$name}) ) + { + return( $self->{$name} ); + } + Fatal( "Can't access $name member of object of class $class" ); +} +sub open { + my $self = shift; + $self->loadMonitor(); + # + # Create a UserAgent for the requests + # + $self->{UA} = LWP::UserAgent->new(); + $self->{UA}->cookie_jar( {} ); + # + # Extract the username/password host/port from ControlAddress + # + my ($user,$pass,$host,$port); + if( $self->{Monitor}{ControlAddress} =~ /^([^:]+):([^@]+)@(.+)/ ) { # user:pass@host... + $user = $1; + $pass = $2; + $host = $3; + } + elsif( $self->{Monitor}{ControlAddress} =~ /^([^@]+)@(.+)/ ) { # user@host... + $user = $1; + $host = $2; + } + else { # Just a host + $host = $self->{Monitor}{ControlAddress}; + } + # Check if it is a host and port or just a host + if( $host =~ /([^:]+):(.+)/ ) { + $host = $1; + $port = $2; + } + else { + $port = 80; + } + # Save the credentials + if( defined($user) ) { + $self->{UA}->credentials( "$host:$port", $self->{Monitor}{ControlDevice}, $user, $pass ); + } + # Save the base url + $self->{BaseURL} = "http://$host:$port"; +} +sub PutCmd { + my $self = shift; + my $cmd = shift; + my $content = shift; + my $req = HTTP::Request->new(PUT => "$self->{BaseURL}/$cmd"); + if(defined($content)) { + $req->content_type("application/x-www-form-urlencoded; charset=UTF-8"); + $req->content('' . "\n" . $content); + } + my $res = $self->{UA}->request($req); + unless( $res->is_success ) { + # + # The camera timeouts connections at short intervals. When this + # happens the user agent connects again and uses the same auth tokens. + # The camera rejects this and asks for another token but the UserAgent + # just gives up. Because of this I try the request again and it should + # succeed the second time if the credentials are correct. + # + if($res->code == 401) { + $res = $self->{UA}->request($req); + unless( $res->is_success ) { + # + # It has failed authentication. The odds are + # that the user has set some paramater incorrectly + # so check the realm against the ControlDevice + # entry and send a message if different + # + my $auth = $res->headers->www_authenticate; + foreach (split(/\s*,\s*/,$auth)) { + if( $_ =~ /^realm\s*=\s*"([^"]+)"/i ) { + if( $self->{Monitor}{ControlDevice} ne $1 ) { + Info "Control Device appears to be incorrect."; + Info "Control Device should be set to \"$1\"."; + Info "Control Device currently set to \"$self->{Monitor}{ControlDevice}\"."; + } + } + } + # + # Check for username/password + # + if( $self->{Monitor}{ControlAddress} =~ /.+:(.+)@.+/ ) { + Info "Check username/password is correct"; + } elsif ( $self->{Monitor}{ControlAddress} =~ /^[^:]+@.+/ ) { + Info "No password in Control Address. Should there be one?"; + } elsif ( $self->{Monitor}{ControlAddress} =~ /^:.+@.+/ ) { + Info "Password but no username in Control Address."; + } else { + Info "Missing username and password in Control Address."; + } + Fatal $res->status_line; + } + } + else { + Fatal $res->status_line; + } + } +} +# +# The move continuous functions all call moveVector +# with the direction to move in. This includes zoom +# +sub moveVector { + my $self = shift; + my $pandirection = shift; + my $tiltdirection = shift; + my $zoomdirection = shift; + my $params = shift; + my $command; # The ISAPI/PTZ command + + # Calculate autostop time + my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; + # Change from microseconds to milliseconds + $duration = int($duration/1000); + my $momentxml; + if( $duration ) { + $momentxml = "$duration"; + $command = "ISAPI/PTZCtrl/channels/$ChannelID/momentary"; + } + else { + $momentxml = ""; + $command = "ISAPI/PTZCtrl/channels/$ChannelID/continuous"; + } + # Calculate movement speeds + my $x = $pandirection * $self->getParam( $params, 'panspeed', 0 ); + my $y = $tiltdirection * $self->getParam( $params, 'tiltspeed', 0 ); + my $z = $zoomdirection * $self->getParam( $params, 'speed', 0 ); + # Create the XML + my $xml = "$x$y$z$momentxml"; + # Send it to the camera + $self->PutCmd($command,$xml); +} +sub moveStop { $_[0]->moveVector( 0, 0, 0, splice(@_,1)); } +sub moveConUp { $_[0]->moveVector( 0, 1, 0, splice(@_,1)); } +sub moveConUpRight { $_[0]->moveVector( 1, 1, 0, splice(@_,1)); } +sub moveConRight { $_[0]->moveVector( 1, 0, 0, splice(@_,1)); } +sub moveConDownRight { $_[0]->moveVector( 1, -1, 0, splice(@_,1)); } +sub moveConDown { $_[0]->moveVector( 0, -1, 0, splice(@_,1)); } +sub moveConDownLeft { $_[0]->moveVector( -1, -1, 0, splice(@_,1)); } +sub moveConLeft { $_[0]->moveVector( -1, 0, 0, splice(@_,1)); } +sub moveConUpLeft { $_[0]->moveVector( -1, 1, 0, splice(@_,1)); } +sub zoomConTele { $_[0]->moveVector( 0, 0, 1, splice(@_,1)); } +sub zoomConWide { $_[0]->moveVector( 0, 0,-1, splice(@_,1)); } +# +# Presets including Home set and clear +# +sub presetGoto { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params,'preset'); + $self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/presets/$preset/goto"); +} +sub presetSet { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params,'preset'); + my $xml = "$preset"; + $self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/presets/$preset",$xml); +} +sub presetHome { + my $self = shift; + my $params = shift; + $self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/homeposition/goto"); +} +# +# Focus controls all call Focus with a +/- speed +# +sub Focus { + my $self = shift; + my $speed = shift; + my $xml = "$speed"; + $self->PutCmd("ISAPI/System/Video/inputs/channels/$ChannelID/focus",$xml); +} +sub focusConNear { + my $self = shift; + my $params = shift; + + # Calculate autostop time + my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; + # Get the focus speed + my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); + $self->Focus(-$speed); + if($duration) { + usleep($duration); + $self->moveStop($params); + } +} +sub Near { + my $self = shift; + my $params = shift; + $self->Focus(-$DefaultFocusSpeed); +} +sub focusAbsNear { + my $self = shift; + my $params = shift; + + # Get the focus speed + my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); + $self->Focus(-$speed); +} +sub focusRelNear { + my $self = shift; + my $params = shift; + # Get the focus speed + my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); + $self->Focus(-$speed); +} +sub focusConFar { + my $self = shift; + my $params = shift; + + # Calculate autostop time + my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; + # Get the focus speed + my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); + $self->Focus($speed); + if($duration) { + usleep($duration); + $self->moveStop($params); + } +} +sub Far { + my $self = shift; + my $params = shift; + $self->Focus($DefaultFocusSpeed); +} +sub focusAbsFar { + my $self = shift; + my $params = shift; + + # Get the focus speed + my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); + $self->Focus($speed); +} +sub focusRelFar { + my $self = shift; + my $params = shift; + + # Get the focus speed + my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed ); + $self->Focus($speed); +} +# +# Iris controls all call Iris with a +/- speed +# +sub Iris { + my $self = shift; + my $speed = shift; + + my $xml = "$speed"; + $self->PutCmd("ISAPI/System/Video/inputs/channels/$ChannelID/iris",$xml); +} +sub irisConClose { + my $self = shift; + my $params = shift; + + # Calculate autostop time + my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; + # Get the iris speed + my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); + $self->Iris(-$speed); + if($duration) { + usleep($duration); + $self->moveStop($params); + } +} +sub Close { + my $self = shift; + my $params = shift; + + $self->Iris(-$DefaultIrisSpeed); +} +sub irisAbsClose { + my $self = shift; + my $params = shift; + + # Get the iris speed + my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); + $self->Iris(-$speed); +} +sub irisRelClose { + my $self = shift; + my $params = shift; + + # Get the iris speed + my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); + $self->Iris(-$speed); +} +sub irisConOpen { + my $self = shift; + my $params = shift; + + # Calculate autostop time + my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout}; + # Get the iris speed + my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); + $self->Iris($speed); + if($duration) { + usleep($duration); + $self->moveStop($params); + } +} +sub Open { + my $self = shift; + my $params = shift; + + $self->Iris($DefaultIrisSpeed); +} +sub irisAbsOpen { + my $self = shift; + my $params = shift; + + # Get the iris speed + my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); + $self->Iris($speed); +} +sub irisRelOpen { + my $self = shift; + my $params = shift; + + # Get the iris speed + my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed ); + $self->Iris($speed); +} +# +# reset (reboot) the device +# +sub reset { + my $self = shift; + + $self->PutCmd("ISAPI/System/reboot"); +} + +1; + diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm index 0f7a75012..38838237b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/SPP1802SWPTZ.pm @@ -1,27 +1,34 @@ +# ========================================================================== +# +# ZoneMinder SunEyes SP-P1802SWPTZ IP Control Protocol Module, $Date: 2017-03-19 23:00:00 +1000 (Sat, 19 March 2017) $, $Revision: 0002 $ +# Copyright (C) 2001-2008 Philip Coombes +# Modified for use with Foscam FI8918W IP Camera by Dave Harris +# Modified Feb 2011 by Howard Durdle (http://durdl.es/x) to: +# fix horizontal panning, add presets and IR on/off +# use Control Device field to pass username and password +# Modified May 2014 by Arun Horne (http://arunhorne.co.uk) to: +# use HTTP basic auth as required by firmware 11.37.x.x upward # Modified on Sep 28 2015 by Bobby Billingsley # Changes made # - Copied FI8918W.pm to SPP1802SWPTZ.pm # - modified to control a SunEyes SP-P1802SWPTZ - -# ========================================================================== -# ZoneMinder SunEyes SP-P1802SWPTZ IP Control Protocol Module +# Modified on 13 March 2017 by Steve Gilvarry +# -Address license and copyright issues # -# This program is free software; you can redistribute it and/or modify it under -# the terms of the GNU General Public License as published by the Free Software -# Foundation; either version 2 of the License, or (at your option) any later -# version. +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. # -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -# ========================================================================== -# # This module contains the implementation of the SunEyes SP-P1802SWPTZ IP # camera control protocol # diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index 9e8e041c8..19374543e 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -75,9 +75,11 @@ sub zmDbConnect { if ( $force ) { zmDbDisconnect(); } - if ( !defined( $dbh ) ) { + my $options = shift; + + if ( ( ! defined( $dbh ) ) or ! $dbh->ping() ) { + my ( $host, $portOrSocket ) = ( $ZoneMinder::Config::Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); my $socket; - my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); if ( defined($portOrSocket) ) { if ( $portOrSocket =~ /^\// ) { @@ -89,7 +91,7 @@ sub zmDbConnect { $socket = ";host=".$Config{ZM_DB_HOST}; } $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .$socket + .$socket . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '' ) , $Config{ZM_DB_USER} , $Config{ZM_DB_PASS} ); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 71ca57680..9d984adf3 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -28,30 +28,13 @@ use 5.006; use strict; use warnings; -require Exporter; require ZoneMinder::Base; +require ZoneMinder::Object; +require ZoneMinder::Storage; require Date::Manip; -our @ISA = qw(Exporter ZoneMinder::Base); - -# Items to export into callers namespace by default. Note: do not export -# names by default without a very good reason. Use EXPORT_OK instead. -# Do not simply export all your public functions/methods/constants. - -# This allows declaration use ZoneMinder ':all'; -# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK -# will save memory. -our %EXPORT_TAGS = ( - 'functions' => [ qw( - ) ] - ); -push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; - -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); - -our @EXPORT = qw(); - -our $VERSION = $ZoneMinder::Base::VERSION; +#our @ISA = qw(ZoneMinder::Object); +use parent qw(ZoneMinder::Object); # ========================================================================== # @@ -62,39 +45,24 @@ our $VERSION = $ZoneMinder::Base::VERSION; use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); +require Date::Parse; + +use vars qw/ $table $primary_key /; +$table = 'Events'; +$primary_key = 'Id'; use POSIX; -sub new { - my ( $parent, $id, $data ) = @_; - - my $self = {}; - bless $self, $parent; - $$self{dbh} = $ZoneMinder::Database::dbh; -#zmDbConnect(); - if ( ( $$self{Id} = $id ) or $data ) { -#$log->debug("loading $parent $id") if $debug or DEBUG_ALL; - $self->load( $data ); +sub Time { + if ( @_ > 1 ) { + $_[0]{Time} = $_[1]; } - return $self; -} # end sub new + if ( ! defined $_[0]{Time} ) { -sub load { - my ( $self, $data ) = @_; - my $type = ref $self; - if ( ! $data ) { -#$log->debug("Object::load Loading from db $type"); - $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Events WHERE Id=?', {}, $$self{Id} ); - if ( ! $data ) { - Error( "Failure to load Event record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); - } else { - Debug( 3, "Loaded Event $$self{Id}" ); - } # end if - } # end if ! $data - if ( $data and %$data ) { - @$self{keys %$data} = values %$data; - } # end if -} # end sub load + $_[0]{Time} = Date::Parse::str2time( $_[0]{StartTime} ); + } + return $_[0]{Time}; +} sub Name { if ( @_ > 1 ) { @@ -130,6 +98,7 @@ sub find { my $filter = new ZoneMinder::Event( $$db_filter{Id}, $db_filter ); push @results, $filter; } # end while + $sth->finish(); return @results; } @@ -138,36 +107,51 @@ sub find_one { return $results[0] if @results; } -sub getEventPath { +sub getPath { + return Path( @_ ); +} +sub Path { my $event = shift; - my $event_path = ""; - if ( $Config{ZM_USE_DEEP_STORAGE} ) { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.strftime( "%y/%m/%d/%H/%M/%S", - localtime($event->{Time}) - ) - ; - } else { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.$event->{Id} - ; + if ( @_ > 1 ) { + $$event{Path} = $_[1]; + if ( ! -e $$event{Path} ) { + Error("Setting path for event $$event{Id} to $_[1] but does not exist!"); + } } - if ( index($Config{ZM_DIR_EVENTS},'/') != 0 ){ - $event_path = $Config{ZM_PATH_WEB} - .'/'.$event_path - ; - } - return( $event_path ); + if ( ! $$event{Path} ) { + my $Storage = $event->Storage(); + + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + if ( $event->Time() ) { + $$event{Path} = join('/', + $Storage->Path(), + $event->{MonitorId}, + strftime( "%y/%m/%d/%H/%M/%S", + localtime($event->Time()) + ), + ); + } else { + Error("Event $$event{Id} has no value for Time(), unable to determine path"); + $$event{Path} = ''; + } + } else { + $$event{Path} = join('/', + $Storage->Path(), + $event->{MonitorId}, + $event->{Id}, + ); + } + } # end if + + return $$event{Path}; } sub GenerateVideo { my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_; - my $event_path = getEventPath( $self ); + my $event_path = $self->getPath( ); chdir( $event_path ); ( my $video_name = $self->{Name} ) =~ s/\s/_/g; @@ -228,9 +212,7 @@ sub GenerateVideo { my $command = $Config{ZM_PATH_FFMPEG} ." -y -r $frame_rate " .$Config{ZM_FFMPEG_INPUT_OPTIONS} - ." -i %0" - .$Config{ZM_EVENT_IMAGE_DIGITS} - ."d-capture.jpg -s $video_size " + .' -i ' . ( $$self{DefaultVideo} ? $$self{DefaultVideo} : '%0'.$Config{ZM_EVENT_IMAGE_DIGITS} .'d-capture.jpg' ) #. " -f concat -i /tmp/event_files.txt" ." -s $video_size " .$Config{ZM_FFMPEG_OUTPUT_OPTIONS} @@ -253,9 +235,122 @@ sub GenerateVideo { Info( "Video file $video_file already exists for event $self->{Id}\n" ); return $event_path.'/'.$video_file; } - return; + return; } # end sub GenerateVideo +sub delete { + my $event = $_[0]; + Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId} $event->{StartTime}\n" ); + $ZoneMinder::Database::dbh->ping(); +# Do it individually to avoid locking up the table for new events + my $sql = 'delete from Events where Id = ?'; + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $sth->finish(); + + if ( ! $Config{ZM_OPT_FAST_DELETE} ) { + my $sql = 'delete from Frames where EventId = ?'; + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $sth->finish(); + + $sql = 'delete from Stats where EventId = ?'; + $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $sth->finish(); + + $event->delete_files( ); + } else { + Debug('Not deleting frames, stats and files for speed.'); + } +} # end sub delete + + +sub delete_files { + + my $Storage = new ZoneMinder::Storage( $_[0]{StorageId} ); + my $storage_path = $Storage->Path(); + + if ( ! $storage_path ) { + Fatal("Empty storage path when deleting files for event $_[0]{Id} with storage id $_[0]{StorageId} "); + return; + } + + chdir ( $storage_path ); + + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + if ( ! $_[0]{MonitorId} ) { + Error("No monitor id assigned to event $_[0]{Id}"); + return; + } + Debug("Deleting files for Event $_[0]{Id} from $storage_path."); + my $link_path = $_[0]{MonitorId}."/*/*/*/.".$_[0]{Id}; +#Debug( "LP1:$link_path" ); + my @links = glob($link_path); +#Debug( "L:".$links[0].": $!" ); + if ( @links ) { + ( $link_path ) = ( $links[0] =~ /^(.*)$/ ); # De-taint +#Debug( "LP2:$link_path" ); + + ( my $day_path = $link_path ) =~ s/\.\d+//; +#Debug( "DP:$day_path" ); + my $event_path = $day_path.readlink( $link_path ); + ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint +#Debug( "EP:$event_path" ); + my $command = "/bin/rm -rf $event_path"; +#Debug( "C:$command" ); + ZoneMinder::General::executeShellCommand( $command ); + + unlink( $link_path ) or Error( "Unable to unlink '$link_path': $!" ); + + my @path_parts = split( /\//, $event_path ); + for ( my $i = int(@path_parts)-2; $i >= 1; $i-- ) { + my $delete_path = join( '/', @path_parts[0..$i] ); +#Debug( "DP$i:$delete_path" ); + my @has_files = glob( join('/', $storage_path,$delete_path,'*' ) ); +#Debug( "HF1:".$has_files[0] ) if ( @has_files ); + last if ( @has_files ); + @has_files = glob( join('/', $storage_path, $delete_path, '.[0-9]*' ) ); +#Debug( "HF2:".$has_files[0] ) if ( @has_files ); + last if ( @has_files ); + my $command = "/bin/rm -rf $storage_path/$delete_path"; + ZoneMinder::General::executeShellCommand( $command ); + } + } + } else { + my $command = "/bin/rm -rf $storage_path/$_[0]{MonitorId}/$_[0]{Id}"; + ZoneMinder::General::executeShellCommand( $command ); + } +} # end sub delete_files + +sub Storage { + return new ZoneMinder::Storage( $_[0]{StorageId} ); +} + +sub check_for_in_filesystem { + my $path = $_[0]->Path(); + if ( $path ) { + my @files = glob( $path . '/*' ); +Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found " . scalar @files . " files"); + return 1 if @files; + } +Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found no files"); + return 0; +} + +sub age { + if ( ! $_[0]{age} ) { + $_[0]{age} = (time() - ($^T - ((-M $_[0]->Path() ) * 24*60*60))); + } + return $_[0]{age}; +} + 1; __END__ # Below is stub documentation for your module. You'd better edit it! diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 22bcb0062..1f4259c6b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -28,31 +28,15 @@ use 5.006; use strict; use warnings; -require Exporter; require ZoneMinder::Base; require Date::Manip; -our @ISA = qw(Exporter ZoneMinder::Base); - -# Items to export into callers namespace by default. Note: do not export -# names by default without a very good reason. Use EXPORT_OK instead. -# Do not simply export all your public functions/methods/constants. - -# This allows declaration use ZoneMinder ':all'; -# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK -# will save memory. -our %EXPORT_TAGS = ( - 'functions' => [ qw( - ) ] -); -push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; - -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); - -our @EXPORT = qw(); - -our $VERSION = $ZoneMinder::Base::VERSION; +use parent qw(ZoneMinder::Object); +#our @ISA = qw(ZoneMinder::Object); +use vars qw/ $table $primary_key /; +$table = 'Events'; +$primary_key = 'Id'; # ========================================================================== # # General Utility Functions @@ -62,40 +46,11 @@ our $VERSION = $ZoneMinder::Base::VERSION; use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); +require ZoneMinder::Storage; +require ZoneMinder::Server; use POSIX; -sub new { - my ( $parent, $id, $data ) = @_; - - my $self = {}; - bless $self, $parent; - $$self{dbh} = $ZoneMinder::Database::dbh; -#zmDbConnect(); - if ( ( $$self{Id} = $id ) or $data ) { -#$log->debug("loading $parent $id") if $debug or DEBUG_ALL; - $self->load( $data ); - } - return $self; -} # end sub new - -sub load { - my ( $self, $data ) = @_; - my $type = ref $self; - if ( ! $data ) { -#$log->debug("Object::load Loading from db $type"); - $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Filter WHERE Id=?', {}, $$self{Id} ); - if ( ! $data ) { - Error( "Failure to load Filter record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); - } else { - Debug( 3, "Loaded Filter $$self{Id}" ); - } # end if - } # end if ! $data - if ( $data and %$data ) { - @$self{keys %$data} = values %$data; - } # end if -} # end sub load - sub Name { if ( @_ > 1 ) { $_[0]{Name} = $_[1]; @@ -130,6 +85,8 @@ sub find { my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); push @results, $filter; } # end while + $sth->finish(); + return @results; } @@ -140,11 +97,10 @@ sub find_one { sub Execute { my $self = $_[0]; - my $sql = $self->Sql(); if ( $self->{HasDiskPercent} ) { - my $disk_percent = getDiskPercent(); + my $disk_percent = getDiskPercent( $$self{Storage} ? $$self{Storage}->Path() : () ); $sql =~ s/zmDiskPercent/$disk_percent/g; } if ( $self->{HasDiskBlocks} ) { @@ -156,8 +112,9 @@ sub Execute { $sql =~ s/zmSystemLoad/$load/g; } - my $sth = $$self{dbh}->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$$self{dbh}->errstr() ); + Debug("Filter::Execute SQL ($sql)"); + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); my $res = $sth->execute(); if ( !$res ) { Error( "Can't execute filter '$sql', ignoring: ".$sth->errstr() ); @@ -169,6 +126,7 @@ sub Execute { push @results, $event; } $sth->finish(); + Debug("Loaded " . @results . " events for filter $_[0]{Name} using query ($sql)"); return @results; } @@ -176,78 +134,77 @@ sub Sql { my $self = $_[0]; if ( ! $$self{Sql} ) { my $filter_expr = ZoneMinder::General::jsonDecode( $self->{Query} ); - my $sql = "SELECT E.Id, - E.MonitorId, + my $sql = "SELECT E.*, + unix_timestamp(E.StartTime) as Time, M.Name as MonitorName, M.DefaultRate, - M.DefaultScale, - E.Name, - E.Cause, - E.Notes, - E.StartTime, - unix_timestamp(E.StartTime) as Time, - E.Length, - E.Frames, - E.AlarmFrames, - E.TotScore, - E.AvgScore, - E.MaxScore, - E.Archived, - E.Videoed, - E.Uploaded, - E.Emailed, - E.Messaged, - E.Executed + M.DefaultScale FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId "; $self->{Sql} = ''; if ( $filter_expr->{terms} ) { - for ( my $i = 0; $i < @{$filter_expr->{terms}}; $i++ ) { - if ( exists($filter_expr->{terms}[$i]->{cnj}) ) { - $self->{Sql} .= " ".$filter_expr->{terms}[$i]->{cnj}." "; + foreach my $term ( @{$filter_expr->{terms}} ) { + + if ( exists($term->{cnj}) ) { + $self->{Sql} .= " ".$term->{cnj}." "; } - if ( exists($filter_expr->{terms}[$i]->{obr}) ) { - $self->{Sql} .= " ".str_repeat( "(", $filter_expr->{terms}[$i]->{obr} )." "; + if ( exists($term->{obr}) ) { + $self->{Sql} .= " ".str_repeat( "(", $term->{obr} )." "; } - my $value = $filter_expr->{terms}[$i]->{val}; + my $value = $term->{val}; my @value_list; - if ( $filter_expr->{terms}[$i]->{attr} ) { - if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) { - my ( $temp_attr_name ) = $filter_expr->{terms}[$i]->{attr} =~ /^Monitor(.+)$/; + if ( $term->{attr} ) { + if ( $term->{attr} =~ /^Monitor/ ) { + my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/; $self->{Sql} .= "M.".$temp_attr_name; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) { + } elsif ( $term->{attr} =~ /^Server/ ) { + $self->{Sql} .= "M.".$term->{attr}; + } elsif ( $term->{attr} eq 'DateTime' ) { $self->{Sql} .= "E.StartTime"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) { + } elsif ( $term->{attr} eq 'Date' ) { $self->{Sql} .= "to_days( E.StartTime )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) { + } elsif ( $term->{attr} eq 'Time' ) { $self->{Sql} .= "extract( hour_second from E.StartTime )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Weekday' ) { + } elsif ( $term->{attr} eq 'Weekday' ) { $self->{Sql} .= "weekday( E.StartTime )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskPercent' ) { + } elsif ( $term->{attr} eq 'DiskPercent' ) { $self->{Sql} .= "zmDiskPercent"; $self->{HasDiskPercent} = !undef; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskBlocks' ) { + } elsif ( $term->{attr} eq 'DiskBlocks' ) { $self->{Sql} .= "zmDiskBlocks"; $self->{HasDiskBlocks} = !undef; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'SystemLoad' ) { + } elsif ( $term->{attr} eq 'SystemLoad' ) { $self->{Sql} .= "zmSystemLoad"; $self->{HasSystemLoad} = !undef; } else { - $self->{Sql} .= "E.".$filter_expr->{terms}[$i]->{attr}; + $self->{Sql} .= "E.".$term->{attr}; } ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { - if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) { + if ( $term->{attr} =~ /^Monitor/ ) { $value = "'$temp_value'"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Name' - || $filter_expr->{terms}[$i]->{attr} eq 'Cause' - || $filter_expr->{terms}[$i]->{attr} eq 'Notes' + } elsif ( $term->{attr} eq 'ServerId' ) { + if ( $temp_value eq 'ZM_SERVER_ID' ) { + $value = "'$Config{ZM_SERVER_ID}'"; + # This gets used later, I forget for what + $$self{Server} = new ZoneMinder::Server( $Config{ZM_SERVER_ID} ); + } else { + $value = "'$temp_value'"; + # This gets used later, I forget for what + $$self{Server} = new ZoneMinder::Server( $temp_value ); + } + } elsif ( $term->{attr} eq 'StorageId' ) { + $value = "'$temp_value'"; + $$self{Storage} = new ZoneMinder::Storage( $temp_value ); + } elsif ( $term->{attr} eq 'Name' + || $term->{attr} eq 'Cause' + || $term->{attr} eq 'Notes' ) { $value = "'$temp_value'"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) { + } elsif ( $term->{attr} eq 'DateTime' ) { $value = DateTimeToSQL( $temp_value ); if ( !$value ) { Error( "Error parsing date/time '$temp_value', " @@ -255,7 +212,7 @@ sub Sql { return; } $value = "'$value'"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) { + } elsif ( $term->{attr} eq 'Date' ) { $value = DateTimeToSQL( $temp_value ); if ( !$value ) { Error( "Error parsing date/time '$temp_value', " @@ -263,7 +220,7 @@ sub Sql { return; } $value = "to_days( '$value' )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) { + } elsif ( $term->{attr} eq 'Time' ) { $value = DateTimeToSQL( $temp_value ); if ( !$value ) { Error( "Error parsing date/time '$temp_value', " @@ -277,53 +234,55 @@ sub Sql { push( @value_list, $value ); } # end foreach temp_value } # end if has an attr - if ( $filter_expr->{terms}[$i]->{op} ) { - if ( $filter_expr->{terms}[$i]->{op} eq '=~' ) { + if ( $term->{op} ) { + if ( $term->{op} eq '=~' ) { $self->{Sql} .= " regexp $value"; - } elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) { + } elsif ( $term->{op} eq '!~' ) { $self->{Sql} .= " not regexp $value"; - } elsif ( $filter_expr->{terms}[$i]->{op} eq '=[]' ) { + } elsif ( $term->{op} eq '=[]' ) { $self->{Sql} .= " in (".join( ",", @value_list ).")"; - } elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) { + } elsif ( $term->{op} eq '!~' ) { $self->{Sql} .= " not in (".join( ",", @value_list ).")"; } else { - $self->{Sql} .= " ".$filter_expr->{terms}[$i]->{op}." $value"; + $self->{Sql} .= " ".$term->{op}." $value"; } } # end if has an operator - if ( exists($filter_expr->{terms}[$i]->{cbr}) ) { - $self->{Sql} .= " ".str_repeat( ")", $filter_expr->{terms}[$i]->{cbr} )." "; + if ( exists($term->{cbr}) ) { + $self->{Sql} .= " ".str_repeat( ")", $term->{cbr} )." "; } } # end foreach term } # end if terms if ( $self->{Sql} ) { - if ( $self->{AutoMessage} ) { + #if ( $self->{AutoMessage} ) { # Include all events, including events that are still ongoing # and have no EndTime yet $sql .= " and ( ".$self->{Sql}." )"; - } else { + #} else { # Only include closed events (events with valid EndTime) - $sql .= " where not isnull(E.EndTime) and ( ".$self->{Sql}." )"; - } + #$sql .= " where not isnull(E.EndTime) and ( ".$self->{Sql}." )"; + #} } my @auto_terms; if ( $self->{AutoArchive} ) { - push( @auto_terms, "E.Archived = 0" ) - } - if ( $self->{AutoVideo} ) { - push( @auto_terms, "E.Videoed = 0" ) + push @auto_terms, "E.Archived = 0"; } + # Don't do this, it prevents re-generation and concatenation. + # If the file already exists, then the video won't be re-recreated + #if ( $self->{AutoVideo} ) { + #push @auto_terms, "E.Videoed = 0"; + #} if ( $self->{AutoUpload} ) { - push( @auto_terms, "E.Uploaded = 0" ) + push @auto_terms, "E.Uploaded = 0"; } if ( $self->{AutoEmail} ) { - push( @auto_terms, "E.Emailed = 0" ) + push @auto_terms, "E.Emailed = 0"; } if ( $self->{AutoMessage} ) { - push( @auto_terms, "E.Messaged = 0" ) + push @auto_terms, "E.Messaged = 0"; } if ( $self->{AutoExecute} ) { - push( @auto_terms, "E.Executed = 0" ) + push @auto_terms, "E.Executed = 0"; } if ( @auto_terms ) { $sql .= " and ( ".join( " or ", @auto_terms )." )"; @@ -361,14 +320,13 @@ sub Sql { if ( $filter_expr->{limit} ) { $sql .= " limit 0,".$filter_expr->{limit}; } - Debug( "SQL:$sql\n" ); $self->{Sql} = $sql; } # end if has Sql return $self->{Sql}; } # end sub Sql sub getDiskPercent { - my $command = "df ."; + my $command = "df " . ($_[0] ? $_[0] : '.'); my $df = qx( $command ); my $space = -1; if ( $df =~ /\s(\d+)%/ms ) { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index bb03fe70c..743cc67e4 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -30,6 +30,7 @@ use warnings; require Exporter; require ZoneMinder::Base; +require ZoneMinder::Storage; our @ISA = qw(Exporter ZoneMinder::Base); @@ -178,26 +179,13 @@ sub runCommand { sub getEventPath { my $event = shift; - my $event_path = ""; - if ( $Config{ZM_USE_DEEP_STORAGE} ) { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.strftime( "%y/%m/%d/%H/%M/%S", - localtime($event->{Time}) - ) - ; - } else { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.$event->{Id} - ; - } + my $Storage = new ZoneMinder::Storage( $$event{StorageId} ); + my $event_path = join( '/', + $Storage->Path(), + $event->{MonitorId}, + ( $Config{ZM_USE_DEEP_STORAGE} ? strftime( "%y/%m/%d/%H/%M/%S", localtime($event->{Time}) ) : $event->{Id} ), + ); - if ( index($Config{ZM_DIR_EVENTS},'/') != 0 ) { - $event_path = $Config{ZM_PATH_WEB} - .'/'.$event_path - ; - } return( $event_path ); } @@ -206,10 +194,8 @@ sub createEventPath { # WARNING assumes running from events directory # my $event = shift; - my $eventRootPath = ($Config{ZM_DIR_EVENTS}=~m|/|) - ? $Config{ZM_DIR_EVENTS} - : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}); - my $eventPath = $eventRootPath.'/'.$event->{MonitorId}; + my $Storage = new ZoneMinder::Storage( $$event{Id} ); + my $eventPath = $Storage->Path() . '/'.$event->{MonitorId}; if ( $Config{ZM_USE_DEEP_STORAGE} ) { my @startTime = localtime( $event->{StartTime} ); @@ -700,7 +686,7 @@ None by default. Mention other useful documentation such as the documentation of related modules or operating system documentation (such as man pages -in UNIX), or any relevant external documentation such as RFCs or + in UNIX), or any relevant external documentation such as RFCs or standards. If you have a mailing list set up for your module, mention it here. diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 8563a95ef..4d51c9e6c 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -140,7 +140,11 @@ sub new { $this->{idArgs} = ""; $this->{level} = INFO; +if (-t STDIN) { + $this->{termLevel} = INFO; +} else { $this->{termLevel} = NOLOG; +} $this->{databaseLevel} = NOLOG; $this->{fileLevel} = NOLOG; $this->{syslogLevel} = NOLOG; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in index 286d83db1..12c08d598 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in @@ -67,6 +67,7 @@ our %EXPORT_TAGS = ( zmIsAlarmed zmInAlarm zmHasAlarmed + zmGetStartupTime zmGetLastEvent zmGetLastWriteTime zmGetLastReadTime @@ -77,7 +78,7 @@ our %EXPORT_TAGS = ( zmTriggerEventOn zmTriggerEventOff zmTriggerEventCancel - zmTriggerShowtext + zmTriggerShowtext ) ], ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; @@ -115,7 +116,7 @@ use constant TRIGGER_OFF => 2; use Storable qw( freeze thaw ); -if ( "@ENABLE_MMAP@" eq 'yes' ) { +if ( '@ENABLE_MMAP@' eq 'yes' ) { # 'yes' if memory is mmapped require ZoneMinder::Memory::Mapped; ZoneMinder::Memory::Mapped->import(); @@ -141,46 +142,46 @@ our $native = $arch/8; our $mem_seq = 0; our $mem_data = { - "shared_data" => { "type"=>"SharedData", "seq"=>$mem_seq++, "contents"=> { - "size" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_write_index" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_read_index" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "state" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_event" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "action" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "brightness" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "hue" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "colour" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "contrast" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "alarm_x" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "alarm_y" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "valid" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "active" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "signal" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "format" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "imagesize" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "epadding1" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "epadding2" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_write_time" => { "type"=>"time_t64", "seq"=>$mem_seq++ }, - "last_read_time" => { "type"=>"time_t64", "seq"=>$mem_seq++ }, - "control_state" => { "type"=>"uint8[256]", "seq"=>$mem_seq++ }, + 'shared_data' => { 'type'=>'SharedData', 'seq'=>$mem_seq++, 'contents'=> { + 'size' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'last_write_index' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'last_read_index' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'state' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'last_event' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'action' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'brightness' => { 'type'=>'int32', 'seq'=>$mem_seq++ }, + 'hue' => { 'type'=>'int32', 'seq'=>$mem_seq++ }, + 'colour' => { 'type'=>'int32', 'seq'=>$mem_seq++ }, + 'contrast' => { 'type'=>'int32', 'seq'=>$mem_seq++ }, + 'alarm_x' => { 'type'=>'int32', 'seq'=>$mem_seq++ }, + 'alarm_y' => { 'type'=>'int32', 'seq'=>$mem_seq++ }, + 'valid' => { 'type'=>'uint8', 'seq'=>$mem_seq++ }, + 'active' => { 'type'=>'uint8', 'seq'=>$mem_seq++ }, + 'signal' => { 'type'=>'uint8', 'seq'=>$mem_seq++ }, + 'format' => { 'type'=>'uint8', 'seq'=>$mem_seq++ }, + 'imagesize' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'epadding1' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'epadding2' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'startup_time' => { 'type'=>'time_t64', 'seq'=>$mem_seq++ }, + 'last_write_time' => { 'type'=>'time_t64', 'seq'=>$mem_seq++ }, + 'last_read_time' => { 'type'=>'time_t64', 'seq'=>$mem_seq++ }, + 'control_state' => { 'type'=>'uint8[256]', 'seq'=>$mem_seq++ }, } }, - "trigger_data" => { "type"=>"TriggerData", "seq"=>$mem_seq++, "contents"=> { - "size" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_state" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_score" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "padding" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_cause" => { "type"=>"int8[32]", "seq"=>$mem_seq++ }, - "trigger_text" => { "type"=>"int8[256]", "seq"=>$mem_seq++ }, - "trigger_showtext" => { "type"=>"int8[256]", "seq"=>$mem_seq++ }, + 'trigger_data' => { 'type'=>'TriggerData', 'seq'=>$mem_seq++, 'contents'=> { + 'size' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'trigger_state' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'trigger_score' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'padding' => { 'type'=>'uint32', 'seq'=>$mem_seq++ }, + 'trigger_cause' => { 'type'=>'int8[32]', 'seq'=>$mem_seq++ }, + 'trigger_text' => { 'type'=>'int8[256]', 'seq'=>$mem_seq++ }, + 'trigger_showtext' => { 'type'=>'int8[256]', 'seq'=>$mem_seq++ }, } }, - "end" => { "seq"=>$mem_seq++, "size"=> 0 } + 'end' => { 'seq'=>$mem_seq++, 'size'=> 0 } }; our $mem_size = 0; -our $mem_verified = {}; sub zmMemInit { my $offset = 0; @@ -196,28 +197,28 @@ sub zmMemInit { } } foreach my $member_data ( sort { $a->{seq} <=> $b->{seq} } values( %{$section_data->{contents}} ) ) { - if ( $member_data->{type} eq "long" - || $member_data->{type} eq "ulong" - || $member_data->{type} eq "size_t" + if ( $member_data->{type} eq 'long' + || $member_data->{type} eq 'ulong' + || $member_data->{type} eq 'size_t' ) { $member_data->{size} = $member_data->{align} = $native; - } elsif ( $member_data->{type} eq "int64" - || $member_data->{type} eq "uint64" - || $member_data->{type} eq "time_t64" + } elsif ( $member_data->{type} eq 'int64' + || $member_data->{type} eq 'uint64' + || $member_data->{type} eq 'time_t64' ) { $member_data->{size} = $member_data->{align} = 8; - } elsif ( $member_data->{type} eq "int32" - || $member_data->{type} eq "uint32" - || $member_data->{type} eq "bool4" + } elsif ( $member_data->{type} eq 'int32' + || $member_data->{type} eq 'uint32' + || $member_data->{type} eq 'bool4' ) { $member_data->{size} = $member_data->{align} = 4; - } elsif ($member_data->{type} eq "int16" - || $member_data->{type} eq "uint16" + } elsif ($member_data->{type} eq 'int16' + || $member_data->{type} eq 'uint16' ) { $member_data->{size} = $member_data->{align} = 2; - } elsif ( $member_data->{type} eq "int8" - || $member_data->{type} eq "uint8" - || $member_data->{type} eq "bool1" + } elsif ( $member_data->{type} eq 'int8' + || $member_data->{type} eq 'uint8' + || $member_data->{type} eq 'bool1' ) { $member_data->{size} = $member_data->{align} = 1; } elsif ( $member_data->{type} =~ /^u?int8\[(\d+)\]$/ ) { @@ -249,62 +250,58 @@ sub zmMemVerify { return( undef ); } - my $mem_key = zmMemKey( $monitor ); - if ( !defined($mem_verified->{$mem_key}) ) { - my $sd_size = zmMemRead( $monitor, "shared_data:size", 1 ); - if ( $sd_size != $mem_data->{shared_data}->{size} ) { - if ( $sd_size ) { - Error( "Shared data size conflict in shared_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{shared_data}->{size} - .", got " - .$sd_size - ); - } else { - Debug( "Shared data size conflict in shared_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{shared_data}->{size} - .", got ".$sd_size - ); - } - return( undef ); + my $sd_size = zmMemRead( $monitor, 'shared_data:size', 1 ); + if ( $sd_size != $mem_data->{shared_data}->{size} ) { + if ( $sd_size ) { + Error( "Shared data size conflict in shared_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{shared_data}->{size} + .", got " + .$sd_size + ); + } else { + Debug( "Shared data size conflict in shared_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{shared_data}->{size} + .", got ".$sd_size + ); } - my $td_size = zmMemRead( $monitor, "trigger_data:size", 1 ); - if ( $td_size != $mem_data->{trigger_data}->{size} ) { - if ( $td_size ) { - Error( "Shared data size conflict in trigger_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{triggger_data}->{size} - .", got " - .$td_size - ); - } else { - Debug( "Shared data size conflict in trigger_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{triggger_data}->{size} - .", got " - .$td_size - ); - } - return( undef ); - } - $mem_verified->{$mem_key} = !undef; + return( undef ); } + my $td_size = zmMemRead( $monitor, 'trigger_data:size', 1 ); + if ( $td_size != $mem_data->{trigger_data}->{size} ) { + if ( $td_size ) { + Error( "Shared data size conflict in trigger_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{triggger_data}->{size} + .", got " + .$td_size + ); + } else { + Debug( "Shared data size conflict in trigger_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{triggger_data}->{size} + .", got " + .$td_size + ); + } + return( undef ); + } + if ( !zmMemRead($monitor, 'shared_data:valid',1) ) { + Error( "Shared data not valid for monitor $$monitor{Id}" ); + return( undef ); + } + return( !undef ); } sub zmMemRead { my $monitor = shift; my $fields = shift; - my $nocheck = shift; - - if ( !($nocheck || zmMemVerify( $monitor )) ) { - return( undef ); - } if ( !ref($fields) ) { $fields = [ $fields ]; @@ -325,32 +322,32 @@ sub zmMemRead { return( undef ); } my $value; - if ( $type eq "long" ) { - ( $value ) = unpack( "l!", $data ); - } elsif ( $type eq "ulong" || $type eq "size_t" ) { - ( $value ) = unpack( "L!", $data ); - } elsif ( $type eq "int64" || $type eq "time_t64" ) { -# The "q" type is only available on 64bit platforms, so use native. - ( $value ) = unpack( "l!", $data ); - } elsif ( $type eq "uint64" ) { -# The "q" type is only available on 64bit platforms, so use native. - ( $value ) = unpack( "L!", $data ); - } elsif ( $type eq "int32" ) { - ( $value ) = unpack( "l", $data ); - } elsif ( $type eq "uint32" || $type eq "bool4" ) { - ( $value ) = unpack( "L", $data ); - } elsif ( $type eq "int16" ) { - ( $value ) = unpack( "s", $data ); - } elsif ( $type eq "uint16" ) { - ( $value ) = unpack( "S", $data ); - } elsif ( $type eq "int8" ) { - ( $value ) = unpack( "c", $data ); - } elsif ( $type eq "uint8" || $type eq "bool1" ) { - ( $value ) = unpack( "C", $data ); + if ( $type eq 'long' ) { + ( $value ) = unpack( 'l!', $data ); + } elsif ( $type eq 'ulong' || $type eq 'size_t' ) { + ( $value ) = unpack( 'L!', $data ); + } elsif ( $type eq 'int64' || $type eq 'time_t64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + ( $value ) = unpack( 'l!', $data ); + } elsif ( $type eq 'uint64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + ( $value ) = unpack( 'L!', $data ); + } elsif ( $type eq 'int32' ) { + ( $value ) = unpack( 'l', $data ); + } elsif ( $type eq 'uint32' || $type eq 'bool4' ) { + ( $value ) = unpack( 'L', $data ); + } elsif ( $type eq 'int16' ) { + ( $value ) = unpack( 's', $data ); + } elsif ( $type eq 'uint16' ) { + ( $value ) = unpack( 'S', $data ); + } elsif ( $type eq 'int8' ) { + ( $value ) = unpack( 'c', $data ); + } elsif ( $type eq 'uint8' || $type eq 'bool1' ) { + ( $value ) = unpack( 'C', $data ); } elsif ( $type =~ /^int8\[\d+\]$/ ) { - ( $value ) = unpack( "Z".$size, $data ); + ( $value ) = unpack( 'Z'.$size, $data ); } elsif ( $type =~ /^uint8\[\d+\]$/ ) { - ( $value ) = unpack( "C".$size, $data ); + ( $value ) = unpack( 'C'.$size, $data ); } else { Fatal( "Unexpected type '".$type."' found for '".$field."'" ); } @@ -366,8 +363,9 @@ sub zmMemInvalidate { my $monitor = shift; my $mem_key = zmMemKey($monitor); if ( $mem_key ) { - delete $mem_verified->{$mem_key}; zmMemDetach( $monitor ); + } else { + print "no memkey in zmMemInvalidate\n"; } } @@ -395,32 +393,32 @@ sub zmMemWrite { my $size = $mem_data->{$section}->{contents}->{$element}->{size}; my $data; - if ( $type eq "long" ) { - $data = pack( "l!", $value ); - } elsif ( $type eq "ulong" || $type eq "size_t" ) { - $data = pack( "L!", $value ); - } elsif ( $type eq "int64" || $type eq "time_t64" ) { -# The "q" type is only available on 64bit platforms, so use native. - $data = pack( "l!", $value ); - } elsif ( $type eq "uint64" ) { -# The "q" type is only available on 64bit platforms, so use native. - $data = pack( "L!", $value ); - } elsif ( $type eq "int32" ) { - $data = pack( "l", $value ); - } elsif ( $type eq "uint32" || $type eq "bool4" ) { - $data = pack( "L", $value ); - } elsif ( $type eq "int16" ) { - $data = pack( "s", $value ); - } elsif ( $type eq "uint16" ) { - $data = pack( "S", $value ); - } elsif ( $type eq "int8" ) { - $data = pack( "c", $value ); - } elsif ( $type eq "uint8" || $type eq "bool1" ) { - $data = pack( "C", $value ); + if ( $type eq 'long' ) { + $data = pack( 'l!', $value ); + } elsif ( $type eq 'ulong' || $type eq 'size_t' ) { + $data = pack( 'L!', $value ); + } elsif ( $type eq 'int64' || $type eq 'time_t64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + $data = pack( 'l!', $value ); + } elsif ( $type eq 'uint64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + $data = pack( 'L!', $value ); + } elsif ( $type eq 'int32' ) { + $data = pack( 'l', $value ); + } elsif ( $type eq 'uint32' || $type eq 'bool4' ) { + $data = pack( 'L', $value ); + } elsif ( $type eq 'int16' ) { + $data = pack( 's', $value ); + } elsif ( $type eq 'uint16' ) { + $data = pack( 'S', $value ); + } elsif ( $type eq 'int8' ) { + $data = pack( 'c', $value ); + } elsif ( $type eq 'uint8' || $type eq 'bool1' ) { + $data = pack( 'C', $value ); } elsif ( $type =~ /^int8\[\d+\]$/ ) { - $data = pack( "Z".$size, $value ); + $data = pack( 'Z'.$size, $value ); } elsif ( $type =~ /^uint8\[\d+\]$/ ) { - $data = pack( "C".$size, $value ); + $data = pack( 'C'.$size, $value ); } else { Fatal( "Unexpected type '".$type."' found for '".$field."'" ); } @@ -439,26 +437,26 @@ sub zmMemWrite { sub zmGetMonitorState { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:state" ) ); + return( zmMemRead( $monitor, 'shared_data:state' ) ); } sub zmGetAlarmLocation { my $monitor = shift; - return( zmMemRead( $monitor, [ "shared_data:alarm_x", "shared_data:alarm_y" ] ) ); + return( zmMemRead( $monitor, [ 'shared_data:alarm_x', 'shared_data:alarm_y' ] ) ); } sub zmSetControlState { my $monitor = shift; my $control_state = shift; - zmMemWrite( $monitor, { "shared_data:control_state" => $control_state } ); + zmMemWrite( $monitor, { 'shared_data:control_state' => $control_state } ); } sub zmGetControlState { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:control_state" ) ); + return( zmMemRead( $monitor, 'shared_data:control_state' ) ); } sub zmSaveControlState { @@ -494,8 +492,8 @@ sub zmHasAlarmed { my $monitor = shift; my $last_event_id = shift; - my ( $state, $last_event ) = zmMemRead( $monitor, [ "shared_data:state" - ,"shared_data:last_event" + my ( $state, $last_event ) = zmMemRead( $monitor, [ 'shared_data:state' + ,'shared_data:last_event' ] ); @@ -507,66 +505,70 @@ sub zmHasAlarmed { return( undef ); } +sub zmGetStartupTime { + return zmMemRead( $_[0], 'shared_data:startup_time' ); +} + sub zmGetLastEvent { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:last_event" ) ); + return( zmMemRead( $monitor, 'shared_data:last_event' ) ); } sub zmGetLastWriteTime { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:last_write_time" ) ); + return( zmMemRead( $monitor, 'shared_data:last_write_time' ) ); } sub zmGetLastReadTime { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:last_read_time" ) ); + return( zmMemRead( $monitor, 'shared_data:last_read_time' ) ); } sub zmGetMonitorActions { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:action" ) ); + return( zmMemRead( $monitor, 'shared_data:action' ) ); } sub zmMonitorEnable { my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); + my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_SUSPEND; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmMonitorDisable { my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); + my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_RESUME; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmMonitorSuspend { my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); + my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_SUSPEND; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmMonitorResume { my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); + my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_RESUME; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmGetTriggerState { my $monitor = shift; - return( zmMemRead( $monitor, "trigger_data:trigger_state" ) ); + return( zmMemRead( $monitor, 'trigger_data:trigger_state' ) ); } sub zmTriggerEventOn { @@ -577,12 +579,12 @@ sub zmTriggerEventOn { my $showtext = shift; my $values = { - "trigger_data:trigger_score" => $score, - "trigger_data:trigger_cause" => $cause, + 'trigger_data:trigger_score' => $score, + 'trigger_data:trigger_cause' => $cause, }; - $values->{"trigger_data:trigger_text"} = $text if ( defined($text) ); - $values->{"trigger_data:trigger_showtext"} = $showtext if ( defined($showtext) ); - $values->{"trigger_data:trigger_state"} = TRIGGER_ON; # Write state last so event not read incomplete + $values->{'trigger_data:trigger_text'} = $text if ( defined($text) ); + $values->{'trigger_data:trigger_showtext'} = $showtext if ( defined($showtext) ); + $values->{'trigger_data:trigger_state'} = TRIGGER_ON; # Write state last so event not read incomplete zmMemWrite( $monitor, $values ); } @@ -591,11 +593,11 @@ sub zmTriggerEventOff { my $monitor = shift; my $values = { - "trigger_data:trigger_state" => TRIGGER_OFF, - "trigger_data:trigger_score" => 0, - "trigger_data:trigger_cause" => "", - "trigger_data:trigger_text" => "", - "trigger_data:trigger_showtext" => "", + 'trigger_data:trigger_state' => TRIGGER_OFF, + 'trigger_data:trigger_score' => 0, + 'trigger_data:trigger_cause' => '', + 'trigger_data:trigger_text' => '', + 'trigger_data:trigger_showtext' => '', }; zmMemWrite( $monitor, $values ); @@ -605,11 +607,11 @@ sub zmTriggerEventCancel { my $monitor = shift; my $values = { - "trigger_data:trigger_state" => TRIGGER_CANCEL, - "trigger_data:trigger_score" => 0, - "trigger_data:trigger_cause" => "", - "trigger_data:trigger_text" => "", - "trigger_data:trigger_showtext" => "", + 'trigger_data:trigger_state' => TRIGGER_CANCEL, + 'trigger_data:trigger_score' => 0, + 'trigger_data:trigger_cause' => '', + 'trigger_data:trigger_text' => '', + 'trigger_data:trigger_showtext' => '', }; zmMemWrite( $monitor, $values ); @@ -620,7 +622,7 @@ sub zmTriggerShowtext { my $showtext = shift; my $values = { - "trigger_data:trigger_showtext" => $showtext, + 'trigger_data:trigger_showtext' => $showtext, }; zmMemWrite( $monitor, $values ); @@ -645,11 +647,11 @@ if ( zmMemVerify( $monitor ) ) { } } -( $lri, $lwi ) = zmMemRead( $monitor, [ "shared_data:last_read_index", - "shared_data:last_write_index" +( $lri, $lwi ) = zmMemRead( $monitor, [ 'shared_data:last_read_index', + 'shared_data:last_write_index' ] ); -zmMemWrite( $monitor, { "trigger_data:trigger_showtext" => "Some Text" } ); +zmMemWrite( $monitor, { 'trigger_data:trigger_showtext' => "Some Text" } ); =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm index e41019647..1de9571ff 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm @@ -68,133 +68,118 @@ use ZoneMinder::Logger qw(:all); use Sys::Mmap; -sub zmMemKey -{ - my $monitor = shift; - return( defined($monitor->{MMapAddr})?$monitor->{MMapAddr}:undef ); +sub zmMemKey { + my $monitor = shift; + return $monitor->{MMapAddr}; } -sub zmMemAttach -{ - my ( $monitor, $size ) = @_; - if ( ! $size ) { - Error( "No size passed to zmMemAttach for monitor $$monitor{Id}\n" ); - return( undef ); +sub zmMemAttach { + my ( $monitor, $size ) = @_; + if ( ! $size ) { + Error( "No size passed to zmMemAttach for monitor $$monitor{Id}\n" ); + return( undef ); + } + if ( !defined($monitor->{MMapAddr}) ) { + + my $mmap_file = $Config{ZM_PATH_MAP}."/zm.mmap.".$monitor->{Id}; + if ( ! -e $mmap_file ) { + Error( sprintf( "Memory map file '%s' does not exist. zmc might not be running." + , $mmap_file + ) + ); + return ( undef ); } - if ( !defined($monitor->{MMapAddr}) ) - { - my $mmap_file = $Config{ZM_PATH_MAP}."/zm.mmap.".$monitor->{Id}; - if ( ! -e $mmap_file ) { - Error( sprintf( "Memory map file '%s' does not exist. zmc might not be running." - , $mmap_file - ) - ); - return ( undef ); - } + my $mmap_file_size = -s $mmap_file; - my $mmap_file_size = -s $mmap_file; - - if ( $mmap_file_size < $size ) { - Error( sprintf( "Memory map file '%s' should have been %d but was instead %d" - , $mmap_file - , $size - , $mmap_file_size - ) - ); - return ( undef ); - } - if ( !open( MMAP, "+<", $mmap_file ) ) - { - Error( sprintf( "Can't open memory map file '%s': $!\n", $mmap_file ) ); - return( undef ); - } - my $mmap = undef; - my $mmap_addr = mmap( $mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, \*MMAP ); - if ( !$mmap_addr || !$mmap ) - { - Error( sprintf( "Can't mmap to file '%s': $!\n", $mmap_file ) ); - close( MMAP ); - return( undef ); - } - $monitor->{MMapHandle} = \*MMAP; - $monitor->{MMapAddr} = $mmap_addr; - $monitor->{MMap} = \$mmap; + if ( $mmap_file_size < $size ) { + Error( sprintf( "Memory map file '%s' should have been %d but was instead %d" + , $mmap_file + , $size + , $mmap_file_size + ) + ); + return ( undef ); } - return( !undef ); + my $MMAP; + if ( !open( $MMAP, "+<", $mmap_file ) ) { + Error( sprintf( "Can't open memory map file '%s': $!\n", $mmap_file ) ); + return( undef ); + } + my $mmap = undef; + my $mmap_addr = mmap( $mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, $MMAP ); + if ( !$mmap_addr || !$mmap ) { + Error( sprintf( "Can't mmap to file '%s': $!\n", $mmap_file ) ); + close( $MMAP ); + return( undef ); + } + $monitor->{MMapHandle} = $MMAP; + $monitor->{MMapAddr} = $mmap_addr; + $monitor->{MMap} = \$mmap; + } + return( !undef ); } -sub zmMemDetach -{ - my $monitor = shift; - - if ( $monitor->{MMap} ) - { - if ( ! munmap( ${$monitor->{MMap}} ) ) { - Warn( "Unable to munmap for monitor $$monitor{Id}\n"); - } - delete $monitor->{MMap}; - } - if ( $monitor->{MMapAddr} ) - { - delete $monitor->{MMapAddr}; - } - if ( $monitor->{MMapHandle} ) - { - close( $monitor->{MMapHandle} ); - delete $monitor->{MMapHandle}; +sub zmMemDetach { + my $monitor = shift; + if ( $monitor->{MMap} ) { + if ( ! munmap( ${$monitor->{MMap}} ) ) { + Warn( "Unable to munmap for monitor $$monitor{Id}\n"); } + delete $monitor->{MMap}; + } + if ( $monitor->{MMapAddr} ) { + delete $monitor->{MMapAddr}; + } + if ( $monitor->{MMapHandle} ) { + close( $monitor->{MMapHandle} ); + delete $monitor->{MMapHandle}; + } } -sub zmMemGet -{ - my $monitor = shift; - my $offset = shift; - my $size = shift; +sub zmMemGet { + my $monitor = shift; + my $offset = shift; + my $size = shift; - my $mmap = $monitor->{MMap}; - if ( !$mmap || !$$mmap ) - { - Error( sprintf( "Can't read from mapped memory for monitor '%d', gone away?" - , $monitor->{Id} - ) + my $mmap = $monitor->{MMap}; + if ( !$mmap || !$$mmap ) { + Error( sprintf( "Can't read from mapped memory for monitor '%d', gone away?" + , $monitor->{Id} + ) ); - return( undef ); - } - my $data = substr( $$mmap, $offset, $size ); - return( $data ); + return( undef ); + } + my $data = substr( $$mmap, $offset, $size ); + return( $data ); } -sub zmMemPut -{ - my $monitor = shift; - my $offset = shift; - my $size = shift; - my $data = shift; +sub zmMemPut { + my $monitor = shift; + my $offset = shift; + my $size = shift; + my $data = shift; - my $mmap = $monitor->{MMap}; - if ( !$mmap || !$$mmap ) - { - Error( sprintf( "Can't write mapped memory for monitor '%d', gone away?" - , $monitor->{Id} - ) + my $mmap = $monitor->{MMap}; + if ( !$mmap || !$$mmap ) { + Error( sprintf( "Can't write mapped memory for monitor '%d', gone away?" + , $monitor->{Id} + ) ); - return( undef ); - } - substr( $$mmap, $offset, $size ) = $data; - return( !undef ); + return( undef ); + } + substr( $$mmap, $offset, $size ) = $data; + return( !undef ); } -sub zmMemClean -{ - Debug( "Removing memory map files\n" ); - my $mapPath = $Config{ZM_PATH_MAP}."/zm.mmap.*"; - foreach my $mapFile( glob( $mapPath ) ) - { - ( $mapFile ) = $mapFile =~ /^(.+)$/; - Debug( "Removing memory map file '$mapFile'\n" ); - unlink( $mapFile ); - } +sub zmMemClean { + Debug( "Removing memory map files\n" ); + my $mapPath = $Config{ZM_PATH_MAP}."/zm.mmap.*"; + foreach my $mapFile( glob( $mapPath ) ) { + ( $mapFile ) = $mapFile =~ /^(.+)$/; + Debug( "Removing memory map file '$mapFile'\n" ); + unlink( $mapFile ); + } } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm new file mode 100644 index 000000000..0ab20d162 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -0,0 +1,113 @@ +# ========================================================================== +# +# ZoneMinder Monitor Module, $Date$, $Revision$ +# Copyright (C) 2001-2008 Philip Coombes +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Monitor; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; +require ZoneMinder::Storage; +require ZoneMinder::Server; + +#our @ISA = qw(Exporter ZoneMinder::Base); +use parent qw(ZoneMinder::Object); + +# ========================================================================== +# +# General Utility Functions +# +# ========================================================================== + +use ZoneMinder::Config qw(:all); +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Database qw(:all); + +use POSIX; +use vars qw/ $table $primary_key /; +$table = 'Monitors'; +$primary_key = 'Id'; + +sub Server { + return new ZoneMinder::Server( $_[0]{ServerId} ); +} # end sub Server + +sub Storage { + return new ZoneMinder::Storage( $_[0]{StorageId} ); +} # end sub Storage + +1; +__END__ +# Below is stub documentation for your module. You'd better edit it! + +=head1 NAME + +ZoneMinder::Database - Perl extension for blah blah blah + +=head1 SYNOPSIS + + use ZoneMinder::Monitor; + blah blah blah + +=head1 DESCRIPTION + +Stub documentation for ZoneMinder, created by h2xs. It looks like the +author of the extension was negligent enough to leave the stub +unedited. + +Blah blah blah. + +=head2 EXPORT + +None by default. + + + +=head1 SEE ALSO + +Mention other useful documentation such as the documentation of +related modules or operating system documentation (such as man pages +in UNIX), or any relevant external documentation such as RFCs or +standards. + +If you have a mailing list set up for your module, mention it here. + +If you have a web site set up for your module, mention it here. + +=head1 AUTHOR + +Philip Coombes, Ephilip.coombes@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2008 Philip Coombes + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in new file mode 100644 index 000000000..3b295c45f --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in @@ -0,0 +1,358 @@ +# ========================================================================== +# +# ZoneMinder ONVIF Module, $Date$, $Revision$ +# Copyright (C) 2001-2008 Philip Coombes +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::ONVIF; + +use 5.006; +use strict; +use warnings; + +require Exporter; +require ZoneMinder::Base; + +our @ISA = qw(Exporter); + +our %EXPORT_TAGS = ( + functions => [ qw( + ) ] + ); +push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; + +our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); + +our @EXPORT = qw(); + +our $VERSION = $ZoneMinder::Base::VERSION; + +use Getopt::Std; +use Data::UUID; + +require ONVIF::Client; + +require WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort; +require WSDiscovery10::Elements::Header; +require WSDiscovery10::Elements::Types; +require WSDiscovery10::Elements::Scopes; + +require WSDiscovery::TransportUDP; + +sub deserialize_message { + my ($wsdl_client, $response) = @_; + +# copied and adapted from SOAP::WSDL::Client + +# get deserializer + my $deserializer = $wsdl_client->get_deserializer(); + + if(! $deserializer) { + $deserializer = SOAP::WSDL::Factory::Deserializer->get_deserializer({ + soap_version => $wsdl_client->get_soap_version(), + %{ $wsdl_client->get_deserializer_args() }, + }); + } +# set class resolver if serializer supports it + $deserializer->set_class_resolver( $wsdl_client->get_class_resolver() ) + if ( $deserializer->can('set_class_resolver') ); + +# Try deserializing response - there may be some, +# even if transport did not succeed (got a 500 response) + if ( $response ) { +# as our faults are false, returning a success marker is the only +# reliable way of determining whether the deserializer succeeded. +# Custom deserializers may return an empty list, or undef, +# and $@ is not guaranteed to be undefined. + my ($success, $result_body, $result_header) = eval { + (1, $deserializer->deserialize( $response )); + }; + if (defined $success) { + return wantarray + ? ($result_body, $result_header) + : $result_body; + } + elsif (blessed $@) { #}&& $@->isa('SOAP::WSDL::SOAP::Typelib::Fault11')) { + return $@; + } + else { + return $deserializer->generate_fault({ + code => 'soap:Server', + role => 'urn:localhost', + message => "Error deserializing message: $@. \n" + . "Message was: \n$response" + }); + } + }; +} +ub interpret_messages { + my ($svc_discover, $services, @responses ) = @_; + + my @results; + foreach my $response ( @responses ) { + + if($verbose) { + print "Received message:\n" . $response . "\n"; + } + + my $result = deserialize_message($svc_discover, $response); + if(not $result) { + print "Error deserializing message. No message returned from deserializer.\n" if $verbose; + next; + } + + my $xaddr; + foreach my $l_xaddr (split ' ', $result->get_ProbeMatch()->get_XAddrs()) { +# find IPv4 address + print "l_xaddr = $l_xaddr\n" if $verbose; + if($l_xaddr =~ m|//[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+[:/]|) { + $xaddr = $l_xaddr; + last; + } else { + print STDERR "Unable to find IPv4 address from xaddr $l_xaddr\n"; + } + } + +# No usable address found + next if not $xaddr; + +# ignore multiple responses from one service + next if defined $services->{$xaddr}; + $services->{$xaddr} = 1; + + print "$xaddr, " . $svc_discover->get_soap_version() . ", "; + + print "("; + my $scopes = $result->get_ProbeMatch()->get_Scopes(); + my $count = 0; + my %scopes; + foreach my $scope(split ' ', $scopes) { + if($scope =~ m|onvif://www\.onvif\.org/(.+)/(.*)|) { + my ($attr, $value) = ($1,$2); + if( 0 < $count ++) { + print ", "; + } + print $attr . "=\'" . $value . "\'"; + $scopes{$attr} = $value; + } + } + print ")\n"; + push @results, { xaddr=>$xaddr, + soap_version => $svc_discover->get_soap_version(), + scopes => \%scopes, + }; + + } + return @results; +} # end sub interpret_messages + +# functions + +sub discover { + my @results; + +## collect all responses + my @responses = (); + + no warnings 'redefine'; + + *WSDiscovery::TransportUDP::_notify_response = sub { + my ($transport, $response) = @_; + push @responses, $response; + }; + +## try both soap versions + my %services; + + my $uuid_gen = Data::UUID->new(); + + if ( ( ! $soap_version ) or ( $soap_version eq '1.1' ) ) { + + if($verbose) { + print "Probing for SOAP 1.1\n" + } + my $svc_discover = WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort->new({ +# no_dispatch => '1', + }); + $svc_discover->set_soap_version('1.1'); + + my $uuid = $uuid_gen->create_str(); + + my $result = $svc_discover->ProbeOp( + { # WSDiscovery::Types::ProbeType + Types => 'http://www.onvif.org/ver10/network/wsdl:NetworkVideoTransmitter http://www.onvif.org/ver10/device/wsdl:Device', # QNameListType + Scopes => { value => '' }, + }, + WSDiscovery10::Elements::Header->new({ + Action => { value => 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' }, + MessageID => { value => "urn:uuid:$uuid" }, + To => { value => 'urn:schemas-xmlsoap-org:ws:2005:04:discovery' }, + }) + ); + print $result . "\n" if $verbose; + + push @results, interpret_messages($svc_discover, \%services, @responses); + @responses = (); + } # end if doing soap 1.1 + + if ( ( ! $soap_version ) or ( $soap_version eq '1.2' ) ) { + if($verbose) { + print "Probing for SOAP 1.2\n" + } + my $svc_discover = WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort->new({ +# no_dispatch => '1', + }); + $svc_discover->set_soap_version('1.2'); + +# copies of the same Probe message must have the same MessageID. +# This is not a copy. So we generate a new uuid. + my $uuid = $uuid_gen->create_str(); + +# Everyone else, like the nodejs onvif code and odm only ask for NetworkVideoTransmitter + my $result = $svc_discover->ProbeOp( + { # WSDiscovery::Types::ProbeType + xmlattr => { 'xmlns:dn' => 'http://www.onvif.org/ver10/network/wsdl', }, + Types => 'dn:NetworkVideoTransmitter', # QNameListType + Scopes => { value => '' }, + }, + WSDiscovery10::Elements::Header->new({ + Action => { value => 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' }, + MessageID => { value => "urn:uuid:$uuid" }, + To => { value => 'urn:schemas-xmlsoap-org:ws:2005:04:discovery' }, + }) + ); + print $result . "\n" if $verbose; + push @results, interpret_messages($svc_discover, \%services, @responses); + } # end if doing soap 1.2 + return @results; +} + +sub profiles { + my $result = $client->get_endpoint('media')->GetProfiles( { } ,, ); + die $result if not $result; + if($verbose) { + print "Received message:\n" . $result . "\n"; + } + + my $profiles = $result->get_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 + foreach my $streamtype ( 'RTP_unicast', 'RTP-Unicast' ) { + $result = $client->get_endpoint('media')->GetStreamUri( { + StreamSetup => { # ONVIF::Media::Types::StreamSetup + Stream => $streamtype, # StreamType + Transport => { # ONVIF::Media::Types::Transport + Protocol => 'RTSP', # TransportProtocol + }, + }, + ProfileToken => $token, # ReferenceToken + } ,, ); + last if $result; + } + die $result if not $result; +# print $result . "\n"; + + print $result->get_MediaUri()->get_Uri() . + "\n"; + } # end foreach profile + +# +# use message parser without schema validation ??? +# + +} + +sub move { + my ($dir) = @_; + + my $result = $client->get_endpoint('ptz')->GetNodes( { } ,, ); + + die $result if not $result; + print $result . "\n"; +} # end sub move + +sub metadata { + my $result = $client->get_endpoint('media')->GetMetadataConfigurations( { } ,, ); + die $result if not $result; + print $result . "\n"; + + $result = $client->get_endpoint('media')->GetVideoAnalyticsConfigurations( { } ,, ); + die $result if not $result; + print $result . "\n"; + +# $result = $client->get_endpoint('analytics')->GetServiceCapabilities( { } ,, ); +# die $result if not $result; +# print $result . "\n"; + +} + + + +1; +__END__ + +=head1 NAME + +ZoneMinder::ONVIF - perl module to access onvif functions for ZoneMinder + +=head1 SYNOPSIS + +use ZoneMinder::ONVIF; + +=head1 DESCRIPTION + +This is a module to contain useful functions and import all the other modules +required for ONVIF to work. + +=head2 EXPORT + +None by default. + +=head1 SEE ALSO + +http://www.zoneminder.com + +=head1 AUTHOR + +Philip Coombes, Ephilip.coombes@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2008 Philip Coombes + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm new file mode 100644 index 000000000..ef9ee9714 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -0,0 +1,166 @@ +# ========================================================================== +# +# ZoneMinder Object Module, $Date$, $Revision$ +# Copyright (C) 2001-2008 Philip Coombes +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Object; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; + +our @ISA = qw(ZoneMinder::Base); + +# ========================================================================== +# +# General Utility Functions +# +# ========================================================================== + +use ZoneMinder::Config qw(:all); +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Database qw(:all); + +use vars qw/ $AUTOLOAD /; + +sub new { + my ( $parent, $id, $data ) = @_; + + my $self = {}; + bless $self, $parent; + no strict 'refs'; + my $primary_key = ${$parent.'::primary_key'}; + if ( ! $primary_key ) { + Error( 'NO primary_key for type ' . $parent ); + return; + } # end if + if ( ( $$self{$primary_key} = $id ) or $data ) { +#$log->debug("loading $parent $id") if $debug or DEBUG_ALL; + $self->load( $data ); + } + return $self; +} # end sub new + +sub load { + my ( $self, $data ) = @_; + my $type = ref $self; + if ( ! $data ) { + no strict 'refs'; + my $table = ${$type.'::table'}; + if ( ! $table ) { + Error( 'NO table for type ' . $type ); + return; + } # end if + my $primary_key = ${$type.'::primary_key'}; + if ( ! $primary_key ) { + Error( 'NO primary_key for type ' . $type ); + return; + } # end if + + if ( ! $$self{$primary_key} ) { + my ( $caller, undef, $line ) = caller; + Error( (ref $self) . "::load called without $primary_key from $caller:$line"); + } else { +#$log->debug("Object::load Loading from db $type"); + Debug("Loading $type from $table WHERE $primary_key = $$self{$primary_key}"); + $data = $ZoneMinder::Database::dbh->selectrow_hashref( "SELECT * FROM $table WHERE $primary_key=?", {}, $$self{$primary_key} ); + if ( ! $data ) { + if ( $ZoneMinder::Database::dbh->errstr ) { + Error( "Failure to load Object record for $$self{$primary_key}: Reason: " . $ZoneMinder::Database::dbh->errstr ); + } else { + Debug("No Results Loading $type from $table WHERE $primary_key = $$self{$primary_key}"); + } # end if + } # end if + } # end if + } # end if ! $data + if ( $data and %$data ) { + @$self{keys %$data} = values %$data; + } # end if +} # end sub load + +sub AUTOLOAD { + my ( $self, $newvalue ) = @_; + my $type = ref($_[0]); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( @_ > 1 ) { + return $_[0]{$name} = $_[1]; + } + return $_[0]{$name}; +} + + +1; +__END__ +# Below is stub documentation for your module. You'd better edit it! + +=head1 NAME + +ZoneMinder::Database - Perl extension for blah blah blah + +=head1 SYNOPSIS + + use ZoneMinder::Object; + + This package should likely not be used directly, as it is meant mainly to be a parent for all other ZoneMinder classes. + +=head1 DESCRIPTION + +Stub documentation for ZoneMinder, created by h2xs. It looks like the +author of the extension was negligent enough to leave the stub +unedited. + +Blah blah blah. + +=head2 EXPORT + +None by default. + + + +=head1 SEE ALSO + +Mention other useful documentation such as the documentation of +related modules or operating system documentation (such as man pages +in UNIX), or any relevant external documentation such as RFCs or +standards. + +If you have a mailing list set up for your module, mention it here. + +If you have a web site set up for your module, mention it here. + +=head1 AUTHOR + +Philip Coombes, Ephilip.coombes@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2008 Philip Coombes + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Storage.pm b/scripts/ZoneMinder/lib/ZoneMinder/Storage.pm new file mode 100644 index 000000000..34158b9e4 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Storage.pm @@ -0,0 +1,162 @@ +# ========================================================================== +# +# ZoneMinder Storage Module, $Date$, $Revision$ +# Copyright (C) 2001-2008 Philip Coombes +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Storage; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; + +use parent qw(Exporter ZoneMinder::Object); + +# ========================================================================== +# +# General Utility Functions +# +# ========================================================================== + +use ZoneMinder::Config qw(:all); +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Database qw(:all); + +use POSIX; + +use vars qw/ $table $primary_key /; +$table = 'Storage'; +$primary_key = 'Id'; +#__PACKAGE__->table('Storage'); +#__PACKAGE__->primary_key('Id'); + +sub find { + shift if $_[0] eq 'ZoneMinder::Storage'; + my %sql_filters = @_; + + my $sql = 'SELECT * FROM Storage'; + my @sql_filters; + my @sql_values; + + if ( exists $sql_filters{Name} ) { + push @sql_filters , ' Name = ? '; + push @sql_values, $sql_filters{Name}; + } + if ( exists $sql_filters{ServerId} ) { + push @sql_filters, ' Id IN ( SELECT StorageId FROM Monitors WHERE ServerId=? )'; + push @sql_values, $sql_filters{ServerId}; + } + + + $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; + $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; + + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( @sql_values ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + my @results; + + while( my $db_filter = $sth->fetchrow_hashref() ) { + my $filter = new ZoneMinder::Storage( $$db_filter{Id}, $db_filter ); + push @results, $filter; + } # end while + return @results; +} + +sub find_one { + my @results = find(@_); + return $results[0] if @results; +} + +sub Path { + if ( @_ > 1 ) { + $_[0]{Path} = $_[1]; + } + if ( ! ( $_[0]{Id} or $_[0]{Path} ) ) { + $_[0]{Path} = ($Config{ZM_DIR_EVENTS}=~/^\//) ? $Config{ZM_DIR_EVENTS} : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) + } + return $_[0]{Path}; +} # end sub Path + +sub Name { + if ( @_ > 1 ) { + $_[0]{Name} = $_[1]; + } + return $_[0]{Name}; +} # end sub Path + +1; +__END__ +# Below is stub documentation for your module. You'd better edit it! + +=head1 NAME + +ZoneMinder::Database - Perl extension for blah blah blah + +=head1 SYNOPSIS + + use ZoneMinder::Storage; + blah blah blah + +=head1 DESCRIPTION + +Stub documentation for ZoneMinder, created by h2xs. It looks like the +author of the extension was negligent enough to leave the stub +unedited. + +Blah blah blah. + +=head2 EXPORT + +None by default. + + + +=head1 SEE ALSO + +Mention other useful documentation such as the documentation of +related modules or operating system documentation (such as man pages +in UNIX), or any relevant external documentation such as RFCs or +standards. + +If you have a mailing list set up for your module, mention it here. + +If you have a web site set up for your module, mention it here. + +=head1 AUTHOR + +Philip Coombes, Ephilip.coombes@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2008 Philip Coombes + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index f74f8ed90..cee574975 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -41,10 +41,12 @@ yet. =head1 OPTIONS - -r, --report - Just report don't actually do anything - -i, --interactive - Ask before applying any changes -c, --continuous - Run continuously + -i, --interactive - Ask before applying any changes + -m, --monitor_id - Restrict zmaudit actions to events pertaining to the specified monitor id + -r, --report - Just report don't actually do anything -v, --version - Print the installed version of ZoneMinder + =cut use strict; @@ -56,8 +58,7 @@ use bytes; # # ========================================================================== -use constant MAX_AGED_DIRS => 10; # Number of event dirs to check age on -use constant RECOVER_TAG => "(r)"; # Tag to append to event name when recovered +use constant RECOVER_TAG => '(r)'; # Tag to append to event name when recovered use constant RECOVER_TEXT => "Recovered."; # Text to append to event notes when recovered # ========================================================================== @@ -90,15 +91,17 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $report = 0; my $interactive = 0; my $continuous = 0; +my $monitor_id = 0; my $version; logInit(); logSetSignal(); GetOptions( - 'report' =>\$report, 'interactive' =>\$interactive, 'continuous' =>\$continuous, + 'monitor_id=s' =>\$monitor_id, + 'report' =>\$report, 'version' =>\$version ) or pod2usage(-exitstatus => -1); @@ -106,15 +109,16 @@ if ( $version ) { print( ZoneMinder::Base::ZM_VERSION . "\n"); exit(0); } -if ( ($report + $interactive + $continuous) > 1 ) -{ +if ( ($report + $interactive + $continuous) > 1 ) { print( STDERR "Error, only one option may be specified\n" ); pod2usage(-exitstatus => -1); } my $dbh = zmDbConnect(); -chdir( EVENT_PATH ); +require ZoneMinder::Monitor; +require ZoneMinder::Storage; +require ZoneMinder::Event; my $max_image_age = 6/24; # 6 hours my $max_swap_age = 24/24; # 24 hours @@ -123,517 +127,558 @@ my $image_path = IMAGE_PATH; my $loop = 1; my $cleaned = 0; MAIN: while( $loop ) { - while ( ! ( $dbh and $dbh->ping() ) ) { - $dbh = zmDbConnect(); - if ( $continuous ) { - Error("Unable to connect to database"); - # if we are running continuously, then just skip to the next - # interval, otherwise we are a one off run, so wait a second and - # retry until someone kills us. - sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); - } else { - Fatal("Unable to connect to database"); - } # end if - } # end while can't connect to the db + while ( ! ( $dbh and $dbh->ping() ) ) { + $dbh = zmDbConnect(); - if ( $continuous ) { - # if we are running continuously, then just skip to the next - # interval, otherwise we are a one off run, so wait a second and - # retry until someone kills us. - sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); - } else { - sleep 1; - } # end if + if ( $continuous ) { + Error('Unable to connect to database'); +# if we are running continuously, then just skip to the next +# interval, otherwise we are a one off run, so wait a second and +# retry until someone kills us. + sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); + } else { + Fatal('Unable to connect to database'); + } # end if + } # end while can't connect to the db - if ( ! exists $Config{ZM_AUDIT_MIN_AGE} ) { - Fatal("ZM_AUDIT_MIN_AGE is not set in config."); + if ( $continuous ) { + # if we are running continuously, then just skip to the next + # interval, otherwise we are a one off run, so wait a second and + # retry until someone kills us. + sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); + } else { + sleep 1; + } # end if + + if ( ! exists $Config{ZM_AUDIT_MIN_AGE} ) { + Fatal("ZM_AUDIT_MIN_AGE is not set in config."); + } + + my %Monitors; + my $db_monitors; + my $monitorSelectSql = $monitor_id ? 'SELECT * FRO Monitors WHERE Id=?' : 'SELECT * FROM Monitors ORDER BY Id'; + my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) + or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); + + my $eventSelectSql = 'SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) as Age + FROM Events WHERE MonitorId = ? ORDER BY Id'; + my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) + or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); + + $cleaned = 0; + my $res = $monitorSelectSth->execute( $monitor_id ? $monitor_id : () ) + or Fatal( "Can't execute: ".$monitorSelectSth->errstr() ); + while( my $monitor = $monitorSelectSth->fetchrow_hashref() ) { + $Monitors{$$monitor{Id}} = $monitor; + + Debug( "Found database monitor '$monitor->{Id}'" ); + my $db_events = $db_monitors->{$monitor->{Id}} = {}; + my $res = $eventSelectSth->execute( $monitor->{Id} ) + or Fatal( "Can't execute: ".$eventSelectSth->errstr() ); + while ( my $event = $eventSelectSth->fetchrow_hashref() ) { + $db_events->{$event->{Id}} = $event->{Age}; } + Debug( 'Got '.int(keys(%$db_events))." events\n" ); + } - my $db_monitors; - my $monitorSelectSql = "select Id from Monitors order by Id"; - my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) - or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); - my $eventSelectSql = "SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) as Age - FROM Events WHERE MonitorId = ? ORDER BY Id"; - my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) - or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); + my $fs_monitors; - $cleaned = 0; - my $res = $monitorSelectSth->execute() - or Fatal( "Can't execute: ".$monitorSelectSth->errstr() ); - while( my $monitor = $monitorSelectSth->fetchrow_hashref() ) - { - Debug( "Found database monitor '$monitor->{Id}'" ); - my $db_events = $db_monitors->{$monitor->{Id}} = {}; - my $res = $eventSelectSth->execute( $monitor->{Id} ) - or Fatal( "Can't execute: ".$eventSelectSth->errstr() ); - while ( my $event = $eventSelectSth->fetchrow_hashref() ) - { - $db_events->{$event->{Id}} = $event->{Age}; - } - Debug( "Got ".int(keys(%$db_events))." events\n" ); - } + foreach my $Storage ( + ZoneMinder::Storage->find( ($Config{ZM_SERVER_ID} ? ( ServerId => $Config{ZM_SERVER_ID} ) : () ) ), + new ZoneMinder::Storage(), + ) { + Debug('Checking events in ' . $Storage->Path() ); + if ( ! chdir( $Storage->Path() ) ) { + Error( 'Unable to change dir to ' . $Storage->Path() ); + next; + } # end if - my $fs_monitors; - foreach my $monitor ( glob("[0-9]*") ) - { - # Thie glob above gives all files starting with a digit. So a monitor with a name starting with a digit will be in this list. - next if $monitor =~ /\D/; - Debug( "Found filesystem monitor '$monitor'" ); - my $fs_events = $fs_monitors->{$monitor} = {}; - ( my $monitor_dir ) = ( $monitor =~ /^(.*)$/ ); # De-taint + # Please note that this glob will take all files beginning with a digit. + foreach my $monitor ( glob('[0-9]*') ) { + next if $monitor =~ /\D/; - if ( $Config{ZM_USE_DEEP_STORAGE} ) - { - foreach my $day_dir ( glob("$monitor_dir/*/*/*") ) - { - Debug( "Checking $day_dir" ); - ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint - chdir( $day_dir ); - opendir( DIR, "." ) - or Fatal( "Can't open directory '$day_dir': $!" ); - my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); - closedir( DIR ); - my $count = 0; - foreach my $event_link ( @event_links ) - { - Debug( "Checking link $event_link" ); - ( my $event = $event_link ) =~ s/^.*\.//; - my $event_path = readlink( $event_link ); - if ( $count++ > MAX_AGED_DIRS ) - { - $fs_events->{$event} = -1; - } - else - { - if ( !-e $event_path ) - { - aud_print( "Event link $day_dir/$event_link does not point to valid target" ); - if ( confirm() ) - { - ( $event_link ) = ( $event_link =~ /^(.*)$/ ); # De-taint - unlink( $event_link ); - $cleaned = 1; - } - } - else - { - $fs_events->{$event} = (time() - ($^T - ((-M $event_path) * 24*60*60))); - } - } - } - chdir( EVENT_PATH ); + Debug( "Found filesystem monitor '$monitor'" ); + $fs_monitors->{$monitor} = {} if ! $fs_monitors->{$monitor}; + my $fs_events = $fs_monitors->{$monitor}; + + # De-taint + ( my $monitor_dir ) = ( $monitor =~ /^(.*)$/ ); + + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + foreach my $day_dir ( glob("$monitor_dir/*/*/*") ) { + Debug( "Checking day dir $day_dir" ); + ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint + if ( ! chdir( $day_dir ) ) { + Error( "Can't chdir to '$$Storage{Path}/$day_dir': $!" ); + next; + } + if ( ! opendir( DIR, '.' ) ) { + Error( "Can't open directory '$$Storage{Path}/$day_dir': $!" ); + next; + } + my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); + closedir( DIR ); + my $count = 0; + foreach my $event_link ( @event_links ) { + if ( $event_link =~ /[^\d\.]/ ) { + Warning("Non-event link found $event_link in $day_dir, skipping"); + next; } + Debug( "Checking link $event_link" ); + ( my $event = $event_link ) =~ s/^.*\.//; + #Event path is hour/minute/sec + my $event_path = readlink( $event_link ); + + if ( !-e $event_path ) { + aud_print( "Event link $day_dir/$event_link does not point to valid target" ); + if ( confirm() ) { + ( $event_link ) = ( $event_link =~ /^(.*)$/ ); # De-taint + unlink( $event_link ); + $cleaned = 1; + } + } else { + Debug( "Checking link $event_link points to $event_path " ); + my $Event = $fs_events->{$event} = new ZoneMinder::Event(); + $$Event{Id} = $event; + $$Event{Path} = join('/', $Storage->Path(), $day_dir,$event_path); + $Event->MonitorId( $monitor_dir ); + $Event->StorageId( $Storage->Id() ); + } # event path exists + } # end foreach event_link + chdir( $Storage->Path() ); + } # end foreach day dir + } else { + if ( ! chdir( $monitor_dir ) ) { + Error( "Can't chdir directory '$$Storage{Path}/$monitor_dir': $!" ); + next; } - else - { - chdir( $monitor_dir ); - opendir( DIR, "." ) or Fatal( "Can't open directory '$monitor_dir': $!" ); - my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); - closedir( DIR ); - my $count = 0; - foreach my $event ( @temp_events ) - { - if ( $count++ > MAX_AGED_DIRS ) - { - $fs_events->{$event} = -1; - } - else - { - $fs_events->{$event} = (time() - ($^T - ((-M $event) * 24*60*60))); - } - } - chdir( EVENT_PATH ); + if ( ! opendir( DIR, "." ) ) { + Error( "Can't open directory '$$Storage{Path}/$monitor_dir': $!" ); + next; } - Debug( "Got ".int(keys(%$fs_events))." events\n" ); - } + my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); + closedir( DIR ); + my $count = 0; + foreach my $event ( @temp_events ) { + my $Event = $fs_events->{$event} = new ZoneMinder::Event(); + $$Event{Id} = $event; + #$$Event{Path} = $event_path; + $Event->MonitorId( $monitor_dir ); + $Event->StorageId( $Storage->Id() ); + } # end foreach event + chdir( $Storage->Path() ); + } # if USE_DEEP_STORAGE + Debug( 'Got '.int(keys(%$fs_events))." events for monitor $monitor_dir\n" ); + + #delete_empty_directories( $monitor_dir ); + } # end foreach monitor redo MAIN if ( $cleaned ); $cleaned = 0; - while ( my ( $fs_monitor, $fs_events ) = each(%$fs_monitors) ) - { - if ( my $db_events = $db_monitors->{$fs_monitor} ) - { - if ( $fs_events ) - { - while ( my ( $fs_event, $age ) = each(%$fs_events ) ) - { - if ( !defined($db_events->{$fs_event}) && ($age < 0 || ($age > $Config{ZM_AUDIT_MIN_AGE})) ) - { - aud_print( "Filesystem event '$fs_monitor/$fs_event' does not exist in database" ); - if ( confirm() ) - { - deleteEventFiles( $fs_event, $fs_monitor ); - $cleaned = 1; - } - } - } - } - } - else - { - aud_print( "Filesystem monitor '$fs_monitor' does not exist in database" ); - if ( confirm() ) - { - my $command = "rm -rf $fs_monitor"; - executeShellCommand( $command ); + while ( my ( $monitor_id, $fs_events ) = each(%$fs_monitors) ) { + if ( my $db_events = $db_monitors->{$monitor_id} ) { + next if ! $fs_events; + + foreach my $fs_event_id ( sort { $a <=> $b } keys %$fs_events ) { + + my $Event = $fs_events->{$fs_event_id}; + + + if ( ! defined( $db_events->{$fs_event_id} ) ) { + my $age = $Event->age(); + + if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { + aud_print( "Filesystem event '".$Event->Path()."' does not exist in database and is $age seconds old" ); + if ( confirm() ) { + $Event->delete_files(); $cleaned = 1; - } + delete $fs_events->{$fs_event_id}; + } # end if confirm + } # end if old enough + } # end if ! in db events + } # end foreach fs event + } else { + aud_print( "Filesystem monitor '$monitor_id' in $$Storage{Path} does not exist in database" ); + if ( confirm() ) { + my $command = "rm -rf $monitor_id"; + executeShellCommand( $command ); + $cleaned = 1; } - } + } + } # end foreach monitor/filesystem events my $monitor_links; - foreach my $link ( glob("*") ) - { - next if ( !-l $link ); - next if ( -e $link ); + foreach my $link ( glob('*') ) { + next if ( !-l $link ); + next if ( -e $link ); - aud_print( "Filesystem monitor link '$link' does not point to valid monitor directory" ); - if ( confirm() ) - { - ( $link ) = ( $link =~ /^(.*)$/ ); # De-taint - my $command = qq`rm "$link"`; - executeShellCommand( $command ); - $cleaned = 1; - } + aud_print( "Filesystem monitor link '$link' does not point to valid monitor directory" ); + if ( confirm() ) { + ( $link ) = ( $link =~ /^(.*)$/ ); # De-taint + my $command = qq`rm "$link"`; + executeShellCommand( $command ); + $cleaned = 1; + } } - redo MAIN if ( $cleaned ); + } # end foreach Storage Area + redo MAIN if ( $cleaned ); - $cleaned = 0; - my $deleteMonitorSql = "delete low_priority from Monitors where Id = ?"; - my $deleteMonitorSth = $dbh->prepare_cached( $deleteMonitorSql ) - or Fatal( "Can't prepare '$deleteMonitorSql': ".$dbh->errstr() ); - my $deleteEventSql = "delete low_priority from Events where Id = ?"; - my $deleteEventSth = $dbh->prepare_cached( $deleteEventSql ) - or Fatal( "Can't prepare '$deleteEventSql': ".$dbh->errstr() ); - my $deleteFramesSql = "delete low_priority from Frames where EventId = ?"; - my $deleteFramesSth = $dbh->prepare_cached( $deleteFramesSql ) - or Fatal( "Can't prepare '$deleteFramesSql': ".$dbh->errstr() ); - my $deleteStatsSql = "delete low_priority from Stats where EventId = ?"; - my $deleteStatsSth = $dbh->prepare_cached( $deleteStatsSql ) - or Fatal( "Can't prepare '$deleteStatsSql': ".$dbh->errstr() ); - while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) ) - { - if ( my $fs_events = $fs_monitors->{$db_monitor} ) - { - if ( $db_events ) - { - while ( my ( $db_event, $age ) = each(%$db_events ) ) - { - if ( !defined($fs_events->{$db_event}) ) { - if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { - aud_print( "Database event '$db_monitor/$db_event' does not exist in filesystem" ); - if ( confirm() ) { - my $res = $deleteEventSth->execute( $db_event ) - or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); - $res = $deleteFramesSth->execute( $db_event ) - or Fatal( "Can't execute: ".$deleteFramesSth->errstr() ); - $res = $deleteStatsSth->execute( $db_event ) - or Fatal( "Can't execute: ".$deleteStatsSth->errstr() ); - $cleaned = 1; - } - } else { - aud_print( "Database event '$db_monitor/$db_event' does not exist in filesystem but too young to delete." ); - } - } - } + $cleaned = 0; + my $deleteMonitorSql = 'delete low_priority from Monitors where Id = ?'; + my $deleteMonitorSth = $dbh->prepare_cached( $deleteMonitorSql ) + or Fatal( "Can't prepare '$deleteMonitorSql': ".$dbh->errstr() ); + my $deleteEventSql = 'delete low_priority from Events where Id = ?'; + my $deleteEventSth = $dbh->prepare_cached( $deleteEventSql ) + or Fatal( "Can't prepare '$deleteEventSql': ".$dbh->errstr() ); + my $deleteFramesSql = 'delete low_priority from Frames where EventId = ?'; + my $deleteFramesSth = $dbh->prepare_cached( $deleteFramesSql ) + or Fatal( "Can't prepare '$deleteFramesSql': ".$dbh->errstr() ); + my $deleteStatsSql = 'delete low_priority from Stats where EventId = ?'; + my $deleteStatsSth = $dbh->prepare_cached( $deleteStatsSql ) + or Fatal( "Can't prepare '$deleteStatsSql': ".$dbh->errstr() ); + + # Foreach database monitor and it's list of events. + while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) ) { + + # If we found the monitor in the file system + if ( my $fs_events = $fs_monitors->{$db_monitor} ) { + next if ! $db_events; + + while ( my ( $db_event, $age ) = each( %$db_events ) ) { + if ( ! defined( $fs_events->{$db_event} ) ) { +Debug("Event $db_event is not in fs."); + my $Event = new ZoneMinder::Event( $db_event ); + if ( ! $Event->StartTime() ) { + Debug("Event $$Event{Id} has no start time. deleting it."); + if ( confirm() ) { + $Event->delete(); + $cleaned = 1; } - } - else - { - aud_print( "Database monitor '$db_monitor' does not exist in filesystem" ); - #if ( confirm() ) - #{ - # We don't actually do this in case it's new - #my $res = $deleteMonitorSth->execute( $db_monitor ) - # or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() ); - #$cleaned = 1; - #} - } - } - redo MAIN if ( $cleaned ); + next; + } + if ( $Event->check_for_in_filesystem() ) { + Debug('Database events apparently exists at ' . $Event->Path() ); + } else { + if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { + aud_print( "Database event '$db_monitor/$db_event' does not exist at " . $Event->Path().' in filesystem' ); + if ( confirm() ) { + $Event->delete(); + $cleaned = 1; + } + } else { + my $Storage = $Event->Storage(); - # Remove orphaned events (with no monitor) - $cleaned = 0; - my $selectOrphanedEventsSql = "SELECT Events.Id, Events.Name - FROM Events LEFT JOIN Monitors ON (Events.MonitorId = Monitors.Id) - WHERE isnull(Monitors.Id)"; - my $selectOrphanedEventsSth = $dbh->prepare_cached( $selectOrphanedEventsSql ) - or Fatal( "Can't prepare '$selectOrphanedEventsSql': ".$dbh->errstr() ); - $res = $selectOrphanedEventsSth->execute() - or Fatal( "Can't execute: ".$selectOrphanedEventsSth->errstr() ); - while( my $event = $selectOrphanedEventsSth->fetchrow_hashref() ) - { - aud_print( "Found orphaned event with no monitor '$event->{Id}'" ); - if ( confirm() ) - { - $res = $deleteEventSth->execute( $event->{Id} ) - or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); - $cleaned = 1; - } - } - redo MAIN if ( $cleaned ); - - # Remove empty events (with no frames) - $cleaned = 0; - my $selectEmptyEventsSql = "SELECT E.Id AS Id, E.StartTime, F.EventId FROM Events as E LEFT JOIN Frames as F ON (E.Id = F.EventId) - WHERE isnull(F.EventId) AND now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second > E.StartTime"; - my $selectEmptyEventsSth = $dbh->prepare_cached( $selectEmptyEventsSql ) - or Fatal( "Can't prepare '$selectEmptyEventsSql': ".$dbh->errstr() ); - $res = $selectEmptyEventsSth->execute() - or Fatal( "Can't execute: ".$selectEmptyEventsSth->errstr() ); - while( my $event = $selectEmptyEventsSth->fetchrow_hashref() ) - { - aud_print( "Found empty event with no frame records '$event->{Id}'" ); - if ( confirm() ) - { - $res = $deleteEventSth->execute( $event->{Id} ) - or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); - $cleaned = 1; - } - } - redo MAIN if ( $cleaned ); - - # Remove orphaned frame records - $cleaned = 0; - my $selectOrphanedFramesSql = "SELECT DISTINCT EventId FROM Frames - WHERE EventId NOT IN (SELECT Id FROM Events)"; - my $selectOrphanedFramesSth = $dbh->prepare_cached( $selectOrphanedFramesSql ) - or Fatal( "Can't prepare '$selectOrphanedFramesSql': ".$dbh->errstr() ); - $res = $selectOrphanedFramesSth->execute() - or Fatal( "Can't execute: ".$selectOrphanedFramesSth->errstr() ); - while( my $frame = $selectOrphanedFramesSth->fetchrow_hashref() ) - { - aud_print( "Found orphaned frame records for event '$frame->{EventId}'" ); - if ( confirm() ) - { - $res = $deleteFramesSth->execute( $frame->{EventId} ) - or Fatal( "Can't execute: ".$deleteFramesSth->errstr() ); - $cleaned = 1; - } - } - redo MAIN if ( $cleaned ); - - # Remove orphaned stats records - $cleaned = 0; - my $selectOrphanedStatsSql = "SELECT DISTINCT EventId FROM Stats - WHERE EventId NOT IN (SELECT Id FROM Events)"; - my $selectOrphanedStatsSth = $dbh->prepare_cached( $selectOrphanedStatsSql ) - or Fatal( "Can't prepare '$selectOrphanedStatsSql': ".$dbh->errstr() ); - $res = $selectOrphanedStatsSth->execute() - or Fatal( "Can't execute: ".$selectOrphanedStatsSth->errstr() ); - while( my $stat = $selectOrphanedStatsSth->fetchrow_hashref() ) - { - aud_print( "Found orphaned statistic records for event '$stat->{EventId}'" ); - if ( confirm() ) - { - $res = $deleteStatsSth->execute( $stat->{EventId} ) - or Fatal( "Can't execute: ".$deleteStatsSth->errstr() ); - $cleaned = 1; - } - } - redo MAIN if ( $cleaned ); - - # New audit to close any events that were left open for longer than MIN_AGE seconds - my $selectUnclosedEventsSql = - "SELECT E.Id, - max(F.TimeStamp) as EndTime, - unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length, - max(F.FrameId) as Frames, - count(if(F.Score>0,1,NULL)) as AlarmFrames, - sum(F.Score) as TotScore, - max(F.Score) as MaxScore, - M.EventPrefix as Prefix - FROM Events as E - LEFT JOIN Monitors as M on E.MonitorId = M.Id - INNER JOIN Frames as F on E.Id = F.EventId - WHERE isnull(E.Frames) or isnull(E.EndTime) - GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}." second)" - ; - my $selectUnclosedEventsSth = $dbh->prepare_cached( $selectUnclosedEventsSql ) - or Fatal( "Can't prepare '$selectUnclosedEventsSql': ".$dbh->errstr() ); - my $updateUnclosedEventsSql = - "UPDATE low_priority Events - SET Name = ?, - EndTime = ?, - Length = ?, - Frames = ?, - AlarmFrames = ?, - TotScore = ?, - AvgScore = ?, - MaxScore = ?, - Notes = concat_ws( ' ', Notes, ? ) - WHERE Id = ?" - ; - my $updateUnclosedEventsSth = $dbh->prepare_cached( $updateUnclosedEventsSql ) - or Fatal( "Can't prepare '$updateUnclosedEventsSql': ".$dbh->errstr() ); - $res = $selectUnclosedEventsSth->execute() - or Fatal( "Can't execute: ".$selectUnclosedEventsSth->errstr() ); - while( my $event = $selectUnclosedEventsSth->fetchrow_hashref() ) - { - aud_print( "Found open event '$event->{Id}'" ); - if ( confirm( 'close', 'closing' ) ) - { - $res = $updateUnclosedEventsSth->execute - ( - sprintf("%s%d%s", - $event->{Prefix}, - $event->{Id}, - RECOVER_TAG - ), - $event->{EndTime}, - $event->{Length}, - $event->{Frames}, - $event->{AlarmFrames}, - $event->{TotScore}, - $event->{AlarmFrames} - ? int($event->{TotScore} / $event->{AlarmFrames}) - : 0 - , - $event->{MaxScore}, - RECOVER_TEXT, - $event->{Id} - ) or Fatal( "Can't execute: ".$updateUnclosedEventsSth->errstr() ); - } - } - - # Now delete any old image files - if ( my @old_files = grep { -M > $max_image_age } <$image_path/*.{jpg,gif,wbmp}> ) - { - aud_print( "Deleting ".int(@old_files)." old images\n" ); - my $untainted_old_files = join( ";", @old_files ); - ( $untainted_old_files ) = ( $untainted_old_files =~ /^(.*)$/ ); - unlink( split( /;/, $untainted_old_files ) ); - } - - # Now delete any old swap files - ( my $swap_image_root ) = ( $Config{ZM_PATH_SWAP} =~ /^(.*)$/ ); # De-taint - File::Find::find( { wanted=>\&deleteSwapImage, untaint=>1 }, $swap_image_root ); - - # Prune the Logs table if required - if ( $Config{ZM_LOG_DATABASE_LIMIT} ) - { - if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ ) - { - # Number of rows - my $selectLogRowCountSql = "SELECT count(*) as Rows from Logs"; - my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql ) - or Fatal( "Can't prepare '$selectLogRowCountSql': ".$dbh->errstr() ); - $res = $selectLogRowCountSth->execute() - or Fatal( "Can't execute: ".$selectLogRowCountSth->errstr() ); - my $row = $selectLogRowCountSth->fetchrow_hashref(); - my $logRows = $row->{Rows}; - if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) - { - my $deleteLogByRowsSql = "DELETE low_priority FROM Logs ORDER BY TimeKey ASC LIMIT ?"; - my $deleteLogByRowsSth = $dbh->prepare_cached( $deleteLogByRowsSql ) - or Fatal( "Can't prepare '$deleteLogByRowsSql': ".$dbh->errstr() ); - $res = $deleteLogByRowsSth->execute( $logRows - $Config{ZM_LOG_DATABASE_LIMIT} ) - or Fatal( "Can't execute: ".$deleteLogByRowsSth->errstr() ); - if ( $deleteLogByRowsSth->rows() ) - { - aud_print( "Deleted ".$deleteLogByRowsSth->rows() - ." log table entries by count\n" ) - ; - } + aud_print( "Database event '".$Event->getPath()." monitor:$db_monitor event:$db_event' does not exist in filesystem but too young to delete age: $age > MIN $Config{ZM_AUDIT_MIN_AGE}.\n" ); } - } - else - { - # Time of record - my $deleteLogByTimeSql = - "DELETE low_priority FROM Logs - WHERE TimeKey < unix_timestamp(now() - interval ".$Config{ZM_LOG_DATABASE_LIMIT}.")"; - my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql ) - or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() ); - $res = $deleteLogByTimeSth->execute() - or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() ); - if ( $deleteLogByTimeSth->rows() ){ - aud_print( "Deleted ".$deleteLogByTimeSth->rows() - ." log table entries by time\n" ) - ; - } - } + } # end if exists in filesystem + } # end if ! in fs_events + } # foreach db_event + } else { + my $Monitor = new ZoneMinder::Monitor( $db_monitor ); + my $Storage = $Monitor->Storage(); + aud_print( "Database monitor '$db_monitor' does not exist in filesystem, should have been at ".$Storage->Path().'/'.$Monitor->Id()."\n" ); +#if ( confirm() ) +#{ +# We don't actually do this in case it's new +#my $res = $deleteMonitorSth->execute( $db_monitor ) +# or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() ); +#$cleaned = 1; +#} } - $loop = $continuous; + } # end foreach db monitors - sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ) if $continuous; + redo MAIN if ( $cleaned ); + +# Remove orphaned events (with no monitor) + $cleaned = 0; + my $selectOrphanedEventsSql = 'SELECT Events.Id, Events.Name + FROM Events LEFT JOIN Monitors ON (Events.MonitorId = Monitors.Id) + WHERE isnull(Monitors.Id)'; + my $selectOrphanedEventsSth = $dbh->prepare_cached( $selectOrphanedEventsSql ) + or Fatal( "Can't prepare '$selectOrphanedEventsSql': ".$dbh->errstr() ); + $res = $selectOrphanedEventsSth->execute() + or Fatal( "Can't execute: ".$selectOrphanedEventsSth->errstr() ); + + while( my $event = $selectOrphanedEventsSth->fetchrow_hashref() ) { + aud_print( "Found orphaned event with no monitor '$event->{Id}'" ); + if ( confirm() ) { + $res = $deleteEventSth->execute( $event->{Id} ) + or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); + $cleaned = 1; + } + } + redo MAIN if ( $cleaned ); + +# Remove empty events (with no frames) + $cleaned = 0; + my $selectEmptyEventsSql = 'SELECT E.Id AS Id, E.StartTime, F.EventId FROM Events as E LEFT JOIN Frames as F ON (E.Id = F.EventId) + WHERE isnull(F.EventId) AND now() - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second > E.StartTime'; + my $selectEmptyEventsSth = $dbh->prepare_cached( $selectEmptyEventsSql ) + or Fatal( "Can't prepare '$selectEmptyEventsSql': ".$dbh->errstr() ); + $res = $selectEmptyEventsSth->execute() + or Fatal( "Can't execute: ".$selectEmptyEventsSth->errstr() ); + while( my $event = $selectEmptyEventsSth->fetchrow_hashref() ) { + aud_print( "Found empty event with no frame records '$event->{Id}'" ); + if ( confirm() ) { + $res = $deleteEventSth->execute( $event->{Id} ) + or Fatal( "Can't execute: ".$deleteEventSth->errstr() ); + $cleaned = 1; + } + } + redo MAIN if ( $cleaned ); + +# Remove orphaned frame records + $cleaned = 0; + my $selectOrphanedFramesSql = 'SELECT DISTINCT EventId FROM Frames + WHERE EventId NOT IN (SELECT Id FROM Events)'; + my $selectOrphanedFramesSth = $dbh->prepare_cached( $selectOrphanedFramesSql ) + or Fatal( "Can't prepare '$selectOrphanedFramesSql': ".$dbh->errstr() ); + $res = $selectOrphanedFramesSth->execute() + or Fatal( "Can't execute: ".$selectOrphanedFramesSth->errstr() ); + while( my $frame = $selectOrphanedFramesSth->fetchrow_hashref() ) { + aud_print( "Found orphaned frame records for event '$frame->{EventId}'" ); + if ( confirm() ) { + $res = $deleteFramesSth->execute( $frame->{EventId} ) + or Fatal( "Can't execute: ".$deleteFramesSth->errstr() ); + $cleaned = 1; + } + } + redo MAIN if ( $cleaned ); + +# Remove orphaned stats records + $cleaned = 0; + my $selectOrphanedStatsSql = 'SELECT DISTINCT EventId FROM Stats + WHERE EventId NOT IN (SELECT Id FROM Events)'; + my $selectOrphanedStatsSth = $dbh->prepare_cached( $selectOrphanedStatsSql ) + or Fatal( "Can't prepare '$selectOrphanedStatsSql': ".$dbh->errstr() ); + $res = $selectOrphanedStatsSth->execute() + or Fatal( "Can't execute: ".$selectOrphanedStatsSth->errstr() ); + while( my $stat = $selectOrphanedStatsSth->fetchrow_hashref() ) { + aud_print( "Found orphaned statistic records for event '$stat->{EventId}'" ); + if ( confirm() ) { + $res = $deleteStatsSth->execute( $stat->{EventId} ) + or Fatal( "Can't execute: ".$deleteStatsSth->errstr() ); + $cleaned = 1; + } + } + redo MAIN if ( $cleaned ); + +# New audit to close any events that were left open for longer than MIN_AGE seconds + my $selectUnclosedEventsSql = +#"SELECT E.Id, ANY_VALUE(E.MonitorId), +# +#max(F.TimeStamp) as EndTime, +#unix_timestamp(max(F.TimeStamp)) - unix_timestamp(E.StartTime) as Length, +#max(F.FrameId) as Frames, +#count(if(F.Score>0,1,NULL)) as AlarmFrames, +#sum(F.Score) as TotScore, +#max(F.Score) as MaxScore +#FROM Events as E +#INNER JOIN Frames as F on E.Id = F.EventId +#WHERE isnull(E.Frames) or isnull(E.EndTime) +#GROUP BY E.Id HAVING EndTime < (now() - interval ".$Config{ZM_AUDIT_MIN_AGE}.' second)' +#; + 'SELECT *, unix_timestamp(StartTime) AS TimeStamp FROM Events WHERE EndTime IS NULL AND StartTime < (now() - interval '.$Config{ZM_AUDIT_MIN_AGE}.' second)'; + + my $selectFrameDataSql = 'SELECT max(TimeStamp) as EndTime, unix_timestamp(max(TimeStamp)) AS EndTimeStamp, max(FrameId) as Frames, + count(if(Score>0,1,NULL)) as AlarmFrames, + sum(Score) as TotScore, + max(Score) as MaxScore + FROM Frames WHERE EventId=?'; + my $selectFrameDataSth = $dbh->prepare_cached($selectFrameDataSql) + or Fatal( "Can't prepare '$selectFrameDataSql': ".$dbh->errstr() ); + + + my $selectUnclosedEventsSth = $dbh->prepare_cached( $selectUnclosedEventsSql ) + or Fatal( "Can't prepare '$selectUnclosedEventsSql': ".$dbh->errstr() ); + my $updateUnclosedEventsSql = + "UPDATE low_priority Events + SET Name = ?, + EndTime = ?, + Length = ?, + Frames = ?, + AlarmFrames = ?, + TotScore = ?, + AvgScore = ?, + MaxScore = ?, + Notes = concat_ws( ' ', Notes, ? ) + WHERE Id = ?" + ; + my $updateUnclosedEventsSth = $dbh->prepare_cached( $updateUnclosedEventsSql ) + or Fatal( "Can't prepare '$updateUnclosedEventsSql': ".$dbh->errstr() ); + $res = $selectUnclosedEventsSth->execute() + or Fatal( "Can't execute: ".$selectUnclosedEventsSth->errstr() ); + while( my $event = $selectUnclosedEventsSth->fetchrow_hashref() ) { + aud_print( "Found open event '$event->{Id}'" ); + if ( confirm( 'close', 'closing' ) ) { + $res = $selectFrameDataSth->execute( $event->{Id} ) or Fatal( "Can't execute: ".$selectFrameDataSth->errstr() ); + my $frame = $selectFrameDataSth->fetchrow_hashref(); + if ( $frame ) { + $res = $updateUnclosedEventsSth->execute( + sprintf('%s%d%s', + $Monitors{$event->{MonitorId}}->{EventPrefix}, + $event->{Id}, + RECOVER_TAG + ), + $frame->{EndTime}, + $frame->{EndTimeStamp} - $event->{TimeStamp}, + $frame->{Frames}, + $frame->{AlarmFrames}, + $frame->{TotScore}, + $frame->{AlarmFrames} + ? int($frame->{TotScore} / $frame->{AlarmFrames}) + : 0 + , + $frame->{MaxScore}, + RECOVER_TEXT, + $event->{Id} + ) or Fatal( "Can't execute: ".$updateUnclosedEventsSth->errstr() ); + } else { + Error('SHOULD DELETE'); + } # end if has frame data + } + } # end while unclosed event + +# Now delete any old image files + if ( my @old_files = grep { -M > $max_image_age } <$image_path/*.{jpg,gif,wbmp}> ) { + aud_print( 'Deleting '.int(@old_files)." old images\n" ); + my $untainted_old_files = join( ';', @old_files ); + ( $untainted_old_files ) = ( $untainted_old_files =~ /^(.*)$/ ); + unlink( split( /;/, $untainted_old_files ) ); + } + + # Now delete any old swap files + ( my $swap_image_root ) = ( $Config{ZM_PATH_SWAP} =~ /^(.*)$/ ); # De-taint + File::Find::find( { wanted=>\&deleteSwapImage, untaint=>1 }, $swap_image_root ); + + # Prune the Logs table if required + if ( $Config{ZM_LOG_DATABASE_LIMIT} ) { + if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ ) { + # Number of rows + my $selectLogRowCountSql = 'SELECT count(*) as Rows from Logs'; + my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql ) + or Fatal( "Can't prepare '$selectLogRowCountSql': ".$dbh->errstr() ); + $res = $selectLogRowCountSth->execute() + or Fatal( "Can't execute: ".$selectLogRowCountSth->errstr() ); + my $row = $selectLogRowCountSth->fetchrow_hashref(); + my $logRows = $row->{Rows}; + if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) { + my $deleteLogByRowsSql = 'DELETE low_priority FROM Logs ORDER BY TimeKey ASC LIMIT ?'; + my $deleteLogByRowsSth = $dbh->prepare_cached( $deleteLogByRowsSql ) + or Fatal( "Can't prepare '$deleteLogByRowsSql': ".$dbh->errstr() ); + $res = $deleteLogByRowsSth->execute( $logRows - $Config{ZM_LOG_DATABASE_LIMIT} ) + or Fatal( "Can't execute: ".$deleteLogByRowsSth->errstr() ); + if ( $deleteLogByRowsSth->rows() ) { + aud_print( 'Deleted '.$deleteLogByRowsSth->rows() + ." log table entries by count\n" ); + } + } + } else { + # Time of record + my $deleteLogByTimeSql = + "DELETE low_priority FROM Logs + WHERE TimeKey < unix_timestamp(now() - interval ".$Config{ZM_LOG_DATABASE_LIMIT}.')'; + my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql ) + or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() ); + $res = $deleteLogByTimeSth->execute() + or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() ); + if ( $deleteLogByTimeSth->rows() ){ + aud_print( 'Deleted '.$deleteLogByTimeSth->rows() + ." log table entries by time\n" ); + } + } + } # end if ZM_LOG_DATABASE_LIMIT + $loop = $continuous; + + sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ) if $continuous; }; exit( 0 ); -sub aud_print -{ - my $string = shift; - if ( !$continuous ) - { - print( $string ); - } - else - { - Info( $string ); - } +sub aud_print { + my $string = shift; + if ( !$continuous ) { + print( $string ); + } else { + Info( $string ); + } } -sub confirm -{ - my $prompt = shift || "delete"; - my $action = shift || "deleting"; +sub confirm { + my $prompt = shift || 'delete'; + my $action = shift || 'deleting'; - my $yesno = 0; - if ( $report ) - { - print( "\n" ); + my $yesno = 0; + if ( $report ) { + print( "\n" ); + } elsif ( $interactive ) { + print( ", $prompt y/n: " ); + my $char = <>; + chomp( $char ); + if ( $char eq 'q' ) { + exit( 0 ); } - elsif ( $interactive ) - { - print( ", $prompt y/n: " ); - my $char = <>; - chomp( $char ); - if ( $char eq 'q' ) - { - exit( 0 ); - } - if ( !$char ) - { - $char = 'y'; - } - $yesno = ( $char =~ /[yY]/ ); + if ( !$char ) { + $char = 'y'; } - else - { - if ( !$continuous ) - { - print( ", $action\n" ); - } - else - { - Info( $action ); - } - $yesno = 1; + $yesno = ( $char =~ /[yY]/ ); + } else { + if ( !$continuous ) { + print( ", $action\n" ); + } else { + Info( $action ); } - return( $yesno ); + $yesno = 1; + } + return( $yesno ); } -sub deleteSwapImage -{ - my $file = $_; +sub deleteSwapImage { + my $file = $_; - if ( $file !~ /^zmswap-/ ) - { - return; - } + return if $file =~ /^./; - # Ignore directories - if ( -d $file ) - { - return; - } + if ( $file !~ /^zmswap-/ ) { + Error( "Trying to delete SwapImage that isnt a swap image $file" ); + return; + } - if ( -M $file > $max_swap_age ) - { - Debug( "Deleting $file" ); - #unlink( $file ); - } +# Ignore directories + if ( -d $file ) { + Error( "Trying to delete a directory instead of a swap image $file" ); + return; + } + + if ( -M $file > $max_swap_age ) { + ( $file ) = ( $file =~ /^(.*)$/ ); + + Debug( "Deleting $file" ); + unlink( $file ); + } } + +sub delete_empty_directories { + my $DIR; + Debug("delete_empty_directories $_[0]"); + if ( ! opendir( $DIR, $_[0] ) ) { + Error( "delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" ); + return; + } + my @contents = map { $_ eq '.' or $_ eq '..' ? () : $_ } readdir( $DIR ); + my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents; + if ( @dirs ) { + foreach ( @dirs ) { + delete_empty_directories( $_[0].'/'.$_ ); + } +#Reload, since we may now be empty + rewinddir $DIR; + @contents = map { $_ eq '.' or $_ eq '..' ? () : $_ } readdir( $DIR ); + } + if ( ! @contents ) { + ( my $dir ) = ( $_[0] =~ /^(.*)$/ ); + unlink $dir; + } + closedir( $DIR ); +} # end sub delete_empty_directories + diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 918df8574..39680a7ed 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -101,8 +101,7 @@ my @daemons = ( ); my $command = shift @ARGV; -if( !$command ) -{ +if( !$command ) { print( STDERR "No command given\n" ); pod2usage(-exitstatus => -1); } @@ -112,37 +111,28 @@ if ( $command eq 'version' ) { } my $needs_daemon = $command !~ /(?:startup|shutdown|status|check|logrot|version)/; my $daemon = shift( @ARGV ); -if( $needs_daemon && !$daemon ) -{ +if( $needs_daemon && !$daemon ) { print( STDERR "No daemon given\n" ); pod2usage(-exitstatus => -1); } my @args; my $daemon_patt = '('.join( '|', @daemons ).')'; -if ( $needs_daemon ) -{ - if ( $daemon =~ /^${daemon_patt}$/ ) - { +if ( $needs_daemon ) { + if ( $daemon =~ /^${daemon_patt}$/ ) { $daemon = $1; - } - else - { + } else { print( STDERR "Invalid daemon '$daemon' specified" ); pod2usage(-exitstatus => -1); } } -foreach my $arg ( @ARGV ) -{ +foreach my $arg ( @ARGV ) { # Detaint arguments, if they look ok #if ( $arg =~ /^(-{0,2}[\w]+)/ ) - if ( $arg =~ /^(-{0,2}[\w\/?&=.-]+)$/ ) - { + if ( $arg =~ /^(-{0,2}[\w\/?&=.-]+)$/ ) { push( @args, $1 ); - } - else - { + } else { print( STDERR "Bogus argument '$arg' found" ); exit( -1 ); } @@ -152,56 +142,42 @@ socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 ) or Fatal( "Can't open socket: $!" ); my $saddr = sockaddr_un( SOCK_FILE ); my $server_up = connect( CLIENT, $saddr ); -if ( !$server_up ) -{ - if ( $command eq "logrot" ) - { +if ( !$server_up ) { + if ( $command eq 'logrot' ) { exit(); } - if ( $command eq "check" ) - { + if ( $command eq 'check' ) { print( "stopped\n" ); exit(); - } - elsif ( $command ne "startup" ) - { - print( "Unable to connect to server\n" ); + } elsif ( $command ne 'startup' ) { + print( "Unable to connect to server using socket at " . SOCK_FILE . "\n" ); exit( -1 ); } # The server isn't there print( "Starting server\n" ); close( CLIENT ); - if ( my $cpid = fork() ) - { + if ( my $cpid = fork() ) { logInit(); # Parent process just sleep and fall through socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 ) or Fatal( "Can't open socket: $!" ); my $attempts = 0; - while (!connect( CLIENT, $saddr )) - { + while (!connect( CLIENT, $saddr )) { $attempts++; Fatal( "Can't connect: $!" ) if ($attempts > MAX_CONNECT_DELAY); sleep(1); } - } - elsif ( defined($cpid) ) - { + } elsif ( defined($cpid) ) { ZMServer::run(); - } - else - { + } else { Fatal( "Can't fork: $!" ); } } -if ( $command eq "check" && !$daemon ) -{ +if ( $command eq 'check' && !$daemon ) { print( "running\n" ); exit(); -} -elsif ( $command eq "startup" ) -{ +} elsif ( $command eq 'startup' ) { # Our work here is done exit() if ( !$server_up ); } @@ -213,8 +189,7 @@ $message .= ";$daemon" if ( $daemon ); $message .= ";".join( ';', @args ) if ( @args ); print( CLIENT $message ); shutdown( CLIENT, 1 ); -while ( my $line = ) -{ +while ( my $line = ) { chomp( $line ); print( "$line\n" ); } @@ -234,16 +209,15 @@ use ZoneMinder; use POSIX; use Socket; use IO::Handle; +use Time::HiRes qw(usleep); #use Data::Dumper; our %cmd_hash; our %pid_hash; -sub run -{ +sub run { my $fd = 0; - while( $fd < POSIX::sysconf( &POSIX::_SC_OPEN_MAX ) ) - { + while( $fd < POSIX::sysconf( &POSIX::_SC_OPEN_MAX ) ) { POSIX::close( $fd++ ); } @@ -251,13 +225,12 @@ sub run logInit(); - dPrint( ZoneMinder::Logger::INFO, "Server starting at " + dPrint( ZoneMinder::Logger::INFO, 'Server starting at ' .strftime( '%y/%m/%d %H:%M:%S', localtime() ) ."\n" ); - if ( open( my $PID, '>', ZM_PID ) ) - { + if ( open( my $PID, '>', ZM_PID ) ) { print( $PID $$ ); close( $PID ); } else { @@ -267,7 +240,7 @@ sub run killAll( 1 ); socket( SERVER, PF_UNIX, SOCK_STREAM, 0 ) or Fatal( "Can't open socket: $!" ); - unlink( main::SOCK_FILE ) or Error( "Unable to unlink " . main::SOCK_FILE .". Error message was: $!" ) if ( -e main::SOCK_FILE ); + unlink( main::SOCK_FILE ) or Error( 'Unable to unlink ' . main::SOCK_FILE .". Error message was: $!" ) if ( -e main::SOCK_FILE ); bind( SERVER, $saddr ) or Fatal( "Can't bind to " . main::SOCK_FILE . ": $!" ); listen( SERVER, SOMAXCONN ) or Fatal( "Can't listen: $!" ); @@ -282,13 +255,10 @@ sub run my $win = $rin; my $ein = $win; my $timeout = 0.1; - while( 1 ) - { + while( 1 ) { my $nfound = select( my $rout = $rin, undef, undef, $timeout ); - if ( $nfound > 0 ) - { - if ( vec( $rout, fileno(SERVER), 1 ) ) - { + if ( $nfound > 0 ) { + if ( vec( $rout, fileno(SERVER), 1 ) ) { my $paddr = accept( CLIENT, SERVER ); my $message = ; @@ -296,146 +266,96 @@ sub run my ( $command, $daemon, @args ) = split( /;/, $message ); - if ( $command eq 'start' ) - { + if ( $command eq 'start' ) { start( $daemon, @args ); - } - elsif ( $command eq 'stop' ) - { + } elsif ( $command eq 'stop' ) { stop( $daemon, @args ); - } - elsif ( $command eq 'restart' ) - { + } elsif ( $command eq 'restart' ) { restart( $daemon, @args ); - } - elsif ( $command eq 'reload' ) - { + } elsif ( $command eq 'reload' ) { reload( $daemon, @args ); - } - elsif ( $command eq 'startup' ) - { + } elsif ( $command eq 'startup' ) { # Do nothing, this is all we're here for dPrint( ZoneMinder::Logger::WARNING, "Already running, ignoring command '$command'\n" ); - } - elsif ( $command eq 'shutdown' ) - { + } elsif ( $command eq 'shutdown' ) { shutdownAll(); - } - elsif ( $command eq 'check' ) - { + } elsif ( $command eq 'check' ) { check( $daemon, @args ); - } - elsif ( $command eq 'status' ) - { - if ( $daemon ) - { + } elsif ( $command eq 'status' ) { + if ( $daemon ) { status( $daemon, @args ); - } - else - { + } else { status(); } - } - elsif ( $command eq 'logrot' ) - { + } elsif ( $command eq 'logrot' ) { logrot(); - } - else - { + } else { dPrint( ZoneMinder::Logger::ERROR, "Invalid command '$command'\n" ); } close( CLIENT ); + } else { + Fatal( 'Bogus descriptor' ); } - else - { - Fatal( "Bogus descriptor" ); - } - } - elsif ( $nfound < 0 ) - { - if ( $! == EINTR ) - { + } elsif ( $nfound < 0 ) { + if ( $! == EINTR ) { # Dead child, will be reaped #print( "Probable dead child\n" ); # See if it needs to start up again restartPending(); - } - elsif ( $! == EPIPE ) - { + } elsif ( $! == EPIPE ) { Error( "Can't select: $!" ); - } - else - { + } else { Fatal( "Can't select: $!" ); } - } - else - { + } else { #print( "Select timed out\n" ); restartPending(); } } - dPrint( ZoneMinder::Logger::INFO, "Server exiting at " + dPrint( ZoneMinder::Logger::INFO, 'Server exiting at ' .strftime( '%y/%m/%d %H:%M:%S', localtime() ) ."\n" ); - unlink( main::SOCK_FILE ) or Error( "Unable to unlink " . main::SOCK_FILE .". Error message was: $!" ) if ( -e main::SOCK_FILE ); - unlink( ZM_PID ) or Error( "Unable to unlink " . ZM_PID .". Error message was: $!" ) if ( -e ZM_PID ); + unlink( main::SOCK_FILE ) or Error( 'Unable to unlink ' . main::SOCK_FILE .". Error message was: $!" ) if ( -e main::SOCK_FILE ); + unlink( ZM_PID ) or Error( 'Unable to unlink ' . ZM_PID .". Error message was: $!" ) if ( -e ZM_PID ); exit(); } -sub cPrint -{ - if ( fileno(CLIENT) ) - { +sub cPrint { + if ( fileno(CLIENT) ) { print CLIENT @_ } } -sub dPrint -{ +sub dPrint { my $logLevel = shift; - if ( fileno(CLIENT) ) - { + if ( fileno(CLIENT) ) { print CLIENT @_ } - if ( $logLevel == ZoneMinder::Logger::DEBUG ) - { + if ( $logLevel == ZoneMinder::Logger::DEBUG ) { Debug( @_ ); - } - elsif ( $logLevel == ZoneMinder::Logger::INFO ) - { + } elsif ( $logLevel == ZoneMinder::Logger::INFO ) { Info( @_ ); - } - elsif ( $logLevel == ZoneMinder::Logger::WARNING ) - { + } elsif ( $logLevel == ZoneMinder::Logger::WARNING ) { Warning( @_ ); - } - elsif ( $logLevel == ZoneMinder::Logger::ERROR ) - { + } elsif ( $logLevel == ZoneMinder::Logger::ERROR ) { Error( @_ ); - } - elsif ( $logLevel == ZoneMinder::Logger::FATAL ) - { + } elsif ( $logLevel == ZoneMinder::Logger::FATAL ) { Fatal( @_ ); } } -sub start -{ +sub start { my $daemon = shift; my @args = @_; my $command = join(' ', $daemon, @args ); my $process = $cmd_hash{$command}; - if ( !$process ) - { + if ( !$process ) { # It's not running, or at least it's not been started by us $process = { daemon=>$daemon, args=>\@args, command=>$command, keepalive=>!undef }; - } - elsif ( $process->{pid} && $pid_hash{$process->{pid}} ) - { + } elsif ( $process->{pid} && $pid_hash{$process->{pid}} ) { dPrint( ZoneMinder::Logger::INFO, "'$process->{command}' already running at " .strftime( '%y/%m/%d %H:%M:%S', localtime( $process->{started}) ) .", pid = $process->{pid}\n" @@ -446,8 +366,7 @@ sub start my $sigset = POSIX::SigSet->new; my $blockset = POSIX::SigSet->new( SIGCHLD ); sigprocmask( SIG_BLOCK, $blockset, $sigset ) or Fatal( "Can't block SIGCHLD: $!" ); - if ( my $cpid = fork() ) - { + if ( my $cpid = fork() ) { logReinit(); $process->{pid} = $cpid; @@ -461,9 +380,9 @@ sub start $cmd_hash{$process->{command}} = $pid_hash{$cpid} = $process; sigprocmask( SIG_SETMASK, $sigset ) or Fatal( "Can't restore SIGCHLD: $!" ); - } - elsif ( defined($cpid ) ) - { + } elsif ( defined($cpid ) ) { + # Force reconnection to the db. + zmDbConnect(1); logReinit(); dPrint( ZoneMinder::Logger::INFO, "'".join( ' ', ( $daemon, @args ) ) @@ -472,25 +391,18 @@ sub start ."\n" ); - if ( $daemon =~ /^${daemon_patt}$/ ) - { + if ( $daemon =~ /^${daemon_patt}$/ ) { $daemon = $Config{ZM_PATH_BIN}.'/'.$1; - } - else - { + } else { Fatal( "Invalid daemon '$daemon' specified" ); } my @good_args; - foreach my $arg ( @args ) - { + foreach my $arg ( @args ) { # Detaint arguments, if they look ok - if ( $arg =~ /^(-{0,2}[\w\/?&=.-]+)$/ ) - { + if ( $arg =~ /^(-{0,2}[\w\/?&=.-]+)$/ ) { push( @good_args, $1 ); - } - else - { + } else { Fatal( "Bogus argument '$arg' found" ); } } @@ -498,8 +410,7 @@ sub start logTerm(); my $fd = 0; - while( $fd < POSIX::sysconf( &POSIX::_SC_OPEN_MAX ) ) - { + while( $fd < POSIX::sysconf( &POSIX::_SC_OPEN_MAX ) ) { POSIX::close( $fd++ ); } @@ -510,9 +421,7 @@ sub start $SIG{ABRT} = 'DEFAULT'; exec( $daemon, @good_args ) or Fatal( "Can't exec: $!" ); - } - else - { + } else { Fatal( "Can't fork: $!" ); } } @@ -533,8 +442,7 @@ sub send_stop { } my $pid = $process->{pid}; - if ( !$pid_hash{$pid} ) - { + if ( !$pid_hash{$pid} ) { dPrint( ZoneMinder::Logger::ERROR, "No process with command of '$command' pid $pid is running\n" ); return(); } @@ -549,22 +457,25 @@ sub send_stop { } # end sub send_stop sub kill_until_dead { - my ( $process ) = @_; - # Now check it has actually gone away, if not kill -9 it - my $count = 0; - while( $process and $$process{pid} and kill( 0, $$process{pid} ) ) - { - if ( $count++ > 5 ) - { - dPrint( ZoneMinder::Logger::WARNING, "'$$process{command}' has not stopped at " - .strftime( '%y/%m/%d %H:%M:%S', localtime() ) - .". Sending KILL to pid $$process{pid}\n" - ); - kill( 'KILL', $$process{pid} ); - } - - sleep( 1 ); + my ( $process ) = @_; +# Now check it has actually gone away, if not kill -9 it + my $count = 0; + my $sigset = POSIX::SigSet->new; + my $blockset = POSIX::SigSet->new(SIGCHLD); + sigprocmask(SIG_BLOCK, $blockset, $sigset ) or die "dying at block...\n"; + while( $process and $$process{pid} and kill( 0, $$process{pid} ) ) { + if ( $count++ > 50 ) { + dPrint( ZoneMinder::Logger::WARNING, "'$$process{command}' has not stopped at " + .strftime( '%y/%m/%d %H:%M:%S', localtime() ) + .". Sending KILL to pid $$process{pid}\n" + ); + kill( 'KILL', $$process{pid} ); } + + sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; + usleep( 100 ); + sigprocmask(SIG_BLOCK, $blockset, $sigset ) or die "dying at block...\n"; + } } sub _stop { @@ -577,13 +488,11 @@ sub _stop { kill_until_dead( $process ); } -sub stop -{ +sub stop { my ( $daemon, @args ) = @_; my $command = join(' ', $daemon, @args ); my $process = $cmd_hash{$command}; - if ( !$process ) - { + if ( !$process ) { dPrint( ZoneMinder::Logger::WARNING, "Can't find process with command of '$command'\n" ); return(); } @@ -591,21 +500,17 @@ sub stop _stop( 1, $process ); } -sub restart -{ +sub restart { my $daemon = shift; my @args = @_; my $command = $daemon; - $command .= ' '.join( ' ', ( @args ) ) if ( @args ); + $command .= ' '.join( ' ', ( @args ) ) if @args; my $process = $cmd_hash{$command}; - if ( $process ) - { - if ( $process->{pid} ) - { + if ( $process ) { + if ( $process->{pid} ) { my $cpid = $process->{pid}; - if ( defined($pid_hash{$cpid}) ) - { + if ( defined($pid_hash{$cpid}) ) { _stop( 0, $process ); return; } @@ -614,47 +519,38 @@ sub restart start( $daemon, @args ); } -sub reload -{ +sub reload { my $daemon = shift; my @args = @_; my $command = $daemon; $command .= ' '.join( ' ', ( @args ) ) if ( @args ); my $process = $cmd_hash{$command}; - if ( $process ) - { - if ( $process->{pid} ) - { + if ( $process ) { + if ( $process->{pid} ) { kill( 'HUP', $process->{pid} ); } } } -sub logrot -{ +sub logrot { logReinit(); - foreach my $process ( values( %pid_hash ) ) - { - if ( $process->{pid} && $process->{command} =~ /^zm.*\.pl/ ) - { + foreach my $process ( values( %pid_hash ) ) { + if ( $process->{pid} && $process->{command} =~ /^zm.*\.pl/ ) { kill( 'HUP', $process->{pid} ); } } } -sub reaper -{ +sub reaper { my $saved_status = $!; - while ( (my $cpid = waitpid( -1, WNOHANG )) > 0 ) - { + while ( (my $cpid = waitpid( -1, WNOHANG )) > 0 ) { my $status = $?; my $process = $pid_hash{$cpid}; delete( $pid_hash{$cpid} ); - if ( !$process ) - { + if ( !$process ) { dPrint( ZoneMinder::Logger::INFO, "Can't find child with pid of '$cpid'\n" ); next; } @@ -668,59 +564,43 @@ sub reaper my $core_dumped = $status&0x01; my $out_str = "'$process->{command}' "; - if ( $exit_signal ) - { - if ( $exit_signal == 15 || $exit_signal == 14 ) # TERM or ALRM - { - $out_str .= "exited"; - } - else - { - $out_str .= "crashed"; + if ( $exit_signal ) { + if ( $exit_signal == 15 || $exit_signal == 14 ) { +# TERM or ALRM + $out_str .= 'exited'; + } else { + $out_str .= 'crashed'; } $out_str .= ", signal $exit_signal"; - } - else - { - $out_str .= "exited "; - if ( $exit_status ) - { + } else { + $out_str .= 'exited '; + if ( $exit_status ) { $out_str .= "abnormally, exit status $exit_status"; - } - else - { - $out_str .= "normally"; + } else { + $out_str .= 'normally'; } } #print( ", core dumped" ) if ( $core_dumped ); $out_str .= "\n"; - if ( $exit_status == 0 ) - { + if ( $exit_status == 0 ) { Info( $out_str ); - } - else - { + } else { Error( $out_str ); } - if ( $process->{keepalive} ) - { + if ( $process->{keepalive} ) { # Schedule for immediate restart $cmd_hash{$process->{command}} = $process; - if ( !$process->{delay} || ($process->{runtime} > $Config{ZM_MAX_RESTART_DELAY} ) ) - { + if ( !$process->{delay} || ($process->{runtime} > $Config{ZM_MAX_RESTART_DELAY} ) ) { #start( $process->{daemon}, @{$process->{args}} ); $process->{pending} = $process->{stopped}; $process->{delay} = 5; - } - else - { + } else { $process->{pending} = $process->{stopped}+$process->{delay}; $process->{delay} *= 2; # Limit the start delay to 15 minutes max - if ( $process->{delay} > $Config{ZM_MAX_RESTART_DELAY} ) - { + if ( $process->{delay} > $Config{ZM_MAX_RESTART_DELAY} ) { $process->{delay} = $Config{ZM_MAX_RESTART_DELAY}; } } @@ -730,21 +610,17 @@ sub reaper $! = $saved_status; } -sub restartPending -{ +sub restartPending { # Restart any pending processes - foreach my $process ( values( %cmd_hash ) ) - { - if ( $process->{pending} && $process->{pending} <= time() ) - { + foreach my $process ( values( %cmd_hash ) ) { + if ( $process->{pending} && $process->{pending} <= time() ) { dPrint( ZoneMinder::Logger::INFO, "Starting pending process, $process->{command}\n" ); start( $process->{daemon}, @{$process->{args}} ); } } } -sub shutdownAll -{ +sub shutdownAll { foreach my $pid ( keys %pid_hash ) { # This is a quick fix because a SIGCHLD can happen and alter pid_hash while we are in here. next if ! $pid_hash{$pid}; @@ -772,64 +648,48 @@ sub shutdownAll exit(); } -sub check -{ +sub check { my $daemon = shift; my @args = @_; my $command = $daemon; $command .= ' '.join( ' ', ( @args ) ) if ( @args ); my $process = $cmd_hash{$command}; - if ( !$process ) - { + if ( !$process ) { cPrint( "unknown\n" ); - } - elsif ( $process->{pending} ) - { + } elsif ( $process->{pending} ) { cPrint( "pending\n" ); - } - else - { + } else { my $cpid = $process->{pid}; - if ( !$pid_hash{$cpid} ) - { + if ( !$pid_hash{$cpid} ) { cPrint( "stopped\n" ); - } - else - { + } else { cPrint( "running\n" ); } } } -sub status -{ +sub status { my $daemon = shift; my @args = @_; - if ( defined($daemon) ) - { + if ( defined($daemon) ) { my $command = $daemon; $command .= ' '.join( ' ', ( @args ) ) if ( @args ); my $process = $cmd_hash{$command}; - if ( !$process ) - { + if ( !$process ) { dPrint( ZoneMinder::Logger::DEBUG, "'$command' not running\n" ); return(); } - if ( $process->{pending} ) - { + if ( $process->{pending} ) { dPrint( ZoneMinder::Logger::DEBUG, "'$process->{command}' pending at " .strftime( '%y/%m/%d %H:%M:%S', localtime( $process->{pending}) ) ."\n" ); - } - else - { + } else { my $cpid = $process->{pid}; - if ( !$pid_hash{$cpid} ) - { + if ( !$pid_hash{$cpid} ) { dPrint( ZoneMinder::Logger::DEBUG, "'$command' not running\n" ); return(); } @@ -838,11 +698,8 @@ sub status .strftime( '%y/%m/%d %H:%M:%S', localtime( $process->{started}) ) .", pid = $process->{pid}" ); - } - else - { - foreach my $process ( values(%pid_hash) ) - { + } else { + foreach my $process ( values(%pid_hash) ) { my $out_str = "'$process->{command}' running since " .strftime( '%y/%m/%d %H:%M:%S', localtime( $process->{started}) ) .", pid = $process->{pid}" @@ -851,10 +708,8 @@ sub status $out_str .= "\n"; dPrint( ZoneMinder::Logger::DEBUG, $out_str ); } - foreach my $process ( values( %cmd_hash ) ) - { - if ( $process->{pending} ) - { + foreach my $process ( values( %cmd_hash ) ) { + if ( $process->{pending} ) { dPrint( ZoneMinder::Logger::DEBUG, "'$process->{command}' pending at " .strftime( '%y/%m/%d %H:%M:%S', localtime( $process->{pending}) ) ."\n" @@ -864,30 +719,29 @@ sub status } } -sub killAll -{ +sub killAll { my $delay = shift; sleep( $delay ); my $killall; - if ( '@HOST_OS@' eq 'BSD' ) - { + if ( '@HOST_OS@' eq 'BSD' ) { $killall = 'killall -q -'; } elsif ( '@HOST_OS@' eq 'solaris' ) { $killall = 'pkill -'; } else { $killall = 'killall -q -s '; } - foreach my $daemon ( @daemons ) - { + foreach my $daemon ( @daemons ) { my $cmd = $killall ."TERM $daemon"; Debug( $cmd ); qx( $cmd ); } sleep( $delay ); - foreach my $daemon ( @daemons ) - { + foreach my $daemon ( @daemons ) { my $cmd = $killall."KILL $daemon"; Debug( $cmd ); qx( $cmd ); } } + +1; +__END__ diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index e51993bec..cbff891ee 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -27,7 +27,7 @@ zmfilter.pl - ZoneMinder tool to filter events =head1 SYNOPSIS - zmfilter.pl [-f ,--filter=] | -v, --version + zmfilter.pl [-f ,--filter=] [--filter_id=] | -v, --version =head1 DESCRIPTION @@ -37,7 +37,9 @@ matching events. =head1 OPTIONS + -f{filter name}, --filter={filter name} - The name of a specific filter to run + --filter_id={filter id} - The id of a specific filter to run -v, --version - Print ZoneMinder version =cut @@ -60,7 +62,6 @@ use constant START_DELAY => 5; # How long to wait before starting @EXTRA_PERL_LIB@ use ZoneMinder; -require ZoneMinder::Filter; use DBI; use POSIX; use Time::HiRes qw/gettimeofday/; @@ -69,6 +70,24 @@ use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use autouse 'Data::Dumper'=>qw(Dumper); +my $filter_name = ""; +my $filter_id; +my $version = 0; + +GetOptions( + 'filter=s' =>\$filter_name, + 'filter_id=s' =>\$filter_id, + 'version' =>\$version +) or pod2usage(-exitstatus => -1); + +if ( $version ) { + print ZoneMinder::Base::ZM_VERSION . "\n"; + exit(0); +} + +require ZoneMinder::Event; +require ZoneMinder::Filter; + use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|) ? $Config{ZM_DIR_EVENTS} : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) @@ -134,20 +153,6 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL}; my $event_id = 0; -my $filter_parm = ""; -my $version = 0; - -# - -GetOptions( - 'filter=s' =>\$filter_parm, - 'version' =>\$version -) or pod2usage(-exitstatus => -1); - -if ( $version ) { - print ZoneMinder::Base::ZM_VERSION . "\n"; - exit(0); -} if ( ! EVENT_PATH ) { Error( "No event path defined. Config was $Config{ZM_DIR_EVENTS}\n" ); @@ -158,39 +163,42 @@ chdir( EVENT_PATH ); my $dbh = zmDbConnect(); -if ( $filter_parm ) -{ - Info( "Scanning for events using filter '$filter_parm'\n" ); -} -else -{ +if ( $filter_name ) { + Info( "Scanning for events using filter '$filter_name'\n" ); +} elsif ( $filter_id ) { + Info( "Scanning for events using filter id '$filter_id'\n" ); +} else { Info( "Scanning for events\n" ); } -if ( !$filter_parm ) -{ +if ( ! ( $filter_name or $filter_id ) ) { sleep( START_DELAY ); } my $filters; my $last_action = 0; -while( 1 ) -{ +while( 1 ) { my $now = time; - if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) - { + if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) { Debug( "Reloading filters\n" ); $last_action = $now; - $filters = getFilters( $filter_parm ); + $filters = getFilters( { Name=>$filter_name, Id=>$filter_id } ); } - foreach my $filter ( @$filters ) - { - checkFilter( $filter ); + foreach my $filter ( @$filters ) { + if ( $$filter{Concurrent} and ! ( $filter_id or $filter_name ) ) { + my ( $proc ) = $0 =~ /(\S+)/; + my ( $id ) = $$filter{Id} =~ /(\d+)/; + Debug("Running concurrent filter process $proc --filter_id $$filter{Id} => $id for $$filter{Name}"); + + system( qq`$proc --filter "$$filter{Name}" &` ); + } else { + checkFilter( $filter ); + } } - last if ( $filter_parm ); + last if ( $filter_name or $filter_id ); Debug( "Sleeping for $delay seconds\n" ); sleep( $delay ); @@ -198,16 +206,18 @@ while( 1 ) sub getFilters { - my $filter_name = shift; + my $sql_filters = @_ ? shift : {}; + my @sql_values; my @filters; my $sql = "SELECT * FROM Filters WHERE"; - if ( $filter_name ) - { + if ( $$sql_filters{Name} ) { $sql .= " Name = ? and"; - } - else - { + push @sql_values, $$sql_filters{Name}; + } elsif ( $$sql_filters{Id} ) { + $sql .= " Id = ? and"; + push @sql_values, $$sql_filters{Id}; + } else { $sql .= " Background = 1 and"; } $sql .= "( AutoArchive = 1 @@ -220,17 +230,8 @@ sub getFilters ) ORDER BY Name"; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res; - if ( $filter_name ) - { - $res = $sth->execute( $filter_name ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - } - else - { - $res = $sth->execute() - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - } + my $res = $sth->execute( @sql_values ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) { my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); @@ -244,7 +245,12 @@ sub getFilters push( @filters, $filter ); } $sth->finish(); - Debug( "Got " . @filters . " filters" ); + if ( ! @filters ) { + Warning("No filter found for $sql with values(@sql_values)"); + } else { + Debug( "Got " . @filters . " filters" ); + } + return( \@filters ); } @@ -307,7 +313,7 @@ $dbh->ping(); } if ( $filter->{AutoExecute} ) { - if ( !$event->{Execute} ) + if ( !$event->{Executed} ) { $delete_ok = undef if ( !executeCommand( $filter, $event ) ); } @@ -316,30 +322,8 @@ $dbh->ping(); { if ( $delete_ok ) { - Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId}\n" ); - # Do it individually to avoid locking up the table for new events - my $sql = "delete from Events where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - if ( ! $Config{ZM_OPT_FAST_DELETE} ) - { - my $sql = "delete from Frames where EventId = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - $sql = "delete from Stats where EventId = ?"; - $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - deleteEventFiles( $event->{Id}, $event->{MonitorId} ); - } + my $Event = new ZoneMinder::Event( $$event{Id}, $event ); + $Event->delete(); } else { diff --git a/scripts/zmsystemctl.pl.in b/scripts/zmsystemctl.pl.in index d4de9cfed..f46833547 100644 --- a/scripts/zmsystemctl.pl.in +++ b/scripts/zmsystemctl.pl.in @@ -48,7 +48,7 @@ use bytes; use autouse 'Pod::Usage'=>qw(pod2usage); @EXTRA_PERL_LIB@ -use ZoneMinder::Logger qw(:all); +use ZoneMinder; my $command = $ARGV[0]; diff --git a/scripts/zmtelemetry.pl.in b/scripts/zmtelemetry.pl.in index 39e7b73ec..426d36b40 100644 --- a/scripts/zmtelemetry.pl.in +++ b/scripts/zmtelemetry.pl.in @@ -2,7 +2,7 @@ # # ========================================================================== # -# ZoneMinder Update Script, $Date$, $Revision$ +# ZoneMinder Telemetry Upload Script, $Date$, $Revision$ # Copyright (C) 2001-2008 Philip Coombes # # This program is free software; you can redistribute it and/or @@ -20,30 +20,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # ========================================================================== - -=head1 NAME - -zmtelemetry.pl - Send usage information to the ZoneMinder development team - -=head1 SYNOPSIS - - zmtelemetry.pl - -=head1 DESCRIPTION - -This script collects usage information of the local system and sends it to the -ZoneMinder development team. This data will be used to determine things like -who and where our customers are, how big their systems are, the underlying -hardware and operating system, etc. This is being done for the sole purpoase of -creating a better product for our target audience. This script is intended to -be completely transparent to the end user, and can be disabled from the web -console under Options. - -=head1 OPTIONS - -none currently - -=cut use strict; use bytes; @@ -61,22 +37,47 @@ $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} ) +my $help = 0; +my $force = 0; +# Interval between version checks +my $interval; +my $version; + +GetOptions( + force => \$force, + help => \$help, + interval => \$interval, + version => \$version +); +if ( $version ) { + print( ZoneMinder::Base::ZM_VERSION . "\n"); + exit(0); +} +if ( $help ) { + pod2usage(-exitstatus => -1); +} +if ( ! defined $interval ) { + $interval = eval($Config{ZM_TELEMETRY_INTERVAL}); +} + +if ( $Config{ZM_TELEMETRY_DATA} or $force ) { - print( "Update agent starting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); + 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 $since_last_check = $now-$lastCheck; + Debug(" Last Check time (now($now) - lastCheck($lastCheck)) = $since_last_check > interval($interval) or force($force)"); + if ( $since_last_check < 0 ) { + Warning( "Seconds since last check is negative! Which means that lastCheck is in the future!" ); + next; + } + if ( ( ($now-$lastCheck) > $interval ) or $force ) { + print "Collecting data to send to ZoneMinder Telemetry server.\n"; my $dbh = zmDbConnect(); # Build the telemetry hash # We should keep *BSD systems in mind when calling system commands @@ -98,17 +99,20 @@ if ( $Config{ZM_TELEMETRY_DATA} ) my $result = jsonEncode( \%telemetry ); if ( sendData($result) ) { - $lastCheck = $now; - my $sql = "update Config set Value = ? where Name = 'ZM_TELEMETRY_LAST_UPLOAD'"; + 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() ); + my $res = $sth->execute( $now ) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); + $Config{ZM_TELEMETRY_LAST_UPLOAD} = $now; } - } + zmDbDisconnect(); + } elsif ( -t STDIN ) { + print "Update agent sleeping for 1 hour because ($now-$lastCheck=$since_last_check > $interval\n"; + } sleep( 3600 ); } - print( "Update agent exiting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" ); + print "Update agent exiting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n"; } ############### @@ -142,7 +146,7 @@ sub sendData { my $msg = shift; my $ua = LWP::UserAgent->new; - my $server_endpoint = ZM_TELEMETRY_SERVER_ENDPOINT; + my $server_endpoint = $Config{ZM_TELEMETRY_SERVER_ENDPOINT}; if ( $Config{ZM_UPDATE_CHECK_PROXY} ) { $ua->proxy( "https", $Config{ZM_UPDATE_CHECK_PROXY} ); @@ -340,4 +344,37 @@ sub linuxDistro { return ($kernel, $distro, $version); } +1; +__END__ + +=head1 NAME + +zmtelemetry.pl - Send usage information to the ZoneMinder development team + +=head1 SYNOPSIS + + zmtelemetry.pl [--force] [--help] [--interval=seconds] [--version] + +=head1 DESCRIPTION + +This script collects usage information of the local system and sends it to the +ZoneMinder development team. This data will be used to determine things like +who and where our customers are, how big their systems are, the underlying +hardware and operating system, etc. This is being done for the sole purpoase of +creating a better product for our target audience. This script is intended to +be completely transparent to the end user, and can be disabled from the web +console under Options. + +=head1 OPTIONS + + --force Force the script to upload it's data instead of waiting + for the defined interval since last upload. + --help Display usage information + --interval Override the default configured interval since last upload. + The value should be given in seconds, but can be an expression + such as 24*60*60. + --version Output the version of ZoneMinder. + + +=cut diff --git a/scripts/zmtrigger.pl.in b/scripts/zmtrigger.pl.in index 3fc5f43e9..c22c67f15 100644 --- a/scripts/zmtrigger.pl.in +++ b/scripts/zmtrigger.pl.in @@ -47,48 +47,48 @@ B|B|B|B|B|B =item B - is the id number or name of the ZM monitor. +is the id number or name of the ZM monitor. =item B - Valid actions are 'on', 'off', 'cancel' or 'show' where - 'on' forces an alarm condition on; - 'off' forces an alarm condition off; - 'cancel' negates the previous 'on' or 'off'; - 'show' updates the auxiliary text represented by the %Q - placeholder, which can optionally be added to the affected monitor's - timestamp label format. - - Ordinarily you would use 'on' and 'cancel', 'off' would tend to be - used to suppress motion based events. Additionally 'on' and 'off' can - take an additional time offset, e.g. on+20 which automatically - cancel's the previous action after that number of seconds. +Valid actions are 'on', 'off', 'cancel' or 'show' where +'on' forces an alarm condition on; +'off' forces an alarm condition off; +'cancel' negates the previous 'on' or 'off'; +'show' updates the auxiliary text represented by the %Q +placeholder, which can optionally be added to the affected monitor's +timestamp label format. + +Ordinarily you would use 'on' and 'cancel', 'off' would tend to be +used to suppress motion based events. Additionally 'on' and 'off' can +take an additional time offset, e.g. on+20 which automatically +cancel's the previous action after that number of seconds. =item B - is the score given to the alarm, usually to indicate it's - importance. For 'on' triggers it should be non-zero, otherwise it should - be zero. +is the score given to the alarm, usually to indicate it's +importance. For 'on' triggers it should be non-zero, otherwise it should +be zero. =item B - is a 32 char max string indicating the reason for, or source of - the alarm e.g. 'Relay 1 open'. This is saved in the 'Cause' field of the - event. Ignored for 'off' or 'cancel' messages. +is a 32 char max string indicating the reason for, or source of +the alarm e.g. 'Relay 1 open'. This is saved in the 'Cause' field of the +event. Ignored for 'off' or 'cancel' messages. =item B - is a 256 char max additional info field, which is saved in the - 'Description' field of an event. Ignored for 'off' or 'cancel' messages. +is a 256 char max additional info field, which is saved in the +'Description' field of an event. Ignored for 'off' or 'cancel' messages. =item B - is up to 32 characters of text that can be displayed in the - timestamp that is added to images. The 'show' action is designed to - update this text without affecting alarms but the text is updated, if - present, for any of the actions. This is designed to allow external input - to appear on the images captured, for instance temperature or personnel - identity etc. +is up to 32 characters of text that can be displayed in the +timestamp that is added to images. The 'show' action is designed to +update this text without affecting alarms but the text is updated, if +present, for any of the actions. This is designed to allow external input +to appear on the images captured, for instance temperature or personnel +identity etc. =back @@ -102,9 +102,9 @@ likely to be easier. =head1 EXAMPLES - 3|on+10|1|motion|text|showtext +3|on+10|1|motion|text|showtext -Triggers "alarm" on camera #3 for 10 seconds with score=1, cause="motion". +Triggers 'alarm' on camera #3 for 10 seconds with score=1, cause='motion'. =cut use strict; @@ -135,23 +135,23 @@ 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" + 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" + 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" ) ); + ); +#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' ) ); # ========================================================================== # @@ -179,25 +179,25 @@ Info( "Trigger daemon starting\n" ); my $dbh = zmDbConnect(); my $base_rin = ''; -foreach my $connection ( @connections ) -{ - Info( "Opening connection '$connection->{name}'\n" ); - $connection->open(); +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; +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 = ''; @@ -205,388 +205,305 @@ 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; - } +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: $!" ); - } - } + 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 ) { - # Check polled connections - foreach my $connection ( @in_poll_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 ); - } - } - } - - # Check for alarms that might have happened - my @out_messages; - foreach my $monitor ( values(%monitors) ) - { - 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" ); + 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(); } - delete( $actions{$action_time} ); + } + } # end foreach connection + } elsif ( $nfound < 0 ) { + if ( $! == EINTR ) { +# Do nothing + } else { + Fatal( "Can't select: $!" ); + } + } # end if select returned activitiy + +# 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; } - # 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 ); - } - } - } + my ( $state, $last_event ) = zmMemRead( $monitor, + [ 'shared_data:state', 'shared_data:last_event' ] + ); - # If necessary reload monitors - if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) - { - foreach my $monitor ( values(%monitors) ) - { - # Free up any used memory handle - zmMemInvalidate( $monitor ); - } - loadMonitors(); +#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; + } # end foreach monitor + + 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 ); + } + } + + if ( my @action_times = keys(%actions) ) { + Debug( "Checking for timed actions\n" ); + my $now = time(); + foreach my $action_time ( sort( grep { $_ < $now } @action_times ) ) { + 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} ); + } + } # end if have timed actions + +# 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(); +sub loadMonitors { + Debug( "Loading monitors\n" ); + $monitor_reload_time = time(); - my %new_monitors = (); + my %new_monitors = (); - my $sql = "SELECT * FROM Monitors - WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )". - ( $Config{ZM_SERVER_ID} ? 'AND ServerId=?' : '' ) + my $sql = "SELECT * FROM Monitors WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )". + ( $Config{ZM_SERVER_ID} ? 'AND ServerId=?' : '' ). + 'ORDER BY Id DESC' ; - 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() ) - { - next if ( !zmMemVerify( $monitor ) ); # Check shared memory ok - - 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; + 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; } - %monitors = %new_monitors; + + $monitor->{LastState} = zmGetMonitorState( $monitor ); + $monitor->{LastEvent} = zmGetLastEvent( $monitor ); + $new_monitors{$monitor->{Id}} = $monitor; + } # end while fetchrow + %monitors = %new_monitors; } -sub handleMessage -{ - my $connection = shift; - my $message = shift; +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 ( $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; + 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 ); } - 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); - } +# 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} ); + } 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 ); + 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( "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" ); - } + Info( "Trigger '$trigger'\n" ); +# Wait til it's finished + while( zmInAlarm( $monitor ) && ($last_event == zmGetLastEvent( $monitor ))) { +# Tenth of a second + usleep( 100000 ); + } + zmTriggerEventCancel( $monitor ); + } + } # end if trigger is on or off + } 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" ); +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__ diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index cb0781c2e..ee1829a99 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -116,7 +116,7 @@ GetOptions( 'dir:s' =>\$updateDir ) or pod2usage(-exitstatus => -1); -my $dbh = zmDbConnect(); +my $dbh = zmDbConnect(undef, { mysql_multi_statements=>1 } ); $Config{ZM_DB_USER} = $dbUser; $Config{ZM_DB_PASS} = $dbPass; @@ -449,47 +449,19 @@ if ( $version ) my $dbh = shift; my $version = shift; - my ( $host, $port ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); - my $command = "mysql -h".$host; - $command .= " -P".$port if defined($port); - if ( $dbUser ) - { - $command .= " -u".$dbUser; - $command .= ' -p"'.$dbPass.'"' if $dbPass; - } - $command .= " ".$Config{ZM_DB_NAME}." < "; - if ( $updateDir ) - { - $command .= $updateDir; - } - else - { - $command .= $Config{ZM_PATH_DATA}."/db"; - } - $command .= "/zm_update-".$version.".sql"; + my $file = ( $updateDir ? $updateDir : $Config{ZM_PATH_DATA}.'/db' ) . "/zm_update-$version.sql"; - print( "Executing '$command'\n" ) if ( logDebugging() ); - my $output = qx($command); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - chomp( $output ); - print( "Output: $output\n" ); - } - if ( $status ) - { - die( "Command '$command' exited with status: $status\n" ); - } - else - { - print( "\nDatabase successfully upgraded to version $version.\n" ); - my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() ); - } + open( my $fh, '<', $file ) or die "Unable to open $file $!"; + $/ = undef; + my $sql = <$fh>; + close $fh; + $dbh->do($sql) or die $dbh->errstr(); + print( "\nDatabase successfully upgraded to version $version.\n" ); + $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() ); } - print( "\nUpgrading database to version ".ZM_VERSION."\n" ); # Update config first of all diff --git a/scripts/zmvideo.pl.in b/scripts/zmvideo.pl.in index 5f71451a3..dcb3633fb 100644 --- a/scripts/zmvideo.pl.in +++ b/scripts/zmvideo.pl.in @@ -27,12 +27,14 @@ zmvideo.pl - ZoneMinder Video Creation Script =head1 SYNOPSIS - zmvideo.pl [ -e ,--event= | --filter= ] [--format ] - [--rate=] - [--scale=] - [--fps=] - [--size=] - [--overwrite] + zmvideo.pl [ -e ,--event= | --filter_name= | --filter_id= ] + [--concat=filename] + [--format ] + [--rate=] + [--scale=] + [--fps=] + [--size=] + [--overwrite] =head1 DESCRIPTION @@ -104,10 +106,10 @@ for ( my $i = 0; $i < @formats; $i++ ) } 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_name=s' =>\$filter_name, + 'filter_id=i' =>\$filter_id, 'format|f=s' =>\$format, 'rate|r=f' =>\$rate, 'scale|s=f' =>\$scale, @@ -181,19 +183,24 @@ my $cwd = getcwd; my $video_name; my @event_ids; if ( $event_id ) { - @event_ids = ( $event_id ); - + Debug("Making a video of event $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 ''; + Debug("Loading filter " .join('', + ($filter_name ? "Name => $filter_name" : '' ), + ($filter_id ? "Id => $filter_id " : '' ), + ) ); + my $Filter = ZoneMinder::Filter->find_one( + ($filter_name ? ( Name => $filter_name ) : () ), + ($filter_id ? ( Id => $filter_id ) : () ), + ); + 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 $sql = " SELECT max(F.Delta)-min(F.Delta) as FullLength, @@ -210,50 +217,52 @@ my $sql = " SELECT max(F.Delta)-min(F.Delta) as FullLength, GROUP BY F.EventId" ; 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 ) { + Debug("Generated $video_file"); + push @video_files, $video_file; + print( STDOUT $video_file."\n" ); + } } # 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 -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 ); diff --git a/scripts/zmwatch.pl.in b/scripts/zmwatch.pl.in index 8f551f79d..cdc7d38d9 100644 --- a/scripts/zmwatch.pl.in +++ b/scripts/zmwatch.pl.in @@ -78,115 +78,107 @@ my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=?' : 'S my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); -while( 1 ) -{ - my $now = time(); - 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() ) - { - next if $monitor->{Function} eq 'None'; - my $restart = 0; - # Prevent open handles building up if we have connect to shared memory - # Many of our error checks below do a next without closing the mem handle. - # zmMemInvalidate will just return of nothing is open, so we can just do this here. +while( 1 ) { + my $now = time(); + 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() ) { + next if $monitor->{Function} eq 'None'; + my $restart = 0; + if ( zmMemVerify( $monitor ) ) { +# Check we have got an image recently + my $image_time = zmGetLastWriteTime( $monitor ); + if ( !defined($image_time) ) { +# Can't read from shared data + Debug( "LastWriteTime is not defined." ); zmMemInvalidate( $monitor ); - if ( zmMemVerify( $monitor ) - && zmMemRead( $monitor, "shared_data:valid" ) - ) - { - # Check we have got an image recently - my $image_time = zmGetLastWriteTime( $monitor ); - if ( !defined($image_time) ) { - # Can't read from shared data - Debug( "LastWriteTime is not defined." ); - next; - } - if ( !$image_time ) { - # We can't get the last capture time so can't be sure it's died. - Debug( "LastWriteTime is = $image_time." ); - next; - } + next; + } + Debug( "LastWriteTime is = $image_time." ); + if ( !$image_time ) { + my $startup_time = zmGetStartupTime( $monitor ); + if ( $now - $startup_time > $Config{ZM_WATCH_MAX_DELAY} ) { + Info( "Restarting capture daemon for ".$monitor->{Name}.", no image since startup. Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}\n" ); + $restart = 1; + } else { + # We can't get the last capture time so can't be sure it's died, it might just be starting up. + zmMemInvalidate( $monitor ); + next; + } + } + if ( ! $restart ) { + my $max_image_delay = ( $monitor->{MaxFPS} + &&($monitor->{MaxFPS}>0) + &&($monitor->{MaxFPS}<1) + ) ? (3/$monitor->{MaxFPS}) + : $Config{ZM_WATCH_MAX_DELAY} + ; + my $image_delay = $now-$image_time; + Debug( "Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay\n" ); + if ( $image_delay > $max_image_delay ) { + Info( "Restarting capture daemon for " + .$monitor->{Name}.", time since last capture $image_delay seconds ($now-$image_time)\n" + ); + $restart = 1; + } + } # end if ! restart + } else { + Info( "Restarting capture daemon for ".$monitor->{Name}.", shared data not valid\n" ); + $restart = 1; + } - my $max_image_delay = ( $monitor->{MaxFPS} - &&($monitor->{MaxFPS}>0) - &&($monitor->{MaxFPS}<1) - ) ? (3/$monitor->{MaxFPS}) - : $Config{ZM_WATCH_MAX_DELAY} - ; - my $image_delay = $now-$image_time; - Debug( "Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay\n" ); - if ( $image_delay > $max_image_delay ) - { - Info( "Restarting capture daemon for " - .$monitor->{Name}.", time since last capture $image_delay seconds ($now-$image_time)\n" - ); - $restart = 1; - } - } - else - { - Info( "Restarting capture daemon for ".$monitor->{Name}.", shared data not valid\n" ); - $restart = 1; + if ( $restart ) { + my $command; + if ( $monitor->{Type} eq 'Local' ) { + $command = "zmdc.pl restart zmc -d $monitor->{Device}"; + } else { + $command = "zmdc.pl restart zmc -m $monitor->{Id}"; + } + runCommand( $command ); + } elsif ( $monitor->{Function} ne 'Monitor' ) { +# Now check analysis daemon + $restart = 0; +# Check we have got an image recently + my $image_time = zmGetLastReadTime( $monitor ); + if ( !defined($image_time) ) { +# Can't read from shared data + $restart = 1; + Error( "Error reading shared data for $$monitor{id} $$monitor{Name}\n"); + } elsif ( !$image_time ) { +# We can't get the last capture time so can't be sure it's died. + $restart = 1; + Error( "Error getting last capture time for $$monitor{id} $$monitor{Name}\n"); + } else { + + my $max_image_delay = ( $monitor->{MaxFPS} + &&($monitor->{MaxFPS}>0) + &&($monitor->{MaxFPS}<1) + ) ? (3/$monitor->{MaxFPS}) + : $Config{ZM_WATCH_MAX_DELAY} + ; + my $image_delay = $now-$image_time; + Debug( "Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay\n" ); + if ( $image_delay > $max_image_delay ) { + Info( "Analysis daemon for $$monitor{id} $$monitor{Name} needs restarting," + ." time since last analysis $image_delay seconds ($now-$image_time)\n" + ); + $restart = 1; } + } - if ( $restart ) - { - my $command; - if ( $monitor->{Type} eq 'Local' ) - { - $command = "zmdc.pl restart zmc -d $monitor->{Device}"; - } - else - { - $command = "zmdc.pl restart zmc -m $monitor->{Id}"; - } - runCommand( $command ); - } - elsif ( $monitor->{Function} ne 'Monitor' ) - { - # Now check analysis daemon - $restart = 0; - # Check we have got an image recently - my $image_time = zmGetLastReadTime( $monitor ); - if ( !defined($image_time) ) { - # Can't read from shared data - $restart = 1; - Error( "Error reading shared data for $$monitor{id} $$monitor{Name}\n"); - } elsif ( !$image_time ) { - # We can't get the last capture time so can't be sure it's died. - $restart = 1; - Error( "Error getting last capture time for $$monitor{id} $$monitor{Name}\n"); - } else { - - my $max_image_delay = ( $monitor->{MaxFPS} - &&($monitor->{MaxFPS}>0) - &&($monitor->{MaxFPS}<1) - ) ? (3/$monitor->{MaxFPS}) - : $Config{ZM_WATCH_MAX_DELAY} - ; - my $image_delay = $now-$image_time; - Debug( "Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay\n" ); - if ( $image_delay > $max_image_delay ) - { - Info( "Analysis daemon for $$monitor{id} $$monitor{Name} needs restarting," - ." time since last analysis $image_delay seconds ($now-$image_time)\n" - ); - $restart = 1; - } - } - - if ( $restart ) - { - Info( "Restarting analysis daemon for $$monitor{id} $$monitor{Name}\n"); - my $command = "zmdc.pl restart zma -m ".$monitor->{Id}; - runCommand( $command ); - } # end if restart - } # end if check analysis daemon - # Prevent open handles building up if we have connect to shared memory - zmMemInvalidate( $monitor ); - } # end foreach monitor - sleep( $Config{ZM_WATCH_CHECK_INTERVAL} ); + if ( $restart ) { + Info( "Restarting analysis daemon for $$monitor{id} $$monitor{Name}\n"); + my $command = "zmdc.pl restart zma -m ".$monitor->{Id}; + runCommand( $command ); + } # end if restart + } # end if check analysis daemon + # Prevent open handles building up if we have connect to shared memory + zmMemInvalidate( $monitor ); # Close our file handle to the zmc process we are about to end + } # end foreach monitor + sleep( $Config{ZM_WATCH_CHECK_INTERVAL} ); } # end while (1) + Info( "Watchdog exiting\n" ); exit(); +1; +__END__ diff --git a/scripts/zmx10.pl.in b/scripts/zmx10.pl.in index 58064a89f..beb10ae48 100644 --- a/scripts/zmx10.pl.in +++ b/scripts/zmx10.pl.in @@ -51,7 +51,7 @@ use bytes; # # ========================================================================== -use constant CAUSE_STRING => "X10"; # What gets written as the cause of any events +use constant CAUSE_STRING => 'X10'; # What gets written as the cause of any events # ========================================================================== # @@ -93,11 +93,11 @@ if ( $version ) { exit(0); } -die( "No command given" ) unless( $command ); -die( "No unit code given" ) +die( 'No command given' ) unless( $command ); +die( 'No unit code given' ) unless( $unit_code || ($command =~ /(?:start|status|shutdown)/) ); -if ( $command eq "start" ) +if ( $command eq 'start' ) { X10Server::runServer(); exit(); @@ -201,7 +201,7 @@ sub runServer vec( $rin, fileno(SERVER),1) = 1; vec( $rin, $x10->select_fds(),1) = 1; my $timeout = 0.2; - #print( "F:".fileno(SERVER)."\n" ); + #print( 'F:'.fileno(SERVER)."\n" ); my $reload = undef; my $reload_count = 0; my $reload_limit = $Config{ZM_X10_DB_RELOAD_INTERVAL} / $timeout; @@ -259,14 +259,14 @@ sub runServer { if ( $device ) { - dPrint( ZoneMinder::Logger::DEBUG, $unit_code." ".$device->{status}."\n" ); + dPrint( ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n" ); } else { foreach my $unit_code ( sort( keys(%device_hash) ) ) { my $device = $device_hash{$unit_code}; - dPrint( ZoneMinder::Logger::DEBUG, $unit_code." ".$device->{status}."\n" ); + dPrint( ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n" ); } } } @@ -299,7 +299,7 @@ sub runServer } else { - Fatal( "Bogus descriptor" ); + Fatal( 'Bogus descriptor' ); } } elsif ( $nfound < 0 ) @@ -330,14 +330,14 @@ sub runServer ) # Gone into alarm state { Debug( "Applying ON_list for $monitor_id\n" ); - $task_list = $monitor->{"ON_list"}; + $task_list = $monitor->{'ON_list'}; } elsif ( ($state == STATE_IDLE && $monitor->{LastState} != STATE_IDLE) || ($state == STATE_TAPE && $monitor->{LastState} != STATE_TAPE) ) # Come out of alarm state { Debug( "Applying OFF_list for $monitor_id\n" ); - $task_list = $monitor->{"OFF_list"}; + $task_list = $monitor->{'OFF_list'}; } if ( $task_list ) { @@ -394,7 +394,7 @@ sub addToDeviceList }; } - my $task = { type=>"device", + my $task = { type=>'device', monitor=>$monitor, address=>$device->{appliance}->address(), function=>$function @@ -404,10 +404,10 @@ sub addToDeviceList $task->{limit} = $limit } - my $task_list = $device->{$event."_list"}; + my $task_list = $device->{$event.'_list'}; if ( !$task_list ) { - $task_list = $device->{$event."_list"} = []; + $task_list = $device->{$event.'_list'} = []; } push( @$task_list, $task ); } @@ -431,7 +431,7 @@ sub addToMonitorList }; } - my $task = { type=>"monitor", + my $task = { type=>'monitor', device=>$device, id=>$monitor->{Id}, function=>$function @@ -441,10 +441,10 @@ sub addToMonitorList $task->{limit} = $limit; } - my $task_list = $monitor->{$event."_list"}; + my $task_list = $monitor->{$event.'_list'}; if ( !$task_list ) { - $task_list = $monitor->{$event."_list"} = []; + $task_list = $monitor->{$event.'_list'} = []; } push( @$task_list, $task ); } @@ -474,7 +474,11 @@ sub loadTasks or Fatal( "Can't execute: ".$sth->errstr() ); while( my $monitor = $sth->fetchrow_hashref() ) { - next if ( !zmMemVerify( $monitor ) ); # Check shared memory ok +# Check shared memory ok + if ( !zmMemVerify( $monitor ) ) { + zmMemInvalidate( $monitor ); + next ; + } $monitor_hash{$monitor->{Id}} = $monitor; @@ -492,20 +496,20 @@ sub loadTasks if ( !$modifier || $modifier eq '+' ) { addToDeviceList( $unit_code, - "ON", + 'ON', $monitor, - !$invert ? "start_active" - : "stop_active", + !$invert ? 'start_active' + : 'stop_active', $limit ); } if ( !$modifier || $modifier eq '-' ) { addToDeviceList( $unit_code, - "OFF", + 'OFF', $monitor, - !$invert ? "stop_active" - : "start_active", + !$invert ? 'stop_active' + : 'start_active', $limit ); } @@ -526,20 +530,20 @@ sub loadTasks if ( !$modifier || $modifier eq '+' ) { addToDeviceList( $unit_code, - "ON", + 'ON', $monitor, - !$invert ? "start_alarm" - : "stop_alarm", + !$invert ? 'start_alarm' + : 'stop_alarm', $limit ); } if ( !$modifier || $modifier eq '-' ) { addToDeviceList( $unit_code, - "OFF", + 'OFF', $monitor, - !$invert ? "stop_alarm" - : "start_alarm", + !$invert ? 'stop_alarm' + : 'start_alarm', $limit ); } @@ -560,20 +564,20 @@ sub loadTasks if ( !$modifier || $modifier eq '+' ) { addToMonitorList( $monitor, - "ON", + 'ON', $unit_code, - !$invert ? "on" - : "off", + !$invert ? 'on' + : 'off', $limit ); } if ( !$modifier || $modifier eq '-' ) { addToMonitorList( $monitor, - "OFF", + 'OFF', $unit_code, - !$invert ? "off" - : "on", + !$invert ? 'off' + : 'on', $limit ); } @@ -600,7 +604,7 @@ sub addPendingTask { push( @$new_pending_list, $pending_task ) } - elsif ( $task->{type} eq "device" ) + elsif ( $task->{type} eq 'device' ) { if (( $task->{monitor}->{Id} != $pending_task->{monitor}->{Id} ) || ( $task->{function} ne $pending_task->{function} )) @@ -608,7 +612,7 @@ sub addPendingTask push( @$new_pending_list, $pending_task ) } } - elsif ( $task->{type} eq "monitor" ) + elsif ( $task->{type} eq 'monitor' ) { if (( $task->{device}->{appliance}->unit_code() != $pending_task->{device}->{appliance}->unit_code() @@ -637,7 +641,7 @@ sub addPendingTask $pending_list = $pending_tasks{$end_time} = []; } my $pending_task; - if ( $task->{type} eq "device" ) + if ( $task->{type} eq 'device' ) { $pending_task = { type=>$task->{type}, monitor=>$task->{monitor}, @@ -645,7 +649,7 @@ sub addPendingTask }; $pending_task->{function} =~ s/start/stop/; } - elsif ( $task->{type} eq "monitor" ) + elsif ( $task->{type} eq 'monitor' ) { $pending_task = { type=>$task->{type}, device=>$task->{device}, @@ -660,13 +664,13 @@ sub processTask { my $task = shift; - if ( $task->{type} eq "device" ) + if ( $task->{type} eq 'device' ) { my ( $instruction, $class ) = ( $task->{function} =~ /^(.+)_(.+)$/ ); - if ( $class eq "active" ) + if ( $class eq 'active' ) { - if ( $instruction eq "start" ) + if ( $instruction eq 'start' ) { zmMonitorEnable( $task->{monitor} ); if ( $task->{limit} ) @@ -674,14 +678,14 @@ sub processTask addPendingTask( $task ); } } - elsif( $instruction eq "stop" ) + elsif( $instruction eq 'stop' ) { zmMonitorDisable( $task->{monitor} ); } } - elsif( $class eq "alarm" ) + elsif( $class eq 'alarm' ) { - if ( $instruction eq "start" ) + if ( $instruction eq 'start' ) { zmTriggerEventOn( $task->{monitor}, 0, @@ -693,15 +697,15 @@ sub processTask addPendingTask( $task ); } } - elsif( $instruction eq "stop" ) + elsif( $instruction eq 'stop' ) { zmTriggerEventCancel( $task->{monitor} ); } } } - elsif( $task->{type} eq "monitor" ) + elsif( $task->{type} eq 'monitor' ) { - if ( $task->{function} eq "on" ) + if ( $task->{function} eq 'on' ) { $task->{device}->{appliance}->on(); if ( $task->{limit} ) @@ -709,7 +713,7 @@ sub processTask addPendingTask( $task ); } } - elsif ( $task->{function} eq "off" ) + elsif ( $task->{function} eq 'off' ) { $task->{device}->{appliance}->off(); } @@ -762,7 +766,7 @@ sub x10listen } next if ( $event->func() !~ /(?:ON|OFF)/ ); $device->{status} = $event->func(); - my $task_list = $device->{$event->func()."_list"}; + my $task_list = $device->{$event->func().'_list'}; if ( $task_list ) { foreach my $task ( @$task_list ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9876e2a87..e33f0f0bb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY) # Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc) -set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_zone.cpp) +set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_camera.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_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 zm_storage.cpp) # A fix for cmake recompiling the source files for every target. add_library(zm STATIC ${ZM_BIN_SRC_FILES}) diff --git a/src/snprintf.cpp b/src/snprintf.cpp new file mode 100644 index 000000000..645534e9c --- /dev/null +++ b/src/snprintf.cpp @@ -0,0 +1,25 @@ +snprintf( swap_path, sizeof(swap_path), "%s/zmswap-m%d/zmswap-q%06d", config.path_swap, monitor->Id(), connkey ); + +int len = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id()); + + + int swap_path_length = strlen(config.path_swap) + snprintf(NULL, 0, "/zmswap-m%d", monitor->Id() ) + snprintf(NULL, 0, "/zmswap-q%06d", connkey ) + 1; // +1 for NULL terminator + + if ( connkey && playback_buffer > 0 ) { + + if ( swap_path_length + max_swap_len_suffix > PATH_MAX ) { + Error( "Swap Path is too long. %d > %d ", swap_path_length+max_swap_len_suffix, PATH_MAX ); + } else { + swap_path = (char *)malloc( swap_path_length+max_swap_len_suffix ); + Debug( 3, "Checking swap image path %s", config.path_swap ); + strncpy( swap_path, config.path_swap, swap_path_length ); + if ( checkSwapPath( swap_path, false ) ) { + snprintf( &(swap_path[swap_path_length]), max_swap_len_suffix, "/zmswap-m%d", monitor->Id() ); + if ( checkSwapPath( swap_path, true ) ) { + snprintf( &(swap_path[swap_path_length]), max_swap_len_suffix, "/zmswap-q%06d", connkey ); + if ( checkSwapPath( swap_path, true ) ) { + buffered_playback = true; + } + } + } + diff --git a/src/zm_buffer.h b/src/zm_buffer.h index bcc952dc6..6fa18984e 100644 --- a/src/zm_buffer.h +++ b/src/zm_buffer.h @@ -153,7 +153,7 @@ public: mHead = mTail = mStorage; else if ( level ) { - if ( (mHead-mStorage) > mSize ) + if ( ((uintptr_t)mHead-(uintptr_t)mStorage) > mSize ) { memcpy( mStorage, mHead, mSize ); mHead = mStorage; diff --git a/src/zm_camera.cpp b/src/zm_camera.cpp index 899d7822a..30918e502 100644 --- a/src/zm_camera.cpp +++ b/src/zm_camera.cpp @@ -20,23 +20,24 @@ #include "zm.h" #include "zm_camera.h" -Camera::Camera( int p_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - id( p_id ), - type( p_type ), - width( p_width), - height( p_height ), - colours( p_colours ), - subpixelorder( p_subpixelorder ), - brightness( p_brightness ), - hue( p_hue ), - colour( p_colour ), - contrast( p_contrast ), - capture( p_capture ) +Camera::Camera( unsigned int p_monitor_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + monitor_id( p_monitor_id ), + type( p_type ), + width( p_width), + height( p_height ), + colours( p_colours ), + subpixelorder( p_subpixelorder ), + brightness( p_brightness ), + hue( p_hue ), + colour( p_colour ), + contrast( p_contrast ), + capture( p_capture ), + record_audio( p_record_audio ) { pixels = width * height; imagesize = pixels * colours; - Debug(2,"New camera id: %d width: %d height: %d colours: %d subpixelorder: %d capture: %d",id,width,height,colours,subpixelorder,capture); + Debug(2,"New camera id: %d width: %d height: %d colours: %d subpixelorder: %d capture: %d",monitor_id,width,height,colours,subpixelorder,capture); /* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */ if((colours == ZM_COLOUR_GRAY8 || colours == ZM_COLOUR_RGB32) && (imagesize % 16) != 0) { @@ -46,7 +47,16 @@ Camera::Camera( int p_id, SourceType p_type, int p_width, int p_height, int p_co } } -Camera::~Camera() -{ +Camera::~Camera() { } +Monitor *Camera::getMonitor() { + if ( ! monitor ) + monitor = Monitor::Load( monitor_id, false, Monitor::QUERY ); + return monitor; +} + +void Camera::setMonitor( Monitor *p_monitor ) { + monitor = p_monitor; + monitor_id = monitor->Id(); +} diff --git a/src/zm_camera.h b/src/zm_camera.h index e56e68c55..ed9647c54 100644 --- a/src/zm_camera.h +++ b/src/zm_camera.h @@ -25,6 +25,10 @@ #include "zm_image.h" +class Camera; + +#include "zm_monitor.h" + // // Abstract base class for cameras. This is intended just to express // common attributes @@ -34,7 +38,8 @@ class Camera protected: typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC } SourceType; - int id; + unsigned int monitor_id; + Monitor * monitor; // Null on instantiation, set as soon as possible. SourceType type; unsigned int width; unsigned int height; @@ -42,17 +47,20 @@ protected: unsigned int subpixelorder; unsigned int pixels; unsigned int imagesize; - int brightness; - int hue; - int colour; - int contrast; - bool capture; + int brightness; + int hue; + int colour; + int contrast; + bool capture; + bool record_audio; public: - Camera( int p_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + Camera( unsigned int p_monitor_id, SourceType p_type, int p_width, int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); virtual ~Camera(); - int getId() const { return( id ); } + unsigned int getId() const { return( monitor_id ); } + Monitor *getMonitor(); + void setMonitor( Monitor *p_monitor ); SourceType Type() const { return( type ); } bool IsLocal() const { return( type == LOCAL_SRC ); } bool IsRemote() const { return( type == REMOTE_SRC ); } @@ -73,11 +81,14 @@ public: virtual int Contrast( int/*p_contrast*/=-1 ) { return( -1 ); } bool CanCapture() const { return( capture ); } - + + bool SupportsNativeVideo() const { return( (type == FFMPEG_SRC )||(type == REMOTE_SRC)); } + virtual int PrimeCapture() { return( 0 ); } virtual int PreCapture()=0; virtual int Capture( Image &image )=0; virtual int PostCapture()=0; + virtual int CaptureAndRecord( Image &image, bool recording, char* event_directory)=0; }; #endif // ZM_CAMERA_H diff --git a/src/zm_config.cpp b/src/zm_config.cpp index 6f4e59b6e..3c0cd0b00 100644 --- a/src/zm_config.cpp +++ b/src/zm_config.cpp @@ -117,7 +117,8 @@ void zmLoadConfig() Debug( 1, "Fetching ZM_SERVER_ID For Name = %s", staticConfig.SERVER_NAME.c_str() ); std::string sql = stringtf("SELECT Id FROM Servers WHERE Name='%s'", staticConfig.SERVER_NAME.c_str() ); - if ( MYSQL_ROW dbrow = zmDbFetchOne( sql.c_str() ) ) { + zmDbRow dbrow; + if ( dbrow.fetch( sql.c_str() ) ) { staticConfig.SERVER_ID = atoi(dbrow[0]); } else { Fatal("Can't get ServerId for Server %s", staticConfig.SERVER_NAME.c_str() ); @@ -128,7 +129,8 @@ void zmLoadConfig() Debug( 1, "Fetching ZM_SERVER_NAME For Id = %d", staticConfig.SERVER_ID ); std::string sql = stringtf("SELECT Name FROM Servers WHERE Id='%d'", staticConfig.SERVER_ID ); - if ( MYSQL_ROW dbrow = zmDbFetchOne( sql.c_str() ) ) { + zmDbRow dbrow; + if ( dbrow.fetch( sql.c_str() ) ) { staticConfig.SERVER_NAME = std::string(dbrow[0]); } else { Fatal("Can't get ServerName for Server ID %d", staticConfig.SERVER_ID ); @@ -145,160 +147,160 @@ StaticConfig staticConfig; ConfigItem::ConfigItem( const char *p_name, const char *p_value, const char *const p_type ) { - name = new char[strlen(p_name)+1]; - strcpy( name, p_name ); - value = new char[strlen(p_value)+1]; - strcpy( value, p_value ); - type = new char[strlen(p_type)+1]; - strcpy( type, p_type ); + name = new char[strlen(p_name)+1]; + strcpy( name, p_name ); + value = new char[strlen(p_value)+1]; + strcpy( value, p_value ); + type = new char[strlen(p_type)+1]; + strcpy( type, p_type ); - //Info( "Created new config item %s = %s (%s)\n", name, value, type ); + //Info( "Created new config item %s = %s (%s)\n", name, value, type ); - accessed = false; + accessed = false; } ConfigItem::~ConfigItem() { - delete[] name; - delete[] value; - delete[] type; + delete[] name; + delete[] value; + delete[] type; } void ConfigItem::ConvertValue() const { - if ( !strcmp( type, "boolean" ) ) - { - cfg_type = CFG_BOOLEAN; - cfg_value.boolean_value = (bool)strtol( value, 0, 0 ); - } - else if ( !strcmp( type, "integer" ) ) - { - cfg_type = CFG_INTEGER; - cfg_value.integer_value = strtol( value, 0, 10 ); - } - else if ( !strcmp( type, "hexadecimal" ) ) - { - cfg_type = CFG_INTEGER; - cfg_value.integer_value = strtol( value, 0, 16 ); - } - else if ( !strcmp( type, "decimal" ) ) - { - cfg_type = CFG_DECIMAL; - cfg_value.decimal_value = strtod( value, 0 ); - } - else - { - cfg_type = CFG_STRING; - cfg_value.string_value = value; - } - accessed = true; + if ( !strcmp( type, "boolean" ) ) + { + cfg_type = CFG_BOOLEAN; + cfg_value.boolean_value = (bool)strtol( value, 0, 0 ); + } + else if ( !strcmp( type, "integer" ) ) + { + cfg_type = CFG_INTEGER; + cfg_value.integer_value = strtol( value, 0, 10 ); + } + else if ( !strcmp( type, "hexadecimal" ) ) + { + cfg_type = CFG_INTEGER; + cfg_value.integer_value = strtol( value, 0, 16 ); + } + else if ( !strcmp( type, "decimal" ) ) + { + cfg_type = CFG_DECIMAL; + cfg_value.decimal_value = strtod( value, 0 ); + } + else + { + cfg_type = CFG_STRING; + cfg_value.string_value = value; + } + accessed = true; } bool ConfigItem::BooleanValue() const { - if ( !accessed ) - ConvertValue(); + if ( !accessed ) + ConvertValue(); - if ( cfg_type != CFG_BOOLEAN ) - { - Error( "Attempt to fetch boolean value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); - exit( -1 ); - } + if ( cfg_type != CFG_BOOLEAN ) + { + Error( "Attempt to fetch boolean value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); + exit( -1 ); + } - return( cfg_value.boolean_value ); + return( cfg_value.boolean_value ); } int ConfigItem::IntegerValue() const { - if ( !accessed ) - ConvertValue(); + if ( !accessed ) + ConvertValue(); - if ( cfg_type != CFG_INTEGER ) - { - Error( "Attempt to fetch integer value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); - exit( -1 ); - } + if ( cfg_type != CFG_INTEGER ) + { + Error( "Attempt to fetch integer value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); + exit( -1 ); + } - return( cfg_value.integer_value ); + return( cfg_value.integer_value ); } double ConfigItem::DecimalValue() const { - if ( !accessed ) - ConvertValue(); + if ( !accessed ) + ConvertValue(); - if ( cfg_type != CFG_DECIMAL ) - { - Error( "Attempt to fetch decimal value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); - exit( -1 ); - } + if ( cfg_type != CFG_DECIMAL ) + { + Error( "Attempt to fetch decimal value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); + exit( -1 ); + } - return( cfg_value.decimal_value ); + return( cfg_value.decimal_value ); } const char *ConfigItem::StringValue() const { - if ( !accessed ) - ConvertValue(); + if ( !accessed ) + ConvertValue(); - if ( cfg_type != CFG_STRING ) - { - Error( "Attempt to fetch string value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); - exit( -1 ); - } + if ( cfg_type != CFG_STRING ) + { + Error( "Attempt to fetch string value for %s, actual type is %s. Try running 'zmupdate.pl -f' to reload config.", name, type ); + exit( -1 ); + } - return( cfg_value.string_value ); + return( cfg_value.string_value ); } Config::Config() { - n_items = 0; - items = 0; + n_items = 0; + items = 0; } Config::~Config() { - if ( items ) - { - for ( int i = 0; i < n_items; i++ ) - { - delete items[i]; - } - delete[] items; - } + if ( items ) + { + for ( int i = 0; i < n_items; i++ ) + { + delete items[i]; + } + delete[] items; + } } void Config::Load() { - static char sql[ZM_SQL_SML_BUFSIZ]; + static char sql[ZM_SQL_SML_BUFSIZ]; - strncpy( sql, "select Name, Value, Type from Config order by Id", sizeof(sql) ); - if ( mysql_query( &dbconn, sql ) ) - { - Error( "Can't run query: %s", mysql_error( &dbconn ) ); - exit( mysql_errno( &dbconn ) ); - } + strncpy( sql, "select Name, Value, Type from Config order by Id", sizeof(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 ) ); - } - n_items = mysql_num_rows( result ); + MYSQL_RES *result = mysql_store_result( &dbconn ); + if ( !result ) + { + Error( "Can't use query result: %s", mysql_error( &dbconn ) ); + exit( mysql_errno( &dbconn ) ); + } + n_items = mysql_num_rows( result ); - if ( n_items <= ZM_MAX_CFG_ID ) - { - Error( "Config mismatch, expected %d items, read %d. Try running 'zmupdate.pl -f' to reload config.", ZM_MAX_CFG_ID+1, n_items ); - exit( -1 ); - } + if ( n_items <= ZM_MAX_CFG_ID ) + { + Error( "Config mismatch, expected %d items, read %d. Try running 'zmupdate.pl -f' to reload config.", ZM_MAX_CFG_ID+1, n_items ); + exit( -1 ); + } - items = new ConfigItem *[n_items]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { - items[i] = new ConfigItem( dbrow[0], dbrow[1], dbrow[2] ); - } - mysql_free_result( result ); + items = new ConfigItem *[n_items]; + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) + { + items[i] = new ConfigItem( dbrow[0], dbrow[1], dbrow[2] ); + } + mysql_free_result( result ); } void Config::Assign() @@ -308,27 +310,27 @@ ZM_CFG_ASSIGN_LIST const ConfigItem &Config::Item( int id ) { - if ( !n_items ) - { - Load(); - Assign(); - } + if ( !n_items ) + { + Load(); + Assign(); + } - if ( id < 0 || id > ZM_MAX_CFG_ID ) - { - Error( "Attempt to access invalid config, id = %d. Try running 'zmupdate.pl -f' to reload config.", id ); - exit( -1 ); - } + if ( id < 0 || id > ZM_MAX_CFG_ID ) + { + Error( "Attempt to access invalid config, id = %d. Try running 'zmupdate.pl -f' to reload config.", id ); + exit( -1 ); + } - ConfigItem *item = items[id]; - - if ( !item ) - { - Error( "Can't find config item %d", id ); - exit( -1 ); - } - - return( *item ); + ConfigItem *item = items[id]; + + if ( !item ) + { + Error( "Can't find config item %d", id ); + exit( -1 ); + } + + return( *item ); } Config config; diff --git a/src/zm_curl_camera.cpp b/src/zm_curl_camera.cpp index 3751088a8..b0f57115b 100644 --- a/src/zm_curl_camera.cpp +++ b/src/zm_curl_camera.cpp @@ -18,8 +18,11 @@ // #include "zm.h" + #include "zm_curl_camera.h" +#include "zm_packetqueue.h" + #if HAVE_LIBCURL #define CURL_MAXRETRY 5 @@ -30,28 +33,24 @@ const char* content_type_match = "Content-Type:"; size_t content_length_match_len; size_t content_type_match_len; -cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), +cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + Camera( p_id, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), bReset( false ), mode ( MODE_UNSET ) { - if ( capture ) - { + if ( capture ) { Initialise(); } } -cURLCamera::~cURLCamera() -{ - if ( capture ) - { +cURLCamera::~cURLCamera() { + if ( capture ) { Terminate(); } } -void cURLCamera::Initialise() -{ +void cURLCamera::Initialise() { content_length_match_len = strlen(content_length_match); content_type_match_len = strlen(content_type_match); @@ -88,8 +87,7 @@ void cURLCamera::Initialise() } } -void cURLCamera::Terminate() -{ +void cURLCamera::Terminate() { /* Signal the thread to terminate */ bTerminate = true; @@ -108,20 +106,17 @@ void cURLCamera::Terminate() } -int cURLCamera::PrimeCapture() -{ +int cURLCamera::PrimeCapture() { //Info( "Priming capture from %s", mPath.c_str() ); return 0; } -int cURLCamera::PreCapture() -{ - // Nothing to do here - return( 0 ); +int cURLCamera::PreCapture() { + // Nothing to do here + return( 0 ); } -int cURLCamera::Capture( Image &image ) -{ +int cURLCamera::Capture( Image &image ) { bool frameComplete = false; /* MODE_STREAM specific variables */ @@ -305,14 +300,19 @@ int cURLCamera::Capture( Image &image ) return 0; } -int cURLCamera::PostCapture() -{ +int cURLCamera::PostCapture() { + // Nothing to do here + return( 0 ); +} + +int cURLCamera::CaptureAndRecord( Image &image, bool recording, char* event_directory ) { + Error("Capture and Record not implemented for the cURL camera type"); // Nothing to do here return( 0 ); } -size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) -{ + +size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { lock(); /* Append the data we just received to our buffer */ @@ -333,8 +333,7 @@ size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void * -size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) -{ +size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) { std::string header; header.assign((const char*)buffer, size*nmemb); @@ -374,8 +373,7 @@ size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, voi return size*nmemb; } -void* cURLCamera::thread_func() -{ +void* cURLCamera::thread_func() { long tRet; double dSize; @@ -521,8 +519,7 @@ int cURLCamera::unlock() { return nRet; } -int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) -{ +int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) { /* Signal the curl thread to terminate */ if(bTerminate) return -10; @@ -531,18 +528,15 @@ int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, } /* These functions call the functions in the class for the correct object */ -size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) -{ +size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) { return ((cURLCamera*)userdata)->data_callback(buffer,size,nmemb,userdata); } -size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) -{ +size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) { return ((cURLCamera*)userdata)->header_callback(buffer,size,nmemb,userdata); } -int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) -{ +int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) { return ((cURLCamera*)userdata)->progress_callback(userdata,dltotal,dlnow,ultotal,ulnow); } @@ -550,6 +544,4 @@ void* thread_func_dispatcher(void* object) { return ((cURLCamera*)object)->thread_func(); } - - #endif // HAVE_LIBCURL diff --git a/src/zm_curl_camera.h b/src/zm_curl_camera.h index 8a06428c6..a8cdc1f16 100644 --- a/src/zm_curl_camera.h +++ b/src/zm_curl_camera.h @@ -39,8 +39,7 @@ // Class representing 'curl' cameras, i.e. those which are // accessed using the curl library // -class cURLCamera : public Camera -{ +class cURLCamera : public Camera { protected: typedef enum {MODE_UNSET, MODE_SINGLE, MODE_STREAM} mode_t; @@ -65,7 +64,7 @@ protected: pthread_cond_t request_complete_cond; public: - cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~cURLCamera(); const std::string &Path() const { return( mPath ); } @@ -79,6 +78,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); + int CaptureAndRecord( Image &image, bool recording, char* event_directory ); size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata); size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata); diff --git a/src/zm_db.cpp b/src/zm_db.cpp index 8eed90569..f4ec6d4b7 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -107,19 +107,38 @@ MYSQL_RES * zmDbFetch( const char * query ) { return result; } // end MYSQL_RES * zmDbFetch( const char * query ); -MYSQL_ROW zmDbFetchOne( const char *query ) { - MYSQL_RES *result = zmDbFetch( query ); - int n_rows = mysql_num_rows( result ); +zmDbRow *zmDbFetchOne( const char *query ) { + zmDbRow *row = new zmDbRow(); + if ( row->fetch( query ) ) { + return row; + } + delete row; + return NULL; +} + +MYSQL_RES *zmDbRow::fetch( const char *query ) { + result_set = zmDbFetch( query ); + if ( ! result_set ) return result_set; + + int n_rows = mysql_num_rows( result_set ); if ( n_rows != 1 ) { Error( "Bogus number of lines return from query, %d returned for query %s.", n_rows, query ); - return NULL; + mysql_free_result( result_set ); + result_set = NULL; + return result_set; } - MYSQL_ROW dbrow = mysql_fetch_row( result ); - mysql_free_result( result ); - if ( ! dbrow ) { + row = mysql_fetch_row( result_set ); + if ( ! row ) { + mysql_free_result( result_set ); + result_set = NULL; Error("Error getting row from query %s. Error is %s", query, mysql_error( &dbconn ) ); - return NULL; + } else { + Debug(3, "Succes"); } - return dbrow; + return result_set; +} +zmDbRow::~zmDbRow() { + if ( result_set ) + mysql_free_result( result_set ); } diff --git a/src/zm_db.h b/src/zm_db.h index 4cadcf587..f9b158be8 100644 --- a/src/zm_db.h +++ b/src/zm_db.h @@ -22,6 +22,21 @@ #include +class zmDbRow { + private: + MYSQL_RES *result_set; + MYSQL_ROW row; + public: + zmDbRow() { result_set = NULL; row = NULL; }; + MYSQL_RES *fetch( const char *query ); + zmDbRow( MYSQL_RES *, MYSQL_ROW *row ); + ~zmDbRow(); + + char *operator[](unsigned int index) const { + return row[index]; + } +}; + #ifdef __cplusplus extern "C" { #endif @@ -33,7 +48,7 @@ void zmDbConnect(); void zmDbClose(); MYSQL_RES * zmDbFetch( const char *query ); -MYSQL_ROW zmDbFetchOne( const char *query ); +zmDbRow *zmDbFetchOne( const char *query ); #ifdef __cplusplus } /* extern "C" */ diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 7902f5036..0e59c8a7e 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -54,15 +54,20 @@ bool Event::initialised = false; char Event::capture_file_format[PATH_MAX]; char Event::analyse_file_format[PATH_MAX]; char Event::general_file_format[PATH_MAX]; +char Event::video_file_format[PATH_MAX]; +constexpr const char * Event::frame_type_names[3]; int Event::pre_alarm_count = 0; + Event::PreAlarmData Event::pre_alarm_data[MAX_PRE_ALARM_FRAMES] = { { 0 } }; -Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap ) : +Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) : monitor( p_monitor ), start_time( p_start_time ), cause( p_cause ), - noteSetMap( p_noteSetMap ) + noteSetMap( p_noteSetMap ), + videoEvent( p_videoEvent ), + videowriter( NULL ) { if ( !initialised ) Initialise(); @@ -71,24 +76,39 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string createNotes( notes ); bool untimedEvent = false; - if ( !start_time.tv_sec ) - { + if ( !start_time.tv_sec ) { untimedEvent = true; gettimeofday( &start_time, 0 ); } - static char sql[ZM_SQL_MED_BUFSIZ]; + Storage * storage = monitor->getStorage(); + unsigned int state_id = 0; + zmDbRow dbrow; + if ( dbrow.fetch( "SELECT Id FROM States WHERE IsActive=1") ) { + state_id = atoi(dbrow[0]); + } + + static char sql[ZM_SQL_MED_BUFSIZ]; struct tm *stime = localtime( &start_time.tv_sec ); - snprintf( sql, sizeof(sql), "insert into Events ( MonitorId, Name, StartTime, Width, Height, Cause, Notes ) values ( %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s' )", monitor->Id(), start_time.tv_sec, monitor->Width(), monitor->Height(), cause.c_str(), notes.c_str() ); - if ( mysql_query( &dbconn, sql ) ) - { - Error( "Can't insert event: %s", mysql_error( &dbconn ) ); + snprintf( sql, sizeof(sql), "insert into Events ( MonitorId, StorageId, Name, StartTime, Width, Height, Cause, Notes, StateId, Orientation, Videoed ) values ( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d )", + monitor->Id(), + storage->Id(), + start_time.tv_sec, + monitor->Width(), + monitor->Height(), + cause.c_str(), + notes.c_str(), + state_id, + monitor->getOrientation(), + videoEvent + ); + if ( mysql_query( &dbconn, sql ) ) { + Error( "Can't insert event: %s. sql was (%s)", mysql_error( &dbconn ), sql ); exit( mysql_errno( &dbconn ) ); } id = mysql_insert_id( &dbconn ); - if ( untimedEvent ) - { + if ( untimedEvent ) { Warning( "Event %d has zero time, setting to current", id ); } end_time.tv_sec = 0; @@ -97,10 +117,12 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string tot_score = 0; max_score = 0; - if ( config.use_deep_storage ) - { + char id_file[PATH_MAX]; + struct stat statbuf; + + if ( config.use_deep_storage ) { char *path_ptr = path; - path_ptr += snprintf( path_ptr, sizeof(path), "%s/%d", config.dir_events, monitor->Id() ); + path_ptr += snprintf( path_ptr, sizeof(path), "%s/%d", storage->Path(), monitor->Id() ); int dt_parts[6]; dt_parts[0] = stime->tm_year-100; @@ -113,17 +135,15 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string char date_path[PATH_MAX] = ""; char time_path[PATH_MAX] = ""; char *time_path_ptr = time_path; - for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ ) - { + for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ ) { path_ptr += snprintf( path_ptr, sizeof(path)-(path_ptr-path), "/%02d", dt_parts[i] ); - struct stat statbuf; errno = 0; + // Do we really need to stat it? Perhaps we could do that on error, instead if ( stat( path, &statbuf ) ) { - if ( errno == ENOENT || errno == ENOTDIR ) - { - if ( mkdir( path, 0755 ) ) - { + if ( errno == ENOENT || errno == ENOTDIR ) { + if ( mkdir( path, 0755 ) ) { + // FIXME This should not be fatal. Should probably move to a different storage area. Fatal( "Can't mkdir %s: %s", path, strerror(errno)); } } else { @@ -135,66 +155,106 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string else if ( i >= 3 ) time_path_ptr += snprintf( time_path_ptr, sizeof(time_path)-(time_path_ptr-time_path), "%s%02d", i>3?"/":"", dt_parts[i] ); } - char id_file[PATH_MAX]; // Create event id symlink snprintf( id_file, sizeof(id_file), "%s/.%d", date_path, id ); if ( symlink( time_path, id_file ) < 0 ) Fatal( "Can't symlink %s -> %s: %s", id_file, path, strerror(errno)); // Create empty id tag file - snprintf( id_file, sizeof(id_file), "%s/.%d", path, id ); - if ( FILE *id_fp = fopen( id_file, "w" ) ) - fclose( id_fp ); - else - Fatal( "Can't fopen %s: %s", id_file, strerror(errno)); - } - else - { - snprintf( path, sizeof(path), "%s/%d/%d", config.dir_events, monitor->Id(), id ); - - struct stat statbuf; + } else { + snprintf( path, sizeof(path), "%s/%d/%d", storage->Path(), monitor->Id(), id ); + errno = 0; stat( path, &statbuf ); - if ( errno == ENOENT || errno == ENOTDIR ) - { - if ( mkdir( path, 0755 ) ) - { + if ( errno == ENOENT || errno == ENOTDIR ) { + if ( mkdir( path, 0755 ) ) { Error( "Can't mkdir %s: %s", path, strerror(errno)); } } - char id_file[PATH_MAX]; - // Create empty id tag file - snprintf( id_file, sizeof(id_file), "%s/.%d", path, id ); - if ( FILE *id_fp = fopen( id_file, "w" ) ) - fclose( id_fp ); - else - Fatal( "Can't fopen %s: %s", id_file, strerror(errno)); - } - last_db_frame = 0; -} + } // deep storage or not -Event::~Event() -{ - if ( frames > last_db_frame ) - { - struct DeltaTimeval delta_time; - DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 ); + // Create empty id tag file + snprintf( id_file, sizeof(id_file), "%s/.%d", path, id ); + if ( FILE *id_fp = fopen( id_file, "w" ) ) + fclose( id_fp ); + else + Fatal( "Can't fopen %s: %s", id_file, strerror(errno)); + + last_db_frame = 0; + + video_name[0] = 0; + + /* Save as video */ + + if ( monitor->GetOptVideoWriter() != 0 ) { + snprintf( video_name, sizeof(video_name), "%d-%s", id, "video.mp4" ); + snprintf( video_file, sizeof(video_file), video_file_format, path, video_name ); + + /* X264 MP4 video writer */ + if(monitor->GetOptVideoWriter() == 1) { +#if ZM_HAVE_VIDEOWRITER_X264MP4 + videowriter = new X264MP4Writer(video_file, monitor->Width(), monitor->Height(), monitor->Colours(), monitor->SubpixelOrder(), monitor->GetOptEncoderParams()); +#else + Error("ZoneMinder was not compiled with the X264 MP4 video writer, check dependencies (x264 and mp4v2)"); +#endif + } + + if(videowriter != NULL) { + + /* Open the video stream */ + int nRet = videowriter->Open(); + if(nRet != 0) { + Error("Failed opening video stream"); + delete videowriter; + videowriter = NULL; + } + + snprintf( timecodes_name, sizeof(timecodes_name), "%d-%s", id, "video.timecodes" ); + snprintf( timecodes_file, sizeof(timecodes_file), video_file_format, path, timecodes_name ); + /* Create timecodes file */ + timecodes_fd = fopen(timecodes_file, "wb"); + if(timecodes_fd == NULL) { + Error("Failed creating timecodes file"); + } + } + } else { + /* No video object */ + videowriter = NULL; + } + +} // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) + +Event::~Event() { + static char sql[ZM_SQL_MED_BUFSIZ]; + struct DeltaTimeval delta_time; + DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 ); + + if ( frames > last_db_frame ) { Debug( 1, "Adding closing frame %d to DB", frames ); - static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, TimeStamp, Delta ) values ( %d, %d, from_unixtime( %ld ), %s%ld.%02ld )", id, frames, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert frame: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } } - static char sql[ZM_SQL_MED_BUFSIZ]; + /* Close the video file */ + if ( videowriter != NULL ) { + int nRet; - struct DeltaTimeval delta_time; - DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 ); + nRet = videowriter->Close(); + if(nRet != 0) { + Error("Failed closing video stream"); + } + delete videowriter; + videowriter = NULL; - snprintf( sql, sizeof(sql), "update Events set Name='%s%d', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, id ); + /* Close the timecodes file */ + fclose(timecodes_fd); + timecodes_fd = NULL; + } + + snprintf( sql, sizeof(sql), "update Events set Name='%s%d', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo = '%s' where Id = %d", monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, video_name, id ); if ( mysql_query( &dbconn, sql ) ) { Error( "Can't update event: %s", mysql_error( &dbconn ) ); @@ -202,16 +262,13 @@ Event::~Event() } } -void Event::createNotes( std::string ¬es ) -{ +void Event::createNotes( std::string ¬es ) { notes.clear(); - for ( StringSetMap::const_iterator mapIter = noteSetMap.begin(); mapIter != noteSetMap.end(); mapIter++ ) - { + for ( StringSetMap::const_iterator mapIter = noteSetMap.begin(); mapIter != noteSetMap.end(); mapIter++ ) { notes += mapIter->first; notes += ": "; const StringSet &stringSet = mapIter->second; - for ( StringSet::const_iterator setIter = stringSet.begin(); setIter != stringSet.end(); setIter++ ) - { + for ( StringSet::const_iterator setIter = stringSet.begin(); setIter != stringSet.end(); setIter++ ) { if ( setIter != stringSet.begin() ) notes += ", "; notes += *setIter; @@ -221,25 +278,20 @@ void Event::createNotes( std::string ¬es ) int Event::sd = -1; -bool Event::OpenFrameSocket( int monitor_id ) -{ - if ( sd > 0 ) - { +bool Event::OpenFrameSocket( int monitor_id ) { + if ( sd > 0 ) { close( sd ); } - sd = socket( AF_UNIX, SOCK_STREAM, 0); - if ( sd < 0 ) - { + sd = socket( AF_UNIX, SOCK_STREAM, 0 ); + if ( sd < 0 ) { Error( "Can't create socket: %s", strerror(errno) ); return( false ); } int socket_buffer_size = config.frame_socket_size; - if ( socket_buffer_size > 0 ) - { - if ( setsockopt( sd, SOL_SOCKET, SO_SNDBUF, &socket_buffer_size, sizeof(socket_buffer_size) ) < 0 ) - { + if ( socket_buffer_size > 0 ) { + if ( setsockopt( sd, SOL_SOCKET, SO_SNDBUF, &socket_buffer_size, sizeof(socket_buffer_size) ) < 0 ) { Error( "Can't get socket buffer size to %d, error = %s", socket_buffer_size, strerror(errno) ); close( sd ); sd = -1; @@ -248,16 +300,14 @@ bool Event::OpenFrameSocket( int monitor_id ) } int flags; - if ( (flags = fcntl( sd, F_GETFL )) < 0 ) - { + if ( (flags = fcntl( sd, F_GETFL )) < 0 ) { Error( "Can't get socket flags, error = %s", strerror(errno) ); close( sd ); sd = -1; return( false ); } flags |= O_NONBLOCK; - if ( fcntl( sd, F_SETFL, flags ) < 0 ) - { + if ( fcntl( sd, F_SETFL, flags ) < 0 ) { Error( "Can't set socket flags, error = %s", strerror(errno) ); close( sd ); sd = -1; @@ -272,8 +322,7 @@ bool Event::OpenFrameSocket( int monitor_id ) strncpy( addr.sun_path, sock_path, sizeof(addr.sun_path) ); addr.sun_family = AF_UNIX; - if ( connect( sd, (struct sockaddr *)&addr, strlen(addr.sun_path)+sizeof(addr.sun_family)+1) < 0 ) - { + if ( connect( sd, (struct sockaddr *)&addr, strlen(addr.sun_path)+sizeof(addr.sun_family)+1) < 0 ) { Warning( "Can't connect to frame server: %s", strerror(errno) ); close( sd ); sd = -1; @@ -284,19 +333,15 @@ bool Event::OpenFrameSocket( int monitor_id ) return( true ); } -bool Event::ValidateFrameSocket( int monitor_id ) -{ - if ( sd < 0 ) - { +bool Event::ValidateFrameSocket( int monitor_id ) { + if ( sd < 0 ) { return( OpenFrameSocket( monitor_id ) ); } return( true ); } -bool Event::SendFrameImage( const Image *image, bool alarm_frame ) -{ - if ( !ValidateFrameSocket( monitor->Id() ) ) - { +bool Event::SendFrameImage( const Image *image, bool alarm_frame ) { + if ( !ValidateFrameSocket( monitor->Id() ) ) { return( false ); } @@ -322,23 +367,16 @@ bool Event::SendFrameImage( const Image *image, bool alarm_frame ) ssize_t writev_size = sizeof(frame_header)+jpg_buffer_size; ssize_t writev_result = writev( sd, iovecs, sizeof(iovecs)/sizeof(*iovecs)); - if ( writev_result != writev_size ) - { - if ( writev_result < 0 ) - { - if ( errno == EAGAIN ) - { + if ( writev_result != writev_size ) { + if ( writev_result < 0 ) { + if ( errno == EAGAIN ) { Warning( "Blocking write detected" ); - } - else - { + } else { Error( "Can't write frame: %s", strerror(errno) ); close( sd ); sd = -1; } - } - else - { + } else { Error( "Incomplete frame write: %zd of %zd bytes written", writev_result, writev_size ); close( sd ); sd = -1; @@ -350,8 +388,7 @@ bool Event::SendFrameImage( const Image *image, bool alarm_frame ) return( true ); } -bool Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame ) -{ +bool Event::WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame ) { Image* ImgToWrite; Image* ts_image = NULL; @@ -360,12 +397,10 @@ bool Event::WriteFrameImage( Image *image, struct timeval timestamp, const char ts_image = new Image(*image); monitor->TimestampImage( ts_image, ×tamp ); ImgToWrite=ts_image; - } - else + } else ImgToWrite=image; - if ( !config.opt_frame_server || !SendFrameImage(ImgToWrite, alarm_frame) ) - { + if ( !config.opt_frame_server || !SendFrameImage(ImgToWrite, alarm_frame) ) { int thisquality = ( alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality) ) ? config.jpeg_alarm_file_quality : 0 ; // quality to use, zero is default ImgToWrite->WriteJpeg( event_file, thisquality, (monitor->Exif() ? timestamp : (timeval){0,0}) ); // exif is only timestamp at present this switches on or off for write } @@ -373,56 +408,76 @@ bool Event::WriteFrameImage( Image *image, struct timeval timestamp, const char return( true ); } -void Event::updateNotes( const StringSetMap &newNoteSetMap ) -{ +bool Event::WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow ) { + const Image* frameimg = image; + Image ts_image; + + /* Checking for invalid parameters */ + if ( videow == NULL ) { + Error("NULL Video object"); + return false; + } + + /* If the image does not contain a timestamp, add the timestamp */ + if (!config.timestamp_on_capture) { + ts_image = *image; + monitor->TimestampImage( &ts_image, ×tamp ); + frameimg = &ts_image; + } + + /* Calculate delta time */ + struct DeltaTimeval delta_time3; + DELTA_TIMEVAL( delta_time3, timestamp, start_time, DT_PREC_3 ); + unsigned int timeMS = (delta_time3.sec * delta_time3.prec) + delta_time3.fsec; + + /* Encode and write the frame */ + if(videowriter->Encode(frameimg, timeMS) != 0) { + Error("Failed encoding video frame"); + } + + /* Add the frame to the timecodes file */ + fprintf(timecodes_fd, "%u\n", timeMS); + + return( true ); +} + +void Event::updateNotes( const StringSetMap &newNoteSetMap ) { bool update = false; //Info( "Checking notes, %d <> %d", noteSetMap.size(), newNoteSetMap.size() ); - if ( newNoteSetMap.size() > 0 ) - { - if ( noteSetMap.size() == 0 ) - { + if ( newNoteSetMap.size() > 0 ) { + if ( noteSetMap.size() == 0 ) { noteSetMap = newNoteSetMap; update = true; - } - else - { - for ( StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); newNoteSetMapIter != newNoteSetMap.end(); newNoteSetMapIter++ ) - { + } else { + for ( StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); newNoteSetMapIter != newNoteSetMap.end(); newNoteSetMapIter++ ) { const std::string &newNoteGroup = newNoteSetMapIter->first; const StringSet &newNoteSet = newNoteSetMapIter->second; //Info( "Got %d new strings", newNoteSet.size() ); - if ( newNoteSet.size() > 0 ) - { + if ( newNoteSet.size() > 0 ) { StringSetMap::iterator noteSetMapIter = noteSetMap.find( newNoteGroup ); - if ( noteSetMapIter == noteSetMap.end() ) - { + if ( noteSetMapIter == noteSetMap.end() ) { //Info( "Can't find note group %s, copying %d strings", newNoteGroup.c_str(), newNoteSet.size() ); noteSetMap.insert( StringSetMap::value_type( newNoteGroup, newNoteSet ) ); update = true; - } - else - { + } else { StringSet ¬eSet = noteSetMapIter->second; //Info( "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size() ); - for ( StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); newNoteSetIter != newNoteSet.end(); newNoteSetIter++ ) - { + for ( StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); newNoteSetIter != newNoteSet.end(); newNoteSetIter++ ) { const std::string &newNote = *newNoteSetIter; StringSet::iterator noteSetIter = noteSet.find( newNote ); - if ( noteSetIter == noteSet.end() ) - { + if ( noteSetIter == noteSet.end() ) { noteSet.insert( newNote ); update = true; } } } } - } - } - } + } // end for + } // end if ( noteSetMap.size() == 0 + } // end if newNoteSetupMap.size() > 0 - if ( update ) - { + if ( update ) { std::string notes; createNotes( notes ); @@ -434,19 +489,16 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) char notesStr[ZM_SQL_MED_BUFSIZ] = ""; unsigned long notesLen = 0; - if ( !stmt ) - { + if ( !stmt ) { const char *sql = "update Events set Notes = ? where Id = ?"; stmt = mysql_stmt_init( &dbconn ); - if ( mysql_stmt_prepare( stmt, sql, strlen(sql) ) ) - { + if ( mysql_stmt_prepare( stmt, sql, strlen(sql) ) ) { Fatal( "Unable to prepare sql '%s': %s", sql, mysql_stmt_error(stmt) ); } /* Get the parameter count from the statement */ - if ( mysql_stmt_param_count( stmt ) != 2 ) - { + if ( mysql_stmt_param_count( stmt ) != 2 ) { Fatal( "Unexpected parameter count %ld in sql '%s'", mysql_stmt_param_count( stmt ), sql ); } @@ -466,8 +518,7 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) bind[1].length= 0; /* Bind the buffers */ - if ( mysql_stmt_bind_param( stmt, bind ) ) - { + if ( mysql_stmt_bind_param( stmt, bind ) ) { Fatal( "Unable to bind sql '%s': %s", sql, mysql_stmt_error(stmt) ); } } @@ -475,8 +526,7 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) strncpy( notesStr, notes.c_str(), sizeof(notesStr) ); notesLen = notes.length(); - if ( mysql_stmt_execute( stmt ) ) - { + if ( mysql_stmt_execute( stmt ) ) { Fatal( "Unable to execute sql '%s': %s", sql, mysql_stmt_error(stmt) ); } #else @@ -485,30 +535,25 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) mysql_real_escape_string( &dbconn, escapedNotes, notes.c_str(), notes.length() ); snprintf( sql, sizeof(sql), "update Events set Notes = '%s' where Id = %d", escapedNotes, id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert event: %s", mysql_error( &dbconn ) ); } #endif } } -void Event::AddFrames( int n_frames, Image **images, struct timeval **timestamps ) -{ +void Event::AddFrames( int n_frames, Image **images, struct timeval **timestamps ) { for (int i = 0; i < n_frames; i += ZM_SQL_BATCH_SIZE) { AddFramesInternal(n_frames, i, images, timestamps); } } -void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ) -{ +void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ) { static char sql[ZM_SQL_LGE_BUFSIZ]; strncpy( sql, "insert into Frames ( EventId, FrameId, TimeStamp, Delta ) values ", sizeof(sql) ); int frameCount = 0; - for ( int i = start_frame; i < n_frames && i - start_frame < ZM_SQL_BATCH_SIZE; i++ ) - { - if ( !timestamps[i]->tv_sec ) - { + for ( int i = start_frame; i < n_frames && i - start_frame < ZM_SQL_BATCH_SIZE; i++ ) { + if ( !timestamps[i]->tv_sec ) { Debug( 1, "Not adding pre-capture frame %d, zero timestamp", i ); continue; } @@ -517,9 +562,21 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st static char event_file[PATH_MAX]; snprintf( event_file, sizeof(event_file), capture_file_format, path, frames ); - - Debug( 1, "Writing pre-capture frame %d", frames ); - WriteFrameImage( images[i], *(timestamps[i]), event_file ); + if ( monitor->GetOptSaveJPEGs() & 4) { + //If this is the first frame, we should add a thumbnail to the event directory + if(frames == 10){ + char snapshot_file[PATH_MAX]; + snprintf( snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path ); + WriteFrameImage( images[i], *(timestamps[i]), snapshot_file ); + } + } + if ( monitor->GetOptSaveJPEGs() & 1) { + Debug( 1, "Writing pre-capture frame %d", frames ); + WriteFrameImage( images[i], *(timestamps[i]), event_file ); + } + if ( videowriter != NULL ) { + WriteFrameVideo( images[i], *(timestamps[i]), videowriter ); + } struct DeltaTimeval delta_time; DELTA_TIMEVAL( delta_time, *(timestamps[i]), start_time, DT_PREC_2 ); @@ -530,27 +587,21 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st frameCount++; } - if ( frameCount ) - { + if ( frameCount ) { Debug( 1, "Adding %d/%d frames to DB", frameCount, n_frames ); *(sql+strlen(sql)-2) = '\0'; - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert frames: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } last_db_frame = frames; - } - else - { + } else { Debug( 1, "No valid pre-capture frames to add" ); } } -void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *alarm_image ) -{ - if ( !timestamp.tv_sec ) - { +void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image *alarm_image ) { + if ( !timestamp.tv_sec ) { Debug( 1, "Not adding new frame, zero timestamp" ); return; } @@ -560,8 +611,21 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * static char event_file[PATH_MAX]; snprintf( event_file, sizeof(event_file), capture_file_format, path, frames ); - Debug( 1, "Writing capture frame %d", frames ); - WriteFrameImage( image, timestamp, event_file ); + if ( monitor->GetOptSaveJPEGs() & 4) { + //If this is the first frame, we should add a thumbnail to the event directory + if(frames == 10){ + char snapshot_file[PATH_MAX]; + snprintf( snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path ); + WriteFrameImage( image, timestamp, snapshot_file ); + } + } + if( monitor->GetOptSaveJPEGs() & 1) { + Debug( 1, "Writing capture frame %d", frames ); + WriteFrameImage( image, timestamp, event_file ); + } + if ( videowriter != NULL ) { + WriteFrameVideo( image, timestamp, videowriter ); + } struct DeltaTimeval delta_time; DELTA_TIMEVAL( delta_time, timestamp, start_time, DT_PREC_2 ); @@ -571,25 +635,21 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * score = 0; bool db_frame = ( frame_type != BULK ) || ((frames%config.bulk_frame_interval)==0) || !frames; - if ( db_frame ) - { + if ( db_frame ) { - Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, frame_type_names[frame_type] ); + Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, Event::frame_type_names[frame_type] ); static char sql[ZM_SQL_MED_BUFSIZ]; snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score ) values ( %d, %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d )", id, frames, frame_type_names[frame_type], timestamp.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, score ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't insert frame: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } last_db_frame = frames; // We are writing a Bulk frame - if ( frame_type == BULK ) - { + if ( frame_type == BULK ) { snprintf( sql, sizeof(sql), "update Events set Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %d", delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't update event: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -599,65 +659,65 @@ void Event::AddFrame( Image *image, struct timeval timestamp, int score, Image * end_time = timestamp; // We are writing an Alarm frame - if ( frame_type == ALARM ) - { + if ( frame_type == ALARM ) { alarm_frames++; tot_score += score; if ( score > (int)max_score ) max_score = score; - if ( alarm_image ) - { + if ( alarm_image ) { snprintf( event_file, sizeof(event_file), analyse_file_format, path, frames ); Debug( 1, "Writing analysis frame %d", frames ); - WriteFrameImage( alarm_image, timestamp, event_file, true ); + if ( monitor->GetOptSaveJPEGs() & 2) { + WriteFrameImage( alarm_image, timestamp, event_file, true ); + } } } - + /* This makes viewing the diagnostic images impossible because it keeps deleting them - if ( config.record_diag_images ) - { - char diag_glob[PATH_MAX] = ""; + if ( config.record_diag_images ) + { + char diag_glob[PATH_MAX] = ""; - snprintf( diag_glob, sizeof(diag_glob), "%s/%d/diag-*.jpg", config.dir_events, monitor->Id() ); - glob_t pglob; - int glob_status = glob( diag_glob, 0, 0, &pglob ); - if ( glob_status != 0 ) - { - if ( glob_status < 0 ) - { - Error( "Can't glob '%s': %s", diag_glob, strerror(errno) ); - } - else - { - Debug( 1, "Can't glob '%s': %d", diag_glob, glob_status ); - } - } - else - { - char new_diag_path[PATH_MAX] = ""; - for ( int i = 0; i < pglob.gl_pathc; i++ ) - { - char *diag_path = pglob.gl_pathv[i]; + snprintf( diag_glob, sizeof(diag_glob), "%s/%d/diag-*.jpg", config.dir_events, monitor->Id() ); + glob_t pglob; + int glob_status = glob( diag_glob, 0, 0, &pglob ); + if ( glob_status != 0 ) + { + if ( glob_status < 0 ) + { + Error( "Can't glob '%s': %s", diag_glob, strerror(errno) ); + } + else + { + Debug( 1, "Can't glob '%s': %d", diag_glob, glob_status ); + } + } + else + { + char new_diag_path[PATH_MAX] = ""; + for ( int i = 0; i < pglob.gl_pathc; i++ ) + { + char *diag_path = pglob.gl_pathv[i]; - char *diag_file = strstr( diag_path, "diag-" ); + char *diag_file = strstr( diag_path, "diag-" ); - if ( diag_file ) - { - snprintf( new_diag_path, sizeof(new_diag_path), general_file_format, path, frames, diag_file ); + if ( diag_file ) + { + snprintf( new_diag_path, sizeof(new_diag_path), general_file_format, path, frames, diag_file ); - if ( rename( diag_path, new_diag_path ) < 0 ) - { - Error( "Can't rename '%s' to '%s': %s", diag_path, new_diag_path, strerror(errno) ); - } - } - } - } - globfree( &pglob ); - } - */ + if ( rename( diag_path, new_diag_path ) < 0 ) + { + Error( "Can't rename '%s' to '%s': %s", diag_path, new_diag_path, strerror(errno) ); + } + } + } + } + globfree( &pglob ); + } + */ } bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) @@ -673,15 +733,13 @@ bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_ROW dbrow = mysql_fetch_row( result ); - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -692,17 +750,13 @@ bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) loadEventData( init_event_id ); - if ( event_time ) - { + if ( event_time ) { curr_stream_time = event_time; curr_frame_id = 1; - if ( event_time >= event_data->start_time ) - { - for (unsigned int i = 0; i < event_data->frame_count; i++ ) - { + if ( event_time >= event_data->start_time ) { + for (unsigned int i = 0; i < event_data->frame_count; i++ ) { //Info( "eft %d > et %d", event_data->frames[i].timestamp, event_time ); - if ( event_data->frames[i].timestamp >= event_time ) - { + if ( event_data->frames[i].timestamp >= event_time ) { curr_frame_id = i+1; Debug( 3, "Set cst:%.2f", curr_stream_time ); Debug( 3, "Set cfid:%d", curr_frame_id ); @@ -715,51 +769,42 @@ bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) return( true ); } -bool EventStream::loadInitialEventData( int init_event_id, unsigned int init_frame_id ) -{ +bool EventStream::loadInitialEventData( int init_event_id, unsigned int init_frame_id ) { loadEventData( init_event_id ); - if ( init_frame_id ) - { + if ( init_frame_id ) { curr_stream_time = event_data->frames[init_frame_id-1].timestamp; curr_frame_id = init_frame_id; - } - else - { + } else { curr_stream_time = event_data->start_time; } return( true ); } -bool EventStream::loadEventData( int event_id ) -{ +bool EventStream::loadEventData( int event_id ) { static char sql[ZM_SQL_MED_BUFSIZ]; - snprintf( sql, sizeof(sql), "select M.Id, M.Name, E.Frames, unix_timestamp( StartTime ) as StartTimestamp, max(F.Delta)-min(F.Delta) as Duration from Events as E inner join Monitors as M on E.MonitorId = M.Id inner join Frames as F on E.Id = F.EventId where E.Id = %d group by E.Id", event_id ); + snprintf( sql, sizeof(sql), "select M.Id, M.Name, E.StorageId, E.Frames, unix_timestamp( StartTime ) as StartTimestamp, (SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=E.Id) as Duration,E.DefaultVideo from Events as E inner join Monitors as M on E.MonitorId = M.Id where E.Id = %d", event_id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } - if ( !mysql_num_rows( result ) ) - { + if ( !mysql_num_rows( result ) ) { Fatal( "Unable to load event %d, not found in DB", event_id ); } MYSQL_ROW dbrow = mysql_fetch_row( result ); - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -768,39 +813,40 @@ bool EventStream::loadEventData( int event_id ) event_data = new EventData; event_data->event_id = event_id; event_data->monitor_id = atoi( dbrow[0] ); - event_data->start_time = atoi(dbrow[3]); - if ( config.use_deep_storage ) - { + event_data->storage_id = dbrow[2] ? atoi( dbrow[2] ) : 0; + event_data->start_time = atoi(dbrow[4]); + + Storage * storage = new Storage( event_data->storage_id ); + const char *storage_path = storage->Path(); + + if ( config.use_deep_storage ) { struct tm *event_time = localtime( &event_data->start_time ); - if ( config.dir_events[0] == '/' ) - snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", config.dir_events, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec ); + if ( storage_path[0] == '/' ) + snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", storage_path, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec ); else - snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", staticConfig.PATH_WEB.c_str(), config.dir_events, event_data->monitor_id, event_time->tm_year-100, event_time->tm_mon+1, event_time->tm_mday, event_time->tm_hour, event_time->tm_min, event_time->tm_sec ); - } - else - { - if ( config.dir_events[0] == '/' ) - snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%ld", config.dir_events, event_data->monitor_id, event_data->event_id ); + snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%02d/%02d/%02d/%02d/%02d/%02d", staticConfig.PATH_WEB.c_str(), storage_path, 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 ( storage_path[0] == '/' ) + snprintf( event_data->path, sizeof(event_data->path), "%s/%ld/%ld", storage_path, event_data->monitor_id, event_data->event_id ); else - snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%ld", staticConfig.PATH_WEB.c_str(), config.dir_events, event_data->monitor_id, event_data->event_id ); + snprintf( event_data->path, sizeof(event_data->path), "%s/%s/%ld/%ld", staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_data->event_id ); } - event_data->frame_count = dbrow[2] == NULL ? 0 : atoi(dbrow[2]); - event_data->duration = atof(dbrow[4]); + event_data->frame_count = dbrow[3] == NULL ? 0 : atoi(dbrow[3]); + event_data->duration = atof(dbrow[5]); + strncpy( event_data->video_file, dbrow[6], sizeof( event_data->video_file )-1 ); updateFrameRate( (double)event_data->frame_count/event_data->duration ); mysql_free_result( result ); snprintf( sql, sizeof(sql), "select FrameId, unix_timestamp( `TimeStamp` ), Delta from Frames where EventId = %d order by FrameId asc", event_id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -811,17 +857,14 @@ bool EventStream::loadEventData( int event_id ) int id, last_id = 0; time_t timestamp, last_timestamp = event_data->start_time; double delta, last_delta = 0.0; - while ( ( dbrow = mysql_fetch_row( result ) ) ) - { + while ( ( dbrow = mysql_fetch_row( result ) ) ) { id = atoi(dbrow[0]); timestamp = atoi(dbrow[1]); delta = atof(dbrow[2]); int id_diff = id - last_id; double frame_delta = (delta-last_delta)/id_diff; - if ( id_diff > 1 ) - { - for ( int i = last_id+1; i < id; i++ ) - { + if ( id_diff > 1 ) { + for ( int i = last_id+1; i < id; i++ ) { event_data->frames[i-1].timestamp = (time_t)(last_timestamp + ((i-last_id)*frame_delta)); event_data->frames[i-1].offset = (time_t)(event_data->frames[i-1].timestamp-event_data->start_time); event_data->frames[i-1].delta = frame_delta; @@ -836,21 +879,19 @@ bool EventStream::loadEventData( int event_id ) last_delta = delta; last_timestamp = timestamp; } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } //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 ); + //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 ( forceEventChange || mode == MODE_ALL_GAPLESS ) { if ( replay_rate > 0 ) curr_stream_time = event_data->frames[0].timestamp; else @@ -861,246 +902,244 @@ bool EventStream::loadEventData( int event_id ) return( true ); } -void EventStream::processCommand( const CmdMsg *msg ) -{ +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" ); + // 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; - } + // 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; + // If we are in single event mode and at the last frame, replay the current event + if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) + curr_frame_id = 1; - replay_rate = ZM_RATE_BASE; - break; - } - case CMD_VARPLAY : - { - Debug( 1, "Got VARPLAY command" ); - if ( paused ) - { - // Clear paused flag - paused = false; - } - 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" ); + 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; + // 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; + } + 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; + } + 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 ); + 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 + } } - 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; - } - 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; - } - 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 ); - 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; @@ -1115,18 +1154,17 @@ void EventStream::processCommand( const CmdMsg *msg ) status_data.zoom = zoom; status_data.paused = paused; Debug( 2, "E:%d, P:%d, p:%d R:%d, Z:%d", - status_data.event, - status_data.paused, - status_data.progress, - status_data.rate, - status_data.zoom - ); + 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 ( sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) ) < 0 ) { //if ( errno != EAGAIN ) { Error( "Can't sendto on sd %d: %s", sd, strerror(errno) ); @@ -1140,49 +1178,39 @@ void EventStream::processCommand( const CmdMsg *msg ) updateFrameRate( (double)event_data->frame_count/event_data->duration ); } -void EventStream::checkEventLoaded() -{ +void EventStream::checkEventLoaded() { bool reload_event = false; static char sql[ZM_SQL_SML_BUFSIZ]; - if ( curr_frame_id <= 0 ) - { + if ( curr_frame_id <= 0 ) { snprintf( sql, sizeof(sql), "select Id from Events where MonitorId = %ld and Id < %ld order by Id desc limit 1", event_data->monitor_id, event_data->event_id ); reload_event = true; - } - else if ( (unsigned int)curr_frame_id > event_data->frame_count ) - { + } else if ( (unsigned int)curr_frame_id > event_data->frame_count ) { snprintf( sql, sizeof(sql), "select Id from Events where MonitorId = %ld and Id > %ld order by Id asc limit 1", event_data->monitor_id, event_data->event_id ); reload_event = true; } - if ( reload_event ) - { - if ( forceEventChange || mode != MODE_SINGLE ) - { + if ( reload_event ) { + if ( forceEventChange || mode != MODE_SINGLE ) { //Info( "SQL:%s", sql ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_ROW dbrow = mysql_fetch_row( result ); - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } - if ( dbrow ) - { + if ( dbrow ) { int event_id = atoi(dbrow[0]); Debug( 1, "Loading new event %d", event_id ); @@ -1194,9 +1222,7 @@ void EventStream::checkEventLoaded() else curr_frame_id = 1; Debug( 2, "New frame id = %d", curr_frame_id ); - } - else - { + } else { if ( curr_frame_id <= 0 ) curr_frame_id = 1; else @@ -1205,9 +1231,7 @@ void EventStream::checkEventLoaded() } mysql_free_result( result ); forceEventChange = false; - } - else - { + } else { if ( curr_frame_id <= 0 ) curr_frame_id = 1; else @@ -1217,32 +1241,46 @@ void EventStream::checkEventLoaded() } } -bool EventStream::sendFrame( int delta_us ) -{ +Image * EventStream::getImage( ) { + Event::Initialise(); + static char filepath[PATH_MAX]; + + Debug( 2, "EventStream::getImage path(%s) frame(%d)", event_data->path, curr_frame_id ); + snprintf( filepath, sizeof(filepath), Event::capture_file_format, event_data->path, curr_frame_id ); + Debug( 2, "EventStream::getImage path(%s) ", filepath, curr_frame_id ); + Image *image = new Image( filepath ); + return image; +} + +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; - + + 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 ); + } else { + Fatal("JPEGS not saved.zms is not capable of streaming jpegs from mp4 yet"); + return false; + } #if HAVE_LIBAVCODEC - if ( type == STREAM_MPEG ) - { + if ( type == STREAM_MPEG ) { Image image( filepath ); Image *send_image = prepareImage( &image ); - if ( !vid_stream ) - { + if ( !vid_stream ) { vid_stream = new VideoStream( "pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height() ); fprintf( stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType() ); vid_stream->OpenStream(); } /* double pts = */ vid_stream->EncodeFrame( send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_us*1000 ); - } - else + } else #endif // HAVE_LIBAVCODEC { static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; @@ -1257,31 +1295,26 @@ bool EventStream::sendFrame( int delta_us ) if ( type != STREAM_JPEG ) send_raw = false; - if ( send_raw ) - { + if ( send_raw ) { fdj = fopen( filepath, "rb" ); - if ( !fdj ) - { + if ( !fdj ) { Error( "Can't open %s: %s", filepath, strerror(errno) ); return( false ); } -#if HAVE_SENDFILE +#if HAVE_SENDFILE if( fstat(fileno(fdj),&filestat) < 0 ) { - Error( "Failed getting information about file %s: %s", filepath, strerror(errno) ); - return( false ); - } + Error( "Failed getting information about file %s: %s", filepath, strerror(errno) ); + return( false ); + } #else - img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj ); + img_buffer_size = fread( img_buffer, 1, sizeof(temp_img_buffer), fdj ); #endif - } - else - { + } else { Image image( filepath ); Image *send_image = prepareImage( &image ); - switch( type ) - { + switch( type ) { case STREAM_JPEG : send_image->EncodeJpeg( img_buffer, &img_buffer_size ); break; @@ -1305,8 +1338,7 @@ bool EventStream::sendFrame( int delta_us ) } } - switch( type ) - { + switch( type ) { case STREAM_JPEG : fprintf( stdout, "Content-Type: image/jpeg\r\n" ); break; @@ -1322,34 +1354,33 @@ bool EventStream::sendFrame( int delta_us ) } - if(send_raw) { + 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 ); + 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 ); } - } -#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 ); + fclose(fdj); /* Close the file handle */ + } else { + fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { + Error( "Unable to send stream frame: %s", strerror(errno) ); + return( false ); + } } - } - + fprintf( stdout, "\r\n\r\n" ); fflush( stdout ); } @@ -1357,8 +1388,7 @@ bool EventStream::sendFrame( int delta_us ) return( true ); } -void EventStream::runStream() -{ +void EventStream::runStream() { Event::Initialise(); openComms(); @@ -1370,15 +1400,13 @@ void EventStream::runStream() if ( type == STREAM_JPEG ) fprintf( stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n" ); - if ( !event_data ) - { + if ( !event_data ) { sendTextFrame( "No event data found" ); exit( 0 ); } unsigned int delta_us = 0; - while( !zm_terminate ) - { + while( !zm_terminate ) { gettimeofday( &now, NULL ); while(checkCommandQueue()); @@ -1394,27 +1422,21 @@ void EventStream::runStream() //Info( "cst:%.2f", curr_stream_time ); //Info( "cfid:%d", curr_frame_id ); //Info( "fdt:%d", frame_data->timestamp ); - if ( !paused ) - { + if ( !paused ) { bool in_event = true; double time_to_event = 0; - if ( replay_rate > 0 ) - { + if ( replay_rate > 0 ) { time_to_event = event_data->frames[0].timestamp - curr_stream_time; if ( time_to_event > 0 ) in_event = false; - } - else if ( replay_rate < 0 ) - { + } else if ( replay_rate < 0 ) { time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp; if ( time_to_event > 0 ) in_event = false; } - if ( !in_event ) - { + if ( !in_event ) { double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; - if ( actual_delta_time > 1 ) - { + if ( actual_delta_time > 1 ) { static char frame_text[64]; snprintf( frame_text, sizeof(frame_text), "Time to next event = %d seconds", (int)time_to_event ); if ( !sendTextFrame( frame_text ) ) @@ -1422,9 +1444,9 @@ void EventStream::runStream() } //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); + 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; } @@ -1432,11 +1454,9 @@ void EventStream::runStream() // Figure out if we should send this frame bool send_frame = false; - if ( !paused ) - { + if ( !paused ) { // If we are streaming and this frame is due to be sent - if ( ((curr_frame_id-1)%frame_mod) == 0 ) - { + if ( ((curr_frame_id-1)%frame_mod) == 0 ) { delta_us = (unsigned int)(frame_data->delta * 1000000); // if effective > base we should speed up frame delivery delta_us = (unsigned int)((delta_us * base_fps)/effective_fps); @@ -1444,19 +1464,14 @@ void EventStream::runStream() delta_us = max(delta_us, 1000000 / maxfps); send_frame = true; } - } - else if ( step != 0 ) - { + } else if ( step != 0 ) { // We are paused and are just stepping forward or backward one frame step = 0; send_frame = true; - } - else - { + } else { // We are paused, and doing nothing double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; - if ( actual_delta_time > MAX_STREAM_DELAY ) - { + if ( actual_delta_time > MAX_STREAM_DELAY ) { // Send keepalive Debug( 2, "Sending keepalive frame" ); send_frame = true; @@ -1469,17 +1484,13 @@ void EventStream::runStream() curr_stream_time = frame_data->timestamp; - if ( !paused ) - { + if ( !paused ) { curr_frame_id += replay_rate>0?1:-1; - if ( send_frame && type != STREAM_MPEG ) - { + if ( send_frame && type != STREAM_MPEG ) { Debug( 3, "dUs: %d", delta_us ); usleep( delta_us ); } - } - else - { + } else { usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) ); } } diff --git a/src/zm_event.h b/src/zm_event.h index 37cb69c64..4ded4ed36 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -37,6 +37,7 @@ #include "zm.h" #include "zm_image.h" #include "zm_stream.h" +#include "zm_video.h" class Zone; class Monitor; @@ -46,225 +47,223 @@ class Monitor; // // Class describing events, i.e. captured periods of activity. // -class Event -{ -friend class EventStream; +class Event { + friend class EventStream; -protected: - static bool initialised; - static char capture_file_format[PATH_MAX]; - static char analyse_file_format[PATH_MAX]; - static char general_file_format[PATH_MAX]; + protected: + static bool initialised; + static char capture_file_format[PATH_MAX]; + static char analyse_file_format[PATH_MAX]; + static char general_file_format[PATH_MAX]; + static char video_file_format[PATH_MAX]; -protected: - static int sd; + protected: + static int sd; -public: - typedef std::set StringSet; - typedef std::map StringSetMap; + public: + typedef std::set StringSet; + typedef std::map StringSetMap; -protected: - typedef enum { NORMAL=0, BULK, ALARM } FrameType; - static const constexpr char * const frame_type_names[] = { "Normal", "Bulk", "Alarm" }; + protected: + typedef enum { NORMAL=0, BULK, ALARM } FrameType; + static constexpr const char * frame_type_names[3] = { "Normal", "Bulk", "Alarm" }; - struct PreAlarmData - { - Image *image; - struct timeval timestamp; - unsigned int score; - Image *alarm_frame; - }; + struct PreAlarmData { + Image *image; + struct timeval timestamp; + unsigned int score; + Image *alarm_frame; + }; - static int pre_alarm_count; - static PreAlarmData pre_alarm_data[MAX_PRE_ALARM_FRAMES]; + static int pre_alarm_count; + static PreAlarmData pre_alarm_data[MAX_PRE_ALARM_FRAMES]; -protected: - unsigned int id; - Monitor *monitor; - struct timeval start_time; - struct timeval end_time; - std::string cause; - StringSetMap noteSetMap; - int frames; - int alarm_frames; - unsigned int tot_score; - unsigned int max_score; - char path[PATH_MAX]; + protected: + unsigned int id; + Monitor *monitor; + struct timeval start_time; + struct timeval end_time; + std::string cause; + StringSetMap noteSetMap; + bool videoEvent; + int frames; + int alarm_frames; + unsigned int tot_score; + unsigned int max_score; + char path[PATH_MAX]; + VideoWriter* videowriter; + FILE* timecodes_fd; + char video_name[PATH_MAX]; + char video_file[PATH_MAX]; + char timecodes_name[PATH_MAX]; + char timecodes_file[PATH_MAX]; -protected: - int last_db_frame; + protected: + int last_db_frame; -protected: - static void Initialise() - { - if ( initialised ) - return; + protected: + static void Initialise() { + if ( initialised ) + return; - snprintf( capture_file_format, sizeof(capture_file_format), "%%s/%%0%dd-capture.jpg", config.event_image_digits ); - snprintf( analyse_file_format, sizeof(analyse_file_format), "%%s/%%0%dd-analyse.jpg", config.event_image_digits ); - snprintf( general_file_format, sizeof(general_file_format), "%%s/%%0%dd-%%s", config.event_image_digits ); + snprintf( capture_file_format, sizeof(capture_file_format), "%%s/%%0%dd-capture.jpg", config.event_image_digits ); + snprintf( analyse_file_format, sizeof(analyse_file_format), "%%s/%%0%dd-analyse.jpg", config.event_image_digits ); + snprintf( general_file_format, sizeof(general_file_format), "%%s/%%0%dd-%%s", config.event_image_digits ); + snprintf( video_file_format, sizeof(video_file_format), "%%s/%%s"); - initialised = true; - } + initialised = true; + } - void createNotes( std::string ¬es ); + void createNotes( std::string ¬es ); -public: - static bool OpenFrameSocket( int ); - static bool ValidateFrameSocket( int ); + public: + static bool OpenFrameSocket( int ); + static bool ValidateFrameSocket( int ); -public: - Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap ); - ~Event(); + public: + Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent=false ); + ~Event(); - int Id() const { return( id ); } - const std::string &Cause() { return( cause ); } - int Frames() const { return( frames ); } - int AlarmFrames() const { return( alarm_frames ); } + int Id() const { return( id ); } + const std::string &Cause() { return( cause ); } + int Frames() const { return( frames ); } + int AlarmFrames() const { return( alarm_frames ); } - const struct timeval &StartTime() const { return( start_time ); } - const struct timeval &EndTime() const { return( end_time ); } - struct timeval &EndTime() { return( end_time ); } + const struct timeval &StartTime() const { return( start_time ); } + const struct timeval &EndTime() const { return( end_time ); } + struct timeval &EndTime() { return( end_time ); } - bool SendFrameImage( const Image *image, bool alarm_frame=false ); - bool WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame=false ); + bool SendFrameImage( const Image *image, bool alarm_frame=false ); + bool WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame=false ); + bool WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow ); - void updateNotes( const StringSetMap &stringSetMap ); + void updateNotes( const StringSetMap &stringSetMap ); - void AddFrames( int n_frames, Image **images, struct timeval **timestamps ); - void AddFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL ); + void AddFrames( int n_frames, Image **images, struct timeval **timestamps ); + void AddFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL ); -private: - void AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ); + private: + void AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ); -public: - static const char *getSubPath( struct tm *time ) - { - static char subpath[PATH_MAX] = ""; - snprintf( subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec ); - return( subpath ); - } - static const char *getSubPath( time_t *time ) - { - return( Event::getSubPath( localtime( time ) ) ); - } + public: + static const char *getSubPath( struct tm *time ) { + static char subpath[PATH_MAX] = ""; + snprintf( subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec ); + return( subpath ); + } + static const char *getSubPath( time_t *time ) { + return( Event::getSubPath( localtime( time ) ) ); + } -public: - static int PreAlarmCount() - { - return( pre_alarm_count ); - } - static void EmptyPreAlarmFrames() - { - if ( pre_alarm_count > 0 ) - { - for ( int i = 0; i < MAX_PRE_ALARM_FRAMES; i++ ) - { - delete pre_alarm_data[i].image; - delete pre_alarm_data[i].alarm_frame; + char* getEventFile(void) { + return video_file; + } + + public: + static int PreAlarmCount() { + return( pre_alarm_count ); + } + static void EmptyPreAlarmFrames() { + if ( pre_alarm_count > 0 ) { + for ( int i = 0; i < MAX_PRE_ALARM_FRAMES; i++ ) { + delete pre_alarm_data[i].image; + delete pre_alarm_data[i].alarm_frame; + } + memset( pre_alarm_data, 0, sizeof(pre_alarm_data) ); } - memset( pre_alarm_data, 0, sizeof(pre_alarm_data) ); + pre_alarm_count = 0; } - pre_alarm_count = 0; - } - static void AddPreAlarmFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL ) - { - pre_alarm_data[pre_alarm_count].image = new Image( *image ); - pre_alarm_data[pre_alarm_count].timestamp = timestamp; - pre_alarm_data[pre_alarm_count].score = score; - if ( alarm_frame ) - { - pre_alarm_data[pre_alarm_count].alarm_frame = new Image( *alarm_frame ); + static void AddPreAlarmFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=NULL ) { + pre_alarm_data[pre_alarm_count].image = new Image( *image ); + pre_alarm_data[pre_alarm_count].timestamp = timestamp; + pre_alarm_data[pre_alarm_count].score = score; + if ( alarm_frame ) { + pre_alarm_data[pre_alarm_count].alarm_frame = new Image( *alarm_frame ); + } + pre_alarm_count++; } - pre_alarm_count++; - } - void SavePreAlarmFrames() - { - for ( int i = 0; i < pre_alarm_count; i++ ) - { - AddFrame( pre_alarm_data[i].image, pre_alarm_data[i].timestamp, pre_alarm_data[i].score, pre_alarm_data[i].alarm_frame ); + void SavePreAlarmFrames() { + for ( int i = 0; i < pre_alarm_count; i++ ) { + AddFrame( pre_alarm_data[i].image, pre_alarm_data[i].timestamp, pre_alarm_data[i].score, pre_alarm_data[i].alarm_frame ); + } + EmptyPreAlarmFrames(); } - EmptyPreAlarmFrames(); - } }; -class EventStream : public StreamBase -{ -public: - typedef enum { MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode; +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; - }; + 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; - }; + struct EventData { + unsigned long event_id; + unsigned long monitor_id; + unsigned long storage_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 + protected: + static const int STREAM_PAUSE_WAIT = 250000; // Microseconds - static const StreamMode DEFAULT_MODE = MODE_SINGLE; + static const StreamMode DEFAULT_MODE = MODE_SINGLE; -protected: - StreamMode mode; - bool forceEventChange; + protected: + StreamMode mode; + bool forceEventChange; -protected: - int curr_frame_id; - double curr_stream_time; + protected: + int curr_frame_id; + double curr_stream_time; - EventData *event_data; + 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 ); + 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 ); + void checkEventLoaded(); + void processCommand( const CmdMsg *msg ); + bool sendFrame( int delta_us ); -public: - EventStream() - { - mode = DEFAULT_MODE; + public: + EventStream() { + mode = DEFAULT_MODE; - forceEventChange = false; + forceEventChange = false; - curr_frame_id = 0; - curr_stream_time = 0.0; + 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(); + 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(); + Image *getImage(); }; #endif // ZM_EVENT_H diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index d088477a8..5ba37f369 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -23,6 +23,16 @@ #if HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE +void FFMPEGInit() { + static bool bInit = false; + + if(!bInit) { + av_register_all(); + av_log_set_level(AV_LOG_DEBUG); + bInit = true; + } +} + #if HAVE_LIBAVUTIL enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder) { enum _AVPIXELFORMAT pf; @@ -31,40 +41,40 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp switch(p_colours) { case ZM_COLOUR_RGB24: - { - if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { - /* BGR subpixel order */ - pf = AV_PIX_FMT_BGR24; - } else { - /* Assume RGB subpixel order */ - pf = AV_PIX_FMT_RGB24; - } - break; - } + { + if(p_subpixelorder == ZM_SUBPIX_ORDER_BGR) { + /* BGR subpixel order */ + pf = AV_PIX_FMT_BGR24; + } else { + /* Assume RGB subpixel order */ + pf = AV_PIX_FMT_RGB24; + } + break; + } case ZM_COLOUR_RGB32: - { - if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - /* ARGB subpixel order */ - pf = AV_PIX_FMT_ARGB; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - /* ABGR subpixel order */ - pf = AV_PIX_FMT_ABGR; - } else if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - /* BGRA subpixel order */ - pf = AV_PIX_FMT_BGRA; - } else { - /* Assume RGBA subpixel order */ - pf = AV_PIX_FMT_RGBA; - } - break; - } + { + if(p_subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + /* ARGB subpixel order */ + pf = AV_PIX_FMT_ARGB; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + /* ABGR subpixel order */ + pf = AV_PIX_FMT_ABGR; + } else if(p_subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + /* BGRA subpixel order */ + pf = AV_PIX_FMT_BGRA; + } else { + /* Assume RGBA subpixel order */ + pf = AV_PIX_FMT_RGBA; + } + break; + } case ZM_COLOUR_GRAY8: - pf = AV_PIX_FMT_GRAY8; - break; + pf = AV_PIX_FMT_GRAY8; + break; default: - Panic("Unexpected colours: %d",p_colours); - pf = AV_PIX_FMT_GRAY8; /* Just to shush gcc variable may be unused warning */ - break; + Panic("Unexpected colours: %d",p_colours); + pf = AV_PIX_FMT_GRAY8; /* Just to shush gcc variable may be unused warning */ + break; } return pf; @@ -166,7 +176,7 @@ SWScale::~SWScale() { sws_freeContext(swscale_ctx); swscale_ctx = NULL; } - + Debug(4,"SWScale object destroyed"); } @@ -189,11 +199,11 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint Error("NULL Input or output buffer"); return -1; } - if(in_pf == 0 || out_pf == 0) { - Error("Invalid input or output pixel formats"); - return -2; - } - if(!width || !height) { + // if(in_pf == 0 || out_pf == 0) { + // Error("Invalid input or output pixel formats"); + // return -2; + // } + if (!width || !height) { Error("Invalid width or height"); return -3; } @@ -223,24 +233,37 @@ int SWScale::Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint #else size_t outsize = avpicture_get_size(out_pf, width, height); #endif + if(outsize < out_buffer_size) { Error("The output buffer is undersized for the output format. Required: %d Available: %d", outsize, out_buffer_size); return -5; } /* Get the context */ - swscale_ctx = sws_getCachedContext( NULL, width, height, in_pf, width, height, out_pf, 0, NULL, NULL, NULL ); + swscale_ctx = sws_getCachedContext( swscale_ctx, width, height, in_pf, width, height, out_pf, SWS_FAST_BILINEAR, NULL, NULL, NULL ); if(swscale_ctx == NULL) { Error("Failed getting swscale context"); return -6; } /* Fill in the buffers */ - if(!avpicture_fill( (AVPicture*)input_avframe, (uint8_t*)in_buffer, in_pf, width, height ) ) { +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + if (av_image_fill_arrays(input_avframe->data, input_avframe->linesize, + (uint8_t*) in_buffer, in_pf, width, height, 1) <= 0) { +#else + if (avpicture_fill((AVPicture*) input_avframe, (uint8_t*) in_buffer, + in_pf, width, height) <= 0) { +#endif Error("Failed filling input frame with input buffer"); return -7; } - if(!avpicture_fill( (AVPicture*)output_avframe, out_buffer, out_pf, width, height ) ) { +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + if (av_image_fill_arrays(output_avframe->data, output_avframe->linesize, + out_buffer, out_pf, width, height, 1) <= 0) { +#else + if (avpicture_fill((AVPicture*) output_avframe, out_buffer, out_pf, width, + height) <= 0) { +#endif Error("Failed filling output frame with output buffer"); return -8; } @@ -291,3 +314,206 @@ int SWScale::ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_si #endif // HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE + +#if HAVE_LIBAVUTIL +int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb){ + int64_t a, b, this_thing; + + av_assert0(in_ts != AV_NOPTS_VALUE); + av_assert0(duration >= 0); + + if (*last == AV_NOPTS_VALUE || !duration || in_tb.num*(int64_t)out_tb.den <= out_tb.num*(int64_t)in_tb.den) { +simple_round: + *last = av_rescale_q(in_ts, in_tb, fs_tb) + duration; + return av_rescale_q(in_ts, in_tb, out_tb); + } + + a = av_rescale_q_rnd(2*in_ts-1, in_tb, fs_tb, AV_ROUND_DOWN) >>1; + b = (av_rescale_q_rnd(2*in_ts+1, in_tb, fs_tb, AV_ROUND_UP )+1)>>1; + if (*last < 2*a - b || *last > 2*b - a) + goto simple_round; + + this_thing = av_clip64(*last, a, b); + *last = this_thing + duration; + + return av_rescale_q(this_thing, fs_tb, out_tb); +} +#endif + +int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename) { + AVFormatContext *s = avformat_alloc_context(); + int ret = 0; + + *avctx = NULL; + if (!s) { + av_log(s, AV_LOG_ERROR, "Out of memory\n"); + ret = AVERROR(ENOMEM); + return ret; + } + + if (!oformat) { + if (format) { + oformat = av_guess_format(format, NULL, NULL); + if (!oformat) { + av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format); + ret = AVERROR(EINVAL); + } + } else { + oformat = av_guess_format(NULL, filename, NULL); + if (!oformat) { + ret = AVERROR(EINVAL); + av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", filename); + } + } + } + + if (!oformat) { + if (format) { + oformat = av_guess_format(format, NULL, NULL); + if (!oformat) { + av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format); + ret = AVERROR(EINVAL); + } + } else { + oformat = av_guess_format(NULL, filename, NULL); + if (!oformat) { + ret = AVERROR(EINVAL); + av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n", filename); + } + } + } + + if (ret) { + avformat_free_context(s); + return ret; + } else { + s->oformat = oformat; + if (s->oformat->priv_data_size > 0) { + s->priv_data = av_mallocz(s->oformat->priv_data_size); + if (s->priv_data) { + if (s->oformat->priv_class) { + *(const AVClass**)s->priv_data= s->oformat->priv_class; + av_opt_set_defaults(s->priv_data); + } + } else { + av_log(s, AV_LOG_ERROR, "Out of memory\n"); + ret = AVERROR(ENOMEM); + return ret; + } + s->priv_data = NULL; + } + + if (filename) strncpy(s->filename, filename, sizeof(s->filename)); + *avctx = s; + return 0; + } +} + +static void zm_log_fps(double d, const char *postfix) { + uint64_t v = lrintf(d * 100); + if (!v) { + Debug(1, "%1.4f %s", d, postfix); + } else if (v % 100) { + Debug(1, "%3.2f %s", d, postfix); + } else if (v % (100 * 1000)) { + Debug(1, "%1.0f %s", d, postfix); + } else + Debug(1, "%1.0fk %s", d / 1000, postfix); +} + +/* "user interface" functions */ +void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) { + char buf[256]; + Debug(1, "Dumping stream index i(%d) index(%d)", i, index ); + int flags = (is_output ? ic->oformat->flags : ic->iformat->flags); + AVStream *st = ic->streams[i]; + AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0); + + avcodec_string(buf, sizeof(buf), st->codec, is_output); + Debug(1, " Stream #%d:%d", index, i); + + /* the pid is an important information, so we display it */ + /* XXX: add a generic system */ + if (flags & AVFMT_SHOW_IDS) + Debug(1, "[0x%x]", st->id); + if (lang) + Debug(1, "(%s)", lang->value); + Debug(1, ", %d, %d/%d", st->codec_info_nb_frames, st->time_base.num, st->time_base.den); + Debug(1, ": %s", buf); + + if (st->sample_aspect_ratio.num && // default + av_cmp_q(st->sample_aspect_ratio, st->codec->sample_aspect_ratio)) { + AVRational display_aspect_ratio; + av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, + st->codec->width * (int64_t)st->sample_aspect_ratio.num, + st->codec->height * (int64_t)st->sample_aspect_ratio.den, + 1024 * 1024); + Debug(1, ", SAR %d:%d DAR %d:%d", + st->sample_aspect_ratio.num, st->sample_aspect_ratio.den, + display_aspect_ratio.num, display_aspect_ratio.den); + } + + if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + int fps = st->avg_frame_rate.den && st->avg_frame_rate.num; + int tbn = st->time_base.den && st->time_base.num; + int tbc = st->codec->time_base.den && st->codec->time_base.num; + + if (fps || tbn || tbc) + Debug(3, "\n" ); + + if (fps) + zm_log_fps(av_q2d(st->avg_frame_rate), tbn || tbc ? "fps, " : "fps"); + if (tbn) + zm_log_fps(1 / av_q2d(st->time_base), tbc ? "stream tb numerator , " : "stream tb numerator"); + if (tbc) + zm_log_fps(1 / av_q2d(st->codec->time_base), "codec time base:"); + } + + if (st->disposition & AV_DISPOSITION_DEFAULT) + Debug(1, " (default)"); + if (st->disposition & AV_DISPOSITION_DUB) + Debug(1, " (dub)"); + if (st->disposition & AV_DISPOSITION_ORIGINAL) + Debug(1, " (original)"); + if (st->disposition & AV_DISPOSITION_COMMENT) + Debug(1, " (comment)"); + if (st->disposition & AV_DISPOSITION_LYRICS) + Debug(1, " (lyrics)"); + if (st->disposition & AV_DISPOSITION_KARAOKE) + Debug(1, " (karaoke)"); + if (st->disposition & AV_DISPOSITION_FORCED) + Debug(1, " (forced)"); + if (st->disposition & AV_DISPOSITION_HEARING_IMPAIRED) + Debug(1, " (hearing impaired)"); + if (st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED) + Debug(1, " (visual impaired)"); + if (st->disposition & AV_DISPOSITION_CLEAN_EFFECTS) + Debug(1, " (clean effects)"); + Debug(1, "\n"); + + //dump_metadata(NULL, st->metadata, " "); + + //dump_sidedata(NULL, st, " "); +} + +int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt) { + const enum AVSampleFormat *p = codec->sample_fmts; + + while (*p != AV_SAMPLE_FMT_NONE) { + if (*p == sample_fmt) + return 1; + else Debug(2, "Not %s", av_get_sample_fmt_name( *p ) ); + p++; + } + return 0; +} + +#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) +#else +unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ) { + dst->data = reinterpret_cast(new uint64_t[(src->size + FF_INPUT_BUFFER_PADDING_SIZE)/sizeof(uint64_t) + 1]); + memcpy(dst->data, src->data, src->size ); + return 0; +} +#endif + diff --git a/src/zm_ffmpeg.h b/src/zm_ffmpeg.h index da3c33a76..1e89de07b 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -29,6 +29,7 @@ extern "C" { // AVUTIL #if HAVE_LIBAVUTIL_AVUTIL_H +#include "libavutil/avassert.h" #include #include #include @@ -40,8 +41,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBAVUTIL_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #if LIBAVUTIL_VERSION_CHECK(50, 29, 0, 29, 0) #include @@ -58,55 +59,55 @@ extern "C" { #include #include #endif /* HAVE_LIBAVUTIL_AVUTIL_H */ - + #if defined(HAVE_LIBAVUTIL_AVUTIL_H) #if LIBAVUTIL_VERSION_CHECK(51, 42, 0, 74, 100) - #define _AVPIXELFORMAT AVPixelFormat + #define _AVPIXELFORMAT AVPixelFormat #else - #define _AVPIXELFORMAT PixelFormat - #define AV_PIX_FMT_NONE PIX_FMT_NONE - #define AV_PIX_FMT_RGB444 PIX_FMT_RGB444 - #define AV_PIX_FMT_RGB555 PIX_FMT_RGB555 - #define AV_PIX_FMT_RGB565 PIX_FMT_RGB565 - #define AV_PIX_FMT_BGR24 PIX_FMT_BGR24 - #define AV_PIX_FMT_RGB24 PIX_FMT_RGB24 - #define AV_PIX_FMT_BGRA PIX_FMT_BGRA - #define AV_PIX_FMT_ARGB PIX_FMT_ARGB - #define AV_PIX_FMT_ABGR PIX_FMT_ABGR - #define AV_PIX_FMT_RGBA PIX_FMT_RGBA - #define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8 - #define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422 - #define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P - #define AV_PIX_FMT_YUV411P PIX_FMT_YUV411P - #define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P - #define AV_PIX_FMT_YUV410P PIX_FMT_YUV410P - #define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P - #define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P - #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 - #define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P - #define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P - #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 - #define AV_PIX_FMT_UYYVYY411 PIX_FMT_UYYVYY411 - #define AV_PIX_FMT_BGR565 PIX_FMT_BGR565 - #define AV_PIX_FMT_BGR555 PIX_FMT_BGR555 - #define AV_PIX_FMT_BGR8 PIX_FMT_BGR8 - #define AV_PIX_FMT_BGR4 PIX_FMT_BGR4 - #define AV_PIX_FMT_BGR4_BYTE PIX_FMT_BGR4_BYTE - #define AV_PIX_FMT_RGB8 PIX_FMT_RGB8 - #define AV_PIX_FMT_RGB4 PIX_FMT_RGB4 - #define AV_PIX_FMT_RGB4_BYTE PIX_FMT_RGB4_BYTE - #define AV_PIX_FMT_NV12 PIX_FMT_NV12 - #define AV_PIX_FMT_NV21 PIX_FMT_NV21 - #define AV_PIX_FMT_RGB32_1 PIX_FMT_RGB32_1 - #define AV_PIX_FMT_BGR32_1 PIX_FMT_BGR32_1 - #define AV_PIX_FMT_GRAY16BE PIX_FMT_GRAY16BE - #define AV_PIX_FMT_GRAY16LE PIX_FMT_GRAY16LE - #define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P - #define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P - #define AV_PIX_FMT_YUVA420P PIX_FMT_YUVA420P - //#define AV_PIX_FMT_VDPAU_H264 PIX_FMT_VDPAU_H264 - //#define AV_PIX_FMT_VDPAU_MPEG1 PIX_FMT_VDPAU_MPEG1 - //#define AV_PIX_FMT_VDPAU_MPEG2 PIX_FMT_VDPAU_MPEG2 + #define _AVPIXELFORMAT PixelFormat + #define AV_PIX_FMT_NONE PIX_FMT_NONE + #define AV_PIX_FMT_RGB444 PIX_FMT_RGB444 + #define AV_PIX_FMT_RGB555 PIX_FMT_RGB555 + #define AV_PIX_FMT_RGB565 PIX_FMT_RGB565 + #define AV_PIX_FMT_BGR24 PIX_FMT_BGR24 + #define AV_PIX_FMT_RGB24 PIX_FMT_RGB24 + #define AV_PIX_FMT_BGRA PIX_FMT_BGRA + #define AV_PIX_FMT_ARGB PIX_FMT_ARGB + #define AV_PIX_FMT_ABGR PIX_FMT_ABGR + #define AV_PIX_FMT_RGBA PIX_FMT_RGBA + #define AV_PIX_FMT_GRAY8 PIX_FMT_GRAY8 + #define AV_PIX_FMT_YUYV422 PIX_FMT_YUYV422 + #define AV_PIX_FMT_YUV422P PIX_FMT_YUV422P + #define AV_PIX_FMT_YUV411P PIX_FMT_YUV411P + #define AV_PIX_FMT_YUV444P PIX_FMT_YUV444P + #define AV_PIX_FMT_YUV410P PIX_FMT_YUV410P + #define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P + #define AV_PIX_FMT_YUVJ444P PIX_FMT_YUVJ444P + #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 + #define AV_PIX_FMT_YUVJ420P PIX_FMT_YUVJ420P + #define AV_PIX_FMT_YUVJ422P PIX_FMT_YUVJ422P + #define AV_PIX_FMT_UYVY422 PIX_FMT_UYVY422 + #define AV_PIX_FMT_UYYVYY411 PIX_FMT_UYYVYY411 + #define AV_PIX_FMT_BGR565 PIX_FMT_BGR565 + #define AV_PIX_FMT_BGR555 PIX_FMT_BGR555 + #define AV_PIX_FMT_BGR8 PIX_FMT_BGR8 + #define AV_PIX_FMT_BGR4 PIX_FMT_BGR4 + #define AV_PIX_FMT_BGR4_BYTE PIX_FMT_BGR4_BYTE + #define AV_PIX_FMT_RGB8 PIX_FMT_RGB8 + #define AV_PIX_FMT_RGB4 PIX_FMT_RGB4 + #define AV_PIX_FMT_RGB4_BYTE PIX_FMT_RGB4_BYTE + #define AV_PIX_FMT_NV12 PIX_FMT_NV12 + #define AV_PIX_FMT_NV21 PIX_FMT_NV21 + #define AV_PIX_FMT_RGB32_1 PIX_FMT_RGB32_1 + #define AV_PIX_FMT_BGR32_1 PIX_FMT_BGR32_1 + #define AV_PIX_FMT_GRAY16BE PIX_FMT_GRAY16BE + #define AV_PIX_FMT_GRAY16LE PIX_FMT_GRAY16LE + #define AV_PIX_FMT_YUV440P PIX_FMT_YUV440P + #define AV_PIX_FMT_YUVJ440P PIX_FMT_YUVJ440P + #define AV_PIX_FMT_YUVA420P PIX_FMT_YUVA420P + //#define AV_PIX_FMT_VDPAU_H264 PIX_FMT_VDPAU_H264 + //#define AV_PIX_FMT_VDPAU_MPEG1 PIX_FMT_VDPAU_MPEG1 + //#define AV_PIX_FMT_VDPAU_MPEG2 PIX_FMT_VDPAU_MPEG2 #endif #endif /* HAVE_LIBAVUTIL_AVUTIL_H */ @@ -121,8 +122,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBAVCODEC_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #elif HAVE_FFMPEG_AVCODEC_H #include @@ -130,9 +131,9 @@ extern "C" { #if defined(HAVE_LIBAVCODEC_AVCODEC_H) #if LIBAVCODEC_VERSION_CHECK(54, 25, 0, 51, 100) - #define _AVCODECID AVCodecID + #define _AVCODECID AVCodecID #else - #define _AVCODECID CodecID + #define _AVCODECID CodecID #endif #endif /* HAVE_LIBAVCODEC_AVCODEC_H */ @@ -146,8 +147,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBAVFORMAT_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #elif HAVE_FFMPEG_AVFORMAT_H #include @@ -162,8 +163,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBAVDEVICE_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBAVDEVICE_VERSION_MICRO < 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBAVDEVICE_VERSION_MICRO >= 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBAVDEVICE_VERSION_MICRO < 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBAVDEVICE_VERSION_MICRO >= 100 && LIBAVDEVICE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #elif HAVE_FFMPEG_AVDEVICE_H #include @@ -178,8 +179,8 @@ extern "C" { * b and c the minor and micro versions of libav * d and e the minor and micro versions of FFmpeg */ #define LIBSWSCALE_VERSION_CHECK(a, b, c, d, e) \ - ( (LIBSWSCALE_VERSION_MICRO < 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ - (LIBSWSCALE_VERSION_MICRO >= 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) + ( (LIBSWSCALE_VERSION_MICRO < 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, b, c) ) || \ + (LIBSWSCALE_VERSION_MICRO >= 100 && LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(a, d, e) ) ) #elif HAVE_FFMPEG_SWSCALE_H #include @@ -199,6 +200,9 @@ extern "C" { #endif #endif +/* A single function to initialize ffmpeg, to avoid multiple initializations */ +void FFMPEGInit(); + #if HAVE_LIBAVUTIL enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder); #endif // HAVE_LIBAVUTIL @@ -208,23 +212,23 @@ enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subp #if HAVE_LIBSWSCALE && HAVE_LIBAVUTIL class SWScale { public: - SWScale(); - ~SWScale(); - int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); - int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size); - int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size); - int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); - int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); + SWScale(); + ~SWScale(); + int SetDefaults(enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); + int ConvertDefaults(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size); + int ConvertDefaults(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size); + int Convert(const Image* img, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); + int Convert(const uint8_t* in_buffer, const size_t in_buffer_size, uint8_t* out_buffer, const size_t out_buffer_size, enum _AVPIXELFORMAT in_pf, enum _AVPIXELFORMAT out_pf, unsigned int width, unsigned int height); protected: - bool gotdefaults; - struct SwsContext* swscale_ctx; - AVFrame* input_avframe; - AVFrame* output_avframe; - enum _AVPIXELFORMAT default_input_pf; - enum _AVPIXELFORMAT default_output_pf; - unsigned int default_width; - unsigned int default_height; + bool gotdefaults; + struct SwsContext* swscale_ctx; + AVFrame* input_avframe; + AVFrame* output_avframe; + enum _AVPIXELFORMAT default_input_pf; + enum _AVPIXELFORMAT default_output_pf; + unsigned int default_width; + unsigned int default_height; }; #endif // HAVE_LIBSWSCALE && HAVE_LIBAVUTIL @@ -261,19 +265,19 @@ protected: */ #ifdef __cplusplus - inline static const std::string av_make_error_string(int errnum) - { - char errbuf[AV_ERROR_MAX_STRING_SIZE]; + inline static const std::string av_make_error_string(int errnum) + { + char errbuf[AV_ERROR_MAX_STRING_SIZE]; #if LIBAVUTIL_VERSION_CHECK(50, 13, 0, 13, 0) - av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE); + av_strerror(errnum, errbuf, AV_ERROR_MAX_STRING_SIZE); #else - snprintf(errbuf, AV_ERROR_MAX_STRING_SIZE, "libav error %d", errnum); + snprintf(errbuf, AV_ERROR_MAX_STRING_SIZE, "libav error %d", errnum); #endif - return (std::string)errbuf; - } + return (std::string)errbuf; + } - #undef av_err2str - #define av_err2str(errnum) av_make_error_string(errnum).c_str() + #undef av_err2str + #define av_err2str(errnum) av_make_error_string(errnum).c_str() /* The following is copied directly from newer ffmpeg */ #if LIBAVUTIL_VERSION_CHECK(52, 7, 0, 17, 100) @@ -288,4 +292,57 @@ protected: #endif // ( HAVE_LIBAVUTIL_AVUTIL_H || HAVE_LIBAVCODEC_AVCODEC_H || HAVE_LIBAVFORMAT_AVFORMAT_H || HAVE_LIBAVDEVICE_AVDEVICE_H ) +#ifndef avformat_alloc_output_context2 +int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename); +#define avformat_alloc_output_context2(x,y,z,a) hacked_up_context2_for_older_ffmpeg(x,y,z,a) +#endif + +#ifndef av_rescale_delta +/** + * Rescale a timestamp while preserving known durations. + */ +int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb); +#endif + +#ifndef av_clip64 +/** + * Clip a signed 64bit integer value into the amin-amax range. + * @param a value to clip + * @param amin minimum value of the clip range + * @param amax maximum value of the clip range + * @return clipped value + */ +static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, int64_t amax) +{ + if (a < amin) return amin; + else if (a > amax) return amax; + else return a; +} + +#define av_clip64 av_clip64_c +#endif + +void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output); +#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) + #define zm_av_packet_unref( packet ) av_packet_unref( packet ) + #define zm_av_packet_ref( dst, src ) av_packet_ref( dst, src ) +#else + #define zm_av_packet_unref( packet ) av_free_packet( packet ) +unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ); +#endif +#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) + #define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) avcodec_decode_video2( context, rawFrame, frameComplete, packet ) +#else + #define zm_avcodec_decode_video(context, rawFrame, frameComplete, packet ) avcodec_decode_video( context, rawFrame, frameComplete, packet->data, packet->size) +#endif + +#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) + #define zm_av_frame_alloc() av_frame_alloc() +#else + #define zm_av_frame_alloc() avcodec_alloc_frame() +#endif + + +int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt); + #endif // ZM_FFMPEG_H diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 5fd7ea382..cb322f335 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -23,6 +23,9 @@ #include "zm_ffmpeg_camera.h" +extern "C"{ +#include "libavutil/time.h" +} #ifndef AV_ERROR_MAX_STRING_SIZE #define AV_ERROR_MAX_STRING_SIZE 64 #endif @@ -33,29 +36,33 @@ #include #endif -FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - Camera( p_id, FFMPEG_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), +FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + Camera( p_id, FFMPEG_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), mPath( p_path ), mMethod( p_method ), mOptions( p_options ) { - if ( capture ) - { + if ( capture ) { Initialise(); } - + mFormatContext = NULL; mVideoStreamId = -1; - mCodecContext = NULL; - mCodec = NULL; + mAudioStreamId = -1; + mVideoCodecContext = NULL; + mAudioCodecContext = NULL; + mVideoCodec = NULL; + mAudioCodec = NULL; mRawFrame = NULL; mFrame = NULL; frameCount = 0; + startTime=0; mIsOpening = false; mCanCapture = false; mOpenStart = 0; mReopenThread = 0; - + videoStore = NULL; + #if HAVE_LIBSWSCALE mConvertContext = NULL; #endif @@ -72,35 +79,37 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri } else { Panic("Unexpected colours: %d",colours); } - + } -FfmpegCamera::~FfmpegCamera() -{ +FfmpegCamera::~FfmpegCamera() { + + if ( videoStore ) { + delete videoStore; + } CloseFfmpeg(); - if ( capture ) - { + if ( capture ) { Terminate(); } } -void FfmpegCamera::Initialise() -{ +void FfmpegCamera::Initialise() { if ( logDebugging() ) av_log_set_level( AV_LOG_DEBUG ); else av_log_set_level( AV_LOG_QUIET ); av_register_all(); + avformat_network_init(); } -void FfmpegCamera::Terminate() -{ +void FfmpegCamera::Terminate() { } -int FfmpegCamera::PrimeCapture() -{ +int FfmpegCamera::PrimeCapture() { + mVideoStreamId = -1; + mAudioStreamId = -1; Info( "Priming capture from %s", mPath.c_str() ); if (OpenFfmpeg() != 0){ @@ -120,101 +129,99 @@ int FfmpegCamera::Capture( Image &image ) if (!mCanCapture){ return -1; } - + // If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread. if (mReopenThread != 0) { void *retval = 0; int ret; - + ret = pthread_join(mReopenThread, &retval); if (ret != 0){ Error("Could not join reopen thread."); } - + Info( "Successfully reopened stream." ); mReopenThread = 0; } - AVPacket packet; - uint8_t* directbuffer; - - /* Request a writeable buffer of the target image */ - directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); - if(directbuffer == NULL) { - Error("Failed requesting writeable buffer for the captured image."); - return (-1); - } - int frameComplete = false; - while ( !frameComplete ) - { + while ( !frameComplete ) { int avResult = av_read_frame( mFormatContext, &packet ); - if ( avResult < 0 ) - { + if ( avResult < 0 ) { char errbuf[AV_ERROR_MAX_STRING_SIZE]; av_strerror(avResult, errbuf, AV_ERROR_MAX_STRING_SIZE); if ( - // Check if EOF. - (avResult == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) || - // Check for Connection failure. - (avResult == -110) - ) - { - Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf); + // Check if EOF. + (avResult == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) || + // Check for Connection failure. + (avResult == -110) + ) { + Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf ); ReopenFfmpeg(); } Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, avResult, errbuf ); return( -1 ); } - Debug( 5, "Got packet from stream %d", packet.stream_index ); - if ( packet.stream_index == mVideoStreamId ) - { + Debug( 5, "Got packet from stream %d dts (%d) pts(%d)", packet.stream_index, packet.pts, packet.dts ); + // What about audio stream? Maybe someday we could do sound detection... + if ( packet.stream_index == mVideoStreamId ) { #if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) - if ( avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ) < 0 ) + if (avcodec_decode_video2(mVideoCodecContext, mRawFrame, &frameComplete, &packet) < 0) #else - if ( avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ) < 0 ) + if (avcodec_decode_video(mVideoCodecContext, mRawFrame, &frameComplete, packet.data, packet.size) < 0) #endif Fatal( "Unable to decode frame at frame %d", frameCount ); Debug( 4, "Decoded video packet at frame %d", frameCount ); - if ( frameComplete ) - { - Debug( 3, "Got frame %d", frameCount ); + if ( frameComplete ) { + Debug( 4, "Got frame %d", frameCount ); + + uint8_t* directbuffer; + + /* Request a writeable buffer of the target image */ + directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); + if(directbuffer == NULL) { + Error("Failed requesting writeable buffer for the captured image."); + return (-1); + } + #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(mFrame->data, mFrame->linesize, - directbuffer, imagePixFormat, width, height, 1); + directbuffer, imagePixFormat, width, height, 1); #else avpicture_fill( (AVPicture *)mFrame, directbuffer, - imagePixFormat, width, height); + imagePixFormat, width, height); #endif - -#if HAVE_LIBSWSCALE - if(mConvertContext == NULL) { - mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); - if(mConvertContext == NULL) - Fatal( "Unable to create conversion context for %s", mPath.c_str() ); - } - - if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) - Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); +#if HAVE_LIBSWSCALE + if(mConvertContext == NULL) { + mConvertContext = sws_getContext(mVideoCodecContext->width, + mVideoCodecContext->height, + mVideoCodecContext->pix_fmt, + width, height, imagePixFormat, + SWS_BICUBIC, NULL, NULL, NULL); + + if(mConvertContext == NULL) + Fatal( "Unable to create conversion context for %s", mPath.c_str() ); + } + + if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) + Fatal("Unable to convert raw format %u to target format %u at frame %d", mVideoCodecContext->pix_fmt, imagePixFormat, frameCount); #else // HAVE_LIBSWSCALE - Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" ); + Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" ); #endif // HAVE_LIBSWSCALE - + frameCount++; - } - } -#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) - av_packet_unref( &packet); -#else - av_free_packet( &packet ); -#endif - } + } // end if frameComplete + } else { + Debug( 4, "Different stream_index %d", packet.stream_index ); + } // end if packet.stream_index == mVideoStreamId + zm_av_packet_unref( &packet ); + } // end while ! frameComplete return (0); -} +} // FfmpegCamera::Capture int FfmpegCamera::PostCapture() { @@ -278,70 +285,111 @@ int FfmpegCamera::OpenFfmpeg() { mIsOpening = false; Debug ( 1, "Opened input" ); + Info( "Stream open %s", mPath.c_str() ); + + //FIXME can speed up initial analysis but need sensible parameters... + //mFormatContext->probesize = 32; + //mFormatContext->max_analyze_duration = 32; // Locate stream info from avformat_open_input #if !LIBAVFORMAT_VERSION_CHECK(53, 6, 0, 6, 0) Debug ( 1, "Calling av_find_stream_info" ); if ( av_find_stream_info( mFormatContext ) < 0 ) #else - Debug ( 1, "Calling avformat_find_stream_info" ); + Debug ( 1, "Calling avformat_find_stream_info" ); if ( avformat_find_stream_info( mFormatContext, 0 ) < 0 ) #endif Fatal( "Unable to find stream info from %s due to: %s", mPath.c_str(), strerror(errno) ); + startTime=av_gettime();//FIXME here or after find_Stream_info Debug ( 1, "Got stream info" ); // Find first video stream present + // The one we want Might not be the first mVideoStreamId = -1; - for (unsigned int i=0; i < mFormatContext->nb_streams; i++ ) - { + mAudioStreamId = -1; + for (unsigned int i=0; i < mFormatContext->nb_streams; i++ ) { #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) + if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) { #else - if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) + if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) { #endif - { - mVideoStreamId = i; - break; + if ( mVideoStreamId == -1 ) { + mVideoStreamId = i; + // if we break, then we won't find the audio stream + continue; + } else { + Debug(2, "Have another video stream." ); + } + } +#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) + if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO ) { +#else + if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) { +#endif + if ( mAudioStreamId == -1 ) { + mAudioStreamId = i; + } else { + Debug(2, "Have another audio stream." ); + } } } if ( mVideoStreamId == -1 ) Fatal( "Unable to locate video stream in %s", mPath.c_str() ); + if ( mAudioStreamId == -1 ) + Debug( 3, "Unable to locate audio stream in %s", mPath.c_str() ); - Debug ( 1, "Found video stream" ); + Debug ( 3, "Found video stream at index %d", mVideoStreamId ); + Debug ( 3, "Found audio stream at index %d", mAudioStreamId ); - mCodecContext = mFormatContext->streams[mVideoStreamId]->codec; + mVideoCodecContext = mFormatContext->streams[mVideoStreamId]->codec; + // STolen from ispy + //this fixes issues with rtsp streams!! woot. + //mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG2_CHUNKS | CODEC_FLAG_LOW_DELAY; // Enable faster H264 decode. + mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY; // Try and get the codec from the codec context - if ( (mCodec = avcodec_find_decoder( mCodecContext->codec_id )) == NULL ) - Fatal( "Can't find codec for video stream from %s", mPath.c_str() ); - - Debug ( 1, "Found decoder" ); - + if ((mVideoCodec = avcodec_find_decoder(mVideoCodecContext->codec_id)) == NULL) { + Fatal("Can't find codec for video stream from %s", mPath.c_str()); + } else { + Debug(1, "Video Found decoder"); + zm_dump_stream_format(mFormatContext, mVideoStreamId, 0, 0); // Open the codec #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) Debug ( 1, "Calling avcodec_open" ); - if ( avcodec_open( mCodecContext, mCodec ) < 0 ) + if (avcodec_open(mVideoCodecContext, mVideoCodec) < 0) #else - Debug ( 1, "Calling avcodec_open2" ); - if ( avcodec_open2( mCodecContext, mCodec, 0 ) < 0 ) + Debug ( 1, "Calling avcodec_open2" ); + if (avcodec_open2(mVideoCodecContext, mVideoCodec, 0) < 0) #endif Fatal( "Unable to open codec for video stream from %s", mPath.c_str() ); + } + + if (mAudioStreamId >= 0) { + mAudioCodecContext = mFormatContext->streams[mAudioStreamId]->codec; + if ((mAudioCodec = avcodec_find_decoder(mAudioCodecContext->codec_id)) == NULL) { + Debug(1, "Can't find codec for audio stream from %s", mPath.c_str()); + } else { + Debug(1, "Audio Found decoder"); + zm_dump_stream_format(mFormatContext, mAudioStreamId, 0, 0); + // Open the codec +#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) + Debug ( 1, "Calling avcodec_open" ); + if (avcodec_open(mAudioCodecContext, mAudioCodec) < 0) +#else + Debug ( 1, "Calling avcodec_open2" ); + if (avcodec_open2(mAudioCodecContext, mAudioCodec, 0) < 0) +#endif + Fatal( "Unable to open codec for video stream from %s", mPath.c_str() ); + } + } Debug ( 1, "Opened codec" ); // Allocate space for the native video frame -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - mRawFrame = av_frame_alloc(); -#else - mRawFrame = avcodec_alloc_frame(); -#endif + mRawFrame = zm_av_frame_alloc(); // Allocate space for the converted video frame -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - mFrame = av_frame_alloc(); -#else - mFrame = avcodec_alloc_frame(); -#endif + mFrame = zm_av_frame_alloc(); if(mRawFrame == NULL || mFrame == NULL) Fatal( "Unable to allocate frame for %s", mPath.c_str() ); @@ -359,21 +407,33 @@ int FfmpegCamera::OpenFfmpeg() { } Debug ( 1, "Validated imagesize" ); - + #if HAVE_LIBSWSCALE Debug ( 1, "Calling sws_isSupportedInput" ); - if(!sws_isSupportedInput(mCodecContext->pix_fmt)) { - Fatal("swscale does not support the codec format: %c%c%c%c",(mCodecContext->pix_fmt)&0xff,((mCodecContext->pix_fmt>>8)&0xff),((mCodecContext->pix_fmt>>16)&0xff),((mCodecContext->pix_fmt>>24)&0xff)); + if (!sws_isSupportedInput(mVideoCodecContext->pix_fmt)) { + Fatal("swscale does not support the codec format: %c%c%c%c", (mVideoCodecContext->pix_fmt)&0xff, ((mVideoCodecContext->pix_fmt >> 8)&0xff), ((mVideoCodecContext->pix_fmt >> 16)&0xff), ((mVideoCodecContext->pix_fmt >> 24)&0xff)); } - + if(!sws_isSupportedOutput(imagePixFormat)) { Fatal("swscale does not support the target format: %c%c%c%c",(imagePixFormat)&0xff,((imagePixFormat>>8)&0xff),((imagePixFormat>>16)&0xff),((imagePixFormat>>24)&0xff)); } - + + mConvertContext = sws_getContext(mVideoCodecContext->width, + mVideoCodecContext->height, + mVideoCodecContext->pix_fmt, + width, height, + imagePixFormat, SWS_BICUBIC, NULL, + NULL, NULL); + if ( mConvertContext == NULL ) + Fatal( "Unable to create conversion context for %s", mPath.c_str() ); #else // HAVE_LIBSWSCALE Fatal( "You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras" ); #endif // HAVE_LIBSWSCALE + if ( mVideoCodecContext->width != width || mVideoCodecContext->height != height ) { + Warning( "Monitor dimensions are %dx%d but camera is sending %dx%d", width, height, mVideoCodecContext->width, mVideoCodecContext->height ); + } + mCanCapture = true; return 0; @@ -405,7 +465,7 @@ int FfmpegCamera::CloseFfmpeg(){ av_freep( &mFrame ); av_freep( &mRawFrame ); #endif - + #if HAVE_LIBSWSCALE if ( mConvertContext ) { @@ -414,13 +474,16 @@ int FfmpegCamera::CloseFfmpeg(){ } #endif - if ( mCodecContext ) - { - avcodec_close( mCodecContext ); - mCodecContext = NULL; // Freed by av_close_input_file + if (mVideoCodecContext) { + avcodec_close(mVideoCodecContext); + mVideoCodecContext = NULL; // Freed by av_close_input_file } - if ( mFormatContext ) - { + if (mAudioCodecContext) { + avcodec_close(mAudioCodecContext); + mAudioCodecContext = NULL; // Freed by av_close_input_file + } + + if ( mFormatContext ) { #if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) av_close_input_file( mFormatContext ); #else @@ -469,4 +532,250 @@ void *FfmpegCamera::ReopenFfmpegThreadCallback(void *ctx){ } } +//Function to handle capture and store +int FfmpegCamera::CaptureAndRecord( Image &image, bool recording, char* event_file ) { + if (!mCanCapture){ + return -1; + } + int ret; + static char errbuf[AV_ERROR_MAX_STRING_SIZE]; + + // If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread. + if (mReopenThread != 0) { + void *retval = 0; + + ret = pthread_join(mReopenThread, &retval); + if (ret != 0){ + Error("Could not join reopen thread."); + } + + Info( "Successfully reopened stream." ); + mReopenThread = 0; + } + + + if (mVideoCodecContext->codec_id != AV_CODEC_ID_H264) { + Error( "Input stream is not h264. The stored event file may not be viewable in browser." ); + } + + int frameComplete = false; + while ( !frameComplete ) { +Debug(5, "Before av_init_packe"); + av_init_packet( &packet ); + +Debug(5, "Before av_read_frame"); + ret = av_read_frame( mFormatContext, &packet ); +Debug(5, "After av_read_frame (%d)", ret ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + if ( + // Check if EOF. + (ret == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) || + // Check for Connection failure. + (ret == -110) + ) { + Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf); + ReopenFfmpeg(); + } + + Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret, errbuf ); + return( -1 ); + } + + int key_frame = packet.flags & AV_PKT_FLAG_KEY; + + Debug( 3, "Got packet from stream %d packet pts (%d) dts(%d), key?(%d)", + packet.stream_index, packet.pts, packet.dts, + key_frame + ); + + //Video recording + if ( recording ) { + // The directory we are recording to is no longer tied to the current event. + // Need to re-init the videostore with the correct directory and start recording again + // for efficiency's sake, we should test for keyframe before we test for directory change... + if ( videoStore && key_frame && (strcmp(oldDirectory, event_file) != 0 ) ) { + // don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?... + // if we store our key frame location with the event will that be enough? + Info("Re-starting video storage module"); + + // I don't know if this is important or not... but I figure we might as well write this last packet out to the store before closing it. + // Also don't know how much it matters for audio. + if ( packet.stream_index == mVideoStreamId ) { + //Write the packet to our video store + int ret = videoStore->writeVideoFramePacket( &packet ); + if ( ret < 0 ) { //Less than zero and we skipped a frame + Warning("Error writing last packet to videostore."); + } + } // end if video + + delete videoStore; + videoStore = NULL; + } + + if ( ( ! videoStore )&& key_frame && ( packet.stream_index == mVideoStreamId ) ) { + //Instantiate the video storage module + + if (record_audio) { + if (mAudioStreamId == -1) { + Debug(3, "Record Audio on but no audio stream found"); + videoStore = new VideoStore((const char *) event_file, "mp4", + mFormatContext->streams[mVideoStreamId], + NULL, + startTime, + this->getMonitor()); + + } else { + Debug(3, "Video module initiated with audio stream"); + videoStore = new VideoStore((const char *) event_file, "mp4", + mFormatContext->streams[mVideoStreamId], + mFormatContext->streams[mAudioStreamId], + startTime, + this->getMonitor()); + } + } else { + Debug(3, "Record_audio is false so exclude audio stream"); + videoStore = new VideoStore((const char *) event_file, "mp4", + mFormatContext->streams[mVideoStreamId], + NULL, + startTime, + this->getMonitor()); + } + strcpy(oldDirectory, event_file); + + // Need to write out all the frames from the last keyframe? + unsigned int packet_count = 0; + AVPacket *queued_packet; + while ( ( queued_packet = packetqueue.popPacket() ) ) { + packet_count += 1; + //Write the packet to our video store + Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", queued_packet->stream_index, queued_packet->flags & AV_PKT_FLAG_KEY, packetqueue.size() ); + if ( queued_packet->stream_index == mVideoStreamId ) { + ret = videoStore->writeVideoFramePacket( queued_packet ); + } else if ( queued_packet->stream_index == mAudioStreamId ) { + ret = videoStore->writeAudioFramePacket( queued_packet ); + } else { + Warning("Unknown stream id in queued packet (%d)", queued_packet->stream_index ); + ret = -1; + } + if ( ret < 0 ) { + //Less than zero and we skipped a frame + } + zm_av_packet_unref( queued_packet ); + av_free( queued_packet ); + } // end while packets in the packetqueue + Debug(2, "Wrote %d queued packets", packet_count ); + } // end if ! wasRecording + + } else { + // Not recording + if ( videoStore ) { + Info("Deleting videoStore instance"); + delete videoStore; + videoStore = NULL; + } + + // Buffer video packets, since we are not recording. + // All audio packets are keyframes, so only if it's a video keyframe + if ( packet.stream_index == mVideoStreamId) { + if ( key_frame ) { + Debug(3, "Clearing queue"); + packetqueue.clearQueue(); + } +#if 0 +// Not sure this is valid. While a camera will PROBABLY always have an increasing pts... it doesn't have to. +// Also, I think there are integer wrap-around issues. + +else if ( packet.pts && video_last_pts > packet.pts ) { + Warning( "Clearing queue due to out of order pts packet.pts(%d) < video_last_pts(%d)"); + packetqueue.clearQueue(); + } +#endif + } + + if ( + ( packet.stream_index != mAudioStreamId || record_audio ) + && + ( key_frame || packetqueue.size() ) + ) { + packetqueue.queuePacket( &packet ); + } + } // end if recording or not + + if ( packet.stream_index == mVideoStreamId ) { + if ( videoStore ) { + //Write the packet to our video store + int ret = videoStore->writeVideoFramePacket( &packet ); + if ( ret < 0 ) { //Less than zero and we skipped a frame + zm_av_packet_unref( &packet ); + return 0; + } + } + Debug(4, "about to decode video" ); + ret = zm_avcodec_decode_video( mVideoCodecContext, mRawFrame, &frameComplete, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to decode frame at frame %d: %s, continuing", frameCount, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + + Debug( 4, "Decoded video packet at frame %d", frameCount ); + + if ( frameComplete ) { + Debug( 4, "Got frame %d", frameCount ); + + uint8_t* directbuffer; + + /* Request a writeable buffer of the target image */ + directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); + if ( directbuffer == NULL ) { + Error("Failed requesting writeable buffer for the captured image."); + zm_av_packet_unref( &packet ); + return (-1); + } + avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height); + + if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, + 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) { + Fatal("Unable to convert raw format %u to target format %u at frame %d", + mVideoCodecContext->pix_fmt, imagePixFormat, frameCount); + } + + frameCount++; + } else { + Debug( 3, "Not framecomplete after av_read_frame" ); + } // end if frameComplete + } else if ( packet.stream_index == mAudioStreamId ) { //FIXME best way to copy all other streams + if ( videoStore ) { + if ( record_audio ) { + Debug(3, "Recording audio packet streamindex(%d) packetstreamindex(%d)", mAudioStreamId, packet.stream_index ); + //Write the packet to our video store + //FIXME no relevance of last key frame + int ret = videoStore->writeAudioFramePacket( &packet ); + if ( ret < 0 ) {//Less than zero and we skipped a frame + Warning("Failure to write audio packet."); + zm_av_packet_unref( &packet ); + return 0; + } + } else { + Debug(4, "Not recording audio packet" ); + } + } + } else { +#if LIBAVUTIL_VERSION_CHECK(54, 23, 0, 23, 0) + Debug( 3, "Some other stream index %d, %s", packet.stream_index, av_get_media_type_string( mFormatContext->streams[packet.stream_index]->codec->codec_type) ); +#else + Debug( 3, "Some other stream index %d", packet.stream_index ); +#endif + } + //if ( videoStore ) { + + // the packet contents are ref counted... when queuing, we allocate another packet and reference it with that one, so we should always need to unref here, which should not affect the queued version. + zm_av_packet_unref( &packet ); + //} + } // end while ! frameComplete + return (frameCount); +} // end FfmpegCamera::CaptureAndRecord + #endif // HAVE_LIBAVFORMAT diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index c11be9b3c..e7b39fde8 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -25,6 +25,8 @@ #include "zm_buffer.h" //#include "zm_utils.h" #include "zm_ffmpeg.h" +#include "zm_videostore.h" +#include "zm_packetqueue.h" // // Class representing 'ffmpeg' cameras, i.e. those which are @@ -32,52 +34,75 @@ // class FfmpegCamera : public Camera { -protected: - std::string mPath; - std::string mMethod; - std::string mOptions; + protected: + std::string mPath; + std::string mMethod; + std::string mOptions; - int frameCount; + int frameCount; #if HAVE_LIBAVFORMAT - AVFormatContext *mFormatContext; - int mVideoStreamId; - AVCodecContext *mCodecContext; - AVCodec *mCodec; - AVFrame *mRawFrame; - AVFrame *mFrame; - _AVPIXELFORMAT imagePixFormat; + AVFormatContext *mFormatContext; + int mVideoStreamId; + int mAudioStreamId; + AVCodecContext *mVideoCodecContext; + AVCodecContext *mAudioCodecContext; + AVCodec *mVideoCodec; + AVCodec *mAudioCodec; + AVFrame *mRawFrame; + AVFrame *mFrame; + _AVPIXELFORMAT imagePixFormat; - int OpenFfmpeg(); - int ReopenFfmpeg(); - int CloseFfmpeg(); - static int FfmpegInterruptCallback(void *ctx); - static void* ReopenFfmpegThreadCallback(void *ctx); - bool mIsOpening; - bool mCanCapture; - int mOpenStart; - pthread_t mReopenThread; + // Need to keep track of these because apparently the stream can start with values for pts/dts and then subsequent packets start at zero. + int64_t audio_last_pts; + int64_t audio_last_dts; + int64_t video_last_pts; + int64_t video_last_dts; + + // Used to store the incoming packet, it will get copied when queued. + // We only ever need one at a time, so instead of constantly allocating + // and freeing this structure, we will just make it a member of the object. + AVPacket packet; + + int OpenFfmpeg(); + int ReopenFfmpeg(); + int CloseFfmpeg(); + static int FfmpegInterruptCallback(void *ctx); + static void* ReopenFfmpegThreadCallback(void *ctx); + bool mIsOpening; + bool mCanCapture; + int mOpenStart; + pthread_t mReopenThread; #endif // HAVE_LIBAVFORMAT + bool wasRecording; + VideoStore *videoStore; + char oldDirectory[4096]; + unsigned int old_event_id; + zm_packetqueue packetqueue; + #if HAVE_LIBSWSCALE - struct SwsContext *mConvertContext; + struct SwsContext *mConvertContext; #endif -public: - FfmpegCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); - ~FfmpegCamera(); + int64_t startTime; - const std::string &Path() const { return( mPath ); } - const std::string &Options() const { return( mOptions ); } - const std::string &Method() const { return( mMethod ); } + public: + FfmpegCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + ~FfmpegCamera(); - void Initialise(); - void Terminate(); + const std::string &Path() const { return( mPath ); } + const std::string &Options() const { return( mOptions ); } + const std::string &Method() const { return( mMethod ); } - int PrimeCapture(); - int PreCapture(); - int Capture( Image &image ); - int PostCapture(); + void Initialise(); + void Terminate(); + + int PrimeCapture(); + int PreCapture(); + int Capture( Image &image ); + int CaptureAndRecord( Image &image, bool recording, char* event_directory ); + int PostCapture(); }; #endif // ZM_FFMPEG_CAMERA_H diff --git a/src/zm_file_camera.cpp b/src/zm_file_camera.cpp index dfb12d38a..b77628963 100644 --- a/src/zm_file_camera.cpp +++ b/src/zm_file_camera.cpp @@ -34,7 +34,7 @@ #include "zm.h" #include "zm_file_camera.h" -FileCamera::FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : Camera( p_id, FILE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ) +FileCamera::FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : Camera( p_id, FILE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ) { strncpy( path, p_path, sizeof(path) ); if ( capture ) @@ -87,5 +87,5 @@ int FileCamera::Capture( Image &image ) int FileCamera::PostCapture() { - return( 0 ); + return( 0 ); } diff --git a/src/zm_file_camera.h b/src/zm_file_camera.h index 84d720050..71f9452f7 100644 --- a/src/zm_file_camera.h +++ b/src/zm_file_camera.h @@ -23,6 +23,7 @@ #include "zm_camera.h" #include "zm_buffer.h" #include "zm_regexp.h" +#include "zm_packetqueue.h" #include @@ -36,7 +37,7 @@ protected: char path[PATH_MAX]; public: - FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~FileCamera(); const char *Path() const { return( path ); } @@ -46,6 +47,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); + int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);}; }; #endif // ZM_FILE_CAMERA_H diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 9c8c12fda..250ed1b45 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -45,7 +45,6 @@ static short *r_v_table; static short *g_v_table; static short *g_u_table; static short *b_u_table; -__attribute__((aligned(16))) static const uint8_t movemask[16] = {0,4,8,12,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; jpeg_compress_struct *Image::writejpg_ccinfo[101] = { 0 }; jpeg_compress_struct *Image::encodejpg_ccinfo[101] = { 0 }; @@ -196,6 +195,9 @@ void Image::Initialise() if(config.cpu_extensions && sseversion >= 20) { fptr_blend = &sse2_fastblend; /* SSE2 fast blend */ Debug(4,"Blend: Using SSE2 fast blend function"); + } else if(config.cpu_extensions && neonversion >= 1) { + fptr_blend = &neon32_armv7_fastblend; /* ARM Neon fast blend */ + Debug(4,"Blend: Using ARM Neon fast blend function"); } else { fptr_blend = &std_fastblend; /* standard fast blend */ Debug(4,"Blend: Using fast blend function"); @@ -249,6 +251,14 @@ void Image::Initialise() // fptr_delta8_abgr = &std_delta8_abgr; fptr_delta8_gray8 = &sse2_delta8_gray8; Debug(4,"Delta: Using SSE2 delta functions"); + } else if(neonversion >= 1) { + /* ARM Neon available */ + fptr_delta8_rgba = &neon32_armv7_delta8_rgba; + fptr_delta8_bgra = &neon32_armv7_delta8_bgra; + fptr_delta8_argb = &neon32_armv7_delta8_argb; + fptr_delta8_abgr = &neon32_armv7_delta8_abgr; + fptr_delta8_gray8 = &neon32_armv7_delta8_gray8; + Debug(4,"Delta: Using ARM Neon delta functions"); } else { /* No suitable SSE version available */ fptr_delta8_rgba = &std_delta8_rgba; @@ -267,24 +277,20 @@ void Image::Initialise() fptr_delta8_gray8 = &std_delta8_gray8; Debug(4,"Delta: CPU extensions disabled, using standard delta functions"); } + + /* + SSSE3 deinterlacing functions were removed because they were usually equal + or slower than the standard code (compiled with -O2 or better) + The function is too complicated to be vectorized efficiently + */ + fptr_deinterlace_4field_rgba = &std_deinterlace_4field_rgba; + fptr_deinterlace_4field_bgra = &std_deinterlace_4field_bgra; + fptr_deinterlace_4field_argb = &std_deinterlace_4field_argb; + fptr_deinterlace_4field_abgr = &std_deinterlace_4field_abgr; + fptr_deinterlace_4field_gray8 = &std_deinterlace_4field_gray8; + Debug(4,"Deinterlace: Using standard functions"); - /* Use SSSE3 deinterlace functions? */ - if(config.cpu_extensions && sseversion >= 35) { - fptr_deinterlace_4field_rgba = &ssse3_deinterlace_4field_rgba; - fptr_deinterlace_4field_bgra = &ssse3_deinterlace_4field_bgra; - fptr_deinterlace_4field_argb = &ssse3_deinterlace_4field_argb; - fptr_deinterlace_4field_abgr = &ssse3_deinterlace_4field_abgr; - fptr_deinterlace_4field_gray8 = &ssse3_deinterlace_4field_gray8; - Debug(4,"Deinterlace: Using SSSE3 delta functions"); - } else { - fptr_deinterlace_4field_rgba = &std_deinterlace_4field_rgba; - fptr_deinterlace_4field_bgra = &std_deinterlace_4field_bgra; - fptr_deinterlace_4field_argb = &std_deinterlace_4field_argb; - fptr_deinterlace_4field_abgr = &std_deinterlace_4field_abgr; - fptr_deinterlace_4field_gray8 = &std_deinterlace_4field_gray8; - Debug(4,"Deinterlace: Using standard delta functions"); - } - +#if defined(__i386__) && !defined(__x86_64__) /* Use SSE2 aligned memory copy? */ if(config.cpu_extensions && sseversion >= 20) { fptr_imgbufcpy = &sse2_aligned_memcpy; @@ -293,6 +299,10 @@ void Image::Initialise() fptr_imgbufcpy = &memcpy; Debug(4,"Image buffer copy: Using standard memcpy"); } +#else + fptr_imgbufcpy = &memcpy; + Debug(4,"Image buffer copy: Using standard memcpy"); +#endif /* Code below relocated from zm_local_camera */ Debug( 3, "Setting up static colour tables" ); @@ -1768,7 +1778,7 @@ const Coord Image::centreCoord( const char *text ) const line = text+index; line_no++; } - int x = (width - (max_line_len * CHAR_WIDTH) ) / 2; + int x = (width - (max_line_len * ZM_CHAR_WIDTH) ) / 2; int y = (height - (line_no * LINE_HEIGHT) ) / 2; return( Coord( x, y ) ); } @@ -1858,7 +1868,7 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int while ( (index < text_len) && (line_len = strcspn( line, "\n" )) ) { - unsigned int line_width = line_len * CHAR_WIDTH * size; + unsigned int line_width = line_len * ZM_CHAR_WIDTH * size; unsigned int lo_line_x = coord.X(); unsigned int lo_line_y = coord.Y() + (line_no * LINE_HEIGHT * size); @@ -1889,17 +1899,17 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int if ( colours == ZM_COLOUR_GRAY8 ) { unsigned char *ptr = &buffer[(lo_line_y*width)+lo_line_x]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (CHAR_HEIGHT * size); y++, r++, ptr += width ) + for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += width ) { unsigned char *temp_ptr = ptr; for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { int f; if (size == 2) - f = bigfontdata[(line[c] * CHAR_HEIGHT * size) + r]; + f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; else - f = fontdata[(line[c] * CHAR_HEIGHT) + r]; - for ( unsigned int i = 0; i < (CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) + f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; + for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) { if ( f & (zm_text_bitmask >> i) ) { @@ -1919,17 +1929,17 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int unsigned int wc = width * colours; unsigned char *ptr = &buffer[((lo_line_y*width)+lo_line_x)*colours]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (CHAR_HEIGHT * size); y++, r++, ptr += wc ) + for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += wc ) { unsigned char *temp_ptr = ptr; for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { int f; if (size == 2) - f = bigfontdata[(line[c] * CHAR_HEIGHT * size) + r]; + f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; else - f = fontdata[(line[c] * CHAR_HEIGHT) + r]; - for ( unsigned int i = 0; i < (CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr += colours ) + f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; + for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr += colours ) { if ( f & (zm_text_bitmask >> i) ) { @@ -1955,17 +1965,17 @@ void Image::Annotate( const char *p_text, const Coord &coord, const unsigned int unsigned int wc = width * colours; uint8_t *ptr = &buffer[((lo_line_y*width)+lo_line_x)<<2]; - for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (CHAR_HEIGHT * size); y++, r++, ptr += wc ) + for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += wc ) { Rgb* temp_ptr = (Rgb*)ptr; for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) { int f; if (size == 2) - f = bigfontdata[(line[c] * CHAR_HEIGHT * size) + r]; + f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r]; else - f = fontdata[(line[c] * CHAR_HEIGHT) + r]; - for ( unsigned int i = 0; i < (CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) + f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r]; + for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) { if ( f & (zm_text_bitmask >> i) ) { @@ -2081,35 +2091,54 @@ void Image::DeColourise() subpixelorder = ZM_SUBPIX_ORDER_NONE; size = width * height; - if ( colours == ZM_COLOUR_RGB32 ) - { + if(colours == ZM_COLOUR_RGB32 && config.cpu_extensions && sseversion >= 35) { + /* Use SSSE3 functions */ switch(subpixelorder) { case ZM_SUBPIX_ORDER_BGRA: - std_convert_bgra_gray8(buffer,buffer,pixels); + ssse3_convert_bgra_gray8(buffer,buffer,pixels); break; case ZM_SUBPIX_ORDER_ARGB: - std_convert_argb_gray8(buffer,buffer,pixels); + ssse3_convert_argb_gray8(buffer,buffer,pixels); break; case ZM_SUBPIX_ORDER_ABGR: - std_convert_abgr_gray8(buffer,buffer,pixels); + ssse3_convert_abgr_gray8(buffer,buffer,pixels); break; case ZM_SUBPIX_ORDER_RGBA: default: - std_convert_rgba_gray8(buffer,buffer,pixels); + ssse3_convert_rgba_gray8(buffer,buffer,pixels); break; } } else { - /* Assume RGB24 */ - switch(subpixelorder) { - case ZM_SUBPIX_ORDER_BGR: - std_convert_bgr_gray8(buffer,buffer,pixels); - break; - case ZM_SUBPIX_ORDER_RGB: - default: - std_convert_rgb_gray8(buffer,buffer,pixels); - break; - } - + /* Use standard functions */ + if ( colours == ZM_COLOUR_RGB32 ) + { + switch(subpixelorder) { + case ZM_SUBPIX_ORDER_BGRA: + std_convert_bgra_gray8(buffer,buffer,pixels); + break; + case ZM_SUBPIX_ORDER_ARGB: + std_convert_argb_gray8(buffer,buffer,pixels); + break; + case ZM_SUBPIX_ORDER_ABGR: + std_convert_abgr_gray8(buffer,buffer,pixels); + break; + case ZM_SUBPIX_ORDER_RGBA: + default: + std_convert_rgba_gray8(buffer,buffer,pixels); + break; + } + } else { + /* Assume RGB24 */ + switch(subpixelorder) { + case ZM_SUBPIX_ORDER_BGR: + std_convert_bgr_gray8(buffer,buffer,pixels); + break; + case ZM_SUBPIX_ORDER_RGB: + default: + std_convert_rgb_gray8(buffer,buffer,pixels); + break; + } + } } } @@ -3279,6 +3308,68 @@ __attribute__((noinline)) void std_fastblend(const uint8_t* col1, const uint8_t* } } +/* FastBlend Neon for AArch32 */ +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +__attribute__((noinline,__target__("fpu=neon"))) +#endif +void neon32_armv7_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) + static int8_t divider = 0; + static double current_blendpercent = 0.0; + + if(current_blendpercent != blendpercent) { + /* Attempt to match the blending percent to one of the possible values */ + if(blendpercent < 2.34375) { + // 1.5625% blending + divider = 6; + } else if(blendpercent >= 2.34375 && blendpercent < 4.6875) { + // 3.125% blending + divider = 5; + } else if(blendpercent >= 4.6875 && blendpercent < 9.375) { + // 6.25% blending + divider = 4; + } else if(blendpercent >= 9.375 && blendpercent < 18.75) { + // 12.5% blending + divider = 3; + } else if(blendpercent >= 18.75 && blendpercent < 37.5) { + // 25% blending + divider = 2; + } else if(blendpercent >= 37.5) { + // 50% blending + divider = 1; + } + // We only have instruction to shift left by a variable, going negative shifts right :) + divider *= -1; + current_blendpercent = blendpercent; + } + + /* Q0(D0,D1) = col1 */ + /* Q1(D2,D3) = col2 */ + /* Q2(D4,D5) = col1 backup */ + /* Q3(D6,D7) = divider */ + + __asm__ __volatile__ ( + "mov r12, %4\n\t" + "vdup.8 q3, r12\n\t" + "neon32_armv7_fastblend_iter:\n\t" + "vldm %0!, {q0}\n\t" + "vldm %1!, {q1}\n\t" + "vrshl.u8 q2, q0, q3\n\t" + "vrshl.u8 q1, q1, q3\n\t" + "vsub.i8 q1, q1, q2\n\t" + "vadd.i8 q1, q1, q0\n\t" + "vstm %2!, {q1}\n\t" + "subs %3, %3, #16\n\t" + "bne neon32_armv7_fastblend_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "g" (divider) + : "%r12", "%q0", "%q1", "%q2", "%q3", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + __attribute__((noinline)) void std_blend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) { double divide = blendpercent / 100.0; double opacity = 1.0 - divide; @@ -3501,6 +3592,88 @@ __attribute__((noinline)) void std_delta8_abgr(const uint8_t* col1, const uint8_ } } +/* Grayscale Neon for AArch32 */ +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +__attribute__((noinline,__target__("fpu=neon"))) +#endif +void neon32_armv7_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) + + /* Q0(D0,D1) = col1 */ + /* Q1(D2,D3) = col2 */ + + __asm__ __volatile__ ( + "neon32_armv7_delta8_gray8_iter:\n\t" + "vldm %0!, {q0}\n\t" + "vldm %1!, {q1}\n\t" + "vabd.u8 q0, q0, q1\n\t" + "vstm %2!, {q0}\n\t" + "subs %3, %3, #16\n\t" + "bne neon32_armv7_delta8_gray8_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count) + : "%q0", "%q1", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + +/* RGB32 Neon for AArch32 */ +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) +__attribute__((noinline,__target__("fpu=neon"))) +#endif +void neon32_armv7_delta8_rgb32(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, uint32_t multiplier) { +#if (defined(__arm__) && !defined(ZM_STRIP_NEON)) + + /* Q0(D0,D1) = col1 */ + /* Q1(D2,D3) = col2 */ + /* Q2(D4,D5) = multiplier */ + + __asm__ __volatile__ ( + "mov r12, %4\n\t" + "vdup.32 q2, r12\n\t" + "neon32_armv7_delta8_rgb32_iter:\n\t" + "vldm %0!, {q0}\n\t" + "vldm %1!, {q1}\n\t" + "vabd.u8 q0, q0, q1\n\t" + "vrshr.u8 q0, q0, #3\n\t" + "vmul.i8 q0, q0, q2\n\t" + "vpadd.i8 d0, d0, d1\n\t" + "vpadd.i8 d2, d2, d3\n\t" + "vpadd.i8 d0, d0, d2\n\t" + "vst1.32 {d0[0]}, [%2]!\n\t" + "subs %3, %3, #4\n\t" + "bne neon32_armv7_delta8_rgb32_iter\n\t" + : + : "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (multiplier) + : "%r12", "%q0", "%q1", "%q2", "cc", "memory" + ); +#else + Panic("Neon function called on a non-ARM platform or Neon code is absent"); +#endif +} + +/* RGB32: RGBA Neon for AArch32 */ +void neon32_armv7_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x00010502); +} + +/* RGB32: BGRA Neon for AArch32 */ +void neon32_armv7_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x00020501); +} + +/* RGB32: ARGB Neon for AArch32 */ +void neon32_armv7_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x01050200); +} + +/* RGB32: ABGR Neon for AArch32 */ +void neon32_armv7_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x02050100); +} + /* Grayscale SSE2 */ #if defined(__i386__) || defined(__x86_64__) __attribute__((noinline,__target__("sse2"))) @@ -3766,25 +3939,31 @@ void sse2_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, #endif } -/* RGB32: RGBA SSSE3 */ +/* RGB32 SSSE3 */ #if defined(__i386__) || defined(__x86_64__) __attribute__((noinline,__target__("ssse3"))) #endif -void ssse3_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { +void ssse3_delta8_rgb32(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, uint32_t multiplier) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - + + /* XMM0 - zero */ + /* XMM1 - col1 */ + /* XMM2 - col2 */ + /* XMM3 - multiplier */ + /* XMM4 - divide mask */ + __asm__ __volatile__ ( "mov $0x1F1F1F1F, %%eax\n\t" "movd %%eax, %%xmm4\n\t" "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %4, %%xmm5\n\t" + "mov %4, %%eax\n\t" + "movd %%eax, %%xmm3\n\t" + "pshufd $0x0, %%xmm3, %%xmm3\n\t" + "pxor %%xmm0, %%xmm0\n\t" "sub $0x10, %0\n\t" "sub $0x10, %1\n\t" "sub $0x4, %2\n\t" - "ssse3_delta8_rgba_iter:\n\t" + "ssse3_delta8_rgb32_iter:\n\t" "movdqa (%0,%3,4), %%xmm1\n\t" "movdqa (%1,%3,4), %%xmm2\n\t" "psrlq $0x3, %%xmm1\n\t" @@ -3792,200 +3971,41 @@ void ssse3_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result "pand %%xmm4, %%xmm1\n\t" "pand %%xmm4, %%xmm2\n\t" "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" + "pabsb %%xmm1, %%xmm1\n\t" + "pmaddubsw %%xmm3, %%xmm1\n\t" + "phaddw %%xmm0, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" "movd %%xmm1, %%eax\n\t" "movnti %%eax, (%2,%3)\n\t" "sub $0x4, %3\n\t" - "jnz ssse3_delta8_rgba_iter\n\t" + "jnz ssse3_delta8_rgb32_iter\n\t" : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" + : "r" (col1), "r" (col2), "r" (result), "r" (count), "g" (multiplier) + : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory" ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif } +/* RGB32: RGBA SSSE3 */ +void ssse3_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { + ssse3_delta8_rgb32(col1, col2, result, count, 0x00010502); +} + /* RGB32: BGRA SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif void ssse3_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %4, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "ssse3_delta8_bgra_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz ssse3_delta8_bgra_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif + ssse3_delta8_rgb32(col1, col2, result, count, 0x00020501); } /* RGB32: ARGB SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif void ssse3_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %4, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "ssse3_delta8_argb_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "psrld $0x8, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x18, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz ssse3_delta8_argb_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif + ssse3_delta8_rgb32(col1, col2, result, count, 0x01050200); } /* RGB32: ABGR SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif void ssse3_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %4, %%xmm5\n\t" - "sub $0x10, %0\n\t" - "sub $0x10, %1\n\t" - "sub $0x4, %2\n\t" - "ssse3_delta8_abgr_iter:\n\t" - "movdqa (%0,%3,4), %%xmm1\n\t" - "movdqa (%1,%3,4), %%xmm2\n\t" - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "psrld $0x8, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x18, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" - "movd %%xmm1, %%eax\n\t" - "movnti %%eax, (%2,%3)\n\t" - "sub $0x4, %3\n\t" - "jnz ssse3_delta8_abgr_iter\n\t" - : - : "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif + ssse3_delta8_rgb32(col1, col2, result, count, 0x02050100); } @@ -4187,55 +4207,68 @@ __attribute__((noinline)) void std_convert_yuyv_gray8(const uint8_t* col1, uint8 } } -/* RGBA to grayscale SSSE3 */ +/* RGB32 to grayscale SSSE3 */ #if defined(__i386__) || defined(__x86_64__) __attribute__((noinline,__target__("ssse3"))) #endif -void ssse3_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { +void ssse3_convert_rgb32_gray8(const uint8_t* col1, uint8_t* result, unsigned long count, uint32_t multiplier) { #if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) + /* XMM0 - zero */ + /* XMM1 - col1 */ + /* XMM3 - multiplier */ + /* XMM4 - divide mask */ + __asm__ __volatile__ ( "mov $0x1F1F1F1F, %%eax\n\t" "movd %%eax, %%xmm4\n\t" "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "mov $0xff, %%eax\n\t" - "movd %%eax, %%xmm0\n\t" - "pshufd $0x0, %%xmm0, %%xmm0\n\t" - "movdqa %3, %%xmm5\n\t" + "mov %3, %%eax\n\t" + "movd %%eax, %%xmm3\n\t" + "pshufd $0x0, %%xmm3, %%xmm3\n\t" + "pxor %%xmm0, %%xmm0\n\t" "sub $0x10, %0\n\t" "sub $0x4, %1\n\t" - "ssse3_convert_rgba_gray8_iter:\n\t" - "movdqa (%0,%2,4), %%xmm3\n\t" - "psrlq $0x3, %%xmm3\n\t" - "pand %%xmm4, %%xmm3\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x8, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "pslld $0x2, %%xmm2\n\t" - "paddd %%xmm1, %%xmm2\n\t" - "movdqa %%xmm3, %%xmm1\n\t" - "pand %%xmm0, %%xmm1\n\t" - "paddd %%xmm1, %%xmm1\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "movdqa %%xmm3, %%xmm2\n\t" - "psrld $0x10, %%xmm2\n\t" - "pand %%xmm0, %%xmm2\n\t" - "paddd %%xmm2, %%xmm1\n\t" - "pshufb %%xmm5, %%xmm1\n\t" + "ssse3_convert_rgb32_gray8_iter:\n\t" + "movdqa (%0,%2,4), %%xmm1\n\t" + "psrlq $0x3, %%xmm1\n\t" + "pand %%xmm4, %%xmm1\n\t" + "pmaddubsw %%xmm3, %%xmm1\n\t" + "phaddw %%xmm0, %%xmm1\n\t" + "packuswb %%xmm1, %%xmm1\n\t" "movd %%xmm1, %%eax\n\t" "movnti %%eax, (%1,%2)\n\t" "sub $0x4, %2\n\t" - "jnz ssse3_convert_rgba_gray8_iter\n\t" + "jnz ssse3_convert_rgb32_gray8_iter\n\t" : - : "r" (col1), "r" (result), "r" (count), "m" (*movemask) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "cc", "memory" + : "r" (col1), "r" (result), "r" (count), "g" (multiplier) + : "%eax", "%xmm0", "%xmm1", "%xmm3", "%xmm4", "cc", "memory" ); #else Panic("SSE function called on a non x86\\x86-64 platform"); #endif } +/* RGBA to grayscale SSSE3 */ +void ssse3_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { + ssse3_convert_rgb32_gray8(col1, result, count, 0x00010502); +} + +/* BGRA to grayscale SSSE3 */ +void ssse3_convert_bgra_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { + ssse3_convert_rgb32_gray8(col1, result, count, 0x00020501); +} + +/* ARGB to grayscale SSSE3 */ +void ssse3_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { + ssse3_convert_rgb32_gray8(col1, result, count, 0x01050200); +} + +/* ABGR to grayscale SSSE3 */ +void ssse3_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) { + ssse3_convert_rgb32_gray8(col1, result, count, 0x02050100); +} + /* Converts a YUYV image into grayscale by extracting the Y channel */ #if defined(__i386__) || defined(__x86_64__) __attribute__((noinline,__target__("ssse3"))) @@ -4855,871 +4888,3 @@ __attribute__((noinline)) void std_deinterlace_4field_abgr(uint8_t* col1, uint8_ pncurrent += 4; } } - -/* Grayscale SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_gray8(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { - -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - union { - uint32_t int32; - uint8_t int8a[4]; - } threshold_mask; - threshold_mask.int8a[0] = threshold; - threshold_mask.int8a[1] = 0; - threshold_mask.int8a[2] = threshold; - threshold_mask.int8a[3] = 0; - - unsigned long row_width = width; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - /* Load the threshold */ - "mov %5, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_gray8:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "pmaxub %%xmm2, %%xmm1\n\t" - "pminub %%xmm5, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "pmaxub %%xmm2, %%xmm1\n\t" - "pminub %%xmm6, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - "movdqa %%xmm1, %%xmm2\n\t" - - /* Do the comparison on words instead of bytes because we don't have unsigned comparison */ - "punpcklbw %%xmm0, %%xmm1\n\t" // Expand pixels 0-7 into words into xmm1 - "punpckhbw %%xmm0, %%xmm2\n\t" // Expand pixels 8-15 into words into xmm2 - "pcmpgtw %%xmm4, %%xmm1\n\t" // Compare average delta with threshold for pixels 0-7 - "pcmpgtw %%xmm4, %%xmm2\n\t" // Compare average delta with threshold for pixels 8-15 - "packsswb %%xmm2, %%xmm1\n\t" // Pack the comparison results into xmm1 - - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_gray8\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_gray8\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "pmaxub %%xmm2, %%xmm1\n\t" - "pminub %%xmm5, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "pmaxub %%xmm2, %%xmm1\n\t" - "pminub %%xmm6, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - "movdqa %%xmm1, %%xmm2\n\t" - - /* Do the comparison on words instead of bytes because we don't have unsigned comparison */ - "punpcklbw %%xmm0, %%xmm1\n\t" // Expand pixels 0-7 into words into xmm1 - "punpckhbw %%xmm0, %%xmm2\n\t" // Expand pixels 8-15 into words into xmm2 - "pcmpgtw %%xmm4, %%xmm1\n\t" // Compare average delta with threshold for pixels 0-7 - "pcmpgtw %%xmm4, %%xmm2\n\t" // Compare average delta with threshold for pixels 8-15 - "packsswb %%xmm2, %%xmm1\n\t" // Pack the comparison results into xmm1 - - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_mask.int32) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} - -/* RGBA SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_rgba(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - __attribute__((aligned(16))) static const uint8_t movemask2[16] = {1,1,1,1,1,0,0,2,9,9,9,9,9,8,8,10}; - - const uint32_t threshold_val = threshold; - - unsigned long row_width = width*4; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "movdqa %6, %%xmm3\n\t" - "mov %5, %%eax\n\t" -#if defined(__x86_64__) - "movd %%eax, %%xmm8\n\t" - "pshufd $0x0, %%xmm8, %%xmm8\n\t" -#endif - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_rgba:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_rgba\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_rgba\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_val), "m" (*movemask2) -#if defined(__x86_64__) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "cc", "memory" -#else - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" -#endif - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} - -/* BGRA SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_bgra(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - __attribute__((aligned(16))) static const uint8_t movemask2[16] = {1,1,1,1,1,2,2,0,9,9,9,9,9,10,10,8}; - - const uint32_t threshold_val = threshold; - - unsigned long row_width = width*4; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "movdqa %6, %%xmm3\n\t" - "mov %5, %%eax\n\t" -#if defined(__x86_64__) - "movd %%eax, %%xmm8\n\t" - "pshufd $0x0, %%xmm8, %%xmm8\n\t" -#endif - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_bgra:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_bgra\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_bgra\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_val), "m" (*movemask2) -#if defined(__x86_64__) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "cc", "memory" -#else - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" -#endif - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} - -/* ARGB SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_argb(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - __attribute__((aligned(16))) static const uint8_t movemask2[16] = {2,2,2,2,2,1,1,3,10,10,10,10,10,9,9,11}; - - const uint32_t threshold_val = threshold; - - unsigned long row_width = width*4; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "movdqa %6, %%xmm3\n\t" - "mov %5, %%eax\n\t" -#if defined(__x86_64__) - "movd %%eax, %%xmm8\n\t" - "pshufd $0x0, %%xmm8, %%xmm8\n\t" -#endif - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_argb:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_argb\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_argb\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_val), "m" (*movemask2) -#if defined(__x86_64__) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "cc", "memory" -#else - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" -#endif - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} - -/* ABGR SSSE3 */ -#if defined(__i386__) || defined(__x86_64__) -__attribute__((noinline,__target__("ssse3"))) -#endif -void ssse3_deinterlace_4field_abgr(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) { -#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE)) - __attribute__((aligned(16))) static const uint8_t movemask2[16] = {2,2,2,2,2,3,3,1,10,10,10,10,10,11,11,9}; - - const uint32_t threshold_val = threshold; - - unsigned long row_width = width*4; - uint8_t* max_ptr = col1 + (row_width * (height-2)); - uint8_t* max_ptr2 = col1 + row_width; - - __asm__ __volatile__ ( - "mov $0x1F1F1F1F, %%eax\n\t" - "movd %%eax, %%xmm4\n\t" - "pshufd $0x0, %%xmm4, %%xmm4\n\t" - "movdqa %6, %%xmm3\n\t" - "mov %5, %%eax\n\t" -#if defined(__x86_64__) - "movd %%eax, %%xmm8\n\t" - "pshufd $0x0, %%xmm8, %%xmm8\n\t" -#endif - /* Zero the temporary register */ - "pxor %%xmm0, %%xmm0\n\t" - - "algo_ssse3_deinterlace_4field_abgr:\n\t" - - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "movdqa (%0,%4), %%xmm2\n\t" // Load pbelow - "pavgb %%xmm5, %%xmm2\n\t" // Average pabove and pbelow - "pand %%xmm1, %%xmm2\n\t" // Filter out pixels in avg that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm2, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - - "sub %4, %0\n\t" // Restore pcurrent to pabove - "sub %4, %1\n\t" // Restore pncurrent to pnabove - - /* Next pixels */ - "add $0x10, %0\n\t" // Add 16 to pcurrent - "add $0x10, %1\n\t" // Add 16 to pncurrent - - /* Check if we reached the row end */ - "cmp %2, %0\n\t" - "jb algo_ssse3_deinterlace_4field_abgr\n\t" // Go for another iteration - - /* Next row */ - "add %4, %0\n\t" // Add width to pcurrent - "add %4, %1\n\t" // Add width to pncurrent - "mov %0, %2\n\t" - "add %4, %2\n\t" // Add width to max_ptr2 - - /* Check if we reached the end */ - "cmp %3, %0\n\t" - "jb algo_ssse3_deinterlace_4field_abgr\n\t" // Go for another iteration - - /* Special case for the last line */ - /* Load pabove into xmm1 and pnabove into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm5\n\t" /* Keep backup of pabove in xmm5 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - "movdqa %%xmm1, %%xmm7\n\t" /* Backup of delta2 in xmm7 for now */ - - /* Next row */ - "add %4, %0\n\t" - "add %4, %1\n\t" - - /* Load pcurrent into xmm1 and pncurrent into xmm2 */ - "movdqa (%0), %%xmm1\n\t" - "movdqa (%1), %%xmm2\n\t" - "movdqa %%xmm1, %%xmm6\n\t" /* Keep backup of pcurrent in xmm6 */ - "psrlq $0x3, %%xmm1\n\t" - "psrlq $0x3, %%xmm2\n\t" - "pand %%xmm4, %%xmm1\n\t" - "pand %%xmm4, %%xmm2\n\t" - "psubb %%xmm2, %%xmm1\n\t" - "pabsb %%xmm1, %%xmm2\n\t" - "movdqa %%xmm2, %%xmm1\n\t" - "punpckldq %%xmm1, %%xmm1\n\t" - "pshufb %%xmm3, %%xmm1\n\t" - "psadbw %%xmm0, %%xmm1\n\t" - "punpckhdq %%xmm2, %%xmm2\n\t" - "pshufb %%xmm3, %%xmm2\n\t" - "psadbw %%xmm0, %%xmm2\n\t" - "packuswb %%xmm2, %%xmm1\n\t" - - "pavgb %%xmm7, %%xmm1\n\t" // Average the two deltas together - -#if defined(__x86_64__) - "pcmpgtd %%xmm8, %%xmm1\n\t" // Compare average delta with the threshold -#else - "movd %%eax, %%xmm7\n\t" // Setup the threshold - "pshufd $0x0, %%xmm7, %%xmm7\n\t" - - "pcmpgtd %%xmm7, %%xmm1\n\t" // Compare average delta with the threshold -#endif - "pand %%xmm1, %%xmm5\n\t" // Filter out pixels in pabove that shouldn't be copied - "pandn %%xmm6, %%xmm1\n\t" // Filter out pixels in pcurrent that should be replaced - - "por %%xmm5, %%xmm1\n\t" // Put the new values in pcurrent - "movntdq %%xmm1, (%0)\n\t" // Write pcurrent - : - : "r" (col1), "r" (col2), "r" (max_ptr2), "r" (max_ptr), "r" (row_width), "m" (threshold_val), "m" (*movemask2) -#if defined(__x86_64__) - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "cc", "memory" -#else - : "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "cc", "memory" -#endif - ); -#else - Panic("SSE function called on a non x86\\x86-64 platform"); -#endif -} diff --git a/src/zm_image.h b/src/zm_image.h index 0a01f1f18..0a90c5c23 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -122,8 +122,8 @@ protected: } public: - enum { CHAR_HEIGHT=11, CHAR_WIDTH=6 }; - enum { LINE_HEIGHT=CHAR_HEIGHT+0 }; + enum { ZM_CHAR_HEIGHT=11, ZM_CHAR_WIDTH=6 }; + enum { LINE_HEIGHT=ZM_CHAR_HEIGHT+0 }; protected: static bool initialised; @@ -264,6 +264,7 @@ public: /* Blend functions */ void sse2_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); void std_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); +void neon32_armv7_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); void std_blend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent); /* Delta functions */ @@ -274,6 +275,11 @@ void std_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, void std_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void std_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void std_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); +void neon32_armv7_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void sse2_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void sse2_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void sse2_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); @@ -293,6 +299,9 @@ void std_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long void std_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); void std_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); void ssse3_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); +void ssse3_convert_bgra_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); +void ssse3_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); +void ssse3_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); void ssse3_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count); void zm_convert_yuyv_rgb(const uint8_t* col1, uint8_t* result, unsigned long count); void zm_convert_yuyv_rgba(const uint8_t* col1, uint8_t* result, unsigned long count); @@ -309,8 +318,3 @@ void std_deinterlace_4field_rgba(uint8_t* col1, uint8_t* col2, unsigned int thre void std_deinterlace_4field_bgra(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); void std_deinterlace_4field_argb(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); void std_deinterlace_4field_abgr(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_gray8(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_rgba(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_bgra(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_argb(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); -void ssse3_deinterlace_4field_abgr(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height); diff --git a/src/zm_libvlc_camera.cpp b/src/zm_libvlc_camera.cpp index 186af1144..5d4b497ce 100644 --- a/src/zm_libvlc_camera.cpp +++ b/src/zm_libvlc_camera.cpp @@ -27,11 +27,11 @@ void* LibvlcLockBuffer(void* opaque, void** planes) { LibvlcPrivateData* data = (LibvlcPrivateData*)opaque; data->mutex.lock(); - + uint8_t* buffer = data->buffer; data->buffer = data->prevBuffer; data->prevBuffer = buffer; - + *planes = data->buffer; return NULL; } @@ -39,7 +39,7 @@ void* LibvlcLockBuffer(void* opaque, void** planes) void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) { LibvlcPrivateData* data = (LibvlcPrivateData*)opaque; - + bool newFrame = false; for(uint32_t i = 0; i < data->bufferSize; i++) { @@ -50,7 +50,7 @@ void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) } } data->mutex.unlock(); - + time_t now; time(&now); // Return frames slightly faster than 1fps (if time() supports greater than one second resolution) @@ -61,8 +61,8 @@ void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) } } -LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - Camera( p_id, LIBVLC_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), +LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + Camera( p_id, LIBVLC_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), mPath( p_path ), mMethod( p_method ), mOptions( p_options ) @@ -89,7 +89,7 @@ LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::stri } else { Panic("Unexpected colours: %d",colours); } - + if ( capture ) { Initialise(); @@ -143,9 +143,9 @@ void LibvlcCamera::Terminate() int LibvlcCamera::PrimeCapture() { Info("Priming capture from %s", mPath.c_str()); - + StringVector opVect = split(Options(), ","); - + // Set transport method as specified by method field, rtpUni is default if ( Method() == "rtpMulti" ) opVect.push_back("--rtsp-mcast"); @@ -168,11 +168,11 @@ int LibvlcCamera::PrimeCapture() mLibvlcInstance = libvlc_new (opVect.size(), (const char* const*)mOptArgV); if(mLibvlcInstance == NULL) Fatal("Unable to create libvlc instance due to: %s", libvlc_errmsg()); - + mLibvlcMedia = libvlc_media_new_location(mLibvlcInstance, mPath.c_str()); if(mLibvlcMedia == NULL) Fatal("Unable to open input %s due to: %s", mPath.c_str(), libvlc_errmsg()); - + mLibvlcMediaPlayer = libvlc_media_player_new_from_media(mLibvlcMedia); if(mLibvlcMediaPlayer == NULL) Fatal("Unable to create player for %s due to: %s", mPath.c_str(), libvlc_errmsg()); @@ -184,16 +184,16 @@ int LibvlcCamera::PrimeCapture() // Libvlc wants 32 byte alignment for images (should in theory do this for all image lines) mLibvlcData.buffer = (uint8_t*)zm_mallocaligned(32, mLibvlcData.bufferSize); mLibvlcData.prevBuffer = (uint8_t*)zm_mallocaligned(32, mLibvlcData.bufferSize); - + mLibvlcData.newImage.setValueImmediate(false); libvlc_media_player_play(mLibvlcMediaPlayer); - + return(0); } int LibvlcCamera::PreCapture() -{ +{ return(0); } @@ -207,7 +207,21 @@ int LibvlcCamera::Capture( Image &image ) image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); mLibvlcData.newImage.setValueImmediate(false); mLibvlcData.mutex.unlock(); - + + return (0); +} + +// Should not return -1 as cancels capture. Always wait for image if available. +int LibvlcCamera::CaptureAndRecord(Image &image, bool recording, char* event_directory) +{ + while(!mLibvlcData.newImage.getValueImmediate()) + mLibvlcData.newImage.getUpdatedValue(1); + + mLibvlcData.mutex.lock(); + image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); + mLibvlcData.newImage.setValueImmediate(false); + mLibvlcData.mutex.unlock(); + return (0); } diff --git a/src/zm_libvlc_camera.h b/src/zm_libvlc_camera.h index 5f571adea..d1df60f3b 100644 --- a/src/zm_libvlc_camera.h +++ b/src/zm_libvlc_camera.h @@ -57,7 +57,7 @@ protected: libvlc_media_player_t *mLibvlcMediaPlayer; public: - LibvlcCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + LibvlcCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~LibvlcCamera(); const std::string &Path() const { return( mPath ); } @@ -70,6 +70,7 @@ public: int PrimeCapture(); int PreCapture(); int Capture( Image &image ); + int CaptureAndRecord( Image &image, bool recording, char* event_directory ); int PostCapture(); }; diff --git a/src/zm_local_camera.cpp b/src/zm_local_camera.cpp index e82115e70..0c68b555e 100644 --- a/src/zm_local_camera.cpp +++ b/src/zm_local_camera.cpp @@ -55,7 +55,7 @@ static int vidioctl( int fd, int request, void *arg ) static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette ) { _AVPIXELFORMAT pixFormat = AV_PIX_FMT_NONE; - + #if ZM_HAS_V4L2 if ( v4l_version == 2 ) { @@ -114,27 +114,27 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette case V4L2_PIX_FMT_UYVY : pixFormat = AV_PIX_FMT_UYVY422; break; - // These don't seem to have ffmpeg equivalents - // See if you can match any of the ones in the default clause below!? + // These don't seem to have ffmpeg equivalents + // See if you can match any of the ones in the default clause below!? case V4L2_PIX_FMT_RGB332 : case V4L2_PIX_FMT_RGB555X : case V4L2_PIX_FMT_RGB565X : - //case V4L2_PIX_FMT_Y16 : - //case V4L2_PIX_FMT_PAL8 : + //case V4L2_PIX_FMT_Y16 : + //case V4L2_PIX_FMT_PAL8 : case V4L2_PIX_FMT_YVU410 : case V4L2_PIX_FMT_YVU420 : case V4L2_PIX_FMT_Y41P : - //case V4L2_PIX_FMT_YUV555 : - //case V4L2_PIX_FMT_YUV565 : - //case V4L2_PIX_FMT_YUV32 : + //case V4L2_PIX_FMT_YUV555 : + //case V4L2_PIX_FMT_YUV565 : + //case V4L2_PIX_FMT_YUV32 : case V4L2_PIX_FMT_NV12 : case V4L2_PIX_FMT_NV21 : case V4L2_PIX_FMT_YYUV : case V4L2_PIX_FMT_HI240 : case V4L2_PIX_FMT_HM12 : - //case V4L2_PIX_FMT_SBGGR8 : - //case V4L2_PIX_FMT_SGBRG8 : - //case V4L2_PIX_FMT_SBGGR16 : + //case V4L2_PIX_FMT_SBGGR8 : + //case V4L2_PIX_FMT_SGBRG8 : + //case V4L2_PIX_FMT_SBGGR16 : case V4L2_PIX_FMT_DV : case V4L2_PIX_FMT_MPEG : case V4L2_PIX_FMT_WNVA : @@ -142,43 +142,43 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette case V4L2_PIX_FMT_PWC1 : case V4L2_PIX_FMT_PWC2 : case V4L2_PIX_FMT_ET61X251 : - //case V4L2_PIX_FMT_SPCA501 : - //case V4L2_PIX_FMT_SPCA505 : - //case V4L2_PIX_FMT_SPCA508 : - //case V4L2_PIX_FMT_SPCA561 : - //case V4L2_PIX_FMT_PAC207 : - //case V4L2_PIX_FMT_PJPG : - //case V4L2_PIX_FMT_YVYU : + //case V4L2_PIX_FMT_SPCA501 : + //case V4L2_PIX_FMT_SPCA505 : + //case V4L2_PIX_FMT_SPCA508 : + //case V4L2_PIX_FMT_SPCA561 : + //case V4L2_PIX_FMT_PAC207 : + //case V4L2_PIX_FMT_PJPG : + //case V4L2_PIX_FMT_YVYU : default : - { - Fatal( "Can't find swscale format for palette %d", palette ); - break; - // These are all spare and may match some of the above - pixFormat = AV_PIX_FMT_YUVJ420P; - pixFormat = AV_PIX_FMT_YUVJ422P; - pixFormat = AV_PIX_FMT_UYVY422; - pixFormat = AV_PIX_FMT_UYYVYY411; - pixFormat = AV_PIX_FMT_BGR565; - pixFormat = AV_PIX_FMT_BGR555; - pixFormat = AV_PIX_FMT_BGR8; - pixFormat = AV_PIX_FMT_BGR4; - pixFormat = AV_PIX_FMT_BGR4_BYTE; - pixFormat = AV_PIX_FMT_RGB8; - pixFormat = AV_PIX_FMT_RGB4; - pixFormat = AV_PIX_FMT_RGB4_BYTE; - pixFormat = AV_PIX_FMT_NV12; - pixFormat = AV_PIX_FMT_NV21; - pixFormat = AV_PIX_FMT_RGB32_1; - pixFormat = AV_PIX_FMT_BGR32_1; - pixFormat = AV_PIX_FMT_GRAY16BE; - pixFormat = AV_PIX_FMT_GRAY16LE; - pixFormat = AV_PIX_FMT_YUV440P; - pixFormat = AV_PIX_FMT_YUVJ440P; - pixFormat = AV_PIX_FMT_YUVA420P; - //pixFormat = AV_PIX_FMT_VDPAU_H264; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; - } + { + Fatal( "Can't find swscale format for palette %d", palette ); + break; + // These are all spare and may match some of the above + pixFormat = AV_PIX_FMT_YUVJ420P; + pixFormat = AV_PIX_FMT_YUVJ422P; + pixFormat = AV_PIX_FMT_UYVY422; + pixFormat = AV_PIX_FMT_UYYVYY411; + pixFormat = AV_PIX_FMT_BGR565; + pixFormat = AV_PIX_FMT_BGR555; + pixFormat = AV_PIX_FMT_BGR8; + pixFormat = AV_PIX_FMT_BGR4; + pixFormat = AV_PIX_FMT_BGR4_BYTE; + pixFormat = AV_PIX_FMT_RGB8; + pixFormat = AV_PIX_FMT_RGB4; + pixFormat = AV_PIX_FMT_RGB4_BYTE; + pixFormat = AV_PIX_FMT_NV12; + pixFormat = AV_PIX_FMT_NV21; + pixFormat = AV_PIX_FMT_RGB32_1; + pixFormat = AV_PIX_FMT_BGR32_1; + pixFormat = AV_PIX_FMT_GRAY16BE; + pixFormat = AV_PIX_FMT_GRAY16LE; + pixFormat = AV_PIX_FMT_YUV440P; + pixFormat = AV_PIX_FMT_YUVJ440P; + pixFormat = AV_PIX_FMT_YUVA420P; + //pixFormat = AV_PIX_FMT_VDPAU_H264; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; + } } } #endif // ZM_HAS_V4L2 @@ -188,17 +188,17 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette switch( palette ) { case VIDEO_PALETTE_RGB32 : - if(BigEndian) - pixFormat = AV_PIX_FMT_ARGB; - else - pixFormat = AV_PIX_FMT_BGRA; - break; + if(BigEndian) + pixFormat = AV_PIX_FMT_ARGB; + else + pixFormat = AV_PIX_FMT_BGRA; + break; case VIDEO_PALETTE_RGB24 : - if(BigEndian) - pixFormat = AV_PIX_FMT_RGB24; - else - pixFormat = AV_PIX_FMT_BGR24; - break; + if(BigEndian) + pixFormat = AV_PIX_FMT_RGB24; + else + pixFormat = AV_PIX_FMT_BGR24; + break; case VIDEO_PALETTE_GREY : pixFormat = AV_PIX_FMT_GRAY8; break; @@ -219,36 +219,36 @@ static _AVPIXELFORMAT getFfPixFormatFromV4lPalette( int v4l_version, int palette pixFormat = AV_PIX_FMT_YUV420P; break; default : - { - Fatal( "Can't find swscale format for palette %d", palette ); - break; - // These are all spare and may match some of the above - pixFormat = AV_PIX_FMT_YUVJ420P; - pixFormat = AV_PIX_FMT_YUVJ422P; - pixFormat = AV_PIX_FMT_YUVJ444P; - pixFormat = AV_PIX_FMT_UYVY422; - pixFormat = AV_PIX_FMT_UYYVYY411; - pixFormat = AV_PIX_FMT_BGR565; - pixFormat = AV_PIX_FMT_BGR555; - pixFormat = AV_PIX_FMT_BGR8; - pixFormat = AV_PIX_FMT_BGR4; - pixFormat = AV_PIX_FMT_BGR4_BYTE; - pixFormat = AV_PIX_FMT_RGB8; - pixFormat = AV_PIX_FMT_RGB4; - pixFormat = AV_PIX_FMT_RGB4_BYTE; - pixFormat = AV_PIX_FMT_NV12; - pixFormat = AV_PIX_FMT_NV21; - pixFormat = AV_PIX_FMT_RGB32_1; - pixFormat = AV_PIX_FMT_BGR32_1; - pixFormat = AV_PIX_FMT_GRAY16BE; - pixFormat = AV_PIX_FMT_GRAY16LE; - pixFormat = AV_PIX_FMT_YUV440P; - pixFormat = AV_PIX_FMT_YUVJ440P; - pixFormat = AV_PIX_FMT_YUVA420P; - //pixFormat = AV_PIX_FMT_VDPAU_H264; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; - //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; - } + { + Fatal( "Can't find swscale format for palette %d", palette ); + break; + // These are all spare and may match some of the above + pixFormat = AV_PIX_FMT_YUVJ420P; + pixFormat = AV_PIX_FMT_YUVJ422P; + pixFormat = AV_PIX_FMT_YUVJ444P; + pixFormat = AV_PIX_FMT_UYVY422; + pixFormat = AV_PIX_FMT_UYYVYY411; + pixFormat = AV_PIX_FMT_BGR565; + pixFormat = AV_PIX_FMT_BGR555; + pixFormat = AV_PIX_FMT_BGR8; + pixFormat = AV_PIX_FMT_BGR4; + pixFormat = AV_PIX_FMT_BGR4_BYTE; + pixFormat = AV_PIX_FMT_RGB8; + pixFormat = AV_PIX_FMT_RGB4; + pixFormat = AV_PIX_FMT_RGB4_BYTE; + pixFormat = AV_PIX_FMT_NV12; + pixFormat = AV_PIX_FMT_NV21; + pixFormat = AV_PIX_FMT_RGB32_1; + pixFormat = AV_PIX_FMT_BGR32_1; + pixFormat = AV_PIX_FMT_GRAY16BE; + pixFormat = AV_PIX_FMT_GRAY16LE; + pixFormat = AV_PIX_FMT_YUV440P; + pixFormat = AV_PIX_FMT_YUVJ440P; + pixFormat = AV_PIX_FMT_YUVA420P; + //pixFormat = AV_PIX_FMT_VDPAU_H264; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG1; + //pixFormat = AV_PIX_FMT_VDPAU_MPEG2; + } } } #endif // ZM_HAS_V4L1 @@ -286,8 +286,26 @@ AVFrame **LocalCamera::capturePictures = 0; LocalCamera *LocalCamera::last_camera = NULL; -LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, int p_standard, bool p_v4l_multi_buffer, unsigned int p_v4l_captures_per_frame, const std::string &p_method, int p_width, int p_height, int p_colours, int p_palette, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, unsigned int p_extras) : - Camera( p_id, LOCAL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), +LocalCamera::LocalCamera( + int p_id, + const std::string &p_device, + int p_channel, + int p_standard, + bool p_v4l_multi_buffer, + unsigned int p_v4l_captures_per_frame, + const std::string &p_method, + int p_width, + int p_height, + int p_colours, + int p_palette, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio, + unsigned int p_extras) : + Camera( p_id, LOCAL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), device( p_device ), channel( p_channel ), standard( p_standard ), @@ -301,7 +319,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, v4l_version = (p_method=="v4l2"?2:1); v4l_multi_buffer = p_v4l_multi_buffer; v4l_captures_per_frame = p_v4l_captures_per_frame; - + if ( capture ) { if ( device_prime ) @@ -322,9 +340,9 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, // We are the second, or subsequent, input using this channel channel_prime = false; } - + } - + /* The V4L1 API doesn't care about endianness, we need to check the endianness of the machine */ uint32_t checkval = 0xAABBCCDD; if(*(unsigned char*)&checkval == 0xDD) { @@ -337,7 +355,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, Error("Unable to detect the processor's endianness. Assuming little-endian."); BigEndian = 0; } - + #if ZM_HAS_V4L2 if( v4l_version == 2 && palette == 0 ) { /* Use automatic format selection */ @@ -353,22 +371,22 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } } #endif - + if( capture ) { if ( last_camera ) { if ( (p_method == "v4l2" && v4l_version != 2) || (p_method == "v4l1" && v4l_version != 1) ) Fatal( "Different Video For Linux version used for monitors sharing same device" ); - + if ( standard != last_camera->standard ) Warning( "Different video standards defined for monitors sharing same device, results may be unpredictable or completely wrong" ); - + if ( palette != last_camera->palette ) Warning( "Different video palettes defined for monitors sharing same device, results may be unpredictable or completely wrong" ); - + if ( width != last_camera->width || height != last_camera->height ) Warning( "Different capture sizes defined for monitors sharing same device, results may be unpredictable or completely wrong" ); } - + #if HAVE_LIBSWSCALE /* Get ffmpeg pixel format based on capture palette and endianness */ capturePixFormat = getFfPixFormatFromV4lPalette( v4l_version, palette ); @@ -380,38 +398,38 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, #if ZM_HAS_V4L2 if ( v4l_version == 2 ) { /* Try to find a match for the selected palette and target colourspace */ - + /* RGB32 palette and 32bit target colourspace */ if(palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_RGB32) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_ARGB; - - /* BGR32 palette and 32bit target colourspace */ + + /* BGR32 palette and 32bit target colourspace */ } else if(palette == V4L2_PIX_FMT_BGR32 && colours == ZM_COLOUR_RGB32) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_BGRA; - - /* RGB24 palette and 24bit target colourspace */ + + /* RGB24 palette and 24bit target colourspace */ } else if(palette == V4L2_PIX_FMT_RGB24 && colours == ZM_COLOUR_RGB24) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_RGB; - - /* BGR24 palette and 24bit target colourspace */ + + /* BGR24 palette and 24bit target colourspace */ } else if(palette == V4L2_PIX_FMT_BGR24 && colours == ZM_COLOUR_RGB24) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_BGR; - - /* Grayscale palette and grayscale target colourspace */ + + /* Grayscale palette and grayscale target colourspace */ } else if(palette == V4L2_PIX_FMT_GREY && colours == ZM_COLOUR_GRAY8) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_NONE; - /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ + /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ } else { if( capture ) #if HAVE_LIBSWSCALE Info("No direct match for the selected palette (%c%c%c%c) and target colorspace (%d). Format conversion is required, performance penalty expected", (capturePixFormat)&0xff,((capturePixFormat>>8)&0xff),((capturePixFormat>>16)&0xff),((capturePixFormat>>24)&0xff), colours ); #else - Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); + Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); #endif #if HAVE_LIBSWSCALE /* Try using swscale for the conversion */ @@ -449,13 +467,13 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, if(colours == ZM_COLOUR_GRAY8 && palette == V4L2_PIX_FMT_YUYV) { conversion_type = 2; } - + /* JPEG */ if(palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { Debug(2,"Using JPEG image decoding"); conversion_type = 3; } - + if(conversion_type == 2) { Debug(2,"Using ZM for image conversion"); if(palette == V4L2_PIX_FMT_RGB32 && colours == ZM_COLOUR_GRAY8) { @@ -500,11 +518,11 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } #endif // ZM_HAS_V4L2 - /* V4L1 format matching */ + /* V4L1 format matching */ #if ZM_HAS_V4L1 if ( v4l_version == 1) { /* Try to find a match for the selected palette and target colourspace */ - + /* RGB32 palette and 32bit target colourspace */ if(palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_RGB32) { conversion_type = 0; @@ -513,8 +531,8 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } else { subpixelorder = ZM_SUBPIX_ORDER_BGRA; } - - /* RGB24 palette and 24bit target colourspace */ + + /* RGB24 palette and 24bit target colourspace */ } else if(palette == VIDEO_PALETTE_RGB24 && colours == ZM_COLOUR_RGB24) { conversion_type = 0; if(BigEndian) { @@ -522,12 +540,12 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } else { subpixelorder = ZM_SUBPIX_ORDER_BGR; } - - /* Grayscale palette and grayscale target colourspace */ + + /* Grayscale palette and grayscale target colourspace */ } else if(palette == VIDEO_PALETTE_GREY && colours == ZM_COLOUR_GRAY8) { conversion_type = 0; subpixelorder = ZM_SUBPIX_ORDER_NONE; - /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ + /* Unable to find a solution for the selected palette and target colourspace. Conversion required. Notify the user of performance penalty */ } else { if( capture ) Info("No direct match for the selected palette and target colorspace. Format conversion is required, performance penalty expected"); @@ -565,7 +583,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, if(colours == ZM_COLOUR_GRAY8 && (palette == VIDEO_PALETTE_YUYV || palette == VIDEO_PALETTE_YUV422)) { conversion_type = 2; } - + if(conversion_type == 2) { Debug(2,"Using ZM for image conversion"); if(palette == VIDEO_PALETTE_RGB32 && colours == ZM_COLOUR_GRAY8) { @@ -610,7 +628,7 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, } } } -#endif // ZM_HAS_V4L1 +#endif // ZM_HAS_V4L1 last_camera = this; Debug(3,"Selected subpixelorder: %d",subpixelorder); @@ -634,13 +652,13 @@ LocalCamera::LocalCamera( int p_id, const std::string &p_device, int p_channel, if( (unsigned int)pSize != imagesize) { Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize); } - + imgConversionContext = sws_getContext(width, height, capturePixFormat, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); - + if ( !imgConversionContext ) { Fatal( "Unable to initialise image scaling context" ); - } - + } + } #endif } @@ -649,13 +667,13 @@ LocalCamera::~LocalCamera() { if ( device_prime && capture ) Terminate(); - + #if HAVE_LIBSWSCALE /* Clean up swscale stuff */ if(capture && conversion_type == 1) { sws_freeContext(imgConversionContext); imgConversionContext = NULL; - + #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) av_frame_free( &tmpPicture ); #else @@ -726,23 +744,23 @@ void LocalCamera::Initialise() v4l2_data.fmt.fmt.pix.height = height; v4l2_data.fmt.fmt.pix.pixelformat = palette; - if ( (extras & 0xff) != 0 ) - { - v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff); - - if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) + if ( (extras & 0xff) != 0 ) { - Warning( "Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff) ); - v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY; + v4l2_data.fmt.fmt.pix.field = (v4l2_field)(extras & 0xff); + + if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) + { + Warning( "Failed to set V4L2 field to %d, falling back to auto", (extras & 0xff) ); + v4l2_data.fmt.fmt.pix.field = V4L2_FIELD_ANY; + if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) { + Fatal( "Failed to set video format: %s", strerror(errno) ); + } + } + } else { if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) { Fatal( "Failed to set video format: %s", strerror(errno) ); } } - } else { - if ( vidioctl( vid_fd, VIDIOC_S_FMT, &v4l2_data.fmt ) < 0 ) { - Fatal( "Failed to set video format: %s", strerror(errno) ); - } - } /* Note VIDIOC_S_FMT may change width and height. */ Debug( 4, " v4l2_data.fmt.type = %08x", v4l2_data.fmt.type ); @@ -764,39 +782,39 @@ void LocalCamera::Initialise() if (v4l2_data.fmt.fmt.pix.sizeimage < min) v4l2_data.fmt.fmt.pix.sizeimage = min; - v4l2_jpegcompression jpeg_comp; - if(palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { - if( vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0 ) { - if(errno == EINVAL) { - Debug(2, "JPEG compression options are not available"); - } else { - Warning("Failed to get JPEG compression options: %s", strerror(errno) ); - } - } else { - /* Set flags and quality. MJPEG should not have the huffman tables defined */ - if(palette == V4L2_PIX_FMT_MJPEG) { - jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI; - } else { - jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_DHT; - } - jpeg_comp.quality = 85; - - /* Update the JPEG options */ - if( vidioctl( vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp ) < 0 ) { - Warning("Failed to set JPEG compression options: %s", strerror(errno) ); - } else { - if(vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0) { - Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno) ); + v4l2_jpegcompression jpeg_comp; + if(palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) { + if( vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0 ) { + if(errno == EINVAL) { + Debug(2, "JPEG compression options are not available"); } else { - Debug(4, "JPEG quality: %d",jpeg_comp.quality); - Debug(4, "JPEG markers: %#x",jpeg_comp.jpeg_markers); + Warning("Failed to get JPEG compression options: %s", strerror(errno) ); + } + } else { + /* Set flags and quality. MJPEG should not have the huffman tables defined */ + if(palette == V4L2_PIX_FMT_MJPEG) { + jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI; + } else { + jpeg_comp.jpeg_markers |= V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI | V4L2_JPEG_MARKER_DHT; + } + jpeg_comp.quality = 85; + + /* Update the JPEG options */ + if( vidioctl( vid_fd, VIDIOC_S_JPEGCOMP, &jpeg_comp ) < 0 ) { + Warning("Failed to set JPEG compression options: %s", strerror(errno) ); + } else { + if(vidioctl( vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp ) < 0) { + Debug(3,"Failed to get updated JPEG compression options: %s", strerror(errno) ); + } else { + Debug(4, "JPEG quality: %d",jpeg_comp.quality); + Debug(4, "JPEG markers: %#x",jpeg_comp.jpeg_markers); + } } } } - } Debug( 3, "Setting up request buffers" ); - + memset( &v4l2_data.reqbufs, 0, sizeof(v4l2_data.reqbufs) ); if ( channel_count > 1 ) { Debug( 3, "Channel count is %d", channel_count ); @@ -857,23 +875,23 @@ void LocalCamera::Initialise() #if HAVE_LIBSWSCALE #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - capturePictures[i] = av_frame_alloc(); + capturePictures[i] = av_frame_alloc(); #else - capturePictures[i] = avcodec_alloc_frame(); + capturePictures[i] = avcodec_alloc_frame(); #endif - if ( !capturePictures[i] ) - Fatal( "Could not allocate picture" ); + if ( !capturePictures[i] ) + Fatal( "Could not allocate picture" ); #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(capturePictures[i]->data, - capturePictures[i]->linesize, - (uint8_t*)v4l2_data.buffers[i].start,capturePixFormat, - v4l2_data.fmt.fmt.pix.width, - v4l2_data.fmt.fmt.pix.height, 1); + av_image_fill_arrays(capturePictures[i]->data, + capturePictures[i]->linesize, + (uint8_t*)v4l2_data.buffers[i].start,capturePixFormat, + v4l2_data.fmt.fmt.pix.width, + v4l2_data.fmt.fmt.pix.height, 1); #else - avpicture_fill( (AVPicture *)capturePictures[i], - (uint8_t*)v4l2_data.buffers[i].start, capturePixFormat, - v4l2_data.fmt.fmt.pix.width, - v4l2_data.fmt.fmt.pix.height ); + avpicture_fill( (AVPicture *)capturePictures[i], + (uint8_t*)v4l2_data.buffers[i].start, capturePixFormat, + v4l2_data.fmt.fmt.pix.width, + v4l2_data.fmt.fmt.pix.height ); #endif #endif // HAVE_LIBSWSCALE } @@ -932,30 +950,30 @@ void LocalCamera::Initialise() switch (vid_pic.palette = palette) { case VIDEO_PALETTE_RGB32 : - { - vid_pic.depth = 32; - break; - } + { + vid_pic.depth = 32; + break; + } case VIDEO_PALETTE_RGB24 : - { - vid_pic.depth = 24; - break; - } + { + vid_pic.depth = 24; + break; + } case VIDEO_PALETTE_GREY : - { - vid_pic.depth = 8; - break; - } + { + vid_pic.depth = 8; + break; + } case VIDEO_PALETTE_RGB565 : case VIDEO_PALETTE_YUYV : case VIDEO_PALETTE_YUV422 : case VIDEO_PALETTE_YUV420P : case VIDEO_PALETTE_YUV422P : default: - { - vid_pic.depth = 16; - break; - } + { + vid_pic.depth = 16; + break; + } } if ( brightness >= 0 ) vid_pic.brightness = brightness; @@ -983,7 +1001,7 @@ void LocalCamera::Initialise() Debug( 4, "Old Y:%d", vid_win.y ); Debug( 4, "Old W:%d", vid_win.width ); Debug( 4, "Old H:%d", vid_win.height ); - + vid_win.x = 0; vid_win.y = 0; vid_win.width = width; @@ -1034,13 +1052,13 @@ void LocalCamera::Initialise() Fatal( "Could not allocate picture" ); #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(capturePictures[i]->data, - capturePictures[i]->linesize, - (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], - capturePixFormat, width, height, 1); + capturePictures[i]->linesize, + (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], + capturePixFormat, width, height, 1); #else avpicture_fill( (AVPicture *)capturePictures[i], - (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], - capturePixFormat, width, height ); + (unsigned char *)v4l1_data.bufptr+v4l1_data.frames.offsets[i], + capturePixFormat, width, height ); #endif } #endif // HAVE_LIBSWSCALE @@ -1079,7 +1097,7 @@ void LocalCamera::Initialise() Debug( 4, "New Y:%d", vid_win.y ); Debug( 4, "New W:%d", vid_win.width ); Debug( 4, "New H:%d", vid_win.height ); - + if ( ioctl( vid_fd, VIDIOCGPICT, &vid_pic) < 0 ) Fatal( "Failed to get window data: %s", strerror(errno) ); @@ -1100,7 +1118,7 @@ void LocalCamera::Terminate() { Debug( 3, "Terminating video stream" ); //enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - // enum v4l2_buf_type type = v4l2_data.fmt.type; + // enum v4l2_buf_type type = v4l2_data.fmt.type; enum v4l2_buf_type type = (v4l2_buf_type)v4l2_data.fmt.type; if ( vidioctl( vid_fd, VIDIOC_STREAMOFF, &type ) < 0 ) Error( "Failed to stop capture stream: %s", strerror(errno) ); @@ -1110,44 +1128,44 @@ void LocalCamera::Terminate() #if HAVE_LIBSWSCALE /* Free capture pictures */ #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &capturePictures[i] ); + av_frame_free( &capturePictures[i] ); #else - av_freep( &capturePictures[i] ); + av_freep( &capturePictures[i] ); #endif #endif if ( munmap( v4l2_data.buffers[i].start, v4l2_data.buffers[i].length ) < 0 ) Error( "Failed to munmap buffer %d: %s", i, strerror(errno) ); - } - + } + } else #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 - if ( v4l_version == 1 ) - { + if ( v4l_version == 1 ) + { #if HAVE_LIBSWSCALE - for(int i=0; i < v4l1_data.frames.frames; i++) { - /* Free capture pictures */ + for(int i=0; i < v4l1_data.frames.frames; i++) { + /* Free capture pictures */ #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &capturePictures[i] ); + av_frame_free( &capturePictures[i] ); #else - av_freep( &capturePictures[i] ); + av_freep( &capturePictures[i] ); #endif - } + } #endif - - Debug( 3, "Unmapping video buffers" ); - if ( munmap((char*)v4l1_data.bufptr, v4l1_data.frames.size) < 0 ) - Error( "Failed to munmap buffers: %s", strerror(errno) ); - delete[] v4l1_data.buffers; - } + Debug( 3, "Unmapping video buffers" ); + if ( munmap((char*)v4l1_data.bufptr, v4l1_data.frames.size) < 0 ) + Error( "Failed to munmap buffers: %s", strerror(errno) ); + + delete[] v4l1_data.buffers; + } #endif // ZM_HAS_V4L1 close( vid_fd ); - + } uint32_t LocalCamera::AutoSelectFormat(int p_colours) { @@ -1160,13 +1178,13 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { unsigned int nIndex = 0; //int nRet = 0; // compiler say it isn't used int enum_fd; - + /* Open the device */ if ((enum_fd = open( device.c_str(), O_RDWR, 0 )) < 0) { Error( "Automatic format selection failed to open video device %s: %s", device.c_str(), strerror(errno) ); return selected_palette; } - + /* Enumerate available formats */ memset(&fmtinfo, 0, sizeof(fmtinfo)); fmtinfo.index = nIndex; @@ -1176,15 +1194,15 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { /* Got a format. Copy it to the array */ strcpy(fmt_desc[nIndex], (const char*)(fmtinfo.description)); fmt_fcc[nIndex] = fmtinfo.pixelformat; - + Debug(6, "Got format: %s (%c%c%c%c) at index %d",fmt_desc[nIndex],fmt_fcc[nIndex]&0xff, (fmt_fcc[nIndex]>>8)&0xff, (fmt_fcc[nIndex]>>16)&0xff, (fmt_fcc[nIndex]>>24)&0xff ,nIndex); - + /* Proceed to the next index */ memset(&fmtinfo, 0, sizeof(fmtinfo)); fmtinfo.index = ++nIndex; fmtinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; } - + /* Select format */ int nIndexUsed = -1; unsigned int n_preferedformats = 0; @@ -1214,17 +1232,17 @@ uint32_t LocalCamera::AutoSelectFormat(int p_colours) { } } } - + /* Have we found a match? */ if(nIndexUsed >= 0) { /* Found a match */ selected_palette = fmt_fcc[nIndexUsed]; strcpy(palette_desc,fmt_desc[nIndexUsed]); } - + /* Close the device */ close(enum_fd); - + #endif /* ZM_HAS_V4L2 */ return selected_palette; } @@ -1288,25 +1306,25 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output+strlen(output), " Bus: %s\n", vid_cap.bus_info ); sprintf( output+strlen(output), " Version: %u.%u.%u\n", (vid_cap.version>>16)&0xff, (vid_cap.version>>8)&0xff, vid_cap.version&0xff ); sprintf( output+strlen(output), " Type: 0x%x\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s", vid_cap.capabilities, - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_CAPTURE, " ", "Supports", "Does not support", "video capture (X)" ), - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT, " ", "Supports", "Does not support", "video output" ), - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OVERLAY, " ", "Supports", "Does not support", "frame buffer overlay" ), - capString( vid_cap.capabilities&V4L2_CAP_VBI_CAPTURE, " ", "Supports", "Does not support", "VBI capture" ), - capString( vid_cap.capabilities&V4L2_CAP_VBI_OUTPUT, " ", "Supports", "Does not support", "VBI output" ), - capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_CAPTURE, " ", "Supports", "Does not support", "sliced VBI capture" ), - capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_OUTPUT, " ", "Supports", "Does not support", "sliced VBI output" ), + capString( vid_cap.capabilities&V4L2_CAP_VIDEO_CAPTURE, " ", "Supports", "Does not support", "video capture (X)" ), + capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT, " ", "Supports", "Does not support", "video output" ), + capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OVERLAY, " ", "Supports", "Does not support", "frame buffer overlay" ), + capString( vid_cap.capabilities&V4L2_CAP_VBI_CAPTURE, " ", "Supports", "Does not support", "VBI capture" ), + capString( vid_cap.capabilities&V4L2_CAP_VBI_OUTPUT, " ", "Supports", "Does not support", "VBI output" ), + capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_CAPTURE, " ", "Supports", "Does not support", "sliced VBI capture" ), + capString( vid_cap.capabilities&V4L2_CAP_SLICED_VBI_OUTPUT, " ", "Supports", "Does not support", "sliced VBI output" ), #ifdef V4L2_CAP_VIDEO_OUTPUT_OVERLAY - capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT_OVERLAY, " ", "Supports", "Does not support", "video output overlay" ), + capString( vid_cap.capabilities&V4L2_CAP_VIDEO_OUTPUT_OVERLAY, " ", "Supports", "Does not support", "video output overlay" ), #else // V4L2_CAP_VIDEO_OUTPUT_OVERLAY - "", + "", #endif // V4L2_CAP_VIDEO_OUTPUT_OVERLAY - capString( vid_cap.capabilities&V4L2_CAP_TUNER, " ", "Has", "Does not have", "tuner" ), - capString( vid_cap.capabilities&V4L2_CAP_AUDIO, " ", "Has", "Does not have", "audio in and/or out" ), - capString( vid_cap.capabilities&V4L2_CAP_RADIO, " ", "Has", "Does not have", "radio" ), - capString( vid_cap.capabilities&V4L2_CAP_READWRITE, " ", "Supports", "Does not support", "read/write i/o (X)" ), - capString( vid_cap.capabilities&V4L2_CAP_ASYNCIO, " ", "Supports", "Does not support", "async i/o" ), - capString( vid_cap.capabilities&V4L2_CAP_STREAMING, " ", "Supports", "Does not support", "streaming i/o (X)" ) - ); + capString( vid_cap.capabilities&V4L2_CAP_TUNER, " ", "Has", "Does not have", "tuner" ), + capString( vid_cap.capabilities&V4L2_CAP_AUDIO, " ", "Has", "Does not have", "audio in and/or out" ), + capString( vid_cap.capabilities&V4L2_CAP_RADIO, " ", "Has", "Does not have", "radio" ), + capString( vid_cap.capabilities&V4L2_CAP_READWRITE, " ", "Supports", "Does not support", "read/write i/o (X)" ), + capString( vid_cap.capabilities&V4L2_CAP_ASYNCIO, " ", "Supports", "Does not support", "async i/o" ), + capString( vid_cap.capabilities&V4L2_CAP_STREAMING, " ", "Supports", "Does not support", "streaming i/o (X)" ) + ); } else { @@ -1318,7 +1336,7 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } if ( verbose ) - sprintf( output+strlen(output), " Standards:\n" ); + sprintf( output+strlen(output), " Standards:\n" ); else sprintf( output+strlen(output), "S:" ); struct v4l2_standard standard; @@ -1347,14 +1365,14 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } } if ( verbose ) - sprintf( output+strlen(output), " %s\n", standard.name ); + sprintf( output+strlen(output), " %s\n", standard.name ); else sprintf( output+strlen(output), "%s/", standard.name ); } while ( standardIndex++ >= 0 ); if ( !verbose && output[strlen(output)-1] == '/') output[strlen(output)-1] = '|'; - + if ( verbose ) sprintf( output+strlen(output), " Formats:\n" ); else @@ -1385,7 +1403,7 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers } } if ( verbose ) - sprintf( output+strlen(output), " %s (%c%c%c%c)\n", format.description, format.pixelformat&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>24)&0xff ); + sprintf( output+strlen(output), " %s (%c%c%c%c)\n", format.description, format.pixelformat&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>24)&0xff ); else sprintf( output+strlen(output), "%c%c%c%c/", format.pixelformat&0xff, (format.pixelformat>>8)&0xff, (format.pixelformat>>16)&0xff, (format.pixelformat>>24)&0xff ); } @@ -1393,55 +1411,55 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers if ( !verbose ) output[strlen(output)-1] = '|'; - if(verbose) - sprintf( output+strlen(output), "Crop Capabilities\n" ); - - struct v4l2_cropcap cropcap; - memset( &cropcap, 0, sizeof(cropcap) ); - cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - if ( vidioctl( vid_fd, VIDIOC_CROPCAP, &cropcap ) < 0 ) - { - if(errno != EINVAL) { - /* Failed querying crop capability, write error to the log and continue as if crop is not supported */ - Error( "Failed to query crop capabilities: %s", strerror(errno) ); - } - - if(verbose) { - sprintf( output+strlen(output), " Cropping is not supported\n"); - } else { - /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ - sprintf( output+strlen(output), "B:%dx%d|",0,0); - } - } else { - struct v4l2_crop crop; - memset( &crop, 0, sizeof(crop) ); - crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - - if ( vidioctl( vid_fd, VIDIOC_G_CROP, &crop ) < 0 ) - { - if ( errno != EINVAL ) + if(verbose) + sprintf( output+strlen(output), "Crop Capabilities\n" ); + + struct v4l2_cropcap cropcap; + memset( &cropcap, 0, sizeof(cropcap) ); + cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if ( vidioctl( vid_fd, VIDIOC_CROPCAP, &cropcap ) < 0 ) { - /* Failed querying crop sizes, write error to the log and continue as if crop is not supported */ - Error( "Failed to query crop: %s", strerror(errno) ); - } - - if ( verbose ) { - sprintf( output+strlen(output), " Cropping is not supported\n"); + if(errno != EINVAL) { + /* Failed querying crop capability, write error to the log and continue as if crop is not supported */ + Error( "Failed to query crop capabilities: %s", strerror(errno) ); + } + + if(verbose) { + sprintf( output+strlen(output), " Cropping is not supported\n"); + } else { + /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ + sprintf( output+strlen(output), "B:%dx%d|",0,0); + } } else { - /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ - sprintf( output+strlen(output), "B:%dx%d|",0,0); - } - } else { - /* Cropping supported */ - if ( verbose ) { - sprintf( output+strlen(output), " Bounds: %d x %d\n", cropcap.bounds.width, cropcap.bounds.height ); - sprintf( output+strlen(output), " Default: %d x %d\n", cropcap.defrect.width, cropcap.defrect.height ); - sprintf( output+strlen(output), " Current: %d x %d\n", crop.c.width, crop.c.height ); - } else { - sprintf( output+strlen(output), "B:%dx%d|", cropcap.bounds.width, cropcap.bounds.height ); - } - } - } /* Crop code */ + struct v4l2_crop crop; + memset( &crop, 0, sizeof(crop) ); + crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if ( vidioctl( vid_fd, VIDIOC_G_CROP, &crop ) < 0 ) + { + if ( errno != EINVAL ) + { + /* Failed querying crop sizes, write error to the log and continue as if crop is not supported */ + Error( "Failed to query crop: %s", strerror(errno) ); + } + + if ( verbose ) { + sprintf( output+strlen(output), " Cropping is not supported\n"); + } else { + /* Send fake crop bounds to not confuse things parsing this, such as monitor probe */ + sprintf( output+strlen(output), "B:%dx%d|",0,0); + } + } else { + /* Cropping supported */ + if ( verbose ) { + sprintf( output+strlen(output), " Bounds: %d x %d\n", cropcap.bounds.width, cropcap.bounds.height ); + sprintf( output+strlen(output), " Default: %d x %d\n", cropcap.defrect.width, cropcap.defrect.height ); + sprintf( output+strlen(output), " Current: %d x %d\n", crop.c.width, crop.c.height ); + } else { + sprintf( output+strlen(output), "B:%dx%d|", cropcap.bounds.width, cropcap.bounds.height ); + } + } + } /* Crop code */ struct v4l2_input input; int inputIndex = 0; @@ -1505,16 +1523,16 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output, "Error, failed to switch to input %d: %s\n", input.index, strerror(errno) ); else sprintf( output, "error%d\n", errno ); - return( false ); + return( false ); } if ( verbose ) { sprintf( output+strlen(output), " Input %d\n", input.index ); - sprintf( output+strlen(output), " Name: %s\n", input.name ); - sprintf( output+strlen(output), " Type: %s\n", input.type==V4L2_INPUT_TYPE_TUNER?"Tuner":(input.type==V4L2_INPUT_TYPE_CAMERA?"Camera":"Unknown") ); - sprintf( output+strlen(output), " Audioset: %08x\n", input.audioset ); - sprintf( output+strlen(output), " Standards: 0x%llx\n", input.std ); + sprintf( output+strlen(output), " Name: %s\n", input.name ); + sprintf( output+strlen(output), " Type: %s\n", input.type==V4L2_INPUT_TYPE_TUNER?"Tuner":(input.type==V4L2_INPUT_TYPE_CAMERA?"Camera":"Unknown") ); + sprintf( output+strlen(output), " Audioset: %08x\n", input.audioset ); + sprintf( output+strlen(output), " Standards: 0x%llx\n", input.std ); } else { @@ -1525,10 +1543,10 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers if ( verbose ) { - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_POWER, "Power ", "off", "on", " (X)" ) ); - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_SIGNAL, "Signal ", "not detected", "detected", " (X)" ) ); - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_COLOR, "Colour Signal ", "not detected", "detected", "" ) ); - sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_H_LOCK, "Horizontal Lock ", "not detected", "detected", "" ) ); + sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_POWER, "Power ", "off", "on", " (X)" ) ); + sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_SIGNAL, "Signal ", "not detected", "detected", " (X)" ) ); + sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_COLOR, "Colour Signal ", "not detected", "detected", "" ) ); + sprintf( output+strlen(output), " %s", capString( input.status&V4L2_IN_ST_NO_H_LOCK, "Horizontal Lock ", "not detected", "detected", "" ) ); } else { @@ -1562,21 +1580,21 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output+strlen(output), "Video Capabilities\n" ); sprintf( output+strlen(output), " Name: %s\n", vid_cap.name ); sprintf( output+strlen(output), " Type: %d\n%s%s%s%s%s%s%s%s%s%s%s%s%s%s", vid_cap.type, - vid_cap.type&VID_TYPE_CAPTURE?" Can capture\n":"", - vid_cap.type&VID_TYPE_TUNER?" Can tune\n":"", - vid_cap.type&VID_TYPE_TELETEXT?" Does teletext\n":"", - vid_cap.type&VID_TYPE_OVERLAY?" Overlay onto frame buffer\n":"", - vid_cap.type&VID_TYPE_CHROMAKEY?" Overlay by chromakey\n":"", - vid_cap.type&VID_TYPE_CLIPPING?" Can clip\n":"", - vid_cap.type&VID_TYPE_FRAMERAM?" Uses the frame buffer memory\n":"", - vid_cap.type&VID_TYPE_SCALES?" Scalable\n":"", - vid_cap.type&VID_TYPE_MONOCHROME?" Monochrome only\n":"", - vid_cap.type&VID_TYPE_SUBCAPTURE?" Can capture subareas of the image\n":"", - vid_cap.type&VID_TYPE_MPEG_DECODER?" Can decode MPEG streams\n":"", - vid_cap.type&VID_TYPE_MPEG_ENCODER?" Can encode MPEG streams\n":"", - vid_cap.type&VID_TYPE_MJPEG_DECODER?" Can decode MJPEG streams\n":"", - vid_cap.type&VID_TYPE_MJPEG_ENCODER?" Can encode MJPEG streams\n":"" - ); + vid_cap.type&VID_TYPE_CAPTURE?" Can capture\n":"", + vid_cap.type&VID_TYPE_TUNER?" Can tune\n":"", + vid_cap.type&VID_TYPE_TELETEXT?" Does teletext\n":"", + vid_cap.type&VID_TYPE_OVERLAY?" Overlay onto frame buffer\n":"", + vid_cap.type&VID_TYPE_CHROMAKEY?" Overlay by chromakey\n":"", + vid_cap.type&VID_TYPE_CLIPPING?" Can clip\n":"", + vid_cap.type&VID_TYPE_FRAMERAM?" Uses the frame buffer memory\n":"", + vid_cap.type&VID_TYPE_SCALES?" Scalable\n":"", + vid_cap.type&VID_TYPE_MONOCHROME?" Monochrome only\n":"", + vid_cap.type&VID_TYPE_SUBCAPTURE?" Can capture subareas of the image\n":"", + vid_cap.type&VID_TYPE_MPEG_DECODER?" Can decode MPEG streams\n":"", + vid_cap.type&VID_TYPE_MPEG_ENCODER?" Can encode MPEG streams\n":"", + vid_cap.type&VID_TYPE_MJPEG_DECODER?" Can decode MJPEG streams\n":"", + vid_cap.type&VID_TYPE_MJPEG_ENCODER?" Can encode MJPEG streams\n":"" + ); sprintf( output+strlen(output), " Video Channels: %d\n", vid_cap.channels ); sprintf( output+strlen(output), " Audio Channels: %d\n", vid_cap.audios ); sprintf( output+strlen(output), " Maximum Width: %d\n", vid_cap.maxwidth ); @@ -1638,25 +1656,25 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers { sprintf( output+strlen(output), "Picture Attributes\n" ); sprintf( output+strlen(output), " Palette: %d - %s\n", vid_pic.palette, - vid_pic.palette==VIDEO_PALETTE_GREY?"Linear greyscale":( - vid_pic.palette==VIDEO_PALETTE_HI240?"High 240 cube (BT848)":( - vid_pic.palette==VIDEO_PALETTE_RGB565?"565 16 bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB24?"24bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB32?"32bit RGB":( - vid_pic.palette==VIDEO_PALETTE_RGB555?"555 15bit RGB":( - vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422 capture":( - vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( - vid_pic.palette==VIDEO_PALETTE_UYVY?"UVYV":( - vid_pic.palette==VIDEO_PALETTE_YUV420?"YUV420":( - vid_pic.palette==VIDEO_PALETTE_YUV411?"YUV411 capture":( - vid_pic.palette==VIDEO_PALETTE_RAW?"RAW capture (BT848)":( - vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( - vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422":( - vid_pic.palette==VIDEO_PALETTE_YUV422P?"YUV 4:2:2 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV411P?"YUV 4:1:1 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV420P?"YUV 4:2:0 Planar":( - vid_pic.palette==VIDEO_PALETTE_YUV410P?"YUV 4:1:0 Planar":"Unknown" - )))))))))))))))))); + vid_pic.palette==VIDEO_PALETTE_GREY?"Linear greyscale":( + vid_pic.palette==VIDEO_PALETTE_HI240?"High 240 cube (BT848)":( + vid_pic.palette==VIDEO_PALETTE_RGB565?"565 16 bit RGB":( + vid_pic.palette==VIDEO_PALETTE_RGB24?"24bit RGB":( + vid_pic.palette==VIDEO_PALETTE_RGB32?"32bit RGB":( + vid_pic.palette==VIDEO_PALETTE_RGB555?"555 15bit RGB":( + vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422 capture":( + vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( + vid_pic.palette==VIDEO_PALETTE_UYVY?"UVYV":( + vid_pic.palette==VIDEO_PALETTE_YUV420?"YUV420":( + vid_pic.palette==VIDEO_PALETTE_YUV411?"YUV411 capture":( + vid_pic.palette==VIDEO_PALETTE_RAW?"RAW capture (BT848)":( + vid_pic.palette==VIDEO_PALETTE_YUYV?"YUYV":( + vid_pic.palette==VIDEO_PALETTE_YUV422?"YUV422":( + vid_pic.palette==VIDEO_PALETTE_YUV422P?"YUV 4:2:2 Planar":( + vid_pic.palette==VIDEO_PALETTE_YUV411P?"YUV 4:1:1 Planar":( + vid_pic.palette==VIDEO_PALETTE_YUV420P?"YUV 4:2:0 Planar":( + vid_pic.palette==VIDEO_PALETTE_YUV410P?"YUV 4:1:0 Planar":"Unknown" + )))))))))))))))))); sprintf( output+strlen(output), " Colour Depth: %d\n", vid_pic.depth ); sprintf( output+strlen(output), " Brightness: %d\n", vid_pic.brightness ); sprintf( output+strlen(output), " Hue: %d\n", vid_pic.hue ); @@ -1695,19 +1713,19 @@ bool LocalCamera::GetCurrentSettings( const char *device, char *output, int vers sprintf( output+strlen(output), " Name: %s\n", vid_src.name ); sprintf( output+strlen(output), " Channel: %d\n", vid_src.channel ); sprintf( output+strlen(output), " Flags: %d\n%s%s", vid_src.flags, - vid_src.flags&VIDEO_VC_TUNER?" Channel has a tuner\n":"", - vid_src.flags&VIDEO_VC_AUDIO?" Channel has audio\n":"" - ); + vid_src.flags&VIDEO_VC_TUNER?" Channel has a tuner\n":"", + vid_src.flags&VIDEO_VC_AUDIO?" Channel has audio\n":"" + ); sprintf( output+strlen(output), " Type: %d - %s\n", vid_src.type, - vid_src.type==VIDEO_TYPE_TV?"TV":( - vid_src.type==VIDEO_TYPE_CAMERA?"Camera":"Unknown" - )); + vid_src.type==VIDEO_TYPE_TV?"TV":( + vid_src.type==VIDEO_TYPE_CAMERA?"Camera":"Unknown" + )); sprintf( output+strlen(output), " Format: %d - %s\n", vid_src.norm, - vid_src.norm==VIDEO_MODE_PAL?"PAL":( - vid_src.norm==VIDEO_MODE_NTSC?"NTSC":( - vid_src.norm==VIDEO_MODE_SECAM?"SECAM":( - vid_src.norm==VIDEO_MODE_AUTO?"AUTO":"Unknown" - )))); + vid_src.norm==VIDEO_MODE_PAL?"PAL":( + vid_src.norm==VIDEO_MODE_NTSC?"NTSC":( + vid_src.norm==VIDEO_MODE_SECAM?"SECAM":( + vid_src.norm==VIDEO_MODE_AUTO?"AUTO":"Unknown" + )))); } else { @@ -1746,7 +1764,7 @@ int LocalCamera::Brightness( int p_brightness ) Error( "Unable to query brightness: %s", strerror(errno) ) else Warning( "Brightness control is not supported" ) - //Info( "Brightness 1 %d", vid_control.value ); + //Info( "Brightness 1 %d", vid_control.value ); } else if ( p_brightness >= 0 ) { @@ -2032,7 +2050,7 @@ int LocalCamera::Capture( Image &image ) static uint8_t* directbuffer = NULL; static int capture_frame = -1; int buffer_bytesused = 0; - + int captures_per_frame = 1; if ( channel_count > 1 ) captures_per_frame = v4l_captures_per_frame; @@ -2040,8 +2058,8 @@ int LocalCamera::Capture( Image &image ) captures_per_frame = 1; Warning( "Invalid Captures Per Frame setting: %d", captures_per_frame ); } - - + + // Do the capture, unless we are the second or subsequent camera on a channel, in which case just reuse the buffer if ( channel_prime ) { @@ -2065,7 +2083,7 @@ int LocalCamera::Capture( Image &image ) Warning( "Capture failure, possible signal loss?: %s", strerror(errno) ) else Error( "Unable to capture frame %d: %s", vid_buf.index, strerror(errno) ) - return( -1 ); + return( -1 ); } v4l2_data.bufptr = &vid_buf; @@ -2086,9 +2104,9 @@ int LocalCamera::Capture( Image &image ) buffer_bytesused = v4l2_data.bufptr->bytesused; if((v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height)) { - Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d",v4l2_data.fmt.fmt.pix.width,v4l2_data.fmt.fmt.pix.height,width,height); + Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d",v4l2_data.fmt.fmt.pix.width,v4l2_data.fmt.fmt.pix.height,width,height); } - + } #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 @@ -2120,12 +2138,12 @@ int LocalCamera::Capture( Image &image ) buffer = v4l1_data.bufptr+v4l1_data.frames.offsets[capture_frame]; } #endif // ZM_HAS_V4L1 - } /* prime capture */ - + } /* prime capture */ + if(conversion_type != 0) { - + Debug( 3, "Performing format conversion" ); - + /* Request a writeable buffer of the target image */ directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); if(directbuffer == NULL) { @@ -2139,36 +2157,36 @@ int LocalCamera::Capture( Image &image ) /* Use swscale to convert the image directly into the shared memory */ #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(tmpPicture->data, - tmpPicture->linesize, directbuffer, - imagePixFormat, width, height, 1); + tmpPicture->linesize, directbuffer, + imagePixFormat, width, height, 1); #else avpicture_fill( (AVPicture *)tmpPicture, directbuffer, - imagePixFormat, width, height ); + imagePixFormat, width, height ); #endif sws_scale( imgConversionContext, capturePictures[capture_frame]->data, capturePictures[capture_frame]->linesize, 0, height, tmpPicture->data, tmpPicture->linesize ); } #endif if(conversion_type == 2) { - + Debug( 9, "Calling the conversion function" ); /* Call the image conversion function and convert directly into the shared memory */ (*conversion_fptr)(buffer, directbuffer, pixels); } else if(conversion_type == 3) { - + Debug( 9, "Decoding the JPEG image" ); /* JPEG decoding */ image.DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder); } - + } else { Debug( 3, "No format conversion performed. Assigning the image" ); - + /* No conversion was performed, the image is in the V4L buffers and needs to be copied into the shared memory */ image.Assign( width, height, colours, subpixelorder, buffer, imagesize); - + } - + return( 0 ); } diff --git a/src/zm_local_camera.h b/src/zm_local_camera.h index 785709c2c..3f4d8fb70 100644 --- a/src/zm_local_camera.h +++ b/src/zm_local_camera.h @@ -23,6 +23,7 @@ #include "zm.h" #include "zm_camera.h" #include "zm_image.h" +#include "zm_packetqueue.h" #if ZM_HAS_V4L @@ -52,31 +53,31 @@ class LocalCamera : public Camera { protected: #if ZM_HAS_V4L2 - struct V4L2MappedBuffer - { - void *start; - size_t length; - }; + struct V4L2MappedBuffer + { + void *start; + size_t length; + }; - struct V4L2Data - { - v4l2_cropcap cropcap; - v4l2_crop crop; - v4l2_format fmt; - v4l2_requestbuffers reqbufs; - V4L2MappedBuffer *buffers; - v4l2_buffer *bufptr; - }; + struct V4L2Data + { + v4l2_cropcap cropcap; + v4l2_crop crop; + v4l2_format fmt; + v4l2_requestbuffers reqbufs; + V4L2MappedBuffer *buffers; + v4l2_buffer *bufptr; + }; #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 - struct V4L1Data - { - int active_frame; - video_mbuf frames; - video_mmap *buffers; - unsigned char *bufptr; - }; + struct V4L1Data + { + int active_frame; + video_mbuf frames; + video_mmap *buffers; + unsigned char *bufptr; + }; #endif // ZM_HAS_V4L1 protected: @@ -104,24 +105,42 @@ protected: unsigned int v4l_captures_per_frame; #if ZM_HAS_V4L2 - static V4L2Data v4l2_data; + static V4L2Data v4l2_data; #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 - static V4L1Data v4l1_data; + static V4L1Data v4l1_data; #endif // ZM_HAS_V4L1 #if HAVE_LIBSWSCALE - static AVFrame **capturePictures; - _AVPIXELFORMAT imagePixFormat; - _AVPIXELFORMAT capturePixFormat; + static AVFrame **capturePictures; + _AVPIXELFORMAT imagePixFormat; + _AVPIXELFORMAT capturePixFormat; struct SwsContext *imgConversionContext; - AVFrame *tmpPicture; + AVFrame *tmpPicture; #endif // HAVE_LIBSWSCALE - static LocalCamera *last_camera; + static LocalCamera *last_camera; public: - LocalCamera( int p_id, const std::string &device, int p_channel, int p_format, bool v4lmultibuffer, unsigned int v4lcapturesperframe, const std::string &p_method, int p_width, int p_height, int p_colours, int p_palette, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, unsigned int p_extras = 0); + LocalCamera( + int p_id, + const std::string &device, + int p_channel, + int p_format, + bool v4lmultibuffer, + unsigned int v4lcapturesperframe, + const std::string &p_method, + int p_width, + int p_height, + int p_colours, + int p_palette, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio, + unsigned int p_extras = 0); ~LocalCamera(); void Initialise(); @@ -143,6 +162,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); + int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);}; static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose ); }; diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index dee423503..ed49ae57d 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -45,307 +45,307 @@ Logger::IntMap Logger::smSyslogPriorities; #if 0 static void subtractTime( struct timeval * const tp1, struct timeval * const tp2 ) { - tp1->tv_sec -= tp2->tv_sec; - if ( tp1->tv_usec <= tp2->tv_usec ) - { - tp1->tv_sec--; - tp1->tv_usec = 1000000 - (tp2->tv_usec - tp1->tv_usec); - } - else - { - tp1->tv_usec = tp1->tv_usec - tp2->tv_usec; - } + tp1->tv_sec -= tp2->tv_sec; + if ( tp1->tv_usec <= tp2->tv_usec ) + { + tp1->tv_sec--; + tp1->tv_usec = 1000000 - (tp2->tv_usec - tp1->tv_usec); + } + else + { + tp1->tv_usec = tp1->tv_usec - tp2->tv_usec; + } } #endif void Logger::usrHandler( int sig ) { - Logger *logger = fetch(); - if ( sig == SIGUSR1 ) - logger->level( logger->level()+1 ); - else if ( sig == SIGUSR2 ) - logger->level( logger->level()-1 ); - Info( "Logger - Level changed to %d", logger->level() ); + Logger *logger = fetch(); + if ( sig == SIGUSR1 ) + logger->level( logger->level()+1 ); + else if ( sig == SIGUSR2 ) + logger->level( logger->level()-1 ); + Info( "Logger - Level changed to %d", logger->level() ); } Logger::Logger() : - mLevel( INFO ), - mTermLevel( NOLOG ), - mDatabaseLevel( NOLOG ), - mFileLevel( NOLOG ), - mSyslogLevel( NOLOG ), - mEffectiveLevel( NOLOG ), - //mLogPath( config.path_logs ), - //mLogFile( mLogPath+"/"+mId+".log" ), - mDbConnected( false ), - mLogFileFP( NULL ), - mHasTerm( false ), - mFlush( false ) + mLevel( INFO ), + mTermLevel( NOLOG ), + mDatabaseLevel( NOLOG ), + mFileLevel( NOLOG ), + mSyslogLevel( NOLOG ), + mEffectiveLevel( NOLOG ), + //mLogPath( config.path_logs ), + //mLogFile( mLogPath+"/"+mId+".log" ), + mDbConnected( false ), + mLogFileFP( NULL ), + mHasTerm( false ), + mFlush( false ) { - if ( smInstance ) - { - Panic( "Attempt to create second instance of Logger class" ); - } - - if ( !smInitialised ) - { - smCodes[INFO] = "INF"; - smCodes[WARNING] = "WAR"; - smCodes[ERROR] = "ERR"; - smCodes[FATAL] = "FAT"; - smCodes[PANIC] = "PNC"; - smCodes[NOLOG] = "OFF"; - - smSyslogPriorities[INFO] = LOG_INFO; - smSyslogPriorities[WARNING] = LOG_WARNING; - smSyslogPriorities[ERROR] = LOG_ERR; - smSyslogPriorities[FATAL] = LOG_ERR; - smSyslogPriorities[PANIC] = LOG_ERR; - - char code[4] = ""; - for ( int i = DEBUG1; i <= DEBUG9; i++ ) + if ( smInstance ) { - snprintf( code, sizeof(code), "DB%d", i ); - smCodes[i] = code; - smSyslogPriorities[i] = LOG_DEBUG; + Panic( "Attempt to create second instance of Logger class" ); } - smInitialised = true; - } + if ( !smInitialised ) + { + smCodes[INFO] = "INF"; + smCodes[WARNING] = "WAR"; + smCodes[ERROR] = "ERR"; + smCodes[FATAL] = "FAT"; + smCodes[PANIC] = "PNC"; + smCodes[NOLOG] = "OFF"; - if ( fileno(stderr) && isatty(fileno(stderr)) ) - mHasTerm = true; + smSyslogPriorities[INFO] = LOG_INFO; + smSyslogPriorities[WARNING] = LOG_WARNING; + smSyslogPriorities[ERROR] = LOG_ERR; + smSyslogPriorities[FATAL] = LOG_ERR; + smSyslogPriorities[PANIC] = LOG_ERR; + + char code[4] = ""; + for ( int i = DEBUG1; i <= DEBUG9; i++ ) + { + snprintf( code, sizeof(code), "DB%d", i ); + smCodes[i] = code; + smSyslogPriorities[i] = LOG_DEBUG; + } + + smInitialised = true; + } + + if ( fileno(stderr) && isatty(fileno(stderr)) ) + mHasTerm = true; } Logger::~Logger() { - terminate(); + terminate(); } void Logger::initialise( const std::string &id, const Options &options ) { - char *envPtr; + char *envPtr; - if ( !id.empty() ) - this->id( id ); + if ( !id.empty() ) + this->id( id ); - std::string tempLogFile; - if ( options.mLogPath.size() ) - { - mLogPath = options.mLogPath; - tempLogFile = mLogPath+"/"+mId+".log"; - } - if ( options.mLogFile.size() ) - tempLogFile = options.mLogFile; - else - tempLogFile = mLogPath+"/"+mId+".log"; - if ( (envPtr = getTargettedEnv( "LOG_FILE" )) ) - tempLogFile = envPtr; - - Level tempLevel = INFO; - Level tempTermLevel = mTermLevel; - Level tempDatabaseLevel = mDatabaseLevel; - Level tempFileLevel = mFileLevel; - Level tempSyslogLevel = mSyslogLevel; - - if ( options.mTermLevel != NOOPT ) - tempTermLevel = options.mTermLevel; - if ( options.mDatabaseLevel != NOOPT ) - tempDatabaseLevel = options.mDatabaseLevel; - else - tempDatabaseLevel = config.log_level_database >= DEBUG1 ? DEBUG9 : config.log_level_database; - if ( options.mFileLevel != NOOPT ) - tempFileLevel = options.mFileLevel; - else - tempFileLevel = config.log_level_file >= DEBUG1 ? DEBUG9 : config.log_level_file; - if ( options.mSyslogLevel != NOOPT ) - tempSyslogLevel = options.mSyslogLevel; - else - tempSyslogLevel = config.log_level_syslog >= DEBUG1 ? DEBUG9 : config.log_level_syslog; - - // Legacy - if ( (envPtr = getenv( "LOG_PRINT" )) ) - tempTermLevel = atoi(envPtr) ? DEBUG9 : NOLOG; - - if ( (envPtr = getTargettedEnv( "LOG_LEVEL" )) ) - tempLevel = atoi(envPtr); - - if ( (envPtr = getTargettedEnv( "LOG_LEVEL_TERM" )) ) - tempTermLevel = atoi(envPtr); - if ( (envPtr = getTargettedEnv( "LOG_LEVEL_DATABASE" )) ) - tempDatabaseLevel = atoi(envPtr); - if ( (envPtr = getTargettedEnv( "LOG_LEVEL_FILE" )) ) - tempFileLevel = atoi(envPtr); - if ( (envPtr = getTargettedEnv( "LOG_LEVEL_SYSLOG" )) ) - tempSyslogLevel = atoi(envPtr); - - if ( config.log_debug ) - { - StringVector targets = split( config.log_debug_target, "|" ); - for ( unsigned int i = 0; i < targets.size(); i++ ) + std::string tempLogFile; + if ( options.mLogPath.size() ) { - const std::string &target = targets[i]; - if ( target == mId || target == "_"+mId || target == "_"+mIdRoot || target == "_"+mIdRoot || target == "" ) - { - if ( config.log_debug_level > NOLOG ) + mLogPath = options.mLogPath; + tempLogFile = mLogPath+"/"+mId+".log"; + } + if ( options.mLogFile.size() ) + tempLogFile = options.mLogFile; + else + tempLogFile = mLogPath+"/"+mId+".log"; + if ( (envPtr = getTargettedEnv( "LOG_FILE" )) ) + tempLogFile = envPtr; + + Level tempLevel = INFO; + Level tempTermLevel = mTermLevel; + Level tempDatabaseLevel = mDatabaseLevel; + Level tempFileLevel = mFileLevel; + Level tempSyslogLevel = mSyslogLevel; + + if ( options.mTermLevel != NOOPT ) + tempTermLevel = options.mTermLevel; + if ( options.mDatabaseLevel != NOOPT ) + tempDatabaseLevel = options.mDatabaseLevel; + else + tempDatabaseLevel = config.log_level_database >= DEBUG1 ? DEBUG9 : config.log_level_database; + if ( options.mFileLevel != NOOPT ) + tempFileLevel = options.mFileLevel; + else + tempFileLevel = config.log_level_file >= DEBUG1 ? DEBUG9 : config.log_level_file; + if ( options.mSyslogLevel != NOOPT ) + tempSyslogLevel = options.mSyslogLevel; + else + tempSyslogLevel = config.log_level_syslog >= DEBUG1 ? DEBUG9 : config.log_level_syslog; + + // Legacy + if ( (envPtr = getenv( "LOG_PRINT" )) ) + tempTermLevel = atoi(envPtr) ? DEBUG9 : NOLOG; + + if ( (envPtr = getTargettedEnv( "LOG_LEVEL" )) ) + tempLevel = atoi(envPtr); + + if ( (envPtr = getTargettedEnv( "LOG_LEVEL_TERM" )) ) + tempTermLevel = atoi(envPtr); + if ( (envPtr = getTargettedEnv( "LOG_LEVEL_DATABASE" )) ) + tempDatabaseLevel = atoi(envPtr); + if ( (envPtr = getTargettedEnv( "LOG_LEVEL_FILE" )) ) + tempFileLevel = atoi(envPtr); + if ( (envPtr = getTargettedEnv( "LOG_LEVEL_SYSLOG" )) ) + tempSyslogLevel = atoi(envPtr); + + if ( config.log_debug ) + { + StringVector targets = split( config.log_debug_target, "|" ); + for ( unsigned int i = 0; i < targets.size(); i++ ) { - tempLevel = config.log_debug_level; - if ( config.log_debug_file[0] ) - { - tempLogFile = config.log_debug_file; - tempFileLevel = tempLevel; - } + const std::string &target = targets[i]; + if ( target == mId || target == "_"+mId || target == "_"+mIdRoot || target == "_"+mIdRoot || target == "" ) + { + if ( config.log_debug_level > NOLOG ) + { + tempLevel = config.log_debug_level; + if ( config.log_debug_file[0] ) + { + tempLogFile = config.log_debug_file; + tempFileLevel = tempLevel; + } + } + } } - } } - } - logFile( tempLogFile ); + logFile( tempLogFile ); - termLevel( tempTermLevel ); - databaseLevel( tempDatabaseLevel ); - fileLevel( tempFileLevel ); - syslogLevel( tempSyslogLevel ); + termLevel( tempTermLevel ); + databaseLevel( tempDatabaseLevel ); + fileLevel( tempFileLevel ); + syslogLevel( tempSyslogLevel ); - level( tempLevel ); + level( tempLevel ); - mFlush = (envPtr = getenv( "LOG_FLUSH")) ? atoi( envPtr ) : false; + mFlush = (envPtr = getenv( "LOG_FLUSH")) ? atoi( envPtr ) : false; - //mRuntime = (envPtr = getenv( "LOG_RUNTIME")) ? atoi( envPtr ) : false; + //mRuntime = (envPtr = getenv( "LOG_RUNTIME")) ? atoi( envPtr ) : false; - { - struct sigaction action; - memset( &action, 0, sizeof(action) ); - action.sa_handler = usrHandler; - action.sa_flags = SA_RESTART; - - if ( sigaction( SIGUSR1, &action, 0 ) < 0 ) { - Fatal( "sigaction(), error = %s", strerror(errno) ); - } - if ( sigaction( SIGUSR2, &action, 0 ) < 0) - { - Fatal( "sigaction(), error = %s", strerror(errno) ); - } - } + struct sigaction action; + memset( &action, 0, sizeof(action) ); + action.sa_handler = usrHandler; + action.sa_flags = SA_RESTART; - mInitialised = true; + if ( sigaction( SIGUSR1, &action, 0 ) < 0 ) + { + Fatal( "sigaction(), error = %s", strerror(errno) ); + } + if ( sigaction( SIGUSR2, &action, 0 ) < 0) + { + Fatal( "sigaction(), error = %s", strerror(errno) ); + } + } - Debug( 1, "LogOpts: level=%s/%s, screen=%s, database=%s, logfile=%s->%s, syslog=%s", smCodes[mLevel].c_str(), smCodes[mEffectiveLevel].c_str(), smCodes[mTermLevel].c_str(), smCodes[mDatabaseLevel].c_str(), smCodes[mFileLevel].c_str(), mLogFile.c_str(), smCodes[mSyslogLevel].c_str() ); + mInitialised = true; + + Debug( 1, "LogOpts: level=%s/%s, screen=%s, database=%s, logfile=%s->%s, syslog=%s", smCodes[mLevel].c_str(), smCodes[mEffectiveLevel].c_str(), smCodes[mTermLevel].c_str(), smCodes[mDatabaseLevel].c_str(), smCodes[mFileLevel].c_str(), mLogFile.c_str(), smCodes[mSyslogLevel].c_str() ); } void Logger::terminate() { - Debug(1, "Terminating Logger" ); + Debug(1, "Terminating Logger" ); - if ( mFileLevel > NOLOG ) - closeFile(); + if ( mFileLevel > NOLOG ) + closeFile(); - if ( mSyslogLevel > NOLOG ) - closeSyslog(); + if ( mSyslogLevel > NOLOG ) + closeSyslog(); - if ( mDatabaseLevel > NOLOG ) - closeDatabase(); + if ( mDatabaseLevel > NOLOG ) + closeDatabase(); } bool Logger::boolEnv( const std::string &name, bool defaultValue ) { - const char *envPtr = getenv( name.c_str() ); - return( envPtr ? atoi( envPtr ) : defaultValue ); + const char *envPtr = getenv( name.c_str() ); + return( envPtr ? atoi( envPtr ) : defaultValue ); } int Logger::intEnv( const std::string &name, bool defaultValue ) { - const char *envPtr = getenv( name.c_str() ); - return( envPtr ? atoi( envPtr ) : defaultValue ); + const char *envPtr = getenv( name.c_str() ); + return( envPtr ? atoi( envPtr ) : defaultValue ); } std::string Logger::strEnv( const std::string &name, const std::string defaultValue ) { - const char *envPtr = getenv( name.c_str() ); - return( envPtr ? envPtr : defaultValue ); + const char *envPtr = getenv( name.c_str() ); + return( envPtr ? envPtr : defaultValue ); } char *Logger::getTargettedEnv( const std::string &name ) { - char *envPtr = NULL; - std::string envName; + char *envPtr = NULL; + std::string envName; - envName = name+"_"+mId; - envPtr = getenv( envName.c_str() ); - if ( !envPtr && mId != mIdRoot ) - { - envName = name+"_"+mIdRoot; + envName = name+"_"+mId; envPtr = getenv( envName.c_str() ); - } - if ( !envPtr ) - envPtr = getenv( name.c_str() ); - return( envPtr ); + if ( !envPtr && mId != mIdRoot ) + { + envName = name+"_"+mIdRoot; + envPtr = getenv( envName.c_str() ); + } + if ( !envPtr ) + envPtr = getenv( name.c_str() ); + return( envPtr ); } const std::string &Logger::id( const std::string &id ) { - std::string tempId = id; + std::string tempId = id; - size_t pos; - // Remove whitespace - while ( (pos = tempId.find_first_of( " \t" )) != std::string::npos ) - { - tempId.replace( pos, 1, "" ); - } - // Replace non-alphanum with underscore - while ( (pos = tempId.find_first_not_of( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" )) != std::string::npos ) - { - tempId.replace( pos, 1, "_" ); - } - if ( mId != tempId ) - { - mId = tempId; - pos = mId.find( '_' ); - if ( pos != std::string::npos ) + size_t pos; + // Remove whitespace + while ( (pos = tempId.find_first_of( " \t" )) != std::string::npos ) { - mIdRoot = mId.substr( 0, pos ); - if ( ++pos < mId.size() ) - mIdArgs = mId.substr( pos ); + tempId.replace( pos, 1, "" ); } - } - return( mId ); + // Replace non-alphanum with underscore + while ( (pos = tempId.find_first_not_of( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" )) != std::string::npos ) + { + tempId.replace( pos, 1, "_" ); + } + if ( mId != tempId ) + { + mId = tempId; + pos = mId.find( '_' ); + if ( pos != std::string::npos ) + { + mIdRoot = mId.substr( 0, pos ); + if ( ++pos < mId.size() ) + mIdArgs = mId.substr( pos ); + } + } + return( mId ); } Logger::Level Logger::level( Logger::Level level ) { - if ( level > NOOPT ) - { - level = limit(level); - if ( mLevel != level ) - mLevel = level; + if ( level > NOOPT ) + { + level = limit(level); + if ( mLevel != level ) + mLevel = level; - mEffectiveLevel = NOLOG; - if ( mTermLevel > mEffectiveLevel ) - mEffectiveLevel = mTermLevel; - if ( mDatabaseLevel > mEffectiveLevel ) - mEffectiveLevel = mDatabaseLevel; - if ( mFileLevel > mEffectiveLevel ) - mEffectiveLevel = mFileLevel; - if ( mSyslogLevel > mEffectiveLevel ) - mEffectiveLevel = mSyslogLevel; - if ( mEffectiveLevel > mLevel) - mEffectiveLevel = mLevel; - } - return( mLevel ); + mEffectiveLevel = NOLOG; + if ( mTermLevel > mEffectiveLevel ) + mEffectiveLevel = mTermLevel; + if ( mDatabaseLevel > mEffectiveLevel ) + mEffectiveLevel = mDatabaseLevel; + if ( mFileLevel > mEffectiveLevel ) + mEffectiveLevel = mFileLevel; + if ( mSyslogLevel > mEffectiveLevel ) + mEffectiveLevel = mSyslogLevel; + if ( mEffectiveLevel > mLevel) + mEffectiveLevel = mLevel; + } + return( mLevel ); } Logger::Level Logger::termLevel( Logger::Level termLevel ) { - if ( termLevel > NOOPT ) - { - if ( !mHasTerm ) - termLevel = NOLOG; - termLevel = limit(termLevel); - if ( mTermLevel != termLevel ) - mTermLevel = termLevel; - } - return( mTermLevel ); + if ( termLevel > NOOPT ) + { + if ( !mHasTerm ) + termLevel = NOLOG; + termLevel = limit(termLevel); + if ( mTermLevel != termLevel ) + mTermLevel = termLevel; + } + return( mTermLevel ); } Logger::Level Logger::databaseLevel( Logger::Level databaseLevel ) @@ -370,7 +370,7 @@ Logger::Level Logger::databaseLevel( Logger::Level databaseLevel ) 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 ) ) + if ( !mysql_real_connect( &mDbConnection, staticConfig.DB_HOST.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), NULL, 0, NULL, 0 ) ) { Fatal( "Can't connect to database: %s", mysql_error( &mDbConnection ) ); exit( mysql_errno( &mDbConnection ) ); @@ -396,7 +396,7 @@ Logger::Level Logger::databaseLevel( Logger::Level databaseLevel ) exit( mysql_errno( &mDbConnection ) ); } } - } + } // end if has colon unsigned long mysqlVersion = mysql_get_server_version( &mDbConnection ); if ( mysqlVersion < 50019 ) if ( mysql_options( &mDbConnection, MYSQL_OPT_RECONNECT, &reconnect ) ) @@ -407,250 +407,249 @@ Logger::Level Logger::databaseLevel( Logger::Level databaseLevel ) exit( mysql_errno( &mDbConnection ) ); } mDbConnected = true; - } - } + } // end if ! mDbConnected + } // end if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) mDatabaseLevel = databaseLevel; - } - } + } // end if ( mDatabaseLevel != databaseLevel ) + } // end if ( databaseLevel > NOOPT ) + return( mDatabaseLevel ); } Logger::Level Logger::fileLevel( Logger::Level fileLevel ) { - if ( fileLevel > NOOPT ) - { - fileLevel = limit(fileLevel); - if ( mFileLevel != fileLevel ) + if ( fileLevel > NOOPT ) { - if ( mFileLevel > NOLOG ) - closeFile(); - mFileLevel = fileLevel; - if ( mFileLevel > NOLOG ) - openFile(); + fileLevel = limit(fileLevel); + if ( mFileLevel != fileLevel ) + { + if ( mFileLevel > NOLOG ) + closeFile(); + mFileLevel = fileLevel; + if ( mFileLevel > NOLOG ) + openFile(); + } } - } - return( mFileLevel ); + return( mFileLevel ); } Logger::Level Logger::syslogLevel( Logger::Level syslogLevel ) { - if ( syslogLevel > NOOPT ) - { - syslogLevel = limit(syslogLevel); - if ( mSyslogLevel != syslogLevel ) + if ( syslogLevel > NOOPT ) { - if ( mSyslogLevel > NOLOG ) - closeSyslog(); - mSyslogLevel = syslogLevel; - if ( mSyslogLevel > NOLOG ) - openSyslog(); + syslogLevel = limit(syslogLevel); + if ( mSyslogLevel != syslogLevel ) + { + if ( mSyslogLevel > NOLOG ) + closeSyslog(); + mSyslogLevel = syslogLevel; + if ( mSyslogLevel > NOLOG ) + openSyslog(); + } } - } - return( mSyslogLevel ); + return( mSyslogLevel ); } void Logger::logFile( const std::string &logFile ) { - bool addLogPid = false; - std::string tempLogFile = logFile; - if ( tempLogFile[tempLogFile.length()-1] == '+' ) - { - tempLogFile.resize(tempLogFile.length()-1); - addLogPid = true; - } - if ( addLogPid ) - mLogFile = stringtf( "%s.%05d", tempLogFile.c_str(), getpid() ); - else - mLogFile = tempLogFile; + bool addLogPid = false; + std::string tempLogFile = logFile; + if ( tempLogFile[tempLogFile.length()-1] == '+' ) + { + tempLogFile.resize(tempLogFile.length()-1); + addLogPid = true; + } + if ( addLogPid ) + mLogFile = stringtf( "%s.%05d", tempLogFile.c_str(), getpid() ); + else + mLogFile = tempLogFile; } void Logger::openFile() { - if ( mLogFile.size() && (mLogFileFP = fopen( mLogFile.c_str() ,"w" )) == (FILE *)NULL ) - { - mFileLevel = NOLOG; - Fatal( "fopen() for %s, error = %s", mLogFile.c_str(), strerror(errno) ); - } + if ( mLogFile.size() && (mLogFileFP = fopen( mLogFile.c_str() ,"w" )) == (FILE *)NULL ) + { + mFileLevel = NOLOG; + Fatal( "fopen() for %s, error = %s", mLogFile.c_str(), strerror(errno) ); + } } void Logger::closeFile() { - if ( mLogFileFP ) - { - fflush( mLogFileFP ); - if ( fclose( mLogFileFP ) < 0 ) + if ( mLogFileFP ) { - Fatal( "fclose(), error = %s",strerror(errno) ); + fflush( mLogFileFP ); + if ( fclose( mLogFileFP ) < 0 ) + { + Fatal( "fclose(), error = %s",strerror(errno) ); + } + mLogFileFP = (FILE *)NULL; } - mLogFileFP = (FILE *)NULL; - } } void Logger::closeDatabase() { - if ( mDbConnected ) - { - mysql_close( &mDbConnection ); - mDbConnected = false; - } + if ( mDbConnected ) + { + mysql_close( &mDbConnection ); + mDbConnected = false; + } } void Logger::openSyslog() { - (void) openlog( mId.c_str(), LOG_PID|LOG_NDELAY, LOG_LOCAL1 ); + (void) openlog( mId.c_str(), LOG_PID|LOG_NDELAY, LOG_LOCAL1 ); } void Logger::closeSyslog() { - (void) closelog(); + (void) closelog(); } void Logger::logPrint( bool hex, const char * const filepath, const int line, const int level, const char *fstring, ... ) { - if ( level <= mEffectiveLevel ) - { - char classString[4]; - char timeString[64]; - char logString[8192]; - va_list argPtr; - struct timeval timeVal; - - const char * const file = basename(filepath); - - if ( level < PANIC || level > DEBUG9 ) - Panic( "Invalid logger level %d", level ); - - strncpy( classString, smCodes[level].c_str(), sizeof(classString) ); - - gettimeofday( &timeVal, NULL ); - - #if 0 - if ( logRuntime ) + if ( level <= mEffectiveLevel ) { - static struct timeval logStart; + char timeString[64]; + char logString[8192]; + va_list argPtr; + struct timeval timeVal; - subtractTime( &timeVal, &logStart ); + const char * const file = basename(filepath); + const char *classString = smCodes[level].c_str(); + + if ( level < PANIC || level > DEBUG9 ) + Panic( "Invalid logger level %d", level ); - snprintf( timeString, sizeof(timeString), "%ld.%03ld", timeVal.tv_sec, timeVal.tv_usec/1000 ); - } - else - { - #endif - char *timePtr = timeString; - timePtr += strftime( timePtr, sizeof(timeString), "%x %H:%M:%S", localtime(&timeVal.tv_sec) ); - snprintf( timePtr, sizeof(timeString)-(timePtr-timeString), ".%06ld", timeVal.tv_usec ); - #if 0 - } - #endif + gettimeofday( &timeVal, NULL ); - pid_t tid; + #if 0 + if ( logRuntime ) + { + static struct timeval logStart; + + subtractTime( &timeVal, &logStart ); + + snprintf( timeString, sizeof(timeString), "%ld.%03ld", timeVal.tv_sec, timeVal.tv_usec/1000 ); + } + else + { + #endif + char *timePtr = timeString; + timePtr += strftime( timePtr, sizeof(timeString), "%x %H:%M:%S", localtime(&timeVal.tv_sec) ); + snprintf( timePtr, sizeof(timeString)-(timePtr-timeString), ".%06ld", timeVal.tv_usec ); + #if 0 + } + #endif + + pid_t tid; #ifdef __FreeBSD__ - long lwpid; - thr_self(&lwpid); - tid = lwpid; + long lwpid; + thr_self(&lwpid); + tid = lwpid; - if (tid < 0 ) // Thread/Process id + if (tid < 0 ) // Thread/Process id #else #ifdef HAVE_SYSCALL #ifdef __FreeBSD_kernel__ - if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id + if ( (syscall(SYS_thr_self, &tid)) < 0 ) // Thread/Process id # else // SOLARIS doesn't have SYS_gettid; don't assume - #ifdef SYS_gettid - if ( (tid = syscall(SYS_gettid)) < 0 ) // Thread/Process id - #endif // SYS_gettid + #ifdef SYS_gettid + if ( (tid = syscall(SYS_gettid)) < 0 ) // Thread/Process id + #endif // SYS_gettid #endif #endif // HAVE_SYSCALL #endif - tid = getpid(); // Process id + tid = getpid(); // Process id - char *logPtr = logString; - logPtr += snprintf( logPtr, sizeof(logString), "%s %s[%d].%s-%s/%d [", - timeString, - mId.c_str(), - tid, - classString, - file, - line - ); - char *syslogStart = logPtr; + char *logPtr = logString; + logPtr += snprintf( logPtr, sizeof(logString), "%s %s[%d].%s-%s/%d [", + timeString, + mId.c_str(), + tid, + classString, + file, + line + ); + char *syslogStart = logPtr; - va_start( argPtr, fstring ); - if ( hex ) - { - unsigned char *data = va_arg( argPtr, unsigned char * ); - int len = va_arg( argPtr, int ); - int i; - logPtr += snprintf( logPtr, sizeof(logString)-(logPtr-logString), "%d:", len ); - for ( i = 0; i < len; i++ ) - { - logPtr += snprintf( logPtr, sizeof(logString)-(logPtr-logString), " %02x", data[i] ); - } - } - else - { - logPtr += vsnprintf( logPtr, sizeof(logString)-(logPtr-logString), fstring, argPtr ); - } - va_end(argPtr); - char *syslogEnd = logPtr; - strncpy( logPtr, "]\n", sizeof(logString)-(logPtr-logString) ); + va_start( argPtr, fstring ); + if ( hex ) + { + unsigned char *data = va_arg( argPtr, unsigned char * ); + int len = va_arg( argPtr, int ); + int i; + logPtr += snprintf( logPtr, sizeof(logString)-(logPtr-logString), "%d:", len ); + for ( i = 0; i < len; i++ ) + { + logPtr += snprintf( logPtr, sizeof(logString)-(logPtr-logString), " %02x", data[i] ); + } + } + else + { + logPtr += vsnprintf( logPtr, sizeof(logString)-(logPtr-logString), fstring, argPtr ); + } + va_end(argPtr); + char *syslogEnd = logPtr; + strncpy( logPtr, "]\n", sizeof(logString)-(logPtr-logString) ); - if ( level <= mTermLevel ) - { - printf( "%s", logString ); - fflush( stdout ); - } - if ( level <= mFileLevel ) - { - fprintf( mLogFileFP, "%s", logString ); - if ( mFlush ) - fflush( mLogFileFP ); - } - *syslogEnd = '\0'; - if ( level <= mDatabaseLevel ) - { - char sql[ZM_SQL_MED_BUFSIZ]; - char escapedString[(strlen(syslogStart)*2)+1]; + if ( level <= mTermLevel ) + { + printf( "%s", logString ); + fflush( stdout ); + } + if ( level <= mFileLevel ) + { + fprintf( mLogFileFP, "%s", logString ); + //if ( mFlush ) + fflush( mLogFileFP ); + } + *syslogEnd = '\0'; + if ( level <= mDatabaseLevel ) + { + char sql[ZM_SQL_MED_BUFSIZ]; + char escapedString[(strlen(syslogStart)*2)+1]; - mysql_real_escape_string( &mDbConnection, escapedString, syslogStart, strlen(syslogStart) ); + mysql_real_escape_string( &mDbConnection, escapedString, syslogStart, strlen(syslogStart) ); - snprintf( sql, sizeof(sql), "insert into Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) values ( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line ); - if ( mysql_query( &mDbConnection, sql ) ) - { - Level tempDatabaseLevel = mDatabaseLevel; - databaseLevel( NOLOG ); - Error( "Can't insert log entry: sql(%s) error(%s)", sql, mysql_error( &mDbConnection ) ); - databaseLevel(tempDatabaseLevel); - } - } - if ( level <= mSyslogLevel ) - { - int priority = smSyslogPriorities[level]; - //priority |= LOG_DAEMON; - syslog( priority, "%s [%s]", classString, syslogStart ); - } + snprintf( sql, sizeof(sql), "insert into Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) values ( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line ); + if ( mysql_query( &mDbConnection, sql ) ) + { + Level tempDatabaseLevel = mDatabaseLevel; + databaseLevel( NOLOG ); + Error( "Can't insert log entry: sql(%s) error(%s)", sql, mysql_error( &mDbConnection ) ); + databaseLevel(tempDatabaseLevel); + } + } + if ( level <= mSyslogLevel ) + { + int priority = smSyslogPriorities[level]; + //priority |= LOG_DAEMON; + syslog( priority, "%s [%s] [%s]", classString, mId.c_str(), syslogStart ); + } - if ( level <= FATAL ) - { - if ( level <= PANIC ) - abort(); - exit( -1 ); + if ( level <= FATAL ) + { + if ( level <= PANIC ) + abort(); + exit( -1 ); + } } - } } void logInit( const char *name, const Logger::Options &options ) { - if ( !Logger::smInstance ) - Logger::smInstance = new Logger(); - Logger::Options tempOptions = options; - tempOptions.mLogPath = config.path_logs; - Logger::smInstance->initialise( name, tempOptions ); + if ( !Logger::smInstance ) + Logger::smInstance = new Logger(); + Logger::Options tempOptions = options; + tempOptions.mLogPath = config.path_logs; + Logger::smInstance->initialise( name, tempOptions ); } void logTerm() { - if ( Logger::smInstance ) - delete Logger::smInstance; + if ( Logger::smInstance ) + delete Logger::smInstance; } diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index b7920c166..eebad7291 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -28,6 +28,7 @@ #include "zm_mpeg.h" #include "zm_signal.h" #include "zm_monitor.h" +#include "zm_video.h" #if ZM_HAS_V4L #include "zm_local_camera.h" #endif // ZM_HAS_V4L @@ -72,10 +73,7 @@ std::vector split(const std::string &s, char delim) { } //============================================================================= - - -Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id ) -{ +Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id ) { strncpy( name, p_name, sizeof(name) ); #if ZM_MEM_MAPPED @@ -94,15 +92,12 @@ Monitor::MonitorLink::MonitorLink( int p_id, const char *p_name ) : id( p_id ) connected = false; } -Monitor::MonitorLink::~MonitorLink() -{ +Monitor::MonitorLink::~MonitorLink() { disconnect(); } -bool Monitor::MonitorLink::connect() -{ - if ( !last_connect_time || (time( 0 ) - last_connect_time) > 60 ) - { +bool Monitor::MonitorLink::connect() { + if ( !last_connect_time || (time( 0 ) - last_connect_time) > 60 ) { last_connect_time = time( 0 ); mem_size = sizeof(SharedData) + sizeof(TriggerData); @@ -110,8 +105,7 @@ bool Monitor::MonitorLink::connect() Debug( 1, "link.mem.size=%d", mem_size ); #if ZM_MEM_MAPPED map_fd = open( mem_file, O_RDWR, (mode_t)0600 ); - if ( map_fd < 0 ) - { + if ( map_fd < 0 ) { Debug( 3, "Can't open linked memory map file %s: %s", mem_file, strerror(errno) ); disconnect(); return( false ); @@ -124,44 +118,37 @@ bool Monitor::MonitorLink::connect() } struct stat map_stat; - if ( fstat( map_fd, &map_stat ) < 0 ) - { + if ( fstat( map_fd, &map_stat ) < 0 ) { Error( "Can't stat linked memory map file %s: %s", mem_file, strerror(errno) ); disconnect(); return( false ); } - if ( map_stat.st_size == 0 ) - { + if ( map_stat.st_size == 0 ) { Error( "Linked memory map file %s is empty: %s", mem_file, strerror(errno) ); disconnect(); return( false ); - } - else if ( map_stat.st_size < mem_size ) - { + } else if ( map_stat.st_size < mem_size ) { Error( "Got unexpected memory map file size %ld, expected %d", map_stat.st_size, mem_size ); disconnect(); return( false ); } mem_ptr = (unsigned char *)mmap( NULL, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0 ); - if ( mem_ptr == MAP_FAILED ) - { + if ( mem_ptr == MAP_FAILED ) { Error( "Can't map file %s (%d bytes) to memory: %s", mem_file, mem_size, strerror(errno) ); disconnect(); return( false ); } #else // ZM_MEM_MAPPED shm_id = shmget( (config.shm_key&0xffff0000)|id, mem_size, 0700 ); - if ( shm_id < 0 ) - { + if ( shm_id < 0 ) { Debug( 3, "Can't shmget link memory: %s", strerror(errno) ); connected = false; return( false ); } mem_ptr = (unsigned char *)shmat( shm_id, 0, 0 ); - if ( mem_ptr < 0 ) - { + if ( mem_ptr < 0 ) { Debug( 3, "Can't shmat link memory: %s", strerror(errno) ); connected = false; return( false ); @@ -171,8 +158,7 @@ bool Monitor::MonitorLink::connect() shared_data = (SharedData *)mem_ptr; trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); - if ( !shared_data->valid ) - { + if ( !shared_data->valid ) { Debug( 3, "Linked memory not initialised by capture daemon" ); disconnect(); return( false ); @@ -187,15 +173,12 @@ bool Monitor::MonitorLink::connect() return( false ); } -bool Monitor::MonitorLink::disconnect() -{ - if ( connected ) - { +bool Monitor::MonitorLink::disconnect() { + if ( connected ) { connected = false; #if ZM_MEM_MAPPED - if ( mem_ptr > 0 ) - { + if ( mem_ptr > (void *)0 ) { msync( mem_ptr, mem_size, MS_ASYNC ); munmap( mem_ptr, mem_size ); } @@ -205,25 +188,21 @@ bool Monitor::MonitorLink::disconnect() map_fd = -1; #else // ZM_MEM_MAPPED struct shmid_ds shm_data; - if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) - { + if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) { Debug( 3, "Can't shmctl: %s", strerror(errno) ); return( false ); } shm_id = 0; - if ( shm_data.shm_nattch <= 1 ) - { - if ( shmctl( shm_id, IPC_RMID, 0 ) < 0 ) - { + if ( shm_data.shm_nattch <= 1 ) { + if ( shmctl( shm_id, IPC_RMID, 0 ) < 0 ) { Debug( 3, "Can't shmctl: %s", strerror(errno) ); return( false ); } } - if ( shmdt( mem_ptr ) < 0 ) - { + if ( shmdt( mem_ptr ) < 0 ) { Debug( 3, "Can't shmdt: %s", strerror(errno) ); return( false ); } @@ -235,32 +214,24 @@ bool Monitor::MonitorLink::disconnect() return( true ); } -bool Monitor::MonitorLink::isAlarmed() -{ - if ( !connected ) - { +bool Monitor::MonitorLink::isAlarmed() { + if ( !connected ) { return( false ); } return( shared_data->state == ALARM ); } -bool Monitor::MonitorLink::inAlarm() -{ - if ( !connected ) - { +bool Monitor::MonitorLink::inAlarm() { + if ( !connected ) { return( false ); } return( shared_data->state == ALARM || shared_data->state == ALERT ); } -bool Monitor::MonitorLink::hasAlarmed() -{ - if ( shared_data->state == ALARM ) - { +bool Monitor::MonitorLink::hasAlarmed() { + if ( shared_data->state == ALARM ) { return( true ); - } - else if( shared_data->last_event != (unsigned int)last_event ) - { + } else if ( shared_data->last_event != (unsigned int)last_event ) { last_event = shared_data->last_event; } return( false ); @@ -270,12 +241,17 @@ Monitor::Monitor( int p_id, const char *p_name, const unsigned int p_server_id, + const unsigned int p_storage_id, int p_function, bool p_enabled, const char *p_linked_monitors, Camera *p_camera, int p_orientation, unsigned int p_deinterlacing, + int p_savejpegs, + VideoWriter p_videowriter, + std::string p_encoderparams, + bool p_record_audio, const char *p_event_prefix, const char *p_label_format, const Coord &p_label_coord, @@ -304,12 +280,17 @@ Monitor::Monitor( Zone *p_zones[] ) : id( p_id ), server_id( p_server_id ), + storage_id( p_storage_id ), function( (Function)p_function ), enabled( p_enabled ), - width( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Height():p_camera->Width() ), - height( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Width():p_camera->Height() ), + width( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Height():p_camera->Width() ), + height( (p_orientation==ROTATE_90||p_orientation==ROTATE_270)?p_camera->Width():p_camera->Height() ), orientation( (Orientation)p_orientation ), deinterlacing( p_deinterlacing ), + savejpegspref( p_savejpegs ), + videowriter( p_videowriter ), + encoderparams( p_encoderparams ), + record_audio( p_record_audio ), label_coord( p_label_coord ), label_size( p_label_size ), image_buffer_count( p_image_buffer_count ), @@ -350,21 +331,20 @@ Monitor::Monitor( // Change \n to actual line feeds char *token_ptr = label_format; const char *token_string = "\n"; - while( ( token_ptr = strstr( token_ptr, token_string ) ) ) - { - if ( *(token_ptr+1) ) - { + while( ( token_ptr = strstr( token_ptr, token_string ) ) ) { + if ( *(token_ptr+1) ) { *token_ptr = '\n'; token_ptr++; strcpy( token_ptr, token_ptr+1 ); - } - else - { + } else { *token_ptr = '\0'; break; } } + /* Parse encoder parameters */ + ParseEncoderParameters(encoderparams.c_str(), &encoderparamsvec); + fps = 0.0; event_count = 0; image_count = 0; @@ -391,6 +371,7 @@ Monitor::Monitor( mem_size = sizeof(SharedData) + sizeof(TriggerData) + + sizeof(VideoStoreData) //Information to pass back to the capture process + (image_buffer_count*sizeof(struct timeval)) + (image_buffer_count*camera->ImageSize()) + 64; /* Padding used to permit aligning the images buffer to 16 byte boundary */ @@ -398,7 +379,25 @@ Monitor::Monitor( Debug( 1, "mem.size=%d", mem_size ); mem_ptr = NULL; + storage = new Storage( storage_id ); + Debug(1, "Storage path: %s", storage->Path() ); + // Should maybe store this for later use + char monitor_dir[PATH_MAX] = ""; + snprintf( monitor_dir, sizeof(monitor_dir), "%s/%d", storage->Path(), id ); + struct stat statbuf; + + if ( stat( monitor_dir, &statbuf ) ) { + if ( errno == ENOENT || errno == ENOTDIR ) { + if ( mkdir( monitor_dir, 0755 ) ) { + Error( "Can't mkdir %s: %s", monitor_dir, strerror(errno)); + } + } else { + Warning( "Error stat'ing %s, may be fatal. error is %s", monitor_dir, strerror(errno)); + } + } + if ( purpose == CAPTURE ) { + this->connect(); if ( ! mem_ptr ) exit(-1); memset( mem_ptr, 0, mem_size ); @@ -426,6 +425,10 @@ Monitor::Monitor( trigger_data->trigger_text[0] = 0; trigger_data->trigger_showtext[0] = 0; shared_data->valid = true; + video_store_data->recording = false; + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "nothing"); + video_store_data->size = sizeof(VideoStoreData); + //video_store_data->frameNumber = 0; } else if ( purpose == ANALYSIS ) { this->connect(); if ( ! mem_ptr ) exit(-1); @@ -435,17 +438,16 @@ Monitor::Monitor( shared_data->alarm_y = -1; } - if ( ( ! mem_ptr ) || ! shared_data->valid ) - { - if ( purpose != QUERY ) - { + if ( ( ! mem_ptr ) || ! shared_data->valid ) { + if ( purpose != QUERY ) { Error( "Shared data not initialised by capture daemon for monitor %s", name ); exit( -1 ); } } // Will this not happen every time a monitor is instantiated? Seems like all the calls to the Monitor constructor pass a zero for n_zones, then load zones after.. - if ( !n_zones ) { + // In my storage areas branch, I took this out.. and didn't notice any problems. + if ( false && !n_zones ) { Debug( 1, "Monitor %s has no zones, adding one.", name ); n_zones = 1; zones = new Zone *[1]; @@ -460,46 +462,15 @@ Monitor::Monitor( Debug( 1, "Monitor %s LBF = '%s', LBX = %d, LBY = %d, LBS = %d", name, label_format, label_coord.X(), label_coord.Y(), label_size ); Debug( 1, "Monitor %s IBC = %d, WUC = %d, pEC = %d, PEC = %d, EAF = %d, FRI = %d, RBP = %d, ARBP = %d, FM = %d", name, image_buffer_count, warmup_count, pre_event_count, post_event_count, alarm_frame_count, fps_report_interval, ref_blend_perc, alarm_ref_blend_perc, track_motion ); - if ( purpose == ANALYSIS ) - { - static char path[PATH_MAX]; - strncpy( path, config.dir_events, sizeof(path) ); + //Set video recording flag for event start constructor and easy reference in code + // TODO: Use enum instead of the # 2. Makes for easier reading + videoRecording = ((GetOptVideoWriter() == H264PASSTHROUGH) && camera->SupportsNativeVideo()); - struct stat statbuf; - errno = 0; - stat( path, &statbuf ); - if ( errno == ENOENT || errno == ENOTDIR ) - { - if ( mkdir( path, 0755 ) ) - { - Error( "Can't make %s: %s", path, strerror(errno)); - } - } - - snprintf( path, sizeof(path), "%s/%d", config.dir_events, id ); - - errno = 0; - stat( path, &statbuf ); - if ( errno == ENOENT || errno == ENOTDIR ) - { - if ( mkdir( path, 0755 ) ) - { - Error( "Can't make %s: %s", path, strerror(errno)); - } - char temp_path[PATH_MAX]; - snprintf( temp_path, sizeof(temp_path), "%d", id ); - if ( chdir( config.dir_events ) < 0 ) - Fatal( "Can't change directory to '%s': %s", config.dir_events, strerror(errno) ); - if ( symlink( temp_path, name ) < 0 ) - Fatal( "Can't symlink '%s' to '%s': %s", temp_path, name, strerror(errno) ); - if ( chdir( ".." ) < 0 ) - Fatal( "Can't change to parent directory: %s", strerror(errno) ); - } + if ( purpose == ANALYSIS ) { while( shared_data->last_write_index == (unsigned int)image_buffer_count - && shared_data->last_write_time == 0) - { + && shared_data->last_write_time == 0) { Warning( "Waiting for capture daemon" ); sleep( 1 ); } @@ -515,6 +486,7 @@ Monitor::Monitor( } bool Monitor::connect() { + Debug(3, "Connecting to monitor. Purpose is %d", purpose ); #if ZM_MEM_MAPPED snprintf( mem_file, sizeof(mem_file), "%s/zm.mmap.%d", config.path_map, id ); map_fd = open( mem_file, O_RDWR|O_CREAT, (mode_t)0600 ); @@ -558,15 +530,15 @@ bool Monitor::connect() { exit( -1 ); } mem_ptr = (unsigned char *)shmat( shm_id, 0, 0 ); - if ( mem_ptr < 0 ) - { + if ( mem_ptr < 0 ) { Error( "Can't shmat: %s", strerror(errno)); exit( -1 ); } #endif // ZM_MEM_MAPPED shared_data = (SharedData *)mem_ptr; trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); - struct timeval *shared_timestamps = (struct timeval *)((char *)trigger_data + sizeof(TriggerData)); + video_store_data = (VideoStoreData *)((char *)trigger_data + sizeof(TriggerData)); + struct timeval *shared_timestamps = (struct timeval *)((char *)video_store_data + sizeof(VideoStoreData)); unsigned char *shared_images = (unsigned char *)((char *)shared_timestamps + (image_buffer_count*sizeof(struct timeval))); if(((unsigned long)shared_images % 16) != 0) { @@ -574,30 +546,27 @@ bool Monitor::connect() { Debug(3,"Aligning shared memory images to the next 16 byte boundary"); shared_images = (uint8_t*)((unsigned long)shared_images + (16 - ((unsigned long)shared_images % 16))); } + Debug(3, "Allocating %d image buffers", image_buffer_count ); image_buffer = new Snapshot[image_buffer_count]; - for ( int i = 0; i < image_buffer_count; i++ ) - { + for ( int i = 0; i < image_buffer_count; i++ ) { image_buffer[i].timestamp = &(shared_timestamps[i]); image_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder(), &(shared_images[i*camera->ImageSize()]) ); image_buffer[i].image->HoldBuffer(true); /* Don't release the internal buffer or replace it with another */ } - if ( (deinterlacing & 0xff) == 4) - { + if ( (deinterlacing & 0xff) == 4) { /* Four field motion adaptive deinterlacing in use */ /* Allocate a buffer for the next image */ next_buffer.image = new Image( width, height, camera->Colours(), camera->SubpixelOrder()); next_buffer.timestamp = new struct timeval; } - if ( ( purpose == ANALYSIS ) && analysis_fps ) - { + if ( ( purpose == ANALYSIS ) && analysis_fps ) { // Size of pre event buffer must be greater than pre_event_count // if alarm_frame_count > 1, because in this case the buffer contains // alarmed images that must be discarded when event is created pre_event_buffer_count = pre_event_count + alarm_frame_count - 1; pre_event_buffer = new Snapshot[pre_event_buffer_count]; - for ( int i = 0; i < pre_event_buffer_count; i++ ) - { + for ( int i = 0; i < pre_event_buffer_count; i++ ) { pre_event_buffer[i].timestamp = new struct timeval; pre_event_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder()); } @@ -606,8 +575,7 @@ bool Monitor::connect() { return true; } -Monitor::~Monitor() -{ +Monitor::~Monitor() { if ( timestamps ) { delete[] timestamps; timestamps = 0; @@ -616,54 +584,48 @@ Monitor::~Monitor() delete[] images; images = 0; } - if ( privacy_bitmask ) { - delete[] privacy_bitmask; - privacy_bitmask = NULL; - } + if ( privacy_bitmask ) { + delete[] privacy_bitmask; + privacy_bitmask = NULL; + } if ( mem_ptr ) { - if ( event ) + if ( event ) { Info( "%s: %03d - Closing event %d, shutting down", name, image_count, event->Id() ); - closeEvent(); + closeEvent(); + } - if ( (deinterlacing & 0xff) == 4) - { + if ( (deinterlacing & 0xff) == 4) { delete next_buffer.image; delete next_buffer.timestamp; } - for ( int i = 0; i < image_buffer_count; i++ ) - { + for ( int i = 0; i < image_buffer_count; i++ ) { delete image_buffer[i].image; } delete[] image_buffer; } // end if mem_ptr - for ( int i = 0; i < n_zones; i++ ) - { + for ( int i = 0; i < n_zones; i++ ) { delete zones[i]; } delete[] zones; delete camera; + delete storage; if ( mem_ptr ) { - if ( purpose == ANALYSIS ) - { + if ( purpose == ANALYSIS ) { shared_data->state = state = IDLE; shared_data->last_read_index = image_buffer_count; shared_data->last_read_time = 0; - if ( analysis_fps ) - { - for ( int i = 0; i < pre_event_buffer_count; i++ ) - { + if ( analysis_fps ) { + for ( int i = 0; i < pre_event_buffer_count; i++ ) { delete pre_event_buffer[i].image; delete pre_event_buffer[i].timestamp; } delete[] pre_event_buffer; } - } - else if ( purpose == CAPTURE ) - { + } else if ( purpose == CAPTURE ) { shared_data->valid = false; memset( mem_ptr, 0, mem_size ); } @@ -676,6 +638,7 @@ Monitor::~Monitor() close( map_fd ); if ( purpose == CAPTURE ) { + // How about we store this in the object on instantiation so that we don't have to do this again. char mmap_path[PATH_MAX] = ""; snprintf( mmap_path, sizeof(mmap_path), "%s/zm.mmap.%d", config.path_map, id ); @@ -699,8 +662,7 @@ Monitor::~Monitor() } // end if mem_ptr } -void Monitor::AddZones( int p_n_zones, Zone *p_zones[] ) -{ +void Monitor::AddZones( int p_n_zones, Zone *p_zones[] ) { for ( int i = 0; i < n_zones; i++ ) delete zones[i]; delete[] zones; @@ -708,19 +670,15 @@ void Monitor::AddZones( int p_n_zones, Zone *p_zones[] ) zones = p_zones; } -void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) -{ +void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) { if ( privacy_bitmask ) delete[] privacy_bitmask; privacy_bitmask = NULL; Image *privacy_image = NULL; - for ( int i = 0; i < n_zones; i++ ) - { - if ( p_zones[i]->IsPrivacy() ) - { - if ( !privacy_image ) - { + for ( int i = 0; i < n_zones; i++ ) { + if ( p_zones[i]->IsPrivacy() ) { + if ( !privacy_image ) { privacy_image = new Image( width, height, 1, ZM_SUBPIX_ORDER_NONE); privacy_image->Clear(); } @@ -732,20 +690,16 @@ void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) privacy_bitmask = privacy_image->Buffer(); } -Monitor::State Monitor::GetState() const -{ +Monitor::State Monitor::GetState() const { return( (State)shared_data->state ); } -int Monitor::GetImage( int index, int scale ) -{ - if ( index < 0 || index > image_buffer_count ) - { +int Monitor::GetImage( int index, int scale ) { + if ( index < 0 || index > image_buffer_count ) { index = shared_data->last_write_index; } - if ( index != image_buffer_count ) - { + if ( index != image_buffer_count ) { Image *image; // If we are going to be modifying the snapshot before writing, then we need to copy it if ( ( scale != ZM_SCALE_BASE ) || ( !config.timestamp_on_capture ) ) { @@ -772,75 +726,59 @@ int Monitor::GetImage( int index, int scale ) static char filename[PATH_MAX]; snprintf( filename, sizeof(filename), "Monitor%d.jpg", id ); image->WriteJpeg( filename ); - } - else - { + } else { Error( "Unable to generate image, no images in buffer" ); } return( 0 ); } -struct timeval Monitor::GetTimestamp( int index ) const -{ - if ( index < 0 || index > image_buffer_count ) - { +struct timeval Monitor::GetTimestamp( int index ) const { + if ( index < 0 || index > image_buffer_count ) { index = shared_data->last_write_index; } - if ( index != image_buffer_count ) - { + if ( index != image_buffer_count ) { Snapshot *snap = &image_buffer[index]; return( *(snap->timestamp) ); - } - else - { + } else { static struct timeval null_tv = { 0, 0 }; return( null_tv ); } } -unsigned int Monitor::GetLastReadIndex() const -{ +unsigned int Monitor::GetLastReadIndex() const { return( shared_data->last_read_index!=(unsigned int)image_buffer_count?shared_data->last_read_index:-1 ); } -unsigned int Monitor::GetLastWriteIndex() const -{ +unsigned int Monitor::GetLastWriteIndex() const { return( shared_data->last_write_index!=(unsigned int)image_buffer_count?shared_data->last_write_index:-1 ); } -unsigned int Monitor::GetLastEvent() const -{ +unsigned int Monitor::GetLastEvent() const { return( shared_data->last_event ); } -double Monitor::GetFPS() const -{ +double Monitor::GetFPS() const { int index1 = shared_data->last_write_index; - if ( index1 == image_buffer_count ) - { + if ( index1 == image_buffer_count ) { return( 0.0 ); } Snapshot *snap1 = &image_buffer[index1]; - if ( !snap1->timestamp || !snap1->timestamp->tv_sec ) - { + if ( !snap1->timestamp || !snap1->timestamp->tv_sec ) { return( 0.0 ); } struct timeval time1 = *snap1->timestamp; int image_count = image_buffer_count; int index2 = (index1+1)%image_buffer_count; - if ( index2 == image_buffer_count ) - { + if ( index2 == image_buffer_count ) { return( 0.0 ); } Snapshot *snap2 = &image_buffer[index2]; - while ( !snap2->timestamp || !snap2->timestamp->tv_sec ) - { - if ( index1 == index2 ) - { + while ( !snap2->timestamp || !snap2->timestamp->tv_sec ) { + if ( index1 == index2 ) { return( 0.0 ); } index2 = (index2+1)%image_buffer_count; @@ -853,143 +791,110 @@ double Monitor::GetFPS() const double curr_fps = image_count/time_diff; - if ( curr_fps < 0.0 ) - { + if ( curr_fps < 0.0 ) { //Error( "Negative FPS %f, time_diff = %lf (%d:%ld.%ld - %d:%ld.%ld), ibc: %d", curr_fps, time_diff, index2, time2.tv_sec, time2.tv_usec, index1, time1.tv_sec, time1.tv_usec, image_buffer_count ); return( 0.0 ); } return( curr_fps ); } -useconds_t Monitor::GetAnalysisRate() -{ +useconds_t Monitor::GetAnalysisRate() { double capturing_fps = GetFPS(); - if ( !analysis_fps ) - { + if ( !analysis_fps ) { return( 0 ); - } - else if ( analysis_fps > capturing_fps ) - { + } else if ( analysis_fps > capturing_fps ) { Warning( "Analysis fps (%.2f) is greater than capturing fps (%.2f)", analysis_fps, capturing_fps ); return( 0 ); - } - else - { + } else { return( ( 1000000 / analysis_fps ) - ( 1000000 / capturing_fps ) ); } } -void Monitor::UpdateAdaptiveSkip() -{ - if ( config.opt_adaptive_skip ) - { +void Monitor::UpdateAdaptiveSkip() { + if ( config.opt_adaptive_skip ) { double capturing_fps = GetFPS(); - if ( adaptive_skip && analysis_fps && ( analysis_fps < capturing_fps ) ) - { + if ( adaptive_skip && analysis_fps && ( analysis_fps < capturing_fps ) ) { Info( "Analysis fps (%.2f) is lower than capturing fps (%.2f), disabling adaptive skip feature", analysis_fps, capturing_fps ); adaptive_skip = false; - } - else if ( !adaptive_skip && ( !analysis_fps || ( analysis_fps >= capturing_fps ) ) ) - { + } else if ( !adaptive_skip && ( !analysis_fps || ( analysis_fps >= capturing_fps ) ) ) { Info( "Enabling adaptive skip feature" ); adaptive_skip = true; } - } - else - { + } else { adaptive_skip = false; } } -void Monitor::ForceAlarmOn( int force_score, const char *force_cause, const char *force_text ) -{ +void Monitor::ForceAlarmOn( int force_score, const char *force_cause, const char *force_text ) { trigger_data->trigger_state = TRIGGER_ON; trigger_data->trigger_score = force_score; strncpy( trigger_data->trigger_cause, force_cause, sizeof(trigger_data->trigger_cause) ); strncpy( trigger_data->trigger_text, force_text, sizeof(trigger_data->trigger_text) ); } -void Monitor::ForceAlarmOff() -{ +void Monitor::ForceAlarmOff() { trigger_data->trigger_state = TRIGGER_OFF; } -void Monitor::CancelForced() -{ +void Monitor::CancelForced() { trigger_data->trigger_state = TRIGGER_CANCEL; } -void Monitor::actionReload() -{ +void Monitor::actionReload() { shared_data->action |= RELOAD; } -void Monitor::actionEnable() -{ +void Monitor::actionEnable() { shared_data->action |= RELOAD; static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "update Monitors set Enabled = 1 where Id = '%d'", id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } } -void Monitor::actionDisable() -{ +void Monitor::actionDisable() { shared_data->action |= RELOAD; static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "update Monitors set Enabled = 0 where Id = '%d'", id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } } -void Monitor::actionSuspend() -{ +void Monitor::actionSuspend() { shared_data->action |= SUSPEND; } -void Monitor::actionResume() -{ +void Monitor::actionResume() { shared_data->action |= RESUME; } -int Monitor::actionBrightness( int p_brightness ) -{ - if ( purpose != CAPTURE ) - { - if ( p_brightness >= 0 ) - { +int Monitor::actionBrightness( int p_brightness ) { + if ( purpose != CAPTURE ) { + if ( p_brightness >= 0 ) { shared_data->brightness = p_brightness; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & SET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to set brightness" ); return( -1 ); } } - } - else - { + } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & GET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to get brightness" ); return( -1 ); } @@ -1000,36 +905,27 @@ int Monitor::actionBrightness( int p_brightness ) return( camera->Brightness( p_brightness ) ); } -int Monitor::actionContrast( int p_contrast ) -{ - if ( purpose != CAPTURE ) - { - if ( p_contrast >= 0 ) - { +int Monitor::actionContrast( int p_contrast ) { + if ( purpose != CAPTURE ) { + if ( p_contrast >= 0 ) { shared_data->contrast = p_contrast; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & SET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to set contrast" ); return( -1 ); } } - } - else - { + } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & GET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to get contrast" ); return( -1 ); } @@ -1040,36 +936,27 @@ int Monitor::actionContrast( int p_contrast ) return( camera->Contrast( p_contrast ) ); } -int Monitor::actionHue( int p_hue ) -{ - if ( purpose != CAPTURE ) - { - if ( p_hue >= 0 ) - { +int Monitor::actionHue( int p_hue ) { + if ( purpose != CAPTURE ) { + if ( p_hue >= 0 ) { shared_data->hue = p_hue; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & SET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to set hue" ); return( -1 ); } } - } - else - { + } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & GET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to get hue" ); return( -1 ); } @@ -1080,36 +967,27 @@ int Monitor::actionHue( int p_hue ) return( camera->Hue( p_hue ) ); } -int Monitor::actionColour( int p_colour ) -{ - if ( purpose != CAPTURE ) - { - if ( p_colour >= 0 ) - { +int Monitor::actionColour( int p_colour ) { + if ( purpose != CAPTURE ) { + if ( p_colour >= 0 ) { shared_data->colour = p_colour; shared_data->action |= SET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & SET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & SET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to set colour" ); return( -1 ); } } - } - else - { + } else { shared_data->action |= GET_SETTINGS; int wait_loops = 10; - while ( shared_data->action & GET_SETTINGS ) - { - if ( wait_loops-- ) + while ( shared_data->action & GET_SETTINGS ) { + if ( wait_loops-- ) { usleep( 100000 ); - else - { + } else { Warning( "Timed out waiting to get colour" ); return( -1 ); } @@ -1120,81 +998,82 @@ int Monitor::actionColour( int p_colour ) return( camera->Colour( p_colour ) ); } -void Monitor::DumpZoneImage( const char *zone_string ) -{ +void Monitor::DumpZoneImage( const char *zone_string ) { int exclude_id = 0; int extra_colour = 0; Polygon extra_zone; - if ( zone_string ) - { - if ( !Zone::ParseZoneString( zone_string, exclude_id, extra_colour, extra_zone ) ) - { + if ( zone_string ) { + if ( !Zone::ParseZoneString( zone_string, exclude_id, extra_colour, extra_zone ) ) { Error( "Failed to parse zone string, ignoring" ); } } - int index = shared_data->last_write_index; - Snapshot *snap = &image_buffer[index]; - Image *snap_image = snap->image; + Image *zone_image = NULL; + if ( ( (!staticConfig.SERVER_ID) || ( staticConfig.SERVER_ID == server_id ) ) && mem_ptr ) { + Debug(3, "Trying to load from local zmc"); + int index = shared_data->last_write_index; + Snapshot *snap = &image_buffer[index]; + zone_image = new Image( *snap->image ); + } else { + Debug(3, "Trying to load from event"); + // Grab the most revent event image + std::string sql = stringtf( "SELECT MAX(Id) FROM Events WHERE MonitorId=%d AND Frames > 0", id ); + zmDbRow eventid_row; + if ( eventid_row.fetch( sql.c_str() ) ) { + int event_id = atoi( eventid_row[0] ); - Image zone_image( *snap_image ); - if(zone_image.Colours() == ZM_COLOUR_GRAY8) { - zone_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB ); + Debug( 3, "Got event %d", event_id ); + EventStream *stream = new EventStream(); + stream->setStreamStart( event_id, (unsigned int)1 ); + zone_image = stream->getImage(); + } else { + Error("Unable to load an event for monitor %d", id ); + return; + } + } + + if(zone_image->Colours() == ZM_COLOUR_GRAY8) { + zone_image->Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB ); } - for( int i = 0; i < n_zones; i++ ) - { + for( int i = 0; i < n_zones; i++ ) { if ( exclude_id && (!extra_colour || extra_zone.getNumCoords()) && zones[i]->Id() == exclude_id ) continue; Rgb colour; - if ( exclude_id && !extra_zone.getNumCoords() && zones[i]->Id() == exclude_id ) - { + if ( exclude_id && !extra_zone.getNumCoords() && zones[i]->Id() == exclude_id ) { colour = extra_colour; - } - else - { - if ( zones[i]->IsActive() ) - { + } else { + if ( zones[i]->IsActive() ) { colour = RGB_RED; - } - else if ( zones[i]->IsInclusive() ) - { + } else if ( zones[i]->IsInclusive() ) { colour = RGB_ORANGE; - } - else if ( zones[i]->IsExclusive() ) - { + } else if ( zones[i]->IsExclusive() ) { colour = RGB_PURPLE; - } - else if ( zones[i]->IsPreclusive() ) - { + } else if ( zones[i]->IsPreclusive() ) { colour = RGB_BLUE; - } - else - { + } else { colour = RGB_WHITE; } } - zone_image.Fill( colour, 2, zones[i]->GetPolygon() ); - zone_image.Outline( colour, zones[i]->GetPolygon() ); + zone_image->Fill( colour, 2, zones[i]->GetPolygon() ); + zone_image->Outline( colour, zones[i]->GetPolygon() ); } - if ( extra_zone.getNumCoords() ) - { - zone_image.Fill( extra_colour, 2, extra_zone ); - zone_image.Outline( extra_colour, extra_zone ); + if ( extra_zone.getNumCoords() ) { + zone_image->Fill( extra_colour, 2, extra_zone ); + zone_image->Outline( extra_colour, extra_zone ); } static char filename[PATH_MAX]; snprintf( filename, sizeof(filename), "Zones%d.jpg", id ); - zone_image.WriteJpeg( filename ); + zone_image->WriteJpeg( filename ); + delete zone_image; } -void Monitor::DumpImage( Image *dump_image ) const -{ - if ( image_count && !(image_count%10) ) - { +void Monitor::DumpImage( Image *dump_image ) const { + if ( image_count && !(image_count%10) ) { static char filename[PATH_MAX]; static char new_filename[PATH_MAX]; snprintf( filename, sizeof(filename), "Monitor%d.jpg", id ); @@ -1204,8 +1083,7 @@ void Monitor::DumpImage( Image *dump_image ) const } } -bool Monitor::CheckSignal( const Image *image ) -{ +bool Monitor::CheckSignal( const Image *image ) { static bool static_undef = true; /* RGB24 colors */ static uint8_t red_val; @@ -1215,10 +1093,8 @@ bool Monitor::CheckSignal( const Image *image ) static Rgb colour_val; /* RGB32 color */ static int usedsubpixorder; - if ( config.signal_check_points > 0 ) - { - if ( static_undef ) - { + if ( config.signal_check_points > 0 ) { + if ( static_undef ) { static_undef = false; usedsubpixorder = camera->SubpixelOrder(); colour_val = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); /* HTML colour code is actually BGR in memory, we want RGB */ @@ -1235,10 +1111,8 @@ bool Monitor::CheckSignal( const Image *image ) int colours = image->Colours(); int index = 0; - for ( int i = 0; i < config.signal_check_points; i++ ) - { - while( true ) - { + for ( int i = 0; i < config.signal_check_points; i++ ) { + while( true ) { index = (int)(((long long)rand()*(long long)(pixels-1))/RAND_MAX); if ( !config.timestamp_on_capture || !label_format[0] ) break; @@ -1247,32 +1121,32 @@ bool Monitor::CheckSignal( const Image *image ) break; } - if(colours == ZM_COLOUR_GRAY8) { - if ( *(buffer+index) != grayscale_val ) - return true; - - } else if(colours == ZM_COLOUR_RGB24) { - const uint8_t *ptr = buffer+(index*colours); - - if ( usedsubpixorder == ZM_SUBPIX_ORDER_BGR) { - if ( (RED_PTR_BGRA(ptr) != red_val) || (GREEN_PTR_BGRA(ptr) != green_val) || (BLUE_PTR_BGRA(ptr) != blue_val) ) - return true; - } else { - /* Assume RGB */ - if ( (RED_PTR_RGBA(ptr) != red_val) || (GREEN_PTR_RGBA(ptr) != green_val) || (BLUE_PTR_RGBA(ptr) != blue_val) ) + if(colours == ZM_COLOUR_GRAY8) { + if ( *(buffer+index) != grayscale_val ) return true; + + } else if(colours == ZM_COLOUR_RGB24) { + const uint8_t *ptr = buffer+(index*colours); + + if ( usedsubpixorder == ZM_SUBPIX_ORDER_BGR) { + if ( (RED_PTR_BGRA(ptr) != red_val) || (GREEN_PTR_BGRA(ptr) != green_val) || (BLUE_PTR_BGRA(ptr) != blue_val) ) + return true; + } else { + /* Assume RGB */ + if ( (RED_PTR_RGBA(ptr) != red_val) || (GREEN_PTR_RGBA(ptr) != green_val) || (BLUE_PTR_RGBA(ptr) != blue_val) ) + return true; + } + + } else if(colours == ZM_COLOUR_RGB32) { + if ( usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR) { + if ( ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val) ) + return true; + } else { + /* Assume RGBA or BGRA */ + if ( RGBA_BGRA_ZEROALPHA(*(((const Rgb*)buffer)+index)) != RGBA_BGRA_ZEROALPHA(colour_val) ) + return true; + } } - - } else if(colours == ZM_COLOUR_RGB32) { - if ( usedsubpixorder == ZM_SUBPIX_ORDER_ARGB || usedsubpixorder == ZM_SUBPIX_ORDER_ABGR) { - if ( ARGB_ABGR_ZEROALPHA(*(((const Rgb*)buffer)+index)) != ARGB_ABGR_ZEROALPHA(colour_val) ) - return true; - } else { - /* Assume RGBA or BGRA */ - if ( RGBA_BGRA_ZEROALPHA(*(((const Rgb*)buffer)+index)) != RGBA_BGRA_ZEROALPHA(colour_val) ) - return true; - } - } } return( false ); @@ -1280,32 +1154,30 @@ bool Monitor::CheckSignal( const Image *image ) return( true ); } -bool Monitor::Analyse() -{ - if ( shared_data->last_read_index == shared_data->last_write_index ) - { +bool Monitor::Analyse() { + if ( shared_data->last_read_index == shared_data->last_write_index ) { + // I wonder how often this happens. Maybe if this happens we should sleep or something? return( false ); } struct timeval now; gettimeofday( &now, NULL ); - if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) - { + if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) { fps = double(fps_report_interval)/(now.tv_sec-last_fps_time); Info( "%s: %d - Analysing at %.2f fps", name, image_count, fps ); last_fps_time = now.tv_sec; } int index; - if ( adaptive_skip ) - { + if ( adaptive_skip ) { int read_margin = shared_data->last_read_index - shared_data->last_write_index; if ( read_margin < 0 ) read_margin += image_buffer_count; int step = 1; - if ( read_margin > 0 ) - { + // Isn't read_margin always > 0 here? + if ( read_margin > 0 ) { + // TODO explain this so... 90% of image buffer / 50% of read margin? step = (9*image_buffer_count)/(5*read_margin); } @@ -1313,21 +1185,15 @@ bool Monitor::Analyse() if ( pending_frames < 0 ) pending_frames += image_buffer_count; Debug( 4, "RI:%d, WI: %d, PF = %d, RM = %d, Step = %d", shared_data->last_read_index, shared_data->last_write_index, pending_frames, read_margin, step ); - if ( step <= pending_frames ) - { + if ( step <= pending_frames ) { index = (shared_data->last_read_index+step)%image_buffer_count; - } - else - { - if ( pending_frames ) - { + } else { + if ( pending_frames ) { Warning( "Approaching buffer overrun, consider slowing capture, simplifying analysis or increasing ring buffer size" ); } index = shared_data->last_write_index%image_buffer_count; } - } - else - { + } else { index = shared_data->last_write_index%image_buffer_count; } @@ -1335,32 +1201,28 @@ bool Monitor::Analyse() struct timeval *timestamp = snap->timestamp; Image *snap_image = snap->image; - if ( shared_data->action ) - { - if ( shared_data->action & RELOAD ) - { + if ( shared_data->action ) { + // Can there be more than 1 bit set in the action? Shouldn't these be elseifs? + if ( shared_data->action & RELOAD ) { Info( "Received reload indication at count %d", image_count ); shared_data->action &= ~RELOAD; Reload(); } - if ( shared_data->action & SUSPEND ) - { - if ( Active() ) - { + if ( shared_data->action & SUSPEND ) { + if ( Active() ) { Info( "Received suspend indication at count %d", image_count ); shared_data->active = false; //closeEvent(); + } else { + Info( "Received suspend indication at count %d, but wasn't active", image_count ); } - if ( config.max_suspend_time ) - { + if ( config.max_suspend_time ) { auto_resume_time = now.tv_sec + config.max_suspend_time; } shared_data->action &= ~SUSPEND; } - if ( shared_data->action & RESUME ) - { - if ( Enabled() && !Active() ) - { + if ( shared_data->action & RESUME ) { + if ( Enabled() && !Active() ) { Info( "Received resume indication at count %d", image_count ); shared_data->active = true; ref_image = *snap_image; @@ -1369,9 +1231,9 @@ bool Monitor::Analyse() } shared_data->action &= ~RESUME; } - } - if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) - { + } // end ifshared_data->action + + if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) { Info( "Auto resuming at count %d", image_count ); shared_data->active = true; ref_image = *snap_image; @@ -1383,31 +1245,29 @@ bool Monitor::Analyse() static int last_section_mod = 0; static bool last_signal; - if ( static_undef ) - { + if ( static_undef ) { +// Sure would be nice to be able to assume that these were already initialized. It's just 1 compare/branch, but really not neccessary. static_undef = false; timestamps = new struct timeval *[pre_event_count]; images = new Image *[pre_event_count]; last_signal = shared_data->signal; } - if ( Enabled() ) - { + if ( Enabled() ) { bool signal = shared_data->signal; bool signal_change = (signal != last_signal); - if ( trigger_data->trigger_state != TRIGGER_OFF ) - { + + Debug(3, "Motion detection is enabled signal(%d) signal_change(%d)", signal, signal_change); + + if ( trigger_data->trigger_state != TRIGGER_OFF ) { unsigned int score = 0; - if ( Ready() ) - { + if ( Ready() ) { std::string cause; Event::StringSetMap noteSetMap; - if ( trigger_data->trigger_state == TRIGGER_ON ) - { + if ( trigger_data->trigger_state == TRIGGER_ON ) { score += trigger_data->trigger_score; - if ( !event ) - { + if ( !event ) { if ( cause.length() ) cause += ", "; cause += trigger_data->trigger_cause; @@ -1416,25 +1276,21 @@ bool Monitor::Analyse() noteSet.insert( trigger_data->trigger_text ); noteSetMap[trigger_data->trigger_cause] = noteSet; } - if ( signal_change ) - { + if ( signal_change ) { const char *signalText; if ( !signal ) signalText = "Lost"; - else - { + else { signalText = "Reacquired"; score += 100; } Warning( "%s: %s", SIGNAL_CAUSE, signalText ); - if ( event && !signal ) - { + if ( event && !signal ) { Info( "%s: %03d - Closing event %d, signal loss", name, image_count, event->Id() ); closeEvent(); last_section_mod = 0; } - if ( !event ) - { + if ( !event ) { if ( cause.length() ) cause += ", "; cause += SIGNAL_CAUSE; @@ -1445,28 +1301,25 @@ bool Monitor::Analyse() shared_data->state = state = IDLE; shared_data->active = signal; ref_image = *snap_image; - } - else if ( signal && Active() && (function == MODECT || function == MOCORD) ) - { + } else if ( signal && Active() && (function == MODECT || function == MOCORD) ) { Event::StringSet zoneSet; int motion_score = last_motion_score; - if ( !(image_count % (motion_frame_skip+1) ) ) - { + if ( !(image_count % (motion_frame_skip+1) ) ) { // Get new score. - motion_score = last_motion_score = DetectMotion( *snap_image, zoneSet ); + motion_score = DetectMotion( *snap_image, zoneSet ); + + Debug( 3, "After motion detection, last_motion_score(%d), new motion score(%d)", last_motion_score, motion_score ); + // Why are we updating the last_motion_score too? + last_motion_score = motion_score; } //int motion_score = DetectBlack( *snap_image, zoneSet ); - if ( motion_score ) - { - if ( !event ) - { + if ( motion_score ) { + if ( !event ) { score += motion_score; if ( cause.length() ) cause += ", "; cause += MOTION_CAUSE; - } - else - { + } else { score += motion_score; } noteSetMap[MOTION_CAUSE] = zoneSet; @@ -1474,20 +1327,14 @@ bool Monitor::Analyse() } shared_data->active = signal; } - if ( (!signal_change && signal) && n_linked_monitors > 0 ) - { + if ( (!signal_change && signal) && n_linked_monitors > 0 ) { bool first_link = true; Event::StringSet noteSet; - for ( int i = 0; i < n_linked_monitors; i++ ) - { - if ( linked_monitors[i]->isConnected() ) - { - if ( linked_monitors[i]->hasAlarmed() ) - { - if ( !event ) - { - if ( first_link ) - { + for ( int i = 0; i < n_linked_monitors; i++ ) { + if ( linked_monitors[i]->isConnected() ) { + if ( linked_monitors[i]->hasAlarmed() ) { + if ( !event ) { + if ( first_link ) { if ( cause.length() ) cause += ", "; cause += LINKED_CAUSE; @@ -1497,146 +1344,133 @@ bool Monitor::Analyse() noteSet.insert( linked_monitors[i]->Name() ); score += 50; } - } - else - { + } else { linked_monitors[i]->connect(); } } if ( noteSet.size() > 0 ) noteSetMap[LINKED_CAUSE] = noteSet; } - if ( (!signal_change && signal) && (function == RECORD || function == MOCORD) ) - { - if ( event ) - { - int section_mod = timestamp->tv_sec%section_length; - if ( section_mod < last_section_mod ) - { - if ( state == IDLE || state == TAPE || event_close_mode == CLOSE_TIME ) - { - if ( state == TAPE ) - { - shared_data->state = state = IDLE; - Info( "%s: %03d - Closing event %d, section end", name, image_count, event->Id() ) - } - else - Info( "%s: %03d - Closing event %d, section end forced ", name, image_count, event->Id() ); - closeEvent(); - last_section_mod = 0; + + //TODO: What happens is the event closes and sets recording to false then recording to true again so quickly that our capture daemon never picks it up. Maybe need a refresh flag? + // iCON Dec 8 2016 This code doesnt seem to be in master. + if ( (!signal_change && signal) && (function == RECORD || function == MOCORD) ) { + if ( event ) { + //TODO: We shouldn't have to do this every time. Not sure why it clears itself if this isn't here?? + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); + + if ( section_length ) { + int section_mod = timestamp->tv_sec%section_length; + Debug( 3, "Section length (%d) Last Section Mod(%d), new section mod(%d)", section_length, last_section_mod, section_mod ); + if ( section_mod < last_section_mod ) { + //if ( state == IDLE || state == TAPE || event_close_mode == CLOSE_TIME ) { + //if ( state == TAPE ) { + //shared_data->state = state = IDLE; + //Info( "%s: %03d - Closing event %d, section end", name, image_count, event->Id() ) + //} else { + Info( "%s: %03d - Closing event %d, section end forced ", name, image_count, event->Id() ); + //} + closeEvent(); + last_section_mod = 0; + //} else { + //Debug( 2, "Time to close event, but state (%d) is not IDLE or TAPE and event_close_mode is not CLOSE_TIME (%d)", state, event_close_mode ); + //} + } else { + last_section_mod = section_mod; } } - else - { - last_section_mod = section_mod; - } - } - if ( !event ) - { + } // end if section_length + + if ( ! event ) { // Create event - event = new Event( this, *timestamp, "Continuous", noteSetMap ); + event = new Event( this, *timestamp, "Continuous", noteSetMap, videoRecording ); shared_data->last_event = event->Id(); + //set up video store data + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); + video_store_data->recording = true; Info( "%s: %03d - Opening new event %d, section start", name, image_count, event->Id() ); /* To prevent cancelling out an existing alert\prealarm\alarm state */ - if ( state == IDLE ) - { + if ( state == IDLE ) { shared_data->state = state = TAPE; } //if ( config.overlap_timed_events ) - if ( false ) - { + if ( false ) { int pre_index; int pre_event_images = pre_event_count; - if ( analysis_fps ) - { + if ( analysis_fps ) { // If analysis fps is set, // compute the index for pre event images in the dedicated buffer pre_index = image_count%pre_event_buffer_count; // Seek forward the next filled slot in to the buffer (oldest data) // from the current position - while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) - { + while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) { pre_index = (pre_index + 1)%pre_event_buffer_count; // Slot is empty, removing image from counter pre_event_images--; } - } - else - { + } else { // If analysis fps is not set (analysis performed at capturing framerate), // compute the index for pre event images in the capturing buffer pre_index = ((index + image_buffer_count) - pre_event_count)%image_buffer_count; // Seek forward the next filled slot in to the buffer (oldest data) // from the current position - while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) - { + while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) { pre_index = (pre_index + 1)%image_buffer_count; // Slot is empty, removing image from counter pre_event_images--; } } - if ( pre_event_images ) - { - if ( analysis_fps ) - for ( int i = 0; i < pre_event_images; i++ ) - { + if ( pre_event_images ) { + if ( analysis_fps ) { + for ( int i = 0; i < pre_event_images; i++ ) { timestamps[i] = pre_event_buffer[pre_index].timestamp; images[i] = pre_event_buffer[pre_index].image; pre_index = (pre_index + 1)%pre_event_buffer_count; } - else - for ( int i = 0; i < pre_event_images; i++ ) - { + } else { + for ( int i = 0; i < pre_event_images; i++ ) { timestamps[i] = image_buffer[pre_index].timestamp; images[i] = image_buffer[pre_index].image; pre_index = (pre_index + 1)%image_buffer_count; } - + } event->AddFrames( pre_event_images, images, timestamps ); } - } - } + } // end if false or config.overlap_timed_events + } // end if ! event } - if ( score ) - { - if ( (state == IDLE || state == TAPE || state == PREALARM ) ) - { - if ( Event::PreAlarmCount() >= (alarm_frame_count-1) ) - { + if ( score ) { + if ( (state == IDLE || state == TAPE || state == PREALARM ) ) { + if ( Event::PreAlarmCount() >= (alarm_frame_count-1) ) { Info( "%s: %03d - Gone into alarm state", name, image_count ); shared_data->state = state = ALARM; - if ( signal_change || (function != MOCORD && state != ALERT) ) - { + if ( signal_change || (function != MOCORD && state != ALERT) ) { int pre_index; int pre_event_images = pre_event_count; - if ( analysis_fps ) - { + if ( analysis_fps ) { // If analysis fps is set, // compute the index for pre event images in the dedicated buffer pre_index = image_count%pre_event_buffer_count; // Seek forward the next filled slot in to the buffer (oldest data) // from the current position - while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) - { + while ( pre_event_images && !pre_event_buffer[pre_index].timestamp->tv_sec ) { pre_index = (pre_index + 1)%pre_event_buffer_count; // Slot is empty, removing image from counter pre_event_images--; } event = new Event( this, *(pre_event_buffer[pre_index].timestamp), cause, noteSetMap ); - } - else - { + } else { // If analysis fps is not set (analysis performed at capturing framerate), // compute the index for pre event images in the capturing buffer if ( alarm_frame_count > 1 ) @@ -1646,8 +1480,7 @@ bool Monitor::Analyse() // Seek forward the next filled slot in to the buffer (oldest data) // from the current position - while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) - { + while ( pre_event_images && !image_buffer[pre_index].timestamp->tv_sec ) { pre_index = (pre_index + 1)%image_buffer_count; // Slot is empty, removing image from counter pre_event_images--; @@ -1656,132 +1489,100 @@ bool Monitor::Analyse() event = new Event( this, *(image_buffer[pre_index].timestamp), cause, noteSetMap ); } shared_data->last_event = event->Id(); + //set up video store data + snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); + video_store_data->recording = true; Info( "%s: %03d - Opening new event %d, alarm start", name, image_count, event->Id() ); - if ( pre_event_images ) - { - if ( analysis_fps ) - for ( int i = 0; i < pre_event_images; i++ ) - { + if ( pre_event_images ) { + if ( analysis_fps ) { + for ( int i = 0; i < pre_event_images; i++ ) { timestamps[i] = pre_event_buffer[pre_index].timestamp; images[i] = pre_event_buffer[pre_index].image; pre_index = (pre_index + 1)%pre_event_buffer_count; } - else - for ( int i = 0; i < pre_event_images; i++ ) - { + } else { + for ( int i = 0; i < pre_event_images; i++ ) { timestamps[i] = image_buffer[pre_index].timestamp; images[i] = image_buffer[pre_index].image; pre_index = (pre_index + 1)%image_buffer_count; } + } event->AddFrames( pre_event_images, images, timestamps ); } - if ( alarm_frame_count ) - { + if ( alarm_frame_count ) { event->SavePreAlarmFrames(); } } - } - else if ( state != PREALARM ) - { + } else if ( state != PREALARM ) { Info( "%s: %03d - Gone into prealarm state", name, image_count ); shared_data->state = state = PREALARM; } - } - else if ( state == ALERT ) - { + } else if ( state == ALERT ) { Info( "%s: %03d - Gone back into alarm state", name, image_count ); shared_data->state = state = ALARM; } last_alarm_count = image_count; - } - else - { - if ( state == ALARM ) - { + } else { + if ( state == ALARM ) { Info( "%s: %03d - Gone into alert state", name, image_count ); shared_data->state = state = ALERT; - } - else if ( state == ALERT ) - { - if ( image_count-last_alarm_count > post_event_count ) - { + } else if ( state == ALERT ) { + if ( image_count-last_alarm_count > post_event_count ) { Info( "%s: %03d - Left alarm state (%d) - %d(%d) images", name, image_count, event->Id(), event->Frames(), event->AlarmFrames() ); //if ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE ) - if ( function != MOCORD || event_close_mode == CLOSE_ALARM ) - { + if ( function != MOCORD || event_close_mode == CLOSE_ALARM ) { shared_data->state = state = IDLE; Info( "%s: %03d - Closing event %d, alarm end%s", name, image_count, event->Id(), (function==MOCORD)?", section truncated":"" ); closeEvent(); - } - else - { + } else { shared_data->state = state = TAPE; } } } - if ( state == PREALARM ) - { - if ( function != MOCORD ) - { + if ( state == PREALARM ) { + if ( function != MOCORD ) { shared_data->state = state = IDLE; - } - else - { + } else { shared_data->state = state = TAPE; } } if ( Event::PreAlarmCount() ) Event::EmptyPreAlarmFrames(); } - if ( state != IDLE ) - { - if ( state == PREALARM || state == ALARM ) - { - if ( config.create_analysis_images ) - { + if ( state != IDLE ) { + if ( state == PREALARM || state == ALARM ) { + if ( config.create_analysis_images ) { bool got_anal_image = false; alarm_image.Assign( *snap_image ); - for( int i = 0; i < n_zones; i++ ) - { - if ( zones[i]->Alarmed() ) - { - if ( zones[i]->AlarmImage() ) - { + for( int i = 0; i < n_zones; i++ ) { + if ( zones[i]->Alarmed() ) { + if ( zones[i]->AlarmImage() ) { alarm_image.Overlay( *(zones[i]->AlarmImage()) ); got_anal_image = true; } - if ( config.record_event_stats && state == ALARM ) - { + if ( config.record_event_stats && state == ALARM ) { zones[i]->RecordStats( event ); } } } - if ( got_anal_image ) - { + if ( got_anal_image ) { if ( state == PREALARM ) Event::AddPreAlarmFrame( snap_image, *timestamp, score, &alarm_image ); else event->AddFrame( snap_image, *timestamp, score, &alarm_image ); - } - else - { + } else { if ( state == PREALARM ) Event::AddPreAlarmFrame( snap_image, *timestamp, score ); else event->AddFrame( snap_image, *timestamp, score ); } - } - else - { - for( int i = 0; i < n_zones; i++ ) - { - if ( zones[i]->Alarmed() ) - { - if ( config.record_event_stats && state == ALARM ) - { + } else { + for( int i = 0; i < n_zones; i++ ) { + if ( zones[i]->Alarmed() ) { + if ( config.record_event_stats && state == ALARM ) { zones[i]->RecordStats( event ); } } @@ -1793,42 +1594,35 @@ bool Monitor::Analyse() } if ( event && noteSetMap.size() > 0 ) event->updateNotes( noteSetMap ); - } - else if ( state == ALERT ) - { + } else if ( state == ALERT ) { event->AddFrame( snap_image, *timestamp ); if ( noteSetMap.size() > 0 ) event->updateNotes( noteSetMap ); - } - else if ( state == TAPE ) - { - if ( !(image_count%(frame_skip+1)) ) - { - if ( config.bulk_frame_interval > 1 ) - { + } else if ( state == TAPE ) { + //Video Storage: activate only for supported cameras. Event::AddFrame knows whether or not we are recording video and saves frames accordingly + if((GetOptVideoWriter() == 2) && camera->SupportsNativeVideo()) { + video_store_data->recording = true; + } + if ( !(image_count%(frame_skip+1)) ) { + if ( config.bulk_frame_interval > 1 ) { event->AddFrame( snap_image, *timestamp, (event->Frames()AddFrame( snap_image, *timestamp ); } } } - } + } // end if ! IDLE } - } - else - { - if ( event ) - { + } else { + if ( event ) { Info( "%s: %03d - Closing event %d, trigger off", name, image_count, event->Id() ); closeEvent(); } shared_data->state = state = IDLE; last_section_mod = 0; - } - if ( (!signal_change && signal) && (function == MODECT || function == MOCORD) ) - { + } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) + + if ( (!signal_change && signal) && (function == MODECT || function == MOCORD) ) { if ( state == ALARM ) { ref_image.Blend( *snap_image, alarm_ref_blend_perc ); } else { @@ -1836,14 +1630,13 @@ bool Monitor::Analyse() } } last_signal = signal; - } + } // end if Enabled() - shared_data->last_read_index = index%image_buffer_count; + shared_data->last_read_index = index % image_buffer_count; //shared_data->last_read_time = image_buffer[index].timestamp->tv_sec; shared_data->last_read_time = now.tv_sec; - if ( analysis_fps ) - { + if ( analysis_fps ) { // If analysis fps is set, add analysed image to dedicated pre event buffer int pre_index = image_count%pre_event_buffer_count; pre_event_buffer[pre_index].image->Assign(*snap->image); @@ -1855,8 +1648,7 @@ bool Monitor::Analyse() return( true ); } -void Monitor::Reload() -{ +void Monitor::Reload() { Debug( 1, "Reloading monitor %s", name ); if ( event ) @@ -1865,29 +1657,26 @@ void Monitor::Reload() closeEvent(); static char sql[ZM_SQL_MED_BUFSIZ]; + // This seems to have fallen out of date. snprintf( sql, sizeof(sql), "select Function+0, Enabled, LinkedMonitors, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, WarmupCount, PreEventCount, PostEventCount, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Id = '%d'", id ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } int n_monitors = mysql_num_rows( result ); - if ( n_monitors != 1 ) - { + if ( n_monitors != 1 ) { Error( "Bogus number of monitors, %d, returned. Can't reload", n_monitors ); return; } - if ( MYSQL_ROW dbrow = mysql_fetch_row( result ) ) - { + if ( MYSQL_ROW dbrow = mysql_fetch_row( result ) ) { int index = 0; function = (Function)atoi(dbrow[index++]); enabled = atoi(dbrow[index++]); @@ -1927,8 +1716,7 @@ void Monitor::Reload() ReloadLinkedMonitors( p_linked_monitors ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -1937,11 +1725,9 @@ void Monitor::Reload() ReloadZones(); } -void Monitor::ReloadZones() -{ +void Monitor::ReloadZones() { Debug( 1, "Reloading zones for monitor %s", name ); - for( int i = 0; i < n_zones; i++ ) - { + for( int i = 0; i < n_zones; i++ ) { delete zones[i]; } delete[] zones; @@ -1950,13 +1736,10 @@ void Monitor::ReloadZones() //DumpZoneImage(); } -void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) -{ +void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) { Debug( 1, "Reloading linked monitors for monitor %s, '%s'", name, p_linked_monitors ); - if ( n_linked_monitors ) - { - for( int i = 0; i < n_linked_monitors; i++ ) - { + if ( n_linked_monitors ) { + for( int i = 0; i < n_linked_monitors; i++ ) { delete linked_monitors[i]; } delete[] linked_monitors; @@ -1964,44 +1747,34 @@ void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) } n_linked_monitors = 0; - if ( p_linked_monitors ) - { + if ( p_linked_monitors ) { int n_link_ids = 0; unsigned int link_ids[256]; char link_id_str[8]; char *dest_ptr = link_id_str; const char *src_ptr = p_linked_monitors; - while( 1 ) - { + while( 1 ) { dest_ptr = link_id_str; - while( *src_ptr >= '0' && *src_ptr <= '9' ) - { - if ( (dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) - { + while( *src_ptr >= '0' && *src_ptr <= '9' ) { + if ( (dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) { *dest_ptr++ = *src_ptr++; - } - else - { + } else { break; } } // Add the link monitor - if ( dest_ptr != link_id_str ) - { + if ( dest_ptr != link_id_str ) { *dest_ptr = '\0'; unsigned int link_id = atoi(link_id_str); - if ( link_id > 0 && link_id != id) - { + if ( link_id > 0 && link_id != id) { Debug( 3, "Found linked monitor id %d", link_id ); int j; - for ( j = 0; j < n_link_ids; j++ ) - { + for ( j = 0; j < n_link_ids; j++ ) { if ( link_ids[j] == link_id ) break; } - if ( j == n_link_ids ) // Not already found - { + if ( j == n_link_ids ) { // Not already found link_ids[n_link_ids++] = link_id; } } @@ -2013,38 +1786,31 @@ void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) if ( !*src_ptr ) break; } - if ( n_link_ids > 0 ) - { + if ( n_link_ids > 0 ) { Debug( 1, "Linking to %d monitors", n_link_ids ); linked_monitors = new MonitorLink *[n_link_ids]; int count = 0; - for ( int i = 0; i < n_link_ids; i++ ) - { + for ( int i = 0; i < n_link_ids; i++ ) { Debug( 1, "Checking linked monitor %d", link_ids[i] ); static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "select Id, Name from Monitors where Id = %d and Function != 'None' and Function != 'Monitor' and Enabled = 1", link_ids[i] ); - if ( mysql_query( &dbconn, sql ) ) - { + if ( mysql_query( &dbconn, sql ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } int n_monitors = mysql_num_rows( result ); - if ( n_monitors == 1 ) - { + if ( n_monitors == 1 ) { MYSQL_ROW dbrow = mysql_fetch_row( result ); Debug( 1, "Linking to monitor %d", link_ids[i] ); linked_monitors[count++] = new MonitorLink( link_ids[i], dbrow[1] ); - } - else - { + } else { Warning( "Can't link to monitor %d, invalid id, function or not enabled", link_ids[i] ); } mysql_free_result( result ); @@ -2055,9 +1821,9 @@ void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) } #if ZM_HAS_V4L -int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose purpose ) -{ - std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Function != 'None' and Type = 'Local'"; +int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose purpose ) { + std::string sql = "select Id, Name, ServerId, StorageId, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Method, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Function != 'None' and Type = 'Local'"; +; if ( device[0] ) { sql += " AND Device='"; sql += device; @@ -2077,13 +1843,13 @@ int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose Debug( 1, "Got %d monitors", n_monitors ); delete[] monitors; monitors = new Monitor *[n_monitors]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int id = atoi(dbrow[col]); col++; const char *name = dbrow[col]; col++; unsigned int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; + unsigned int storage_id = atoi(dbrow[col]); col++; int function = atoi(dbrow[col]); col++; int enabled = atoi(dbrow[col]); col++; const char *linked_monitors = dbrow[col]; col++; @@ -2107,7 +1873,7 @@ int Monitor::LoadLocalMonitors( const char *device, Monitor **&monitors, Purpose } else { v4l_captures_per_frame = config.captures_per_frame; } -Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); + Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); col++; const char *method = dbrow[col]; col++; @@ -2117,6 +1883,12 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); int palette = atoi(dbrow[col]); col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; + + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; @@ -2174,6 +1946,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); hue, colour, purpose==CAPTURE, + record_audio, extras ); @@ -2181,12 +1954,17 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); id, name, server_id, + storage_id, function, enabled, linked_monitors, camera, orientation, deinterlacing, + savejpegs, + videowriter, + encoderparams, + record_audio, event_prefix, label_format, Coord( label_x, label_y ), @@ -2214,14 +1992,14 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); 0, 0 ); + camera->setMonitor( monitors[i] ); Zone **zones = 0; int n_zones = Zone::Load( monitors[i], zones ); monitors[i]->AddZones( n_zones, zones ); monitors[i]->AddPrivacyBitmask( zones ); Debug( 1, "Loaded monitor %d(%s), %d zones", id, name, n_zones ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2232,9 +2010,8 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); } #endif // ZM_HAS_V4L -int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const char *port, const char *path, Monitor **&monitors, Purpose purpose ) -{ - std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Remote'"; +int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const char *port, const char *path, Monitor **&monitors, Purpose purpose ) { + std::string sql = "select Id, Name, ServerId, StorageId, Function+0, Enabled, LinkedMonitors, Protocol, Method, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Remote'"; if ( staticConfig.SERVER_ID ) { sql += stringtf( " AND ServerId=%d", staticConfig.SERVER_ID ); } @@ -2253,22 +2030,22 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c Debug( 1, "Got %d monitors", n_monitors ); delete[] monitors; monitors = new Monitor *[n_monitors]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int id = atoi(dbrow[col]); col++; std::string name = dbrow[col]; col++; unsigned int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; + unsigned int storage_id = atoi(dbrow[col]); col++; int function = atoi(dbrow[col]); col++; int enabled = atoi(dbrow[col]); col++; const char *linked_monitors = dbrow[col]; col++; - std::string protocol = dbrow[col]; col++; - std::string method = dbrow[col]; col++; - std::string host = dbrow[col]; col++; - std::string port = dbrow[col]; col++; - std::string path = dbrow[col]; col++; + std::string protocol = dbrow[col] ? dbrow[col] : ""; col++; + std::string method = dbrow[col] ? dbrow[col] : ""; col++; + std::string host = dbrow[col] ? dbrow[col] : ""; col++; + std::string port = dbrow[col] ? dbrow[col] : ""; col++; + std::string path = dbrow[col] ? dbrow[col] : ""; col++; int width = atoi(dbrow[col]); col++; int height = atoi(dbrow[col]); col++; @@ -2276,14 +2053,19 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c /* int palette = atoi(dbrow[col]); */ col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; - bool rtsp_describe = (*dbrow[col] != '0'); col++; + bool rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++; + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - std::string event_prefix = dbrow[col]; col++; - std::string label_format = dbrow[col]; col++; + std::string event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + std::string label_format = dbrow[col] ? dbrow[col] : ""; col++; int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; @@ -2309,8 +2091,7 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c bool embed_exif = (*dbrow[col] != '0'); col++; Camera *camera = 0; - if ( protocol == "http" ) - { + if ( protocol == "http" ) { camera = new RemoteCameraHttp( id, method, @@ -2324,12 +2105,12 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); } #if HAVE_LIBAVFORMAT - else if ( protocol == "rtsp" ) - { + else if ( protocol == "rtsp" ) { camera = new RemoteCameraRtsp( id, method, @@ -2344,12 +2125,12 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); } #endif // HAVE_LIBAVFORMAT - else - { + else { Fatal( "Unexpected remote camera protocol '%s'", protocol.c_str() ); } @@ -2357,12 +2138,17 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c id, name.c_str(), server_id, + storage_id, function, enabled, linked_monitors, camera, orientation, deinterlacing, + savejpegs, + videowriter, + encoderparams, + record_audio, event_prefix.c_str(), label_format.c_str(), Coord( label_x, label_y ), @@ -2386,19 +2172,18 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c track_motion, RGB_WHITE, embed_exif, - purpose, + purpose, 0, 0 - ); + camera->setMonitor( monitors[i] ); Zone **zones = 0; int n_zones = Zone::Load( monitors[i], zones ); monitors[i]->AddZones( n_zones, zones ); monitors[i]->AddPrivacyBitmask( zones ); Debug( 1, "Loaded monitor %d(%s), %d zones", id, name.c_str(), n_zones ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2408,9 +2193,8 @@ int Monitor::LoadRemoteMonitors( const char *protocol, const char *host, const c return( n_monitors ); } -int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose purpose ) -{ - std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'File'"; +int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose purpose ) { + std::string sql = "select Id, Name, ServerId, StorageId, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'File'"; if ( file[0] ) { sql += " AND Path='"; sql += file; @@ -2421,8 +2205,7 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu } Debug( 1, "Loading File Monitors with %s", sql.c_str() ); MYSQL_RES *result = zmDbFetch( sql.c_str() ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2430,13 +2213,13 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu Debug( 1, "Got %d monitors", n_monitors ); delete[] monitors; monitors = new Monitor *[n_monitors]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int id = atoi(dbrow[col]); col++; const char *name = dbrow[col]; col++; unsigned int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; + unsigned int storage_id = atoi(dbrow[col]); col++; int function = atoi(dbrow[col]); col++; int enabled = atoi(dbrow[col]); col++; const char *linked_monitors = dbrow[col]; col++; @@ -2449,13 +2232,19 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu /* int palette = atoi(dbrow[col]); */ col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; + + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col]; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - const char *event_prefix = dbrow[col]; col++; - const char *label_format = dbrow[col]; col++; + std::string event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + std::string label_format = dbrow[col] ? dbrow[col] : ""; col++; int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; @@ -2490,21 +2279,27 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); monitors[i] = new Monitor( id, name, server_id, + storage_id, function, enabled, linked_monitors, camera, orientation, deinterlacing, - event_prefix, - label_format, + savejpegs, + videowriter, + encoderparams, + record_audio, + event_prefix.c_str(), + label_format.c_str(), Coord( label_x, label_y ), label_size, image_buffer_count, @@ -2530,14 +2325,14 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu 0, 0 ); + camera->setMonitor( monitors[i] ); Zone **zones = 0; int n_zones = Zone::Load( monitors[i], zones ); monitors[i]->AddZones( n_zones, zones ); monitors[i]->AddPrivacyBitmask( zones ); Debug( 1, "Loaded monitor %d(%s), %d zones", id, name, n_zones ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2548,9 +2343,8 @@ int Monitor::LoadFileMonitors( const char *file, Monitor **&monitors, Purpose pu } #if HAVE_LIBAVFORMAT -int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose purpose ) -{ - std::string sql = "select Id, Name, ServerId, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Ffmpeg'"; +int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose purpose ) { + std::string sql = "select Id, Name, ServerId, StorageId, Function+0, Enabled, LinkedMonitors, Path, Method, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif from Monitors where Function != 'None' and Type = 'Ffmpeg'"; if ( file[0] ) { sql += " AND Path = '"; sql += file; @@ -2570,20 +2364,20 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose Debug( 1, "Got %d monitors", n_monitors ); delete[] monitors; monitors = new Monitor *[n_monitors]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int col = 0; int id = atoi(dbrow[col]); col++; const char *name = dbrow[col]; col++; unsigned int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; + unsigned int storage_id = atoi(dbrow[col]); col++; int function = atoi(dbrow[col]); col++; int enabled = atoi(dbrow[col]); col++; - const char *linked_monitors = dbrow[col]; col++; + const char *linked_monitors = dbrow[col] ? dbrow[col] : ""; col++; const char *path = dbrow[col]; col++; const char *method = dbrow[col]; col++; - const char *options = dbrow[col]; col++; + const char *options = dbrow[col] ? dbrow[col] : ""; col++; int width = atoi(dbrow[col]); col++; int height = atoi(dbrow[col]); col++; @@ -2591,13 +2385,19 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose /* int palette = atoi(dbrow[col]); */ col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; + + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - const char *event_prefix = dbrow[col]; col++; - const char *label_format = dbrow[col]; col++; + std::string event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + std::string label_format = dbrow[col] ? dbrow[col] : ""; col++; int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; @@ -2614,8 +2414,10 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose int motion_frame_skip = atoi(dbrow[col]); col++; double analysis_fps = dbrow[col] ? strtod(dbrow[col], NULL) : 0; col++; unsigned int analysis_update_delay = strtoul(dbrow[col++], NULL, 0); - int capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++; - int alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++; + double capture_fps = dbrow[col] ? atof(dbrow[col]) : 0;col++; + int capture_delay = capture_fps >0.0 ?int(DT_PREC_3/capture_fps):0; + double alarm_capture_fps = dbrow[col] ? atof(dbrow[col]) : 0; col++; + int alarm_capture_delay = alarm_capture_fps > 0.0 ?int(DT_PREC_3/alarm_capture_fps):0; int fps_report_interval = atoi(dbrow[col]); col++; int ref_blend_perc = atoi(dbrow[col]); col++; int alarm_ref_blend_perc = atoi(dbrow[col]); col++; @@ -2634,21 +2436,27 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); monitors[i] = new Monitor( id, name, server_id, + storage_id, function, enabled, linked_monitors, camera, orientation, deinterlacing, - event_prefix, - label_format, + savejpegs, + videowriter, + encoderparams, + record_audio, + event_prefix.c_str(), + label_format.c_str(), Coord( label_x, label_y ), label_size, image_buffer_count, @@ -2674,14 +2482,15 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose 0, 0 ); + + camera->setMonitor( monitors[i] ); Zone **zones = 0; int n_zones = Zone::Load( monitors[i], zones ); monitors[i]->AddZones( n_zones, zones ); monitors[i]->AddPrivacyBitmask( zones ); Debug( 1, "Loaded monitor %d(%s), %d zones", id, name, n_zones ); } - if ( mysql_errno( &dbconn ) ) - { + if ( mysql_errno( &dbconn ) ) { Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2692,12 +2501,11 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose } #endif // HAVE_LIBAVFORMAT -Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) -{ - std::string sql = stringtf( "select Id, Name, ServerId, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Protocol, Method, Host, Port, Path, Options, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Id = %d", p_id ); +Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) { + std::string sql = stringtf( "select Id, Name, ServerId, StorageId, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, Protocol, Method, Host, Port, Path, Options, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, SaveJPEGs, VideoWriter, EncoderParameters, RecordAudio, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPS, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour, Exif from Monitors where Id = %d", p_id ); - MYSQL_ROW dbrow = zmDbFetchOne( sql.c_str() ); - if ( ! dbrow ) { + zmDbRow dbrow; + if ( ! dbrow.fetch( sql.c_str() ) ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -2707,10 +2515,11 @@ Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) unsigned int id = atoi(dbrow[col]); col++; std::string name = dbrow[col]; col++; unsigned int server_id = dbrow[col] ? atoi(dbrow[col]) : 0; col++; + unsigned int storage_id = atoi(dbrow[col]); col++; std::string type = dbrow[col]; col++; int function = atoi(dbrow[col]); col++; int enabled = atoi(dbrow[col]); col++; - std::string linked_monitors = dbrow[col]; col++; + std::string linked_monitors = dbrow[col] ? dbrow[col] : ""; col++; std::string device = dbrow[col]; col++; int channel = atoi(dbrow[col]); col++; @@ -2732,17 +2541,17 @@ Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) } else { v4l_captures_per_frame = config.captures_per_frame; } -Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); + Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); col++; - std::string protocol = dbrow[col]; col++; - std::string method = dbrow[col]; col++; - std::string host = dbrow[col]; col++; - std::string port = dbrow[col]; col++; - std::string path = dbrow[col]; col++; - std::string options = dbrow[col]; col++; - std::string user = dbrow[col]; col++; - std::string pass = dbrow[col]; col++; + std::string protocol = dbrow[col] ? dbrow[col] : ""; col++; + std::string method = dbrow[col] ? dbrow[col] : ""; col++; + std::string host = dbrow[col] ? dbrow[col] : ""; col++; + std::string port = dbrow[col] ? dbrow[col] : ""; col++; + std::string path = dbrow[col] ? dbrow[col] : ""; col++; + std::string options = dbrow[col] ? dbrow[col] : ""; col++; + std::string user = dbrow[col] ? dbrow[col] : ""; col++; + std::string pass = dbrow[col] ? dbrow[col] : ""; col++; int width = atoi(dbrow[col]); col++; int height = atoi(dbrow[col]); col++; @@ -2750,14 +2559,19 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); int palette = atoi(dbrow[col]); col++; Orientation orientation = (Orientation)atoi(dbrow[col]); col++; unsigned int deinterlacing = atoi(dbrow[col]); col++; - bool rtsp_describe = (*dbrow[col] != '0'); col++; + bool rtsp_describe = (dbrow[col] && *dbrow[col] != '0'); col++; + int savejpegs = atoi(dbrow[col]); col++; + VideoWriter videowriter = (VideoWriter)atoi(dbrow[col]); col++; + std::string encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + bool record_audio = (*dbrow[col] != '0'); col++; + int brightness = atoi(dbrow[col]); col++; int contrast = atoi(dbrow[col]); col++; int hue = atoi(dbrow[col]); col++; int colour = atoi(dbrow[col]); col++; - std::string event_prefix = dbrow[col]; col++; - std::string label_format = dbrow[col]; col++; + std::string event_prefix = dbrow[col] ? dbrow[col] : ""; col++; + std::string label_format = dbrow[col] ? dbrow[col] : ""; col++; int label_x = atoi(dbrow[col]); col++; int label_y = atoi(dbrow[col]); col++; @@ -2792,8 +2606,7 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); int extras = (deinterlacing>>24)&0xff; Camera *camera = 0; - if ( type == "Local" ) - { + if ( type == "Local" ) { #if ZM_HAS_V4L camera = new LocalCamera( id, @@ -2812,16 +2625,14 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); hue, colour, purpose==CAPTURE, + record_audio, extras ); #else // ZM_HAS_V4L Fatal( "You must have video4linux libraries and headers installed to use local analog or USB cameras for monitor %d", id ); #endif // ZM_HAS_V4L - } - else if ( type == "Remote" ) - { - if ( protocol == "http" ) - { + } else if ( type == "Remote" ) { + if ( protocol == "http" ) { camera = new RemoteCameraHttp( id, method.c_str(), @@ -2835,11 +2646,10 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); - } - else if ( protocol == "rtsp" ) - { + } else if ( protocol == "rtsp" ) { #if HAVE_LIBAVFORMAT camera = new RemoteCameraRtsp( id, @@ -2855,19 +2665,16 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); #else // HAVE_LIBAVFORMAT Fatal( "You must have ffmpeg libraries installed to use remote camera protocol '%s' for monitor %d", protocol.c_str(), id ); #endif // HAVE_LIBAVFORMAT - } - else - { + } else { Fatal( "Unexpected remote camera protocol '%s' for monitor %d", protocol.c_str(), id ); } - } - else if ( type == "File" ) - { + } else if ( type == "File" ) { camera = new FileCamera( id, path.c_str(), @@ -2878,11 +2685,10 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); - } - else if ( type == "Ffmpeg" ) - { + } else if ( type == "Ffmpeg" ) { #if HAVE_LIBAVFORMAT camera = new FfmpegCamera( id, @@ -2896,14 +2702,13 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); #else // HAVE_LIBAVFORMAT Fatal( "You must have ffmpeg libraries installed to use ffmpeg cameras for monitor %d", id ); #endif // HAVE_LIBAVFORMAT - } - else if (type == "Libvlc") - { + } else if (type == "Libvlc") { #if HAVE_LIBVLC camera = new LibvlcCamera( id, @@ -2917,14 +2722,13 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); #else // HAVE_LIBVLC Fatal( "You must have vlc libraries installed to use vlc cameras for monitor %d", id ); #endif // HAVE_LIBVLC - } - else if ( type == "cURL" ) - { + } else if ( type == "cURL" ) { #if HAVE_LIBCURL camera = new cURLCamera( id, @@ -2938,26 +2742,30 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); contrast, hue, colour, - purpose==CAPTURE + purpose==CAPTURE, + record_audio ); #else // HAVE_LIBCURL Fatal( "You must have libcurl installed to use ffmpeg cameras for monitor %d", id ); #endif // HAVE_LIBCURL - } - else - { + } else { Fatal( "Bogus monitor type '%s' for monitor %d", type.c_str(), id ); } monitor = new Monitor( id, name.c_str(), server_id, + storage_id, function, enabled, linked_monitors.c_str(), camera, orientation, deinterlacing, + savejpegs, + videowriter, + encoderparams, + record_audio, event_prefix.c_str(), label_format.c_str(), Coord( label_x, label_y ), @@ -2987,9 +2795,10 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); ); + camera->setMonitor( monitor ); + int n_zones = 0; - if ( load_zones ) - { + if ( load_zones ) { Zone **zones = 0; n_zones = Zone::Load( monitor, zones ); monitor->AddZones( n_zones, zones ); @@ -2999,22 +2808,35 @@ Debug( 1, "Got %d for v4l_captures_per_frame", v4l_captures_per_frame ); return( monitor ); } -int Monitor::Capture() -{ - static int FirstCapture = 1; +/* Returns 0 on success, even if no new images are available (transient error) + * Returns -1 on failure. + */ +int Monitor::Capture() { + static int FirstCapture = 1; // Used in de-interlacing to indicate whether this is the even or odd image int captureResult; - int index = image_count%image_buffer_count; + unsigned int index = image_count%image_buffer_count; Image* capture_image = image_buffer[index].image; - if ( (deinterlacing & 0xff) == 4) { + unsigned int deinterlacing_value = deinterlacing & 0xff; + + if ( deinterlacing_value == 4 ) { if ( FirstCapture != 1 ) { /* Copy the next image into the shared memory */ capture_image->CopyBuffer(*(next_buffer.image)); } - + /* Capture a new next image */ - captureResult = camera->Capture(*(next_buffer.image)); + + //Check if FFMPEG camera + // Icon: I don't think we can support de-interlacing on ffmpeg input.... most of the time it will be h264 or mpeg4 + if(( videowriter == H264PASSTHROUGH ) && camera->SupportsNativeVideo()){ + captureResult = camera->CaptureAndRecord(*(next_buffer.image), + video_store_data->recording, + video_store_data->event_file ); + }else{ + captureResult = camera->Capture(*(next_buffer.image)); + } if ( FirstCapture ) { FirstCapture = 0; @@ -3022,12 +2844,23 @@ int Monitor::Capture() } } else { - /* Capture directly into image buffer, avoiding the need to memcpy() */ - captureResult = camera->Capture(*capture_image); + //Check if FFMPEG camera + if ( (videowriter == H264PASSTHROUGH ) && camera->SupportsNativeVideo()){ + //Warning("ZMC: Recording: %d", video_store_data->recording); + captureResult = camera->CaptureAndRecord(*capture_image, video_store_data->recording, video_store_data->event_file); + }else{ + /* Capture directly into image buffer, avoiding the need to memcpy() */ + captureResult = camera->Capture(*capture_image); + } } - if ( captureResult != 0 ) - { + // CaptureAndRecord returns # of frames captured I think + if ( ( videowriter == H264PASSTHROUGH ) && ( captureResult > 0 ) ) { + //video_store_data->frameNumber = captureResult; + captureResult = 0; + } + + if ( captureResult != 0 ) { // Unable to capture image for temporary reason // Fake a signal loss image Rgb signalcolor; @@ -3038,65 +2871,52 @@ int Monitor::Capture() captureResult = 1; } - if ( captureResult == 1 ) - { + if ( captureResult == 1 ) { /* Deinterlacing */ - if ( (deinterlacing & 0xff) == 1 ) { + if ( deinterlacing_value == 1 ) { capture_image->Deinterlace_Discard(); - } else if ( (deinterlacing & 0xff) == 2 ) { + } else if ( deinterlacing_value == 2 ) { capture_image->Deinterlace_Linear(); - } else if ( (deinterlacing & 0xff) == 3 ) { + } else if ( deinterlacing_value == 3 ) { capture_image->Deinterlace_Blend(); - } else if ( (deinterlacing & 0xff) == 4 ) { + } else if ( deinterlacing_value == 4 ) { capture_image->Deinterlace_4Field( next_buffer.image, (deinterlacing>>8)&0xff ); - } else if ( (deinterlacing & 0xff) == 5 ) { + } else if ( deinterlacing_value == 5 ) { capture_image->Deinterlace_Blend_CustomRatio( (deinterlacing>>8)&0xff ); } - - if ( orientation != ROTATE_0 ) - { - switch ( orientation ) - { - case ROTATE_0 : - { + if ( orientation != ROTATE_0 ) { + switch ( orientation ) { + case ROTATE_0 : { // No action required break; } case ROTATE_90 : case ROTATE_180 : - case ROTATE_270 : - { + case ROTATE_270 : { capture_image->Rotate( (orientation-1)*90 ); break; } case FLIP_HORI : - case FLIP_VERT : - { + case FLIP_VERT : { capture_image->Flip( orientation==FLIP_HORI ); break; } } } - } - if ( true ) { - - if ( capture_image->Size() > camera->ImageSize() ) - { + if ( capture_image->Size() > camera->ImageSize() ) { Error( "Captured image %d does not match expected size %d check width, height and colour depth",capture_image->Size(),camera->ImageSize() ); return( -1 ); } - if ( ((unsigned int)index == shared_data->last_read_index) && (function > MONITOR) ) - { + if ( (index == shared_data->last_read_index) && (function > MONITOR) ) { Warning( "Buffer overrun at index %d, image %d, slow down capture, speed up analysis or increase ring buffer size", index, image_count ); time_t now = time(0); double approxFps = double(image_buffer_count)/double(now-image_buffer[index].timestamp->tv_sec); time_t last_read_delta = now - shared_data->last_read_time; - if ( last_read_delta > (image_buffer_count/approxFps) ) - { + if ( last_read_delta > (image_buffer_count/approxFps) ) { Warning( "Last image read from shared memory %ld seconds ago, zma may have gone away", last_read_delta ) shared_data->last_read_index = image_buffer_count; } @@ -3106,8 +2926,7 @@ int Monitor::Capture() capture_image->MaskPrivacy( privacy_bitmask ); gettimeofday( image_buffer[index].timestamp, NULL ); - if ( config.timestamp_on_capture ) - { + if ( config.timestamp_on_capture ) { TimestampImage( capture_image, image_buffer[index].timestamp ); } shared_data->signal = CheckSignal(capture_image); @@ -3116,8 +2935,7 @@ int Monitor::Capture() image_count++; - if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) - { + if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) { time_t now = image_buffer[index].timestamp->tv_sec; fps = double(fps_report_interval)/(now-last_fps_time); //Info( "%d -> %d -> %d", fps_report_interval, now, last_fps_time ); @@ -3126,16 +2944,15 @@ int Monitor::Capture() last_fps_time = now; } - if ( shared_data->action & GET_SETTINGS ) - { + // Icon: I'm not sure these should be here. They have nothing to do with capturing + if ( shared_data->action & GET_SETTINGS ) { shared_data->brightness = camera->Brightness(); shared_data->hue = camera->Hue(); shared_data->colour = camera->Colour(); shared_data->contrast = camera->Contrast(); shared_data->action &= ~GET_SETTINGS; } - if ( shared_data->action & SET_SETTINGS ) - { + if ( shared_data->action & SET_SETTINGS ) { camera->Brightness( shared_data->brightness ); camera->Hue( shared_data->hue ); camera->Colour( shared_data->colour ); @@ -3143,15 +2960,13 @@ int Monitor::Capture() shared_data->action &= ~SET_SETTINGS; } return( 0 ); - } + } // end if captureResults == 1 which is success I think shared_data->signal = false; return( -1 ); } -void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) const -{ - if ( label_format[0] ) - { +void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) const { + if ( label_format[0] ) { // Expand the strftime macros first char label_time_text[256]; strftime( label_time_text, sizeof(label_time_text), label_format, localtime( &ts_time->tv_sec ) ); @@ -3159,13 +2974,10 @@ void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) c char label_text[1024]; const char *s_ptr = label_time_text; char *d_ptr = label_text; - while ( *s_ptr && ((d_ptr-label_text) < (unsigned int)sizeof(label_text)) ) - { - if ( *s_ptr == '%' ) - { + while ( *s_ptr && ((d_ptr-label_text) < (unsigned int)sizeof(label_text)) ) { + if ( *s_ptr == '%' ) { bool found_macro = false; - switch ( *(s_ptr+1) ) - { + switch ( *(s_ptr+1) ) { case 'N' : d_ptr += snprintf( d_ptr, sizeof(label_text)-(d_ptr-label_text), "%s", name ); found_macro = true; @@ -3179,8 +2991,7 @@ void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) c found_macro = true; break; } - if ( found_macro ) - { + if ( found_macro ) { s_ptr += 2; continue; } @@ -3192,59 +3003,52 @@ void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) c } } -bool Monitor::closeEvent() -{ - if ( event ) - { - if ( function == RECORD || function == MOCORD ) - { +bool Monitor::closeEvent() { + if ( event ) { + if ( function == RECORD || function == MOCORD ) { gettimeofday( &(event->EndTime()), NULL ); } delete event; + video_store_data->recording = false; event = 0; return( true ); } return( false ); } -unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ) -{ +unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ) { bool alarm = false; unsigned int score = 0; if ( n_zones <= 0 ) return( alarm ); - if ( config.record_diag_images ) - { + Storage *storage = this->getStorage(); + + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { - snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-r.jpg", config.dir_events, id ); + if ( !diag_path[0] ) { + snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-r.jpg", storage->Path(), id ); } ref_image.WriteJpeg( diag_path ); } ref_image.Delta( comp_image, &delta_image); - if ( config.record_diag_images ) - { + if ( config.record_diag_images ) { static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { - snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-d.jpg", config.dir_events, id ); + if ( !diag_path[0] ) { + snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-d.jpg", storage->Path(), id ); } delta_image.WriteJpeg( diag_path ); } // Blank out all exclusion zones - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; // need previous alarmed state for preclusive zone, so don't clear just yet if (!zone->IsPreclusive()) zone->ClearAlarm(); - if ( !zone->IsInactive() ) - { + if ( !zone->IsInactive() ) { continue; } Debug( 3, "Blanking inactive zone %s", zone->Label() ); @@ -3252,18 +3056,15 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z } // Check preclusive zones first - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; - if ( !zone->IsPreclusive() ) - { + if ( !zone->IsPreclusive() ) { continue; } int old_zone_score = zone->Score(); bool old_zone_alarmed = zone->Alarmed(); Debug( 3, "Checking preclusive zone %s - old score: %d, state: %s", zone->Label(),old_zone_score, zone->Alarmed()?"alarmed":"quiet" ); - if ( zone->CheckAlarms( &delta_image ) ) - { + if ( zone->CheckAlarms( &delta_image ) ) { alarm = true; score += zone->Score(); zone->SetAlarm(); @@ -3279,7 +3080,7 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z } if (zone->CheckExtendAlarmCount()) { alarm=true; - zone->SetAlarm(); + zone->SetAlarm(); } else { zone->ClearAlarm(); } @@ -3290,33 +3091,25 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z Coord alarm_centre; int top_score = -1; - if ( alarm ) - { + if ( alarm ) { alarm = false; score = 0; - } - else - { + } else { // Find all alarm pixels in active zones - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; - if ( !zone->IsActive() || zone->IsPreclusive()) - { + if ( !zone->IsActive() || zone->IsPreclusive()) { continue; } Debug( 3, "Checking active zone %s", zone->Label() ); - if ( zone->CheckAlarms( &delta_image ) ) - { + if ( zone->CheckAlarms( &delta_image ) ) { alarm = true; score += zone->Score(); zone->SetAlarm(); Debug( 3, "Zone is alarmed, zone score = %d", zone->Score() ); zoneSet.insert( zone->Label() ); - if ( config.opt_control && track_motion ) - { - if ( (int)zone->Score() > top_score ) - { + if ( config.opt_control && track_motion ) { + if ( (int)zone->Score() > top_score ) { top_score = zone->Score(); alarm_centre = zone->GetAlarmCentre(); } @@ -3324,47 +3117,36 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z } } - if ( alarm ) - { - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + if ( alarm ) { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; - if ( !zone->IsInclusive() ) - { + if ( !zone->IsInclusive() ) { continue; } Debug( 3, "Checking inclusive zone %s", zone->Label() ); - if ( zone->CheckAlarms( &delta_image ) ) - { + if ( zone->CheckAlarms( &delta_image ) ) { alarm = true; score += zone->Score(); zone->SetAlarm(); Debug( 3, "Zone is alarmed, zone score = %d", zone->Score() ); zoneSet.insert( zone->Label() ); - if ( config.opt_control && track_motion ) - { - if ( zone->Score() > (unsigned int)top_score ) - { + if ( config.opt_control && track_motion ) { + if ( zone->Score() > (unsigned int)top_score ) { top_score = zone->Score(); alarm_centre = zone->GetAlarmCentre(); } } } } - } - else - { + } else { // Find all alarm pixels in exclusive zones - for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) - { + for ( int n_zone = 0; n_zone < n_zones; n_zone++ ) { Zone *zone = zones[n_zone]; - if ( !zone->IsExclusive() ) - { + if ( !zone->IsExclusive() ) { continue; } Debug( 3, "Checking exclusive zone %s", zone->Label() ); - if ( zone->CheckAlarms( &delta_image ) ) - { + if ( zone->CheckAlarms( &delta_image ) ) { alarm = true; score += zone->Score(); zone->SetAlarm(); @@ -3372,18 +3154,15 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z zoneSet.insert( zone->Label() ); } } - } + } // end if alaram or not } - if ( top_score > 0 ) - { + if ( top_score > 0 ) { shared_data->alarm_x = alarm_centre.X(); shared_data->alarm_y = alarm_centre.Y(); Info( "Got alarm centre at %d,%d, at count %d", shared_data->alarm_x, shared_data->alarm_y, image_count ); - } - else - { + } else { shared_data->alarm_x = shared_data->alarm_y = -1; } @@ -3391,44 +3170,36 @@ unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &z return( score?score:alarm ); } -bool Monitor::DumpSettings( char *output, bool verbose ) -{ +bool Monitor::DumpSettings( char *output, bool verbose ) { output[0] = 0; sprintf( output+strlen(output), "Id : %d\n", id ); sprintf( output+strlen(output), "Name : %s\n", name ); sprintf( output+strlen(output), "Type : %s\n", camera->IsLocal()?"Local":(camera->IsRemote()?"Remote":"File") ); #if ZM_HAS_V4L - if ( camera->IsLocal() ) - { + if ( camera->IsLocal() ) { sprintf( output+strlen(output), "Device : %s\n", ((LocalCamera *)camera)->Device().c_str() ); sprintf( output+strlen(output), "Channel : %d\n", ((LocalCamera *)camera)->Channel() ); sprintf( output+strlen(output), "Standard : %d\n", ((LocalCamera *)camera)->Standard() ); - } - else + } else #endif // ZM_HAS_V4L - if ( camera->IsRemote() ) - { + if ( camera->IsRemote() ) { sprintf( output+strlen(output), "Protocol : %s\n", ((RemoteCamera *)camera)->Protocol().c_str() ); sprintf( output+strlen(output), "Host : %s\n", ((RemoteCamera *)camera)->Host().c_str() ); sprintf( output+strlen(output), "Port : %s\n", ((RemoteCamera *)camera)->Port().c_str() ); sprintf( output+strlen(output), "Path : %s\n", ((RemoteCamera *)camera)->Path().c_str() ); - } - else if ( camera->IsFile() ) - { + } else if ( camera->IsFile() ) { sprintf( output+strlen(output), "Path : %s\n", ((FileCamera *)camera)->Path() ); } #if HAVE_LIBAVFORMAT - else if ( camera->IsFfmpeg() ) - { + else if ( camera->IsFfmpeg() ) { sprintf( output+strlen(output), "Path : %s\n", ((FfmpegCamera *)camera)->Path().c_str() ); } #endif // HAVE_LIBAVFORMAT sprintf( output+strlen(output), "Width : %d\n", camera->Width() ); sprintf( output+strlen(output), "Height : %d\n", camera->Height() ); #if ZM_HAS_V4L - if ( camera->IsLocal() ) - { + if ( camera->IsLocal() ) { sprintf( output+strlen(output), "Palette : %d\n", ((LocalCamera *)camera)->Palette() ); } #endif // ZM_HAS_V4L @@ -3459,78 +3230,61 @@ bool Monitor::DumpSettings( char *output, bool verbose ) function==NODECT?"Externally Triggered only, no Motion Detection":"Unknown" )))))); sprintf( output+strlen(output), "Zones : %d\n", n_zones ); - for ( int i = 0; i < n_zones; i++ ) - { + for ( int i = 0; i < n_zones; i++ ) { zones[i]->DumpSettings( output+strlen(output), verbose ); } return( true ); -} +} // bool Monitor::DumpSettings( char *output, bool verbose ) -bool MonitorStream::checkSwapPath( const char *path, bool create_path ) -{ +bool MonitorStream::checkSwapPath( const char *path, bool create_path ) { uid_t uid = getuid(); gid_t gid = getgid(); struct stat stat_buf; - if ( stat( path, &stat_buf ) < 0 ) - { - if ( create_path && errno == ENOENT ) - { + if ( stat( path, &stat_buf ) < 0 ) { + if ( create_path && errno == ENOENT ) { Debug( 3, "Swap path '%s' missing, creating", path ); - if ( mkdir( path, 0755 ) ) - { + if ( mkdir( path, 0755 ) ) { Error( "Can't mkdir %s: %s", path, strerror(errno)); return( false ); } - if ( stat( path, &stat_buf ) < 0 ) - { + if ( stat( path, &stat_buf ) < 0 ) { Error( "Can't stat '%s': %s", path, strerror(errno) ); return( false ); } - } - else - { + } else { Error( "Can't stat '%s': %s", path, strerror(errno) ); return( false ); } } - if ( !S_ISDIR(stat_buf.st_mode) ) - { + if ( !S_ISDIR(stat_buf.st_mode) ) { Error( "Swap image path '%s' is not a directory", path ); return( false ); } mode_t mask = 0; - if ( uid == stat_buf.st_uid ) - { + if ( uid == stat_buf.st_uid ) { // If we are the owner mask = 00700; - } - else if ( gid == stat_buf.st_gid ) - { + } else if ( gid == stat_buf.st_gid ) { // If we are in the owner group mask = 00070; - } - else - { + } else { // We are neither the owner nor in the group mask = 00007; } - if ( (stat_buf.st_mode & mask) != mask ) - { + if ( (stat_buf.st_mode & mask) != mask ) { Error( "Insufficient permissions on swap image path '%s'", path ); return( false ); } return( true ); } -void MonitorStream::processCommand( const CmdMsg *msg ) -{ +void MonitorStream::processCommand( const CmdMsg *msg ) { Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] ); // Check for incoming command - switch( (MsgCommand)msg->msg_data[0] ) - { + switch( (MsgCommand)msg->msg_data[0] ) { case CMD_PAUSE : { Debug( 1, "Got PAUSE command" ); @@ -3545,8 +3299,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) case CMD_PLAY : { Debug( 1, "Got PLAY command" ); - if ( paused ) - { + if ( paused ) { // Clear paused flag paused = false; // Set delayed_play flag @@ -3558,8 +3311,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) case CMD_VARPLAY : { Debug( 1, "Got VARPLAY command" ); - if ( paused ) - { + if ( paused ) { // Clear paused flag paused = false; // Set delayed_play flag @@ -3581,8 +3333,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) case CMD_FASTFWD : { Debug( 1, "Got FAST FWD command" ); - if ( paused ) - { + if ( paused ) { // Clear paused flag paused = false; // Set delayed_play flag @@ -3639,16 +3390,14 @@ void MonitorStream::processCommand( const CmdMsg *msg ) case CMD_FASTREV : { Debug( 1, "Got FAST REV command" ); - if ( paused ) - { + if ( paused ) { // Clear paused flag paused = false; // Set delayed_play flag delayed = true; } // Set play rate - switch ( replay_rate ) - { + switch ( replay_rate ) { case -2 * ZM_RATE_BASE : replay_rate = -5 * ZM_RATE_BASE; break; @@ -3673,8 +3422,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) 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 ) - { + switch ( zoom ) { case 100: zoom = 150; break; @@ -3697,8 +3445,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) case CMD_ZOOMOUT : { Debug( 1, "Got ZOOM OUT command" ); - switch ( zoom ) - { + switch ( zoom ) { case 500: zoom = 400; break; @@ -3792,8 +3539,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) status_msg.msg_type = MSG_DATA_WATCH; memcpy( &status_msg.msg_data, &status_data, sizeof(status_data) ); int nbytes = 0; - if ( (nbytes = sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) )) < 0 ) - { + if ( (nbytes = sendto( sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr) )) < 0 ) { //if ( errno != EAGAIN ) { Error( "Can't sendto on sd %d: %s", sd, strerror(errno) ); @@ -3808,8 +3554,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) updateFrameRate( monitor->GetFPS() ); } -bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) -{ +bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) { bool send_raw = ((scale>=ZM_SCALE_BASE)&&(zoom==ZM_SCALE_BASE)); if ( type != STREAM_JPEG ) @@ -3817,25 +3562,19 @@ bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) if ( !config.timestamp_on_capture && timestamp ) send_raw = false; - if ( !send_raw ) - { + if ( !send_raw ) { Image temp_image( filepath ); return( sendFrame( &temp_image, timestamp ) ); - } - else - { + } else { int img_buffer_size = 0; static unsigned char img_buffer[ZM_MAX_IMAGE_SIZE]; FILE *fdj = NULL; - if ( (fdj = fopen( filepath, "r" )) ) - { + if ( (fdj = fopen( filepath, "r" )) ) { img_buffer_size = fread( img_buffer, 1, sizeof(img_buffer), fdj ); fclose( fdj ); - } - else - { + } else { Error( "Can't open %s: %s", filepath, strerror(errno) ); return( false ); } @@ -3847,9 +3586,8 @@ bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) fprintf( stdout, "--ZoneMinderFrame\r\n" ); fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size ); fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" ); - if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) - { - if ( !zm_terminate ) + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { + if ( ! zm_terminate ) Error( "Unable to send stream frame: %s", strerror(errno) ); return( false ); } @@ -3860,8 +3598,7 @@ bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) gettimeofday( &frameEndTime, NULL ); int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime ); - if ( frameSendTime > 1000/maxfps ) - { + if ( frameSendTime > 1000/maxfps ) { maxfps /= 2; Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps ); } @@ -3873,17 +3610,14 @@ bool MonitorStream::sendFrame( const char *filepath, struct timeval *timestamp ) return( false ); } -bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) -{ +bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) { Image *send_image = prepareImage( image ); if ( !config.timestamp_on_capture && timestamp ) monitor->TimestampImage( send_image, timestamp ); #if HAVE_LIBAVCODEC - if ( type == STREAM_MPEG ) - { - if ( !vid_stream ) - { + if ( type == STREAM_MPEG ) { + if ( !vid_stream ) { vid_stream = new VideoStream( "pipe:", format, bitrate, effective_fps, send_image->Colours(), send_image->SubpixelOrder(), send_image->Width(), send_image->Height() ); fprintf( stdout, "Content-type: %s\r\n\r\n", vid_stream->MimeType() ); vid_stream->OpenStream(); @@ -3894,8 +3628,7 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) base_time = *timestamp; DELTA_TIMEVAL( delta_time, *timestamp, base_time, DT_PREC_3 ); /* double pts = */ vid_stream->EncodeFrame( send_image->Buffer(), send_image->Size(), config.mpeg_timed_frames, delta_time.delta ); - } - else + } else #endif // HAVE_LIBAVCODEC { static unsigned char temp_img_buffer[ZM_MAX_IMAGE_SIZE]; @@ -3908,8 +3641,7 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) gettimeofday( &frameStartTime, NULL ); fprintf( stdout, "--ZoneMinderFrame\r\n" ); - switch( type ) - { + switch( type ) { case STREAM_JPEG : send_image->EncodeJpeg( img_buffer, &img_buffer_size ); fprintf( stdout, "Content-Type: image/jpeg\r\n" ); @@ -3930,8 +3662,7 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) break; } fprintf( stdout, "Content-Length: %d\r\n\r\n", img_buffer_size ); - if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) - { + if ( fwrite( img_buffer, img_buffer_size, 1, stdout ) != 1 ) { if ( !zm_terminate ) Error( "Unable to send stream frame: %s", strerror(errno) ); return( false ); @@ -3943,20 +3674,17 @@ bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) gettimeofday( &frameEndTime, NULL ); int frameSendTime = tvDiffMsec( frameStartTime, frameEndTime ); - if ( frameSendTime > 1000/maxfps ) - { + if ( frameSendTime > 1000/maxfps ) { maxfps /= 1.5; Error( "Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps ); } } last_frame_sent = TV_2_FLOAT( now ); return( true ); -} +} // end bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) -void MonitorStream::runStream() -{ - if ( type == STREAM_SINGLE ) - { +void MonitorStream::runStream() { + if ( type == STREAM_SINGLE ) { // Not yet migrated over to stream class monitor->SingleImage( scale ); return; @@ -4033,28 +3761,23 @@ void MonitorStream::runStream() } float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs) - while ( !zm_terminate ) - { + while ( !zm_terminate ) { bool got_command = false; - if ( feof( stdout ) || ferror( stdout ) || !monitor->ShmValid() ) - { + if ( feof( stdout ) || ferror( stdout ) || !monitor->ShmValid() ) { break; } gettimeofday( &now, NULL ); - if ( connkey ) - { + if ( connkey ) { while(checkCommandQueue()) { got_command = true; } } //bool frame_sent = false; - if ( buffered_playback && delayed ) - { - if ( temp_read_index == temp_write_index ) - { + if ( buffered_playback && delayed ) { + if ( temp_read_index == temp_write_index ) { // Go back to live viewing Debug( 1, "Exceeded temporary streaming buffer" ); // Clear paused flag @@ -4062,37 +3785,29 @@ void MonitorStream::runStream() // Clear delayed_play flag delayed = false; replay_rate = ZM_RATE_BASE; - } - else - { - if ( !paused ) - { + } else { + if ( !paused ) { int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count ); //Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index ); SwapImage *swap_image = &temp_image_buffer[temp_index]; - if ( !swap_image->valid ) - { + if ( !swap_image->valid ) { paused = true; delayed = true; temp_read_index = MOD_ADD( temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count ); - } - else - { + } else { //Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) ); double expected_delta_time = ((TV_2_FLOAT( swap_image->timestamp ) - TV_2_FLOAT( last_frame_timestamp )) * ZM_RATE_BASE)/replay_rate; double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; //Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) ); // If the next frame is due - if ( actual_delta_time > expected_delta_time ) - { + if ( actual_delta_time > expected_delta_time ) { //Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time ); - if ( temp_index%frame_mod == 0 ) - { + if ( temp_index%frame_mod == 0 ) { Debug( 2, "Sending delayed frame %d", temp_index ); // Send the next frame - if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) ) + if ( ! sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) ) zm_terminate = true; memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) ); //frame_sent = true; @@ -4100,9 +3815,7 @@ void MonitorStream::runStream() temp_read_index = MOD_ADD( temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count ); } } - } - else if ( step != 0 ) - { + } else if ( step != 0 ) { temp_read_index = MOD_ADD( temp_read_index, (step>0?1:-1), temp_image_buffer_count ); SwapImage *swap_image = &temp_image_buffer[temp_read_index]; @@ -4113,14 +3826,11 @@ void MonitorStream::runStream() memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) ); //frame_sent = true; step = 0; - } - else - { + } else { int temp_index = MOD_ADD( temp_read_index, 0, temp_image_buffer_count ); double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; - if ( got_command || actual_delta_time > 5 ) - { + if ( got_command || actual_delta_time > 5 ) { // Send keepalive Debug( 2, "Sending keepalive frame %d", temp_index ); // Send the next frame @@ -4130,8 +3840,7 @@ void MonitorStream::runStream() } } } - if ( temp_read_index == temp_write_index ) - { + if ( temp_read_index == temp_write_index ) { // Go back to live viewing Warning( "Rewound over write index, resuming live play" ); // Clear paused flag @@ -4141,15 +3850,12 @@ void MonitorStream::runStream() replay_rate = ZM_RATE_BASE; } } - if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) - { + if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) { int index = monitor->shared_data->last_write_index%monitor->image_buffer_count; last_read_index = monitor->shared_data->last_write_index; //Debug( 1, "%d: %x - %x", index, image_buffer[index].image, image_buffer[index].image->buffer ); - if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) - { - if ( !paused && !delayed ) - { + if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { + if ( !paused && !delayed ) { // Send the next frame Monitor::Snapshot *snap = &monitor->image_buffer[index]; @@ -4161,24 +3867,19 @@ void MonitorStream::runStream() temp_read_index = temp_write_index; } } - if ( buffered_playback ) - { - if ( monitor->shared_data->valid ) - { - if ( monitor->image_buffer[index].timestamp->tv_sec ) - { + if ( buffered_playback ) { + if ( monitor->shared_data->valid ) { + if ( monitor->image_buffer[index].timestamp->tv_sec ) { int temp_index = temp_write_index%temp_image_buffer_count; Debug( 2, "Storing frame %d", temp_index ); - if ( !temp_image_buffer[temp_index].valid ) - { + if ( !temp_image_buffer[temp_index].valid ) { snprintf( temp_image_buffer[temp_index].file_name, sizeof(temp_image_buffer[0].file_name), "%s/zmswap-i%05d.jpg", swap_path, temp_index ); temp_image_buffer[temp_index].valid = true; } memcpy( &(temp_image_buffer[temp_index].timestamp), monitor->image_buffer[index].timestamp, sizeof(temp_image_buffer[0].timestamp) ); monitor->image_buffer[index].image->WriteJpeg( temp_image_buffer[temp_index].file_name, config.jpeg_file_quality ); temp_write_index = MOD_ADD( temp_write_index, 1, temp_image_buffer_count ); - if ( temp_write_index == temp_read_index ) - { + if ( temp_write_index == temp_read_index ) { // Go back to live viewing Warning( "Exceeded temporary buffer, resuming live play" ); // Clear paused flag @@ -4187,79 +3888,56 @@ void MonitorStream::runStream() delayed = false; replay_rate = ZM_RATE_BASE; } - } - else - { + } else { Warning( "Unable to store frame as timestamp invalid" ); } - } - else - { + } else { Warning( "Unable to store frame as shared memory invalid" ); } } frame_count++; } usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) ); - if ( ttl ) - { - if ( (now.tv_sec - stream_start_time) > ttl ) - { + if ( ttl ) { + if ( (now.tv_sec - stream_start_time) > ttl ) { break; } } - if ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame ) - { + if ( (TV_2_FLOAT( now ) - last_frame_sent) > max_secs_since_last_sent_frame ) { Error( "Terminating, last frame sent time %f secs more than maximum of %f", TV_2_FLOAT( now ) - last_frame_sent, max_secs_since_last_sent_frame ); break; } } - if ( buffered_playback ) - { + if ( buffered_playback ) { Debug( 1, "Cleaning swap files from %s", swap_path ); struct stat stat_buf; - if ( stat( swap_path, &stat_buf ) < 0 ) - { - if ( errno != ENOENT ) - { + if ( stat( swap_path, &stat_buf ) < 0 ) { + if ( errno != ENOENT ) { Error( "Can't stat '%s': %s", swap_path, strerror(errno) ); } - } - else if ( !S_ISDIR(stat_buf.st_mode) ) - { + } else if ( !S_ISDIR(stat_buf.st_mode) ) { Error( "Swap image path '%s' is not a directory", swap_path ); - } - else - { + } else { char glob_pattern[PATH_MAX] = ""; snprintf( glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path ); glob_t pglob; int glob_status = glob( glob_pattern, 0, 0, &pglob ); - if ( glob_status != 0 ) - { - if ( glob_status < 0 ) - { + if ( glob_status != 0 ) { + if ( glob_status < 0 ) { Error( "Can't glob '%s': %s", glob_pattern, strerror(errno) ); - } - else - { + } else { Debug( 1, "Can't glob '%s': %d", glob_pattern, glob_status ); } - } - else - { - for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) - { - if ( unlink( pglob.gl_pathv[i] ) < 0 ) - { + } else { + for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) { + if ( unlink( pglob.gl_pathv[i] ) < 0 ) { Error( "Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno) ); } } } globfree( &pglob ); - if ( rmdir( swap_path ) < 0 ) - { + if ( rmdir( swap_path ) < 0 ) { Error( "Can't rmdir '%s': %s", swap_path, strerror(errno) ); } } @@ -4268,8 +3946,7 @@ void MonitorStream::runStream() closeComms(); } -void Monitor::SingleImage( int scale) -{ +void Monitor::SingleImage( int scale) { int img_buffer_size = 0; static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; @@ -4277,14 +3954,12 @@ void Monitor::SingleImage( int scale) Snapshot *snap = &image_buffer[index]; Image *snap_image = snap->image; - if ( scale != ZM_SCALE_BASE ) - { + if ( scale != ZM_SCALE_BASE ) { scaled_image.Assign( *snap_image ); scaled_image.Scale( scale ); snap_image = &scaled_image; } - if ( !config.timestamp_on_capture ) - { + if ( !config.timestamp_on_capture ) { TimestampImage( snap_image, snap->timestamp ); } snap_image->EncodeJpeg( img_buffer, &img_buffer_size ); @@ -4294,21 +3969,18 @@ void Monitor::SingleImage( int scale) fwrite( img_buffer, img_buffer_size, 1, stdout ); } -void Monitor::SingleImageRaw( int scale) -{ +void Monitor::SingleImageRaw( int scale) { Image scaled_image; int index = shared_data->last_write_index%image_buffer_count; Snapshot *snap = &image_buffer[index]; Image *snap_image = snap->image; - if ( scale != ZM_SCALE_BASE ) - { + if ( scale != ZM_SCALE_BASE ) { scaled_image.Assign( *snap_image ); scaled_image.Scale( scale ); snap_image = &scaled_image; } - if ( !config.timestamp_on_capture ) - { + if ( !config.timestamp_on_capture ) { TimestampImage( snap_image, snap->timestamp ); } @@ -4317,8 +3989,7 @@ void Monitor::SingleImageRaw( int scale) fwrite( snap_image->Buffer(), snap_image->Size(), 1, stdout ); } -void Monitor::SingleImageZip( int scale) -{ +void Monitor::SingleImageZip( int scale) { unsigned long img_buffer_size = 0; static Bytef img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; @@ -4326,14 +3997,12 @@ void Monitor::SingleImageZip( int scale) Snapshot *snap = &image_buffer[index]; Image *snap_image = snap->image; - if ( scale != ZM_SCALE_BASE ) - { + if ( scale != ZM_SCALE_BASE ) { scaled_image.Assign( *snap_image ); scaled_image.Scale( scale ); snap_image = &scaled_image; } - if ( !config.timestamp_on_capture ) - { + if ( !config.timestamp_on_capture ) { TimestampImage( snap_image, snap->timestamp ); } snap_image->Zip( img_buffer, &img_buffer_size ); @@ -4342,3 +4011,16 @@ void Monitor::SingleImageZip( int scale) fprintf( stdout, "Content-Type: image/x-rgbz\r\n\r\n" ); fwrite( img_buffer, img_buffer_size, 1, stdout ); } + +unsigned int Monitor::Colours() const { return( camera->Colours() ); } +unsigned int Monitor::SubpixelOrder() const { return( camera->SubpixelOrder() ); } +int Monitor::PrimeCapture() { + return( camera->PrimeCapture() ); +} +int Monitor::PreCapture() { + return( camera->PreCapture() ); +} +int Monitor::PostCapture() { + return( camera->PostCapture() ); +} +Monitor::Orientation Monitor::getOrientation() const { return orientation; } diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 7d33cc725..d17da0477 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -29,7 +29,9 @@ #include "zm_rgb.h" #include "zm_zone.h" #include "zm_event.h" +class Monitor; #include "zm_camera.h" +#include "zm_storage.h" #include "zm_utils.h" #include "zm_image_analyser.h" @@ -47,9 +49,9 @@ // class Monitor { -friend class MonitorStream; + friend class MonitorStream; -public: + public: typedef enum { QUERY=0, @@ -86,7 +88,13 @@ public: TAPE } State; -protected: + typedef enum { + DISABLED, + X264ENCODE, + H264PASSTHROUGH, + } VideoWriter; + + protected: typedef std::set ZoneSet; typedef enum { GET_SETTINGS=0x1, SET_SETTINGS=0x2, RELOAD=0x4, SUSPEND=0x10, RESUME=0x20 } Action; @@ -96,43 +104,47 @@ protected: /* sizeof(SharedData) expected to be 336 bytes on 32bit and 64bit */ typedef struct { - uint32_t size; /* +0 */ - uint32_t last_write_index; /* +4 */ - uint32_t last_read_index; /* +8 */ - uint32_t state; /* +12 */ - uint32_t last_event; /* +16 */ - uint32_t action; /* +20 */ - int32_t brightness; /* +24 */ - int32_t hue; /* +28 */ - int32_t colour; /* +32 */ - int32_t contrast; /* +36 */ - int32_t alarm_x; /* +40 */ - int32_t alarm_y; /* +44 */ - uint8_t valid; /* +48 */ - uint8_t active; /* +49 */ - uint8_t signal; /* +50 */ - uint8_t format; /* +51 */ - uint32_t imagesize; /* +52 */ - uint32_t epadding1; /* +56 */ - uint32_t epadding2; /* +60 */ + uint32_t size; /* +0 */ + uint32_t last_write_index; /* +4 */ + uint32_t last_read_index; /* +8 */ + uint32_t state; /* +12 */ + uint32_t last_event; /* +16 */ + uint32_t action; /* +20 */ + int32_t brightness; /* +24 */ + int32_t hue; /* +28 */ + int32_t colour; /* +32 */ + int32_t contrast; /* +36 */ + int32_t alarm_x; /* +40 */ + int32_t alarm_y; /* +44 */ + uint8_t valid; /* +48 */ + uint8_t active; /* +49 */ + uint8_t signal; /* +50 */ + uint8_t format; /* +51 */ + uint32_t imagesize; /* +52 */ + uint32_t epadding1; /* +56 */ + uint32_t epadding2; /* +60 */ /* - ** This keeps 32bit time_t and 64bit time_t identical and compatible as long as time is before 2038. - ** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16. - */ - union { /* +64 */ - time_t last_write_time; - uint64_t extrapad1; + ** This keeps 32bit time_t and 64bit time_t identical and compatible as long as time is before 2038. + ** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16. + */ + union { /* +64 */ + time_t startup_time; /* When the zmc process started. zmwatch uses this to see how long the process has been running without getting any images */ + uint64_t extrapad1; }; - union { /* +72 */ - time_t last_read_time; - uint64_t extrapad2; + union { /* +72 */ + time_t last_write_time; + uint64_t extrapad2; }; - uint8_t control_state[256]; /* +80 */ - + union { /* +80 */ + time_t last_read_time; + uint64_t extrapad3; + }; + uint8_t control_state[256]; /* +88 */ + } SharedData; typedef enum { TRIGGER_CANCEL, TRIGGER_ON, TRIGGER_OFF } TriggerState; - + /* sizeof(TriggerData) expected to be 560 on 32bit & and 64bit */ typedef struct { @@ -153,73 +165,92 @@ protected: void* padding; }; - class MonitorLink + //TODO: Technically we can't exclude this struct when people don't have avformat as the Memory.pm module doesn't know about avformat +#if 1 + //sizeOf(VideoStoreData) expected to be 4104 bytes on 32bit and 64bit + typedef struct { - protected: - unsigned int id; - char name[64]; + uint32_t size; + char event_file[4096]; + uint32_t recording; //bool arch dependent so use uint32 instead + //uint32_t frameNumber; + } VideoStoreData; - bool connected; - time_t last_connect_time; +#endif // HAVE_LIBAVFORMAT + + class MonitorLink { + protected: + unsigned int id; + char name[64]; + + bool connected; + time_t last_connect_time; #if ZM_MEM_MAPPED - int map_fd; - char mem_file[PATH_MAX]; + int map_fd; + char mem_file[PATH_MAX]; #else // ZM_MEM_MAPPED - int shm_id; + int shm_id; #endif // ZM_MEM_MAPPED - off_t mem_size; - unsigned char *mem_ptr; + off_t mem_size; + unsigned char *mem_ptr; - volatile SharedData *shared_data; - volatile TriggerData *trigger_data; + volatile SharedData *shared_data; + volatile TriggerData *trigger_data; + volatile VideoStoreData *video_store_data; - int last_state; - int last_event; + int last_state; + int last_event; - public: - MonitorLink( int p_id, const char *p_name ); - ~MonitorLink(); - inline int Id() const - { - return( id ); - } - inline const char *Name() const - { - return( name ); - } + public: + MonitorLink( int p_id, const char *p_name ); + ~MonitorLink(); - inline bool isConnected() const - { - return( connected ); - } - inline time_t getLastConnectTime() const - { - return( last_connect_time ); - } + inline int Id() const { + return( id ); + } + inline const char *Name() const { + return( name ); + } - bool connect(); - bool disconnect(); + inline bool isConnected() const { + return( connected ); + } + inline time_t getLastConnectTime() const { + return( last_connect_time ); + } - bool isAlarmed(); - bool inAlarm(); - bool hasAlarmed(); + bool connect(); + bool disconnect(); + + bool isAlarmed(); + bool inAlarm(); + bool hasAlarmed(); }; -protected: + protected: // These are read from the DB and thereafter remain unchanged unsigned int id; char name[64]; - unsigned int server_id; - Function function; // What the monitor is doing - bool enabled; // Whether the monitor is enabled or asleep - unsigned int width; // Normally the same as the camera, but not if partly rotated - unsigned int height; // Normally the same as the camera, but not if partly rotated + unsigned int server_id; // Id of the Server object + unsigned int storage_id; // Id of the Storage Object, which currently will just provide a path, but in future may do more. + Function function; // What the monitor is doing + bool enabled; // Whether the monitor is enabled or asleep + unsigned int width; // Normally the same as the camera, but not if partly rotated + unsigned int height; // Normally the same as the camera, but not if partly rotated bool v4l_multi_buffer; unsigned int v4l_captures_per_frame; Orientation orientation; // Whether the image has to be rotated at all unsigned int deinterlacing; +bool videoRecording; + + int savejpegspref; + VideoWriter videowriter; + std::string encoderparams; + std::vector encoderparamsvec; + bool record_audio; // Whether to store the audio that we receive + int brightness; // The statically saved brightness of the camera int contrast; // The statically saved contrast of the camera int hue; // The statically saved hue of the camera @@ -227,20 +258,20 @@ protected: char event_prefix[64]; // The prefix applied to event names as they are created char label_format[64]; // The format of the timestamp on the images Coord label_coord; // The coordinates of the timestamp on the images - int label_size; // Size of the timestamp on the images + int label_size; // Size of the timestamp on the images int image_buffer_count; // Size of circular image buffer, at least twice the size of the pre_event_count int pre_event_buffer_count; // Size of dedicated circular pre event buffer used when analysis is not performed at capturing framerate, - // value is pre_event_count + alarm_frame_count - 1 + // value is pre_event_count + alarm_frame_count - 1 int warmup_count; // How many images to process before looking for events int pre_event_count; // How many images to hold and prepend to an alarm event int post_event_count; // How many unalarmed images must occur before the alarm state is reset int stream_replay_buffer; // How many frames to store to support DVR functions, IGNORED from this object, passed directly into zms now int section_length; // How long events should last in continuous modes - bool adaptive_skip; // Whether to use the newer adaptive algorithm for this monitor + bool adaptive_skip; // Whether to use the newer adaptive algorithm for this monitor int frame_skip; // How many frames to skip in continuous modes int motion_frame_skip; // How many frames to skip in motion detection - double analysis_fps; // Target framerate for video analysis - unsigned int analysis_update_delay; // How long we wait before updating analysis parameters + double analysis_fps; // Target framerate for video analysis + unsigned int analysis_update_delay; // How long we wait before updating analysis parameters int capture_delay; // How long we wait between capture frames int alarm_capture_delay; // How long we wait between capture frames when in alarm state int alarm_frame_count; // How many alarm frames are required before an event is triggered @@ -248,8 +279,8 @@ protected: int ref_blend_perc; // Percentage of new image going into reference image. int alarm_ref_blend_perc; // Percentage of new image going into reference image during alarm. bool track_motion; // Whether this monitor tries to track detected motion - Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected - bool embed_exif; // Whether to embed Exif data into each image frame or not + Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected + bool embed_exif; // Whether to embed Exif data into each image frame or not double fps; Image delta_image; @@ -269,7 +300,7 @@ protected: time_t start_time; time_t last_fps_time; time_t auto_resume_time; - unsigned int last_motion_score; + unsigned int last_motion_score; EventCloseMode event_close_mode; @@ -281,9 +312,11 @@ protected: #endif // ZM_MEM_MAPPED off_t mem_size; unsigned char *mem_ptr; + Storage *storage; SharedData *shared_data; TriggerData *trigger_data; + VideoStoreData *video_store_data; Snapshot *image_buffer; Snapshot next_buffer; /* Used by four field deinterlacing */ @@ -305,64 +338,108 @@ protected: MonitorLink **linked_monitors; public: + Monitor( int p_id ); + // OurCheckAlarms seems to be unused. Check it on zm_monitor.cpp for more info. //bool OurCheckAlarms( Zone *zone, const Image *pImage ); - Monitor( int p_id, const char *p_name, unsigned int p_server_id, int p_function, bool p_enabled, const char *p_linked_monitors, Camera *p_camera, int p_orientation, unsigned int p_deinterlacing, const char *p_event_prefix, const char *p_label_format, const Coord &p_label_coord, int label_size, int p_image_buffer_count, int p_warmup_count, int p_pre_event_count, int p_post_event_count, int p_stream_replay_buffer, int p_alarm_frame_count, int p_section_length, int p_frame_skip, int p_motion_frame_skip, double p_analysis_fps, unsigned int p_analysis_update_delay, int p_capture_delay, int p_alarm_capture_delay, int p_fps_report_interval, int p_ref_blend_perc, int p_alarm_ref_blend_perc, bool p_track_motion, Rgb p_signal_check_colour, bool p_embed_exif, Purpose p_purpose, int p_n_zones=0, Zone *p_zones[]=0 ); + Monitor( + int p_id, + const char *p_name, + unsigned int p_server_id, + unsigned int p_storage_id, + int p_function, + bool p_enabled, + const char *p_linked_monitors, + Camera *p_camera, + int p_orientation, + unsigned int p_deinterlacing, + int p_savejpegs, + VideoWriter p_videowriter, + std::string p_encoderparams, + bool p_record_audio, + const char *p_event_prefix, + const char *p_label_format, + const Coord &p_label_coord, + int label_size, + int p_image_buffer_count, + int p_warmup_count, + int p_pre_event_count, + int p_post_event_count, + int p_stream_replay_buffer, + int p_alarm_frame_count, + int p_section_length, + int p_frame_skip, + int p_motion_frame_skip, + double p_analysis_fps, + unsigned int p_analysis_update_delay, + int p_capture_delay, + int p_alarm_capture_delay, + int p_fps_report_interval, + int p_ref_blend_perc, + int p_alarm_ref_blend_perc, + bool p_track_motion, + Rgb p_signal_check_colour, + bool p_embed_exif, + Purpose p_purpose, + int p_n_zones=0, + Zone *p_zones[]=0 + ); ~Monitor(); void AddZones( int p_n_zones, Zone *p_zones[] ); void AddPrivacyBitmask( Zone *p_zones[] ); bool connect(); - inline int ShmValid() const - { + inline int ShmValid() const { return( shared_data->valid ); } - inline int Id() const - { + inline int Id() const { return( id ); } - inline const char *Name() const - { + inline const char *Name() const { return( name ); } - inline Function GetFunction() const - { + inline Storage *getStorage() { + if ( ! storage ) { + storage = new Storage( storage_id ); + } + return( storage ); + } + inline Function GetFunction() const { return( function ); } - inline bool Enabled() - { + inline bool Enabled() { if ( function <= MONITOR ) return( false ); return( enabled ); } - inline const char *EventPrefix() const - { + inline const char *EventPrefix() const { return( event_prefix ); } - inline bool Ready() - { + inline bool Ready() { if ( function <= MONITOR ) return( false ); return( image_count > ready_count ); } - inline bool Active() - { + inline bool Active() { if ( function <= MONITOR ) return( false ); return( enabled && shared_data->active ); } - inline bool Exif() - { + inline bool Exif() { return( embed_exif ); } + Orientation getOrientation() const; - unsigned int Width() const { return( width ); } - unsigned int Height() const { return( height ); } - unsigned int Colours() const { return( camera->Colours() ); } - unsigned int SubpixelOrder() const { return( camera->SubpixelOrder() ); } + unsigned int Width() const { return width; } + unsigned int Height() const { return height; } + unsigned int Colours() const; + unsigned int SubpixelOrder() const; + int GetOptSaveJPEGs() const { return( savejpegspref ); } + VideoWriter GetOptVideoWriter() const { return( videowriter ); } + const std::vector* GetOptEncoderParams() const { return( &encoderparamsvec ); } State GetState() const; int GetImage( int index=-1, int scale=100 ); @@ -380,6 +457,12 @@ public: void ForceAlarmOff(); void CancelForced(); TriggerState GetTriggerState() const { return( (TriggerState)(trigger_data?trigger_data->trigger_state:TRIGGER_CANCEL )); } + inline time_t getStartupTime() const { + return( shared_data->startup_time ); + } + inline void setStartupTime( time_t p_time ) { + shared_data->startup_time = p_time; + } void actionReload(); void actionEnable(); @@ -392,19 +475,10 @@ public: int actionColour( int p_colour=-1 ); int actionContrast( int p_contrast=-1 ); - inline int PrimeCapture() - { - return( camera->PrimeCapture() ); - } - inline int PreCapture() - { - return( camera->PreCapture() ); - } + int PrimeCapture(); + int PreCapture(); int Capture(); - int PostCapture() - { - return( camera->PostCapture() ); - } + int PostCapture(); unsigned int DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ); // DetectBlack seems to be unused. Check it on zm_monitor.cpp for more info. @@ -445,54 +519,49 @@ public: #define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit)) -class MonitorStream : public StreamBase -{ -protected: - typedef struct SwapImage { - bool valid; - struct timeval timestamp; - char file_name[PATH_MAX]; - } SwapImage; +class MonitorStream : public StreamBase { + protected: + typedef struct SwapImage { + bool valid; + struct timeval timestamp; + char file_name[PATH_MAX]; + } SwapImage; -private: - SwapImage *temp_image_buffer; - int temp_image_buffer_count; - int temp_read_index; - int temp_write_index; + private: + SwapImage *temp_image_buffer; + int temp_image_buffer_count; + int temp_read_index; + int temp_write_index; -protected: - time_t ttl; + protected: + time_t ttl; -protected: - int playback_buffer; - bool delayed; + protected: + int playback_buffer; + bool delayed; - int frame_count; + int frame_count; -protected: - bool checkSwapPath( const char *path, bool create_path ); + protected: + bool checkSwapPath( const char *path, bool create_path ); - bool sendFrame( const char *filepath, struct timeval *timestamp ); - bool sendFrame( Image *image, struct timeval *timestamp ); - void processCommand( const CmdMsg *msg ); + bool sendFrame( const char *filepath, struct timeval *timestamp ); + bool sendFrame( Image *image, struct timeval *timestamp ); + void processCommand( const CmdMsg *msg ); -public: - MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) - { - } - void setStreamBuffer( int p_playback_buffer ) - { - playback_buffer = p_playback_buffer; - } - void setStreamTTL( time_t p_ttl ) - { - ttl = p_ttl; - } - bool setStreamStart( int monitor_id ) - { - return loadMonitor( monitor_id ); - } - void runStream(); + public: + MonitorStream() : playback_buffer( 0 ), delayed( false ), frame_count( 0 ) { + } + void setStreamBuffer( int p_playback_buffer ) { + playback_buffer = p_playback_buffer; + } + void setStreamTTL( time_t p_ttl ) { + ttl = p_ttl; + } + bool setStreamStart( int monitor_id ) { + return loadMonitor( monitor_id ); + } + void runStream(); }; #endif // ZM_MONITOR_H diff --git a/src/zm_mpeg.cpp b/src/zm_mpeg.cpp index 7b46cd397..e5b4aa51f 100644 --- a/src/zm_mpeg.cpp +++ b/src/zm_mpeg.cpp @@ -25,8 +25,7 @@ #include "zm_mpeg.h" #if HAVE_LIBAVCODEC -extern "C" -{ +extern "C" { #include #include } @@ -34,319 +33,287 @@ extern "C" bool VideoStream::initialised = false; VideoStream::MimeData VideoStream::mime_data[] = { - { "asf", "video/x-ms-asf" }, - { "swf", "application/x-shockwave-flash" }, - { "flv", "video/x-flv" }, - { "mov", "video/quicktime" } + { "asf", "video/x-ms-asf" }, + { "swf", "application/x-shockwave-flash" }, + { "flv", "video/x-flv" }, + { "mov", "video/quicktime" } }; -void VideoStream::Initialise( ) -{ - if ( logDebugging() ) +void VideoStream::Initialise( ) { + if ( logDebugging() ) { av_log_set_level( AV_LOG_DEBUG ); - else - av_log_set_level( AV_LOG_QUIET ); - - av_register_all( ); -#if LIBAVFORMAT_VERSION_CHECK(53, 13, 0, 19, 0) - avformat_network_init(); -#endif - initialised = true; -} - -void VideoStream::SetupFormat( ) -{ - /* allocate the output media context */ - ofc = NULL; -#if (LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 2, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - avformat_alloc_output_context2( &ofc, NULL, format, filename ); -#else - AVFormatContext *s= avformat_alloc_context(); - if(!s) - { - Fatal( "avformat_alloc_context failed %d \"%s\"", (size_t)ofc, av_err2str((size_t)ofc) ); - } - - AVOutputFormat *oformat; - if (format) { -#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) - oformat = av_guess_format(format, NULL, NULL); -#else - oformat = guess_format(format, NULL, NULL); -#endif - if (!oformat) { - Fatal( "Requested output format '%s' is not a suitable output format", format ); - } } else { -#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) - oformat = av_guess_format(NULL, filename, NULL); -#else - oformat = guess_format(NULL, filename, NULL); -#endif - if (!oformat) { - Fatal( "Unable to find a suitable output format for '%s'", format ); - } - } - s->oformat = oformat; - - if (s->oformat->priv_data_size > 0) { - s->priv_data = av_mallocz(s->oformat->priv_data_size); - if (!s->priv_data) - { - Fatal( "Could not allocate private data for output format." ); - } -#if LIBAVFORMAT_VERSION_CHECK(52, 92, 0, 92, 0) - if (s->oformat->priv_class) { - *(const AVClass**)s->priv_data = s->oformat->priv_class; - av_opt_set_defaults(s->priv_data); - } -#endif - } - else - { - s->priv_data = NULL; - } - - if(filename) - { - snprintf( s->filename, sizeof(s->filename), "%s", filename ); - } - - ofc = s; -#endif - if ( !ofc ) - { - Fatal( "avformat_alloc_..._context failed: %d", ofc ); + av_log_set_level( AV_LOG_QUIET ); } - of = ofc->oformat; - Debug( 1, "Using output format: %s (%s)", of->name, of->long_name ); + av_register_all( ); +#if LIBAVFORMAT_VERSION_CHECK(53, 13, 0, 19, 0) + avformat_network_init(); +#endif + initialised = true; } -void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate ) -{ - /* ffmpeg format matching */ - switch(colours) { - case ZM_COLOUR_RGB24: - { - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { - /* BGR subpixel order */ - pf = AV_PIX_FMT_BGR24; - } else { - /* Assume RGB subpixel order */ - pf = AV_PIX_FMT_RGB24; - } - break; - } - case ZM_COLOUR_RGB32: - { - if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { - /* ARGB subpixel order */ - pf = AV_PIX_FMT_ARGB; - } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { - /* ABGR subpixel order */ - pf = AV_PIX_FMT_ABGR; - } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { - /* BGRA subpixel order */ - pf = AV_PIX_FMT_BGRA; - } else { - /* Assume RGBA subpixel order */ - pf = AV_PIX_FMT_RGBA; - } - break; - } - case ZM_COLOUR_GRAY8: - pf = AV_PIX_FMT_GRAY8; - break; - default: - Panic("Unexpected colours: %d",colours); - break; - } - - if ( strcmp( "rtp", of->name ) == 0 ) - { - // RTP must have a packet_size. - // Not sure what this value should be really... - ofc->packet_size = width*height; - - if ( of->video_codec == AV_CODEC_ID_NONE) - { - // RTP does not have a default codec in ffmpeg <= 0.8. - of->video_codec = AV_CODEC_ID_MPEG4; - } - } - - _AVCODECID codec_id = of->video_codec; - if ( codec_name ) - { - AVCodec *a = avcodec_find_encoder_by_name(codec_name); - if ( a ) - { - codec_id = a->id; - } - else - { -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - Debug( 1, "Could not find codec \"%s\". Using default \"%s\"", codec_name, avcodec_get_name( codec_id ) ); +void VideoStream::SetupFormat( ) { + /* allocate the output media context */ + ofc = NULL; +#if (LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 2, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) + avformat_alloc_output_context2( &ofc, NULL, format, filename ); #else - Debug( 1, "Could not find codec \"%s\". Using default \"%d\"", codec_name, codec_id ); + AVFormatContext *s= avformat_alloc_context(); + if(!s) { + Fatal( "avformat_alloc_context failed %d \"%s\"", (size_t)ofc, av_err2str((size_t)ofc) ); + } + + AVOutputFormat *oformat; + if (format) { +#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) + oformat = av_guess_format(format, NULL, NULL); +#else + oformat = guess_format(format, NULL, NULL); #endif - } - } - - /* add the video streams using the default format codecs - and initialize the codecs */ - ost = NULL; - if ( codec_id != AV_CODEC_ID_NONE ) - { - codec = avcodec_find_encoder( codec_id ); - if ( !codec ) - { -#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - Fatal( "Could not find encoder for '%s'", avcodec_get_name( codec_id ) ); + if (!oformat) { + Fatal( "Requested output format '%s' is not a suitable output format", format ); + } + } else { +#if LIBAVFORMAT_VERSION_CHECK(52, 45, 0, 45, 0) + oformat = av_guess_format(NULL, filename, NULL); #else - Fatal( "Could not find encoder for '%d'", codec_id ); + oformat = guess_format(NULL, filename, NULL); +#endif + if (!oformat) { + Fatal( "Unable to find a suitable output format for '%s'", format ); + } + } + s->oformat = oformat; + + if (s->oformat->priv_data_size > 0) { + s->priv_data = av_mallocz(s->oformat->priv_data_size); + if (!s->priv_data) { + Fatal( "Could not allocate private data for output format." ); + } +#if LIBAVFORMAT_VERSION_CHECK(52, 92, 0, 92, 0) + if (s->oformat->priv_class) { + *(const AVClass**)s->priv_data = s->oformat->priv_class; + av_opt_set_defaults(s->priv_data); + } +#endif + } else { + s->priv_data = NULL; + } + + if ( filename ) { + snprintf( s->filename, sizeof(s->filename), "%s", filename ); + } + + ofc = s; +#endif + if ( !ofc ) { + Fatal( "avformat_alloc_..._context failed: %d", ofc ); + } + + of = ofc->oformat; + Debug( 1, "Using output format: %s (%s)", of->name, of->long_name ); +} + +void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate ) { + /* ffmpeg format matching */ + switch(colours) { + case ZM_COLOUR_RGB24: + { + if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + /* BGR subpixel order */ + pf = AV_PIX_FMT_BGR24; + } else { + /* Assume RGB subpixel order */ + pf = AV_PIX_FMT_RGB24; + } + break; + } + case ZM_COLOUR_RGB32: + { + if(subpixelorder == ZM_SUBPIX_ORDER_ARGB) { + /* ARGB subpixel order */ + pf = AV_PIX_FMT_ARGB; + } else if(subpixelorder == ZM_SUBPIX_ORDER_ABGR) { + /* ABGR subpixel order */ + pf = AV_PIX_FMT_ABGR; + } else if(subpixelorder == ZM_SUBPIX_ORDER_BGRA) { + /* BGRA subpixel order */ + pf = AV_PIX_FMT_BGRA; + } else { + /* Assume RGBA subpixel order */ + pf = AV_PIX_FMT_RGBA; + } + break; + } + case ZM_COLOUR_GRAY8: + pf = AV_PIX_FMT_GRAY8; + break; + default: + Panic("Unexpected colours: %d",colours); + break; + } + + if ( strcmp( "rtp", of->name ) == 0 ) { + // RTP must have a packet_size. + // Not sure what this value should be really... + ofc->packet_size = width*height; + + if ( of->video_codec == AV_CODEC_ID_NONE ) { + // RTP does not have a default codec in ffmpeg <= 0.8. + of->video_codec = AV_CODEC_ID_MPEG4; + } + } + + _AVCODECID codec_id = of->video_codec; + if ( codec_name ) { + AVCodec *a = avcodec_find_encoder_by_name(codec_name); + if ( a ) { + codec_id = a->id; + } else { +#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) + Debug( 1, "Could not find codec \"%s\". Using default \"%s\"", codec_name, avcodec_get_name( codec_id ) ); +#else + Debug( 1, "Could not find codec \"%s\". Using default \"%d\"", codec_name, codec_id ); #endif } + } + + /* add the video streams using the default format codecs + and initialize the codecs */ + ost = NULL; + if ( codec_id != AV_CODEC_ID_NONE ) { + codec = avcodec_find_encoder( codec_id ); + if ( !codec ) { +#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) + Fatal( "Could not find encoder for '%s'", avcodec_get_name( codec_id ) ); +#else + Fatal( "Could not find encoder for '%d'", codec_id ); +#endif + } #if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) - Debug( 1, "Found encoder for '%s'", avcodec_get_name( codec_id ) ); + Debug( 1, "Found encoder for '%s'", avcodec_get_name( codec_id ) ); #else - Debug( 1, "Found encoder for '%d'", codec_id ); + Debug( 1, "Found encoder for '%d'", codec_id ); #endif #if LIBAVFORMAT_VERSION_CHECK(53, 10, 0, 17, 0) - ost = avformat_new_stream( ofc, codec ); + ost = avformat_new_stream( ofc, codec ); #else - ost = av_new_stream( ofc, 0 ); + ost = av_new_stream( ofc, 0 ); #endif - - if ( !ost ) - { - Fatal( "Could not alloc stream" ); - } - ost->id = ofc->nb_streams - 1; + + if ( !ost ) { + Fatal( "Could not alloc stream" ); + } + ost->id = ofc->nb_streams - 1; - Debug( 1, "Allocated stream" ); + Debug( 1, "Allocated stream" ); - AVCodecContext *c = ost->codec; + AVCodecContext *c = ost->codec; - c->codec_id = codec->id; - c->codec_type = codec->type; + c->codec_id = codec->id; + c->codec_type = codec->type; - c->pix_fmt = strcmp( "mjpeg", ofc->oformat->name ) == 0 ? AV_PIX_FMT_YUVJ422P : AV_PIX_FMT_YUV420P; - if ( bitrate <= 100 ) - { - // Quality based bitrate control (VBR). Scale is 1..31 where 1 is best. - // This gets rid of artifacts in the beginning of the movie; and well, even quality. - c->flags |= CODEC_FLAG_QSCALE; - c->global_quality = FF_QP2LAMBDA * (31 - (31 * (bitrate / 100.0))); - } - else - { - c->bit_rate = bitrate; - } + c->pix_fmt = strcmp( "mjpeg", ofc->oformat->name ) == 0 ? AV_PIX_FMT_YUVJ422P : AV_PIX_FMT_YUV420P; + if ( bitrate <= 100 ) { + // Quality based bitrate control (VBR). Scale is 1..31 where 1 is best. + // This gets rid of artifacts in the beginning of the movie; and well, even quality. + c->flags |= CODEC_FLAG_QSCALE; + c->global_quality = FF_QP2LAMBDA * (31 - (31 * (bitrate / 100.0))); + } else { + c->bit_rate = bitrate; + } - /* resolution must be a multiple of two */ - c->width = width; - c->height = height; - /* time base: this is the fundamental unit of time (in seconds) in terms - of which frame timestamps are represented. for fixed-fps content, - timebase should be 1/framerate and timestamp increments should be - identically 1. */ - c->time_base.den = frame_rate; - c->time_base.num = 1; - - Debug( 1, "Will encode in %d fps.", c->time_base.den ); - - /* emit one intra frame every second */ - c->gop_size = frame_rate; + /* resolution must be a multiple of two */ + c->width = width; + c->height = height; + /* time base: this is the fundamental unit of time (in seconds) in terms + of which frame timestamps are represented. for fixed-fps content, + timebase should be 1/framerate and timestamp increments should be + identically 1. */ + c->time_base.den = frame_rate; + c->time_base.num = 1; + + Debug( 1, "Will encode in %d fps.", c->time_base.den ); + + /* emit one intra frame every second */ + c->gop_size = frame_rate; - // some formats want stream headers to be separate - if ( of->flags & AVFMT_GLOBALHEADER ) - c->flags |= CODEC_FLAG_GLOBAL_HEADER; - } - else - { - Fatal( "of->video_codec == AV_CODEC_ID_NONE" ); - } + // some formats want stream headers to be separate + if ( of->flags & AVFMT_GLOBALHEADER ) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + } else { + Fatal( "of->video_codec == AV_CODEC_ID_NONE" ); + } } -void VideoStream::SetParameters( ) -{ +void VideoStream::SetParameters( ) { } -const char *VideoStream::MimeType( ) const -{ - for ( unsigned int i = 0; i < sizeof (mime_data) / sizeof (*mime_data); i++ ) - { - if ( strcmp( format, mime_data[i].format ) == 0 ) - { - Debug( 1, "MimeType is \"%s\"", mime_data[i].mime_type ); - return ( mime_data[i].mime_type); - } - } - const char *mime_type = of->mime_type; - if ( !mime_type ) - { - std::string mime = "video/"; - mime = mime.append( format ); - mime_type = mime.c_str( ); - Warning( "Unable to determine mime type for '%s' format, using '%s' as default", format, mime_type ); - } +const char *VideoStream::MimeType( ) const { + for ( unsigned int i = 0; i < sizeof (mime_data) / sizeof (*mime_data); i++ ) { + if ( strcmp( format, mime_data[i].format ) == 0 ) { + Debug( 1, "MimeType is \"%s\"", mime_data[i].mime_type ); + return ( mime_data[i].mime_type); + } + } + const char *mime_type = of->mime_type; + if ( !mime_type ) { + std::string mime = "video/"; + mime = mime.append( format ); + mime_type = mime.c_str( ); + Warning( "Unable to determine mime type for '%s' format, using '%s' as default", format, mime_type ); + } - Debug( 1, "MimeType is \"%s\"", mime_type ); + Debug( 1, "MimeType is \"%s\"", mime_type ); - return ( mime_type); + return ( mime_type); } -void VideoStream::OpenStream( ) -{ - int avRet; +void VideoStream::OpenStream( ) { + int avRet; - /* now that all the parameters are set, we can open the - video codecs and allocate the necessary encode buffers */ - if ( ost ) - { - AVCodecContext *c = ost->codec; - - /* open the codec */ + /* now that all the parameters are set, we can open the + video codecs and allocate the necessary encode buffers */ + if ( ost ) { + AVCodecContext *c = ost->codec; + + /* open the codec */ #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - if ( (avRet = avcodec_open( c, codec )) < 0 ) + if ( (avRet = avcodec_open( c, codec )) < 0 ) #else - if ( (avRet = avcodec_open2( c, codec, 0 )) < 0 ) + if ( (avRet = avcodec_open2( c, codec, 0 )) < 0 ) #endif - { - Fatal( "Could not open codec. Error code %d \"%s\"", avRet, av_err2str( avRet ) ); - } + { + Fatal( "Could not open codec. Error code %d \"%s\"", avRet, av_err2str( avRet ) ); + } - Debug( 1, "Opened codec" ); + Debug( 1, "Opened codec" ); - /* allocate the encoded raw picture */ + /* allocate the encoded raw picture */ #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - opicture = av_frame_alloc( ); + opicture = av_frame_alloc( ); #else - opicture = avcodec_alloc_frame( ); + opicture = avcodec_alloc_frame( ); #endif - if ( !opicture ) - { + if ( !opicture ) { Panic( "Could not allocate opicture" ); } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - int size = av_image_get_buffer_size( c->pix_fmt, c->width, - c->height, 1 ); + int size = av_image_get_buffer_size( c->pix_fmt, c->width, c->height, 1 ); #else int size = avpicture_get_size( c->pix_fmt, c->width, c->height ); #endif uint8_t *opicture_buf = (uint8_t *)av_malloc( size ); - if ( !opicture_buf ) - { + if ( !opicture_buf ) { #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &opicture ); + av_frame_free( &opicture ); #else - av_freep( &opicture ); + av_freep( &opicture ); #endif Panic( "Could not allocate opicture_buf" ); } @@ -362,30 +329,26 @@ void VideoStream::OpenStream( ) picture is needed too. It is then converted to the required output format */ tmp_opicture = NULL; - if ( c->pix_fmt != pf ) - { + if ( c->pix_fmt != pf ) { #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - tmp_opicture = av_frame_alloc( ); + tmp_opicture = av_frame_alloc( ); #else - tmp_opicture = avcodec_alloc_frame( ); + tmp_opicture = avcodec_alloc_frame( ); #endif - if ( !tmp_opicture ) - { + if ( !tmp_opicture ) { Panic( "Could not allocate tmp_opicture" ); } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - int size = av_image_get_buffer_size( pf, c->width, - c->height,1 ); + int size = av_image_get_buffer_size( pf, c->width, c->height,1 ); #else int size = avpicture_get_size( pf, c->width, c->height ); #endif uint8_t *tmp_opicture_buf = (uint8_t *)av_malloc( size ); - if ( !tmp_opicture_buf ) - { + if ( !tmp_opicture_buf ) { #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &tmp_opicture ); + av_frame_free( &tmp_opicture ); #else - av_freep( &tmp_opicture ); + av_freep( &tmp_opicture ); #endif Panic( "Could not allocate tmp_opicture_buf" ); } @@ -401,307 +364,270 @@ void VideoStream::OpenStream( ) } /* open the output file, if needed */ - if ( !(of->flags & AVFMT_NOFILE) ) - { + if ( !(of->flags & AVFMT_NOFILE) ) { int ret; #if LIBAVFORMAT_VERSION_CHECK(53, 15, 0, 21, 0) - ret = avio_open2( &ofc->pb, filename, AVIO_FLAG_WRITE, NULL, NULL ); + ret = avio_open2( &ofc->pb, filename, AVIO_FLAG_WRITE, NULL, NULL ); #elif LIBAVFORMAT_VERSION_CHECK(52, 102, 0, 102, 0) - ret = avio_open( &ofc->pb, filename, AVIO_FLAG_WRITE ); + ret = avio_open( &ofc->pb, filename, AVIO_FLAG_WRITE ); #else - ret = url_fopen( &ofc->pb, filename, AVIO_FLAG_WRITE ); + ret = url_fopen( &ofc->pb, filename, AVIO_FLAG_WRITE ); #endif - if ( ret < 0 ) - { - Fatal( "Could not open '%s'", filename ); - } + if ( ret < 0 ) { + Fatal( "Could not open '%s'", filename ); + } - Debug( 1, "Opened output \"%s\"", filename ); - } - else - { - Fatal( "of->flags & AVFMT_NOFILE" ); - } + Debug( 1, "Opened output \"%s\"", filename ); + } else { + Fatal( "of->flags & AVFMT_NOFILE" ); + } - video_outbuf = NULL; - if ( !(of->flags & AVFMT_RAWPICTURE) ) - { - /* allocate output buffer */ - /* XXX: API change will be done */ - // TODO: Make buffer dynamic. - video_outbuf_size = 4000000; - video_outbuf = (uint8_t *)malloc( video_outbuf_size ); - if ( video_outbuf == NULL ) { - Fatal("Unable to malloc memory for outbuf"); - } - } + video_outbuf = NULL; + if ( !(of->flags & AVFMT_RAWPICTURE) ) { + /* allocate output buffer */ + /* XXX: API change will be done */ + // TODO: Make buffer dynamic. + video_outbuf_size = 4000000; + video_outbuf = (uint8_t *)malloc( video_outbuf_size ); + if ( video_outbuf == NULL ) { + Fatal("Unable to malloc memory for outbuf"); + } + } #if LIBAVFORMAT_VERSION_CHECK(52, 101, 0, 101, 0) - av_dump_format(ofc, 0, filename, 1); + av_dump_format(ofc, 0, filename, 1); #else - dump_format(ofc, 0, filename, 1); + dump_format(ofc, 0, filename, 1); #endif #if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0) - int ret = av_write_header( ofc ); + int ret = av_write_header( ofc ); #else - int ret = avformat_write_header( ofc, NULL ); + int ret = avformat_write_header( ofc, NULL ); #endif - if ( ret < 0 ) - { - Fatal( "?_write_header failed with error %d \"%s\"", ret, av_err2str( ret ) ); - } + if ( ret < 0 ) { + Fatal( "?_write_header failed with error %d \"%s\"", ret, av_err2str( ret ) ); + } } VideoStream::VideoStream( const char *in_filename, const char *in_format, int bitrate, double frame_rate, int colours, int subpixelorder, int width, int height ) : - filename(in_filename), - format(in_format), - last_pts( -1 ), - streaming_thread(0), - do_streaming(true), - buffer_copy(NULL), - buffer_copy_lock(new pthread_mutex_t), - buffer_copy_size(0), - buffer_copy_used(0), + filename(in_filename), + format(in_format), + last_pts( -1 ), + streaming_thread(0), + do_streaming(true), + buffer_copy(NULL), + buffer_copy_lock(new pthread_mutex_t), + buffer_copy_size(0), + buffer_copy_used(0), packet_index(0) { - if ( !initialised ) - { - Initialise( ); - } - - if ( format ) - { - int length = strlen(format); - codec_and_format = new char[length+1];; - strcpy( codec_and_format, format ); - format = codec_and_format; - codec_name = NULL; - char *f = strchr(codec_and_format, '/'); - if (f != NULL) - { - *f = 0; - codec_name = f+1; - } - } + if ( !initialised ) { + Initialise( ); + } + + if ( format ) { + int length = strlen(format); + codec_and_format = new char[length+1];; + strcpy( codec_and_format, format ); + format = codec_and_format; + codec_name = NULL; + char *f = strchr(codec_and_format, '/'); + if (f != NULL) { + *f = 0; + codec_name = f+1; + } + } - SetupFormat( ); - SetupCodec( colours, subpixelorder, width, height, bitrate, frame_rate ); - SetParameters( ); - + SetupFormat( ); + SetupCodec( colours, subpixelorder, width, height, bitrate, frame_rate ); + SetParameters( ); + // Allocate buffered packets. packet_buffers = new AVPacket*[2]; packet_buffers[0] = new AVPacket(); packet_buffers[1] = new AVPacket(); packet_index = 0; - - // Initialize mutex used by streaming thread. - if ( pthread_mutex_init( buffer_copy_lock, NULL ) != 0 ) - { - Fatal("pthread_mutex_init failed"); - } + + // Initialize mutex used by streaming thread. + if ( pthread_mutex_init( buffer_copy_lock, NULL ) != 0 ) { + Fatal("pthread_mutex_init failed"); + } } -VideoStream::~VideoStream( ) -{ - Debug( 1, "VideoStream destructor." ); - - // Stop streaming thread. - if ( streaming_thread ) - { - do_streaming = false; - void* thread_exit_code; +VideoStream::~VideoStream( ) { + Debug( 1, "VideoStream destructor." ); + + // Stop streaming thread. + if ( streaming_thread ) { + do_streaming = false; + void* thread_exit_code; + + Debug( 1, "Asking streaming thread to exit." ); + + // Wait for thread to exit. + pthread_join(streaming_thread, &thread_exit_code); + } + + if ( buffer_copy != NULL ) { + av_free( buffer_copy ); + } - Debug( 1, "Asking streaming thread to exit." ); + if ( buffer_copy_lock ) { + if ( pthread_mutex_destroy( buffer_copy_lock ) != 0 ) { + Error( "pthread_mutex_destroy failed" ); + } + delete buffer_copy_lock; + } - // Wait for thread to exit. - pthread_join(streaming_thread, &thread_exit_code); - } - - if ( buffer_copy != NULL ) - { - av_free( buffer_copy ); - } - - if ( buffer_copy_lock ) - { - if ( pthread_mutex_destroy( buffer_copy_lock ) != 0 ) - { - Error( "pthread_mutex_destroy failed" ); - } - delete buffer_copy_lock; - } - if (packet_buffers) { delete packet_buffers[0]; delete packet_buffers[1]; delete[] packet_buffers; } - - /* close each codec */ - if ( ost ) - { - avcodec_close( ost->codec ); - av_free( opicture->data[0] ); + + /* close each codec */ + if ( ost ) { + avcodec_close( ost->codec ); + av_free( opicture->data[0] ); #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &opicture ); + av_frame_free( &opicture ); #else - av_freep( &opicture ); + av_freep( &opicture ); #endif - if ( tmp_opicture ) - { - av_free( tmp_opicture->data[0] ); + if ( tmp_opicture ) { + av_free( tmp_opicture->data[0] ); #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - av_frame_free( &tmp_opicture ); + av_frame_free( &tmp_opicture ); #else - av_freep( &tmp_opicture ); + av_freep( &tmp_opicture ); #endif - } - av_free( video_outbuf ); - } + } + av_free( video_outbuf ); + } - /* write the trailer, if any */ - av_write_trailer( ofc ); + /* write the trailer, if any */ + av_write_trailer( ofc ); - /* free the streams */ - for ( unsigned int i = 0; i < ofc->nb_streams; i++ ) - { - av_freep( &ofc->streams[i] ); - } + /* free the streams */ + for ( unsigned int i = 0; i < ofc->nb_streams; i++ ) { + av_freep( &ofc->streams[i] ); + } - if ( !(of->flags & AVFMT_NOFILE) ) - { - /* close the output file */ + if ( !(of->flags & AVFMT_NOFILE) ) { + /* close the output file */ #if LIBAVFORMAT_VERSION_CHECK(52, 105, 0, 105, 0) - avio_close( ofc->pb ); + avio_close( ofc->pb ); #else - url_fclose( ofc->pb ); + url_fclose( ofc->pb ); #endif - } + } - /* free the stream */ - av_free( ofc ); - - /* free format and codec_name data. */ - if ( codec_and_format ) - { - delete codec_and_format; - } + /* free the stream */ + av_free( ofc ); + + /* free format and codec_name data. */ + if ( codec_and_format ) { + delete codec_and_format; + } } -double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool _add_timestamp, unsigned int _timestamp ) -{ - if ( pthread_mutex_lock( buffer_copy_lock ) != 0 ) - { - Fatal( "EncodeFrame: pthread_mutex_lock failed." ); - } - - if (buffer_copy_size < buffer_size) - { - if ( buffer_copy ) - { - av_free( buffer_copy ); - } - - // Allocate a buffer to store source images for the streaming thread to encode. - buffer_copy = (uint8_t *)av_malloc( buffer_size ); - if ( !buffer_copy ) - { - Panic( "Could not allocate buffer_copy" ); - } - buffer_copy_size = buffer_size; - } - - add_timestamp = _add_timestamp; - timestamp = _timestamp; - buffer_copy_used = buffer_size; - memcpy(buffer_copy, buffer, buffer_size); - - if ( pthread_mutex_unlock( buffer_copy_lock ) != 0 ) - { - Fatal( "EncodeFrame: pthread_mutex_unlock failed." ); - } - - if ( streaming_thread == 0 ) - { - Debug( 1, "Starting streaming thread" ); - - // Start a thread for streaming encoded video. - if (pthread_create( &streaming_thread, NULL, StreamingThreadCallback, (void*) this) != 0){ - // Log a fatal error and exit the process. - Fatal( "VideoStream failed to create streaming thread." ); - } - } - - //return ActuallyEncodeFrame( buffer, buffer_size, add_timestamp, timestamp); - - return _timestamp; +double VideoStream::EncodeFrame( const uint8_t *buffer, int buffer_size, bool _add_timestamp, unsigned int _timestamp ) { + if ( pthread_mutex_lock( buffer_copy_lock ) != 0 ) { + Fatal( "EncodeFrame: pthread_mutex_lock failed." ); + } + + if (buffer_copy_size < buffer_size) { + if ( buffer_copy ) { + av_free( buffer_copy ); + } + + // Allocate a buffer to store source images for the streaming thread to encode. + buffer_copy = (uint8_t *)av_malloc( buffer_size ); + if ( !buffer_copy ) { + Panic( "Could not allocate buffer_copy" ); + } + buffer_copy_size = buffer_size; + } + + add_timestamp = _add_timestamp; + timestamp = _timestamp; + buffer_copy_used = buffer_size; + memcpy(buffer_copy, buffer, buffer_size); + + if ( pthread_mutex_unlock( buffer_copy_lock ) != 0 ) { + Fatal( "EncodeFrame: pthread_mutex_unlock failed." ); + } + + if ( streaming_thread == 0 ) { + Debug( 1, "Starting streaming thread" ); + + // Start a thread for streaming encoded video. + if (pthread_create( &streaming_thread, NULL, StreamingThreadCallback, (void*) this) != 0){ + // Log a fatal error and exit the process. + Fatal( "VideoStream failed to create streaming thread." ); + } + } + + //return ActuallyEncodeFrame( buffer, buffer_size, add_timestamp, timestamp); + + return _timestamp; } -double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp, unsigned int timestamp ) -{ +double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, bool add_timestamp, unsigned int timestamp ) { #ifdef HAVE_LIBSWSCALE - static struct SwsContext *img_convert_ctx = 0; + static struct SwsContext *img_convert_ctx = 0; #endif // HAVE_LIBSWSCALE - AVCodecContext *c = ost->codec; + AVCodecContext *c = ost->codec; - if ( c->pix_fmt != pf ) - { - memcpy( tmp_opicture->data[0], buffer, buffer_size ); + if ( c->pix_fmt != pf ) { + memcpy( tmp_opicture->data[0], buffer, buffer_size ); #ifdef HAVE_LIBSWSCALE - if ( !img_convert_ctx ) - { - img_convert_ctx = sws_getCachedContext( NULL, c->width, c->height, pf, c->width, c->height, c->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL ); - if ( !img_convert_ctx ) - Panic( "Unable to initialise image scaling context" ); - } - sws_scale( img_convert_ctx, tmp_opicture->data, tmp_opicture->linesize, 0, c->height, opicture->data, opicture->linesize ); + if ( !img_convert_ctx ) { + img_convert_ctx = sws_getCachedContext( NULL, c->width, c->height, pf, c->width, c->height, c->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL ); + if ( !img_convert_ctx ) + Panic( "Unable to initialise image scaling context" ); + } + sws_scale( img_convert_ctx, tmp_opicture->data, tmp_opicture->linesize, 0, c->height, opicture->data, opicture->linesize ); #else // HAVE_LIBSWSCALE - Fatal( "swscale is required for MPEG mode" ); + Fatal( "swscale is required for MPEG mode" ); #endif // HAVE_LIBSWSCALE - } - else - { - memcpy( opicture->data[0], buffer, buffer_size ); - } - AVFrame *opicture_ptr = opicture; - - AVPacket *pkt = packet_buffers[packet_index]; - av_init_packet( pkt ); + } else { + memcpy( opicture->data[0], buffer, buffer_size ); + } + AVFrame *opicture_ptr = opicture; + + AVPacket *pkt = packet_buffers[packet_index]; + av_init_packet( pkt ); int got_packet = 0; - if ( of->flags & AVFMT_RAWPICTURE ) - { + if ( of->flags & AVFMT_RAWPICTURE ) { #if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2) - pkt->flags |= AV_PKT_FLAG_KEY; + pkt->flags |= AV_PKT_FLAG_KEY; #else - pkt->flags |= PKT_FLAG_KEY; + pkt->flags |= PKT_FLAG_KEY; #endif - pkt->stream_index = ost->index; - pkt->data = (uint8_t *)opicture_ptr; - pkt->size = sizeof (AVPicture); + pkt->stream_index = ost->index; + pkt->data = (uint8_t *)opicture_ptr; + pkt->size = sizeof (AVPicture); got_packet = 1; - } - else - { - opicture_ptr->pts = c->frame_number; - opicture_ptr->quality = c->global_quality; + } else { + opicture_ptr->pts = c->frame_number; + opicture_ptr->quality = c->global_quality; #if LIBAVFORMAT_VERSION_CHECK(54, 1, 0, 2, 100) - int ret = avcodec_encode_video2( c, pkt, opicture_ptr, &got_packet ); - if ( ret != 0 ) - { - Fatal( "avcodec_encode_video2 failed with errorcode %d \"%s\"", ret, av_err2str( ret ) ); - } + int ret = avcodec_encode_video2( c, pkt, opicture_ptr, &got_packet ); + if ( ret != 0 ) { + Fatal( "avcodec_encode_video2 failed with errorcode %d \"%s\"", ret, av_err2str( ret ) ); + } #else - int out_size = avcodec_encode_video( c, video_outbuf, video_outbuf_size, opicture_ptr ); - got_packet = out_size > 0 ? 1 : 0; - pkt->data = got_packet ? video_outbuf : NULL; - pkt->size = got_packet ? out_size : 0; + int out_size = avcodec_encode_video( c, video_outbuf, video_outbuf_size, opicture_ptr ); + got_packet = out_size > 0 ? 1 : 0; + pkt->data = got_packet ? video_outbuf : NULL; + pkt->size = got_packet ? out_size : 0; #endif - if ( got_packet ) - { + if ( got_packet ) { // if ( c->coded_frame->key_frame ) // { //#if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2) @@ -711,12 +637,10 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, //#endif // } - if ( pkt->pts != (int64_t)AV_NOPTS_VALUE ) - { + if ( pkt->pts != (int64_t)AV_NOPTS_VALUE ) { pkt->pts = av_rescale_q( pkt->pts, c->time_base, ost->time_base ); } - if ( pkt->dts != (int64_t)AV_NOPTS_VALUE ) - { + if ( pkt->dts != (int64_t)AV_NOPTS_VALUE ) { pkt->dts = av_rescale_q( pkt->dts, c->time_base, ost->time_base ); } pkt->duration = av_rescale_q( pkt->duration, c->time_base, ost->time_base ); @@ -728,52 +652,49 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, } int VideoStream::SendPacket(AVPacket *packet) { - - int ret = av_write_frame( ofc, packet ); - if ( ret != 0 ) - { - Fatal( "Error %d while writing video frame: %s", ret, av_err2str( errno ) ); - } + + int ret = av_write_frame( ofc, packet ); + if ( ret != 0 ) { + Fatal( "Error %d while writing video frame: %s", ret, av_err2str( errno ) ); + } #if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) av_packet_unref( packet ); #else av_free_packet( packet ); #endif - return ret; + return ret; } void *VideoStream::StreamingThreadCallback(void *ctx){ - - Debug( 1, "StreamingThreadCallback started" ); - + + Debug( 1, "StreamingThreadCallback started" ); + if (ctx == NULL) return NULL; VideoStream* videoStream = reinterpret_cast(ctx); - - const uint64_t nanosecond_multiplier = 1000000000; - - uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->ost->codec->time_base.num) / (videoStream->ost->codec->time_base.den) ); - uint64_t frame_count = 0; - timespec start_time; - clock_gettime(CLOCK_MONOTONIC, &start_time); - uint64_t start_time_ns = (start_time.tv_sec*nanosecond_multiplier) + start_time.tv_nsec; - while(videoStream->do_streaming) - { - timespec current_time; - clock_gettime(CLOCK_MONOTONIC, ¤t_time); - uint64_t current_time_ns = (current_time.tv_sec*nanosecond_multiplier) + current_time.tv_nsec; - uint64_t target_ns = start_time_ns + (target_interval_ns * frame_count); - - if ( current_time_ns < target_ns ) - { - // It's not time to render a frame yet. - usleep( (target_ns - current_time_ns) * 0.001 ); - } - + + const uint64_t nanosecond_multiplier = 1000000000; + + uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->ost->codec->time_base.num) / (videoStream->ost->codec->time_base.den) ); + uint64_t frame_count = 0; + timespec start_time; + clock_gettime(CLOCK_MONOTONIC, &start_time); + uint64_t start_time_ns = (start_time.tv_sec*nanosecond_multiplier) + start_time.tv_nsec; + while(videoStream->do_streaming) { + timespec current_time; + clock_gettime(CLOCK_MONOTONIC, ¤t_time); + uint64_t current_time_ns = (current_time.tv_sec*nanosecond_multiplier) + current_time.tv_nsec; + uint64_t target_ns = start_time_ns + (target_interval_ns * frame_count); + + if ( current_time_ns < target_ns ) { + // It's not time to render a frame yet. + usleep( (target_ns - current_time_ns) * 0.001 ); + } + // By sending the last rendered frame we deliver frames to the client more accurate. // If we're encoding the frame before sending it there will be lag. // Since this lag is not constant the client may skip frames. - + // Get the last rendered packet. AVPacket *packet = videoStream->packet_buffers[videoStream->packet_index]; if (packet->size) { @@ -785,29 +706,26 @@ void *VideoStream::StreamingThreadCallback(void *ctx){ av_free_packet( packet ); #endif videoStream->packet_index = videoStream->packet_index ? 0 : 1; - + // Lock buffer and render next frame. - - if ( pthread_mutex_lock( videoStream->buffer_copy_lock ) != 0 ) - { - Fatal( "StreamingThreadCallback: pthread_mutex_lock failed." ); - } - - if ( videoStream->buffer_copy ) - { - // Encode next frame. - videoStream->ActuallyEncodeFrame( videoStream->buffer_copy, videoStream->buffer_copy_used, videoStream->add_timestamp, videoStream->timestamp ); - } - - if ( pthread_mutex_unlock( videoStream->buffer_copy_lock ) != 0 ) - { - Fatal( "StreamingThreadCallback: pthread_mutex_unlock failed." ); - } - - frame_count++; - } - - return 0; + + if ( pthread_mutex_lock( videoStream->buffer_copy_lock ) != 0 ) { + Fatal( "StreamingThreadCallback: pthread_mutex_lock failed." ); + } + + if ( videoStream->buffer_copy ) { + // Encode next frame. + videoStream->ActuallyEncodeFrame( videoStream->buffer_copy, videoStream->buffer_copy_used, videoStream->add_timestamp, videoStream->timestamp ); + } + + if ( pthread_mutex_unlock( videoStream->buffer_copy_lock ) != 0 ) { + Fatal( "StreamingThreadCallback: pthread_mutex_unlock failed." ); + } + + frame_count++; + } + + return 0; } #endif // HAVE_LIBAVCODEC diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp new file mode 100644 index 000000000..207053e6e --- /dev/null +++ b/src/zm_packetqueue.cpp @@ -0,0 +1,76 @@ +//ZoneMinder Packet Queue Implementation Class +//Copyright 2016 Steve Gilvarry +// +//This file is part of ZoneMinder. +// +//ZoneMinder is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//ZoneMinder is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with ZoneMinder. If not, see . + + +#include "zm_packetqueue.h" +#include "zm_ffmpeg.h" + +#define VIDEO_QUEUESIZE 200 +#define AUDIO_QUEUESIZE 50 + +using namespace std; + +zm_packetqueue::zm_packetqueue(){ + +} + +zm_packetqueue::~zm_packetqueue() { + +} + +bool zm_packetqueue::queuePacket( AVPacket* packet ) { + + AVPacket *input_ref = (AVPacket *)av_malloc(sizeof(AVPacket)); + av_init_packet( input_ref ); + if ( zm_av_packet_ref( input_ref, packet ) < 0 ) { + Error("error refing packet"); + av_free(input_ref); + return false; + } + + pktQueue.push( input_ref ); + + return true; +} + +AVPacket* zm_packetqueue::popPacket( ) { + if ( pktQueue.empty() ) { + return NULL; + } + + AVPacket *packet = pktQueue.front(); + pktQueue.pop(); + + return packet; +} + +void zm_packetqueue::clearQueue() { + AVPacket *packet = NULL; + while(!pktQueue.empty()) { + + packet = pktQueue.front(); + pktQueue.pop(); + // If we clear it, then no freeing gets done, whereas when we pop off, we assume that the packet was freed somewhere else. + zm_av_packet_unref( packet ); + av_free( packet ); + } +} + +unsigned int zm_packetqueue::size() { + return pktQueue.size(); +} diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h new file mode 100644 index 000000000..60a9620a4 --- /dev/null +++ b/src/zm_packetqueue.h @@ -0,0 +1,50 @@ +//ZoneMinder Packet Queue Interface Class +//Copyright 2016 Steve Gilvarry +// +//This file is part of ZoneMinder. +// +//ZoneMinder is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//ZoneMinder is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. +// +//You should have received a copy of the GNU General Public License +//along with ZoneMinder. If not, see . + + +#ifndef ZM_PACKETQUEUE_H +#define ZM_PACKETQUEUE_H + +//#include +//#include +//#include +#include + +extern "C" { +#include +} + +class zm_packetqueue { +public: + zm_packetqueue(); + virtual ~zm_packetqueue(); + bool queuePacket( AVPacket* packet ); + AVPacket * popPacket( ); + bool popVideoPacket(AVPacket* packet); + bool popAudioPacket(AVPacket* packet); + void clearQueue( ); + unsigned int size(); +private: + std::queue pktQueue; + +}; + + + +#endif /* ZM_PACKETQUEUE_H */ + diff --git a/src/zm_remote_camera.cpp b/src/zm_remote_camera.cpp index cc2e49739..99423fca1 100644 --- a/src/zm_remote_camera.cpp +++ b/src/zm_remote_camera.cpp @@ -21,67 +21,82 @@ #include "zm_utils.h" -RemoteCamera::RemoteCamera( int p_id, const std::string &p_protocol, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - Camera( p_id, REMOTE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ), - protocol( p_protocol ), - host( p_host ), - port( p_port ), - path( p_path ), - hp( 0 ) +RemoteCamera::RemoteCamera( + unsigned int p_monitor_id, + const std::string &p_protocol, + const std::string &p_host, + const std::string &p_port, + const std::string &p_path, + int p_width, + int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ) : + Camera( p_monitor_id, REMOTE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), + protocol( p_protocol ), + host( p_host ), + port( p_port ), + path( p_path ), + hp( 0 ) { - if ( path[0] != '/' ) - path = '/'+path; + if ( path[0] != '/' ) + path = '/'+path; } RemoteCamera::~RemoteCamera() { - if(hp != NULL) { - freeaddrinfo(hp); - hp = NULL; - } + if(hp != NULL) { + freeaddrinfo(hp); + hp = NULL; + } } void RemoteCamera::Initialise() { - if( protocol.empty() ) - Fatal( "No protocol specified for remote camera" ); + if( protocol.empty() ) + Fatal( "No protocol specified for remote camera" ); - if( host.empty() ) - Fatal( "No host specified for remote camera" ); + if( host.empty() ) + Fatal( "No host specified for remote camera" ); - if( port.empty() ) - Fatal( "No port specified for remote camera" ); + if( port.empty() ) + Fatal( "No port specified for remote camera" ); - //if( path.empty() ) - //Fatal( "No path specified for remote camera" ); + //if( path.empty() ) + //Fatal( "No path specified for remote camera" ); - // Cache as much as we can to speed things up - std::string::size_type authIndex = host.rfind( '@' ); + // Cache as much as we can to speed things up + std::string::size_type authIndex = host.rfind( '@' ); - if ( authIndex != std::string::npos ) - { - auth = host.substr( 0, authIndex ); - host.erase( 0, authIndex+1 ); - auth64 = base64Encode( auth ); + if ( authIndex != std::string::npos ) + { + auth = host.substr( 0, authIndex ); + host.erase( 0, authIndex+1 ); + auth64 = base64Encode( auth ); - authIndex = auth.rfind( ':' ); - username = auth.substr(0,authIndex); - password = auth.substr( authIndex+1, auth.length() ); + authIndex = auth.rfind( ':' ); + username = auth.substr(0,authIndex); + password = auth.substr( authIndex+1, auth.length() ); - } + } - mNeedAuth = false; - mAuthenticator = new zm::Authenticator(username,password); + mNeedAuth = false; + mAuthenticator = new zm::Authenticator(username,password); - struct addrinfo hints; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; - int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp); - if ( ret != 0 ) - { - Fatal( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) ); - } + int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp); + if ( ret != 0 ) + { + Fatal( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) ); + } } diff --git a/src/zm_remote_camera.h b/src/zm_remote_camera.h index 5378d56a1..f25f3f4b4 100644 --- a/src/zm_remote_camera.h +++ b/src/zm_remote_camera.h @@ -22,11 +22,13 @@ #include "zm_camera.h" #include "zm_rtsp_auth.h" +#include "zm_packetqueue.h" #include #include #include #include +#include // // Class representing 'remote' cameras, i.e. those which are @@ -55,7 +57,22 @@ protected: struct addrinfo *hp; public: - RemoteCamera( int p_id, const std::string &p_proto, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + RemoteCamera( + unsigned int p_monitor_id, + const std::string &p_proto, + const std::string &p_host, + const std::string &p_port, + const std::string &p_path, + int p_width, + int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ); virtual ~RemoteCamera(); const std::string &Protocol() const { return( protocol ); } @@ -73,6 +90,7 @@ public: virtual int PreCapture() = 0; virtual int Capture( Image &image ) = 0; virtual int PostCapture() = 0; + virtual int CaptureAndRecord( Image &image, bool recording, char* event_directory )=0; }; #endif // ZM_REMOTE_CAMERA_H diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index b354dc521..95c8ae507 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -30,9 +30,39 @@ #ifdef SOLARIS #include // FIONREAD and friends #endif +#ifdef __FreeBSD__ +#include +#endif -RemoteCameraHttp::RemoteCameraHttp( int p_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - RemoteCamera( p_id, "http", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture ) +RemoteCameraHttp::RemoteCameraHttp( + unsigned int p_monitor_id, + const std::string &p_method, + const std::string &p_host, + const std::string &p_port, + const std::string &p_path, + int p_width, int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio ) : + RemoteCamera( + p_monitor_id, + "http", + p_host, + p_port, + p_path, + p_width, + p_height, + p_colours, + p_brightness, + p_contrast, + p_hue, + p_colour, + p_capture, + p_record_audio ) { sd = -1; @@ -44,7 +74,7 @@ RemoteCameraHttp::RemoteCameraHttp( int p_id, const std::string &p_method, const else if ( p_method == "regexp" ) method = REGEXP; else - Fatal( "Unrecognised method '%s' when creating HTTP camera %d", p_method.c_str(), id ); + Fatal( "Unrecognised method '%s' when creating HTTP camera %d", p_method.c_str(), monitor_id ); if ( capture ) { Initialise(); @@ -108,12 +138,17 @@ int RemoteCameraHttp::Connect() { close(sd); sd = -1; - Warning("Can't connect to remote camera: %s", strerror(errno) ); + char buf[sizeof(struct in6_addr)]; + struct sockaddr_in *addr; + addr = (struct sockaddr_in *)p->ai_addr; + inet_ntop( AF_INET, &(addr->sin_addr), buf, INET6_ADDRSTRLEN ); + + Warning("Can't connect to remote camera mid: %d at %s: %s", monitor_id, buf, strerror(errno) ); continue; } - /* If we got here, we must have connected successfully */ - break; + /* If we got here, we must have connected successfully */ + break; } if(p == NULL) { @@ -154,8 +189,7 @@ int RemoteCameraHttp::SendRequest() * > 0 is the # of bytes read. */ -int RemoteCameraHttp::ReadData( Buffer &buffer, int bytes_expected ) -{ +int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { fd_set rfds; FD_ZERO(&rfds); FD_SET(sd, &rfds); @@ -163,37 +197,49 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, int bytes_expected ) struct timeval temp_timeout = timeout; int n_found = select( sd+1, &rfds, NULL, NULL, &temp_timeout ); - if( n_found == 0 ) - { + if( n_found == 0 ) { Debug( 4, "Select timed out timeout was %d secs %d usecs", temp_timeout.tv_sec, temp_timeout.tv_usec ); + int error = 0; + socklen_t len = sizeof (error); + int retval = getsockopt (sd, SOL_SOCKET, SO_ERROR, &error, &len); + if(retval != 0 ) { + Debug( 1, "error getting socket error code %s", strerror(retval) ); + } + if (error != 0) { + return -1; + } // Why are we disconnecting? It's just a timeout, meaning that data wasn't available. //Disconnect(); return( 0 ); - } - else if ( n_found < 0) - { + } else if ( n_found < 0) { Error( "Select error: %s", strerror(errno) ); return( -1 ); } - int total_bytes_to_read = 0; + unsigned int total_bytes_to_read = 0; - if ( bytes_expected ) - { + if ( bytes_expected ) { total_bytes_to_read = bytes_expected; - } - else - { - if ( ioctl( sd, FIONREAD, &total_bytes_to_read ) < 0 ) - { + } else { + if ( ioctl( sd, FIONREAD, &total_bytes_to_read ) < 0 ) { Error( "Can't ioctl(): %s", strerror(errno) ); return( -1 ); } - if ( total_bytes_to_read == 0 ) - { - if( mode == SINGLE_IMAGE ) - return( 0 ); + if ( total_bytes_to_read == 0 ) { + if ( mode == SINGLE_IMAGE ) { + int error = 0; + socklen_t len = sizeof (error); + int retval = getsockopt( sd, SOL_SOCKET, SO_ERROR, &error, &len ); + if(retval != 0 ) { + Debug( 1, "error getting socket error code %s", strerror(retval) ); + } + if (error != 0) { + return -1; + } + // Case where we are grabbing a single jpg, but no content-length was given, so the expectation is that we read until close. + return( 0 ); + } // If socket is closed locally, then select will fail, but if it is closed remotely // then we have an exception on our socket.. but no data. Debug( 3, "Socket closed remotely" ); @@ -208,34 +254,27 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, int bytes_expected ) } else { Debug(3, "Just getting %d", total_bytes_to_read ); } - } + } // end if bytes_expected or not Debug( 3, "Expecting %d bytes", total_bytes_to_read ); int total_bytes_read = 0; - do - { + do { int bytes_read = buffer.read_into( sd, total_bytes_to_read ); - if ( bytes_read < 0) - { + if ( bytes_read < 0 ) { Error( "Read error: %s", strerror(errno) ); return( -1 ); - } - else if ( bytes_read == 0) - { + } else if ( bytes_read == 0 ) { Debug( 2, "Socket closed" ); //Disconnect(); // Disconnect is done outside of ReadData now. return( -1 ); - } - else if ( bytes_read < total_bytes_to_read ) - { + } else if ( (unsigned int)bytes_read < total_bytes_to_read ) { Error( "Incomplete read, expected %d, got %d", total_bytes_to_read, bytes_read ); return( -1 ); } Debug( 3, "Read %d bytes", bytes_read ); total_bytes_read += bytes_read; total_bytes_to_read -= bytes_read; - } - while ( total_bytes_to_read ); + } while ( total_bytes_to_read ); Debug( 4, buffer ); @@ -267,279 +306,282 @@ int RemoteCameraHttp::GetResponse() switch( state ) { case HEADER : - { - static RegExpr *header_expr = 0; - static RegExpr *status_expr = 0; - static RegExpr *connection_expr = 0; - static RegExpr *content_length_expr = 0; - static RegExpr *content_type_expr = 0; + { + static RegExpr *header_expr = 0; + static RegExpr *status_expr = 0; + static RegExpr *connection_expr = 0; + static RegExpr *content_length_expr = 0; + static RegExpr *content_type_expr = 0; - while ( ! ( buffer_len = ReadData( buffer ) ) ) { - } + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(4, "Timeout waiting for REGEXP HEADER"); + } if ( buffer_len < 0 ) { Error( "Unable to read header data" ); return( -1 ); } - if ( !header_expr ) - header_expr = new RegExpr( "^(.+?\r?\n\r?\n)", PCRE_DOTALL ); - if ( header_expr->Match( (char*)buffer, buffer.size() ) == 2 ) - { - header = header_expr->MatchString( 1 ); - header_len = header_expr->MatchLength( 1 ); - Debug( 4, "Captured header (%d bytes):\n'%s'", header_len, header ); - - if ( !status_expr ) - status_expr = new RegExpr( "^HTTP/(1\\.[01]) +([0-9]+) +(.+?)\r?\n", PCRE_CASELESS ); - if ( status_expr->Match( header, header_len ) < 4 ) + if ( !header_expr ) + header_expr = new RegExpr( "^(.+?\r?\n\r?\n)", PCRE_DOTALL ); + if ( header_expr->Match( (char*)buffer, buffer.size() ) == 2 ) { - Error( "Unable to extract HTTP status from header" ); - return( -1 ); - } - http_version = status_expr->MatchString( 1 ); - status_code = atoi( status_expr->MatchString( 2 ) ); - status_mesg = status_expr->MatchString( 3 ); + header = header_expr->MatchString( 1 ); + header_len = header_expr->MatchLength( 1 ); + Debug( 4, "Captured header (%d bytes):\n'%s'", header_len, header ); - if ( status_code == 401 ) { - if ( mNeedAuth ) { - Error( "Failed authentication: " ); + if ( !status_expr ) + status_expr = new RegExpr( "^HTTP/(1\\.[01]) +([0-9]+) +(.+?)\r?\n", PCRE_CASELESS ); + if ( status_expr->Match( header, header_len ) < 4 ) + { + Error( "Unable to extract HTTP status from header" ); return( -1 ); } - mNeedAuth = true; - std::string Header = header; - - mAuthenticator->checkAuthResponse(Header); - if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { - Debug( 2, "Need Digest Authentication" ); - request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); - request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); - request += stringtf( "Host: %s\r\n", host.c_str()); - if ( strcmp( config.http_version, "1.0" ) == 0 ) - request += stringtf( "Connection: Keep-Alive\r\n" ); - request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); - request += "\r\n"; - - Debug( 2, "New request header: %s", request.c_str() ); - return( 0 ); - } + http_version = status_expr->MatchString( 1 ); + status_code = atoi( status_expr->MatchString( 2 ) ); + status_mesg = status_expr->MatchString( 3 ); - } else if ( status_code < 200 || status_code > 299 ) { - Error( "Invalid response status %d: %s\n%s", status_code, status_mesg, (char *)buffer ); - return( -1 ); - } - Debug( 3, "Got status '%d' (%s), http version %s", status_code, status_mesg, http_version ); + if ( status_code == 401 ) { + if ( mNeedAuth ) { + Error( "Failed authentication: " ); + return( -1 ); + } + mNeedAuth = true; + std::string Header = header; - if ( !connection_expr ) - connection_expr = new RegExpr( "Connection: ?(.+?)\r?\n", PCRE_CASELESS ); - if ( connection_expr->Match( header, header_len ) == 2 ) - { - connection_type = connection_expr->MatchString( 1 ); - Debug( 3, "Got connection '%s'", connection_type ); - } + mAuthenticator->checkAuthResponse(Header); + if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { + Debug( 2, "Need Digest Authentication" ); + request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); + request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); + request += stringtf( "Host: %s\r\n", host.c_str()); + if ( strcmp( config.http_version, "1.0" ) == 0 ) + request += stringtf( "Connection: Keep-Alive\r\n" ); + request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); + request += "\r\n"; - if ( !content_length_expr ) - content_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); - if ( content_length_expr->Match( header, header_len ) == 2 ) - { - content_length = atoi( content_length_expr->MatchString( 1 ) ); - Debug( 3, "Got content length '%d'", content_length ); - } + Debug( 2, "New request header: %s", request.c_str() ); + return( 0 ); + } - if ( !content_type_expr ) - content_type_expr = new RegExpr( "Content-type: ?(.+?)(?:; ?boundary=(.+?))?\r?\n", PCRE_CASELESS ); - if ( content_type_expr->Match( header, header_len ) >= 2 ) - { - content_type = content_type_expr->MatchString( 1 ); - Debug( 3, "Got content type '%s'\n", content_type ); - if ( content_type_expr->MatchCount() > 2 ) - { - content_boundary = content_type_expr->MatchString( 2 ); - Debug( 3, "Got content boundary '%s'", content_boundary ); - } - } - - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = JPEG; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGB; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGBZ; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) - { - // Image stream, so start processing - if ( !content_boundary[0] ) - { - Error( "No content boundary found in header '%s'", header ); + } else if ( status_code < 200 || status_code > 299 ) { + Error( "Invalid response status %d: %s\n%s", status_code, status_mesg, (char *)buffer ); return( -1 ); } - mode = MULTI_IMAGE; - state = SUBHEADER; - } - //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) - //{ + Debug( 3, "Got status '%d' (%s), http version %s", status_code, status_mesg, http_version ); + + if ( !connection_expr ) + connection_expr = new RegExpr( "Connection: ?(.+?)\r?\n", PCRE_CASELESS ); + if ( connection_expr->Match( header, header_len ) == 2 ) + { + connection_type = connection_expr->MatchString( 1 ); + Debug( 3, "Got connection '%s'", connection_type ); + } + + if ( !content_length_expr ) + content_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); + if ( content_length_expr->Match( header, header_len ) == 2 ) + { + content_length = atoi( content_length_expr->MatchString( 1 ) ); + Debug( 3, "Got content length '%d'", content_length ); + } + + if ( !content_type_expr ) + content_type_expr = new RegExpr( "Content-type: ?(.+?)(?:; ?boundary=\x22?(.+?)\x22?)?\r?\n", PCRE_CASELESS ); + if ( content_type_expr->Match( header, header_len ) >= 2 ) + { + content_type = content_type_expr->MatchString( 1 ); + Debug( 3, "Got content type '%s'\n", content_type ); + if ( content_type_expr->MatchCount() > 2 ) + { + content_boundary = content_type_expr->MatchString( 2 ); + Debug( 3, "Got content boundary '%s'", content_boundary ); + } + } + + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) + { + // Single image + mode = SINGLE_IMAGE; + format = JPEG; + state = CONTENT; + } + else if ( !strcasecmp( content_type, "image/x-rgb" ) ) + { + // Single image + mode = SINGLE_IMAGE; + format = X_RGB; + state = CONTENT; + } + else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) + { + // Single image + mode = SINGLE_IMAGE; + format = X_RGBZ; + state = CONTENT; + } + else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) + { + // Image stream, so start processing + if ( !content_boundary[0] ) + { + Error( "No content boundary found in header '%s'", header ); + return( -1 ); + } + mode = MULTI_IMAGE; + state = SUBHEADER; + } + //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) + //{ //// MPEG stream, coming soon! - //} + //} + else + { + Error( "Unrecognised content type '%s'", content_type ); + return( -1 ); + } + buffer.consume( header_len ); + } else { - Error( "Unrecognised content type '%s'", content_type ); - return( -1 ); + Debug( 3, "Unable to extract header from stream, retrying" ); + //return( -1 ); } - buffer.consume( header_len ); + break; } - else - { - Debug( 3, "Unable to extract header from stream, retrying" ); - //return( -1 ); - } - break; - } case SUBHEADER : - { - static RegExpr *subheader_expr = 0; - static RegExpr *subcontent_length_expr = 0; - static RegExpr *subcontent_type_expr = 0; - - if ( !subheader_expr ) { - char subheader_pattern[256] = ""; - snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary ); - subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL ); - } - if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) - { - subheader = subheader_expr->MatchString( 1 ); - subheader_len = subheader_expr->MatchLength( 1 ); - Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader ); + static RegExpr *subheader_expr = 0; + static RegExpr *subcontent_length_expr = 0; + static RegExpr *subcontent_type_expr = 0; - if ( !subcontent_length_expr ) - subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); - if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) + if ( !subheader_expr ) { - content_length = atoi( subcontent_length_expr->MatchString( 1 ) ); - Debug( 3, "Got subcontent length '%d'", content_length ); + char subheader_pattern[256] = ""; + snprintf( subheader_pattern, sizeof(subheader_pattern), "^((?:\r?\n){0,2}?(?:--)?%s\r?\n.+?\r?\n\r?\n)", content_boundary ); + subheader_expr = new RegExpr( subheader_pattern, PCRE_DOTALL ); } - - if ( !subcontent_type_expr ) - subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS ); - if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) + if ( subheader_expr->Match( (char *)buffer, (int)buffer ) == 2 ) { - content_type = subcontent_type_expr->MatchString( 1 ); - Debug( 3, "Got subcontent type '%s'", content_type ); - } + subheader = subheader_expr->MatchString( 1 ); + subheader_len = subheader_expr->MatchLength( 1 ); + Debug( 4, "Captured subheader (%d bytes):'%s'", subheader_len, subheader ); - buffer.consume( subheader_len ); - state = CONTENT; - } - else - { - Debug( 3, "Unable to extract subheader from stream, retrying" ); - while ( ! ( buffer_len = ReadData( buffer ) ) ) { + if ( !subcontent_length_expr ) + subcontent_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); + if ( subcontent_length_expr->Match( subheader, subheader_len ) == 2 ) + { + content_length = atoi( subcontent_length_expr->MatchString( 1 ) ); + Debug( 3, "Got subcontent length '%d'", content_length ); + } + + if ( !subcontent_type_expr ) + subcontent_type_expr = new RegExpr( "Content-type: ?(.+?)\r?\n", PCRE_CASELESS ); + if ( subcontent_type_expr->Match( subheader, subheader_len ) == 2 ) + { + content_type = subcontent_type_expr->MatchString( 1 ); + Debug( 3, "Got subcontent type '%s'", content_type ); + } + + buffer.consume( subheader_len ); + state = CONTENT; } + else + { + Debug( 3, "Unable to extract subheader from stream, retrying" ); + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(4, "Timeout waiting to extract subheader"); + } if ( buffer_len < 0 ) { Error( "Unable to extract subheader data" ); return( -1 ); } - } - break; - } - case CONTENT : - { - - // if content_type is something like image/jpeg;size=, this will strip the ;size= - char * semicolon = strchr( (char *)content_type, ';' ); - if ( semicolon ) { - *semicolon = '\0'; - } - - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - format = JPEG; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - format = X_RGB; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - format = X_RGBZ; - } - else - { - Error( "Found unsupported content type '%s'", content_type ); - return( -1 ); - } - - if ( content_length ) - { - while ( (long)buffer.size() < content_length ) - { -Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length ); - if ( ReadData( buffer ) < 0 ) { - Error( "Unable to read content" ); - return( -1 ); - } } - Debug( 3, "Got end of image by length, content-length = %d", content_length ); + break; } - else + case CONTENT : { - while ( !content_length ) + + // if content_type is something like image/jpeg;size=, this will strip the ;size= + char * semicolon = strchr( (char *)content_type, ';' ); + if ( semicolon ) { + *semicolon = '\0'; + } + + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { - while ( ! ( buffer_len = ReadData( buffer ) ) ) { + format = JPEG; + } + else if ( !strcasecmp( content_type, "image/x-rgb" ) ) + { + format = X_RGB; + } + else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) + { + format = X_RGBZ; + } + else + { + Error( "Found unsupported content type '%s'", content_type ); + return( -1 ); + } + + if ( content_length ) + { + while ( (long)buffer.size() < content_length ) + { + Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length ); + if ( ReadData( buffer ) < 0 ) { + Error( "Unable to read content" ); + return( -1 ); + } } + Debug( 3, "Got end of image by length, content-length = %d", content_length ); + } + else + { + while ( !content_length ) + { + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(4, "Timeout waiting for content"); + } if ( buffer_len < 0 ) { Error( "Unable to read content" ); return( -1 ); } - static RegExpr *content_expr = 0; - if ( mode == MULTI_IMAGE ) - { - if ( !content_expr ) + static RegExpr *content_expr = 0; + if ( mode == MULTI_IMAGE ) { - char content_pattern[256] = ""; - snprintf( content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary ); - content_expr = new RegExpr( content_pattern, PCRE_DOTALL ); - } - if ( content_expr->Match( buffer, buffer.size() ) == 2 ) - { - content_length = content_expr->MatchLength( 1 ); - Debug( 3, "Got end of image by pattern, content-length = %d", content_length ); + if ( !content_expr ) + { + char content_pattern[256] = ""; + snprintf( content_pattern, sizeof(content_pattern), "^(.+?)(?:\r?\n)*(?:--)?%s\r?\n", content_boundary ); + content_expr = new RegExpr( content_pattern, PCRE_DOTALL ); + } + if ( content_expr->Match( buffer, buffer.size() ) == 2 ) + { + content_length = content_expr->MatchLength( 1 ); + Debug( 3, "Got end of image by pattern, content-length = %d", content_length ); + } } } } + if ( mode == SINGLE_IMAGE ) + { + state = HEADER; + Disconnect(); + } + else + { + state = SUBHEADER; + } + Debug( 3, "Returning %d (%d) bytes of captured content", content_length, buffer.size() ); + return( content_length ); } - if ( mode == SINGLE_IMAGE ) - { - state = HEADER; - Disconnect(); - } - else - { - state = SUBHEADER; - } - Debug( 3, "Returning %d (%d) bytes of captured content", content_length, buffer.size() ); - return( content_length ); - } case HEADERCONT : case SUBHEADERCONT : - { - // Ignore - break; - } + { + // Ignore + break; + } } } } @@ -590,7 +632,7 @@ Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_ static char *authenticate_header; static char subcontent_length_header[32]; static char subcontent_type_header[64]; - + static char http_version[16]; static char status_code[16]; static char status_mesg[256]; @@ -600,506 +642,422 @@ Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_ static char content_boundary[64]; static int content_boundary_len; - while ( true ) - { - switch( state ) - { + while ( true ) { + switch( state ) { case HEADER : - { - n_headers = 0; - http_header = 0; - connection_header = 0; - content_length_header = 0; - content_type_header = 0; - authenticate_header = 0; + { + n_headers = 0; + http_header = 0; + connection_header = 0; + content_length_header = 0; + content_type_header = 0; + authenticate_header = 0; - http_version[0] = '\0'; - status_code [0]= '\0'; - status_mesg [0]= '\0'; - connection_type [0]= '\0'; - content_length = 0; - content_type[0] = '\0'; - content_boundary[0] = '\0'; - content_boundary_len = 0; - } - case HEADERCONT : - { - while ( ! ( buffer_len = ReadData( buffer ) ) ) { + http_version[0] = '\0'; + status_code [0]= '\0'; + status_mesg [0]= '\0'; + connection_type [0]= '\0'; + content_length = 0; + content_type[0] = '\0'; + content_boundary[0] = '\0'; + content_boundary_len = 0; } + case HEADERCONT : + { + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(4, "Timeout waiting for HEADERCONT"); + } if ( buffer_len < 0 ) { Error( "Unable to read header" ); return( -1 ); } - char *crlf = 0; - char *header_ptr = (char *)buffer; - int header_len = buffer.size(); - bool all_headers = false; + char *crlf = 0; + char *header_ptr = (char *)buffer; + int header_len = buffer.size(); + bool all_headers = false; - while( true ) - { - int crlf_len = memspn( header_ptr, "\r\n", header_len ); - if ( n_headers ) - { - if ( (crlf_len == 2 && !strncmp( header_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( header_ptr, "\r\n\r\n", crlf_len )) ) - { - *header_ptr = '\0'; - header_ptr += crlf_len; + while( true ) { + int crlf_len = memspn( header_ptr, "\r\n", header_len ); + if ( n_headers ) { + if ( (crlf_len == 2 && !strncmp( header_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( header_ptr, "\r\n\r\n", crlf_len )) ) { + *header_ptr = '\0'; + header_ptr += crlf_len; + header_len -= buffer.consume( header_ptr-(char *)buffer ); + all_headers = true; + break; + } + } + if ( crlf_len ) { + if ( header_len == crlf_len ) { + break; + } else { + *header_ptr = '\0'; + header_ptr += crlf_len; + header_len -= buffer.consume( header_ptr-(char *)buffer ); + } + } + + Debug( 6, "%s", header_ptr ); + if ( (crlf = mempbrk( header_ptr, "\r\n", header_len )) ) { + //headers[n_headers++] = header_ptr; + n_headers++; + + if ( !http_header && (strncasecmp( header_ptr, http_match, http_match_len ) == 0) ) { + http_header = header_ptr+http_match_len; + Debug( 6, "Got http header '%s'", header_ptr ); + } else if ( !connection_header && (strncasecmp( header_ptr, connection_match, connection_match_len) == 0) ) { + connection_header = header_ptr+connection_match_len; + Debug( 6, "Got connection header '%s'", header_ptr ); + } else if ( !content_length_header && (strncasecmp( header_ptr, content_length_match, content_length_match_len) == 0) ) { + content_length_header = header_ptr+content_length_match_len; + Debug( 6, "Got content length header '%s'", header_ptr ); + } else if ( !authenticate_header && (strncasecmp( header_ptr, authenticate_match, authenticate_match_len) == 0) ) { + authenticate_header = header_ptr; + Debug( 6, "Got authenticate header '%s'", header_ptr ); + } else if ( !content_type_header && (strncasecmp( header_ptr, content_type_match, content_type_match_len) == 0) ) { + content_type_header = header_ptr+content_type_match_len; + Debug( 6, "Got content type header '%s'", header_ptr ); + } else { + Debug( 6, "Got ignored header '%s'", header_ptr ); + } + header_ptr = crlf; header_len -= buffer.consume( header_ptr-(char *)buffer ); - all_headers = true; - break; - } - } - if ( crlf_len ) - { - if ( header_len == crlf_len ) - { - break; - } - else - { - *header_ptr = '\0'; - header_ptr += crlf_len; - header_len -= buffer.consume( header_ptr-(char *)buffer ); - } - } - - Debug( 6, "%s", header_ptr ); - if ( (crlf = mempbrk( header_ptr, "\r\n", header_len )) ) - { - //headers[n_headers++] = header_ptr; - n_headers++; - - if ( !http_header && (strncasecmp( header_ptr, http_match, http_match_len ) == 0) ) - { - http_header = header_ptr+http_match_len; - Debug( 6, "Got http header '%s'", header_ptr ); - } - else if ( !connection_header && (strncasecmp( header_ptr, connection_match, connection_match_len) == 0) ) - { - connection_header = header_ptr+connection_match_len; - Debug( 6, "Got connection header '%s'", header_ptr ); - } - else if ( !content_length_header && (strncasecmp( header_ptr, content_length_match, content_length_match_len) == 0) ) - { - content_length_header = header_ptr+content_length_match_len; - Debug( 6, "Got content length header '%s'", header_ptr ); - } - - else if ( !authenticate_header && (strncasecmp( header_ptr, authenticate_match, authenticate_match_len) == 0) ) - { - authenticate_header = header_ptr; - Debug( 6, "Got authenticate header '%s'", header_ptr ); - } - else if ( !content_type_header && (strncasecmp( header_ptr, content_type_match, content_type_match_len) == 0) ) - { - content_type_header = header_ptr+content_type_match_len; - Debug( 6, "Got content type header '%s'", header_ptr ); - } - else - { - Debug( 6, "Got ignored header '%s'", header_ptr ); - } - header_ptr = crlf; - header_len -= buffer.consume( header_ptr-(char *)buffer ); - } - else - { - // No end of line found - break; - } - } - - if ( all_headers ) - { - char *start_ptr, *end_ptr; - - if ( !http_header ) - { - Error( "Unable to extract HTTP status from header" ); - return( -1 ); - } - - start_ptr = http_header; - end_ptr = start_ptr+strspn( start_ptr, "10." ); - - memset( http_version, 0, sizeof(http_version) ); - strncpy( http_version, start_ptr, end_ptr-start_ptr ); - - start_ptr = end_ptr; - start_ptr += strspn( start_ptr, " " ); - end_ptr = start_ptr+strspn( start_ptr, "0123456789" ); - - memset( status_code, 0, sizeof(status_code) ); - strncpy( status_code, start_ptr, end_ptr-start_ptr ); - int status = atoi( status_code ); - - start_ptr = end_ptr; - start_ptr += strspn( start_ptr, " " ); - strcpy( status_mesg, start_ptr ); - - if ( status == 401 ) { - if ( mNeedAuth ) { - Error( "Failed authentication: " ); - return( -1 ); - } - if ( ! authenticate_header ) { - Error( "Failed authentication, but don't have an authentication header: " ); - return( -1 ); - } - mNeedAuth = true; - std::string Header = authenticate_header; - Debug(2, "Checking for digest auth in %s", authenticate_header ); - - mAuthenticator->checkAuthResponse(Header); - if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { - Debug( 2, "Need Digest Authentication" ); - request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); - request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); - request += stringtf( "Host: %s\r\n", host.c_str()); - if ( strcmp( config.http_version, "1.0" ) == 0 ) - request += stringtf( "Connection: Keep-Alive\r\n" ); - request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); - request += "\r\n"; - - Debug( 2, "New request header: %s", request.c_str() ); - return( 0 ); } else { - Debug( 2, "Need some other kind of Authentication" ); + // No end of line found + break; } - } else if ( status < 200 || status > 299 ) - { - Error( "Invalid response status %s: %s", status_code, status_mesg ); - return( -1 ); - } - Debug( 3, "Got status '%d' (%s), http version %s", status, status_mesg, http_version ); + } // end while search for headers - if ( connection_header ) - { - memset( connection_type, 0, sizeof(connection_type) ); - start_ptr = connection_header + strspn( connection_header, " " ); - strcpy( connection_type, start_ptr ); - Debug( 3, "Got connection '%s'", connection_type ); - } - if ( content_length_header ) - { - start_ptr = content_length_header + strspn( content_length_header, " " ); - content_length = atoi( start_ptr ); - Debug( 3, "Got content length '%d'", content_length ); - } - if ( content_type_header ) - { - memset( content_type, 0, sizeof(content_type) ); - start_ptr = content_type_header + strspn( content_type_header, " " ); - if ( (end_ptr = strchr( start_ptr, ';' )) ) - { - strncpy( content_type, start_ptr, end_ptr-start_ptr ); - Debug( 3, "Got content type '%s'", content_type ); + if ( all_headers ) { + char *start_ptr, *end_ptr; - start_ptr = end_ptr + strspn( end_ptr, "; " ); - - if ( strncasecmp( start_ptr, boundary_match, boundary_match_len ) == 0 ) - { - start_ptr += boundary_match_len; - start_ptr += strspn( start_ptr, "-" ); - content_boundary_len = sprintf( content_boundary, "--%s", start_ptr ); - Debug( 3, "Got content boundary '%s'", content_boundary ); - } - else - { - Error( "No content boundary found in header '%s'", content_type_header ); - } - } - else - { - strcpy( content_type, start_ptr ); - Debug( 3, "Got content type '%s'", content_type ); - } - } - - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = JPEG; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGB; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - // Single image - mode = SINGLE_IMAGE; - format = X_RGBZ; - state = CONTENT; - } - else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) - { - // Image stream, so start processing - if ( !content_boundary[0] ) - { - Error( "No content boundary found in header '%s'", content_type_header ); + if ( !http_header ) { + Error( "Unable to extract HTTP status from header" ); return( -1 ); } - mode = MULTI_IMAGE; - state = SUBHEADER; - } - //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) - //{ + + start_ptr = http_header; + end_ptr = start_ptr+strspn( start_ptr, "10." ); + + // FIXME WHy are we memsetting every time? Can we not do it once? + memset( http_version, 0, sizeof(http_version) ); + strncpy( http_version, start_ptr, end_ptr-start_ptr ); + + start_ptr = end_ptr; + start_ptr += strspn( start_ptr, " " ); + end_ptr = start_ptr+strspn( start_ptr, "0123456789" ); + + memset( status_code, 0, sizeof(status_code) ); + strncpy( status_code, start_ptr, end_ptr-start_ptr ); + int status = atoi( status_code ); + + start_ptr = end_ptr; + start_ptr += strspn( start_ptr, " " ); + strcpy( status_mesg, start_ptr ); + + if ( status == 401 ) { + if ( mNeedAuth ) { + Error( "Failed authentication: " ); + return( -1 ); + } + if ( ! authenticate_header ) { + Error( "Failed authentication, but don't have an authentication header: " ); + return( -1 ); + } + mNeedAuth = true; + std::string Header = authenticate_header; + Debug(2, "Checking for digest auth in %s", authenticate_header ); + + mAuthenticator->checkAuthResponse(Header); + if ( mAuthenticator->auth_method() == zm::AUTH_DIGEST ) { + Debug( 2, "Need Digest Authentication" ); + request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); + request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); + request += stringtf( "Host: %s\r\n", host.c_str()); + if ( strcmp( config.http_version, "1.0" ) == 0 ) + request += stringtf( "Connection: Keep-Alive\r\n" ); + request += mAuthenticator->getAuthHeader( "GET", path.c_str() ); + request += "\r\n"; + + Debug( 2, "New request header: %s", request.c_str() ); + return( 0 ); + } else { + Debug( 2, "Need some other kind of Authentication" ); + } + } else if ( status < 200 || status > 299 ) { + Error( "Invalid response status %s: %s", status_code, status_mesg ); + return( -1 ); + } + Debug( 3, "Got status '%d' (%s), http version %s", status, status_mesg, http_version ); + + if ( connection_header ) { + memset( connection_type, 0, sizeof(connection_type) ); + start_ptr = connection_header + strspn( connection_header, " " ); + // FIXME Should we not use strncpy? + strcpy( connection_type, start_ptr ); + Debug( 3, "Got connection '%s'", connection_type ); + } + if ( content_length_header ) { + start_ptr = content_length_header + strspn( content_length_header, " " ); + content_length = atoi( start_ptr ); + Debug( 3, "Got content length '%d'", content_length ); + } + if ( content_type_header ) { + memset( content_type, 0, sizeof(content_type) ); + start_ptr = content_type_header + strspn( content_type_header, " " ); + if ( (end_ptr = strchr( start_ptr, ';' )) ) { + strncpy( content_type, start_ptr, end_ptr-start_ptr ); + Debug( 3, "Got content type '%s'", content_type ); + + start_ptr = end_ptr + strspn( end_ptr, "; " ); + + if ( strncasecmp( start_ptr, boundary_match, boundary_match_len ) == 0 ) { + start_ptr += boundary_match_len; + start_ptr += strspn( start_ptr, "-" ); + content_boundary_len = sprintf( content_boundary, "--%s", start_ptr ); + Debug( 3, "Got content boundary '%s'", content_boundary ); + } else { + Error( "No content boundary found in header '%s'", content_type_header ); + } + } else { + strcpy( content_type, start_ptr ); + Debug( 3, "Got content type '%s'", content_type ); + } + } + + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { + // Single image + mode = SINGLE_IMAGE; + format = JPEG; + state = CONTENT; + } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { + // Single image + mode = SINGLE_IMAGE; + format = X_RGB; + state = CONTENT; + } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { + // Single image + mode = SINGLE_IMAGE; + format = X_RGBZ; + state = CONTENT; + } else if ( !strcasecmp( content_type, "multipart/x-mixed-replace" ) ) { + // Image stream, so start processing + if ( !content_boundary[0] ) { + Error( "No content boundary found in header '%s'", content_type_header ); + return( -1 ); + } + mode = MULTI_IMAGE; + state = SUBHEADER; + } + //else if ( !strcasecmp( content_type, "video/mpeg" ) || !strcasecmp( content_type, "video/mpg" ) ) + //{ //// MPEG stream, coming soon! - //} - else - { - Error( "Unrecognised content type '%s'", content_type ); - return( -1 ); + //} + else { + Error( "Unrecognised content type '%s'", content_type ); + return( -1 ); + } + } else { + Debug( 3, "Unable to extract entire header from stream, continuing" ); + state = HEADERCONT; + //return( -1 ); } + break; } - else - { - Debug( 3, "Unable to extract entire header from stream, continuing" ); - state = HEADERCONT; - //return( -1 ); - } - break; - } case SUBHEADER : - { - n_subheaders = 0; - boundary_header = 0; - subcontent_length_header[0] = '\0'; - subcontent_type_header[0] = '\0'; - content_length = 0; - content_type[0] = '\0'; - } + { + n_subheaders = 0; + boundary_header = 0; + subcontent_length_header[0] = '\0'; + subcontent_type_header[0] = '\0'; + content_length = 0; + content_type[0] = '\0'; + } case SUBHEADERCONT : - { - char *crlf = 0; - char *subheader_ptr = (char *)buffer; - int subheader_len = buffer.size(); - bool all_headers = false; - - while( true ) { - int crlf_len = memspn( subheader_ptr, "\r\n", subheader_len ); - if ( n_subheaders ) - { - if ( (crlf_len == 2 && !strncmp( subheader_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( subheader_ptr, "\r\n\r\n", crlf_len )) ) - { - *subheader_ptr = '\0'; - subheader_ptr += crlf_len; + char *crlf = 0; + char *subheader_ptr = (char *)buffer; + int subheader_len = buffer.size(); + bool all_headers = false; + + while( true ) { + int crlf_len = memspn( subheader_ptr, "\r\n", subheader_len ); + if ( n_subheaders ) { + if ( (crlf_len == 2 && !strncmp( subheader_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( subheader_ptr, "\r\n\r\n", crlf_len )) ) { + *subheader_ptr = '\0'; + subheader_ptr += crlf_len; + subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); + all_headers = true; + break; + } + } + if ( crlf_len ) { + if ( subheader_len == crlf_len ) { + break; + } else { + *subheader_ptr = '\0'; + subheader_ptr += crlf_len; + subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); + } + } + + Debug( 6, "%d: %s", subheader_len, subheader_ptr ); + + if ( (crlf = mempbrk( subheader_ptr, "\r\n", subheader_len )) ) { + //subheaders[n_subheaders++] = subheader_ptr; + n_subheaders++; + + if ( !boundary_header && (strncasecmp( subheader_ptr, content_boundary, content_boundary_len ) == 0) ) { + boundary_header = subheader_ptr; + Debug( 4, "Got boundary subheader '%s'", subheader_ptr ); + } else if ( !subcontent_length_header[0] && (strncasecmp( subheader_ptr, content_length_match, content_length_match_len) == 0) ) { + strncpy( subcontent_length_header, subheader_ptr+content_length_match_len, sizeof(subcontent_length_header) ); + *(subcontent_length_header+strcspn( subcontent_length_header, "\r\n" )) = '\0'; + Debug( 4, "Got content length subheader '%s'", subcontent_length_header ); + } else if ( !subcontent_type_header[0] && (strncasecmp( subheader_ptr, content_type_match, content_type_match_len) == 0) ) { + strncpy( subcontent_type_header, subheader_ptr+content_type_match_len, sizeof(subcontent_type_header) ); + *(subcontent_type_header+strcspn( subcontent_type_header, "\r\n" )) = '\0'; + Debug( 4, "Got content type subheader '%s'", subcontent_type_header ); + } else { + Debug( 6, "Got ignored subheader '%s' found", subheader_ptr ); + } + subheader_ptr = crlf; subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); - all_headers = true; + } else { + // No line end found break; } } - if ( crlf_len ) - { - if ( subheader_len == crlf_len ) - { - break; - } - else - { - *subheader_ptr = '\0'; - subheader_ptr += crlf_len; - subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); - } - } - Debug( 6, "%d: %s", subheader_len, subheader_ptr ); + if ( all_headers && boundary_header ) { + char *start_ptr/*, *end_ptr*/; - if ( (crlf = mempbrk( subheader_ptr, "\r\n", subheader_len )) ) - { - //subheaders[n_subheaders++] = subheader_ptr; - n_subheaders++; + Debug( 3, "Got boundary '%s'", boundary_header ); - if ( !boundary_header && (strncasecmp( subheader_ptr, content_boundary, content_boundary_len ) == 0) ) - { - boundary_header = subheader_ptr; - Debug( 4, "Got boundary subheader '%s'", subheader_ptr ); + if ( subcontent_length_header[0] ) { + start_ptr = subcontent_length_header + strspn( subcontent_length_header, " " ); + content_length = atoi( start_ptr ); + Debug( 3, "Got subcontent length '%d'", content_length ); } - else if ( !subcontent_length_header[0] && (strncasecmp( subheader_ptr, content_length_match, content_length_match_len) == 0) ) - { - strncpy( subcontent_length_header, subheader_ptr+content_length_match_len, sizeof(subcontent_length_header) ); - *(subcontent_length_header+strcspn( subcontent_length_header, "\r\n" )) = '\0'; - Debug( 4, "Got content length subheader '%s'", subcontent_length_header ); + if ( subcontent_type_header[0] ) { + memset( content_type, 0, sizeof(content_type) ); + start_ptr = subcontent_type_header + strspn( subcontent_type_header, " " ); + strcpy( content_type, start_ptr ); + Debug( 3, "Got subcontent type '%s'", content_type ); } - else if ( !subcontent_type_header[0] && (strncasecmp( subheader_ptr, content_type_match, content_type_match_len) == 0) ) - { - strncpy( subcontent_type_header, subheader_ptr+content_type_match_len, sizeof(subcontent_type_header) ); - *(subcontent_type_header+strcspn( subcontent_type_header, "\r\n" )) = '\0'; - Debug( 4, "Got content type subheader '%s'", subcontent_type_header ); + state = CONTENT; + } else { + Debug( 3, "Unable to extract subheader from stream, retrying" ); + while ( ! ( buffer_len = ReadData( buffer ) ) ) { + Debug(1, "Timeout waiting to extra subheader non regexp"); } - else - { - Debug( 6, "Got ignored subheader '%s' found", subheader_ptr ); - } - subheader_ptr = crlf; - subheader_len -= buffer.consume( subheader_ptr-(char *)buffer ); - } - else - { - // No line end found - break; - } - } - - if ( all_headers && boundary_header ) - { - char *start_ptr/*, *end_ptr*/; - - Debug( 3, "Got boundary '%s'", boundary_header ); - - if ( subcontent_length_header[0] ) - { - start_ptr = subcontent_length_header + strspn( subcontent_length_header, " " ); - content_length = atoi( start_ptr ); - Debug( 3, "Got subcontent length '%d'", content_length ); - } - if ( subcontent_type_header[0] ) - { - memset( content_type, 0, sizeof(content_type) ); - start_ptr = subcontent_type_header + strspn( subcontent_type_header, " " ); - strcpy( content_type, start_ptr ); - Debug( 3, "Got subcontent type '%s'", content_type ); - } - state = CONTENT; - } - else - { - Debug( 3, "Unable to extract subheader from stream, retrying" ); - while ( ! ( buffer_len = ReadData( buffer ) ) ) { - } if ( buffer_len < 0 ) { Error( "Unable to read subheader" ); return( -1 ); } - state = SUBHEADERCONT; + state = SUBHEADERCONT; + } + break; } - break; - } - case CONTENT : - { + case CONTENT : { - // if content_type is something like image/jpeg;size=, this will strip the ;size= - char * semicolon = strchr( content_type, ';' ); - if ( semicolon ) { - *semicolon = '\0'; - } + // if content_type is something like image/jpeg;size=, this will strip the ;size= + char * semicolon = strchr( content_type, ';' ); + if ( semicolon ) { + *semicolon = '\0'; + } - if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) - { - format = JPEG; - } - else if ( !strcasecmp( content_type, "image/x-rgb" ) ) - { - format = X_RGB; - } - else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) - { - format = X_RGBZ; - } - else - { - Error( "Found unsupported content type '%s'", content_type ); - return( -1 ); - } - - if ( format == JPEG && buffer.size() >= 2 ) - { - if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) - { - Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); + if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { + format = JPEG; + } else if ( !strcasecmp( content_type, "image/x-rgb" ) ) { + format = X_RGB; + } else if ( !strcasecmp( content_type, "image/x-rgbz" ) ) { + format = X_RGBZ; + } else { + Error( "Found unsupported content type '%s'", content_type ); return( -1 ); } - } - if ( content_length ) - { - while ( (long)buffer.size() < content_length ) - { - //int buffer_len = ReadData( buffer, content_length-buffer.size() ); - if ( ReadData( buffer ) < 0 ) { - Error( "Unable to read content" ); + // This is an early test for jpeg content, so we can bail early + if ( format == JPEG && buffer.size() >= 2 ) { + if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) { + Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); return( -1 ); } } - Debug( 3, "Got end of image by length, content-length = %d", content_length ); - } - else - { - int content_pos = 0; - while ( !content_length ) - { - buffer_len = ReadData( buffer ); - if ( buffer_len < 0 ) - { - Error( "Unable to read content" ); + + if ( content_length ) { + while ( (long)buffer.size() < content_length ) { + //int buffer_len = ReadData( buffer, content_length-buffer.size() ); + Debug(4, "getting more data"); + if ( ReadData( buffer ) < 0 ) { + Error( "Unable to read content" ); + return( -1 ); + } + } + Debug( 3, "Got end of image by length, content-length = %d", content_length ); + } else { + // Read until we find the end of image or the stream closes. + while ( !content_length ) { + Debug(4, "!content_length, ReadData"); + buffer_len = ReadData( buffer ); + if ( buffer_len < 0 ) + { + Error( "Unable to read content" ); + return( -1 ); + } + int buffer_size = buffer.size(); + if ( buffer_len ) { + // Got some data + + if ( mode == MULTI_IMAGE ) { + // Look for the boundary marker, determine content length using it's position + if ( char *start_ptr = (char *)memstr( (char *)buffer, "\r\n--", buffer_size ) ) { + content_length = start_ptr - (char *)buffer; + Debug( 2, "Got end of image by pattern (crlf--), content-length = %d", content_length ); + } else { + Debug( 2, "Did not find end of image by patten (crlf--) yet, content-length = %d", content_length ); + } + } // end if MULTI_IMAGE + } else { + content_length = buffer_size; + Debug( 2, "Got end of image by closure, content-length = %d", content_length ); + if ( mode == SINGLE_IMAGE ) { + char *end_ptr = (char *)buffer+buffer_size; + + // strip off any last line feeds + while( *end_ptr == '\r' || *end_ptr == '\n' ) { + content_length--; + end_ptr--; + } + + if ( end_ptr != ((char *)buffer+buffer_size) ) { + Debug( 2, "Trimmed end of image, new content-length = %d", content_length ); + } + } // end if SINGLE_IMAGE + } // end if read some data + } // end while ! content_length + } // end if content_length + + if ( mode == SINGLE_IMAGE ) { + state = HEADER; + Disconnect(); + } else { + state = SUBHEADER; + } + + if ( format == JPEG && buffer.size() >= 2 ) { + if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) { + Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); return( -1 ); } - int buffer_size = buffer.size(); - if ( buffer_len ) - { - if ( mode == MULTI_IMAGE ) - { - while ( char *start_ptr = (char *)memstr( (char *)buffer+content_pos, "\r\n--", buffer_size-content_pos ) ) - { - content_length = start_ptr - (char *)buffer; - Debug( 3, "Got end of image by pattern (crlf--), content-length = %d", content_length ); - break; - } - } - } - else - { - content_length = buffer_size; - Debug( 3, "Got end of image by closure, content-length = %d", content_length ); - if ( mode == SINGLE_IMAGE ) - { - char *end_ptr = (char *)buffer+buffer_size; - - while( *end_ptr == '\r' || *end_ptr == '\n' ) - { - content_length--; - end_ptr--; - } - - if ( end_ptr != ((char *)buffer+buffer_size) ) - { - Debug( 3, "Trimmed end of image, new content-length = %d", content_length ); - } - } - } } - } - if ( mode == SINGLE_IMAGE ) - { - state = HEADER; - Disconnect(); - } - else - { - state = SUBHEADER; - } - if ( format == JPEG && buffer.size() >= 2 ) - { - if ( buffer[0] != 0xff || buffer[1] != 0xd8 ) - { - Error( "Found bogus jpeg header '%02x%02x'", buffer[0], buffer[1] ); - return( -1 ); - } - } - - Debug( 3, "Returning %d bytes, buffer size: (%d) bytes of captured content", content_length, buffer.size() ); - return( content_length ); - } - } + Debug( 3, "Returning %d bytes, buffer size: (%d) bytes of captured content", content_length, buffer.size() ); + return( content_length ); + } // end cast CONTENT + } // end switch } } return( 0 ); @@ -1147,43 +1105,43 @@ int RemoteCameraHttp::Capture( Image &image ) switch( format ) { case JPEG : - { - if ( !image.DecodeJpeg( buffer.extract( content_length ), content_length, colours, subpixelorder ) ) { - Error( "Unable to decode jpeg" ); - Disconnect(); - return( -1 ); + if ( !image.DecodeJpeg( buffer.extract( content_length ), content_length, colours, subpixelorder ) ) + { + Error( "Unable to decode jpeg" ); + Disconnect(); + return( -1 ); + } + break; } - break; - } case X_RGB : - { - if ( content_length != (long)image.Size() ) { - Error( "Image length mismatch, expected %d bytes, content length was %d", image.Size(), content_length ); - Disconnect(); - return( -1 ); + if ( content_length != (long)image.Size() ) + { + Error( "Image length mismatch, expected %d bytes, content length was %d", image.Size(), content_length ); + Disconnect(); + return( -1 ); + } + image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); + break; } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - break; - } case X_RGBZ : - { - if ( !image.Unzip( buffer.extract( content_length ), content_length ) ) { - Error( "Unable to unzip RGB image" ); + if ( !image.Unzip( buffer.extract( content_length ), content_length ) ) + { + Error( "Unable to unzip RGB image" ); + Disconnect(); + return( -1 ); + } + image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); + break; + } + default : + { + Error( "Unexpected image format encountered" ); Disconnect(); return( -1 ); } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - break; - } - default : - { - Error( "Unexpected image format encountered" ); - Disconnect(); - return( -1 ); - } } return( 0 ); } diff --git a/src/zm_remote_camera_http.h b/src/zm_remote_camera_http.h index e10175de3..db76ef3e6 100644 --- a/src/zm_remote_camera_http.h +++ b/src/zm_remote_camera_http.h @@ -25,6 +25,7 @@ #include "zm_buffer.h" #include "zm_regexp.h" #include "zm_utils.h" +#include "zm_packetqueue.h" // // Class representing 'http' cameras, i.e. those which are @@ -45,7 +46,7 @@ protected: enum { SIMPLE, REGEXP } method; public: - RemoteCameraHttp( int p_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + RemoteCameraHttp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~RemoteCameraHttp(); void Initialise(); @@ -53,11 +54,12 @@ public: int Connect(); int Disconnect(); int SendRequest(); - int ReadData( Buffer &buffer, int bytes_expected=0 ); + int ReadData( Buffer &buffer, unsigned int bytes_expected=0 ); int GetResponse(); int PreCapture(); int Capture( Image &image ); int PostCapture(); + int CaptureAndRecord( Image &image, bool recording, char* event_directory ) {return(0);}; }; #endif // ZM_REMOTE_CAMERA_HTTP_H diff --git a/src/zm_remote_camera_rtsp.cpp b/src/zm_remote_camera_rtsp.cpp index 0f1997198..fbb1b9b9d 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -28,8 +28,8 @@ #include #include -RemoteCameraRtsp::RemoteCameraRtsp( int p_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : - RemoteCamera( p_id, "rtsp", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture ), +RemoteCameraRtsp::RemoteCameraRtsp( unsigned int p_monitor_id, const std::string &p_method, const std::string &p_host, const std::string &p_port, const std::string &p_path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : + RemoteCamera( p_monitor_id, "rtsp", p_host, p_port, p_path, p_width, p_height, p_colours, p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ), rtsp_describe( p_rtsp_describe ), rtspThread( 0 ) @@ -43,7 +43,7 @@ RemoteCameraRtsp::RemoteCameraRtsp( int p_id, const std::string &p_method, const else if ( p_method == "rtpRtspHttp" ) method = RtspThread::RTP_RTSP_HTTP; else - Fatal( "Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), id ); + Fatal( "Unrecognised method '%s' when creating RTSP camera %d", p_method.c_str(), monitor_id ); if ( capture ) { @@ -52,13 +52,15 @@ RemoteCameraRtsp::RemoteCameraRtsp( int p_id, const std::string &p_method, const mFormatContext = NULL; mVideoStreamId = -1; + mAudioStreamId = -1; mCodecContext = NULL; mCodec = NULL; mRawFrame = NULL; mFrame = NULL; frameCount = 0; + startTime=0; -#if HAVE_LIBSWSCALE +#if HAVE_LIBSWSCALE mConvertContext = NULL; #endif /* Has to be located inside the constructor so other components such as zma will receive correct colours and subpixel order */ @@ -113,6 +115,8 @@ void RemoteCameraRtsp::Initialise() int max_size = width*height*colours; + // This allocates a buffer able to hold a raw fframe, which is a little artbitrary. Might be nice to get some + // decent data on how large a buffer is really needed. I think in ffmpeg there are now some functions to do that. buffer.size( max_size ); if ( logDebugging() ) @@ -132,7 +136,7 @@ void RemoteCameraRtsp::Terminate() int RemoteCameraRtsp::Connect() { - rtspThread = new RtspThread( id, method, protocol, host, port, path, auth, rtsp_describe ); + rtspThread = new RtspThread( monitor_id, method, protocol, host, port, path, auth, rtsp_describe ); rtspThread->start(); @@ -167,19 +171,41 @@ int RemoteCameraRtsp::PrimeCapture() // Find first video stream present mVideoStreamId = -1; + mAudioStreamId = -1; - for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) + // Find the first video stream. + for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) { #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) #else if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) #endif { - mVideoStreamId = i; - break; + if ( mVideoStreamId == -1 ) { + mVideoStreamId = i; + continue; + } else { + Debug(2, "Have another video stream." ); + } } +#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) + if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO ) +#else + if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) +#endif + { + if ( mAudioStreamId == -1 ) { + mAudioStreamId = i; + } else { + Debug(2, "Have another audio stream." ); + } + } + } // end foreach stream + if ( mVideoStreamId == -1 ) Fatal( "Unable to locate video stream" ); + if ( mAudioStreamId == -1 ) + Debug( 3, "Unable to locate audio stream" ); // Get a pointer to the codec context for the video stream mCodecContext = mFormatContext->streams[mVideoStreamId]->codec; @@ -241,8 +267,7 @@ int RemoteCameraRtsp::PrimeCapture() return( 0 ); } -int RemoteCameraRtsp::PreCapture() -{ +int RemoteCameraRtsp::PreCapture() { if ( !rtspThread->isRunning() ) return( -1 ); if ( !rtspThread->hasSources() ) @@ -253,8 +278,7 @@ int RemoteCameraRtsp::PreCapture() return( 0 ); } -int RemoteCameraRtsp::Capture( Image &image ) -{ +int RemoteCameraRtsp::Capture( Image &image ) { AVPacket packet; uint8_t* directbuffer; int frameComplete = false; @@ -266,14 +290,12 @@ int RemoteCameraRtsp::Capture( Image &image ) return (-1); } - while ( true ) - { + while ( true ) { buffer.clear(); if ( !rtspThread->isRunning() ) return (-1); - if ( rtspThread->getFrame( buffer ) ) - { + if ( rtspThread->getFrame( buffer ) ) { Debug( 3, "Read frame %d bytes", buffer.size() ); Debug( 4, "Address %p", buffer.head() ); Hexdump( 4, buffer.head(), 16 ); @@ -281,18 +303,17 @@ int RemoteCameraRtsp::Capture( Image &image ) if ( !buffer.size() ) return( -1 ); - if(mCodecContext->codec_id == AV_CODEC_ID_H264) - { + if(mCodecContext->codec_id == AV_CODEC_ID_H264) { // SPS and PPS frames should be saved and appended to IDR frames int nalType = (buffer.head()[3] & 0x1f); - // SPS + // SPS The SPS NAL unit contains parameters that apply to a series of consecutive coded video pictures if(nalType == 7) { lastSps = buffer; continue; } - // PPS + // PPS The PPS NAL unit contains parameters that apply to the decoding of one or more individual pictures inside a coded video sequence else if(nalType == 8) { lastPps = buffer; @@ -304,79 +325,251 @@ int RemoteCameraRtsp::Capture( Image &image ) buffer += lastSps; buffer += lastPps; } + } else { + Debug(3, "Not an h264 packet"); } av_init_packet( &packet ); - - while ( !frameComplete && buffer.size() > 0 ) - { - packet.data = buffer.head(); - packet.size = buffer.size(); + + while ( !frameComplete && buffer.size() > 0 ) { + packet.data = buffer.head(); + packet.size = buffer.size(); + + // So I think this is the magic decode step. Result is a raw image? #if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) - int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ); + int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ); #else - int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ); + int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ); #endif - if ( len < 0 ) - { - Error( "Error while decoding frame %d", frameCount ); - Hexdump( Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size() ); - buffer.clear(); - continue; + if ( len < 0 ) { + Error( "Error while decoding frame %d", frameCount ); + Hexdump( Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size() ); + buffer.clear(); + continue; + } + Debug( 2, "Frame: %d - %d/%d", frameCount, len, buffer.size() ); + //if ( buffer.size() < 400 ) + //Hexdump( 0, buffer.head(), buffer.size() ); + + buffer -= len; } - Debug( 2, "Frame: %d - %d/%d", frameCount, len, buffer.size() ); - //if ( buffer.size() < 400 ) - //Hexdump( 0, buffer.head(), buffer.size() ); + // At this point, we either have a frame or ran out of buffer. What happens if we run out of buffer? + if ( frameComplete ) { + + Debug( 3, "Got frame %d", frameCount ); + + avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height ); + + #if HAVE_LIBSWSCALE + if(mConvertContext == NULL) { + mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); + + if(mConvertContext == NULL) + Fatal( "Unable to create conversion context"); + } + + if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) + Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); + #else // HAVE_LIBSWSCALE + Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); + #endif // HAVE_LIBSWSCALE + + frameCount++; + + } /* frame complete */ - buffer -= len; - - } - if ( frameComplete ) { - - Debug( 3, "Got frame %d", frameCount ); - -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(mFrame->data, mFrame->linesize, - directbuffer, imagePixFormat, width, height, 1); -#else - avpicture_fill( (AVPicture *)mFrame, directbuffer, - imagePixFormat, width, height); -#endif - -#if HAVE_LIBSWSCALE - if(mConvertContext == NULL) { - mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); - - if(mConvertContext == NULL) - Fatal( "Unable to create conversion context"); - } + zm_av_packet_unref( &packet ); + } /* getFrame() */ + + if(frameComplete) + return (0); - if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) - Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); -#else // HAVE_LIBSWSCALE - Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); -#endif // HAVE_LIBSWSCALE - - frameCount++; + } // end while true - } /* frame complete */ - -#if LIBAVCODEC_VERSION_CHECK(57, 8, 0, 12, 100) - av_packet_unref( &packet); -#else - av_free_packet( &packet ); -#endif - } /* getFrame() */ - - if(frameComplete) - return (0); - - } + // can never get here. return (0) ; } -int RemoteCameraRtsp::PostCapture() -{ +//Function to handle capture and store + +int RemoteCameraRtsp::CaptureAndRecord(Image &image, bool recording, char* event_file ) { + AVPacket packet; + uint8_t* directbuffer; + int frameComplete = false; + + + while ( true ) { + +// WHY Are we clearing it? Might be something good in it. + buffer.clear(); + + if ( !rtspThread->isRunning() ) + return (-1); + + //Video recording + if ( recording ) { + // The directory we are recording to is no longer tied to the current event. + // Need to re-init the videostore with the correct directory and start recording again + // Not sure why we are only doing this on keyframe, al + if ( videoStore && (strcmp(oldDirectory, event_file)!=0) ) { + //don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?...if we store our key frame location with the event will that be enough? + Info("Re-starting video storage module"); + if ( videoStore ) { + delete videoStore; + videoStore = NULL; + } + } // end if changed to new event + + if ( ! videoStore ) { + //Instantiate the video storage module + + videoStore = new VideoStore((const char *)event_file, "mp4", + mFormatContext->streams[mVideoStreamId], + mAudioStreamId==-1?NULL:mFormatContext->streams[mAudioStreamId], + startTime, + this->getMonitor() ); + strcpy(oldDirectory, event_file); + } // end if ! videoStore + + } else { + if ( videoStore ) { + Info("Deleting videoStore instance"); + delete videoStore; + videoStore = NULL; + } + } // end if recording or not + + if ( rtspThread->getFrame( buffer ) ) { + Debug( 3, "Read frame %d bytes", buffer.size() ); + Debug( 4, "Address %p", buffer.head() ); + Hexdump( 4, buffer.head(), 16 ); + + if ( !buffer.size() ) + return( -1 ); + + if ( mCodecContext->codec_id == AV_CODEC_ID_H264 ) { + // SPS and PPS frames should be saved and appended to IDR frames + int nalType = (buffer.head()[3] & 0x1f); + + // SPS + if(nalType == 7) { + lastSps = buffer; + continue; + } + // PPS + else if(nalType == 8) { + lastPps = buffer; + continue; + } + // IDR + else if(nalType == 5) { + buffer += lastSps; + buffer += lastPps; + } + } // end if H264, what about other codecs? + + av_init_packet( &packet ); + + // Keep decoding until a complete frame is had. + while ( !frameComplete && buffer.size() > 0 ) { + packet.data = buffer.head(); + packet.size = buffer.size(); + + // Why are we checking for it being the video stream? Because it might be audio or something else. + // Um... we just initialized packet... we can't be testing for what it is yet.... + if ( packet.stream_index == mVideoStreamId ) { + // So this does the decode +#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) + int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ); +#else + int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ); +#endif + if ( len < 0 ) { + Error( "Error while decoding frame %d", frameCount ); + Hexdump( Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size() ); + buffer.clear(); + continue; + } + Debug( 2, "Frame: %d - %d/%d", frameCount, len, buffer.size() ); + //if ( buffer.size() < 400 ) + //Hexdump( 0, buffer.head(), buffer.size() ); + + buffer -= len; + + if ( frameComplete ) { + + Debug( 3, "Got frame %d", frameCount ); + + /* Request a writeable buffer of the target image */ + directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); + if(directbuffer == NULL) { + Error("Failed requesting writeable buffer for the captured image."); + return (-1); + } + +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + av_image_fill_arrays(mFrame->data, mFrame->linesize, + directbuffer, imagePixFormat, width, height, 1); +#else + avpicture_fill( (AVPicture *)mFrame, directbuffer, + imagePixFormat, width, height); +#endif + + } // endif frameComplete + + if ( videoStore ) { + //Write the packet to our video store + int ret = videoStore->writeVideoFramePacket(&packet);//, &lastKeyframePkt); + if ( ret < 0 ) {//Less than zero and we skipped a frame +// Should not + zm_av_packet_unref( &packet ); + return 0; + } + } // end if videoStore, so we are recording + +#if HAVE_LIBSWSCALE + // Why are we re-scaling after writing out the packet? + if ( mConvertContext == NULL ) { + mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); + + if ( mConvertContext == NULL ) + Fatal( "Unable to create conversion context"); + } + + if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) + Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); +#else // HAVE_LIBSWSCALE + Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); +#endif // HAVE_LIBSWSCALE + + frameCount++; + + } else if ( packet.stream_index == mAudioStreamId ) { + Debug( 4, "Got audio packet" ); + if ( videoStore && record_audio ) { + Debug( 4, "Storing Audio packet" ); + //Write the packet to our video store + int ret = videoStore->writeAudioFramePacket( &packet ); //FIXME no relevance of last key frame + if ( ret < 0 ) { //Less than zero and we skipped a frame + zm_av_packet_unref( &packet ); + return 0; + } + } + } // end if video or audio packet + + zm_av_packet_unref( &packet ); + } // end while ! framecomplete and buffer.size() + if(frameComplete) + return (0); + } /* getFrame() */ + +} // end while true + +// can never get here. + return (0) ; +} // int RemoteCameraRtsp::CaptureAndRecord( Image &image, bool recording, char* event_file ) + +int RemoteCameraRtsp::PostCapture() { return( 0 ); } #endif // HAVE_LIBAVFORMAT diff --git a/src/zm_remote_camera_rtsp.h b/src/zm_remote_camera_rtsp.h index 079f38a56..79f656ac5 100644 --- a/src/zm_remote_camera_rtsp.h +++ b/src/zm_remote_camera_rtsp.h @@ -26,6 +26,8 @@ #include "zm_utils.h" #include "zm_rtsp.h" #include "zm_ffmpeg.h" +#include "zm_videostore.h" +#include "zm_packetqueue.h" // // Class representing 'rtsp' cameras, i.e. those which are @@ -51,23 +53,28 @@ protected: RtspThread *rtspThread; int frameCount; - + #if HAVE_LIBAVFORMAT - AVFormatContext *mFormatContext; - int mVideoStreamId; - AVCodecContext *mCodecContext; - AVCodec *mCodec; - AVFrame *mRawFrame; - AVFrame *mFrame; - _AVPIXELFORMAT imagePixFormat; + AVFormatContext *mFormatContext; + int mVideoStreamId; + int mAudioStreamId; + AVCodecContext *mCodecContext; + AVCodec *mCodec; + AVFrame *mRawFrame; + AVFrame *mFrame; + _AVPIXELFORMAT imagePixFormat; #endif // HAVE_LIBAVFORMAT + bool wasRecording; + VideoStore *videoStore; + char oldDirectory[4096]; + int64_t startTime; #if HAVE_LIBSWSCALE struct SwsContext *mConvertContext; #endif public: - RemoteCameraRtsp( int p_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); + RemoteCameraRtsp( unsigned int p_monitor_id, const std::string &method, const std::string &host, const std::string &port, const std::string &path, int p_width, int p_height, bool p_rtsp_describe, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); ~RemoteCameraRtsp(); void Initialise(); @@ -79,7 +86,7 @@ public: int PreCapture(); int Capture( Image &image ); int PostCapture(); - + int CaptureAndRecord( Image &image, bool recording, char* event_directory ); }; #endif // ZM_REMOTE_CAMERA_RTSP_H diff --git a/src/zm_rtp_source.cpp b/src/zm_rtp_source.cpp index 64106a667..ed4275b1c 100644 --- a/src/zm_rtp_source.cpp +++ b/src/zm_rtp_source.cpp @@ -64,7 +64,7 @@ RtpSource::RtpSource( int id, const std::string &localHost, int localPortBase, c mLastSrTimeReal = tvZero(); mLastSrTimeNtp = tvZero(); mLastSrTimeRtp = 0; - + if(mCodecId != AV_CODEC_ID_H264 && mCodecId != AV_CODEC_ID_MPEG4) Warning( "The device is using a codec that may not be supported. Do not be surprised if things don't work." ); } @@ -120,7 +120,7 @@ bool RtpSource::updateSeq( uint16_t seq ) { if ( uDelta == 1 ) { - Debug( 3, "Packet in sequence, gap %d", uDelta ); + Debug( 4, "Packet in sequence, gap %d", uDelta ); } else { @@ -198,7 +198,7 @@ void RtpSource::updateRtcpData( uint32_t ntpTimeSecs, uint32_t ntpTimeFrac, uint struct timeval ntpTime = tvMake( ntpTimeSecs, suseconds_t((USEC_PER_SEC*(ntpTimeFrac>>16))/(1<<16)) ); Debug( 5, "ntpTime: %ld.%06ld, rtpTime: %x", ntpTime.tv_sec, ntpTime.tv_usec, rtpTime ); - + if ( mBaseTimeNtp.tv_sec == 0 ) { mBaseTimeReal = tvNow(); @@ -262,6 +262,11 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) int rtpHeaderSize = 12 + rtpHeader->cc * 4; // No need to check for nal type as non fragmented packets already have 001 start sequence appended bool h264FragmentEnd = (mCodecId == AV_CODEC_ID_H264) && (packet[rtpHeaderSize+1] & 0x40); + // M stands for Marker, it is the 8th bit + // The interpretation of the marker is defined by a profile. It is intended + // to allow significant events such as frame boundaries to be marked in the + // packet stream. A profile may define additional marker bits or specify + // that there is no marker bit by changing the number of bits in the payload type field. bool thisM = rtpHeader->m || h264FragmentEnd; if ( updateSeq( ntohs(rtpHeader->seqN) ) ) @@ -271,46 +276,54 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) if ( mFrameGood ) { int extraHeader = 0; - + if( mCodecId == AV_CODEC_ID_H264 ) { int nalType = (packet[rtpHeaderSize] & 0x1f); - + Debug( 3, "Have H264 frame: nal type is %d", nalType ); + switch (nalType) { - case 24: - { - extraHeader = 2; - break; - } - case 25: case 26: case 27: - { - extraHeader = 3; - break; - } - // FU-A and FU-B - case 28: case 29: - { - // Is this NAL the first NAL in fragmentation sequence - if ( packet[rtpHeaderSize+1] & 0x80 ) + case 24: // STAP-A { - // Now we will form new header of frame - mFrame.append( "\x0\x0\x1\x0", 4 ); - // Reconstruct NAL header from FU headers - *(mFrame+3) = (packet[rtpHeaderSize+1] & 0x1f) | - (packet[rtpHeaderSize] & 0xe0); + extraHeader = 2; + break; } - - extraHeader = 2; - break; - } + case 25: // STAP-B + case 26: // MTAP-16 + case 27: // MTAP-24 + { + extraHeader = 3; + break; + } + // FU-A and FU-B + case 28: case 29: + { + // Is this NAL the first NAL in fragmentation sequence + if ( packet[rtpHeaderSize+1] & 0x80 ) + { + // Now we will form new header of frame + mFrame.append( "\x0\x0\x1\x0", 4 ); + // Reconstruct NAL header from FU headers + *(mFrame+3) = (packet[rtpHeaderSize+1] & 0x1f) | + (packet[rtpHeaderSize] & 0xe0); + } + + extraHeader = 2; + break; + } + default: { + Debug(3, "Unhandled nalType %d", nalType ); + } } - + // Append NAL frame start code if ( !mFrame.size() ) mFrame.append( "\x0\x0\x1", 3 ); } mFrame.append( packet+rtpHeaderSize+extraHeader, packetLen-rtpHeaderSize-extraHeader ); + } else { + Debug( 3, "NOT H264 frame: type is %d", mCodecId ); } Hexdump( 4, mFrame.head(), 16 ); @@ -325,6 +338,9 @@ bool RtpSource::handlePacket( const unsigned char *packet, size_t packetLen ) mFrameReady.updateValueSignal( true ); if ( !mFrameProcessed.getValueImmediate() ) { + // What is the point of this for loop? Is it just me, or will it call getUpdatedValue once or twice? Could it not be better written as + // if ( ! mFrameProcessed.getUpdatedValue( 1 ) && mFrameProcessed.getUpdatedValue( 1 ) ) return false; + for ( int count = 0; !mFrameProcessed.getUpdatedValue( 1 ); count++ ) if( count > 1 ) return( false ); @@ -377,7 +393,7 @@ bool RtpSource::getFrame( Buffer &buffer ) buffer = mFrame; mFrameReady.setValueImmediate( false ); mFrameProcessed.updateValueSignal( true ); - Debug( 3, "Copied %d bytes", buffer.size() ); + Debug( 4, "Copied %d bytes", buffer.size() ); return( true ); } diff --git a/src/zm_rtsp.cpp b/src/zm_rtsp.cpp index d08ba166d..f84b2aa53 100644 --- a/src/zm_rtsp.cpp +++ b/src/zm_rtsp.cpp @@ -739,8 +739,8 @@ Debug(5, "sendkeepalive %d, timeout %d, now: %d last: %d since: %d", sendKeepali { if ( buffer[0] == '$' ) { - if ( buffer.size() < 4 ) - break; + if ( buffer.size() < 4 ) + break; unsigned char channel = buffer[1]; unsigned short len = ntohs( *((unsigned short *)(buffer+2)) ); diff --git a/src/zm_storage.cpp b/src/zm_storage.cpp new file mode 100644 index 000000000..8856208e3 --- /dev/null +++ b/src/zm_storage.cpp @@ -0,0 +1,82 @@ +/* + * ZoneMinder regular expression 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "zm.h" +#include "zm_db.h" + +#include "zm_storage.h" + +#include +#include +#include + + + +Storage::Storage() { + Warning("Instantiating default Storage Object. Should not happen."); + id = 0; + strcpy(name, "Default"); + if ( config.dir_events[0] != '/' ) { + // not using an absolute path. Make it one by appending ZM_PATH_WEB + snprintf( path, sizeof (path), "%s/%s", staticConfig.PATH_WEB.c_str( ), config.dir_events ); + } else { + strncpy(path, config.dir_events, sizeof(path) ); + } +} + +Storage::Storage( MYSQL_ROW &dbrow ) { + unsigned int index = 0; + id = atoi( dbrow[index++] ); + strncpy( name, dbrow[index++], sizeof(name) ); + strncpy( path, dbrow[index++], sizeof(path) ); +} + +/* If a zero or invalid p_id is passed, then the old default path will be assumed. */ +Storage::Storage( unsigned int p_id ) { + id = 0; + + if ( p_id ) { + char sql[ZM_SQL_SML_BUFSIZ]; + snprintf( sql, sizeof(sql), "SELECT Id, Name, Path from Storage WHERE Id=%d", p_id ); + Debug(2,"Loading Storage for %d using %s", p_id, sql ); + zmDbRow dbrow; + if ( ! dbrow.fetch( sql ) ) { + Error( "Unable to load storage area for id %d: %s", p_id, mysql_error( &dbconn ) ); + } else { + unsigned int index = 0; + id = atoi( dbrow[index++] ); + strncpy( name, dbrow[index++], sizeof(name) ); + strncpy( path, dbrow[index++], sizeof(path) ); + Debug( 1, "Loaded Storage area %d '%s'", id, this->Name() ); + } + } + if ( ! id ) { + if ( config.dir_events[0] != '/' ) { + // not using an absolute path. Make it one by appending ZM_PATH_WEB + snprintf( path, sizeof (path), "%s/%s", staticConfig.PATH_WEB.c_str( ), config.dir_events ); + } else { + strncpy(path, config.dir_events, sizeof(path) ); + } + Debug(1,"No id passed to Storage constructor. Using default path %s instead", path ); + strcpy(name, "Default"); + } +} + +Storage::~Storage() { +} diff --git a/src/zm_storage.h b/src/zm_storage.h new file mode 100644 index 000000000..e47986801 --- /dev/null +++ b/src/zm_storage.h @@ -0,0 +1,44 @@ +/* + * ZoneMinder Storage Class Interface, $Date$, $Revision$ + * Copyright (C) 2001-2008 Philip Coombes + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "zm_db.h" + +#ifndef ZM_STORAGE_H +#define ZM_STORAGE_H + +class Storage { + public: + +protected: + unsigned int id; + char name[64+1]; + char path[64+1]; + +public: + Storage(); + Storage( MYSQL_ROW &dbrow ); + Storage( unsigned int p_id ); + ~Storage(); + + unsigned int Id() const { return( id ); } + const char *Name() const { return( name ); } + const char *Path() const { return( path ); } +}; + +#endif // ZM_STORAGE_H diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 01b9fa4ed..525d7b8ef 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -302,8 +302,7 @@ void StreamBase::openComms() { Error("Unable to open sock lock file %s: %s", sock_path_lock, strerror(errno) ); lock_fd = 0; - } - else if ( flock(lock_fd, LOCK_EX) != 0 ) + } else if ( flock(lock_fd, LOCK_EX) != 0 ) { Error("Unable to lock sock lock file %s: %s", sock_path_lock, strerror(errno) ); close(lock_fd); @@ -318,6 +317,8 @@ void StreamBase::openComms() if ( sd < 0 ) { Fatal( "Can't create socket: %s", strerror(errno) ); + } else { + Debug(3, "Have socket %d", sd ); } length = snprintf( loc_sock_path, sizeof(loc_sock_path), "%s/zms-%06ds.sock", config.path_socks, connkey ); @@ -332,6 +333,7 @@ void StreamBase::openComms() strncpy( loc_addr.sun_path, loc_sock_path, sizeof(loc_addr.sun_path) ); loc_addr.sun_family = AF_UNIX; + Debug(3, "Binding to %s", loc_sock_path ); if ( bind( sd, (struct sockaddr *)&loc_addr, strlen(loc_addr.sun_path)+sizeof(loc_addr.sun_family)+1 ) < 0 ) { Fatal( "Can't bind: %s", strerror(errno) ); @@ -341,6 +343,7 @@ void StreamBase::openComms() strncpy( rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path) ); rem_addr.sun_family = AF_UNIX; } // end if connKey > 0 + Debug(3, "comms open" ); } void StreamBase::closeComms() diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 12dcf0cf4..11b794a2d 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -171,7 +171,7 @@ User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) Debug( 1, "Attempting to authenticate user from auth string '%s'", auth ); char sql[ZM_SQL_SML_BUFSIZ] = ""; - snprintf( sql, sizeof(sql), "select Username, Password, Enabled, Stream+0, Events+0, Control+0, Monitors+0, System+0, MonitorIds from Users where Enabled = 1" ); + snprintf( sql, sizeof(sql), "SELECT Username, Password, Enabled, Stream+0, Events+0, Control+0, Monitors+0, System+0, MonitorIds FROM Users WHERE Enabled = 1" ); if ( mysql_query( &dbconn, sql ) ) { @@ -204,10 +204,16 @@ User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) unsigned char md5sum[md5len]; time_t now = time( 0 ); - int max_tries = 2; + unsigned int hours =config.auth_hash_ttl; - for ( int i = 0; i < max_tries; i++, now -= (60*60) ) - { + if ( ! hours ) { + Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2."); + hours = 2; + } else { + Debug( 1, "AUTH_HASH_TTL is %d", hours ); + } + + for ( unsigned int i = 0; i < hours; i++, now -= 3600 ) { struct tm *now_tm = localtime( &now ); snprintf( auth_key, sizeof(auth_key), "%s%s%s%s%d%d%d%d", @@ -232,7 +238,7 @@ User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) { sprintf( &auth_md5[2*j], "%02x", md5sum[j] ); } - Debug( 1, "Checking auth_key '%s' -> auth_md5 '%s'", auth_key, auth_md5 ); + Debug( 1, "Checking auth_key '%s' -> auth_md5 '%s' == '%s'", auth_key, auth_md5, auth ); if ( !strcmp( auth, auth_md5 ) ) { @@ -240,11 +246,14 @@ User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) User *user = new User( dbrow ); Debug(1, "Authenticated user '%s'", user->getUsername() ); return( user ); + } else { + Debug(1, "No match for %s", auth ); } } } #else // HAVE_DECL_MD5 Error( "You need to build with gnutls or openssl installed to use hash based authentication" ); #endif // HAVE_DECL_MD5 + Debug(1, "No user found for auth_key %s", auth ); return( 0 ); } diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index e777a9266..988a5c9ea 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -24,8 +24,16 @@ #include #include #include +#if defined(__arm__) +#include +#endif + +#ifdef HAVE_CURL_CURL_H +#include +#endif unsigned int sseversion = 0; +unsigned int neonversion = 0; std::string trimSet(std::string str, std::string trimset) { // Trim Both leading and trailing sets @@ -234,30 +242,59 @@ int pairsplit(const char* string, const char delim, std::string& name, std::stri return 0; } -/* Sets sse_version */ -void ssedetect() { +/* Detect special hardware features, such as SIMD instruction sets */ +void hwcaps_detect() { + neonversion = 0; + sseversion = 0; #if (defined(__i386__) || defined(__x86_64__)) /* x86 or x86-64 processor */ - uint32_t r_edx, r_ecx; - + uint32_t r_edx, r_ecx, r_ebx; + +#ifdef __x86_64__ __asm__ __volatile__( -#if defined(__i386__) - "pushl %%ebx;\n\t" -#endif + "push %%rbx\n\t" + "mov $0x0,%%ecx\n\t" + "mov $0x7,%%eax\n\t" + "cpuid\n\t" + "push %%rbx\n\t" "mov $0x1,%%eax\n\t" "cpuid\n\t" -#if defined(__i386__) - "popl %%ebx;\n\t" -#endif - : "=d" (r_edx), "=c" (r_ecx) + "pop %%rax\n\t" + "pop %%rbx\n\t" + : "=d" (r_edx), "=c" (r_ecx), "=a" (r_ebx) + : : - : "%eax" -#if !defined(__i386__) - , "%ebx" -#endif ); - - if (r_ecx & 0x00000200) { +#else + __asm__ __volatile__( + "push %%ebx\n\t" + "mov $0x0,%%ecx\n\t" + "mov $0x7,%%eax\n\t" + "cpuid\n\t" + "push %%ebx\n\t" + "mov $0x1,%%eax\n\t" + "cpuid\n\t" + "pop %%eax\n\t" + "pop %%ebx\n\t" + : "=d" (r_edx), "=c" (r_ecx), "=a" (r_ebx) + : + : + ); +#endif + + if (r_ebx & 0x00000020) { + sseversion = 52; /* AVX2 */ + Debug(1,"Detected a x86\\x86-64 processor with AVX2"); + } else if (r_ecx & 0x10000000) { + sseversion = 51; /* AVX */ + Debug(1,"Detected a x86\\x86-64 processor with AVX"); + } else if (r_ecx & 0x00100000) { + sseversion = 42; /* SSE4.2 */ + Debug(1,"Detected a x86\\x86-64 processor with SSE4.2"); + } else if (r_ecx & 0x00080000) { + sseversion = 41; /* SSE4.1 */ + Debug(1,"Detected a x86\\x86-64 processor with SSE4.1"); + } else if (r_ecx & 0x00000200) { sseversion = 35; /* SSSE3 */ Debug(1,"Detected a x86\\x86-64 processor with SSSE3"); } else if (r_ecx & 0x00000001) { @@ -272,12 +309,20 @@ void ssedetect() { } else { sseversion = 0; Debug(1,"Detected a x86\\x86-64 processor"); + } +#elif defined(__arm__) + // ARM processor + // To see if it supports NEON, we need to get that information from the kernel + unsigned long auxval = getauxval(AT_HWCAP); + if (auxval & HWCAP_ARM_NEON) { + Debug(1,"Detected ARM processor with Neon"); + neonversion = 1; + } else { + Debug(1,"Detected ARM processor"); } - #else - /* Non x86 or x86-64 processor, SSE2 is not available */ - Debug(1,"Detected a non x86\\x86-64 processor"); - sseversion = 0; + // Unknown processor + Debug(1,"Detected unknown processor architecture"); #endif } @@ -345,3 +390,18 @@ void timespec_diff(struct timespec *start, struct timespec *end, struct timespec } } +std::string UriDecode( const std::string &encoded ) { +#ifdef HAVE_LIBCURL + CURL *curl = curl_easy_init(); + int outlength; + char *cres = curl_easy_unescape(curl, encoded.c_str(), encoded.length(), &outlength); + std::string res(cres, cres + outlength); + curl_free(cres); + curl_easy_cleanup(curl); + return res; +#else +Warning("ZM Compiled without LIBCURL. UriDecoding not implemented."); + return encoded; +#endif +} + diff --git a/src/zm_utils.h b/src/zm_utils.h index 6dbf76a4d..7235bb15f 100644 --- a/src/zm_utils.h +++ b/src/zm_utils.h @@ -54,10 +54,13 @@ inline int min( int a, int b ) return( a<=b?a:b ); } -void ssedetect(); void* sse2_aligned_memcpy(void* dest, const void* src, size_t bytes); void timespec_diff(struct timespec *start, struct timespec *end, struct timespec *diff); +void hwcaps_detect(); extern unsigned int sseversion; +extern unsigned int neonversion; + +std::string UriDecode( const std::string &encoded ); #endif // ZM_UTILS_H diff --git a/src/zm_video.cpp b/src/zm_video.cpp new file mode 100644 index 000000000..82cb75d1d --- /dev/null +++ b/src/zm_video.cpp @@ -0,0 +1,518 @@ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +#include "zm.h" +#include "zm_video.h" +#include "zm_image.h" +#include "zm_utils.h" +#include "zm_rgb.h" +#include + +VideoWriter::VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder) : +container(p_container), codec(p_codec), path(p_path), width(p_width), height(p_height), colours(p_colours), subpixelorder(p_subpixelorder), frame_count(0) { + Debug(7,"Video object created"); + + /* Parameter checking */ + if(path.empty()) { + Error("Invalid file path"); + } + if(!width || !height) { + Error("Invalid width or height"); + } + +} + +VideoWriter::~VideoWriter() { + Debug(7,"Video object destroyed"); + +} + +int VideoWriter::Reset(const char* new_path) { + /* Common variables reset */ + + /* If there is a new path, use it */ + if(new_path != NULL) { + path = new_path; + } + + /* Reset frame counter */ + frame_count = 0; + + return 0; +} + + +#if ZM_HAVE_VIDEOWRITER_X264MP4 +X264MP4Writer::X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector* p_user_params) : VideoWriter("mp4", "h264", p_path, p_width, p_height, p_colours, p_subpixelorder), bOpen(false), bGotH264AVCInfo(false), bFirstFrame(true) { + + /* Initialize ffmpeg if it hasn't been initialized yet */ + FFMPEGInit(); + + /* Initialize swscale */ + zm_pf = GetFFMPEGPixelFormat(colours,subpixelorder); + if(zm_pf == 0) { + Error("Unable to match ffmpeg pixelformat"); + } + codec_pf = AV_PIX_FMT_YUV420P; + + swscaleobj.SetDefaults(zm_pf, codec_pf, width, height); + + /* Calculate the image sizes. We will need this for parameter checking */ + zm_imgsize = colours * width * height; + codec_imgsize = avpicture_get_size( codec_pf, width, height); + if(!codec_imgsize) { + Error("Failed calculating codec pixel format image size"); + } + + /* If supplied with user parameters to the encoder, copy them */ + if(p_user_params != NULL) { + user_params = *p_user_params; + } + + /* Setup x264 parameters */ + if(x264config() < 0) { + Error("Failed setting x264 parameters"); + } + + /* Allocate x264 input picture */ + x264_picture_alloc(&x264picin, X264_CSP_I420, x264params.i_width, x264params.i_height); + +} + +X264MP4Writer::~X264MP4Writer() { + + /* Free x264 input picture */ + x264_picture_clean(&x264picin); + + if(bOpen) + Close(); + +} + +int X264MP4Writer::Open() { + + /* Open the encoder */ + x264enc = x264_encoder_open(&x264params); + if(x264enc == NULL) { + Error("Failed opening x264 encoder"); + return -1; + } + + // Debug(4,"x264 maximum delayed frames: %d",x264_encoder_maximum_delayed_frames(x264enc)); + + x264_nal_t* nals; + int i_nals; + if(!x264_encoder_headers(x264enc,&nals,&i_nals)) { + Error("Failed getting encoder headers"); + return -2; + } + + /* Search SPS NAL for AVC information */ + for(int i=0;i 0; i-- ) { + x264encodeloop(true); + } + + /* Close the encoder */ + x264_encoder_close(x264enc); + + /* Close MP4 handle */ + MP4Close(mp4h); + + /* Required for proper HTTP streaming */ + MP4Optimize((path + ".incomplete").c_str(), path.c_str()); + + /* Delete the temporary file */ + unlink((path + ".incomplete").c_str()); + + bOpen = false; + + Debug(7, "Video closed. Total frames: %d", frame_count); + + return 0; +} + +int X264MP4Writer::Reset(const char* new_path) { + + /* Close the encoder and file */ + if(bOpen) + Close(); + + /* Reset common variables */ + VideoWriter::Reset(new_path); + + /* Reset local variables */ + bFirstFrame = true; + bGotH264AVCInfo = false; + prevnals.clear(); + prevpayload.clear(); + + /* Reset x264 parameters */ + x264config(); + + /* Open the encoder */ + Open(); + + return 0; +} + +int X264MP4Writer::Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) { + + /* Parameter checking */ + if(data == NULL) { + Error("NULL buffer"); + return -1; + } + + if(data_size != zm_imgsize) { + Error("The data buffer size does not match the expected size. Expected: %d Current: %d", zm_imgsize, data_size); + return -2; + } + + if(!bOpen) { + Warning("The encoder was not initialized, initializing now"); + Open(); + } + + /* Convert the image into the x264 input picture */ + if(swscaleobj.ConvertDefaults(data, data_size, x264picin.img.plane[0], codec_imgsize) < 0) { + Error("Image conversion failed"); + return -3; + } + + /* Set PTS */ + x264picin.i_pts = frame_time; + + /* Do the encoding */ + x264encodeloop(); + + /* Increment frame counter */ + frame_count++; + + return 0; +} + +int X264MP4Writer::Encode(const Image* img, const unsigned int frame_time) { + + if(img->Width() != width) { + Error("Source image width differs. Source: %d Output: %d",img->Width(), width); + return -12; + } + + if(img->Height() != height) { + Error("Source image height differs. Source: %d Output: %d",img->Height(), height); + return -13; + } + + return Encode(img->Buffer(),img->Size(),frame_time); +} + +int X264MP4Writer::x264config() { + /* Sets up the encoder configuration */ + + int x264ret; + + /* Defaults */ + const char* preset = "veryfast"; + const char* tune = "stillimage"; + const char* profile = "main"; + + /* Search the user parameters for preset, tune and profile */ + for(unsigned int i=0; i < user_params.size(); i++) { + if(strcmp(user_params[i].pname, "preset") == 0) { + /* Got preset */ + preset = user_params[i].pvalue; + } else if(strcmp(user_params[i].pname, "tune") == 0) { + /* Got tune */ + tune = user_params[i].pvalue; + } else if(strcmp(user_params[i].pname, "profile") == 0) { + /* Got profile */ + profile = user_params[i].pvalue; + } + } + + /* Set the defaults and preset and tune */ + x264ret = x264_param_default_preset(&x264params, preset, tune); + if(x264ret != 0) { + Error("Failed setting x264 preset %s and tune %s : %d",preset,tune,x264ret); + } + + /* Set the profile */ + x264ret = x264_param_apply_profile(&x264params, profile); + if(x264ret != 0) { + Error("Failed setting x264 profile %s : %d",profile,x264ret); + } + + /* Input format */ + x264params.i_width = width; + x264params.i_height = height; + x264params.i_csp = X264_CSP_I420; + + /* Quality control */ + x264params.rc.i_rc_method = X264_RC_CRF; + x264params.rc.f_rf_constant = 23.0; + + /* Enable b-frames */ + x264params.i_bframe = 16; + x264params.i_bframe_adaptive = 1; + + /* Timebase */ + x264params.i_timebase_num = 1; + x264params.i_timebase_den = 1000; + + /* Enable variable frame rate */ + x264params.b_vfr_input = 1; + + /* Disable annex-b (start codes) */ + x264params.b_annexb = 0; + + /* TODO: Setup error handler */ + //x264params.i_log_level = X264_LOG_DEBUG; + + /* Process user parameters (excluding preset, tune and profile) */ + for(unsigned int i=0; i < user_params.size(); i++) { + /* Skip preset, tune and profile */ + if( (strcmp(user_params[i].pname, "preset") == 0) || (strcmp(user_params[i].pname, "tune") == 0) || (strcmp(user_params[i].pname, "profile") == 0) ) { + continue; + } + + /* Pass the name and value to x264 */ + x264ret = x264_param_parse(&x264params, user_params[i].pname, user_params[i].pvalue); + + /* Error checking */ + if(x264ret != 0) { + if(x264ret == X264_PARAM_BAD_NAME) { + Error("Failed processing x264 user parameter %s=%s : Bad name", user_params[i].pname, user_params[i].pvalue); + } else if(x264ret == X264_PARAM_BAD_VALUE) { + Error("Failed processing x264 user parameter %s=%s : Bad value", user_params[i].pname, user_params[i].pvalue); + } else { + Error("Failed processing x264 user parameter %s=%s : Unknown error (%d)", user_params[i].pname, user_params[i].pvalue, x264ret); + } + } + } + + return 0; +} + +void X264MP4Writer::x264encodeloop(bool bFlush) { + + x264_nal_t* nals; + int i_nals; + int frame_size; + + if(bFlush) { + frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, NULL, &x264picout); + } else { + frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, &x264picin, &x264picout); + } + + if (frame_size > 0 || bFlush) { + Debug(8, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",frame_count, x264picout.i_pts, x264picout.i_dts, frame_size); + + /* Handle the previous frame */ + if(!bFirstFrame) { + + buffer.clear(); + + /* Process the NALs for the previous frame */ + for(unsigned int i=0; i < prevnals.size(); i++) { + Debug(9,"Processing NAL: Type %d Size %d",prevnals[i].i_type,prevnals[i].i_payload); + + switch(prevnals[i].i_type) { + case NAL_PPS: + /* PPS NAL */ + MP4AddH264PictureParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4); + break; + case NAL_SPS: + /* SPS NAL */ + MP4AddH264SequenceParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4); + break; + default: + /* Anything else, hopefully frames, so copy it into the sample */ + buffer.append(prevnals[i].p_payload, prevnals[i].i_payload); + } + } + + /* Calculate frame duration and offset */ + int duration = x264picout.i_dts - prevDTS; + int offset = prevPTS - prevDTS; + + /* Write the sample */ + if(!buffer.empty()) { + if(!MP4WriteSample(mp4h, mp4vtid, buffer.extract(buffer.size()), buffer.size(), duration, offset, prevKeyframe)) { + Error("Failed writing sample"); + } + } + + /* Cleanup */ + prevnals.clear(); + prevpayload.clear(); + + } + + /* Got a frame. Copy this new frame into the previous frame */ + if(frame_size > 0) { + /* Copy the NALs and the payloads */ + for(int i=0;i* vec) { + if(vec == NULL) { + Error("NULL Encoder parameters vector pointer"); + return -1; + } + + if(str == NULL) { + Error("NULL Encoder parameters string"); + return -2; + } + + vec->clear(); + + if(str[0] == 0) { + /* Empty */ + return 0; + } + + std::string line; + std::stringstream ss(str); + size_t valueoffset; + size_t valuelen; + unsigned int lineno = 0; + EncoderParameter_t param; + + while(std::getline(ss, line) ) { + lineno++; + + /* Remove CR if exists */ + if(line.length() >= 1 && line[line.length()-1] == '\r') { + line.erase(line.length()-1); + } + + /* Skip comments and empty lines */ + if(line.empty() || line[0] == '#') { + continue; + } + + valueoffset = line.find('='); + if(valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0) { + Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno); + continue; + } + + + if(valueoffset > (sizeof(param.pname)-1) ) { + Warning("Failed parsing encoder parameters line %d: Name too long", lineno); + continue; + } + + valuelen = line.length() - (valueoffset+1); + + if( valuelen > (sizeof(param.pvalue)-1) ) { + Warning("Failed parsing encoder parameters line %d: Value too long", lineno); + continue; + } + + /* Copy and NULL terminate */ + line.copy(param.pname, valueoffset, 0); + line.copy(param.pvalue, valuelen, valueoffset+1); + param.pname[valueoffset] = 0; + param.pvalue[valuelen] = 0; + + /* Push to the vector */ + vec->push_back(param); + + Debug(7, "Parsed encoder parameter: %s = %s", param.pname, param.pvalue); + } + + Debug(7, "Parsed %d lines", lineno); + + return 0; +} + + diff --git a/src/zm_video.h b/src/zm_video.h new file mode 100644 index 000000000..936adfeb8 --- /dev/null +++ b/src/zm_video.h @@ -0,0 +1,173 @@ +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// + +#ifndef ZM_VIDEO_H +#define ZM_VIDEO_H + +#include "zm.h" +#include "zm_rgb.h" +#include "zm_utils.h" +#include "zm_ffmpeg.h" +#include "zm_buffer.h" + +/* +#define HAVE_LIBX264 1 +#define HAVE_LIBMP4V2 1 +#define HAVE_X264_H 1 +#define HAVE_MP4_H 1 +*/ + +#if HAVE_MP4V2_MP4V2_H +#include +#endif +#if HAVE_MP4V2_H +#include +#endif +#if HAVE_MP4_H +#include +#endif + +#if HAVE_X264_H +#ifdef __cplusplus +extern "C" { +#endif +#include +#ifdef __cplusplus +} +#endif +#endif + +/* Structure for user parameters to the encoder */ +struct EncoderParameter_t { + char pname[48]; + char pvalue[48]; + +}; +int ParseEncoderParameters(const char* str, std::vector* vec); + +/* VideoWriter is a generic interface that ZM uses to save events as videos */ +/* It is relatively simple and the functions are pure virtual, so they must be implemented by the deriving class */ + +class VideoWriter { + +protected: + std::string container; + std::string codec; + std::string path; + unsigned int width; + unsigned int height; + unsigned int colours; + unsigned int subpixelorder; + + unsigned int frame_count; + +public: + VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); + virtual ~VideoWriter(); + virtual int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) = 0; + virtual int Encode(const Image* img, const unsigned int frame_time) = 0; + virtual int Open() = 0; + virtual int Close() = 0; + virtual int Reset(const char* new_path = NULL); + + const char* GetContainer() const { + return container.c_str(); + } + const char* GetCodec() const { + return codec.c_str(); + } + const char* GetPath() const { + return path.c_str(); + } + unsigned int GetWidth() const { + return width; + } + unsigned int GetHeight() const { + return height; + } + unsigned int GetColours() const { + return colours; + } + unsigned int GetSubpixelorder () const { + return subpixelorder; + } + unsigned int GetFrameCount() const { + return frame_count; + } +}; + +#if HAVE_LIBX264 && HAVE_LIBMP4V2 && HAVE_LIBAVUTIL && HAVE_LIBSWSCALE +#define ZM_HAVE_VIDEOWRITER_X264MP4 1 +class X264MP4Writer : public VideoWriter { + +protected: + + bool bOpen; + bool bGotH264AVCInfo; + bool bFirstFrame; + + /* SWScale */ + SWScale swscaleobj; + enum _AVPIXELFORMAT zm_pf; + enum _AVPIXELFORMAT codec_pf; + size_t codec_imgsize; + size_t zm_imgsize; + + /* User parameters */ + std::vector user_params; + + /* AVC Information */ + uint8_t x264_profleindication; + uint8_t x264_profilecompat; + uint8_t x264_levelindication; + + /* NALs */ + Buffer buffer; + + /* Previous frame */ + int prevPTS; + int prevDTS; + bool prevKeyframe; + Buffer prevpayload; + std::vector prevnals; + + /* Internal functions */ + int x264config(); + void x264encodeloop(bool bFlush = false); + + /* x264 objects */ + x264_t* x264enc; + x264_param_t x264params; + x264_picture_t x264picin; + x264_picture_t x264picout; + + /* MP4v2 objects */ + MP4FileHandle mp4h; + MP4TrackId mp4vtid; + + +public: + X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector* p_user_params = NULL); + ~X264MP4Writer(); + int Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time); + int Encode(const Image* img, const unsigned int frame_time); + int Open(); + int Close(); + int Reset(const char* new_path = NULL); + +}; +#endif // HAVE_LIBX264 && HAVE_LIBMP4V2 && HAVE_LIBAVUTIL && HAVE_LIBSWSCALE + +#endif // ZM_VIDEO_H diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp new file mode 100644 index 000000000..b4d2ef741 --- /dev/null +++ b/src/zm_videostore.cpp @@ -0,0 +1,941 @@ +// +// ZoneMinder Video Storage Implementation +// Written by Chris Wiggins +// http://chriswiggins.co.nz +// Modification by Steve Gilvarry +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// + +#define __STDC_FORMAT_MACROS 1 + +#include +#include +#include + +#include "zm.h" +#include "zm_videostore.h" + +extern "C"{ + #include "libavutil/time.h" +} + +VideoStore::VideoStore(const char *filename_in, const char *format_in, + AVStream *p_video_input_stream, + AVStream *p_audio_input_stream, + int64_t nStartTime, + Monitor * monitor + ) { + video_input_stream = p_video_input_stream; + audio_input_stream = p_audio_input_stream; + + video_input_context = video_input_stream->codec; + + //store inputs in variables local to class + filename = filename_in; + format = format_in; + + keyframeMessage = false; + keyframeSkipNumber = 0; + + Info("Opening video storage stream %s format: %s\n", filename, format); + + //Init everything we need, shouldn't have to do this, ffmpeg_camera or something else will call it. + //av_register_all(); + + ret = avformat_alloc_output_context2(&oc, NULL, NULL, filename); + if ( ret < 0 ) { + Warning("Could not create video storage stream %s as no output context" + " could be assigned based on filename: %s", + filename, + av_make_error_string(ret).c_str() + ); + } else { + Debug(2, "Success alocateing output context"); + } + + //Couldn't deduce format from filename, trying from format name + if (!oc) { + avformat_alloc_output_context2(&oc, NULL, format, filename); + if (!oc) { + Fatal("Could not create video storage stream %s as no output context" + " could not be assigned based on filename or format %s", + filename, format); + } + } else { + Debug(2, "Success alocateing output context"); + } + + AVDictionary *pmetadata = NULL; + int dsr = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0); + if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); + + oc->metadata = pmetadata; + + output_format = oc->oformat; + + video_output_stream = avformat_new_stream(oc, (AVCodec*)video_input_context->codec); + if (!video_output_stream) { + Fatal("Unable to create video out stream\n"); + } else { + Debug(2, "Success creating video out stream" ); + } + + video_output_context = video_output_stream->codec; + +#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0) + Debug(2, "setting parameters"); + ret = avcodec_parameters_to_context( video_output_context, video_input_stream->codecpar ); + if ( ret < 0 ) { + Error( "Could not initialize stream parameteres"); + return; + } else { + Debug(2, "Success getting parameters"); + } +#else + ret = avcodec_copy_context(video_output_context, video_input_context ); + if (ret < 0) { + Fatal("Unable to copy input video context to output video context %s\n", + av_make_error_string(ret).c_str()); + } else { + Debug(3, "Success copying context" ); + } +#endif + + // Just copy them from the input, no reason to choose different + video_output_context->time_base = video_input_context->time_base; + video_output_stream->time_base = video_input_stream->time_base; + + Debug(3, "Time bases: VIDEO input stream (%d/%d) input codec: (%d/%d) output stream: (%d/%d) output codec (%d/%d)", + video_input_stream->time_base.num, + video_input_stream->time_base.den, + video_input_context->time_base.num, + video_input_context->time_base.den, + video_output_stream->time_base.num, + video_output_stream->time_base.den, + video_output_context->time_base.num, + video_output_context->time_base.den + ); + +#if 0 + if ( video_input_context->sample_aspect_ratio.den && ( video_output_stream->sample_aspect_ratio.den != video_input_context->sample_aspect_ratio.den ) ) { + Warning("Fixing sample_aspect_ratio.den from (%d) to (%d)", video_output_stream->sample_aspect_ratio.den, video_input_context->sample_aspect_ratio.den ); + video_output_stream->sample_aspect_ratio.den = video_input_context->sample_aspect_ratio.den; + } else { + Debug(3, "aspect ratio denominator is (%d)", video_output_stream->sample_aspect_ratio.den ); + } + if ( video_input_context->sample_aspect_ratio.num && ( video_output_stream->sample_aspect_ratio.num != video_input_context->sample_aspect_ratio.num ) ) { + Warning("Fixing sample_aspect_ratio.num from video_output_stream(%d) to video_input_stream(%d)", video_output_stream->sample_aspect_ratio.num, video_input_context->sample_aspect_ratio.num ); + video_output_stream->sample_aspect_ratio.num = video_input_context->sample_aspect_ratio.num; + } else { + Debug(3, "aspect ratio numerator is (%d)", video_output_stream->sample_aspect_ratio.num ); + } + if ( video_output_context->codec_id != video_input_context->codec_id ) { + Warning("Fixing video_output_context->codec_id"); + video_output_context->codec_id = video_input_context->codec_id; + } + if ( ! video_output_context->time_base.num ) { + Warning("video_output_context->time_base.num is not set%d/%d. Fixing by setting it to 1", video_output_context->time_base.num, video_output_context->time_base.den); + Warning("video_output_context->time_base.num is not set%d/%d. Fixing by setting it to 1", video_output_stream->time_base.num, video_output_stream->time_base.den); + video_output_context->time_base.num = video_output_stream->time_base.num; + video_output_context->time_base.den = video_output_stream->time_base.den; + } + + if ( video_output_stream->sample_aspect_ratio.den != video_output_context->sample_aspect_ratio.den ) { + Warning("Fixingample_aspect_ratio.den"); + video_output_stream->sample_aspect_ratio.den = video_output_context->sample_aspect_ratio.den; + } + if ( video_output_stream->sample_aspect_ratio.num != video_input_context->sample_aspect_ratio.num ) { + Warning("Fixingample_aspect_ratio.num"); + video_output_stream->sample_aspect_ratio.num = video_input_context->sample_aspect_ratio.num; + } + if ( video_output_context->codec_id != video_input_context->codec_id ) { + Warning("Fixing video_output_context->codec_id"); + video_output_context->codec_id = video_input_context->codec_id; + } + if ( ! video_output_context->time_base.num ) { + Warning("video_output_context->time_base.num is not set%d/%d. Fixing by setting it to 1", video_output_context->time_base.num, video_output_context->time_base.den); + Warning("video_output_context->time_base.num is not set%d/%d. Fixing by setting it to 1", video_output_stream->time_base.num, video_output_stream->time_base.den); + video_output_context->time_base.num = video_output_stream->time_base.num; + video_output_context->time_base.den = video_output_stream->time_base.den; + } +#endif + + // WHY? + //video_output_context->codec_tag = 0; + if (!video_output_context->codec_tag) { + Debug(2, "No codec_tag"); + if (! oc->oformat->codec_tag + || av_codec_get_id (oc->oformat->codec_tag, video_input_context->codec_tag) == video_output_context->codec_id + || av_codec_get_tag(oc->oformat->codec_tag, video_input_context->codec_id) <= 0) { + Warning("Setting codec tag"); + video_output_context->codec_tag = video_input_context->codec_tag; + } + } + + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { + video_output_context->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + + Monitor::Orientation orientation = monitor->getOrientation(); + if ( orientation ) { + if ( orientation == Monitor::ROTATE_0 ) { + + } else if ( orientation == Monitor::ROTATE_90 ) { + dsr = av_dict_set( &video_output_stream->metadata, "rotate", "90", 0); + if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); + } else if ( orientation == Monitor::ROTATE_180 ) { + dsr = av_dict_set( &video_output_stream->metadata, "rotate", "180", 0); + if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); + } else if ( orientation == Monitor::ROTATE_270 ) { + dsr = av_dict_set( &video_output_stream->metadata, "rotate", "270", 0); + if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); + } else { + Warning( "Unsupported Orientation(%d)", orientation ); + } + } + + audio_output_codec = NULL; + audio_input_context = NULL; + + if (audio_input_stream) { + audio_input_context = audio_input_stream->codec; + + if ( audio_input_context->codec_id != AV_CODEC_ID_AAC ) { +#ifdef HAVE_LIBSWRESAMPLE + resample_context = NULL; + char error_buffer[256]; + avcodec_string(error_buffer, sizeof(error_buffer), audio_input_context, 0 ); + Debug(3, "Got something other than AAC (%s)", error_buffer ); + audio_output_stream = NULL; + + audio_output_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); + if ( audio_output_codec ) { +Debug(2, "Have audio output codec"); + audio_output_stream = avformat_new_stream( oc, audio_output_codec ); + + audio_output_context = audio_output_stream->codec; + + if ( audio_output_context ) { + +Debug(2, "Have audio_output_context"); + AVDictionary *opts = NULL; + av_dict_set(&opts, "strict", "experimental", 0); + + /* put sample parameters */ + audio_output_context->bit_rate = audio_input_context->bit_rate; + audio_output_context->sample_rate = audio_input_context->sample_rate; + audio_output_context->channels = audio_input_context->channels; + audio_output_context->channel_layout = audio_input_context->channel_layout; + audio_output_context->sample_fmt = audio_input_context->sample_fmt; + //audio_output_context->refcounted_frames = 1; + + if (audio_output_codec->supported_samplerates) { + int found = 0; + for ( unsigned int i = 0; audio_output_codec->supported_samplerates[i]; i++) { + if ( audio_output_context->sample_rate == audio_output_codec->supported_samplerates[i] ) { + found = 1; + break; + } + } + if ( found ) { + Debug(3, "Sample rate is good"); + } else { + audio_output_context->sample_rate = audio_output_codec->supported_samplerates[0]; + Debug(1, "Sampel rate is no good, setting to (%d)", audio_output_codec->supported_samplerates[0] ); + } + } + + /* check that the encoder supports s16 pcm input */ + if (!check_sample_fmt( audio_output_codec, audio_output_context->sample_fmt)) { + Debug( 3, "Encoder does not support sample format %s, setting to FLTP", + av_get_sample_fmt_name( audio_output_context->sample_fmt)); + audio_output_context->sample_fmt = AV_SAMPLE_FMT_FLTP; + } + + //audio_output_stream->time_base = audio_input_stream->time_base; + audio_output_context->time_base = (AVRational){ 1, audio_output_context->sample_rate }; + + Debug(3, "Audio Time bases input stream (%d/%d) input codec: (%d/%d) output_stream (%d/%d) output codec (%d/%d)", + audio_input_stream->time_base.num, + audio_input_stream->time_base.den, + audio_input_context->time_base.num, + audio_input_context->time_base.den, + audio_output_stream->time_base.num, + audio_output_stream->time_base.den, + audio_output_context->time_base.num, + audio_output_context->time_base.den + ); + + ret = avcodec_open2(audio_output_context, audio_output_codec, &opts ); + if ( ret < 0 ) { + av_strerror(ret, error_buffer, sizeof(error_buffer)); + Fatal( "could not open codec (%d) (%s)\n", ret, error_buffer ); + } else { + + Debug(1, "Audio output bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) layout(%d) frame_size(%d), refcounted_frames(%d)", + audio_output_context->bit_rate, + audio_output_context->sample_rate, + audio_output_context->channels, + audio_output_context->sample_fmt, + audio_output_context->channel_layout, + audio_output_context->frame_size, + audio_output_context->refcounted_frames + ); +#if 1 + /** Create the FIFO buffer based on the specified output sample format. */ + if (!(fifo = av_audio_fifo_alloc(audio_output_context->sample_fmt, + audio_output_context->channels, 1))) { + Error("Could not allocate FIFO\n"); + return; + } +#endif + output_frame_size = audio_output_context->frame_size; + /** Create a new frame to store the audio samples. */ + if (!(input_frame = zm_av_frame_alloc())) { + Error("Could not allocate input frame"); + return; + } + + /** Create a new frame to store the audio samples. */ + if (!(output_frame = zm_av_frame_alloc())) { + Error("Could not allocate output frame"); + av_frame_free(&input_frame); + return; + } + /** + * Create a resampler context for the conversion. + * Set the conversion parameters. + * Default channel layouts based on the number of channels + * are assumed for simplicity (they are sometimes not detected + * properly by the demuxer and/or decoder). + */ + resample_context = swr_alloc_set_opts(NULL, + av_get_default_channel_layout(audio_output_context->channels), + audio_output_context->sample_fmt, + audio_output_context->sample_rate, + av_get_default_channel_layout( audio_input_context->channels), + audio_input_context->sample_fmt, + audio_input_context->sample_rate, + 0, NULL); + if (!resample_context) { + Error( "Could not allocate resample context\n"); + return; + } + /** + * Perform a sanity check so that the number of converted samples is + * not greater than the number of samples to be converted. + * If the sample rates differ, this case has to be handled differently + */ + av_assert0(audio_output_context->sample_rate == audio_input_context->sample_rate); + /** Open the resampler with the specified parameters. */ + if ((ret = swr_init(resample_context)) < 0) { + Error( "Could not open resample context\n"); + swr_free(&resample_context); + return; + } + /** + * Allocate as many pointers as there are audio channels. + * Each pointer will later point to the audio samples of the corresponding + * channels (although it may be NULL for interleaved formats). + */ + if (!( converted_input_samples = (uint8_t *)calloc( audio_output_context->channels, sizeof(*converted_input_samples))) ) { + Error( "Could not allocate converted input sample pointers\n"); + return; + } + /** + * Allocate memory for the samples of all channels in one consecutive + * block for convenience. + */ + if ((ret = av_samples_alloc( &converted_input_samples, NULL, + audio_output_context->channels, + audio_output_context->frame_size, + audio_output_context->sample_fmt, 0)) < 0) { + Error( "Could not allocate converted input samples (error '%s')\n", + av_make_error_string(ret).c_str() ); + + av_freep(converted_input_samples); + free(converted_input_samples); + return; + } + Debug(2, "Success opening AAC codec"); + } + av_dict_free(&opts); + } else { + Error( "could not allocate codec context for AAC\n"); + } + } else { + Error( "could not find codec for AAC\n"); + } +#else + Error("Not built with libswresample library. Cannot do audio conversion to AAC"); + audio_output_stream = NULL; +#endif + } else { + Debug(3, "Got AAC" ); + + audio_output_stream = avformat_new_stream(oc, (AVCodec*)audio_input_context->codec); + if ( ! audio_output_stream ) { + Error("Unable to create audio out stream\n"); + audio_output_stream = NULL; + } + audio_output_context = audio_output_stream->codec; + + ret = avcodec_copy_context(audio_output_context, audio_input_context); + if (ret < 0) { + Fatal("Unable to copy audio context %s\n", av_make_error_string(ret).c_str()); + } + audio_output_context->codec_tag = 0; + if ( audio_output_context->channels > 1 ) { + Warning("Audio isn't mono, changing it."); + audio_output_context->channels = 1; + } else { + Debug(3, "Audio is mono"); + } + } // end if is AAC + +if ( audio_output_stream ) { + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { + audio_output_context->flags |= CODEC_FLAG_GLOBAL_HEADER; + } + } + + } else { + Debug(3, "No Audio output stream"); + audio_output_stream = NULL; + } + + /* open the output file, if needed */ + if (!(output_format->flags & AVFMT_NOFILE)) { + ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE,NULL,NULL); + if (ret < 0) { + Fatal("Could not open output file '%s': %s\n", filename, + av_make_error_string(ret).c_str()); + } + } + + //av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); + //if ((ret = avformat_write_header(ctx, &opts)) < 0) { + //} + //os->ctx_inited = 1; + //avio_flush(ctx->pb); + //av_dict_free(&opts); + zm_dump_stream_format( oc, 0, 0, 1 ); + if ( audio_output_stream ) + zm_dump_stream_format( oc, 1, 0, 1 ); + + /* Write the stream header, if any. */ + ret = avformat_write_header(oc, NULL); + if (ret < 0) { + Error("Error occurred when writing output file header to %s: %s\n", + filename, + av_make_error_string(ret).c_str()); + } + + video_last_pts = 0; + video_last_dts = 0; + audio_last_pts = 0; + audio_last_dts = 0; + previous_pts = 0; + previous_dts = 0; + + filter_in_rescale_delta_last = AV_NOPTS_VALUE; + + // now - when streaming started + //startTime=av_gettime()-nStartTime;//oc->start_time; + //Info("VideoStore startTime=%d\n",startTime); +} // VideoStore::VideoStore + + +VideoStore::~VideoStore(){ + if ( audio_output_codec ) { +Debug(1, "Have audio encoder, need to flush it's output" ); + // Do we need to flush the outputs? I have no idea. + AVPacket pkt; + int got_packet; + av_init_packet(&pkt); + pkt.data = NULL; + pkt.size = 0; + int64_t size; + + while(1) { + ret = avcodec_encode_audio2( audio_output_context, &pkt, NULL, &got_packet ); + if (ret < 0) { + Error("ERror encoding audio while flushing"); + break; + } +Debug(1, "Have audio encoder, need to flush it's output" ); + size += pkt.size; + if (!got_packet) { + break; + } +Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts, pkt.duration ); + if (pkt.pts != AV_NOPTS_VALUE) + pkt.pts = av_rescale_q(pkt.pts, audio_output_context->time_base, audio_output_stream->time_base); + if (pkt.dts != AV_NOPTS_VALUE) + pkt.dts = av_rescale_q(pkt.dts, audio_output_context->time_base, audio_output_stream->time_base); + if (pkt.duration > 0) + pkt.duration = av_rescale_q(pkt.duration, audio_output_context->time_base, audio_output_stream->time_base); +Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts, pkt.duration ); + pkt.stream_index = audio_output_stream->index; + av_interleaved_write_frame( oc, &pkt ); + zm_av_packet_unref( &pkt ); + } // while 1 + } + + // Flush Queues + av_interleaved_write_frame( oc, NULL ); + + /* Write the trailer before close */ + if ( int rc = av_write_trailer(oc) ) { + Error("Error writing trailer %s", av_err2str( rc ) ); + } else { + Debug(3, "Sucess Writing trailer"); + } + + // I wonder if we should be closing the file first. + // I also wonder if we really need to be doing all the context allocation/de-allocation constantly, or whether we can just re-use it. Just do a file open/close/writeheader/etc. + // What if we were only doing audio recording? + if ( video_output_stream ) { + avcodec_close(video_output_context); + } + if (audio_output_stream) { + avcodec_close(audio_output_context); + } + + // WHen will be not using a file ? + if (!(output_format->flags & AVFMT_NOFILE)) { + /* Close the output file. */ + if ( int rc = avio_close(oc->pb) ) { + Error("Error closing avio %s", av_err2str( rc ) ); + } + } else { + Debug(3, "Not closing avio because we are not writing to a file."); + } + + /* free the stream */ + avformat_free_context(oc); + +#ifdef HAVE_LIBSWRESAMPLE + if ( resample_context ) + swr_free( &resample_context ); +#endif +} + + +void VideoStore::dumpPacket( AVPacket *pkt ){ + char b[10240]; + + snprintf(b, sizeof(b), " pts: %" PRId64 ", dts: %" PRId64 ", data: %p, size: %d, sindex: %d, dflags: %04x, s-pos: %" PRId64 ", c-duration: %" PRId64 "\n" + , pkt->pts + , pkt->dts + , pkt->data + , pkt->size + , pkt->stream_index + , pkt->flags + , pkt->pos + , pkt->duration + ); + Debug(1, "%s:%d:DEBUG: %s", __FILE__, __LINE__, b); +} + +int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) { + av_init_packet(&opkt); + + //Scale the PTS of the outgoing packet to be the correct time base + if (ipkt->pts != AV_NOPTS_VALUE) { + + if ( ! video_last_pts ) { + // This is the first packet. + opkt.pts = 0; + Debug(2, "Starting video video_last_pts will become (%d)", ipkt->pts ); + } else { + if ( ipkt->pts < video_last_pts ) { + Debug(1, "Resetting video_last_pts from (%d) to (%d)", video_last_pts, ipkt->pts ); + // wrap around, need to figure out the distance FIXME having this wrong should cause a jump, but then play ok? + opkt.pts = previous_pts + av_rescale_q( ipkt->pts, video_input_stream->time_base, video_output_stream->time_base); + } else { + opkt.pts = previous_pts + av_rescale_q( ipkt->pts - video_last_pts, video_input_stream->time_base, video_output_stream->time_base); + } + } + Debug(3, "opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, video_last_pts ); + video_last_pts = ipkt->pts; + } else { + Debug(3, "opkt.pts = undef"); + opkt.pts = AV_NOPTS_VALUE; + } + + //Scale the DTS of the outgoing packet to be the correct time base + + // Just because the input stream wraps, doesn't mean the output needs to. Really, if we are limiting ourselves to 10min segments I can't imagine every wrapping in the output. So need to handle input wrap, without causing output wrap. + if ( ! video_last_dts ) { + // This is the first packet. + opkt.dts = 0; + Debug(1, "Starting video video_last_pts will become (%d)", ipkt->dts ); + } else { + if ( ipkt->dts == AV_NOPTS_VALUE ) { + // why are we using cur_dts instead of packet.dts? I think cur_dts is in AV_TIME_BASE_Q, but ipkt.dts is in video_input_stream->time_base + if ( video_input_stream->cur_dts < video_last_dts ) { + Debug(1, "Resetting video_last_dts from (%d) to (%d) p.dts was (%d)", video_last_dts, video_input_stream->cur_dts, ipkt->dts ); + opkt.dts = previous_dts + av_rescale_q(video_input_stream->cur_dts, AV_TIME_BASE_Q, video_output_stream->time_base); + } else { + opkt.dts = previous_dts + av_rescale_q(video_input_stream->cur_dts - video_last_dts, AV_TIME_BASE_Q, video_output_stream->time_base); + } + Debug(3, "opkt.dts = %d from video_input_stream->cur_dts(%d) - previus_dts(%d)", + opkt.dts, video_input_stream->cur_dts, video_last_dts + ); + video_last_dts = video_input_stream->cur_dts; + } else { + if ( ipkt->dts < video_last_dts ) { + Debug(1, "Resetting video_last_dts from (%d) to (%d)", video_last_dts, ipkt->dts ); + opkt.dts = previous_dts + av_rescale_q( ipkt->dts, video_input_stream->time_base, video_output_stream->time_base); + } else { + opkt.dts = previous_dts + av_rescale_q( ipkt->dts - video_last_dts, video_input_stream->time_base, video_output_stream->time_base); + } + Debug(3, "opkt.dts = %d from ipkt.dts(%d) - previus_dts(%d)", + opkt.dts, ipkt->dts, video_last_dts + ); + video_last_dts = ipkt->dts; + } + } + if ( opkt.dts > opkt.pts ) { + Debug( 1, "opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen before presentation.", opkt.dts, opkt.pts ); + opkt.dts = opkt.pts; + } + + opkt.duration = av_rescale_q(ipkt->duration, video_input_stream->time_base, video_output_stream->time_base); + opkt.flags = ipkt->flags; + opkt.pos=-1; + + opkt.data = ipkt->data; + opkt.size = ipkt->size; + + // Some camera have audio on stream 0 and video on stream 1. So when we remove the audio, video stream has to go on 0 + if ( ipkt->stream_index > 0 and ! audio_output_stream ) { + Debug(1,"Setting stream index to 0 instead of %d", ipkt->stream_index ); + opkt.stream_index = 0; + } else { + opkt.stream_index = ipkt->stream_index; + } + + /*opkt.flags |= AV_PKT_FLAG_KEY;*/ + +#if 0 + if (video_output_context->codec_type == AVMEDIA_TYPE_VIDEO && (output_format->flags & AVFMT_RAWPICTURE)) { + AVPicture pict; +Debug(3, "video and RAWPICTURE"); + /* store AVPicture in AVPacket, as expected by the output format */ + avpicture_fill(&pict, opkt.data, video_output_context->pix_fmt, video_output_context->width, video_output_context->height, 0); + av_image_fill_arrays( + opkt.data = (uint8_t *)&pict; + opkt.size = sizeof(AVPicture); + opkt.flags |= AV_PKT_FLAG_KEY; + } else { +Debug(4, "Not video and RAWPICTURE"); + } +#endif + + AVPacket safepkt; + memcpy(&safepkt, &opkt, sizeof(AVPacket)); + + if ((opkt.data == NULL)||(opkt.size < 1)) { + Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__ ); + dumpPacket( ipkt); + dumpPacket(&opkt); + + } else if ((previous_dts > 0) && (previous_dts > opkt.dts)) { + Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame", __FILE__, __LINE__, previous_dts, opkt.dts); + previous_dts = opkt.dts; + dumpPacket(&opkt); + + } else { + + previous_dts = opkt.dts; // Unsure if av_interleaved_write_frame() clobbers opkt.dts when out of order, so storing in advance + previous_pts = opkt.pts; + ret = av_interleaved_write_frame(oc, &opkt); + if(ret<0){ + // There's nothing we can really do if the frame is rejected, just drop it and get on with the next + Warning("%s:%d: Writing frame [av_interleaved_write_frame()] failed: %s(%d) ", __FILE__, __LINE__, av_make_error_string(ret).c_str(), (ret)); + dumpPacket(&safepkt); + } + } + + zm_av_packet_unref(&opkt); + + return 0; + +} + +int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { + Debug(4, "writeAudioFrame"); + + if(!audio_output_stream) { + Debug(1, "Called writeAudioFramePacket when no audio_output_stream"); + return 0;//FIXME -ve return codes do not free packet in ffmpeg_camera at the moment + } + /*if(!keyframeMessage) + return -1;*/ + //zm_dump_stream_format( oc, ipkt->stream_index, 0, 1 ); + + av_init_packet(&opkt); + Debug(5, "after init packet" ); + +#if 1 + //Scale the PTS of the outgoing packet to be the correct time base + if ( ipkt->pts != AV_NOPTS_VALUE ) { + if ( !audio_last_pts ) { + opkt.pts = 0; + } else { + if ( audio_last_pts > ipkt->pts ) { + Debug(1, "Resetting audeo_start_pts from (%d) to (%d)", audio_last_pts, ipkt->pts ); + } + opkt.pts = previous_pts + av_rescale_q(ipkt->pts - audio_last_pts, audio_input_stream->time_base, audio_output_stream->time_base); + Debug(2, "opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, audio_last_pts ); + } + audio_last_pts = ipkt->pts; + } else { + Debug(2, "opkt.pts = undef"); + opkt.pts = AV_NOPTS_VALUE; + } + + //Scale the DTS of the outgoing packet to be the correct time base + if ( ! audio_last_dts ) { + opkt.dts = 0; + } else { + if( ipkt->dts == AV_NOPTS_VALUE ) { + // So if the input has no dts assigned... still need an output dts... so we use cur_dts? + + if ( audio_last_dts > audio_input_stream->cur_dts ) { + Debug(1, "Resetting audio_last_pts from (%d) to cur_dts (%d)", audio_last_dts, audio_input_stream->cur_dts ); + opkt.dts = previous_dts + av_rescale_q( audio_input_stream->cur_dts, AV_TIME_BASE_Q, audio_output_stream->time_base); + } else { + opkt.dts = previous_dts + av_rescale_q( audio_input_stream->cur_dts - audio_last_dts, AV_TIME_BASE_Q, audio_output_stream->time_base); + } + audio_last_dts = audio_input_stream->cur_dts; + Debug(2, "opkt.dts = %d from video_input_stream->cur_dts(%d) - last_dts(%d)", opkt.dts, audio_input_stream->cur_dts, audio_last_dts ); + } else { + if ( audio_last_dts > ipkt->dts ) { + Debug(1, "Resetting audio_last_dts from (%d) to (%d)", audio_last_dts, ipkt->dts ); + opkt.dts = previous_dts + av_rescale_q(ipkt->dts, audio_input_stream->time_base, audio_output_stream->time_base); + } else { + opkt.dts = previous_dts + av_rescale_q(ipkt->dts - audio_last_dts, audio_input_stream->time_base, audio_output_stream->time_base); + } + Debug(2, "opkt.dts = %d from ipkt->dts(%d) - last_dts(%d)", opkt.dts, ipkt->dts, audio_last_dts ); + } + } + if ( opkt.dts > opkt.pts ) { + Debug(1,"opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen before presentation.", opkt.dts, opkt.pts ); + opkt.dts = opkt.pts; + } + //opkt.pts = AV_NOPTS_VALUE; + //opkt.dts = AV_NOPTS_VALUE; + + // I wonder if we could just use duration instead of all the hoop jumping above? + opkt.duration = av_rescale_q(ipkt->duration, audio_input_stream->time_base, audio_output_stream->time_base); +#else +#endif + + // pkt.pos: byte position in stream, -1 if unknown + opkt.pos = -1; + opkt.flags = ipkt->flags; + opkt.stream_index = ipkt->stream_index; +Debug(2, "Stream index is %d", opkt.stream_index ); + + if ( audio_output_codec ) { + +#ifdef HAVE_LIBSWRESAMPLE + // Need to re-encode +#if 0 + ret = avcodec_send_packet( audio_input_context, ipkt ); + if ( ret < 0 ) { + Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str()); + return 0; + } + + ret = avcodec_receive_frame( audio_input_context, input_frame ); + if ( ret < 0 ) { + Error("avcodec_receive_frame fail %s", av_make_error_string(ret).c_str()); + return 0; + } +Debug(2, "Frame: samples(%d), format(%d), sample_rate(%d), channel layout(%d) refd(%d)", +input_frame->nb_samples, +input_frame->format, +input_frame->sample_rate, +input_frame->channel_layout, +audio_output_context->refcounted_frames +); + + ret = avcodec_send_frame( audio_output_context, input_frame ); + if ( ret < 0 ) { + av_frame_unref( input_frame ); + Error("avcodec_send_frame fail(%d), %s codec is open(%d) is_encoder(%d)", ret, av_make_error_string(ret).c_str(), +avcodec_is_open( audio_output_context ), +av_codec_is_encoder( audio_output_context->codec) +); + return 0; + } + ret = avcodec_receive_packet( audio_output_context, &opkt ); + if ( ret < 0 ) { + av_frame_unref( input_frame ); + Error("avcodec_receive_packet fail %s", av_make_error_string(ret).c_str()); + return 0; + } + av_frame_unref( input_frame ); +#else + + + /** + * Decode the audio frame stored in the packet. + * The input audio stream decoder is used to do this. + * If we are at the end of the file, pass an empty packet to the decoder + * to flush it. + */ + if ((ret = avcodec_decode_audio4(audio_input_context, input_frame, + &data_present, ipkt)) < 0) { + Error( "Could not decode frame (error '%s')\n", + av_make_error_string(ret).c_str()); + dumpPacket( ipkt ); + av_frame_free(&input_frame); + zm_av_packet_unref(&opkt); + return 0; + } + if ( ! data_present ) { + Debug(2, "Not ready to transcode a frame yet."); + zm_av_packet_unref(&opkt); + return 0; + } + + int frame_size = input_frame->nb_samples; + Debug(4, "Frame size: %d", frame_size ); + + + Debug(4, "About to convert"); + + /** Convert the samples using the resampler. */ + if ((ret = swr_convert(resample_context, + &converted_input_samples, frame_size, + (const uint8_t **)input_frame->extended_data , frame_size)) < 0) { + Error( "Could not convert input samples (error '%s')\n", + av_make_error_string(ret).c_str() + ); + return 0; + } + + Debug(4, "About to realloc"); + if ((ret = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + frame_size)) < 0) { + Error( "Could not reallocate FIFO to %d\n", av_audio_fifo_size(fifo) + frame_size ); + return 0; + } + /** Store the new samples in the FIFO buffer. */ + Debug(4, "About to write"); + if (av_audio_fifo_write(fifo, (void **)&converted_input_samples, frame_size) < frame_size) { + Error( "Could not write data to FIFO\n"); + return 0; + } + + /** + * Set the frame's parameters, especially its size and format. + * av_frame_get_buffer needs this to allocate memory for the + * audio samples of the frame. + * Default channel layouts based on the number of channels + * are assumed for simplicity. + */ + output_frame->nb_samples = audio_output_context->frame_size; + output_frame->channel_layout = audio_output_context->channel_layout; + output_frame->channels = audio_output_context->channels; + output_frame->format = audio_output_context->sample_fmt; + output_frame->sample_rate = audio_output_context->sample_rate; + /** + * Allocate the samples of the created frame. This call will make + * sure that the audio frame can hold as many samples as specified. + */ + Debug(4, "getting buffer"); + if (( ret = av_frame_get_buffer( output_frame, 0)) < 0) { + Error( "Couldnt allocate output frame buffer samples (error '%s')", + av_make_error_string(ret).c_str() ); + Error("Frame: samples(%d) layout (%d) format(%d) rate(%d)", output_frame->nb_samples, + output_frame->channel_layout, output_frame->format , output_frame->sample_rate + ); + zm_av_packet_unref(&opkt); + return 0; + } + + Debug(4, "About to read"); + if (av_audio_fifo_read(fifo, (void **)output_frame->data, frame_size) < frame_size) { + Error( "Could not read data from FIFO\n"); + return 0; + } + + /** Set a timestamp based on the sample rate for the container. */ + output_frame->pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base ); + + // convert the packet to the codec timebase from the stream timebase +Debug(3, "output_frame->pts(%d) best effort(%d)", output_frame->pts, +av_frame_get_best_effort_timestamp(output_frame) + ); + /** + * Encode the audio frame and store it in the temporary packet. + * The output audio stream encoder is used to do this. + */ + if (( ret = avcodec_encode_audio2( audio_output_context, &opkt, + output_frame, &data_present )) < 0) { + Error( "Could not encode frame (error '%s')", + av_make_error_string(ret).c_str()); + zm_av_packet_unref(&opkt); + return 0; + } + if ( ! data_present ) { + Debug(2, "Not ready to output a frame yet."); + zm_av_packet_unref(&opkt); + return 0; + } + + +Debug(2, "opkt dts (%d) pts(%d) duration:(%d)", opkt.dts, opkt.pts, opkt.duration ); + + // Convert tb from code back to stream + //av_packet_rescale_ts(&opkt, audio_output_context->time_base, audio_output_stream->time_base); +if (opkt.pts != AV_NOPTS_VALUE) { + opkt.pts = av_rescale_q( opkt.pts, audio_output_context->time_base, audio_output_stream->time_base); +} + if ( opkt.dts != AV_NOPTS_VALUE) + opkt.dts = av_rescale_q( opkt.dts, audio_output_context->time_base, audio_output_stream->time_base); + if ( opkt.duration > 0) + opkt.duration = av_rescale_q( opkt.duration, audio_output_context->time_base, audio_output_stream->time_base); +Debug(2, "opkt dts (%d) pts(%d) duration:(%d) pos(%d) ", opkt.dts, opkt.pts, opkt.duration, opkt.pos ); + + +//opkt.dts = AV_NOPTS_VALUE; + + +#endif +#endif + } else { + opkt.data = ipkt->data; + opkt.size = ipkt->size; + } + + AVPacket safepkt; + memcpy(&safepkt, &opkt, sizeof(AVPacket)); + ret = av_interleaved_write_frame(oc, &opkt); + if(ret!=0){ + Error("Error writing audio frame packet: %s\n", av_make_error_string(ret).c_str()); + dumpPacket(&safepkt); + } else { + Debug(2,"Success writing audio frame" ); + } + zm_av_packet_unref(&opkt); + return 0; +} diff --git a/src/zm_videostore.h b/src/zm_videostore.h new file mode 100644 index 000000000..b9c68bd4e --- /dev/null +++ b/src/zm_videostore.h @@ -0,0 +1,80 @@ +#ifndef ZM_VIDEOSTORE_H +#define ZM_VIDEOSTORE_H + +#include "zm_ffmpeg.h" +extern "C" { +#include "libavutil/audio_fifo.h" + +#ifdef HAVE_LIBSWRESAMPLE +#include "libswresample/swresample.h" +#endif +} + +#if HAVE_LIBAVCODEC + +#include "zm_monitor.h" + +class VideoStore { +private: + unsigned int packets_written; + + AVOutputFormat *output_format; + AVFormatContext *oc; + AVStream *video_output_stream; + AVStream *audio_output_stream; + AVCodecContext *video_output_context; + + AVStream *video_input_stream; + AVStream *audio_input_stream; + + // Move this into the object so that we aren't constantly allocating/deallocating it on the stack + AVPacket opkt; + // we are transcoding + AVFrame *input_frame; + AVFrame *output_frame; + + AVCodecContext *video_input_context; + AVCodecContext *audio_input_context; + int ret; + + // The following are used when encoding the audio stream to AAC + AVCodec *audio_output_codec; + AVCodecContext *audio_output_context; + int data_present; + AVAudioFifo *fifo; + int output_frame_size; +#ifdef HAVE_LIBSWRESAMPLE + SwrContext *resample_context = NULL; +#endif + uint8_t *converted_input_samples = NULL; + + const char *filename; + const char *format; + + bool keyframeMessage; + int keyframeSkipNumber; + + // These are for input + int64_t video_last_pts; + int64_t video_last_dts; + int64_t audio_last_pts; + int64_t audio_last_dts; + + // These are for output, should start at zero. We assume they do not wrap because we just aren't going to save files that big. + int64_t previous_pts; + int64_t previous_dts; + + int64_t filter_in_rescale_delta_last; + +public: + VideoStore(const char *filename_in, const char *format_in, AVStream *video_input_stream, AVStream *audio_input_stream, int64_t nStartTime, Monitor * p_monitor ); + ~VideoStore(); + + int writeVideoFramePacket( AVPacket *pkt ); + int writeAudioFramePacket( AVPacket *pkt ); + void dumpPacket( AVPacket *pkt ); +}; + +#endif //havelibav +#endif //zm_videostore_h + diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index 90ba855dc..243e4a50f 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -25,170 +25,173 @@ void Zone::Setup( Monitor *p_monitor, int p_id, const char *p_label, ZoneType p_type, const Polygon &p_polygon, const Rgb p_alarm_rgb, CheckMethod p_check_method, int p_min_pixel_threshold, int p_max_pixel_threshold, int p_min_alarm_pixels, int p_max_alarm_pixels, const Coord &p_filter_box, int p_min_filter_pixels, int p_max_filter_pixels, int p_min_blob_pixels, int p_max_blob_pixels, int p_min_blobs, int p_max_blobs, int p_overload_frames, int p_extend_alarm_frames ) { - monitor = p_monitor; + monitor = p_monitor; - id = p_id; - label = new char[strlen(p_label)+1]; - strcpy( label, p_label ); - type = p_type; - polygon = p_polygon; - alarm_rgb = p_alarm_rgb; - check_method = p_check_method; - min_pixel_threshold = p_min_pixel_threshold; - max_pixel_threshold = p_max_pixel_threshold; - min_alarm_pixels = p_min_alarm_pixels; - max_alarm_pixels = p_max_alarm_pixels; - filter_box = p_filter_box; - min_filter_pixels = p_min_filter_pixels; - max_filter_pixels = p_max_filter_pixels; - min_blob_pixels = p_min_blob_pixels; - max_blob_pixels = p_max_blob_pixels; - min_blobs = p_min_blobs; - max_blobs = p_max_blobs; - overload_frames = p_overload_frames; - extend_alarm_frames = p_extend_alarm_frames; + id = p_id; + label = new char[strlen(p_label)+1]; + strcpy( label, p_label ); + type = p_type; + polygon = p_polygon; + alarm_rgb = p_alarm_rgb; + check_method = p_check_method; + min_pixel_threshold = p_min_pixel_threshold; + max_pixel_threshold = p_max_pixel_threshold; + min_alarm_pixels = p_min_alarm_pixels; + max_alarm_pixels = p_max_alarm_pixels; + filter_box = p_filter_box; + min_filter_pixels = p_min_filter_pixels; + max_filter_pixels = p_max_filter_pixels; + min_blob_pixels = p_min_blob_pixels; + max_blob_pixels = p_max_blob_pixels; + min_blobs = p_min_blobs; + max_blobs = p_max_blobs; + overload_frames = p_overload_frames; + extend_alarm_frames = p_extend_alarm_frames; - Debug( 1, "Initialised zone %d/%s - %d - %dx%d - Rgb:%06x, CM:%d, MnAT:%d, MxAT:%d, MnAP:%d, MxAP:%d, FB:%dx%d, MnFP:%d, MxFP:%d, MnBS:%d, MxBS:%d, MnB:%d, MxB:%d, OF: %d, AF: %d", id, label, type, polygon.Width(), polygon.Height(), alarm_rgb, check_method, min_pixel_threshold, max_pixel_threshold, min_alarm_pixels, max_alarm_pixels, filter_box.X(), filter_box.Y(), min_filter_pixels, max_filter_pixels, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs, overload_frames, extend_alarm_frames ); + //Debug( 1, "Initialised zone %d/%s - %d - %dx%d - Rgb:%06x, CM:%d, MnAT:%d, MxAT:%d, MnAP:%d, MxAP:%d, FB:%dx%d, MnFP:%d, MxFP:%d, MnBS:%d, MxBS:%d, MnB:%d, MxB:%d, OF: %d, AF: %d", id, label, type, polygon.Width(), polygon.Height(), alarm_rgb, check_method, min_pixel_threshold, max_pixel_threshold, min_alarm_pixels, max_alarm_pixels, filter_box.X(), filter_box.Y(), min_filter_pixels, max_filter_pixels, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs, overload_frames, extend_alarm_frames ); + //Debug( 1, "Initialised zone %d/%s - %d - %dx%d - Rgb:%06x, CM:%d, MnAT:%d, MxAT:%d, MnAP:%d, MxAP:%d, FB:%dx%d, MnFP:%d, MxFP:%d, MnBS:%d, MxBS:%d, MnB:%d, MxB:%d, OF: %d", id, label, type, polygon.Width(), polygon.Height(), alarm_rgb, check_method, min_pixel_threshold, max_pixel_threshold, min_alarm_pixels, max_alarm_pixels, filter_box.X(), filter_box.Y(), min_filter_pixels, max_filter_pixels, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs, overload_frames ); + //Debug( 1, "Initialised zone %d/%s - %d - %dx%d - Rgb:%06x, CM:%d", id, label, type, polygon.Width(), polygon.Height(), alarm_rgb, check_method ); + Debug( 1, "Initialised zone %d/%s - %d - %dx%d", id, label, type, polygon.Width(), polygon.Height() ); - alarmed = false; - pixel_diff = 0; - alarm_pixels = 0; - alarm_filter_pixels = 0; - alarm_blob_pixels = 0; - alarm_blobs = 0; - min_blob_size = 0; - max_blob_size = 0; - image = 0; - score = 0; + alarmed = false; + pixel_diff = 0; + alarm_pixels = 0; + alarm_filter_pixels = 0; + alarm_blob_pixels = 0; + alarm_blobs = 0; + min_blob_size = 0; + max_blob_size = 0; + image = 0; + score = 0; - overload_count = 0; - extend_alarm_count = 0; + overload_count = 0; + extend_alarm_count = 0; - pg_image = new Image( monitor->Width(), monitor->Height(), 1, ZM_SUBPIX_ORDER_NONE); - pg_image->Clear(); - pg_image->Fill( 0xff, polygon ); - pg_image->Outline( 0xff, polygon ); + pg_image = new Image( monitor->Width(), monitor->Height(), 1, ZM_SUBPIX_ORDER_NONE); + pg_image->Clear(); + pg_image->Fill( 0xff, polygon ); + pg_image->Outline( 0xff, polygon ); - ranges = new Range[monitor->Height()]; - for ( unsigned int y = 0; y < monitor->Height(); y++) - { - ranges[y].lo_x = -1; - ranges[y].hi_x = 0; - ranges[y].off_x = 0; - const uint8_t *ppoly = pg_image->Buffer( 0, y ); - for ( unsigned int x = 0; x < monitor->Width(); x++, ppoly++ ) - { - if ( *ppoly ) - { - if ( ranges[y].lo_x == -1 ) - { - ranges[y].lo_x = x; - } - if ( (unsigned int)ranges[y].hi_x < x ) - { - ranges[y].hi_x = x; - } - } - } - } - - if ( config.record_diag_images ) - { - static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { - snprintf( diag_path, sizeof(diag_path), "%s/%s/diag-%d-poly.jpg", config.dir_events, monitor->Name(), id); - } - pg_image->WriteJpeg( diag_path ); - } + ranges = new Range[monitor->Height()]; + for ( unsigned int y = 0; y < monitor->Height(); y++) + { + ranges[y].lo_x = -1; + ranges[y].hi_x = 0; + ranges[y].off_x = 0; + const uint8_t *ppoly = pg_image->Buffer( 0, y ); + for ( unsigned int x = 0; x < monitor->Width(); x++, ppoly++ ) + { + if ( *ppoly ) + { + if ( ranges[y].lo_x == -1 ) + { + ranges[y].lo_x = x; + } + if ( (unsigned int)ranges[y].hi_x < x ) + { + ranges[y].hi_x = x; + } + } + } + } + + if ( config.record_diag_images ) + { + static char diag_path[PATH_MAX] = ""; + if ( !diag_path[0] ) + { + snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-poly.jpg", monitor->getStorage()->Path(), monitor->Id(), id); + } + pg_image->WriteJpeg( diag_path ); + } } Zone::~Zone() { - delete[] label; - delete image; - delete pg_image; - delete[] ranges; + delete[] label; + delete image; + delete pg_image; + delete[] ranges; } void Zone::RecordStats( const Event *event ) { static char sql[ZM_SQL_MED_BUFSIZ]; - snprintf( sql, sizeof(sql), "insert into Stats set MonitorId=%d, ZoneId=%d, EventId=%d, FrameId=%d, PixelDiff=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, MinX=%d, MinY=%d, MaxX=%d, MaxY=%d, Score=%d", monitor->Id(), id, event->Id(), event->Frames()+1, pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, min_blob_size, max_blob_size, alarm_box.LoX(), alarm_box.LoY(), alarm_box.HiX(), alarm_box.HiY(), score ); - if ( mysql_query( &dbconn, sql ) ) - { - Error( "Can't insert event stats: %s", mysql_error( &dbconn ) ); - exit( mysql_errno( &dbconn ) ); - } + snprintf( sql, sizeof(sql), "insert into Stats set MonitorId=%d, ZoneId=%d, EventId=%d, FrameId=%d, PixelDiff=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, MinX=%d, MinY=%d, MaxX=%d, MaxY=%d, Score=%d", monitor->Id(), id, event->Id(), event->Frames()+1, pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, min_blob_size, max_blob_size, alarm_box.LoX(), alarm_box.LoY(), alarm_box.HiX(), alarm_box.HiY(), score ); + if ( mysql_query( &dbconn, sql ) ) + { + Error( "Can't insert event stats: %s", mysql_error( &dbconn ) ); + exit( mysql_errno( &dbconn ) ); + } } //============================================================================= bool Zone::CheckOverloadCount() { - Info("Overloaded count: %d, Overloaded frames: %d", overload_count, overload_frames); - if ( overload_count ) - { - Info( "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); - Debug( 4, "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); - overload_count--; - return( false ); - } - return true; + Info("Overloaded count: %d, Overloaded frames: %d", overload_count, overload_frames); + if ( overload_count ) + { + Info( "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); + Debug( 4, "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); + overload_count--; + return( false ); + } + return true; } void Zone::SetScore(unsigned int nScore) { - score = nScore; + score = nScore; } void Zone::SetAlarmImage(const Image* srcImage) { - delete image; - image = new Image(*srcImage); + delete image; + image = new Image(*srcImage); } int Zone::GetOverloadCount() { - return overload_count; + return overload_count; } void Zone::SetOverloadCount(int nOverCount) { - overload_count = nOverCount; + overload_count = nOverCount; } int Zone::GetOverloadFrames() { - return overload_frames; + return overload_frames; } int Zone::GetExtendAlarmCount() { - return extend_alarm_count; + return extend_alarm_count; } void Zone::SetExtendAlarmCount(int nExtendAlarmCount) { - extend_alarm_count = nExtendAlarmCount; + extend_alarm_count = nExtendAlarmCount; } int Zone::GetExtendAlarmFrames() { - return extend_alarm_frames; + return extend_alarm_frames; } bool Zone::CheckExtendAlarmCount() { - Info("ExtendAlarm count: %d, ExtendAlarm frames: %d", extend_alarm_count, extend_alarm_frames); - if ( extend_alarm_count ) - { - Debug( 3, "In extend mode, %d frames of %d remaining", extend_alarm_count, extend_alarm_frames ); - extend_alarm_count--; - return( true ); - } - return false; + Info("ExtendAlarm count: %d, ExtendAlarm frames: %d", extend_alarm_count, extend_alarm_frames); + if ( extend_alarm_count ) + { + Debug( 3, "In extend mode, %d frames of %d remaining", extend_alarm_count, extend_alarm_frames ); + extend_alarm_count--; + return( true ); + } + return false; } @@ -198,920 +201,915 @@ bool Zone::CheckExtendAlarmCount() bool Zone::CheckAlarms( const Image *delta_image ) { - ResetStats(); + ResetStats(); - if ( overload_count ) - { - Info( "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); - Debug( 4, "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); - overload_count--; - return( false ); - } + if ( overload_count ) + { + Info( "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); + Debug( 4, "In overload mode, %d frames of %d remaining", overload_count, overload_frames ); + overload_count--; + return( false ); + } - delete image; - // Get the difference image - Image *diff_image = image = new Image( *delta_image ); - int diff_width = diff_image->Width(); - uint8_t* diff_buff = (uint8_t*)diff_image->Buffer(); - uint8_t* pdiff; - const uint8_t* ppoly; + delete image; + // Get the difference image + Image *diff_image = image = new Image( *delta_image ); + int diff_width = diff_image->Width(); + uint8_t* diff_buff = (uint8_t*)diff_image->Buffer(); + uint8_t* pdiff; + const uint8_t* ppoly; - unsigned int pixel_diff_count = 0; + unsigned int pixel_diff_count = 0; - int alarm_lo_x = 0; - int alarm_hi_x = 0; - int alarm_lo_y = 0; - int alarm_hi_y = 0; + int alarm_lo_x = 0; + int alarm_hi_x = 0; + int alarm_lo_y = 0; + int alarm_hi_y = 0; - int alarm_mid_x = -1; - int alarm_mid_y = -1; - - unsigned int lo_y = polygon.LoY(); - unsigned int lo_x = polygon.LoX(); - unsigned int hi_x = polygon.HiX(); - unsigned int hi_y = polygon.HiY(); + int alarm_mid_x = -1; + int alarm_mid_y = -1; + + unsigned int lo_y = polygon.LoY(); + unsigned int lo_x = polygon.LoX(); + unsigned int hi_x = polygon.HiX(); + unsigned int hi_y = polygon.HiY(); - Debug( 4, "Checking alarms for zone %d/%s in lines %d -> %d", id, label, lo_y, hi_y ); - - - Debug( 5, "Checking for alarmed pixels" ); - /* if(config.cpu_extensions && sseversion >= 20) { - sse2_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); - } else { - std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); - } */ - std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); - - if ( config.record_diag_images ) - { - static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { - snprintf( diag_path, sizeof(diag_path), "%s/%s/diag-%d-%d.jpg", config.dir_events, monitor->Name(), id, 1 ); - } - diff_image->WriteJpeg( diag_path ); - } - - if ( pixel_diff_count && alarm_pixels ) - pixel_diff = pixel_diff_count/alarm_pixels; - Debug( 5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d", alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff ); + Debug( 4, "Checking alarms for zone %d/%s in lines %d -> %d", id, label, lo_y, hi_y ); - if( alarm_pixels ) { - if( min_alarm_pixels && (alarm_pixels < (unsigned int)min_alarm_pixels) ) { - /* Not enough pixels alarmed */ - return (false); - } else if( max_alarm_pixels && (alarm_pixels > (unsigned int)max_alarm_pixels) ) { - /* Too many pixels alarmed */ - overload_count = overload_frames; - return (false); - } - } else { - /* No alarmed pixels */ - return (false); - } - - score = (100*alarm_pixels)/polygon.Area(); - if(score < 1) - score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ - Debug( 5, "Current score is %d", score ); - - if ( check_method >= FILTERED_PIXELS ) - { - int bx = filter_box.X(); - int by = filter_box.Y(); - int bx1 = bx-1; - int by1 = by-1; + Storage *storage = monitor->getStorage(); + + Debug( 5, "Checking for alarmed pixels" ); + /* if(config.cpu_extensions && sseversion >= 20) { + sse2_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); + } else { + std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); + } */ + std_alarmedpixels(diff_image, pg_image, &alarm_pixels, &pixel_diff_count); + + if ( config.record_diag_images ) + { + static char diag_path[PATH_MAX] = ""; + if ( !diag_path[0] ) + { + snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", storage->Path(), monitor->Id(), id, 1 ); + } + diff_image->WriteJpeg( diag_path ); + } + + if ( pixel_diff_count && alarm_pixels ) + pixel_diff = pixel_diff_count/alarm_pixels; + Debug( 5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d", alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff ); - Debug( 5, "Checking for filtered pixels" ); - if ( bx > 1 || by > 1 ) - { - // Now remove any pixels smaller than our filter size - unsigned char *cpdiff; - int ldx, hdx, ldy, hdy; - bool block; - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { - int lo_x = ranges[y].lo_x; - int hi_x = ranges[y].hi_x; + if( alarm_pixels ) { + if( min_alarm_pixels && (alarm_pixels < (unsigned int)min_alarm_pixels) ) { + /* Not enough pixels alarmed */ + return (false); + } else if( max_alarm_pixels && (alarm_pixels > (unsigned int)max_alarm_pixels) ) { + /* Too many pixels alarmed */ + overload_count = overload_frames; + return (false); + } + } else { + /* No alarmed pixels */ + return (false); + } + + score = (100*alarm_pixels)/polygon.Area(); + if(score < 1) + score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ + Debug( 5, "Current score is %d", score ); + + if ( check_method >= FILTERED_PIXELS ) + { + int bx = filter_box.X(); + int by = filter_box.Y(); + int bx1 = bx-1; + int by1 = by-1; - pdiff = (uint8_t*)diff_image->Buffer( lo_x, y ); + Debug( 5, "Checking for filtered pixels" ); + if ( bx > 1 || by > 1 ) + { + // Now remove any pixels smaller than our filter size + unsigned char *cpdiff; + int ldx, hdx, ldy, hdy; + bool block; + for ( unsigned int y = lo_y; y <= hi_y; y++ ) + { + int lo_x = ranges[y].lo_x; + int hi_x = ranges[y].hi_x; - for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) - { - if ( *pdiff == WHITE ) - { - // Check participation in an X block - ldx = (x>=(lo_x+bx1))?-bx1:lo_x-x; - hdx = (x<=(hi_x-bx1))?0:((hi_x-x)-bx1); - ldy = (y>=(lo_y+by1))?-by1:lo_y-y; - hdy = (y<=(hi_y-by1))?0:((hi_y-y)-by1); - block = false; - for ( int dy = ldy; !block && dy <= hdy; dy++ ) - { - for ( int dx = ldx; !block && dx <= hdx; dx++ ) - { - block = true; - for ( int dy2 = 0; block && dy2 < by; dy2++ ) - { - for ( int dx2 = 0; block && dx2 < bx; dx2++ ) - { - cpdiff = diff_buff + (((y+dy+dy2)*diff_width) + (x+dx+dx2)); - if ( !*cpdiff ) - { - block = false; - } - } - } - } - } - if ( !block ) - { - *pdiff = BLACK; - continue; - } - alarm_filter_pixels++; - } - } - } - } - else - { - alarm_filter_pixels = alarm_pixels; - } - - if ( config.record_diag_images ) - { - static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { - snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", config.dir_events, monitor->Id(), id, 2 ); - } - diff_image->WriteJpeg( diag_path ); - } - - Debug( 5, "Got %d filtered pixels, need %d -> %d", alarm_filter_pixels, min_filter_pixels, max_filter_pixels ); - - if( alarm_filter_pixels ) { - if( min_filter_pixels && (alarm_filter_pixels < min_filter_pixels) ) { - /* Not enough pixels alarmed */ - return (false); - } else if( max_filter_pixels && (alarm_filter_pixels > max_filter_pixels) ) { - /* Too many pixels alarmed */ - overload_count = overload_frames; - return (false); - } - } else { - /* No filtered pixels */ - return (false); - } - - score = (100*alarm_filter_pixels)/(polygon.Area()); - if(score < 1) - score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ - Debug( 5, "Current score is %d", score ); + pdiff = (uint8_t*)diff_image->Buffer( lo_x, y ); - if ( check_method >= BLOBS ) - { - Debug( 5, "Checking for blob pixels" ); - typedef struct { unsigned char tag; int count; int lo_x; int hi_x; int lo_y; int hi_y; } BlobStats; - BlobStats blob_stats[256]; - memset( blob_stats, 0, sizeof(BlobStats)*256 ); - uint8_t *spdiff; - uint8_t last_x, last_y; - BlobStats *bsx, *bsy; - BlobStats *bsm, *bss; - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { - int lo_x = ranges[y].lo_x; - int hi_x = ranges[y].hi_x; + for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) + { + if ( *pdiff == WHITE ) + { + // Check participation in an X block + ldx = (x>=(lo_x+bx1))?-bx1:lo_x-x; + hdx = (x<=(hi_x-bx1))?0:((hi_x-x)-bx1); + ldy = (y>=(lo_y+by1))?-by1:lo_y-y; + hdy = (y<=(hi_y-by1))?0:((hi_y-y)-by1); + block = false; + for ( int dy = ldy; !block && dy <= hdy; dy++ ) + { + for ( int dx = ldx; !block && dx <= hdx; dx++ ) + { + block = true; + for ( int dy2 = 0; block && dy2 < by; dy2++ ) + { + for ( int dx2 = 0; block && dx2 < bx; dx2++ ) + { + cpdiff = diff_buff + (((y+dy+dy2)*diff_width) + (x+dx+dx2)); + if ( !*cpdiff ) + { + block = false; + } + } + } + } + } + if ( !block ) + { + *pdiff = BLACK; + continue; + } + alarm_filter_pixels++; + } + } + } + } + else + { + alarm_filter_pixels = alarm_pixels; + } + + if ( config.record_diag_images ) + { + static char diag_path[PATH_MAX] = ""; + if ( !diag_path[0] ) + { + snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", storage->Path(), monitor->Id(), id, 2 ); + } + diff_image->WriteJpeg( diag_path ); + } + + Debug( 5, "Got %d filtered pixels, need %d -> %d", alarm_filter_pixels, min_filter_pixels, max_filter_pixels ); + + if( alarm_filter_pixels ) { + if( min_filter_pixels && (alarm_filter_pixels < min_filter_pixels) ) { + /* Not enough pixels alarmed */ + return (false); + } else if( max_filter_pixels && (alarm_filter_pixels > max_filter_pixels) ) { + /* Too many pixels alarmed */ + overload_count = overload_frames; + return (false); + } + } else { + /* No filtered pixels */ + return (false); + } + + score = (100*alarm_filter_pixels)/(polygon.Area()); + if(score < 1) + score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ + Debug( 5, "Current score is %d", score ); - pdiff = (uint8_t*)diff_image->Buffer( lo_x, y ); - for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) - { - if ( *pdiff == WHITE ) - { - Debug( 9, "Got white pixel at %d,%d (%p)", x, y, pdiff ); - //last_x = (x>lo_x)?*(pdiff-1):0; - //last_y = (y>lo_y&&x>=last_lo_x&&x<=last_hi_x)?*(pdiff-diff_width):0; + if ( check_method >= BLOBS ) + { + Debug( 5, "Checking for blob pixels" ); + typedef struct { unsigned char tag; int count; int lo_x; int hi_x; int lo_y; int hi_y; } BlobStats; + BlobStats blob_stats[256]; + memset( blob_stats, 0, sizeof(BlobStats)*256 ); + uint8_t *spdiff; + uint8_t last_x, last_y; + BlobStats *bsx, *bsy; + BlobStats *bsm, *bss; + for ( unsigned int y = lo_y; y <= hi_y; y++ ) + { + int lo_x = ranges[y].lo_x; + int hi_x = ranges[y].hi_x; + + pdiff = (uint8_t*)diff_image->Buffer( lo_x, y ); + for ( int x = lo_x; x <= hi_x; x++, pdiff++ ) + { + if ( *pdiff == WHITE ) + { + Debug( 9, "Got white pixel at %d,%d (%p)", x, y, pdiff ); + //last_x = (x>lo_x)?*(pdiff-1):0; + //last_y = (y>lo_y&&x>=last_lo_x&&x<=last_hi_x)?*(pdiff-diff_width):0; + + last_x = 0; + if(x > 0) { + if((x-1) >= lo_x) { + last_x = *(pdiff-1); + } + } + + last_y = 0; + if(y > 0) { + if((y-1) >= lo_y && ranges[(y-1)].lo_x <= x && ranges[(y-1)].hi_x >= x) { + last_y = *(pdiff-diff_width); + } + } + + if ( last_x ) + { + Debug( 9, "Left neighbour is %d", last_x ); + bsx = &blob_stats[last_x]; + if ( last_y ) + { + Debug( 9, "Top neighbour is %d", last_y ); + bsy = &blob_stats[last_y]; + if ( last_x == last_y ) + { + Debug( 9, "Matching neighbours, setting to %d", last_x ); + // Add to the blob from the x side (either side really) + *pdiff = last_x; + alarm_blob_pixels++; + bsx->count++; + if ( x > bsx->hi_x ) bsx->hi_x = x; + if ( (int)y > bsx->hi_y ) bsx->hi_y = y; + } + else + { + // Aggregate blobs + bsm = bsx->count>=bsy->count?bsx:bsy; + bss = bsm==bsx?bsy:bsx; + + Debug( 9, "Different neighbours, setting pixels of %d to %d", bss->tag, bsm->tag ); + Debug( 9, "Master blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bsm->tag, bsm->count, bsm->lo_x, bsm->hi_x, bsm->lo_y, bsm->hi_y ); + Debug( 9, "Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bss->tag, bss->count, bss->lo_x, bss->hi_x, bss->lo_y, bss->hi_y ); + // Now change all those pixels to the other setting + int changed = 0; + for ( int sy = bss->lo_y; sy <= bss->hi_y; sy++) + { + int lo_sx = bss->lo_x>=ranges[sy].lo_x?bss->lo_x:ranges[sy].lo_x; + int hi_sx = bss->hi_x<=ranges[sy].hi_x?bss->hi_x:ranges[sy].hi_x; + + Debug( 9, "Changing %d, %d->%d", sy, lo_sx, hi_sx ); + Debug( 9, "Range %d, %d->%d", sy, ranges[sy].lo_x, ranges[sy].hi_x ); + spdiff = diff_buff + ((diff_width * sy) + lo_sx); + for ( int sx = lo_sx; sx <= hi_sx; sx++, spdiff++ ) + { + Debug( 9, "Pixel at %d,%d (%p) is %d", sx, sy, spdiff, *spdiff ); + if ( *spdiff == bss->tag ) + { + Debug( 9, "Setting pixel" ); + *spdiff = bsm->tag; + changed++; + } + } + } + *pdiff = bsm->tag; + alarm_blob_pixels++; + if ( !changed ) + { + Info( "Master blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bsm->tag, bsm->count, bsm->lo_x, bsm->hi_x, bsm->lo_y, bsm->hi_y ); + Info( "Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bss->tag, bss->count, bss->lo_x, bss->hi_x, bss->lo_y, bss->hi_y ); + Error( "No pixels changed, exiting" ); + exit( -1 ); + } + + // Merge the slave blob into the master + bsm->count += bss->count+1; + if ( x > bsm->hi_x ) bsm->hi_x = x; + if ( (int)y > bsm->hi_y ) bsm->hi_y = y; + if ( bss->lo_x < bsm->lo_x ) bsm->lo_x = bss->lo_x; + if ( bss->lo_y < bsm->lo_y ) bsm->lo_y = bss->lo_y; + if ( bss->hi_x > bsm->hi_x ) bsm->hi_x = bss->hi_x; + if ( bss->hi_y > bsm->hi_y ) bsm->hi_y = bss->hi_y; + + alarm_blobs--; + + Debug( 6, "Merging blob %d with %d at %d,%d, %d current blobs", bss->tag, bsm->tag, x, y, alarm_blobs ); + + // Clear out the old blob + bss->tag = 0; + bss->count = 0; + bss->lo_x = 0; + bss->lo_y = 0; + bss->hi_x = 0; + bss->hi_y = 0; + } + } + else + { + Debug( 9, "Setting to left neighbour %d", last_x ); + // Add to the blob from the x side + *pdiff = last_x; + alarm_blob_pixels++; + bsx->count++; + if ( x > bsx->hi_x ) bsx->hi_x = x; + if ( (int)y > bsx->hi_y ) bsx->hi_y = y; + } + } + else + { + if ( last_y ) + { + Debug( 9, "Top neighbour is %d", last_y ); + Debug( 9, "Setting to top neighbour %d", last_y ); + + // Add to the blob from the y side + BlobStats *bsy = &blob_stats[last_y]; + + *pdiff = last_y; + alarm_blob_pixels++; + bsy->count++; + if ( x > bsy->hi_x ) bsy->hi_x = x; + if ( (int)y > bsy->hi_y ) bsy->hi_y = y; + } + else + { + // Create a new blob + int i; + for ( i = (WHITE-1); i > 0; i-- ) + { + BlobStats *bs = &blob_stats[i]; + // See if we can recycle one first, only if it's at least two rows up + if ( bs->count && bs->hi_y < (int)(y-1) ) + { + if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) + { + if ( config.create_analysis_images || config.record_diag_images ) + { + for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) + { + spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); + for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) + { + if ( *spdiff == bs->tag ) + { + *spdiff = BLACK; + } + } + } + } + alarm_blobs--; + alarm_blob_pixels -= bs->count; + + Debug( 6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs ); + + bs->tag = 0; + bs->count = 0; + bs->lo_x = 0; + bs->lo_y = 0; + bs->hi_x = 0; + bs->hi_y = 0; + } + } + if ( !bs->count ) + { + Debug( 9, "Creating new blob %d", i ); + *pdiff = i; + alarm_blob_pixels++; + bs->tag = i; + bs->count++; + bs->lo_x = bs->hi_x = x; + bs->lo_y = bs->hi_y = y; + alarm_blobs++; + + Debug( 6, "Created blob %d at %d,%d, %d current blobs", bs->tag, x, y, alarm_blobs ); + break; + } + } + if ( i == 0 ) + { + Warning( "Max blob count reached. Unable to allocate new blobs so terminating. Zone settings may be too sensitive." ); + x = hi_x+1; + y = hi_y+1; + } + } + } + } + } + } + if ( config.record_diag_images ) + { + static char diag_path[PATH_MAX] = ""; + if ( !diag_path[0] ) + { + snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", storage->Path(), monitor->Id(), id, 3 ); + } + diff_image->WriteJpeg( diag_path ); + } + + if ( !alarm_blobs ) + { + return( false ); + } + + Debug( 5, "Got %d raw blob pixels, %d raw blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs ); + + // Now eliminate blobs under the threshold + for ( int i = 1; i < WHITE; i++ ) + { + BlobStats *bs = &blob_stats[i]; + if ( bs->count ) + { + if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) + { + if ( config.create_analysis_images || config.record_diag_images ) + { + for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) + { + spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); + for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) + { + if ( *spdiff == bs->tag ) + { + *spdiff = BLACK; + } + } + } + } + alarm_blobs--; + alarm_blob_pixels -= bs->count; + + Debug( 6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs ); + + bs->tag = 0; + bs->count = 0; + bs->lo_x = 0; + bs->lo_y = 0; + bs->hi_x = 0; + bs->hi_y = 0; + } + else + { + Debug( 6, "Preserved blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs ); + if ( !min_blob_size || bs->count < min_blob_size ) min_blob_size = bs->count; + if ( !max_blob_size || bs->count > max_blob_size ) max_blob_size = bs->count; + } + } + } + if ( config.record_diag_images ) + { + static char diag_path[PATH_MAX] = ""; + if ( !diag_path[0] ) + { + snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", storage->Path(), monitor->Id(), id, 4 ); + } + diff_image->WriteJpeg( diag_path ); + } + Debug( 5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs ); - last_x = 0; - if(x > 0) { - if((x-1) >= lo_x) { - last_x = *(pdiff-1); - } - } - - last_y = 0; - if(y > 0) { - if((y-1) >= lo_y && ranges[(y-1)].lo_x <= x && ranges[(y-1)].hi_x >= x) { - last_y = *(pdiff-diff_width); - } - } - - if ( last_x ) - { - Debug( 9, "Left neighbour is %d", last_x ); - bsx = &blob_stats[last_x]; - if ( last_y ) - { - Debug( 9, "Top neighbour is %d", last_y ); - bsy = &blob_stats[last_y]; - if ( last_x == last_y ) - { - Debug( 9, "Matching neighbours, setting to %d", last_x ); - // Add to the blob from the x side (either side really) - *pdiff = last_x; - alarm_blob_pixels++; - bsx->count++; - if ( x > bsx->hi_x ) bsx->hi_x = x; - if ( (int)y > bsx->hi_y ) bsx->hi_y = y; - } - else - { - // Aggregate blobs - bsm = bsx->count>=bsy->count?bsx:bsy; - bss = bsm==bsx?bsy:bsx; + if( alarm_blobs ) { + if( min_blobs && (alarm_blobs < min_blobs) ) { + /* Not enough pixels alarmed */ + return (false); + } else if(max_blobs && (alarm_blobs > max_blobs) ) { + /* Too many pixels alarmed */ + overload_count = overload_frames; + return (false); + } + } else { + /* No blobs */ + return (false); + } + + score = (100*alarm_blob_pixels)/(polygon.Area()); + if(score < 1) + score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ + Debug( 5, "Current score is %d", score ); - Debug( 9, "Different neighbours, setting pixels of %d to %d", bss->tag, bsm->tag ); - Debug( 9, "Master blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bsm->tag, bsm->count, bsm->lo_x, bsm->hi_x, bsm->lo_y, bsm->hi_y ); - Debug( 9, "Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bss->tag, bss->count, bss->lo_x, bss->hi_x, bss->lo_y, bss->hi_y ); - // Now change all those pixels to the other setting - int changed = 0; - for ( int sy = bss->lo_y; sy <= bss->hi_y; sy++) - { - int lo_sx = bss->lo_x>=ranges[sy].lo_x?bss->lo_x:ranges[sy].lo_x; - int hi_sx = bss->hi_x<=ranges[sy].hi_x?bss->hi_x:ranges[sy].hi_x; + alarm_lo_x = polygon.HiX()+1; + alarm_hi_x = polygon.LoX()-1; + alarm_lo_y = polygon.HiY()+1; + alarm_hi_y = polygon.LoY()-1; + for ( int i = 1; i < WHITE; i++ ) + { + BlobStats *bs = &blob_stats[i]; + if ( bs->count ) + { + if ( bs->count == max_blob_size ) + { + if ( config.weighted_alarm_centres ) + { + unsigned long x_total = 0; + unsigned long y_total = 0; - Debug( 9, "Changing %d, %d->%d", sy, lo_sx, hi_sx ); - Debug( 9, "Range %d, %d->%d", sy, ranges[sy].lo_x, ranges[sy].hi_x ); - spdiff = diff_buff + ((diff_width * sy) + lo_sx); - for ( int sx = lo_sx; sx <= hi_sx; sx++, spdiff++ ) - { - Debug( 9, "Pixel at %d,%d (%p) is %d", sx, sy, spdiff, *spdiff ); - if ( *spdiff == bss->tag ) - { - Debug( 9, "Setting pixel" ); - *spdiff = bsm->tag; - changed++; - } - } - } - *pdiff = bsm->tag; - alarm_blob_pixels++; - if ( !changed ) - { - Info( "Master blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bsm->tag, bsm->count, bsm->lo_x, bsm->hi_x, bsm->lo_y, bsm->hi_y ); - Info( "Slave blob t:%d, c:%d, lx:%d, hx:%d, ly:%d, hy:%d", bss->tag, bss->count, bss->lo_x, bss->hi_x, bss->lo_y, bss->hi_y ); - Error( "No pixels changed, exiting" ); - exit( -1 ); - } + for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) + { + spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); + for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) + { + if ( *spdiff == bs->tag ) + { + x_total += sx; + y_total += sy; + } + } + } + alarm_mid_x = int(round(x_total/bs->count)); + alarm_mid_y = int(round(y_total/bs->count)); + } + else + { + alarm_mid_x = int((bs->hi_x+bs->lo_x+1)/2); + alarm_mid_y = int((bs->hi_y+bs->lo_y+1)/2); + } + } - // Merge the slave blob into the master - bsm->count += bss->count+1; - if ( x > bsm->hi_x ) bsm->hi_x = x; - if ( (int)y > bsm->hi_y ) bsm->hi_y = y; - if ( bss->lo_x < bsm->lo_x ) bsm->lo_x = bss->lo_x; - if ( bss->lo_y < bsm->lo_y ) bsm->lo_y = bss->lo_y; - if ( bss->hi_x > bsm->hi_x ) bsm->hi_x = bss->hi_x; - if ( bss->hi_y > bsm->hi_y ) bsm->hi_y = bss->hi_y; + if ( alarm_lo_x > bs->lo_x ) alarm_lo_x = bs->lo_x; + if ( alarm_lo_y > bs->lo_y ) alarm_lo_y = bs->lo_y; + if ( alarm_hi_x < bs->hi_x ) alarm_hi_x = bs->hi_x; + if ( alarm_hi_y < bs->hi_y ) alarm_hi_y = bs->hi_y; + } + } + } + else + { + alarm_mid_x = int((alarm_hi_x+alarm_lo_x+1)/2); + alarm_mid_y = int((alarm_hi_y+alarm_lo_y+1)/2); + } + } - alarm_blobs--; + if ( type == INCLUSIVE ) + { + // score >>= 1; + score /= 2; + } + else if ( type == EXCLUSIVE ) + { + // score <<= 1; + score *= 2; + + } - Debug( 6, "Merging blob %d with %d at %d,%d, %d current blobs", bss->tag, bsm->tag, x, y, alarm_blobs ); + Debug( 5, "Adjusted score is %d", score ); - // Clear out the old blob - bss->tag = 0; - bss->count = 0; - bss->lo_x = 0; - bss->lo_y = 0; - bss->hi_x = 0; - bss->hi_y = 0; - } - } - else - { - Debug( 9, "Setting to left neighbour %d", last_x ); - // Add to the blob from the x side - *pdiff = last_x; - alarm_blob_pixels++; - bsx->count++; - if ( x > bsx->hi_x ) bsx->hi_x = x; - if ( (int)y > bsx->hi_y ) bsx->hi_y = y; - } - } - else - { - if ( last_y ) - { - Debug( 9, "Top neighbour is %d", last_y ); - Debug( 9, "Setting to top neighbour %d", last_y ); - - // Add to the blob from the y side - BlobStats *bsy = &blob_stats[last_y]; + // Now outline the changed region + if ( score ) + { + alarm_box = Box( Coord( alarm_lo_x, alarm_lo_y ), Coord( alarm_hi_x, alarm_hi_y ) ); - *pdiff = last_y; - alarm_blob_pixels++; - bsy->count++; - if ( x > bsy->hi_x ) bsy->hi_x = x; - if ( (int)y > bsy->hi_y ) bsy->hi_y = y; - } - else - { - // Create a new blob - int i; - for ( i = (WHITE-1); i > 0; i-- ) - { - BlobStats *bs = &blob_stats[i]; - // See if we can recycle one first, only if it's at least two rows up - if ( bs->count && bs->hi_y < (int)(y-1) ) - { - if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) - { - if ( config.create_analysis_images || config.record_diag_images ) - { - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) - { - spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) - { - if ( *spdiff == bs->tag ) - { - *spdiff = BLACK; - } - } - } - } - alarm_blobs--; - alarm_blob_pixels -= bs->count; - - Debug( 6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs ); + //if ( monitor->followMotion() ) + if ( true ) + { + alarm_centre = Coord( alarm_mid_x, alarm_mid_y ); + } + else + { + alarm_centre = alarm_box.Centre(); + } - bs->tag = 0; - bs->count = 0; - bs->lo_x = 0; - bs->lo_y = 0; - bs->hi_x = 0; - bs->hi_y = 0; - } - } - if ( !bs->count ) - { - Debug( 9, "Creating new blob %d", i ); - *pdiff = i; - alarm_blob_pixels++; - bs->tag = i; - bs->count++; - bs->lo_x = bs->hi_x = x; - bs->lo_y = bs->hi_y = y; - alarm_blobs++; + if ( (type < PRECLUSIVE) && check_method >= BLOBS && config.create_analysis_images ) + { - Debug( 6, "Created blob %d at %d,%d, %d current blobs", bs->tag, x, y, alarm_blobs ); - break; - } - } - if ( i == 0 ) - { - Warning( "Max blob count reached. Unable to allocate new blobs so terminating. Zone settings may be too sensitive." ); - x = hi_x+1; - y = hi_y+1; - } - } - } - } - } - } - if ( config.record_diag_images ) - { - static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { - snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", config.dir_events, monitor->Id(), id, 3 ); - } - diff_image->WriteJpeg( diag_path ); - } + // First mask out anything we don't want + for ( unsigned int y = lo_y; y <= hi_y; y++ ) + { + pdiff = diff_buff + ((diff_width * y) + lo_x); - if ( !alarm_blobs ) - { - return( false ); - } - - Debug( 5, "Got %d raw blob pixels, %d raw blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs ); + int lo_x2 = ranges[y].lo_x; + int hi_x2 = ranges[y].hi_x; - // Now eliminate blobs under the threshold - for ( int i = 1; i < WHITE; i++ ) - { - BlobStats *bs = &blob_stats[i]; - if ( bs->count ) - { - if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) - { - if ( config.create_analysis_images || config.record_diag_images ) - { - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) - { - spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) - { - if ( *spdiff == bs->tag ) - { - *spdiff = BLACK; - } - } - } - } - alarm_blobs--; - alarm_blob_pixels -= bs->count; - - Debug( 6, "Eliminated blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs ); + int lo_gap = lo_x2-lo_x; + if ( lo_gap > 0 ) + { + if ( lo_gap == 1 ) + { + *pdiff++ = BLACK; + } + else + { + memset( pdiff, BLACK, lo_gap ); + pdiff += lo_gap; + } + } - bs->tag = 0; - bs->count = 0; - bs->lo_x = 0; - bs->lo_y = 0; - bs->hi_x = 0; - bs->hi_y = 0; - } - else - { - Debug( 6, "Preserved blob %d, %d pixels (%d,%d - %d,%d), %d current blobs", i, bs->count, bs->lo_x, bs->lo_y, bs->hi_x, bs->hi_y, alarm_blobs ); - if ( !min_blob_size || bs->count < min_blob_size ) min_blob_size = bs->count; - if ( !max_blob_size || bs->count > max_blob_size ) max_blob_size = bs->count; - } - } - } - if ( config.record_diag_images ) - { - static char diag_path[PATH_MAX] = ""; - if ( !diag_path[0] ) - { - snprintf( diag_path, sizeof(diag_path), "%s/%d/diag-%d-%d.jpg", config.dir_events, monitor->Id(), id, 4 ); - } - diff_image->WriteJpeg( diag_path ); - } - Debug( 5, "Got %d blob pixels, %d blobs, need %d -> %d, %d -> %d", alarm_blob_pixels, alarm_blobs, min_blob_pixels, max_blob_pixels, min_blobs, max_blobs ); - - if( alarm_blobs ) { - if( min_blobs && (alarm_blobs < min_blobs) ) { - /* Not enough pixels alarmed */ - return (false); - } else if(max_blobs && (alarm_blobs > max_blobs) ) { - /* Too many pixels alarmed */ - overload_count = overload_frames; - return (false); - } - } else { - /* No blobs */ - return (false); - } - - score = (100*alarm_blob_pixels)/(polygon.Area()); - if(score < 1) - score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ - Debug( 5, "Current score is %d", score ); + ppoly = pg_image->Buffer( lo_x2, y ); + for ( int x = lo_x2; x <= hi_x2; x++, pdiff++, ppoly++ ) + { + if ( !*ppoly ) + { + *pdiff = BLACK; + } + } - alarm_lo_x = polygon.HiX()+1; - alarm_hi_x = polygon.LoX()-1; - alarm_lo_y = polygon.HiY()+1; - alarm_hi_y = polygon.LoY()-1; - for ( int i = 1; i < WHITE; i++ ) - { - BlobStats *bs = &blob_stats[i]; - if ( bs->count ) - { - if ( bs->count == max_blob_size ) - { - if ( config.weighted_alarm_centres ) - { - unsigned long x_total = 0; - unsigned long y_total = 0; + int hi_gap = hi_x-hi_x2; + if ( hi_gap > 0 ) + { + if ( hi_gap == 1 ) + { + *pdiff = BLACK; + } + else + { + memset( pdiff, BLACK, hi_gap ); + } + } + } + + if( monitor->Colours() == ZM_COLOUR_GRAY8 ) { + image = diff_image->HighlightEdges( alarm_rgb, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB, &polygon.Extent() ); + } else { + image = diff_image->HighlightEdges( alarm_rgb, monitor->Colours(), monitor->SubpixelOrder(), &polygon.Extent() ); + } + + // Only need to delete this when 'image' becomes detached and points somewhere else + delete diff_image; + } + else + { + delete image; + image = 0; + } - for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) - { - spdiff = diff_buff + ((diff_width * sy) + bs->lo_x); - for ( int sx = bs->lo_x; sx <= bs->hi_x; sx++, spdiff++ ) - { - if ( *spdiff == bs->tag ) - { - x_total += sx; - y_total += sy; - } - } - } - alarm_mid_x = int(round(x_total/bs->count)); - alarm_mid_y = int(round(y_total/bs->count)); - } - else - { - alarm_mid_x = int((bs->hi_x+bs->lo_x+1)/2); - alarm_mid_y = int((bs->hi_y+bs->lo_y+1)/2); - } - } - - if ( alarm_lo_x > bs->lo_x ) alarm_lo_x = bs->lo_x; - if ( alarm_lo_y > bs->lo_y ) alarm_lo_y = bs->lo_y; - if ( alarm_hi_x < bs->hi_x ) alarm_hi_x = bs->hi_x; - if ( alarm_hi_y < bs->hi_y ) alarm_hi_y = bs->hi_y; - } - } - } - else - { - alarm_mid_x = int((alarm_hi_x+alarm_lo_x+1)/2); - alarm_mid_y = int((alarm_hi_y+alarm_lo_y+1)/2); - } - } - - if ( type == INCLUSIVE ) - { - // score >>= 1; - score /= 2; - } - else if ( type == EXCLUSIVE ) - { - // score <<= 1; - score *= 2; - - } - - Debug( 5, "Adjusted score is %d", score ); - - // Now outline the changed region - if ( score ) - { - alarm_box = Box( Coord( alarm_lo_x, alarm_lo_y ), Coord( alarm_hi_x, alarm_hi_y ) ); - - //if ( monitor->followMotion() ) - if ( true ) - { - alarm_centre = Coord( alarm_mid_x, alarm_mid_y ); - } - else - { - alarm_centre = alarm_box.Centre(); - } - - if ( (type < PRECLUSIVE) && check_method >= BLOBS && config.create_analysis_images ) - { - - // First mask out anything we don't want - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { - pdiff = diff_buff + ((diff_width * y) + lo_x); - - int lo_x2 = ranges[y].lo_x; - int hi_x2 = ranges[y].hi_x; - - int lo_gap = lo_x2-lo_x; - if ( lo_gap > 0 ) - { - if ( lo_gap == 1 ) - { - *pdiff++ = BLACK; - } - else - { - memset( pdiff, BLACK, lo_gap ); - pdiff += lo_gap; - } - } - - ppoly = pg_image->Buffer( lo_x2, y ); - for ( int x = lo_x2; x <= hi_x2; x++, pdiff++, ppoly++ ) - { - if ( !*ppoly ) - { - *pdiff = BLACK; - } - } - - int hi_gap = hi_x-hi_x2; - if ( hi_gap > 0 ) - { - if ( hi_gap == 1 ) - { - *pdiff = BLACK; - } - else - { - memset( pdiff, BLACK, hi_gap ); - } - } - } - - if( monitor->Colours() == ZM_COLOUR_GRAY8 ) { - image = diff_image->HighlightEdges( alarm_rgb, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB, &polygon.Extent() ); - } else { - image = diff_image->HighlightEdges( alarm_rgb, monitor->Colours(), monitor->SubpixelOrder(), &polygon.Extent() ); - } - - // Only need to delete this when 'image' becomes detached and points somewhere else - delete diff_image; - } - else - { - delete image; - image = 0; - } - - Debug( 1, "%s: Pixel Diff: %d, Alarm Pixels: %d, Filter Pixels: %d, Blob Pixels: %d, Blobs: %d, Score: %d", Label(), pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, score ); - } - return( true ); + Debug( 3, "%s: Pixel Diff: %d, Alarm Pixels: %d, Filter Pixels: %d, Blob Pixels: %d, Blobs: %d, Score: %d", Label(), pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, score ); + } + return( true ); } bool Zone::ParsePolygonString( const char *poly_string, Polygon &polygon ) { - Debug( 3, "Parsing polygon string '%s'", poly_string ); + Debug( 3, "Parsing polygon string '%s'", poly_string ); - char *str_ptr = new char[strlen(poly_string)+1]; - char *str = str_ptr; - strcpy( str, poly_string ); + char *str_ptr = new char[strlen(poly_string)+1]; + char *str = str_ptr; + strcpy( str, poly_string ); - char *ws; - int n_coords = 0; - int max_n_coords = strlen(str)/4; - Coord *coords = new Coord[max_n_coords]; - while( true ) - { - if ( *str == '\0' ) - { - break; - } - ws = strchr( str, ' ' ); - if ( ws ) - { - *ws = '\0'; - } - char *cp = strchr( str, ',' ); - if ( !cp ) - { - Error( "Bogus coordinate %s found in polygon string", str ); - delete[] coords; - delete[] str_ptr; - return( false ); - } - else - { - *cp = '\0'; - char *xp = str; - char *yp = cp+1; + char *ws; + int n_coords = 0; + int max_n_coords = strlen(str)/4; + Coord *coords = new Coord[max_n_coords]; + while( true ) + { + if ( *str == '\0' ) + { + break; + } + ws = strchr( str, ' ' ); + if ( ws ) + { + *ws = '\0'; + } + char *cp = strchr( str, ',' ); + if ( !cp ) + { + Error( "Bogus coordinate %s found in polygon string", str ); + delete[] coords; + delete[] str_ptr; + return( false ); + } + else + { + *cp = '\0'; + char *xp = str; + char *yp = cp+1; - int x = atoi(xp); - int y = atoi(yp); + int x = atoi(xp); + int y = atoi(yp); - Debug( 3, "Got coordinate %d,%d from polygon string", x, y ); + Debug( 3, "Got coordinate %d,%d from polygon string", x, y ); #if 0 - if ( x < 0 ) - x = 0; - else if ( x >= width ) - x = width-1; - if ( y < 0 ) - y = 0; - else if ( y >= height ) - y = height-1; + if ( x < 0 ) + x = 0; + else if ( x >= width ) + x = width-1; + if ( y < 0 ) + y = 0; + else if ( y >= height ) + y = height-1; #endif - coords[n_coords++] = Coord( x, y ); - } - if ( ws ) - str = ws+1; - else - break; - } - polygon = Polygon( n_coords, coords ); + coords[n_coords++] = Coord( x, y ); + } + if ( ws ) + str = ws+1; + else + break; + } + polygon = Polygon( n_coords, coords ); - Debug( 3, "Successfully parsed polygon string" ); - //printf( "Area: %d\n", pg.Area() ); - //printf( "Centre: %d,%d\n", pg.Centre().X(), pg.Centre().Y() ); + Debug( 3, "Successfully parsed polygon string" ); + //printf( "Area: %d\n", pg.Area() ); + //printf( "Centre: %d,%d\n", pg.Centre().X(), pg.Centre().Y() ); - delete[] coords; - delete[] str_ptr; + delete[] coords; + delete[] str_ptr; - return( true ); + return( true ); } bool Zone::ParseZoneString( const char *zone_string, int &zone_id, int &colour, Polygon &polygon ) { - Debug( 3, "Parsing zone string '%s'", zone_string ); + Debug( 3, "Parsing zone string '%s'", zone_string ); - char *str_ptr = new char[strlen(zone_string)+1]; - char *str = str_ptr; - strcpy( str, zone_string ); + char *str_ptr = new char[strlen(zone_string)+1]; + char *str = str_ptr; + strcpy( str, zone_string ); - char *ws = strchr( str, ' ' ); - if ( !ws ) - { - Debug( 3, "No initial whitespace found in zone string '%s', finishing", str ); - } - zone_id = strtol( str, 0, 10 ); - Debug( 3, "Got zone %d from zone string", zone_id ); - if ( !ws ) - { - delete str_ptr; - return( true ); - } + char *ws = strchr( str, ' ' ); + if ( !ws ) + { + Debug( 3, "No initial whitespace found in zone string '%s', finishing", str ); + } + zone_id = strtol( str, 0, 10 ); + Debug( 3, "Got zone %d from zone string", zone_id ); + if ( !ws ) + { + delete str_ptr; + return( true ); + } - *ws = '\0'; - str = ws+1; + *ws = '\0'; + str = ws+1; - ws = strchr( str, ' ' ); - if ( !ws ) - { - Debug( 3, "No secondary whitespace found in zone string '%s', finishing", zone_string ); - } - colour = strtol( str, 0, 16 ); - Debug( 3, "Got colour %06x from zone string", colour ); - if ( !ws ) - { - delete str_ptr; - return( true ); - } - *ws = '\0'; - str = ws+1; + ws = strchr( str, ' ' ); + if ( !ws ) + { + Debug( 3, "No secondary whitespace found in zone string '%s', finishing", zone_string ); + } + colour = strtol( str, 0, 16 ); + Debug( 3, "Got colour %06x from zone string", colour ); + if ( !ws ) + { + delete str_ptr; + return( true ); + } + *ws = '\0'; + str = ws+1; - bool result = ParsePolygonString( str, polygon ); + bool result = ParsePolygonString( str, polygon ); - //printf( "Area: %d\n", pg.Area() ); - //printf( "Centre: %d,%d\n", pg.Centre().X(), pg.Centre().Y() ); + //printf( "Area: %d\n", pg.Area() ); + //printf( "Centre: %d,%d\n", pg.Centre().X(), pg.Centre().Y() ); - delete[] str_ptr; + delete[] str_ptr; - return( result ); + return( result ); } int Zone::Load( Monitor *monitor, Zone **&zones ) { - static char sql[ZM_SQL_MED_BUFSIZ]; - snprintf( sql, sizeof(sql), "select Id,Name,Type+0,Units,Coords,AlarmRGB,CheckMethod+0,MinPixelThreshold,MaxPixelThreshold,MinAlarmPixels,MaxAlarmPixels,FilterX,FilterY,MinFilterPixels,MaxFilterPixels,MinBlobPixels,MaxBlobPixels,MinBlobs,MaxBlobs,OverloadFrames,ExtendAlarmFrames from Zones where MonitorId = %d order by Type, Id", monitor->Id() ); - if ( mysql_query( &dbconn, sql ) ) - { - Error( "Can't run query: %s", mysql_error( &dbconn ) ); - exit( mysql_errno( &dbconn ) ); - } + static char sql[ZM_SQL_MED_BUFSIZ]; + snprintf( sql, sizeof(sql), "select Id,Name,Type+0,Units,Coords,AlarmRGB,CheckMethod+0,MinPixelThreshold,MaxPixelThreshold,MinAlarmPixels,MaxAlarmPixels,FilterX,FilterY,MinFilterPixels,MaxFilterPixels,MinBlobPixels,MaxBlobPixels,MinBlobs,MaxBlobs,OverloadFrames,ExtendAlarmFrames from Zones where MonitorId = %d order by Type, Id", monitor->Id() ); + if ( mysql_query( &dbconn, sql ) ) { + 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 ) ); - } - int n_zones = mysql_num_rows( result ); - Debug( 1, "Got %d zones for monitor %s", n_zones, monitor->Name() ); - delete[] zones; - zones = new Zone *[n_zones]; - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { - int col = 0; + MYSQL_RES *result = mysql_store_result( &dbconn ); + if ( !result ) { + Error( "Can't use query result: %s", mysql_error( &dbconn ) ); + exit( mysql_errno( &dbconn ) ); + } + int n_zones = mysql_num_rows( result ); + Debug( 1, "Got %d zones for monitor %s", n_zones, monitor->Name() ); + delete[] zones; + zones = new Zone *[n_zones]; + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { + zones[i] = NULL; + int col = 0; - int Id = atoi(dbrow[col++]); - const char *Name = dbrow[col++]; - int Type = atoi(dbrow[col++]); - const char *Units = dbrow[col++]; - const char *Coords = dbrow[col++]; - int AlarmRGB = dbrow[col]?atoi(dbrow[col]):0; col++; - int CheckMethod = atoi(dbrow[col++]); - int MinPixelThreshold = dbrow[col]?atoi(dbrow[col]):0; col++; - int MaxPixelThreshold = dbrow[col]?atoi(dbrow[col]):0; col++; - int MinAlarmPixels = dbrow[col]?atoi(dbrow[col]):0; col++; - int MaxAlarmPixels = dbrow[col]?atoi(dbrow[col]):0; col++; - int FilterX = dbrow[col]?atoi(dbrow[col]):0; col++; - int FilterY = dbrow[col]?atoi(dbrow[col]):0; col++; - int MinFilterPixels = dbrow[col]?atoi(dbrow[col]):0; col++; - int MaxFilterPixels = dbrow[col]?atoi(dbrow[col]):0; col++; - int MinBlobPixels = dbrow[col]?atoi(dbrow[col]):0; col++; - int MaxBlobPixels = dbrow[col]?atoi(dbrow[col]):0; col++; - int MinBlobs = dbrow[col]?atoi(dbrow[col]):0; col++; - int MaxBlobs = dbrow[col]?atoi(dbrow[col]):0; col++; - int OverloadFrames = dbrow[col]?atoi(dbrow[col]):0; col++; - int ExtendAlarmFrames = dbrow[col]?atoi(dbrow[col]):0; col++; - - /* HTML colour code is actually BGR in memory, we want RGB */ - AlarmRGB = rgb_convert(AlarmRGB, ZM_SUBPIX_ORDER_BGR); + int Id = atoi(dbrow[col++]); + const char *Name = dbrow[col++]; + ZoneType Type = (ZoneType) atoi(dbrow[col++]); + const char *Units = dbrow[col++]; + const char *Coords = dbrow[col++]; + int AlarmRGB = dbrow[col]?atoi(dbrow[col]):0; col++; + int CheckMethod = atoi(dbrow[col++]); + int MinPixelThreshold = dbrow[col]?atoi(dbrow[col]):0; col++; + int MaxPixelThreshold = dbrow[col]?atoi(dbrow[col]):0; col++; + int MinAlarmPixels = dbrow[col]?atoi(dbrow[col]):0; col++; + int MaxAlarmPixels = dbrow[col]?atoi(dbrow[col]):0; col++; + int FilterX = dbrow[col]?atoi(dbrow[col]):0; col++; + int FilterY = dbrow[col]?atoi(dbrow[col]):0; col++; + int MinFilterPixels = dbrow[col]?atoi(dbrow[col]):0; col++; + int MaxFilterPixels = dbrow[col]?atoi(dbrow[col]):0; col++; + int MinBlobPixels = dbrow[col]?atoi(dbrow[col]):0; col++; + int MaxBlobPixels = dbrow[col]?atoi(dbrow[col]):0; col++; + int MinBlobs = dbrow[col]?atoi(dbrow[col]):0; col++; + int MaxBlobs = dbrow[col]?atoi(dbrow[col]):0; col++; + int OverloadFrames = dbrow[col]?atoi(dbrow[col]):0; col++; + int ExtendAlarmFrames = dbrow[col]?atoi(dbrow[col]):0; col++; + + /* HTML colour code is actually BGR in memory, we want RGB */ + AlarmRGB = rgb_convert(AlarmRGB, ZM_SUBPIX_ORDER_BGR); - Debug( 5, "Parsing polygon %s", Coords ); - Polygon polygon; - if ( !ParsePolygonString( Coords, polygon ) ) { - Error( "Unable to parse polygon string '%s' for zone %d/%s for monitor %s, ignoring", Coords, Id, Name, monitor->Name() ); + Debug( 5, "Parsing polygon %s", Coords ); + Polygon polygon; + if ( !ParsePolygonString( Coords, polygon ) ) { + Error( "Unable to parse polygon string '%s' for zone %d/%s for monitor %s, ignoring", Coords, Id, Name, monitor->Name() ); + n_zones -= 1; continue; } - if ( polygon.LoX() < 0 || polygon.HiX() >= (int)monitor->Width() - || polygon.LoY() < 0 || polygon.HiY() >= (int)monitor->Height() ) { - Error( "Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), ignoring", Id, Name, monitor->Name(), polygon.LoX(), polygon.LoY(), polygon.HiX(), polygon.HiY() ); + if ( polygon.LoX() < 0 || polygon.HiX() >= (int)monitor->Width() + || polygon.LoY() < 0 || polygon.HiY() >= (int)monitor->Height() ) { + Error( "Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), ignoring", Id, Name, monitor->Name(), polygon.LoX(), polygon.LoY(), polygon.HiX(), polygon.HiY() ); + n_zones -= 1; continue; } - if ( false && !strcmp( Units, "Percent" ) ) - { - MinAlarmPixels = (MinAlarmPixels*polygon.Area())/100; - MaxAlarmPixels = (MaxAlarmPixels*polygon.Area())/100; - MinFilterPixels = (MinFilterPixels*polygon.Area())/100; - MaxFilterPixels = (MaxFilterPixels*polygon.Area())/100; - MinBlobPixels = (MinBlobPixels*polygon.Area())/100; - MaxBlobPixels = (MaxBlobPixels*polygon.Area())/100; - } + if ( false && !strcmp( Units, "Percent" ) ) { + MinAlarmPixels = (MinAlarmPixels*polygon.Area())/100; + MaxAlarmPixels = (MaxAlarmPixels*polygon.Area())/100; + MinFilterPixels = (MinFilterPixels*polygon.Area())/100; + MaxFilterPixels = (MaxFilterPixels*polygon.Area())/100; + MinBlobPixels = (MinBlobPixels*polygon.Area())/100; + MaxBlobPixels = (MaxBlobPixels*polygon.Area())/100; + } - if ( atoi(dbrow[2]) == Zone::INACTIVE ) - { - zones[i] = new Zone( monitor, Id, Name, polygon ); - } - else if ( atoi(dbrow[2]) == Zone::PRIVACY ) - { - zones[i] = new Zone( monitor, Id, Name, (Zone::ZoneType)Type, polygon ); - } - { - zones[i] = new Zone( monitor, Id, Name, (Zone::ZoneType)Type, polygon, AlarmRGB, (Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold, MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ), MinFilterPixels, MaxFilterPixels, MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, OverloadFrames, ExtendAlarmFrames ); - } - } - if ( mysql_errno( &dbconn ) ) - { - Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); - exit( mysql_errno( &dbconn ) ); - } - // Yadda yadda - mysql_free_result( result ); - return( n_zones ); + if ( Type == INACTIVE ) { + zones[i] = new Zone( monitor, Id, Name, polygon ); + } else if ( Type == PRIVACY ) { + zones[i] = new Zone( monitor, Id, Name, Type, polygon ); + } else { + zones[i] = new Zone( monitor, Id, Name, Type, polygon, AlarmRGB, (Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold, MinAlarmPixels, MaxAlarmPixels, Coord( FilterX, FilterY ), MinFilterPixels, MaxFilterPixels, MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs, OverloadFrames, ExtendAlarmFrames ); + } + } // end foreach row in zones table + if ( mysql_errno( &dbconn ) ) { + Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); + exit( mysql_errno( &dbconn ) ); + } + // Yadda yadda + mysql_free_result( result ); + return( n_zones ); } bool Zone::DumpSettings( char *output, bool /*verbose*/ ) { - output[0] = 0; + output[0] = 0; - sprintf( output+strlen(output), " Id : %d\n", id ); - sprintf( output+strlen(output), " Label : %s\n", label ); - sprintf( output+strlen(output), " Type: %d - %s\n", type, - type==ACTIVE?"Active":( - type==INCLUSIVE?"Inclusive":( - type==EXCLUSIVE?"Exclusive":( - type==PRECLUSIVE?"Preclusive":( - type==INACTIVE?"Inactive":( - type==PRIVACY?"Privacy":"Unknown" - )))))); - sprintf( output+strlen(output), " Shape : %d points\n", polygon.getNumCoords() ); - for ( int i = 0; i < polygon.getNumCoords(); i++ ) - { - sprintf( output+strlen(output), " %i: %d,%d\n", i, polygon.getCoord( i ).X(), polygon.getCoord( i ).Y() ); - } - sprintf( output+strlen(output), " Alarm RGB : %06x\n", alarm_rgb ); - sprintf( output+strlen(output), " Check Method: %d - %s\n", check_method, - check_method==ALARMED_PIXELS?"Alarmed Pixels":( - check_method==FILTERED_PIXELS?"FilteredPixels":( - check_method==BLOBS?"Blobs":"Unknown" - ))); - sprintf( output+strlen(output), " Min Pixel Threshold : %d\n", min_pixel_threshold ); - sprintf( output+strlen(output), " Max Pixel Threshold : %d\n", max_pixel_threshold ); - sprintf( output+strlen(output), " Min Alarm Pixels : %d\n", min_alarm_pixels ); - sprintf( output+strlen(output), " Max Alarm Pixels : %d\n", max_alarm_pixels ); - sprintf( output+strlen(output), " Filter Box : %d,%d\n", filter_box.X(), filter_box.Y() ); - sprintf( output+strlen(output), " Min Filter Pixels : %d\n", min_filter_pixels ); - sprintf( output+strlen(output), " Max Filter Pixels : %d\n", max_filter_pixels ); - sprintf( output+strlen(output), " Min Blob Pixels : %d\n", min_blob_pixels ); - sprintf( output+strlen(output), " Max Blob Pixels : %d\n", max_blob_pixels ); - sprintf( output+strlen(output), " Min Blobs : %d\n", min_blobs ); - sprintf( output+strlen(output), " Max Blobs : %d\n", max_blobs ); - return( true ); + sprintf( output+strlen(output), " Id : %d\n", id ); + sprintf( output+strlen(output), " Label : %s\n", label ); + sprintf( output+strlen(output), " Type: %d - %s\n", type, + type==ACTIVE?"Active":( + type==INCLUSIVE?"Inclusive":( + type==EXCLUSIVE?"Exclusive":( + type==PRECLUSIVE?"Preclusive":( + type==INACTIVE?"Inactive":( + type==PRIVACY?"Privacy":"Unknown" + )))))); + sprintf( output+strlen(output), " Shape : %d points\n", polygon.getNumCoords() ); + for ( int i = 0; i < polygon.getNumCoords(); i++ ) + { + sprintf( output+strlen(output), " %i: %d,%d\n", i, polygon.getCoord( i ).X(), polygon.getCoord( i ).Y() ); + } + sprintf( output+strlen(output), " Alarm RGB : %06x\n", alarm_rgb ); + sprintf( output+strlen(output), " Check Method: %d - %s\n", check_method, + check_method==ALARMED_PIXELS?"Alarmed Pixels":( + check_method==FILTERED_PIXELS?"FilteredPixels":( + check_method==BLOBS?"Blobs":"Unknown" + ))); + sprintf( output+strlen(output), " Min Pixel Threshold : %d\n", min_pixel_threshold ); + sprintf( output+strlen(output), " Max Pixel Threshold : %d\n", max_pixel_threshold ); + sprintf( output+strlen(output), " Min Alarm Pixels : %d\n", min_alarm_pixels ); + sprintf( output+strlen(output), " Max Alarm Pixels : %d\n", max_alarm_pixels ); + sprintf( output+strlen(output), " Filter Box : %d,%d\n", filter_box.X(), filter_box.Y() ); + sprintf( output+strlen(output), " Min Filter Pixels : %d\n", min_filter_pixels ); + sprintf( output+strlen(output), " Max Filter Pixels : %d\n", max_filter_pixels ); + sprintf( output+strlen(output), " Min Blob Pixels : %d\n", min_blob_pixels ); + sprintf( output+strlen(output), " Max Blob Pixels : %d\n", max_blob_pixels ); + sprintf( output+strlen(output), " Min Blobs : %d\n", min_blobs ); + sprintf( output+strlen(output), " Max Blobs : %d\n", max_blobs ); + return( true ); } void Zone::std_alarmedpixels(Image* pdiff_image, const Image* ppoly_image, unsigned int* pixel_count, unsigned int* pixel_sum) { - uint32_t pixelsalarmed = 0; - uint32_t pixelsdifference = 0; - uint8_t *pdiff; - const uint8_t *ppoly; - uint8_t calc_max_pixel_threshold = 255; - unsigned int lo_y; - unsigned int hi_y; - unsigned int lo_x; - unsigned int hi_x; - - if(max_pixel_threshold) - calc_max_pixel_threshold = max_pixel_threshold; - - lo_y = polygon.LoY(); - hi_y = polygon.HiY(); - for ( unsigned int y = lo_y; y <= hi_y; y++ ) - { - lo_x = ranges[y].lo_x; - hi_x = ranges[y].hi_x; - - Debug( 7, "Checking line %d from %d -> %d", y, lo_x, hi_x ); - pdiff = (uint8_t*)pdiff_image->Buffer( lo_x, y ); - ppoly = ppoly_image->Buffer( lo_x, y ); - - for ( unsigned int x = lo_x; x <= hi_x; x++, pdiff++, ppoly++ ) - { - if ( *ppoly && (*pdiff > min_pixel_threshold) && (*pdiff <= calc_max_pixel_threshold) ) - { - pixelsalarmed++; - pixelsdifference += *pdiff; - *pdiff = WHITE; - } - else - { - *pdiff = BLACK; - } - } - } - - /* Store the results */ - *pixel_count = pixelsalarmed; - *pixel_sum = pixelsdifference; + uint32_t pixelsalarmed = 0; + uint32_t pixelsdifference = 0; + uint8_t *pdiff; + const uint8_t *ppoly; + uint8_t calc_max_pixel_threshold = 255; + unsigned int lo_y; + unsigned int hi_y; + unsigned int lo_x; + unsigned int hi_x; + + if(max_pixel_threshold) + calc_max_pixel_threshold = max_pixel_threshold; + + lo_y = polygon.LoY(); + hi_y = polygon.HiY(); + for ( unsigned int y = lo_y; y <= hi_y; y++ ) + { + lo_x = ranges[y].lo_x; + hi_x = ranges[y].hi_x; + + Debug( 7, "Checking line %d from %d -> %d", y, lo_x, hi_x ); + pdiff = (uint8_t*)pdiff_image->Buffer( lo_x, y ); + ppoly = ppoly_image->Buffer( lo_x, y ); + + for ( unsigned int x = lo_x; x <= hi_x; x++, pdiff++, ppoly++ ) + { + if ( *ppoly && (*pdiff > min_pixel_threshold) && (*pdiff <= calc_max_pixel_threshold) ) + { + pixelsalarmed++; + pixelsdifference += *pdiff; + *pdiff = WHITE; + } + else + { + *pdiff = BLACK; + } + } + } + + /* Store the results */ + *pixel_count = pixelsalarmed; + *pixel_sum = pixelsdifference; Debug( 7, "STORED"); } diff --git a/src/zma.cpp b/src/zma.cpp index f9557ba59..8d446668b 100644 --- a/src/zma.cpp +++ b/src/zma.cpp @@ -133,7 +133,7 @@ int main( int argc, char *argv[] ) logInit( log_id_string ); - ssedetect(); + hwcaps_detect(); Monitor *monitor = Monitor::Load( id, true, Monitor::ANALYSIS ); diff --git a/src/zmc.cpp b/src/zmc.cpp index 288fc0d7e..58b60e7a9 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -73,8 +73,7 @@ possible, this should run at more or less constant speed. #include "zm_signal.h" #include "zm_monitor.h" -void Usage() -{ +void Usage() { fprintf( stderr, "zmc -d or -r -H -P -p or -f or -m \n" ); fprintf( stderr, "Options:\n" ); @@ -91,8 +90,7 @@ void Usage() exit( 0 ); } -int main( int argc, char *argv[] ) -{ +int main( int argc, char *argv[] ) { self = argv[0]; srand( getpid() * time( 0 ) ); @@ -110,21 +108,19 @@ int main( int argc, char *argv[] ) {"protocol", 1, 0, 'r'}, {"host", 1, 0, 'H'}, {"port", 1, 0, 'P'}, - {"path", 1, 0, 'p'}, - {"file", 1, 0, 'f'}, + {"path", 1, 0, 'p'}, + {"file", 1, 0, 'f'}, {"monitor", 1, 0, 'm'}, {"help", 0, 0, 'h'}, {"version", 0, 0, 'v'}, {0, 0, 0, 0} }; - while (1) - { + while (1) { int option_index = 0; int c = getopt_long (argc, argv, "d:H:P:p:f:m:h:v", long_options, &option_index); - if (c == -1) - { + if (c == -1) { break; } @@ -161,8 +157,7 @@ int main( int argc, char *argv[] ) } } - if (optind < argc) - { + if (optind < argc) { fprintf( stderr, "Extraneous options, " ); while (optind < argc) printf ("%s ", argv[optind++]); @@ -171,37 +166,28 @@ int main( int argc, char *argv[] ) } int modes = ( device[0]?1:0 + host[0]?1:0 + file[0]?1:0 + (monitor_id>0?1:0) ); - if ( modes > 1 ) - { + if ( modes > 1 ) { fprintf( stderr, "Only one of device, host/port/path, file or monitor id allowed\n" ); Usage(); exit( 0 ); } - if ( modes < 1 ) - { + if ( modes < 1 ) { fprintf( stderr, "One of device, host/port/path, file or monitor id must be specified\n" ); Usage(); exit( 0 ); } char log_id_string[32] = ""; - if ( device[0] ) - { + if ( device[0] ) { const char *slash_ptr = strrchr( device, '/' ); snprintf( log_id_string, sizeof(log_id_string), "zmc_d%s", slash_ptr?slash_ptr+1:device ); - } - else if ( host[0] ) - { + } else if ( host[0] ) { snprintf( log_id_string, sizeof(log_id_string), "zmc_h%s", host ); - } - else if ( file[0] ) - { + } else if ( file[0] ) { const char *slash_ptr = strrchr( file, '/' ); snprintf( log_id_string, sizeof(log_id_string), "zmc_f%s", slash_ptr?slash_ptr+1:file ); - } - else - { + } else { snprintf( log_id_string, sizeof(log_id_string), "zmc_m%d", monitor_id ); } @@ -209,40 +195,31 @@ int main( int argc, char *argv[] ) logInit( log_id_string ); - ssedetect(); + hwcaps_detect(); Monitor **monitors = 0; int n_monitors = 0; #if ZM_HAS_V4L - if ( device[0] ) - { + if ( device[0] ) { n_monitors = Monitor::LoadLocalMonitors( device, monitors, Monitor::CAPTURE ); - } - else + } else #endif // ZM_HAS_V4L - if ( host[0] ) - { + if ( host[0] ) { if ( !port ) port = "80"; n_monitors = Monitor::LoadRemoteMonitors( protocol, host, port, path, monitors, Monitor::CAPTURE ); - } - else if ( file[0] ) - { + } else if ( file[0] ) { n_monitors = Monitor::LoadFileMonitors( file, monitors, Monitor::CAPTURE ); - } - else - { + } else { Monitor *monitor = Monitor::Load( monitor_id, true, Monitor::CAPTURE ); - if ( monitor ) - { + if ( monitor ) { monitors = new Monitor *[1]; monitors[0] = monitor; n_monitors = 1; } } - if ( !n_monitors ) - { + if ( !n_monitors ) { Error( "No monitors found" ); exit ( -1 ); } @@ -258,8 +235,8 @@ int main( int argc, char *argv[] ) sigaddset( &block_set, SIGUSR1 ); sigaddset( &block_set, SIGUSR2 ); - if ( monitors[0]->PrimeCapture() < 0 ) - { + monitors[0]->setStartupTime( (time_t)time(NULL) ); + if ( monitors[0]->PrimeCapture() < 0 ) { Error( "Failed to prime capture of initial monitor" ); exit( -1 ); } @@ -268,8 +245,7 @@ int main( int argc, char *argv[] ) long *alarm_capture_delays = new long[n_monitors]; long *next_delays = new long[n_monitors]; struct timeval * last_capture_times = new struct timeval[n_monitors]; - for ( int i = 0; i < n_monitors; i++ ) - { + for ( int i = 0; i < n_monitors; i++ ) { last_capture_times[i].tv_sec = last_capture_times[i].tv_usec = 0; capture_delays[i] = monitors[i]->GetCaptureDelay(); alarm_capture_delays[i] = monitors[i]->GetAlarmCaptureDelay(); @@ -278,18 +254,14 @@ int main( int argc, char *argv[] ) int result = 0; struct timeval now; struct DeltaTimeval delta_time; - while( !zm_terminate ) - { + while( !zm_terminate ) { sigprocmask( SIG_BLOCK, &block_set, 0 ); - for ( int i = 0; i < n_monitors; i++ ) - { + for ( int i = 0; i < n_monitors; i++ ) { long min_delay = MAXINT; gettimeofday( &now, NULL ); - for ( int j = 0; j < n_monitors; j++ ) - { - if ( last_capture_times[j].tv_sec ) - { + for ( int j = 0; j < n_monitors; j++ ) { + if ( last_capture_times[j].tv_sec ) { DELTA_TIMEVAL( delta_time, now, last_capture_times[j], DT_PREC_3 ); if ( monitors[i]->GetState() == Monitor::ALARM ) next_delays[j] = alarm_capture_delays[j]-delta_time.delta; @@ -297,58 +269,49 @@ int main( int argc, char *argv[] ) next_delays[j] = capture_delays[j]-delta_time.delta; if ( next_delays[j] < 0 ) next_delays[j] = 0; - } - else - { + } else { next_delays[j] = 0; } - if ( next_delays[j] <= min_delay ) - { + if ( next_delays[j] <= min_delay ) { min_delay = next_delays[j]; } } - if ( next_delays[i] <= min_delay || next_delays[i] <= 0 ) - { - if ( monitors[i]->PreCapture() < 0 ) - { + if ( next_delays[i] <= min_delay || next_delays[i] <= 0 ) { + if ( monitors[i]->PreCapture() < 0 ) { Error( "Failed to pre-capture monitor %d %d (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors ); zm_terminate = true; result = -1; break; } - if ( monitors[i]->Capture() < 0 ) - { + if ( monitors[i]->Capture() < 0 ) { Error( "Failed to capture image from monitor %d %s (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors ); zm_terminate = true; result = -1; break; } - if ( monitors[i]->PostCapture() < 0 ) - { + if ( monitors[i]->PostCapture() < 0 ) { Error( "Failed to post-capture monitor %d %s (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors ); zm_terminate = true; result = -1; break; } - if ( next_delays[i] > 0 ) - { + if ( next_delays[i] > 0 ) { gettimeofday( &now, NULL ); DELTA_TIMEVAL( delta_time, now, last_capture_times[i], DT_PREC_3 ); long sleep_time = next_delays[i]-delta_time.delta; - if ( sleep_time > 0 ) - { + if ( sleep_time > 0 ) { usleep( sleep_time*(DT_MAXGRAN/DT_PREC_3) ); } } gettimeofday( &(last_capture_times[i]), NULL ); - } - } + } // end if next_delay <= min_delay || next_delays[i] <= 0 ) + + } // end foreach n_monitors sigprocmask( SIG_UNBLOCK, &block_set, 0 ); - } - for ( int i = 0; i < n_monitors; i++ ) - { + } // end while ! zm_terminate + for ( int i = 0; i < n_monitors; i++ ) { delete monitors[i]; } delete [] monitors; diff --git a/src/zmf.cpp b/src/zmf.cpp index 2245d9ba3..fac1b76c8 100644 --- a/src/zmf.cpp +++ b/src/zmf.cpp @@ -42,9 +42,9 @@ them itself. =head1 OPTIONS - -m, --monitor_id - ID of the monitor to use - -h, --help - Display usage information - -v, --version - Print the installed version of ZoneMinder + -m, --monitor_id - ID of the monitor to use + -h, --help - Display usage information + -v, --version - Print the installed version of ZoneMinder =cut @@ -73,67 +73,67 @@ them itself. int OpenSocket( int monitor_id ) { - int sd = socket( AF_UNIX, SOCK_STREAM, 0); - if ( sd < 0 ) - { - Error( "Can't create socket: %s", strerror(errno) ); - return( -1 ); - } + int sd = socket( AF_UNIX, SOCK_STREAM, 0); + if ( sd < 0 ) + { + Error( "Can't create socket: %s", strerror(errno) ); + return( -1 ); + } - char sock_path[PATH_MAX] = ""; - snprintf( sock_path, sizeof(sock_path), "%s/zmf-%d.sock", config.path_socks, monitor_id ); - if ( unlink( sock_path ) < 0 ) - { - Warning( "Can't unlink '%s': %s", sock_path, strerror(errno) ); - } + char sock_path[PATH_MAX] = ""; + snprintf( sock_path, sizeof(sock_path), "%s/zmf-%d.sock", config.path_socks, monitor_id ); + if ( unlink( sock_path ) < 0 ) + { + Warning( "Can't unlink '%s': %s", sock_path, strerror(errno) ); + } - struct sockaddr_un addr; + struct sockaddr_un addr; - strncpy( addr.sun_path, sock_path, sizeof(addr.sun_path) ); - addr.sun_family = AF_UNIX; + strncpy( addr.sun_path, sock_path, sizeof(addr.sun_path) ); + addr.sun_family = AF_UNIX; - if ( bind( sd, (struct sockaddr *)&addr, strlen(addr.sun_path)+sizeof(addr.sun_family)) < 0 ) - { - Error( "Can't bind: %s", strerror(errno) ); - exit( -1 ); - } + if ( bind( sd, (struct sockaddr *)&addr, strlen(addr.sun_path)+sizeof(addr.sun_family)) < 0 ) + { + Error( "Can't bind: %s", strerror(errno) ); + exit( -1 ); + } - if ( listen( sd, SOMAXCONN ) < 0 ) - { - Error( "Can't listen: %s", strerror(errno) ); - return( -1 ); - } + if ( listen( sd, SOMAXCONN ) < 0 ) + { + Error( "Can't listen: %s", strerror(errno) ); + return( -1 ); + } - struct sockaddr_un rem_addr; - socklen_t rem_addr_len = sizeof(rem_addr); - int new_sd = -1; - if ( (new_sd = accept( sd, (struct sockaddr *)&rem_addr, &rem_addr_len )) < 0 ) - { - Error( "Can't accept: %s", strerror(errno) ); - exit( -1 ); - } - close( sd ); + struct sockaddr_un rem_addr; + socklen_t rem_addr_len = sizeof(rem_addr); + int new_sd = -1; + if ( (new_sd = accept( sd, (struct sockaddr *)&rem_addr, &rem_addr_len )) < 0 ) + { + Error( "Can't accept: %s", strerror(errno) ); + exit( -1 ); + } + close( sd ); - sd = new_sd; + sd = new_sd; - Info( "Frame server socket open, awaiting images" ); - return( sd ); + Info( "Frame server socket open, awaiting images" ); + return( sd ); } int ReopenSocket( int &sd, int monitor_id ) { - close( sd ); - return( sd = OpenSocket( monitor_id ) ); + close( sd ); + return( sd = OpenSocket( monitor_id ) ); } void Usage() { - fprintf( stderr, "zmf -m \n" ); - fprintf( stderr, "Options:\n" ); - fprintf( stderr, " -m, --monitor : Specify which monitor to use\n" ); - fprintf( stderr, " -h, --help : This screen\n" ); - fprintf( stderr, " -v, --version : Report the installed version of ZoneMinder\n" ); - exit( 0 ); + fprintf( stderr, "zmf -m \n" ); + fprintf( stderr, "Options:\n" ); + fprintf( stderr, " -m, --monitor : Specify which monitor to use\n" ); + fprintf( stderr, " -h, --help : This screen\n" ); + fprintf( stderr, " -v, --version : Report the installed version of ZoneMinder\n" ); + exit( 0 ); } int main( int argc, char *argv[] ) @@ -202,7 +202,7 @@ int main( int argc, char *argv[] ) logInit( "zmf" ); - ssedetect(); + hwcaps_detect(); Monitor *monitor = Monitor::Load( id, false, Monitor::QUERY ); @@ -212,10 +212,12 @@ int main( int argc, char *argv[] ) exit( -1 ); } + Storage *Storage = monitor->getStorage(); + char capt_path[PATH_MAX]; char anal_path[PATH_MAX]; - snprintf( capt_path, sizeof(capt_path), "%s/%d/%%s/%%0%dd-capture.jpg", config.dir_events, monitor->Id(), config.event_image_digits ); - snprintf( anal_path, sizeof(anal_path), "%s/%d/%%s/%%0%dd-analyse.jpg", config.dir_events, monitor->Id(), config.event_image_digits ); + snprintf( capt_path, sizeof(capt_path), "%s/%d/%%s/%%0%dd-capture.jpg", Storage->Path(), monitor->Id(), config.event_image_digits ); + snprintf( anal_path, sizeof(anal_path), "%s/%d/%%s/%%0%dd-analyse.jpg", Storage->Path(), monitor->Id(), config.event_image_digits ); zmSetDefaultTermHandler(); zmSetDefaultDieHandler(); @@ -285,66 +287,67 @@ int main( int argc, char *argv[] ) // print some informational messages if (bytes_read == 0) { - Debug(4,"Image read : Short read %d bytes of %d expected bytes",n_bytes,frame_header.image_length); - } - else if (bytes_read+n_bytes == (int)frame_header.image_length) + Debug(4,"Image read : Short read %d bytes of %d expected bytes",n_bytes,frame_header.image_length); + } + else if (bytes_read+n_bytes == (int)frame_header.image_length) + { + Debug(5,"Image read : Read rest of short read: %d bytes read total of %d bytes",n_bytes,frame_header.image_length); + } + else + { + Debug(6,"Image read : continuing, read %d bytes (%d so far)", n_bytes, bytes_read+n_bytes); + } + } + bytes_read+= n_bytes; + } while (n_bytes>0 && (bytes_read < (ssize_t)frame_header.image_length) ); + + // Print errors if there was a problem + if ( n_bytes < 1 ) { - Debug(5,"Image read : Read rest of short read: %d bytes read total of %d bytes",n_bytes,frame_header.image_length); + + Error( "Only read %d bytes of %d\n", bytes_read, frame_header.image_length); + if ( n_bytes < 0 ) + { + Error( "Can't read frame image data: %s", strerror(errno) ); + } + else + { + Warning( "Socket closed at remote end" ); + } + ReopenSocket( sd, monitor->Id() ); + continue; + } + + static char subpath[PATH_MAX] = ""; + if ( config.use_deep_storage ) + { + struct tm *time = localtime( &frame_header.event_time ); + snprintf( subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec ); } else { - Debug(6,"Image read : continuing, read %d bytes (%d so far)", n_bytes, bytes_read+n_bytes); + snprintf( subpath, sizeof(subpath), "%ld", frame_header.event_id ); } - } - bytes_read+= n_bytes; - } while (n_bytes>0 && (bytes_read < (ssize_t)frame_header.image_length) ); - // Print errors if there was a problem - if ( n_bytes < 1 ) - { - Error( "Only read %d bytes of %d\n", bytes_read, frame_header.image_length); - if ( n_bytes < 0 ) - { - Error( "Can't read frame image data: %s", strerror(errno) ); - } - else - { - Warning( "Socket closed at remote end" ); - } - ReopenSocket( sd, monitor->Id() ); - continue; - } + static char path[PATH_MAX] = ""; + snprintf( path, sizeof(path), frame_header.alarm_frame?anal_path:capt_path, subpath, frame_header.frame_id ); + Debug( 1, "Got image, writing to %s", path ); - static char subpath[PATH_MAX] = ""; - if ( config.use_deep_storage ) - { - struct tm *time = localtime( &frame_header.event_time ); - snprintf( subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec ); - } - else - { - snprintf( subpath, sizeof(subpath), "%ld", frame_header.event_id ); - } + FILE *fd = 0; + if ( (fd = fopen( path, "w" )) < 0 ) + { + Error( "Can't fopen '%s': %s", path, strerror(errno) ); + exit( -1 ); + } + if ( 0 == fwrite( image_data, frame_header.image_length, 1, fd ) ) + { + Error( "Can't fwrite image data: %s", strerror(errno) ); + exit( -1 ); + } + fclose( fd ); - static char path[PATH_MAX] = ""; - snprintf( path, sizeof(path), frame_header.alarm_frame?anal_path:capt_path, subpath, frame_header.frame_id ); - Debug( 1, "Got image, writing to %s", path ); - - FILE *fd = 0; - if ( (fd = fopen( path, "w" )) < 0 ) - { - Error( "Can't fopen '%s': %s", path, strerror(errno) ); - exit( -1 ); - } - if ( 0 == fwrite( image_data, frame_header.image_length, 1, fd ) ) - { - Error( "Can't fwrite image data: %s", strerror(errno) ); - exit( -1 ); - } - fclose( fd ); - - sigprocmask( SIG_UNBLOCK, &block_set, 0 ); - } - logTerm(); - zmDbClose(); + sigprocmask( SIG_UNBLOCK, &block_set, 0 ); + } + logTerm(); + zmDbClose(); } diff --git a/src/zms.cpp b/src/zms.cpp index 87eaf3202..4deacb03b 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -26,234 +26,209 @@ #include "zm_signal.h" #include "zm_monitor.h" -bool ValidateAccess( User *user, int mon_id ) -{ - bool allowed = true; +bool ValidateAccess( User *user, int mon_id ) { + bool allowed = true; - if ( mon_id > 0 ) - { - if ( user->getStream() < User::PERM_VIEW ) - allowed = false; - if ( !user->canAccess( mon_id ) ) - allowed = false; - } - else - { - if ( user->getEvents() < User::PERM_VIEW ) - allowed = false; - } - if ( !allowed ) - { - Error( "Error, insufficient privileges for requested action" ); - exit( -1 ); - } - return( allowed ); + if ( mon_id > 0 ) { + if ( user->getStream() < User::PERM_VIEW ) + allowed = false; + if ( !user->canAccess( mon_id ) ) + allowed = false; + } else { + if ( user->getEvents() < User::PERM_VIEW ) + allowed = false; + } + if ( !allowed ) { + Error( "Error, insufficient privileges for requested action" ); + exit( -1 ); + } else { + Debug( 1, "User allowed."); + } + return( allowed ); } int main( int argc, const char *argv[] ) { - self = argv[0]; + self = argv[0]; - srand( getpid() * time( 0 ) ); + srand( getpid() * time( 0 ) ); - enum { ZMS_MONITOR, ZMS_EVENT } source = ZMS_MONITOR; - enum { ZMS_JPEG, ZMS_MPEG, ZMS_RAW, ZMS_ZIP, ZMS_SINGLE } mode = ZMS_JPEG; - char format[32] = ""; - int monitor_id = 0; - time_t event_time = 0; - int event_id = 0; - unsigned int frame_id = 1; - unsigned int scale = 100; - unsigned int rate = 100; - double maxfps = 10.0; - unsigned int bitrate = 100000; - unsigned int ttl = 0; + enum { ZMS_MONITOR, ZMS_EVENT } source = ZMS_MONITOR; + enum { ZMS_JPEG, ZMS_MPEG, ZMS_RAW, ZMS_ZIP, ZMS_SINGLE } mode = ZMS_JPEG; + char format[32] = ""; + int monitor_id = 0; + time_t event_time = 0; + int event_id = 0; + unsigned int frame_id = 1; + unsigned int scale = 100; + unsigned int rate = 100; + double maxfps = 10.0; + unsigned int bitrate = 100000; + unsigned int ttl = 0; EventStream::StreamMode replay = EventStream::MODE_SINGLE; - char username[64] = ""; - char password[64] = ""; + std::string username; + std::string password; char auth[64] = ""; unsigned int connkey = 0; unsigned int playback_buffer = 0; - bool nph = false; - const char *basename = strrchr( argv[0], '/' ); - if (basename) //if we found a / lets skip past it - basename++; - 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) ) ) - { - nph = true; - } - - zmLoadConfig(); + bool nph = false; + const char *basename = strrchr( argv[0], '/' ); + if (basename) //if we found a / lets skip past it + basename++; + 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) ) ) { + nph = true; + } + + zmLoadConfig(); - logInit( "zms" ); + logInit( "zms" ); - ssedetect(); + hwcaps_detect(); - zmSetDefaultTermHandler(); - zmSetDefaultDieHandler(); + zmSetDefaultTermHandler(); + zmSetDefaultDieHandler(); - const char *query = getenv( "QUERY_STRING" ); - if ( query ) - { - Debug( 1, "Query: %s", query ); - - char temp_query[1024]; - strncpy( temp_query, query, sizeof(temp_query) ); - 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, "&" )) ) - { - parm_no++; - q_ptr = NULL; - } - - for ( int p = 0; p < parm_no; p++ ) - { - char *name = strtok( parms[p], "=" ); - char *value = strtok( NULL, "=" ); + const char *query = getenv( "QUERY_STRING" ); + if ( query ) { + Debug( 1, "Query: %s", query ); + + char temp_query[1024]; + strncpy( temp_query, query, sizeof(temp_query) ); + 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, "&" )) ) { + parm_no++; + q_ptr = NULL; + } + + 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" ) ) - { - source = !strcmp( value, "event" )?ZMS_EVENT:ZMS_MONITOR; - } - 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" ) ) - strncpy( format, value, sizeof(format) ); - else if ( !strcmp( name, "monitor" ) ) - monitor_id = atoi( value ); - else if ( !strcmp( name, "time" ) ) - event_time = atoi( value ); - else if ( !strcmp( name, "event" ) ) - event_id = strtoull( value, (char **)NULL, 10 ); - else if ( !strcmp( name, "frame" ) ) - frame_id = strtoull( value, (char **)NULL, 10 ); - else if ( !strcmp( name, "scale" ) ) - scale = atoi( value ); - else if ( !strcmp( name, "rate" ) ) - rate = atoi( value ); - else if ( !strcmp( name, "maxfps" ) ) - maxfps = atof( value ); - else if ( !strcmp( name, "bitrate" ) ) - bitrate = atoi( value ); - else if ( !strcmp( name, "ttl" ) ) - ttl = atoi(value); - 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" ) ) - connkey = atoi(value); - 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" ) ) - { - strncpy( username, value, sizeof(username) ); - } - } - else - { - //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) - { - if ( !strcmp( name, "auth" ) ) - { - strncpy( auth, value, sizeof(auth) ); - } - } - //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) - { - if ( !strcmp( name, "user" ) ) - { - strncpy( username, value, sizeof(username) ); - } - if ( !strcmp( name, "pass" ) ) - { - strncpy( password, value, sizeof(password) ); - } - } - } - } - } - } + if ( !strcmp( name, "source" ) ) { + source = !strcmp( value, "event" )?ZMS_EVENT:ZMS_MONITOR; + } 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" ) ) + strncpy( format, value, sizeof(format) ); + else if ( !strcmp( name, "monitor" ) ) + monitor_id = atoi( value ); + else if ( !strcmp( name, "time" ) ) + event_time = atoi( value ); + else if ( !strcmp( name, "event" ) ) + event_id = strtoull( value, (char **)NULL, 10 ); + else if ( !strcmp( name, "frame" ) ) + frame_id = strtoull( value, (char **)NULL, 10 ); + else if ( !strcmp( name, "scale" ) ) + scale = atoi( value ); + else if ( !strcmp( name, "rate" ) ) + rate = atoi( value ); + else if ( !strcmp( name, "maxfps" ) ) + maxfps = atof( value ); + else if ( !strcmp( name, "bitrate" ) ) + bitrate = atoi( value ); + else if ( !strcmp( name, "ttl" ) ) + ttl = atoi(value); + 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" ) ) + connkey = atoi(value); + 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" ) ) { + username = UriDecode( value ); + } + } else { + //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) + { + if ( !strcmp( name, "auth" ) ) { + strncpy( auth, value, sizeof(auth) ); + } + } + //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) + { + if ( !strcmp( name, "user" ) ) { + username = UriDecode( value ); + Debug( 1, "Have %s for username", username.c_str() ); + } + if ( !strcmp( name, "pass" ) ) { + password = UriDecode( value ); + Debug( 1, "Have %s for password", password.c_str() ); + } + } + } + } + } // end foreach parm + } // end if query - if ( config.opt_use_auth ) - { - User *user = 0; + if ( config.opt_use_auth ) { + User *user = 0; - if ( strcmp( config.auth_relay, "none" ) == 0 ) - { - if ( *username ) - { - user = zmLoadUser( username ); - } - } - else - { - //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) - { - if ( *auth ) - { - user = zmLoadAuthUser( auth, config.auth_hash_ips ); - } - } - //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) - { - if ( *username && *password ) - { - user = zmLoadUser( username, password ); - } - } - } - if ( !user ) - { - Error( "Unable to authenticate user" ); - logTerm(); - zmDbClose(); - return( -1 ); - } - ValidateAccess( user, monitor_id ); - } + if ( strcmp( config.auth_relay, "none" ) == 0 ) { + if ( username.length() ) { + user = zmLoadUser( username.c_str() ); + } + } else { + //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) + { + if ( *auth ) { + user = zmLoadAuthUser( auth, config.auth_hash_ips ); + } else { + Debug( 1, "Need both username and password" ); + } + } + //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) + { + if ( username.length() && password.length() ) { + user = zmLoadUser( username.c_str(), password.c_str() ); + } else { + Debug( 1, "Need both username and password" ); + } + } + } // auth is none or something else + if ( !user ) { + Error( "Unable to authenticate user" ); + logTerm(); + zmDbClose(); + return( -1 ); + } + ValidateAccess( user, monitor_id ); + } // end if use_auth - setbuf( stdout, 0 ); - if ( nph ) - { - fprintf( stdout, "HTTP/1.0 200 OK\r\n" ); - } - fprintf( stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION ); - - time_t now = time( 0 ); - char date_string[64]; - strftime( date_string, sizeof(date_string)-1, "%a, %d %b %Y %H:%M:%S GMT", gmtime( &now ) ); + setbuf( stdout, 0 ); + if ( nph ) { + fprintf( stdout, "HTTP/1.0 200 OK\r\n" ); + } + fprintf( stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION ); + + time_t now = time( 0 ); + char date_string[64]; + strftime( date_string, sizeof(date_string)-1, "%a, %d %b %Y %H:%M:%S GMT", gmtime( &now ) ); - fprintf( stdout, "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" ); - fprintf( stdout, "Last-Modified: %s\r\n", date_string ); - fprintf( stdout, "Cache-Control: no-store, no-cache, must-revalidate\r\n" ); - fprintf( stdout, "Cache-Control: post-check=0, pre-check=0\r\n" ); - fprintf( stdout, "Pragma: no-cache\r\n"); - // Removed as causing more problems than it fixed. - //if ( !nph ) - //{ - //fprintf( stdout, "Content-Length: 0\r\n"); - //} + fprintf( stdout, "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" ); + fprintf( stdout, "Last-Modified: %s\r\n", date_string ); + fprintf( stdout, "Cache-Control: no-store, no-cache, must-revalidate\r\n" ); + fprintf( stdout, "Cache-Control: post-check=0, pre-check=0\r\n" ); + fprintf( stdout, "Pragma: no-cache\r\n"); + // Removed as causing more problems than it fixed. + //if ( !nph ) + //{ + //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 +244,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 +266,24 @@ int main( int argc, const char *argv[] ) #endif // HAVE_LIBAVCODEC } stream.runStream(); - } - else if ( source == ZMS_EVENT ) - { + } else if ( source == ZMS_EVENT ) { + if ( ! event_id ) { + Fatal( "Can't view an event without specifying an event_id." ); + } 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 ); @@ -334,9 +295,9 @@ int main( int argc, const char *argv[] ) zmDbClose(); return( -1 ); #endif // HAVE_LIBAVCODEC - } + } // end if jpeg or mpeg stream.runStream(); - } + } // end if monitor or event logTerm(); zmDbClose(); diff --git a/src/zmstreamer.cpp b/src/zmstreamer.cpp index 74cc01226..3a4e74d9f 100644 --- a/src/zmstreamer.cpp +++ b/src/zmstreamer.cpp @@ -200,7 +200,7 @@ int main(int argc, char** argv) { logInit("zmstreamer"); - ssedetect(); + hwcaps_detect(); // Setting stream parameters MonitorStream stream; diff --git a/src/zmu.cpp b/src/zmu.cpp index 21b623c4b..d62fe0ff2 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -41,46 +41,46 @@ problems. =head1 OPTIONS General options: - -v, --verbose - Produce more verbose output - -l, --list - List the current status of active (or all with -v) monitors - -h, --help - Display usage information - -v, --version - Print the installed version of ZoneMinder + -v, --verbose - Produce more verbose output + -l, --list - List the current status of active (or all with -v) monitors + -h, --help - Display usage information + -v, --version - Print the installed version of ZoneMinder Options for use with devices: - -d, --device [device_path] - Get the current video device settings for [device_path] or all devices - -V, --version - Set the Video 4 Linux API version to use for the query, use 1 or 2 - -q, --query - Query the current settings for the device + -d, --device [device_path] - Get the current video device settings for [device_path] or all devices + -V, --version - Set the Video 4 Linux API version to use for the query, use 1 or 2 + -q, --query - Query the current settings for the device Options for use with monitors: - -m, --monitor - Specify which monitor to address, default 1 if absent - -q, --query - Query the current settings for the monitor - -s, --state - Output the current monitor state, 0 = idle, 1 = prealarm, 2 = alarm, - 3 = alert, 4 = tape - -B, --brightness [value] - Output the current brightness, set to value if given - -C, --contrast [value] - Output the current contrast, set to value if given - -H, --hue [value] - Output the current hue, set to value if given - -O, --colour [value] - Output the current colour, set to value if given - -i, --image [image_index] - Write captured image to disk as .jpg, last image captured - or specified ring buffer index if given. - -S, --scale - With --image specify any scaling (in %%) to be applied to the image - -t, --timestamp [image_index] - Output captured image timestamp, last image captured or specified - ring buffer index if given - -R, --read_index - Output ring buffer read index - -W, --write_index - Output ring buffer write index - -e, --event - Output last event index - -f, --fps - Output last Frames Per Second captured reading - -z, --zones - Write last captured image overlaid with zones to -Zones.jpg - -a, --alarm - Force alarm in monitor, this will trigger recording until cancelled with -c - -n, --noalarm - Force no alarms in monitor, this will prevent alarms until cancelled with -c - -c, --cancel - Cancel a forced alarm/noalarm in monitor, required after being enabled with -a or -n - -L, --reload - Signal monitor to reload settings - -E, --enable - Enable detection, wake monitor up - -D, --disable - Disable detection, put monitor to sleep - -u, --suspend - Suspend detection, useful to prevent bogus alarms when panning etc - -r, --resume - Resume detection after a suspend - -U, --username - When running in authenticated mode the username and - -P, --password - password combination of the given user - -A, --auth - Pass authentication hash string instead of user details + -m, --monitor - Specify which monitor to address, default 1 if absent + -q, --query - Query the current settings for the monitor + -s, --state - Output the current monitor state, 0 = idle, 1 = prealarm, 2 = alarm, + 3 = alert, 4 = tape + -B, --brightness [value] - Output the current brightness, set to value if given + -C, --contrast [value] - Output the current contrast, set to value if given + -H, --hue [value] - Output the current hue, set to value if given + -O, --colour [value] - Output the current colour, set to value if given + -i, --image [image_index] - Write captured image to disk as .jpg, last image captured + or specified ring buffer index if given. + -S, --scale - With --image specify any scaling (in %%) to be applied to the image + -t, --timestamp [image_index] - Output captured image timestamp, last image captured or specified + ring buffer index if given + -R, --read_index - Output ring buffer read index + -W, --write_index - Output ring buffer write index + -e, --event - Output last event index + -f, --fps - Output last Frames Per Second captured reading + -z, --zones - Write last captured image overlaid with zones to -Zones.jpg + -a, --alarm - Force alarm in monitor, this will trigger recording until cancelled with -c + -n, --noalarm - Force no alarms in monitor, this will prevent alarms until cancelled with -c + -c, --cancel - Cancel a forced alarm/noalarm in monitor, required after being enabled with -a or -n + -L, --reload - Signal monitor to reload settings + -E, --enable - Enable detection, wake monitor up + -D, --disable - Disable detection, put monitor to sleep + -u, --suspend - Suspend detection, useful to prevent bogus alarms when panning etc + -r, --resume - Resume detection after a suspend + -U, --username - When running in authenticated mode the username and + -P, --password - password combination of the given user + -A, --auth - Pass authentication hash string instead of user details =cut @@ -97,739 +97,738 @@ Options for use with monitors: void Usage( int status=-1 ) { - fprintf( stderr, "zmu <-d device_path> [-v] [function] [-U -P]\n" ); - fprintf( stderr, "zmu <-m monitor_id> [-v] [function] [-U -P]\n" ); - fprintf( stderr, "General options:\n" ); - fprintf( stderr, " -h, --help : This screen\n" ); - fprintf( stderr, " -v, --verbose : Produce more verbose output\n" ); - fprintf( stderr, " -l, --list : List the current status of active (or all with -v) monitors\n" ); - fprintf( stderr, "Options for use with devices:\n" ); - fprintf( stderr, " -d, --device [device_path] : Get the current video device settings for [device_path] or all devices\n" ); - fprintf( stderr, " -V, --version : Set the Video 4 Linux API version to use for the query, use 1 or 2\n" ); - fprintf( stderr, " -q, --query : Query the current settings for the device\n" ); - fprintf( stderr, "Options for use with monitors:\n" ); - fprintf( stderr, " -m, --monitor : Specify which monitor to address, default 1 if absent\n" ); - fprintf( stderr, " -q, --query : Query the current settings for the monitor\n" ); - fprintf( stderr, " -s, --state : Output the current monitor state, 0 = idle, 1 = prealarm, 2 = alarm,\n" ); - fprintf( stderr, " 3 = alert, 4 = tape\n" ); - fprintf( stderr, " -B, --brightness [value] : Output the current brightness, set to value if given \n" ); - fprintf( stderr, " -C, --contrast [value] : Output the current contrast, set to value if given \n" ); - fprintf( stderr, " -H, --hue [value] : Output the current hue, set to value if given \n" ); - fprintf( stderr, " -O, --colour [value] : Output the current colour, set to value if given \n" ); - fprintf( stderr, " -i, --image [image_index] : Write captured image to disk as .jpg, last image captured\n" ); - fprintf( stderr, " or specified ring buffer index if given.\n" ); - fprintf( stderr, " -S, --scale : With --image specify any scaling (in %%) to be applied to the image\n" ); - fprintf( stderr, " -t, --timestamp [image_index] : Output captured image timestamp, last image captured or specified\n" ); - fprintf( stderr, " ring buffer index if given\n" ); - fprintf( stderr, " -R, --read_index : Output ring buffer read index\n" ); - fprintf( stderr, " -W, --write_index : Output ring buffer write index\n" ); - fprintf( stderr, " -e, --event : Output last event index\n" ); - fprintf( stderr, " -f, --fps : Output last Frames Per Second captured reading\n" ); - fprintf( stderr, " -z, --zones : Write last captured image overlaid with zones to -Zones.jpg\n" ); - fprintf( stderr, " -a, --alarm : Force alarm in monitor, this will trigger recording until cancelled with -c\n" ); - fprintf( stderr, " -n, --noalarm : Force no alarms in monitor, this will prevent alarms until cancelled with -c\n" ); - fprintf( stderr, " -c, --cancel : Cancel a forced alarm/noalarm in monitor, required after being enabled with -a or -n\n" ); - fprintf( stderr, " -L, --reload : Signal monitor to reload settings\n" ); - fprintf( stderr, " -E, --enable : Enable detection, wake monitor up\n" ); - fprintf( stderr, " -D, --disable : Disable detection, put monitor to sleep\n" ); - fprintf( stderr, " -u, --suspend : Suspend detection, useful to prevent bogus alarms when panning etc\n" ); - fprintf( stderr, " -r, --resume : Resume detection after a suspend\n" ); - fprintf( stderr, " -U, --username : When running in authenticated mode the username and\n" ); - fprintf( stderr, " -P, --password : password combination of the given user\n" ); - fprintf( stderr, " -A, --auth : Pass authentication hash string instead of user details\n" ); + fprintf( stderr, "zmu <-d device_path> [-v] [function] [-U -P]\n" ); + fprintf( stderr, "zmu <-m monitor_id> [-v] [function] [-U -P]\n" ); + fprintf( stderr, "General options:\n" ); + fprintf( stderr, " -h, --help : This screen\n" ); + fprintf( stderr, " -v, --verbose : Produce more verbose output\n" ); + fprintf( stderr, " -l, --list : List the current status of active (or all with -v) monitors\n" ); + fprintf( stderr, "Options for use with devices:\n" ); + fprintf( stderr, " -d, --device [device_path] : Get the current video device settings for [device_path] or all devices\n" ); + fprintf( stderr, " -V, --version : Set the Video 4 Linux API version to use for the query, use 1 or 2\n" ); + fprintf( stderr, " -q, --query : Query the current settings for the device\n" ); + fprintf( stderr, "Options for use with monitors:\n" ); + fprintf( stderr, " -m, --monitor : Specify which monitor to address, default 1 if absent\n" ); + fprintf( stderr, " -q, --query : Query the current settings for the monitor\n" ); + fprintf( stderr, " -s, --state : Output the current monitor state, 0 = idle, 1 = prealarm, 2 = alarm,\n" ); + fprintf( stderr, " 3 = alert, 4 = tape\n" ); + fprintf( stderr, " -B, --brightness [value] : Output the current brightness, set to value if given \n" ); + fprintf( stderr, " -C, --contrast [value] : Output the current contrast, set to value if given \n" ); + fprintf( stderr, " -H, --hue [value] : Output the current hue, set to value if given \n" ); + fprintf( stderr, " -O, --colour [value] : Output the current colour, set to value if given \n" ); + fprintf( stderr, " -i, --image [image_index] : Write captured image to disk as .jpg, last image captured\n" ); + fprintf( stderr, " or specified ring buffer index if given.\n" ); + fprintf( stderr, " -S, --scale : With --image specify any scaling (in %%) to be applied to the image\n" ); + fprintf( stderr, " -t, --timestamp [image_index] : Output captured image timestamp, last image captured or specified\n" ); + fprintf( stderr, " ring buffer index if given\n" ); + fprintf( stderr, " -R, --read_index : Output ring buffer read index\n" ); + fprintf( stderr, " -W, --write_index : Output ring buffer write index\n" ); + fprintf( stderr, " -e, --event : Output last event index\n" ); + fprintf( stderr, " -f, --fps : Output last Frames Per Second captured reading\n" ); + fprintf( stderr, " -z, --zones : Write last captured image overlaid with zones to -Zones.jpg\n" ); + fprintf( stderr, " -a, --alarm : Force alarm in monitor, this will trigger recording until cancelled with -c\n" ); + fprintf( stderr, " -n, --noalarm : Force no alarms in monitor, this will prevent alarms until cancelled with -c\n" ); + fprintf( stderr, " -c, --cancel : Cancel a forced alarm/noalarm in monitor, required after being enabled with -a or -n\n" ); + fprintf( stderr, " -L, --reload : Signal monitor to reload settings\n" ); + fprintf( stderr, " -E, --enable : Enable detection, wake monitor up\n" ); + fprintf( stderr, " -D, --disable : Disable detection, put monitor to sleep\n" ); + fprintf( stderr, " -u, --suspend : Suspend detection, useful to prevent bogus alarms when panning etc\n" ); + fprintf( stderr, " -r, --resume : Resume detection after a suspend\n" ); + fprintf( stderr, " -U, --username : When running in authenticated mode the username and\n" ); + fprintf( stderr, " -P, --password : password combination of the given user\n" ); + fprintf( stderr, " -A, --auth : Pass authentication hash string instead of user details\n" ); - exit( status ); + exit( status ); } typedef enum { - ZMU_BOGUS = 0x00000000, - ZMU_STATE = 0x00000001, - ZMU_IMAGE = 0x00000002, - ZMU_TIME = 0x00000004, - ZMU_READ_IDX = 0x00000008, - ZMU_WRITE_IDX = 0x00000010, - ZMU_EVENT = 0x00000020, - ZMU_FPS = 0x00000040, - ZMU_ZONES = 0x00000080, - ZMU_ALARM = 0x00000100, - ZMU_NOALARM = 0x00000200, - ZMU_CANCEL = 0x00000400, - ZMU_QUERY = 0x00000800, - ZMU_BRIGHTNESS = 0x00001000, - ZMU_CONTRAST = 0x00002000, - ZMU_HUE = 0x00004000, - ZMU_COLOUR = 0x00008000, - ZMU_RELOAD = 0x00010000, - ZMU_ENABLE = 0x00100000, - ZMU_DISABLE = 0x00200000, - ZMU_SUSPEND = 0x00400000, - ZMU_RESUME = 0x00800000, - ZMU_LIST = 0x10000000, + ZMU_BOGUS = 0x00000000, + ZMU_STATE = 0x00000001, + ZMU_IMAGE = 0x00000002, + ZMU_TIME = 0x00000004, + ZMU_READ_IDX = 0x00000008, + ZMU_WRITE_IDX = 0x00000010, + ZMU_EVENT = 0x00000020, + ZMU_FPS = 0x00000040, + ZMU_ZONES = 0x00000080, + ZMU_ALARM = 0x00000100, + ZMU_NOALARM = 0x00000200, + ZMU_CANCEL = 0x00000400, + ZMU_QUERY = 0x00000800, + ZMU_BRIGHTNESS = 0x00001000, + ZMU_CONTRAST = 0x00002000, + ZMU_HUE = 0x00004000, + ZMU_COLOUR = 0x00008000, + ZMU_RELOAD = 0x00010000, + ZMU_ENABLE = 0x00100000, + ZMU_DISABLE = 0x00200000, + ZMU_SUSPEND = 0x00400000, + ZMU_RESUME = 0x00800000, + ZMU_LIST = 0x10000000, } Function; bool ValidateAccess( User *user, int mon_id, int function ) { - bool allowed = true; - if ( function & (ZMU_STATE|ZMU_IMAGE|ZMU_TIME|ZMU_READ_IDX|ZMU_WRITE_IDX|ZMU_FPS) ) - { - if ( user->getStream() < User::PERM_VIEW ) - allowed = false; - } - if ( function & ZMU_EVENT ) - { - if ( user->getEvents() < User::PERM_VIEW ) - allowed = false; - } - if ( function & (ZMU_ZONES|ZMU_QUERY|ZMU_LIST) ) - { - if ( user->getMonitors() < User::PERM_VIEW ) - allowed = false; - } - if ( function & (ZMU_ALARM|ZMU_NOALARM|ZMU_CANCEL|ZMU_RELOAD|ZMU_ENABLE|ZMU_DISABLE|ZMU_SUSPEND|ZMU_RESUME|ZMU_BRIGHTNESS|ZMU_CONTRAST|ZMU_HUE|ZMU_COLOUR) ) - { - if ( user->getMonitors() < User::PERM_EDIT ) - allowed = false; - } - if ( mon_id > 0 ) - { - if ( !user->canAccess( mon_id ) ) - { - allowed = false; - } - } - if ( !allowed ) - { - fprintf( stderr, "Error, insufficient privileges for requested action\n" ); - exit( -1 ); - } - return( allowed ); + bool allowed = true; + if ( function & (ZMU_STATE|ZMU_IMAGE|ZMU_TIME|ZMU_READ_IDX|ZMU_WRITE_IDX|ZMU_FPS) ) + { + if ( user->getStream() < User::PERM_VIEW ) + allowed = false; + } + if ( function & ZMU_EVENT ) + { + if ( user->getEvents() < User::PERM_VIEW ) + allowed = false; + } + if ( function & (ZMU_ZONES|ZMU_QUERY|ZMU_LIST) ) + { + if ( user->getMonitors() < User::PERM_VIEW ) + allowed = false; + } + if ( function & (ZMU_ALARM|ZMU_NOALARM|ZMU_CANCEL|ZMU_RELOAD|ZMU_ENABLE|ZMU_DISABLE|ZMU_SUSPEND|ZMU_RESUME|ZMU_BRIGHTNESS|ZMU_CONTRAST|ZMU_HUE|ZMU_COLOUR) ) + { + if ( user->getMonitors() < User::PERM_EDIT ) + allowed = false; + } + if ( mon_id > 0 ) + { + if ( !user->canAccess( mon_id ) ) + { + allowed = false; + } + } + if ( !allowed ) + { + fprintf( stderr, "Error, insufficient privileges for requested action\n" ); + exit( -1 ); + } + return( allowed ); } int main( int argc, char *argv[] ) { - if ( access(ZM_CONFIG, R_OK) != 0 ) - { - fprintf( stderr, "Can't open %s: %s\n", ZM_CONFIG, strerror(errno) ); - exit( -1 ); - } + if ( access(ZM_CONFIG, R_OK) != 0 ) + { + fprintf( stderr, "Can't open %s: %s\n", ZM_CONFIG, strerror(errno) ); + exit( -1 ); + } - self = argv[0]; + self = argv[0]; - srand( getpid() * time( 0 ) ); + srand( getpid() * time( 0 ) ); - static struct option long_options[] = { - {"device", 2, 0, 'd'}, - {"monitor", 1, 0, 'm'}, - {"verbose", 0, 0, 'v'}, - {"image", 2, 0, 'i'}, - {"scale", 1, 0, 'S'}, - {"timestamp", 2, 0, 't'}, - {"state", 0, 0, 's'}, - {"brightness", 2, 0, 'B'}, - {"contrast", 2, 0, 'C'}, - {"hue", 2, 0, 'H'}, - {"contrast", 2, 0, 'O'}, - {"read_index", 0, 0, 'R'}, - {"write_index", 0, 0, 'W'}, - {"event", 0, 0, 'e'}, - {"fps", 0, 0, 'f'}, - {"zones", 2, 0, 'z'}, - {"alarm", 0, 0, 'a'}, - {"noalarm", 0, 0, 'n'}, - {"cancel", 0, 0, 'c'}, - {"reload", 0, 0, 'L'}, - {"enable", 0, 0, 'E'}, - {"disable", 0, 0, 'D'}, - {"suspend", 0, 0, 'u'}, - {"resume", 0, 0, 'r'}, - {"query", 0, 0, 'q'}, - {"username", 1, 0, 'U'}, - {"password", 1, 0, 'P'}, - {"auth", 1, 0, 'A'}, - {"version", 1, 0, 'V'}, - {"help", 0, 0, 'h'}, - {"list", 0, 0, 'l'}, - {0, 0, 0, 0} - }; + static struct option long_options[] = { + {"device", 2, 0, 'd'}, + {"monitor", 1, 0, 'm'}, + {"verbose", 0, 0, 'v'}, + {"image", 2, 0, 'i'}, + {"scale", 1, 0, 'S'}, + {"timestamp", 2, 0, 't'}, + {"state", 0, 0, 's'}, + {"brightness", 2, 0, 'B'}, + {"contrast", 2, 0, 'C'}, + {"hue", 2, 0, 'H'}, + {"contrast", 2, 0, 'O'}, + {"read_index", 0, 0, 'R'}, + {"write_index", 0, 0, 'W'}, + {"event", 0, 0, 'e'}, + {"fps", 0, 0, 'f'}, + {"zones", 2, 0, 'z'}, + {"alarm", 0, 0, 'a'}, + {"noalarm", 0, 0, 'n'}, + {"cancel", 0, 0, 'c'}, + {"reload", 0, 0, 'L'}, + {"enable", 0, 0, 'E'}, + {"disable", 0, 0, 'D'}, + {"suspend", 0, 0, 'u'}, + {"resume", 0, 0, 'r'}, + {"query", 0, 0, 'q'}, + {"username", 1, 0, 'U'}, + {"password", 1, 0, 'P'}, + {"auth", 1, 0, 'A'}, + {"version", 1, 0, 'V'}, + {"help", 0, 0, 'h'}, + {"list", 0, 0, 'l'}, + {0, 0, 0, 0} + }; - const char *device = 0; - int mon_id = 0; - bool verbose = false; - int function = ZMU_BOGUS; + const char *device = 0; + int mon_id = 0; + bool verbose = false; + int function = ZMU_BOGUS; - int image_idx = -1; - int scale = -1; - int brightness = -1; - int contrast = -1; - int hue = -1; - int colour = -1; - char *zoneString = 0; - char *username = 0; - char *password = 0; - char *auth = 0; + int image_idx = -1; + int scale = -1; + int brightness = -1; + int contrast = -1; + int hue = -1; + int colour = -1; + char *zoneString = 0; + char *username = 0; + char *password = 0; + char *auth = 0; #if ZM_HAS_V4L #if ZM_HAS_V4L2 - int v4lVersion = 2; + int v4lVersion = 2; #elif ZM_HAS_V4L1 - int v4lVersion = 1; + int v4lVersion = 1; #endif // ZM_HAS_V4L2/1 #endif // ZM_HAS_V4L - while (1) - { - int option_index = 0; + while (1) + { + int option_index = 0; - int c = getopt_long (argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::U:P:A:V:", long_options, &option_index); - if (c == -1) - { - break; - } + int c = getopt_long (argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::U:P:A:V:", long_options, &option_index); + if (c == -1) + { + break; + } - switch (c) - { - case 'd': - if ( optarg ) - device = optarg; - break; - case 'm': - mon_id = atoi(optarg); - break; - case 'v': - verbose = true; - break; - case 's': - function |= ZMU_STATE; - break; - case 'i': - function |= ZMU_IMAGE; - if ( optarg ) - image_idx = atoi( optarg ); - break; - case 'S': - scale = atoi(optarg); - break; - case 't': - function |= ZMU_TIME; - if ( optarg ) - image_idx = atoi( optarg ); - break; - case 'R': - function |= ZMU_READ_IDX; - break; - case 'W': - function |= ZMU_WRITE_IDX; - break; - case 'e': - function |= ZMU_EVENT; - break; - case 'f': - function |= ZMU_FPS; - break; - case 'z': - function |= ZMU_ZONES; - if ( optarg ) - zoneString = optarg; - break; - case 'a': - function |= ZMU_ALARM; - break; - case 'n': - function |= ZMU_NOALARM; - break; - case 'c': - function |= ZMU_CANCEL; - break; - case 'L': - function |= ZMU_RELOAD; - break; - case 'E': - function |= ZMU_ENABLE; - break; - case 'D': - function |= ZMU_DISABLE; - break; - case 'u': - function |= ZMU_SUSPEND; - break; - case 'r': - function |= ZMU_RESUME; - break; - case 'q': - function |= ZMU_QUERY; - break; - case 'B': - function |= ZMU_BRIGHTNESS; - if ( optarg ) - brightness = atoi( optarg ); - break; - case 'C': - function |= ZMU_CONTRAST; - if ( optarg ) - contrast = atoi( optarg ); - break; - case 'H': - function |= ZMU_HUE; - if ( optarg ) - hue = atoi( optarg ); - break; - case 'O': - function |= ZMU_COLOUR; - if ( optarg ) - colour = atoi( optarg ); - break; - case 'U': - username = optarg; - break; - case 'P': - password = optarg; - break; - case 'A': - auth = optarg; - break; + switch (c) + { + case 'd': + if ( optarg ) + device = optarg; + break; + case 'm': + mon_id = atoi(optarg); + break; + case 'v': + verbose = true; + break; + case 's': + function |= ZMU_STATE; + break; + case 'i': + function |= ZMU_IMAGE; + if ( optarg ) + image_idx = atoi( optarg ); + break; + case 'S': + scale = atoi(optarg); + break; + case 't': + function |= ZMU_TIME; + if ( optarg ) + image_idx = atoi( optarg ); + break; + case 'R': + function |= ZMU_READ_IDX; + break; + case 'W': + function |= ZMU_WRITE_IDX; + break; + case 'e': + function |= ZMU_EVENT; + break; + case 'f': + function |= ZMU_FPS; + break; + case 'z': + function |= ZMU_ZONES; + if ( optarg ) + zoneString = optarg; + break; + case 'a': + function |= ZMU_ALARM; + break; + case 'n': + function |= ZMU_NOALARM; + break; + case 'c': + function |= ZMU_CANCEL; + break; + case 'L': + function |= ZMU_RELOAD; + break; + case 'E': + function |= ZMU_ENABLE; + break; + case 'D': + function |= ZMU_DISABLE; + break; + case 'u': + function |= ZMU_SUSPEND; + break; + case 'r': + function |= ZMU_RESUME; + break; + case 'q': + function |= ZMU_QUERY; + break; + case 'B': + function |= ZMU_BRIGHTNESS; + if ( optarg ) + brightness = atoi( optarg ); + break; + case 'C': + function |= ZMU_CONTRAST; + if ( optarg ) + contrast = atoi( optarg ); + break; + case 'H': + function |= ZMU_HUE; + if ( optarg ) + hue = atoi( optarg ); + break; + case 'O': + function |= ZMU_COLOUR; + if ( optarg ) + colour = atoi( optarg ); + break; + case 'U': + username = optarg; + break; + case 'P': + password = optarg; + break; + case 'A': + auth = optarg; + break; #if ZM_HAS_V4L - case 'V': - v4lVersion = (atoi(optarg)==1)?1:2; - break; + case 'V': + v4lVersion = (atoi(optarg)==1)?1:2; + break; #endif // ZM_HAS_V4L - case 'h': - Usage( 0 ); - break; - case 'l': - function |= ZMU_LIST; - break; - case '?': - Usage(); - break; - default: - //fprintf( stderr, "?? getopt returned character code 0%o ??\n", c ); - break; - } - } + case 'h': + Usage( 0 ); + break; + case 'l': + function |= ZMU_LIST; + break; + case '?': + Usage(); + break; + default: + //fprintf( stderr, "?? getopt returned character code 0%o ??\n", c ); + break; + } + } - if (optind < argc) - { - fprintf( stderr, "Extraneous options, " ); - while (optind < argc) - fprintf( stderr, "%s ", argv[optind++]); - fprintf( stderr, "\n"); - Usage(); - } + if (optind < argc) + { + fprintf( stderr, "Extraneous options, " ); + while (optind < argc) + fprintf( stderr, "%s ", argv[optind++]); + fprintf( stderr, "\n"); + Usage(); + } - if ( device && !(function&ZMU_QUERY) ) - { - fprintf( stderr, "Error, -d option cannot be used with this option\n" ); - Usage(); - } - if ( scale != -1 && !(function&ZMU_IMAGE) ) - { - fprintf( stderr, "Error, -S option cannot be used with this option\n" ); - Usage(); - } - //printf( "Monitor %d, Function %d\n", mon_id, function ); + if ( device && !(function&ZMU_QUERY) ) + { + fprintf( stderr, "Error, -d option cannot be used with this option\n" ); + Usage(); + } + if ( scale != -1 && !(function&ZMU_IMAGE) ) + { + fprintf( stderr, "Error, -S option cannot be used with this option\n" ); + Usage(); + } + //printf( "Monitor %d, Function %d\n", mon_id, function ); - zmLoadConfig(); + zmLoadConfig(); - logInit( "zmu" ); + logInit( "zmu" ); - zmSetDefaultTermHandler(); - zmSetDefaultDieHandler(); + zmSetDefaultTermHandler(); + zmSetDefaultDieHandler(); - User *user = 0; + User *user = 0; - if ( config.opt_use_auth ) - { - if ( strcmp( config.auth_relay, "none" ) == 0 ) - { - if ( !username ) - { - fprintf( stderr, "Error, username must be supplied\n" ); - exit( -1 ); - } + if ( config.opt_use_auth ) + { + if ( strcmp( config.auth_relay, "none" ) == 0 ) + { + if ( !username ) + { + fprintf( stderr, "Error, username must be supplied\n" ); + exit( -1 ); + } - if ( username ) - { - user = zmLoadUser( username ); - } - } - else - { - if ( !(username && password) && !auth ) - { - fprintf( stderr, "Error, username and password or auth string must be supplied\n" ); - exit( -1 ); - } + if ( username ) + { + user = zmLoadUser( username ); + } + } + else + { + if ( !(username && password) && !auth ) + { + fprintf( stderr, "Error, username and password or auth string must be supplied\n" ); + exit( -1 ); + } - //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) - { - if ( auth ) - { - user = zmLoadAuthUser( auth, false ); - } - } - //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) - { - if ( username && password ) - { - user = zmLoadUser( username, password ); - } - } - } - if ( !user ) - { - fprintf( stderr, "Error, unable to authenticate user\n" ); - exit( -1 ); - } - ValidateAccess( user, mon_id, function ); - } - + //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) + { + if ( auth ) + { + user = zmLoadAuthUser( auth, false ); + } + } + //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) + { + if ( username && password ) + { + user = zmLoadUser( username, password ); + } + } + } + if ( !user ) + { + fprintf( stderr, "Error, unable to authenticate user\n" ); + exit( -1 ); + } + ValidateAccess( user, mon_id, function ); + } + - if ( mon_id > 0 ) - { - Monitor *monitor = Monitor::Load( mon_id, function&(ZMU_QUERY|ZMU_ZONES), Monitor::QUERY ); - if ( monitor ) - { - if ( verbose ) - { - printf( "Monitor %d(%s)\n", monitor->Id(), monitor->Name() ); - } - if ( ! monitor->connect() ) { - Error( "Can't connect to capture daemon: %d %s", monitor->Id(), monitor->Name() ); - exit( -1 ); - } + if ( mon_id > 0 ) + { + Monitor *monitor = Monitor::Load( mon_id, function&(ZMU_QUERY|ZMU_ZONES), Monitor::QUERY ); + if ( monitor ) + { + if ( verbose ) + { + printf( "Monitor %d(%s)\n", monitor->Id(), monitor->Name() ); + } + if ( ! monitor->connect() ) { + Error( "Can't connect to capture daemon: %d %s", monitor->Id(), monitor->Name() ); + } - char separator = ' '; - bool have_output = false; - if ( function & ZMU_STATE ) - { - Monitor::State state = monitor->GetState(); - if ( verbose ) - printf( "Current state: %s\n", state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle") ); - else - { - if ( have_output ) printf( "%c", separator ); - printf( "%d", state ); - have_output = true; - } - } - if ( function & ZMU_TIME ) - { - struct timeval timestamp = monitor->GetTimestamp( image_idx ); - if ( verbose ) - { - char timestamp_str[64] = "None"; - if ( timestamp.tv_sec ) - strftime( timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime( ×tamp.tv_sec ) ); - if ( image_idx == -1 ) - printf( "Time of last image capture: %s.%02ld\n", timestamp_str, timestamp.tv_usec/10000 ); - else - printf( "Time of image %d capture: %s.%02ld\n", image_idx, timestamp_str, timestamp.tv_usec/10000 ); - } - else - { - if ( have_output ) printf( "%c", separator ); - printf( "%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000 ); - have_output = true; - } - } - if ( function & ZMU_READ_IDX ) - { - if ( verbose ) - printf( "Last read index: %d\n", monitor->GetLastReadIndex() ); - else - { - if ( have_output ) printf( "%c", separator ); - printf( "%d", monitor->GetLastReadIndex() ); - have_output = true; - } - } - if ( function & ZMU_WRITE_IDX ) - { - if ( verbose ) - printf( "Last write index: %d\n", monitor->GetLastWriteIndex() ); - else - { - if ( have_output ) printf( "%c", separator ); - printf( "%d", monitor->GetLastWriteIndex() ); - have_output = true; - } - } - if ( function & ZMU_EVENT ) - { - if ( verbose ) - printf( "Last event id: %d\n", monitor->GetLastEvent() ); - else - { - if ( have_output ) printf( "%c", separator ); - printf( "%d", monitor->GetLastEvent() ); - have_output = true; - } - } - if ( function & ZMU_FPS ) - { - if ( verbose ) - printf( "Current capture rate: %.2f frames per second\n", monitor->GetFPS() ); - else - { - if ( have_output ) printf( "%c", separator ); - printf( "%.2f", monitor->GetFPS() ); - have_output = true; - } - } - if ( function & ZMU_IMAGE ) - { - if ( verbose ) - { - if ( image_idx == -1 ) - printf( "Dumping last image captured to Monitor%d.jpg", monitor->Id() ); - else - printf( "Dumping buffer image %d to Monitor%d.jpg", image_idx, monitor->Id() ); - if ( scale != -1 ) - printf( ", scaling by %d%%", scale ); - printf( "\n" ); - } - monitor->GetImage( image_idx, scale>0?scale:100 ); - } - if ( function & ZMU_ZONES ) - { - if ( verbose ) - printf( "Dumping zone image to Zones%d.jpg\n", monitor->Id() ); - monitor->DumpZoneImage( zoneString ); - } - if ( function & ZMU_ALARM ) - { - if ( verbose ) - printf( "Forcing alarm on\n" ); - monitor->ForceAlarmOn( config.forced_alarm_score, "Forced Web" ); - } - if ( function & ZMU_NOALARM ) - { - if ( verbose ) - printf( "Forcing alarm off\n" ); - monitor->ForceAlarmOff(); - } - if ( function & ZMU_CANCEL ) - { - if ( verbose ) - printf( "Cancelling forced alarm on/off\n" ); - monitor->CancelForced(); - } - if ( function & ZMU_RELOAD ) - { - if ( verbose ) - printf( "Reloading monitor settings\n" ); - monitor->actionReload(); - } - if ( function & ZMU_ENABLE ) - { - if ( verbose ) - printf( "Enabling event generation\n" ); - monitor->actionEnable(); - } - if ( function & ZMU_DISABLE ) - { - if ( verbose ) - printf( "Disabling event generation\n" ); - monitor->actionDisable(); - } - if ( function & ZMU_SUSPEND ) - { - if ( verbose ) - printf( "Suspending event generation\n" ); - monitor->actionSuspend(); - } - if ( function & ZMU_RESUME ) - { - if ( verbose ) - printf( "Resuming event generation\n" ); - monitor->actionResume(); - } - if ( function & ZMU_QUERY ) - { - char monString[16382] = ""; - monitor->DumpSettings( monString, verbose ); - printf( "%s\n", monString ); - } - if ( function & ZMU_BRIGHTNESS ) - { - if ( verbose ) - { - if ( brightness >= 0 ) - printf( "New brightness: %d\n", monitor->actionBrightness( brightness ) ); - else - printf( "Current brightness: %d\n", monitor->actionBrightness() ); - } - else - { - if ( have_output ) printf( "%c", separator ); - if ( brightness >= 0 ) - printf( "%d", monitor->actionBrightness( brightness ) ); - else - printf( "%d", monitor->actionBrightness() ); - have_output = true; - } - } - if ( function & ZMU_CONTRAST ) - { - if ( verbose ) - { - if ( contrast >= 0 ) - printf( "New brightness: %d\n", monitor->actionContrast( contrast ) ); - else - printf( "Current contrast: %d\n", monitor->actionContrast() ); - } - else - { - if ( have_output ) printf( "%c", separator ); - if ( contrast >= 0 ) - printf( "%d", monitor->actionContrast( contrast ) ); - else - printf( "%d", monitor->actionContrast() ); - have_output = true; - } - } - if ( function & ZMU_HUE ) - { - if ( verbose ) - { - if ( hue >= 0 ) - printf( "New hue: %d\n", monitor->actionHue( hue ) ); - else - printf( "Current hue: %d\n", monitor->actionHue() ); - } - else - { - if ( have_output ) printf( "%c", separator ); - if ( hue >= 0 ) - printf( "%d", monitor->actionHue( hue ) ); - else - printf( "%d", monitor->actionHue() ); - have_output = true; - } - } - if ( function & ZMU_COLOUR ) - { - if ( verbose ) - { - if ( colour >= 0 ) - printf( "New colour: %d\n", monitor->actionColour( colour ) ); - else - printf( "Current colour: %d\n", monitor->actionColour() ); - } - else - { - if ( have_output ) printf( "%c", separator ); - if ( colour >= 0 ) - printf( "%d", monitor->actionColour( colour ) ); - else - printf( "%d", monitor->actionColour() ); - have_output = true; - } - } - if ( have_output ) - { - printf( "\n" ); - } - if ( !function ) - { - Usage(); - } - delete monitor; - } - else - { - fprintf( stderr, "Error, invalid monitor id %d\n", mon_id ); - exit( -1 ); - } - } - else - { - if ( function & ZMU_QUERY ) - { + char separator = ' '; + bool have_output = false; + if ( function & ZMU_STATE ) + { + Monitor::State state = monitor->GetState(); + if ( verbose ) + printf( "Current state: %s\n", state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle") ); + else + { + if ( have_output ) printf( "%c", separator ); + printf( "%d", state ); + have_output = true; + } + } + if ( function & ZMU_TIME ) + { + struct timeval timestamp = monitor->GetTimestamp( image_idx ); + if ( verbose ) + { + char timestamp_str[64] = "None"; + if ( timestamp.tv_sec ) + strftime( timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime( ×tamp.tv_sec ) ); + if ( image_idx == -1 ) + printf( "Time of last image capture: %s.%02ld\n", timestamp_str, timestamp.tv_usec/10000 ); + else + printf( "Time of image %d capture: %s.%02ld\n", image_idx, timestamp_str, timestamp.tv_usec/10000 ); + } + else + { + if ( have_output ) printf( "%c", separator ); + printf( "%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000 ); + have_output = true; + } + } + if ( function & ZMU_READ_IDX ) + { + if ( verbose ) + printf( "Last read index: %d\n", monitor->GetLastReadIndex() ); + else + { + if ( have_output ) printf( "%c", separator ); + printf( "%d", monitor->GetLastReadIndex() ); + have_output = true; + } + } + if ( function & ZMU_WRITE_IDX ) + { + if ( verbose ) + printf( "Last write index: %d\n", monitor->GetLastWriteIndex() ); + else + { + if ( have_output ) printf( "%c", separator ); + printf( "%d", monitor->GetLastWriteIndex() ); + have_output = true; + } + } + if ( function & ZMU_EVENT ) + { + if ( verbose ) + printf( "Last event id: %d\n", monitor->GetLastEvent() ); + else + { + if ( have_output ) printf( "%c", separator ); + printf( "%d", monitor->GetLastEvent() ); + have_output = true; + } + } + if ( function & ZMU_FPS ) + { + if ( verbose ) + printf( "Current capture rate: %.2f frames per second\n", monitor->GetFPS() ); + else + { + if ( have_output ) printf( "%c", separator ); + printf( "%.2f", monitor->GetFPS() ); + have_output = true; + } + } + if ( function & ZMU_IMAGE ) + { + if ( verbose ) + { + if ( image_idx == -1 ) + printf( "Dumping last image captured to Monitor%d.jpg", monitor->Id() ); + else + printf( "Dumping buffer image %d to Monitor%d.jpg", image_idx, monitor->Id() ); + if ( scale != -1 ) + printf( ", scaling by %d%%", scale ); + printf( "\n" ); + } + monitor->GetImage( image_idx, scale>0?scale:100 ); + } + if ( function & ZMU_ZONES ) + { + if ( verbose ) + printf( "Dumping zone image to Zones%d.jpg\n", monitor->Id() ); + monitor->DumpZoneImage( zoneString ); + } + if ( function & ZMU_ALARM ) + { + if ( verbose ) + printf( "Forcing alarm on\n" ); + monitor->ForceAlarmOn( config.forced_alarm_score, "Forced Web" ); + } + if ( function & ZMU_NOALARM ) + { + if ( verbose ) + printf( "Forcing alarm off\n" ); + monitor->ForceAlarmOff(); + } + if ( function & ZMU_CANCEL ) + { + if ( verbose ) + printf( "Cancelling forced alarm on/off\n" ); + monitor->CancelForced(); + } + if ( function & ZMU_RELOAD ) + { + if ( verbose ) + printf( "Reloading monitor settings\n" ); + monitor->actionReload(); + } + if ( function & ZMU_ENABLE ) + { + if ( verbose ) + printf( "Enabling event generation\n" ); + monitor->actionEnable(); + } + if ( function & ZMU_DISABLE ) + { + if ( verbose ) + printf( "Disabling event generation\n" ); + monitor->actionDisable(); + } + if ( function & ZMU_SUSPEND ) + { + if ( verbose ) + printf( "Suspending event generation\n" ); + monitor->actionSuspend(); + } + if ( function & ZMU_RESUME ) + { + if ( verbose ) + printf( "Resuming event generation\n" ); + monitor->actionResume(); + } + if ( function & ZMU_QUERY ) + { + char monString[16382] = ""; + monitor->DumpSettings( monString, verbose ); + printf( "%s\n", monString ); + } + if ( function & ZMU_BRIGHTNESS ) + { + if ( verbose ) + { + if ( brightness >= 0 ) + printf( "New brightness: %d\n", monitor->actionBrightness( brightness ) ); + else + printf( "Current brightness: %d\n", monitor->actionBrightness() ); + } + else + { + if ( have_output ) printf( "%c", separator ); + if ( brightness >= 0 ) + printf( "%d", monitor->actionBrightness( brightness ) ); + else + printf( "%d", monitor->actionBrightness() ); + have_output = true; + } + } + if ( function & ZMU_CONTRAST ) + { + if ( verbose ) + { + if ( contrast >= 0 ) + printf( "New brightness: %d\n", monitor->actionContrast( contrast ) ); + else + printf( "Current contrast: %d\n", monitor->actionContrast() ); + } + else + { + if ( have_output ) printf( "%c", separator ); + if ( contrast >= 0 ) + printf( "%d", monitor->actionContrast( contrast ) ); + else + printf( "%d", monitor->actionContrast() ); + have_output = true; + } + } + if ( function & ZMU_HUE ) + { + if ( verbose ) + { + if ( hue >= 0 ) + printf( "New hue: %d\n", monitor->actionHue( hue ) ); + else + printf( "Current hue: %d\n", monitor->actionHue() ); + } + else + { + if ( have_output ) printf( "%c", separator ); + if ( hue >= 0 ) + printf( "%d", monitor->actionHue( hue ) ); + else + printf( "%d", monitor->actionHue() ); + have_output = true; + } + } + if ( function & ZMU_COLOUR ) + { + if ( verbose ) + { + if ( colour >= 0 ) + printf( "New colour: %d\n", monitor->actionColour( colour ) ); + else + printf( "Current colour: %d\n", monitor->actionColour() ); + } + else + { + if ( have_output ) printf( "%c", separator ); + if ( colour >= 0 ) + printf( "%d", monitor->actionColour( colour ) ); + else + printf( "%d", monitor->actionColour() ); + have_output = true; + } + } + if ( have_output ) + { + printf( "\n" ); + } + if ( !function ) + { + Usage(); + } + delete monitor; + } + else + { + fprintf( stderr, "Error, invalid monitor id %d\n", mon_id ); + exit( -1 ); + } + } + else + { + if ( function & ZMU_QUERY ) + { #if ZM_HAS_V4L - char vidString[0x10000] = ""; - bool ok = LocalCamera::GetCurrentSettings( device, vidString, v4lVersion, verbose ); - printf( "%s", vidString ); - exit( ok?0:-1 ); + char vidString[0x10000] = ""; + bool ok = LocalCamera::GetCurrentSettings( device, vidString, v4lVersion, verbose ); + printf( "%s", vidString ); + exit( ok?0:-1 ); #else // ZM_HAS_V4L - fprintf( stderr, "Error, video4linux is required for device querying\n" ); - exit( -1 ); + fprintf( stderr, "Error, video4linux is required for device querying\n" ); + exit( -1 ); #endif // ZM_HAS_V4L - } + } - if ( function & ZMU_LIST ) - { - std::string sql = "select Id, Function+0 from Monitors"; - if ( !verbose ) - { - sql += "where Function != 'None'"; - } - sql += " order by Id asc"; + if ( function & ZMU_LIST ) + { + std::string sql = "select Id, Function+0 from Monitors"; + if ( !verbose ) + { + sql += "where Function != 'None'"; + } + sql += " order by Id asc"; - if ( mysql_query( &dbconn, sql.c_str() ) ) - { - Error( "Can't run query: %s", mysql_error( &dbconn ) ); - exit( mysql_errno( &dbconn ) ); - } + if ( mysql_query( &dbconn, sql.c_str() ) ) + { + Error( "Can't run query: %s", mysql_error( &dbconn ) ); + exit( mysql_errno( &dbconn ) ); + } - MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { - Error( "Can't use query result: %s", mysql_error( &dbconn ) ); - exit( mysql_errno( &dbconn ) ); - } - int n_monitors = mysql_num_rows( result ); - Debug( 1, "Got %d monitors", n_monitors ); + MYSQL_RES *result = mysql_store_result( &dbconn ); + if ( !result ) + { + Error( "Can't use query result: %s", mysql_error( &dbconn ) ); + exit( mysql_errno( &dbconn ) ); + } + int n_monitors = mysql_num_rows( result ); + Debug( 1, "Got %d monitors", n_monitors ); - printf( "%4s%5s%6s%9s%14s%6s%6s%8s%8s\n", "Id", "Func", "State", "TrgState", "LastImgTim", "RdIdx", "WrIdx", "LastEvt", "FrmRate" ); - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { - int mon_id = atoi(dbrow[0]); - int function = atoi(dbrow[1]); - if ( !user || user->canAccess( mon_id ) ) - { - if ( function > 1 ) - { - Monitor *monitor = Monitor::Load( mon_id, false, Monitor::QUERY ); - if ( monitor && monitor->connect() ) - { - struct timeval tv = monitor->GetTimestamp(); - printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", - monitor->Id(), - function, - monitor->GetState(), - monitor->GetTriggerState(), - tv.tv_sec, tv.tv_usec/10000, - monitor->GetLastReadIndex(), - monitor->GetLastWriteIndex(), - monitor->GetLastEvent(), - monitor->GetFPS() - ); - delete monitor; - } - } - else - { - struct timeval tv = { 0, 0 }; - printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", - mon_id, - function, - 0, - 0, - tv.tv_sec, tv.tv_usec/10000, - 0, - 0, - 0, - 0.0 - ); - } - } - } - mysql_free_result( result ); - } - } - delete user; + printf( "%4s%5s%6s%9s%14s%6s%6s%8s%8s\n", "Id", "Func", "State", "TrgState", "LastImgTim", "RdIdx", "WrIdx", "LastEvt", "FrmRate" ); + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) + { + int mon_id = atoi(dbrow[0]); + int function = atoi(dbrow[1]); + if ( !user || user->canAccess( mon_id ) ) + { + if ( function > 1 ) + { + Monitor *monitor = Monitor::Load( mon_id, false, Monitor::QUERY ); + if ( monitor && monitor->connect() ) + { + struct timeval tv = monitor->GetTimestamp(); + printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", + monitor->Id(), + function, + monitor->GetState(), + monitor->GetTriggerState(), + tv.tv_sec, tv.tv_usec/10000, + monitor->GetLastReadIndex(), + monitor->GetLastWriteIndex(), + monitor->GetLastEvent(), + monitor->GetFPS() + ); + delete monitor; + } + } + else + { + struct timeval tv = { 0, 0 }; + printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", + mon_id, + function, + 0, + 0, + tv.tv_sec, tv.tv_usec/10000, + 0, + 0, + 0, + 0.0 + ); + } + } + } + mysql_free_result( result ); + } + } + delete user; - logTerm(); - zmDbClose(); + logTerm(); + zmDbClose(); - return( 0 ); + return( 0 ); } diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 49c91acfc..de7b31178 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -8,88 +8,251 @@ exit; fi +for i in "$@" +do +case $i in + -b=*|--branch=*) + BRANCH="${i#*=}" + shift # past argument=value + ;; + -d=*|--distro=*) + DISTRO="${i#*=}" + shift # past argument=value + ;; + -i=*|--interactive=*) + INTERACTIVE="${i#*=}" + shift # past argument=value + ;; + -r=*|--release=*) + RELEASE="${i#*=}" + shift + ;; + -s=*|--snapshot=*) + SNAPSHOT="${i#*=}" + shift # past argument=value + ;; + -t=*|--type=*) + TYPE="${i#*=}" + shift # past argument=value + ;; + -u=*|--urgency=*) + URGENCY="${i#*=}" + shift # past argument=value + ;; + -f=*|--fork=*) + GITHUB_FORK="${i#*=}" + shift # past argument=value + ;; + -v=*|--version=*) + PACKAGE_VERSION="${i#*=}" + shift + ;; + --default) + DEFAULT=YES + shift # past argument with no value + ;; + *) + # unknown option + ;; +esac +done DATE=`date -R` -DISTRO=$1 -SNAPSHOT=$2 -if [ "$SNAPSHOT" == "stable" ]; then -SNAPSHOT=""; -fi; - - -TYPE=$3 if [ "$TYPE" == "" ]; then -TYPE="source"; + echo "Defaulting to source build" + TYPE="source"; fi; -BRANCH=$4 +if [ "$GITHUB_FORK" == "" ]; then + echo "Defaulting to ZoneMinder upstream git" + GITHUB_FORK="ZoneMinder" +fi; + +# Release is a special mode... it uploads to the release ppa and cannot have a snapshot +if [ "$RELEASE" != "" ]; then + if [ "$SNAPSHOT" != "" ]; then + echo "Releases cannot have a snapshot.... exiting." + fi + if [ "$GITHUB_FORK" != "" ]; then + echo "Releases cannot have a fork.... exiting." + fi + BRANCH=$RELEASE +else + if [ "$SNAPSHOT" == "stable" ]; then + if [ "$BRANCH" == "" ]; then + BRANCH=$(git describe --tags $(git rev-list --tags --max-count=1)); + echo "Latest stable branch is $BRANCH"; + fi; + else + if [ "$BRANCH" == "" ]; then + echo "Defaulting to master branch"; + BRANCH="master"; + fi; + if [ "$SNAPSHOT" == "NOW" ]; then + SNAPSHOT=`date +%Y%m%d%H%M%S`; + fi; + fi; +fi -if [ ! -d 'zoneminder_release' ]; then - git clone https://github.com/ZoneMinder/ZoneMinder.git zoneminder_release +# Instead of cloning from github each time, if we have a fork lying around, update it and pull from there instead. +if [ ! -d "${GITHUB_FORK}_zoneminder_release" ]; then + if [ -d "${GITHUB_FORK}_ZoneMinder.git" ]; then + echo "Using local clone ${GITHUB_FORK}_ZoneMinder.git to pull from." + cd "${GITHUB_FORK}_ZoneMinder.git" + echo "git checkout $BRANCH" + git checkout $BRANCH + echo "git pull..." + git pull + cd ../ + echo "git clone ${GITHUB_FORK}_ZoneMinder.git ${GITHUB_FORK}_zoneminder_release" + git clone "${GITHUB_FORK}_ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release" + else + echo "git clone https://github.com/$GITHUB_FORK/ZoneMinder.git ${GITHUB_FORK}_zoneminder_release" + git clone "https://github.com/$GITHUB_FORK/ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release" + fi +else + echo "release dir already exists. Please remove it." + exit 0; fi; -if [ "$BRANCH" != "" ]; then - cd zoneminder_release - if [ "$BRANCH" == "stable" ]; then - BRANCH=$(git describe --tags $(git rev-list --tags --max-count=1)); - echo "Latest stable branch is $BRANCH"; - - fi - git checkout $BRANCH - cd ../ + +cd "${GITHUB_FORK}_zoneminder_release" +if [ $RELEASE ]; then + git checkout $RELEASE +else + git checkout $BRANCH fi; -VERSION=`cat zoneminder_release/version` +cd ../ + +VERSION=`cat ${GITHUB_FORK}_zoneminder_release/version` + if [ $VERSION == "" ]; then exit 1; fi; -echo "Doing $TYPE release zoneminder_$VERSION-$DISTRO-$SNAPSHOT"; -mv zoneminder_release zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig -cd zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig -git submodule init -git submodule update --init --recursive -if [ $DISTRO == "trusty" ]; then -ln -sf distros/ubuntu1204 debian -else -ln -sf distros/ubuntu1604 debian +if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then + VERSION="$VERSION~$SNAPSHOT"; fi; -# Auto-install all ZoneMinder's depedencies using the Debian control file -sudo apt-get install devscripts equivs -sudo mk-build-deps -ir ./debian/control +DIRECTORY="zoneminder_$VERSION-$DISTRO${PACKAGE_VERSION}"; +echo "Doing $TYPE release $DIRECTORY"; +mv "${GITHUB_FORK}_zoneminder_release" "$DIRECTORY.orig"; +cd "$DIRECTORY.orig"; -if [ -z `hostname -d` ] ; then - AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`.local>" +git submodule init +git submodule update --init --recursive +if [ $DISTRO == "trusty" ] || [ $DISTRO == "precise" ]; then + ln -sf distros/ubuntu1204 debian +else + if [ $DISTRO == "wheezy" ]; then + ln -sf distros/debian debian + else + ln -sf distros/ubuntu1604 debian + fi; +fi; + +if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then + AUTHOR="$DEBFULLNAME <$DEBEMAIL>" else - AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`>" + if [ -z `hostname -d` ] ; then + AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`.local>" + else + AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`>" + fi fi +if [ "$URGENCY" = "" ]; then + URGENCY="medium" +fi; + +if [ "$SNAPSHOT" == "stable" ]; then cat < debian/changelog -zoneminder ($VERSION-$DISTRO-$SNAPSHOT) $DISTRO; urgency=medium +zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY + + * Release $VERSION + + -- $AUTHOR $DATE + +EOF +else +cat < debian/changelog +zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY * -- $AUTHOR $DATE EOF +fi; + +# Auto-install all ZoneMinder's depedencies using the Debian control file +sudo apt-get install devscripts equivs +sudo mk-build-deps -ir ./debian/control +echo "Status: $?" + #rm -rf .git #rm .gitignore #cd ../ #tar zcf zoneminder_$VERSION-$DISTRO.orig.tar.gz zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig #cd zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig if [ $TYPE == "binary" ]; then - debuild + DEBUILD=debuild else - if [ $TYPE == "local" ]; then - debuild -i -us -uc -b - else - debuild -S -sa + if [ $TYPE == "local" ]; then + DEBUILD="debuild -i -us -uc -b" + else + DEBUILD="debuild -S -sa" + fi; +fi; +if [ "$DEBSIGN_KEYID" != "" ]; then + DEBUILD="$DEBUILD -k$DEBSIGN_KEYID" +fi +$DEBUILD +echo "Status: $?" + +cd ../ +if [ "$INTERACTIVE" != "no" ]; then + read -p "Do you want to keep the checked out version of Zoneminder (incase you want to modify it later) [y/N]" + [[ $REPLY == [yY] ]] && { mv $DIRECTORY zoneminder_release; echo "The checked out copy is preserved in zoneminder_release"; } || { rm -fr $DIRECTORY; echo "The checked out copy has been deleted"; } + echo "Done!" +else + rm -fr $DIRECTORY; echo "The checked out copy has been deleted"; +fi + +if [ $TYPE == "binary" ]; then + if [ "$INTERACTIVE" != "no" ]; then + echo "Not doing dput since it's a binary release. Do you want to install it? (Y/N)" + read install + if [ "$install" == "Y" ]; then + sudo dpkg -i $DIRECTORY*.deb + fi; + if [ "$DISTRO" == "jessie" ]; then + echo "Do you want to upload this binary to zmrepo? (y/N)" + read install + if [ "$install" == "Y" ]; then + scp "zoneminder_*-${VERSION}-${DISTRO}*" "zmrepo@zmrepo.connortechnology.com:debian/${BRANCH}/mini-dinstall/incoming/" + fi; + fi; + fi; +else + SC="zoneminder_${VERSION}-${DISTRO}${PACKAGE_VERSION}_source.changes"; + PPA=""; + if [ "$RELEASE" != "" ]; then + PPA="ppa:iconnor/zoneminder"; + else + if [ "$BRANCH" == "" ]; then + PPA="ppa:iconnor/zoneminder-master"; + else + PPA="ppa:iconnor/zoneminder-$BRANCH"; + fi; + fi; + + dput="Y"; + if [ "$INTERACTIVE" != "no" ]; then + echo "Ready to dput $SC to $PPA ? Y/N..."; + read dput + fi + if [ "$dput" == "Y" -o "$dput" == "y" ]; then + dput $PPA $SC fi; fi; -cd ../ - -read -p "Do you want to keep the checked out version of Zoneminder (incase you want to modify it later) [y/N]" -[[ $REPLY == [yY] ]] && { mv zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig zoneminder_release; echo "The checked out copy is preserved in zoneminder_release"; } || { rm -fr zoneminder_$VERSION-$DISTRO-$SNAPSHOT.orig; echo "The checked out copy has been deleted"; } -echo "Done!" - - diff --git a/utils/docker/apache-vhost b/utils/docker/apache-vhost index 514897a16..e1821932e 100644 --- a/utils/docker/apache-vhost +++ b/utils/docker/apache-vhost @@ -1,12 +1,14 @@ - DocumentRoot /usr/local/share/zoneminder + DocumentRoot /usr/local/share/zoneminder/www DirectoryIndex index.php - ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ - + ScriptAlias /cgi-bin/ /usr/local/libexec/zoneminder/cgi-bin/ + + Require all granted + + AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - require all granted + Require all granted - diff --git a/utils/docker/start.sh b/utils/docker/start.sh index ff3d6c705..29cb2f567 100755 --- a/utils/docker/start.sh +++ b/utils/docker/start.sh @@ -36,9 +36,6 @@ service apache2 restart # Start ZoneMinder /usr/local/bin/zmpkg.pl start -# Start SSHD -/usr/sbin/sshd - while : do sleep 3600 diff --git a/utils/packpack/autosetup.patch b/utils/packpack/autosetup.patch new file mode 100644 index 000000000..3955e975e --- /dev/null +++ b/utils/packpack/autosetup.patch @@ -0,0 +1,17 @@ +--- a/packpack/pack/rpm.mk 2017-01-14 14:01:50.364217882 -0600 ++++ b/packpack/pack/rpm.mk 2017-01-14 14:01:19.594985311 -0600 +@@ -23,11 +23,13 @@ + -e 's/Release:\([ ]*\).*/Release: $(RELEASE)%{dist}/' \ + -e 's/Source0:\([ ]*\).*/Source0: $(TARBALL)/' \ + -e 's/%setup .*/%setup -q -n $(PRODUCT)-$(VERSION)/' \ ++ -e 's/%autosetup -n .*/%autosetup -n $(PRODUCT)-$(VERSION)/' \ + -i $@.tmp + grep -F "Version: $(VERSION)" $@.tmp && \ + grep -F "Release: $(RELEASE)" $@.tmp && \ + grep -F "Source0: $(TARBALL)" $@.tmp && \ +- grep -F "%setup -q -n $(PRODUCT)-$(VERSION)" $@.tmp || \ ++ (grep -F "%setup -q -n $(PRODUCT)-$(VERSION)" $@.tmp || \ ++ grep -F "%autosetup" $@.tmp) || \ + (echo "Failed to patch RPM spec" && exit 1) + @ mv -f $@.tmp $@ + @echo diff --git a/utils/packpack/deb.mk.patch b/utils/packpack/deb.mk.patch new file mode 100644 index 000000000..0cf0100f2 --- /dev/null +++ b/utils/packpack/deb.mk.patch @@ -0,0 +1,11 @@ +--- a/packpack/pack/deb.mk 2017-01-15 16:41:32.938418279 -0600 ++++ b/packpack/pack/deb.mk 2017-02-16 15:44:43.267900717 -0600 +@@ -14,7 +14,7 @@ + DPKG_BUILD:=$(PRODUCT)_$(DEB_VERSION)-$(RELEASE)_$(DPKG_ARCH).build + DPKG_DSC:=$(PRODUCT)_$(DEB_VERSION)-$(RELEASE).dsc + DPKG_ORIG_TARBALL:=$(PRODUCT)_$(DEB_VERSION).orig.tar.$(TARBALL_COMPRESSOR) +-DPKG_DEBIAN_TARBALL:=$(PRODUCT)_$(DEB_VERSION)-$(RELEASE).debian.tar.$(TARBALL_COMPRESSOR) ++DPKG_DEBIAN_TARBALL:=$(PRODUCT)_$(DEB_VERSION)-$(RELEASE).tar.$(TARBALL_COMPRESSOR) + + # gh-7: Ubuntu/Debian should export DEBIAN_FRONTEND=noninteractive + export DEBIAN_FRONTEND=noninteractive diff --git a/utils/packpack/redhat_package.mk b/utils/packpack/redhat_package.mk new file mode 100644 index 000000000..c15e03528 --- /dev/null +++ b/utils/packpack/redhat_package.mk @@ -0,0 +1,8 @@ +.PHONY: redhat_package +.NOTPARALLEL: redhat_package + +redhat_package: redhat_bootstrap package + +redhat_bootstrap: + sudo yum install -y --nogpgcheck build/zmrepo.noarch.rpm + diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh new file mode 100755 index 000000000..117f1c1a9 --- /dev/null +++ b/utils/packpack/startpackpack.sh @@ -0,0 +1,117 @@ +#!/bin/bash +# packpack setup file for the ZoneMinder project +# Written by Andrew Bauer + +# Check to see if this script has access to all the commands it needs +for CMD in set echo curl repoquery git ln mkdir patch rmdir; do + type $CMD 2>&1 > /dev/null + + if [ $? -ne 0 ]; then + echo + echo "ERROR: The script cannot find the required command \"${CMD}\"." + echo + exit $? + fi +done + +# Verify OS & DIST environment variables have been set before calling this script +if [ -z "${OS}" ] || [ -z "${DIST}" ]; then + echo "ERROR: both OS and DIST environment variables must be set" + exit 1 +fi + +# Steps common to all builds +mkdir -p build +if [ -e "packpack/Makefile" ]; then + echo "Checking packpack github repo for changes..." + git -C packpack pull origin master +else + echo "Cloning pakcpack github repo..." + git clone https://github.com/packpack/packpack.git packpack +fi + +# The rpm specfile requires we download the tarball and manually move it into place +# Might as well do this for Debian as well, rather than git submodule init +CRUDVER="3.0.10" +if [ -e "build/crud-${CRUDVER}.tar.gz" ]; then + echo "Found existing Crud ${CRUDVER} tarball..." +else + echo "Retrieving Crud ${CRUDVER} submodule..." + curl -L https://github.com/FriendsOfCake/crud/archive/v${CRUDVER}.tar.gz > build/crud-${CRUDVER}.tar.gz + if [ $? -ne 0 ]; then + echo "ERROR: Crud tarball retreival failed..." + exit $? + fi +fi + +# Steps common to Redhat distros +if [ "${OS}" == "el" ] || [ "${OS}" == "fedora" ]; then + echo "Begin Redhat build..." + + # %autosetup support has been merged upstream. No need to patch + #patch -p1 < utils/packpack/autosetup.patch + ln -sf distros/redhat rpm + + # The rpm specfile requires the Crud submodule folder to be empty + if [ -e "web/api/app/Plugin/Crud/LICENSE.txt" ]; then + rm -rf web/api/app/Plugin/Crud + mkdir web/api/app/Plugin/Crud + fi + + if [ "${OS}" == "el" ]; then + zmrepodistro=${OS} + else + zmrepodistro="f" + fi + + # Let repoquery determine the full url and filename of the zmrepo rpm we are interested in + result=`repoquery --repofrompath=zmpackpack,https://zmrepo.zoneminder.com/${zmrepodistro}/${DIST}/x86_64/ --repoid=zmpackpack --qf="%{location}" zmrepo 2> /dev/null` + + if [ -n "$result" ] && [ $? -eq 0 ]; then + echo "Retrieving ZMREPO rpm..." + curl $result > build/zmrepo.noarch.rpm + else + echo "ERROR: Failed to retrieve zmrepo rpm..." + if [ $? -ne 0 ]; then + echo $? + else + echo 1 + fi + fi + + echo "Starting packpack..." + packpack/packpack -f utils/packpack/redhat_package.mk redhat_package + +# Steps common the Debian based distros +elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then + echo "Begin Debian build..." + + # patch packpack to remove "debian" from the source tarball filename + patch --dry-run --silent -f -p1 < utils/packpack/deb.mk.patch 2>/dev/null + if [ $? -eq 0 ]; then + patch -p1 < utils/packpack/deb.mk.patch + fi + + # Uncompress the Crud tarball and move it into place + if [ -e "web/api/app/Plugin/Crud/LICENSE.txt" ]; then + echo "Crud plugin already installed..." + else + echo "Unpacking Crud plugin..." + tar -xzf build/crud-${CRUDVER}.tar.gz + rmdir web/api/app/Plugin/Crud + mv -f crud-${CRUDVER} web/api/app/Plugin/Crud + fi + + if [ ${DIST} == "trusty" ] || [ ${DIST} == "precise" ]; then + ln -sf distros/ubuntu1204 debian + elif [ ${DIST} == "wheezy" ]; then + ln -sf distros/debian debian + else + ln -sf distros/ubuntu1604 debian + fi + + echo "Starting packpack..." + packpack/packpack +fi + + diff --git a/version b/version index 7f3c3affd..56abadc20 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.30.1 +1.30.13 diff --git a/web/.editorconfig b/web/.editorconfig new file mode 100644 index 000000000..217a0e30c --- /dev/null +++ b/web/.editorconfig @@ -0,0 +1,13 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +indent_style = tab +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.bat] +end_of_line = crlf diff --git a/web/api/.gitignore b/web/.gitignore similarity index 91% rename from web/api/.gitignore rename to web/.gitignore index 4a1adb886..90d971d4b 100644 --- a/web/api/.gitignore +++ b/web/.gitignore @@ -8,6 +8,7 @@ /build /dist /tags +/app/webroot/events # OS generated files # ###################### @@ -18,4 +19,4 @@ .Trashes Icon? ehthumbs.db -Thumbs.db \ No newline at end of file +Thumbs.db diff --git a/web/.htaccess b/web/.htaccess new file mode 100644 index 000000000..f23dbaf66 --- /dev/null +++ b/web/.htaccess @@ -0,0 +1,5 @@ + + RewriteEngine on + RewriteRule ^$ app/webroot/ [L] + RewriteRule (.*) app/webroot/$1 [L] + \ No newline at end of file diff --git a/web/.travis.yml b/web/.travis.yml new file mode 100644 index 000000000..5283442c4 --- /dev/null +++ b/web/.travis.yml @@ -0,0 +1,116 @@ +language: php + +php: + - 5.2 + - 5.3 + - 5.4 + +env: + - DB=mysql + - DB=pgsql + - DB=sqlite + +matrix: + include: + - php: 5.4 + env: + - PHPCS=1 + +before_script: + - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test;'; fi" + - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test2;'; fi" + - sh -c "if [ '$DB' = 'mysql' ]; then mysql -e 'CREATE DATABASE cakephp_test3;'; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE DATABASE cakephp_test;' -U postgres; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE SCHEMA test2;' -U postgres -d cakephp_test; fi" + - sh -c "if [ '$DB' = 'pgsql' ]; then psql -c 'CREATE SCHEMA test3;' -U postgres -d cakephp_test; fi" + - chmod -R 777 ./app/tmp + - sudo apt-get install lighttpd + - pear channel-discover pear.cakephp.org + - pear install --alldeps cakephp/CakePHP_CodeSniffer + - phpenv rehash + - set +H + - echo " array( + 'datasource' => 'Database/Mysql', + 'host' => '0.0.0.0', + 'login' => 'travis' + ), + 'pgsql' => array( + 'datasource' => 'Database/Postgres', + 'host' => '127.0.0.1', + 'login' => 'postgres', + 'database' => 'cakephp_test', + 'schema' => array( + 'default' => 'public', + 'test' => 'public', + 'test2' => 'test2', + 'test_database_three' => 'test3' + ) + ), + 'sqlite' => array( + 'datasource' => 'Database/Sqlite', + 'database' => array( + 'default' => ':memory:', + 'test' => ':memory:', + 'test2' => '/tmp/cakephp_test2.db', + 'test_database_three' => '/tmp/cakephp_test3.db' + ), + ) + ); + public \$default = array( + 'persistent' => false, + 'host' => '', + 'login' => '', + 'password' => '', + 'database' => 'cakephp_test', + 'prefix' => '' + ); + public \$test = array( + 'persistent' => false, + 'host' => '', + 'login' => '', + 'password' => '', + 'database' => 'cakephp_test', + 'prefix' => '' + ); + public \$test2 = array( + 'persistent' => false, + 'host' => '', + 'login' => '', + 'password' => '', + 'database' => 'cakephp_test2', + 'prefix' => '' + ); + public \$test_database_three = array( + 'persistent' => false, + 'host' => '', + 'login' => '', + 'password' => '', + 'database' => 'cakephp_test3', + 'prefix' => '' + ); + public function __construct() { + \$db = 'mysql'; + if (!empty(\$_SERVER['DB'])) { + \$db = \$_SERVER['DB']; + } + foreach (array('default', 'test', 'test2', 'test_database_three') as \$source) { + \$config = array_merge(\$this->{\$source}, \$this->identities[\$db]); + if (is_array(\$config['database'])) { + \$config['database'] = \$config['database'][\$source]; + } + if (!empty(\$config['schema']) && is_array(\$config['schema'])) { + \$config['schema'] = \$config['schema'][\$source]; + } + \$this->{\$source} = \$config; + } + } + }" > app/Config/database.php + +script: + - sh -c "if [ '$PHPCS' != '1' ]; then ./lib/Cake/Console/cake test core AllTests --stderr; else phpcs -p --extensions=php --standard=CakePHP ./lib/Cake; fi" + +notifications: + email: false \ No newline at end of file diff --git a/web/ajax/console.php b/web/ajax/console.php new file mode 100644 index 000000000..c22a51b28 --- /dev/null +++ b/web/ajax/console.php @@ -0,0 +1,33 @@ + diff --git a/web/ajax/event.php b/web/ajax/event.php index a78ccd1a9..5291f0f7a 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -6,7 +6,7 @@ if ( empty($_REQUEST['id']) && empty($_REQUEST['eids']) ) { if ( canView( 'Events' ) ) { switch ( $_REQUEST['action'] ) { - case "video" : { + case 'video' : { if ( empty($_REQUEST['videoFormat']) ) { ajaxError( "Video Generation Failure, no format given" ); } elseif ( empty($_REQUEST['rate']) ) { @@ -77,11 +77,9 @@ if ( canView( 'Events' ) ) { } } -if ( canEdit( 'Events' ) ) -{ - switch ( $_REQUEST['action'] ) - { - case "rename" : +if ( canEdit( 'Events' ) ) { + switch ( $_REQUEST['action'] ) { + case 'rename' : { if ( !empty($_REQUEST['eventName']) ) dbQuery( 'UPDATE Events SET Name = ? WHERE Id = ?', array( $_REQUEST['eventName'], $_REQUEST['id'] ) ); @@ -90,24 +88,29 @@ if ( canEdit( 'Events' ) ) ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>true ) ); break; } - case "eventdetail" : + case 'eventdetail' : { dbQuery( 'UPDATE Events SET Cause = ?, Notes = ? WHERE Id = ?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['id'] ) ); ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>true ) ); break; } - case "archive" : - case "unarchive" : + case 'archive' : + case 'unarchive' : { $archiveVal = ($_REQUEST['action'] == "archive")?1:0; dbQuery( 'UPDATE Events SET Archived = ? WHERE Id = ?', array( $archiveVal, $_REQUEST['id']) ); ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>false ) ); break; } - case "delete" : + case 'delete' : { - deleteEvent( $_REQUEST['id'] ); - ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true ) ); + $Event = new Event( $_REQUEST['id'] ); + if ( ! $Event->Id() ) { + ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true, 'message'=> 'Event not found.' ) ); + } else { + $Event->delete(); + ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true ) ); + } break; } } diff --git a/web/ajax/log.php b/web/ajax/log.php index 66dcfc2b5..c38a6d5d0 100644 --- a/web/ajax/log.php +++ b/web/ajax/log.php @@ -1,5 +1,9 @@ Id()] = $server; - } + $servers = Server::find_all(); + $servers_by_Id = array(); +# There is probably a better way to do this. + foreach ( $servers as $server ) { + $servers_by_Id[$server->Id()] = $server; + } $minTime = isset($_POST['minTime'])?$_POST['minTime']:NULL; $maxTime = isset($_POST['maxTime'])?$_POST['maxTime']:NULL; - $limit = isset($_POST['limit'])?$_POST['limit']:100; - $filter = isset($_POST['filter'])?$_POST['filter']:array(); - $sortField = isset($_POST['sortField'])?$_POST['sortField']:'TimeKey'; + $limit = 100; + if ( isset($_POST['limit']) ) { + if ( ( !is_integer( $_POST['limit'] ) and !ctype_digit($_POST['limit']) ) ) { + Error("Invalid value for limit " . $_POST['limit'] ); + } else { + $limit = $_POST['limit']; + } + } + $sortField = 'TimeKey'; + if ( isset($_POST['sortField']) ) { + if ( ! in_array( $_POST['sortField'], $filterFields ) and ( $_POST['sortField'] != 'TimeKey' ) ) { + Error("Invalid sort field " . $_POST['sortField'] ); + } else { + $sortField = $_POST['sortField']; + } + } $sortOrder = (isset($_POST['sortOrder']) and $_POST['sortOrder']) == 'asc' ? 'asc':'desc'; + $filter = isset($_POST['filter'])?$_POST['filter']:array(); - $filterFields = array( 'Component', 'ServerId', 'Pid', 'Level', 'File', 'Line' ); - - $total = dbFetchOne( "SELECT count(*) AS Total FROM Logs", 'Total' ); + $total = dbFetchOne( 'SELECT count(*) AS Total FROM Logs', 'Total' ); $sql = 'SELECT * FROM Logs'; $where = array(); - $values = array(); + $values = array(); if ( $minTime ) { - $where[] = "TimeKey > ?"; - $values[] = $minTime; + $where[] = "TimeKey > ?"; + $values[] = $minTime; } elseif ( $maxTime ) { - $where[] = "TimeKey < ?"; - $values[] = $maxTime; - } + $where[] = "TimeKey < ?"; + $values[] = $maxTime; + } + foreach ( $filter as $field=>$value ) { - if ( $field == 'Level' ){ - $where[] = $field." <= ?"; - $values[] = $value; - } else { - $where[] = $field." = ?"; - $values[] = $value; - } - } + if ( ! in_array( $field, $filterFields ) ) { + Error("$field is not in valid filter fields"); + continue; + } + if ( $field == 'Level' ){ + $where[] = $field." <= ?"; + $values[] = $value; + } else { + $where[] = $field." = ?"; + $values[] = $value; + } + } if ( count($where) ) - $sql.= ' WHERE '.join( ' AND ', $where ); + $sql.= ' WHERE '.join( ' AND ', $where ); $sql .= " order by ".$sortField." ".$sortOrder." limit ".$limit; $logs = array(); foreach ( dbFetchAll( $sql, NULL, $values ) as $log ) { $log['DateTime'] = preg_replace( '/^\d+/', strftime( "%Y-%m-%d %H:%M:%S", intval($log['TimeKey']) ), $log['TimeKey'] ); - $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; + $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; $logs[] = $log; } $options = array(); $where = array(); - $values = array(); + $values = array(); foreach( $filter as $field=>$value ) { if ( $field == 'Level' ) { $where[$field] = $field." <= ?"; - $values[$field] = $value; + $values[$field] = $value; } else { $where[$field] = $field." = ?"; - $values[$field] = $value; - } - } + $values[$field] = $value; + } + } foreach( $filterFields as $field ) { $sql = "SELECT DISTINCT $field FROM Logs WHERE NOT isnull($field)"; $fieldWhere = array_diff_key( $where, array( $field=>true ) ); - $fieldValues = array_diff_key( $values, array( $field=>true ) ); + $fieldValues = array_diff_key( $values, array( $field=>true ) ); if ( count($fieldWhere) ) $sql.= " AND ".join( ' AND ', $fieldWhere ); $sql.= " ORDER BY $field ASC"; @@ -108,7 +129,7 @@ switch ( $_REQUEST['task'] ) { foreach( dbFetchAll( $sql, $field, array_values($fieldValues) ) as $value ) $options['ServerId'][$value] = ( $value and isset($servers_by_Id[$value]) ) ? $servers_by_Id[$value]->Name() : ''; - + } else { @@ -147,44 +168,51 @@ switch ( $_REQUEST['task'] ) } //$limit = isset($_POST['limit'])?$_POST['limit']:1000; $filter = isset($_POST['filter'])?$_POST['filter']:array(); - $sortField = isset($_POST['sortField'])?$_POST['sortField']:'TimeKey'; - $sortOrder = isset($_POST['sortOrder'])?$_POST['sortOrder']:'asc'; + $sortField = 'TimeKey'; + if ( isset($_POST['sortField']) ) { + if ( ! in_array( $_POST['sortField'], $filterFields ) and ( $_POST['sortField'] != 'TimeKey' ) ) { + Error("Invalid sort field " . $_POST['sortField'] ); + } else { + $sortField = $_POST['sortField']; + } + } + $sortOrder = (isset($_POST['sortOrder']) and $_POST['sortOrder']) == 'asc' ? 'asc':'desc'; - $servers = Server::find_all(); - $servers_by_Id = array(); - # There is probably a better way to do this. - foreach ( $servers as $server ) { - $servers_by_Id[$server->Id()] = $server; - } + $servers = Server::find_all(); + $servers_by_Id = array(); + # There is probably a better way to do this. + foreach ( $servers as $server ) { + $servers_by_Id[$server->Id()] = $server; + } $sql = "select * from Logs"; $where = array(); - $values = array(); + $values = array(); if ( $minTime ) { preg_match( '/(.+)(\.\d+)/', $minTime, $matches ); $minTime = strtotime($matches[1]).$matches[2]; $where[] = "TimeKey >= ?"; - $values[] = $minTime; + $values[] = $minTime; } if ( $maxTime ) { preg_match( '/(.+)(\.\d+)/', $maxTime, $matches ); $maxTime = strtotime($matches[1]).$matches[2]; $where[] = "TimeKey <= ?"; - $values[] = $maxTime; + $values[] = $maxTime; } foreach ( $filter as $field=>$value ) { if ( $value != '' ) { if ( $field == 'Level' ) { $where[] = $field." <= ?"; - $values[] = $value; + $values[] = $value; } else { $where[] = $field." = ?'"; - $values[] = $value; - } - } - } + $values[] = $value; + } + } + } if ( count($where) ) $sql.= " where ".join( " and ", $where ); $sql .= " order by ".$sortField." ".$sortOrder; @@ -216,7 +244,7 @@ switch ( $_REQUEST['task'] ) foreach ( dbFetchAll( $sql, NULL, $values ) as $log ) { $log['DateTime'] = preg_replace( '/^\d+/', strftime( "%Y-%m-%d %H:%M:%S", intval($log['TimeKey']) ), $log['TimeKey'] ); - $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; + $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; $logs[] = $log; } switch( $format ) @@ -234,20 +262,20 @@ switch ( $_REQUEST['task'] ) } case 'tsv' : { - # This line doesn't need fprintf, it could use fwrite +# This line doesn't need fprintf, it could use fwrite fprintf( $exportFP, join( "\t", - translate('DateTime'), - translate('Component'), - translate('Server'), - translate('Pid'), - translate('Level'), - translate('Message'), - translate('File'), - translate('Line') - )."\n" ); + translate('DateTime'), + translate('Component'), + translate('Server'), + translate('Pid'), + translate('Level'), + translate('Message'), + translate('File'), + translate('Line') + )."\n" ); foreach ( $logs as $log ) { - fprintf( $exportFP, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n", $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line'] ); + fprintf( $exportFP, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n", $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line'] ); } break; } diff --git a/web/ajax/status.php b/web/ajax/status.php index f7ed3d099..fbd91cf08 100644 --- a/web/ajax/status.php +++ b/web/ajax/status.php @@ -2,415 +2,395 @@ $statusData = array( "system" => array( - "permission" => "System", - "table" => "Monitors", - "limit" => 1, - "elements" => array( - "MonitorCount" => array( "sql" => "count(*)" ), - "ActiveMonitorCount" => array( "sql" => "count(if(Function != 'None',1,NULL))" ), - "State" => array( "func" => "daemonCheck()?".translate('Running').":".translate('Stopped') ), - "Load" => array( "func" => "getLoad()" ), - "Disk" => array( "func" => "getDiskPercent()" ), + "permission" => "System", + "table" => "Monitors", + "limit" => 1, + "elements" => array( + "MonitorCount" => array( "sql" => "count(*)" ), + "ActiveMonitorCount" => array( "sql" => "count(if(Function != 'None',1,NULL))" ), + "State" => array( "func" => "daemonCheck()?".translate('Running').":".translate('Stopped') ), + "Load" => array( "func" => "getLoad()" ), + "Disk" => array( "func" => "getDiskPercent()" ), ), - ), + ), "monitor" => array( - "permission" => "Monitors", - "table" => "Monitors", - "limit" => 1, - "selector" => "Monitors.Id", - "elements" => array( - "Id" => array( "sql" => "Monitors.Id" ), - "Name" => array( "sql" => "Monitors.Name" ), - "Type" => true, - "Function" => true, - "Enabled" => true, - "LinkedMonitors" => true, - "Triggers" => true, - "Device" => true, - "Channel" => true, - "Format" => true, - "Host" => true, - "Port" => true, - "Path" => true, - "Width" => array( "sql" => "Monitors.Width" ), - "Height" => array( "sql" => "Monitors.Height" ), - "Palette" => true, - "Orientation" => true, - "Brightness" => true, - "Contrast" => true, - "Hue" => true, - "Colour" => true, - "EventPrefix" => true, - "LabelFormat" => true, - "LabelX" => true, - "LabelY" => true, - "LabelSize" => true, - "ImageBufferCount" => true, - "WarmupCount" => true, - "PreEventCount" => true, - "PostEventCount" => true, - "AlarmFrameCount" => true, - "SectionLength" => true, - "FrameSkip" => true, - "MotionFrameSkip" => true, - "MaxFPS" => true, - "AlarmMaxFPS" => true, - "FPSReportInterval" => true, - "RefBlendPerc" => true, - "Controllable" => true, - "ControlId" => true, - "ControlDevice" => true, - "ControlAddress" => true, - "AutoStopTimeout" => true, - "TrackMotion" => true, - "TrackDelay" => true, - "ReturnLocation" => true, - "ReturnDelay" => true, - "DefaultView" => true, - "DefaultRate" => true, - "DefaultScale" => true, - "WebColour" => true, - "Sequence" => true, - "MinEventId" => array( "sql" => "min(Events.Id)", "table" => "Events", "join" => "Events.MonitorId = Monitors.Id", "group" => "Events.MonitorId" ), - "MaxEventId" => array( "sql" => "max(Events.Id)", "table" => "Events", "join" => "Events.MonitorId = Monitors.Id", "group" => "Events.MonitorId" ), - "TotalEvents" => array( "sql" => "count(Events.Id)", "table" => "Events", "join" => "Events.MonitorId = Monitors.Id", "group" => "Events.MonitorId" ), - "Status" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -s" ), - "FrameRate" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -f" ), + "permission" => "Monitors", + "table" => "Monitors", + "limit" => 1, + "selector" => "Monitors.Id", + "elements" => array( + "Id" => array( "sql" => "Monitors.Id" ), + "Name" => array( "sql" => "Monitors.Name" ), + "Type" => true, + "Function" => true, + "Enabled" => true, + "LinkedMonitors" => true, + "Triggers" => true, + "Device" => true, + "Channel" => true, + "Format" => true, + "Host" => true, + "Port" => true, + "Path" => true, + "Width" => array( "sql" => "Monitors.Width" ), + "Height" => array( "sql" => "Monitors.Height" ), + "Palette" => true, + "Orientation" => true, + "Brightness" => true, + "Contrast" => true, + "Hue" => true, + "Colour" => true, + "EventPrefix" => true, + "LabelFormat" => true, + "LabelX" => true, + "LabelY" => true, + "LabelSize" => true, + "ImageBufferCount" => true, + "WarmupCount" => true, + "PreEventCount" => true, + "PostEventCount" => true, + "AlarmFrameCount" => true, + "SectionLength" => true, + "FrameSkip" => true, + "MotionFrameSkip" => true, + "MaxFPS" => true, + "AlarmMaxFPS" => true, + "FPSReportInterval" => true, + "RefBlendPerc" => true, + "Controllable" => true, + "ControlId" => true, + "ControlDevice" => true, + "ControlAddress" => true, + "AutoStopTimeout" => true, + "TrackMotion" => true, + "TrackDelay" => true, + "ReturnLocation" => true, + "ReturnDelay" => true, + "DefaultView" => true, + "DefaultRate" => true, + "DefaultScale" => true, + "WebColour" => true, + "Sequence" => true, + "MinEventId" => array( "sql" => "(SELECT min(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), + "MaxEventId" => array( "sql" => "(SELECT max(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), + "TotalEvents" => array( "sql" => "(SELECT count(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), + "Status" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -s" ), + "FrameRate" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -f" ), ), - ), - "events" => array( - "permission" => "Events", - "table" => "Events", - "selector" => "Events.MonitorId", - "elements" => array( - "Id" => true, - "Name" => true, - "Cause" => true, - "Notes" => true, - "StartTime" => true, - "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), - "EndTime" => true, - "Width" => true, - "Height" => true, - "Length" => true, - "Frames" => true, - "AlarmFrames" => true, - "TotScore" => true, - "AvgScore" => true, - "MaxScore" => true, - ), - ), - "event" => array( - "permission" => "Events", - "table" => "Events", - "limit" => 1, - "selector" => "Events.Id", - "elements" => array( - "Id" => array( "sql" => "Events.Id" ), - "MonitorId" => true, - "Name" => true, - "Cause" => true, - "StartTime" => true, - "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), - "EndTime" => true, - "Width" => true, - "Height" => true, - "Length" => true, - "Frames" => true, - "AlarmFrames" => true, - "TotScore" => true, - "AvgScore" => true, - "MaxScore" => true, - "Archived" => true, - "Videoed" => true, - "Uploaded" => true, - "Emailed" => true, - "Messaged" => true, - "Executed" => true, - "Notes" => true, - "MinFrameId" => array( "sql" => "min(Frames.FrameId)", "table" => "Frames", "join" => "Events.Id = Frames.EventId", "group" => "Frames.EventId" ), - "MaxFrameId" => array( "sql" => "max(Frames.FrameId)", "table" => "Frames", "join" => "Events.Id = Frames.EventId", "group" => "Frames.EventId" ), - "MinFrameDelta" => array( "sql" => "min(Frames.Delta)", "table" => "Frames", "join" => "Events.Id = Frames.EventId", "group" => "Frames.EventId" ), - "MaxFrameDelta" => array( "sql" => "max(Frames.Delta)", "table" => "Frames", "join" => "Events.Id = Frames.EventId", "group" => "Frames.EventId" ), - //"Path" => array( "postFunc" => "getEventPath" ), - ), - ), - "frame" => array( - "permission" => "Events", - "table" => "Frames", - "limit" => 1, - "selector" => array( array( "table" => "Events", "join" => "Events.Id = Frames.EventId", "selector"=>"Events.Id" ), "Frames.FrameId" ), - "elements" => array( - //"Id" => array( "sql" => "Frames.FrameId" ), - "FrameId" => true, - "EventId" => true, - "Type" => true, - "TimeStamp" => true, - "TimeStampShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), - "Delta" => true, - "Score" => true, - //"Image" => array( "postFunc" => "getFrameImage" ), - ), - ), - "frameimage" => array( - "permission" => "Events", - "func" => "getFrameImage()" - ), - "nearframe" => array( - "permission" => "Events", - "func" => "getNearFrame()" - ), - "nearevents" => array( - "permission" => "Events", - "func" => "getNearEvents()" - ) -); + ), + "events" => array( + "permission" => "Events", + "table" => "Events", + "selector" => "Events.MonitorId", + "elements" => array( + "Id" => true, + "Name" => true, + "Cause" => true, + "Notes" => true, + "StartTime" => true, + "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), + "EndTime" => true, + "Width" => true, + "Height" => true, + "Length" => true, + "Frames" => true, + "AlarmFrames" => true, + "TotScore" => true, + "AvgScore" => true, + "MaxScore" => true, + ), + ), + "event" => array( + "permission" => "Events", + "table" => "Events", + "limit" => 1, + "selector" => "Events.Id", + "elements" => array( + "Id" => array( "sql" => "Events.Id" ), + "MonitorId" => true, + "Name" => true, + "Cause" => true, + "StartTime" => true, + "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), + "EndTime" => true, + "Width" => true, + "Height" => true, + "Length" => true, + "Frames" => true, + "DefaultVideo" => true, + "AlarmFrames" => true, + "TotScore" => true, + "AvgScore" => true, + "MaxScore" => true, + "Archived" => true, + "Videoed" => true, + "Uploaded" => true, + "Emailed" => true, + "Messaged" => true, + "Executed" => true, + "Notes" => true, + "MinFrameId" => array( "sql" => "(SELECT min(Frames.FrameId) FROM Frames WHERE EventId=Events.Id)" ), + "MaxFrameId" => array( "sql" => "(SELECT max(Frames.FrameId) FROM Frames WHERE Events.Id = Frames.EventId)" ), + "MinFrameDelta" => array( "sql" => "(SELECT min(Frames.Delta) FROM Frames WHERE Events.Id = Frames.EventId)" ), + "MaxFrameDelta" => array( "sql" => "(SELECT max(Frames.Delta) FROM Frames WHERE Events.Id = Frames.EventId)" ), + //"Path" => array( "postFunc" => "getEventPath" ), + ), + ), + "frame" => array( + "permission" => "Events", + "table" => "Frames", + "limit" => 1, + "selector" => array( array( "table" => "Events", "join" => "Events.Id = Frames.EventId", "selector"=>"Events.Id" ), "Frames.FrameId" ), + "elements" => array( + //"Id" => array( "sql" => "Frames.FrameId" ), + "FrameId" => true, + "EventId" => true, + "Type" => true, + "TimeStamp" => true, + "TimeStampShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), + "Delta" => true, + "Score" => true, + //"Image" => array( "postFunc" => "getFrameImage" ), + ), + ), + "frameimage" => array( + "permission" => "Events", + "func" => "getFrameImage()" + ), + "nearframe" => array( + "permission" => "Events", + "func" => "getNearFrame()" + ), + "nearevents" => array( + "permission" => "Events", + "func" => "getNearEvents()" + ) + ); -function collectData() -{ - global $statusData; +function collectData() { + global $statusData; - $entitySpec = &$statusData[strtolower(validJsStr($_REQUEST['entity']))]; - #print_r( $entitySpec ); - if ( !canView( $entitySpec['permission'] ) ) - ajaxError( 'Unrecognised action or insufficient permissions' ); + $entitySpec = &$statusData[strtolower(validJsStr($_REQUEST['entity']))]; +#print_r( $entitySpec ); + if ( !canView( $entitySpec['permission'] ) ) + ajaxError( 'Unrecognised action or insufficient permissions' ); - if ( !empty($entitySpec['func']) ) - { - $data = eval( "return( ".$entitySpec['func']." );" ); + if ( !empty($entitySpec['func']) ) { + $data = eval( "return( ".$entitySpec['func']." );" ); + } else { + $data = array(); + $postFuncs = array(); + + $fieldSql = array(); + $joinSql = array(); + $groupSql = array(); + + $elements = &$entitySpec['elements']; + $lc_elements = array_change_key_case( $elements ); + + $id = false; + if ( isset($_REQUEST['id']) ) + if ( !is_array($_REQUEST['id']) ) + $id = array( validJsStr($_REQUEST['id']) ); + else + $id = array_values( $_REQUEST['id'] ); + + if ( !isset($_REQUEST['element']) ) + $_REQUEST['element'] = array_keys( $elements ); + else if ( !is_array($_REQUEST['element']) ) + $_REQUEST['element'] = array( validJsStr($_REQUEST['element']) ); + + if ( isset($entitySpec['selector']) ) { + if ( !is_array($entitySpec['selector']) ) + $entitySpec['selector'] = array( $entitySpec['selector'] ); + foreach( $entitySpec['selector'] as $selector ) + if ( is_array( $selector ) && isset($selector['table']) && isset($selector['join']) ) + $joinSql[] = "left join ".$selector['table']." on ".$selector['join']; } - else - { - $data = array(); - $postFuncs = array(); - $fieldSql = array(); - $joinSql = array(); - $groupSql = array(); - - $elements = &$entitySpec['elements']; - $lc_elements = array_change_key_case( $elements ); - - $id = false; - if ( isset($_REQUEST['id']) ) - if ( !is_array($_REQUEST['id']) ) - $id = array( validJsStr($_REQUEST['id']) ); - else - $id = array_values( $_REQUEST['id'] ); - - if ( !isset($_REQUEST['element']) ) - $_REQUEST['element'] = array_keys( $elements ); - else if ( !is_array($_REQUEST['element']) ) - $_REQUEST['element'] = array( validJsStr($_REQUEST['element']) ); - - if ( isset($entitySpec['selector']) ) - { - if ( !is_array($entitySpec['selector']) ) - $entitySpec['selector'] = array( $entitySpec['selector'] ); - foreach( $entitySpec['selector'] as $selector ) - if ( is_array( $selector ) && isset($selector['table']) && isset($selector['join']) ) - $joinSql[] = "left join ".$selector['table']." on ".$selector['join']; + foreach ( $_REQUEST['element'] as $element ) { + if ( !($elementData = $lc_elements[strtolower($element)]) ) + ajaxError( "Bad ".validJsStr($_REQUEST['entity'])." element ".$element ); + if ( isset($elementData['func']) ) + $data[$element] = eval( "return( ".$elementData['func']." );" ); + else if ( isset($elementData['postFunc']) ) + $postFuncs[$element] = $elementData['postFunc']; + else if ( isset($elementData['zmu']) ) + $data[$element] = exec( escapeshellcmd( getZmuCommand( " ".$elementData['zmu'] ) ) ); + else { + if ( isset($elementData['sql']) ) + $fieldSql[] = $elementData['sql']." as ".$element; + else + $fieldSql[] = $element; + if ( isset($elementData['table']) && isset($elementData['join']) ) { + $joinSql[] = "left join ".$elementData['table']." on ".$elementData['join']; } - - foreach ( $_REQUEST['element'] as $element ) - { - if ( !($elementData = $lc_elements[strtolower($element)]) ) - ajaxError( "Bad ".validJsStr($_REQUEST['entity'])." element ".$element ); - if ( isset($elementData['func']) ) - $data[$element] = eval( "return( ".$elementData['func']." );" ); - else if ( isset($elementData['postFunc']) ) - $postFuncs[$element] = $elementData['postFunc']; - else if ( isset($elementData['zmu']) ) - $data[$element] = exec( escapeshellcmd( getZmuCommand( " ".$elementData['zmu'] ) ) ); - else - { - if ( isset($elementData['sql']) ) - $fieldSql[] = $elementData['sql']." as ".$element; - else - $fieldSql[] = $element; - if ( isset($elementData['table']) && isset($elementData['join']) ) - { - $joinSql[] = "left join ".$elementData['table']." on ".$elementData['join']; - } - if ( isset($elementData['group']) ) - { - $groupSql[] = $elementData['group']; - } - } - } - - if ( count($fieldSql) ) - { - $sql = "select ".join( ", ", $fieldSql )." from ".$entitySpec['table']; - if ( $joinSql ) - $sql .= " ".join( " ", array_unique( $joinSql ) ); - if ( $id && !empty($entitySpec['selector']) ) - { - $index = 0; - $where = array(); - $values = array(); - foreach( $entitySpec['selector'] as $selector ) { - if ( is_array( $selector ) ) { - $where[] = $selector['selector'].' = ?'; - $values[] = validInt($id[$index]); - } else { - $where[] = $selector.' = ?'; - $values[] = validInt($id[$index]); - } - $index++; - } - $sql .= " where ".join( " and ", $where ); - } - if ( $groupSql ) - $sql .= " group by ".join( ",", array_unique( $groupSql ) ); - if ( !empty($_REQUEST['sort']) ) - $sql .= " order by ".$_REQUEST['sort']; - if ( !empty($entitySpec['limit']) ) - $limit = $entitySpec['limit']; - elseif ( !empty($_REQUEST['count']) ) - $limit = validInt($_REQUEST['count']); - $limit_offset=""; - if ( !empty($_REQUEST['offset']) ) - $limit_offset = validInt($_REQUEST['offset']) . ", "; - if ( !empty( $limit ) ) - $sql .= " limit ".$limit_offset.$limit; - if ( isset($limit) && $limit == 1 ) { - if ( $sqlData = dbFetchOne( $sql, NULL, $values ) ) { - foreach ( $postFuncs as $element=>$func ) - $sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' ); - $data = array_merge( $data, $sqlData ); - } - } else { - $count = 0; - foreach( dbFetchAll( $sql, NULL, $values ) as $sqlData ) { - foreach ( $postFuncs as $element=>$func ) - $sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' ); - $data[] = $sqlData; - if ( isset($limi) && ++$count >= $limit ) - break; - } - } + if ( isset($elementData['group']) ) { + $groupSql[] = $elementData['group']; } + } } - #print_r( $data ); - return( $data ); + + if ( count($fieldSql) ) { + $sql = "select ".join( ", ", $fieldSql )." from ".$entitySpec['table']; + if ( $joinSql ) + $sql .= " ".join( " ", array_unique( $joinSql ) ); + if ( $id && !empty($entitySpec['selector']) ) { + $index = 0; + $where = array(); + $values = array(); + foreach( $entitySpec['selector'] as $selector ) { + if ( is_array( $selector ) ) { + $where[] = $selector['selector'].' = ?'; + $values[] = validInt($id[$index]); + } else { + $where[] = $selector.' = ?'; + $values[] = validInt($id[$index]); + } + $index++; + } + $sql .= " where ".join( " and ", $where ); + } + if ( $groupSql ) + $sql .= " GROUP BY ".join( ",", array_unique( $groupSql ) ); + if ( !empty($_REQUEST['sort']) ) + $sql .= " order by ".$_REQUEST['sort']; + if ( !empty($entitySpec['limit']) ) + $limit = $entitySpec['limit']; + elseif ( !empty($_REQUEST['count']) ) + $limit = validInt($_REQUEST['count']); + $limit_offset=""; + if ( !empty($_REQUEST['offset']) ) + $limit_offset = validInt($_REQUEST['offset']) . ", "; + if ( !empty( $limit ) ) + $sql .= " limit ".$limit_offset.$limit; + if ( isset($limit) && $limit == 1 ) { + if ( $sqlData = dbFetchOne( $sql, NULL, $values ) ) { + foreach ( $postFuncs as $element=>$func ) + $sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' ); + $data = array_merge( $data, $sqlData ); + } + } else { + $count = 0; + foreach( dbFetchAll( $sql, NULL, $values ) as $sqlData ) { + foreach ( $postFuncs as $element=>$func ) + $sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' ); + $data[] = $sqlData; + if ( isset($limi) && ++$count >= $limit ) + break; + } + } + } + } +#print_r( $data ); + return( $data ); } $data = collectData(); -if ( !isset($_REQUEST['layout']) ) -{ - $_REQUEST['layout'] = "json"; +if ( !isset($_REQUEST['layout']) ) { + $_REQUEST['layout'] = "json"; } -switch( $_REQUEST['layout'] ) -{ - case 'xml NOT CURRENTLY SUPPORTED' : + +switch( $_REQUEST['layout'] ) { + case 'xml NOT CURRENTLY SUPPORTED' : { - header("Content-type: application/xml" ); - echo( ''."\n" ); - echo "<".strtolower($_REQUEST['entity']).">\n"; - foreach ( $data as $key=>$value ) - { - $key = strtolower( $key ); - echo "<$key>".htmlentities($value)."\n"; - } - echo "\n"; - break; + header("Content-type: application/xml" ); + echo( ''."\n" ); + echo "<".strtolower($_REQUEST['entity']).">\n"; + foreach ( $data as $key=>$value ) { + $key = strtolower( $key ); + echo "<$key>".htmlentities($value)."\n"; + } + echo "\n"; + break; } - case 'json' : + case 'json' : { - $response = array( strtolower(validJsStr($_REQUEST['entity'])) => $data ); - if ( isset($_REQUEST['loopback']) ) - $response['loopback'] = validJsStr($_REQUEST['loopback']); - ajaxResponse( $response ); - break; + $response = array( strtolower(validJsStr($_REQUEST['entity'])) => $data ); + if ( isset($_REQUEST['loopback']) ) + $response['loopback'] = validJsStr($_REQUEST['loopback']); + ajaxResponse( $response ); + break; } - case 'text' : + case 'text' : { - header("Content-type: text/plain" ); - echo join( " ", array_values( $data ) ); - break; + header("Content-type: text/plain" ); + echo join( " ", array_values( $data ) ); + break; } } -function getFrameImage() -{ - $eventId = $_REQUEST['id'][0]; - $frameId = $_REQUEST['id'][1]; +function getFrameImage() { + $eventId = $_REQUEST['id'][0]; + $frameId = $_REQUEST['id'][1]; - $sql = 'select * from Frames where EventId = ? and FrameId = ?'; - if ( !($frame = dbFetchOne( $sql, NULL, array( $eventId, $frameId ) )) ) - { - $frame = array(); - $frame['EventId'] = $eventId; - $frame['FrameId'] = $frameId; - $frame['Type'] = "Virtual"; - } - $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $frame['EventId'] ) ); - $frame['Image'] = getImageSrc( $event, $frame, SCALE_BASE ); - return( $frame ); + $sql = 'select * from Frames where EventId = ? and FrameId = ?'; + if ( !($frame = dbFetchOne( $sql, NULL, array( $eventId, $frameId ) )) ) { + $frame = array(); + $frame['EventId'] = $eventId; + $frame['FrameId'] = $frameId; + $frame['Type'] = "Virtual"; + } + $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $frame['EventId'] ) ); + $frame['Image'] = getImageSrc( $event, $frame, SCALE_BASE ); + return( $frame ); } -function getNearFrame() -{ - $eventId = $_REQUEST['id'][0]; - $frameId = $_REQUEST['id'][1]; +function getNearFrame() { + $eventId = $_REQUEST['id'][0]; + $frameId = $_REQUEST['id'][1]; - $sql = 'select FrameId from Frames where EventId = ? and FrameId <= ? order by FrameId desc limit 1'; - if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) - { - $sql = 'select * from Frames where EventId = ? and FrameId > ? order by FrameId asc limit 1'; - if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) - { - return( array() ); - } + $sql = 'select FrameId from Frames where EventId = ? and FrameId <= ? order by FrameId desc limit 1'; + if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) { + $sql = 'select * from Frames where EventId = ? and FrameId > ? order by FrameId asc limit 1'; + if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) { + return( array() ); } - $_REQUEST['entity'] = "frame"; - $_REQUEST['id'][1] = $nearFrameId; - return( collectData() ); + } + $_REQUEST['entity'] = "frame"; + $_REQUEST['id'][1] = $nearFrameId; + return( collectData() ); } -function getNearEvents() -{ - global $user, $sortColumn, $sortOrder; +function getNearEvents() { + global $user, $sortColumn, $sortOrder; - $eventId = $_REQUEST['id']; - $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $eventId ) ); + $eventId = $_REQUEST['id']; + $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $eventId ) ); - parseFilter( $_REQUEST['filter'] ); - parseSort(); + parseFilter( $_REQUEST['filter'] ); + parseSort(); - if ( $user['MonitorIds'] ) - $midSql = " and MonitorId in (".join( ",", preg_split( '/["\'\s]*,["\'\s]*/', $user['MonitorIds'] ) ).")"; - else - $midSql = ''; + if ( $user['MonitorIds'] ) + $midSql = " and MonitorId in (".join( ",", preg_split( '/["\'\s]*,["\'\s]*/', $user['MonitorIds'] ) ).")"; + else + $midSql = ''; - $sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where ".dbEscape($sortColumn)." ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn ".($sortOrder=='asc'?'desc':'asc'); - $result = dbQuery( $sql ); - while ( $id = dbFetchNext( $result, 'Id' ) ) - { - if ( $id == $eventId ) - { - $prevId = dbFetchNext( $result, 'Id' ); - break; - } + $sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where ".dbEscape($sortColumn)." ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn ".($sortOrder=='asc'?'desc':'asc'); + $result = dbQuery( $sql ); + while ( $id = dbFetchNext( $result, 'Id' ) ) { + if ( $id == $eventId ) { + $prevEvent = dbFetchNext( $result ); + break; } + } - $sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn $sortOrder"; - $result = dbQuery( $sql ); - while ( $id = dbFetchNext( $result, 'Id' ) ) - { - if ( $id == $eventId ) - { - $nextId = dbFetchNext( $result, 'Id' ); - break; - } + $sql = "select E.Id as Id from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn $sortOrder"; + $result = dbQuery( $sql ); + while ( $id = dbFetchNext( $result, 'Id' ) ) { + if ( $id == $eventId ) { + $nextEvent = dbFetchNext( $result ); + break; } + } - $result = array( 'EventId'=>$eventId ); - $result['PrevEventId'] = empty($prevId)?0:$prevId; - $result['NextEventId'] = empty($nextId)?0:$nextId; - return( $result ); + $result = array( 'EventId'=>$eventId ); + $result['PrevEventId'] = empty($prevEvent)?0:$prevEvent['Id']; + $result['NextEventId'] = empty($nextEvent)?0:$nextEvent['Id']; + $result['PrevEventDefVideoPath'] = empty($prevEvent)?0:(getEventDefaultVideoPath($prevEvent)); + $result['NextEventDefVideoPath'] = empty($nextEvent)?0:(getEventDefaultVideoPath($nextEvent)); + return( $result ); } ?> diff --git a/web/ajax/stream.php b/web/ajax/stream.php index 4f453d50f..463558640 100644 --- a/web/ajax/stream.php +++ b/web/ajax/stream.php @@ -52,7 +52,7 @@ while ( !file_exists($remSockFile) && $max_socket_tries-- ) { //sometimes we are } if ( !file_exists($remSockFile) ) { - ajaxError("Socket $remSocketFile does not exist. This file is created by zms, and since it does not exist, either zms did not run, or zms exited early. Please check your zms logs and ensure that CGI is enabled in apache and check that the PATH_ZMS is set correctly. Make sure that ZM is actually recording. If you are trying to view a live stream and the capture process (zmc) is not running then zms will exit. Please go to http://zoneminder.readthedocs.io/en/latest/faq.html#why-can-t-i-see-streamed-images-when-i-can-see-stills-in-the-zone-window-etc for more information."); + ajaxError("Socket $remSockFile does not exist. This file is created by zms, and since it does not exist, either zms did not run, or zms exited early. Please check your zms logs and ensure that CGI is enabled in apache and check that the PATH_ZMS is set correctly. Make sure that ZM is actually recording. If you are trying to view a live stream and the capture process (zmc) is not running then zms will exit. Please go to http://zoneminder.readthedocs.io/en/latest/faq.html#why-can-t-i-see-streamed-images-when-i-can-see-stills-in-the-zone-window-etc for more information."); } else { if ( !@socket_sendto( $socket, $msg, strlen($msg), 0, $remSockFile ) ) { ajaxError( "socket_sendto( $remSockFile ) failed: ".socket_strerror(socket_last_error()) ); @@ -108,10 +108,19 @@ switch ( $data['type'] ) case MSG_DATA_WATCH : { $data = unpack( "ltype/imonitor/istate/dfps/ilevel/irate/ddelay/izoom/Cdelayed/Cpaused/Cenabled/Cforced", $msg ); - $data['fps'] = sprintf( "%.2f", $data['fps'] ); + $data['fps'] = round( $data['fps'], 2 ); $data['rate'] /= RATE_BASE; - $data['delay'] = sprintf( "%.2f", $data['delay'] ); - $data['zoom'] = sprintf( "%.1f", $data['zoom']/SCALE_BASE ); + $data['delay'] = round( $data['delay'], 2 ); + $data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 ); + if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == "hashed" ) { + session_start(); + $time = time(); + // Regenerate auth hash after half the lifetime of the hash + if ( $_SESSION['AuthHashGeneratedAt'] < $time - (ZM_AUTH_HASH_TTL * 1800) ) { + $data['auth'] = generateAuthHash( ZM_AUTH_HASH_IPS ); + } + session_write_close(); + } ajaxResponse( array( 'status'=>$data ) ); break; } @@ -120,7 +129,16 @@ switch ( $data['type'] ) $data = unpack( "ltype/ievent/iprogress/irate/izoom/Cpaused", $msg ); //$data['progress'] = sprintf( "%.2f", $data['progress'] ); $data['rate'] /= RATE_BASE; - $data['zoom'] = sprintf( "%.1f", $data['zoom']/SCALE_BASE ); + $data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 ); + if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == "hashed" ) { + session_start(); + $time = time(); + // Regenerate auth hash after half the lifetime of the hash + if ( $_SESSION['AuthHashGeneratedAt'] < $time - (ZM_AUTH_HASH_TTL * 1800) ) { + $data['auth'] = generateAuthHash( ZM_AUTH_HASH_IPS ); + } + session_write_close(); + } ajaxResponse( array( 'status'=>$data ) ); break; } diff --git a/web/api/.gitattributes b/web/api/.gitattributes deleted file mode 100644 index fc3f49166..000000000 --- a/web/api/.gitattributes +++ /dev/null @@ -1,33 +0,0 @@ -# Define the line ending behavior of the different file extensions -# Set default behaviour, in case users don't have core.autocrlf set. -* text=auto - -# Explicitly declare text files we want to always be normalized and converted -# to native line endings on checkout. -*.php text -*.default text -*.ctp text -*.sql text -*.md text -*.po text -*.js text -*.css text -*.ini text -*.properties text -*.txt text -*.xml text -*.yml text -.htaccess text - -# Declare files that will always have CRLF line endings on checkout. -*.bat eol=crlf - -# Declare files that will always have LF line endings on checkout. -*.pem eol=lf - -# Denote all files that are truly binary and should not be modified. -*.png binary -*.jpg binary -*.gif binary -*.ico binary -*.mo binary \ No newline at end of file diff --git a/web/api/app/Config/core.php.default b/web/api/app/Config/core.php.default index 43736a61f..a210fbd79 100644 --- a/web/api/app/Config/core.php.default +++ b/web/api/app/Config/core.php.default @@ -31,7 +31,7 @@ * In production mode, flash messages redirect after a time interval. * In development mode, you need to click the flash message to continue. */ - Configure::write('debug', 2); + Configure::write('debug', 0); /** * Configure the Error handler used to handle errors for your application. By default diff --git a/web/api/app/Config/database.php.default b/web/api/app/Config/database.php.default index aeaba1cb1..52ad549a2 100644 --- a/web/api/app/Config/database.php.default +++ b/web/api/app/Config/database.php.default @@ -67,6 +67,7 @@ class DATABASE_CONFIG { public $default = array( 'datasource' => 'Database/Mysql', 'persistent' => false, + 'host' => ZM_DB_HOST, 'login' => ZM_DB_USER, 'password' => ZM_DB_PASS, 'database' => ZM_DB_NAME, diff --git a/web/api/app/Console/cake.bat b/web/api/app/Console/cake.bat index 919ecac49..c33bf22f8 100644 --- a/web/api/app/Console/cake.bat +++ b/web/api/app/Console/cake.bat @@ -1,31 +1,31 @@ -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -:: -:: Bake is a shell script for running CakePHP bake script -:: -:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org) -:: Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) -:: -:: Licensed under The MIT License -:: Redistributions of files must retain the above copyright notice. -:: -:: @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) -:: @link http://cakephp.org CakePHP(tm) Project -:: @package app.Console -:: @since CakePHP(tm) v 2.0 -:: @license http://www.opensource.org/licenses/mit-license.php MIT License -:: -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -:: In order for this script to work as intended, the cake\console\ folder must be in your PATH - -@echo. -@echo off - -SET app=%0 -SET lib=%~dp0 - -php -q "%lib%cake.php" -working "%CD% " %* - -echo. - -exit /B %ERRORLEVEL% +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: Bake is a shell script for running CakePHP bake script +:: +:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org) +:: Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) +:: +:: Licensed under The MIT License +:: Redistributions of files must retain the above copyright notice. +:: +:: @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) +:: @link http://cakephp.org CakePHP(tm) Project +:: @package app.Console +:: @since CakePHP(tm) v 2.0 +:: @license http://www.opensource.org/licenses/mit-license.php MIT License +:: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +:: In order for this script to work as intended, the cake\console\ folder must be in your PATH + +@echo. +@echo off + +SET app=%0 +SET lib=%~dp0 + +php -q "%lib%cake.php" -working "%CD% " %* + +echo. + +exit /B %ERRORLEVEL% diff --git a/web/api/app/Controller/AppController.php b/web/api/app/Controller/AppController.php index 6775fa846..bfb20aa49 100644 --- a/web/api/app/Controller/AppController.php +++ b/web/api/app/Controller/AppController.php @@ -91,7 +91,7 @@ class AppController extends Controller { if( ! $this->Session->Read('user.Username') ) { throw new UnauthorizedException(__('Not Authenticated')); return; - } else if ( ! $this->Session->Read('user.Username') ) { + } else if ( ! $this->Session->Read('user.Enabled') ) { throw new UnauthorizedException(__('User is not enabled')); return; } diff --git a/web/api/lib/Cake/Console/Templates/skel/Console/cake.bat b/web/api/lib/Cake/Console/Templates/skel/Console/cake.bat index 0aa43c024..e37d4a524 100644 --- a/web/api/lib/Cake/Console/Templates/skel/Console/cake.bat +++ b/web/api/lib/Cake/Console/Templates/skel/Console/cake.bat @@ -1,30 +1,30 @@ -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -:: -:: Bake is a shell script for running CakePHP bake script -:: -:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org) -:: Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) -:: -:: Licensed under The MIT License -:: Redistributions of files must retain the above copyright notice. -:: -:: @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) -:: @link http://cakephp.org CakePHP(tm) Project -:: @package app.Console -:: @since CakePHP(tm) v 2.0 -:: -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -:: In order for this script to work as intended, the cake\console\ folder must be in your PATH - -@echo. -@echo off - -SET app=%0 -SET lib=%~dp0 - -php -q "%lib%cake.php" -working "%CD% " %* - -echo. - -exit /B %ERRORLEVEL% +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: Bake is a shell script for running CakePHP bake script +:: +:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org) +:: Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) +:: +:: Licensed under The MIT License +:: Redistributions of files must retain the above copyright notice. +:: +:: @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) +:: @link http://cakephp.org CakePHP(tm) Project +:: @package app.Console +:: @since CakePHP(tm) v 2.0 +:: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +:: In order for this script to work as intended, the cake\console\ folder must be in your PATH + +@echo. +@echo off + +SET app=%0 +SET lib=%~dp0 + +php -q "%lib%cake.php" -working "%CD% " %* + +echo. + +exit /B %ERRORLEVEL% diff --git a/web/api/lib/Cake/Console/cake.bat b/web/api/lib/Cake/Console/cake.bat index 905cc39fb..34429cb24 100644 --- a/web/api/lib/Cake/Console/cake.bat +++ b/web/api/lib/Cake/Console/cake.bat @@ -1,28 +1,28 @@ -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -:: -:: Bake is a shell script for running CakePHP bake script -:: -:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org) -:: Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) -:: -:: Licensed under The MIT License -:: Redistributions of files must retain the above copyright notice. -:: -:: @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) -:: @link http://cakephp.org CakePHP(tm) Project -:: @package Cake.Console -:: @since CakePHP(tm) v 1.2.0.5012 -:: @license http://www.opensource.org/licenses/mit-license.php MIT License -:: -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -@echo off - -SET app=%0 -SET lib=%~dp0 - -php -q "%lib%cake.php" -working "%CD% " %* - -echo. - -exit /B %ERRORLEVEL% +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: Bake is a shell script for running CakePHP bake script +:: +:: CakePHP(tm) : Rapid Development Framework (http://cakephp.org) +:: Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) +:: +:: Licensed under The MIT License +:: Redistributions of files must retain the above copyright notice. +:: +:: @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) +:: @link http://cakephp.org CakePHP(tm) Project +:: @package Cake.Console +:: @since CakePHP(tm) v 1.2.0.5012 +:: @license http://www.opensource.org/licenses/mit-license.php MIT License +:: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +@echo off + +SET app=%0 +SET lib=%~dp0 + +php -q "%lib%cake.php" -working "%CD% " %* + +echo. + +exit /B %ERRORLEVEL% diff --git a/web/css/Makefile.am b/web/css/Makefile.am new file mode 100644 index 000000000..3094d2afa --- /dev/null +++ b/web/css/Makefile.am @@ -0,0 +1,9 @@ +AUTOMAKE_OPTIONS = gnu + +webdir = @WEB_PREFIX@/css + +dist_web_DATA = \ + bootstrap.min.css \ + reset.css \ + spinner.css \ + overlay.css diff --git a/web/css/bootstrap.min.css b/web/css/bootstrap.min.css index ed3905e0e..73bd8aed0 100644 --- a/web/css/bootstrap.min.css +++ b/web/css/bootstrap.min.css @@ -3,4 +3,3 @@ * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/web/includes/Event.php b/web/includes/Event.php index e9ecd4bae..11a8f4faa 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -196,7 +196,7 @@ class Event { } // end function createListThumbnail function getImageSrc( $frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=false ) { - $Storage = new Storage( $this->{'StorageId'} ); + $Storage = new Storage( isset($this->{'StorageId'}) ? $this->{'StorageId'} : NULL ); $Event = $this; $eventPath = $Event->Path(); diff --git a/web/includes/Frame.php b/web/includes/Frame.php index 661654d24..eaec94c58 100644 --- a/web/includes/Frame.php +++ b/web/includes/Frame.php @@ -6,7 +6,7 @@ class Frame { public function __construct( $IdOrRow ) { $row = NULL; if ( $IdOrRow ) { - if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + if ( is_integer( $IdOrRow ) or ctype_digit($IdOrRow) ) { $row = dbFetchOne( 'SELECT * FROM Frames WHERE Id=?', NULL, array( $IdOrRow ) ); if ( ! $row ) { Error("Unable to load Frame record for Id=" . $IdOrRow ); @@ -34,7 +34,7 @@ class Frame { return new Event( $this->{'EventId'} ); } public function __call( $fn, array $args){ - if(isset($this->{$fn})){ + if( array_key_exists( $fn, $this ) ) { return $this->{$fn}; #array_unshift($args, $this); #call_user_func_array( $this->{$fn}, $args); @@ -70,7 +70,7 @@ class Frame { } public function getImageSrc( $show='capture' ) { - return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'Id'}.'&show='.$show;; + return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'Id'}.'&show='.$show.'&filename='.$this->Event()->MonitorId().'_'.$this->{'EventId'}.'_'.$this->{'FrameId'}.'.jpg'; } // end function getImageSrc public static function find( $parameters = array(), $limit = NULL ) { @@ -84,7 +84,15 @@ class Frame { $values = array_values( $parameters ); } if ( $limit ) { - $sql .= ' LIMIT ' . $limit; + if ( is_integer( $limit ) or ctype_digit( $limit ) ) { + $sql .= ' LIMIT ' . $limit; + } else { + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + Error("Invalid value for limit($limit) passed to Frame::find from $file:$line"); + return; + } } $results = dbFetchAll( $sql, NULL, $values ); if ( $results ) { diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 6b983018e..9e4c6a95a 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -3,94 +3,238 @@ require_once( 'database.php' ); require_once( 'Server.php' ); class Monitor { - public function __construct( $IdOrRow ) { - $row = NULL; - if ( $IdOrRow ) { - if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { - $row = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array( $IdOrRow ) ); - if ( ! $row ) { - Error("Unable to load Server record for Id=" . $IdOrRow ); - } - } elseif ( is_array( $IdOrRow ) ) { - $row = $IdOrRow; - } else { - Error("Unknown argument passed to Monitor Constructor ($IdOrRow)"); - return; - } - } # end if isset($IdOrRow) - if ( $row ) { - foreach ($row as $k => $v) { - $this->{$k} = $v; - } - if ( $this->{'Controllable'} ) { - $s = dbFetchOne( 'SELECT * FROM Controls WHERE Id=?', NULL, array( $this->{'ControlId'} ) ); - foreach ($s as $k => $v) { - if ( $k == 'Id' ) { - continue; - } - $this->{$k} = $v; - } - } +private $control_fields = array( + 'Name' => '', + 'Type' => 'Local', + 'Protocol' => NULL, + 'CanWake' => '0', + 'CanSleep' => '0', + 'CanReset' => '0', + 'CanZoom' => '0', + 'CanAutoZoom' => '0', + 'CanZoomAbs' => '0', + 'CanZoomRel' => '0', + 'CanZoomCon' => '0', + 'MinZoomRange' => NULL, + 'MaxZoomRange' => NULL, + 'MinZoomStep' => NULL, + 'MaxZoomStep' => NULL, + 'HasZoomSpeed' => '0', + 'MinZoomSpeed' => NULL, + 'MaxZoomSpeed' => NULL, + 'CanFocus' => '0', + 'CanAutoFocus' => '0', + 'CanFocusAbs' => '0', + 'CanFocusRel' => '0', + 'CanFocusCon' => '0', + 'MinFocusRange' => NULL, + 'MaxFocusRange' => NULL, + 'MinFocusStep' => NULL, + 'MaxFocusStep' => NULL, + 'HasFocusSpeed' => '0', + 'MinFocusSpeed' => NULL, + 'MaxFocusSpeed' => NULL, + 'CanIris' => '0', + 'CanAutoIris' => '0', + 'CanIrisAbs' => '0', + 'CanIrisRel' => '0', + 'CanIrisCon' => '0', + 'MinIrisRange' => NULL, + 'MaxIrisRange' => NULL, + 'MinIrisStep' => NULL, + 'MaxIrisStep' => NULL, + 'HasIrisSpeed' => '0', + 'MinIrisSpeed' => NULL, + 'MaxIrisSpeed' => NULL, + 'CanGain' => '0', + 'CanAutoGain' => '0', + 'CanGainAbs' => '0', + 'CanGainRel' => '0', + 'CanGainCon' => '0', + 'MinGainRange' => NULL, + 'MaxGainRange' => NULL, + 'MinGainStep' => NULL, + 'MaxGainStep' => NULL, + 'HasGainSpeed' => '0', + 'MinGainSpeed' => NULL, + 'MaxGainSpeed' => NULL, + 'CanWhite' => '0', + 'CanAutoWhite' => '0', + 'CanWhiteAbs' => '0', + 'CanWhiteRel' => '0', + 'CanWhiteCon' => '0', + 'MinWhiteRange' => NULL, + 'MaxWhiteRange' => NULL, + 'MinWhiteStep' => NULL, + 'MaxWhiteStep' => NULL, + 'HasWhiteSpeed' => '0', + 'MinWhiteSpeed' => NULL, + 'MaxWhiteSpeed' => NULL, + 'HasPresets' => '0', + 'NumPresets' => '0', + 'HasHomePreset' => '0', + 'CanSetPresets' => '0', + 'CanMove' => '0', + 'CanMoveDiag' => '0', + 'CanMoveMap' => '0', + 'CanMoveAbs' => '0', + 'CanMoveRel' => '0', + 'CanMoveCon' => '0', + 'CanPan' => '0', + 'MinPanRange' => NULL, + 'MaxPanRange' => NULL, + 'MinPanStep' => NULL, + 'MaxPanStep' => NULL, + 'HasPanSpeed' => '0', + 'MinPanSpeed' => NULL, + 'MaxPanSpeed' => NULL, + 'HasTurboPan' => '0', + 'TurboPanSpeed' => NULL, + 'CanTilt' => '0', + 'MinTiltRange' => NULL, + 'MaxTiltRange' => NULL, + 'MinTiltStep' => NULL, + 'MaxTiltStep' => NULL, + 'HasTiltSpeed' => '0', + 'MinTiltSpeed' => NULL, + 'MaxTiltSpeed' => NULL, + 'HasTurboTilt' => '0', + 'TurboTiltSpeed' => NULL, + 'CanAutoScan' => '0', + 'NumScanPaths' => '0', +); - } else { - Error("No row for Monitor " . $IdOrRow ); - } - } // end function __construct - public function Server() { - return new Server( $this->{'ServerId'} ); - } - public function __call( $fn, array $args){ - if(isset($this->{$fn})){ - return $this->{$fn}; - #array_unshift($args, $this); - #call_user_func_array( $this->{$fn}, $args); + public function __construct( $IdOrRow = NULL ) { + if ( $IdOrRow ) { + $row = NULL; + if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + $row = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array( $IdOrRow ) ); + if ( ! $row ) { + Error("Unable to load Server record for Id=" . $IdOrRow ); } - } - public function getStreamSrc( $args, $querySep='&' ) { - if ( isset($this->{'ServerId'}) and $this->{'ServerId'} ) { - $Server = new Server( $this->{'ServerId'} ); - $streamSrc = ZM_BASE_PROTOCOL.'://'.$Server->Hostname().ZM_PATH_ZMS; + } elseif ( is_array( $IdOrRow ) ) { + $row = $IdOrRow; + } else { + Error("Unknown argument passed to Monitor Constructor ($IdOrRow)"); + return; + } + + if ( $row ) { + foreach ($row as $k => $v) { + $this->{$k} = $v; + } + if ( $this->{'Controllable'} ) { + $s = dbFetchOne( 'SELECT * FROM Controls WHERE Id=?', NULL, array( $this->{'ControlId'} ) ); + foreach ($s as $k => $v) { + if ( $k == 'Id' ) { + continue; +# The reason for these is that the name overlaps Monitor fields. + } else if ( $k == 'Protocol' ) { + $this->{'ControlProtocol'} = $v; + } else if ( $k == 'Name' ) { + $this->{'ControlName'} = $v; + } else if ( $k == 'Type' ) { + $this->{'ControlType'} = $v; + } else { + $this->{$k} = $v; + } + } + } + + } else { + Error('No row for Monitor ' . $IdOrRow ); + } + } # end if isset($IdOrRow) + } // end function __construct + public function Server() { + return new Server( $this->{'ServerId'} ); + } + public function __call( $fn, array $args){ + if ( count( $args ) ) { + $this->{$fn} = $args[0]; + } + if ( array_key_exists( $fn, $this ) ) { + return $this->{$fn}; + #array_unshift($args, $this); + #call_user_func_array( $this->{$fn}, $args); } else { - $streamSrc = ZM_BASE_URL.ZM_PATH_ZMS; - } + if ( array_key_exists( $fn, $this->control_fields ) ) { + return $this->control_fields{$fn}; + } else { - $args[] = "monitor=".$this->{'Id'}; + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + Warning( "Unknown function call Monitor->$fn from $file:$line" ); + } + } + } - if ( ZM_OPT_USE_AUTH ) { - if ( ZM_AUTH_RELAY == "hashed" ) { - $args[] = "auth=".generateAuthHash( ZM_AUTH_HASH_IPS ); - } elseif ( ZM_AUTH_RELAY == "plain" ) { - $args[] = "user=".$_SESSION['username']; - $args[] = "pass=".$_SESSION['password']; - } elseif ( ZM_AUTH_RELAY == "none" ) { - $args[] = "user=".$_SESSION['username']; - } - } - if ( !in_array( "mode=single", $args ) && !empty($GLOBALS['connkey']) ) { - $args[] = "connkey=".$GLOBALS['connkey']; - } - if ( ZM_RAND_STREAM ) { - $args[] = "rand=".time(); - } + public function getStreamSrc( $args, $querySep='&' ) { + if ( isset($this->{'ServerId'}) and $this->{'ServerId'} ) { + $Server = new Server( $this->{'ServerId'} ); + $streamSrc = ZM_BASE_PROTOCOL.'://'.$Server->Hostname().ZM_PATH_ZMS; + } else { + $streamSrc = ZM_BASE_URL.ZM_PATH_ZMS; + } - if ( count($args) ) { - $streamSrc .= "?".join( $querySep, $args ); - } + $args['monitor'] = $this->{'Id'}; - return( $streamSrc ); - } // end function etStreamSrc - public function Width() { - if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { - return $this->{'Height'}; - } - return $this->{'Width'}; - } - public function Height() { - if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { - return $this->{'Width'}; - } - return $this->{'Height'}; - } + if ( ZM_OPT_USE_AUTH ) { + if ( ZM_AUTH_RELAY == 'hashed' ) { + $args['auth'] = generateAuthHash( ZM_AUTH_HASH_IPS ); + } elseif ( ZM_AUTH_RELAY == 'plain' ) { + $args['user'] = $_SESSION['username']; + $args['pass'] = $_SESSION['password']; + } elseif ( ZM_AUTH_RELAY == 'none' ) { + $args['user'] = $_SESSION['username']; + } + } + if ( ( (!isset($args['mode'])) or ( $args['mode'] != 'single' ) ) && !empty($GLOBALS['connkey']) ) { + $args['connkey'] = $GLOBALS['connkey']; + } + if ( ZM_RAND_STREAM ) { + $args['rand'] = time(); + } + + if ( count($args) ) { + $streamSrc .= '?'.http_build_query( $args,'', $querySep ); + } + + return( $streamSrc ); + } // end function getStreamSrc + + public function Width() { + if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { + return $this->{'Height'}; + } + return $this->{'Width'}; + } + + public function Height() { + if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { + return $this->{'Width'}; + } + return $this->{'Height'}; + } + + public function set( $data ) { + foreach ($data as $k => $v) { + if ( is_array( $v ) ) { + # perhaps should turn into a comma-separated string + $this->{$k} = implode(',',$v); + } else if ( is_string( $v ) ) { + $this->{$k} = trim( $v ); + } else if ( is_integer( $v ) ) { + $this->{$k} = $v; + } else if ( is_bool( $v ) ) { + $this->{$k} = $v; + } else { + Error( "Unknown type $k => $v of var " . gettype( $v ) ); + $this->{$k} = $v; + } + } + } } ?> diff --git a/web/includes/Server.php b/web/includes/Server.php index dfce67eb8..fa892f8d9 100644 --- a/web/includes/Server.php +++ b/web/includes/Server.php @@ -5,7 +5,7 @@ class Server { public function __construct( $IdOrRow = NULL ) { $row = NULL; if ( $IdOrRow ) { - if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + if ( is_integer( $IdOrRow ) or ctype_digit( $IdOrRow ) ) { $row = dbFetchOne( 'SELECT * FROM Servers WHERE Id=?', NULL, array( $IdOrRow ) ); if ( ! $row ) { Error("Unable to load Server record for Id=" . $IdOrRow ); @@ -47,7 +47,7 @@ class Server { return $this->{'Name'}; } public function __call( $fn, array $args= NULL){ - if(isset($this->{$fn})){ + if( array_key_exists( $fn, $this) ) { return $this->{$fn}; #array_unshift($args, $this); #call_user_func_array( $this->{$fn}, $args); @@ -63,9 +63,15 @@ class Server { ) ); $values = array_values( $parameters ); } - if ( $limit ) { - $sql .= ' LIMIT ' . $limit; - } + if ( is_integer( $limit ) or ctype_digit( $limit ) ) { + $sql .= ' LIMIT ' . $limit; + } else { + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + Error("Invalid value for limit($limit) passed to Server::find from $file:$line"); + return; + } $results = dbFetchAll( $sql, NULL, $values ); if ( $results ) { return array_map( function($id){ return new Server($id); }, $results ); diff --git a/web/includes/Storage.php b/web/includes/Storage.php index 546cb9cbe..940653404 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -1,51 +1,89 @@ $v) { - $this->{$k} = $v; - } - } else { - $this->{'Name'} = ''; - $this->{'Path'} = ''; - } + public function __construct( $IdOrRow = NULL ) { + $row = NULL; + if ( $IdOrRow ) { + if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + $row = dbFetchOne( 'SELECT * FROM Storage WHERE Id=?', NULL, array( $IdOrRow ) ); + if ( ! $row ) { + Error("Unable to load Storage record for Id=" . $IdOrRow ); + } + } elseif ( is_array( $IdOrRow ) ) { + $row = $IdOrRow; + } } + if ( $row ) { + foreach ($row as $k => $v) { + $this->{$k} = $v; + } + } else { + $this->{'Name'} = ''; + $this->{'Path'} = ''; + } + } - public function Path() { - if ( isset( $this->{'Path'} ) and ( $this->{'Path'} != '' ) ) { - return $this->{'Path'}; - } else if ( ! isset($this->{'Id'}) ) { - return ZM_DIR_EVENTS; - } - return $this->{'Name'}; - } - public function __call( $fn, array $args= NULL){ - if(isset($this->{$fn})){ - return $this->{$fn}; - #array_unshift($args, $this); - #call_user_func_array( $this->{$fn}, $args); - } + public function Path() { + if ( isset( $this->{'Path'} ) and ( $this->{'Path'} != '' ) ) { + return $this->{'Path'}; + } else if ( ! isset($this->{'Id'}) ) { + $path = ZM_DIR_EVENTS; + if ( $path[0] != '/' ) { + $this->{'Path'} = ZM_PATH_WEB.'/'.ZM_DIR_EVENTS; + } else { + $this->{'Path'} = ZM_DIR_EVENTS; + } + return $this->{'Path'}; + } - public static function find_all() { - $storage_areas = array(); - $result = dbQuery( 'SELECT * FROM Storage ORDER BY Name'); - $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Storage' ); - foreach ( $results as $row => $obj ) { - $storage_areas[] = $obj; - } - return $storage_areas; + return $this->{'Name'}; + } + public function Name() { + if ( isset( $this->{'Name'} ) and ( $this->{'Name'} != '' ) ) { + return $this->{'Name'}; + } else if ( ! isset($this->{'Id'}) ) { + return 'Default'; } + return $this->{'Name'}; + } + + public function __call( $fn, array $args= NULL){ + if(isset($this->{$fn})){ + return $this->{$fn}; +#array_unshift($args, $this); +#call_user_func_array( $this->{$fn}, $args); + } + } + public static function find_all() { + $storage_areas = array(); + $result = dbQuery( 'SELECT * FROM Storage ORDER BY Name'); + $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Storage' ); + foreach ( $results as $row => $obj ) { + $storage_areas[] = $obj; + } + return $storage_areas; + } + public function disk_usage_percent() { + $path = $this->Path(); + if ( ! $path ) { + Warning("Storage::disk_usage_percent: path is empty"); + return 0; + } else if ( ! file_exists( $path ) ) { + Warning("Storage::disk_usage_percent: path $path does not exist"); + return 0; + } + + $total = disk_total_space( $path ); + if ( ! $total ) { + Error("disk_total_space returned false for " . $path ); + return 0; + } + $free = disk_free_space( $path ); + if ( ! $free ) { + Error("disk_free_space returned false for " . $path ); + } + $usage = round(($total - $free) / $total * 100); + return $usage; + } } ?> diff --git a/web/includes/actions.php b/web/includes/actions.php index 8c1300f37..d306c3bbc 100644 --- a/web/includes/actions.php +++ b/web/includes/actions.php @@ -23,12 +23,11 @@ // credit: http://wezfurlong.org/blog/2006/nov/http-post-from-php-without-curl/ -function do_post_request($url, $data, $optional_headers = null) -{ +function do_post_request($url, $data, $optional_headers = null) { $params = array('http' => array( - 'method' => 'POST', - 'content' => $data - )); + 'method' => 'POST', + 'content' => $data + )); if ($optional_headers !== null) { $params['http']['header'] = $optional_headers; } @@ -44,990 +43,864 @@ function do_post_request($url, $data, $optional_headers = null) return $response; } -function getAffectedIds( $name ) -{ - $names = $name."s"; - $ids = array(); - if ( isset($_REQUEST[$names]) || isset($_REQUEST[$name]) ) - { - if ( isset($_REQUEST[$names]) ) - $ids = validInt($_REQUEST[$names]); - else if ( isset($_REQUEST[$name]) ) - $ids[] = validInt($_REQUEST[$name]); - } - return( $ids ); +function getAffectedIds( $name ) { + $names = $name.'s'; + $ids = array(); + if ( isset($_REQUEST[$names]) || isset($_REQUEST[$name]) ) { + if ( isset($_REQUEST[$names]) ) + $ids = validInt($_REQUEST[$names]); + else if ( isset($_REQUEST[$name]) ) + $ids[] = validInt($_REQUEST[$name]); + } + return( $ids ); } -if ( ZM_OPT_USE_AUTH && ZM_AUTH_HASH_LOGINS && empty($user) && !empty($_REQUEST['auth']) ) -{ - if ( $authUser = getAuthUser( $_REQUEST['auth'] ) ) + +if ( !empty($action) ) { + if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 'remote' || isset($_REQUEST['password']) ) ) { + // if true, a popup will display after login + // PP - lets validate reCaptcha if it exists + if ( defined('ZM_OPT_USE_GOOG_RECAPTCHA') + && defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY') + && defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY') + && ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_GOOG_RECAPTCHA_SECRETKEY + && ZM_OPT_GOOG_RECAPTCHA_SITEKEY) { - userLogin( $authUser['Username'], $authUser['Password'], true ); - } -} - -if ( !empty($action) ) -{ - if ( $action == "login" && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == "remote" || isset($_REQUEST['password']) ) ) - { - // if true, a popup will display after login - // PP - lets validate reCaptcha if it exists - if ( defined('ZM_OPT_USE_GOOG_RECAPTCHA') - && defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY') - && defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY') - && ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_GOOG_RECAPTCHA_SECRETKEY - && ZM_OPT_GOOG_RECAPTCHA_SITEKEY) - { - $url = 'https://www.google.com/recaptcha/api/siteverify'; - $fields = array ( - 'secret'=> ZM_OPT_GOOG_RECAPTCHA_SECRETKEY, - 'response' => $_REQUEST['g-recaptcha-response'], - 'remoteip'=> $_SERVER['REMOTE_ADDR'] - - ); - $res= do_post_request($url, http_build_query($fields)); - $responseData = json_decode($res,true); - // PP - credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php - // if recaptcha resulted in error, we might have to deny login - if (isset($responseData['success']) && $responseData['success'] == false) - { - // PP - before we deny auth, let's make sure the error was not 'invalid secret' - // because that means the user did not configure the secret key correctly - // in this case, we prefer to let him login in and display a message to correct - // the key. Unfortunately, there is no way to check for invalid site key in code - // as it produces the same error as when you don't answer a recaptcha - if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) - { - if (!in_array('invalid-input-secret',$responseData['error-codes'])) - { - Error ("reCaptcha authentication failed"); - userLogout(); - $view='login'; - $refreshParent = true; - } - else - { - //Let them login but show an error - echo ''; - Error ("Invalid recaptcha secret detected"); - - } - } - - } - - } - } - - // General scope actions - if ( $action == "login" && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == "remote" || isset($_REQUEST['password']) ) ) - { - $username = validStr( $_REQUEST['username'] ); - $password = isset($_REQUEST['password'])?validStr($_REQUEST['password']):''; - userLogin( $username, $password ); - } - elseif ( $action == "logout" ) - { - userLogout(); - $refreshParent = true; - $view = 'none'; - } - elseif ( $action == "bandwidth" && isset($_REQUEST['newBandwidth']) ) - { - $_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']); - setcookie( "zmBandwidth", validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10 ); - $refreshParent = true; - } - - // Event scope actions, view permissions only required - if ( canView( 'Events' ) ) - { - if ( $action == "filter" ) - { - if ( !empty($_REQUEST['subaction']) ) - { - if ( $_REQUEST['subaction'] == "addterm" ) - $_REQUEST['filter'] = addFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); - elseif ( $_REQUEST['subaction'] == "delterm" ) - $_REQUEST['filter'] = delFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); - } - elseif ( canEdit( 'Events' ) ) - { - if ( !empty($_REQUEST['execute']) ) - $tempFilterName = "_TempFilter".time(); - if ( isset($tempFilterName) ) - $filterName = $tempFilterName; - elseif ( !empty($_REQUEST['newFilterName']) ) - $filterName = $_REQUEST['newFilterName']; - if ( !empty($filterName) ) - { - $_REQUEST['filter']['sort_field'] = validStr($_REQUEST['sort_field']); - $_REQUEST['filter']['sort_asc'] = validStr($_REQUEST['sort_asc']); - $_REQUEST['filter']['limit'] = validInt($_REQUEST['limit']); - $sql = "replace into Filters set Name = ".dbEscape($filterName).", Query = ".dbEscape(jsonEncode($_REQUEST['filter'])); - if ( !empty($_REQUEST['AutoArchive']) ) - $sql .= ", AutoArchive = ".dbEscape($_REQUEST['AutoArchive']); - if ( !empty($_REQUEST['AutoVideo']) ) - $sql .= ", AutoVideo = ".dbEscape($_REQUEST['AutoVideo']); - if ( !empty($_REQUEST['AutoUpload']) ) - $sql .= ", AutoUpload = ".dbEscape($_REQUEST['AutoUpload']); - if ( !empty($_REQUEST['AutoEmail']) ) - $sql .= ", AutoEmail = ".dbEscape($_REQUEST['AutoEmail']); - if ( !empty($_REQUEST['AutoMessage']) ) - $sql .= ", AutoMessage = ".dbEscape($_REQUEST['AutoMessage']); - if ( !empty($_REQUEST['AutoExecute']) && !empty($_REQUEST['AutoExecuteCmd']) ) - $sql .= ", AutoExecute = ".dbEscape($_REQUEST['AutoExecute']).", AutoExecuteCmd = ".dbEscape($_REQUEST['AutoExecuteCmd']); - if ( !empty($_REQUEST['AutoDelete']) ) - $sql .= ", AutoDelete = ".dbEscape($_REQUEST['AutoDelete']); - if ( !empty($_REQUEST['background']) ) - $sql .= ", Background = ".dbEscape($_REQUEST['background']); - dbQuery( $sql ); - $refreshParent = true; - } - } - } - } - - // Event scope actions, edit permissions required - if ( canEdit( 'Events' ) ) - { - if ( $action == "rename" && isset($_REQUEST['eventName']) && !empty($_REQUEST['eid']) ) - { - dbQuery( 'UPDATE Events SET Name=? WHERE Id=?', array( $_REQUEST['eventName'], $_REQUEST['eid'] ) ); - } - else if ( $action == "eventdetail" ) - { - if ( !empty($_REQUEST['eid']) ) - { - dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['eid'] ) ); - $refreshParent = true; - } - else - { - foreach( getAffectedIds( 'markEid' ) as $markEid ) - { - dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid ) ); - $refreshParent = true; - } - } - } - elseif ( $action == "archive" || $action == "unarchive" ) - { - $archiveVal = ($action == "archive")?1:0; - if ( !empty($_REQUEST['eid']) ) - { - dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $_REQUEST['eid']) ); - } - else - { - foreach( getAffectedIds( 'markEid' ) as $markEid ) - { - dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $markEid ) ); - $refreshParent = true; - } - } - } - elseif ( $action == "delete" ) - { - foreach( getAffectedIds( 'markEid' ) as $markEid ) - { - deleteEvent( $markEid ); - $refreshParent = true; - } - if ( !empty($_REQUEST['fid']) ) - { - dbQuery( 'DELETE FROM Filters WHERE Name=?', array( $_REQUEST['fid'] ) ); - //$refreshParent = true; - } - } - } - - // Monitor control actions, require a monitor id and control view permissions for that monitor - if ( !empty($_REQUEST['mid']) && canView( 'Control', $_REQUEST['mid'] ) ) - { - require_once( 'control_functions.php' ); - require_once( 'Monitor.php' ); - $mid = validInt($_REQUEST['mid']); - if ( $action == "control" ) - { - $monitor = new Monitor( $mid ); - - $ctrlCommand = buildControlCommand( $monitor ); - sendControlCommand( $monitor->Id(), $ctrlCommand ); - } - elseif ( $action == "settings" ) - { - $args = " -m " . escapeshellarg($mid); - $args .= " -B" . escapeshellarg($_REQUEST['newBrightness']); - $args .= " -C" . escapeshellarg($_REQUEST['newContrast']); - $args .= " -H" . escapeshellarg($_REQUEST['newHue']); - $args .= " -O" . escapeshellarg($_REQUEST['newColour']); - - $zmuCommand = getZmuCommand( $args ); - - $zmuOutput = exec( $zmuCommand ); - list( $brightness, $contrast, $hue, $colour ) = explode( ' ', $zmuOutput ); - dbQuery( "update Monitors set Brightness = ?, Contrast = ?, Hue = ?, Colour = ? where Id = ?", array($brightness, $contrast, $hue, $colour, $mid)); - } - } - - // Control capability actions, require control edit permissions - if ( canEdit( 'Control' ) ) - { - if ( $action == "controlcap" ) - { - if ( !empty($_REQUEST['cid']) ) - { - $control = dbFetchOne( "select * from Controls where Id = ?", NULL, array($_REQUEST['cid']) ); - } - else - { - $control = array(); - } - - // Define a field type for anything that's not simple text equivalent - $types = array( - // Empty - ); - - $columns = getTableColumns( 'Controls' ); - foreach ( $columns as $name=>$type ) - { - if ( preg_match( '/^(Can|Has)/', $name ) ) - { - $types[$name] = 'toggle'; - } - } - $changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); - - if ( count( $changes ) ) - { - if ( !empty($_REQUEST['cid']) ) - { - dbQuery( "update Controls set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['cid']) ); - } - else - { - dbQuery( "insert into Controls set ".implode( ", ", $changes ) ); - //$_REQUEST['cid'] = dbInsertId(); - } - $refreshParent = true; - } - $view = 'none'; - } - elseif ( $action == "delete" ) - { - if ( isset($_REQUEST['markCids']) ) - { - foreach( $_REQUEST['markCids'] as $markCid ) - { - dbQuery( "delete from Controls where Id = ?", array($markCid) ); - dbQuery( "update Monitors set Controllable = 0, ControlId = 0 where ControlId = ?", array($markCid) ); - $refreshParent = true; - } - } - } - } - - // Monitor edit actions, require a monitor id and edit permissions for that monitor - if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) - { - $mid = validInt($_REQUEST['mid']); - if ( $action == "function" ) - { - $monitor = dbFetchOne( "SELECT * FROM Monitors WHERE Id=?", NULL, array($mid) ); - - $newFunction = validStr($_REQUEST['newFunction']); - # Because we use a checkbox, it won't get passed in the request. So not being in _REQUEST means 0 - $newEnabled = ( !isset( $_REQUEST['newEnabled'] ) or $_REQUEST['newEnabled'] != '1' ) ? '0' : '1'; - - $oldFunction = $monitor['Function']; - $oldEnabled = $monitor['Enabled']; - if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) - { - dbQuery( "update Monitors set Function=?, Enabled=? where Id=?", array( $newFunction, $newEnabled, $mid ) ); - - $monitor['Function'] = $newFunction; - $monitor['Enabled'] = $newEnabled; - //if ( $cookies ) session_write_close(); - if ( daemonCheck() ) - { - $restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled); - zmaControl( $monitor, "stop" ); - zmcControl( $monitor, $restart?"restart":"" ); - zmaControl( $monitor, "start" ); - } - $refreshParent = true; - } - } - elseif ( $action == "zone" && isset( $_REQUEST['zid'] ) ) - { - $zid = validInt($_REQUEST['zid']); - $monitor = dbFetchOne( "SELECT * FROM Monitors WHERE Id=?", NULL, array($mid) ); - - if ( !empty($zid) ) - { - $zone = dbFetchOne( "SELECT * FROM Zones WHERE MonitorId=? AND Id=?", NULL, array( $mid, $zid ) ); - } - else - { - $zone = array(); - } - - if ( $_REQUEST['newZone']['Units'] == 'Percent' ) - { - $_REQUEST['newZone']['MinAlarmPixels'] = intval(($_REQUEST['newZone']['MinAlarmPixels']*$_REQUEST['newZone']['Area'])/100); - $_REQUEST['newZone']['MaxAlarmPixels'] = intval(($_REQUEST['newZone']['MaxAlarmPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MinFilterPixels']) ) - $_REQUEST['newZone']['MinFilterPixels'] = intval(($_REQUEST['newZone']['MinFilterPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MaxFilterPixels']) ) - $_REQUEST['newZone']['MaxFilterPixels'] = intval(($_REQUEST['newZone']['MaxFilterPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MinBlobPixels']) ) - $_REQUEST['newZone']['MinBlobPixels'] = intval(($_REQUEST['newZone']['MinBlobPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MaxBlobPixels']) ) - $_REQUEST['newZone']['MaxBlobPixels'] = intval(($_REQUEST['newZone']['MaxBlobPixels']*$_REQUEST['newZone']['Area'])/100); - } - - unset( $_REQUEST['newZone']['Points'] ); - $types = array(); - $changes = getFormChanges( $zone, $_REQUEST['newZone'], $types ); - - if ( count( $changes ) ) - { - if ( $zid > 0 ) - { - dbQuery( "UPDATE Zones SET ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zid) ); - } - else - { - dbQuery( "INSERT INTO Zones SET MonitorId=?, ".implode( ", ", $changes ), array( $mid ) ); - } - //if ( $cookies ) session_write_close(); - if ( daemonCheck() ) - { - if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) - { - zmaControl( $monitor, "stop" ); - zmcControl( $monitor, "restart" ); - zmaControl( $monitor, "start" ); - } - else - { - zmaControl( $mid, "restart" ); - } - } - if ( $_REQUEST['newZone']['Type'] == 'Privacy' && $monitor['Controllable'] ) { - require_once( 'control_functions.php' ); - sendControlCommand( $mid, 'quit' ); - } - $refreshParent = true; - } - $view = 'none'; - } - elseif ( $action == "plugin" && isset($_REQUEST['pl'])) - { - $sql="SELECT * FROM PluginsConfig WHERE MonitorId=? AND ZoneId=? AND pluginName=?"; - $pconfs=dbFetchAll( $sql, NULL, array( $mid, $_REQUEST['zid'], $_REQUEST['pl'] ) ); - $changes=0; - foreach( $pconfs as $pconf ) - { - $value=$_REQUEST['pluginOpt'][$pconf['Name']]; - if(array_key_exists($pconf['Name'], $_REQUEST['pluginOpt']) && ($pconf['Value']!=$value)) - { - dbQuery("UPDATE PluginsConfig SET Value=? WHERE id=?", array( $value, $pconf['Id'] ) ); - $changes++; - } - } - if($changes>0) - { - if ( daemonCheck() ) - { - zmaControl( $mid, "restart" ); - } - $refreshParent = true; - } - $view = 'none'; - } - elseif ( $action == "sequence" && isset($_REQUEST['smid']) ) - { - $smid = validInt($_REQUEST['smid']); - $monitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ); - $smonitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($smid) ); - - dbQuery( "update Monitors set Sequence=? where Id=?", array( $smonitor['Sequence'], $monitor['Id'] ) ); - dbQuery( "update Monitors set Sequence=? WHERE Id=?", array( $monitor['Sequence'], $smonitor['Id'] ) ); - + $url = 'https://www.google.com/recaptcha/api/siteverify'; + $fields = array ( + 'secret'=> ZM_OPT_GOOG_RECAPTCHA_SECRETKEY, + 'response' => $_REQUEST['g-recaptcha-response'], + 'remoteip'=> $_SERVER['REMOTE_ADDR'] + ); + $res= do_post_request($url, http_build_query($fields)); + $responseData = json_decode($res,true); + // PP - credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php + // if recaptcha resulted in error, we might have to deny login + if (isset($responseData['success']) && $responseData['success'] == false) { + // PP - before we deny auth, let's make sure the error was not 'invalid secret' + // because that means the user did not configure the secret key correctly + // in this case, we prefer to let him login in and display a message to correct + // the key. Unfortunately, there is no way to check for invalid site key in code + // as it produces the same error as when you don't answer a recaptcha + if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) { + if (!in_array('invalid-input-secret',$responseData['error-codes'])) { + Error ('reCaptcha authentication failed'); + userLogout(); + $view='login'; $refreshParent = true; - fixSequences(); + } else { + //Let them login but show an error + echo ''; + Error ("Invalid recaptcha secret detected"); + } } - if ( $action == "delete" ) - { - if ( isset($_REQUEST['markZids']) ) - { - $deletedZid = 0; - foreach( $_REQUEST['markZids'] as $markZid ) - { - $zone = dbFetchOne( "select * from Zones where Id=?", NULL, array($markZid) ); - dbQuery( "delete from Zones WHERE MonitorId=? AND Id=?", array( $mid, $markZid) ); - $deletedZid = 1; - } - if ( $deletedZid ) - { - //if ( $cookies ) - //session_write_close(); - if ( daemonCheck() ) - if ( $zone['Type'] == 'Privacy' ) - { - zmaControl( $mid, "stop" ); - zmcControl( $mid, "restart" ); - zmaControl( $mid, "start" ); - } - else - { - zmaControl( $mid, "restart" ); - } - $refreshParent = true; - } - } + } // end if success==false + + } // end if using reCaptcha + + $username = validStr( $_REQUEST['username'] ); + $password = isset($_REQUEST['password'])?validStr($_REQUEST['password']):''; + userLogin( $username, $password ); + $refreshParent = true; + $view = 'console'; + $redirect = true; + } else if ( $action == 'logout' ) { + userLogout(); + $refreshParent = true; + $view = 'none'; + } else if ( $action == 'bandwidth' && isset($_REQUEST['newBandwidth']) ) { + $_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']); + setcookie( 'zmBandwidth', validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10 ); + $refreshParent = true; + } + + // Event scope actions, view permissions only required + if ( canView( 'Events' ) ) { + + if ( $action == 'filter' ) { + if ( !empty($_REQUEST['subaction']) ) { + if ( $_REQUEST['subaction'] == 'addterm' ) + $_REQUEST['filter'] = addFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); + elseif ( $_REQUEST['subaction'] == 'delterm' ) + $_REQUEST['filter'] = delFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); + } elseif ( canEdit( 'Events' ) ) { + $sql = ''; + $endSql = ''; + $filterName = ''; + if ( !empty($_REQUEST['execute']) ) { + // TempFilterName is used in event listing later on + $tempFilterName = $filterName = '_TempFilter'.time(); + } elseif ( !empty($_REQUEST['newFilterName']) ) { + $filterName = $_REQUEST['newFilterName']; } - } - - // Monitor edit actions, monitor id derived, require edit permissions for that monitor - if ( canEdit( 'Monitors' ) ) - { - if ( $action == "monitor" ) - { - if ( !empty($_REQUEST['mid']) ) - { - $mid = validInt($_REQUEST['mid']); - $monitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ); - - if ( ZM_OPT_X10 ) - { - $x10Monitor = dbFetchOne( "select * from TriggersX10 where MonitorId=?", NULL, array($mid) ); - if ( !$x10Monitor ) - $x10Monitor = array(); - } - } - else - { - $monitor = array(); - if ( ZM_OPT_X10 ) - { - $x10Monitor = array(); - } - } - - // Define a field type for anything that's not simple text equivalent - $types = array( - 'Triggers' => 'set', - 'Controllable' => 'toggle', - 'TrackMotion' => 'toggle', - 'Enabled' => 'toggle', - 'DoNativeMotDet' => 'toggle', - 'Exif' => 'toggle', - 'RTSPDescribe' => 'toggle', - ); - - $columns = getTableColumns( 'Monitors' ); - $changes = getFormChanges( $monitor, $_REQUEST['newMonitor'], $types, $columns ); - - if ( count( $changes ) ) - { - if ( !empty($_REQUEST['mid']) ) - { - $mid = validInt($_REQUEST['mid']); - dbQuery( "update Monitors set ".implode( ", ", $changes )." where Id =?", array($mid) ); - if ( isset($changes['Name']) ) - { - $saferOldName = basename( $monitor['Name'] ); - $saferNewName = basename( $_REQUEST['newMonitor']['Name'] ); - rename( ZM_DIR_EVENTS."/".$saferOldName, ZM_DIR_EVENTS."/".$saferNewName); - } - if ( isset($changes['Width']) || isset($changes['Height']) ) - { - $newW = $_REQUEST['newMonitor']['Width']; - $newH = $_REQUEST['newMonitor']['Height']; - $newA = $newW * $newH; - $oldW = $monitor['Width']; - $oldH = $monitor['Height']; - $oldA = $oldW * $oldH; - - $zones = dbFetchAll( "select * from Zones where MonitorId=?", NULL, array($mid) ); - foreach ( $zones as $zone ) - { - $newZone = $zone; - $points = coordsToPoints( $zone['Coords'] ); - for ( $i = 0; $i < count($points); $i++ ) - { - $points[$i]['x'] = intval(($points[$i]['x']*($newW-1))/($oldW-1)); - $points[$i]['y'] = intval(($points[$i]['y']*($newH-1))/($oldH-1)); - } - $newZone['Coords'] = pointsToCoords( $points ); - $newZone['Area'] = intval(round(($zone['Area']*$newA)/$oldA)); - $newZone['MinAlarmPixels'] = intval(round(($newZone['MinAlarmPixels']*$newA)/$oldA)); - $newZone['MaxAlarmPixels'] = intval(round(($newZone['MaxAlarmPixels']*$newA)/$oldA)); - $newZone['MinFilterPixels'] = intval(round(($newZone['MinFilterPixels']*$newA)/$oldA)); - $newZone['MaxFilterPixels'] = intval(round(($newZone['MaxFilterPixels']*$newA)/$oldA)); - $newZone['MinBlobPixels'] = intval(round(($newZone['MinBlobPixels']*$newA)/$oldA)); - $newZone['MaxBlobPixels'] = intval(round(($newZone['MaxBlobPixels']*$newA)/$oldA)); - - $changes = getFormChanges( $zone, $newZone, $types ); - - if ( count( $changes ) ) - { - dbQuery( "update Zones set ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zone['Id'] ) ); - } - } - } - } - elseif ( !$user['MonitorIds'] ) - { - # FIXME This is actually a race condition. Should lock the table. - $maxSeq = dbFetchOne( "select max(Sequence) as MaxSequence from Monitors", "MaxSequence" ); - $changes[] = "Sequence = ".($maxSeq+1); - - dbQuery( "insert into Monitors set ".implode( ", ", $changes ) ); - $mid = dbInsertId(); - $zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height']; - dbQuery( "insert into Zones set MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) ); - //$view = 'none'; - mkdir( ZM_DIR_EVENTS.'/'.$mid, 0755 ); - $saferName = basename($_REQUEST['newMonitor']['Name']); - symlink( $mid, ZM_DIR_EVENTS.'/'.$saferName ); - if ( isset($_COOKIE['zmGroup']) ) - { - dbQuery( "UPDATE Groups SET MonitorIds = concat(MonitorIds,',".$mid."') WHERE Id=?", array($_COOKIE['zmGroup']) ); - } - } - $restart = true; - } - - if ( ZM_OPT_X10 ) - { - $x10Changes = getFormChanges( $x10Monitor, $_REQUEST['newX10Monitor'] ); - - if ( count( $x10Changes ) ) - { - if ( $x10Monitor && isset($_REQUEST['newX10Monitor']) ) - { - dbQuery( "update TriggersX10 set ".implode( ", ", $x10Changes )." where MonitorId=?", array($mid) ); - } - elseif ( !$user['MonitorIds'] ) - { - if ( !$x10Monitor ) - { - dbQuery( "insert into TriggersX10 set MonitorId = ?".implode( ", ", $x10Changes ), array( $mid ) ); - } - else - { - dbQuery( "delete from TriggersX10 where MonitorId = ?", array($mid) ); - } - } - $restart = true; - } - } - - if ( $restart ) - { - $monitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($mid) ); - //fixDevices(); - //if ( $cookies ) - //session_write_close(); - if ( daemonCheck() ) - { - zmaControl( $monitor, "stop" ); - zmcControl( $monitor, "restart" ); - zmaControl( $monitor, "start" ); - } - if ( $monitor['Controllable'] ) { - require_once( 'control_functions.php' ); - sendControlCommand( $mid, 'quit' ); - } - //daemonControl( 'restart', 'zmwatch.pl' ); - $refreshParent = true; - } - $view = 'none'; - } - if ( $action == "delete" ) - { - if ( isset($_REQUEST['markMids']) && !$user['MonitorIds'] ) - { - foreach( $_REQUEST['markMids'] as $markMid ) - { - if ( canEdit( 'Monitors', $markMid ) ) - { - if ( $monitor = dbFetchOne( "select * from Monitors where Id = ?", NULL, array($markMid) ) ) - { - if ( daemonCheck() ) - { - zmaControl( $monitor, "stop" ); - zmcControl( $monitor, "stop" ); - } - - // This is the important stuff - dbQuery( "delete from Monitors where Id = ?", array($markMid) ); - dbQuery( "delete from Zones where MonitorId = ?", array($markMid) ); - if ( ZM_OPT_X10 ) - dbQuery( "delete from TriggersX10 where MonitorId=?", array($markMid) ); - - fixSequences(); - - // If fast deletes are on, then zmaudit will clean everything else up later - // If fast deletes are off and there are lots of events then this step may - // well time out before completing, in which case zmaudit will still tidy up - if ( !ZM_OPT_FAST_DELETE ) - { - // Slight hack, we maybe should load *, but we happen to know that the deleteEvent function uses Id and StartTime. - $markEids = dbFetchAll( "SELECT Id,StartTime FROM Events WHERE MonitorId=?", NULL, array($markMid) ); - foreach( $markEids as $markEid ) - deleteEvent( $markEid, $markMid ); - - deletePath( ZM_DIR_EVENTS."/".basename($monitor['Name']) ); - deletePath( ZM_DIR_EVENTS."/".$monitor['Id'] ); // I'm trusting the Id. - } - } - } - } - } - } - } - - // Device view actions - if ( canEdit( 'Devices' ) ) - { - if ( $action == "device" ) - { - if ( !empty($_REQUEST['command']) ) - { - setDeviceStatusX10( $_REQUEST['key'], $_REQUEST['command'] ); - } - elseif ( isset( $_REQUEST['newDevice'] ) ) - { - if ( isset($_REQUEST['did']) ) - { - dbQuery( "update Devices set Name=?, KeyString=? where Id=?", array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'], $_REQUEST['did']) ); - } - else - { - dbQuery( "insert into Devices set Name=?, KeyString=?", array( $_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'] ) ); - } - $refreshParent = true; - $view = 'none'; - } - } - elseif ( $action == "delete" ) - { - if ( isset($_REQUEST['markDids']) ) - { - foreach( $_REQUEST['markDids'] as $markDid ) - { - dbQuery( "delete from Devices where Id=?", array($markDid) ); - $refreshParent = true; - } - } - } - } - - // Group view actions - if ( canView( 'Groups' ) && $action == "setgroup" ) { - if ( !empty($_REQUEST['gid']) ) { - setcookie( "zmGroup", validInt($_REQUEST['gid']), time()+3600*24*30*12*10 ); + if ( $filterName ) { + # Replace will teplace any filter with the same Id + # Since we aren't specifying the Id , this is effectively an insert + $sql = 'REPLACE INTO Filters SET Name = '.dbEscape($filterName).','; } else { - setcookie( "zmGroup", "", time()-3600*24*2 ); + $sql = 'UPDATE Filters SET'; + $endSql = 'WHERE Id = '.$_REQUEST['Id']; } - $refreshParent = true; - } - // Group edit actions - if ( canEdit( 'Groups' ) ) { - if ( $action == "group" ) { - # Should probably verfy that each monitor id is a valid monitor, that we have access to. HOwever at the moment, you have to have System permissions to do this - $monitors = empty( $_POST['newGroup']['MonitorIds'] ) ? NULL : implode(',', $_POST['newGroup']['MonitorIds']); - if ( !empty($_POST['gid']) ) { - dbQuery( "UPDATE Groups SET Name=?, MonitorIds=? WHERE Id=?", array($_POST['newGroup']['Name'], $monitors, $_POST['gid']) ); + # endSql is only set if ! filterName... so... woulnd't this always be true + if ( !empty($filterName) || $endSql ) { + $_REQUEST['filter']['sort_field'] = validStr($_REQUEST['sort_field']); + $_REQUEST['filter']['sort_asc'] = validStr($_REQUEST['sort_asc']); + $_REQUEST['filter']['limit'] = validInt($_REQUEST['limit']); + $sql .= ' Query = '.dbEscape(jsonEncode($_REQUEST['filter'])); + if ( !empty($_REQUEST['AutoArchive']) ) + $sql .= ', AutoArchive = '.dbEscape($_REQUEST['AutoArchive']); + if ( !empty($_REQUEST['AutoVideo']) ) + $sql .= ', AutoVideo = '.dbEscape($_REQUEST['AutoVideo']); + if ( !empty($_REQUEST['AutoUpload']) ) + $sql .= ', AutoUpload = '.dbEscape($_REQUEST['AutoUpload']); + if ( !empty($_REQUEST['AutoEmail']) ) + $sql .= ', AutoEmail = '.dbEscape($_REQUEST['AutoEmail']); + if ( !empty($_REQUEST['AutoMessage']) ) + $sql .= ', AutoMessage = '.dbEscape($_REQUEST['AutoMessage']); + if ( !empty($_REQUEST['AutoExecute']) && !empty($_REQUEST['AutoExecuteCmd']) ) + $sql .= ', AutoExecute = '.dbEscape($_REQUEST['AutoExecute']).", AutoExecuteCmd = ".dbEscape($_REQUEST['AutoExecuteCmd']); + if ( !empty($_REQUEST['AutoDelete']) ) + $sql .= ', AutoDelete = '.dbEscape($_REQUEST['AutoDelete']); + if ( !empty($_REQUEST['background']) ) + $sql .= ', Background = '.dbEscape($_REQUEST['background']); + if ( !empty($_REQUEST['concurrent']) ) + $sql .= ', Concurrent = '.dbEscape($_REQUEST['concurrent']); + $sql .= $endSql; + dbQuery( $sql ); + if ( $filterName ) { + $filter = dbFetchOne( 'SELECT * FROM Filters WHERE Name=?', NULL, array($filterName) ); + if ( $filter ) { + # This won't work yet because refreshparent refreshes the old filter. Need to do a redirect instead of a refresh. + $_REQUEST['Id'] = $filter['Id']; } else { - dbQuery( "INSERT INTO Groups SET Name=?, MonitorIds=?", array( $_POST['newGroup']['Name'], $monitors ) ); + Error("No new Id despite new name"); } - $view = 'none'; + } + $refreshParent = '/index.php?view=filter&Id='.$_REQUEST['Id']; } - if ( !empty($_REQUEST['gid']) && $action == "delete" ) { - dbQuery( "delete from Groups where Id = ?", array($_REQUEST['gid']) ); - if ( isset($_COOKIE['zmGroup']) ) - { - if ( $_REQUEST['gid'] == $_COOKIE['zmGroup'] ) - { - unset( $_COOKIE['zmGroup'] ); - setcookie( "zmGroup", "", time()-3600*24*2 ); - $refreshParent = true; - } - } + } // end if canedit events + } // end if action == filter + } // end if canview events + + // Event scope actions, edit permissions required + if ( canEdit( 'Events' ) ) { + if ( $action == 'rename' && isset($_REQUEST['eventName']) && !empty($_REQUEST['eid']) ) { + dbQuery( 'UPDATE Events SET Name=? WHERE Id=?', array( $_REQUEST['eventName'], $_REQUEST['eid'] ) ); + } else if ( $action == 'eventdetail' ) { + if ( !empty($_REQUEST['eid']) ) { + dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['eid'] ) ); + $refreshParent = true; + } else { + foreach( getAffectedIds( 'markEid' ) as $markEid ) { + dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid ) ); + $refreshParent = true; + } + } + } elseif ( $action == 'archive' || $action == 'unarchive' ) { + $archiveVal = ($action == 'archive')?1:0; + if ( !empty($_REQUEST['eid']) ) { + dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $_REQUEST['eid']) ); + } else { + foreach( getAffectedIds( 'markEid' ) as $markEid ) { + dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $markEid ) ); + $refreshParent = true; + } + } + } elseif ( $action == 'delete' ) { + foreach( getAffectedIds( 'markEid' ) as $markEid ) { + deleteEvent( $markEid ); + $refreshParent = true; + } + if ( isset( $_REQUEST['object'] ) and ( $_REQUEST['object'] == 'filter' ) ) { + if ( !empty($_REQUEST['Id']) ) { + dbQuery( 'DELETE FROM Filters WHERE Id=?', array( $_REQUEST['Id'] ) ); + //$refreshParent = true; + } + } + } + } + + // Monitor control actions, require a monitor id and control view permissions for that monitor + if ( !empty($_REQUEST['mid']) && canView( 'Control', $_REQUEST['mid'] ) ) { + require_once( 'control_functions.php' ); + require_once( 'Monitor.php' ); + $mid = validInt($_REQUEST['mid']); + if ( $action == 'control' ) { + $monitor = new Monitor( $mid ); + + $ctrlCommand = buildControlCommand( $monitor ); + sendControlCommand( $monitor->Id(), $ctrlCommand ); + } elseif ( $action == 'settings' ) { + $args = " -m " . escapeshellarg($mid); + $args .= " -B" . escapeshellarg($_REQUEST['newBrightness']); + $args .= " -C" . escapeshellarg($_REQUEST['newContrast']); + $args .= " -H" . escapeshellarg($_REQUEST['newHue']); + $args .= " -O" . escapeshellarg($_REQUEST['newColour']); + + $zmuCommand = getZmuCommand( $args ); + + $zmuOutput = exec( $zmuCommand ); + list( $brightness, $contrast, $hue, $colour ) = explode( ' ', $zmuOutput ); + dbQuery( 'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?', array($brightness, $contrast, $hue, $colour, $mid)); + } + } + + // Control capability actions, require control edit permissions + if ( canEdit( 'Control' ) ) { + if ( $action == 'controlcap' ) { + if ( !empty($_REQUEST['cid']) ) { + $control = dbFetchOne( 'SELECT * FROM Controls WHERE Id = ?', NULL, array($_REQUEST['cid']) ); + } else { + $control = array(); + } + + // Define a field type for anything that's not simple text equivalent + $types = array( + // Empty + ); + + $columns = getTableColumns( 'Controls' ); + foreach ( $columns as $name=>$type ) { + if ( preg_match( '/^(Can|Has)/', $name ) ) { + $types[$name] = 'toggle'; + } + } + $changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['cid']) ) { + dbQuery( "update Controls set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['cid']) ); + } else { + dbQuery( "insert into Controls set ".implode( ", ", $changes ) ); + //$_REQUEST['cid'] = dbInsertId(); } $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markCids']) ) { + foreach( $_REQUEST['markCids'] as $markCid ) { + dbQuery( "delete from Controls where Id = ?", array($markCid) ); + dbQuery( "update Monitors set Controllable = 0, ControlId = 0 where ControlId = ?", array($markCid) ); + $refreshParent = true; + } + } } + } - // System edit actions - if ( canEdit( 'System' ) ) - { - if ( isset( $_REQUEST['object'] ) and ( $_REQUEST['object'] == 'server' ) ) { + // Monitor edit actions, require a monitor id and edit permissions for that monitor + if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) { + $mid = validInt($_REQUEST['mid']); + if ( $action == 'function' ) { + $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array($mid) ); - if ( $action == "Save" ) { - if ( !empty($_REQUEST['id']) ) - $dbServer = dbFetchOne( "SELECT * FROM Servers WHERE Id=?", NULL, array($_REQUEST['id']) ); - else - $dbServer = array(); + $newFunction = validStr($_REQUEST['newFunction']); + # Because we use a checkbox, it won't get passed in the request. So not being in _REQUEST means 0 + $newEnabled = ( !isset( $_REQUEST['newEnabled'] ) or $_REQUEST['newEnabled'] != '1' ) ? '0' : '1'; + $oldFunction = $monitor['Function']; + $oldEnabled = $monitor['Enabled']; + if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) { + dbQuery( 'UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?', array( $newFunction, $newEnabled, $mid ) ); - $types = array(); - $changes = getFormChanges( $dbServer, $_REQUEST['newServer'], $types ); - - if ( count( $changes ) ) { - if ( !empty($_REQUEST['id']) ) { - dbQuery( "UPDATE Servers SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); - } else { - dbQuery( "INSERT INTO Servers set ".implode( ", ", $changes ) ); - } - $refreshParent = true; - } - $view = 'none'; - } else if ( $action == 'delete' ) { - if ( !empty($_REQUEST['markIds']) ) { - foreach( $_REQUEST['markIds'] as $Id ) - dbQuery( "DELETE FROM Servers WHERE Id=?", array($Id) ); - } - $refreshParent = true; - } else { - Error( "Unknown action $action in saving Server" ); - } - - } else if ( $action == "version" && isset($_REQUEST['option']) ) - { - $option = $_REQUEST['option']; - switch( $option ) - { - case 'go' : - { - // Ignore this, the caller will open the page itself - break; - } - case 'ignore' : - { - dbQuery( "update Config set Value = '".ZM_DYN_LAST_VERSION."' where Name = 'ZM_DYN_CURR_VERSION'" ); - break; - } - case 'hour' : - case 'day' : - case 'week' : - { - $nextReminder = time(); - if ( $option == 'hour' ) - { - $nextReminder += 60*60; - } - elseif ( $option == 'day' ) - { - $nextReminder += 24*60*60; - } - elseif ( $option == 'week' ) - { - $nextReminder += 7*24*60*60; - } - dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_NEXT_REMINDER'" ); - break; - } - case 'never' : - { - dbQuery( "update Config set Value = '0' where Name = 'ZM_CHECK_FOR_UPDATES'" ); - break; - } - } + $monitor['Function'] = $newFunction; + $monitor['Enabled'] = $newEnabled; + if ( daemonCheck() ) { + $restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled); + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, $restart?'restart':'' ); + zmaControl( $monitor, 'start' ); } - if ( $action == "donate" && isset($_REQUEST['option']) ) - { - $option = $_REQUEST['option']; - switch( $option ) - { - case 'go' : - { - // Ignore this, the caller will open the page itself - break; - } - case 'hour' : - case 'day' : - case 'week' : - case 'month' : - { - $nextReminder = time(); - if ( $option == 'hour' ) - { - $nextReminder += 60*60; - } - elseif ( $option == 'day' ) - { - $nextReminder += 24*60*60; - } - elseif ( $option == 'week' ) - { - $nextReminder += 7*24*60*60; - } - elseif ( $option == 'month' ) - { - $nextReminder += 30*24*60*60; - } - dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_DONATE_REMINDER_TIME'" ); - break; - } - case 'never' : - case 'already' : - { - dbQuery( "update Config set Value = '0' where Name = 'ZM_DYN_SHOW_DONATE_REMINDER'" ); - break; - } - } + $refreshParent = true; + } + } elseif ( $action == 'zone' && isset( $_REQUEST['zid'] ) ) { + $zid = validInt($_REQUEST['zid']); + $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array($mid) ); + + if ( !empty($zid) ) { + $zone = dbFetchOne( 'SELECT * FROM Zones WHERE MonitorId=? AND Id=?', NULL, array( $mid, $zid ) ); + } else { + $zone = array(); + } + + if ( $_REQUEST['newZone']['Units'] == 'Percent' ) { + $_REQUEST['newZone']['MinAlarmPixels'] = intval(($_REQUEST['newZone']['MinAlarmPixels']*$_REQUEST['newZone']['Area'])/100); + $_REQUEST['newZone']['MaxAlarmPixels'] = intval(($_REQUEST['newZone']['MaxAlarmPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MinFilterPixels']) ) + $_REQUEST['newZone']['MinFilterPixels'] = intval(($_REQUEST['newZone']['MinFilterPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MaxFilterPixels']) ) + $_REQUEST['newZone']['MaxFilterPixels'] = intval(($_REQUEST['newZone']['MaxFilterPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MinBlobPixels']) ) + $_REQUEST['newZone']['MinBlobPixels'] = intval(($_REQUEST['newZone']['MinBlobPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MaxBlobPixels']) ) + $_REQUEST['newZone']['MaxBlobPixels'] = intval(($_REQUEST['newZone']['MaxBlobPixels']*$_REQUEST['newZone']['Area'])/100); + } + + unset( $_REQUEST['newZone']['Points'] ); + $types = array(); + $changes = getFormChanges( $zone, $_REQUEST['newZone'], $types ); + + if ( count( $changes ) ) { + if ( $zid > 0 ) { + dbQuery( "UPDATE Zones SET ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zid) ); + } else { + dbQuery( "INSERT INTO Zones SET MonitorId=?, ".implode( ", ", $changes ), array( $mid ) ); } - if ( $action == "options" && isset($_REQUEST['tab']) ) - { - $configCat = $configCats[$_REQUEST['tab']]; - $changed = false; - foreach ( $configCat as $name=>$value ) - { - unset( $newValue ); - if ( $value['Type'] == "boolean" && empty($_REQUEST['newConfig'][$name]) ) - $newValue = 0; - elseif ( isset($_REQUEST['newConfig'][$name]) ) - $newValue = preg_replace( "/\r\n/", "\n", stripslashes( $_REQUEST['newConfig'][$name] ) ); - - if ( isset($newValue) && ($newValue != $value['Value']) ) - { - dbQuery( 'UPDATE Config SET Value=? WHERE Name=?', array( $newValue, $name ) ); - $changed = true; - } - } - if ( $changed ) - { - switch( $_REQUEST['tab'] ) - { - case "system" : - case "config" : - case "paths" : - $restartWarning = true; - break; - case "web" : - case "tools" : - break; - case "logging" : - case "network" : - case "mail" : - case "upload" : - $restartWarning = true; - break; - case "highband" : - case "medband" : - case "lowband" : - break; - } - } - loadConfig( false ); - } - elseif ( $action == "user" ) - { - if ( !empty($_REQUEST['uid']) ) - $dbUser = dbFetchOne( "SELECT * FROM Users WHERE Id=?", NULL, array($_REQUEST['uid']) ); - else - $dbUser = array(); - - $types = array(); - $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); - - if ( $_REQUEST['newUser']['Password'] ) - $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; - else - unset( $changes['Password'] ); - - if ( count( $changes ) ) - { - if ( !empty($_REQUEST['uid']) ) - { - dbQuery( "update Users set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['uid']) ); - } - else - { - dbQuery( "insert into Users set ".implode( ", ", $changes ) ); - } - $refreshParent = true; - if ( $dbUser['Username'] == $user['Username'] ) - userLogin( $dbUser['Username'], $dbUser['Password'] ); - } - $view = 'none'; - } - elseif ( $action == "state" ) - { - if ( !empty($_REQUEST['runState']) ) - { - //if ( $cookies ) session_write_close(); - packageControl( $_REQUEST['runState'] ); - $refreshParent = true; - } - } - elseif ( $action == "save" ) - { - if ( !empty($_REQUEST['runState']) || !empty($_REQUEST['newState']) ) - { - $sql = "select Id,Function,Enabled from Monitors order by Id"; - $definitions = array(); - foreach( dbFetchAll( $sql ) as $monitor ) - { - $definitions[] = $monitor['Id'].":".$monitor['Function'].":".$monitor['Enabled']; - } - $definition = join( ',', $definitions ); - if ( $_REQUEST['newState'] ) - $_REQUEST['runState'] = $_REQUEST['newState']; - dbQuery( "replace into States set Name=?, Definition=?", array( $_REQUEST['runState'],$definition) ); - } - } - elseif ( $action == "delete" ) - { - if ( isset($_REQUEST['runState']) ) - dbQuery( "delete from States where Name=?", array($_REQUEST['runState']) ); - - if ( isset($_REQUEST['markUids']) ) - { - foreach( $_REQUEST['markUids'] as $markUid ) - dbQuery( "delete from Users where Id = ?", array($markUid) ); - if ( $markUid == $user['Id'] ) - userLogout(); - } - } - } - else - { - if ( ZM_USER_SELF_EDIT && $action == "user" ) - { - $uid = $user['Id']; - - $dbUser = dbFetchOne( "select Id, Password, Language from Users where Id = ?", NULL, array($uid) ); - - $types = array(); - $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); - - if ( !empty($_REQUEST['newUser']['Password']) ) - $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; - else - unset( $changes['Password'] ); - if ( count( $changes ) ) - { - dbQuery( "update Users set ".implode( ", ", $changes )." where Id=?", array($uid) ); - $refreshParent = true; - } - $view = 'none'; - } - } - - if ( $action == "reset" ) - { - $_SESSION['zmEventResetTime'] = strftime( STRF_FMT_DATETIME_DB ); - setcookie( "zmEventResetTime", $_SESSION['zmEventResetTime'], time()+3600*24*30*12*10 ); //if ( $cookies ) session_write_close(); + if ( daemonCheck() ) { + if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) { + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'restart' ); + zmaControl( $monitor, 'start' ); + } else { + zmaControl( $mid, 'restart' ); + } + } + if ( $_REQUEST['newZone']['Type'] == 'Privacy' && $monitor['Controllable'] ) { + require_once( 'control_functions.php' ); + sendControlCommand( $mid, 'quit' ); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'plugin' && isset($_REQUEST['pl'])) { + $sql='SELECT * FROM PluginsConfig WHERE MonitorId=? AND ZoneId=? AND pluginName=?'; + $pconfs=dbFetchAll( $sql, NULL, array( $mid, $_REQUEST['zid'], $_REQUEST['pl'] ) ); + $changes=0; + foreach( $pconfs as $pconf ) { + $value=$_REQUEST['pluginOpt'][$pconf['Name']]; + if(array_key_exists($pconf['Name'], $_REQUEST['pluginOpt']) && ($pconf['Value']!=$value)) { + dbQuery("UPDATE PluginsConfig SET Value=? WHERE id=?", array( $value, $pconf['Id'] ) ); + $changes++; + } + } + if($changes>0) { + if ( daemonCheck() ) { + zmaControl( $mid, 'restart' ); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'sequence' && isset($_REQUEST['smid']) ) { + $smid = validInt($_REQUEST['smid']); + $monitor = dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($mid) ); + $smonitor = dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($smid) ); + + dbQuery( 'update Monitors set Sequence=? where Id=?', array( $smonitor['Sequence'], $monitor['Id'] ) ); + dbQuery( 'update Monitors set Sequence=? WHERE Id=?', array( $monitor['Sequence'], $smonitor['Id'] ) ); + + $refreshParent = true; + fixSequences(); + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markZids']) ) { + $deletedZid = 0; + foreach( $_REQUEST['markZids'] as $markZid ) { + $zone = dbFetchOne( 'select * from Zones where Id=?', NULL, array($markZid) ); + dbQuery( 'delete from Zones WHERE MonitorId=? AND Id=?', array( $mid, $markZid) ); + $deletedZid = 1; + } + if ( $deletedZid ) { + //if ( $cookies ) + //session_write_close(); + if ( daemonCheck() ) { + if ( $zone['Type'] == 'Privacy' ) { + zmaControl( $mid, 'stop' ); + zmcControl( $mid, 'restart' ); + zmaControl( $mid, 'start' ); + } else { + zmaControl( $mid, 'restart' ); + } + } // end if daemonCheck() + $refreshParent = true; + } // end if deletedzid + } // end if isset($_REQUEST['markZids']) + } // end if action + } // end if $mid and canEdit($mid) + + // Monitor edit actions, monitor id derived, require edit permissions for that monitor + if ( canEdit( 'Monitors' ) ) { + if ( $action == 'monitor' ) { + $mid = 0; + if ( !empty($_REQUEST['mid']) ) { + $mid = validInt($_REQUEST['mid']); + $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid) ); + + if ( ZM_OPT_X10 ) { + $x10Monitor = dbFetchOne( 'SELECT * FROM TriggersX10 WHERE MonitorId=?', NULL, array($mid) ); + if ( !$x10Monitor ) + $x10Monitor = array(); + } + } else { + $monitor = array(); + if ( ZM_OPT_X10 ) { + $x10Monitor = array(); + } + } + + // Define a field type for anything that's not simple text equivalent + $types = array( + 'Triggers' => 'set', + 'Controllable' => 'toggle', + 'TrackMotion' => 'toggle', + 'Enabled' => 'toggle', + 'DoNativeMotDet' => 'toggle', + 'Exif' => 'toggle', + 'RTSPDescribe' => 'toggle', + 'RecordAudio' => 'toggle', + ); + + $columns = getTableColumns( 'Monitors' ); + $changes = getFormChanges( $monitor, $_REQUEST['newMonitor'], $types, $columns ); + + if ( count( $changes ) ) { + if ( $mid ) { + + # If we change anything that changes the shared mem size, zma can complain. So let's stop first. + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'stop' ); + dbQuery( 'UPDATE Monitors SET '.implode( ", ", $changes ).' WHERE Id =?', array($mid) ); + if ( isset($changes['Name']) ) { + $saferOldName = basename( $monitor['Name'] ); + $saferNewName = basename( $_REQUEST['newMonitor']['Name'] ); + rename( ZM_DIR_EVENTS."/".$saferOldName, ZM_DIR_EVENTS."/".$saferNewName); + } + if ( isset($changes['Width']) || isset($changes['Height']) ) { + $newW = $_REQUEST['newMonitor']['Width']; + $newH = $_REQUEST['newMonitor']['Height']; + $newA = $newW * $newH; + $oldW = $monitor['Width']; + $oldH = $monitor['Height']; + $oldA = $oldW * $oldH; + + $zones = dbFetchAll( 'SELECT * FROM Zones WHERE MonitorId=?', NULL, array($mid) ); + foreach ( $zones as $zone ) { + $newZone = $zone; + $points = coordsToPoints( $zone['Coords'] ); + for ( $i = 0; $i < count($points); $i++ ) { + $points[$i]['x'] = intval(($points[$i]['x']*($newW-1))/($oldW-1)); + $points[$i]['y'] = intval(($points[$i]['y']*($newH-1))/($oldH-1)); + } + $newZone['Coords'] = pointsToCoords( $points ); + $newZone['Area'] = intval(round(($zone['Area']*$newA)/$oldA)); + $newZone['MinAlarmPixels'] = intval(round(($newZone['MinAlarmPixels']*$newA)/$oldA)); + $newZone['MaxAlarmPixels'] = intval(round(($newZone['MaxAlarmPixels']*$newA)/$oldA)); + $newZone['MinFilterPixels'] = intval(round(($newZone['MinFilterPixels']*$newA)/$oldA)); + $newZone['MaxFilterPixels'] = intval(round(($newZone['MaxFilterPixels']*$newA)/$oldA)); + $newZone['MinBlobPixels'] = intval(round(($newZone['MinBlobPixels']*$newA)/$oldA)); + $newZone['MaxBlobPixels'] = intval(round(($newZone['MaxBlobPixels']*$newA)/$oldA)); + + $changes = getFormChanges( $zone, $newZone, $types ); + + if ( count( $changes ) ) { + dbQuery( "update Zones set ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zone['Id'] ) ); + } + } + } + } elseif ( ! $user['MonitorIds'] ) { // Can only create new monitors if we are not restricted to specific monitors +# FIXME This is actually a race condition. Should lock the table. + $maxSeq = dbFetchOne( 'SELECT max(Sequence) AS MaxSequence FROM Monitors', 'MaxSequence' ); + $changes[] = 'Sequence = '.($maxSeq+1); + + dbQuery( 'INSERT INTO Monitors SET '.implode( ', ', $changes ) ); + $mid = dbInsertId(); + $zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height']; + dbQuery( "insert into Zones set MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) ); + //$view = 'none'; + mkdir( ZM_DIR_EVENTS.'/'.$mid, 0755 ); + $saferName = basename($_REQUEST['newMonitor']['Name']); + symlink( $mid, ZM_DIR_EVENTS.'/'.$saferName ); + if ( isset($_COOKIE['zmGroup']) ) { + dbQuery( "UPDATE Groups SET MonitorIds = concat(MonitorIds,',".$mid."') WHERE Id=?", array($_COOKIE['zmGroup']) ); + } + } else { + Error("Users with Monitors restrictions cannot create new monitors."); + } + $restart = true; + } + + if ( ZM_OPT_X10 ) { + $x10Changes = getFormChanges( $x10Monitor, $_REQUEST['newX10Monitor'] ); + + if ( count( $x10Changes ) ) { + if ( $x10Monitor && isset($_REQUEST['newX10Monitor']) ) { + dbQuery( "update TriggersX10 set ".implode( ", ", $x10Changes )." where MonitorId=?", array($mid) ); + } elseif ( !$user['MonitorIds'] ) { + if ( !$x10Monitor ) { + dbQuery( "insert into TriggersX10 set MonitorId = ?, ".implode( ", ", $x10Changes ), array( $mid ) ); + } else { + dbQuery( "delete from TriggersX10 where MonitorId = ?", array($mid) ); + } + } + $restart = true; + } + } + + if ( $restart ) { + $new_monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid) ); + //fixDevices(); + //if ( $cookies ) + //session_write_close(); + + zmcControl( $new_monitor, 'start' ); + zmaControl( $new_monitor, 'start' ); + + if ( $monitor['Controllable'] ) { + require_once( 'control_functions.php' ); + sendControlCommand( $mid, 'quit' ); + } + // really should thump zmwatch and maybe zmtrigger too. + //daemonControl( 'restart', 'zmwatch.pl' ); + $refreshParent = true; + } // end if restart + $view = 'none'; } + if ( $action == 'delete' ) { + if ( isset($_REQUEST['markMids']) && !$user['MonitorIds'] ) { + foreach( $_REQUEST['markMids'] as $markMid ) { + if ( canEdit( 'Monitors', $markMid ) ) { + if ( $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($markMid) ) ) { + if ( daemonCheck() ) { + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'stop' ); + } + + // This is the important stuff + dbQuery( 'DELETE FROM Monitors WHERE Id = ?', array($markMid) ); + dbQuery( 'DELETE FROM Zones WHERE MonitorId = ?', array($markMid) ); + if ( ZM_OPT_X10 ) + dbQuery( 'DELETE FROM TriggersX10 WHERE MonitorId=?', array($markMid) ); + + fixSequences(); + + // If fast deletes are on, then zmaudit will clean everything else up later + // If fast deletes are off and there are lots of events then this step may + // well time out before completing, in which case zmaudit will still tidy up + if ( !ZM_OPT_FAST_DELETE ) { + // Slight hack, we maybe should load *, but we happen to know that the deleteEvent function uses Id and StartTime. + $markEids = dbFetchAll( 'SELECT Id,StartTime FROM Events WHERE MonitorId=?', NULL, array($markMid) ); + foreach( $markEids as $markEid ) + deleteEvent( $markEid, $markMid ); + + deletePath( ZM_DIR_EVENTS.'/'.basename($monitor['Name']) ); + deletePath( ZM_DIR_EVENTS.'/'.$monitor['Id'] ); // I'm trusting the Id. + } // end if ZM_OPT_FAST_DELETE + } // end if found the monitor in the db + } // end if canedit this monitor + } // end foreach monitor in MarkMid + } // markMids is set and we aren't limited to specific monitors + } // end if action == Delete + } + + // Device view actions + if ( canEdit( 'Devices' ) ) { + if ( $action == 'device' ) { + if ( !empty($_REQUEST['command']) ) { + setDeviceStatusX10( $_REQUEST['key'], $_REQUEST['command'] ); + } elseif ( isset( $_REQUEST['newDevice'] ) ) { + if ( isset($_REQUEST['did']) ) { + dbQuery( "update Devices set Name=?, KeyString=? where Id=?", array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'], $_REQUEST['did']) ); + } else { + dbQuery( "insert into Devices set Name=?, KeyString=?", array( $_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'] ) ); + } + $refreshParent = true; + $view = 'none'; + } + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markDids']) ) { + foreach( $_REQUEST['markDids'] as $markDid ) { + dbQuery( "delete from Devices where Id=?", array($markDid) ); + $refreshParent = true; + } + } + } // end if action + } // end if canedit devices + + // Group view actions + if ( canView( 'Groups' ) && $action == 'setgroup' ) { + if ( !empty($_REQUEST['gid']) ) { + setcookie( 'zmGroup', validInt($_REQUEST['gid']), time()+3600*24*30*12*10 ); + } else { + setcookie( 'zmGroup', '', time()-3600*24*2 ); + } + $refreshParent = true; + } + + // Group edit actions + if ( canEdit( 'Groups' ) ) { + if ( $action == 'group' ) { +# Should probably verfy that each monitor id is a valid monitor, that we have access to. HOwever at the moment, you have to have System permissions to do this + $monitors = empty( $_POST['newGroup']['MonitorIds'] ) ? NULL : implode(',', $_POST['newGroup']['MonitorIds']); + if ( !empty($_POST['gid']) ) { + dbQuery( "UPDATE Groups SET Name=?, MonitorIds=? WHERE Id=?", array($_POST['newGroup']['Name'], $monitors, $_POST['gid']) ); + } else { + dbQuery( "INSERT INTO Groups SET Name=?, MonitorIds=?", array( $_POST['newGroup']['Name'], $monitors ) ); + } + $view = 'none'; + } + if ( !empty($_REQUEST['gid']) && $action == 'delete' ) { + dbQuery( 'DELETE FROM Groups WHERE Id = ?', array($_REQUEST['gid']) ); + if ( isset($_COOKIE['zmGroup']) ) { + if ( $_REQUEST['gid'] == $_COOKIE['zmGroup'] ) { + unset( $_COOKIE['zmGroup'] ); + setcookie( 'zmGroup', '', time()-3600*24*2 ); + $refreshParent = true; + } + } + } + $refreshParent = true; + } // end if can edit groups + + // System edit actions + if ( canEdit( 'System' ) ) { + if ( isset( $_REQUEST['object'] ) ) { + if ( $_REQUEST['object'] == 'server' ) { + + if ( $action == 'Save' ) { + if ( !empty($_REQUEST['id']) ) + $dbServer = dbFetchOne( 'SELECT * FROM Servers WHERE Id=?', NULL, array($_REQUEST['id']) ); + else + $dbServer = array(); + + $types = array(); + $changes = getFormChanges( $dbServer, $_REQUEST['newServer'], $types ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['id']) ) { + dbQuery( "UPDATE Servers SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); + } else { + dbQuery( "INSERT INTO Servers set ".implode( ", ", $changes ) ); + } + $refreshParent = true; + } + $view = 'none'; + } else if ( $action == 'delete' ) { + if ( !empty($_REQUEST['markIds']) ) { + foreach( $_REQUEST['markIds'] as $Id ) + dbQuery( "DELETE FROM Servers WHERE Id=?", array($Id) ); + } + $refreshParent = true; + } else { + Error( "Unknown action $action in saving Server" ); + } + } else if ( $_REQUEST['object'] == 'storage' ) { + if ( $action == 'Save' ) { + if ( !empty($_REQUEST['id']) ) + $dbStorage = dbFetchOne( 'SELECT * FROM Storage WHERE Id=?', NULL, array($_REQUEST['id']) ); + else + $dbStorage = array(); + + $types = array(); + $changes = getFormChanges( $dbStorage, $_REQUEST['newStorage'], $types ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['id']) ) { + dbQuery( "UPDATE Storage SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); + } else { + dbQuery( "INSERT INTO Storage set ".implode( ", ", $changes ) ); + } + $refreshParent = true; + } + $view = 'none'; + } else if ( $action == 'delete' ) { + if ( !empty($_REQUEST['markIds']) ) { + foreach( $_REQUEST['markIds'] as $Id ) + dbQuery( 'DELETE FROM Storage WHERE Id=?', array($Id) ); + } + $refreshParent = true; + } else { + Error( "Unknown action $action in saving Storage" ); + } + } # end if isset($_REQUEST['object'] ) + + } else if ( $action == 'version' && isset($_REQUEST['option']) ) { + $option = $_REQUEST['option']; + switch( $option ) { + case 'go' : + { + // Ignore this, the caller will open the page itself + break; + } + case 'ignore' : + { + dbQuery( "update Config set Value = '".ZM_DYN_LAST_VERSION."' where Name = 'ZM_DYN_CURR_VERSION'" ); + break; + } + case 'hour' : + case 'day' : + case 'week' : + { + $nextReminder = time(); + if ( $option == 'hour' ) { + $nextReminder += 60*60; + } elseif ( $option == 'day' ) { + $nextReminder += 24*60*60; + } elseif ( $option == 'week' ) { + $nextReminder += 7*24*60*60; + } + dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_NEXT_REMINDER'" ); + break; + } + case 'never' : + { + dbQuery( "update Config set Value = '0' where Name = 'ZM_CHECK_FOR_UPDATES'" ); + break; + } + } + } + if ( $action == 'donate' && isset($_REQUEST['option']) ) { + $option = $_REQUEST['option']; + switch( $option ) { + case 'go' : + { + // Ignore this, the caller will open the page itself + break; + } + case 'hour' : + case 'day' : + case 'week' : + case 'month' : + { + $nextReminder = time(); + if ( $option == 'hour' ) { + $nextReminder += 60*60; + } elseif ( $option == 'day' ) { + $nextReminder += 24*60*60; + } elseif ( $option == 'week' ) { + $nextReminder += 7*24*60*60; + } elseif ( $option == 'month' ) { + $nextReminder += 30*24*60*60; + } + dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_DONATE_REMINDER_TIME'" ); + break; + } + case 'never' : + case 'already' : + { + dbQuery( "update Config set Value = '0' where Name = 'ZM_DYN_SHOW_DONATE_REMINDER'" ); + break; + } + } // end switch option + } + if ( $action == 'options' && isset($_REQUEST['tab']) ) { + $configCat = $configCats[$_REQUEST['tab']]; + $changed = false; + foreach ( $configCat as $name=>$value ) { + unset( $newValue ); + if ( $value['Type'] == 'boolean' && empty($_REQUEST['newConfig'][$name]) ) + $newValue = 0; + elseif ( isset($_REQUEST['newConfig'][$name]) ) + $newValue = preg_replace( "/\r\n/", "\n", stripslashes( $_REQUEST['newConfig'][$name] ) ); + + if ( isset($newValue) && ($newValue != $value['Value']) ) { + dbQuery( 'UPDATE Config SET Value=? WHERE Name=?', array( $newValue, $name ) ); + $changed = true; + } + } + if ( $changed ) { + switch( $_REQUEST['tab'] ) { + case 'system' : + case 'config' : + case 'paths' : + $restartWarning = true; + break; + case 'web' : + case 'tools' : + break; + case 'logging' : + case 'network' : + case 'mail' : + case 'upload' : + $restartWarning = true; + break; + case 'highband' : + case 'medband' : + case 'lowband' : + break; + } + } + loadConfig( false ); + } elseif ( $action == 'user' ) { + if ( !empty($_REQUEST['uid']) ) + $dbUser = dbFetchOne( "SELECT * FROM Users WHERE Id=?", NULL, array($_REQUEST['uid']) ); + else + $dbUser = array(); + + $types = array(); + $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); + + if ( $_REQUEST['newUser']['Password'] ) + $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; + else + unset( $changes['Password'] ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['uid']) ) { + dbQuery( "update Users set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['uid']) ); + # If we are updating the logged in user, then update our session user data. + if ( $user and ( $dbUser['Username'] == $user['Username'] ) ) + userLogin( $dbUser['Username'], $dbUser['Password'] ); + } else { + dbQuery( "insert into Users set ".implode( ", ", $changes ) ); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'state' ) { + if ( !empty($_REQUEST['runState']) ) { + //if ( $cookies ) session_write_close(); + packageControl( $_REQUEST['runState'] ); + $refreshParent = true; + } + } elseif ( $action == 'save' ) { + if ( !empty($_REQUEST['runState']) || !empty($_REQUEST['newState']) ) { + $sql = 'SELECT Id,Function,Enabled FROM Monitors ORDER BY Id'; + $definitions = array(); + foreach( dbFetchAll( $sql ) as $monitor ) + { + $definitions[] = $monitor['Id'].":".$monitor['Function'].":".$monitor['Enabled']; + } + $definition = join( ',', $definitions ); + if ( $_REQUEST['newState'] ) + $_REQUEST['runState'] = $_REQUEST['newState']; + dbQuery( "replace into States set Name=?, Definition=?", array( $_REQUEST['runState'],$definition) ); + } + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['runState']) ) + dbQuery( "delete from States where Name=?", array($_REQUEST['runState']) ); + + if ( isset($_REQUEST['markUids']) ) { + foreach( $_REQUEST['markUids'] as $markUid ) + dbQuery( "delete from Users where Id = ?", array($markUid) ); + if ( $markUid == $user['Id'] ) + userLogout(); + } + } + } else { + if ( ZM_USER_SELF_EDIT && $action == 'user' ) { + $uid = $user['Id']; + + $dbUser = dbFetchOne( 'SELECT Id, Password, Language FROM Users WHERE Id = ?', NULL, array($uid) ); + + $types = array(); + $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); + + if ( !empty($_REQUEST['newUser']['Password']) ) + $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; + else + unset( $changes['Password'] ); + if ( count( $changes ) ) { + dbQuery( "update Users set ".implode( ", ", $changes )." where Id=?", array($uid) ); + $refreshParent = true; + } + $view = 'none'; + } + } + + if ( $action == 'reset' ) { + $_SESSION['zmEventResetTime'] = strftime( STRF_FMT_DATETIME_DB ); + setcookie( 'zmEventResetTime', $_SESSION['zmEventResetTime'], time()+3600*24*30*12*10 ); + //if ( $cookies ) session_write_close(); + } } ?> diff --git a/web/includes/control_functions.php b/web/includes/control_functions.php index dfbc8cb3e..d5fa06675 100644 --- a/web/includes/control_functions.php +++ b/web/includes/control_functions.php @@ -9,7 +9,7 @@ function buildControlCommand( $monitor ) $slow = 0.9; // Threshold for slow speed/timeouts $turbo = 0.9; // Threshold for turbo speed - if ( preg_match( '/^([a-z]+)([A-Z][a-z]+)([A-Z][a-z]+)+$/', $_REQUEST['control'], $matches ) ) + if ( preg_match( '/^([a-z]+)([A-Z][a-z]+)([A-Za-z]+)+$/', $_REQUEST['control'], $matches ) ) { $command = $matches[1]; $mode = $matches[2]; @@ -278,6 +278,8 @@ function buildControlCommand( $monitor ) } } } + } else { + Error("Invalid control parameter: " . $_REQUEST['control'] ); } } elseif ( isset($_REQUEST['x']) && isset($_REQUEST['y']) ) diff --git a/web/includes/csrf/LICENSE.txt b/web/includes/csrf/LICENSE.txt new file mode 100644 index 000000000..37b07717d --- /dev/null +++ b/web/includes/csrf/LICENSE.txt @@ -0,0 +1,9 @@ +Copyright (c) 2008-2013, Edward Z. Yang +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/web/includes/csrf/NEWS.txt b/web/includes/csrf/NEWS.txt new file mode 100644 index 000000000..66d52f6da --- /dev/null +++ b/web/includes/csrf/NEWS.txt @@ -0,0 +1,69 @@ + + [[ news ]] + +1.0.4 released 2013-07-17 + + [SECURITY FIXES] + + - When secret key was not explicitly set, it was not being used + by the csrf_hash() function. Thanks sparticvs for reporting. + + [FEATURES] + + - The default 'CSRF check failed' page now offers a handy 'Try + again' button, which resubmits the form. + + [BUG FIXES] + + - The fix for 1.0.3 inadvertantly turned off XMLHttpRequest + overloading for all browsers; it has now been fixed to only + apply to IE. + +1.0.3 released 2012-01-31 + + [BUG FIXES] + + - Internet Explorer 8 adds support for XMLHttpRequest.prototype, + but this support is broken for method overloading. We + explicitly disable JavaScript overloading for Internet Explorer. + Thanks Kelly Lu for reporting. + + - A global declaration was omitted, resulting in a variable + not being properly introduced in PHP 5.3. Thanks Whitney Beck for + reporting. + +1.0.2 released 2009-03-08 + + [SECURITY FIXES] + + - Due to a typo, csrf-magic accidentally treated the secret key + as always present. This means that there was a possible CSRF + attack against users without any cookies. No attacks in the + wild were known at the time of this release. Thanks Jakub + Vrรกna for reporting. + +1.0.1 released 2008-11-02 + + [NEW FEATURES] + + - Support for composite tokens; this also fixes a bug with using + IP-based tokens for users with cookies disabled. + + - Native support cookie tokens; use csrf_conf('cookie', $name) to + specify the name of a cookie that the CSRF token should be + placed in. This is useful if you have a Squid cache, and need + to configure it to ignore this token. + + - Tips/tricks section in README.txt. + + - There is now a two hour expiration time on all tokens. This + can be modified using csrf_conf('expires', $seconds). + + - ClickJacking protection using an iframe breaker. Disable with + csrf_conf('frame-breaker', false). + + [BUG FIXES] + + - CsrfMagic.send() incorrectly submitted GET requests twice, + once without the magic token and once with the token. Reported + by Kelly Lu . diff --git a/web/includes/csrf/README.txt b/web/includes/csrf/README.txt new file mode 100644 index 000000000..98d225dba --- /dev/null +++ b/web/includes/csrf/README.txt @@ -0,0 +1,160 @@ + + [[ csrf-magic ]] + +Add the following line to the top of all web-accessible PHP pages. If you have +a common file included by everything, put it there. + + include_once '/path/to/csrf-magic.php'; + +Do it, test it, then forget about it. csrf-magic is protecting you if nothing +bad happens. Read on if you run into problems. + + + TABLE OF CONTENTS + + ------------------- + + 1. TIPS AND TRICKS + 2. AJAX + 3. CONFIGURE + 4. THANKS + 5. FOOTNOTES + + ------------------- + + + +1. TIPS AND TRICKS + + * If your JavaScript and AJAX is persistently getting errors, check the + AJAX section below on how to fix. + + * The CSS overlay protection makes it impossible to display your website + in frame/iframe elements. You can disable it with + csrf_conf('frame-breaker', false) in your csrf_startup() function. + + * csrf-magic will start a session. To disable, use csrf_conf('auto-session', + false) in your csrf_startup() function. + + * The default error message is a little user unfriendly. Write your own + function which outputs an error message and set csrf_conf('callback', + 'myCallbackFunction') in your csrf_startup() function. + + * Make sure csrf_conf('secret', 'ABCDEFG') has something random in it. If + the directory csrf-magic.php is in is writable, csrf-magic will generate + a secret key for you in the csrf-secret.php file. + + * Remember you can use auto_prepend to include csrf-magic.php on all your + pages. You may want to create a stub file which you can include that + includes csrf-magic.php as well as performs configuration. + + * The default expiration time for tokens is two hours. If you expect your + users to need longer to fill out forms, be sure to enable double + submission when the token is invalid. + + +2. AJAX + +csrf-magic has the ability to dynamically rewrite AJAX requests which use +XMLHttpRequest. However, due to the invasiveness of this procedure, it is +not enabled by default. You can enable it by adding this code before you +include csrf-magic.php. + + function csrf_startup() { + csrf_conf('rewrite-js', '/web/path/to/csrf-magic.js'); + } + // include_once '/path/to/csrf-magic.php'; + +(Be sure to place csrf-magic.js somewhere web accessible). + +The default method CSRF Magic uses to rewrite AJAX requests will +only work for browsers with support for XmlHttpRequest.prototype (this excludes +all versions of Internet Explorer). See this page for more information: +http://stackoverflow.com/questions/664315/internet-explorer-8-prototypes-and-xmlhttprequest + +However, csrf-magic.js will +automatically detect and play nice with the following JavaScript frameworks: + + * jQuery + * Prototype + * MooTools + * Ext + * Dojo + +(Note 2013-07-16: It has been a long time since this manual support has +been updated, and some JavaScript libraries have placed their copies of XHR +in local variables in closures, which makes it difficult for us to monkey-patch +it in automatically.) + +To rewrite your own JavaScript library to use csrf-magic.js, you should modify +your function that generates XMLHttpRequest to have this at the end: + + return new CsrfMagic(xhrObject); + +With whatever xhrObject may be. If you have literal instances of XMLHttpRequest +in your code, find and replace ''new XMLHttpRequest'' with ''new CsrfMagic'' +(CsrfMagic will automatically instantiate an XMLHttpRequest object in a +cross-platform manner as necessary). + +If you don't want csrf-magic monkeying around with your XMLHttpRequest object, +you can manually rewrite your AJAX code to include the variable. The important +information is stored in the global variables csrfMagicName and csrfMagicToken. +CsrfMagic.process may also be of interest, as it takes one parameter, a +querystring, and prepends the CSRF token to the value. + + +3. CONFIGURE + +csrf-magic has some configuration options that you can set inside the +csrf_startup() function. They are described in csrf-magic.php, and you can +set them using the convenience function csrf_conf($name, $value). + +For example, this is a recommended configuration: + + /** + * This is a function that gets called if a csrf check fails. csrf-magic will + * then exit afterwards. + */ + function my_csrf_callback() { + echo "You're doing bad things young man!"; + } + + function csrf_startup() { + + // While csrf-magic has a handy little heuristic for determining whether + // or not the content in the buffer is HTML or not, you should really + // give it a nudge and turn rewriting *off* when the content is + // not HTML. Implementation details will vary. + if (isset($_POST['ajax'])) csrf_conf('rewrite', false); + + // This is a secret value that must be set in order to enable username + // and IP based checks. Don't show this to anyone. A secret id will + // automatically be generated for you if the directory csrf-magic.php + // is placed in is writable. + csrf_conf('secret', 'ABCDEFG123456'); + + // This enables JavaScript rewriting and will ensure your AJAX calls + // don't stop working. + csrf_conf('rewrite-js', '/csrf-magic.js'); + + // This makes csrf-magic call my_csrf_callback() before exiting when + // there is a bad csrf token. This lets me customize the error page. + csrf_conf('callback', 'my_csrf_callback'); + + // While this is enabled by default to boost backwards compatibility, + // for security purposes it should ideally be off. Some users can be + // NATted or have dialup addresses which rotate frequently. Cookies + // are much more reliable. + csrf_conf('allow-ip', false); + + } + + // Finally, include the library + include_once '/path/to/csrf-magic.php'; + +Configuration gets stored in the $GLOBALS['csrf'] array. + + +4. THANKS + +My thanks to Chris Shiflett, for unintentionally inspiring the idea, as well +as telling me the original variant of the Bob and Mallory story, +and the Django CSRF Middleware authors, who thought up of this before me. +Gareth Heyes suggested using the frame-breaker option to protect against +CSS overlay attacks. diff --git a/web/includes/csrf/csrf-magic.js b/web/includes/csrf/csrf-magic.js new file mode 100644 index 000000000..0989c1065 --- /dev/null +++ b/web/includes/csrf/csrf-magic.js @@ -0,0 +1,191 @@ +/** + * @file + * + * Rewrites XMLHttpRequest to automatically send CSRF token with it. In theory + * plays nice with other JavaScript libraries, needs testing though. + */ + +// Here are the basic overloaded method definitions +// The wrapper must be set BEFORE onreadystatechange is written to, since +// a bug in ActiveXObject prevents us from properly testing for it. +CsrfMagic = function(real) { + // try to make it ourselves, if you didn't pass it + if (!real) try { real = new XMLHttpRequest; } catch (e) {;} + if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {;} + if (!real) try { real = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {;} + if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP.4.0'); } catch (e) {;} + this.csrf = real; + // properties + var csrfMagic = this; + real.onreadystatechange = function() { + csrfMagic._updateProps(); + return csrfMagic.onreadystatechange ? csrfMagic.onreadystatechange() : null; + }; + csrfMagic._updateProps(); +} + +CsrfMagic.prototype = { + + open: function(method, url, async, username, password) { + if (method == 'POST') this.csrf_isPost = true; + // deal with Opera bug, thanks jQuery + if (username) return this.csrf_open(method, url, async, username, password); + else return this.csrf_open(method, url, async); + }, + csrf_open: function(method, url, async, username, password) { + if (username) return this.csrf.open(method, url, async, username, password); + else return this.csrf.open(method, url, async); + }, + + send: function(data) { + if (!this.csrf_isPost) return this.csrf_send(data); + prepend = csrfMagicName + '=' + csrfMagicToken + '&'; + // XXX: Removed to eliminate 'Refused to set unsafe header "Content-length" ' errors in modern browsers + // if (this.csrf_purportedLength === undefined) { + // this.csrf_setRequestHeader("Content-length", this.csrf_purportedLength + prepend.length); + // delete this.csrf_purportedLength; + // } + delete this.csrf_isPost; + return this.csrf_send(prepend + data); + }, + csrf_send: function(data) { + return this.csrf.send(data); + }, + + setRequestHeader: function(header, value) { + // We have to auto-set this at the end, since we don't know how long the + // nonce is when added to the data. + if (this.csrf_isPost && header == "Content-length") { + this.csrf_purportedLength = value; + return; + } + return this.csrf_setRequestHeader(header, value); + }, + csrf_setRequestHeader: function(header, value) { + return this.csrf.setRequestHeader(header, value); + }, + + abort: function() { + return this.csrf.abort(); + }, + getAllResponseHeaders: function() { + return this.csrf.getAllResponseHeaders(); + }, + getResponseHeader: function(header) { + return this.csrf.getResponseHeader(header); + } // , +} + +// proprietary +CsrfMagic.prototype._updateProps = function() { + this.readyState = this.csrf.readyState; + if (this.readyState == 4) { + this.responseText = this.csrf.responseText; + this.responseXML = this.csrf.responseXML; + this.status = this.csrf.status; + this.statusText = this.csrf.statusText; + } +} +CsrfMagic.process = function(base) { + if(typeof base == 'object') { + base[csrfMagicName] = csrfMagicToken; + return base; + } + var prepend = csrfMagicName + '=' + csrfMagicToken; + if (base) return prepend + '&' + base; + return prepend; +} +// callback function for when everything on the page has loaded +CsrfMagic.end = function() { + // This rewrites forms AGAIN, so in case buffering didn't work this + // certainly will. + forms = document.getElementsByTagName('form'); + for (var i = 0; i < forms.length; i++) { + form = forms[i]; + if (form.method.toUpperCase() !== 'POST') continue; + if (form.elements[csrfMagicName]) continue; + var input = document.createElement('input'); + input.setAttribute('name', csrfMagicName); + input.setAttribute('value', csrfMagicToken); + input.setAttribute('type', 'hidden'); + form.appendChild(input); + } +} + +// Sets things up for Mozilla/Opera/nice browsers +// We very specifically match against Internet Explorer, since they haven't +// implemented prototypes correctly yet. +if (window.XMLHttpRequest && window.XMLHttpRequest.prototype && '\v' != 'v') { + var x = XMLHttpRequest.prototype; + var c = CsrfMagic.prototype; + + // Save the original functions + x.csrf_open = x.open; + x.csrf_send = x.send; + x.csrf_setRequestHeader = x.setRequestHeader; + + // Notice that CsrfMagic is itself an instantiatable object, but only + // open, send and setRequestHeader are necessary as decorators. + x.open = c.open; + x.send = c.send; + x.setRequestHeader = c.setRequestHeader; +} else { + // The only way we can do this is by modifying a library you have been + // using. We support YUI, script.aculo.us, prototype, MooTools, + // jQuery, Ext and Dojo. + if (window.jQuery) { + // jQuery didn't implement a new XMLHttpRequest function, so we have + // to do this the hard way. + jQuery.csrf_ajax = jQuery.ajax; + jQuery.ajax = function( s ) { + if (s.type && s.type.toUpperCase() == 'POST') { + s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s)); + if ( s.data && s.processData && typeof s.data != "string" ) { + s.data = jQuery.param(s.data); + } + s.data = CsrfMagic.process(s.data); + } + return jQuery.csrf_ajax( s ); + } + } + if (window.Prototype) { + // This works for script.aculo.us too + Ajax.csrf_getTransport = Ajax.getTransport; + Ajax.getTransport = function() { + return new CsrfMagic(Ajax.csrf_getTransport()); + } + } + if (window.MooTools) { + Browser.csrf_Request = Browser.Request; + Browser.Request = function () { + return new CsrfMagic(Browser.csrf_Request()); + } + } + if (window.YAHOO) { + // old YUI API + YAHOO.util.Connect.csrf_createXhrObject = YAHOO.util.Connect.createXhrObject; + YAHOO.util.Connect.createXhrObject = function (transaction) { + obj = YAHOO.util.Connect.csrf_createXhrObject(transaction); + obj.conn = new CsrfMagic(obj.conn); + return obj; + } + } + if (window.Ext) { + // Ext can use other js libraries as loaders, so it has to come last + // Ext's implementation is pretty identical to Yahoo's, but we duplicate + // it for comprehensiveness's sake. + Ext.lib.Ajax.csrf_createXhrObject = Ext.lib.Ajax.createXhrObject; + Ext.lib.Ajax.createXhrObject = function (transaction) { + obj = Ext.lib.Ajax.csrf_createXhrObject(transaction); + obj.conn = new CsrfMagic(obj.conn); + return obj; + } + } + if (window.dojo) { + // NOTE: this doesn't work with latest dojo + dojo.csrf__xhrObj = dojo._xhrObj; + dojo._xhrObj = function () { + return new CsrfMagic(dojo.csrf__xhrObj()); + } + } +} diff --git a/web/includes/csrf/csrf-magic.php b/web/includes/csrf/csrf-magic.php new file mode 100644 index 000000000..153417f4e --- /dev/null +++ b/web/includes/csrf/csrf-magic.php @@ -0,0 +1,405 @@ + + */ +$GLOBALS['csrf']['input-name'] = '__csrf_magic'; + +/** + * Set this to false if your site must work inside of frame/iframe elements, + * but do so at your own risk: this configuration protects you against CSS + * overlay attacks that defeat tokens. + */ +$GLOBALS['csrf']['frame-breaker'] = true; + +/** + * Whether or not CSRF Magic should be allowed to start a new session in order + * to determine the key. + */ +$GLOBALS['csrf']['auto-session'] = true; + +/** + * Whether or not csrf-magic should produce XHTML style tags. + */ +$GLOBALS['csrf']['xhtml'] = true; + +// FUNCTIONS: + +// Don't edit this! +$GLOBALS['csrf']['version'] = '1.0.4'; + +/** + * Rewrites
on the fly to add CSRF tokens to them. This can also + * inject our JavaScript library. + */ +function csrf_ob_handler($buffer, $flags) { + // Even though the user told us to rewrite, we should do a quick heuristic + // to check if the page is *actually* HTML. We don't begin rewriting until + // we hit the first "; + $buffer = preg_replace('#(]*method\s*=\s*["\']post["\'][^>]*>)#i', '$1' . $input, $buffer); + if ($GLOBALS['csrf']['frame-breaker']) { + $buffer = str_ireplace('', '', $buffer); + } + if ($js = $GLOBALS['csrf']['rewrite-js']) { + $buffer = str_ireplace( + '', + ''. + '', + $buffer + ); + $script = ''; + $buffer = str_ireplace('', $script . '', $buffer, $count); + if (!$count) { + $buffer .= $script; + } + } + return $buffer; +} + +/** + * Checks if this is a post request, and if it is, checks if the nonce is valid. + * @param bool $fatal Whether or not to fatally error out if there is a problem. + * @return True if check passes or is not necessary, false if failure. + */ +function csrf_check($fatal = true) { + if ($_SERVER['REQUEST_METHOD'] !== 'POST') return true; + csrf_start(); + $name = $GLOBALS['csrf']['input-name']; + $ok = false; + $tokens = ''; + do { + if (!isset($_POST[$name])) break; + // we don't regenerate a token and check it because some token creation + // schemes are volatile. + $tokens = $_POST[$name]; + if (!csrf_check_tokens($tokens)) break; + $ok = true; + } while (false); + if ($fatal && !$ok) { + $callback = $GLOBALS['csrf']['callback']; + if (trim($tokens, 'A..Za..z0..9:;,') !== '') $tokens = 'hidden'; + $callback($tokens); + exit; + } + return $ok; +} + +/** + * Retrieves a valid token(s) for a particular context. Tokens are separated + * by semicolons. + */ +function csrf_get_tokens() { + $has_cookies = !empty($_COOKIE); + + // $ip implements a composite key, which is sent if the user hasn't sent + // any cookies. It may or may not be used, depending on whether or not + // the cookies "stick" + $secret = csrf_get_secret(); + if (!$has_cookies && $secret) { + // :TODO: Harden this against proxy-spoofing attacks + $IP_ADDRESS = (isset($_SERVER['IP_ADDRESS']) ? $_SERVER['IP_ADDRESS'] : $_SERVER['REMOTE_ADDR']); + $ip = ';ip:' . csrf_hash($IP_ADDRESS); + } else { + $ip = ''; + } + csrf_start(); + + // These are "strong" algorithms that don't require per se a secret + if (session_id()) return 'sid:' . csrf_hash(session_id()) . $ip; + if ($GLOBALS['csrf']['cookie']) { + $val = csrf_generate_secret(); + setcookie($GLOBALS['csrf']['cookie'], $val); + return 'cookie:' . csrf_hash($val) . $ip; + } + if ($GLOBALS['csrf']['key']) return 'key:' . csrf_hash($GLOBALS['csrf']['key']) . $ip; + // These further algorithms require a server-side secret + if (!$secret) return 'invalid'; + if ($GLOBALS['csrf']['user'] !== false) { + return 'user:' . csrf_hash($GLOBALS['csrf']['user']); + } + if ($GLOBALS['csrf']['allow-ip']) { + return ltrim($ip, ';'); + } + return 'invalid'; +} + +function csrf_flattenpost($data) { + $ret = array(); + foreach($data as $n => $v) { + $ret = array_merge($ret, csrf_flattenpost2(1, $n, $v)); + } + return $ret; +} +function csrf_flattenpost2($level, $key, $data) { + if(!is_array($data)) return array($key => $data); + $ret = array(); + foreach($data as $n => $v) { + $nk = $level >= 1 ? $key."[$n]" : "[$n]"; + $ret = array_merge($ret, csrf_flattenpost2($level+1, $nk, $v)); + } + return $ret; +} + +/** + * @param $tokens is safe for HTML consumption + */ +function csrf_callback($tokens) { + // (yes, $tokens is safe to echo without escaping) + header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); + $data = ''; + foreach (csrf_flattenpost($_POST) as $key => $value) { + if ($key == $GLOBALS['csrf']['input-name']) continue; + $data .= ''; + } + echo "CSRF check failed + +

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

+ $data +

Debug: $tokens

+"; +} + +/** + * Checks if a composite token is valid. Outward facing code should use this + * instead of csrf_check_token() + */ +function csrf_check_tokens($tokens) { + if (is_string($tokens)) $tokens = explode(';', $tokens); + foreach ($tokens as $token) { + if (csrf_check_token($token)) return true; + } + return false; +} + +/** + * Checks if a token is valid. + */ +function csrf_check_token($token) { + if (strpos($token, ':') === false) return false; + list($type, $value) = explode(':', $token, 2); + if (strpos($value, ',') === false) return false; + list($x, $time) = explode(',', $token, 2); + if ($GLOBALS['csrf']['expires']) { + if (time() > $time + $GLOBALS['csrf']['expires']) return false; + } + switch ($type) { + case 'sid': + return $value === csrf_hash(session_id(), $time); + case 'cookie': + $n = $GLOBALS['csrf']['cookie']; + if (!$n) return false; + if (!isset($_COOKIE[$n])) return false; + return $value === csrf_hash($_COOKIE[$n], $time); + case 'key': + if (!$GLOBALS['csrf']['key']) return false; + return $value === csrf_hash($GLOBALS['csrf']['key'], $time); + // We could disable these 'weaker' checks if 'key' was set, but + // that doesn't make me feel good then about the cookie-based + // implementation. + case 'user': + if (!csrf_get_secret()) return false; + if ($GLOBALS['csrf']['user'] === false) return false; + return $value === csrf_hash($GLOBALS['csrf']['user'], $time); + case 'ip': + if (!csrf_get_secret()) return false; + // do not allow IP-based checks if the username is set, or if + // the browser sent cookies + if ($GLOBALS['csrf']['user'] !== false) return false; + if (!empty($_COOKIE)) return false; + if (!$GLOBALS['csrf']['allow-ip']) return false; + $IP_ADDRESS = (isset($_SERVER['IP_ADDRESS']) ? $_SERVER['IP_ADDRESS'] : $_SERVER['REMOTE_ADDR']); + return $value === csrf_hash($IP_ADDRESS, $time); + } + return false; +} + +/** + * Sets a configuration value. + */ +function csrf_conf($key, $val) { + if (!isset($GLOBALS['csrf'][$key])) { + trigger_error('No such configuration ' . $key, E_USER_WARNING); + return; + } + $GLOBALS['csrf'][$key] = $val; +} + +/** + * Starts a session if we're allowed to. + */ +function csrf_start() { + if ($GLOBALS['csrf']['auto-session'] && !session_id()) { + session_start(); + } +} + +/** + * Retrieves the secret, and generates one if necessary. + */ +function csrf_get_secret() { + if ($GLOBALS['csrf']['secret']) return $GLOBALS['csrf']['secret']; + $dir = dirname(__FILE__); + $file = $dir . '/csrf-secret.php'; + $secret = ''; + if (file_exists($file)) { + include $file; + return $secret; + } + if (is_writable($dir)) { + $secret = csrf_generate_secret(); + $fh = fopen($file, 'w'); + fwrite($fh, 'setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbConn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch(PDOException $ex ) { echo "Unable to connect to ZM db." . $ex->getMessage(); diff --git a/web/includes/functions.php b/web/includes/functions.php index 36a80cf2e..4937408e6 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -19,7 +19,7 @@ // // Compatibility functions -if ( version_compare( phpversion(), "4.3.0", "<") ) { +if ( version_compare( phpversion(), '4.3.0', '<') ) { function ob_get_clean() { $buffer = ob_get_contents(); ob_end_clean(); @@ -27,24 +27,24 @@ if ( version_compare( phpversion(), "4.3.0", "<") ) { } } -function userLogin( $username, $password="", $passwordHashed=false ) { +function userLogin( $username, $password='', $passwordHashed=false ) { global $user, $cookies; - $sql = "select * from Users where Enabled = 1"; + $sql = 'SELECT * FROM Users WHERE Enabled = 1'; $sql_values = NULL; - if ( ZM_AUTH_TYPE == "builtin" ) { + if ( ZM_AUTH_TYPE == 'builtin' ) { if ( $passwordHashed ) { - $sql .= " AND Username=? AND Password=?"; + $sql .= ' AND Username=? AND Password=?'; } else { - $sql .= " AND Username=? AND Password=password(?)"; + $sql .= ' AND Username=? AND Password=password(?)'; } $sql_values = array( $username, $password ); } else { - $sql .= " AND Username = ?"; + $sql .= ' AND Username = ?'; $sql_values = array( $username ); } $_SESSION['username'] = $username; - if ( ZM_AUTH_RELAY == "plain" ) { + if ( ZM_AUTH_RELAY == 'plain' ) { // Need to save this in session $_SESSION['password'] = $password; } @@ -56,6 +56,7 @@ function userLogin( $username, $password="", $passwordHashed=false ) { if ( ZM_AUTH_TYPE == "builtin" ) { $_SESSION['passwordHash'] = $user['Password']; } + session_regenerate_id(); } else { Warning( "Login denied for user \"$username\"" ); $_SESSION['loginFailed'] = true; @@ -67,10 +68,7 @@ function userLogin( $username, $password="", $passwordHashed=false ) { function userLogout() { global $user; - $username = $user['Username']; - - Info( "User \"$username\" logged out" ); - + Info( 'User "'.$user['Username'].'" logged out' ); unset( $_SESSION['user'] ); unset( $user ); @@ -95,12 +93,12 @@ function CORSHeaders() { # Only need CORSHeaders in the event that there are multiple servers in use. return; } - foreach( dbFetchAll( 'SELECT * FROM Servers' ) as $row ) { + foreach( $servers as $row ) { $Server = new Server( $row ); if ( $_SERVER['HTTP_ORIGIN'] == $Server->Url() ) { - $valid = true; header("Access-Control-Allow-Origin: " . $Server->Url() ); header("Access-Control-Allow-Headers: x-requested-with,x-request"); + $valid = true; } } if ( ! $valid ) { @@ -110,20 +108,27 @@ function CORSHeaders() { } function getAuthUser( $auth ) { - if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == "hashed" && !empty($auth) ) { - $remoteAddr = ""; + if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' && !empty($auth) ) { + $remoteAddr = ''; if ( ZM_AUTH_HASH_IPS ) { $remoteAddr = $_SERVER['REMOTE_ADDR']; if ( !$remoteAddr ) { Error( "Can't determine remote address for authentication, using empty string" ); - $remoteAddr = ""; + $remoteAddr = ''; } } - $sql = "select Username, Password, Enabled, Stream+0, Events+0, Control+0, Monitors+0, System+0, MonitorIds from Users where Enabled = 1"; + if ( $_SESSION['username'] ) { + # Most of the time we will be logged in already and the session will have our username, so we can significantly speed up our hash testing by only looking at our user. + # Only really important if you have a lot of users. + $sql = "SELECT * FROM Users WHERE Enabled = 1 AND Username='".$_SESSION['username']."'"; + } else { + $sql = 'SELECT * FROM Users WHERE Enabled = 1'; + } + foreach ( dbFetchAll( $sql ) as $user ) { $now = time(); - for ( $i = 0; $i < 2; $i++, $now -= (60*60) ) { // Try for last two hours + for ( $i = 0; $i < ZM_AUTH_HASH_TTL; $i++, $now -= (3600) ) { // Try for last two hours $time = localtime( $now ); $authKey = ZM_AUTH_HASH_SECRET.$user['Username'].$user['Password'].$remoteAddr.$time[2].$time[3].$time[4].$time[5]; $authHash = md5( $authKey ); @@ -131,24 +136,38 @@ function getAuthUser( $auth ) { if ( $auth == $authHash ) { return( $user ); } - } - } - } + } // end foreach hour + } // end foreach user + } // end if using auth hash Error( "Unable to authenticate user from auth hash '$auth'" ); return( false ); } function generateAuthHash( $useRemoteAddr ) { - if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == "hashed" ) { - $time = localtime(); - if ( $useRemoteAddr ) { - $authKey = ZM_AUTH_HASH_SECRET.$_SESSION['username'].$_SESSION['passwordHash'].$_SESSION['remoteAddr'].$time[2].$time[3].$time[4].$time[5]; + if ( ZM_OPT_USE_AUTH and ZM_AUTH_RELAY == 'hashed' and isset($_SESSION['username']) and $_SESSION['passwordHash'] ) { + # regenerate a hash at half the liftetime of a hash, an hour is 3600 so half is 1800 + if ( ( ! isset($_SESSION['AuthHash']) ) or ( $_SESSION['AuthHashGeneratedAt'] < time() - ( ZM_AUTH_HASH_TTL * 1800 ) ) ) { + # Don't both regenerating Auth Hash if an hour hasn't gone by yet + $time = localtime(); + $authKey = ''; + if ( $useRemoteAddr ) { + $authKey = ZM_AUTH_HASH_SECRET.$_SESSION['username'].$_SESSION['passwordHash'].$_SESSION['remoteAddr'].$time[2].$time[3].$time[4].$time[5]; + } else { + $authKey = ZM_AUTH_HASH_SECRET.$_SESSION['username'].$_SESSION['passwordHash'].$time[2].$time[3].$time[4].$time[5]; + } + $auth = md5( $authKey ); + if ( session_status() == PHP_SESSION_NONE ) { + Warning("Session is not active. AuthHash will not be cached."); + } + $_SESSION['AuthHash'] = $auth; + $_SESSION['AuthHashGeneratedAt'] = time(); + Debug("Generated new auth $auth at " . $_SESSION['AuthHashGeneratedAt']. " using $authKey" ); } else { - $authKey = ZM_AUTH_HASH_SECRET.$_SESSION['username'].$_SESSION['passwordHash'].$time[2].$time[3].$time[4].$time[5]; - } - $auth = md5( $authKey ); + Debug( "Using cached auth " . $_SESSION['AuthHash'] ); + } # end if AuthHash is not cached + return $_SESSION['AuthHash']; } else { - $auth = ""; + $auth = ''; } return( $auth ); } @@ -319,9 +338,9 @@ function outputImageStream( $id, $src, $width, $height, $title="" ) { function getImageStream( $id, $src, $width, $height, $title="" ) { if ( canStreamIframe() ) { - return '