Merge branch 'master' into zma_to_thread

This commit is contained in:
Isaac Connor 2020-05-02 18:03:42 -04:00
commit 4ff341a0f5
175 changed files with 5018 additions and 3602 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: [connortechnology,pliablepixels] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: zoneminder # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@ -13,7 +13,7 @@ addons:
ssh_known_hosts: zmrepo.zoneminder.com
apt:
sources:
- sourceline: ppa:iconnor/zoneminder
- sourceline: ppa:iconnor/zoneminder-master
- key_url: http://keyserver.ubuntu.com:11371/pks/lookup?op=get&search=0x4D0BF748776FFB04
packages:
- gdebi
@ -33,24 +33,23 @@ install:
env:
- SMPFLAGS=-j4 OS=el DIST=7
- SMPFLAGS=-j4 OS=el DIST=8
- SMPFLAGS=-j4 OS=fedora DIST=29 DOCKER_REPO=knnniggett/packpack
- SMPFLAGS=-j4 OS=el DIST=8 DOCKER_REPO=knnniggett/packpack
- SMPFLAGS=-j4 OS=fedora DIST=30
- SMPFLAGS=-j4 OS=ubuntu DIST=trusty DOCKER_REPO=iconzm/packpack USE_SFTP=yes
- SMPFLAGS=-j4 OS=ubuntu DIST=xenial DOCKER_REPO=iconzm/packpack USE_SFTP=yes
- SMPFLAGS=-j4 OS=ubuntu DIST=bionic DOCKER_REPO=iconzm/packpack USE_SFTP=yes
- SMPFLAGS=-j4 OS=ubuntu DIST=disco DOCKER_REPO=iconzm/packpack USE_SFTP=yes
- SMPFLAGS=-j4 OS=ubuntu DIST=eoan DOCKER_REPO=iconzm/packpack USE_SFTP=yes
- SMPFLAGS=-j4 OS=debian DIST=jessie DOCKER_REPO=iconzm/packpack USE_SFTP=yes
- SMPFLAGS=-j4 OS=debian DIST=stretch DOCKER_REPO=iconzm/packpack USE_SFTP=yes
- SMPFLAGS=-j4 OS=debian DIST=buster DOCKER_REPO=iconzm/packpack USE_SFTP=yes
- SMPFLAGS=-j4 OS=fedora DIST=31
- SMPFLAGS=-j4 OS=ubuntu DIST=trusty DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=ubuntu DIST=xenial DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=ubuntu DIST=bionic DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=ubuntu DIST=disco DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=ubuntu DIST=eoan DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=debian DIST=jessie DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=debian DIST=stretch DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=debian DIST=buster DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=ubuntu DIST=trusty ARCH=i386
- SMPFLAGS=-j4 OS=ubuntu DIST=xenial ARCH=i386
- SMPFLAGS=-j4 OS=ubuntu DIST=bionic ARCH=i386
- SMPFLAGS=-j4 OS=ubuntu DIST=disco ARCH=i386
- SMPFLAGS=-j4 OS=debian DIST=buster ARCH=i386
- SMPFLAGS=-j4 OS=debian DIST=stretch ARCH=i386
- SMPFLAGS=-j4 OS=raspbian DIST=stretch ARCH=armhf DOCKER_REPO=knnniggett/packpack
- SMPFLAGS=-j4 OS=eslint DIST=eslint
compiler:
- gcc
@ -58,12 +57,6 @@ services:
- mysql
- docker
jobs:
include:
- name: eslint
install: npm install -g eslint@5.12.0 eslint-config-google@0.11.0 eslint-plugin-html@5.0.0 eslint-plugin-php-markup@0.2.5
script: eslint --ext .php,.js .
script:
- utils/packpack/startpackpack.sh

View File

@ -140,9 +140,9 @@ set(ZM_TMPDIR "/var/tmp/zm" CACHE PATH
"Location of temporary files, default: /tmp/zm")
set(ZM_LOGDIR "/var/log/zm" CACHE PATH
"Location of generated log files, default: /var/log/zm")
set(ZM_WEBDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/zoneminder/www" CACHE PATH
set(ZM_WEBDIR "${CMAKE_INSTALL_FULL_DATADIR}/zoneminder/www" CACHE PATH
"Location of the web files, default: <prefix>/${CMAKE_INSTALL_DATADIR}/zoneminder/www")
set(ZM_CGIDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/cgi-bin" CACHE PATH
set(ZM_CGIDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/zoneminder/cgi-bin" CACHE PATH
"Location of the cgi-bin files, default: <prefix>/${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/cgi-bin")
set(ZM_CACHEDIR "/var/cache/zoneminder" CACHE PATH
"Location of the web server cache busting files, default: /var/cache/zoneminder")
@ -547,6 +547,7 @@ if(AVCODEC_LIBRARIES)
check_include_file("libavcodec/avcodec.h" HAVE_LIBAVCODEC_AVCODEC_H)
set(optlibsfound "${optlibsfound} AVCodec")
else(AVCODEC_LIBRARIES)
message(WARNING "\nWhile it should be possible to build ZM without AVCODEC the result will pretty useless.")
set(optlibsnotfound "${optlibsnotfound} AVCodec")
endif(AVCODEC_LIBRARIES)
@ -905,7 +906,7 @@ message(STATUS "Optional libraries not found:${optlibsnotfound}")
# Run ZM configuration generator
message(STATUS "Running ZoneMinder configuration generator")
execute_process(COMMAND perl ./zmconfgen.pl RESULT_VARIABLE zmconfgen_result)
execute_process(COMMAND perl ${CMAKE_CURRENT_BINARY_DIR}/zmconfgen.pl RESULT_VARIABLE zmconfgen_result)
if(zmconfgen_result EQUAL 0)
message(STATUS
"ZoneMinder configuration generator completed successfully")

View File

@ -1,9 +1,10 @@
ZoneMinder
==========
[![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)](https://travis-ci.org/ZoneMinder/zoneminder)
[![Bounty Source](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)
[![Join Slack](https://github.com/ozonesecurity/ozonebase/blob/master/img/slacksm.png?raw=true)](https://join.slack.com/t/zoneminder-chat/shared_invite/enQtNTU0NDkxMDM5NDQwLTdhZmQ5Y2M2NWQyN2JkYTBiN2ZkMzIzZGQ0MDliMTRmM2FjZWRlYzUwYTQ2MjMwMTVjMzQ1NjYxOTdmMjE2MTE)
[![IRC Network](https://img.shields.io/badge/irc-%23zoneminder-blue.svg "IRC Freenode")](https://webchat.freenode.net/?channels=zoneminder)
All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org

View File

@ -287,6 +287,9 @@ CREATE TABLE `Filters` (
`AutoVideo` tinyint(3) unsigned NOT NULL default '0',
`AutoUpload` tinyint(3) unsigned NOT NULL default '0',
`AutoEmail` tinyint(3) unsigned NOT NULL default '0',
`EmailTo` TEXT,
`EmailSubject` TEXT,
`EmailBody` TEXT,
`AutoMessage` tinyint(3) unsigned NOT NULL default '0',
`AutoExecute` tinyint(3) unsigned NOT NULL default '0',
`AutoExecuteCmd` tinytext,
@ -434,6 +437,7 @@ DROP TABLE IF EXISTS `Monitors`;
CREATE TABLE `Monitors` (
`Id` int(10) unsigned NOT NULL auto_increment,
`Name` varchar(64) NOT NULL default '',
`Notes` TEXT,
`ServerId` int(10) unsigned,
`StorageId` smallint(5) unsigned default 0,
`Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket') NOT NULL default 'Local',
@ -759,18 +763,91 @@ insert into Users VALUES (NULL,'admin','$2b$12$NHZsm6AM2f2LQVROriz79ul3D6DnmFiZC
-- Add a sample filter to purge the oldest 100 events when the disk is 95% full
--
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}',
INSERT INTO `Filters`
(
`Name`,
`Query_json`,
`AutoArchive`,
`AutoVideo`,
`AutoUpload`,
`AutoEmail`,
`EmailTo`,
`EmailSubject`,
`EmailBody`,
`AutoMessage`,
`AutoExecute`,
`AutoExecuteCmd`,
`AutoDelete`,
`AutoMove`,
`AutoMoveTo`,
`AutoCopy`,
`AutoCopyTo`,
`UpdateDiskSpace`,
`Background`,
`Concurrent`
)
VALUES
(
'PurgeWhenFull',
'{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}',
0/*AutoArchive*/,
0/*AutoVideo*/,
0/*AutoUpload*/,
0/*AutoEmail*/,
''/*EmailTo*/,
''/*EmailSubject*/,
''/*EmailBody*/,
0/*AutoMessage*/,
0/*AutoExecute*/,'',
1/*AutoDelete*/,
0/*AutoMove*/,0/*MoveTo*/,
0/*AutoCopy*/,0/*CopyTo*/,
0/*UpdateDiskSpace*/,1/*Background*/,0/*Concurrent*/);
insert into Filters values (NULL,'Update DiskSpace','{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"}]}',0,0,0,0,0,0,'',0,0,0,0,0,1,1,0);
0/*UpdateDiskSpace*/,
1/*Background*/,
0/*Concurrent*/
);
INSERT INTO `Filters`
(
`Name`,
`Query_json`,
`AutoArchive`,
`AutoVideo`,
`AutoUpload`,
`AutoEmail`,
`EmailTo`,
`EmailSubject`,
`EmailBody`,
`AutoMessage`,
`AutoExecute`,
`AutoExecuteCmd`,
`AutoDelete`,
`AutoMove`,
`AutoMoveTo`,
`AutoCopy`,
`AutoCopyTo`,
`UpdateDiskSpace`,
`Background`,
`Concurrent`
)
VALUES (
'Update DiskSpace',
'{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"}]}',
0/*AutoArchive*/,
0/*AutoVideo*/,
0/*AutoUpload*/,
0/*AutoEmail*/,
''/*EmailTo*/,
''/*EmailSubject*/,
''/*EmailBody*/,
0/*AutoMessage*/,
0/*AutoExecute*/,'',
0/*AutoDelete*/,
0/*AutoMove*/,0/*MoveTo*/,
0/*AutoCopy*/,0/*CopyTo*/,
1/*UpdateDiskSpace*/,
1/*Background*/,
0/*Concurrent*/
);
--
-- Add in some sample control protocol definitions
@ -808,6 +885,7 @@ INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-423','Ffmpeg','Reolink',0,0,1,0
INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-411','Ffmpeg','Reolink',0,0,1,0,1,0,0,0,1,0,0,1,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-420','Ffmpeg','Reolink',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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,0,1,0,0,0,0,0,0,0,0,0,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,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,'D-Link DCS-5020L','Remote','DCS5020L',1,1,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,0,1,24,1,0,1,1,1,0,1,0,1,0,0,1,30,0,0,0,0,0,1,0,0,1,30,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,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,'Dericam P2','Ffmpeg','DericamP2',0,1,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,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',1,1,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,0,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
@ -819,6 +897,9 @@ INSERT INTO `Controls` VALUES (NULL,'Amcrest HTTP API','Ffmpeg','Amcrest_HTTP',0
--
-- Add some monitor preset values
--
INSERT into MonitorPresets VALUES (NULL,'Amcrest, IP8M-T2499EW 640x480, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://<username>:<password>@<ip-address>/cam/realmonitor?channel=1&subtype=1',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,'Amcrest, IP8M-T2499EW 3840x2160, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://<username>:<password>@<ip-address>/cam/realmonitor?channel=1&subtype=0',NULL,3840,2160,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
@ -842,6 +923,7 @@ INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, multicast','Remote','rt
INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'D-Link DCS-5020L, 640x480, mjpeg','Remote','http',0,0,'http','simple','<username>:<pwd>@<ip-address>','80','/video.cgi',NULL,640,480,0,NULL,1,'34',NULL,'<username>:<pwd>@<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100);

12
db/zm_update-1.33.16.sql Normal file
View File

@ -0,0 +1,12 @@
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Monitors'
AND column_name = 'Notes'
) > 0,
"SELECT 'Column Notes already exists in Monitors'",
"ALTER TABLE `Monitors` ADD `Notes` TEXT AFTER `Name`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

5
db/zm_update-1.34.0.sql Normal file
View File

@ -0,0 +1,5 @@
--
-- This updates a 1.33.16 database to 1.34.0
--
-- No changes required
--

5
db/zm_update-1.34.1.sql Normal file
View File

@ -0,0 +1,5 @@
--
-- This updates a 1.34.0 database to 1.34.1
--
-- No changes required
--

5
db/zm_update-1.34.2.sql Normal file
View File

@ -0,0 +1,5 @@
--
-- This updates a 1.34.1 database to 1.34.2
--
-- No changes required
--

5
db/zm_update-1.34.3.sql Normal file
View File

@ -0,0 +1,5 @@
--
-- This updates a 1.34.2 database to 1.34.3
--
-- No changes required
--

5
db/zm_update-1.34.4.sql Normal file
View File

@ -0,0 +1,5 @@
--
-- This updates a 1.34.3 database to 1.34.4
--
-- No changes required
--

5
db/zm_update-1.34.5.sql Normal file
View File

@ -0,0 +1,5 @@
--
-- This updates a 1.34.4 database to 1.34.5
--
-- No changes required
--

41
db/zm_update-1.35.0.sql Normal file
View File

@ -0,0 +1,41 @@
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Filters'
AND column_name = 'EmailTo'
) > 0,
"SELECT 'Column EmailTo already exists in Filters'",
"ALTER TABLE `Filters` ADD `EmailTo` TEXT AFTER `AutoEmail`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
UPDATE Filters SET EmailTo=(SELECT Value FROM Config WHERE Name='ZM_EMAIL_ADDRESS') WHERE AutoEmail=1;
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Filters'
AND column_name = 'EmailSubject'
) > 0,
"SELECT 'Column EmailSubject already exists in Filters'",
"ALTER TABLE `Filters` ADD `EmailSubject` TEXT AFTER `EmailTo`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
UPDATE Filters SET EmailSubject=(SELECT Value FROM Config WHERE Name='ZM_EMAIL_SUBJECT') WHERE AutoEmail=1;
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Filters'
AND column_name = 'EmailBody'
) > 0,
"SELECT 'Column EmailBody already exists in Filters'",
"ALTER TABLE `Filters` ADD `EmailBody` TEXT AFTER `EmailSubject`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
UPDATE Filters SET EmailBody=(SELECT Value FROM Config WHERE Name='ZM_EMAIL_BODY') WHERE AutoEmail=1;

View File

@ -22,14 +22,10 @@ What's New
switching between httpd <-> nginx requires manaully changing ownership of
all event folders and the php session folder after the change.
4. If you have installed ZoneMinder from the FedBerry repositories, this build
of ZoneMinder has support for Raspberry Pi hardware acceleration when using
ffmpeg. Unforunately, there is a problem with the same hardware acceleration
when using libvlc. Consequently, libvlc support in this build of ZoneMinder
has been disabled until the problem is resolved. See the following bug
report for details: https://trac.videolan.org/vlc/ticket/18594
4. The timezone must now be set from the ZoneMinder web console. See the
appropriate README, mentioned in the next step, for details.
5. Continue on to the next README that corresponds to the chosen webserver:
6. Continue on to the next README that corresponds to your chosen webserver:
README.httpd - Follow these steps when using Apache
README.nginx - Follow these steps when using Nginx

View File

@ -36,20 +36,17 @@ NOTE: EL7 users should replace "dnf" with "yum" in the instructions below.
sudo chown root:apache *.conf
sudo chmod 640 *.conf
4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local
timezone. PHP will complain loudly if this is not set, or if it is set
incorrectly, and these complaints will show up in the zoneminder logging
system as errors.
4. Manually setting the timezone in /etc/php.ini is deprecated.
If you are not sure of the proper timezone specification to use, look at
http://php.net/date.timezone
Instead, navigate to Options -> System from the ZoneMinder web console.
Do this after completing step 10, below.
Note that timezone errors will appear in the ZoneMinder log until this
has been completed.
5. Disable SELinux
We currently do not have the resources to create and maintain an accurate
SELinux policy for ZoneMinder on Fedora. We will gladly accept pull
reqeusts from anyone who wishes to do the work. In the meantime, SELinux
will need to be disabled or put into permissive mode.
SELinux must be disabled or put into permissive mode. This is not optional!
To immediately disbale SELinux for the current seesion, issue the following
from the command line:
@ -166,3 +163,11 @@ Upgrades
sudo systemctl restart httpd
sudo systemctl start zoneminder
6. Manually setting the timezone in /etc/php.ini is deprecated.
Instead, navigate to Options -> System from the ZoneMinder web console.
Do this now.
Note that timezone errors will appear in the ZoneMinder log until this
has been completed.

View File

@ -34,13 +34,13 @@ New installs
sudo chown root:nginx *.conf
sudo chmod 640 *.conf
4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local
timezone. PHP will complain loudly if this is not set, or if it is set
incorrectly, and these complaints will show up in the zoneminder logging
system as errors.
4. Manually setting the timezone in /etc/php.ini is deprecated.
If you are not sure of the proper timezone specification to use, look at
http://php.net/date.timezone
Instead, navigate to Options -> System from the ZoneMinder web console.
Do this after completing step 10, below.
Note that timezone errors will appear in the ZoneMinder log until this
has been completed.
5. Disable SELinux
@ -169,3 +169,11 @@ Upgrades
sudo systemctl restart php-fpm
sudo systemctl start zoneminder
6. Manually setting the timezone in /etc/php.ini is deprecated.
Instead, navigate to Options -> System from the ZoneMinder web console.
Do this now.
Note that timezone errors will appear in the ZoneMinder log until this
has been completed.

View File

@ -14,16 +14,21 @@
# This will tell zoneminder's cmake process we are building against a known distro
%global zmtargetdistro %{?rhel:el%{rhel}}%{!?rhel:fc%{fedora}}
# Fedora >= 25 needs apcu backwards compatibility module
%if 0%{?fedora} >= 25
# Fedora needs apcu backwards compatibility module
%if 0%{?fedora}
%global with_apcu_bc 1
%endif
# Newer php's keep json functions in a subpackage
%if 0%{?fedora} || 0%{?rhel} >= 8
%global with_php_json 1
%endif
# The default for everything but el7 these days
%global _hardened_build 1
Name: zoneminder
Version: 1.33.14
Version: 1.35.0
Release: 1%{?dist}
Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons
@ -105,7 +110,7 @@ Summary: Common files for ZoneMinder, not tied to a specific web server
Requires: php-mysqli
Requires: php-common
Requires: php-gd
%{?fedora:Requires: php-json}
%{?with_php_json:Requires: php-json}
Requires: php-pecl-apcu
%{?with_apcu_bc:Requires: php-pecl-apcu-bc}
Requires: cambozola
@ -411,26 +416,26 @@ EOF
%dir %attr(755,nginx,nginx) %{_localstatedir}/spool/zoneminder-upload
%changelog
* Sun Aug 11 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.14-1
- Bump to 1.33.13 Development
* Tue Feb 04 2020 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.34.2-1
- 1.34.2 Release
* Sun Jul 07 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.12-1
- Bump to 1.33.12 Development
* Fri Jan 31 2020 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.34.1-1
- 1.34.1 Release
* Sun Jun 23 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.9-1
- Bump to 1.33.9 Development
* Sat Jan 18 2020 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.34.0-1
- 1.34.0 Release
* Tue Apr 30 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.8-1
- Bump to 1.33.8 Development
* Tue Dec 17 2019 Leigh Scott <leigh123linux@gmail.com> - 1.32.3-5
- Mass rebuild for x264
* Sun Apr 07 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.6-1
- Bump to 1.33.6 Development
* Wed Aug 07 2019 Leigh Scott <leigh123linux@gmail.com> - 1.32.3-4
- Rebuild for new ffmpeg version
* Sat Mar 30 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.4-1
- Bump to 1.33.4 Development
* Tue Mar 12 2019 Sérgio Basto <sergio@serjux.com> - 1.32.3-3
- Mass rebuild for x264
* Tue Dec 11 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.0-1
- Bump to 1.33.0 Development
* Tue Mar 05 2019 RPM Fusion Release Engineering <leigh123linux@gmail.com> - 1.32.3-2
- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
* Sat Dec 08 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.32.3-1
- 1.32.3 Release

View File

@ -6,6 +6,7 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin"
Require all granted
</Directory>
# Order matters. This alias must come first.
Alias /zm/cache /var/cache/zoneminder/cache
<Directory /var/cache/zoneminder/cache>

View File

@ -61,7 +61,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,libjson-maybexs-perl
,libsys-mmap-perl [!hurd-any]
,liburi-encode-perl
,libwww-perl
,libwww-perl, liburi-perl
,libdata-dump-perl
,libdatetime-perl
,libclass-std-fast-perl
@ -74,7 +74,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,libfile-slurp-perl
,mysql-client | mariadb-client | virtual-mysql-client
,perl-modules
,php5-mysql | php-mysql, php5-gd | php-gd , php5-apcu | php-apcu , php-apc | php-apcu-bc
,php5-mysql | php-mysql, php5-gd | php-gd , php5-apcu | php-apcu , php-apc | php-apcu-bc, php-json | php5-json
,policykit-1
,rsyslog | system-log-daemon
,zip

View File

@ -127,7 +127,7 @@ If you are using the old credentials mechanism present in v1.0, then the credent
Key lifetime (v2.0)
^^^^^^^^^^^^^^^^^^^^^^
In version 2.0, it is easy to know when a key will expire before you use it. You can find that out from the ``access_token_expires`` and ``refresh_token_exipres`` values (in seconds) after you decode the JWT key (there are JWT decode libraries for every language you want). You should refresh the keys before the timeout occurs, or you will not be able to use the APIs.
In version 2.0, it is easy to know when a key will expire before you use it. You can find that out from the ``access_token_expires`` and ``refresh_token_expires`` values (in seconds) after you decode the JWT key (there are JWT decode libraries for every language you want). You should refresh the keys before the timeout occurs, or you will not be able to use the APIs.
Understanding access/refresh tokens (v2.0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -482,6 +482,7 @@ Create a Zone
&Zone[Units]=Percent\
&Zone[NumCoords]=4\
&Zone[Coords]=0,0 639,0 639,479 0,479\
&Zone[Area]=307200\
&Zone[AlarmRGB]=16711680\
&Zone[CheckMethod]=Blobs\
&Zone[MinPixelThreshold]=25\

View File

@ -68,9 +68,13 @@ Here is what the filter window looks like
* %ESM% Maximum score of the event
* %EP% Path to the event
* %EPS% Path to the event stream
* %EPF1% Path to the frame view for the first alarmed event image
* %EPFM% Path to the frame view for the (first) event image with the highest score
* %EFMOD% Path to image containing object detection, in frame view
* %EPI% Path to the event images
* %EPI1% Path to the first alarmed event image
* %EPIM% Path to the (first) event image with the highest score
* %EPI1% Path to the first alarmed event image, suitable for use in img tags
* %EPIM% Path to the (first) event image with the highest score, suitable for use in img tags
* %EIMOD% Path to image containing object detection, suitable for use in img tags
* %EI1% Attach first alarmed event image
* %EIM% Attach (first) event image with the highest score
* %EV% Attach event mpeg video
@ -81,7 +85,6 @@ Here is what the filter window looks like
* %MEW% Number of events for the monitor in the last week
* %MEM% Number of events for the monitor in the last month
* %MEA% Number of archived events for the monitor
* %MOD% Path to image containing object detection
* %MP% Path to the monitor window
* %MPS% Path to the monitor stream
* %MPI% Path to the monitor recent image

View File

@ -109,7 +109,7 @@ This brings up the new monitor window:
* In this example, the Function is 'Modect', which means it will start recording if motion is detected on that camera feed. The parameters for what constitutes motion detected is specific in :doc:`definezone`
* In Analytis FPS, we've put in 5FPS here. Note that you should not put an FPS that is greater than the camera FPS. In my case, 5FPS is sufficient for my needs
* In Analysis FPS, we've put in 5FPS here. Note that you should not put an FPS that is greater than the camera FPS. In my case, 5FPS is sufficient for my needs
.. note::
Leave Maximum FPS and Alarm Maximum FPS **empty** if you are configuring an IP camera. In older versions of ZoneMinder, you were encouraged to put a value here, but that is no longer recommended. Infact, if you see your feed going much slower than the feed is supposed to go, or you get a lot of buffering/display issues, make sure this is empty. If you need to control camera FPS, please do it directly on the camera (via its own web interface, for example)
@ -123,6 +123,8 @@ This brings up the new monitor window:
* Let's select a protocol of RTSP and a remote method of RTP/RTSP (this is an RTSP camera)
* Note that starting ZM 1.34, GPUs are supported. In my case, I have an NVIDIA GeForce GTX1050i. These ``cuda`` and ``cuvid`` parameters are what my system supports to use the NVIDIA hardware decoder and GPU resources. If you don't have a GPU, or don't know how to configure your ffmpeg to support it, leave it empty for now. In future, we will add a section on how to set up a GPU
**NOTE**: It is entirely possible that ``cuda`` and ``cuvid`` don't work for you and you need different values. Isaac uses ``cuda`` in ``DecoderHWAccelName`` and leaves ``DecoderHWAccelDevice`` empty. Try that too.
.. todo::
add GPU docs

View File

@ -28,3 +28,4 @@ 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:]]+"\]$
zmeventnotification\[[[:digit:]]+\]: INF

View File

@ -114,7 +114,7 @@ sub get_service_urls {
my $result = $self->service('device', 'ep')->GetServices( {
IncludeCapability => 'true', # boolean
},,
}
);
if ( $result ) {
foreach my $svc ( @{ $result->get_Service() } ) {
@ -142,7 +142,7 @@ sub get_service_urls {
if ( my $function = $capabilities->can( "get_$capability" ) ) {
my $Services = $function->( $capabilities );
if ( !$Services ) {
print "Nothing returned ffrom get_$capability\n";
#print "Nothing returned from get_$capability\n";
} else {
foreach my $svc ( @{ $Services } ) {
# The capability versions don't have a namespace, so just lowercase them.
@ -202,7 +202,7 @@ sub BUILD {
# deserializer_args => { strict => 0 }
});
$services_of{$ident}{'device'} = { url => $url_svc_device, ep => $svc_device };
$services_of{$ident}{device} = { url => $url_svc_device, ep => $svc_device };
# Can't, don't have credentials yet
# $self->get_service_urls();
@ -260,7 +260,7 @@ sub set_credentials {
sub create_services {
my ($self) = @_;
#$self->get_service_urls();
$self->get_service_urls();
if ( defined $self->service('media', 'url') ) {
$self->set_service('media', 'ep', ONVIF::Media::Interfaces::Media::MediaPort->new({

View File

@ -43,7 +43,7 @@ my %message_of :ATTR(:name<message> :default<()>);
my %is_success_of :ATTR(:name<is_success> :default<()>);
my %local_addr_of :ATTR(:name<local_addr> :init_arg<local_addr> :default<()>);
my $net_interface;
# create methods normally inherited from SOAP::Client
SUBFACTORY: {
@ -60,14 +60,22 @@ sub _notify_response
}
sub set_net_interface {
my $self = shift;
$net_interface = shift;
}
sub send_multi() {
my ($self, $address, $port, $utf8_string) = @_;
my $destination = $address . ':' . $port;
my $socket = IO::Socket::Multicast->new(PROTO => 'udp',
LocalPort=>$port, PeerAddr=>$destination, ReuseAddr=>1)
or die 'Cannot open multicast socket to ' . ${address} . ':' . ${port};
my $socket = IO::Socket::Multicast->new(
PROTO => 'udp',
LocalPort=>$port,
PeerAddr=>$destination,
ReuseAddr=>1
) or die 'Cannot open multicast socket to ' . ${address} . ':' . ${port};
$_ = $socket->mcast_if($net_interface) if $net_interface;
my $bytes = $utf8_string;
utf8::encode($bytes);
@ -80,9 +88,11 @@ sub receive_multi() {
my ($self, $address, $port) = @_;
my $data = undef;
my $socket = IO::Socket::Multicast->new(PROTO => 'udp',
LocalPort=>$port, ReuseAddr=>1);
$socket->mcast_add($address);
my $socket = IO::Socket::Multicast->new(
PROTO => 'udp',
LocalPort=>$port,
ReuseAddr=>1);
$socket->mcast_add($address, $net_interface);
my $readbits = '';
vec($readbits, $socket->fileno, 1) = 1;
@ -98,10 +108,14 @@ sub receive_uni() {
my ($self, $address, $port, $localaddr) = @_;
my $data = undef;
my $socket = IO::Socket::Multicast->new(PROTO => 'udp',
LocalAddr => $localaddr, LocalPort=>$port, ReuseAddr=>1);
my $socket = IO::Socket::Multicast->new(
PROTO => 'udp',
LocalAddr => $localaddr,
LocalPort=>$port,
ReuseAddr=>1
);
$socket->mcast_add($address);
$socket->mcast_add($address, $net_interface);
my $readbits = '';
vec($readbits, $socket->fileno, 1) = 1;
@ -126,6 +140,7 @@ sub send_receive {
$self->send_multi($address, $port, $envelope);
my $localaddr = $self->get_local_addr();
#warn "localddr $localaddr";
my ($response, $last_response);
my $wait = WAIT_COUNT;
@ -146,13 +161,12 @@ sub send_receive {
if ( $last_response ) {
$self->set_code();
$self->set_message("");
$self->set_message('');
$self->set_is_success(1);
$self->set_status('OK');
}
else{
} else {
$self->set_code();
$self->set_message("Timed out waiting for response");
$self->set_message('Timed out waiting for response');
$self->set_is_success(0);
$self->set_status('TIMEOUT');
}
@ -161,3 +175,4 @@ sub send_receive {
}
1;
__END__

View File

@ -785,7 +785,7 @@ our @options = (
},
{
name => 'ZM_TIMEZONE',
default => 'UTC',
default => '',
description => 'The timezone that php should use.',
help => q`
This should be set equal to the system timezone of the mysql server`,
@ -1127,7 +1127,7 @@ our @options = (
},
{
name => 'ZM_LOG_LEVEL_FILE',
default => '-5',
default => '1',
description => 'Save logging output to component files',
help => q`
ZoneMinder logging is now more integrated between
@ -1312,7 +1312,7 @@ our @options = (
},
{
name => 'ZM_LOG_DEBUG_FILE',
default => '@ZM_LOGDIR@/zm_debug.log+',
default => '',
description => 'Where extra debug is output to',
help => q`
This option allows you to specify a different target for debug
@ -2213,7 +2213,7 @@ our @options = (
that match the appropriate filters will be sent to.
`,
type => $types{email},
category => 'mail',
category => 'hidden',
},
{
name => 'ZM_EMAIL_TEXT',
@ -2253,7 +2253,7 @@ our @options = (
sent for any events that match the appropriate filters.
`,
type => $types{string},
category => 'mail',
category => 'hidden',
},
{
name => 'ZM_EMAIL_BODY',
@ -2280,7 +2280,7 @@ our @options = (
sent for any events that match the appropriate filters.
`,
type => $types{text},
category => 'mail',
category => 'hidden',
},
{
name => 'ZM_OPT_MESSAGE',
@ -2468,7 +2468,7 @@ our @options = (
},
{
name => 'ZM_WATCH_MAX_DELAY',
default => '5',
default => '45',
description => 'The maximum delay allowed since the last captured image',
help => q`
The zmwatch daemon checks the image capture performance of the

View File

@ -1,6 +1,6 @@
# ==========================================================================
#
# ZoneMinder Base Control Module, $Date$, $Revision$
# ZoneMinder Base Control Module
# Copyright (C) 2001-2008 Philip Coombes
#
# This program is free software; you can redistribute it and/or
@ -46,11 +46,11 @@ our $AUTOLOAD;
sub new {
my $class = shift;
my $id = shift;
if ( !defined($id) ) {
Fatal('No monitor defined when invoking protocol '.$class);
}
my $self = {};
$self->{name} = $class;
if ( !defined($id) ) {
Fatal('No monitor defined when invoking protocol '.$self->{name});
}
$self->{id} = $id;
bless($self, $class);
return $self;
@ -78,7 +78,7 @@ sub AUTOLOAD {
sub getKey {
my $self = shift;
return( $self->{id} );
return $self->{id};
}
sub open {

View File

@ -1,16 +1,6 @@
# ==========================================================================
#
# ZoneMinder Acrest HTTP API Control Protocol Module, 20180214, Rev 3.0
#
# Change Log
#
# Rev 3.0:
# - Fixes incorrect method names
# - Updates control sequences to Amcrest HTTP Protocol API v 2.12
# - Extends control features
#
# Rev 2.0:
# - Fixed installation instructions text, no changes to functionality.
# ZoneMinder Amcrest HTTP API Control Protocol Module
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@ -38,6 +28,8 @@ use Time::HiRes qw( usleep );
require ZoneMinder::Base;
require ZoneMinder::Control;
require LWP::UserAgent;
use URI;
our @ISA = qw(ZoneMinder::Control);
@ -50,130 +42,130 @@ our @ISA = qw(ZoneMinder::Control);
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
sub new
{
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/.*://;
Debug( "Received command: $name" );
if ( exists($self->{$name}) )
{
return( $self->{$name} );
}
Fatal( "Can't access $name member of object of class $class" );
}
sub open
{
sub open {
my $self = shift;
$self->loadMonitor();
$self->{state} = 'open';
if ( $self->{Monitor}->{ControlAddress} !~ /^\w+:\/\// ) {
# Has no scheme at the beginning, so won't parse as a URI
$self->{Monitor}->{ControlAddress} = 'http://'.$self->{Monitor}->{ControlAddress};
}
my $uri = URI->new($self->{Monitor}->{ControlAddress});
sub initUA
{
my $self = shift;
my $user = undef;
my $password = undef;
my $address = undef;
if ( $self->{Monitor}->{ControlAddress} =~ /(.*):(.*)@(.*)/ )
{
$user = $1;
$password = $2;
$address = $3;
}
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->credentials("$address", "Login to " . $self->{Monitor}->{ControlDevice}, "$user", "$password");
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION );
$self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION);
my ( $username, $password );
my $realm = 'Login to ' . $self->{Monitor}->{ControlDevice};
if ( $self->{Monitor}->{ControlAddress} ) {
( $username, $password ) = $uri->authority() =~ /^(.*):(.*)@(.*)$/;
$$self{address} = $uri->host_port();
$self->{ua}->credentials($uri->host_port(), $realm, $username, $password);
# Testing seems to show that we need the username/password in each url as well as credentials
$$self{base_url} = $uri->canonical();
Debug('Using initial credentials for '.$uri->host_port().", $realm, $username, $password, base_url: $$self{base_url} auth:".$uri->authority());
}
sub destroyUA
{
my $self = shift;
# Detect REALM, has to be /cgi-bin/ptz.cgi because just / accepts no auth
my $res = $self->{ua}->get($$self{base_url}.'cgi-bin/ptz.cgi');
$self->{ua} = undef;
if ( $res->is_success ) {
$self->{state} = 'open';
return;
}
sub close
{
if ( $res->status_line() eq '401 Unauthorized' ) {
my $headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
}
if ( $$headers{'www-authenticate'} ) {
my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
if ( $tokens =~ /realm="([^"]+)"/i ) {
if ( $realm ne $1 ) {
$realm = $1;
Debug("Changing REALM to ($realm)");
$self->{ua}->credentials($$self{address}, $realm, $username, $password);
$res = $self->{ua}->get($$self{base_url}.'cgi-bin/ptz.cgi');
if ( $res->is_success() ) {
$self->{state} = 'open';
return;
} elsif ( $res->status_line eq '400 Bad Request' ) {
# In testing, this second request fails with Bad Request, I assume because we didn't actually give it a command.
$self->{state} = 'open';
return;
} else {
Error('Authentication still failed after updating REALM' . $res->status_line);
$headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Header $k => $$headers{$k}");
} # end foreach
}
} else {
Error('Authentication failed, not a REALM problem');
}
} else {
Error('Failed to match realm in tokens');
} # end if
} else {
Debug('No headers line');
} # end if headers
} else {
Error("Failed to get $$self{base_url}cgi-bin/ptz.cgi ".$res->status_line());
} # end if $res->status_line() eq '401 Unauthorized'
$self->{state} = 'closed';
}
sub close {
my $self = shift;
$self->{state} = 'closed';
}
sub printMsg
{
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendCmd
{
sub sendCmd {
my $self = shift;
my $cmd = shift;
my $result = undef;
destroyUA($self);
initUA($self);
$self->printMsg($cmd, 'Tx');
my $user = undef;
my $password = undef;
my $address = undef;
my $res = $self->{ua}->get($$self{base_url}.$cmd);
if ( $self->{Monitor}->{ControlAddress} =~ /(.*):(.*)@(.*)/ )
{
$user = $1;
$password = $2;
$address = $3;
}
printMsg( $cmd, "Tx" );
my $req = HTTP::Request->new( GET=>"http://$address/$cmd" );
my $res = $self->{ua}->request($req);
if ( $res->is_success )
{
if ( $res->is_success ) {
$result = !undef;
# Command to camera appears successful, write Info item to log
Info( "Camera control: '".$res->status_line()."' for URL ".$self->{Monitor}->{ControlAddress}."/$cmd" );
Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd);
# TODO: Add code to retrieve $res->message_decode or some such. Then we could do things like check the camera status.
} else {
# Try again
$res = $self->{ua}->get($$self{base_url}.$cmd);
if ( $res->is_success ) {
# Command to camera appears successful, write Info item to log
Info('Camera control 2: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd);
} else {
Error('Camera control command FAILED: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd);
$res = $self->{ua}->get('http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd);
}
else
{
Error( "Camera control command FAILED: '".$res->status_line()."' for URL ".$self->{Monitor}->{ControlAddress}."/$cmd" );
}
return( $result );
return $result;
}
sub reset
{
sub reset {
my $self = shift;
# This reboots the camera effectively resetting it
Debug( "Camera Reset" );
$self->sendCmd('cgi-bin/magicBox.cgi?action=reboot');
##FIXME: Exit is a bad idea as it appears to cause zmc to run away.
#Exit (0);
}
# NOTE: I'm putting this in, but absolute camera movement does not seem to be well supported in the classic skin ATM.
@ -188,79 +180,71 @@ sub moveAbs ## Up, Down, Left, Right, etc. ??? Doesn't make sense here...
my $pan_degrees = shift || 0;
my $tilt_degrees = shift || 0;
my $speed = shift || 1;
Debug( "Move ABS" );
Debug('Move ABS');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1='.$pan_degrees.'&arg2='.$tilt_degrees.'&arg3=0&arg4='.$speed);
}
sub moveConUp
{
sub moveConUp {
my $self = shift;
Debug( "Move Up" );
Debug('Move Up');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Up&channel=0&arg1=0&arg2=1&arg3=0');
usleep(500); ##XXX Should this be passed in as a "speed" parameter?
$self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Up&channel=0&arg1=0&arg2=1&arg3=0');
}
sub moveConDown
{
sub moveConDown {
my $self = shift;
Debug( "Move Down" );
Debug('Move Down');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Down&channel=0&arg1=0&arg2=1&arg3=0');
usleep(500);
$self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Down&channel=0&arg1=0&arg2=1&arg3=0');
}
sub moveConLeft
{
sub moveConLeft {
my $self = shift;
Debug( "Move Left" );
Debug('Move Left');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Left&channel=0&arg1=0&arg2=1&arg3=0');
usleep(500);
$self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Left&channel=0&arg1=0&arg2=1&arg3=0');
}
sub moveConRight
{
sub moveConRight {
my $self = shift;
Debug( "Move Right" );
Debug('Move Right');
# $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1=270&arg2=5&arg3=0' );
$self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Right&channel=0&arg1=0&arg2=1&arg3=0');
usleep(500);
Debug( "Move Right Stop" );
Debug('Move Right Stop');
$self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Right&channel=0&arg1=0&arg2=1&arg3=0');
}
sub moveConUpRight
{
sub moveConUpRight {
my $self = shift;
Debug( "Move Diagonally Up Right" );
Debug('Move Diagonally Up Right');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&code=RightUp&channel=0&arg1=1&arg2=1&arg3=0');
usleep(500);
$self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=RightUp&channel=0&arg1=0&arg2=1&arg3=0');
}
sub moveConDownRight
{
sub moveConDownRight {
my $self = shift;
Debug( "Move Diagonally Down Right" );
Debug('Move Diagonally Down Right');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&code=RightDown&channel=0&arg1=1&arg2=1&arg3=0');
usleep(500);
$self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=RightDown&channel=0&arg1=0&arg2=1&arg3=0');
}
sub moveConUpLeft
{
sub moveConUpLeft {
my $self = shift;
Debug( "Move Diagonally Up Left" );
Debug('Move Diagonally Up Left');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&code=LeftUp&channel=0&arg1=1&arg2=1&arg3=0');
usleep(500);
$self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=LeftUp&channel=0&arg1=0&arg2=1&arg3=0');
}
sub moveConDownLeft
{
sub moveConDownLeft {
my $self = shift;
Debug( "Move Diagonally Down Left" );
Debug('Move Diagonally Down Left');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&code=LeftDown&channel=0&arg1=1&arg2=1&arg3=0');
usleep (500);
$self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=LeftDown&channel=0&arg1=0&arg2=1&arg3=0');
@ -270,10 +254,9 @@ sub moveConDownLeft
# So we'll just send the camera to 0* Horz, 0* Vert, zoom out; Also, Amcrest does not seem to
# support a generic stop-all-current-action command.
sub moveStop
{
sub moveStop {
my $self = shift;
Debug( "Move Stop/Center" );
Debug('Move Stop/Center');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1=0&arg2=0&arg3=0&arg4=1');
}
@ -281,15 +264,13 @@ sub moveStop
# The current API does not support a Home per se, so we'll just send the camera to preset #1
# NOTE: It goes without saying that the user must have set up preset #1 for this to work.
sub presetHome
{
sub presetHome {
my $self = shift;
Debug( "Home Preset" );
Debug('Home Preset');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=GotoPreset&&arg1=0&arg2=1&arg3=0&arg4=0');
}
sub presetGoto
{
sub presetGoto {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
@ -297,19 +278,17 @@ sub presetGoto
$self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=GotoPreset&&arg1=0&arg2='.$preset.'&arg3=0&arg4=0');
}
sub presetSet
{
sub presetSet {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
Debug( "Set Preset" );
Debug('Set Preset');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=SetPreset&arg1=0&arg2='.$preset.'&arg3=0&arg4=0');
}
# NOTE: This does not appear to be implemented in the classic skin. But we'll leave it here for later.
sub moveMap
{
sub moveMap {
my $self = shift;
my $params = shift;
@ -325,19 +304,17 @@ sub moveMap
$self->sendCmd('cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1='.$pan.'&arg2='.$tilt.'&arg3=1&arg4=1');
}
sub zoomConTele
{
sub zoomConTele {
my $self = shift;
Debug( "Zoom continuous tele" );
Debug('Zoom continuous tele');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=ZoomTele&arg1=0&arg2=0&arg3=0&arg4=0');
usleep(100000);
$self->sendCmd('cgi-bin/ptz.cgi?action=stop&channel=0&code=ZoomTele&arg1=0&arg2=0&arg3=0&arg4=0');
}
sub zoomConWide
{
sub zoomConWide {
my $self = shift;
Debug( "Zoom continuous wide" );
Debug('Zoom continuous wide');
$self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=ZoomWide&arg1=0&arg2=0&arg3=0&arg4=0');
usleep (100000);
$self->sendCmd('cgi-bin/ptz.cgi?action=stop&channel=0&code=ZoomWide&arg1=0&arg2=0&arg3=0&arg4=0');

View File

@ -1,6 +1,6 @@
# ==========================================================================
#
# ZoneMinder Axis version 2 API Control Protocol Module, $Date$, $Revision$
# ZoneMinder Axis version 2 API Control Protocol Module
# Copyright (C) 2001-2008 Philip Coombes
#
# This program is free software; you can redistribute it and/or
@ -43,179 +43,206 @@ use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use Time::HiRes qw( usleep );
use URI;
sub open
{
our $ADDRESS;
sub open {
my $self = shift;
$self->loadMonitor();
if ( $self->{Monitor}->{ControlAddress} !~ /^\w+:\/\// ) {
# Has no scheme at the beginning, so won't parse as a URI
$self->{Monitor}->{ControlAddress} = 'http://'.$self->{Monitor}->{ControlAddress};
}
my $uri = URI->new($self->{Monitor}->{ControlAddress});
$ADDRESS = $uri->scheme.'://'.$uri->authority().$uri->path().($uri->port()?':'.$uri->port():'');
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION );
$self->{ua}->cookie_jar( {} );
$self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION);
$self->{state} = 'closed';
my ( $username, $password, $host ) = ( $uri->authority() =~ /^([^:]+):([^@]*)@(.+)$/ );
my $realm = $self->{Monitor}->{ControlDevice};
$self->{ua}->credentials($ADDRESS, $realm, $username, $password);
# test auth
my $res = $self->{ua}->get($ADDRESS.'/cgi/ptdc.cgi');
if ( $res->is_success ) {
$self->{state} = 'open';
return;
}
sub printMsg
{
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
if ( $res->status_line() eq '401 Unauthorized' ) {
Debug( $msg."[".$msg_len."]" );
my $headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
}
sub sendCmd
{
if ( $$headers{'www-authenticate'} ) {
Debug('Authenticating');
my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
if ( $tokens =~ /\w+="([^"]+)"/i ) {
if ( $realm ne $1 ) {
$realm = $1;
Debug("Changing REALM to $realm");
$self->{ua}->credentials($host, $realm, $username, $password);
$res = $self->{ua}->get($ADDRESS);
if ( $res->is_success() ) {
$self->{state} = 'open';
return;
}
Error('Authentication still failed after updating REALM'.$res->status_line);
$headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
} # end foreach
} else {
Error('Authentication failed, not a REALM problem');
}
} else {
Error('Failed to match realm in tokens');
} # end if
} else {
Debug('No headers line');
} # end if headers
} # end if $res->status_line() eq '401 Unauthorized'
} # end sub open
sub sendCmd {
my $self = shift;
my $cmd = shift;
my $result = undef;
$self->printMsg($cmd, 'Tx');
printMsg( $cmd, "Tx" );
my $url = $ADDRESS.$cmd;
my $res = $self->{ua}->get($url);
#print( "http://$address/$cmd\n" );
my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" );
my $res = $self->{ua}->request($req);
if ( $res->is_success )
{
$result = !undef;
}
else
{
Error( "Error check failed: '".$res->status_line()."'" );
if ( $res->is_success ) {
Debug('sndCmd command: ' . $url . ' content: '.$res->content);
return !undef;
}
return( $result );
Error("Error cmd $url failed: '".$res->status_line()."'");
return undef;
}
sub cameraReset
{
sub cameraReset {
my $self = shift;
Debug( "Camera Reset" );
my $cmd = "/axis-cgi/admin/restart.cgi";
Debug('Camera Reset');
my $cmd = '/axis-cgi/admin/restart.cgi';
$self->sendCmd($cmd);
}
sub moveConUp
{
sub moveConUp {
my $self = shift;
Debug( "Move Up" );
my $cmd = "/axis-cgi/com/ptz.cgi?move=up";
Debug('Move Up');
my $cmd = '/axis-cgi/com/ptz.cgi?move=up';
$self->sendCmd($cmd);
}
sub moveConDown
{
sub moveConDown {
my $self = shift;
Debug( "Move Down" );
my $cmd = "/axis-cgi/com/ptz.cgi?move=down";
Debug('Move Down');
my $cmd = '/axis-cgi/com/ptz.cgi?move=down';
$self->sendCmd($cmd);
}
sub moveConLeft
{
sub moveConLeft {
my $self = shift;
Debug( "Move Left" );
my $cmd = "/axis-cgi/com/ptz.cgi?move=left";
Debug('Move Left');
my $cmd = '/axis-cgi/com/ptz.cgi?move=left';
$self->sendCmd($cmd);
}
sub moveConRight
{
sub moveConRight {
my $self = shift;
Debug( "Move Right" );
my $cmd = "/axis-cgi/com/ptz.cgi?move=right";
Debug('Move Right');
my $cmd = '/axis-cgi/com/ptz.cgi?move=right';
$self->sendCmd($cmd);
}
sub moveConUpRight
{
sub moveConUpRight {
my $self = shift;
Debug( "Move Up/Right" );
my $cmd = "/axis-cgi/com/ptz.cgi?move=upright";
Debug('Move Up/Right');
my $cmd = '/axis-cgi/com/ptz.cgi?move=upright';
$self->sendCmd($cmd);
}
sub moveConUpLeft
{
sub moveConUpLeft {
my $self = shift;
Debug( "Move Up/Left" );
my $cmd = "/axis-cgi/com/ptz.cgi?move=upleft";
Debug('Move Up/Left');
my $cmd = '/axis-cgi/com/ptz.cgi?move=upleft';
$self->sendCmd($cmd);
}
sub moveConDownRight
{
sub moveConDownRight {
my $self = shift;
Debug( "Move Down/Right" );
my $cmd = "/axis-cgi/com/ptz.cgi?move=downright";
Debug('Move Down/Right');
my $cmd = '/axis-cgi/com/ptz.cgi?move=downright';
$self->sendCmd( $cmd );
}
sub moveConDownLeft
{
sub moveConDownLeft {
my $self = shift;
Debug( "Move Down/Left" );
my $cmd = "/axis-cgi/com/ptz.cgi?move=downleft";
Debug('Move Down/Left');
my $cmd = '/axis-cgi/com/ptz.cgi?move=downleft';
$self->sendCmd($cmd);
}
sub moveMap
{
sub moveMap {
my $self = shift;
my $params = shift;
my $xcoord = $self->getParam($params, 'xcoord');
my $ycoord = $self->getParam($params, 'ycoord');
Debug("Move Map to $xcoord,$ycoord");
my $cmd = "/axis-cgi/com/ptz.cgi?center=$xcoord,$ycoord&imagewidth=".$self->{Monitor}->{Width}."&imageheight=".$self->{Monitor}->{Height};
my $cmd = "/axis-cgi/com/ptz.cgi?center=$xcoord,$ycoord&imagewidth=".$self->{Monitor}->{Width}.'&imageheight='.$self->{Monitor}->{Height};
$self->sendCmd($cmd);
}
sub moveRelUp
{
sub moveRelUp {
my $self = shift;
my $params = shift;
my $step = $self->getParam($params, 'tiltstep');
Debug("Step Up $step");
my $cmd = "/axis-cgi/com/ptz.cgi?rtilt=$step";
my $cmd = '/axis-cgi/com/ptz.cgi?rtilt='.$step;
$self->sendCmd($cmd);
}
sub moveRelDown
{
sub moveRelDown {
my $self = shift;
my $params = shift;
my $step = $self->getParam($params, 'tiltstep');
Debug("Step Down $step");
my $cmd = "/axis-cgi/com/ptz.cgi?rtilt=-$step";
my $cmd = '/axis-cgi/com/ptz.cgi?rtilt=-'.$step;
$self->sendCmd($cmd);
}
sub moveRelLeft
{
sub moveRelLeft {
my $self = shift;
my $params = shift;
my $step = $self->getParam($params, 'panstep');
Debug("Step Left $step");
my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$step";
my $cmd = '/axis-cgi/com/ptz.cgi?rpan=-'.$step;
$self->sendCmd($cmd);
}
sub moveRelRight
{
sub moveRelRight {
my $self = shift;
my $params = shift;
my $step = $self->getParam($params, 'panstep');
Debug("Step Right $step");
my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$step";
my $cmd = '/axis-cgi/com/ptz.cgi?rpan='.$step;
$self->sendCmd($cmd);
}
sub moveRelUpRight
{
sub moveRelUpRight {
my $self = shift;
my $params = shift;
my $panstep = $self->getParam($params, 'panstep');
@ -225,8 +252,7 @@ sub moveRelUpRight
$self->sendCmd($cmd);
}
sub moveRelUpLeft
{
sub moveRelUpLeft {
my $self = shift;
my $params = shift;
my $panstep = $self->getParam($params, 'panstep');
@ -236,8 +262,7 @@ sub moveRelUpLeft
$self->sendCmd($cmd);
}
sub moveRelDownRight
{
sub moveRelDownRight {
my $self = shift;
my $params = shift;
my $panstep = $self->getParam($params, 'panstep');
@ -247,8 +272,7 @@ sub moveRelDownRight
$self->sendCmd($cmd);
}
sub moveRelDownLeft
{
sub moveRelDownLeft {
my $self = shift;
my $params = shift;
my $panstep = $self->getParam($params, 'panstep');
@ -258,100 +282,89 @@ sub moveRelDownLeft
$self->sendCmd($cmd);
}
sub zoomRelTele
{
sub zoomRelTele {
my $self = shift;
my $params = shift;
my $step = $self->getParam($params, 'step');
Debug( "Zoom Tele" );
Debug('Zoom Tele');
my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=$step";
$self->sendCmd($cmd);
}
sub zoomRelWide
{
sub zoomRelWide {
my $self = shift;
my $params = shift;
my $step = $self->getParam($params, 'step');
Debug( "Zoom Wide" );
Debug('Zoom Wide');
my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=-$step";
$self->sendCmd($cmd);
}
sub focusRelNear
{
sub focusRelNear {
my $self = shift;
my $params = shift;
my $step = $self->getParam($params, 'step');
Debug( "Focus Near" );
Debug('Focus Near');
my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=-$step";
$self->sendCmd($cmd);
}
sub focusRelFar
{
sub focusRelFar {
my $self = shift;
my $params = shift;
my $step = $self->getParam($params, 'step');
Debug( "Focus Far" );
Debug('Focus Far');
my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=$step";
$self->sendCmd($cmd);
}
sub focusAuto
{
sub focusAuto {
my $self = shift;
Debug( "Focus Auto" );
my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=on";
Debug('Focus Auto');
my $cmd = '/axis-cgi/com/ptz.cgi?autofocus=on';
$self->sendCmd($cmd);
}
sub focusMan
{
sub focusMan {
my $self = shift;
Debug( "Focus Manual" );
my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=off";
Debug('Focus Manual');
my $cmd = '/axis-cgi/com/ptz.cgi?autofocus=off';
$self->sendCmd($cmd);
}
sub irisRelOpen
{
sub irisRelOpen {
my $self = shift;
my $params = shift;
my $step = $self->getParam($params, 'step');
Debug( "Iris Open" );
Debug('Iris Open');
my $cmd = "/axis-cgi/com/ptz.cgi?riris=$step";
$self->sendCmd($cmd);
}
sub irisRelClose
{
sub irisRelClose {
my $self = shift;
my $params = shift;
my $step = $self->getParam($params, 'step');
Debug( "Iris Close" );
Debug('Iris Close');
my $cmd = "/axis-cgi/com/ptz.cgi?riris=-$step";
$self->sendCmd($cmd);
}
sub irisAuto
{
sub irisAuto {
my $self = shift;
Debug( "Iris Auto" );
my $cmd = "/axis-cgi/com/ptz.cgi?autoiris=on";
Debug('Iris Auto');
my $cmd = '/axis-cgi/com/ptz.cgi?autoiris=on';
$self->sendCmd($cmd);
}
sub irisMan
{
sub irisMan {
my $self = shift;
Debug( "Iris Manual" );
my $cmd = "/axis-cgi/com/ptz.cgi?autoiris=off";
Debug('Iris Manual');
my $cmd = '/axis-cgi/com/ptz.cgi?autoiris=off';
$self->sendCmd($cmd);
}
sub presetClear
{
sub presetClear {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
@ -360,8 +373,7 @@ sub presetClear
$self->sendCmd($cmd);
}
sub presetSet
{
sub presetSet {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
@ -370,8 +382,7 @@ sub presetSet
$self->sendCmd($cmd);
}
sub presetGoto
{
sub presetGoto {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
@ -380,11 +391,10 @@ sub presetGoto
$self->sendCmd($cmd);
}
sub presetHome
{
sub presetHome {
my $self = shift;
Debug( "Home Preset" );
my $cmd = "/axis-cgi/com/ptz.cgi?move=home";
Debug('Home Preset');
my $cmd = '/axis-cgi/com/ptz.cgi?move=home';
$self->sendCmd($cmd);
}

View File

@ -0,0 +1,356 @@
# =========================================================================r
#
# ZoneMinder D-Link DCS-5020L IP Control Protocol Module, $Date: $, $Revision: $
# Copyright (C) 2013 Art Scheel
#
# 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 implementation of the D-Link DCS-5020L IP camera control
# protocol.
#
package ZoneMinder::Control::DCS5020L;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
our $VERSION = $ZoneMinder::Base::VERSION;
# ==========================================================================
#
# D-Link DCS-5020L Control Protocol
#
# ==========================================================================
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use Time::HiRes qw( usleep );
sub new
{
my $class = shift;
my $id = shift;
my $self = ZoneMinder::Control->new( $id );
bless( $self, $class );
srand( time() );
return $self;
}
our $AUTOLOAD;
sub AUTOLOAD
{
my $self = shift;
my $class = ref($self) || croak( "$self not object" );
my $name = $AUTOLOAD;
$name =~ s/.*://;
if ( exists($self->{$name}) )
{
return( $self->{$name} );
}
Fatal( "Can't access $name member of object of class $class" );
}
sub open
{
my $self = shift;
$self->loadMonitor();
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/" . ZoneMinder::Base::ZM_VERSION );
$self->{state} = 'open';
}
sub close
{
my $self = shift;
$self->{state} = 'closed';
}
sub printMsg
{
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendCmd
{
my $self = shift;
my $cmd = shift;
my $cgi = shift;
my $result = undef;
printMsg( $cmd, "Tx" );
my $req = HTTP::Request->new( POST=>"http://$self->{Monitor}->{ControlAddress}/$cgi.cgi" );
$req->content($cmd);
my $res = $self->{ua}->request($req);
if ( $res->is_success )
{
$result = !undef;
}
else
{
Error( "Error check failed: '".$res->status_line()."'" );
}
return( $result );
}
sub move
{
my $self = shift;
my $dir = shift;
my $panStep = shift;
my $tiltStep = shift;
my $cmd = "PanSingleMoveDegree=$panStep&TiltSingleMoveDegree=$tiltStep&PanTiltSingleMove=$dir";
$self->sendCmd( $cmd, 'pantiltcontrol' );
}
sub moveRel
{
my $self = shift;
my $params = shift;
my $panStep = $self->getParam($params, 'panstep', 0);
my $tiltStep = $self->getParam($params, 'tiltstep', 0);
my $dir = shift;
$self->move( $dir, $panStep, $tiltStep );
}
sub moveRelUpLeft
{
my $self = shift;
my $params = shift;
$self->moveRel( $params, 0 );
}
sub moveRelUp
{
my $self = shift;
my $params = shift;
$self->moveRel( $params, 1 );
}
sub moveRelUpRight
{
my $self = shift;
my $params = shift;
$self->moveRel( $params, 2 );
}
sub moveRelLeft
{
my $self = shift;
my $params = shift;
$self->moveRel( $params, 3 );
}
sub moveRelRight
{
my $self = shift;
my $params = shift;
$self->moveRel( $params, 5 );
}
sub moveRelDownLeft
{
my $self = shift;
my $params = shift;
$self->moveRel( $params, 6 );
}
sub moveRelDown
{
my $self = shift;
my $params = shift;
$self->moveRel( $params, 7 );
}
sub moveRelDownRight
{
my $self = shift;
my $params = shift;
$self->moveRel( $params, 8 );
}
# moves the camera to center on the point that the user clicked on in the video image.
# This isn't extremely accurate but good enough for most purposes
sub moveMap
{
# if the camera moves too much or too little, try increasing or decreasing this value
my $f = 11;
my $self = shift;
my $params = shift;
my $xcoord = $self->getParam( $params, 'xcoord' );
my $ycoord = $self->getParam( $params, 'ycoord' );
my $hor = $xcoord * 100 / $self->{Monitor}->{Width};
my $ver = $ycoord * 100 / $self->{Monitor}->{Height};
my $direction;
my $horSteps;
my $verSteps;
if ($hor < 50 && $ver < 50) {
# up left
$horSteps = (50 - $hor) / $f;
$verSteps = (50 - $ver) / $f;
$direction = 0;
} elsif ($hor >= 50 && $ver < 50) {
# up right
$horSteps = ($hor - 50) / $f;
$verSteps = (50 - $ver) / $f;
$direction = 2;
} elsif ($hor < 50 && $ver >= 50) {
# down left
$horSteps = (50 - $hor) / $f;
$verSteps = ($ver - 50) / $f;
$direction = 6;
} elsif ($hor >= 50 && $ver >= 50) {
# down right
$horSteps = ($hor - 50) / $f;
$verSteps = ($ver - 50) / $f;
$direction = 8;
}
my $v = int($verSteps + .5);
my $h = int($horSteps + .5);
Debug( "Move Map to $xcoord,$ycoord, hor=$h, ver=$v with direction $direction" );
$self->move( $direction, $h, $v );
}
sub presetClear
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
Debug( "Clear Preset $preset" );
my $cmd = "ClearPosition=$preset";
$self->sendCmd( $cmd, 'pantiltcontrol' );
}
sub presetSet
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
Debug( "Set Preset $preset" );
my $cmd = "SetCurrentPosition=$preset&SetName=preset_$preset";
$self->sendCmd( $cmd, 'pantiltcontrol' );
}
sub presetGoto
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
Debug( "Goto Preset $preset" );
my $cmd = "PanTiltPresetPositionMove=$preset";
$self->sendCmd( $cmd, 'pantiltcontrol' );
}
sub presetHome
{
my $self = shift;
Debug( "Home Preset" );
$self->move( 4, 0, 0 );
}
# IR Controls
#
# wake = IR on
# sleep = IR off
# reset = IR auto
sub setDayNightMode {
my $self = shift;
my $mode = shift;
my $cmd = "DayNightMode=$mode&ConfigReboot=No";
$self->sendCmd( $cmd, 'daynight' );
}
sub wake
{
my $self = shift;
Debug( "Wake - IR on" );
$self->setDayNightMode(2);
}
sub sleep
{
my $self = shift;
Debug( "Sleep - IR off" );
$self->setDayNightMode(3);
}
sub reset
{
my $self = shift;
Debug( "Reset - IR auto" );
$self->setDayNightMode(0);
}
1;
__END__
# Below is stub documentation for your module. You'd better edit it!
=head1 NAME
ZoneMinder::Database - Perl extension for DCS-5020L
=head1 SYNOPSIS
use ZoneMinder::Database;
DLINK DCS-5020L
=head1 DESCRIPTION
ZoneMinder driver for the D-Link consumer camera DCS-5020L.
=head2 EXPORT
None by default.
=head1 SEE ALSO
See if there are better instructions for the DCS-5020L at
http://www.zoneminder.com/wiki/index.php/Dlink
=head1 AUTHOR
Art Scheel <lt>ascheel (at) gmail<gt>
=head1 COPYRIGHT AND LICENSE
LGPLv3
=cut

View File

@ -95,9 +95,9 @@ sub PutCmd {
my $self = shift;
my $cmd = shift;
my $content = shift;
my $req = HTTP::Request->new(PUT => "$self->{BaseURL}/$cmd");
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_type('application/x-www-form-urlencoded; charset=UTF-8');
$req->content('<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $content);
}
my $res = $self->{UA}->request($req);
@ -135,13 +135,13 @@ sub PutCmd {
# Check for username/password
#
if ( $self->{Monitor}{ControlAddress} =~ /.+:(.+)@.+/ ) {
Info("Check username/password is correct");
Info('Check username/password is correct');
} elsif ( $self->{Monitor}{ControlAddress} =~ /^[^:]+@.+/ ) {
Info("No password in Control Address. Should there be one?");
Info('No password in Control Address. Should there be one?');
} elsif ( $self->{Monitor}{ControlAddress} =~ /^:.+@.+/ ) {
Info("Password but no username in Control Address.");
Info('Password but no username in Control Address.');
} else {
Info("Missing username and password in Control Address.");
Info('Missing username and password in Control Address.');
}
Fatal($res->status_line);
}
@ -382,7 +382,7 @@ sub irisRelOpen {
sub reset {
my $self = shift;
$self->PutCmd("ISAPI/System/reboot");
$self->PutCmd('ISAPI/System/reboot');
}
1;

View File

@ -1,6 +1,6 @@
# ==========================================================================
#
# ZoneMinder Airlink SkyIPCam AICN747/AICN747W Control Protocol Module, $Date: 2008-09-13 17:30:29 +0000 (Sat, 13 Sept 2008) $, $Revision: 2229 $
# ZoneMinder Airlink SkyIPCam AICN747/AICN747W Control Protocol Module
# Copyright (C) 2008 Brian Rudy (brudyNO@SPAMpraecogito.com)
#
# This program is free software; you can redistribute it and/or
@ -43,8 +43,6 @@ our @ISA = qw(ZoneMinder::Control);
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use Time::HiRes qw( usleep );
sub open {
my $self = shift;
@ -52,29 +50,21 @@ sub open {
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION );
$self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION);
$self->{state} = 'open';
}
sub printMsg {
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendCmd {
my $self = shift;
my $cmd = shift;
my $result = undef;
printMsg( $cmd, "Tx" );
$self->printMsg($cmd, 'Tx');
my $url;
if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) {
if ( $self->{Monitor}->{ControlAddress} =~ /^http/i ) {
$url = $self->{Monitor}->{ControlAddress}.$cmd;
} else {
$url = 'http://'.$self->{Monitor}->{ControlAddress}.$cmd;
@ -86,16 +76,16 @@ sub sendCmd {
if ( $res->is_success ) {
$result = !undef;
} else {
Error( "Error check failed: '".$res->status_line()."'" );
Error('Error check failed: \''.$res->status_line().'\'');
}
return( $result );
return $result;
}
sub reset {
my $self = shift;
Debug( "Camera Reset" );
my $cmd = "/admin/ptctl.cgi?move=reset";
Debug('Camera Reset');
my $cmd = '/admin/ptctl.cgi?move=reset';
$self->sendCmd($cmd);
}
@ -125,19 +115,19 @@ sub moveMap {
elsif ( $hor > 50 ) {
# right
$horSteps = (($hor - 50) / 50) * $maxhor;
$horDir = "right";
$horDir = 'right';
}
# Vertical movement
if ( $ver < 50 ) {
# up
$verSteps = ((50 - $ver) / 50) * $maxver;
$verDir = "up";
$verDir = 'up';
}
elsif ( $ver > 50 ) {
# down
$verSteps = (($ver - 50) / 50) * $maxver;
$verDir = "down";
$verDir = 'down';
}
my $v = int($verSteps);
@ -155,7 +145,7 @@ sub moveRelUp {
my $params = shift;
my $step = $self->getParam($params, 'tiltstep');
Debug("Step Up $step");
my $cmd = "/admin/ptctl.cgi?move=up";
my $cmd = '/admin/ptctl.cgi?move=up';
$self->sendCmd($cmd);
}
@ -164,7 +154,7 @@ sub moveRelDown {
my $params = shift;
my $step = $self->getParam($params, 'tiltstep');
Debug("Step Down $step");
my $cmd = "/admin/ptctl.cgi?move=down";
my $cmd = '/admin/ptctl.cgi?move=down';
$self->sendCmd($cmd);
}
@ -173,12 +163,12 @@ sub moveRelLeft {
my $params = shift;
my $step = $self->getParam($params, 'panstep');
if ( $self->{Monitor}->{Orientation} eq "hori" ) {
Debug( "Stepping Right because flipped horizontally " );
$self->sendCmd( "/admin/ptctl.cgi?move=right" );
if ( $self->{Monitor}->{Orientation} eq 'FLIP_HORI' ) {
Debug('Stepping Right because flipped horizontally');
$self->sendCmd('/admin/ptctl.cgi?move=right');
} else {
Debug( "Step Left" );
$self->sendCmd( "/admin/ptctl.cgi?move=left" );
Debug('Step Left');
$self->sendCmd('/admin/ptctl.cgi?move=left');
}
}
@ -186,12 +176,12 @@ sub moveRelRight {
my $self = shift;
my $params = shift;
my $step = $self->getParam($params, 'panstep');
if ( $self->{Monitor}->{Orientation} eq "hori" ) {
Debug( "Stepping Left because flipped horizontally " );
$self->sendCmd( "/admin/ptctl.cgi?move=left" );
if ( $self->{Monitor}->{Orientation} eq 'FLIP_HORI' ) {
Debug('Stepping Left because flipped horizontally');
$self->sendCmd('/admin/ptctl.cgi?move=left');
} else {
Debug( "Step Right" );
$self->sendCmd( "/admin/ptctl.cgi?move=right" );
Debug('Step Right');
$self->sendCmd('/admin/ptctl.cgi?move=right');
}
}
@ -209,7 +199,7 @@ sub presetSet {
my $params = shift;
my $preset = $self->getParam($params, 'preset');
Debug("Set Preset $preset");
my $cmd = "/admin/ptctl.cgi?position=" . ($preset - 1) . "&positionname=zm$preset";
my $cmd = '/admin/ptctl.cgi?position=' . ($preset - 1) . "&positionname=zm$preset";
$self->sendCmd( $cmd );
}
@ -218,14 +208,14 @@ sub presetGoto {
my $params = shift;
my $preset = $self->getParam($params, 'preset');
Debug("Goto Preset $preset");
my $cmd = "/admin/ptctl.cgi?move=p" . ($preset - 1);
my $cmd = '/admin/ptctl.cgi?move=p'.($preset - 1);
$self->sendCmd($cmd);
}
sub presetHome {
my $self = shift;
Debug( "Home Preset" );
my $cmd = "/admin/ptctl.cgi?move=h";
Debug('Home Preset');
my $cmd = '/admin/ptctl.cgi?move=h';
$self->sendCmd($cmd);
}

View File

@ -1,6 +1,6 @@
# ==========================================================================
#
# ZoneMinder Filter Module, $Date$, $Revision$
# ZoneMinder Filter Module
# Copyright (C) 2001-2008 Philip Coombes
#
# This program is free software; you can redistribute it and/or
@ -162,7 +162,9 @@ sub Sql {
my $value = $term->{val};
my @value_list;
if ( $term->{attr} ) {
if ( $term->{attr} =~ /^Monitor/ ) {
if ( $term->{attr} eq 'AlarmedZoneId' ) {
$term->{op} = 'EXISTS';
} elsif ( $term->{attr} =~ /^Monitor/ ) {
my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/;
$self->{Sql} .= 'M.'.$temp_attr_name;
} elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) {
@ -214,7 +216,10 @@ sub Sql {
( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/;
foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) {
if ( $term->{attr} =~ /^MonitorName/ ) {
if ( $term->{attr} eq 'AlarmedZoneId' ) {
$value = '(SELECT * FROM Stats WHERE EventId=E.Id AND ZoneId='.$value.')';
} elsif ( $term->{attr} =~ /^MonitorName/ ) {
$value = "'$temp_value'";
} elsif ( $term->{attr} =~ /ServerId/) {
Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})");
@ -256,6 +261,8 @@ sub Sql {
} elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} elsif ( $temp_value eq 'CURDATE()' or $temp_value eq 'NOW()' ) {
$value = 'to_days('.$temp_value.')';
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
@ -294,6 +301,8 @@ sub Sql {
} else {
$self->{Sql} .= " IS $value";
}
} elsif ( $term->{op} eq 'EXISTS' ) {
$self->{Sql} .= " EXISTS $value";
} elsif ( $term->{op} eq 'IS NOT' ) {
$self->{Sql} .= " IS NOT $value";
} elsif ( $term->{op} eq '=[]' ) {

View File

@ -503,11 +503,14 @@ sub openFile {
$LOGFILE->autoflush() if $this->{autoFlush};
my $webUid = (getpwnam($ZoneMinder::Config::Config{ZM_WEB_USER}))[2];
Error("Can't get uid for $ZoneMinder::Config::Config{ZM_WEB_USER}") if ! defined $webUid;
my $webGid = (getgrnam($ZoneMinder::Config::Config{ZM_WEB_GROUP}))[2];
Error("Can't get gid for $ZoneMinder::Config::Config{ZM_WEB_USER}") if ! defined $webGid;
if ( $> == 0 ) {
chown( $webUid, $webGid, $this->{logFile} )
or Fatal("Can't change permissions on log file $$this{logFile}: $!");
}
# If we are root, we want to make sure that www-data or whatever owns the file
chown($webUid, $webGid, $this->{logFile} ) or
Error("Can't change permissions on log file $$this{logFile}: $!");
} # end if are root
} else {
$this->fileLevel(NOLOG);
$this->termLevel(INFO);

View File

@ -36,9 +36,172 @@ require ZoneMinder::Server;
#our @ISA = qw(Exporter ZoneMinder::Base);
use parent qw(ZoneMinder::Object);
use vars qw/ $table $primary_key /;
use vars qw/ $table $primary_key %fields $serial %defaults $debug/;
$table = 'Monitors';
$primary_key = 'Id';
$serial = $primary_key = 'Id';
%fields = map { $_ => $_ } qw(
Id
Name
Notes
ServerId
StorageId
Type
Function
Enabled
LinkedMonitors
Triggers
Device
Channel
Format
V4LMultiBuffer
V4LCapturesPerFrame
Protocol
Method
Host
Port
SubPath
Path
Options
User
Pass
Width
Height
Colours
Palette
Orientation
Deinterlacing
DecoderHWAccelName
DecoderHWAccelDevice
SaveJPEGs
VideoWriter
OutputCodec
OutputContainer
EncoderParameters
RecordAudio
RTSPDescribe
Brightness
Contrast
Hue
Colour
EventPrefix
LabelFormat
LabelX
LabelY
LabelSize
ImageBufferCount
WarmupCount
PreEventCount
PostEventCount
StreamReplayBuffer
AlarmFrameCount
SectionLength
MinSectionLength
FrameSkip
MotionFrameSkip
AnalysisFPSLimit
AnalysisUpdateDelay
MaxFPS
AlarmMaxFPS
FPSReportInterval
RefBlendPerc
AlarmRefBlendPerc
Controllable
ControlId
ControlDevice
ControlAddress
AutoStopTimeout
TrackMotion
TrackDelay
ReturnLocation
ReturnDelay
DefaultRate
DefaultScale
SignalCheckPoints
SignalCheckColour
WebColour
Exif
Sequence
);
%defaults = (
ServerId => 0,
StorageId => 0,
Type => 'Ffmpeg',
Function => 'Mocord',
Enabled => 1,
LinkedMonitors => undef,
Device => '',
Channel => 0,
Format => 0,
V4LMultiBuffer => undef,
V4LCapturesPerFrame => 1,
Protocol => undef,
Method => '',
Host => undef,
Port => '',
SubPath => '',
Path => undef,
Options => undef,
User => undef,
Pass => undef,
Width => undef,
Height => undef,
Colours => 4,
Palette => 0,
Orientation => undef,
Deinterlacing => 0,
DecoderHWAccelName => undef,
DecoderHWAccelDevice => undef,
SaveJPEGs => 3,
VideoWriter => 0,
OutputCodec => undef,
OutputContainer => undef,
EncoderParameters => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n",
RecordAudio=>0,
RTSPDescribe=>0,
Brightness => -1,
Contrast => -1,
Hue => -1,
Colour => -1,
EventPrefix => 'Event-',
LabelFormat => '%N - %d/%m/%y %H:%M:%S',
LabelX => 0,
LabelY => 0,
LabelSize => 1,
ImageBufferCount => 20,
WarmupCount => 0,
PreEventCount => 5,
PostEventCount => 5,
StreamReplayBuffer => 0,
AlarmFrameCount => 1,
SectionLength => 600,
MinSectionLength => 10,
FrameSkip => 0,
MotionFrameSkip => 0,
AnalysisFPSLimit => undef,
AnalysisUpdateDelay => 0,
MaxFPS => undef,
AlarmMaxFPS => undef,
FPSReportInterval => 100,
RefBlendPerc => 6,
AlarmRefBlendPerc => 6,
Controllable => 0,
ControlId => undef,
ControlDevice => undef,
ControlAddress => undef,
AutoStopTimeout => undef,
TrackMotion => 0,
TrackDelay => undef,
ReturnLocation => -1,
ReturnDelay => undef,
DefaultRate => 100,
DefaultScale => 100,
SignalCheckPoints => 0,
SignalCheckColour => '#0000BE',
WebColour => '#ff0000',
Exif => 0,
Sequence => undef,
);
sub Server {
return new ZoneMinder::Server( $_[0]{ServerId} );

View File

@ -172,7 +172,7 @@ sub interpret_messages {
# functions
sub discover {
my ( $soap_version ) = @_;
my ( $soap_version, $net_interface ) = @_;
my @results;
## collect all responses
@ -193,12 +193,17 @@ sub discover {
my %services;
if ( $verbose ) {
print "Probing for SOAP 1.1\n"
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');
if ( $net_interface ) {
my $transport = $svc_discover->get_transport();
print "Setting net interface for $transport to $net_interface\n";
$transport->set_net_interface($net_interface);
}
my $uuid = $uuid_gen->create_str();
@ -222,12 +227,17 @@ sub discover {
if ( ( ! $soap_version ) or ( $soap_version eq '1.2' ) ) {
my %services;
if ( $verbose ) {
print "Probing for SOAP 1.2\n"
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');
if ( $net_interface ) {
my $transport = $svc_discover->get_transport();
print "Setting net interface for $transport to $net_interface\n";
$transport->set_net_interface($net_interface);
}
# copies of the same Probe message must have the same MessageID.
# This is not a copy. So we generate a new uuid.
@ -250,20 +260,20 @@ sub discover {
push @results, interpret_messages($svc_discover, \%services, @responses);
} # end if doing soap 1.2
return @results;
}
} # end sub discover
sub profiles {
my ( $client ) = @_;
my $endpoint = $client->get_endpoint('media');
if ( ! $endpoint ) {
print "No media enpoint for client.\n";
my $media = $client->get_endpoint('media');
if ( ! $media ) {
print "No media endpoint for client.\n";
return;
}
my $result = $endpoint->GetProfiles( { } ,, );
my $result = $media->GetProfiles( { } ,, );
if ( ! $result ) {
print "No result from GetProfiles\n";
print "No result from GetProfiles.\n";
return;
}
if ( $verbose ) {
@ -275,23 +285,18 @@ sub profiles {
foreach my $profile ( @{ $profiles } ) {
my $token = $profile->attr()->get_token() ;
my $video_encoder_configuration = $profile->get_VideoEncoderConfiguration();
if ( ! $video_encoder_configuration ) {
print "Unknown profile $token " . $profile->get_Name()."\n";
my $Name = $profile->get_Name();
my $VideoEncoderConfiguration = $profile->get_VideoEncoderConfiguration();
if ( ! $VideoEncoderConfiguration ) {
print "Unknown profile $token $Name.\n";
next;
}
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( {
foreach my $streamtype ( 'RTP_unicast', 'RTP-Unicast', 'RTP-multicast', 'RTP-Multicast' ) {
my $StreamUri = $media->GetStreamUri( {
StreamSetup => { # ONVIF::Media::Types::StreamSetup
Stream => $streamtype, # StreamType
Transport => { # ONVIF::Media::Types::Transport
@ -299,21 +304,30 @@ sub profiles {
},
},
ProfileToken => $token, # ReferenceToken
} ,, );
last if $result;
}
die $result if not $result;
# print $result . "\n";
} );
next if ! ( $StreamUri and $StreamUri->can('get_MediaUri') );
my $MediaUri = $StreamUri->get_MediaUri();
next if ! $MediaUri;
my $Uri = $MediaUri->get_Uri();
next if ! $Uri;
print join(', ', $token,
$Name,
$VideoEncoderConfiguration->get_Encoding(),
$VideoEncoderConfiguration->get_Resolution()->get_Width(),
$VideoEncoderConfiguration->get_Resolution()->get_Height(),
$VideoEncoderConfiguration->get_RateControl()->get_FrameRateLimit(),
$Uri,
) . "\n";
} # end foreach streamtype
print $result->get_MediaUri()->get_Uri() .
"\n";
} # end foreach profile
#
# use message parser without schema validation ???
#
}
} # end sub profiles
sub move {
my ($client, $dir) = @_;
@ -326,13 +340,22 @@ sub move {
sub metadata {
my ( $client ) = @_;
my $result = $client->get_endpoint('media')->GetMetadataConfigurations( { } ,, );
die $result if not $result;
print $result . "\n";
my $media = $client->get_endpoint('media');
die 'No media endpoint.' if !$media;
$result = $client->get_endpoint('media')->GetVideoAnalyticsConfigurations( { } ,, );
die $result if not $result;
my $result = $media->GetMetadataConfigurations( { } ,, );
if ( ! $result ) {
print "No MetaDataConfigurations\n" if $verbose;
} else {
print $result . "\n";
}
$result = $media->GetVideoAnalyticsConfigurations( { } ,, );
if ( ! $result ) {
print "No VideoAnalyticsConfigurations\n" if $verbose;
} else {
print $result . "\n";
}
# $result = $client->get_endpoint('analytics')->GetServiceCapabilities( { } ,, );
# die $result if not $result;

View File

@ -420,7 +420,7 @@ MAIN: while( $loop ) {
Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir");
{
my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*");
Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . ' entries.' );
Debug('glob("'.$monitor_dir.'/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned '.(scalar @event_dirs).' entries.');
foreach my $event_dir ( @event_dirs ) {
if ( ! -d $event_dir ) {
Debug("$event_dir is not a dir. Skipping");
@ -428,7 +428,7 @@ MAIN: while( $loop ) {
}
my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/;
if ( !$event_id ) {
Debug("Unable to parse date/event_id from $event_dir");
Debug('Unable to parse date/event_id from '.$event_dir);
next;
}
my $Event = $fs_events->{$event_id} = new ZoneMinder::Event();
@ -438,6 +438,7 @@ MAIN: while( $loop ) {
$Event->MonitorId( $monitor_dir );
$Event->StorageId( $Storage->Id() );
$Event->Path();
$Event->age();
Debug("Have event $$Event{Id} at $$Event{Path}");
$Event->StartTime(POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($Event->Path()))));
} # end foreach event
@ -466,13 +467,13 @@ MAIN: while( $loop ) {
} # end foreach event
chdir( $Storage->Path() );
} # if USE_DEEP_STORAGE
Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir" );
Debug('Got '.int(keys(%$fs_events)).' filesystem events for monitor '.$monitor_dir);
delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir);
} # end foreach monitor
if ( $cleaned ) {
Debug("First stage cleaning done. Restarting.");
Debug('First stage cleaning done. Restarting.');
redo MAIN;
}
@ -484,7 +485,7 @@ MAIN: while( $loop ) {
next;
}
my @event_ids = keys %$fs_events;
Debug('Have ' .scalar @event_ids . " events for monitor $monitor_id");
Debug('Have ' .scalar @event_ids . ' events for monitor '.$monitor_id);
foreach my $fs_event_id ( sort { $a <=> $b } keys %$fs_events ) {
@ -499,7 +500,7 @@ MAIN: while( $loop ) {
}
my $age = $Event->age();
if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) {
if ( $age and ($age > $Config{ZM_AUDIT_MIN_AGE}) ) {
aud_print("Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old");
if ( confirm() ) {
$Event->delete_files();
@ -586,7 +587,7 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) {
} else {
Debug("$$Event{Id} Not found at $path");
}
}
} # end foreach Storage
if ( $Event->Archived() ) {
Warning("Event $$Event{Id} is Archived. Taking no further action on it.");
next;
@ -638,18 +639,13 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) {
Info("Updating storage area on event $$Event{Id} from $$Event{StorageId} to $$fs_events{$db_event}{StorageId}");
$Event->StorageId($$fs_events{$db_event}->StorageId());
}
if ( $$fs_events{$db_event}->StartTime() ne $Event->StartTime() ) {
if ( ! $Event->StartTime() ) {
Info("Updating StartTime on event $$Event{Id} from $$Event{StartTime} to $$fs_events{$db_event}{StartTime}");
if ( $$Event{Scheme} eq 'Deep' ) {
$Event->StartTime($$fs_events{$db_event}->StartTime());
} else {
$Event->StartTime($$fs_events{$db_event}->StartTime());
}
$Event->save();
}
$Event->save();
}
} # end if Event exists in db and not in filesystem
} # end if ! in fs_events
} # foreach db_event
} # end foreach db_monitor
@ -944,6 +940,10 @@ FROM `Frames` WHERE `EventId`=?';
$eventcounts_day_sth->execute() or Error("Can't execute: ".$eventcounts_sth->errstr());
$eventcounts_week_sth->execute() or Error("Can't execute: ".$eventcounts_sth->errstr());
$eventcounts_month_sth->execute() or Error("Can't execute: ".$eventcounts_sth->errstr());
my $storage_diskspace_sth = $dbh->prepare_cached('UPDATE Storage SET DiskSpace=(SELECT SUM(DiskSpace) FROM Events WHERE StorageId=Storage.Id)');
$storage_diskspace_sth->execute() or Error("Can't execute: ".$storage_diskspace_sth->errstr());
sleep($Config{ZM_AUDIT_CHECK_INTERVAL}) if $continuous;
};

View File

@ -351,8 +351,14 @@ sub exportsql {
}
}
if ($ARGV[0]) {
$command .= qq( --where="Name = '$ARGV[0]'");
my $name = $ARGV[0];
if ( $name ) {
if ( $name =~ /^([A-Za-z0-9 ,.&()\/\-]+)$/ ) { # Allow alphanumeric and " ,.&()/-"
$name = $1;
$command .= qq( --where="Name = '$name'");
} else {
print "Invalid characters in Name\n";
}
}
$command .= " zm Controls MonitorPresets";

View File

@ -108,6 +108,9 @@ if ( $options{command} ) {
Fatal("Unable to load control data for monitor $id");
}
my $protocol = $monitor->{Protocol};
if ( !$protocol ) {
Fatal('No protocol is set in monitor. Please edit the monitor, edit control type, select the control capability and fill in the Protocol field');
}
if ( -x $protocol ) {
# Protocol is actually a script!

View File

@ -196,7 +196,7 @@ my $last_action = 0;
while( !$zm_terminate ) {
my $now = time;
if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) {
Debug("Reloading filters");
Debug('Reloading filters');
$last_action = $now;
@filters = getFilters({ Name=>$filter_name, Id=>$filter_id });
}
@ -699,8 +699,10 @@ sub substituteTags {
$text =~ s/%ESM%/$Event->{MaxScore}/g;
if ( $first_alarm_frame ) {
$text =~ s/%EPI1%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$first_alarm_frame->{FrameId}/g;
$text =~ s/%EPIM%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$max_alarm_frame->{FrameId}/g;
$text =~ s/%EPF1%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$first_alarm_frame->{FrameId}/g;
$text =~ s/%EPFM%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$max_alarm_frame->{FrameId}/g;
$text =~ s/%EPI1%/$url?view=image&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$first_alarm_frame->{FrameId}/g;
$text =~ s/%EPIM%/$url?view=image&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$max_alarm_frame->{FrameId}/g;
if ( $attachments_ref ) {
if ( $text =~ s/%EI1%//g ) {
my $path = generateImage($Event, $first_alarm_frame);
@ -748,13 +750,14 @@ sub substituteTags {
}
}
if ( $text =~ s/%EIMOD%//g ) {
$text =~ s/%EIMOD%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g;
if ( $text =~ s/%EIMOD%//g or $text =~ s/%EFMOD%//g ) {
$text =~ s/%EFMOD%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g;
$text =~ s/%EIMOD%/$url?view=image&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g;
my $path = $Event->Path().'/objdetect.jpg';
if ( -e $path ) {
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
} else {
Warning('No image for EIMOD at ' . $path);
Warning('No image for MOD at '.$path);
}
}
@ -796,17 +799,17 @@ sub sendEmail {
Error('No from email address defined, not sending email');
return 0;
}
if ( ! $Config{ZM_EMAIL_ADDRESS} ) {
if ( ! $$filter{EmailTo} ) {
Error('No email address defined, not sending email');
return 0;
}
Info('Creating notification email');
my $subject = substituteTags($Config{ZM_EMAIL_SUBJECT}, $filter, $Event);
my $subject = substituteTags($$filter{EmailSubject}, $filter, $Event);
return 0 if !$subject;
my @attachments;
my $body = substituteTags($Config{ZM_EMAIL_BODY}, $filter, $Event, \@attachments);
my $body = substituteTags($$filter{EmailBody}, $filter, $Event, \@attachments);
return 0 if !$body;
Info("Sending notification email '$subject'");
@ -816,7 +819,7 @@ sub sendEmail {
### Create the multipart container
my $mail = MIME::Lite->new (
From => $Config{ZM_FROM_EMAIL},
To => $Config{ZM_EMAIL_ADDRESS},
To => $$filter{EmailTo},
Subject => $subject,
Type => 'multipart/mixed'
);
@ -849,7 +852,7 @@ sub sendEmail {
$mail->send();
} else {
### Send using SSMTP
$mail->send('sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS});
$mail->send('sendmail', $ssmtp_location, $$filter{EmailTo});
}
} else {
MIME::Lite->send('smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60);
@ -858,9 +861,9 @@ sub sendEmail {
} else {
my $mail = MIME::Entity->build(
From => $Config{ZM_FROM_EMAIL},
To => $Config{ZM_EMAIL_ADDRESS},
To => $$filter{EmailTo},
Subject => $subject,
Type => (($body=~/<html>/)?'text/html':'text/plain'),
Type => (($body=~/<html/)?'text/html':'text/plain'),
Data => $body
);
@ -962,7 +965,7 @@ sub sendMessage {
From => $Config{ZM_FROM_EMAIL},
To => $Config{ZM_MESSAGE_ADDRESS},
Subject => $subject,
Type => (($body=~/<html>/)?'text/html':'text/plain'),
Type => (($body=~/<html/)?'text/html':'text/plain'),
Data => $body
);

5
scripts/zmonvif-probe.pl.in Executable file → Normal file
View File

@ -41,7 +41,7 @@ my $OPTIONS = 'v';
sub HELP_MESSAGE {
my ($fh, $pkg, $ver, $opts) = @_;
print $fh "Usage: " . __FILE__ . " [-v] probe <soap version>\n";
print $fh "Usage: " . __FILE__ . " [-v] probe <soap version> <network interface>\n";
print $fh " " . __FILE__ . " [-v] <command> <device URI> <soap version> <user> <password>\n";
print $fh <<EOF
Commands are:
@ -84,7 +84,8 @@ if ( defined $opt_v ) {
if ( $action eq 'probe' ) {
my $soap_version = shift;
ZoneMinder::ONVIF::discover($soap_version);
my $net_interface = shift;
ZoneMinder::ONVIF::discover($soap_version, $net_interface);
} else {
# all other actions need URI and credentials
my $url_svc_device = shift @ARGV;

View File

@ -23,6 +23,7 @@
use strict;
use bytes;
use utf8;
@EXTRA_PERL_LIB@
use ZoneMinder;
@ -34,6 +35,7 @@ use Sys::MemInfo qw(totalmem);
use Sys::CPU qw(cpu_count);
use POSIX qw(strftime uname);
use JSON::MaybeXS;
use Encode;
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
@ -166,7 +168,7 @@ sub sendData {
$req->header('content-length' => length($msg));
$req->header('connection' => 'Close');
$req->content($msg);
$req->content(encode('UTF-8',$msg));
my $resp = $ua->request($req);
my $resp_msg = $resp->decoded_content;
@ -196,7 +198,7 @@ sub getUUID {
$uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array();
$sth->finish();
$sql = q`UPDATE Config set Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'`;
$sql = q`UPDATE Config SET Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'`;
$sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
$res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
@ -232,9 +234,9 @@ sub countQuery {
my $dbh = shift;
my $table = shift;
my $sql = "SELECT count(*) FROM $table";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() );
my $sql = "SELECT count(*) FROM `$table`";
my $sth = $dbh->prepare_cached($sql) or die "Can't prepare '$sql': ".$dbh->errstr();
my $res = $sth->execute() or die 'Can\'t execute: '.$sth->errstr();
my $count = $sth->fetchrow_array();
$sth->finish();
@ -245,7 +247,7 @@ sub countQuery {
sub getMonitorRef {
my $dbh = shift;
my $sql = 'SELECT Id,Name,Type,Function,Width,Height,Colours,MaxFPS,AlarmMaxFPS FROM Monitors';
my $sql = 'SELECT `Id`,`Name`,`Type`,`Function`,`Width`,`Height`,`Colours`,`MaxFPS`,`AlarmMaxFPS` FROM `Monitors`';
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() );
my $arrayref = $sth->fetchall_arrayref({});

View File

@ -329,14 +329,13 @@ sub loadMonitor {
} # end sub loadMonitor
sub loadMonitors {
Debug('Loading monitors');
$monitor_reload_time = time();
my %new_monitors = ();
my $sql = q`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`=?' : '' )
;
my $sth = $dbh->prepare_cached( $sql )
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );

View File

@ -93,12 +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();
}
@ -108,14 +107,12 @@ socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 )
my $saddr = sockaddr_un(SOCK_FILE);
if ( !connect( CLIENT, $saddr ) )
{
if ( !connect(CLIENT, $saddr) ) {
# The server isn't there
print("Unable to connect, starting server\n");
close(CLIENT);
if ( my $cpid = fork() )
{
if ( my $cpid = fork() ) {
# Parent process just sleep and fall through
sleep(2);
logReinit();
@ -123,28 +120,23 @@ if ( !connect( CLIENT, $saddr ) )
or Fatal("Can't open socket: $!");
connect(CLIENT, $saddr)
or Fatal("Can't connect: $!");
}
elsif ( defined($cpid) )
{
} elsif ( defined($cpid) ) {
setpgrp();
logReinit();
X10Server::runServer();
}
else
{
} else {
Fatal("Can't fork: $!");
}
}
# The server is there, connect to it
#print( "Writing commands\n" );
CLIENT->autoflush();
my $message = "$command";
$message .= ";$unit_code" if ( $unit_code );
my $message = $command;
$message .= ';'.$unit_code if $unit_code;
print(CLIENT $message);
shutdown(CLIENT, 1);
while ( my $line = <CLIENT> )
{
while ( my $line = <CLIENT> ) {
chomp($line);
print("$line\n");
}
@ -178,9 +170,8 @@ our %monitor_hash;
our %device_hash;
our %pending_tasks;
sub runServer
{
Info( "X10 server starting\n" );
sub runServer {
Info('X10 server starting');
socket(SERVER, PF_UNIX, SOCK_STREAM, 0)
or Fatal("Can't open socket: $!");
@ -191,7 +182,11 @@ sub runServer
$dbh = zmDbConnect();
$x10 = new X10::ActiveHome( port=>$Config{ZM_X10_DEVICE}, house_code=>$Config{ZM_X10_HOUSE_CODE}, debug=>0 );
$x10 = new X10::ActiveHome(
port=>$Config{ZM_X10_DEVICE},
house_code=>$Config{ZM_X10_HOUSE_CODE},
debug=>0
);
loadTasks();
@ -205,46 +200,38 @@ sub runServer
my $reload = undef;
my $reload_count = 0;
my $reload_limit = $Config{ZM_X10_DB_RELOAD_INTERVAL} / $timeout;
while( 1 )
{
while( 1 ) {
my $nfound = select(my $rout = $rin, undef, undef, $timeout);
#print( "Off select, NF:$nfound, ER:$!\n" );
#print( vec( $rout, fileno(SERVER),1)."\n" );
#print( vec( $rout, $x10->select_fds(),1)."\n" );
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 = <CLIENT>;
my ( $command, $unit_code ) = split( /;/, $message );
my ($command, $unit_code) = split(';', $message);
my $device;
if ( defined($unit_code) )
{
if ( $unit_code < 1 || $unit_code > 16 )
{
if ( defined($unit_code) ) {
if ( $unit_code < 1 || $unit_code > 16 ) {
dPrint(ZoneMinder::Logger::ERROR, "Invalid unit code '$unit_code'\n");
next;
}
$device = $device_hash{$unit_code};
if ( !$device )
{
$device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ),
if ( !$device ) {
$device = $device_hash{$unit_code} = {
appliance=>$x10->Appliance(unit_code=>$unit_code),
status=>'unknown'
};
}
}
} # end if defined($unit_code)
my $result;
if ( $command eq 'on' )
{
if ( $command eq 'on' ) {
$result = $device->{appliance}->on();
}
elsif ( $command eq 'off' )
{
} elsif ( $command eq 'off' ) {
$result = $device->{appliance}->off();
}
#elsif ( $command eq 'dim' )
@ -255,128 +242,97 @@ sub runServer
#{
#$result = $device->{appliance}->bright();
#}
elsif ( $command eq 'status' )
{
if ( $device )
{
elsif ( $command eq 'status' ) {
if ( $device ) {
dPrint(ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n");
}
else
{
foreach my $unit_code ( sort( keys(%device_hash) ) )
{
} else {
foreach my $unit_code ( sort( keys(%device_hash) ) ) {
my $device = $device_hash{$unit_code};
dPrint(ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n");
}
}
}
elsif ( $command eq 'shutdown' )
{
} elsif ( $command eq 'shutdown' ) {
last;
}
else
{
} else {
dPrint(ZoneMinder::Logger::ERROR, "Invalid command '$command'\n");
}
if ( defined($result) )
{
if ( 1 || $result )
{
if ( defined($result) ) {
# FIXME
if ( 1 || $result ) {
$device->{status} = uc($command);
dPrint(ZoneMinder::Logger::DEBUG, $device->{appliance}->address()." $command, ok\n");
#x10listen( new X10::Event( sprintf("%s %s", $device->{appliance}->address, uc($command) ) ) );
}
else
{
} else {
dPrint(ZoneMinder::Logger::ERROR, $device->{appliance}->address()." $command, failed\n");
}
}
} # end if defined result
close(CLIENT);
}
elsif ( vec( $rout, $x10->select_fds(),1) )
{
} elsif ( vec($rout, $x10->select_fds(),1) ) {
$x10->handle_input();
}
else
{
} else {
Fatal('Bogus descriptor');
}
}
elsif ( $nfound < 0 )
{
if ( $! != EINTR )
{
} elsif ( $nfound < 0 ) {
if ( $! != EINTR ) {
Fatal("Can't select: $!");
}
}
else
{
} else {
#print( "Select timed out\n" );
# Check for state changes
foreach my $monitor_id ( sort(keys(%monitor_hash) ) )
{
foreach my $monitor_id ( sort(keys(%monitor_hash) ) ) {
my $monitor = $monitor_hash{$monitor_id};
my $state = zmGetMonitorState($monitor);
if ( !defined($state) )
{
if ( !defined($state) ) {
$reload = !undef;
next;
}
if ( defined( $monitor->{LastState} ) )
{
if ( defined( $monitor->{LastState} ) ) {
my $task_list;
if ( ($state == STATE_ALARM || $state == STATE_ALERT)
&& ($monitor->{LastState} == STATE_IDLE || $monitor->{LastState} == STATE_TAPE)
) # Gone into alarm state
{
Debug( "Applying ON_list for $monitor_id\n" );
$task_list = $monitor->{'ON_list'};
}
elsif ( ($state == STATE_IDLE && $monitor->{LastState} != STATE_IDLE)
Debug("Applying ON_list for $monitor_id");
$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'};
Debug("Applying OFF_list for $monitor_id");
$task_list = $monitor->{OFF_list};
}
if ( $task_list )
{
foreach my $task ( @$task_list )
{
if ( $task_list ) {
foreach my $task ( @$task_list ) {
processTask($task);
}
}
}
} # end if defined laststate
$monitor->{LastState} = $state;
}
} # end foreach monitor
# Check for pending tasks
my $now = time();
foreach my $activation_time ( sort(keys(%pending_tasks) ) )
{
foreach my $activation_time ( sort(keys(%pending_tasks) ) ) {
last if ( $activation_time > $now );
my $pending_list = $pending_tasks{$activation_time};
foreach my $task ( @$pending_list )
{
foreach my $task ( @$pending_list ) {
processTask($task);
}
delete( $pending_tasks{$activation_time} );
delete $pending_tasks{$activation_time};
}
if ( $reload || ++$reload_count >= $reload_limit )
{
if ( $reload or (++$reload_count >= $reload_limit) ) {
loadTasks();
$reload = undef;
$reload_count = 0;
}
}
}
Info( "X10 server exiting\n" );
Info("X10 server exiting");
close(SERVER);
exit();
}
sub addToDeviceList
{
sub addToDeviceList {
my $unit_code = shift;
my $event = shift;
my $monitor = shift;
@ -384,36 +340,35 @@ sub addToDeviceList
my $limit = shift;
Debug("Adding to device list, uc:$unit_code, ev:$event, mo:"
.$monitor->{Id}.", fu:$function, li:$limit\n"
.$monitor->{Id}.", fu:$function, li:$limit"
);
my $device = $device_hash{$unit_code};
if ( !$device )
{
$device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ),
if ( !$device ) {
$device = $device_hash{$unit_code} = {
appliance=>$x10->Appliance(unit_code=>$unit_code),
status=>'unknown'
};
}
my $task = { type=>'device',
my $task = {
type=>'device',
monitor=>$monitor,
address=>$device->{appliance}->address(),
function=>$function
};
if ( $limit )
{
if ( $limit ) {
$task->{limit} = $limit
}
my $task_list = $device->{$event.'_list'};
if ( !$task_list )
{
if ( !$task_list ) {
$task_list = $device->{$event.'_list'} = [];
}
push( @$task_list, $task );
}
push @$task_list, $task;
} # end sub addToDeviceList
sub addToMonitorList
{
sub addToMonitorList {
my $monitor = shift;
my $event = shift;
my $unit_code = shift;
@ -421,59 +376,54 @@ sub addToMonitorList
my $limit = shift;
Debug("Adding to monitor list, uc:$unit_code, ev:$event, mo:".$monitor->{Id}
.", fu:$function, li:$limit\n"
.", fu:$function, li:$limit"
);
my $device = $device_hash{$unit_code};
if ( !$device )
{
$device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ),
if ( !$device ) {
$device = $device_hash{$unit_code} = {
appliance=>$x10->Appliance(unit_code=>$unit_code),
status=>'unknown'
};
}
my $task = { type=>'monitor',
my $task = {
type=>'monitor',
device=>$device,
id=>$monitor->{Id},
function=>$function
};
if ( $limit )
{
if ( $limit ) {
$task->{limit} = $limit;
}
my $task_list = $monitor->{$event.'_list'};
if ( !$task_list )
{
if ( !$task_list ) {
$task_list = $monitor->{$event.'_list'} = [];
}
push( @$task_list, $task );
}
push @$task_list, $task;
} # end sub addToMonitorList
sub loadTasks
{
sub loadTasks {
%monitor_hash = ();
Debug( "Loading tasks\n" );
Debug('Loading tasks');
# Clear out all old device task lists
foreach my $unit_code ( sort( keys(%device_hash) ) )
{
foreach my $unit_code ( sort keys(%device_hash) ) {
my $device = $device_hash{$unit_code};
$device->{ON_list} = [];
$device->{OFF_list} = [];
}
my $sql = "SELECT M.*,T.* from Monitors as M
my $sql = 'SELECT M.*,T.* FROM Monitors as M
INNER JOIN TriggersX10 as T on (M.Id = T.MonitorId)
WHERE find_in_set( M.Function, 'Modect,Record,Mocord,Nodect' )
AND M.Enabled = 1
AND find_IN_set( 'X10', M.Triggers )"
;
WHERE find_in_set(M.`Function`, \'Modect,Record,Mocord,Nodect\')
AND M.`Enabled` = 1
AND find_IN_set(\'X10\', M.Triggers)';
my $sth = $dbh->prepare_cached( $sql )
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute()
or Fatal("Can't execute: ".$sth->errstr());
while( my $monitor = $sth->fetchrow_hashref() )
{
while( my $monitor = $sth->fetchrow_hashref() ) {
# Check shared memory ok
if ( !zmMemVerify($monitor) ) {
zmMemInvalidate($monitor);
@ -482,301 +432,235 @@ sub loadTasks
$monitor_hash{$monitor->{Id}} = $monitor;
if ( $monitor->{Activation} )
{
Debug( "$monitor->{Name} has active string '$monitor->{Activation}'\n" );
foreach my $code_string ( split( /,/, $monitor->{Activation} ) )
{
if ( $monitor->{Activation} ) {
Debug("$monitor->{Name} has active string '$monitor->{Activation}'");
foreach my $code_string ( split(',', $monitor->{Activation}) ) {
#Debug( "Code string: $code_string\n" );
my ( $invert, $unit_code, $modifier, $limit )
= ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ );
$limit = 0 if ( !$limit );
if ( $unit_code )
{
if ( !$modifier || $modifier eq '+' )
{
$limit = 0 if !$limit;
if ( $unit_code ) {
if ( !$modifier || $modifier eq '+' ) {
addToDeviceList( $unit_code,
'ON',
$monitor,
!$invert ? 'start_active'
: 'stop_active',
(!$invert ? 'start_active' : 'stop_active'),
$limit
);
}
if ( !$modifier || $modifier eq '-' )
{
if ( !$modifier || $modifier eq '-' ) {
addToDeviceList( $unit_code,
'OFF',
$monitor,
!$invert ? 'stop_active'
: 'start_active',
(!$invert ? 'stop_active' : 'start_active'),
$limit
);
}
} # end if unit_code
} # end foreach code_string
}
}
}
if ( $monitor->{AlarmInput} )
{
Debug( "$monitor->{Name} has alarm input string '$monitor->{AlarmInput}'\n" );
foreach my $code_string ( split( /,/, $monitor->{AlarmInput} ) )
{
if ( $monitor->{AlarmInput} ) {
Debug("$monitor->{Name} has alarm input string '$monitor->{AlarmInput}'");
foreach my $code_string ( split(',', $monitor->{AlarmInput}) ) {
#Debug( "Code string: $code_string\n" );
my ( $invert, $unit_code, $modifier, $limit )
= ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ );
$limit = 0 if ( !$limit );
if ( $unit_code )
{
if ( !$modifier || $modifier eq '+' )
{
$limit = 0 if !$limit;
if ( $unit_code ) {
if ( !$modifier || $modifier eq '+' ) {
addToDeviceList( $unit_code,
'ON',
$monitor,
!$invert ? 'start_alarm'
: 'stop_alarm',
(!$invert ? 'start_alarm' : 'stop_alarm'),
$limit
);
}
if ( !$modifier || $modifier eq '-' )
{
if ( !$modifier || $modifier eq '-' ) {
addToDeviceList( $unit_code,
'OFF',
$monitor,
!$invert ? 'stop_alarm'
: 'start_alarm',
(!$invert ? 'stop_alarm' : 'start_alarm'),
$limit
);
}
}
}
}
if ( $monitor->{AlarmOutput} )
{
Debug( "$monitor->{Name} has alarm output string '$monitor->{AlarmOutput}'\n" );
foreach my $code_string ( split( /,/, $monitor->{AlarmOutput} ) )
{
} # end if unit_code
} # end foreach code_string
} # end if AlarmInput
if ( $monitor->{AlarmOutput} ) {
Debug("$monitor->{Name} has alarm output string '$monitor->{AlarmOutput}'");
foreach my $code_string ( split( ',', $monitor->{AlarmOutput} ) ) {
#Debug( "Code string: $code_string\n" );
my ( $invert, $unit_code, $modifier, $limit )
= ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ );
$limit = 0 if ( !$limit );
if ( $unit_code )
{
if ( !$modifier || $modifier eq '+' )
{
$limit = 0 if !$limit;
if ( $unit_code ) {
if ( !$modifier || $modifier eq '+' ) {
addToMonitorList( $monitor,
'ON',
$unit_code,
!$invert ? 'on'
: 'off',
(!$invert ? 'on' : 'off'),
$limit
);
}
if ( !$modifier || $modifier eq '-' )
{
if ( !$modifier || $modifier eq '-' ) {
addToMonitorList( $monitor,
'OFF',
$unit_code,
!$invert ? 'off'
: 'on',
(!$invert ? 'off' : 'on'),
$limit
);
}
}
}
}
} # end if unit_code
} # end foreach code_string
} # end if AlarmOutput
zmMemInvalidate($monitor);
}
}
} # end sub loadTasks
sub addPendingTask
{
sub addPendingTask {
my $task = shift;
# Check whether we are just extending a previous pending task
# and remove it if it's there
foreach my $activation_time ( sort(keys(%pending_tasks) ) )
{
foreach my $activation_time ( sort keys(%pending_tasks) ) {
my $pending_list = $pending_tasks{$activation_time};
my $new_pending_list = [];
foreach my $pending_task ( @$pending_list )
{
if ( $task->{type} ne $pending_task->{type} )
{
foreach my $pending_task ( @$pending_list ) {
if ( $task->{type} ne $pending_task->{type} ) {
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} ))
{
push( @$new_pending_list, $pending_task )
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()
)
|| ( $task->{function} ne $pending_task->{function} )
)
{
push( @$new_pending_list, $pending_task )
) {
push @$new_pending_list, $pending_task;
}
}
}
if ( @$new_pending_list )
{
} # end switch task->type
} # end foreach pending_task
if ( @$new_pending_list ) {
$pending_tasks{$activation_time} = $new_pending_list;
} else {
delete $pending_tasks{$activation_time};
}
else
{
delete( $pending_tasks{$activation_time} );
}
}
} # end foreach activation_time
my $end_time = time() + $task->{limit};
my $pending_list = $pending_tasks{$end_time};
if ( !$pending_list )
{
if ( !$pending_list ) {
$pending_list = $pending_tasks{$end_time} = [];
}
my $pending_task;
if ( $task->{type} eq 'device' )
{
$pending_task = { type=>$task->{type},
if ( $task->{type} eq 'device' ) {
$pending_task = {
type=>$task->{type},
monitor=>$task->{monitor},
function=>$task->{function}
};
$pending_task->{function} =~ s/start/stop/;
}
elsif ( $task->{type} eq 'monitor' )
{
$pending_task = { type=>$task->{type},
} elsif ( $task->{type} eq 'monitor' ) {
$pending_task = {
type=>$task->{type},
device=>$task->{device},
function=>$task->{function}
};
$pending_task->{function} =~ s/on/off/;
}
push( @$pending_list, $pending_task );
}
push @$pending_list, $pending_task;
} # end sub addPendingTask
sub processTask
{
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 ( $instruction eq 'start' )
{
if ( $class eq 'active' ) {
if ( $instruction eq 'start' ) {
zmMonitorEnable($task->{monitor});
if ( $task->{limit} )
{
if ( $task->{limit} ) {
addPendingTask($task);
}
}
elsif( $instruction eq 'stop' )
{
} elsif( $instruction eq 'stop' ) {
zmMonitorDisable($task->{monitor});
}
}
elsif( $class eq 'alarm' )
{
if ( $instruction eq 'start' )
{
zmTriggerEventOn( $task->{monitor},
} elsif( $class eq 'alarm' ) {
if ( $instruction eq 'start' ) {
zmTriggerEventOn(
$task->{monitor},
0,
main::CAUSE_STRING,
$task->{address}
);
if ( $task->{limit} )
{
if ( $task->{limit} ) {
addPendingTask($task);
}
}
elsif( $instruction eq 'stop' )
{
} elsif( $instruction eq 'stop' ) {
zmTriggerEventCancel($task->{monitor});
}
}
}
elsif( $task->{type} eq 'monitor' )
{
if ( $task->{function} eq 'on' )
{
} # end switch class
} elsif( $task->{type} eq 'monitor' ) {
if ( $task->{function} eq 'on' ) {
$task->{device}->{appliance}->on();
if ( $task->{limit} )
{
if ( $task->{limit} ) {
addPendingTask($task);
}
}
elsif ( $task->{function} eq 'off' )
{
} elsif ( $task->{function} eq 'off' ) {
$task->{device}->{appliance}->off();
}
}
}
sub dPrint
{
sub dPrint {
my $dbg_level = shift;
if ( fileno(CLIENT) )
{
if ( fileno(CLIENT) ) {
print CLIENT @_
}
if ( $dbg_level == ZoneMinder::Logger::DEBUG )
{
if ( $dbg_level == ZoneMinder::Logger::DEBUG ) {
Debug(@_);
}
elsif ( $dbg_level == ZoneMinder::Logger::INFO )
{
} elsif ( $dbg_level == ZoneMinder::Logger::INFO ) {
Info(@_);
}
elsif ( $dbg_level == ZoneMinder::Logger::WARNING )
{
} elsif ( $dbg_level == ZoneMinder::Logger::WARNING ) {
Warning(@_);
}
elsif ( $dbg_level == ZoneMinder::Logger::ERROR )
{
elsif ( $dbg_level == ZoneMinder::Logger::ERROR ) {
Error( @_ );
}
elsif ( $dbg_level == ZoneMinder::Logger::FATAL )
{
} elsif ( $dbg_level == ZoneMinder::Logger::FATAL ) {
Fatal( @_ );
}
}
sub x10listen
{
foreach my $event ( @_ )
{
sub x10listen {
foreach my $event ( @_ ) {
#print( Data::Dumper( $_ )."\n" );
if ( $event->house_code() eq $Config{ZM_X10_HOUSE_CODE} )
{
if ( $event->house_code() eq $Config{ZM_X10_HOUSE_CODE} ) {
my $unit_code = $event->unit_code();
my $device = $device_hash{$unit_code};
if ( !$device )
{
$device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ),
if ( !$device ) {
$device = $device_hash{$unit_code} = {
appliance=>$x10->Appliance(unit_code=>$unit_code),
status=>'unknown'
};
}
next if ( $event->func() !~ /(?:ON|OFF)/ );
$device->{status} = $event->func();
my $task_list = $device->{$event->func().'_list'};
if ( $task_list )
{
foreach my $task ( @$task_list )
{
if ( $task_list ) {
foreach my $task ( @$task_list ) {
processTask($task);
}
}
} # end if correct house code
Info('Got event - '.$event->as_string());
}
Info( "Got event - ".$event->as_string()."\n" );
}
}
} # end sub x10listen
1;
__END__

View File

@ -33,37 +33,33 @@
// Class used for storing a box, which is defined as a region
// defined by two coordinates
//
class Box
{
class Box {
private:
Coord lo, hi;
Coord size;
public:
inline Box()
{
}
inline Box() { }
explicit inline Box( int p_size ) : lo( 0, 0 ), hi ( p_size-1, p_size-1 ), size( Coord::Range( hi, lo ) ) { }
inline Box( int p_x_size, int p_y_size ) : lo( 0, 0 ), hi ( p_x_size-1, p_y_size-1 ), size( Coord::Range( hi, lo ) ) { }
inline Box( int lo_x, int lo_y, int hi_x, int hi_y ) : lo( lo_x, lo_y ), hi( hi_x, hi_y ), size( Coord::Range( hi, lo ) ) { }
inline Box( const Coord &p_lo, const Coord &p_hi ) : lo( p_lo ), hi( p_hi ), size( Coord::Range( hi, lo ) ) { }
inline const Coord &Lo() const { return( lo ); }
inline int LoX() const { return( lo.X() ); }
inline int LoY() const { return( lo.Y() ); }
inline const Coord &Hi() const { return( hi ); }
inline int HiX() const { return( hi.X() ); }
inline int HiY() const { return( hi.Y() ); }
inline const Coord &Size() const { return( size ); }
inline int Width() const { return( size.X() ); }
inline int Height() const { return( size.Y() ); }
inline int Area() const { return( size.X()*size.Y() ); }
inline const Coord &Lo() const { return lo; }
inline int LoX() const { return lo.X(); }
inline int LoY() const { return lo.Y(); }
inline const Coord &Hi() const { return hi; }
inline int HiX() const { return hi.X(); }
inline int HiY() const { return hi.Y(); }
inline const Coord &Size() const { return size; }
inline int Width() const { return size.X(); }
inline int Height() const { return size.Y(); }
inline int Area() const { return size.X()*size.Y(); }
inline const Coord Centre() const
{
inline const Coord Centre() const {
int mid_x = int(round(lo.X()+(size.X()/2.0)));
int mid_y = int(round(lo.Y()+(size.Y()/2.0)));
return( Coord( mid_x, mid_y ) );
return Coord( mid_x, mid_y );
}
inline bool Inside( const Coord &coord ) const
{

View File

@ -64,7 +64,9 @@ void zmLoadConfig() {
closedir(configSubFolder);
}
zmDbConnect();
if ( !zmDbConnect() ) {
Fatal("Can't connect to db. Can't continue.");
}
config.Load();
config.Assign();

View File

@ -25,8 +25,7 @@
//
// Class used for storing an x,y pair, i.e. a coordinate
//
class Coord
{
class Coord {
private:
int x, y;
@ -44,8 +43,7 @@ public:
inline int &Y() { return( y ); }
inline const int &Y() const { return( y ); }
inline static Coord Range( const Coord &coord1, const Coord &coord2 )
{
inline static Coord Range( const Coord &coord1, const Coord &coord2 ) {
Coord result( (coord1.x-coord2.x)+1, (coord1.y-coord2.y)+1 );
return( result );
}

View File

@ -259,6 +259,20 @@ Event::~Event() {
if ( frame_data.size() )
WriteDbFrames();
// update frame deltas to refer to video start time which may be a few frames before event start
struct timeval video_offset = {0};
struct timeval video_start_time = monitor->GetVideoWriterStartTime();
if (video_start_time.tv_sec > 0) {
timersub(&video_start_time, &start_time, &video_offset);
Debug(1, "Updating frames delta by %d sec %d usec",
video_offset.tv_sec, video_offset.tv_usec);
UpdateFramesDelta(video_offset.tv_sec + video_offset.tv_usec*1e-6);
}
else {
Debug(3, "Video start_time %d sec %d usec not valid -- frame deltas not updated",
video_start_time.tv_sec, video_start_time.tv_usec);
}
// Should not be static because we might be multi-threaded
char sql[ZM_SQL_LGE_BUFSIZ];
snprintf(sql, sizeof(sql),
@ -472,7 +486,7 @@ void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, str
// neccessarily be of the motion. But some events are less than 10 frames,
// so I am changing this to 1, but we should overwrite it later with a better snapshot.
if ( frames == 1 ) {
WriteFrameImage(images[i], *(timestamps[i]), snapshot_file);
WriteFrameImage(images[i], *(timestamps[i]), snapshot_file.c_str());
}
struct DeltaTimeval delta_time;
@ -553,6 +567,27 @@ void Event::WriteDbFrames() {
db_mutex.unlock();
} // end void Event::WriteDbFrames()
// Subtract an offset time from frames deltas to match with video start time
void Event::UpdateFramesDelta(double offset) {
char sql[ZM_SQL_MED_BUFSIZ];
if (offset == 0.0) return;
// the table is set to auto update timestamp so we force it to keep current value
snprintf(sql, sizeof(sql),
"UPDATE Frames SET timestamp = timestamp, Delta = Delta - (%.4f) WHERE EventId = %" PRIu64,
offset, id);
db_mutex.lock();
if (mysql_query(&dbconn, sql)) {
db_mutex.unlock();
Error("Can't update frames: %s, sql was %s", mysql_error(&dbconn), sql);
return;
}
db_mutex.unlock();
Info("Updating frames delta by %0.2f sec to match video file", offset);
}
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");
@ -562,10 +597,6 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a
frames++;
bool write_to_db = false;
FrameType frame_type = score>0?ALARM:(score<0?BULK:NORMAL);
// < 0 means no motion detection is being done.
if ( score < 0 )
score = 0;
if ( save_jpegs & 1 ) {
static char event_file[PATH_MAX];
@ -579,9 +610,14 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a
// If this is the first frame, we should add a thumbnail to the event directory
if ( (frames == 1) || (score > (int)max_score) ) {
write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it.
WriteFrameImage(image, timestamp, snapshot_file);
WriteFrameImage(image, timestamp, snapshot_file.c_str());
}
FrameType frame_type = score>0?ALARM:(score<0?BULK:NORMAL);
// < 0 means no motion detection is being done.
if ( score < 0 )
score = 0;
// We are writing an Alarm frame
if ( frame_type == ALARM ) {
// The first frame with a score will be the frame that alarmed the event
@ -590,17 +626,30 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a
alarm_frame_written = true;
WriteFrameImage(image, timestamp, alarm_file.c_str());
}
if ( videowriter != NULL ) {
WriteFrameVideo(image, timestamp, videowriter);
}
alarm_frames++;
struct DeltaTimeval delta_time;
DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2);
tot_score += score;
if ( score > (int)max_score )
max_score = score;
if ( alarm_image ) {
if ( save_jpegs & 2 ) {
static char event_file[PATH_MAX];
snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path.c_str(), frames);
Debug(1, "Writing analysis frame %d", frames);
if ( ! WriteFrameImage(alarm_image, timestamp, event_file, true) ) {
Error("Failed to write analysis frame image");
}
}
}
}
bool db_frame = ( frame_type != BULK ) || (frames==1) || ((frames%config.bulk_frame_interval)==0) ;
if ( db_frame ) {
static char sql[ZM_SQL_MED_BUFSIZ];
struct DeltaTimeval delta_time;
DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2);
frame_data.push(new Frame(id, frames, frame_type, timestamp, delta_time, score));
if ( write_to_db || ( frame_data.size() > 20 ) ) {
@ -635,23 +684,4 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a
end_time = timestamp;
// We are writing an Alarm frame
if ( frame_type == ALARM ) {
alarm_frames++;
tot_score += score;
if ( score > (int)max_score )
max_score = score;
if ( alarm_image ) {
if ( save_jpegs & 2 ) {
static char event_file[PATH_MAX];
snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path.c_str(), frames);
Debug(1, "Writing analysis frame %d", frames);
if ( ! WriteFrameImage(alarm_image, timestamp, event_file, true) ) {
Error("Failed to write analysis frame image");
}
}
}
} // end if frame_type == ALARM
} // end void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image)

View File

@ -94,8 +94,6 @@ class Event {
std::string snapshot_file;
std::string alarm_file;
VideoStore *videoStore;
std::string snapshot_file;
std::string alarm_file;
VideoWriter* videowriter;
char video_name[PATH_MAX];
@ -137,6 +135,7 @@ class Event {
private:
void AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps );
void WriteDbFrames();
void UpdateFramesDelta(double offset);
public:
static const char *getSubPath( struct tm *time ) {

View File

@ -117,7 +117,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
snprintf(sql, sizeof(sql),
"SELECT `MonitorId`, `StorageId`, `Frames`, unix_timestamp( `StartTime` ) AS StartTimestamp, "
"(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events`.`Id`) AS Duration, "
"`DefaultVideo`, `Scheme`, `SaveJPEGs` FROM `Events` WHERE `Id` = %" PRIu64, event_id);
"`DefaultVideo`, `Scheme`, `SaveJPEGs`, `Orientation`+0 FROM `Events` WHERE `Id` = %" PRIu64, event_id);
if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn));
@ -160,6 +160,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
event_data->scheme = Storage::SHALLOW;
}
event_data->SaveJPEGs = dbrow[7] == NULL ? 0 : atoi(dbrow[7]);
event_data->Orientation = (Monitor::Orientation)(dbrow[8] == NULL ? 0 : atoi(dbrow[8]));
mysql_free_result(result);
Storage * storage = new Storage(event_data->storage_id);
@ -703,6 +704,34 @@ Debug(1, "Loading image");
Error("Failed getting a frame.");
return false;
}
// when stored as an mp4, we just have the rotation as a flag in the headers
// so we need to rotate it before outputting
if (
(monitor->GetOptVideoWriter() == Monitor::H264PASSTHROUGH)
and
(event_data->Orientation != Monitor::ROTATE_0)
) {
Debug(2, "Rotating image %d", event_data->Orientation);
switch ( event_data->Orientation ) {
case Monitor::ROTATE_0 :
// No action required
break;
case Monitor::ROTATE_90 :
case Monitor::ROTATE_180 :
case Monitor::ROTATE_270 :
image->Rotate((event_data->Orientation-1)*90);
break;
case Monitor::FLIP_HORI :
case Monitor::FLIP_VERT :
image->Flip(event_data->Orientation==Monitor::FLIP_HORI);
break;
default:
Error("Invalid Orientation: %d", event_data->Orientation);
}
} else {
Debug(2, "Not Rotating image %d", event_data->Orientation);
} // end if have rotation
} else {
Error("Unable to get a frame");
return false;

View File

@ -66,6 +66,7 @@ class EventStream : public StreamBase {
char video_file[PATH_MAX];
Storage::Schemes scheme;
int SaveJPEGs;
Monitor::Orientation Orientation;
};
protected:

View File

@ -81,7 +81,7 @@ void FFMPEGInit() {
av_log_set_callback(log_libav_callback);
Info("Enabling ffmpeg logs, as LOG_DEBUG+LOG_FFMPEG are enabled in options");
} else {
Info("Not enabling ffmpeg logs, as LOG_FFMPEG and/or LOG_DEBUG is disabled in options, or this monitor not part of your debug targets");
Info("Not enabling ffmpeg logs, as LOG_FFMPEG and/or LOG_DEBUG is disabled in options, or this monitor is not part of your debug targets");
av_log_set_level(AV_LOG_QUIET);
}
#if !LIBAVFORMAT_VERSION_CHECK(58, 9, 0, 64, 0)
@ -291,8 +291,9 @@ static void zm_log_fps(double d, const char *postfix) {
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
void zm_dump_codecpar ( const AVCodecParameters *par ) {
Debug(1, "Dumping codecpar codec_type(%d) codec_id(%d %s) codec_tag(%d) width(%d) height(%d) bit_rate(%d) format(%d = %s)",
Debug(1, "Dumping codecpar codec_type(%d %s) codec_id(%d %s) codec_tag(%" PRIu32 ") width(%d) height(%d) bit_rate(%" PRIu64 ") format(%d %s)",
par->codec_type,
av_get_media_type_string(par->codec_type),
par->codec_id,
avcodec_get_name(par->codec_id),
par->codec_tag,
@ -300,7 +301,7 @@ void zm_dump_codecpar ( const AVCodecParameters *par ) {
par->height,
par->bit_rate,
par->format,
((AVPixelFormat)par->format == AV_PIX_FMT_NONE ? "none" : av_get_pix_fmt_name((AVPixelFormat)par->format))
(((AVPixelFormat)par->format == AV_PIX_FMT_NONE) ? "none" : av_get_pix_fmt_name((AVPixelFormat)par->format))
);
}
#endif

View File

@ -402,19 +402,19 @@ int FfmpegCamera::OpenFfmpeg() {
Debug(1, "Selected hw_pix_fmt %d %s",
hw_pix_fmt, av_get_pix_fmt_name(hw_pix_fmt));
mVideoCodecContext->get_format = get_hw_format;
ret = av_hwdevice_ctx_create(&hw_device_ctx, type,
(hwaccel_device != "" ? hwaccel_device.c_str(): NULL), NULL, 0);
if ( ret < 0 ) {
Error("Failed to create hwaccel device.");
return -1;
}
Error("Failed to create hwaccel device. %s",av_make_error_string(ret).c_str());
hw_pix_fmt = AV_PIX_FMT_NONE;
} else {
Debug(1, "Created hwdevice for %s", hwaccel_device.c_str());
mVideoCodecContext->get_format = get_hw_format;
mVideoCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx);
hwFrame = zm_av_frame_alloc();
}
} else {
Debug(1, "Failed to setup hwaccel.");
Debug(1, "Failed to find suitable hw_pix_fmt.");
}
#else
Debug(1, "AVCodec not new enough for hwaccel");
@ -422,7 +422,7 @@ int FfmpegCamera::OpenFfmpeg() {
#else
Warning("HWAccel support not compiled in.");
#endif
} // end if hwacel_name
} // end if hwaccel_name
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0)
ret = avcodec_open(mVideoCodecContext, mVideoCodec);
@ -542,7 +542,6 @@ int FfmpegCamera::Close() {
return 0;
} // end FfmpegCamera::Close
int FfmpegCamera::transfer_to_image(
Image &image,
AVFrame *output_frame,
@ -557,9 +556,12 @@ int FfmpegCamera::transfer_to_image(
return -1;
}
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
// From what I've read, we should align the linesizes to 32bit so that ffmpeg can use SIMD instructions too.
int size = av_image_fill_arrays(
output_frame->data, output_frame->linesize,
directbuffer, imagePixFormat, width, height, 32);
directbuffer, imagePixFormat, width, height,
(AV_PIX_FMT_RGBA == imagePixFormat ? 32 : 1)
);
if ( size < 0 ) {
Error("Problem setting up data pointers into image %s",
av_make_error_string(size).c_str());
@ -598,19 +600,31 @@ int FfmpegCamera::transfer_to_image(
mConvertContext, input_frame->data, input_frame->linesize,
0, mVideoCodecContext->height,
output_frame->data, output_frame->linesize);
if ( ret <= 0 ) {
Error("Unable to convert format %u %s linesize %d height %d to format %u %s linesize %d at frame %d codec %u %s : code: %d",
if ( ret < 0 ) {
Error("Unable to convert format %u %s linesize %d,%d height %d to format %u %s linesize %d,%d at frame %d codec %u %s lines %d: code: %d",
input_frame->format, av_get_pix_fmt_name((AVPixelFormat)input_frame->format),
input_frame->linesize, mVideoCodecContext->height,
input_frame->linesize[0], input_frame->linesize[1], mVideoCodecContext->height,
imagePixFormat,
av_get_pix_fmt_name(imagePixFormat),
output_frame->linesize,
output_frame->linesize[0], output_frame->linesize[1],
frameCount,
mVideoCodecContext->pix_fmt, av_get_pix_fmt_name(mVideoCodecContext->pix_fmt),
mVideoCodecContext->height,
ret
);
return -1;
}
Debug(4, "Able to convert format %u %s linesize %d,%d height %d to format %u %s linesize %d,%d at frame %d codec %u %s %dx%d ",
input_frame->format, av_get_pix_fmt_name((AVPixelFormat)input_frame->format),
input_frame->linesize[0], input_frame->linesize[1], mVideoCodecContext->height,
imagePixFormat,
av_get_pix_fmt_name(imagePixFormat),
output_frame->linesize[0], output_frame->linesize[1],
frameCount,
mVideoCodecContext->pix_fmt, av_get_pix_fmt_name(mVideoCodecContext->pix_fmt),
output_frame->width,
output_frame->height
);
#else // HAVE_LIBSWSCALE
Fatal("You must compile ffmpeg with the --enable-swscale "
"option to use ffmpeg cameras");

View File

@ -53,6 +53,7 @@ class FfmpegCamera : public Camera {
AVCodec *mAudioCodec;
AVFrame *mRawFrame;
AVFrame *mFrame;
_AVPIXELFORMAT imagePixFormat;
AVFrame *input_frame; // Use to point to mRawFrame or hwFrame;

View File

@ -10,6 +10,7 @@ FFmpeg_Input::FFmpeg_Input() {
FFMPEGInit();
streams = NULL;
frame = NULL;
last_seek_request = -1;
}
FFmpeg_Input::~FFmpeg_Input() {
@ -22,6 +23,17 @@ FFmpeg_Input::~FFmpeg_Input() {
}
}
int FFmpeg_Input::Open( AVStream * video_in_stream, AVStream * audio_in_stream ) {
video_stream_id = video_in_stream->index;
int max_stream_index = video_in_stream->index;
if ( audio_in_stream ) {
max_stream_index = video_in_stream->index > audio_in_stream->index ? video_in_stream->index : audio_in_stream->index;
audio_stream_id = audio_in_stream->index;
}
streams = new stream[max_stream_index];
}
int FFmpeg_Input::Open(const char *filepath) {
int error;
@ -127,7 +139,6 @@ int FFmpeg_Input::Close( ) {
} // end int FFmpeg_Input::Close()
AVFrame *FFmpeg_Input::get_frame(int stream_id) {
Debug(1, "Getting frame from stream %d", stream_id);
int frameComplete = false;
AVPacket packet;
@ -166,12 +177,14 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id) {
frame = zm_av_frame_alloc();
}
ret = zm_send_packet_receive_frame(context, frame, packet);
if ( ret <= 0 ) {
if ( ret < 0 ) {
Error("Unable to decode frame at frame %d: %s, continuing",
streams[packet.stream_index].frame_count, av_make_error_string(ret).c_str());
zm_av_packet_unref(&packet);
av_frame_free(&frame);
continue;
} else {
zm_dump_frame(frame, "resulting frame");
}
frameComplete = 1;
@ -203,7 +216,18 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) {
get_frame(stream_id);
} // end if ! frame
if ( frame->pts > seek_target ) {
if ( !frame ) {
Warning("Unable to get frame.");
return NULL;
}
if (
(last_seek_request >= 0)
&&
(last_seek_request > seek_target )
&&
(frame->pts > seek_target)
) {
zm_dump_frame(frame, "frame->pts > seek_target, seek backwards");
// our frame must be beyond our seek target. so go backwards to before it
if ( ( ret = av_seek_frame(input_format_context, stream_id, seek_target,
@ -217,6 +241,8 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) {
zm_dump_frame(frame, "frame->pts > seek_target, got");
} // end if frame->pts > seek_target
last_seek_request = seek_target;
// Seeking seems to typically seek to a keyframe, so then we have to decode until we get the frame we want.
if ( frame->pts <= seek_target ) {
zm_dump_frame(frame, "pts <= seek_target");

View File

@ -42,6 +42,7 @@ class FFmpeg_Input {
int audio_stream_id;
AVFormatContext *input_format_context;
AVFrame *frame;
int64_t last_seek_request;
};
#endif

View File

@ -165,8 +165,10 @@ Image::Image( const AVFrame *frame ) {
width = frame->width;
height = frame->height;
pixels = width*height;
colours = ZM_COLOUR_RGB32;
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
size = pixels*colours;
buffer = 0;
holdbuffer = 0;

View File

@ -157,6 +157,7 @@ void Logger::initialise(const std::string &id, const Options &options) {
if ( options.mTerminalLevel != NOOPT )
tempTerminalLevel = options.mTerminalLevel;
// DEBUG1 == 1. So >= DEBUG1, we set to DEBUG9?! Why?
if ( options.mDatabaseLevel != NOOPT )
tempDatabaseLevel = options.mDatabaseLevel;
else
@ -358,7 +359,7 @@ Logger::Level Logger::databaseLevel(Logger::Level databaseLevel) {
if ( databaseLevel > NOOPT ) {
databaseLevel = limit(databaseLevel);
if ( mDatabaseLevel != databaseLevel ) {
if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) {
if ( (databaseLevel > NOLOG) && (mDatabaseLevel <= NOLOG) ) { // <= NOLOG would be NOOPT
if ( !zmDbConnect() ) {
databaseLevel = NOLOG;
}
@ -534,8 +535,11 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con
fflush(stdout);
}
if ( level <= mFileLevel ) {
if ( !mLogFileFP )
if ( !mLogFileFP ) {
log_mutex.unlock();
openFile();
log_mutex.lock();
}
if ( mLogFileFP ) {
fputs(logString, mLogFileFP);
if ( mFlush )
@ -553,8 +557,10 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con
if ( level <= mDatabaseLevel ) {
if ( !db_mutex.trylock() ) {
char escapedString[(strlen(syslogStart)*2)+1];
mysql_real_escape_string(&dbconn, escapedString, syslogStart, strlen(syslogStart));
char sql[ZM_SQL_MED_BUFSIZ];
snprintf(sql, sizeof(sql),
"INSERT INTO `Logs` "
"( `TimeKey`, `Component`, `ServerId`, `Pid`, `Level`, `Code`, `Message`, `File`, `Line` )"
@ -572,7 +578,7 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con
} else {
Level tempDatabaseLevel = mDatabaseLevel;
databaseLevel(NOLOG);
Error("Can't insert log entry: sql(%s) error(%s)", sql, mysql_error(&dbconn));
Error("Can't insert log entry: sql(%s) error(%s)", syslogStart, mysql_error(&dbconn));
databaseLevel(tempDatabaseLevel);
}
db_mutex.unlock();

View File

@ -288,6 +288,8 @@ Monitor::Monitor()
orientation(ROTATE_0),
deinterlacing(0),
deinterlacing_value(0),
decoder_hwaccel_name(""),
decoder_hwaccel_device(""),
videoRecording(0),
rtsp_describe(0),
@ -410,6 +412,12 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++;
alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++;
if (analysis_fps > 0.0) {
uint64_t usec = round(1000000*pre_event_count/analysis_fps);
video_buffer_duration.tv_sec = usec/1000000;
video_buffer_duration.tv_usec = usec % 1000000;
}
if ( dbrow[col] )
strncpy(device, dbrow[col], sizeof(device)-1);
else
@ -545,6 +553,8 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
last_signal = false;
camera = NULL;
uint64_t image_size = width * height * colours;
mem_size = sizeof(SharedData)
+ sizeof(TriggerData)
+ sizeof(VideoStoreData) //Information to pass back to the capture process
@ -556,7 +566,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
sizeof(mem_size),
sizeof(SharedData), sizeof(TriggerData), sizeof(VideoStoreData),
(image_buffer_count*sizeof(struct timeval)),
image_buffer_count, camera->ImageSize(), (image_buffer_count*camera->ImageSize()),
image_buffer_count, image_size, (image_buffer_count*image_size),
mem_size);
mem_ptr = NULL;
@ -582,7 +592,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
}
}
//this0>delta_image( width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE ),
//this->delta_image( width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE ),
//ref_image( width, height, p_camera->Colours(), p_camera->SubpixelOrder() ),
Debug(1, "Loaded monitor %d(%s), %d zones", id, name, n_zones);
getCamera();

View File

@ -257,10 +257,10 @@ protected:
Orientation orientation; // Whether the image has to be rotated at all
unsigned int deinterlacing;
unsigned int deinterlacing_value;
bool videoRecording;
bool rtsp_describe;
std::string decoder_hwaccel_name;
std::string decoder_hwaccel_device;
bool videoRecording;
bool rtsp_describe;
int savejpegs;
int colours;
@ -297,6 +297,7 @@ protected:
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_limit; // Target framerate for video analysis
struct timeval video_buffer_duration; // How long a video segment to keep in buffer (set only if analysis fps != 0 )
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
@ -461,7 +462,11 @@ public:
uint64_t GetVideoWriterEventId() const { return video_store_data->current_event; }
void SetVideoWriterEventId( uint64_t p_event_id ) { video_store_data->current_event = p_event_id; }
struct timeval GetVideoWriterStartTime() const { return video_store_data->recording; }
void SetVideoWriterStartTime(struct timeval &t) { video_store_data->recording = t; }
unsigned int GetPreEventCount() const { return pre_event_count; };
struct timeval GetVideoBufferDuration() const { return video_buffer_duration; };
int GetImageBufferCount() const { return image_buffer_count; };
State GetState() const;
int GetImage( int index=-1, int scale=100 );

View File

@ -432,7 +432,7 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) {
Warning("Frame send time %d msec too slow, throttling maxfps to %.2f",
frameSendTime, maxfps);
}
}
} // Not mpeg
last_frame_sent = TV_2_FLOAT(now);
return true;
} // end bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp )
@ -478,6 +478,7 @@ void MonitorStream::runStream() {
Image *paused_image = NULL;
struct timeval paused_timestamp;
if ( connkey && ( playback_buffer > 0 ) ) {
// 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id
const int max_swap_len_suffix = 15;
@ -486,8 +487,6 @@ void MonitorStream::runStream() {
int subfolder2_length = snprintf(NULL, 0, "/zmswap-q%06d", connkey) + 1;
int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length;
if ( connkey && ( playback_buffer > 0 ) ) {
if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) {
Error("Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX);
} else {
@ -612,8 +611,9 @@ void MonitorStream::runStream() {
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;
}
@ -626,9 +626,17 @@ void MonitorStream::runStream() {
SwapImage *swap_image = &temp_image_buffer[temp_read_index];
// Send the next frame
if ( !sendFrame( temp_image_buffer[temp_read_index].file_name, &temp_image_buffer[temp_read_index].timestamp ) )
if ( !sendFrame(
temp_image_buffer[temp_read_index].file_name,
&temp_image_buffer[temp_read_index].timestamp
) ) {
zm_terminate = true;
memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) );
}
memcpy(
&last_frame_timestamp,
&(swap_image->timestamp),
sizeof(last_frame_timestamp)
);
// frame_sent = true;
step = 0;
} else {
@ -636,12 +644,13 @@ void MonitorStream::runStream() {
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
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;
}
// frame_sent = true;
}
} // end if (!paused) or step or paused
@ -658,17 +667,9 @@ void MonitorStream::runStream() {
}
} // end if ( buffered_playback && delayed )
if ( last_read_index != monitor->shared_data->last_write_index ) {
// have a new image to send
int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; // % shouldn't be neccessary
#if 0
// I don't know what this is about
ZMPacket *snap = &monitor->image_buffer[index];
if ( tvCmp(last_frame_time, *(snap->timestamp)) ) {
last_read_index = monitor->shared_data->last_write_index;
Debug(2, "index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)",
index, frame_mod, frame_count, paused, delayed );
#endif
if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) {
if ( !paused && !delayed ) {
last_read_index = monitor->shared_data->last_write_index;
@ -840,8 +841,10 @@ void MonitorStream::SingleImage( int scale ) {
}
snap_image->EncodeJpeg(img_buffer, &img_buffer_size);
fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size );
fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" );
fprintf(stdout,
"Content-Length: %d\r\n"
"Content-Type: image/jpeg\r\n\r\n",
img_buffer_size);
fwrite(img_buffer, img_buffer_size, 1, stdout);
}
@ -859,12 +862,13 @@ void MonitorStream::SingleImageRaw( int scale ) {
monitor->TimestampImage(snap_image, snap->timestamp);
}
fprintf( stdout, "Content-Length: %d\r\n", snap_image->Size() );
fprintf( stdout, "Content-Type: image/x-rgb\r\n\r\n" );
fprintf(stdout,
"Content-Length: %d\r\n"
"Content-Type: image/x-rgb\r\n\r\n",
snap_image->Size());
fwrite(snap_image->Buffer(), snap_image->Size(), 1, stdout);
}
#ifdef HAVE_ZLIB_H
void MonitorStream::SingleImageZip(int scale) {
unsigned long img_buffer_size = 0;
@ -884,8 +888,10 @@ void MonitorStream::SingleImageZip( int scale ) {
}
snap_image->Zip(img_buffer, &img_buffer_size);
fprintf( stdout, "Content-Length: %ld\r\n", img_buffer_size );
fprintf( stdout, "Content-Type: image/x-rgbz\r\n\r\n" );
fprintf(stdout,
"Content-Length: %ld\r\n"
"Content-Type: image/x-rgbz\r\n\r\n",
img_buffer_size);
fwrite(img_buffer, img_buffer_size, 1, stdout);
}
#endif // HAVE_ZLIB_H

View File

@ -235,6 +235,73 @@ void zm_packetqueue::clearQueue() {
mutex.unlock();
}
// clear queue keeping only specified duration of video -- return number of pkts removed
unsigned int zm_packetqueue::clearQueue(struct timeval *duration, int streamId) {
if ( pktQueue.empty() ) {
return 0;
}
struct timeval keep_from;
std::list<ZMPacket *>::reverse_iterator it;
it = pktQueue.rbegin();
struct timeval *t = (*it)->timestamp;
timersub(t, duration, &keep_from);
++it;
Debug(3, "Looking for frame before queue keep time with stream id (%d), queue has %d packets",
streamId, pktQueue.size());
for ( ; it != pktQueue.rend(); ++it) {
ZMPacket *zm_packet = *it;
AVPacket *av_packet = &(zm_packet->packet);
if (av_packet->stream_index == streamId
&& timercmp( zm_packet->timestamp, &keep_from, <= )) {
Debug(3, "Found frame before keep time with stream index %d at %d.%d",
av_packet->stream_index,
zm_packet->timestamp->tv_sec,
zm_packet->timestamp->tv_usec);
break;
}
}
if (it == pktQueue.rend()) {
Debug(1, "Didn't find a frame before queue preserve time. keeping all");
return 0;
}
Debug(3, "Looking for keyframe");
for ( ; it != pktQueue.rend(); ++it) {
ZMPacket *zm_packet = *it;
AVPacket *av_packet = &(zm_packet->packet);
if (av_packet->flags & AV_PKT_FLAG_KEY
&& av_packet->stream_index == streamId) {
Debug(3, "Found keyframe before start with stream index %d at %d.%d",
av_packet->stream_index,
zm_packet->timestamp->tv_sec,
zm_packet->timestamp->tv_usec );
break;
}
}
if ( it == pktQueue.rend() ) {
Debug(1, "Didn't find a keyframe before event starttime. keeping all" );
return 0;
}
unsigned int deleted_frames = 0;
ZMPacket *zm_packet = NULL;
while (distance(it, pktQueue.rend()) > 1) {
zm_packet = pktQueue.front();
pktQueue.pop_front();
packet_counts[zm_packet->packet.stream_index] -= 1;
delete zm_packet;
deleted_frames += 1;
}
zm_packet = NULL;
Debug(3, "Deleted %d frames", deleted_frames);
return deleted_frames;
}
unsigned int zm_packetqueue::size() {
return pktQueue.size();
}

View File

@ -54,6 +54,7 @@ class zm_packetqueue {
bool popVideoPacket(ZMPacket* packet);
bool popAudioPacket(ZMPacket* packet);
unsigned int clearQueue(unsigned int video_frames_to_keep, int stream_id);
unsigned int clearQueue(struct timeval *duration, int streamid);
void clearQueue();
void dumpQueue();
unsigned int size();

View File

@ -26,11 +26,9 @@
#include <cmath>
#endif
void Polygon::calcArea()
{
void Polygon::calcArea() {
double float_area = 0.0L;
for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ )
{
for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) {
double trap_area = ((coords[i].X()-coords[j].X())*((coords[i].Y()+coords[j].Y())))/2.0L;
float_area += trap_area;
//printf( "%.2f (%.2f)\n", float_area, trap_area );
@ -38,13 +36,11 @@ void Polygon::calcArea()
area = (int)round(fabs(float_area));
}
void Polygon::calcCentre()
{
void Polygon::calcCentre() {
if ( !area && n_coords )
calcArea();
double float_x = 0.0L, float_y = 0.0L;
for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ )
{
for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) {
float_x += ((coords[i].Y()-coords[j].Y())*((coords[i].X()*2)+(coords[i].X()*coords[j].X())+(coords[j].X()*2)));
float_y += ((coords[j].X()-coords[i].X())*((coords[i].Y()*2)+(coords[i].Y()*coords[j].Y())+(coords[j].Y()*2)));
}
@ -54,16 +50,14 @@ void Polygon::calcCentre()
centre = Coord( (int)round(float_x), (int)round(float_y) );
}
Polygon::Polygon( int p_n_coords, const Coord *p_coords ) : n_coords( p_n_coords )
{
Polygon::Polygon(int p_n_coords, const Coord *p_coords) : n_coords( p_n_coords ) {
coords = new Coord[n_coords];
int min_x = -1;
int max_x = -1;
int min_y = -1;
int max_y = -1;
for( int i = 0; i < n_coords; i++ )
{
for ( int i = 0; i < n_coords; i++ ) {
coords[i] = p_coords[i];
if ( min_x == -1 || coords[i].X() < min_x )
min_x = coords[i].X();
@ -79,38 +73,36 @@ Polygon::Polygon( int p_n_coords, const Coord *p_coords ) : n_coords( p_n_coords
calcCentre();
}
Polygon::Polygon( const Polygon &p_polygon ) : n_coords( p_polygon.n_coords ), extent( p_polygon.extent ), area( p_polygon.area ), centre( p_polygon.centre )
Polygon::Polygon( const Polygon &p_polygon ) :
n_coords(p_polygon.n_coords),
extent(p_polygon.extent),
area(p_polygon.area),
centre(p_polygon.centre)
{
coords = new Coord[n_coords];
for( int i = 0; i < n_coords; i++ )
{
for( int i = 0; i < n_coords; i++ ) {
coords[i] = p_polygon.coords[i];
}
}
Polygon &Polygon::operator=( const Polygon &p_polygon )
{
if ( n_coords < p_polygon.n_coords )
{
Polygon &Polygon::operator=( const Polygon &p_polygon ) {
if ( n_coords < p_polygon.n_coords ) {
delete[] coords;
coords = new Coord[p_polygon.n_coords];
}
n_coords = p_polygon.n_coords;
for( int i = 0; i < n_coords; i++ )
{
for ( int i = 0; i < n_coords; i++ ) {
coords[i] = p_polygon.coords[i];
}
extent = p_polygon.extent;
area = p_polygon.area;
centre = p_polygon.centre;
return( *this );
return *this ;
}
bool Polygon::isInside( const Coord &coord ) const
{
bool Polygon::isInside( const Coord &coord ) const {
bool inside = false;
for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ )
{
for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) {
if ( (((coords[i].Y() <= coord.Y()) && (coord.Y() < coords[j].Y()) )
|| ((coords[j].Y() <= coord.Y()) && (coord.Y() < coords[i].Y())))
&& (coord.X() < (coords[j].X() - coords[i].X()) * (coord.Y() - coords[i].Y()) / (coords[j].Y() - coords[i].Y()) + coords[i].X()))
@ -118,5 +110,5 @@ bool Polygon::isInside( const Coord &coord ) const
inside = !inside;
}
}
return( inside );
return inside;
}

View File

@ -41,13 +41,13 @@ protected:
static int CompareYX( const void *p1, const void *p2 ) {
const Edge *e1 = reinterpret_cast<const Edge *>(p1), *e2 = reinterpret_cast<const Edge *>(p2);
if ( e1->min_y == e2->min_y )
return( int(e1->min_x - e2->min_x) );
return int(e1->min_x - e2->min_x);
else
return( int(e1->min_y - e2->min_y) );
return int(e1->min_y - e2->min_y);
}
static int CompareX( const void *p1, const void *p2 ) {
const Edge *e1 = reinterpret_cast<const Edge *>(p1), *e2 = reinterpret_cast<const Edge *>(p2);
return( int(e1->min_x - e2->min_x) );
return int(e1->min_x - e2->min_x);
}
};
@ -93,22 +93,22 @@ public:
Polygon &operator=( const Polygon &p_polygon );
inline int getNumCoords() const { return( n_coords ); }
inline int getNumCoords() const { return n_coords; }
inline const Coord &getCoord( int index ) const {
return( coords[index] );
return coords[index];
}
inline const Box &Extent() const { return( extent ); }
inline int LoX() const { return( extent.LoX() ); }
inline int HiX() const { return( extent.HiX() ); }
inline int LoY() const { return( extent.LoY() ); }
inline int HiY() const { return( extent.HiY() ); }
inline int Width() const { return( extent.Width() ); }
inline int Height() const { return( extent.Height() ); }
inline const Box &Extent() const { return extent; }
inline int LoX() const { return extent.LoX(); }
inline int HiX() const { return extent.HiX(); }
inline int LoY() const { return extent.LoY(); }
inline int HiY() const { return extent.HiY(); }
inline int Width() const { return extent.Width(); }
inline int Height() const { return extent.Height(); }
inline int Area() const { return( area ); }
inline int Area() const { return area; }
inline const Coord &Centre() const {
return( centre );
return centre;
}
bool isInside( const Coord &coord ) const;
};

View File

@ -235,7 +235,7 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) {
} else {
if ( ioctl( sd, FIONREAD, &total_bytes_to_read ) < 0 ) {
Error( "Can't ioctl(): %s", strerror(errno) );
return( -1 );
return -1;
}
if ( total_bytes_to_read == 0 ) {
@ -250,13 +250,13 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) {
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 );
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");
//Disconnect(); // Disconnect is done outside of ReadData now.
return( -1 );
return -1;
}
// There can be lots of bytes available. I've seen 4MB or more. This will vastly inflate our buffer size unnecessarily.
@ -293,6 +293,18 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) {
return total_bytes_read;
}
int RemoteCameraHttp::GetData() {
time_t start_time = time(NULL);
int buffer_len = 0;
while ( !( buffer_len = ReadData(buffer) ) ) {
if ( zm_terminate || ( start_time - time(NULL) < ZM_WATCH_MAX_DELAY ))
return -1;
Debug(4, "Timeout waiting for REGEXP HEADER");
usleep(100000);
}
return buffer_len;
}
int RemoteCameraHttp::GetResponse() {
int buffer_len;
#if HAVE_LIBPCRE
@ -315,9 +327,7 @@ int RemoteCameraHttp::GetResponse() {
switch( state ) {
case HEADER :
{
while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) {
Debug(4, "Timeout waiting for REGEXP HEADER");
}
buffer_len = GetData();
if ( buffer_len < 0 ) {
Error("Unable to read header data");
return -1;
@ -457,9 +467,7 @@ int RemoteCameraHttp::GetResponse() {
state = CONTENT;
} else {
Debug( 3, "Unable to extract subheader from stream, retrying" );
while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) {
Debug(4, "Timeout waiting to extract subheader");
}
buffer_len = GetData();
if ( buffer_len < 0 ) {
Error( "Unable to extract subheader data" );
return( -1 );
@ -491,7 +499,7 @@ int RemoteCameraHttp::GetResponse() {
if ( content_length ) {
while ( ((long)buffer.size() < content_length ) && ! zm_terminate ) {
Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length );
int bytes_read = ReadData( buffer );
int bytes_read = GetData();
if ( bytes_read < 0 ) {
Error( "Unable to read content" );
@ -502,9 +510,7 @@ int RemoteCameraHttp::GetResponse() {
Debug( 3, "Got end of image by length, content-length = %d", content_length );
} else {
while ( !content_length ) {
while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) {
Debug(4, "Timeout waiting for content");
}
buffer_len = GetData();
if ( buffer_len < 0 ) {
Error( "Unable to read content" );
return( -1 );
@ -616,9 +622,7 @@ int RemoteCameraHttp::GetResponse() {
}
case HEADERCONT :
{
while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) {
Debug(1, "Timeout waiting for HEADERCONT");
}
buffer_len = GetData();
if ( buffer_len < 0 ) {
Error("Unable to read header");
return -1;
@ -903,9 +907,7 @@ int RemoteCameraHttp::GetResponse() {
state = CONTENT;
} else {
Debug( 3, "Unable to extract subheader from stream, retrying" );
while ( !( buffer_len = ReadData(buffer) ) &&!zm_terminate ) {
Debug(1, "Timeout waiting to extra subheader non regexp");
}
buffer_len = GetData();
if ( buffer_len < 0 ) {
Error( "Unable to read subheader" );
return( -1 );
@ -945,7 +947,7 @@ int RemoteCameraHttp::GetResponse() {
if ( content_length ) {
while ( ( (long)buffer.size() < content_length ) && ! zm_terminate ) {
Debug(4, "getting more data");
int bytes_read = ReadData(buffer);
int bytes_read = GetData();
if ( bytes_read < 0 ) {
Error("Unable to read content");
return -1;
@ -958,8 +960,7 @@ int RemoteCameraHttp::GetResponse() {
while ( !content_length && !zm_terminate ) {
Debug(4, "!content_length, ReadData");
buffer_len = ReadData( buffer );
if ( buffer_len < 0 )
{
if ( buffer_len < 0 ) {
Error( "Unable to read content" );
return( -1 );
}
@ -1024,7 +1025,6 @@ int RemoteCameraHttp::PreCapture() {
if ( sd < 0 ) {
Connect();
if ( sd < 0 ) {
Error("Unable to connect to camera");
return -1;
}
mode = SINGLE_IMAGE;

View File

@ -68,6 +68,7 @@ public:
int Disconnect();
int SendRequest();
int ReadData( Buffer &buffer, unsigned int bytes_expected=0 );
int GetData();
int GetResponse();
int PreCapture();
int Capture( ZMPacket &p );

View File

@ -342,7 +342,7 @@ int RemoteCameraRtsp::Capture( ZMPacket &zm_packet ) {
while ( !frameComplete && (buffer.size() > 0) ) {
packet->data = buffer.head();
packet->size = buffer.size();
bytes += packet.size;
bytes += packet->size;
// So I think this is the magic decode step. Result is a raw image?
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)

View File

@ -203,7 +203,7 @@ User *zmLoadTokenUser (std::string jwt_token_str, bool use_remote_addr ) {
return NULL;
}
Debug (1,"Got stored expiry time of %u",stored_iat);
Debug (1,"Got last token revoke time of: %u",stored_iat);
Debug (1,"Authenticated user '%s' via token", username.c_str());
mysql_free_result(result);
return user;

View File

@ -58,6 +58,7 @@ VideoStore::VideoStore(
packets_written = 0;
frame_count = 0;
in_frame = NULL;
video_in_frame = NULL;
video_in_ctx = NULL;
// In future, we should just pass in the codec context instead of the stream. Don't really need the stream.
@ -85,10 +86,8 @@ VideoStore::VideoStore(
video_start_pts = 0;
audio_next_pts = 0;
audio_next_dts = 0;
out_format = NULL;
oc = NULL;
ret = 0;
} // VideoStore::VideoStore
bool VideoStore::open() {
@ -100,8 +99,6 @@ bool VideoStore::open() {
"Could not create video storage stream %s as no out ctx"
" could be assigned based on filename: %s",
filename, av_make_error_string(ret).c_str());
} else {
Debug(4, "Success allocating out format ctx");
}
// Couldn't deduce format from filename, trying from format name
@ -113,11 +110,8 @@ bool VideoStore::open() {
" could not be assigned based on filename or format %s",
filename, format);
return false;
} else {
Debug(4, "Success allocating out ctx");
}
} // end if ! oc
Debug(2, "Success opening output contect");
AVDictionary *pmetadata = NULL;
ret = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0);
@ -145,6 +139,7 @@ bool VideoStore::open() {
zm_dump_codec(video_in_ctx);
#endif
} else {
// FIXME delete?
Debug(2, "No input ctx");
video_in_ctx = avcodec_alloc_context3(NULL);
video_in_stream_index = 0;
@ -170,14 +165,14 @@ bool VideoStore::open() {
}
max_stream_index = video_out_stream->index;
// FIXME SHould check that we are set to passthrough. Might be same codec, but want privacy overlays
// FIXME Should check that we are set to passthrough. Might be same codec, but want privacy overlays
if ( video_in_stream && ( video_in_ctx->codec_id == wanted_codec ) ) {
// Copy params from instream to ctx
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
ret = avcodec_parameters_to_context(video_out_ctx, video_in_stream->codecpar);
#else
ret = avcodec_copy_context(video_out_ctx, video_in_ctx);
#endif
// Copy params from instream to ctx
if ( ret < 0 ) {
Error("Could not initialize ctx parameteres");
return false;
@ -219,7 +214,7 @@ bool VideoStore::open() {
Warning("Unsupported Orientation(%d)", orientation);
}
} // end if orientation
} else {
} else { // Either no video in or not the desired codec
for ( unsigned int i = 0; i < sizeof(codec_data) / sizeof(*codec_data); i++ ) {
if ( codec_data[i].codec_id != monitor->OutputCodec() )
continue;
@ -244,7 +239,7 @@ bool VideoStore::open() {
Debug(2,"No timebase found in video in context, defaulting to Q");
video_out_ctx->time_base = AV_TIME_BASE_Q;
}
video_out_stream->time_base = video_in_stream->time_base;
video_out_stream->time_base = video_in_stream ? video_in_stream->time_base : AV_TIME_BASE_Q;
if ( video_out_ctx->codec_id == AV_CODEC_ID_H264 ) {
video_out_ctx->max_b_frames = 1;
@ -327,6 +322,7 @@ bool VideoStore::open() {
}
#else
avcodec_copy_context(video_out_stream->codec, video_out_ctx);
#endif
converted_in_samples = NULL;
audio_out_codec = NULL;
@ -458,10 +454,7 @@ bool VideoStore::open() {
for ( int i = 0; i <= max_stream_index; i++ ) {
next_dts[i] = 0;
}
} // VideoStore::VideoStore
bool VideoStore::open() {
int ret;
/* open the out file, if needed */
if ( !(out_format->flags & AVFMT_NOFILE) ) {
if ( (ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, NULL, NULL) ) < 0 ) {
@ -501,27 +494,8 @@ bool VideoStore::open() {
return true;
} // end bool VideoStore::open()
void VideoStore::write_audio_packet( AVPacket &pkt ) {
//Debug(2, "writing audio packet pts(%d) dts(%d) duration(%d)", pkt.pts,
//pkt.dts, pkt.duration);
pkt.pts = audio_next_pts;
pkt.dts = audio_next_dts;
Debug(2, "writing audio packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts, pkt.duration);
if ( pkt.duration > 0 ) {
pkt.duration =
av_rescale_q(pkt.duration, audio_out_ctx->time_base,
audio_out_stream->time_base);
}
audio_next_pts += pkt.duration;
audio_next_dts += pkt.duration;
Debug(2, "writing audio packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts, pkt.duration);
pkt.stream_index = audio_out_stream->index;
av_interleaved_write_frame(oc, &pkt);
} // end void VideoStore::Write_audio_packet( AVPacket &pkt )
void VideoStore::flush_codecs() {
int ret;
// The codec queues data. We need to send a flush command and out
// whatever we get. Failures are not fatal.
@ -560,7 +534,7 @@ void VideoStore::flush_codecs() {
break;
}
#endif
write_video_packet(pkt);
write_packet(&pkt, video_out_stream);
zm_av_packet_unref(&pkt);
} // while have buffered frames
} // end if have delay capability
@ -635,8 +609,6 @@ void VideoStore::flush_codecs() {
zm_av_packet_unref(&pkt);
} // while have buffered frames
} // end if audio_out_codec
} // end if audio_out_codec
} // end flush_codecs
VideoStore::~VideoStore() {
@ -1057,6 +1029,7 @@ int VideoStore::writePacket( ZMPacket *ipkt ) {
}
int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) {
int ret;
frame_count += 1;
// if we have to transcode
@ -1191,7 +1164,7 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) {
video_in_stream->time_base,
video_out_stream->time_base);
Debug(1, "duration from ipkt: pts(%" PRId64 ") - last_pts(%" PRId64 ") = (%" PRId64 ") => (%" PRId64 ") (%d/%d) (%d/%d)",
zm_packet->in_frame->pkt_pts,
zm_packet->in_frame->pts,
video_last_pts,
zm_packet->in_frame->pkt_duration,
duration,
@ -1203,13 +1176,13 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) {
} else {
duration =
av_rescale_q(
zm_packet->in_frame->pkt_pts - video_last_pts,
zm_packet->in_frame->pts - video_last_pts,
video_in_stream->time_base,
video_out_stream->time_base);
Debug(1, "duration calc: pts(%" PRId64 ") - last_pts(%" PRId64 ") = (%" PRId64 ") => (%" PRId64 ")",
zm_packet->in_frame->pkt_pts,
zm_packet->in_frame->pts,
video_last_pts,
zm_packet->in_frame->pkt_pts - video_last_pts,
zm_packet->in_frame->pts - video_last_pts,
duration
);
if ( duration <= 0 ) {
@ -1226,7 +1199,7 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) {
opkt.data = ipkt->data;
opkt.size = ipkt->size;
opkt.flags = ipkt->flags;
opkt.duration = pkt->duration;
opkt.duration = ipkt->duration;
if ( ipkt->dts != AV_NOPTS_VALUE ) {
if ( !video_first_dts ) {
@ -1394,7 +1367,6 @@ int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) {
av_make_error_string(ret).c_str());
} else {
Debug(2, "Success writing packet");
>>>>>>> master
}
return ret;
} // end int VideoStore::write_packet(AVPacket *pkt, AVStream *stream)

View File

@ -41,11 +41,17 @@ static struct CodecData codec_data[];
int audio_in_stream_index;
AVCodec *video_out_codec;
AVCodecContext *video_in_ctx;
AVCodecContext *video_out_ctx;
AVStream *video_in_stream;
AVStream *audio_in_stream;
const AVCodec *audio_in_codec;
AVCodecContext *audio_in_ctx;
// The following are used when encoding the audio stream to AAC
AVCodec *audio_out_codec;
AVCodecContext *audio_out_ctx;
// Move this into the object so that we aren't constantly allocating/deallocating it on the stack
AVPacket opkt;
// we are transcoding
@ -53,18 +59,10 @@ static struct CodecData codec_data[];
AVFrame *in_frame;
AVFrame *out_frame;
AVCodecContext *video_in_ctx;
const AVCodec *audio_in_codec;
AVCodecContext *audio_in_ctx;
SWScale swscale;
unsigned int packets_written;
unsigned int frame_count;
// The following are used when encoding the audio stream to AAC
AVStream *audio_out_stream;
AVCodec *audio_out_codec;
AVCodecContext *audio_out_ctx;
#ifdef HAVE_LIBSWRESAMPLE
SwrContext *resample_ctx;
#else

View File

@ -349,7 +349,7 @@ int main(int argc, char *argv[]) {
if ( result < 0 ) {
// Failure, try reconnecting
sleep(1);
sleep(5);
break;
}

View File

@ -106,9 +106,9 @@ int main(int argc, const char *argv[]) {
for ( int p = 0; p < parm_no; p++ ) {
char *name = strtok(parms[p], "=");
char *value = strtok(NULL, "=");
char const *value = strtok(NULL, "=");
if ( !value )
value = (char *)"";
value = "";
if ( !strcmp(name, "source") ) {
source = !strcmp(value, "event")?ZMS_EVENT:ZMS_MONITOR;
if ( !strcmp(value, "fifo") )
@ -127,10 +127,10 @@ int main(int argc, const char *argv[]) {
} else if ( !strcmp(name, "time") ) {
event_time = atoi(value);
} else if ( !strcmp(name, "event") ) {
event_id = strtoull(value, (char **)NULL, 10);
event_id = strtoull(value, NULL, 10);
source = ZMS_EVENT;
} else if ( !strcmp(name, "frame") ) {
frame_id = strtoull(value, (char **)NULL, 10);
frame_id = strtoull(value, NULL, 10);
source = ZMS_EVENT;
} else if ( !strcmp(name, "scale") ) {
scale = atoi(value);
@ -237,11 +237,13 @@ int main(int argc, const char *argv[]) {
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));
strftime(date_string, sizeof(date_string)-1,
"%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
fprintf(stdout, "Last-Modified: %s\r\n", date_string);
fputs("Last-Modified: ", stdout);
fputs(date_string, stdout);
fputs(
"Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
"\r\nExpires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
"Cache-Control: no-store, no-cache, must-revalidate\r\n"
"Cache-Control: post-check=0, pre-check=0\r\n"
"Pragma: no-cache\r\n",
@ -279,7 +281,9 @@ int main(int argc, const char *argv[]) {
stream.setStreamType(MonitorStream::STREAM_MPEG);
#else // HAVE_LIBAVCODEC
Error("MPEG streaming of '%s' attempted while disabled", query);
fprintf(stderr, "MPEG streaming is disabled.\nYou should configure with the --with-ffmpeg option and rebuild to use this functionality.\n");
fprintf(stderr, "MPEG streaming is disabled.\n"
"You should configure with the --with-ffmpeg"
" option and rebuild to use this functionality.\n");
logTerm();
zmDbClose();
return -1;

View File

@ -430,7 +430,9 @@ int main(int argc, char *argv[]) {
User *user = 0;
if ( config.opt_use_auth ) {
if ( strcmp(config.auth_relay, "none") == 0 ) {
if ( jwt_token_str != "" ) {
user = zmLoadTokenUser(jwt_token_str, false);
} else if ( strcmp(config.auth_relay, "none") == 0 ) {
if ( !username ) {
Error("Username must be supplied");
exit_zmu(-1);
@ -444,13 +446,10 @@ int main(int argc, char *argv[]) {
user = zmLoadUser(username);
} else {
if ( !(username && password) && !auth && (jwt_token_str=="")) {
if ( !(username && password) && !auth ) {
Error("Username and password or auth/token string must be supplied");
exit_zmu(-1);
}
if (jwt_token_str != "") {
user = zmLoadTokenUser(jwt_token_str, false);
}
if ( auth ) {
user = zmLoadAuthUser(auth, false);
}

View File

@ -128,14 +128,14 @@ else
fi;
fi
IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE"
if [ "$PPA" == "" ]; then
if [ "$RELEASE" != "" ]; then
# We need to use our official tarball for the original source, so grab it and overwrite our generated one.
IFS='.' read -r -a VERSION <<< "$RELEASE"
if [ "${VERSION[0]}.${VERSION[1]}" == "1.30" ]; then
if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then
PPA="ppa:iconnor/zoneminder-stable"
else
PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}"
PPA="ppa:iconnor/zoneminder-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}"
fi;
else
if [ "$BRANCH" == "" ]; then
@ -175,7 +175,7 @@ cd ../
VERSION=`cat ${GITHUB_FORK}_zoneminder_release/version`
if [ $VERSION == "" ]; then
if [ -z "$VERSION" ]; then
exit 1;
fi;
if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then
@ -316,7 +316,7 @@ EOF
read -p "Do you want to upload this binary to zmrepo? (y/N)"
if [[ $REPLY == [yY] ]]; then
if [ "$RELEASE" != "" ]; then
scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/stable/mini-dinstall/incoming/"
scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/release-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}/mini-dinstall/incoming/"
else
if [ "$BRANCH" == "" ]; then
scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/master/mini-dinstall/incoming/"

View File

@ -1,5 +1,10 @@
#!/bin/bash
# We don't deploy during eslint checks, so exit immediately
if [ "${DIST}" == "eslint" ]; then
exit 0
fi
# Check to see if this script has access to all the commands it needs
for CMD in sshfs rsync find fusermount mkdir; do
type $CMD 2>&1 > /dev/null
@ -12,12 +17,17 @@ for CMD in sshfs rsync find fusermount mkdir; do
fi
done
# We only want to deploy packages during cron events
# See https://docs.travis-ci.com/user/cron-jobs/
if [ "${TRAVIS_EVENT_TYPE}" == "cron" ] || [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then
if [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then
if [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbian" ]; then
if [ "${RELEASE}" != "" ]; then
IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE"
if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then
targetfolder="debian/release/mini-dinstall/incoming"
else
targetfolder="debian/release-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}/mini-dinstall/incoming"
fi
else
targetfolder="debian/master/mini-dinstall/incoming"
fi
else
targetfolder="travis"
fi
@ -25,40 +35,17 @@ if [ "${TRAVIS_EVENT_TYPE}" == "cron" ] || [ "${OS}" == "debian" ] || [ "${OS}"
echo
echo "Target subfolder set to $targetfolder"
echo
if [ "${USE_SFTP}" == "yes" ]; then
echo "Running \$(rsync -v -e 'ssh -vvv' build/* zmrepo@zmrepo.zoneminder.com:${targetfolder}/ 2>&1)"
rsync -v -e 'ssh -vvv' build/* zmrepo@zmrepo.zoneminder.com:${targetfolder}/ 2>&1
if [ $? -eq 0 ]; then
echo "Running \$(rsync -v -e 'ssh -vvv' build/*.{rpm,deb,dsc,tar.xz,buildinfo,changes} zmrepo@zmrepo.zoneminder.com:${targetfolder}/ 2>&1)"
rsync -v --ignore-missing-args --exclude 'external-repo.noarch.rpm' -e 'ssh -vvv' build/*.{rpm,deb,dsc,tar.xz,buildinfo,changes} zmrepo@zmrepo.zoneminder.com:${targetfolder}/ 2>&1
if [ "$?" -eq 0 ]; then
echo
echo "Files copied successfully."
echo
else
echo
echo "ERROR: Attempt to rsync to zmrepo.zoneminder.com failed!"
echo "See log output for details."
echo
exit 99
fi
else
mkdir -p ./zmrepo
ssh_mntchk="$(sshfs zmrepo@zmrepo.zoneminder.com:./ ./zmrepo -o workaround=rename,reconnect 2>&1)"
if [ -z "$ssh_mntchk" ]; then
echo
echo "Remote filesystem mounted successfully."
echo "Begin transfering files..."
echo
# Don't keep packages older than 5 days
find ./zmrepo/$targetfolder/ -maxdepth 1 -type f,l -mtime +5 -delete
rsync -vzlh --ignore-errors build/* zmrepo/$targetfolder/
fusermount -zu zmrepo
else
echo
echo "ERROR: Attempt to mount zmrepo.zoneminder.com failed!"
echo "sshfs gave the following error message:"
echo \"$ssh_mntchk\"
echo
exit 99
fi
fi
fi

View File

@ -9,7 +9,7 @@
# General sanity checks
checksanity () {
# Check to see if this script has access to all the commands it needs
for CMD in set echo curl git ln mkdir rmdir cat patch; do
for CMD in set echo curl git ln mkdir rmdir cat patch sed; do
type $CMD 2>&1 > /dev/null
if [ $? -ne 0 ]; then
@ -30,7 +30,7 @@ checksanity () {
ARCH="x86_64"
fi
if [[ "${ARCH}" != "x86_64" && "${ARCH}" != "i386" && "${ARCH}" != "armhf" ]]; then
if [[ "${ARCH}" != "x86_64" && "${ARCH}" != "i386" && "${ARCH}" != "armhf" && "${ARCH}" != "aarch64" ]]; then
echo
echo "ERROR: Unsupported architecture specified \"${ARCH}\"."
echo
@ -150,7 +150,7 @@ install_deb () {
exit 1
fi
# Install and test the zoneminder package (only) for Ubuntu Trusty
# Install and test the zoneminder package (only) for Ubuntu Xenial
pkgname="build/zoneminder_${VERSION}-${RELEASE}_amd64.deb"
if [ -e $pkgname ]; then
@ -275,6 +275,8 @@ checkdeploytarget () {
echo "*** TRACEROUTE ***"
echo
traceroute -w 2 -m 15 ${DEPLOYTARGET}
exit 97
fi
}
@ -291,16 +293,16 @@ if [ "${TRAVIS}" == "true" ]; then
fi
checksanity
# We don't want to build packages for all supported distros after every commit
# Only build all packages when executed via cron
# See https://docs.travis-ci.com/user/cron-jobs/
# Steps common to Redhat distros
if [ "${OS}" == "el" ] || [ "${OS}" == "fedora" ]; then
if [ "${TRAVIS_EVENT_TYPE}" == "cron" ] || [ "${TRAVIS}" != "true" ]; then
commonprep
echo "Begin Redhat build..."
# Newer Redhat distros use dnf package manager rather than yum
if [ "${DIST}" -gt "7" ]; then
sed -i 's\yum\dnf\' utils/packpack/redhat_package.mk
fi
setrpmpkgname
ln -sfT distros/redhat rpm
@ -326,7 +328,7 @@ if [ "${OS}" == "el" ] || [ "${OS}" == "fedora" ]; then
echo "Starting packpack..."
execpackpack
fi;
# Steps common to Debian based distros
elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbian" ]; then
commonprep
@ -348,14 +350,27 @@ elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbia
echo "Starting packpack..."
execpackpack
# We were not triggered via cron so just build and test trusty
if [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "xenial" ] && [ "${ARCH}" == "x86_64" ]; then
# If we are running inside Travis then attempt to install the deb we just built
if [ "${TRAVIS}" == "true" ]; then
# Try to install and run the newly built zoneminder package
if [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "xenial" ] && [ "${ARCH}" == "x86_64" ] && [ "${TRAVIS}" == "true" ]; then
echo "Begin Deb package installation..."
install_deb
fi
fi
# Steps common to eslint checks
elif [ "${OS}" == "eslint" ] || [ "${DIST}" == "eslint" ]; then
# Check we've got npm installed
type npm 2>&1 > /dev/null
if [ $? -ne 0 ]; then
echo
echo "ERROR: The script cannot find the required command \"npm\"."
echo
exit 1
fi
exit 0
npm install -g eslint@5.12.0 eslint-config-google@0.11.0 eslint-plugin-html@5.0.0 eslint-plugin-php-markup@0.2.5
echo "Begin eslint checks..."
eslint --ext .php,.js .
fi

View File

@ -1 +1 @@
1.33.15
1.35.0

View File

@ -54,18 +54,18 @@ if ( 0 ) {
SOL_SOCKET, // socket level
SO_SNDTIMEO, // timeout option
array(
"sec"=>0, // Timeout in seconds
"usec"=>500 // I assume timeout in microseconds
'sec'=>0, // Timeout in seconds
'usec'=>500 // I assume timeout in microseconds
)
);
$new_stream = null;
Info("Testing connection to " . $url_bits['host'].':'.$port);
Info('Testing connection to '.$url_bits['host'].':'.$port);
if ( socket_connect( $socket, $url_bits['host'], $port ) ) {
$new_stream = $url_bits; // make a copy
$new_stream['port'] = $port;
} else {
socket_close($socket);
ZM\Info("No connection to ".$url_bits['host'] . " on port $port");
ZM\Info('No connection to '.$url_bits['host'].' on port '.$port);
continue;
}
if ( $new_stream ) {
@ -103,7 +103,7 @@ Info("Testing connection to " . $url_bits['host'].':'.$port);
$stream['url'] = unparse_url($stream, array('path'=>'/','query'=>'action=stream'));
$monitors = ZM\Monitor::find(array('Path'=>$stream['url']));
if ( count($monitors) ) {
ZM\Info("Found monitors matching " . $stream['url'] );
ZM\Info('Found monitors matching ' . $stream['url'] );
$stream['Monitor'] = $monitors[0];
if ( isset( $stream['Width'] ) and ( $stream['Monitor']->Width() != $stream['Width'] ) ) {
$stream['Warning'] .= 'Monitor width ('.$stream['Monitor']->Width().') and stream width ('.$stream['Width'].") do not match!\n";
@ -155,7 +155,7 @@ if ( 0 ) {
}
if ( ! $url_bits ) {
ajaxError("The given URL was too malformed to parse.");
ajaxError('The given URL was too malformed to parse.');
return;
}
@ -169,16 +169,16 @@ if ( 0 ) {
$file = $_FILES['import_file'];
if ($file["error"] > 0) {
ajaxError($file["error"]);
if ( $file['error'] > 0 ) {
ajaxError($file['error']);
return;
} else {
$filename = $file["name"];
$filename = $file['name'];
$available_streams = array();
$row = 1;
if ( ($handle = fopen($file['tmp_name'], 'r')) !== FALSE ) {
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
while ( ($data = fgetcsv($handle, 1000, ',')) !== FALSE ) {
$name = $data[0];
$url = $data[1];
$group = $data[2];
@ -207,21 +207,17 @@ if ( 0 ) {
fclose($handle);
ajaxResponse(array('Streams'=>$available_streams));
} else {
ajaxError("Uploaded file does not exist");
ajaxError('Uploaded file does not exist');
return;
}
}
} // end case import
default:
{
ZM\Warning("unknown action " . $_REQUEST['action'] );
} // end ddcase default
}
ZM\Warning('unknown action '.$_REQUEST['action']);
} // end switch action
} else {
ZM\Warning("Cannot edit monitors" );
ZM\Warning('Cannot edit monitors');
}
ajaxError( 'Unrecognised action or insufficient permissions' );
ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user ' . $user['Username']);
?>

View File

@ -12,7 +12,7 @@ if ( canEdit('Monitors') ) {
$monitor_id = $monitor_ids[$i];
$monitor_id = preg_replace('/^monitor_id-/', '', $monitor_id);
if ( ( !$monitor_id ) or ! ( is_integer($monitor_id) or ctype_digit($monitor_id) ) ) {
Warning("Got $monitor_id from " . $monitor_ids[$i]);
Warning('Got '.$monitor_id.' from '.$monitor_ids[$i]);
continue;
}
dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($i, $monitor_id));
@ -23,13 +23,11 @@ if ( canEdit('Monitors') ) {
return;
} // end case sort
default:
{
ZM\Warning('unknown action '.$_REQUEST['action']);
} // end ddcase default
}
} else {
ZM\Warning('Cannot edit monitors');
}
ajaxError('Unrecognised action or insufficient permissions');
ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user ' . $user['Username']);
?>

View File

@ -155,5 +155,5 @@ if ( canEdit('Events') ) {
} // end switch action
} // end if canEdit('Events')
ajaxError('Unrecognised action or insufficient permissions');
ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user ' . $user['Username']);
?>

View File

@ -58,7 +58,7 @@ function buildLogQuery($action) {
$sql .= ' ORDER BY '.$sortField.' '.$sortOrder.' LIMIT '.$limit;
return array('sql'=>$sql, 'values'=>$values);
}
} # function buildLogQuery($action)
switch ( $_REQUEST['task'] ) {
case 'create' :
@ -70,14 +70,16 @@ switch ( $_REQUEST['task'] ) {
$string = $_POST['message'];
$file = !empty($_POST['file']) ? preg_replace( '/\w+:\/\/[\w.:]+\//', '', $_POST['file'] ) : '';
if ( !empty( $_POST['line'] ) )
if ( !empty( $_POST['line'] ) ) {
$line = validInt($_POST['line']);
else
} else {
$line = NULL;
}
$levels = array_flip(ZM\Logger::$codes);
if ( !isset($levels[$_POST['level']]) )
if ( !isset($levels[$_POST['level']]) ) {
ZM\Panic('Unexpected logger level '.$_POST['level']);
}
$level = $levels[$_POST['level']];
ZM\Logger::fetch()->logPrint($level, $string, $file, $line);
}
@ -141,6 +143,10 @@ switch ( $_REQUEST['task'] ) {
$logs[] = $log;
}
foreach ( $options as $field => $values ) {
asort($options[$field]);
}
$available = count($logs);
ajaxResponse( array(
'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG)?strftime(DATE_FMT_CONSOLE_LONG):date(DATE_FMT_CONSOLE_LONG),

View File

@ -1,8 +1,11 @@
<?php
if ( $_REQUEST['entity'] == 'navBar' ) {
$data = array();
if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) {
$data['auth'] = generateAuthHash( ZM_AUTH_HASH_IPS );
if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) {
$auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS);
if ( isset($_REQUEST['auth']) and ($_REQUEST['auth'] != $auth_hash) ) {
$data['auth'] = $auth_hash;
}
}
$data['message'] = getNavBarHtml('reload');
ajaxResponse($data);
@ -83,8 +86,8 @@ $statusData = array(
'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' ),
'Status' => (isset($_REQUEST['id'])?array( 'zmu' => '-m '.escapeshellarg($_REQUEST['id'][0]).' -s' ):null),
'FrameRate' => (isset($_REQUEST['id'])?array( 'zmu' => '-m '.escapeshellarg($_REQUEST['id'][0]).' -f' ):null),
),
),
'events' => array(
@ -204,6 +207,7 @@ function collectData() {
$fieldSql = array();
$joinSql = array();
$groupSql = array();
$values = array();
$elements = &$entitySpec['elements'];
$lc_elements = array_change_key_case( $elements );
@ -258,7 +262,6 @@ function collectData() {
if ( $id && !empty($entitySpec['selector']) ) {
$index = 0;
$where = array();
$values = array();
foreach( $entitySpec['selector'] as $selIndex => $selector ) {
$selectorParamName = ':selector' . $selIndex;
if ( is_array( $selector ) ) {

View File

@ -86,10 +86,8 @@ if ( sem_acquire($semaphore,1) !== false ) {
$numSockets = socket_select($rSockets, $wSockets, $eSockets, intval($timeout/1000), ($timeout%1000)*1000);
if ( $numSockets === false ) {
ZM\Error('socket_select failed: ' . socket_strerror(socket_last_error()));
ajaxError('socket_select failed: '.socket_strerror(socket_last_error()));
} else if ( $numSockets < 0 ) {
ZM\Error("Socket closed $remSockFile");
ajaxError("Socket closed $remSockFile");
} else if ( $numSockets == 0 ) {
ZM\Error("Timed out waiting for msg $remSockFile");
@ -97,7 +95,6 @@ if ( sem_acquire($semaphore,1) !== false ) {
#ajaxError("Timed out waiting for msg $remSockFile");
} else if ( $numSockets > 0 ) {
if ( count($rSockets) != 1 ) {
ZM\Error('Bogus return from select, '.count($rSockets).' sockets available');
ajaxError('Bogus return from select, '.count($rSockets).' sockets available');
}
}
@ -119,17 +116,17 @@ if ( sem_acquire($semaphore,1) !== false ) {
switch ( $data['type'] ) {
case MSG_DATA_WATCH :
$data = unpack('ltype/imonitor/istate/dfps/ilevel/irate/ddelay/izoom/Cdelayed/Cpaused/Cenabled/Cforced', $msg);
ZM\Logger::Debug('FPS: ' . $data['fps']);
$data['fps'] = round( $data['fps'], 2 );
ZM\Logger::Debug('FPS: ' . $data['fps'] );
$data['rate'] /= RATE_BASE;
$data['delay'] = round( $data['delay'], 2 );
$data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 );
if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) {
$time = time();
// Regenerate auth hash after half the lifetime of the hash
if ( (!isset($_SESSION['AuthHashGeneratedAt'])) or ( $_SESSION['AuthHashGeneratedAt'] < $time - (ZM_AUTH_HASH_TTL * 1800) ) ) {
$data['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS);
if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) {
$auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS);
if ( isset($_REQUEST['auth']) and ($_REQUEST['auth'] != $auth_hash) ) {
$data['auth'] = $auth_hash;
ZM\Logger::Debug("including nw auth hash " . $data['auth']);
} else {
ZM\Logger::Debug('Not including nw auth hash becase it hashn\'t changed '.$auth_hash);
}
}
ajaxResponse(array('status'=>$data));
@ -144,11 +141,10 @@ if ( sem_acquire($semaphore,1) !== false ) {
}
$data['rate'] /= RATE_BASE;
$data['zoom'] = round($data['zoom']/SCALE_BASE, 1);
if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) {
$time = time();
// Regenerate auth hash after half the lifetime of the hash
if ( (!isset($_SESSION['AuthHashGeneratedAt'])) or ( $_SESSION['AuthHashGeneratedAt'] < $time - (ZM_AUTH_HASH_TTL * 1800) ) ) {
$data['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS);
if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) {
$auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS);
if ( isset($_REQUEST['auth']) and ($_REQUEST['auth'] != $auth_hash) ) {
$data['auth'] = $auth_hash;
}
}
ajaxResponse(array('status'=>$data));

View File

@ -15,6 +15,10 @@ class EventsController extends AppController {
*/
public $components = array('RequestHandler', 'Scaler', 'Image', 'Paginator');
public function beforeRender() {
$this->set($this->Event->enumValues());
}
public function beforeFilter() {
parent::beforeFilter();
global $user;

View File

@ -77,16 +77,24 @@ class GroupsController extends AppController {
}
$this->Group->create();
if ( $this->Group->save($this->request->data) ) {
if ( $this->request->data['Group']['MonitorIds'] and ! isset($this->request->data['Monitor']) ) {
$this->request->data['Monitor'] = explode(',', $this->request->data['Group']['MonitorIds']);
unset($this->request->data['Group']['MonitorIds']);
}
if ( $this->Group->saveAssociated($this->request->data, array('atomic'=>true)) ) {
return $this->flash(
__('The group has been saved.'),
array('action' => 'index')
);
} else {
ZM\Error("Failed to save Group");
debug($this->Group->invalidFields());
}
}
} # end if post
$monitors = $this->Group->Monitor->find('list');
$this->set(compact('monitors'));
}
} # end add
/**
* edit method

View File

@ -50,8 +50,10 @@ class HostController extends AppController {
$cred_depr = [];
if ( $username && $password ) {
ZM\Logger::Debug('Username and password provided, generating access and refresh tokens');
$cred = $this->_getCredentials(true, '', $username); // generate refresh
} else {
ZM\Logger::Debug('Only generating access token');
$cred = $this->_getCredentials(false, $token); // don't generate refresh
}
@ -69,6 +71,8 @@ class HostController extends AppController {
$cred_depr = $this->_getCredentialsDeprecated();
$login_array['credentials'] = $cred_depr[0];
$login_array['append_password'] = $cred_depr[1];
} else {
ZM\Logger::Debug('Legacy Auth is disabled, not generating auth= credentials');
}
$login_array['version'] = $ver[0];
@ -108,8 +112,11 @@ class HostController extends AppController {
private function _getCredentials($generate_refresh_token=false, $token='', $username='') {
if ( !ZM_OPT_USE_AUTH )
if ( !ZM_OPT_USE_AUTH ) {
ZM\Error('OPT_USE_AUTH is turned off. Tokens will be null');
return;
}
if ( !ZM_AUTH_HASH_SECRET )
throw new ForbiddenException(__('Please create a valid AUTH_HASH_SECRET in ZoneMinder'));
@ -136,7 +143,7 @@ class HostController extends AppController {
}*/
$access_issued_at = time();
$access_ttl = (ZM_AUTH_HASH_TTL || 2) * 3600;
$access_ttl = max(ZM_AUTH_HASH_TTL,1) * 3600;
// by default access token will expire in 2 hrs
// you can change it by changing the value of ZM_AUTH_HASH_TLL

View File

@ -44,33 +44,26 @@ class MonitorsController extends AppController {
} else {
$conditions = array();
}
global $user;
$allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null;
if ( $allowedMonitors ) {
$conditions['Monitor.Id' ] = $allowedMonitors;
}
$find_array = array('conditions'=>$conditions,'contain'=>array('Group'));
if ( isset($conditions['GroupId']) ) {
$find_array['joins'] = array(
$find_array = array(
'conditions' => &$conditions,
'contain' => array('Group'),
'joins' => array(
array(
'table' => 'Groups_Monitors',
'type' => 'inner',
'type' => 'left',
'conditions' => array(
'Groups_Monitors.MonitorId = Monitor.Id'
'Groups_Monitors.MonitorId = Monitor.Id',
),
),
//array(
//'table' => 'Groups',
//'type' => 'inner',
//'conditions' => array(
//'Groups.Id = Groups_Monitors.GroupId',
//'Groups.Id' => $this->request->params['GroupId'],
//),
//)
),
'group' => '`Monitor`.`Id`',
);
}
$monitors = $this->Monitor->find('all',$find_array);
$this->set(array(
'monitors' => $monitors,
@ -249,16 +242,11 @@ class MonitorsController extends AppController {
// where C=on|off|status
public function alarm() {
$id = $this->request->params['named']['id'];
$cmd = strtolower($this->request->params['named']['command']);
if ( !$this->Monitor->exists($id) ) {
throw new NotFoundException(__('Invalid monitor'));
}
if ( $cmd != 'on' && $cmd != 'off' && $cmd != 'status' ) {
throw new BadRequestException(__('Invalid command'));
}
$zm_path_bin = Configure::read('ZM_PATH_BIN');
$mToken = $this->request->query('token') ? $this->request->query('token') : null;
$cmd = strtolower($this->request->params['named']['command']);
switch ($cmd) {
case 'on':
$q = '-a';
@ -272,42 +260,43 @@ class MonitorsController extends AppController {
$verbose = ''; // zmu has a bug - gives incorrect verbose output in this case
$q = '-s';
break;
default :
throw new BadRequestException(__('Invalid command'));
}
// form auth key based on auth credentials
$this->loadModel('Config');
$options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH'));
$config = $this->Config->find('first', $options);
$zmOptAuth = $config['Config']['Value'];
$options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_RELAY'));
$config = $this->Config->find('first', $options);
$zmAuthRelay = $config['Config']['Value'];
$auth = '';
if ( $zmOptAuth ) {
if ( ZM_OPT_USE_AUTH ) {
global $user;
$mToken = $this->request->query('token') ? $this->request->query('token') : $this->request->data('token');;
if ( $mToken ) {
$auth = ' -T '.$mToken;
} else if ( ZM_AUTH_RELAY == 'hashed' ) {
$auth = ' -A '.generateAuthHash(ZM_AUTH_HASH_IPS);
} else if ( ZM_AUTH_RELAY == 'plain' ) {
# Plain requires the plain text password which must either be in request or stored in session
$password = $this->request->query('pass') ? $this->request->query('pass') : $this->request->data('pass');;
if ( !$password )
$password = $this->request->query('password') ? $this->request->query('password') : $this->request->data('password');
if ( ! $password ) {
# during auth the session will have been populated with the plaintext password
$stateful = $this->request->query('stateful') ? $this->request->query('stateful') : $this->request->data('stateful');
if ( $stateful ) {
$password = $_SESSION['password'];
}
} else if ( $_COOKIE['ZMSESSID'] ) {
$password = $_SESSION['password'];
}
elseif ( $zmAuthRelay == 'hashed' ) {
$options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_HASH_SECRET'));
$config = $this->Config->find('first', $options);
$zmAuthHashSecret = $config['Config']['Value'];
$time = localtime();
$ak = $zmAuthHashSecret.$this->Session->Read('username').$this->Session->Read('passwordHash').$time[2].$time[3].$time[4].$time[5];
$ak = md5($ak);
$auth = ' -A '.$ak;
} else if ( $zmAuthRelay == 'plain' ) {
$auth = ' -U ' .$this->Session->Read('username').' -P '.$this->Session->Read('password');
} else if ( $zmAuthRelay == 'none' ) {
$auth = ' -U ' .$this->Session->Read('username');
$auth = ' -U ' .$user['Username'].' -P '.$password;
} else if ( ZM_AUTH_RELAY == 'none' ) {
$auth = ' -U ' .$user['Username'];
}
}
$shellcmd = escapeshellcmd("$zm_path_bin/zmu $verbose -m$id $q $auth");
$shellcmd = escapeshellcmd(ZM_PATH_BIN."/zmu $verbose -m$id $q $auth");
$status = exec ($shellcmd);
$this->set(array(

View File

@ -17,12 +17,17 @@ class ServersController extends AppController {
public function beforeFilter() {
parent::beforeFilter();
/*
* A user needs the server data to calculate how to view a monitor, and there really isn't anything sensitive in this data.
* So it has been decided for now to just let everyone read it.
global $user;
$canView = (!$user) || ($user['System'] != 'None');
if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
*/
}
/**

View File

@ -59,7 +59,7 @@ class Group extends AppModel {
*
* @var array
*/
public $hasMany = array(
public $hasAndBelongsToMany = array(
'Monitor' => array(
'className' => 'Monitor',
'joinTable' => 'Groups_Monitors',
@ -77,4 +77,5 @@ class Group extends AppModel {
'counterQuery' => ''
),
);
var $actsAs = array( 'Containable' );
}

View File

@ -128,12 +128,16 @@ class Control extends ZM_Object {
$cmds['PresetHome'] = 'presetHome';
if ( $this->CanZoom() ) {
if ( $this->CanZoomCon() )
if ( $this->CanZoomCon() ) {
$cmds['ZoomRoot'] = 'zoomCon';
elseif ( $this->CanZoomRel() )
} else if ( $this->CanZoomRel() ) {
$cmds['ZoomRoot'] = 'zoomRel';
elseif ( $this->CanZoomAbs() )
} else if ( $this->CanZoomAbs() ) {
$cmds['ZoomRoot'] = 'zoomAbs';
} else {
$cmds['ZoomRoot'] = '';
Error('No zoom type selected. Please select Continuous, Relative, Absolute');
}
$cmds['ZoomTele'] = $cmds['ZoomRoot'].'Tele';
$cmds['ZoomWide'] = $cmds['ZoomRoot'].'Wide';
$cmds['ZoomStop'] = 'zoomStop';
@ -142,12 +146,16 @@ class Control extends ZM_Object {
}
if ( $this->CanFocus() ) {
if ( $this->CanFocusCon() )
if ( $this->CanFocusCon() ) {
$cmds['FocusRoot'] = 'focusCon';
elseif ( $this->CanFocusRel() )
} else if ( $this->CanFocusRel() ) {
$cmds['FocusRoot'] = 'focusRel';
elseif ( $this->CanFocusAbs() )
} else if ( $this->CanFocusAbs() ) {
$cmds['FocusRoot'] = 'focusAbs';
} else {
$cmds['FocusRoot'] = '';
Error('No focus type selected. Please select Continuous, Relative, Absolute');
}
$cmds['FocusFar'] = $cmds['FocusRoot'].'Far';
$cmds['FocusNear'] = $cmds['FocusRoot'].'Near';
$cmds['FocusStop'] = 'focusStop';
@ -156,12 +164,16 @@ class Control extends ZM_Object {
}
if ( $this->CanIris() ) {
if ( $this->CanIrisCon() )
if ( $this->CanIrisCon() ) {
$cmds['IrisRoot'] = 'irisCon';
elseif ( $this->CanIrisRel() )
} else if ( $this->CanIrisRel() ) {
$cmds['IrisRoot'] = 'irisRel';
elseif ( $this->CanIrisAbs() )
} else if ( $this->CanIrisAbs() ) {
$cmds['IrisRoot'] = 'irisAbs';
} else {
$cmds['IrisRoot'] = '';
Error('No iris type selected. Please select Continuous, Relative, Absolute');
}
$cmds['IrisOpen'] = $cmds['IrisRoot'].'Open';
$cmds['IrisClose'] = $cmds['IrisRoot'].'Close';
$cmds['IrisStop'] = 'irisStop';
@ -170,12 +182,16 @@ class Control extends ZM_Object {
}
if ( $this->CanWhite() ) {
if ( $this->CanWhiteCon() )
if ( $this->CanWhiteCon() ) {
$cmds['WhiteRoot'] = 'whiteCon';
elseif ( $this->CanWhiteRel() )
} else if ( $this->CanWhiteRel() ) {
$cmds['WhiteRoot'] = 'whiteRel';
elseif ( $this->CanWhiteAbs() )
} else if ( $this->CanWhiteAbs() ) {
$cmds['WhiteRoot'] = 'whiteAbs';
} else {
Error('No White type selected. Please select Continuous, Relative, Absolute');
$cmds['WhiteRoot'] = '';
}
$cmds['WhiteIn'] = $cmds['WhiteRoot'].'In';
$cmds['WhiteOut'] = $cmds['WhiteRoot'].'Out';
$cmds['WhiteAuto'] = 'whiteAuto';
@ -183,12 +199,16 @@ class Control extends ZM_Object {
}
if ( $this->CanGain() ) {
if ( $this->CanGainCon() )
if ( $this->CanGainCon() ) {
$cmds['GainRoot'] = 'gainCon';
elseif ( $this->CanGainRel() )
} else if ( $this->CanGainRel() ) {
$cmds['GainRoot'] = 'gainRel';
elseif ( $this->CanGainAbs() )
} else if ( $this->CanGainAbs() ) {
$cmds['GainRoot'] = 'gainAbs';
} else {
Error('No Gain type selected');
$cmds['GainRoot'] = '';
}
$cmds['GainUp'] = $cmds['GainRoot'].'Up';
$cmds['GainDown'] = $cmds['GainRoot'].'Down';
$cmds['GainAuto'] = 'gainAuto';
@ -207,6 +227,7 @@ class Control extends ZM_Object {
$cmds['Center'] = $cmds['PresetHome'];
} else {
$cmds['MoveRoot'] = '';
Error('No move type selected. Please select Continuous, Relative, Absolute');
}
$cmds['MoveUp'] = $cmds['MoveRoot'].'Up';

View File

@ -47,14 +47,18 @@ class Event extends ZM_Object {
return ZM_Object::_find_one(get_class(), $parameters, $options);
}
public static function clear_cache() {
return ZM_Object::_clear_cache(get_class());
}
public function Storage( $new = null ) {
if ( $new ) {
$this->{'Storage'} = $new;
}
if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) ) {
if ( ! ( property_exists($this, 'Storage') and $this->{'Storage'} ) ) {
if ( isset($this->{'StorageId'}) and $this->{'StorageId'} )
$this->{'Storage'} = Storage::find_one(array('Id'=>$this->{'StorageId'}));
if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) )
if ( ! ( property_exists($this, 'Storage') and $this->{'Storage'} ) )
$this->{'Storage'} = new Storage(NULL);
}
return $this->{'Storage'};
@ -64,10 +68,10 @@ class Event extends ZM_Object {
if ( $new ) {
$this->{'SecondaryStorage'} = $new;
}
if ( ! ( array_key_exists('SecondaryStorage', $this) and $this->{'SecondaryStorage'} ) ) {
if ( ! ( property_exists($this, 'SecondaryStorage') and $this->{'SecondaryStorage'} ) ) {
if ( isset($this->{'SecondaryStorageId'}) and $this->{'SecondaryStorageId'} )
$this->{'SecondaryStorage'} = Storage::find_one(array('Id'=>$this->{'SecondaryStorageId'}));
if ( ! ( array_key_exists('SecondaryStorage', $this) and $this->{'SecondaryStorage'} ) )
if ( ! ( property_exists($this, 'SecondaryStorage') and $this->{'SecondaryStorage'} ) )
$this->{'SecondaryStorage'} = new Storage(NULL);
}
return $this->{'SecondaryStorage'};
@ -262,7 +266,7 @@ class Event extends ZM_Object {
if ( is_null($new) or ( $new != '' ) ) {
$this->{'DiskSpace'} = $new;
}
if ( (!array_key_exists('DiskSpace',$this)) or (null === $this->{'DiskSpace'}) ) {
if ( (!property_exists($this, 'DiskSpace')) or (null === $this->{'DiskSpace'}) ) {
$this->{'DiskSpace'} = folder_size($this->Path());
dbQuery('UPDATE Events SET DiskSpace=? WHERE Id=?', array($this->{'DiskSpace'}, $this->{'Id'}));
}
@ -298,7 +302,7 @@ class Event extends ZM_Object {
} // end function createListThumbnail
function ThumbnailWidth( ) {
if ( ! ( array_key_exists('ThumbnailWidth', $this) ) ) {
if ( ! ( property_exists($this, 'ThumbnailWidth') ) ) {
if ( ZM_WEB_LIST_THUMB_WIDTH ) {
$this->{'ThumbnailWidth'} = ZM_WEB_LIST_THUMB_WIDTH;
$scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'};
@ -315,7 +319,7 @@ class Event extends ZM_Object {
} // end function ThumbnailWidth
function ThumbnailHeight( ) {
if ( ! ( array_key_exists('ThumbnailHeight', $this) ) ) {
if ( ! ( property_exists($this, 'ThumbnailHeight') ) ) {
if ( ZM_WEB_LIST_THUMB_WIDTH ) {
$this->{'ThumbnailWidth'} = ZM_WEB_LIST_THUMB_WIDTH;
$scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'};
@ -413,26 +417,22 @@ class Event extends ZM_Object {
}
} // end if capture file exists
} // end if analyze file exists
}
} // end if frame or snapshot
$captPath = $eventPath.'/'.$captImage;
if ( ! file_exists($captPath) ) {
Error("Capture file does not exist at $captPath");
}
//echo "CI:$captImage, CP:$captPath, TCP:$captPath<br>";
$analImage = sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyse.jpg', $frame['FrameId']);
$analPath = $eventPath.'/'.$analImage;
//echo "AI:$analImage, AP:$analPath, TAP:$analPath<br>";
$alarmFrame = $frame['Type']=='Alarm';
$hasAnalImage = $alarmFrame && file_exists($analPath) && filesize($analPath);
$isAnalImage = $hasAnalImage && !$captureOnly;
if ( !ZM_WEB_SCALE_THUMBS || $scale >= SCALE_BASE || !function_exists('imagecreatefromjpeg') ) {
if ( !ZM_WEB_SCALE_THUMBS || ($scale >= SCALE_BASE) || !function_exists('imagecreatefromjpeg') ) {
$imagePath = $thumbPath = $isAnalImage ? $analPath : $captPath;
$imageFile = $imagePath;
$thumbFile = $thumbPath;
@ -508,10 +508,11 @@ class Event extends ZM_Object {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS );
} else if ( ZM_AUTH_RELAY == 'plain' ) {
$url = '?user='.$_SESSION['username'];
$url = '?pass='.$_SESSION['password'];
} elseif ( ZM_AUTH_RELAY == 'none' ) {
$url = '?user='.$_SESSION['username'];
$url .= '?user='.$_SESSION['username'];
$url .= '?pass='.$_SESSION['password'];
} else {
Error('Multi-Server requires AUTH_RELAY be either HASH or PLAIN');
return;
}
}
Logger::Debug("sending command to $url");
@ -555,10 +556,11 @@ class Event extends ZM_Object {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS);
} elseif ( ZM_AUTH_RELAY == 'plain' ) {
$url = '?user='.$_SESSION['username'];
$url = '?pass='.$_SESSION['password'];
} elseif ( ZM_AUTH_RELAY == 'none' ) {
$url = '?user='.$_SESSION['username'];
$url .= '?user='.$_SESSION['username'];
$url .= '?pass='.$_SESSION['password'];
} else {
Error('Multi-Server requires AUTH_RELAY be either HASH or PLAIN');
return;
}
}
Logger::Debug("sending command to $url");

View File

@ -11,6 +11,9 @@ class Filter extends ZM_Object {
'AutoExecute' => 0,
'AutoExecuteCmd' => 0,
'AutoEmail' => 0,
'EmailTo' => '',
'EmailSubject' => '',
'EmailBody' => '',
'AutoDelete' => 0,
'AutoArchive' => 0,
'AutoVideo' => 0,
@ -39,8 +42,8 @@ class Filter extends ZM_Object {
$this->{'Query'} = func_get_arg(0);;
$this->{'Query_json'} = jsonEncode($this->{'Query'});
}
if ( !array_key_exists('Query', $this) ) {
if ( array_key_exists('Query_json', $this) and $this->{'Query_json'} ) {
if ( !property_exists($this, 'Query') ) {
if ( property_exists($this, 'Query_json') and $this->{'Query_json'} ) {
$this->{'Query'} = jsonDecode($this->{'Query_json'});
} else {
$this->{'Query'} = array();
@ -115,13 +118,17 @@ class Filter extends ZM_Object {
public function control($command, $server_id=null) {
$Servers = $server_id ? Server::find(array('Id'=>$server_id)) : Server::find(array('Status'=>'Running'));
if ( !count($Servers) and !$server_id ) {
if ( !count($Servers) ) {
if ( !$server_id ) {
# This will be the non-multi-server case
$Servers = array(new Server());
} else {
Warning("Server not found for id $server_id");
}
}
foreach ( $Servers as $Server ) {
if ( !defined('ZM_SERVER_ID') or !$Server->Id() or ZM_SERVER_ID==$Server->Id() ) {
if ( (!defined('ZM_SERVER_ID')) or (!$Server->Id()) or (ZM_SERVER_ID==$Server->Id()) ) {
# Local
Logger::Debug("Controlling filter locally $command for server ".$Server->Id());
daemonControl($command, 'zmfilter.pl', '--filter_id='.$this->{'Id'}.' --daemon');
@ -139,7 +146,7 @@ class Filter extends ZM_Object {
$url = '?user='.$_SESSION['username'];
}
}
$url .= '&view=filter&action=control&command='.$command.'&Id='.$this->Id().'&ServerId='.$Server->Id();
$url .= '&view=filter&object=filter&action=control&command='.$command.'&Id='.$this->Id().'&ServerId='.$Server->Id();
Logger::Debug("sending command to $url");
$data = array();
if ( defined('ZM_ENABLE_CSRF_MAGIC') ) {

View File

@ -18,7 +18,7 @@ class Group extends ZM_Object {
}
public function delete() {
if ( array_key_exists('Id', $this) ) {
if ( property_exists($this, 'Id') ) {
dbQuery('DELETE FROM Groups_Monitors WHERE GroupId=?', array($this->{'Id'}));
dbQuery('UPDATE Groups SET ParentId=NULL WHERE ParentId=?', array($this->{'Id'}));
dbQuery('DELETE FROM Groups WHERE Id=?', array($this->{'Id'}));
@ -35,7 +35,7 @@ class Group extends ZM_Object {
if ( isset($new) ) {
$this->{'depth'} = $new;
}
if ( !array_key_exists('depth', $this) or ($this->{'depth'} === null) ) {
if ( !property_exists($this, 'depth') or ($this->{'depth'} === null) ) {
$this->{'depth'} = 0;
if ( $this->{'ParentId'} != null ) {
$Parent = Group::find_one(array('Id'=>$this->{'ParentId'}));
@ -46,7 +46,7 @@ class Group extends ZM_Object {
} // end public function depth
public function MonitorIds( ) {
if ( ! array_key_exists('MonitorIds', $this) ) {
if ( ! property_exists($this, 'MonitorIds') ) {
$this->{'MonitorIds'} = dbFetchAll('SELECT MonitorId FROM Groups_Monitors WHERE GroupId=?', 'MonitorId', array($this->{'Id'}));
}
return $this->{'MonitorIds'};

View File

@ -12,6 +12,7 @@ class Monitor extends ZM_Object {
protected $defaults = array(
'Id' => null,
'Name' => '',
'Notes' => '',
'ServerId' => 0,
'StorageId' => 0,
'Type' => 'Ffmpeg',
@ -45,6 +46,7 @@ protected $defaults = array(
'SaveJPEGs' => 3,
'VideoWriter' => '0',
'OutputCodec' => null,
'Encoder' => 'auto',
'OutputContainer' => null,
'EncoderParameters' => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n",
'RecordAudio' => array('type'=>'boolean', 'default'=>0),
@ -58,10 +60,10 @@ protected $defaults = array(
'LabelX' => 0,
'LabelY' => 0,
'LabelSize' => 1,
'ImageBufferCount' => 100,
'ImageBufferCount' => 20,
'WarmupCount' => 0,
'PreEventCount' => 0,
'PostEventCount' => 0,
'PreEventCount' => 5,
'PostEventCount' => 5,
'StreamReplayBuffer' => 0,
'AlarmFrameCount' => 1,
'SectionLength' => 600,
@ -69,10 +71,6 @@ protected $defaults = array(
'FrameSkip' => 0,
'MotionFrameSkip' => 0,
'AnalysisFPSLimit' => null,
'OutputCodec' => '0',
'Encoder' => 'auto',
'OutputContainer' => 'auto',
'Triggers' => null,
'AnalysisUpdateDelay' => 0,
'MaxFPS' => null,
'AlarmMaxFPS' => null,
@ -92,7 +90,7 @@ protected $defaults = array(
'DefaultScale' => 100,
'SignalCheckPoints' => 0,
'SignalCheckColour' => '#0000BE',
'WebColour' => 'red',
'WebColour' => '#ff0000',
'Exif' => array('type'=>'boolean','default'=>0),
'Sequence' => null,
'TotalEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
@ -120,18 +118,25 @@ private $status_fields = array(
);
public function Control() {
if ( !array_key_exists('Control', $this) ) {
if ( !property_exists($this, 'Control') ) {
if ( $this->ControlId() )
$this->{'Control'} = Control::find_one(array('Id'=>$this->{'ControlId'}));
if ( !(array_key_exists('Control', $this) and $this->{'Control'}) )
if ( !(property_exists($this, 'Control') and $this->{'Control'}) )
$this->{'Control'} = new Control();
}
return $this->{'Control'};
}
public function Server() {
return new Server($this->{'ServerId'});
if ( !property_exists($this, 'Server') ) {
if ( $this->ServerId() )
$this->{'Server'} = Server::find_one(array('Id'=>$this->{'ServerId'}));
if ( !property_exists($this, 'Server') ) {
$this->{'Server'} = new Server();
}
}
return $this->{'Server'};
}
public function __call($fn, array $args){
@ -142,7 +147,7 @@ private $status_fields = array(
$this->{$fn} = $args[0];
}
}
if ( array_key_exists($fn, $this) ) {
if ( property_exists($this, $fn) ) {
return $this->{$fn};
} else if ( array_key_exists($fn, $this->defaults) ) {
if ( is_array($this->defaults[$fn]) ) {
@ -215,9 +220,9 @@ private $status_fields = array(
$this->{'Width'} = $new;
$field = ( $this->Orientation() == 'ROTATE_90' or $this->Orientation() == 'ROTATE_270' ) ? 'Height' : 'Width';
if ( array_key_exists($field, $this) )
if ( property_exists($this, $field) )
return $this->{$field};
return $this->defaults{$field};
return $this->defaults[$field];
} // end function Width
public function ViewHeight($new=null) {
@ -225,9 +230,9 @@ private $status_fields = array(
$this->{'Height'} = $new;
$field = ( $this->Orientation() == 'ROTATE_90' or $this->Orientation() == 'ROTATE_270' ) ? 'Width' : 'Height';
if ( array_key_exists($field, $this) )
if ( property_exists($this, $field) )
return $this->{$field};
return $this->defaults{$field};
return $this->defaults[$field];
} // end function Height
public function SignalCheckColour($new=null) {
@ -238,10 +243,10 @@ private $status_fields = array(
// Validate that it's a valid colour (we seem to allow color names, not just hex).
// This also helps prevent XSS.
if (array_key_exists($field, $this) && preg_match('/^[#0-9a-zA-Z]+$/', $this->{$field})) {
if ( property_exists($this, $field) && preg_match('/^[#0-9a-zA-Z]+$/', $this->{$field})) {
return $this->{$field};
}
return $this->defaults{$field};
return $this->defaults[$field];
} // end function SignalCheckColour
public static function find( $parameters = array(), $options = array() ) {
@ -257,7 +262,7 @@ private $status_fields = array(
Warning('Attempt to control a monitor with no Id');
return;
}
if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) {
if ( (!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) {
if ( $this->Type() == 'Local' ) {
$zmcArgs = '-d '.$this->{'Device'};
} else {
@ -282,10 +287,11 @@ private $status_fields = array(
if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS);
} else if ( ZM_AUTH_RELAY == 'plain' ) {
$url = '?user='.$_SESSION['username'];
$url = '?pass='.$_SESSION['password'];
} elseif ( ZM_AUTH_RELAY == 'none' ) {
$url = '?user='.$_SESSION['username'];
$url .= '?user='.$_SESSION['username'];
$url .= '?pass='.$_SESSION['password'];
} else {
Error('Multi-Server requires AUTH_RELAY be either HASH or PLAIN');
return;
}
}
Logger::Debug("sending command to $url");
@ -310,7 +316,7 @@ private $status_fields = array(
return;
}
if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) {
if ( (!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) {
if ( $this->{'Function'} == 'None' || $this->{'Function'} == 'Monitor' || $mode == 'stop' ) {
if ( ZM_OPT_CONTROL ) {
daemonControl('stop', 'zmtrack.pl', '-m '.$this->{'Id'});
@ -340,10 +346,11 @@ private $status_fields = array(
if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS);
} else if ( ZM_AUTH_RELAY == 'plain' ) {
$url = '?user='.$_SESSION['username'];
$url = '?pass='.$_SESSION['password'];
} elseif ( ZM_AUTH_RELAY == 'none' ) {
$url = '?user='.$_SESSION['username'];
$url .= '?user='.$_SESSION['username'];
$url .= '?pass='.$_SESSION['password'];
} else {
Error('Multi-Server requires AUTH_RELAY be either HASH or PLAIN');
return;
}
}
Logger::Debug("sending command to $url");
@ -371,8 +378,8 @@ private $status_fields = array(
}
}
if ( !array_key_exists('GroupIds', $this) ) {
if ( array_key_exists('Id', $this) and $this->{'Id'} ) {
if ( !property_exists($this, 'GroupIds') ) {
if ( property_exists($this, 'Id') and $this->{'Id'} ) {
$this->{'GroupIds'} = dbFetchAll('SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=?', 'GroupId', array($this->{'Id'}) );
if ( ! $this->{'GroupIds'} )
$this->{'GroupIds'} = array();
@ -421,7 +428,7 @@ private $status_fields = array(
if ( $new ) {
$this->{'Storage'} = $new;
}
if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) ) {
if ( ! ( property_exists($this, 'Storage') and $this->{'Storage'} ) ) {
$this->{'Storage'} = isset($this->{'StorageId'}) ?
Storage::find_one(array('Id'=>$this->{'StorageId'})) :
new Storage(NULL);
@ -471,8 +478,8 @@ private $status_fields = array(
return $source;
} // end function Source
public function UrlToIndex() {
return $this->Server()->UrlToIndex();
public function UrlToIndex($port=null) {
return $this->Server()->UrlToIndex($port);
//ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : null);
}
@ -497,7 +504,7 @@ public function sendControlCommand($command) {
}
}
if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) {
if ( (!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) {
# Local
Logger::Debug('Trying to send options ' . print_r($options, true));

View File

@ -1,133 +1,22 @@
<?php
namespace ZM;
require_once('Object.php');
require_once('database.php');
class MontageLayout {
private $defaults = array(
class MontageLayout extends ZM_Object {
protected static $table = 'MontageLayouts';
protected $defaults = array(
'Id' => null,
'Name' => '',
'Positions' => 0,
);
public function __construct( $IdOrRow = NULL ) {
if ( $IdOrRow ) {
$row = NULL;
if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) {
$row = dbFetchOne( 'SELECT * FROM MontageLayouts WHERE Id=?', NULL, array( $IdOrRow ) );
if ( ! $row ) {
Error("Unable to load MontageLayout record for Id=" . $IdOrRow );
}
} else if ( is_array( $IdOrRow ) ) {
$row = $IdOrRow;
} else {
Error("Unknown argument passed to MontageLayout Constructor ($IdOrRow)");
return;
public static function find( $parameters = array(), $options = array() ) {
return ZM_Object::_find(get_class(), $parameters, $options);
}
if ( $row ) {
foreach ($row as $k => $v) {
$this->{$k} = $v;
public static function find_one( $parameters = array(), $options = array() ) {
return ZM_Object::_find_one(get_class(), $parameters, $options);
}
} else {
Error('No row for MontageLayout ' . $IdOrRow );
}
} # end if isset($IdOrRow)
} // end function __construct
public function __call($fn, array $args){
if ( count($args) ) {
$this->{$fn} = $args[0];
}
if ( array_key_exists($fn, $this) ) {
return $this->{$fn};
} else {
if ( array_key_exists( $fn, $this->defaults ) ) {
return $this->defaults{$fn};
} else {
$backTrace = debug_backtrace();
$file = $backTrace[1]['file'];
$line = $backTrace[1]['line'];
Warning( "Unknown function call MontageLayout->$fn from $file:$line" );
}
}
}
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;
}
}
}
public static function find( $parameters = null, $options = null ) {
$filters = array();
$sql = 'SELECT * FROM MontageLayouts ';
$values = array();
if ( $parameters ) {
$fields = array();
$sql .= 'WHERE ';
foreach ( $parameters as $field => $value ) {
if ( $value == null ) {
$fields[] = $field.' IS NULL';
} else if ( is_array( $value ) ) {
$func = function(){return '?';};
$fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')';
$values += $value;
} else {
$fields[] = $field.'=?';
$values[] = $value;
}
}
$sql .= implode(' AND ', $fields );
}
if ( $options and isset($options['order']) ) {
$sql .= ' ORDER BY ' . $options['order'];
}
$result = dbQuery($sql, $values);
if ( $result ) {
$results = $result->fetchALL();
foreach ( $results as $row ) {
$filters[] = new MontageLayout($row);
}
}
return $filters;
}
public function save( $new_values = null ) {
if ( $new_values ) {
foreach ( $new_values as $k=>$v ) {
$this->{$k} = $v;
}
}
$fields = array_values( array_filter( array_keys($this->defaults), function($field){return $field != 'Id';} ) );
$values = null;
if ( isset($this->{'Id'}) ) {
$sql = 'UPDATE MontageLayouts SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $fields ) ) . ' WHERE Id=?';
$values = array_map( function($field){return $this->{$field};}, $fields );
$values[] = $this->{'Id'};
dbQuery($sql, $values);
} else {
$sql = 'INSERT INTO MontageLayouts ('.implode( ',', $fields ).') VALUES ('.implode(',',array_map( function(){return '?';}, $fields ) ).')';
$values = array_map( function($field){return $this->{$field};}, $fields );
dbQuery($sql, $values);
global $dbConn;
$this->{'Id'} = $dbConn->lastInsertId();
}
} // end function save
} // end class MontageLayout
?>

View File

@ -45,7 +45,7 @@ class ZM_Object {
$this->{$fn} = $args[0];
}
if ( array_key_exists($fn, $this) ) {
if ( property_exists($this, $fn) ) {
return $this->{$fn};
} else {
if ( array_key_exists($fn, $this->defaults) ) {
@ -110,8 +110,9 @@ class ZM_Object {
public static function _find_one($class, $parameters = array(), $options = array() ) {
global $object_cache;
if ( ! isset($object_cache[$class]) )
if ( ! isset($object_cache[$class]) ) {
$object_cache[$class] = array();
}
$cache = &$object_cache[$class];
if (
( count($parameters) == 1 ) and
@ -127,6 +128,11 @@ class ZM_Object {
return $results[0];
}
public static function _clear_cache($class) {
global $object_cache;
$object_cache[$class] = array();
}
public static function Objects_Indexed_By_Id($class) {
$results = array();
foreach ( ZM_Object::_find($class, null, array('order'=>'lower(Name)')) as $Object ) {
@ -140,10 +146,10 @@ class ZM_Object {
foreach ($this->defaults as $key => $value) {
if ( is_callable(array($this, $key)) ) {
$json[$key] = $this->$key();
} else if ( array_key_exists($key, $this) ) {
} else if ( property_exists($this, $key) ) {
$json[$key] = $this->{$key};
} else {
$json[$key] = $this->defaults{$key};
$json[$key] = $this->defaults[$key];
}
}
return json_encode($json);
@ -158,14 +164,24 @@ class ZM_Object {
# perhaps should turn into a comma-separated string
$this->{$k} = implode(',', $v);
} else if ( is_string($v) ) {
if ( $v == '' and array_key_exists($k, $this->defaults) ) {
if ( is_array($this->defaults[$k]) )
if ( 0 ) {
# Remarking this out. We are setting a value, not asking for a default to be set.
# So don't do defaults here, do them somewhere else
if ( ($v == null) and array_key_exists($k, $this->defaults) ) {
Logger::Debug("$k => Have default for $v: ");
if ( is_array($this->defaults[$k]) ) {
$this->{$k} = $this->defaults[$k]['default'];
else
} else {
$this->{$k} = $this->defaults[$k];
Logger::Debug("$k => Have default for $v: " . $this->{$k});
}
} else {
$this->{$k} = trim($v);
}
} else {
$this->{$k} = trim($v);
}
} else if ( is_integer($v) ) {
$this->{$k} = $v;
} else if ( is_bool($v) ) {
@ -192,7 +208,7 @@ class ZM_Object {
continue;
}
if ( $this->defaults[$field] ) {
if ( isset($this->defaults[$field]) ) {
if ( is_array($this->defaults[$field]) ) {
$new_values[$field] = $this->defaults[$field]['default'];
} else {
@ -215,7 +231,7 @@ class ZM_Object {
} else if ( $this->$field() != $value ) {
$changes[$field] = $value;
}
} else if ( array_key_exists($field, $this) ) {
} else if ( property_exists($this, $field) ) {
$type = (array_key_exists($field, $this->defaults) && is_array($this->defaults[$field])) ? $this->defaults[$field]['type'] : 'scalar';
Logger::Debug("Checking field $field => current ".
(is_array($this->{$field}) ? implode(',',$this->{$field}) : $this->{$field}) . ' ?= ' .
@ -280,6 +296,18 @@ class ZM_Object {
$this->set($new_values);
}
# Set defaults. Note that we only replace "" with null, not other values
# because for example if we want to clear TimestampFormat, we clear it, but the default is a string value
foreach ( $this->defaults as $field => $default ) {
if ( (!property_exists($this, $field)) or ($this->{$field} == '') ) {
if ( is_array($default) ) {
$this->{$field} = $default['default'];
} else if ( $default == null ) {
$this->{$field} = $default;
}
}
}
$fields = array_filter(
$this->defaults,
function($v) {

View File

@ -80,7 +80,7 @@ class Server extends ZM_Object {
public function PathToZMS( $new = null ) {
if ( $new != null )
$this{'PathToZMS'} = $new;
$this->{'PathToZMS'} = $new;
if ( $this->Id() and $this->{'PathToZMS'} ) {
return $this->{'PathToZMS'};
} else {

View File

@ -58,6 +58,13 @@ class Storage extends ZM_Object {
return $this->{'Events'};
}
public function EventCount() {
if ( (! property_exists($this, 'EventCount')) or (!$this->{'EventCount'}) ) {
$this->{'EventCount'} = dbFetchOne('SELECT COUNT(*) AS EventCount FROM Events WHERE StorageId=?', 'EventCount', array($this->Id()));
}
return $this->{'EventCount'};
}
public function disk_usage_percent() {
$path = $this->Path();
if ( ! $path ) {
@ -80,7 +87,7 @@ class Storage extends ZM_Object {
}
public function disk_total_space() {
if ( !array_key_exists('disk_total_space', $this) ) {
if ( !property_exists($this, 'disk_total_space') ) {
$path = $this->Path();
if ( file_exists($path) ) {
$this->{'disk_total_space'} = disk_total_space($path);
@ -94,7 +101,7 @@ class Storage extends ZM_Object {
public function disk_used_space() {
# This isn't a function like this in php, so we have to add up the space used in each event.
if ( ( !array_key_exists('disk_used_space', $this)) or !$this->{'disk_used_space'} ) {
if ( ( !property_exists($this, 'disk_used_space')) or !$this->{'disk_used_space'} ) {
if ( $this->{'Type'} == 's3fs' ) {
$this->{'disk_used_space'} = $this->event_disk_space();
} else {
@ -112,17 +119,18 @@ class Storage extends ZM_Object {
public function event_disk_space() {
# This isn't a function like this in php, so we have to add up the space used in each event.
if ( (! array_key_exists('DiskSpace', $this)) or (!$this->{'DiskSpace'}) ) {
if ( (! property_exists($this, 'DiskSpace')) or (!$this->{'DiskSpace'}) ) {
$used = dbFetchOne('SELECT SUM(DiskSpace) AS DiskSpace FROM Events WHERE StorageId=? AND DiskSpace IS NOT NULL', 'DiskSpace', array($this->Id()));
do {
# Do in batches of 1000 so as to not useup all ram
# Do in batches of 1000 so as to not useup all ram, Event will do caching though...
$events = Event::find(array('StorageId'=>$this->Id(), 'DiskSpace'=>null), array('limit'=>1000));
foreach ( $events as $Event ) {
$Event->Storage($this); // Prevent further db hit
# DiskSpace will update the event
$used += $Event->DiskSpace();
} #end foreach
Event::clear_cache();
} while ( count($events) == 1000 );
$this->{'DiskSpace'} = $used;
}
@ -130,8 +138,8 @@ class Storage extends ZM_Object {
} // end function event_disk_space
public function Server() {
if ( ! array_key_exists('Server',$this) ) {
if ( array_key_exists('ServerId', $this) ) {
if ( ! property_exists($this, 'Server') ) {
if ( property_exists($this, 'ServerId') ) {
$this->{'Server'} = Server::find_one(array('Id'=>$this->{'ServerId'}));
if ( !$this->{'Server'} ) {

41
web/includes/Zone.php Normal file
View File

@ -0,0 +1,41 @@
<?php
namespace ZM;
require_once('database.php');
require_once('Object.php');
class Zone extends ZM_Object {
protected static $table = 'Zones';
protected $defaults = array(
'Id' => null,
'Name' => '',
'Type' => 'Active',
'Units' => 'Pixels',
'CheckMethod' => 'Blobs',
'MinPixelThreshold' => null,
'MaxPixelThreshold' => null,
'MinAlarmPixels' => null,
'MaxAlarmPixels' => null,
'FilterX' => null,
'FilterY' => null,
'MinFilterPixels' => null,
'MaxFilterPixels' => null,
'MinBlobPixels' => null,
'MaxBlobPixels' => null,
'MinBlobs' => null,
'MaxBlobs' => null,
'OverloadFrames' => 0,
'ExtendAlarmFrames' => 0,
);
public static function find( $parameters = array(), $options = array() ) {
return ZM_Object::_find(get_class(), $parameters, $options);
}
public static function find_one( $parameters = array(), $options = array() ) {
return ZM_Object::_find_one(get_class(), $parameters, $options);
}
} # end class Zone
?>

Some files were not shown because too many files have changed in this diff Show More