Merge branch 'master' into fix_startup_db_connection
This commit is contained in:
commit
0c5eead929
|
@ -1,23 +1,43 @@
|
|||
You should only file an issue if you found a bug. Feature and enhancement requests, general discussions and support questions should occur in one of the following areas:
|
||||
**THIS FORUM IS FOR BUG REPORTS ONLY**
|
||||
|
||||
- The ZoneMinder IRC channel - irc.freenode.net #zoneminder
|
||||
Do not post feature or enhancement requests, general discussions or support questions here.
|
||||
|
||||
Feature and enhancement requests, general discussions, and support questions should occur in one of the following areas:
|
||||
|
||||
- The [ZoneMinder-Chat Slack channel](https://zoneminder-chat.herokuapp.com/)
|
||||
- The [ZoneMinder Forum](https://forums.zoneminder.com/)
|
||||
|
||||
**Do not post feature or enhancement requests, general discussions or support questions here.**
|
||||
|
||||
Docker related issues should be posted here: https://github.com/ZoneMinder/zmdockerfiles
|
||||
|
||||
Make sure you are running the latest version of ZoneMinder before reporting an issue.
|
||||
In order to submit a bug report, please populate the fields below. This is required.
|
||||
|
||||
**ZoneMinder Version (`zmaudit.pl -v`):**
|
||||
**Describe Your Environment**
|
||||
- Version of ZoneMinder [release version, development version, or commit]
|
||||
- How you installed ZoneMinder [e.g. PPA, RPMFusion, from-source, etc]
|
||||
- Full name and version of OS
|
||||
|
||||
**Are you using a development snapshot / git checkout? If so, what is the latest commit? (`git rev-parse HEAD`):**
|
||||
**If the issue concerns a camera**
|
||||
- Make and Model
|
||||
- frame rate
|
||||
- resolution
|
||||
- ZoneMinder Source Type:
|
||||
|
||||
**Linux Distribution and Version (`cat /etc/os-release` or `cat /etc/redhat-release`):**
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**If the issue concerns a camera, provide the make, model, frame rate, resolution and ZoneMinder Source Type:**
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Relevant log lines:**
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Debug Logs**
|
||||
```
|
||||
log lines here
|
||||
|
||||
<insert debug logs here, please make sure they are within the ``` quotes so they are formatted properly>
|
||||
|
||||
```
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# Configuration for welcome - https://github.com/behaviorbot/welcome
|
||||
|
||||
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
|
||||
|
||||
# Comment to be posted to on first time issues
|
||||
newIssueWelcomeComment: >
|
||||
Thanks for opening your first issue here! Just a reminder, this forum is for Bug Reports only. Be sure to follow the issue template!
|
||||
|
||||
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
|
||||
|
||||
# Comment to be posted to on PRs from first time contributors in your repository
|
||||
#newPRWelcomeComment: >
|
||||
# Thanks for opening this pull request! Please check out our contributing guidelines.
|
||||
|
||||
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
|
||||
|
||||
# Comment to be posted to on pull requests merged by a first time user
|
||||
#firstPRMergeComment: >
|
||||
# Congrats on merging your first pull request! We here at behaviorbot are proud of you!
|
||||
|
||||
# It is recommend to include as many gifs and emojis as possible
|
|
@ -0,0 +1,13 @@
|
|||
# Configuration for probot-no-response - https://github.com/probot/no-response
|
||||
|
||||
# Number of days of inactivity before an Issue is closed for lack of response
|
||||
daysUntilClose: 7
|
||||
# Label requiring a response
|
||||
responseRequiredLabel: more-information-needed
|
||||
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue has been automatically closed because there has been no response
|
||||
to our request for more information from the original author. With only the
|
||||
information that is currently in the issue, we don't have enough information
|
||||
to take action. Please reach out if you have or find the answers we need so
|
||||
that we can investigate further.
|
|
@ -0,0 +1,26 @@
|
|||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 7
|
||||
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- "Under Review"
|
||||
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
|
@ -38,6 +38,7 @@ env:
|
|||
- OS=el DIST=7
|
||||
- OS=fedora DIST=27 DOCKER_REPO=knnniggett/packpack
|
||||
- OS=fedora DIST=28 DOCKER_REPO=knnniggett/packpack
|
||||
- OS=fedora DIST=29 DOCKER_REPO=knnniggett/packpack
|
||||
- OS=ubuntu DIST=trusty
|
||||
- OS=ubuntu DIST=xenial
|
||||
- OS=ubuntu DIST=trusty ARCH=i386
|
||||
|
|
|
@ -802,6 +802,24 @@ if(WITH_SYSTEMD)
|
|||
endif(NOT POLKIT_FOUND)
|
||||
endif(WITH_SYSTEMD)
|
||||
|
||||
# Find the path to an arp compatible executable
|
||||
if(ZM_PATH_ARP STREQUAL "")
|
||||
find_program(ARP_EXECUTABLE arp)
|
||||
if(ARP_EXECUTABLE)
|
||||
set(ZM_PATH_ARP "${ARP_EXECUTABLE}")
|
||||
mark_as_advanced(ARP_EXECUTABLE)
|
||||
else(ARP_EXECUTABLE)
|
||||
find_program(ARP_EXECUTABLE ip)
|
||||
if(ARP_EXECUTABLE)
|
||||
set(ZM_PATH_ARP "${ARP_EXECUTABLE} neigh")
|
||||
mark_as_advanced(ARP_EXECUTABLE)
|
||||
endif(ARP_EXECUTABLE)
|
||||
endif(ARP_EXECUTABLE)
|
||||
if(ARP_EXECUTABLE-NOTFOUND)
|
||||
message(WARNING "Unable to find a compatible arp binary. Monitor probe will not function." )
|
||||
endif(ARP_EXECUTABLE-NOTFOUND)
|
||||
endif(ZM_PATH_ARP STREQUAL "")
|
||||
|
||||
# Some variables that zm expects
|
||||
set(ZM_PID "${ZM_RUNDIR}/zm.pid")
|
||||
set(ZM_CONFIG "${ZM_CONFIG_DIR}/zm.conf")
|
||||
|
|
|
@ -3,6 +3,8 @@ 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)
|
||||
|
||||
[![Join Slack](https://github.com/ozonesecurity/ozonebase/blob/master/img/slacksm.png?raw=true)](https://zoneminder-chat.herokuapp.com)
|
||||
|
||||
All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org
|
||||
|
||||
## Overview
|
||||
|
@ -40,7 +42,7 @@ This is the recommended method to install ZoneMinder onto your system. ZoneMinde
|
|||
|
||||
If a repository that hosts ZoneMinder packages is not available for your distro, then you are encouraged to build your own package, rather than build from source. While each distro is different in ways that set it apart from all the others, they are often similar enough to allow you to adapt another distro's package building instructions to your own.
|
||||
|
||||
### Building a ZoneMinder Package
|
||||
### Building a ZoneMinder Package ###
|
||||
|
||||
Building ZoneMinder into a package is not any harder than building from source. As a matter of fact, if you have successfully built ZoneMinder from source in the past, then you may find these steps to be easier.
|
||||
|
||||
|
@ -51,9 +53,9 @@ Lastly, if you desire to build a development snapshot from the master branch, it
|
|||
Please visit our [ReadtheDocs site](https://zoneminder.readthedocs.org/en/stable/installationguide/index.html) for distro specific instructions.
|
||||
|
||||
### Package Maintainers
|
||||
Many of the ZoneMinder configration variable default values are not configurable at build time through autotools or cmake. A new tool called *zmeditconfigdata.sh* has been added to allow package maintainers to manipulate any variable stored in ConfigData.pm without patching the source.
|
||||
Many of the ZoneMinder configuration variable default values are not configurable at build time through autotools or cmake. A new tool called *zmeditconfigdata.sh* has been added to allow package maintainers to manipulate any variable stored in ConfigData.pm without patching the source.
|
||||
|
||||
For example, let's say I have created a new ZoneMinder package that contains the cambolzola javascript file. However, by default cambozola support is turned off. To fix that, add this to the pacakging script:
|
||||
For example, let's say I have created a new ZoneMinder package that contains the cambozola javascript file. However, by default cambozola support is turned off. To fix that, add this to the packaging script:
|
||||
```bash
|
||||
./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes
|
||||
```
|
||||
|
|
|
@ -50,4 +50,4 @@ ZM_PATH_SWAP=@ZM_TMPDIR@
|
|||
|
||||
# Full path to optional arp binary
|
||||
# ZoneMinder will find the arp binary automatically on most systems
|
||||
ZM_PATH_ARP=@ZM_PATH_ARP@
|
||||
ZM_PATH_ARP="@ZM_PATH_ARP@"
|
||||
|
|
|
@ -517,6 +517,7 @@ CREATE TABLE `Monitors` (
|
|||
`ArchivedEvents` int(10) default NULL,
|
||||
`ArchivedEventDiskSpace` bigint default NULL,
|
||||
`ZoneCount` TINYINT NOT NULL DEFAULT 0,
|
||||
`Refresh` int(10) unsigned default NULL,
|
||||
PRIMARY KEY (`Id`)
|
||||
) ENGINE=@ZM_MYSQL_ENGINE@;
|
||||
|
||||
|
@ -555,11 +556,16 @@ INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');
|
|||
DROP TABLE IF EXISTS `Servers`;
|
||||
CREATE TABLE `Servers` (
|
||||
`Id` int(10) unsigned NOT NULL auto_increment,
|
||||
`Protocol` TEXT,
|
||||
`Hostname` TEXT,
|
||||
`Port` INTEGER UNSIGNED,
|
||||
`PathToIndex` TEXT,
|
||||
`PathToZMS` TEXT,
|
||||
`PathToApi` TEXT,
|
||||
`Name` varchar(64) NOT NULL default '',
|
||||
`State_Id` int(10) unsigned,
|
||||
`Status` enum('Unknown','NotRunning','Running') NOT NULL default 'Unknown',
|
||||
`Load` DECIMAL(5,1),
|
||||
`CpuLoad` DECIMAL(5,1) default NULL,
|
||||
`TotalMem` bigint unsigned default null,
|
||||
`FreeMem` bigint unsigned default null,
|
||||
`TotalSwap` bigint unsigned default null,
|
||||
|
@ -580,7 +586,7 @@ DROP TABLE IF EXISTS `Stats`;
|
|||
CREATE TABLE `Stats` (
|
||||
`MonitorId` int(10) unsigned NOT NULL default '0',
|
||||
`ZoneId` int(10) unsigned NOT NULL default '0',
|
||||
`EventId` int(10) unsigned NOT NULL default '0',
|
||||
`EventId` BIGINT UNSIGNED NOT NULL,
|
||||
`FrameId` int(10) unsigned NOT NULL default '0',
|
||||
`PixelDiff` tinyint(3) unsigned NOT NULL default '0',
|
||||
`AlarmPixels` int(10) unsigned NOT NULL default '0',
|
||||
|
@ -777,6 +783,11 @@ INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-423','Ffmpeg','Reolink',0,0,1,1
|
|||
INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-411','Ffmpeg','Reolink',0,0,1,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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);
|
||||
INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',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,'IOS Camera','Ffmpeg','IPCAMIOS',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,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,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);
|
||||
INSERT INTO `Controls` VALUES (NULL,'PSIA','Remote','PSIA',0,0,0,1,0,0,1,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,1,0,1,0,0,0,0,1,-100,100,0,0,1,0,0,0,0,1,-100,100,0,0,0,0);
|
||||
INSERT INTO `Controls` VALUES (NULL,'Dahua','Remote','Dahua',0,0,0,1,0,0,1,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,1,0,1,0,0,0,0,1,1,8,0,0,1,0,0,0,0,1,1,8,0,0,0,0);
|
||||
|
||||
--
|
||||
-- Add some monitor preset values
|
||||
|
@ -803,6 +814,7 @@ INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, unicast','Remote','rtsp
|
|||
INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, multicast','Remote','rtsp',0,255,'rtsp','rtpMulti','<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','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,'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);
|
||||
|
@ -819,6 +831,7 @@ INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg','Remote','http',0,0,'
|
|||
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100);
|
||||
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/GetData.cgi',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
|
||||
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100);
|
||||
INSERT INTO MonitorPresets VALUES (NULL,'IP Webcam by Pavel Khlebovich 1920x1080','Remote','/dev/video<?>','0',255,'http','simple','<ip-address>','8080','/video','',1920,1080,0,NULL,0,'0','','',100,100);
|
||||
INSERT INTO MonitorPresets VALUES (NULL,'VEO Observer, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
|
||||
INSERT INTO MonitorPresets VALUES (NULL,'Blue Net Video Server, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/cgi-bin/image.cgi?control=0&id=admin&passwd=admin',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
|
||||
INSERT into MonitorPresets VALUES (NULL,'ACTi IP, mpeg4, unicast','Remote',NULL,NULL,NULL,'rtsp','rtpUni','<ip-address>',7070,'','/track',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
DROP TABLE IF EXISTS `Monitor_Status`;
|
||||
CREATE TABLE `Monitor_Status` (
|
||||
`MonitorId` int(10) unsigned NOT NULL,
|
||||
`Status` enum('Unknown','NotRunning','Running','Connected','Signal') NOT NULL default 'Unknown',
|
||||
`CaptureFPS` DECIMAL(10,2) NOT NULL default 0,
|
||||
`AnalysisFPS` DECIMAL(5,2) NOT NULL default 0,
|
||||
PRIMARY KEY (`MonitorId`)
|
||||
) ENGINE=MEMORY;
|
||||
|
||||
SET SESSION sql_mode='NO_AUTO_VALUE_ON_ZERO';
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM Storage WHERE Name = 'Default' AND Id=0 AND Path='/var/cache/zoneminder/events'
|
||||
) > 0,
|
||||
"SELECT 'Default Storage Area already exists.'",
|
||||
"INSERT INTO Storage (Id,Name,Path,Scheme,ServerId) VALUES (0,'Default','/var/cache/zoneminder/events','Medium',NULL)"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
|
@ -5,7 +5,7 @@
|
|||
-- Add Refresh column to Monitors table
|
||||
--
|
||||
|
||||
ALTER TABLE `zm`.`Monitors`
|
||||
ALTER TABLE `Monitors`
|
||||
CHANGE COLUMN `Type` `Type` ENUM('Local', 'Remote', 'File', 'Ffmpeg', 'Libvlc', 'cURL', 'WebSite') NOT NULL DEFAULT 'Local' ;
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
|
@ -16,7 +16,7 @@ SET @s = (SELECT IF(
|
|||
AND column_name = 'Refresh'
|
||||
) > 0,
|
||||
"SELECT 'Column Refresh exists in Monitors'",
|
||||
"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL"
|
||||
"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL AFTER `ZoneCount`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
--
|
||||
-- This updates a 1.31.44 database to 1.31.45
|
||||
--
|
||||
-- Add WebSite enum to Monitor.Type
|
||||
-- Add Refresh column to Monitors table
|
||||
|
||||
-- This is the same as the update to 1.31.43, but due to Refresh not being added to zm_create.sql.in we need to have it
|
||||
-- again in order to fix people who did a fresh install from 1.31.43 or 1.31.44.
|
||||
--
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = 'Monitors'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'Refresh'
|
||||
) > 0,
|
||||
"SELECT 'Column Refresh exists in Monitors'",
|
||||
"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL AFTER `ZoneCount`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE Stats MODIFY COLUMN EventId bigint unsigned NOT NULL;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE Frames MODIFY COLUMN EventId bigint unsigned NOT NULL;
|
|
@ -0,0 +1,5 @@
|
|||
--
|
||||
-- This updates a 1.31.47 database to 1.32.0
|
||||
--
|
||||
-- No changes required
|
||||
--
|
|
@ -0,0 +1,5 @@
|
|||
--
|
||||
-- This updates a 1.32.0 database to 1.32.1
|
||||
--
|
||||
-- No changes required
|
||||
--
|
|
@ -0,0 +1,5 @@
|
|||
--
|
||||
-- This updates a 1.32.1 database to 1.32.2
|
||||
--
|
||||
-- No changes required
|
||||
--
|
|
@ -0,0 +1,362 @@
|
|||
--
|
||||
-- This updates a 1.32.2 database to 1.32.3
|
||||
--
|
||||
|
||||
delimiter //
|
||||
DROP TRIGGER IF EXISTS Events_Hour_delete_trigger//
|
||||
CREATE TRIGGER Events_Hour_delete_trigger BEFORE DELETE ON Events_Hour
|
||||
FOR EACH ROW BEGIN
|
||||
UPDATE Monitors SET
|
||||
HourEvents = COALESCE(HourEvents,1)-1,
|
||||
HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0)
|
||||
WHERE Id=OLD.MonitorId;
|
||||
END;
|
||||
//
|
||||
|
||||
DROP TRIGGER IF EXISTS Events_Hour_update_trigger//
|
||||
|
||||
CREATE TRIGGER Events_Hour_update_trigger AFTER UPDATE ON Events_Hour
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
declare diff BIGINT default 0;
|
||||
|
||||
set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0);
|
||||
IF ( diff ) THEN
|
||||
IF ( NEW.MonitorID != OLD.MonitorID ) THEN
|
||||
UPDATE Monitors SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0) WHERE Monitors.Id=OLD.MonitorId;
|
||||
UPDATE Monitors SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)-COALESCE(NEW.DiskSpace,0) WHERE Monitors.Id=NEW.MonitorId;
|
||||
ELSE
|
||||
UPDATE Monitors SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+diff WHERE Monitors.Id=NEW.MonitorId;
|
||||
END IF;
|
||||
END IF;
|
||||
END;
|
||||
//
|
||||
DELIMITER ;
|
||||
|
||||
delimiter //
|
||||
DROP TRIGGER IF EXISTS Events_Day_delete_trigger//
|
||||
CREATE TRIGGER Events_Day_delete_trigger BEFORE DELETE ON Events_Day
|
||||
FOR EACH ROW BEGIN
|
||||
UPDATE Monitors SET
|
||||
DayEvents = COALESCE(DayEvents,1)-1,
|
||||
DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0)
|
||||
WHERE Id=OLD.MonitorId;
|
||||
END;
|
||||
//
|
||||
|
||||
DROP TRIGGER IF EXISTS Events_Day_update_trigger;
|
||||
CREATE TRIGGER Events_Day_update_trigger AFTER UPDATE ON Events_Day
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
declare diff BIGINT default 0;
|
||||
|
||||
set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0);
|
||||
IF ( diff ) THEN
|
||||
IF ( NEW.MonitorID != OLD.MonitorID ) THEN
|
||||
UPDATE Monitors SET DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0) WHERE Monitors.Id=OLD.MonitorId;
|
||||
UPDATE Monitors SET DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitors.Id=NEW.MonitorId;
|
||||
ELSE
|
||||
UPDATE Monitors SET DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)+diff WHERE Monitors.Id=NEW.MonitorId;
|
||||
END IF;
|
||||
END IF;
|
||||
END;
|
||||
//
|
||||
|
||||
|
||||
DROP TRIGGER IF EXISTS Events_Week_delete_trigger//
|
||||
CREATE TRIGGER Events_Week_delete_trigger BEFORE DELETE ON Events_Week
|
||||
FOR EACH ROW BEGIN
|
||||
UPDATE Monitors SET
|
||||
WeekEvents = COALESCE(WeekEvents,1)-1,
|
||||
WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0)
|
||||
WHERE Id=OLD.MonitorId;
|
||||
END;
|
||||
//
|
||||
|
||||
DROP TRIGGER IF EXISTS Events_Week_update_trigger;
|
||||
CREATE TRIGGER Events_Week_update_trigger AFTER UPDATE ON Events_Week
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
declare diff BIGINT default 0;
|
||||
|
||||
set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0);
|
||||
IF ( diff ) THEN
|
||||
IF ( NEW.MonitorID != OLD.MonitorID ) THEN
|
||||
UPDATE Monitors SET WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0) WHERE Monitors.Id=OLD.MonitorId;
|
||||
UPDATE Monitors SET WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitors.Id=NEW.MonitorId;
|
||||
ELSE
|
||||
UPDATE Monitors SET WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)+diff WHERE Monitors.Id=NEW.MonitorId;
|
||||
END IF;
|
||||
END IF;
|
||||
END;
|
||||
//
|
||||
|
||||
DROP TRIGGER IF EXISTS Events_Month_delete_trigger//
|
||||
CREATE TRIGGER Events_Month_delete_trigger BEFORE DELETE ON Events_Month
|
||||
FOR EACH ROW BEGIN
|
||||
UPDATE Monitors SET
|
||||
MonthEvents = COALESCE(MonthEvents,1)-1,
|
||||
MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0)
|
||||
WHERE Id=OLD.MonitorId;
|
||||
END;
|
||||
//
|
||||
|
||||
DROP TRIGGER IF EXISTS Events_Month_update_trigger;
|
||||
CREATE TRIGGER Events_Month_update_trigger AFTER UPDATE ON Events_Month
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
declare diff BIGINT default 0;
|
||||
|
||||
set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0);
|
||||
IF ( diff ) THEN
|
||||
IF ( NEW.MonitorID != OLD.MonitorID ) THEN
|
||||
UPDATE Monitors SET MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace) WHERE Monitors.Id=OLD.MonitorId;
|
||||
UPDATE Monitors SET MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)+COALESCE(NEW.DiskSpace) WHERE Monitors.Id=NEW.MonitorId;
|
||||
ELSE
|
||||
UPDATE Monitors SET MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)+diff WHERE Monitors.Id=NEW.MonitorId;
|
||||
END IF;
|
||||
END IF;
|
||||
END;
|
||||
//
|
||||
|
||||
drop procedure if exists update_storage_stats//
|
||||
|
||||
drop trigger if exists event_update_trigger//
|
||||
|
||||
CREATE TRIGGER event_update_trigger AFTER UPDATE ON Events
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
declare diff BIGINT default 0;
|
||||
|
||||
set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0);
|
||||
IF ( NEW.StorageId = OLD.StorageID ) THEN
|
||||
IF ( diff ) THEN
|
||||
UPDATE Storage SET DiskSpace = COALESCE(DiskSpace,0) + diff WHERE Id = OLD.StorageId;
|
||||
END IF;
|
||||
ELSE
|
||||
IF ( NEW.DiskSpace ) THEN
|
||||
UPDATE Storage SET DiskSpace = COALESCE(DiskSpace,0) + NEW.DiskSpace WHERE Id = NEW.StorageId;
|
||||
END IF;
|
||||
IF ( OLD.DiskSpace ) THEN
|
||||
UPDATE Storage SET DiskSpace = COALESCE(DiskSpace,0) - OLD.DiskSpace WHERE Id = OLD.StorageId;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
UPDATE Events_Hour SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
|
||||
UPDATE Events_Day SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
|
||||
UPDATE Events_Week SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
|
||||
UPDATE Events_Month SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
|
||||
|
||||
IF ( NEW.Archived != OLD.Archived ) THEN
|
||||
IF ( NEW.Archived ) THEN
|
||||
INSERT INTO Events_Archived (EventId,MonitorId,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.DiskSpace);
|
||||
UPDATE Monitors SET ArchivedEvents = COALESCE(ArchivedEvents,0)+1, ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) + COALESCE(NEW.DiskSpace,0) WHERE Id=NEW.MonitorId;
|
||||
ELSEIF ( OLD.Archived ) THEN
|
||||
DELETE FROM Events_Archived WHERE EventId=OLD.Id;
|
||||
UPDATE Monitors SET ArchivedEvents = COALESCE(ArchivedEvents,0)-1, ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) WHERE Id=OLD.MonitorId;
|
||||
ELSE
|
||||
IF ( OLD.DiskSpace != NEW.DiskSpace ) THEN
|
||||
UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
|
||||
UPDATE Monitors SET
|
||||
ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0)
|
||||
WHERE Id=OLD.MonitorId;
|
||||
END IF;
|
||||
END IF;
|
||||
ELSEIF ( NEW.Archived AND diff ) THEN
|
||||
UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
|
||||
END IF;
|
||||
|
||||
IF ( diff ) THEN
|
||||
UPDATE Monitors SET TotalEventDiskSpace = COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0) WHERE Id=OLD.MonitorId;
|
||||
END IF;
|
||||
|
||||
END;
|
||||
|
||||
//
|
||||
|
||||
delimiter ;
|
||||
|
||||
DROP TRIGGER IF EXISTS event_insert_trigger;
|
||||
|
||||
delimiter //
|
||||
/* The assumption is that when an Event is inserted, it has no size yet, so don't bother updating the DiskSpace, just the count.
|
||||
* The DiskSpace will get update in the Event Update Trigger
|
||||
*/
|
||||
CREATE TRIGGER event_insert_trigger AFTER INSERT ON Events
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
|
||||
INSERT INTO Events_Hour (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0);
|
||||
INSERT INTO Events_Day (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0);
|
||||
INSERT INTO Events_Week (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0);
|
||||
INSERT INTO Events_Month (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0);
|
||||
UPDATE Monitors SET
|
||||
HourEvents = COALESCE(HourEvents,0)+1,
|
||||
DayEvents = COALESCE(DayEvents,0)+1,
|
||||
WeekEvents = COALESCE(WeekEvents,0)+1,
|
||||
MonthEvents = COALESCE(MonthEvents,0)+1,
|
||||
TotalEvents = COALESCE(TotalEvents,0)+1
|
||||
WHERE Id=NEW.MonitorId;
|
||||
END;
|
||||
//
|
||||
|
||||
DROP TRIGGER IF EXISTS event_delete_trigger//
|
||||
|
||||
CREATE TRIGGER event_delete_trigger BEFORE DELETE ON Events
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
IF ( OLD.DiskSpace ) THEN
|
||||
UPDATE Storage SET DiskSpace = COALESCE(DiskSpace,0) - CAST(OLD.DiskSpace AS SIGNED) WHERE Id = OLD.StorageId;
|
||||
END IF;
|
||||
DELETE FROM Events_Hour WHERE EventId=OLD.Id;
|
||||
DELETE FROM Events_Day WHERE EventId=OLD.Id;
|
||||
DELETE FROM Events_Week WHERE EventId=OLD.Id;
|
||||
DELETE FROM Events_Month WHERE EventId=OLD.Id;
|
||||
IF ( OLD.Archived ) THEN
|
||||
DELETE FROM Events_Archived WHERE EventId=OLD.Id;
|
||||
UPDATE Monitors SET
|
||||
ArchivedEvents = COALESCE(ArchivedEvents,1) - 1,
|
||||
ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),
|
||||
TotalEvents = COALESCE(TotalEvents,1) - 1,
|
||||
TotalEventDiskSpace = COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0)
|
||||
WHERE Id=OLD.MonitorId;
|
||||
ELSE
|
||||
UPDATE Monitors SET
|
||||
TotalEvents = COALESCE(TotalEvents,1)-1,
|
||||
TotalEventDiskSpace=COALESCE(TotalEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0)
|
||||
WHERE Id=OLD.MonitorId;
|
||||
END IF;
|
||||
END;
|
||||
|
||||
//
|
||||
|
||||
DROP TRIGGER IF EXISTS Zone_Insert_Trigger//
|
||||
CREATE TRIGGER Zone_Insert_Trigger AFTER INSERT ON Zones
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE Monitors SET ZoneCount=(SELECT COUNT(*) FROM Zones WHERE MonitorId=NEW.MonitorId) WHERE Id=NEW.MonitorID;
|
||||
END
|
||||
//
|
||||
DROP TRIGGER IF EXISTS Zone_Delete_Trigger//
|
||||
CREATE TRIGGER Zone_Delete_Trigger AFTER DELETE ON Zones
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE Monitors SET ZoneCount=(SELECT COUNT(*) FROM Zones WHERE MonitorId=OLD.MonitorId) WHERE Id=OLD.MonitorID;
|
||||
END
|
||||
//
|
||||
|
||||
DELIMITER ;
|
||||
|
||||
REPLACE INTO Events_Day SELECT Id,MonitorId,StartTime,DiskSpace FROM Events WHERE StartTime > DATE_SUB(NOW(), INTERVAL 1 day);
|
||||
REPLACE INTO Events_Week SELECT Id,MonitorId,StartTime,DiskSpace FROM Events WHERE StartTime > DATE_SUB(NOW(), INTERVAL 1 week);
|
||||
REPLACE INTO Events_Month SELECT Id,MonitorId,StartTime,DiskSpace FROM Events WHERE StartTime > DATE_SUB(NOW(), INTERVAL 1 month);
|
||||
REPLACE INTO Events_Archived SELECT Id,MonitorId,DiskSpace FROM Events WHERE Archived=1;
|
||||
|
||||
UPDATE Monitors INNER JOIN (
|
||||
SELECT MonitorId,
|
||||
COUNT(Id) AS TotalEvents,
|
||||
SUM(DiskSpace) AS TotalEventDiskSpace,
|
||||
SUM(IF(Archived,1,0)) AS ArchivedEvents,
|
||||
SUM(IF(Archived,DiskSpace,0)) AS ArchivedEventDiskSpace,
|
||||
SUM(IF(StartTime > DATE_SUB(NOW(), INTERVAL 1 hour),1,0)) AS HourEvents,
|
||||
SUM(IF(StartTime > DATE_SUB(NOW(), INTERVAL 1 hour),DiskSpace,0)) AS HourEventDiskSpace,
|
||||
SUM(IF(StartTime > DATE_SUB(NOW(), INTERVAL 1 day),1,0)) AS DayEvents,
|
||||
SUM(IF(StartTime > DATE_SUB(NOW(), INTERVAL 1 day),DiskSpace,0)) AS DayEventDiskSpace,
|
||||
SUM(IF(StartTime > DATE_SUB(NOW(), INTERVAL 1 week),1,0)) AS WeekEvents,
|
||||
SUM(IF(StartTime > DATE_SUB(NOW(), INTERVAL 1 week),DiskSpace,0)) AS WeekEventDiskSpace,
|
||||
SUM(IF(StartTime > DATE_SUB(NOW(), INTERVAL 1 month),1,0)) AS MonthEvents,
|
||||
SUM(IF(StartTime > DATE_SUB(NOW(), INTERVAL 1 month),DiskSpace,0)) AS MonthEventDiskSpace
|
||||
FROM Events GROUP BY MonitorId
|
||||
) AS E ON E.MonitorId=Monitors.Id SET
|
||||
Monitors.TotalEvents = E.TotalEvents,
|
||||
Monitors.TotalEventDiskSpace = E.TotalEventDiskSpace,
|
||||
Monitors.ArchivedEvents = E.ArchivedEvents,
|
||||
Monitors.ArchivedEventDiskSpace = E.ArchivedEventDiskSpace,
|
||||
Monitors.HourEvents = E.HourEvents,
|
||||
Monitors.HourEventDiskSpace = E.HourEventDiskSpace,
|
||||
Monitors.DayEvents = E.DayEvents,
|
||||
Monitors.DayEventDiskSpace = E.DayEventDiskSpace,
|
||||
Monitors.WeekEvents = E.WeekEvents,
|
||||
Monitors.WeekEventDiskSpace = E.WeekEventDiskSpace,
|
||||
Monitors.MonthEvents = E.MonthEvents,
|
||||
Monitors.MonthEventDiskSpace = E.MonthEventDiskSpace;
|
||||
|
||||
--
|
||||
-- Add Protocol column to Storage
|
||||
--
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||
AND table_name = 'Servers'
|
||||
AND column_name = 'Protocol'
|
||||
) > 0,
|
||||
"SELECT 'Column Protocol already exists in Servers'",
|
||||
"ALTER TABLE Servers ADD `Protocol` TEXT AFTER `Id`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
--
|
||||
-- Add PathToIndex column to Storage
|
||||
--
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||
AND table_name = 'Servers'
|
||||
AND column_name = 'PathToIndex'
|
||||
) > 0,
|
||||
"SELECT 'Column PathToIndex already exists in Servers'",
|
||||
"ALTER TABLE Servers ADD `PathToIndex` TEXT AFTER `Hostname`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
--
|
||||
-- Add PathToZMS column to Storage
|
||||
--
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||
AND table_name = 'Servers'
|
||||
AND column_name = 'PathToZMS'
|
||||
) > 0,
|
||||
"SELECT 'Column PathToZMS already exists in Servers'",
|
||||
"ALTER TABLE Servers ADD `PathToZMS` TEXT AFTER `PathToIndex`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
--
|
||||
-- Add PathToApi column to Storage
|
||||
--
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||
AND table_name = 'Servers'
|
||||
AND column_name = 'PathToApi'
|
||||
) > 0,
|
||||
"SELECT 'Column PathToApi already exists in Servers'",
|
||||
"ALTER TABLE Servers ADD `PathToApi` TEXT AFTER `PathToZMS`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
--
|
||||
-- Add Port column to Storage
|
||||
--
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
|
||||
AND table_name = 'Servers'
|
||||
AND column_name = 'Port'
|
||||
) > 0,
|
||||
"SELECT 'Column Port already exists in Servers'",
|
||||
"ALTER TABLE Servers ADD `Port` INTEGER UNSIGNED AFTER `Hostname`"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
|
@ -13,6 +13,7 @@ Build-Depends: debhelper (>= 9), cmake
|
|||
, libv4l-dev (>= 0.8.3)
|
||||
, libbz2-dev
|
||||
, ffmpeg | libav-tools
|
||||
, net-tools
|
||||
, libnetpbm10-dev
|
||||
, libvlccore-dev, libvlc-dev
|
||||
, libcurl4-gnutls-dev | libcurl4-nss-dev | libcurl4-openssl-dev
|
||||
|
@ -36,11 +37,13 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
|||
, libphp-serialization-perl
|
||||
, libdate-manip-perl, libmime-lite-perl, libmime-tools-perl, libdbd-mysql-perl
|
||||
, libwww-perl, libarchive-tar-perl, libarchive-zip-perl, libdevice-serialport-perl
|
||||
, libmodule-load-perl, libsys-mmap-perl, libjson-any-perl
|
||||
, libmodule-load-perl, libsys-mmap-perl, libjson-any-perl, libjson-maybexs-perl
|
||||
, libnet-sftp-foreign-perl, libio-pty-perl, libexpect-perl
|
||||
, libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl, libio-socket-multicast-perl, libdigest-sha-perl
|
||||
, libsys-cpu-perl, libsys-meminfo-perl
|
||||
, libdata-uuid-perl
|
||||
,libnumber-bytes-human-perl
|
||||
,libfile-slurp-perl
|
||||
, libpcre3
|
||||
, ffmpeg | libav-tools, libavdevice53 | libavdevice55 | libavdevice57
|
||||
, rsyslog | system-log-daemon
|
||||
|
|
|
@ -8,16 +8,6 @@ SET(zmgid_final www)
|
|||
SET(webroot /srv/www/htdocs)
|
||||
SET(zm_webdir ${webroot}/zoneminder)
|
||||
|
||||
# Download jscalendar & move files into position
|
||||
file(DOWNLOAD http://downloads.sourceforge.net/jscalendar/jscalendar-1.0.zip ${CMAKE_CURRENT_SOURCE_DIR}/jscalendar-1.0.zip STATUS download_jsc)
|
||||
if(download_jsc EQUAL 0)
|
||||
message(STATUS "Jscalander successfully downloaded. Installing...")
|
||||
execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/jscalendar.sh WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ERROR_VARIABLE unzip_jsc)
|
||||
message(STATUS "Status of jscalender script was: ${unzip_jsc}")
|
||||
else(download_jsc EQUAL 0)
|
||||
message(STATUS "Unable to download optional jscalander. Skipping...")
|
||||
endif(download_jsc EQUAL 0)
|
||||
|
||||
# Create several empty folders
|
||||
file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp)
|
||||
|
||||
|
@ -45,7 +35,3 @@ install(FILES zoneminder.tmpfiles DESTINATION /etc/tmpfiles.d RENAME zoneminder.
|
|||
install(FILES redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/sounds PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
install(FILES zoneminder.service DESTINATION /usr/lib/systemd/system PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
|
||||
# Install jscalendar
|
||||
if(unzip_jsc STREQUAL "")
|
||||
install(DIRECTORY jscalendar-1.0/ DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/tools/jscalendar)
|
||||
endif(unzip_jsc STREQUAL "")
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
unzip -o jscalendar-1.0.zip
|
||||
mkdir -v jscalendar-doc
|
||||
cd jscalendar-1.0
|
||||
mv -v *html *php doc/* README ../jscalendar-doc
|
||||
rmdir -v doc
|
|
@ -16,7 +16,6 @@ Version: 1.27.0
|
|||
Release: 1%{?dist}
|
||||
Summary: A camera monitoring and analysis tool
|
||||
Group: System Environment/Daemons
|
||||
# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/
|
||||
# Mootools is under the MIT license: http://mootools.net/
|
||||
License: GPLv2+ and LGPLv2+ and MIT
|
||||
URL: http://www.zoneminder.com/
|
||||
|
@ -141,7 +140,7 @@ fi
|
|||
|
||||
%files
|
||||
%defattr(-,root,root,-)
|
||||
%doc AUTHORS COPYING README.md distros/opensuse/README.OpenSuse distros/opensuse/jscalendar-doc
|
||||
%doc AUTHORS COPYING README.md distros/opensuse/README.OpenSuse
|
||||
%docdir /opt/zoneminder/share/man
|
||||
%config %attr(640,root,%{zmgid_final}) /etc/zm.conf
|
||||
%config(noreplace) %attr(644,root,root) /etc/apache2/conf.d/zoneminder.conf
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
# CMakeLists.txt for the Redhat/CentOS Target Distro.
|
||||
# CMakeLists.txt for the Redhat Target Distros.
|
||||
|
||||
#
|
||||
# General strategy is to configure and install all files specific to Apache and Nginx
|
||||
# Then let the rpm specfile sort them into the appropriate sub-package
|
||||
#
|
||||
|
||||
# Display a message to show the RHEL build options are being processed.
|
||||
if(ZM_TARGET_DISTRO MATCHES "^el")
|
||||
|
@ -9,40 +14,39 @@ else(ZM_TARGET_DISTRO MATCHES "^el")
|
|||
message([WARNING] "Unknown Build Option Detected" ...)
|
||||
endif(ZM_TARGET_DISTRO MATCHES "^el")
|
||||
|
||||
if((NOT ZM_TARGET_DISTRO MATCHES "^fc") AND (ZM_WEB_USER STREQUAL "nginx"))
|
||||
message([FATAL_ERROR] "Experimental Nginx support is currently only supported on Fedora" ...)
|
||||
endif((NOT ZM_TARGET_DISTRO MATCHES "^fc") AND (ZM_WEB_USER STREQUAL "nginx"))
|
||||
#
|
||||
# CONFIGURE STAGE
|
||||
#
|
||||
|
||||
# Configure the zoneminder service files
|
||||
configure_file(systemd/zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY)
|
||||
if(ZM_WEB_USER STREQUAL "nginx")
|
||||
configure_file(nginx/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY)
|
||||
configure_file(nginx/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
|
||||
configure_file(nginx/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY)
|
||||
configure_file(nginx/zoneminder.php-fpm.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.php-fpm.conf @ONLY)
|
||||
configure_file(nginx/README.Fedora ${CMAKE_CURRENT_SOURCE_DIR}/readme/README.Fedora COPYONLY)
|
||||
else(ZM_WEB_USER STREQUAL "nginx")
|
||||
configure_file(systemd/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY)
|
||||
configure_file(apache/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
|
||||
configure_file(systemd/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY)
|
||||
endif(ZM_WEB_USER STREQUAL "nginx")
|
||||
|
||||
# Unpack jscalendar & move files into position
|
||||
message(STATUS "Unpacking and Installing jscalendar...")
|
||||
execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/misc/jscalendar.sh
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
ERROR_VARIABLE unzip_jsc
|
||||
)
|
||||
if("${unzip_jsc}" STREQUAL "")
|
||||
message(STATUS "jscalendar successfully installed.")
|
||||
else("${unzip_jsc}" STREQUAL "")
|
||||
message(FATAL_ERROR "\nAn error occurred while jscalendar was being processed:\n${unzip_jsc}")
|
||||
endif("${unzip_jsc}" STREQUAL "")
|
||||
|
||||
# Create several empty folders
|
||||
# Configure the common zoneminder files
|
||||
configure_file(common/zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY)
|
||||
configure_file(common/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY)
|
||||
file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp)
|
||||
|
||||
# Install the empty folders
|
||||
# Configure the Apache zoneminder files
|
||||
configure_file(httpd/zm-httpd.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zm-httpd.conf @ONLY)
|
||||
configure_file(httpd/zoneminder.httpd.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.httpd.conf @ONLY)
|
||||
configure_file(httpd/zoneminder.httpd.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.httpd.tmpfiles.conf @ONLY)
|
||||
configure_file(httpd/com.zoneminder.systemctl.rules.httpd.in ${CMAKE_CURRENT_SOURCE_DIR}/com.zoneminder.systemctl.rules.httpd @ONLY)
|
||||
|
||||
# Configure the Nginx zoneminder files
|
||||
configure_file(nginx/zm-nginx.conf ${CMAKE_CURRENT_SOURCE_DIR}/zm-nginx.conf COPYONLY)
|
||||
configure_file(nginx/zoneminder.nginx.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.nginx.conf @ONLY)
|
||||
configure_file(nginx/redirect.nginx.conf ${CMAKE_CURRENT_SOURCE_DIR}/redirect.nginx.conf COPYONLY)
|
||||
configure_file(nginx/zoneminder.nginx.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.nginx.tmpfiles.conf @ONLY)
|
||||
configure_file(nginx/zm-web-user.conf ${CMAKE_CURRENT_SOURCE_DIR}/zm-web-user.conf COPYONLY)
|
||||
configure_file(nginx/zoneminder.php-fpm.conf ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.php-fpm.conf COPYONLY)
|
||||
configure_file(nginx/com.zoneminder.systemctl.rules.nginx ${CMAKE_CURRENT_SOURCE_DIR}/com.zoneminder.systemctl.rules.nginx COPYONLY)
|
||||
|
||||
#
|
||||
# INSTALLATION STAGE
|
||||
#
|
||||
|
||||
|
||||
# Install the common zoneminder files
|
||||
install(FILES zoneminder.logrotate DESTINATION /etc/logrotate.d RENAME zoneminder PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES zoneminder.service DESTINATION /usr/lib/systemd/system PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
|
||||
install(DIRECTORY sock swap DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
install(DIRECTORY zoneminder DESTINATION /var/log DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
install(DIRECTORY zoneminder DESTINATION /var/run DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
|
@ -50,6 +54,23 @@ install(DIRECTORY zoneminder DESTINATION /var/cache DIRECTORY_PERMISSIONS OWNER_
|
|||
install(DIRECTORY zoneminder-upload DESTINATION /var/spool DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
install(DIRECTORY events images temp DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
|
||||
# Install the Apache zoneminder files
|
||||
install(FILES zm-httpd.conf DESTINATION /usr/lib/systemd/system/zoneminder.service.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES zoneminder.httpd.conf DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES zoneminder.httpd.tmpfiles.conf DESTINATION /usr/lib/tmpfiles.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES com.zoneminder.systemctl.rules.httpd DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
|
||||
# Install the Nginx zoneminder files
|
||||
install(FILES zm-nginx.conf DESTINATION /usr/lib/systemd/system/zoneminder.service.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES zoneminder.nginx.conf DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES redirect.nginx.conf DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES zoneminder.nginx.tmpfiles.conf DESTINATION /usr/lib/tmpfiles.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES com.zoneminder.systemctl.rules.nginx DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES zm-web-user.conf DESTINATION /etc/zm/conf.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES zoneminder.php-fpm.conf DESTINATION /etc/php-fpm.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
|
||||
# Miscellaneous
|
||||
|
||||
# Symlink the cake php temp folder to the ZoneMinder temp folder
|
||||
install(CODE "execute_process(COMMAND ln -sf ../../../../../../var/lib/zoneminder/temp \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/api/app/tmp\")")
|
||||
|
||||
|
@ -57,17 +78,5 @@ install(CODE "execute_process(COMMAND ln -sf ../../../../../../var/lib/zoneminde
|
|||
install(CODE "execute_process(COMMAND ln -sf ../../java/cambozola.jar \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/cambozola.jar\")")
|
||||
|
||||
# Install auxiliary files
|
||||
install(FILES misc/redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/sounds PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
install(DIRECTORY jscalendar-1.0/ DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/tools/jscalendar)
|
||||
|
||||
# Install zoneminder service files
|
||||
install(FILES zoneminder.logrotate DESTINATION /etc/logrotate.d RENAME zoneminder PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES zoneminder.conf DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
|
||||
if(ZM_WEB_USER STREQUAL "nginx")
|
||||
install(FILES zoneminder.php-fpm.conf DESTINATION /etc/php-fpm.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ RENAME zoneminder.conf)
|
||||
endif(ZM_WEB_USER STREQUAL "nginx")
|
||||
|
||||
install(FILES zoneminder.service DESTINATION /usr/lib/systemd/system PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES zoneminder.tmpfiles DESTINATION /usr/lib/tmpfiles.d RENAME zoneminder.conf PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
|
||||
install(FILES common/redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/sounds PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
@ZM_LOGDIR@/*.log {
|
||||
missingok
|
||||
notifempty
|
||||
sharedscripts
|
||||
delaycompress
|
||||
compress
|
||||
postrotate
|
||||
@BINDIR@/zmpkg.pl logrot > /dev/null 2>/dev/null || true
|
||||
endscript
|
||||
daily
|
||||
rotate 7
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
# ZoneMinder systemd unit file for RedHat distros and clones
|
||||
# See drop-in folder for additional config directives
|
||||
|
||||
[Unit]
|
||||
Description=ZoneMinder CCTV recording and security system
|
||||
After=network.target mariadb.service httpd.service
|
||||
Requires=mariadb.service httpd.service
|
||||
After=network.target mariadb.service
|
||||
Requires=mariadb.service
|
||||
|
||||
[Service]
|
||||
User=@WEB_USER@
|
||||
Group=@WEB_GROUP@
|
||||
Type=forking
|
||||
ExecStart=@BINDIR@/zmpkg.pl start
|
||||
ExecReload=@BINDIR@/zmpkg.pl restart
|
|
@ -0,0 +1,7 @@
|
|||
polkit.addRule(function(action, subject) {
|
||||
if (action.id == "com.zoneminder.policykit.pkexec.run-zmsystemctl" &&
|
||||
subject.user != "@WEB_USER@") {
|
||||
return polkit.Result.NO;
|
||||
}
|
||||
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
# Additional config directives for ZoneMinder with Apache web server
|
||||
|
||||
[Unit]
|
||||
After=httpd.service
|
||||
|
||||
[Service]
|
||||
User=@WEB_USER@
|
||||
Group=@WEB_GROUP@
|
|
@ -1,5 +1,6 @@
|
|||
D @ZM_TMPDIR@ 0755 @WEB_USER@ @WEB_GROUP@
|
||||
D @ZM_SOCKDIR@ 0755 @WEB_USER@ @WEB_GROUP@
|
||||
D @ZM_CACHEDIR@ 0755 @WEB_USER@ @WEB_GROUP@
|
||||
D @ZM_DIR_EVENTS@ 0755 @WEB_USER@ @WEB_GROUP@
|
||||
d @ZM_DIR_EVENTS@ 0755 @WEB_USER@ @WEB_GROUP@
|
||||
D @ZM_DIR_IMAGES@ 0755 @WEB_USER@ @WEB_GROUP@
|
||||
|
Binary file not shown.
|
@ -1,7 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
unzip -o misc/jscalendar-1.0.zip
|
||||
mkdir -v jscalendar-doc
|
||||
cd jscalendar-1.0
|
||||
mv -v *html *php doc/* README ../jscalendar-doc
|
||||
rmdir -v doc
|
|
@ -1,125 +0,0 @@
|
|||
module local_zoneminder 1.2;
|
||||
|
||||
require {
|
||||
type afs_ka_port_t;
|
||||
type netsupport_port_t;
|
||||
type port_t;
|
||||
type presence_port_t;
|
||||
type postfix_master_t;
|
||||
type postfix_qmgr_t;
|
||||
type postfix_pickup_t;
|
||||
type httpd_t;
|
||||
type var_lib_t;
|
||||
type ionixnetmon_port_t;
|
||||
type glance_port_t;
|
||||
type mmcc_port_t;
|
||||
type postfix_master_t;
|
||||
type commplex_port_t;
|
||||
type syslogd_port_t;
|
||||
type dcc_port_t;
|
||||
type sip_port_t;
|
||||
type amqp_port_t;
|
||||
type condor_port_t;
|
||||
type afs_fs_port_t;
|
||||
type nodejs_debug_port_t;
|
||||
type httpd_var_lib_t;
|
||||
type websm_port_t;
|
||||
type afs_pt_port_t;
|
||||
type postfix_qmgr_t;
|
||||
type git_port_t;
|
||||
type ipp_port_t;
|
||||
type aol_port_t;
|
||||
type unconfined_t;
|
||||
type kernel_t;
|
||||
type init_t;
|
||||
type auditd_t;
|
||||
type mysqld_t;
|
||||
type httpd_log_t;
|
||||
type syslogd_t;
|
||||
type httpd_t;
|
||||
type initrc_state_t;
|
||||
type initrc_t;
|
||||
type var_lib_t;
|
||||
type udev_t;
|
||||
type mysqld_safe_t;
|
||||
type sshd_t;
|
||||
type crond_t;
|
||||
type getty_t;
|
||||
type httpd_var_lib_t;
|
||||
type initrc_var_run_t;
|
||||
type tmpfs_t;
|
||||
type dhcpc_t;
|
||||
type v4l_device_t;
|
||||
type file_t;
|
||||
class sock_file { write create unlink };
|
||||
class unix_stream_socket { read connectto };
|
||||
class lnk_file { write create getattr read lock unlink };
|
||||
class dir {search getattr };
|
||||
class udp_socket name_bind;
|
||||
class file { write getattr read lock unlink open };
|
||||
class shm { unix_read unix_write associate read write getattr };
|
||||
class chr_file getattr;
|
||||
}
|
||||
|
||||
#============= httpd_t ==============
|
||||
allow httpd_t auditd_t:dir { search getattr };
|
||||
allow httpd_t auditd_t:file { read getattr open };
|
||||
allow httpd_t crond_t:dir { search getattr };
|
||||
allow httpd_t crond_t:file { read getattr open };
|
||||
allow httpd_t dhcpc_t:dir { search getattr };
|
||||
allow httpd_t dhcpc_t:file { read getattr open };
|
||||
allow httpd_t getty_t:dir { search getattr };
|
||||
allow httpd_t getty_t:file { read getattr open };
|
||||
allow httpd_t httpd_log_t:file write;
|
||||
allow httpd_t httpd_var_lib_t:lnk_file { write getattr read lock unlink };
|
||||
allow httpd_t init_t:dir { search getattr };
|
||||
allow httpd_t init_t:file { read getattr open };
|
||||
#!!!! The source type 'httpd_t' can write to a 'file' of the following types:
|
||||
#squirrelmail_spool_t, mirrormanager_var_run_t, dirsrvadmin_config_t, httpd_lock_t, httpd_tmp_t, dirsrv_config_t, dirsrvadmin_tmp_t, httpd_cache_t, httpd_tmpfs_t, httpd_squirrelmail_t, dirsrv_var_run_t, dirsrv_var_log_t, httpd_var_lib_t, httpd_var_run_t, zarafa_var_lib_t, httpd_prewikka_rw_content_t, httpd_mediawiki_rw_content_t, httpd_squid_rw_content_t, passenger_var_run_t, httpd_smokeping_cgi_rw_content_t, httpd_openshift_rw_content_t, httpd_dirsrvadmin_rw_content_t, httpd_w3c_validator_rw_content_t, httpd_collectd_rw_content_t, cluster_var_lib_t, cluster_var_run_t, httpd_user_rw_content_t, httpd_awstats_rw_content_t, httpdcontent, root_t, httpd_cobbler_rw_content_t, httpd_munin_rw_content_t, cluster_conf_t, httpd_bugzilla_rw_content_t, passenger_tmp_t, httpd_cvs_rw_content_t, httpd_git_rw_content_t, httpd_sys_rw_content_t, httpd_sys_rw_content_t, httpd_nagios_rw_content_t, httpd_apcupsd_cgi_rw_content_t, httpd_nutups_cgi_rw_content_t, httpd_dspam_rw_content_t
|
||||
|
||||
allow httpd_t initrc_state_t:file { read write getattr unlink open };
|
||||
allow httpd_t initrc_t:unix_stream_socket connectto;
|
||||
allow httpd_t initrc_t:shm { unix_read unix_write associate read write getattr };
|
||||
allow httpd_t initrc_var_run_t:file { write read lock open };
|
||||
allow httpd_t kernel_t:dir { search getattr };
|
||||
allow httpd_t kernel_t:file { read getattr open };
|
||||
allow httpd_t mysqld_safe_t:dir { search getattr };
|
||||
allow httpd_t mysqld_safe_t:file { read getattr open };
|
||||
allow httpd_t mysqld_t:dir { search getattr };
|
||||
allow httpd_t mysqld_t:file { read getattr open };
|
||||
allow httpd_t sshd_t:dir { search getattr };
|
||||
allow httpd_t sshd_t:file { read getattr open };
|
||||
allow httpd_t syslogd_t:dir { search getattr };
|
||||
allow httpd_t syslogd_t:file { read getattr open };
|
||||
allow httpd_t tmpfs_t:sock_file write;
|
||||
allow httpd_t udev_t:dir { search getattr };
|
||||
allow httpd_t udev_t:file { read getattr open };
|
||||
allow httpd_t unconfined_t:dir { search getattr };
|
||||
allow httpd_t unconfined_t:file { read getattr open };
|
||||
allow httpd_t var_lib_t:lnk_file { write getattr read lock unlink };
|
||||
allow httpd_t var_lib_t:sock_file { write unlink };
|
||||
allow httpd_t v4l_device_t:chr_file getattr;
|
||||
allow httpd_t afs_fs_port_t:udp_socket name_bind;
|
||||
allow httpd_t afs_ka_port_t:udp_socket name_bind;
|
||||
allow httpd_t afs_pt_port_t:udp_socket name_bind;
|
||||
allow httpd_t amqp_port_t:udp_socket name_bind;
|
||||
allow httpd_t aol_port_t:udp_socket name_bind;
|
||||
allow httpd_t commplex_port_t:udp_socket name_bind;
|
||||
allow httpd_t condor_port_t:udp_socket name_bind;
|
||||
allow httpd_t dcc_port_t:udp_socket name_bind;
|
||||
allow httpd_t git_port_t:udp_socket name_bind;
|
||||
allow httpd_t glance_port_t:udp_socket name_bind;
|
||||
allow httpd_t httpd_var_lib_t:lnk_file create;
|
||||
allow httpd_t ionixnetmon_port_t:udp_socket name_bind;
|
||||
allow httpd_t ipp_port_t:udp_socket name_bind;
|
||||
allow httpd_t mmcc_port_t:udp_socket name_bind;
|
||||
allow httpd_t netsupport_port_t:udp_socket name_bind;
|
||||
allow httpd_t nodejs_debug_port_t:udp_socket name_bind;
|
||||
allow httpd_t port_t:udp_socket name_bind;
|
||||
allow httpd_t postfix_master_t:dir { search getattr };
|
||||
allow httpd_t postfix_master_t:file { read getattr open };
|
||||
allow httpd_t postfix_pickup_t:dir { search getattr };
|
||||
allow httpd_t postfix_pickup_t:file { read getattr open };
|
||||
allow httpd_t postfix_qmgr_t:dir { search getattr };
|
||||
allow httpd_t postfix_qmgr_t:file { read getattr open };
|
||||
allow httpd_t presence_port_t:udp_socket name_bind;
|
|
@ -1,165 +0,0 @@
|
|||
What's New
|
||||
==========
|
||||
|
||||
1. This is an *experimental* build of zoneminder which uses the
|
||||
nginx web server.
|
||||
|
||||
2. The Apache ScriptAlias has been changed from "/cgi-bin/zm/zms" to
|
||||
"/cgi-bin-zm/zms". This has been to done to avoid this bug:
|
||||
https://bugzilla.redhat.com/show_bug.cgi?id=973067
|
||||
|
||||
IMPORTANT: You must manually inspect the value for PATH_ZMS under Options
|
||||
and verify it is set to "/cgi-bin-zm/nph-zms". Failure to do so will result
|
||||
in a broken system. You have been warned.
|
||||
|
||||
3. Due to the active state of the ZoneMinder project, we now recommend granting
|
||||
ALL permission to the ZoneMinder mysql account. This change must be done
|
||||
manually before ZoneMinder will run. See the installation steps below.
|
||||
|
||||
4. This package uses the HTTPS protocol by default to access the web portal.
|
||||
Requests using HTTP will auto-redirect to HTTPS. See README.https for
|
||||
more information.
|
||||
|
||||
5. This package ships with the new ZoneMinder API enabled.
|
||||
|
||||
New installs
|
||||
============
|
||||
|
||||
1. This package supports either community-mysql-server or mariadb-server with
|
||||
mariadb being the preferred choice. Unless you are already using MariaDB or
|
||||
Mysql server, you need to ensure that the server is configured to start
|
||||
during boot and properly secured by running:
|
||||
|
||||
sudo dnf install mariadb-server
|
||||
sudo systemctl enable mariadb
|
||||
sudo systemctl start mariadb.service
|
||||
mysql_secure_installation
|
||||
|
||||
2. Assuming the database is local and using the password for the root account
|
||||
set during the previous step, you will need to create the ZoneMinder
|
||||
database and configure a database account for ZoneMinder to use:
|
||||
|
||||
mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql
|
||||
mysql -uroot -p -e "grant all on zm.* to \
|
||||
'zmuser'@localhost identified by 'zmpass';"
|
||||
mysqladmin -uroot -p reload
|
||||
|
||||
The database account credentials, zmuser/zmpass, are arbitrary. Set them to
|
||||
anything that suits your environment.
|
||||
|
||||
3. If you have chosen to change the zoneminder database account credentials to
|
||||
something other than zmuser/zmpass, you must now edit /etc/zm/zm.conf.
|
||||
Change ZM_DB_USER and ZM_DB_PASS to the values you created in the previous
|
||||
step.
|
||||
|
||||
This version of zoneminder no longer requires you to make a similar change
|
||||
to the credentials in /usr/share/zoneminder/www/api/app/Config/database.php
|
||||
This now happens dynamically. Do *not* make any changes to this file.
|
||||
|
||||
4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local
|
||||
timezone. PHP will complain loudly if this is not set, or if it is set
|
||||
incorrectly, and these complaints will show up in the zoneminder logging
|
||||
system as errors.
|
||||
|
||||
If you are not sure of the proper timezone specification to use, look at
|
||||
http://php.net/date.timezone
|
||||
|
||||
5. Disable SELinux
|
||||
|
||||
We currently do not have the resources to create and maintain an accurate
|
||||
SELinux policy for ZoneMinder on Fedora. We will gladly accept pull
|
||||
reqeusts from anyone who wishes to do the work. In the meantime, SELinux
|
||||
will need to be disabled or put into permissive mode.
|
||||
|
||||
To immediately disbale SELinux for the current seesion, issue the following
|
||||
from the command line:
|
||||
|
||||
sudo setenforce 0
|
||||
|
||||
To permanently disable SELinux, edit /etc/selinux/config and change the
|
||||
SELINUX line from "enforcing" to "disabled". This change will take
|
||||
effect after a reboot.
|
||||
|
||||
6. This package comes preconfigured for HTTPS using the default self signed
|
||||
certificate on your system. We recommend you keep this configuration.
|
||||
|
||||
If this does not meet your needs, then read README.https to
|
||||
learn about alternatives.
|
||||
|
||||
7. Edit /etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of
|
||||
simulatneous streams the server should support. Generally, a good minimum
|
||||
value for this equals the total number of cameras you expect to view at the
|
||||
same time.
|
||||
|
||||
8. Now start the web server:
|
||||
|
||||
sudo systemctl enable nginx
|
||||
sudo systemctl start nginx
|
||||
|
||||
9. Now start zoneminder:
|
||||
|
||||
sudo systemctl enable zoneminder
|
||||
sudo systemctl start zoneminder
|
||||
|
||||
10.The Fedora repos have a ZoneMinder package available, but it does not
|
||||
support ffmpeg or libvlc, which many modern IP cameras require. Most users
|
||||
will want to prevent the ZoneMinder package in the Fedora repos from
|
||||
overwriting the ZoneMinder package in zmrepo, during a future dnf update. To
|
||||
prevent that from happening you must edit /etc/yum.repos.d/fedora.repo
|
||||
and /etc/yum.repos.d/fedora-updates.repo. Add the line "exclude=zoneminder*"
|
||||
without the quotes under the [fedora] and [fedora-updates] blocks,
|
||||
respectively.
|
||||
|
||||
Upgrades
|
||||
========
|
||||
|
||||
1. Verify /etc/zm/zm.conf.
|
||||
|
||||
If zm.conf was manually edited before running the upgrade, the installation
|
||||
may not overwrite it. In this case, it will create the file
|
||||
/etc/zm/zm.conf.rpmnew.
|
||||
|
||||
For example, this will happen if you are using database account credentials
|
||||
other than zmuser/zmpass.
|
||||
|
||||
Compare /etc/zm/zm.conf to /etc/zm/zm.conf.rpmnew. Verify that zm.conf
|
||||
contains any new config settings that may be in zm.conf.rpmnew.
|
||||
|
||||
This version of zoneminder no longer requires you to make a similar change
|
||||
to the credentials in /usr/share/zoneminder/www/api/app/Config/database.php
|
||||
This now happens dynamically. Do *not* make any changes to this file.
|
||||
|
||||
2. Verify permissions of the zmuser account.
|
||||
|
||||
Over time, the database account permissions required for normal operation
|
||||
have increased. Verify the zmuser database account has been granted all
|
||||
permission to the ZoneMinder database:
|
||||
|
||||
mysql -uroot -p -e "show grants for zmuser@localhost;"
|
||||
|
||||
See step 2 of the Installation section to add missing permissions.
|
||||
|
||||
3. Verify the ZoneMinder Apache configuration file in the folder
|
||||
/etc/httpd/conf.d. You will have a file called "zoneminder.conf" and there
|
||||
may also be a file called "zoneminder.conf.rpmnew". If the rpmnew file
|
||||
exists, inspect it and merge anything new in that file with zoneminder.conf.
|
||||
Verify the SSL REquirements meet your needs. Read README.https if necessary.
|
||||
|
||||
4. Upgrade the database before starting ZoneMinder.
|
||||
|
||||
Most upgrades can be performed by executing the following command:
|
||||
|
||||
sudo zmupdate.pl
|
||||
|
||||
Recent versions of ZoneMinder don't require any parameters added to the
|
||||
zmupdate command. However, if ZoneMinder complains, you may need to call
|
||||
zmupdate in the following manner:
|
||||
|
||||
sudo zmupdate.pl --user=root --pass=<mysql_root_pwd> --version=<from version>
|
||||
|
||||
5. Now restart nginx and php-fpm then start and zoneminder:
|
||||
|
||||
sudo systemctl restart nginx
|
||||
sudo systemctl restart php-fpm
|
||||
sudo systemctl start zoneminder
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
polkit.addRule(function(action, subject) {
|
||||
if (action.id == "com.zoneminder.policykit.pkexec.run-zmsystemctl" &&
|
||||
subject.user != "nginx") {
|
||||
return polkit.Result.NO;
|
||||
}
|
||||
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
# Auto redirect to https
|
||||
return 301 https://$host$request_uri;
|
|
@ -0,0 +1,9 @@
|
|||
# Additional config directives for ZoneMinder with Nginx web server
|
||||
|
||||
[Unit]
|
||||
After=nginx.service php-fpm.service fcgiwrap.service
|
||||
Requires=php-fpm.service fcgiwrap@nginx.service
|
||||
|
||||
[Service]
|
||||
User=nginx
|
||||
Group=nginx
|
|
@ -0,0 +1,3 @@
|
|||
ZM_WEB_USER=nginx
|
||||
ZM_WEB_GROUP=nginx
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
|
||||
ssl_certificate "/etc/pki/tls/certs/localhost.crt";
|
||||
ssl_certificate_key "/etc/pki/tls/private/localhost.key";
|
||||
ssl_session_cache shared:SSL:1m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_ciphers PROFILE=SYSTEM;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
# Auto-redirect HTTP requests to HTTPS
|
||||
if ($scheme != "https") {
|
||||
rewrite ^/?(zm)(.*)$ https://$host/$1$2 permanent;
|
||||
}
|
||||
|
||||
location /cgi-bin-zm {
|
||||
gzip off;
|
||||
alias "@ZM_CGIDIR@";
|
||||
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||
fastcgi_pass unix:/run/fcgiwrap.sock;
|
||||
}
|
||||
|
||||
location /zm {
|
||||
gzip off;
|
||||
alias "@ZM_WEBDIR@";
|
||||
index index.php;
|
||||
|
||||
location ~ \.php$ {
|
||||
if (!-f $request_filename) { return 404; }
|
||||
expires epoch;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_pass unix:/run/php-fpm/www.sock;
|
||||
}
|
||||
|
||||
location ~ \.(jpg|jpeg|gif|png|ico)$ {
|
||||
access_log off;
|
||||
expires 33d;
|
||||
}
|
||||
|
||||
location /zm/api/ {
|
||||
alias "@ZM_WEBDIR@";
|
||||
rewrite ^/zm/api(.+)$ /zm/api/index.php?p=$1 last;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
server {
|
||||
listen 443 ssl default_server;
|
||||
listen [::]:443 ssl default_server;
|
||||
server_name = localhost $hostname;
|
||||
|
||||
ssl_certificate "/etc/pki/tls/certs/localhost.crt";
|
||||
ssl_certificate_key "/etc/pki/tls/private/localhost.key";
|
||||
ssl_session_cache shared:SSL:1m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_ciphers PROFILE=SYSTEM;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
# Auto redirect to server/zm when no url suffix was given
|
||||
location = / {
|
||||
return 301 zm;
|
||||
}
|
||||
|
||||
location /cgi-bin-zm {
|
||||
gzip off;
|
||||
alias "@ZM_CGIDIR@";
|
||||
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||
fastcgi_pass unix:/run/fcgiwrap.sock;
|
||||
}
|
||||
|
||||
location /zm/cache {
|
||||
alias "@ZM_CACHEDIR@";
|
||||
}
|
||||
|
||||
location /zm {
|
||||
gzip off;
|
||||
alias "@ZM_WEBDIR@";
|
||||
index index.php;
|
||||
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
expires epoch;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_pass unix:/run/php-fpm/www.sock;
|
||||
}
|
||||
|
||||
location ~ \.(jpg|jpeg|gif|png|ico)$ {
|
||||
access_log off;
|
||||
expires 33d;
|
||||
}
|
||||
|
||||
location /zm/api/ {
|
||||
alias "@ZM_WEBDIR@";
|
||||
rewrite ^/zm/api(.+)$ /zm/api/app/webroot/index.php?p=$1 last;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
D @ZM_TMPDIR@ 0755 nginx nginx
|
||||
D @ZM_SOCKDIR@ 0755 nginx nginx
|
||||
D @ZM_CACHEDIR@ 0755 nginx nginx
|
||||
d @ZM_DIR_EVENTS@ 0755 nginx nginx
|
||||
D @ZM_DIR_IMAGES@ 0755 nginx nginx
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
; This config file is needed when using ZoneMinder with web servers other
|
||||
; than Apache. You can ignore this file if you are using Apache web server.
|
||||
; Change the user and group of the default pool to the web server account
|
||||
[www]
|
||||
|
||||
user = nginx
|
||||
group = nginx
|
||||
|
||||
; These parameters are typically a tradoff between performance and memory
|
||||
; consumption. See the contents of www.conf for details.
|
||||
|
||||
pm = ondemand
|
||||
pm.max_children = 50
|
||||
pm.process_idle_timeout = 10s
|
|
@ -1,10 +0,0 @@
|
|||
# Change the user and group of the default pool to the web server account
|
||||
[www]
|
||||
|
||||
user = @WEB_USER@
|
||||
group = @WEB_GROUP@
|
||||
|
||||
# Uncomment these on machines with little memory
|
||||
#pm = ondemand
|
||||
#pm.max_children = 10
|
||||
#pm.process_idle_timeout = 10s
|
|
@ -1,22 +0,0 @@
|
|||
# ZoneMinder systemd unit file for Fedora
|
||||
# Replace mariadb with community-mysql if using mysql service instead of mariadb
|
||||
|
||||
[Unit]
|
||||
Description=ZoneMinder CCTV recording and security system
|
||||
After=network.target mariadb.service nginx.service php-fpm.service fcgiwrap.service
|
||||
Requires=mariadb.service nginx.service php-fpm.service fcgiwrap.service
|
||||
|
||||
[Service]
|
||||
User=@WEB_USER@
|
||||
Group=@WEB_GROUP@
|
||||
Type=forking
|
||||
ExecStart=@BINDIR@/zmpkg.pl start
|
||||
ExecReload=@BINDIR@/zmpkg.pl restart
|
||||
ExecStop=@BINDIR@/zmpkg.pl stop
|
||||
PIDFile=@ZM_RUNDIR@/zm.pid
|
||||
Environment=TZ=/etc/localtime
|
||||
RuntimeDirectory=zoneminder
|
||||
RuntimeDirectoryMode=0755
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,5 +0,0 @@
|
|||
D @ZM_TMPDIR@ 0755 @WEB_USER@ @WEB_GROUP@
|
||||
D @ZM_SOCKDIR@ 0755 @WEB_USER@ @WEB_GROUP@
|
||||
D /var/lib/php/session 770 root @WEB_GROUP@
|
||||
D /var/lib/php/wsdlcache 770 root @WEB_GROUP@
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
What's New
|
||||
==========
|
||||
|
||||
1. See the ZoneMinder release notes for a list of new features:
|
||||
https://github.com/ZoneMinder/zoneminder/releases
|
||||
|
||||
2. The contents of the ZoneMinder Apache config file have changed. In
|
||||
addition, this ZoneMinder package now requires you to manually symlink the
|
||||
ZoneMinder Apache config file. See new install step 6 and upgrade step 3
|
||||
in the appropriate README for details.
|
||||
|
||||
3. This package has been split into sub-packages to allow compatibility with
|
||||
other web servers. Here is a breakdown of the available packages:
|
||||
|
||||
zoneminder - Meta-package installs zoneminder-common and zoneminder-httpd
|
||||
This exists soley for backwards compatibility.
|
||||
zoneminder-common - Common files that do not differ based on the web server
|
||||
zoneminder-httpd - Files needed for compatibility with the Apache web server
|
||||
zoneminder-nginx - Files needed for compatibility with the Nginx web server
|
||||
|
||||
You can switch between different subpackages with dnf/yum. Be advised that,
|
||||
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
|
||||
|
||||
5. Continue on to the next README that corresponds to the chosen webserver:
|
||||
|
||||
README.httpd - Follow these steps when using Apache
|
||||
README.nginx - Follow these steps when using Nginx
|
||||
|
|
@ -1,17 +1,8 @@
|
|||
What's New
|
||||
==========
|
||||
|
||||
1. See the ZoneMinder release notes for a list of new features:
|
||||
https://github.com/ZoneMinder/zoneminder/releases
|
||||
|
||||
2. The contents of the ZoneMinder Apache config file have changed. In
|
||||
addition, this ZoneMinder package now requires you to manually symlink the
|
||||
ZoneMinder Apache config file. See new install step 6 and upgrade step 3
|
||||
below for details.
|
||||
|
||||
New installs
|
||||
============
|
||||
|
||||
NOTE: EL7 users should replace "dnf" with "yum" in the instructions below.
|
||||
|
||||
1. Unless you are already using MariaDB server, you need to ensure that the
|
||||
server is configured to start during boot and properly secured by running:
|
||||
|
||||
|
@ -72,19 +63,19 @@ New installs
|
|||
6. Configure the web server
|
||||
|
||||
This package uses the HTTPS protocol by default to access the web portal,
|
||||
using rhe default self signed certificate on your system. Requests using
|
||||
using the default self signed certificate on your system. Requests using
|
||||
HTTP will auto-redirect to HTTPS.
|
||||
|
||||
Inspect the web server configuration file and verify it meets your needs:
|
||||
|
||||
/etc/zm/www/zoneminder.conf
|
||||
/etc/zm/www/zoneminder.httpd.conf
|
||||
|
||||
If you are running other web enabled services then you may need to edit
|
||||
this file to suite. See README.https to learn about other alternatives.
|
||||
|
||||
When in doubt, proceed with the default:
|
||||
|
||||
sudo ln -s /etc/zm/www/zoneminder.conf /etc/httpd/conf.d/
|
||||
sudo ln -sf /etc/zm/www/zoneminder.httpd.conf /etc/httpd/conf.d/
|
||||
sudo dnf install mod_ssl
|
||||
|
||||
7. Now start the web server:
|
||||
|
@ -129,7 +120,7 @@ New installs
|
|||
Upgrades
|
||||
========
|
||||
|
||||
1. Conf.d folder support has been added to ZoneMinder 1.31.0. Any custom
|
||||
1. Conf.d folder support has been added to ZoneMinder. Any custom
|
||||
changes previously made to zm.conf must now be made in one or more custom
|
||||
config files, created under the conf.d folder. Do this now. See
|
||||
/etc/zm/conf.d/README for details. Once you recreate any custom config changes
|
||||
|
@ -146,10 +137,22 @@ Upgrades
|
|||
See step 2 of the Installation section to add missing permissions.
|
||||
|
||||
3. Verify the ZoneMinder Apache configuration file in the folder
|
||||
/etc/zm/www. You will have a file called "zoneminder.conf" and there
|
||||
may also be a file called "zoneminder.conf.rpmnew". If the rpmnew file
|
||||
/etc/zm/www. You will have a file called "zoneminder.httpd.conf" and there
|
||||
may also be one or more files with "rpmnew" extenstion. If the rpmnew file
|
||||
exists, inspect it and merge anything new in that file with zoneminder.conf.
|
||||
Verify the SSL REquirements meet your needs. Read README.https if necessary.
|
||||
Verify the SSL Requirements meet your needs. Read README.https if necessary.
|
||||
|
||||
The contents of this file must be merged into your Apache configuration.
|
||||
See step 6 of the installation section if you have not already done this
|
||||
during a previous upgrade.
|
||||
|
||||
<<<<<<< HEAD
|
||||
If this step is not performed correctly, the web console will appear
|
||||
mostly empty and/or significantly corrupted post-upgrade.
|
||||
=======
|
||||
IMPORTANT: Failure to complete this step properly will result in a mostly
|
||||
empty or significantly corrupted web console post-upgrade.
|
||||
>>>>>>> 5b211d250910918782d20bf32b0e33a38a0df4dd
|
||||
|
||||
4. Upgrade the database before starting ZoneMinder.
|
||||
|
|
@ -20,7 +20,8 @@ experience.
|
|||
to do this: https://wiki.centos.org/HowTos/Https . Additionally, Googling
|
||||
"centos certificate" reveals many articles on the subject.
|
||||
|
||||
3. You can turn off HTTPS entirely by simply commenting out the SSLRequireSSL
|
||||
directives found in /etc/httpd/conf.d/zoneminder.conf. You should also
|
||||
comment out the HTTP -> HTTPS Rewrite rule.
|
||||
3. When using Apache, you can turn off HTTPS entirely by simply commenting
|
||||
out the SSLRequireSSL directives found in
|
||||
/etc/zm/www/zoneminder.apache.conf. You should also comment out the
|
||||
HTTP -> HTTPS Rewrite rule.
|
||||
|
||||
|
|
|
@ -1,28 +1,17 @@
|
|||
What's New
|
||||
==========
|
||||
|
||||
1. See the ZoneMinder release notes for a list of new features:
|
||||
https://github.com/ZoneMinder/zoneminder/releases
|
||||
|
||||
2. The contents of the ZoneMinder Apache config file have changed. In
|
||||
addition, this ZoneMinder package now requires you to manually symlink the
|
||||
ZoneMinder Apache config file. See new install step 6 and upgrade step 3
|
||||
below for details.
|
||||
|
||||
New installs
|
||||
============
|
||||
|
||||
1. Unless you are already using MariaDB server, you need to ensure that the
|
||||
server is configured to start during boot and properly secured by running:
|
||||
|
||||
sudo yum install mariadb-server
|
||||
sudo dnf install mariadb-server
|
||||
sudo systemctl enable mariadb
|
||||
sudo systemctl start mariadb.service
|
||||
mysql_secure_installation
|
||||
|
||||
2. Using the password for the root account set during the previous step, you
|
||||
will need to create the ZoneMinder database and configure a database
|
||||
account for ZoneMinder to use:
|
||||
2. Assuming the database is local and using the password for the root account
|
||||
set during the previous step, you will need to create the ZoneMinder
|
||||
database and configure a database account for ZoneMinder to use:
|
||||
|
||||
mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql
|
||||
mysql -uroot -p -e "grant all on zm.* to \
|
||||
|
@ -42,8 +31,8 @@ New installs
|
|||
|
||||
Once the file has been saved, set proper file & ownership permissions on it:
|
||||
|
||||
sudo chown root:apache *.conf
|
||||
sudo chmod 640 *.conf
|
||||
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
|
||||
|
@ -56,7 +45,7 @@ New installs
|
|||
5. Disable SELinux
|
||||
|
||||
We currently do not have the resources to create and maintain an accurate
|
||||
SELinux policy for ZoneMinder on CentOS 7. We will gladly accept pull
|
||||
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.
|
||||
|
||||
|
@ -72,8 +61,7 @@ New installs
|
|||
6. Configure the web server
|
||||
|
||||
This package uses the HTTPS protocol by default to access the web portal,
|
||||
using rhe default self signed certificate on your system. Requests using
|
||||
HTTP will auto-redirect to HTTPS.
|
||||
using the default self signed certificate on your system.
|
||||
|
||||
Inspect the web server configuration file and verify it meets your needs:
|
||||
|
||||
|
@ -82,22 +70,30 @@ New installs
|
|||
If you are running other web enabled services then you may need to edit
|
||||
this file to suite. See README.https to learn about other alternatives.
|
||||
|
||||
If you wish http requests to auto-redirect to https requests, then link or
|
||||
copy /etc/zm/www/redirect.nginx.conf into /etc/nginx/default.d folder.
|
||||
|
||||
When in doubt, proceed with the default:
|
||||
|
||||
sudo ln -s /etc/zm/www/zoneminder.conf /etc/httpd/conf.d/
|
||||
sudo dnf install mod_ssl
|
||||
sudo ln -sf /etc/zm/www/zoneminder.nginx.conf /etc/nginx/conf.d/
|
||||
sudo ln -sf /etc/zm/www/redirect.nginx.conf /etc/nginx/default.d/
|
||||
|
||||
7. Now start the web server:
|
||||
7. Edit /etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of
|
||||
simulatneous streams the server should support. Generally, a good minimum
|
||||
value for this equals the total number of cameras you expect to view at the
|
||||
same time.
|
||||
|
||||
sudo systemctl enable httpd
|
||||
sudo systemctl start httpd
|
||||
8. Now start the web server:
|
||||
|
||||
8. Now start zoneminder:
|
||||
sudo systemctl enable nginx
|
||||
sudo systemctl start nginx
|
||||
|
||||
9. Now start zoneminder:
|
||||
|
||||
sudo systemctl enable zoneminder
|
||||
sudo systemctl start zoneminder
|
||||
|
||||
9. Optionally configure the firewall
|
||||
10. Optionally configure the firewall
|
||||
|
||||
All Redhat distros ship with the firewall enabled. That means you will not
|
||||
be able to access the ZoneMinder web console from a remote machine until
|
||||
|
@ -117,7 +113,7 @@ New installs
|
|||
security requirements and how you use the system. It is up to you to verify
|
||||
these commands are sufficient.
|
||||
|
||||
10. Access the ZoneMinder web console
|
||||
11. Access the ZoneMinder web console
|
||||
|
||||
You may now access the ZoneMinder web console from your web browser using
|
||||
an appropriate url. Here are some examples:
|
||||
|
@ -129,7 +125,7 @@ New installs
|
|||
Upgrades
|
||||
========
|
||||
|
||||
1. Conf.d folder support has been added to ZoneMinder 1.31.0. Any custom
|
||||
1. Conf.d folder support has been added to ZoneMinder. Any custom
|
||||
changes previously made to zm.conf must now be made in one or more custom
|
||||
config files, created under the conf.d folder. Do this now. See
|
||||
/etc/zm/conf.d/README for details. Once you recreate any custom config changes
|
||||
|
@ -144,13 +140,17 @@ Upgrades
|
|||
mysql -uroot -p -e "show grants for zmuser@localhost;"
|
||||
|
||||
See step 2 of the Installation section to add missing permissions.
|
||||
|
||||
3. Verify the ZoneMinder Apache configuration file in the folder
|
||||
|
||||
3. Verify the ZoneMinder Nginx configuration file in the folder
|
||||
/etc/zm/www. You will have a file called "zoneminder.conf" and there
|
||||
may also be a file called "zoneminder.conf.rpmnew". If the rpmnew file
|
||||
exists, inspect it and merge anything new in that file with zoneminder.conf.
|
||||
Verify the SSL REquirements meet your needs. Read README.https if necessary.
|
||||
|
||||
The contents of this file must be merged into your Nginx configuration.
|
||||
See step 6 of the installation section if you have not already done this
|
||||
during a previous upgrade.
|
||||
|
||||
4. Upgrade the database before starting ZoneMinder.
|
||||
|
||||
Most upgrades can be performed by executing the following command:
|
||||
|
@ -163,9 +163,9 @@ Upgrades
|
|||
|
||||
sudo zmupdate.pl --user=root --pass=<mysql_root_pwd> --version=<from version>
|
||||
|
||||
5. Now restart the web server then start zoneminder:
|
||||
5. Now restart nginx and php-fpm then start zoneminder:
|
||||
|
||||
sudo systemctl restart httpd
|
||||
sudo systemctl restart nginx
|
||||
sudo systemctl restart php-fpm
|
||||
sudo systemctl start zoneminder
|
||||
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
@ZM_LOGDIR@/*.log {
|
||||
missingok
|
||||
notifempty
|
||||
sharedscripts
|
||||
postrotate
|
||||
@BINDIR@/zmpkg.pl logrot 2> /dev/null > /dev/null || :
|
||||
endscript
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
# Leaving this to allow one to build zoneminder-http subpackage using arbitrary user account
|
||||
%global zmuid_final apache
|
||||
%global zmgid_final apache
|
||||
|
||||
|
@ -7,10 +8,6 @@
|
|||
# CakePHP-Enum-Behavior is configured as a git submodule
|
||||
%global ceb_version 1.0-zm
|
||||
|
||||
%if "%{zmuid_final}" == "nginx"
|
||||
%global with_nginx 1
|
||||
%endif
|
||||
|
||||
%global sslcert %{_sysconfdir}/pki/tls/certs/localhost.crt
|
||||
%global sslkey %{_sysconfdir}/pki/tls/private/localhost.key
|
||||
|
||||
|
@ -22,15 +19,14 @@
|
|||
%global with_apcu_bc 1
|
||||
%endif
|
||||
|
||||
%global readme_suffix %{?rhel:Redhat%{?rhel}}%{!?rhel:Fedora}
|
||||
# The default for everything but el7 these days
|
||||
%global _hardened_build 1
|
||||
|
||||
Name: zoneminder
|
||||
Version: 1.31.44
|
||||
Release: 1%{?dist}
|
||||
Version: 1.32.2
|
||||
Release: 2%{?dist}
|
||||
Summary: A camera monitoring and analysis tool
|
||||
Group: System Environment/Daemons
|
||||
# jscalendar is LGPL (any version): http://www.dynarch.com/projects/calendar/
|
||||
# Mootools is inder the MIT license: http://mootools.net/
|
||||
# CakePHP is under the MIT license: https://github.com/cakephp/cakephp
|
||||
# Crud is under the MIT license: https://github.com/FriendsOfCake/crud
|
||||
|
@ -53,6 +49,7 @@ BuildRequires: pcre-devel
|
|||
BuildRequires: libjpeg-turbo-devel
|
||||
BuildRequires: findutils
|
||||
BuildRequires: coreutils
|
||||
BuildRequires: net-tools
|
||||
BuildRequires: perl
|
||||
BuildRequires: perl-generators
|
||||
BuildRequires: perl(Archive::Tar)
|
||||
|
@ -74,21 +71,40 @@ BuildRequires: gcc-c++
|
|||
BuildRequires: vlc-devel
|
||||
BuildRequires: libcurl-devel
|
||||
BuildRequires: libv4l-devel
|
||||
BuildRequires: desktop-file-utils
|
||||
BuildRequires: gzip
|
||||
|
||||
# ZoneMinder looks for and records the location of the ffmpeg binary during build
|
||||
BuildRequires: ffmpeg
|
||||
BuildRequires: ffmpeg-devel
|
||||
BuildRequires: desktop-file-utils
|
||||
|
||||
# Required for mp4 container support
|
||||
BuildRequires: libmp4v2-devel
|
||||
BuildRequires: x264-devel
|
||||
|
||||
%{?with_nginx:Requires: nginx}
|
||||
%{?with_nginx:Requires: fcgiwrap}
|
||||
%{?with_nginx:Requires: php-fpm}
|
||||
%{!?with_nginx:Requires: httpd}
|
||||
%{!?with_nginx:Requires: php}
|
||||
# Allow existing user base to seamlessly transition to sub-packages
|
||||
Requires: %{name}-common%{?_isa} = %{version}-%{release}
|
||||
Requires: %{name}-httpd%{?_isa} = %{version}-%{release}
|
||||
|
||||
%description
|
||||
ZoneMinder is a set of applications which is intended to provide a complete
|
||||
solution allowing you to capture, analyze, record and monitor any cameras you
|
||||
have attached to a Linux based machine. It is designed to run on kernels which
|
||||
support the Video For Linux (V4L) interface and has been tested with cameras
|
||||
attached to BTTV cards, various USB cameras and IP network cameras. It is
|
||||
designed to support as many cameras as you can attach to your computer without
|
||||
too much degradation of performance.
|
||||
|
||||
This is a meta package for backwards compatibility with the existing
|
||||
ZoneMinder user base.
|
||||
|
||||
%package common
|
||||
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}
|
||||
Requires: php-pecl-apcu
|
||||
%{?with_apcu_bc:Requires: php-pecl-apcu-bc}
|
||||
Requires: cambozola
|
||||
|
@ -109,16 +125,12 @@ Requires: perl(Net::FTP)
|
|||
Requires: perl(LWP::Protocol::https)
|
||||
Requires: ca-certificates
|
||||
Requires: zip
|
||||
|
||||
Requires(post): systemd
|
||||
Requires(post): systemd-sysv
|
||||
Requires(preun): systemd
|
||||
Requires(postun): systemd
|
||||
%{?systemd_requires}
|
||||
|
||||
Requires(post): %{_bindir}/gpasswd
|
||||
Requires(post): %{_bindir}/less
|
||||
Requires(post): %{_bindir}/chown
|
||||
|
||||
%description
|
||||
%description common
|
||||
ZoneMinder is a set of applications which is intended to provide a complete
|
||||
solution allowing you to capture, analyze, record and monitor any cameras you
|
||||
have attached to a Linux based machine. It is designed to run on kernels which
|
||||
|
@ -127,15 +139,57 @@ attached to BTTV cards, various USB cameras and IP network cameras. It is
|
|||
designed to support as many cameras as you can attach to your computer without
|
||||
too much degradation of performance.
|
||||
|
||||
This is a meta-package that exists solely to allow the existing user base to
|
||||
seamlessly transition to sub-packages.
|
||||
|
||||
%package httpd
|
||||
Summary: ZoneMinder configuration for Apache web server
|
||||
Requires: %{name}-common%{?_isa} = %{version}-%{release}
|
||||
Requires: httpd
|
||||
Requires: php
|
||||
|
||||
Conflicts: %{name}-nginx
|
||||
|
||||
%description httpd
|
||||
ZoneMinder is a set of applications which is intended to provide a complete
|
||||
solution allowing you to capture, analyze, record and monitor any cameras you
|
||||
have attached to a Linux based machine. It is designed to run on kernels which
|
||||
support the Video For Linux (V4L) interface and has been tested with cameras
|
||||
attached to BTTV cards, various USB cameras and IP network cameras. It is
|
||||
designed to support as many cameras as you can attach to your computer without
|
||||
too much degradation of performance.
|
||||
|
||||
This sub-package contains configuration specific to Apache web server
|
||||
|
||||
%package nginx
|
||||
Summary: ZoneMinder configuration for Nginx web server
|
||||
Requires: %{name}-common%{?_isa} = %{version}-%{release}
|
||||
Requires: nginx
|
||||
Requires: php-fpm
|
||||
Requires: fcgiwrap
|
||||
|
||||
Conflicts: %{name}-httpd
|
||||
|
||||
%description nginx
|
||||
ZoneMinder is a set of applications which is intended to provide a complete
|
||||
solution allowing you to capture, analyze, record and monitor any cameras you
|
||||
have attached to a Linux based machine. It is designed to run on kernels which
|
||||
support the Video For Linux (V4L) interface and has been tested with cameras
|
||||
attached to BTTV cards, various USB cameras and IP network cameras. It is
|
||||
designed to support as many cameras as you can attach to your computer without
|
||||
too much degradation of performance.
|
||||
|
||||
This sub-package contains support for ZoneMinder with the Nginx web server
|
||||
|
||||
%prep
|
||||
%autosetup -p 1 -a 1 -n ZoneMinder-%{version}
|
||||
%{__rm} -rf ./web/api/app/Plugin/Crud
|
||||
%{__mv} -f crud-%{crud_version} ./web/api/app/Plugin/Crud
|
||||
%autosetup -p 1 -a 1
|
||||
rm -rf ./web/api/app/Plugin/Crud
|
||||
mv -f crud-%{crud_version} ./web/api/app/Plugin/Crud
|
||||
|
||||
# The all powerful autosetup macro does not work after the second source tarball
|
||||
%{__gzip} -dc %{_sourcedir}/cakephp-enum-behavior-%{ceb_version}.tar.gz | tar -xvvf -
|
||||
%{__rm} -rf ./web/api/app/Plugin/CakePHP-Enum-Behavior
|
||||
%{__mv} -f CakePHP-Enum-Behavior-%{ceb_version} ./web/api/app/Plugin/CakePHP-Enum-Behavior
|
||||
gzip -dc %{_sourcedir}/cakephp-enum-behavior-%{ceb_version}.tar.gz | tar -xvvf -
|
||||
rm -rf ./web/api/app/Plugin/CakePHP-Enum-Behavior
|
||||
mv -f CakePHP-Enum-Behavior-%{ceb_version} ./web/api/app/Plugin/CakePHP-Enum-Behavior
|
||||
|
||||
# Change the following default values
|
||||
./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes
|
||||
|
@ -148,7 +202,7 @@ too much degradation of performance.
|
|||
%build
|
||||
%cmake \
|
||||
-DZM_WEB_USER="%{zmuid_final}" \
|
||||
-DZM_WEB_GROUP="%{zmuid_final}" \
|
||||
-DZM_WEB_GROUP="%{zmgid_final}" \
|
||||
-DZM_TARGET_DISTRO="%{zmtargetdistro}" \
|
||||
.
|
||||
|
||||
|
@ -170,10 +224,13 @@ find %{buildroot} \( -name .htaccess -or -name .editorconfig -or -name .packlist
|
|||
find %{buildroot}%{_datadir}/zoneminder/www/api \( -name cake -or -name cake.php \) -type f -exec sed -i 's\^#!/usr/bin/env bash$\#!%{_buildshell}\' {} \; -exec %{__chmod} 755 {} \;
|
||||
|
||||
# Use the system cacert file rather then the one bundled with CakePHP
|
||||
%{__rm} -f %{buildroot}%{_datadir}/zoneminder/www/api/lib/Cake/Config/cacert.pem
|
||||
%{__ln_s} ../../../../../../../..%{_sysconfdir}/pki/tls/certs/ca-bundle.crt %{buildroot}%{_datadir}/zoneminder/www/api/lib/Cake/Config/cacert.pem
|
||||
rm -f %{buildroot}%{_datadir}/zoneminder/www/api/lib/Cake/Config/cacert.pem
|
||||
ln -s ../../../../../../../..%{_sysconfdir}/pki/tls/certs/ca-bundle.crt %{buildroot}%{_datadir}/zoneminder/www/api/lib/Cake/Config/cacert.pem
|
||||
|
||||
%post
|
||||
# Handle the polkit file differently for web server agnostic support (see post)
|
||||
rm -f %{buildroot}%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
|
||||
|
||||
%post common
|
||||
# Initial installation
|
||||
if [ $1 -eq 1 ] ; then
|
||||
%systemd_post %{name}.service
|
||||
|
@ -181,28 +238,48 @@ fi
|
|||
|
||||
# Upgrade from a previous version of zoneminder
|
||||
if [ $1 -eq 2 ] ; then
|
||||
|
||||
# Add any new PTZ control configurations to the database (will not overwrite)
|
||||
%{_bindir}/zmcamtool.pl --import >/dev/null 2>&1 || :
|
||||
|
||||
# Freshen the database
|
||||
%{_bindir}/zmupdate.pl -f >/dev/null 2>&1 || :
|
||||
|
||||
# We can't run this automatically when new sql account permissions need to
|
||||
# be manually added first
|
||||
# Run zmupdate non-interactively
|
||||
# zmupdate.pl --nointeractive
|
||||
fi
|
||||
|
||||
# Warn the end user to read the README file
|
||||
echo -e "\nVERY IMPORTANT: Before starting ZoneMinder, you must read the README file\nto finish the installation or upgrade!"
|
||||
echo -e "\nThe README file is located here: %{_pkgdocdir}-common/README\n"
|
||||
|
||||
%post httpd
|
||||
# For the case of changing from nginx <-> httpd, files in these folders must change ownership if they exist
|
||||
%{_bindir}/chown -R %{zmuid_final}:%{zmgid_final} %{_sharedstatedir}/php/session/* >/dev/null 2>&1 || :
|
||||
%{_bindir}/chown -R %{zmuid_final}:%{zmgid_final} %{_localstatedir}/log/zoneminder/* >/dev/null 2>&1 || :
|
||||
|
||||
ln -sf %{_sysconfdir}/zm/www/com.zoneminder.systemctl.rules.httpd %{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
|
||||
# backwards compatibility
|
||||
ln -sf %{_sysconfdir}/zm/www/zoneminder.httpd.conf %{_sysconfdir}/zm/www/zoneminder.conf
|
||||
|
||||
# Allow zoneminder access to local video sources, serial ports, and x10
|
||||
%{_bindir}/gpasswd -a %{zmuid_final} video >/dev/null 2>&1 || :
|
||||
%{_bindir}/gpasswd -a %{zmuid_final} dialout >/dev/null 2>&1 || :
|
||||
|
||||
# Warn the end user to read the README file
|
||||
echo -e "\nVERY IMPORTANT: Before starting ZoneMinder, read README.%{readme_suffix} to finish the\ninstallation or upgrade!\n"
|
||||
echo -e "\nThe README file is located here: %{_docdir}/%{name}\n"
|
||||
%post nginx
|
||||
|
||||
# Php package owns the session folder and sets group ownership to apache account
|
||||
# We could override the folder permission, but adding nginx to the apache group works better
|
||||
%{_bindir}/gpasswd -a nginx apache >/dev/null 2>&1 || :
|
||||
|
||||
# For the case of changing from httpd <-> nginx, files in these folders must change ownership if they exist
|
||||
%{_bindir}/chown -R nginx:nginx %{_sharedstatedir}/php/session/* >/dev/null 2>&1 || :
|
||||
%{_bindir}/chown -R nginx:nginx %{_localstatedir}/log/zoneminder/* >/dev/null 2>&1 || :
|
||||
|
||||
ln -sf %{_sysconfdir}/zm/www/com.zoneminder.systemctl.rules.nginx %{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
|
||||
# backwards compatibility
|
||||
ln -sf %{_sysconfdir}/zm/www/zoneminder.nginx.conf %{_sysconfdir}/zm/www/zoneminder.conf
|
||||
|
||||
# Allow zoneminder access to local video sources, serial ports, and x10
|
||||
%{_bindir}/gpasswd -a nginx video >/dev/null 2>&1 || :
|
||||
%{_bindir}/gpasswd -a nginx dialout >/dev/null 2>&1 || :
|
||||
|
||||
%if 0%{?with_nginx}
|
||||
# Nginx does not create an SSL certificate like the apache package does so lets do that here
|
||||
if [ -f %{sslkey} -o -f %{sslcert} ]; then
|
||||
exit 0
|
||||
|
@ -228,7 +305,6 @@ SomeOrganizationalUnit
|
|||
${FQDN}
|
||||
root@${FQDN}
|
||||
EOF
|
||||
%endif
|
||||
|
||||
%preun
|
||||
%systemd_preun %{name}.service
|
||||
|
@ -236,19 +312,12 @@ EOF
|
|||
%postun
|
||||
%systemd_postun_with_restart %{name}.service
|
||||
|
||||
%triggerun -- zoneminder < 1.25.0-4
|
||||
# Save the current service runlevel info
|
||||
# User must manually run systemd-sysv-convert --apply zoneminder
|
||||
# to migrate them to systemd targets
|
||||
%{_bindir}/systemd-sysv-convert --save zoneminder >/dev/null 2>&1 ||:
|
||||
|
||||
# Run these because the SysV package being removed won't do them
|
||||
/sbin/chkconfig --del zoneminder >/dev/null 2>&1 || :
|
||||
/bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || :
|
||||
|
||||
%files
|
||||
# nothing
|
||||
|
||||
%files common
|
||||
%license COPYING
|
||||
%doc AUTHORS README.md distros/redhat/readme/README.%{readme_suffix} distros/redhat/readme/README.https distros/redhat/jscalendar-doc
|
||||
%doc AUTHORS README.md distros/redhat/readme/README distros/redhat/readme/README.httpd distros/redhat/readme/README.nginx distros/redhat/readme/README.https
|
||||
|
||||
# We want these two folders to have "normal" read permission
|
||||
# compared to the folder contents
|
||||
|
@ -258,21 +327,11 @@ EOF
|
|||
# Config folder contents contain sensitive info
|
||||
# and should not be readable by normal users
|
||||
%{_sysconfdir}/zm/conf.d/README
|
||||
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/zm.conf
|
||||
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/*.conf
|
||||
%ghost %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/zmcustom.conf
|
||||
|
||||
%config(noreplace) %attr(644,root,root) /etc/zm/www/zoneminder.conf
|
||||
%config(noreplace) %{_sysconfdir}/logrotate.d/zoneminder
|
||||
|
||||
%if 0%{?with_nginx}
|
||||
%config(noreplace) %{_sysconfdir}/php-fpm.d/zoneminder.conf
|
||||
%endif
|
||||
|
||||
%{_tmpfilesdir}/zoneminder.conf
|
||||
%{_unitdir}/zoneminder.service
|
||||
%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy
|
||||
%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
|
||||
%{_bindir}/zmsystemctl.pl
|
||||
|
||||
%{_bindir}/zma
|
||||
|
@ -303,8 +362,19 @@ EOF
|
|||
|
||||
%{_libexecdir}/zoneminder/
|
||||
%{_datadir}/zoneminder/
|
||||
%{_datadir}/applications/*%{name}.desktop
|
||||
%{_datadir}/applications/*zoneminder.desktop
|
||||
|
||||
%files httpd
|
||||
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/zm.conf
|
||||
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/0*.conf
|
||||
%ghost %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/zmcustom.conf
|
||||
%config(noreplace) %{_sysconfdir}/zm/www/zoneminder.httpd.conf
|
||||
%ghost %{_sysconfdir}/zm/www/zoneminder.conf
|
||||
%config(noreplace) %{_sysconfdir}/zm/www/com.zoneminder.systemctl.rules.httpd
|
||||
%ghost %{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
|
||||
|
||||
%{_unitdir}/zoneminder.service.d/zm-httpd.conf
|
||||
%{_tmpfilesdir}/zoneminder.httpd.tmpfiles.conf
|
||||
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder
|
||||
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/events
|
||||
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/images
|
||||
|
@ -314,14 +384,73 @@ EOF
|
|||
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/cache/zoneminder
|
||||
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/log/zoneminder
|
||||
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/spool/zoneminder-upload
|
||||
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/run/zoneminder
|
||||
|
||||
%files nginx
|
||||
%config(noreplace) %attr(640,root,nginx) %{_sysconfdir}/zm/zm.conf
|
||||
%config(noreplace) %attr(640,root,nginx) %{_sysconfdir}/zm/conf.d/*.conf
|
||||
%ghost %attr(640,root,nginx) %{_sysconfdir}/zm/conf.d/zmcustom.conf
|
||||
%config(noreplace) %{_sysconfdir}/zm/www/zoneminder.nginx.conf
|
||||
%config(noreplace) %{_sysconfdir}/zm/www/redirect.nginx.conf
|
||||
%ghost %{_sysconfdir}/zm/www/zoneminder.conf
|
||||
%config(noreplace) %{_sysconfdir}/zm/www/com.zoneminder.systemctl.rules.nginx
|
||||
%ghost %{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
|
||||
|
||||
%config(noreplace) %{_sysconfdir}/php-fpm.d/zoneminder.php-fpm.conf
|
||||
|
||||
|
||||
%{_unitdir}/zoneminder.service.d/zm-nginx.conf
|
||||
%{_tmpfilesdir}/zoneminder.nginx.tmpfiles.conf
|
||||
%dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder
|
||||
%dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder/events
|
||||
%dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder/images
|
||||
%dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder/sock
|
||||
%dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder/swap
|
||||
%dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder/temp
|
||||
%dir %attr(755,nginx,nginx) %{_localstatedir}/cache/zoneminder
|
||||
%dir %attr(755,nginx,nginx) %{_localstatedir}/log/zoneminder
|
||||
%dir %attr(755,nginx,nginx) %{_localstatedir}/spool/zoneminder-upload
|
||||
|
||||
%changelog
|
||||
* Sun Apr 22 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.31.42-1
|
||||
- Remove support for sysvinit a.k.a. el6
|
||||
- use desktop-file-install for new zoneminder.desktop file
|
||||
- add new web cache folder
|
||||
- 1.31.42 development snapshot
|
||||
* Wed Nov 14 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.32.2-2
|
||||
- Break into sub-packages
|
||||
|
||||
* Sat Oct 13 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.32.2-1
|
||||
- 1.32.2 release
|
||||
- Bug fix release
|
||||
|
||||
* Thu Oct 04 2018 Sérgio Basto <sergio@serjux.com> - 1.32.1-2
|
||||
- Mass rebuild for x264 and/or x265
|
||||
|
||||
* Tue Oct 2 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.32.1-1
|
||||
- 1.32.1 release
|
||||
- Bug fix release
|
||||
|
||||
* Wed Sep 12 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.32.0-1
|
||||
- 1.32.0 release
|
||||
- remove el6 (sys v init) support
|
||||
- Make README name consistent across all supported distros
|
||||
- remove jscalendar
|
||||
- add requires php-json, zip
|
||||
- support zm/conf.d folder
|
||||
- support zm cache (busting) folder
|
||||
|
||||
* Sun Aug 19 2018 Leigh Scott <leigh123linux@googlemail.com> - 1.30.4-9
|
||||
- Rebuilt for Fedora 29 Mass Rebuild binutils issue
|
||||
|
||||
* Fri Jul 27 2018 RPM Fusion Release Engineering <leigh123linux@gmail.com> - 1.30.4-8
|
||||
- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
|
||||
|
||||
* Thu Mar 08 2018 RPM Fusion Release Engineering <leigh123linux@googlemail.com> - 1.30.4-7
|
||||
- Rebuilt for new ffmpeg snapshot
|
||||
|
||||
* Thu Mar 01 2018 RPM Fusion Release Engineering <leigh123linux@googlemail.com> - 1.30.4-6
|
||||
- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild
|
||||
|
||||
* Thu Jan 18 2018 Leigh Scott <leigh123linux@googlemail.com> - 1.30.4-5
|
||||
- Rebuilt for ffmpeg-3.5 git
|
||||
|
||||
* Thu Aug 31 2017 RPM Fusion Release Engineering <kwizart@rpmfusion.org> - 1.30.4-4
|
||||
- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild
|
||||
|
||||
* Tue May 09 2017 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.30.4-1
|
||||
- modify autosetup macro parameters
|
||||
|
|
|
@ -9,7 +9,17 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin"
|
|||
# Order matters. This Alias must come first
|
||||
Alias /zm/cache /var/cache/zoneminder/cache
|
||||
<Directory /var/cache/zoneminder/cache>
|
||||
Options -Indexes +FollowSymLinks
|
||||
Options -Indexes +FollowSymLinks
|
||||
AllowOverride None
|
||||
<IfModule mod_authz_core.c>
|
||||
# Apache 2.4
|
||||
Require all granted
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
# Apache 2.2
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</IfModule>
|
||||
</Directory>
|
||||
|
||||
Alias /zm /usr/share/zoneminder/www
|
||||
|
@ -45,4 +55,3 @@ Alias /zm /usr/share/zoneminder/www
|
|||
RewriteRule ^ index.php [L]
|
||||
RewriteBase /zm/api
|
||||
</Directory>
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
|||
,libdevice-serialport-perl
|
||||
,libimage-info-perl
|
||||
,libjson-any-perl
|
||||
,libjson-maybexs-perl
|
||||
,libsys-mmap-perl [!hurd-any]
|
||||
,liburi-encode-perl
|
||||
,libwww-perl
|
||||
|
|
|
@ -28,7 +28,7 @@ override_dh_auto_configure:
|
|||
-DZM_CACHEDIR="/var/cache/zoneminder/cache" \
|
||||
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
|
||||
-DZM_DIR_IMAGES="/var/cache/zoneminder/images" \
|
||||
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms" \
|
||||
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms"
|
||||
|
||||
override_dh_clean:
|
||||
dh_clean $(MANPAGES1)
|
||||
|
|
|
@ -32,7 +32,7 @@ Package: libzoneminder-perl
|
|||
Section: perl
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, ${perl:Depends}, libdbi-perl,
|
||||
libdevice-serialport-perl, libimage-info-perl, libjson-any-perl,
|
||||
libdevice-serialport-perl, libimage-info-perl, libjson-any-perl, libjson-maybexs-perl,
|
||||
libsys-mmap-perl, liburi-encode-perl, libwww-perl
|
||||
Description: Perl libraries for ZoneMinder
|
||||
ZoneMinder is a video camera security and surveillance solution.
|
||||
|
|
|
@ -45,7 +45,7 @@ Package: libzoneminder-perl
|
|||
Section: perl
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, ${perl:Depends}, libdbi-perl,
|
||||
libdevice-serialport-perl, libimage-info-perl, libjson-any-perl,
|
||||
libdevice-serialport-perl, libimage-info-perl, libjson-any-perl, libjson-maybexs-perl,
|
||||
libsys-mmap-perl, liburi-encode-perl, libwww-perl
|
||||
Description: Perl libraries for ZoneMinder
|
||||
ZoneMinder is a video camera security and surveillance solution.
|
||||
|
|
|
@ -9,7 +9,17 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin"
|
|||
# Order matters. This alias must come first.
|
||||
Alias /zm/cache /var/cache/zoneminder/cache
|
||||
<Directory /var/cache/zoneminder/cache>
|
||||
Options -Indexes +FollowSymLinks
|
||||
Options -Indexes +FollowSymLinks
|
||||
AllowOverride None
|
||||
<IfModule mod_authz_core.c>
|
||||
# Apache 2.4
|
||||
Require all granted
|
||||
</IfModule>
|
||||
<IfModule !mod_authz_core.c>
|
||||
# Apache 2.2
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</IfModule>
|
||||
</Directory>
|
||||
|
||||
Alias /zm /usr/share/zoneminder/www
|
||||
|
|
|
@ -3,7 +3,7 @@ Section: net
|
|||
Priority: optional
|
||||
Maintainer: Dmitry Smirnov <onlyjob@debian.org>
|
||||
Uploaders: Vagrant Cascadian <vagrant@debian.org>
|
||||
Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apache2-dev, dh-linktree
|
||||
Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apache2-dev, dh-linktree, dh-systemd, dh-apache2
|
||||
,cmake
|
||||
,libx264-dev, libmp4v2-dev
|
||||
,libavdevice-dev (>= 6:10~)
|
||||
|
@ -11,6 +11,8 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa
|
|||
,libavformat-dev (>= 6:10~)
|
||||
,libavutil-dev (>= 6:10~)
|
||||
,libswscale-dev (>= 6:10~)
|
||||
,ffmpeg | libav-tools
|
||||
,net-tools
|
||||
,libbz2-dev
|
||||
,libgcrypt-dev | libgcrypt11-dev
|
||||
,libcurl4-gnutls-dev
|
||||
|
@ -39,7 +41,7 @@ Package: zoneminder
|
|||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
||||
,javascript-common
|
||||
,libmp4v2-2, libx264-142|libx264-148|libx264-152, libswscale-ffmpeg3|libswscale4|libswscale3
|
||||
,libmp4v2-2, libx264-142|libx264-148|libx264-152, libswscale-ffmpeg3|libswscale4|libswscale3|libswscale5
|
||||
,ffmpeg | libav-tools
|
||||
,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl
|
||||
,libdbd-mysql-perl
|
||||
|
@ -51,6 +53,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
|||
,libdevice-serialport-perl
|
||||
,libimage-info-perl
|
||||
,libjson-any-perl
|
||||
,libjson-maybexs-perl
|
||||
,libsys-mmap-perl [!hurd-any]
|
||||
,liburi-encode-perl
|
||||
,libwww-perl
|
||||
|
|
|
@ -22,7 +22,7 @@ if [ "$1" = "configure" ]; then
|
|||
|
||||
if [ "$ZM_DB_HOST" = "localhost" ]; then
|
||||
|
||||
if [ -e "/lib/systemd/system/mysql.service" ] || [ -e "/lib/systemd/system/mariadb.service" ]; then
|
||||
if [ -e "/lib/systemd/system/mysql.service" ] || [ -e "/lib/systemd/system/mariadb.service" ] || [ -e "/etc/init.d/mysql" ]; then
|
||||
# Ensure zoneminder is stopped
|
||||
deb-systemd-invoke stop zoneminder.service || exit $?
|
||||
|
||||
|
@ -68,6 +68,7 @@ if [ "$1" = "configure" ]; then
|
|||
|
||||
# Add any new PTZ control configurations to the database (will not overwrite)
|
||||
zmcamtool.pl --import >/dev/null 2>&1
|
||||
echo "Done Updating; starting ZoneMinder."
|
||||
else
|
||||
echo 'NOTE: MySQL/MariaDB not running; please start mysql and run dpkg-reconfigure zoneminder when it is running.'
|
||||
fi
|
||||
|
@ -78,7 +79,6 @@ if [ "$1" = "configure" ]; then
|
|||
else
|
||||
echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)."
|
||||
fi
|
||||
echo "Done Updating; starting ZoneMinder."
|
||||
deb-systemd-invoke restart zoneminder.service
|
||||
|
||||
fi
|
||||
|
|
280
docs/api.rst
280
docs/api.rst
|
@ -5,7 +5,6 @@ This document will provide an overview of ZoneMinder's API. This is work in prog
|
|||
|
||||
Overview
|
||||
^^^^^^^^
|
||||
|
||||
In an effort to further 'open up' ZoneMinder, an API was needed. This will
|
||||
allow quick integration with and development of ZoneMinder.
|
||||
|
||||
|
@ -13,8 +12,87 @@ The API is built in CakePHP and lives under the ``/api`` directory. It
|
|||
provides a RESTful service and supports CRUD (create, retrieve, update, delete)
|
||||
functions for Monitors, Events, Frames, Zones and Config.
|
||||
|
||||
Security
|
||||
^^^^^^^^^
|
||||
Streaming Interface
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
Developers working on their application often ask if there is an "API" to receive live streams, or recorded event streams.
|
||||
It is possible to stream both live and recorded streams. This isn't strictly an "API" per-se (that is, it is not integrated
|
||||
into the Cake PHP based API layer discussed here) and also why we've used the term "Interface" instead of an "API".
|
||||
|
||||
Live Streams
|
||||
~~~~~~~~~~~~~~
|
||||
What you need to know is that if you want to display "live streams", ZoneMinder sends you streaming JPEG images (MJPEG)
|
||||
which can easily be rendered in a browser using an ``img src`` tag.
|
||||
|
||||
For example:
|
||||
|
||||
::
|
||||
|
||||
<img src="https://yourserver/zm/cgi-bin/nph-zms?scale=50&width=640p&height=480px&mode=jpeg&maxfps=5&buffer=1000&&monitor=1&auth=b54a589e09f330498f4ae2203&connkey=36139" />
|
||||
|
||||
will display a live feed from monitor id 1, scaled down by 50% in quality and resized to 640x480px.
|
||||
|
||||
* This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system
|
||||
* The "auth" token you see above is required if you use ZoneMinder authentication. To understand how to get the auth token, please read the "Login, Logout & API security" section below.
|
||||
* The "connkey" parameter is essentially a random number which uniquely identifies a stream. If you don't specify a connkey, ZM will generate its own. It is recommended to generate a connkey because you can then use it to "control" the stream (pause/resume etc.)
|
||||
* Instead of dealing with the "auth" token, you can also use ``&user=username&pass=password`` where "username" and "password" are your ZoneMinder username and password respectively. Note that this is not recommended because you are transmitting them in a URL and even if you use HTTPS, they may show up in web server logs.
|
||||
|
||||
|
||||
PTZ on live streams
|
||||
-------------------
|
||||
PTZ commands are pretty cryptic in ZoneMinder. This is not meant to be an exhaustive guide, but just something to whet your appetite:
|
||||
|
||||
|
||||
Lets assume you have a monitor, with ID=6. Let's further assume you want to pan it left.
|
||||
|
||||
You'd need to send a:
|
||||
``POST`` command to ``https://yourserver/zm/index.php`` with the following data payload in the command (NOT in the URL)
|
||||
|
||||
``view=request&request=control&id=6&control=moveConLeft&xge=30&yge=30``
|
||||
|
||||
Obviously, if you are using authentication, you need to be logged in for this to work.
|
||||
|
||||
Like I said, at this stage, this is only meant to get you started. Explore the ZoneMinder code and use "Inspect source" as you use PTZ commands in the ZoneMinder source code.
|
||||
`control_functions.php <https://github.com/ZoneMinder/zoneminder/blob/10531df54312f52f0f32adec3d4720c063897b62/web/skins/classic/includes/control_functions.php>`__ is a great place to start.
|
||||
|
||||
|
||||
Pre-recorded (past event) streams
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Similar to live playback, if you have chosen to store events in JPEG mode, you can play it back using:
|
||||
|
||||
::
|
||||
|
||||
<img src="https://yourserver/zm/cgi-bin/nph-zms?mode=jpeg&frame=1&replay=none&source=event&event=293820&connkey=77493&auth=b54a58f5f4ae2203" />
|
||||
|
||||
|
||||
* This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system
|
||||
* This will playback event 293820, starting from frame 1 as an MJPEG stream
|
||||
* Like before, you can add more parameters like ``scale`` etc.
|
||||
* auth and connkey have the same meaning as before, and yes, you can replace auth by ``&user=usename&pass=password`` as before and the same security concerns cited above apply.
|
||||
|
||||
If instead, you have chosen to use the MP4 (Video) storage mode for events, you can directly play back the saved video file:
|
||||
|
||||
::
|
||||
|
||||
<video src="https://yourserver/zm/index.php?view=view_video&eid=294690&auth=33f3d558af84cf08" type="video/mp4"></video>
|
||||
|
||||
* This will play back the video recording for event 294690
|
||||
|
||||
What other parameters are supported?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
The best way to answer this question is to play with ZoneMinder console. Open a browser, play back live or recorded feed, and do an "Inspect Source" to see what parameters
|
||||
are generated. Change and observe.
|
||||
|
||||
|
||||
Enabling API
|
||||
^^^^^^^^^^^^
|
||||
A default ZoneMinder installs with APIs enabled. You can explictly enable/disable the APIs
|
||||
via the Options->System menu by enabling/disabling ``OPT_USE_API``. Note that if you intend
|
||||
to use APIs with 3rd party apps, such as zmNinja or others that use APIs, you should also
|
||||
enable ``AUTH_HASH_LOGINS``.
|
||||
|
||||
Login, Logout & API Security
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
The APIs tie into ZoneMinder's existing security model. This means if you have
|
||||
OPT_AUTH enabled, you need to log into ZoneMinder using the same browser you plan to
|
||||
use the APIs from. If you are developing an app that relies on the API, you need
|
||||
|
@ -23,11 +101,32 @@ to do a POST login from the app into ZoneMinder before you can access the API.
|
|||
Then, you need to re-use the authentication information of the login (returned as cookie states)
|
||||
with subsequent APIs for the authentication information to flow through to the APIs.
|
||||
|
||||
This means if you plan to use cuRL to experiment with these APIs, you first need to do
|
||||
This means if you plan to use cuRL to experiment with these APIs, you first need to login:
|
||||
|
||||
**Login process for ZoneMinder v1.32.0 and above**
|
||||
|
||||
::
|
||||
|
||||
curl -d "username=XXXX&password=YYYY&action=login&view=console" -c cookies.txt http://yourzmip/zm/index.php
|
||||
curl -XPOST -d "user=XXXX&pass=YYYY" -c cookies.txt http://yourzmip/zm/api/host/login.json
|
||||
|
||||
Staring ZM 1.32.0, you also have a `logout` API that basically clears your session. It looks like this:
|
||||
|
||||
::
|
||||
|
||||
curl -b cookies.txt http://yourzmip/zm/api/host/logout.json
|
||||
|
||||
|
||||
**Login process for older versions of ZoneMinder**
|
||||
|
||||
::
|
||||
|
||||
curl -d "username=XXXX&password=YYYY&action=login&view=console" -c cookies.txt http://yourzmip/zm/index.php
|
||||
|
||||
The equivalent logout process for older versions of ZoneMinder is:
|
||||
|
||||
::
|
||||
|
||||
curl -XPOST -d "username=XXXX&password=YYYY&action=logout&view=console" -b cookies.txt http://yourzmip/zm/index.php
|
||||
|
||||
replacing *XXXX* and *YYYY* with your username and password, respectively.
|
||||
|
||||
|
@ -36,25 +135,55 @@ and the command will silently fail.
|
|||
|
||||
|
||||
What the "-c cookies.txt" does is store a cookie state reflecting that you have logged into ZM. You now need
|
||||
to apply that cookie state to all subsequent APIs. You do that by using a '-b cookies.txt' to subsequent APIs if you are
|
||||
to apply that cookie state to all subsequent APIs. You do that by using a '-b cookies.txt' to subsequent APIs if you are
|
||||
using CuRL like so:
|
||||
|
||||
::
|
||||
|
||||
curl -b cookies.txt http://yourzmip/zm/api/monitors.json
|
||||
curl -b cookies.txt http://yourzmip/zm/api/monitors.json
|
||||
|
||||
This would return a list of monitors and pass on the authentication information to the ZM API layer.
|
||||
|
||||
A deeper dive into the login process
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
As you might have seen above, there are two ways to login, one that uses the `login.json` API and the other that logs in using the ZM portal. If you are running ZoneMinder 1.32.0 and above, it is *strongly* recommended you use the `login.json` approach. The "old" approach will still work but is not as powerful as the API based login. Here are the reasons why:
|
||||
|
||||
* The "old" approach basically uses the same login webpage (`index.php`) that a user would log into when viewing the ZM console. This is not really using an API and more importantly, if you have additional components like reCAPTCHA enabled, this will not work. Using the API approach is much cleaner and will work irrespective of reCAPTCHA
|
||||
|
||||
* The new login API returns important information that you can use to stream videos as well, right after login. Consider for example, a typical response to the login API (`/login.json`):
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"credentials": "auth=f5b9cf48693fe8552503c8ABCD5",
|
||||
"append_password": 0,
|
||||
"version": "1.31.44",
|
||||
"apiversion": "1.0"
|
||||
}
|
||||
|
||||
In this example I have `OPT_AUTH` enabled in ZoneMinder and it returns my credential key. You can then use this key to stream images like so:
|
||||
|
||||
::
|
||||
|
||||
<img src="https://server/zm/cgi-bin/nph-zms?monitor=1&auth=<authval>" />
|
||||
|
||||
Where `authval` is the credentials returned to start streaming videos.
|
||||
|
||||
The `append_password` field will contain 1 when it is necessary for you to append your ZM password. This is the case when you set `AUTH_RELAY` in ZM options to "plain", for example. In that case, the `credentials` field may contain something like `&user=admin&pass=` and you have to add your password to that string.
|
||||
|
||||
|
||||
.. NOTE:: It is recommended you invoke the `login` API once every 60 minutes to make sure the session stays alive. The same is true if you use the old login method too.
|
||||
|
||||
This would return a list of monitors and pass on the authentication information to the ZM API layer.
|
||||
|
||||
So remember, if you are using authentication, please add a ``-b cookies.txt`` to each of the commands below if you are using
|
||||
CuRL. If you are not using CuRL and writing your own app, you need to make sure you pass on cookies to subsequent requests
|
||||
in your app.
|
||||
|
||||
Examples (please read security notice above)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You will see each URL ending in either ``.xml`` or ``.json``. This is the
|
||||
format of the request, and it determines the format that any data returned to
|
||||
you will be in. I like json, however you can use xml if you'd like.
|
||||
Please remember, if you are using authentication, please add a ``-b cookies.txt`` to each of the commands below if you are using
|
||||
CuRL. If you are not using CuRL and writing your own app, you need to make sure you pass on cookies to subsequent requests
|
||||
in your app.
|
||||
|
||||
|
||||
(In all examples, replace 'server' with IP or hostname & port where ZoneMinder is running)
|
||||
|
||||
|
@ -74,6 +203,22 @@ Return a list of all monitors
|
|||
|
||||
curl http://server/zm/api/monitors.json
|
||||
|
||||
It is worthwhile to note that starting ZM 1.32.3 and beyond, this API also returns a ``Monitor_Status`` object per monitor. It looks like this:
|
||||
|
||||
::
|
||||
|
||||
"Monitor_Status": {
|
||||
"MonitorId": "2",
|
||||
"Status": "Connected",
|
||||
"CaptureFPS": "1.67",
|
||||
"AnalysisFPS": "1.67",
|
||||
"CaptureBandwidth": "52095"
|
||||
}
|
||||
|
||||
|
||||
If you don't see this in your API, you are running an older version of ZM. This gives you a very convenient way to check monitor status without calling the ``daemonCheck`` API described later.
|
||||
|
||||
|
||||
Retrieve monitor 1
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -90,6 +235,13 @@ This API changes monitor 1 to Modect and Enabled
|
|||
::
|
||||
|
||||
curl -XPOST http://server/zm/api/monitors/1.json -d "Monitor[Function]=Modect&Monitor[Enabled]=1"
|
||||
|
||||
Get Daemon Status of Monitor 1
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
::
|
||||
|
||||
curl http://server/zm/api/monitors/daemonStatus/id:1/daemon:zmc.json
|
||||
|
||||
Add a monitor
|
||||
^^^^^^^^^^^^^^
|
||||
|
@ -326,11 +478,13 @@ Create a Zone
|
|||
&Zone[MaxBlobs]=\
|
||||
&Zone[OverloadFrames]=0"
|
||||
|
||||
PTZ Control APIs
|
||||
^^^^^^^^^^^^^^^^
|
||||
PTZ Control Meta-Data APIs
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
PTZ controls associated with a monitor are stored in the Controls table and not the Monitors table inside ZM. What that means is when you get the details of a Monitor, you will only know if it is controllable (isControllable:true) and the control ID.
|
||||
To be able to retrieve PTZ information related to that Control ID, you need to use the controls API
|
||||
|
||||
Note that these APIs only retrieve control data related to PTZ. They don't actually move the camera. See the "PTZ on live streams" section to move the camera.
|
||||
|
||||
This returns all the control definitions:
|
||||
::
|
||||
|
||||
|
@ -348,7 +502,97 @@ ZM APIs have various APIs that help you in determining host (aka ZM) daemon stat
|
|||
|
||||
::
|
||||
|
||||
curl -XGET http://server/zm/api/host/daemonCheck.json # 1 = ZM running 0=not running
|
||||
curl -XGET http://server/zm/api/host/getLoad.json # returns current load of ZM
|
||||
curl -XGET http://server/zm/api/host/getDiskPercent.json # returns in GB (not percentage), disk usage per monitor (that is, space taken to store various event related information,images etc. per monitor)
|
||||
|
||||
# Note that ZM 1.32.3 onwards has the same information in Monitors.json which is more reliable and works for multi-server too.
|
||||
curl -XGET http://server/zm/api/host/daemonCheck.json # 1 = ZM running 0=not running
|
||||
|
||||
# The API below uses "du" to calculate disk space. We no longer recommend you use it if you have many events. Use the Storage APIs instead, described later
|
||||
curl -XGET http://server/zm/api/host/getDiskPercent.json # returns in GB (not percentage), disk usage per monitor (that is,space taken to store various event related information,images etc. per monitor)
|
||||
|
||||
|
||||
Storage and Server APIs
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
ZoneMinder introduced many new options that allowed you to configure multiserver/multistorage configurations. While a part of this was available in previous versions, a lot of rework was done as part of ZM 1.31 and 1.32. As part of that work, a lot of new and useful APIs were added. Some of these are part of ZM 1.32 and others will be part of ZM 1.32.3 (of course, if you build from master, you can access them right away, or wait till a stable release is out.
|
||||
|
||||
|
||||
|
||||
This returns storage data for my single server install. If you are using multi-storage, you'll see many such "Storage" entries, one for each storage defined:
|
||||
|
||||
::
|
||||
|
||||
curl http://server/zm/api/storage.json
|
||||
|
||||
Returns:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"storage": [
|
||||
{
|
||||
"Storage": {
|
||||
"Id": "0",
|
||||
"Path": "\/var\/cache\/zoneminder\/events",
|
||||
"Name": "Default",
|
||||
"Type": "local",
|
||||
"Url": null,
|
||||
"DiskSpace": "364705447651",
|
||||
"Scheme": "Medium",
|
||||
"ServerId": null,
|
||||
"DoDelete": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
"DiskSpace" is the disk used in bytes. While this doesn't return disk space data as rich as ``/host/getDiskPercent``, it is much more efficient.
|
||||
|
||||
Similarly,
|
||||
|
||||
::
|
||||
|
||||
curl http://server/zm/api/servers.json
|
||||
|
||||
Returns:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"servers": [
|
||||
{
|
||||
"Server": {
|
||||
"Id": "1",
|
||||
"Name": "server1",
|
||||
"Hostname": "server1.mydomain.com",
|
||||
"State_Id": null,
|
||||
"Status": "Running",
|
||||
"CpuLoad": "0.9",
|
||||
"TotalMem": "6186237952",
|
||||
"FreeMem": "156102656",
|
||||
"TotalSwap": "536866816",
|
||||
"FreeSwap": "525697024",
|
||||
"zmstats": false,
|
||||
"zmaudit": false,
|
||||
"zmtrigger": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
This only works if you have a multiserver setup in place. If you don't it will return an empty array.
|
||||
|
||||
|
||||
Further Reading
|
||||
^^^^^^^^^^^^^^^^
|
||||
As described earlier, treat this document as an "introduction" to the important parts of the API and streaming interfaces.
|
||||
There are several details that haven't yet been documented. Till they are, here are some resources:
|
||||
|
||||
* zmNinja, the open source mobile app for ZoneMinder is 100% based on ZM APIs. Explore its `source code <https://github.com/pliablepixels/zmNinja>`__ to see how things work.
|
||||
* Launch up ZM console in a browser, and do an "Inspect source". See how images are being rendered. Go to the networks tab of the inspect source console and look at network requests that are made when you pause/play/forward streams.
|
||||
* If you still can't find an answer, post your question in the `forums <https://forums.zoneminder.com/index.php>`__ (not the github repo).
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -311,7 +311,7 @@ There are a number of specific reasons why processor loads can be high either by
|
|||
|
||||
The main causes are.
|
||||
|
||||
* Using a video palette other than greyscale or RGB24. This can cause a relatively minor performace hit, though still significant. Although some cameras and cards require using planar palettes ZM currently doesn't support this format internally and each frame is converted to an RGB representation prior to processing. Unless you have compelling reasons for using YUV or reduced RGB type palettes such as hitting USB transfer limits I would experiment to see if RGB24 or greyscale is quicker. Put your monitors into 'Monitor' mode so that only the capture daemons are running and monitor the process load of these (the 'zmc' processes) using top. Try it with various palettes to see if it makes a difference.
|
||||
* Using a video palette other than greyscale or RGB24. This can cause a relatively minor performance hit, though still significant. Although some cameras and cards require using planar palettes ZM currently doesn't support this format internally and each frame is converted to an RGB representation prior to processing. Unless you have compelling reasons for using YUV or reduced RGB type palettes such as hitting USB transfer limits I would experiment to see if RGB24 or greyscale is quicker. Put your monitors into 'Monitor' mode so that only the capture daemons are running and monitor the process load of these (the 'zmc' processes) using top. Try it with various palettes to see if it makes a difference.
|
||||
* Big image sizes. A image of 640x480 requires at least four times the processing of a 320x240 image. Experiment with different sizes to see what effect it may have. Sometimes a large image is just two interlaced smaller frames so has no real benefit anyway. This is especially true for analog cameras/cards as image height over 320 (NTSC) or 352 PAL) are invariably interlaced.
|
||||
* Capture frame rates. Unless there's a compelling reason in your case there is often little benefit in running cameras at 25fps when 5-10fps would often get you results just as good. Try changing your monitor settings to limit your cameras to lower frame rates. You can still configure ZM to ignore these limits and capture as fast as possible when motion is detected.
|
||||
* Run function. Obviously running in Record or Mocord modes or in Modect with lots of events generates a lot of DB and file activity and so CPU and load will increase.
|
||||
|
|
|
@ -35,7 +35,7 @@ guide you with a quick search.
|
|||
`releases page <https://github.com/ZoneMinder/zoneminder/releases>`_ for
|
||||
the latest release.
|
||||
|
||||
Alternatively, the ZoneMinder project team maintains a ppa, which is updated immediately
|
||||
Alternatively, the ZoneMinder project team maintains a `PPA <https://askubuntu.com/questions/4983/what-are-ppas-and-how-do-i-use-them>`_, which is updated immediately
|
||||
following a new release of ZoneMinder. To use this repository instead of the
|
||||
official Ubuntu repository, enter the following from the command line:
|
||||
|
||||
|
@ -43,6 +43,15 @@ guide you with a quick search.
|
|||
|
||||
add-apt-repository ppa:iconnor/zoneminder
|
||||
|
||||
Please note that as of 1.32.0 We are creating a new PPA for each major version, as a means to prevent automatic upgrades from one major version to another. So instead of the above ppa line use the following:
|
||||
|
||||
::
|
||||
|
||||
add-apt-repository ppa:iconnor/zoneminder-1.32
|
||||
|
||||
If you are on Trusty or Xenial, you may want to add both, as there are some packages for dependencies included in the old ppa.
|
||||
|
||||
|
||||
Update repo and upgrade.
|
||||
|
||||
::
|
||||
|
@ -51,6 +60,7 @@ Update repo and upgrade.
|
|||
apt-get upgrade
|
||||
apt-get dist-upgrade
|
||||
|
||||
|
||||
**Step 3:** Configure MySQL
|
||||
|
||||
.. sidebar :: Note
|
||||
|
@ -62,8 +72,10 @@ Update repo and upgrade.
|
|||
| /etc/alternatives/my.cnf -> /etc/mysql/mysql.cnf
|
||||
| /etc/mysql/mysql.cnf is a basic file
|
||||
|
||||
Certain new defaults in MySQL 5.7 are currently causing some issues with ZoneMinder,
|
||||
the workaround is to modify the sql_mode setting of MySQL.
|
||||
Certain new defaults in MySQL 5.7 cause some issues with ZoneMinder < 1.32.0,
|
||||
the workaround is to modify the sql_mode setting of MySQL. Please note that these
|
||||
changes are NOT required for ZoneMinder 1.32.0 and some people have reported them
|
||||
causing problems in 1.32.0.
|
||||
|
||||
To better manage the MySQL server it is recommended to copy the sample config file and
|
||||
replace the default my.cnf symbolic link.
|
||||
|
@ -104,10 +116,12 @@ Restart MySQL
|
|||
|
||||
**Step 5:** Configure the ZoneMinder Database
|
||||
|
||||
This step should not be required on ZoneMinder 1.32.0.
|
||||
|
||||
::
|
||||
|
||||
mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql
|
||||
mysql -uroot -p -e "grant select,insert,update,delete,create,alter,index,lock tables on zm.* to 'zmuser'@localhost identified by 'zmpass';"
|
||||
mysql -uroot -p -e "grant lock tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on zm.* to 'zmuser'@localhost identified by 'zmpass';"
|
||||
|
||||
|
||||
**Step 6:** Set permissions
|
||||
|
@ -124,9 +138,16 @@ Set /etc/zm/zm.conf to root:www-data 740 and www-data access to content
|
|||
|
||||
::
|
||||
|
||||
a2enconf zoneminder
|
||||
a2enmod cgi
|
||||
a2enmod cgi
|
||||
a2enmod rewrite
|
||||
a2enconf zoneminder
|
||||
|
||||
You may also want to enable to following modules to improve caching performance
|
||||
|
||||
::
|
||||
|
||||
a2enmod expires
|
||||
a2enmod headers
|
||||
|
||||
**Step 8:** Enable and start Zoneminder
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ Maximum FPS
|
|||
Alarm Maximum FPS
|
||||
If you have specified a Maximum FPS it may be that you don’t want this limitation to apply when your monitor is recording motion or other event. This setting allows you to override the Maximum FPS value if this circumstance occurs. As with the Maximum FPS setting leaving this blank implies no limit so if you have set a maximum fps in the previous option then when an alarm occurs this limit would be ignored and ZoneMinder would capture as fast as possible for the duration of the alarm, returning to the limited value after the alarm has concluded. Equally you could set this to the same, or higher (or even lower) value than Maximum FPS for more precise control over the capture rate in the event of an alarm.
|
||||
|
||||
**IMPORTANT:** This field is subject to the same limitations as the Maxium FPS field. Ignoring these limitations will produce undesriable results.
|
||||
**IMPORTANT:** This field is subject to the same limitations as the Maximum FPS field. Ignoring these limitations will produce undesriable results.
|
||||
|
||||
Reference Image Blend %ge
|
||||
Each analysed image in ZoneMinder is a composite of previous images and is formed by applying the current image as a certain percentage of the previous reference image. Thus, if we entered the value of 10 here, each image’s part in the reference image will diminish by a factor of 0.9 each time round. So a typical reference image will be 10% the previous image, 9% the one before that and then 8.1%, 7.2%, 6.5% and so on of the rest of the way. An image will effectively vanish around 25 images later than when it was added. This blend value is what is specified here and if higher will make slower progressing events less detectable as the reference image would change more quickly. Similarly events will be deemed to be over much sooner as the reference image adapts to the new images more quickly. In signal processing terms the higher this value the steeper the event attack and decay of the signal. It depends on your particular requirements what the appropriate value would be for you but start with 10 here and adjust it (usually down) later if necessary.
|
||||
|
@ -150,7 +150,7 @@ Orientation
|
|||
WebSite
|
||||
^^^^^^^
|
||||
|
||||
This Source Type allows one to configure an arbitrary website as a non-reocrdable, fully interactive, monitor in ZoneMinder. Note that sites with self-signed certificates will not display until the end user first manually navigates to the site and accpets the unsigned certificate. Also note that some sites will set an X-Frame option in the header, which discourages their site from being displayed within a frame. ZoneMinder will detect this condition and present a warning in the log. When this occurs, the end user can choose to install a browser plugin or extension to workaround this issue.
|
||||
This Source Type allows one to configure an arbitrary website as a non-recordable, fully interactive, monitor in ZoneMinder. Note that sites with self-signed certificates will not display until the end user first manually navigates to the site and accpets the unsigned certificate. Also note that some sites will set an X-Frame option in the header, which discourages their site from being displayed within a frame. ZoneMinder will detect this condition and present a warning in the log. When this occurs, the end user can choose to install a browser plugin or extension to workaround this issue.
|
||||
|
||||
Website URL
|
||||
Enter the full http or https url to the desired website.
|
||||
|
@ -164,6 +164,29 @@ Height (pixels)
|
|||
Web Site Refresh
|
||||
If the website in question has static content, optionally enter a time period in seconds for ZoneMinder to refresh the content.
|
||||
|
||||
Storage Tab
|
||||
-----------
|
||||
|
||||
The storage section allows for each monitor to configure if and how video and audio are recorded.
|
||||
|
||||
Save JPEGs
|
||||
Records video in individual JPEG frames. Storing JPEG frames requires more storage space than h264 but it allows to view an event anytime while it is being recorded.
|
||||
|
||||
* Disabled – video is not recorded as JPEG frames. If this setting is selected, then "Video Writer" should be enabled otherwise there is no video recording at all.
|
||||
* Frames only – video is recorded in individual JPEG frames.
|
||||
* Analysis images only (if available) – video is recorded in invidual JPEG frames with an overlay of the motion detection analysis information. Note that this overlay remains permanently visible in the frames.
|
||||
* Frames + Analysis images (if available) – video is recorded twice, once as normal individual JPEG frames and once in invidual JPEG frames with analysis information overlaid.
|
||||
|
||||
Video Writer
|
||||
Records video in real video format. It provides much better compression results than saving JPEGs, thus longer video history can be stored.
|
||||
|
||||
* Disabled – video is not recorded in video format. If this setting is selected, then "Save JPEGs" should be enabled otherwise there is no video recording at all.
|
||||
* X264 Encode – the video or picture frames received from the camera are transcoded into h264 and stored as a video. This option is useful if the camera cannot natively stream h264.
|
||||
* H264 Camera Passthrough – this option assumes that the camera is already sending an h264 stream. Video will be recorded as is, without any post-processing in zoneminder. Video characteristics such as bitrate, encoding mode, etc. should be set directly in the camera.
|
||||
|
||||
Recording Audio
|
||||
Check the box labeled "Whether to store the audio stream when saving an event." in order to save audio (if available) when events are recorded.
|
||||
|
||||
Timestamp Tab
|
||||
-------------
|
||||
|
||||
|
@ -182,7 +205,8 @@ Warm-up Frames
|
|||
Pre/Post Event Image Buffer
|
||||
These options determine how many frames from before and after an event should be preserved with it. This allows you to view what happened immediately prior and subsequent to the event. A value of 10 for both of these will get you started but if you get a lot of short events and would prefer them to run together to form fewer longer ones then increase the Post Event buffer size. The pre-event buffer is a true buffer and should not really exceed half the ring buffer size. However the post-event buffer is just a count that is applied to captured frames and so can be managed more flexibly. You should also bear in mind the frame rate of the camera when choosing these values. For instance a network camera capturing at 1FPS will give you 10 seconds before and after each event if you chose 10 here. This may well be too much and pad out events more than necessary. However a fast video card may capture at 25FPS and you will want to ensure that this setting enables you to view a reasonable time frame pre and post event.
|
||||
Stream Replay Image Buffer
|
||||
This option ...
|
||||
The number of frames buffered to allow pausing and rewinding of the stream when live viewing a monitor. A value of 0 disables the feature.
|
||||
Frames are buffered to ZM_PATH_SWAP. If this path points to a physical drive, a lot of IO will be caused during live view / montage. If you experience high system load in those situations, either disable the feature or use a RAM drive for ZM_PATH_SWAP.
|
||||
Alarm Frame Count
|
||||
This option allows you to specify how many consecutive alarm frames must occur before an alarm event is generated. The usual, and default, value is 1 which implies that any alarm frame will cause or participate in an event. You can enter any value up to 16 here to eliminate bogus events caused perhaps by screen flickers or other transients. Values over 3 or 4 are unlikely to be useful however. Please note that if you have statistics recording enabled then currently statistics are not recorded for the first ‘Alarm Frame Count’-1 frames of an event. So if you set this value to 5 then the first 4 frames will be missing statistics whereas the more usual value of 1 will ensure that all alarm frames have statistics recorded.
|
||||
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
Introduction
|
||||
============
|
||||
|
||||
Welcome to ZoneMinder, the all-in-one Linux GPL'd security camera solution.
|
||||
Welcome to ZoneMinder, the all-in-one security camera solution for Linux with GPL License.
|
||||
|
||||
Most commercial "security systems" are designed as a monitoring system that also records. Recording quality can vary from bad to unusable, locating the relevant video can range from challenging to impractical, and exporting can often only be done with the manual present. ZoneMinder was designed primarily to record, and allow easy searches and exporting. Recordings are of the best possible quality, easy to filter and find, and simple to export using any system with a web browser. It also monitors.
|
||||
Commercial "security systems" are often designed as a monitoring system with little attention to recording quality. In such a system, locating and exporting relevant video can be challenging and often requires extensive human intervention. ZoneMinder was designed to provide the best possible record quality while allowing easy searching, filtering and exporting of security footage.
|
||||
|
||||
ZoneMinder is designed around a series of independent components that only function when necessary limiting any wasted resource and maximising the efficiency of your machine. A fairly ancient Pentium II PC should be able to track one camera per device at up to 25 frames per second with this dropping by half approximately for each additional camera on the same device. Additional cameras on other devices do not interact so can maintain this frame rate. Even monitoring several cameras still will not overload the CPU as frame processing is designed to synchronise with capture and not stall it.
|
||||
ZoneMinder is designed around a series of independent components that only function when necessary, limiting any wasted resource and maximising the efficiency of your machine. An outdated Pentium II PC can have multiple recording devices connected to it, and it is able to track one camera per device at up to 25 frames per second, which drops by approximately half for each additional camera on the same device. Additional cameras on devices that do not interact with other devices can maintain the 25 frame rate per second. Monitoring several cameras will not overload the CPU as frame processing is designed to synchronise with capture.
|
||||
|
||||
As well as being fast ZoneMinder is designed to be friendly and even more than that, actually useful. As well as the fast video interface core it also comes with a user friendly and comprehensive PHP based web interface allowing you to control and monitor your cameras from home, at work, on the road, or even a web enabled cell phone. It supports variable web capabilities based on available bandwidth. The web interface also allows you to view events that your cameras have captured and archive them or review them time and again, or delete the ones you no longer wish to keep. The web pages directly interact with the core daemons ensuring full co-operation at all times. ZoneMinder can even be installed as a system service ensuring it is right there if your computer has to reboot for any reason.
|
||||
A fast video interface core, a user-friendly and comprehensive PHP based web interface allows ZoneMinder to be efficient, friendly and most importantly useful. You can control and monitor your cameras from home, at work, on the road, or a web-enabled cell phone. It supports variable web capabilities based on available bandwidth. The web interface also allows you to view events that your cameras have captured, which can be archived, reviewed or deleted. The web application directly interacts with the core daemons ensuring full co-operation at all times. ZoneMinder can also be installed as a system service to reboot a system remotely.
|
||||
|
||||
The core of ZoneMinder is the capture and analysis of images and there is a highly configurable set of parameters that allow you to ensure that you can eliminate false positives whilst ensuring that anything you don't want to miss will be captured and saved. ZoneMinder allows you to define a set of 'zones' for each camera of varying sensitivity and functionality. This allows you to eliminate regions that you don't wish to track or define areas that will alarm if various thresholds are exceeded in conjunction with other zones.
|
||||
|
||||
ZoneMinder is free, but if you do find it useful then please feel free to visit http://www.zoneminder.com/donate.html and help to fund future improvements to ZoneMinder.
|
||||
The core of ZoneMinder is the capture and analysis of images and a highly configurable set of parameters that eliminate false positives whilst ensuring minimum loss of footage. For example, you can define a set of 'zones' for each camera of varying sensitivity and functionality. This eliminates zones that you don't wish to track or define areas that will alarm if various thresholds are exceeded in conjunction with other zones.
|
||||
|
||||
ZoneMinder is free under GPL License, but if you do find it useful, then please feel free to visit http://www.zoneminder.com/donate.html and help us fund our future improvements.
|
||||
|
|
|
@ -15,7 +15,7 @@ AUTH_HASH_SECRET - When ZoneMinder is running in hashed authenticated mode it is
|
|||
|
||||
AUTH_HASH_IPS - When ZoneMinder is running in hashed authenticated mode it can optionally include the requesting IP address in the resultant hash. This adds an extra level of security as only requests from that address may use that authentication key. However in some circumstances, such as access over mobile networks, the requesting address can change for each request which will cause most requests to fail. This option allows you to control whether IP addresses are included in the authentication hash on your system. If you experience intermitent problems with authentication, switching this option off may help.
|
||||
|
||||
AUTH_HASH_LOGINS - The normal process for logging into ZoneMinder is via the login screen with username and password. In some circumstances it may be desirable to allow access directly to one or more pages, for instance from a third party application. If this option is enabled then adding an 'auth' parameter to any request will include a shortcut login bypassing the login screen, if not already logged in. As authentication hashes are time and, optionally, IP limited this can allow short-term access to ZoneMinder screens from other web pages etc. In order to use this the calling application will hae to generate the authentication hash itself and ensure it is valid. If you use this option you should ensure that you have modified the ZM_AUTH_HASH_SECRET to somethign unique to your system.
|
||||
AUTH_HASH_LOGINS - The normal process for logging into ZoneMinder is via the login screen with username and password. In some circumstances it may be desirable to allow access directly to one or more pages, for instance from a third party application. If this option is enabled then adding an 'auth' parameter to any request will include a shortcut login bypassing the login screen, if not already logged in. As authentication hashes are time and, optionally, IP limited, this can allow short-term access to ZoneMinder screens from other web pages etc. In order to use this, the calling application will have to generate the authentication hash itself and ensure it is valid. If you use this option you should ensure that you have modified the ZM_AUTH_HASH_SECRET to something unique to your system.
|
||||
|
||||
OPT_FAST_DELETE - Normally an event created as the result of an alarm consists of entries in one or more database tables plus the various files associated with it. When deleting events in the browser it can take a long time to remove all of this if your are trying to do a lot of events at once. It is recommended that you set this option which means that the browser client only deletes the key entries in the events table, which means the events will no longer appear in the listing, and leaves the zmaudit daemon to clear up the rest later.
|
||||
|
||||
|
@ -38,6 +38,7 @@ OPT_CONTROL - ZoneMinder includes limited support for controllable cameras. A nu
|
|||
OPT_TRIGGERS - ZoneMinder can interact with external systems which prompt or cancel alarms. This is done via the zmtrigger.pl script. This option indicates whether you want to use these external triggers. Most people will say no here.
|
||||
|
||||
CHECK_FOR_UPDATES - From ZoneMinder version 1.17.0 onwards new versions are expected to be more frequent. To save checking manually for each new version ZoneMinder can check with the zoneminder.com website to determine the most recent release. These checks are infrequent, about once per week, and no personal or system information is transmitted other than your current version number. If you do not wish these checks to take place or your ZoneMinder system has no internet access you can switch these check off with this configuration variable
|
||||
|
||||
UPDATE_CHECK_PROXY - If you use a proxy to access the internet then ZoneMinder needs to know so it can access zoneminder.com to check for updates. If you do use a proxy enter the full proxy url here in the form of http://<proxy host>:<proxy port>/
|
||||
|
||||
SHM_KEY - ZoneMinder uses shared memory to speed up communication between modules. To identify the right area to use shared memory keys are used. This option controls what the base key is, each monitor will have it's Id or'ed with this to get the actual key used. You will not normally need to change this value unless it clashes with another instance of ZoneMinder on the same machine. Only the first four hex digits are used, the lower four will be masked out and ignored.
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
# Create files from the .in files
|
||||
configure_file(apache.conf.in "${CMAKE_CURRENT_BINARY_DIR}/apache.conf" @ONLY)
|
||||
configure_file(nginx.conf.in "${CMAKE_CURRENT_BINARY_DIR}/nginx.conf" @ONLY)
|
||||
configure_file(logrotate.conf.in "${CMAKE_CURRENT_BINARY_DIR}/logrotate.conf" @ONLY)
|
||||
configure_file(syslog.conf.in "${CMAKE_CURRENT_BINARY_DIR}/syslog.conf" @ONLY)
|
||||
configure_file(com.zoneminder.systemctl.policy.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" @ONLY)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# This configuration is only needed for compatibility with zmninja
|
||||
|
||||
# If not using VirtualHosts, copy or symlink this file into the Apache config folder
|
||||
# If using VirtualHosts, then this config must be placed inside the appropriate
|
||||
# <VirtualHost> directive.
|
||||
|
||||
# Make sure you have enabled/loaded header manipulation modules
|
||||
# For example, in Debian based distros the command is "sudo a2enmod headers"
|
||||
|
||||
# zmNinja header permissions. Tweak to your needs
|
||||
|
||||
Header always set Access-Control-Allow-Credentials true
|
||||
#zmNinja's WKWebView will set the origin header as localhost:8080
|
||||
Header always set Access-Control-Allow-Origin "http://localhost:8080"
|
||||
Header always set Access-Control-Request-Methods "Authorization"
|
||||
Header always set Access-Control-Methods "OPTIONS,GET,POST,DELETE,PUT"
|
||||
Header always set Access-Control-Allow-Headers "X-Requested-With, Content-Type, Authorization, Origin, Accept, client-security-token"
|
||||
Header always set Access-Control-Expose-Headers "Content-Security-Policy, Location"
|
||||
Header always set Access-Control-Max-Age "1000"
|
||||
RewriteCond %{REQUEST_METHOD} OPTIONS
|
||||
RewriteRule ^(.*)$ $1 [R=200,L]
|
|
@ -0,0 +1,61 @@
|
|||
#
|
||||
# PLEASE NOTE THAT THIS FILE IS INTENDED FOR GUIDANCE ONLY AND MAY NOT BE APPROPRIATE FOR YOUR DISTRIBUTION
|
||||
#
|
||||
|
||||
server {
|
||||
listen 443 ssl default_server;
|
||||
listen [::]:443 ssl default_server;
|
||||
server_name = localhost $hostname;
|
||||
|
||||
ssl_certificate "/etc/pki/tls/certs/localhost.crt";
|
||||
ssl_certificate_key "/etc/pki/tls/private/localhost.key";
|
||||
ssl_session_cache shared:SSL:1m;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_ciphers PROFILE=SYSTEM;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
# Auto redirect to server/zm when no url suffix was given
|
||||
location = / {
|
||||
return 301 zm;
|
||||
}
|
||||
|
||||
location /cgi-bin-zm {
|
||||
gzip off;
|
||||
alias "@ZM_CGIDIR@";
|
||||
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||
fastcgi_pass unix:/run/fcgiwrap.sock;
|
||||
}
|
||||
|
||||
location /zm/cache {
|
||||
alias "@ZM_CACHEDIR@";
|
||||
}
|
||||
|
||||
location /zm {
|
||||
gzip off;
|
||||
alias "@ZM_WEBDIR@";
|
||||
index index.php;
|
||||
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
expires epoch;
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_pass unix:/run/php-fpm/www.sock;
|
||||
}
|
||||
|
||||
location ~ \.(jpg|jpeg|gif|png|ico)$ {
|
||||
access_log off;
|
||||
expires 33d;
|
||||
}
|
||||
|
||||
location /zm/api/ {
|
||||
alias "@ZM_WEBDIR@";
|
||||
rewrite ^/zm/api(.+)$ /zm/api/app/webroot/index.php?p=$1 last;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -25,15 +25,13 @@ sub GetServices {
|
|||
soap_action => 'http://www.onvif.org/ver10/device/wsdl/GetServices',
|
||||
style => 'document',
|
||||
body => {
|
||||
|
||||
|
||||
'use' => 'literal',
|
||||
use => 'literal',
|
||||
namespace => 'http://schemas.xmlsoap.org/wsdl/soap/',
|
||||
encodingStyle => '',
|
||||
parts => [qw( ONVIF::Device::Elements::GetServices )],
|
||||
},
|
||||
header => {
|
||||
|
||||
|
||||
},
|
||||
headerfault => {
|
||||
|
||||
|
@ -50,9 +48,7 @@ sub GetServiceCapabilities {
|
|||
soap_action => 'http://www.onvif.org/ver10/device/wsdl/GetServiceCapabilities',
|
||||
style => 'document',
|
||||
body => {
|
||||
|
||||
|
||||
'use' => 'literal',
|
||||
use => 'literal',
|
||||
namespace => 'http://schemas.xmlsoap.org/wsdl/soap/',
|
||||
encodingStyle => '',
|
||||
parts => [qw( ONVIF::Device::Elements::GetServiceCapabilities )],
|
||||
|
@ -3059,7 +3055,7 @@ Returns a L<ONVIF::Device::Elements::SetClientCertificateModeResponse|ONVIF::Dev
|
|||
|
||||
=head3 GetRelayOutputs
|
||||
|
||||
This method has been depricated with version 2.0. Refer to the DeviceIO service.
|
||||
This method has been deprecated with version 2.0. Refer to the DeviceIO service.
|
||||
|
||||
Returns a L<ONVIF::Device::Elements::GetRelayOutputsResponse|ONVIF::Device::Elements::GetRelayOutputsResponse> object.
|
||||
|
||||
|
@ -3069,7 +3065,7 @@ Returns a L<ONVIF::Device::Elements::GetRelayOutputsResponse|ONVIF::Device::Elem
|
|||
|
||||
=head3 SetRelayOutputSettings
|
||||
|
||||
This method has been depricated with version 2.0. Refer to the DeviceIO service.
|
||||
This method has been deprecated with version 2.0. Refer to the DeviceIO service.
|
||||
|
||||
Returns a L<ONVIF::Device::Elements::SetRelayOutputSettingsResponse|ONVIF::Device::Elements::SetRelayOutputSettingsResponse> object.
|
||||
|
||||
|
@ -3085,7 +3081,7 @@ Returns a L<ONVIF::Device::Elements::SetRelayOutputSettingsResponse|ONVIF::Devic
|
|||
|
||||
=head3 SetRelayOutputState
|
||||
|
||||
This method has been depricated with version 2.0. Refer to the DeviceIO service.
|
||||
This method has been deprecated with version 2.0. Refer to the DeviceIO service.
|
||||
|
||||
Returns a L<ONVIF::Device::Elements::SetRelayOutputStateResponse|ONVIF::Device::Elements::SetRelayOutputStateResponse> object.
|
||||
|
||||
|
|
|
@ -830,7 +830,7 @@ Returns a L<ONVIF::PTZ::Elements::SetPresetResponse|ONVIF::PTZ::Elements::SetPre
|
|||
|
||||
=head3 RemovePreset
|
||||
|
||||
Operation to remove a PTZ preset for the Node in the selected profile. The operation is supported if the PresetPosition capability exists for teh Node in the selected profile.
|
||||
Operation to remove a PTZ preset for the Node in the selected profile. The operation is supported if the PresetPosition capability exists for the Node in the selected profile.
|
||||
|
||||
Returns a L<ONVIF::PTZ::Elements::RemovePresetResponse|ONVIF::PTZ::Elements::RemovePresetResponse> object.
|
||||
|
||||
|
|
|
@ -1,27 +1,3 @@
|
|||
# ==========================================================================
|
||||
#
|
||||
# ZoneMinder Base Module, $Date$, $Revision$
|
||||
# Copyright (C) 2001-2008 Philip Coombes
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# ==========================================================================
|
||||
#
|
||||
# This module contains the common definitions and functions used by the rest
|
||||
# of the ZoneMinder scripts
|
||||
#
|
||||
package ZoneMinder::Base;
|
||||
|
||||
use 5.006;
|
||||
|
@ -82,11 +58,18 @@ Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
|
|||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2001-2008 Philip Coombes
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This library is free software; you can redistribute it and/or modify
|
||||
it under the same terms as Perl itself, either Perl version 5.8.3 or,
|
||||
at your option, any later version of Perl 5 you may have available.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
=cut
|
||||
|
|
|
@ -149,7 +149,7 @@ BEGIN {
|
|||
foreach my $str ( <$CONFIG> ) {
|
||||
next if ( $str =~ /^\s*$/ );
|
||||
next if ( $str =~ /^\s*#/ );
|
||||
my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*(.*?)\s*$/;
|
||||
my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/;
|
||||
if ( ! $name ) {
|
||||
print( STDERR "Warning, bad line in $config_file: $str\n" );
|
||||
next;
|
||||
|
@ -309,6 +309,8 @@ saving configuration is a convenient way to ensure that the configuration
|
|||
held in the database corresponds with the most recent definitions and that
|
||||
all components are using the same set of configuration.
|
||||
|
||||
=back
|
||||
|
||||
=head2 EXPORT
|
||||
|
||||
None by default.
|
||||
|
|
|
@ -409,8 +409,8 @@ our @options = (
|
|||
that is used to get notifications for alarms detected by ZoneMinder
|
||||
in real time. zmNinja requires this server for push notifications to
|
||||
mobile phones. This option only enables the server if its already installed.
|
||||
Please visit https://github.com/pliablepixels/zmeventserver for installation
|
||||
instructions.
|
||||
Please visit the [zmeventserver project site](https://github.com/pliablepixels/zmeventserver)
|
||||
for installation instructions.
|
||||
`,
|
||||
type => $types{boolean},
|
||||
category => 'system',
|
||||
|
@ -442,7 +442,7 @@ our @options = (
|
|||
description => 'Your recaptcha site-key',
|
||||
help => q`You need to generate your keys from
|
||||
the Google reCaptcha website.
|
||||
Please refer to https://www.google.com/recaptcha/
|
||||
Please refer to the [recaptcha project site](https://www.google.com/recaptcha/)
|
||||
for more details.
|
||||
`,
|
||||
requires => [
|
||||
|
@ -457,7 +457,7 @@ our @options = (
|
|||
description => 'Your recaptcha secret-key',
|
||||
help => q`You need to generate your keys from
|
||||
the Google reCaptcha website.
|
||||
Please refer to https://www.google.com/recaptcha/
|
||||
Please refer to the [recaptcha project site](https://www.google.com/recaptcha/)
|
||||
for more details.
|
||||
`,
|
||||
requires => [
|
||||
|
@ -674,9 +674,9 @@ our @options = (
|
|||
ZoneMinder uses to view image streams on browsers such as
|
||||
Internet Explorer that don't natively support this format. If
|
||||
you use this browser it is highly recommended to install this
|
||||
from http://www.charliemouse.com/code/cambozola/ however if it
|
||||
is not installed still images at a lower refresh rate can still
|
||||
be viewed.
|
||||
from the [cambozola project site](http://www.charliemouse.com/code/cambozola/).
|
||||
However, if it is not installed still images at a lower refresh rate can
|
||||
still be viewed.
|
||||
`,
|
||||
type => $types{boolean},
|
||||
category => 'images',
|
||||
|
@ -690,9 +690,9 @@ our @options = (
|
|||
ZoneMinder uses to view image streams on browsers such as
|
||||
Internet Explorer that don't natively support this format. If
|
||||
you use this browser it is highly recommended to install this
|
||||
from http://www.charliemouse.com/code/cambozola/ however if it
|
||||
is not installed still images at a lower refresh rate can still
|
||||
be viewed. Leave this as 'cambozola.jar' if cambozola is
|
||||
from the [cambozola project site](http://www.charliemouse.com/code/cambozola/).
|
||||
However if it is not installed still images at a lower refresh rate can
|
||||
still be viewed. Leave this as 'cambozola.jar' if cambozola is
|
||||
installed in the same directory as the ZoneMinder web client
|
||||
files.
|
||||
`,
|
||||
|
@ -2711,7 +2711,7 @@ our @options = (
|
|||
},
|
||||
{
|
||||
name => 'ZM_TELEMETRY_DATA',
|
||||
default => 'yes',
|
||||
default => 'no',
|
||||
description => 'Send usage information to ZoneMinder',
|
||||
help => q`
|
||||
Enable collection of usage information of the local system and send
|
||||
|
@ -2721,7 +2721,8 @@ our @options = (
|
|||
This is being done for the sole purpoase of creating a better
|
||||
product for our target audience. This script is intended to be
|
||||
completely transparent to the end user, and can be disabled from
|
||||
the web console under Options.
|
||||
the web console under Options. For more details on what information
|
||||
we collect, please refer to our [privacy](?view=privacy) statement.
|
||||
`,
|
||||
type => $types{boolean},
|
||||
category => 'system',
|
||||
|
@ -2954,7 +2955,7 @@ our @options = (
|
|||
When creating a Web Site monitor, if the target web site has
|
||||
X-Frame-Options set to sameorigin in the header, the site will
|
||||
not display in ZoneMinder. This is a design feature in most modern
|
||||
browsers. When this condiction has occured, ZoneMinder will write a
|
||||
browsers. When this condition occurs, ZoneMinder will write a
|
||||
warning to the log file. To get around this, one can install a browser
|
||||
plugin or extension to ignore X-Frame headers, and then the page will
|
||||
display properly. Once the plugin or extenstion has ben installed,
|
||||
|
@ -2963,6 +2964,37 @@ our @options = (
|
|||
type => $types{boolean},
|
||||
category => 'web',
|
||||
},
|
||||
{
|
||||
name => 'ZM_WEB_FILTER_SOURCE',
|
||||
default => 'Hostname',
|
||||
description => 'How to filter information in the source column.',
|
||||
help => q`
|
||||
This option only affects monitors with a source type of Ffmpeg,
|
||||
Libvlc, or WebSite. This setting controls what information is
|
||||
displayed in the Source column on the console. Selecting 'None'
|
||||
will not filter anything. The entire source string will be
|
||||
displayed, which may contain sensitive information. Selecting
|
||||
'NoCredentials' will strip out usernames and passwords from the
|
||||
string. If there are any port numbers in the string and they are
|
||||
common (80, 554, etc) then those will be removed as well.
|
||||
Selecting 'Hostname' will filter out all information except for
|
||||
the hostname or ip address. When in doubt, stay with the default
|
||||
'Hostname'. This feature uses the php function 'url_parts' to
|
||||
identify the various pieces of the url. If the url in question
|
||||
is unusual or not standard in some way, then filtering may not
|
||||
produce the desired results.
|
||||
`,
|
||||
type => {
|
||||
db_type =>'string',
|
||||
hint =>'None|Hostname|NoCredentials',
|
||||
pattern =>qr|^([NH])|i,
|
||||
format =>q( ($1 =~ /^Non/)
|
||||
? 'None'
|
||||
: ($1 =~ /^H/ ? 'Hostname' : 'NoCredentials' )
|
||||
)
|
||||
},
|
||||
category => 'web',
|
||||
},
|
||||
{
|
||||
name => 'ZM_WEB_H_REFRESH_MAIN',
|
||||
default => '60',
|
||||
|
@ -3851,6 +3883,15 @@ our @options = (
|
|||
readonly => 1,
|
||||
category => 'dynamic',
|
||||
},
|
||||
{
|
||||
name => 'ZM_SHOW_PRIVACY',
|
||||
default => 'yes',
|
||||
description => 'Present the privacy statment',
|
||||
help => '',
|
||||
type => $types{boolean},
|
||||
readonly => 1,
|
||||
category => 'dynamic',
|
||||
},
|
||||
{
|
||||
name => 'ZM_SSMTP_MAIL',
|
||||
default => 'no',
|
||||
|
@ -3867,7 +3908,7 @@ our @options = (
|
|||
SSMTP is a lightweight and efficient method to send email.
|
||||
The SSMTP application is not installed by default.
|
||||
NEW_MAIL_MODULES must also be enabled.
|
||||
Please visit: http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder
|
||||
Please visit the ZoneMinder [SSMTP Wiki page](http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder)
|
||||
for setup and configuration help.
|
||||
`,
|
||||
type => $types{boolean},
|
||||
|
|
|
@ -0,0 +1,362 @@
|
|||
package ZoneMinder::Control::Dahua;
|
||||
|
||||
use 5.8.0;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
require ZoneMinder::Base;
|
||||
require ZoneMinder::Control;
|
||||
|
||||
our @ISA = qw(ZoneMinder::Control);
|
||||
|
||||
our $REALM = '';
|
||||
our $USERNAME = '';
|
||||
our $PASSWORD = '';
|
||||
our $ADDRESS = '';
|
||||
our $PROTOCOL = 'http://';
|
||||
|
||||
use Time::HiRes qw(usleep);
|
||||
|
||||
use ZoneMinder::Logger qw(:all);
|
||||
use ZoneMinder::Config qw(:all);
|
||||
use ZoneMinder::Database qw(zmDbConnect);
|
||||
|
||||
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();
|
||||
|
||||
# The Dahua camera firmware API supports the concept of having multiple
|
||||
# channels on a single IP controller.
|
||||
# As most cameras only have a single channel, and there is no similar
|
||||
# information model in Zoneminder, I'm hardcoding the first and default
|
||||
# channel "0", here.
|
||||
$self->{dahua_channel_number} = "0";
|
||||
|
||||
if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?<PROTOCOL>https?:\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>.*)$/ ) ) {
|
||||
$PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL};
|
||||
$USERNAME = $+{USERNAME} if $+{USERNAME};
|
||||
$PASSWORD = $+{PASSWORD} if $+{PASSWORD};
|
||||
$ADDRESS = $+{ADDRESS} if $+{ADDRESS};
|
||||
} else {
|
||||
Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress});
|
||||
$ADDRESS = $self->{Monitor}->{ControlAddress};
|
||||
}
|
||||
if ( !($ADDRESS =~ /:/) ) {
|
||||
Error('You generally need to also specify the port. I will append :80');
|
||||
$ADDRESS .= ':80';
|
||||
}
|
||||
|
||||
use LWP::UserAgent;
|
||||
$self->{ua} = LWP::UserAgent->new;
|
||||
$self->{ua}->agent("ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION);
|
||||
$self->{state} = 'closed';
|
||||
# credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string)
|
||||
Debug("sendCmd credentials control address:'".$ADDRESS
|
||||
."' realm:'" . $REALM
|
||||
. "' username:'" . $USERNAME
|
||||
. "' password:'".$PASSWORD
|
||||
."'"
|
||||
);
|
||||
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
|
||||
|
||||
# Detect REALM
|
||||
my $get_config_url = $PROTOCOL . $ADDRESS . "/cgi-bin/configManager.cgi?action=getConfig&name=Ptz";
|
||||
my $req = HTTP::Request->new(GET=>$get_config_url);
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ($res->is_success) {
|
||||
$self->{state} = 'open';
|
||||
return;
|
||||
}
|
||||
|
||||
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 =~ /\w+="([^"]+)"/i) {
|
||||
if ($REALM ne $1) {
|
||||
$REALM = $1;
|
||||
Debug("Changing REALM to '" . $REALM . "'");
|
||||
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
|
||||
my $req = HTTP::Request->new(GET=>$get_config_url);
|
||||
$res = $self->{ua}->request($req);
|
||||
if ($res->is_success()) {
|
||||
$self->{state} = 'open';
|
||||
return;
|
||||
}
|
||||
Debug('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 {
|
||||
Error('No WWW-Authenticate Header');
|
||||
} # end if headers
|
||||
} # end if $res->status_line() eq '401 Unauthorized'
|
||||
}
|
||||
|
||||
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 sendGetRequest {
|
||||
my $self = shift;
|
||||
my $url_path = shift;
|
||||
|
||||
my $result = undef;
|
||||
|
||||
my $url = $PROTOCOL . $ADDRESS . $url_path;
|
||||
my $req = HTTP::Request->new(GET=>$url);
|
||||
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ($res->is_success) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
if ($res->status_line() eq '401 Unauthorized') {
|
||||
Debug("Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD);
|
||||
Debug("Content was " . $res->content() );
|
||||
my $res = $self->{ua}->request($req);
|
||||
if ($res->is_success) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
Error("Content was " . $res->content() );
|
||||
}
|
||||
}
|
||||
if ( ! $result ) {
|
||||
Error("Error check failed: '".$res->status_line());
|
||||
}
|
||||
}
|
||||
return($result);
|
||||
}
|
||||
|
||||
sub sendPtzCommand
|
||||
{
|
||||
my $self = shift;
|
||||
my $action = shift;
|
||||
my $command_code = shift;
|
||||
my $arg1 = shift;
|
||||
my $arg2 = shift;
|
||||
my $arg3 = shift;
|
||||
|
||||
my $channel = $self->{dahua_channel_number};
|
||||
|
||||
my $url_path = "/cgi-bin/ptz.cgi?";
|
||||
$url_path .= "action=" . $action . "&";
|
||||
$url_path .= "channel=" . $channel . "&";
|
||||
$url_path .= "code=" . $command_code . "&";
|
||||
$url_path .= "arg1=" . $arg1 . "&";
|
||||
$url_path .= "arg2=" . $arg2 . "&";
|
||||
$url_path .= "arg3=" . $arg3;
|
||||
$self->sendGetRequest($url_path);
|
||||
}
|
||||
sub sendMomentaryPtzCommand
|
||||
{
|
||||
my $self = shift;
|
||||
my $command_code = shift;
|
||||
my $arg1 = shift;
|
||||
my $arg2 = shift;
|
||||
my $arg3 = shift;
|
||||
my $duration_ms = shift;
|
||||
|
||||
$self->sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3);
|
||||
my $duration_ns = $duration_ms * 1000;
|
||||
usleep($duration_ns);
|
||||
$self->sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3);
|
||||
}
|
||||
|
||||
sub moveRelUpLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Up Left");
|
||||
$self->sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelUp
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Up");
|
||||
$self->sendMomentaryPtzCommand("Up", 0, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelUpRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Up Right");
|
||||
$self->sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Left");
|
||||
$self->sendMomentaryPtzCommand("Left", 0, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Right");
|
||||
$self->sendMomentaryPtzCommand("Right", 0, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelDownLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Down Left");
|
||||
$self->sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelDown
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Down");
|
||||
$self->sendMomentaryPtzCommand("Down", 0, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelDownRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Down Right");
|
||||
$self->sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub zoomRelTele
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Zoom Relative Tele");
|
||||
$self->sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500);
|
||||
}
|
||||
|
||||
sub zoomRelWide
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Zoom Relative Wide");
|
||||
$self->sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500);
|
||||
}
|
||||
|
||||
|
||||
sub presetClear
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset_id = $self->getParam($params, 'preset');
|
||||
$self->sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0);
|
||||
}
|
||||
|
||||
|
||||
sub presetSet
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
my $preset_id = $self->getParam($params, 'preset');
|
||||
|
||||
my $dbh = zmDbConnect(1);
|
||||
my $sql = 'SELECT * FROM ControlPresets WHERE MonitorId = ? AND Preset = ?';
|
||||
my $sth = $dbh->prepare($sql)
|
||||
or Fatal("Can't prepare sql '$sql': " . $dbh->errstr());
|
||||
my $res = $sth->execute($self->{Monitor}->{Id}, $preset_id)
|
||||
or Fatal("Can't execute sql '$sql': " . $sth->errstr());
|
||||
my $control_preset_row = $sth->fetchrow_hashref();
|
||||
my $new_label_name = $control_preset_row->{'Label'};
|
||||
|
||||
$self->sendPtzCommand("start", "SetPreset", 0, $preset_id, 0);
|
||||
$self->sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0);
|
||||
}
|
||||
|
||||
sub presetGoto
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset_id = $self->getParam($params, 'preset');
|
||||
|
||||
$self->sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0);
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
ZoneMinder::Control::Dahua - Perl module for Dahua cameras
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use ZoneMinder::Control::Dahua;
|
||||
place this in /usr/share/perl5/ZoneMinder/Control
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is an implementation of the Dahua IP camera HTTP control API.
|
||||
|
||||
=head2 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2018 ZoneMinder LLC
|
||||
|
||||
This library 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 library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
=cut
|
|
@ -0,0 +1,475 @@
|
|||
# ==========================================================================
|
||||
#
|
||||
# ZoneMinder Dericam P2 Control Protocol Module
|
||||
# Copyright (C) Roman Dissertori
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# ==========================================================================
|
||||
#
|
||||
# This module contains the implementation of the Dericam P2 device control protocol
|
||||
#
|
||||
package ZoneMinder::Control::DericamP2;
|
||||
|
||||
use 5.006;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
require ZoneMinder::Control;
|
||||
|
||||
our @ISA = qw(ZoneMinder::Control);
|
||||
|
||||
our %CamParams = ();
|
||||
|
||||
# ==========================================================================
|
||||
#
|
||||
# Dericam P2 Control Protocol
|
||||
#
|
||||
# On ControlAddress use the format :
|
||||
# USERNAME:PASSWORD@ADDRESS:PORT
|
||||
# eg : admin:@10.1.2.1:80
|
||||
# zoneminder:zonepass@10.0.100.1:40000
|
||||
#
|
||||
# ==========================================================================
|
||||
|
||||
use ZoneMinder::Logger qw(:all);
|
||||
use ZoneMinder::Config qw(:all);
|
||||
|
||||
use Time::HiRes qw( usleep );
|
||||
|
||||
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 printMsg
|
||||
{
|
||||
my $self = shift;
|
||||
my $msg = shift;
|
||||
my $msg_len = length($msg);
|
||||
|
||||
Debug( $msg."[".$msg_len."]" );
|
||||
}
|
||||
|
||||
sub sendCmd
|
||||
{
|
||||
my $self = shift;
|
||||
my $cmd = shift;
|
||||
my $result = undef;
|
||||
printMsg( $cmd, "Tx" );
|
||||
|
||||
my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" );
|
||||
Info( "http://".$self->{Monitor}->{ControlAddress}."/$cmd".$self->{Monitor}->{ControlDevice} );
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ( $res->is_success )
|
||||
{
|
||||
$result = !undef;
|
||||
}
|
||||
else
|
||||
{
|
||||
Error( "Error check failed:'".$res->status_line()."'" );
|
||||
}
|
||||
|
||||
return( $result );
|
||||
}
|
||||
|
||||
sub getCamParams
|
||||
{
|
||||
my $self = shift;
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=getimageattr";
|
||||
|
||||
my $req = $self->sendCmd( $cmd );
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ( $res->is_success )
|
||||
{
|
||||
# Parse results setting values in %FCParams
|
||||
my $content = $res->decoded_content;
|
||||
|
||||
while ($content =~ s/var\s+([^=]+)=([^;]+);//ms) {
|
||||
$CamParams{$1} = $2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Error( "Error check failed:'".$res->status_line()."'" );
|
||||
}
|
||||
}
|
||||
|
||||
#autoStop
|
||||
#This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab
|
||||
sub autoStop
|
||||
{
|
||||
my $self = shift;
|
||||
my $stop_command = shift;
|
||||
my $autostop = shift;
|
||||
if( $stop_command && $autostop)
|
||||
{
|
||||
Debug( "Auto Stop" );
|
||||
usleep( $autostop );
|
||||
my $cmd = "decoder_control.cgi?command=".$stop_command;
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Reset the Camera
|
||||
sub reset
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Camera Reset" );
|
||||
# Move to default position
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=home";
|
||||
$self->sendCmd( $cmd );
|
||||
|
||||
# Reset all other values to default
|
||||
$cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-image_type=1&-default=on";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
# Reboot Camera (on Sleep button)
|
||||
sub sleep
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Camera Reboot" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=sysreboot";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
# Stop the Camera
|
||||
sub moveStop
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Camera Stop" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=stop";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
#Up Arrow
|
||||
sub moveConUp
|
||||
{
|
||||
my $self = shift;
|
||||
my $stop_command = "1";
|
||||
Debug( "Move Up" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=up&-speed=45";
|
||||
$self->sendCmd( $cmd );
|
||||
#$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} );
|
||||
}
|
||||
|
||||
#Down Arrow
|
||||
sub moveConDown
|
||||
{
|
||||
my $self = shift;
|
||||
my $stop_command = "3";
|
||||
Debug( "Move Down" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=down&-speed=45";
|
||||
$self->sendCmd( $cmd );
|
||||
#$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} );
|
||||
}
|
||||
|
||||
#Left Arrow
|
||||
sub moveConLeft
|
||||
{
|
||||
my $self = shift;
|
||||
my $stop_command = "5";
|
||||
Debug( "Move Left" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=left&-speed=45";
|
||||
$self->sendCmd( $cmd );
|
||||
#$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} );
|
||||
}
|
||||
|
||||
#Right Arrow
|
||||
sub moveConRight
|
||||
{
|
||||
my $self = shift;
|
||||
my $stop_command = "7";
|
||||
Debug( "Move Right" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=right&-speed=45";
|
||||
$self->sendCmd( $cmd );
|
||||
#$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} );
|
||||
}
|
||||
|
||||
#Zoom In
|
||||
sub zoomConTele
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Zoom Tele" );
|
||||
my $cmd = "decoder_control.cgi?command=18";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
#Zoom Out
|
||||
sub zoomConWide
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Zoom Wide" );
|
||||
my $cmd = "decoder_control.cgi?command=16";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
#Diagonally Up Right Arrow
|
||||
#This camera does not have builtin diagonal commands so we emulate them
|
||||
sub moveConUpRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Diagonally Up Right" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=upright&-speed=45";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
#Diagonally Down Right Arrow
|
||||
#This camera does not have builtin diagonal commands so we emulate them
|
||||
sub moveConDownRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Diagonally Down Right" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=downright&-speed=45";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
#Diagonally Up Left Arrow
|
||||
#This camera does not have builtin diagonal commands so we emulate them
|
||||
sub moveConUpLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Diagonally Up Left" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=upleft&-speed=45";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
#Diagonally Down Left Arrow
|
||||
#This camera does not have builtin diagonal commands so we emulate them
|
||||
sub moveConDownLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Diagonally Down Left" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=downnleft&-speed=45";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
#Set Camera Preset
|
||||
#Presets must be translated into values internal to the camera
|
||||
#Those values are: 30,32,34,36,38,40,42,44 for presets 1-8 respectively
|
||||
sub presetSet
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam( $params, 'preset' );
|
||||
Debug( "Set Preset $preset" );
|
||||
|
||||
if (( $preset >= 1 ) && ( $preset <= 8 )) {
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=preset&-act=set&-number=".(($preset*2) + 28);
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
}
|
||||
|
||||
#Recall Camera Preset
|
||||
#Presets must be translated into values internal to the camera
|
||||
#Those values are: 31,33,35,37,39,41,43,45 for presets 1-8 respectively
|
||||
sub presetGoto
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam( $params, 'preset' );
|
||||
Debug( "Goto Preset $preset" );
|
||||
|
||||
if (( $preset >= 1 ) && ( $preset <= 8 )) {
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=preset&-act=goto&-number=".(($preset*2) + 29);
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
if ( $preset == 9 ) {
|
||||
$self->horizontalPatrol();
|
||||
}
|
||||
|
||||
if ( $preset == 10 ) {
|
||||
$self->verticalPatrol();
|
||||
}
|
||||
}
|
||||
|
||||
#Horizontal Patrol
|
||||
sub horizontalPatrol
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Horizontal Patrol" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=hscan";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
#Vertical Patrol
|
||||
sub verticalPatrol
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Vertical Patrol" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=vscan";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
# Increase Brightness
|
||||
sub irisAbsOpen
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'brightness'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
|
||||
$CamParams{'brightness'} += $step;
|
||||
$CamParams{'brightness'} = 100 if ($CamParams{'brightness'} > 100);
|
||||
Debug( "Increase Brightness" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-brightness=".$CamParams{'brightness'};
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
# Decrease Brightness
|
||||
sub irisAbsClose
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'brightness'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
|
||||
$CamParams{'brightness'} -= $step;
|
||||
$CamParams{'brightness'} = 0 if ($CamParams{'brightness'} < 0);
|
||||
Debug( "Decrease Brightness" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-brightness=".$CamParams{'brightness'};
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
# Increase Contrast
|
||||
sub whiteAbsIn
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'contrast'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
|
||||
$CamParams{'contrast'} += $step;
|
||||
$CamParams{'contrast'} = 100 if ($CamParams{'contrast'} > 100);
|
||||
Debug( "Increase Contrast" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-contrast=".$CamParams{'contrast'};
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
# Decrease Contrast
|
||||
sub whiteAbsOut
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'contrast'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
|
||||
$CamParams{'contrast'} -= $step;
|
||||
$CamParams{'contrast'} = 0 if ($CamParams{'contrast'} < 0);
|
||||
Debug( "Decrease Contrast" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-contrast=".$CamParams{'contrast'};
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
#TODO Saturation cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=44 [0-255]
|
||||
sub satIncrease
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'saturation'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
|
||||
$CamParams{'saturation'} += $step;
|
||||
$CamParams{'saturation'} = 255 if ($CamParams{'saturation'} > 255);
|
||||
Debug( "Increase Saturation" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=".$CamParams{'saturation'};
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
sub satDecrease
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'saturation'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
|
||||
$CamParams{'saturation'} -= $step;
|
||||
$CamParams{'saturation'} = 0 if ($CamParams{'saturation'} < 0);
|
||||
Debug( "Decrease Saturation" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=".$CamParams{'saturation'};
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
#TODO Sharpness cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=37 [0-100]
|
||||
sub sharpIncrease
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'sharpness'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
|
||||
$CamParams{'sharpness'} += $step;
|
||||
$CamParams{'sharpness'} = 100 if ($CamParams{'sharpness'} > 100);
|
||||
Debug( "Increase Sharpness" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=".$CamParams{'sharpness'};
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
sub sharpDecrease
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'sharpness'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
|
||||
$CamParams{'sharpness'} -= $step;
|
||||
$CamParams{'sharpness'} = 0 if ($CamParams{'sharpness'} < 0);
|
||||
Debug( "Decrease Sharpness" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=".$CamParams{'sharpness'};
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
#TODO Hue cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=37 [0-100]
|
||||
sub hueIncrease
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'hue'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
|
||||
$CamParams{'hue'} += $step;
|
||||
$CamParams{'hue'} = 100 if ($CamParams{'hue'} > 100);
|
||||
Debug( "Increase Hue" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=".$CamParams{'hue'};
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
sub hueDecrease
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'hue'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
|
||||
$CamParams{'hue'} -= $step;
|
||||
$CamParams{'hue'} = 0 if ($CamParams{'hue'} < 0);
|
||||
Debug( "Decrease Hue" );
|
||||
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=".$CamParams{'hue'};
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
1;
|
|
@ -0,0 +1,326 @@
|
|||
# ==========================================================================
|
||||
#
|
||||
# ZoneMinder iPhone Control Protocol Module, $Date: 2018-07-15 00:20:00 +0000 $, $Revision: 0003 $
|
||||
# Copyright (C) 2001-2008 Philip Coombes
|
||||
#
|
||||
# Modified for iPhone ipcamera for IOS BY PETER ZARGLIS n 2018-06-09 13:45:00
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# ==========================================================================
|
||||
#
|
||||
# This module contains the implementation of the iPhone ipcamera for IOS
|
||||
# control protocol.
|
||||
#
|
||||
# ==========================================================================
|
||||
package ZoneMinder::Control::IPCAMIOS;
|
||||
|
||||
use 5.006;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
require ZoneMinder::Base;
|
||||
require ZoneMinder::Control;
|
||||
|
||||
our @ISA = qw(ZoneMinder::Control);
|
||||
|
||||
our $VERSION = $ZoneMinder::Base::VERSION;
|
||||
|
||||
# ==========================================================================
|
||||
#
|
||||
# iPhone ipcamera for IOS Protocol
|
||||
#
|
||||
# ==========================================================================
|
||||
|
||||
use ZoneMinder::Logger qw(:all);
|
||||
use ZoneMinder::Config qw(:all);
|
||||
use Time::HiRes qw( usleep );
|
||||
|
||||
my $loopfactor=100000;
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = shift;
|
||||
my $id = shift;
|
||||
my $self = ZoneMinder::Control->new( $id );
|
||||
my $logindetails = "";
|
||||
bless( $self, $class );
|
||||
srand( time() );
|
||||
return $self;
|
||||
}
|
||||
|
||||
our $AUTOLOAD;
|
||||
|
||||
sub AUTOLOAD
|
||||
{
|
||||
my $self = shift;
|
||||
my $class = ref($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" );
|
||||
|
||||
|
||||
$self->{state} = 'open';
|
||||
}
|
||||
|
||||
sub close
|
||||
{
|
||||
my $self = shift;
|
||||
$self->{state} = 'closed';
|
||||
}
|
||||
|
||||
sub sendCmd
|
||||
{
|
||||
my $self = shift;
|
||||
my $cmd = shift;
|
||||
my $result = undef;
|
||||
my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" );
|
||||
my $res = $self->{ua}->request($req);
|
||||
if ( $res->is_success )
|
||||
{
|
||||
$result = $res->decoded_content;
|
||||
}
|
||||
else
|
||||
{
|
||||
Error( "Error check failed: '".$res->status_line()."'" );
|
||||
}
|
||||
return( $result );
|
||||
}
|
||||
sub getDisplayAttr
|
||||
{
|
||||
my $self = shift;
|
||||
my $param = shift;
|
||||
my $cmdget = "parameters?";
|
||||
my $resp = $self->sendCmd( $cmdget );
|
||||
my @fields = split(',',$resp);
|
||||
my $response=$fields[$param];
|
||||
my @buffer=split(':',$response);
|
||||
my $response2=$buffer[1];
|
||||
return ($response2);
|
||||
}
|
||||
|
||||
sub sleep
|
||||
{
|
||||
|
||||
}
|
||||
# Flip image vertically -> Horz -> off
|
||||
sub moveConUp
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
Debug( "Flip Image" );
|
||||
my $dvalue=$self->getDisplayAttr(3);
|
||||
if ( $dvalue == 2 )
|
||||
{
|
||||
$dvalue=0;
|
||||
my $cmd = "parameters?flip=$dvalue";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
else
|
||||
{
|
||||
$dvalue=$dvalue+1;
|
||||
my $cmd = "parameters?flip=$dvalue";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
}
|
||||
# Change camera (front facing or back)
|
||||
sub moveConDown
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
Debug( "Change Camera" );
|
||||
my $dvalue=$self->getDisplayAttr(7);
|
||||
if ( $dvalue == 0 )
|
||||
{
|
||||
my $cmd = "parameters?camera=1";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
else
|
||||
{
|
||||
my $cmd = "parameters?camera=0";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
}
|
||||
# Picture Orientation Clockwise
|
||||
sub moveConRight
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
Debug( "Orientation" );
|
||||
my $dvalue=$self->getDisplayAttr(10);
|
||||
if ( $dvalue == 1 )
|
||||
{
|
||||
$dvalue=4;
|
||||
my $cmd = "parameters?rotation=$dvalue";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
else
|
||||
{
|
||||
$dvalue=$dvalue-1;
|
||||
my $cmd = "parameters?rotation=$dvalue";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
}
|
||||
# Picture Orientation Anti-Clockwise
|
||||
sub moveConLeft
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
Debug( "Orientation" );
|
||||
my $dvalue=$self->getDisplayAttr(10);
|
||||
if ( $dvalue == 4 )
|
||||
{
|
||||
$dvalue=1;
|
||||
my $cmd = "parameters?rotation=$dvalue";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
else
|
||||
{
|
||||
$dvalue=$dvalue+1;
|
||||
my $cmd = "parameters?rotation=$dvalue";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
}
|
||||
|
||||
# presetHome is used to turn off Torch, unlock Focus, unlock Exposure, unlock white-balance, rotation, image flipping
|
||||
# Just basically reset all the little variables and set it to medium quality
|
||||
# Rotation = 0 means it will autoselect using built in detection
|
||||
sub presetHome
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Home Preset" );
|
||||
my $cmd = "parameters?torch=0&focus=0&wb=0&exposure=0&rotation=0&flip=0&quality=0.5";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
sub focusAbsNear
|
||||
# Focus Un/Lock
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
Debug( "Focus Un/Lock" );
|
||||
my $dvalue=$self->getDisplayAttr(2);
|
||||
if ( $dvalue == 0 )
|
||||
{
|
||||
my $cmd = "parameters?focus=1";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
else
|
||||
{
|
||||
my $cmd = "parameters?focus=0";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
}
|
||||
|
||||
sub focusAbsFar
|
||||
# Exposure Un/Lock
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
Debug( "Exposure Un/Lock" );
|
||||
my $dvalue=$self->getDisplayAttr(11);
|
||||
if ( $dvalue == 0 )
|
||||
{
|
||||
my $cmd = "parameters?exposure=1";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
else
|
||||
{
|
||||
my $cmd = "parameters?exposure=0";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
}
|
||||
# Increase stream Quality (from 0 to 10)
|
||||
sub irisAbsOpen
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
Debug( "Quality" );
|
||||
my $dvalue=$self->getDisplayAttr(8);
|
||||
if ( $dvalue < 1 )
|
||||
{
|
||||
$dvalue=$dvalue+0.1;
|
||||
my $cmd = "parameters?quality=$dvalue";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
}
|
||||
|
||||
# Decrease stream Quality (from 10 to 0)
|
||||
sub irisAbsClose
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
Debug( "Quality" );
|
||||
my $dvalue=$self->getDisplayAttr(8);
|
||||
if ( $dvalue > 0 )
|
||||
{
|
||||
$dvalue=$dvalue-0.1;
|
||||
my $cmd = "parameters?quality=$dvalue";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
}
|
||||
# White Balance Un/Lock
|
||||
sub whiteAbsIn
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
Debug( "White Balance" );
|
||||
my $dvalue=$self->getDisplayAttr(9);
|
||||
if ( $dvalue == 0 )
|
||||
{
|
||||
my $cmd = "parameters?wb=1";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
else
|
||||
{
|
||||
my $cmd = "parameters?wb=0";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
}
|
||||
|
||||
# Torch control on/off
|
||||
sub whiteAbsOut
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
Debug( "Torch" );
|
||||
my $dvalue=$self->getDisplayAttr(5);
|
||||
if ( $dvalue == 0 )
|
||||
{
|
||||
my $cmd = "parameters?torch=1";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
else
|
||||
{
|
||||
my $cmd = "parameters?torch=0";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
|
@ -74,377 +74,351 @@ use ZoneMinder::Config qw(:all);
|
|||
|
||||
use Time::HiRes qw( usleep );
|
||||
|
||||
sub open
|
||||
{
|
||||
my $self = shift;
|
||||
sub open {
|
||||
my $self = shift;
|
||||
|
||||
$self->loadMonitor();
|
||||
$self->loadMonitor();
|
||||
|
||||
use LWP::UserAgent;
|
||||
$self->{ua} = LWP::UserAgent->new;
|
||||
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION );
|
||||
use LWP::UserAgent;
|
||||
$self->{ua} = LWP::UserAgent->new;
|
||||
$self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION);
|
||||
|
||||
$self->{state} = 'open';
|
||||
$self->{state} = 'open';
|
||||
}
|
||||
|
||||
sub printMsg
|
||||
{
|
||||
my $self = shift;
|
||||
my $msg = shift;
|
||||
my $msg_len = length($msg);
|
||||
sub printMsg {
|
||||
my $self = shift;
|
||||
my $msg = shift;
|
||||
my $msg_len = length($msg);
|
||||
|
||||
Debug( $msg."[".$msg_len."]" );
|
||||
Debug($msg.'['.$msg_len.']');
|
||||
}
|
||||
|
||||
sub sendCmd
|
||||
{
|
||||
my $self = shift;
|
||||
my $cmd = shift;
|
||||
my $msg = shift;
|
||||
my $content_type = shift;
|
||||
my $result = undef;
|
||||
sub sendCmd {
|
||||
my $self = shift;
|
||||
my $cmd = shift;
|
||||
my $msg = shift;
|
||||
my $content_type = shift;
|
||||
my $result = undef;
|
||||
|
||||
printMsg( $cmd, "Tx" );
|
||||
printMsg($cmd, 'Tx');
|
||||
|
||||
my $server_endpoint = "http://".$self->{Monitor}->{ControlAddress}."/$cmd";
|
||||
my $req = HTTP::Request->new( POST => $server_endpoint );
|
||||
$req->header('content-type' => $content_type);
|
||||
$req->header('Host' => $self->{Monitor}->{ControlAddress});
|
||||
$req->header('content-length' => length($msg));
|
||||
$req->header('accept-encoding' => 'gzip, deflate');
|
||||
$req->header('connection' => 'Close');
|
||||
$req->content($msg);
|
||||
my $server_endpoint = 'http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd;
|
||||
my $req = HTTP::Request->new(POST => $server_endpoint);
|
||||
$req->header('content-type' => $content_type);
|
||||
$req->header('Host' => $self->{Monitor}->{ControlAddress});
|
||||
$req->header('content-length' => length($msg));
|
||||
$req->header('accept-encoding' => 'gzip, deflate');
|
||||
$req->header('connection' => 'Close');
|
||||
$req->content($msg);
|
||||
|
||||
my $res = $self->{ua}->request($req);
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ( $res->is_success ) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
Error( "After sending PTZ command, camera returned the following error:'".$res->status_line()."'" );
|
||||
if ( $res->is_success ) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
Error("After sending PTZ command, camera returned the following error:'".$res->status_line()."'");
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub getCamParams {
|
||||
my $self = shift;
|
||||
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken></GetImagingSettings></s:Body></s:Envelope>';
|
||||
my $server_endpoint = 'http://'.$self->{Monitor}->{ControlAddress}.'/onvif/imaging';
|
||||
my $req = HTTP::Request->new(POST => $server_endpoint);
|
||||
$req->header('content-type' => 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/GetImagingSettings"');
|
||||
$req->header('Host' => $self->{Monitor}->{ControlAddress});
|
||||
$req->header('content-length' => length($msg));
|
||||
$req->header('accept-encoding' => 'gzip, deflate');
|
||||
$req->header('connection' => 'Close');
|
||||
$req->content($msg);
|
||||
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ( $res->is_success ) {
|
||||
# We should really use an xml or soap library to parse the xml tags
|
||||
my $content = $res->decoded_content;
|
||||
|
||||
if ( $content =~ /.*<tt:(Brightness)>(.+)<\/tt:Brightness>.*/ ) {
|
||||
$CamParams{$1} = $2;
|
||||
}
|
||||
return( $result );
|
||||
}
|
||||
|
||||
sub getCamParams
|
||||
{
|
||||
my $self = shift;
|
||||
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken></GetImagingSettings></s:Body></s:Envelope>';
|
||||
my $server_endpoint = "http://".$self->{Monitor}->{ControlAddress}."/onvif/imaging";
|
||||
my $req = HTTP::Request->new( POST => $server_endpoint );
|
||||
$req->header('content-type' => 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/GetImagingSettings"');
|
||||
$req->header('Host' => $self->{Monitor}->{ControlAddress});
|
||||
$req->header('content-length' => length($msg));
|
||||
$req->header('accept-encoding' => 'gzip, deflate');
|
||||
$req->header('connection' => 'Close');
|
||||
$req->content($msg);
|
||||
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ( $res->is_success ) {
|
||||
# We should really use an xml or soap library to parse the xml tags
|
||||
my $content = $res->decoded_content;
|
||||
|
||||
if ($content =~ /.*<tt:(Brightness)>(.+)<\/tt:Brightness>.*/) {
|
||||
$CamParams{$1} = $2;
|
||||
}
|
||||
if ($content =~ /.*<tt:(Contrast)>(.+)<\/tt:Contrast>.*/) {
|
||||
$CamParams{$1} = $2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Error( "Unable to retrieve camera image settings:'".$res->status_line()."'" );
|
||||
if ( $content =~ /.*<tt:(Contrast)>(.+)<\/tt:Contrast>.*/ ) {
|
||||
$CamParams{$1} = $2;
|
||||
}
|
||||
} else {
|
||||
Error("Unable to retrieve camera image settings:'".$res->status_line()."'");
|
||||
}
|
||||
}
|
||||
|
||||
#autoStop
|
||||
#This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab
|
||||
sub autoStop
|
||||
{
|
||||
my $self = shift;
|
||||
my $autostop = shift;
|
||||
sub autoStop {
|
||||
my $self = shift;
|
||||
my $autostop = shift;
|
||||
|
||||
if( $autostop ) {
|
||||
Debug( "Auto Stop" );
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
usleep( $autostop );
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
}
|
||||
if ( $autostop ) {
|
||||
Debug('Auto Stop');
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
usleep($autostop);
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
}
|
||||
}
|
||||
|
||||
# Reset the Camera
|
||||
sub reset
|
||||
{
|
||||
Debug( "Camera Reset" );
|
||||
my $self = shift;
|
||||
my $cmd = "";
|
||||
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SystemReboot xmlns="http://www.onvif.org/ver10/device/wsdl"/></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/SystemReboot"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
sub reset {
|
||||
Debug('Camera Reset');
|
||||
my $self = shift;
|
||||
my $cmd = '';
|
||||
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SystemReboot xmlns="http://www.onvif.org/ver10/device/wsdl"/></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/SystemReboot"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
}
|
||||
|
||||
#Up Arrow
|
||||
sub moveConUp
|
||||
{
|
||||
Debug( "Move Up" );
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
|
||||
sub moveConUp {
|
||||
Debug('Move Up');
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
||||
}
|
||||
|
||||
#Down Arrow
|
||||
sub moveConDown
|
||||
{
|
||||
Debug( "Move Down" );
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
|
||||
sub moveConDown {
|
||||
Debug('Move Down');
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
||||
}
|
||||
|
||||
#Left Arrow
|
||||
sub moveConLeft
|
||||
{
|
||||
Debug( "Move Left" );
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.49" y="0" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
|
||||
sub moveConLeft {
|
||||
Debug('Move Left');
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.49" y="0" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
||||
}
|
||||
|
||||
#Right Arrow
|
||||
sub moveConRight
|
||||
{
|
||||
Debug( "Move Right" );
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.49" y="0" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
|
||||
sub moveConRight {
|
||||
Debug('Move Right');
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.49" y="0" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
||||
}
|
||||
|
||||
#Zoom In
|
||||
sub zoomConTele
|
||||
{
|
||||
Debug( "Zoom Tele" );
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><Zoom x="0.49" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
|
||||
sub zoomConTele {
|
||||
Debug('Zoom Tele');
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><Zoom x="0.49" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
||||
}
|
||||
|
||||
#Zoom Out
|
||||
sub zoomConWide
|
||||
{
|
||||
Debug( "Zoom Wide" );
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><Zoom x="-0.49" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
|
||||
sub zoomConWide {
|
||||
Debug('Zoom Wide');
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><Zoom x="-0.49" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
||||
}
|
||||
|
||||
#Diagonally Up Right Arrow
|
||||
#This camera does not have builtin diagonal commands so we emulate them
|
||||
sub moveConUpRight
|
||||
{
|
||||
Debug( "Move Diagonally Up Right" );
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.5" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
|
||||
sub moveConUpRight {
|
||||
Debug('Move Diagonally Up Right');
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.5" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
||||
}
|
||||
|
||||
#Diagonally Down Right Arrow
|
||||
#This camera does not have builtin diagonal commands so we emulate them
|
||||
sub moveConDownRight
|
||||
{
|
||||
Debug( "Move Diagonally Down Right" );
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.5" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
|
||||
sub moveConDownRight {
|
||||
Debug('Move Diagonally Down Right');
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.5" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
||||
}
|
||||
|
||||
#Diagonally Up Left Arrow
|
||||
#This camera does not have builtin diagonal commands so we emulate them
|
||||
sub moveConUpLeft
|
||||
{
|
||||
Debug( "Move Diagonally Up Left" );
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.5" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
|
||||
sub moveConUpLeft {
|
||||
Debug('Move Diagonally Up Left');
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.5" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
||||
}
|
||||
|
||||
#Diagonally Down Left Arrow
|
||||
#This camera does not have builtin diagonal commands so we emulate them
|
||||
sub moveConDownLeft
|
||||
{
|
||||
Debug( "Move Diagonally Down Left" );
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.5" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
|
||||
sub moveConDownLeft {
|
||||
Debug('Move Diagonally Down Left');
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.5" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
$self->autoStop($self->{Monitor}->{AutoStopTimeout});
|
||||
}
|
||||
|
||||
#Stop
|
||||
sub moveStop
|
||||
{
|
||||
Debug( "Move Stop" );
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
sub moveStop {
|
||||
Debug('Move Stop');
|
||||
my $self = shift;
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
}
|
||||
|
||||
#Set Camera Preset
|
||||
sub presetSet
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam( $params, 'preset' );
|
||||
Debug( "Set Preset $preset" );
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetPreset xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PresetToken>'.$preset.'</PresetToken></SetPreset></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/SetPreset"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
sub presetSet {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam($params, 'preset');
|
||||
Debug("Set Preset $preset");
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetPreset xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PresetToken>'.$preset.'</PresetToken></SetPreset></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/SetPreset"';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
}
|
||||
|
||||
#Recall Camera Preset
|
||||
sub presetGoto
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam( $params, 'preset' );
|
||||
Debug( "Goto Preset $preset" );
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GotoPreset xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PresetToken>'.$preset.'</PresetToken></GotoPreset></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/GotoPreset"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
sub presetGoto {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam($params, 'preset');
|
||||
Debug("Goto Preset $preset");
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GotoPreset xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PresetToken>'.$preset.'</PresetToken></GotoPreset></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/GotoPreset"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
}
|
||||
|
||||
#Horizontal Patrol
|
||||
#To be determined if this camera supports this feature
|
||||
sub horizontalPatrol
|
||||
{
|
||||
Debug( "Horizontal Patrol" );
|
||||
my $self = shift;
|
||||
my $cmd = '';
|
||||
my $msg ='';
|
||||
my $content_type = '';
|
||||
# $self->sendCmd( $cmd, $msg, $content_type );
|
||||
Error( "PTZ Command not implemented in control script." );
|
||||
sub horizontalPatrol {
|
||||
Debug('Horizontal Patrol');
|
||||
my $self = shift;
|
||||
my $cmd = '';
|
||||
my $msg ='';
|
||||
my $content_type = '';
|
||||
# $self->sendCmd( $cmd, $msg, $content_type );
|
||||
Error('PTZ Command not implemented in control script.');
|
||||
}
|
||||
|
||||
#Horizontal Patrol Stop
|
||||
#To be determined if this camera supports this feature
|
||||
sub horizontalPatrolStop
|
||||
{
|
||||
Debug( "Horizontal Patrol Stop" );
|
||||
my $self = shift;
|
||||
my $cmd = '';
|
||||
my $msg ='';
|
||||
my $content_type = '';
|
||||
# $self->sendCmd( $cmd, $msg, $content_type );
|
||||
Error( "PTZ Command not implemented in control script." );
|
||||
sub horizontalPatrolStop {
|
||||
Debug('Horizontal Patrol Stop');
|
||||
my $self = shift;
|
||||
my $cmd = '';
|
||||
my $msg ='';
|
||||
my $content_type = '';
|
||||
# $self->sendCmd( $cmd, $msg, $content_type );
|
||||
Error('PTZ Command not implemented in control script.');
|
||||
}
|
||||
|
||||
# Increase Brightness
|
||||
sub irisAbsOpen
|
||||
{
|
||||
Debug( "Iris $CamParams{'Brightness'}" );
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'Brightness'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
my $max = 100;
|
||||
sub irisAbsOpen {
|
||||
Debug("Iris $CamParams{Brightness}");
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{Brightness});
|
||||
my $step = $self->getParam($params, 'step');
|
||||
my $max = 100;
|
||||
|
||||
$CamParams{'Brightness'} += $step;
|
||||
$CamParams{'Brightness'} = $max if ($CamParams{'Brightness'} > $max);
|
||||
$CamParams{Brightness} += $step;
|
||||
$CamParams{Brightness} = $max if ($CamParams{Brightness} > $max);
|
||||
|
||||
my $cmd = 'onvif/imaging';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Brightness'}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
my $cmd = 'onvif/imaging';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{Brightness}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
}
|
||||
|
||||
# Decrease Brightness
|
||||
sub irisAbsClose
|
||||
{
|
||||
Debug( "Iris $CamParams{'Brightness'}" );
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'brightness'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
my $min = 0;
|
||||
Debug( "Iris $CamParams{Brightness}" );
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{brightness});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
my $min = 0;
|
||||
|
||||
$CamParams{'Brightness'} -= $step;
|
||||
$CamParams{'Brightness'} = $min if ($CamParams{'Brightness'} < $min);
|
||||
$CamParams{Brightness} -= $step;
|
||||
$CamParams{Brightness} = $min if ($CamParams{Brightness} < $min);
|
||||
|
||||
my $cmd = 'onvif/imaging';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Brightness'}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
my $cmd = 'onvif/imaging';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{Brightness}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
|
||||
$self->sendCmd( $cmd, $msg, $content_type );
|
||||
}
|
||||
|
||||
# Increase Contrast
|
||||
sub whiteAbsIn
|
||||
{
|
||||
Debug( "Iris $CamParams{'Contrast'}" );
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'Contrast'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
my $max = 100;
|
||||
sub whiteAbsIn {
|
||||
Debug("Iris $CamParams{Contrast}");
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{Contrast});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
my $max = 100;
|
||||
|
||||
$CamParams{'Contrast'} += $step;
|
||||
$CamParams{'Contrast'} = $max if ($CamParams{'Contrast'} > $max);
|
||||
$CamParams{Contrast} += $step;
|
||||
$CamParams{Contrast} = $max if ($CamParams{Contrast} > $max);
|
||||
|
||||
my $cmd = 'onvif/imaging';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Contrast'}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
|
||||
my $cmd = 'onvif/imaging';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{Contrast}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
|
||||
}
|
||||
|
||||
# Decrease Contrast
|
||||
sub whiteAbsOut
|
||||
{
|
||||
Debug( "Iris $CamParams{'Contrast'}" );
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{'Contrast'});
|
||||
my $step = $self->getParam( $params, 'step' );
|
||||
my $min = 0;
|
||||
sub whiteAbsOut {
|
||||
Debug("Iris $CamParams{Contrast}");
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
$self->getCamParams() unless($CamParams{Contrast});
|
||||
my $step = $self->getParam($params, 'step');
|
||||
my $min = 0;
|
||||
|
||||
$CamParams{'Contrast'} -= $step;
|
||||
$CamParams{'Contrast'} = $min if ($CamParams{'Contrast'} < $min);
|
||||
$CamParams{Contrast} -= $step;
|
||||
$CamParams{Contrast} = $min if ($CamParams{Contrast} < $min);
|
||||
|
||||
my $cmd = 'onvif/imaging';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Contrast'}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
|
||||
my $cmd = 'onvif/imaging';
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{Contrast}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
|
|
@ -0,0 +1,365 @@
|
|||
package ZoneMinder::Control::PSIA;
|
||||
|
||||
use 5.006;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
require ZoneMinder::Base;
|
||||
require ZoneMinder::Control;
|
||||
|
||||
our @ISA = qw(ZoneMinder::Control);
|
||||
|
||||
our $REALM = 'TV-IP450PI';
|
||||
our $USERNAME = 'admin';
|
||||
our $PASSWORD = '';
|
||||
our $ADDRESS = '';
|
||||
our $PROTOCOL = 'http://';
|
||||
|
||||
use ZoneMinder::Logger qw(:all);
|
||||
use ZoneMinder::Config qw(:all);
|
||||
use ZoneMinder::Database qw(zmDbConnect);
|
||||
|
||||
sub open
|
||||
{
|
||||
my $self = shift;
|
||||
$self->loadMonitor();
|
||||
|
||||
if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?<PROTOCOL>https?:\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>.*)$/ ) ) {
|
||||
$PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL};
|
||||
$USERNAME = $+{USERNAME} if $+{USERNAME};
|
||||
$PASSWORD = $+{PASSWORD} if $+{PASSWORD};
|
||||
$ADDRESS = $+{ADDRESS} if $+{ADDRESS};
|
||||
} else {
|
||||
Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress});
|
||||
$ADDRESS = $self->{Monitor}->{ControlAddress};
|
||||
}
|
||||
if ( !($ADDRESS =~ /:/) ) {
|
||||
Error('You generally need to also specify the port. I will append :80');
|
||||
$ADDRESS .= ':80';
|
||||
}
|
||||
|
||||
use LWP::UserAgent;
|
||||
$self->{ua} = LWP::UserAgent->new;
|
||||
$self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION );
|
||||
$self->{state} = 'closed';
|
||||
Debug( "sendCmd credentials control address:'".$ADDRESS
|
||||
."' realm:'" . $REALM
|
||||
. "' username:'" . $USERNAME
|
||||
. "' password:'".$PASSWORD
|
||||
."'"
|
||||
);
|
||||
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
|
||||
|
||||
# Detect REALM
|
||||
my $req = HTTP::Request->new(GET=>$PROTOCOL . $ADDRESS . "/PSIA/PTZ/channels");
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ($res->is_success) {
|
||||
$self->{state} = 'open';
|
||||
return;
|
||||
} elsif (! $res->is_success) {
|
||||
Debug("Need newer REALM");
|
||||
if ( $res->status_line() eq '401 Unauthorized' ) {
|
||||
my $headers = $res->headers();
|
||||
foreach my $k ( keys %$headers ) {
|
||||
Debug("Initial Header $k => $$headers{$k}");
|
||||
} # end foreach
|
||||
if ( $$headers{'www-authenticate'} ) {
|
||||
my ($auth, $tokens) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
|
||||
if ($tokens =~ /\w+="([^"]+)"/i) {
|
||||
$REALM = $1;
|
||||
Debug("Changing REALM to $REALM");
|
||||
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
|
||||
} # end if
|
||||
} else {
|
||||
Debug("No WWW-Authenticate header");
|
||||
} # end if www-authenticate header
|
||||
} # end if $res->status_line() eq '401 Unauthorized'
|
||||
} # end elsif ! $res->is_success
|
||||
}
|
||||
|
||||
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 sendGetRequest {
|
||||
my $self = shift;
|
||||
my $url_path = shift;
|
||||
|
||||
my $result = undef;
|
||||
|
||||
my $url = $PROTOCOL . $ADDRESS . $url_path;
|
||||
my $req = HTTP::Request->new(GET=>$url);
|
||||
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ($res->is_success) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
if ( $res->status_line() eq '401 Unauthorized' ) {
|
||||
Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
|
||||
Error("Content was " . $res->content() );
|
||||
my $res = $self->{ua}->request($req);
|
||||
if ( $res->is_success ) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
Error("Content was " . $res->content() );
|
||||
}
|
||||
}
|
||||
if ( ! $result ) {
|
||||
Error("Error check failed: '".$res->status_line());
|
||||
}
|
||||
}
|
||||
return($result);
|
||||
}
|
||||
sub sendPutRequest {
|
||||
my $self = shift;
|
||||
my $url_path = shift;
|
||||
my $content = shift;
|
||||
|
||||
my $result = undef;
|
||||
|
||||
my $url = $PROTOCOL . $ADDRESS . $url_path;
|
||||
my $req = HTTP::Request->new(PUT=>$url);
|
||||
if(defined($content)) {
|
||||
$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);
|
||||
|
||||
if ($res->is_success) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
if ( $res->status_line() eq '401 Unauthorized' ) {
|
||||
Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
|
||||
Error("Content was " . $res->content() );
|
||||
my $res = $self->{ua}->request($req);
|
||||
if ( $res->is_success ) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
Error("Content was " . $res->content() );
|
||||
}
|
||||
}
|
||||
if ( ! $result ) {
|
||||
Error( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" );
|
||||
}
|
||||
}
|
||||
return($result);
|
||||
}
|
||||
sub sendDeleteRequest {
|
||||
my $self = shift;
|
||||
my $url_path = shift;
|
||||
|
||||
my $result = undef;
|
||||
|
||||
my $url = $PROTOCOL . $ADDRESS . $url_path;
|
||||
my $req = HTTP::Request->new(DELETE=>$url);
|
||||
my $res = $self->{ua}->request($req);
|
||||
if ($res->is_success) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
if ( $res->status_line() eq '401 Unauthorized' ) {
|
||||
Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
|
||||
Error("Content was " . $res->content() );
|
||||
my $res = $self->{ua}->request($req);
|
||||
if ( $res->is_success ) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
Error("Content was " . $res->content() );
|
||||
}
|
||||
}
|
||||
if ( ! $result ) {
|
||||
Error( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" );
|
||||
}
|
||||
}
|
||||
return($result);
|
||||
}
|
||||
|
||||
sub move
|
||||
{
|
||||
my $self = shift;
|
||||
my $panPercentage = shift;
|
||||
my $tiltPercentage = shift;
|
||||
my $zoomPercentage = shift;
|
||||
|
||||
my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps";
|
||||
my $ptzdata = '<PTZData version="1.0" xmlns="urn:psialliance-org">';
|
||||
$ptzdata .= '<pan>' . $panPercentage . '</pan>';
|
||||
$ptzdata .= '<tilt>' . $tiltPercentage . '</tilt>';
|
||||
$ptzdata .= '<zoom>' . $zoomPercentage . '</zoom>';
|
||||
$ptzdata .= '<Momentary><duration>500</duration></Momentary>';
|
||||
$ptzdata .= '</PTZData>';
|
||||
$self->sendPutRequest("/PSIA/PTZ/channels/1/momentary", $ptzdata);
|
||||
}
|
||||
|
||||
sub moveRelUpLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Up Left" );
|
||||
$self->move(-50, 50, 0);
|
||||
}
|
||||
|
||||
sub moveRelUp
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Up" );
|
||||
$self->move(0, 50, 0);
|
||||
}
|
||||
|
||||
sub moveRelUpRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Up Right" );
|
||||
$self->move(50, 50, 0);
|
||||
}
|
||||
|
||||
sub moveRelLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Left" );
|
||||
$self->move(-50, 0, 0);
|
||||
}
|
||||
|
||||
sub moveRelRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Right" );
|
||||
$self->move(50, 0, 0);
|
||||
}
|
||||
|
||||
sub moveRelDownLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Down Left" );
|
||||
$self->move(-50, -50, 0);
|
||||
}
|
||||
|
||||
sub moveRelDown
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Down" );
|
||||
$self->move(0, -50, 0);
|
||||
}
|
||||
|
||||
sub moveRelDownRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Down Right" );
|
||||
$self->move(50, -50, 0);
|
||||
}
|
||||
|
||||
sub zoomRelTele
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Zoom Relative Tele");
|
||||
$self->move(0, 0, 50);
|
||||
}
|
||||
|
||||
sub zoomRelWide
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Zoom Relative Wide");
|
||||
$self->move(0, 0, -50);
|
||||
}
|
||||
|
||||
|
||||
sub presetClear
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset_id = $self->getParam($params, 'preset');
|
||||
my $url_path = "/PSIA/PTZ/channels/1/presets/" . $preset_id;
|
||||
$self->sendDeleteRequest($url_path);
|
||||
}
|
||||
|
||||
|
||||
sub presetSet
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
|
||||
my $preset_id = $self->getParam($params, 'preset');
|
||||
|
||||
my $dbh = zmDbConnect(1);
|
||||
my $sql = 'SELECT * FROM ControlPresets WHERE MonitorId = ? AND Preset = ?';
|
||||
my $sth = $dbh->prepare($sql)
|
||||
or Fatal("Can't prepare sql '$sql': " . $dbh->errstr());
|
||||
my $res = $sth->execute($self->{Monitor}->{Id}, $preset_id)
|
||||
or Fatal("Can't execute sql '$sql': " . $sth->errstr());
|
||||
my $control_preset_row = $sth->fetchrow_hashref();
|
||||
my $new_label_name = $control_preset_row->{'Label'};
|
||||
|
||||
my $url_path = "/PSIA/PTZ/channels/1/presets/" . $preset_id;
|
||||
my $ptz_preset_data = '<PTZPreset>';
|
||||
$ptz_preset_data .= '<id>' . $preset_id . '</id>';
|
||||
$ptz_preset_data .= '<presetName>' . $new_label_name . '</presetName>';
|
||||
$ptz_preset_data .= '</PTZPreset>';
|
||||
$self->sendPutRequest($url_path, $ptz_preset_data);
|
||||
}
|
||||
|
||||
sub presetGoto
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset_id = $self->getParam($params, 'preset');
|
||||
|
||||
my $url_path = '/PSIA/PTZ/channels/1/presets/' . $preset_id . '/goto';
|
||||
|
||||
$self->sendPutRequest($url_path);
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
ZoneMinder::Control::PSIA - Perl module for cameras implementing the PSIA
|
||||
(Physical Security Interoperability Alliance), IP Media Devices API
|
||||
specification
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use ZoneMinder::Control::PSIA;
|
||||
place this in /usr/share/perl5/ZoneMinder/Control
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This has so far been tested with:
|
||||
- Trendnet TV-IP450PI
|
||||
|
||||
=head2 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2018 ZoneMinder LLC
|
||||
|
||||
This library 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 library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
=cut
|
|
@ -16,7 +16,7 @@
|
|||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# ==========================================================================
|
||||
#
|
||||
|
|
|
@ -1,504 +0,0 @@
|
|||
# =========================================================================
|
||||
#
|
||||
# ZoneMinder Trendnet TV-IP862IC IP Control Protocol Module, $Date: $, $Revision: $
|
||||
# Copyright (C) 2014 Vincent Giovannone
|
||||
#
|
||||
#
|
||||
# ==========================================================================
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# ==========================================================================
|
||||
#
|
||||
# This module contains the implementation of the Trendnet TV-IP672PI IP camera control
|
||||
# protocol. Also works or TV-IP862IC
|
||||
#
|
||||
# For Zoneminder 1.26+
|
||||
#
|
||||
# Under control capability:
|
||||
#
|
||||
# * Main: name it (suggest TVIP672PI), type is FFMPEG (or remote if you're using MJPEG), protocol is TVIP672PI
|
||||
# * Main (more): Can wake, can sleep, can reset
|
||||
# * Move: Can move, can move diagonally, can move mapped, can move relative
|
||||
# * Pan: Can pan
|
||||
# * Tilt: Can tilt
|
||||
# * Presets: Has presets, num presets 20, has home preset (don't set presets via camera's web server, only set via ZM.)
|
||||
#
|
||||
# Under control tab in the monitor itself:
|
||||
#
|
||||
# * Controllable
|
||||
# * Control type is the name you gave it in control capability above
|
||||
# * Control device is the password you use to authenticate to the camera (see further below if you need to change the username from "admin")
|
||||
# * Control address is the camera's ip address AND web port. example: 192.168.1.1:80
|
||||
#
|
||||
#
|
||||
# If using with anything but a TV-IP672PI (ex: TV-IP672WI), YOU MUST MATCH THE REALM TO MATCH YOUR CAMERA FURTHER DOWN!
|
||||
#
|
||||
#
|
||||
# Due to how the TVIP672 represents presets internally, you MUST define the presets in order... i.e. 1,2,3,4... not 1,10,3,4.
|
||||
# (see much further down for why, if you care...)
|
||||
#
|
||||
|
||||
|
||||
package ZoneMinder::Control::TVIP862;
|
||||
|
||||
use 5.006;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
require ZoneMinder::Base;
|
||||
require ZoneMinder::Control;
|
||||
|
||||
our @ISA = qw(ZoneMinder::Control);
|
||||
|
||||
#
|
||||
# ******** YOU MUST CHANGE THE FOLLOWING LINES TO MATCH YOUR CAMERA! **********
|
||||
#
|
||||
# I assume that "TV-IP672WI" would work for the TV-IP672WI, but can't test since I don't own one.
|
||||
#
|
||||
# TV-IP672PI works for the PI version, of course.
|
||||
#
|
||||
# Finally, the username is the username you'd like to authenticate as.
|
||||
#
|
||||
our $REALM = 'TV-IP862IC';
|
||||
our $USERNAME = 'admin';
|
||||
our $PASSWORD = '';
|
||||
our $ADDRESS = '';
|
||||
|
||||
# ==========================================================================
|
||||
#
|
||||
# Trendnet TV-IP672PI Control Protocol
|
||||
#
|
||||
# ==========================================================================
|
||||
|
||||
use ZoneMinder::Logger qw(:all);
|
||||
use ZoneMinder::Config qw(:all);
|
||||
|
||||
sub open
|
||||
{
|
||||
my $self = shift;
|
||||
$self->loadMonitor();
|
||||
|
||||
my ( $protocol, $username, $password, $address )
|
||||
= $self->{Monitor}->{ControlAddress} =~ /^(https?:\/\/)?([^:]+):([^\/@]+)@(.*)$/;
|
||||
if ( $username ) {
|
||||
$USERNAME = $username;
|
||||
$PASSWORD = $password;
|
||||
$ADDRESS = $address;
|
||||
} else {
|
||||
Error( "Failed to parse auth from address");
|
||||
$ADDRESS = $self->{Monitor}->{ControlAddress};
|
||||
}
|
||||
if ( ! $ADDRESS =~ /:/ ) {
|
||||
Error( "You generally need to also specify the port. I will append :80" );
|
||||
$ADDRESS .= ':80';
|
||||
}
|
||||
|
||||
use LWP::UserAgent;
|
||||
$self->{ua} = LWP::UserAgent->new;
|
||||
$self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION );
|
||||
$self->{state} = 'open';
|
||||
# credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string)
|
||||
Debug ( "sendCmd credentials control address:'".$ADDRESS
|
||||
."' realm:'" . $REALM
|
||||
. "' username:'" . $USERNAME
|
||||
. "' password:'".$PASSWORD
|
||||
."'"
|
||||
);
|
||||
$self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD);
|
||||
|
||||
# Detect REALM
|
||||
my $req = HTTP::Request->new( GET=>"http://".$ADDRESS."/cgi/ptdc.cgi" );
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ( ! $res->is_success ) {
|
||||
Debug("Need newer REALM");
|
||||
if ( $res->status_line() eq '401 Unauthorized' ) {
|
||||
my $headers = $res->headers();
|
||||
foreach my $k ( keys %$headers ) {
|
||||
Debug("Initial Header $k => $$headers{$k}");
|
||||
} # end foreach
|
||||
if ( $$headers{'www-authenticate'} ) {
|
||||
my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
|
||||
if ( $tokens =~ /\w+="([^"]+)"/i ) {
|
||||
$REALM = $1;
|
||||
Debug( "Changing REALM to $REALM" );
|
||||
$self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD);
|
||||
} # end if
|
||||
} else {
|
||||
Debug("No headers line");
|
||||
} # end if headers
|
||||
} # end if $res->status_line() eq '401 Unauthorized'
|
||||
} # end if ! $res->is_success
|
||||
}
|
||||
|
||||
sub printMsg
|
||||
{
|
||||
my $self = shift;
|
||||
my $msg = shift;
|
||||
my $msg_len = length($msg);
|
||||
|
||||
Debug( $msg."[".$msg_len."]" );
|
||||
}
|
||||
|
||||
sub sendCmd
|
||||
{
|
||||
|
||||
# This routine is used for all moving, which are all GET commands...
|
||||
|
||||
my $self = shift;
|
||||
my $cmd = shift;
|
||||
|
||||
my $result = undef;
|
||||
|
||||
my $url = "http://".$ADDRESS."/cgi/ptdc.cgi?command=".$cmd;
|
||||
my $req = HTTP::Request->new( GET=>$url );
|
||||
|
||||
Debug ("sendCmd command: " . $url );
|
||||
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ( $res->is_success ) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
if ( $res->status_line() eq '401 Unauthorized' ) {
|
||||
Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
|
||||
Error("Content was " . $res->content() );
|
||||
my $res = $self->{ua}->request($req);
|
||||
if ( $res->is_success ) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
Error("Content was " . $res->content() );
|
||||
}
|
||||
}
|
||||
if ( ! $result ) {
|
||||
Error( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" );
|
||||
}
|
||||
}
|
||||
|
||||
return( $result );
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub sendCmdPost
|
||||
{
|
||||
|
||||
#
|
||||
# This routine is used for setting/clearing presets and IR commands, which are POST commands...
|
||||
#
|
||||
|
||||
my $self = shift;
|
||||
my $url = shift;
|
||||
my $cmd = shift;
|
||||
|
||||
my $result = undef;
|
||||
|
||||
if ($url eq undef)
|
||||
{
|
||||
Error ("url passed to sendCmdPost is undefined.");
|
||||
return(-1);
|
||||
}
|
||||
|
||||
Debug ("sendCmdPost url: " . $url . " cmd: " . $cmd);
|
||||
|
||||
my $req = HTTP::Request->new(POST => "http://".$ADDRESS.$url);
|
||||
$req->content_type('application/x-www-form-urlencoded');
|
||||
$req->content($cmd);
|
||||
|
||||
Debug ( "sendCmdPost credentials control address:'".$ADDRESS."' realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD."'");
|
||||
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ( $res->is_success )
|
||||
{
|
||||
$result = !undef;
|
||||
}
|
||||
else
|
||||
{
|
||||
Error( "sendCmdPost Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" );
|
||||
if ( $res->status_line() eq '401 Unauthorized' ) {
|
||||
Error( "sendCmdPost Error check failed: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
|
||||
} else {
|
||||
Error( "sendCmdPost Error check failed: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
|
||||
} # endif
|
||||
}
|
||||
|
||||
return( $result );
|
||||
}
|
||||
|
||||
|
||||
|
||||
sub move
|
||||
{
|
||||
my $self = shift;
|
||||
my $panSteps = shift;
|
||||
my $tiltSteps = shift;
|
||||
|
||||
my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
sub moveRelUpLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Up Left" );
|
||||
$self->move(-3, 3);
|
||||
}
|
||||
|
||||
sub moveRelUp
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Up" );
|
||||
$self->move(0, 3);
|
||||
}
|
||||
|
||||
sub moveRelUpRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Up Right" );
|
||||
$self->move(3, 3);
|
||||
}
|
||||
|
||||
sub moveRelLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Left" );
|
||||
$self->move(-3, 0);
|
||||
}
|
||||
|
||||
sub moveRelRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Right" );
|
||||
$self->move(3, 0);
|
||||
}
|
||||
|
||||
sub moveRelDownLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Down Left" );
|
||||
$self->move(-3, -3);
|
||||
}
|
||||
|
||||
sub moveRelDown
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Down" );
|
||||
$self->move(0, -3);
|
||||
}
|
||||
|
||||
sub moveRelDownRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Down Right" );
|
||||
$self->move(3, -3);
|
||||
}
|
||||
|
||||
|
||||
# moves the camera to center on the point that the user clicked on in the video image.
|
||||
# This isn't mega accurate but good enough for most purposes
|
||||
|
||||
sub moveMap
|
||||
{
|
||||
|
||||
# If the camera moves too much, increase hscale and vscale. (...if it doesn't move enough, try decreasing!)
|
||||
# They scale the movement and are here to compensate for manufacturing variation.
|
||||
# It's never going to be perfect, so just get somewhere in the ballpark and call it a day.
|
||||
# (Don't forget to kill the zmcontrol process while tweaking!)
|
||||
|
||||
# 1280x800
|
||||
my $hscale = 31;
|
||||
my $vscale = 25;
|
||||
|
||||
# 1280x800 with fisheye
|
||||
#my $hscale = 15;
|
||||
#my $vscale = 15;
|
||||
|
||||
# 640x400
|
||||
#my $hscale = 14;
|
||||
#my $vscale = 12;
|
||||
|
||||
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $xcoord = $self->getParam( $params, 'xcoord' );
|
||||
my $ycoord = $self->getParam( $params, 'ycoord' );
|
||||
|
||||
my $hor = ($xcoord - ($self->{Monitor}->{Width} / 2))/$hscale;
|
||||
my $ver = ($ycoord - ($self->{Monitor}->{Height} / 2))/$vscale;
|
||||
|
||||
$hor = int($hor);
|
||||
$ver = -1 * int($ver);
|
||||
|
||||
Debug( "Move Map to $xcoord,$ycoord, hor=$hor, ver=$ver" );
|
||||
$self->move( $hor, $ver );
|
||||
}
|
||||
|
||||
|
||||
# **** PRESETS ****
|
||||
#
|
||||
# OK, presets work a little funky but they DO work, provided you define them in order and don't skip any.
|
||||
#
|
||||
# The problem is that when you load the web page for this camera, it gives a list of preset names tied to index numbers.
|
||||
# So let's say you have four presets... A, B, C, and D, and defined them in that order.
|
||||
# So A is index 0, B is index 1, C is index 2, D is index 3. When you tell the camera to go to a preset, you actually tell it by number, not by name.
|
||||
# (So "Go to D" is really "go to index 3".)
|
||||
#
|
||||
# Now let's say somebody deletes C via the camera's web GUI. The camera re-numbers the existing presets A=0, B=1, D=2.
|
||||
# There's really no easy way for ZM to discover this re-numbering, so zoneminder would still send "go to preset 3" thinking
|
||||
# it's telling the camera to go to point D. In actuality it's telling the camera to go to a preset that no longer exists.
|
||||
#
|
||||
# As long as you define your presets in order (i.e. define preset 1, then preset 2, then preset 3, etc.) everything will work just
|
||||
# fine in ZoneMinder.
|
||||
#
|
||||
# (Home preset needs to be set via the camera's web gui, and is unaffected by any of this.)
|
||||
#
|
||||
# So that's the limitation: DEFINE YOUR PRESETS IN ORDER THROUGH (and only through!) ZM AND DON'T SKIP ANY.
|
||||
#
|
||||
|
||||
|
||||
sub presetClear
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam( $params, 'preset' );
|
||||
my $cmd = "presetName=$preset&command=del";
|
||||
my $url = "/eng/admin/cam_control.cgi";
|
||||
Debug ("presetClear: " . $preset . " cmd: " . $cmd);
|
||||
$self->sendCmdPost($url,$cmd);
|
||||
}
|
||||
|
||||
|
||||
sub presetSet
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam( $params, 'preset' );
|
||||
my $cmd = "presetName=$preset&command=add";
|
||||
my $url = "/eng/admin/cam_control.cgi";
|
||||
Debug ("presetSet " . $preset . " cmd: " . $cmd);
|
||||
$self->sendCmdPost ($url,$cmd);
|
||||
}
|
||||
|
||||
sub presetGoto
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam( $params, 'preset' );
|
||||
$preset = $preset - 1;
|
||||
Debug( "Goto Preset $preset" );
|
||||
my $cmd = "goto_preset_position&index=$preset";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
sub presetHome
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Home Preset" );
|
||||
my $cmd = "go_home";
|
||||
$self->sendCmd( $cmd );
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# **** IR CONTROLS ****
|
||||
#
|
||||
#
|
||||
# Wake: Force IR on, always. (always night mode)
|
||||
#
|
||||
# Sleep: Force IR off, always. (always day mode)
|
||||
#
|
||||
# Reset: Automatic IR mode. (day/night mode determined by camera)
|
||||
#
|
||||
|
||||
|
||||
sub wake
|
||||
{
|
||||
# force IR on ("always night mode")
|
||||
|
||||
my $self = shift;
|
||||
my $url = "/eng/admin/adv_audiovideo.cgi";
|
||||
my $cmd = "irMode=3";
|
||||
|
||||
Debug("Wake -- IR on");
|
||||
|
||||
$self->sendCmdPost ($url,$cmd);
|
||||
}
|
||||
|
||||
sub sleep
|
||||
{
|
||||
# force IR off ("always day mode")
|
||||
|
||||
my $self=shift;
|
||||
my $url = "/eng/admin/adv_audiovideo.cgi";
|
||||
my $cmd = "irMode=2";
|
||||
|
||||
Debug("Sleep -- IR off");
|
||||
|
||||
$self->sendCmdPost ($url,$cmd);
|
||||
}
|
||||
|
||||
sub reset
|
||||
{
|
||||
# IR auto
|
||||
|
||||
my $self=shift;
|
||||
my $url = "/eng/admin/adv_audiovideo.cgi";
|
||||
my $cmd = "irMode=0";
|
||||
|
||||
Debug("Reset -- IR auto");
|
||||
|
||||
$self->sendCmdPost ($url,$cmd);
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
__END__
|
||||
# Below is stub documentation for your module. You'd better edit it!
|
||||
|
||||
=head1 NAME
|
||||
|
||||
ZoneMinder::Database - Perl extension for Trendnet TVIP672
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use ZoneMinder::Database;
|
||||
stuff this in /usr/share/perl5/ZoneMinder/Control , then eat a sandwich
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Stub documentation for Trendnet TVIP672, created by Vince.
|
||||
|
||||
=head2 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
Read the comments at the beginning of this file to see the usage for zoneminder 1.25.0
|
||||
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Vincent Giovannone, I'd rather you not email me.
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2014 by Vincent Giovannone
|
||||
|
||||
This library is free software; you can redistribute it and/or modify
|
||||
it under the same terms as Perl itself, either Perl version 5.8.3 or,
|
||||
at your option, any later version of Perl 5 you may have available.
|
||||
|
||||
|
||||
=cut
|
|
@ -0,0 +1,431 @@
|
|||
package ZoneMinder::Control::Trendnet;
|
||||
|
||||
use 5.006;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
require ZoneMinder::Base;
|
||||
require ZoneMinder::Control;
|
||||
|
||||
our @ISA = qw(ZoneMinder::Control);
|
||||
|
||||
# You do not need to change the REALM, but you can get slightly faster response
|
||||
# by setting so that the first auth request succeeds.
|
||||
#
|
||||
# The username and password should be passed in the ControlAddress field but you
|
||||
# can set them here if you want.
|
||||
#
|
||||
|
||||
our $REALM = '';
|
||||
our $PROTOCOL = 'http://';
|
||||
our $USERNAME = 'admin';
|
||||
our $PASSWORD = '';
|
||||
our $ADDRESS = '';
|
||||
|
||||
use ZoneMinder::Logger qw(:all);
|
||||
use ZoneMinder::Config qw(:all);
|
||||
|
||||
sub open {
|
||||
my $self = shift;
|
||||
$self->loadMonitor();
|
||||
|
||||
if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?<PROTOCOL>https?:\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>.*)$/ ) ) {
|
||||
$PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL};
|
||||
$USERNAME = $+{USERNAME} if $+{USERNAME};
|
||||
$PASSWORD = $+{PASSWORD} if $+{PASSWORD};
|
||||
$ADDRESS = $+{ADDRESS} if $+{ADDRESS};
|
||||
} else {
|
||||
Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress});
|
||||
$ADDRESS = $self->{Monitor}->{ControlAddress};
|
||||
}
|
||||
if ( !($ADDRESS =~ /:/) ) {
|
||||
Error('You generally need to also specify the port. I will append :80');
|
||||
$ADDRESS .= ':80';
|
||||
}
|
||||
|
||||
use LWP::UserAgent;
|
||||
$self->{ua} = LWP::UserAgent->new;
|
||||
$self->{ua}->agent('ZoneMinder Control Agent/'.$ZoneMinder::Base::ZM_VERSION);
|
||||
$self->{state} = 'closed';
|
||||
# credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string)
|
||||
Debug ( "sendCmd credentials control address:'".$ADDRESS
|
||||
."' realm:'" . $REALM
|
||||
. "' username:'" . $USERNAME
|
||||
. "' password:'".$PASSWORD
|
||||
."'"
|
||||
);
|
||||
$self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD);
|
||||
|
||||
# Detect REALM
|
||||
my $res = $self->{ua}->get($PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi');
|
||||
|
||||
if ( $res->is_success ) {
|
||||
$self->{state} = 'open';
|
||||
return;
|
||||
}
|
||||
|
||||
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 =~ /\w+="([^"]+)"/i ) {
|
||||
if ( $REALM ne $1 ) {
|
||||
$REALM = $1;
|
||||
Debug("Changing REALM to $REALM");
|
||||
$self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD);
|
||||
$res = $self->{ua}->get($PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi');
|
||||
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 printMsg {
|
||||
my $self = shift;
|
||||
my $msg = shift;
|
||||
my $msg_len = length($msg);
|
||||
|
||||
Debug($msg.'['.$msg_len.']');
|
||||
}
|
||||
|
||||
sub sendCmd {
|
||||
|
||||
# This routine is used for all moving, which are all GET commands...
|
||||
|
||||
my $self = shift;
|
||||
my $cmd = shift;
|
||||
|
||||
my $url = $PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi?command='.$cmd;
|
||||
my $res = $self->{ua}->get($url);
|
||||
|
||||
Debug('sendCmd command: ' . $url);
|
||||
if ( $res->is_success ) {
|
||||
return !undef;
|
||||
}
|
||||
Error("Error check failed: '".$res->status_line()."' cmd:'".$cmd."'");
|
||||
return;
|
||||
}
|
||||
|
||||
sub sendCmdPost {
|
||||
|
||||
#
|
||||
# This routine is used for setting/clearing presets and IR commands, which are POST commands...
|
||||
#
|
||||
|
||||
my $self = shift;
|
||||
my $url = shift;
|
||||
my $form = shift;
|
||||
|
||||
my $result = undef;
|
||||
|
||||
if ( $url eq undef ) {
|
||||
Error('url passed to sendCmdPost is undefined.');
|
||||
return -1;
|
||||
}
|
||||
|
||||
#Debug('sendCmdPost url: ' . $url . ' cmd: ' . $cmd);
|
||||
|
||||
my $res;
|
||||
$res = $self->{ua}->post(
|
||||
$PROTOCOL.$ADDRESS.$url,
|
||||
Referer=>$PROTOCOL.$ADDRESS.$url,
|
||||
Content=>$form
|
||||
);
|
||||
|
||||
Debug("sendCmdPost credentials control to: $PROTOCOL$ADDRESS$url realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD."'");
|
||||
|
||||
if ( $res->is_success ) {
|
||||
return !undef;
|
||||
}
|
||||
Error("sendCmdPost Error check failed: '".$res->status_line()."' cmd:");
|
||||
|
||||
return $result;
|
||||
} # end sub sendCmdPost
|
||||
|
||||
sub move {
|
||||
my $self = shift;
|
||||
my $panSteps = shift;
|
||||
my $tiltSteps = shift;
|
||||
|
||||
my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps";
|
||||
$self->sendCmd($cmd);
|
||||
}
|
||||
|
||||
sub moveRelUpLeft {
|
||||
my $self = shift;
|
||||
Debug('Move Up Left');
|
||||
$self->move(-3, 3);
|
||||
}
|
||||
|
||||
sub moveRelUp {
|
||||
my $self = shift;
|
||||
Debug('Move Up');
|
||||
$self->move(0, 3);
|
||||
}
|
||||
|
||||
sub moveRelUpRight {
|
||||
my $self = shift;
|
||||
Debug('Move Up Right');
|
||||
$self->move(3, 3);
|
||||
}
|
||||
|
||||
sub moveRelLeft {
|
||||
my $self = shift;
|
||||
Debug('Move Left');
|
||||
$self->move(-3, 0);
|
||||
}
|
||||
|
||||
sub moveRelRight {
|
||||
my $self = shift;
|
||||
Debug('Move Right');
|
||||
$self->move(3, 0);
|
||||
}
|
||||
|
||||
sub moveRelDownLeft {
|
||||
my $self = shift;
|
||||
Debug('Move Down Left');
|
||||
$self->move(-3, -3);
|
||||
}
|
||||
|
||||
sub moveRelDown {
|
||||
my $self = shift;
|
||||
Debug('Move Down');
|
||||
$self->move(0, -3);
|
||||
}
|
||||
|
||||
sub moveRelDownRight {
|
||||
my $self = shift;
|
||||
Debug('Move Down Right');
|
||||
$self->move(3, -3);
|
||||
}
|
||||
|
||||
# moves the camera to center on the point that the user clicked on in the video image.
|
||||
# This isn't mega accurate but good enough for most purposes
|
||||
|
||||
sub moveMap {
|
||||
|
||||
# If the camera moves too much, increase hscale and vscale. (...if it doesn't move enough, try decreasing!)
|
||||
# They scale the movement and are here to compensate for manufacturing variation.
|
||||
# It's never going to be perfect, so just get somewhere in the ballpark and call it a day.
|
||||
# (Don't forget to kill the zmcontrol process while tweaking!)
|
||||
|
||||
# 1280x800
|
||||
my $hscale = 31;
|
||||
my $vscale = 25;
|
||||
|
||||
# 1280x800 with fisheye
|
||||
#my $hscale = 15;
|
||||
#my $vscale = 15;
|
||||
|
||||
# 640x400
|
||||
#my $hscale = 14;
|
||||
#my $vscale = 12;
|
||||
|
||||
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $xcoord = $self->getParam( $params, 'xcoord' );
|
||||
my $ycoord = $self->getParam( $params, 'ycoord' );
|
||||
|
||||
my $hor = ($xcoord - ($self->{Monitor}->{Width} / 2))/$hscale;
|
||||
my $ver = ($ycoord - ($self->{Monitor}->{Height} / 2))/$vscale;
|
||||
|
||||
$hor = int($hor);
|
||||
$ver = -1 * int($ver);
|
||||
|
||||
Debug("Move Map to $xcoord,$ycoord, hor=$hor, ver=$ver");
|
||||
$self->move($hor, $ver);
|
||||
}
|
||||
|
||||
|
||||
# **** PRESETS ****
|
||||
#
|
||||
# OK, presets work a little funky but they DO work, provided you define them
|
||||
# in order and don't skip any.
|
||||
#
|
||||
# The problem is that when you load the web page for this camera, it gives a
|
||||
# list of preset names tied to index numbers.
|
||||
# So let's say you have four presets... A, B, C, and D, and defined them in
|
||||
# that order.
|
||||
# So A is index 0, B is index 1, C is index 2, D is index 3. When you tell
|
||||
# the camera to go to a preset, you actually tell it by number, not by name.
|
||||
# (So "Go to D" is really "go to index 3".)
|
||||
#
|
||||
# Now let's say somebody deletes C via the camera's web GUI. The camera
|
||||
# re-numbers the existing presets A=0, B=1, D=2.
|
||||
# There's really no easy way for ZM to discover this re-numbering, so
|
||||
# zoneminder would still send "go to preset 3" thinking
|
||||
# it's telling the camera to go to point D. In actuality it's telling the
|
||||
# camera to go to a preset that no longer exists.
|
||||
#
|
||||
# As long as you define your presets in order (i.e. define preset 1, then
|
||||
# preset 2, then preset 3, etc.) everything will work just
|
||||
# fine in ZoneMinder.
|
||||
#
|
||||
# (Home preset needs to be set via the camera's web gui, and is unaffected by
|
||||
# any of this.)
|
||||
#
|
||||
# So that's the limitation: DEFINE YOUR PRESETS IN ORDER THROUGH (and only
|
||||
# through!) ZM AND DON'T SKIP ANY.
|
||||
#
|
||||
|
||||
sub presetClear {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam($params, 'preset');
|
||||
my $cmd = "presetName=$preset&command=del";
|
||||
my $url = '/eng/admin/cam_control.cgi';
|
||||
Debug('presetClear: ' . $preset . ' cmd: ' . $cmd);
|
||||
$self->sendCmdPost($url,{presetName=>$preset, command=>'del'});
|
||||
}
|
||||
|
||||
sub presetSet {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam($params, 'preset');
|
||||
my $cmd = "presetName=$preset&command=add";
|
||||
my $url = '/eng/admin/cam_control.cgi';
|
||||
Debug('presetSet ' . $preset . ' cmd: ' . $cmd);
|
||||
$self->sendCmdPost($url,{presetName=>$preset, command=>'add', Submit=>'Add'});
|
||||
}
|
||||
|
||||
sub presetGoto {
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset = $self->getParam($params, 'preset');
|
||||
$preset = $preset - 1;
|
||||
Debug("Goto Preset $preset");
|
||||
my $cmd = "goto_preset_position&index=$preset";
|
||||
$self->sendCmd($cmd);
|
||||
}
|
||||
|
||||
sub presetHome {
|
||||
my $self = shift;
|
||||
Debug('Home Preset');
|
||||
my $cmd = 'go_home';
|
||||
$self->sendCmd($cmd);
|
||||
}
|
||||
|
||||
#
|
||||
# **** IR CONTROLS ****
|
||||
#
|
||||
#
|
||||
# Wake: Force IR on, always. (always night mode)
|
||||
#
|
||||
# Sleep: Force IR off, always. (always day mode)
|
||||
#
|
||||
# Reset: Automatic IR mode. (day/night mode determined by camera)
|
||||
#
|
||||
|
||||
sub wake {
|
||||
# force IR on ("always night mode")
|
||||
|
||||
my $self = shift;
|
||||
my $url = '/eng/admin/adv_audiovideo.cgi';
|
||||
my $cmd = 'irMode=3';
|
||||
|
||||
Debug('Wake -- IR on');
|
||||
|
||||
$self->sendCmdPost($url,$cmd);
|
||||
}
|
||||
|
||||
sub sleep {
|
||||
# force IR off ("always day mode")
|
||||
|
||||
my $self = shift;
|
||||
my $url = '/eng/admin/adv_audiovideo.cgi';
|
||||
my $cmd = 'irMode=2';
|
||||
|
||||
Debug('Sleep -- IR off');
|
||||
|
||||
$self->sendCmdPost($url,$cmd);
|
||||
}
|
||||
|
||||
sub reset {
|
||||
# IR auto
|
||||
|
||||
my $self=shift;
|
||||
my $url = '/eng/admin/adv_audiovideo.cgi';
|
||||
my $cmd = 'irMode=0';
|
||||
|
||||
Debug('Reset -- IR auto');
|
||||
|
||||
$self->sendCmdPost($url,$cmd);
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
ZoneMinder::Control::Trendnet - Perl module for Trendnet cameras
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use ZoneMinder::Control::Trendnet;
|
||||
place this in /usr/share/perl5/ZoneMinder/Control
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains the implementation of the Trendnet # IP camera control
|
||||
protocol. Has been tested with TV-IP862IC
|
||||
|
||||
Under control capability:
|
||||
|
||||
* Main: Can wake, can sleep, can reset
|
||||
* Move: Can move, can move diagonally, can move mapped, can move relative
|
||||
* Pan: Can pan
|
||||
* Tilt: Can tilt
|
||||
* Presets: Has presets, num presets 20, has home preset (don't set presets via camera's web server, only set via ZM.)
|
||||
|
||||
Under control tab in the monitor itself:
|
||||
|
||||
Controllable
|
||||
Control type is the name you gave it in control capability above
|
||||
Control address is the camera's ip address AND web port. example: 192.168.1.1:80
|
||||
You can also put the authentication information here and change the
|
||||
protocol to https using something like https://admin:password@192.168.1.1:80
|
||||
|
||||
=head2 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2018 ZoneMinder LLC
|
||||
|
||||
This library 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 library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
=cut
|
|
@ -41,17 +41,18 @@ our @ISA = qw(Exporter ZoneMinder::Base);
|
|||
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
|
||||
# will save memory.
|
||||
our %EXPORT_TAGS = (
|
||||
'functions' => [ qw(
|
||||
functions => [ qw(
|
||||
zmDbConnect
|
||||
zmDbDisconnect
|
||||
zmDbGetMonitors
|
||||
zmDbGetMonitor
|
||||
zmDbGetMonitorAndControl
|
||||
zmDbDo
|
||||
) ]
|
||||
);
|
||||
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
|
||||
|
||||
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
|
||||
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
|
||||
|
||||
our @EXPORT = qw();
|
||||
|
||||
|
@ -66,8 +67,6 @@ our $VERSION = $ZoneMinder::Base::VERSION;
|
|||
use ZoneMinder::Logger qw(:all);
|
||||
use ZoneMinder::Config qw(:all);
|
||||
|
||||
use Carp;
|
||||
|
||||
our $dbh = undef;
|
||||
|
||||
sub zmDbConnect {
|
||||
|
@ -93,7 +92,7 @@ sub zmDbConnect {
|
|||
|
||||
my $sslOptions = '';
|
||||
if ( $Config{ZM_DB_SSL_CA_CERT} ) {
|
||||
$sslOptions = ';'.join(';',
|
||||
$sslOptions = join(';','',
|
||||
'mysql_ssl=1',
|
||||
'mysql_ssl_ca_file='.$Config{ZM_DB_SSL_CA_CERT},
|
||||
'mysql_ssl_client_key='.$Config{ZM_DB_SSL_CLIENT_KEY},
|
||||
|
@ -102,13 +101,14 @@ sub zmDbConnect {
|
|||
}
|
||||
|
||||
eval {
|
||||
$dbh = DBI->connect( 'DBI:mysql:database='.$Config{ZM_DB_NAME}
|
||||
.$socket . $sslOptions . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '')
|
||||
$dbh = DBI->connect(
|
||||
'DBI:mysql:database='.$Config{ZM_DB_NAME}
|
||||
.$socket . $sslOptions . ($options?join(';', '', map { $_.'='.$$options{$_} } keys %{$options} ) : '')
|
||||
, $Config{ZM_DB_USER}
|
||||
, $Config{ZM_DB_PASS}
|
||||
);
|
||||
};
|
||||
if ( !$dbh or $@ ) {
|
||||
if ( !$dbh or $@ ) {
|
||||
Error("Error reconnecting to db: errstr:$DBI::errstr error val:$@");
|
||||
} else {
|
||||
$dbh->{AutoCommit} = 1;
|
||||
|
@ -125,7 +125,7 @@ sub zmDbConnect {
|
|||
|
||||
sub zmDbDisconnect {
|
||||
if ( defined( $dbh ) ) {
|
||||
$dbh->disconnect();
|
||||
$dbh->disconnect() or Error('Error disconnecting db? ' . $dbh->errstr());
|
||||
$dbh = undef;
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ sub zmDbGetMonitors {
|
|||
zmDbConnect();
|
||||
|
||||
my $function = shift || DB_MON_ALL;
|
||||
my $sql = "select * from Monitors";
|
||||
my $sql = 'SELECT * FROM Monitors';
|
||||
|
||||
if ( $function ) {
|
||||
if ( $function == DB_MON_CAPT ) {
|
||||
|
@ -156,26 +156,38 @@ sub zmDbGetMonitors {
|
|||
$sql .= " where Function = 'Nodect'";
|
||||
}
|
||||
}
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or croak( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
my $res = $sth->execute()
|
||||
or croak( "Can't execute '$sql': ".$sth->errstr() );
|
||||
my $sth = $dbh->prepare_cached( $sql );
|
||||
if ( ! $sth ) {
|
||||
Error("Can't prepare '$sql': ".$dbh->errstr());
|
||||
return undef;
|
||||
}
|
||||
my $res = $sth->execute();
|
||||
if ( ! $res ) {
|
||||
Error("Can't execute '$sql': ".$sth->errstr());
|
||||
return undef;
|
||||
}
|
||||
|
||||
my @monitors;
|
||||
while( my $monitor = $sth->fetchrow_hashref() ) {
|
||||
push( @monitors, $monitor );
|
||||
}
|
||||
$sth->finish();
|
||||
return( \@monitors );
|
||||
return \@monitors;
|
||||
}
|
||||
|
||||
sub zmSQLExecute {
|
||||
my $sql = shift;
|
||||
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or croak( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
my $res = $sth->execute( @_ )
|
||||
or croak( "Can't execute '$sql': ".$sth->errstr() );
|
||||
|
||||
my $sth = $dbh->prepare_cached( $sql );
|
||||
if ( ! $sth ) {
|
||||
Error("Can't prepare '$sql': ".$dbh->errstr());
|
||||
return undef;
|
||||
}
|
||||
my $res = $sth->execute( @_ );
|
||||
if ( ! $res ) {
|
||||
Error("Can't execute '$sql': ".$sth->errstr());
|
||||
return undef;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -184,16 +196,24 @@ sub zmDbGetMonitor {
|
|||
|
||||
my $id = shift;
|
||||
|
||||
return( undef ) if ( !defined($id) );
|
||||
if ( !defined($id) ) {
|
||||
Error('Undefined id in zmDbgetMonitor');
|
||||
return undef ;
|
||||
}
|
||||
|
||||
my $sql = "select * from Monitors where Id = ?";
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or croak( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
my $res = $sth->execute( $id )
|
||||
or croak( "Can't execute '$sql': ".$sth->errstr() );
|
||||
my $sql = 'SELECT * FROM Monitors WHERE Id = ?';
|
||||
my $sth = $dbh->prepare_cached($sql);
|
||||
if ( !$sth ) {
|
||||
Error("Can't prepare '$sql': ".$dbh->errstr());
|
||||
return undef;
|
||||
}
|
||||
my $res = $sth->execute($id);
|
||||
if ( $res ) {
|
||||
Error("Can't execute '$sql': ".$sth->errstr());
|
||||
return undef;
|
||||
}
|
||||
my $monitor = $sth->fetchrow_hashref();
|
||||
|
||||
return( $monitor );
|
||||
return $monitor;
|
||||
}
|
||||
|
||||
sub zmDbGetMonitorAndControl {
|
||||
|
@ -201,25 +221,28 @@ sub zmDbGetMonitorAndControl {
|
|||
|
||||
my $id = shift;
|
||||
|
||||
return( undef ) if ( !defined($id) );
|
||||
return undef if !defined($id);
|
||||
|
||||
my $sql = "SELECT C.*,M.*,C.Protocol
|
||||
my $sql = 'SELECT C.*,M.*,C.Protocol
|
||||
FROM Monitors as M
|
||||
INNER JOIN Controls as C on (M.ControlId = C.Id)
|
||||
WHERE M.Id = ?"
|
||||
WHERE M.Id = ?'
|
||||
;
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
my $res = $sth->execute( $id )
|
||||
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
||||
my $sth = $dbh->prepare_cached($sql);
|
||||
if ( !$sth ) {
|
||||
Error("Can't prepare '$sql': ".$dbh->errstr());
|
||||
return undef;
|
||||
}
|
||||
my $res = $sth->execute( $id );
|
||||
if ( !$res ) {
|
||||
Error("Can't execute '$sql': ".$sth->errstr());
|
||||
return undef;
|
||||
}
|
||||
my $monitor = $sth->fetchrow_hashref();
|
||||
|
||||
return( $monitor );
|
||||
return $monitor;
|
||||
}
|
||||
|
||||
sub start_transaction {
|
||||
#my ( $caller, undef, $line ) = caller;
|
||||
#$openprint::log->debug("Called start_transaction from $caller : $line");
|
||||
my $d = shift;
|
||||
$d = $dbh if ! $d;
|
||||
my $ac = $d->{AutoCommit};
|
||||
|
@ -228,68 +251,54 @@ sub start_transaction {
|
|||
} # end sub start_transaction
|
||||
|
||||
sub end_transaction {
|
||||
#my ( $caller, undef, $line ) = caller;
|
||||
#$openprint::log->debug("Called end_transaction from $caller : $line");
|
||||
my ( $d, $ac ) = @_;
|
||||
if ( ! defined $ac ) {
|
||||
Error("Undefined ac");
|
||||
}
|
||||
my ( $d, $ac ) = @_;
|
||||
if ( ! defined $ac ) {
|
||||
Error("Undefined ac");
|
||||
}
|
||||
$d = $dbh if ! $d;
|
||||
if ( $ac ) {
|
||||
#$log->debug("Committing");
|
||||
$d->commit();
|
||||
} # end if
|
||||
$d->{AutoCommit} = $ac;
|
||||
} # end sub end_transaction
|
||||
|
||||
# Basic execution of $dbh->do but with some pretty logging of the sql on error.
|
||||
# Returns 1 on success, 0 on error
|
||||
sub zmDbDo {
|
||||
my $sql = shift;
|
||||
if ( ! $dbh->do($sql, undef, @_) ) {
|
||||
$sql =~ s/\?/'%s'/;
|
||||
Error(sprintf("Failed $sql :", @_).$dbh->errstr());
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
# Below is stub documentation for your module. You'd better edit it!
|
||||
|
||||
=head1 NAME
|
||||
|
||||
ZoneMinder::Database - Perl extension for blah blah blah
|
||||
ZoneMinder::Database - Perl module containing database functions used in ZM
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use ZoneMinder::Database;
|
||||
blah blah blah
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Stub documentation for ZoneMinder, created by h2xs. It looks like the
|
||||
author of the extension was negligent enough to leave the stub
|
||||
unedited.
|
||||
|
||||
Blah blah blah.
|
||||
|
||||
=head2 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
Mention other useful documentation such as the documentation of
|
||||
related modules or operating system documentation (such as man pages
|
||||
in UNIX), or any relevant external documentation such as RFCs or
|
||||
standards.
|
||||
|
||||
If you have a mailing list set up for your module, mention it here.
|
||||
|
||||
If you have a web site set up for your module, mention it here.
|
||||
zmDbConnect
|
||||
zmDbDisconnect
|
||||
zmDbGetMonitors
|
||||
zmDbGetMonitor
|
||||
zmDbGetMonitorAndControl
|
||||
zmDbDo
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2001-2008 Philip Coombes
|
||||
|
||||
This library is free software; you can redistribute it and/or modify
|
||||
it under the same terms as Perl itself, either Perl version 5.8.3 or,
|
||||
at your option, any later version of Perl 5 you may have available.
|
||||
|
||||
|
||||
=cut
|
||||
|
|
|
@ -35,7 +35,6 @@ require Date::Manip;
|
|||
require File::Find;
|
||||
require File::Path;
|
||||
require File::Copy;
|
||||
require File::Slurp;
|
||||
require File::Basename;
|
||||
require Number::Bytes::Human;
|
||||
|
||||
|
@ -71,6 +70,7 @@ $serial = $primary_key = 'Id';
|
|||
Frames
|
||||
AlarmFrames
|
||||
DefaultVideo
|
||||
SaveJPEGs
|
||||
TotScore
|
||||
AvgScore
|
||||
MaxScore
|
||||
|
@ -84,6 +84,7 @@ $serial = $primary_key = 'Id';
|
|||
StateId
|
||||
Orientation
|
||||
DiskSpace
|
||||
Scheme
|
||||
);
|
||||
|
||||
use POSIX;
|
||||
|
@ -169,6 +170,8 @@ sub Path {
|
|||
|
||||
sub Scheme {
|
||||
my $self = shift;
|
||||
$$self{Scheme} = shift if @_;
|
||||
|
||||
if ( ! $$self{Scheme} ) {
|
||||
if ( $$self{RelativePath} ) {
|
||||
if ( $$self{RelativePath} =~ /^\d+\/\d{4}\-\d{2}\-\d{2}\/\d+$/ ) {
|
||||
|
@ -239,7 +242,7 @@ sub LinkPath {
|
|||
'.'.$$event{Id}
|
||||
);
|
||||
} elsif ( $$event{Path} ) {
|
||||
if ( ( $$event{Path} =~ /^(\d+\/\d{4}\/\d{2}\/\d{2})/ ) ) {
|
||||
if ( ( $event->RelativePath() =~ /^(\d+\/\d{4}\/\d{2}\/\d{2})/ ) ) {
|
||||
$$event{LinkPath} = $1.'/.'.$$event{Id};
|
||||
} else {
|
||||
Error("Unable to get LinkPath from Path for $$event{Id} $$event{Path}");
|
||||
|
@ -255,6 +258,33 @@ sub LinkPath {
|
|||
return $$event{LinkPath};
|
||||
} # end sub LinkPath
|
||||
|
||||
sub createPath {
|
||||
makePath($_[0]->Path());
|
||||
}
|
||||
|
||||
sub createLinkPath {
|
||||
my $LinkPath = $_[0]->LinkPath();
|
||||
my $EventPath = $_[0]->EventPath();
|
||||
if ( $LinkPath ) {
|
||||
if ( !symlink($EventPath, $LinkPath) ) {
|
||||
Error("Failed symlinking $EventPath to $LinkPath");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub idPath {
|
||||
return sprintf('%s/.%d', $_[0]->Path(), $_[0]->{Id});
|
||||
}
|
||||
|
||||
sub createIdFile {
|
||||
my $event = shift;
|
||||
my $idFile = $event->idPath();
|
||||
open( my $ID_FP, '>', $idFile )
|
||||
or Error("Can't open $idFile: $!");
|
||||
close($ID_FP);
|
||||
setFileOwner($idFile);
|
||||
}
|
||||
|
||||
sub GenerateVideo {
|
||||
my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_;
|
||||
|
||||
|
@ -290,11 +320,11 @@ sub GenerateVideo {
|
|||
my $file_size = 'S'.$size;
|
||||
push( @file_parts, $file_size );
|
||||
}
|
||||
my $video_file = "$video_name-".$file_parts[0]."-".$file_parts[1].".$format";
|
||||
my $video_file = join('-', $video_name, $file_parts[0], $file_parts[1] ).'.'.$format;
|
||||
if ( $overwrite || !-s $video_file ) {
|
||||
Info( "Creating video file $video_file for event $self->{Id}\n" );
|
||||
Info("Creating video file $video_file for event $self->{Id}");
|
||||
|
||||
my $frame_rate = sprintf( "%.2f", $self->{Frames}/$self->{FullLength} );
|
||||
my $frame_rate = sprintf('%.2f', $self->{Frames}/$self->{FullLength});
|
||||
if ( $rate ) {
|
||||
if ( $rate != 1.0 ) {
|
||||
$frame_rate *= $rate;
|
||||
|
@ -325,19 +355,19 @@ sub GenerateVideo {
|
|||
.$Config{ZM_FFMPEG_OUTPUT_OPTIONS}
|
||||
." '$video_file' > ffmpeg.log 2>&1"
|
||||
;
|
||||
Debug( $command."\n" );
|
||||
Debug($command);
|
||||
my $output = qx($command);
|
||||
|
||||
my $status = $? >> 8;
|
||||
if ( $status ) {
|
||||
Error( "Unable to generate video, check $event_path/ffmpeg.log for details");
|
||||
Error("Unable to generate video, check $event_path/ffmpeg.log for details");
|
||||
return;
|
||||
}
|
||||
|
||||
Info( "Finished $video_file\n" );
|
||||
Info("Finished $video_file");
|
||||
return $event_path.'/'.$video_file;
|
||||
} else {
|
||||
Info( "Video file $video_file already exists for event $self->{Id}\n" );
|
||||
Info("Video file $video_file already exists for event $self->{Id}");
|
||||
return $event_path.'/'.$video_file;
|
||||
}
|
||||
return;
|
||||
|
@ -345,57 +375,49 @@ sub GenerateVideo {
|
|||
|
||||
sub delete {
|
||||
my $event = $_[0];
|
||||
if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) {
|
||||
my ( $caller, undef, $line ) = caller;
|
||||
Warning( "Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from $caller:$line\n" );
|
||||
return;
|
||||
}
|
||||
if ( ! -e $event->Storage()->Path() ) {
|
||||
Warning("Not deleting event because storage path doesn't exist");
|
||||
return;
|
||||
}
|
||||
Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}\n" );
|
||||
$ZoneMinder::Database::dbh->ping();
|
||||
|
||||
$ZoneMinder::Database::dbh->begin_work();
|
||||
#$event->lock_and_load();
|
||||
my $in_zmaudit = ( $0 =~ 'zmaudit.pl$');
|
||||
|
||||
{
|
||||
my $sql = 'DELETE FROM Frames WHERE EventId=?';
|
||||
my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql )
|
||||
or Error( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() );
|
||||
my $res = $sth->execute( $event->{Id} )
|
||||
or Error( "Can't execute '$sql': ".$sth->errstr() );
|
||||
$sth->finish();
|
||||
if ( ! $in_zmaudit ) {
|
||||
if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) {
|
||||
# zmfilter shouldn't delete anything in an odd situation. zmaudit will though.
|
||||
my ( $caller, undef, $line ) = caller;
|
||||
Warning("$0 Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:".
|
||||
(defined($event->{StartTime})?$event->{StartTime}:'undef')." from $caller:$line");
|
||||
return;
|
||||
}
|
||||
if ( !($event->Storage()->Path() and -e $event->Storage()->Path()) ) {
|
||||
Warning('Not deleting event because storage path doesn\'t exist');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $$event{Id} ) {
|
||||
# Need to have an event Id if we are to delete from the db.
|
||||
Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}");
|
||||
$ZoneMinder::Database::dbh->ping();
|
||||
|
||||
$ZoneMinder::Database::dbh->begin_work();
|
||||
#$event->lock_and_load();
|
||||
|
||||
ZoneMinder::Database::zmDbDo('DELETE FROM Frames WHERE EventId=?', $$event{Id});
|
||||
if ( $ZoneMinder::Database::dbh->errstr() ) {
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
return;
|
||||
}
|
||||
ZoneMinder::Database::zmDbDo('DELETE FROM Stats WHERE EventId=?', $$event{Id});
|
||||
if ( $ZoneMinder::Database::dbh->errstr() ) {
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
return;
|
||||
}
|
||||
|
||||
$sql = 'DELETE FROM Stats WHERE EventId=?';
|
||||
$sth = $ZoneMinder::Database::dbh->prepare_cached( $sql )
|
||||
or Error( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() );
|
||||
$res = $sth->execute( $event->{Id} )
|
||||
or Error( "Can't execute '$sql': ".$sth->errstr() );
|
||||
$sth->finish();
|
||||
if ( $ZoneMinder::Database::dbh->errstr() ) {
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
return;
|
||||
}
|
||||
# Do it individually to avoid locking up the table for new events
|
||||
ZoneMinder::Database::zmDbDo('DELETE FROM Events WHERE Id=?', $$event{Id});
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
}
|
||||
|
||||
# Do it individually to avoid locking up the table for new events
|
||||
{
|
||||
my $sql = 'DELETE FROM Events WHERE Id=?';
|
||||
my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql )
|
||||
or Error( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() );
|
||||
my $res = $sth->execute( $event->{Id} )
|
||||
or Error( "Can't execute '$sql': ".$sth->errstr() );
|
||||
$sth->finish();
|
||||
}
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
if ( (! $Config{ZM_OPT_FAST_DELETE}) and $event->Storage()->DoDelete() ) {
|
||||
$event->delete_files( );
|
||||
if ( ( $in_zmaudit or (!$Config{ZM_OPT_FAST_DELETE})) and $event->Storage()->DoDelete() ) {
|
||||
$event->delete_files();
|
||||
} else {
|
||||
Debug('Not deleting event files from '.$event->Path().' for speed.');
|
||||
}
|
||||
|
@ -404,11 +426,11 @@ sub delete {
|
|||
sub delete_files {
|
||||
my $event = shift;
|
||||
|
||||
my $Storage = @_ ? $_[0] : new ZoneMinder::Storage( $$event{StorageId} );
|
||||
my $Storage = @_ ? $_[0] : new ZoneMinder::Storage($$event{StorageId});
|
||||
my $storage_path = $Storage->Path();
|
||||
|
||||
if ( ! $storage_path ) {
|
||||
Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId} ");
|
||||
Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -417,13 +439,13 @@ sub delete_files {
|
|||
return;
|
||||
}
|
||||
my $event_path = $event->RelativePath();
|
||||
Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path.");
|
||||
Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path, scheme is $$event{Scheme}.");
|
||||
if ( $event_path ) {
|
||||
( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint
|
||||
( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint
|
||||
|
||||
my $deleted = 0;
|
||||
if ( $$Storage{Type} eq 's3fs' ) {
|
||||
if ( $$Storage{Type} and ( $$Storage{Type} eq 's3fs' ) ) {
|
||||
my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ );
|
||||
eval {
|
||||
require Net::Amazon::S3;
|
||||
|
@ -440,23 +462,23 @@ sub delete_files {
|
|||
if ( $bucket->delete_key($event_path) ) {
|
||||
$deleted = 1;
|
||||
} else {
|
||||
Error("Failed to delete from S3:".$s3->err . ": " . $s3->errstr);
|
||||
Error('Failed to delete from S3:'.$s3->err . ': ' . $s3->errstr);
|
||||
}
|
||||
};
|
||||
Error($@) if $@;
|
||||
}
|
||||
if ( ! $deleted ) {
|
||||
}
|
||||
if ( !$deleted ) {
|
||||
my $command = "/bin/rm -rf $storage_path/$event_path";
|
||||
ZoneMinder::General::executeShellCommand( $command );
|
||||
ZoneMinder::General::executeShellCommand($command);
|
||||
}
|
||||
}
|
||||
|
||||
if ( $event->Scheme() eq 'Deep' ) {
|
||||
my $link_path = $event->LinkPath();
|
||||
Debug("Deleting files for Event $$event{Id} from $storage_path/$link_path.");
|
||||
Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path.");
|
||||
if ( $link_path ) {
|
||||
( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint
|
||||
unlink($storage_path.'/'.$link_path) or Error( "Unable to unlink '$storage_path/$link_path': $!" );
|
||||
unlink($storage_path.'/'.$link_path) or Error("Unable to unlink '$storage_path/$link_path': $!");
|
||||
}
|
||||
}
|
||||
} # end sub delete_files
|
||||
|
@ -467,7 +489,7 @@ sub Storage {
|
|||
}
|
||||
if ( ! $_[0]{Storage} ) {
|
||||
$_[0]{Storage} = new ZoneMinder::Storage($_[0]{StorageId});
|
||||
}
|
||||
}
|
||||
return $_[0]{Storage};
|
||||
}
|
||||
|
||||
|
@ -476,7 +498,7 @@ sub check_for_in_filesystem {
|
|||
if ( $path ) {
|
||||
if ( -e $path ) {
|
||||
my @files = glob "$path/*";
|
||||
Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found " . scalar @files . " files");
|
||||
Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found " . scalar @files . ' files');
|
||||
return 1 if @files;
|
||||
} else {
|
||||
Warning("Path not found for Event $_[0]{Id} at $path");
|
||||
|
@ -488,7 +510,7 @@ sub check_for_in_filesystem {
|
|||
|
||||
sub age {
|
||||
if ( ! $_[0]{age} ) {
|
||||
if ( -e $_[0]->Path() ) {
|
||||
if ( -e $_[0]->Path() ) {
|
||||
# $^T is the time the program began running. -M is program start time - file modification time in days
|
||||
$_[0]{age} = (time() - ($^T - ((-M $_[0]->Path() ) * 24*60*60)));
|
||||
} else {
|
||||
|
@ -547,7 +569,7 @@ sub MoveTo {
|
|||
|
||||
if ( $$OldStorage{Id} != $$self{StorageId} ) {
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
return "Old Storage path changed, Event has moved somewhere else.";
|
||||
return 'Old Storage path changed, Event has moved somewhere else.';
|
||||
}
|
||||
|
||||
$$self{Storage} = $NewStorage;
|
||||
|
@ -564,6 +586,7 @@ sub MoveTo {
|
|||
my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$NewStorage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ );
|
||||
eval {
|
||||
require Net::Amazon::S3;
|
||||
require File::Slurp;
|
||||
my $s3 = Net::Amazon::S3->new( {
|
||||
aws_access_key_id => $aws_id,
|
||||
aws_secret_access_key => $aws_secret,
|
||||
|
@ -590,11 +613,11 @@ Debug("Files to move @files");
|
|||
Debug("Moving file $file to $NewPath");
|
||||
my $size = -s $file;
|
||||
if ( ! $size ) {
|
||||
Info("Not moving file with 0 size");
|
||||
Info('Not moving file with 0 size');
|
||||
}
|
||||
my $file_contents = File::Slurp::read_file($file);
|
||||
if ( ! $file_contents ) {
|
||||
die "Loaded empty file, but it had a size. Giving up";
|
||||
die 'Loaded empty file, but it had a size. Giving up';
|
||||
}
|
||||
|
||||
my $filename = $event_path.'/'.File::Basename::basename($file);
|
||||
|
@ -602,7 +625,7 @@ Debug("Files to move @files");
|
|||
die "Unable to add key for $filename";
|
||||
}
|
||||
my $duration = time - $starttime;
|
||||
Debug("PUT to S3 " . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec');
|
||||
Debug('PUT to S3 ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec');
|
||||
} # end foreach file.
|
||||
|
||||
$moved = 1;
|
||||
|
@ -656,7 +679,7 @@ Debug("Files to move @files");
|
|||
}
|
||||
|
||||
# Succeeded in copying all files, so we may now update the Event.
|
||||
$$self{StorageId} = $$NewStorage{Id};
|
||||
$$self{StorageId} = $$NewStorage{Id};
|
||||
$$self{Storage} = $NewStorage;
|
||||
$error .= $self->save();
|
||||
if ( $error ) {
|
||||
|
|
|
@ -1,27 +1,3 @@
|
|||
# ==========================================================================
|
||||
#
|
||||
# ZoneMinder General Utility Module, $Date$, $Revision$
|
||||
# Copyright (C) 2001-2008 Philip Coombes
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# ==========================================================================
|
||||
#
|
||||
# This module contains the common definitions and functions used by the rest
|
||||
# of the ZoneMinder scripts
|
||||
#
|
||||
package ZoneMinder::General;
|
||||
|
||||
use 5.006;
|
||||
|
@ -42,15 +18,13 @@ our @ISA = qw(Exporter ZoneMinder::Base);
|
|||
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
|
||||
# will save memory.
|
||||
our %EXPORT_TAGS = (
|
||||
'functions' => [ qw(
|
||||
functions => [ qw(
|
||||
executeShellCommand
|
||||
getCmdFormat
|
||||
runCommand
|
||||
setFileOwner
|
||||
getEventPath
|
||||
createEventPath
|
||||
createEvent
|
||||
deleteEventFiles
|
||||
makePath
|
||||
jsonEncode
|
||||
jsonDecode
|
||||
|
@ -58,7 +32,7 @@ our %EXPORT_TAGS = (
|
|||
);
|
||||
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
|
||||
|
||||
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
|
||||
our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
|
||||
|
||||
our @EXPORT = qw();
|
||||
|
||||
|
@ -82,74 +56,74 @@ sub executeShellCommand {
|
|||
my $output = qx( $command );
|
||||
my $status = $? >> 8;
|
||||
if ( $status || logDebugging() ) {
|
||||
Debug( "Command: $command\n" );
|
||||
Debug("Command: $command");
|
||||
chomp( $output );
|
||||
Debug( "Output: $output\n" );
|
||||
Debug("Output: $output");
|
||||
}
|
||||
return( $status );
|
||||
return $status;
|
||||
}
|
||||
|
||||
sub getCmdFormat {
|
||||
Debug( "Testing valid shell syntax\n" );
|
||||
Debug("Testing valid shell syntax");
|
||||
|
||||
my ( $name ) = getpwuid( $> );
|
||||
if ( $name eq $Config{ZM_WEB_USER} ) {
|
||||
Debug( "Running as '$name', su commands not needed\n" );
|
||||
return( "" );
|
||||
Debug("Running as '$name', su commands not needed");
|
||||
return '';
|
||||
}
|
||||
|
||||
my $null_command = "true";
|
||||
my $null_command = 'true';
|
||||
|
||||
my $prefix = "sudo -u ".$Config{ZM_WEB_USER}." ";
|
||||
my $suffix = "";
|
||||
my $prefix = 'sudo -u '.$Config{ZM_WEB_USER}.' ';
|
||||
my $suffix = '';
|
||||
my $command = $prefix.$null_command.$suffix;
|
||||
Debug( "Testing \"$command\"\n" );
|
||||
Debug("Testing \"$command\"");
|
||||
my $output = qx($command 2>&1);
|
||||
my $status = $? >> 8;
|
||||
$output //= $!;
|
||||
|
||||
if ( !$status ) {
|
||||
Debug( "Test ok, using format \"$prefix<command>$suffix\"\n" );
|
||||
Debug("Test ok, using format \"$prefix<command>$suffix\"");
|
||||
return( $prefix, $suffix );
|
||||
} else {
|
||||
chomp( $output );
|
||||
Debug( "Test failed, '$output'\n" );
|
||||
Debug("Test failed, '$output'");
|
||||
|
||||
$prefix = "su ".$Config{ZM_WEB_USER}." --shell=/bin/sh --command='";
|
||||
$suffix = "'";
|
||||
$prefix = 'su '.$Config{ZM_WEB_USER}.q` --shell=/bin/sh --command='`;
|
||||
$suffix = q`'`;
|
||||
$command = $prefix.$null_command.$suffix;
|
||||
Debug( "Testing \"$command\"\n" );
|
||||
Debug("Testing \"$command\"");
|
||||
my $output = qx($command 2>&1);
|
||||
my $status = $? >> 8;
|
||||
$output //= $!;
|
||||
|
||||
if ( !$status ) {
|
||||
Debug( "Test ok, using format \"$prefix<command>$suffix\"\n" );
|
||||
Debug("Test ok, using format \"$prefix<command>$suffix\"");
|
||||
return( $prefix, $suffix );
|
||||
} else {
|
||||
chomp( $output );
|
||||
Debug( "Test failed, '$output'\n" );
|
||||
chomp($output);
|
||||
Debug("Test failed, '$output'");
|
||||
|
||||
$prefix = "su ".$Config{ZM_WEB_USER}." -c '";
|
||||
$suffix = "'";
|
||||
$command = $prefix.$null_command.$suffix;
|
||||
Debug( "Testing \"$command\"\n" );
|
||||
Debug("Testing \"$command\"");
|
||||
$output = qx($command 2>&1);
|
||||
$status = $? >> 8;
|
||||
$output //= $!;
|
||||
|
||||
if ( !$status ) {
|
||||
Debug( "Test ok, using format \"$prefix<command>$suffix\"\n" );
|
||||
Debug("Test ok, using format \"$prefix<command>$suffix\"");
|
||||
return( $prefix, $suffix );
|
||||
} else {
|
||||
chomp( $output );
|
||||
Debug( "Test failed, '$output'\n" );
|
||||
chomp($output);
|
||||
Debug("Test failed, '$output'");
|
||||
}
|
||||
}
|
||||
}
|
||||
Error( "Unable to find valid 'su' syntax\n" );
|
||||
exit( -1 );
|
||||
}
|
||||
Error("Unable to find valid 'su' syntax");
|
||||
exit -1;
|
||||
} # end sub getCmdFormat
|
||||
|
||||
our $testedShellSyntax = 0;
|
||||
our ( $cmdPrefix, $cmdSuffix );
|
||||
|
@ -163,88 +137,32 @@ sub runCommand {
|
|||
}
|
||||
|
||||
my $command = shift;
|
||||
$command = $Config{ZM_PATH_BIN}."/".$command;
|
||||
$command = $Config{ZM_PATH_BIN}.'/'.$command;
|
||||
if ( $cmdPrefix ) {
|
||||
$command = $cmdPrefix.$command.$cmdSuffix;
|
||||
}
|
||||
Debug( "Command: $command\n" );
|
||||
Debug("Command: $command");
|
||||
my $output = qx($command);
|
||||
my $status = $? >> 8;
|
||||
chomp( $output );
|
||||
chomp($output);
|
||||
if ( $status || logDebugging() ) {
|
||||
if ( $status ) {
|
||||
Error( "Unable to run \"$command\", output is \"$output\", status is $status\n" );
|
||||
Error("Unable to run \"$command\", output is \"$output\", status is $status");
|
||||
} else {
|
||||
Debug( "Output: $output\n" );
|
||||
Debug("Output: $output");
|
||||
}
|
||||
}
|
||||
return( $output );
|
||||
}
|
||||
|
||||
sub getEventPath {
|
||||
my $event = shift;
|
||||
|
||||
my $Storage = new ZoneMinder::Storage( $$event{StorageId} );
|
||||
my $event_path = join( '/',
|
||||
$Storage->Path(),
|
||||
$event->{MonitorId},
|
||||
( $Config{ZM_USE_DEEP_STORAGE} ? strftime( "%y/%m/%d/%H/%M/%S", localtime($event->{Time}) ) : $event->{Id} ),
|
||||
);
|
||||
|
||||
return( $event_path );
|
||||
}
|
||||
return $output;
|
||||
} # end sub runCommand
|
||||
|
||||
sub createEventPath {
|
||||
#
|
||||
# WARNING assumes running from events directory
|
||||
#
|
||||
my $event = shift;
|
||||
my $Storage = new ZoneMinder::Storage( $$event{Id} );
|
||||
my $eventPath = $Storage->Path() . '/'.$event->{MonitorId};
|
||||
my $eventPath = $event->Path();
|
||||
$event->createPath();
|
||||
$event->createIdFile();
|
||||
$event->createLinkPath();
|
||||
|
||||
if ( $Config{ZM_USE_DEEP_STORAGE} ) {
|
||||
my @startTime = localtime( $event->{StartTime} );
|
||||
|
||||
my @datetimeParts = ();
|
||||
$datetimeParts[0] = sprintf( "%02d", $startTime[5]-100 );
|
||||
$datetimeParts[1] = sprintf( "%02d", $startTime[4]+1 );
|
||||
$datetimeParts[2] = sprintf( "%02d", $startTime[3] );
|
||||
$datetimeParts[3] = sprintf( "%02d", $startTime[2] );
|
||||
$datetimeParts[4] = sprintf( "%02d", $startTime[1] );
|
||||
$datetimeParts[5] = sprintf( "%02d", $startTime[0] );
|
||||
|
||||
my $datePath = join('/',@datetimeParts[0..2]);
|
||||
my $timePath = join('/',@datetimeParts[3..5]);
|
||||
|
||||
makePath( $datePath, $eventPath );
|
||||
$eventPath .= '/'.$datePath;
|
||||
|
||||
# Create event id symlink
|
||||
my $idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} );
|
||||
symlink( $timePath, $idFile )
|
||||
or Fatal( "Can't symlink $idFile -> $eventPath: $!" );
|
||||
|
||||
makePath( $timePath, $eventPath );
|
||||
$eventPath .= '/'.$timePath;
|
||||
setFileOwner( $idFile ); # Must come after directory has been created
|
||||
|
||||
# Create empty id tag file
|
||||
$idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} );
|
||||
open( my $ID_FP, ">", $idFile )
|
||||
or Fatal( "Can't open $idFile: $!" );
|
||||
close( $ID_FP );
|
||||
setFileOwner( $idFile );
|
||||
} else {
|
||||
makePath( $event->{Id}, $eventPath );
|
||||
$eventPath .= '/'.$event->{Id};
|
||||
|
||||
my $idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} );
|
||||
open( my $ID_FP, ">", $idFile )
|
||||
or Fatal( "Can't open $idFile: $!" );
|
||||
close( $ID_FP );
|
||||
setFileOwner( $idFile );
|
||||
}
|
||||
return( $eventPath );
|
||||
return $eventPath;
|
||||
}
|
||||
|
||||
use Data::Dumper;
|
||||
|
@ -268,7 +186,7 @@ sub _checkProcessOwner {
|
|||
$_setFileOwner = 0;
|
||||
}
|
||||
}
|
||||
return( $_setFileOwner );
|
||||
return $_setFileOwner;
|
||||
}
|
||||
|
||||
sub setFileOwner {
|
||||
|
@ -277,7 +195,7 @@ sub setFileOwner {
|
|||
if ( _checkProcessOwner() ) {
|
||||
chown( $_ownerUid, $_ownerGid, $file )
|
||||
or Fatal( "Can't change ownership of file '$file' to '"
|
||||
.$Config{ZM_WEB_USER}.":".$Config{ZM_WEB_GROUP}."': $!"
|
||||
.$Config{ZM_WEB_USER}.':'.$Config{ZM_WEB_GROUP}."': $!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -292,13 +210,13 @@ sub _checkForImageInfo {
|
|||
};
|
||||
$_hasImageInfo = $@?0:1;
|
||||
}
|
||||
return( $_hasImageInfo );
|
||||
return $_hasImageInfo;
|
||||
}
|
||||
|
||||
sub createEvent {
|
||||
my $event = shift;
|
||||
|
||||
Debug( "Creating event" );
|
||||
Debug('Creating event');
|
||||
#print( Dumper( $event )."\n" );
|
||||
|
||||
_checkForImageInfo();
|
||||
|
@ -481,53 +399,6 @@ sub updateEvent {
|
|||
or Fatal( "Can't execute sql '$sql': ".$sth->errstr() );
|
||||
}
|
||||
|
||||
sub deleteEventFiles {
|
||||
#
|
||||
# WARNING assumes running from events directory
|
||||
#
|
||||
my $event_id = shift;
|
||||
my $monitor_id = shift;
|
||||
$monitor_id = '*' if ( !defined($monitor_id) );
|
||||
|
||||
if ( $Config{ZM_USE_DEEP_STORAGE} ) {
|
||||
my $link_path = $monitor_id."/*/*/*/.".$event_id;
|
||||
#Debug( "LP1:$link_path" );
|
||||
my @links = glob($link_path);
|
||||
#Debug( "L:".$links[0].": $!" );
|
||||
if ( @links ) {
|
||||
( $link_path ) = ( $links[0] =~ /^(.*)$/ ); # De-taint
|
||||
#Debug( "LP2:$link_path" );
|
||||
|
||||
( my $day_path = $link_path ) =~ s/\.\d+//;
|
||||
#Debug( "DP:$day_path" );
|
||||
my $event_path = $day_path.readlink( $link_path );
|
||||
( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint
|
||||
#Debug( "EP:$event_path" );
|
||||
my $command = "/bin/rm -rf ".$event_path;
|
||||
#Debug( "C:$command" );
|
||||
executeShellCommand( $command );
|
||||
|
||||
unlink( $link_path ) or Error( "Unable to unlink '$link_path': $!" );
|
||||
my @path_parts = split( /\//, $event_path );
|
||||
for ( my $i = int(@path_parts)-2; $i >= 1; $i-- ) {
|
||||
my $delete_path = join( '/', @path_parts[0..$i] );
|
||||
#Debug( "DP$i:$delete_path" );
|
||||
my @has_files = glob( $delete_path."/*" );
|
||||
#Debug( "HF1:".$has_files[0] ) if ( @has_files );
|
||||
last if ( @has_files );
|
||||
@has_files = glob( $delete_path."/.[0-9]*" );
|
||||
#Debug( "HF2:".$has_files[0] ) if ( @has_files );
|
||||
last if ( @has_files );
|
||||
my $command = "/bin/rm -rf ".$delete_path;
|
||||
executeShellCommand( $command );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
my $command = "/bin/rm -rf $monitor_id/$event_id";
|
||||
executeShellCommand( $command );
|
||||
}
|
||||
}
|
||||
|
||||
sub makePath {
|
||||
my $path = shift;
|
||||
my $root = shift;
|
||||
|
@ -557,8 +428,8 @@ our $hasJSONAny = 0;
|
|||
sub _testJSON {
|
||||
return if ( $testedJSON );
|
||||
my $result = eval {
|
||||
require JSON::Any;
|
||||
JSON::Any->import();
|
||||
require JSON::MaybeXS;
|
||||
JSON::MaybeXS->import();
|
||||
};
|
||||
$testedJSON = 1;
|
||||
$hasJSONAny = 1 if ( $result );
|
||||
|
@ -581,7 +452,7 @@ sub jsonEncode {
|
|||
|
||||
_testJSON();
|
||||
if ( $hasJSONAny ) {
|
||||
my $string = eval { JSON::Any->objToJson( $value ) };
|
||||
my $string = eval { JSON::MaybeXS->encode_json( $value ) };
|
||||
Fatal( "Unable to encode object to JSON: $@" ) unless( $string );
|
||||
return( $string );
|
||||
}
|
||||
|
@ -616,7 +487,7 @@ sub jsonDecode {
|
|||
|
||||
_testJSON();
|
||||
if ( $hasJSONAny ) {
|
||||
my $object = eval { JSON::Any->jsonToObj( $value ) };
|
||||
my $object = eval { JSON::MaybeXS->decode_json( $value ) };
|
||||
Fatal( "Unable to decode JSON string '$value': $@" ) unless( $object );
|
||||
return( $object );
|
||||
}
|
||||
|
@ -654,7 +525,7 @@ sub jsonDecode {
|
|||
$out =~ s/=>null/=>undef/g;
|
||||
$out =~ s/`/'/g;
|
||||
$out =~ s/qx/qq/g;
|
||||
( $out ) = $out =~ m/^({.+})$/; # Detaint and check it's a valid object syntax
|
||||
( $out ) = $out =~ m/^(\{.+\})$/; # Detaint and check it's a valid object syntax
|
||||
my $result = eval $out;
|
||||
Fatal( $@ ) if ( $@ );
|
||||
return( $result );
|
||||
|
@ -666,38 +537,33 @@ __END__
|
|||
|
||||
=head1 NAME
|
||||
|
||||
ZoneMinder::Database - Perl extension for blah blah blah
|
||||
ZoneMinder::General - Utility Functions for ZoneMinder
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use ZoneMinder::Database;
|
||||
use ZoneMinder::General;
|
||||
blah blah blah
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Stub documentation for ZoneMinder, created by h2xs. It looks like the
|
||||
author of the extension was negligent enough to leave the stub
|
||||
unedited.
|
||||
|
||||
Blah blah blah.
|
||||
This module contains the common definitions and functions used by the rest
|
||||
of the ZoneMinder scripts
|
||||
|
||||
=head2 EXPORT
|
||||
|
||||
None by default.
|
||||
functions => [ qw(
|
||||
executeShellCommand
|
||||
getCmdFormat
|
||||
runCommand
|
||||
setFileOwner
|
||||
createEventPath
|
||||
createEvent
|
||||
makePath
|
||||
jsonEncode
|
||||
jsonDecode
|
||||
) ]
|
||||
|
||||
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
Mention other useful documentation such as the documentation of
|
||||
related modules or operating system documentation (such as man pages
|
||||
in UNIX), or any relevant external documentation such as RFCs or
|
||||
standards.
|
||||
|
||||
If you have a mailing list set up for your module, mention it here.
|
||||
|
||||
If you have a web site set up for your module, mention it here.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
|
||||
|
@ -706,9 +572,18 @@ Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
|
|||
|
||||
Copyright (C) 2001-2008 Philip Coombes
|
||||
|
||||
This library is free software; you can redistribute it and/or modify
|
||||
it under the same terms as Perl itself, either Perl version 5.8.3 or,
|
||||
at your option, any later version of Perl 5 you may have available.
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
=cut
|
||||
|
|
|
@ -483,7 +483,7 @@ sub openSyslog {
|
|||
|
||||
sub closeSyslog {
|
||||
my $this = shift;
|
||||
#closelog();
|
||||
closelog();
|
||||
}
|
||||
|
||||
sub logFile {
|
||||
|
@ -523,64 +523,70 @@ sub logPrint {
|
|||
my $this = shift;
|
||||
my $level = shift;
|
||||
my $string = shift;
|
||||
my ($caller, undef, $line) = @_ ? @_ : caller;
|
||||
|
||||
if ( $level <= $this->{effectiveLevel} ) {
|
||||
$string =~ s/[\r\n]+$//g;
|
||||
|
||||
my $code = $codes{$level};
|
||||
if ( $level <= $this->{syslogLevel} ) {
|
||||
syslog($priorities{$level}, $codes{$level}.' [%s]', $string);
|
||||
}
|
||||
|
||||
my ($seconds, $microseconds) = gettimeofday();
|
||||
my $message = sprintf(
|
||||
'%s.%06d %s[%d].%s [%s]'
|
||||
, strftime('%x %H:%M:%S', localtime($seconds))
|
||||
, $microseconds
|
||||
, $this->{id}
|
||||
, $$
|
||||
, $code
|
||||
, $string
|
||||
);
|
||||
if ( $this->{trace} ) {
|
||||
$message = Carp::shortmess($message);
|
||||
} else {
|
||||
$message = $message."\n";
|
||||
if ( $level <= $this->{fileLevel} or $level <= $this->{termLevel} ) {
|
||||
my $message = sprintf(
|
||||
'%s.%06d %s[%d].%s [%s:%d] [%s]'
|
||||
, strftime('%x %H:%M:%S', localtime($seconds))
|
||||
, $microseconds
|
||||
, $this->{id}
|
||||
, $$
|
||||
, $codes{$level}
|
||||
, $caller
|
||||
, $line
|
||||
, $string
|
||||
);
|
||||
if ( $this->{trace} ) {
|
||||
$message = Carp::shortmess($message);
|
||||
} else {
|
||||
$message = $message."\n";
|
||||
}
|
||||
print($LOGFILE $message) if $level <= $this->{fileLevel};
|
||||
print(STDERR $message) if $level <= $this->{termLevel};
|
||||
}
|
||||
if ( $level <= $this->{syslogLevel} ) {
|
||||
syslog($priorities{$level}, $code.' [%s]', $string);
|
||||
}
|
||||
print($LOGFILE $message) if $level <= $this->{fileLevel};
|
||||
print(STDERR $message) if $level <= $this->{termLevel};
|
||||
|
||||
if ( $level <= $this->{databaseLevel} ) {
|
||||
if ( ! ( $this->{dbh} and $this->{dbh}->ping() ) ) {
|
||||
if ( ! ( $ZoneMinder::Database::dbh and $ZoneMinder::Database::dbh->ping() ) ) {
|
||||
$this->{sth} = undef;
|
||||
|
||||
my $databaseLevel = $this->{databaseLevel};
|
||||
# Turn this off because zDbConnect will do logging calls.
|
||||
my $oldlevel = $this->{databaseLevel};
|
||||
$this->{databaseLevel} = NOLOG;
|
||||
if ( ! ( $this->{dbh} = ZoneMinder::Database::zmDbConnect(1) ) ) {
|
||||
if ( ! ZoneMinder::Database::zmDbConnect() ) {
|
||||
#print(STDERR "Can't log to database: ");
|
||||
return;
|
||||
}
|
||||
$this->{databaseLevel} = $databaseLevel;
|
||||
$this->{databaseLevel} = $oldlevel;
|
||||
}
|
||||
|
||||
my $sql = 'INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, NULL )';
|
||||
$this->{sth} = $this->{dbh}->prepare_cached($sql) if ! $this->{sth};
|
||||
my $sql = 'INSERT INTO Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, NULL )';
|
||||
$this->{sth} = $ZoneMinder::Database::dbh->prepare_cached($sql) if ! $this->{sth};
|
||||
if ( !$this->{sth} ) {
|
||||
$this->{databaseLevel} = NOLOG;
|
||||
Error("Can't prepare log entry '$sql': ".$this->{dbh}->errstr());
|
||||
Error("Can't prepare log entry '$sql': ".$ZoneMinder::Database::dbh->errstr());
|
||||
return;
|
||||
}
|
||||
|
||||
my $res = $this->{sth}->execute($seconds+($microseconds/1000000.0)
|
||||
, $this->{id}
|
||||
, $$
|
||||
, $level
|
||||
, $code
|
||||
, $string
|
||||
, $this->{fileName}
|
||||
my $res = $this->{sth}->execute(
|
||||
$seconds+($microseconds/1000000.0),
|
||||
$this->{id},
|
||||
($Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : undef),
|
||||
$$,
|
||||
$level,
|
||||
$codes{$level},
|
||||
$string,
|
||||
$this->{fileName},
|
||||
);
|
||||
if ( !$res ) {
|
||||
$this->{databaseLevel} = NOLOG;
|
||||
Error("Can't execute log entry '$sql': ".$this->{dbh}->errstr());
|
||||
Error("Can't execute log entry '$sql': ".$ZoneMinder::Database::dbh->errstr());
|
||||
}
|
||||
} # end if doing db logging
|
||||
} # end if level < effectivelevel
|
||||
|
@ -658,47 +664,49 @@ sub Dump {
|
|||
|
||||
sub debug {
|
||||
my $log = shift;
|
||||
$log->logPrint(DEBUG, @_);
|
||||
}
|
||||
$log->logPrint(DEBUG, @_, caller);
|
||||
}
|
||||
|
||||
sub Debug( @ ) {
|
||||
fetch()->logPrint(DEBUG, @_);
|
||||
fetch()->logPrint(DEBUG, @_, caller);
|
||||
}
|
||||
|
||||
sub Info( @ ) {
|
||||
fetch()->logPrint(INFO, @_);
|
||||
fetch()->logPrint(INFO, @_, caller);
|
||||
}
|
||||
sub info {
|
||||
my $log = shift;
|
||||
$log->logPrint(INFO, @_);
|
||||
$log->logPrint(INFO, @_, caller);
|
||||
}
|
||||
|
||||
sub Warning( @ ) {
|
||||
fetch()->logPrint(WARNING, @_);
|
||||
fetch()->logPrint(WARNING, @_, caller);
|
||||
}
|
||||
sub warn {
|
||||
my $log = shift;
|
||||
$log->logPrint(WARNING, @_);
|
||||
$log->logPrint(WARNING, @_, caller);
|
||||
}
|
||||
|
||||
sub Error( @ ) {
|
||||
fetch()->logPrint(ERROR, @_);
|
||||
fetch()->logPrint(ERROR, @_, caller);
|
||||
}
|
||||
sub error {
|
||||
my $log = shift;
|
||||
$log->logPrint(ERROR, @_);
|
||||
$log->logPrint(ERROR, @_, caller);
|
||||
}
|
||||
|
||||
sub Fatal( @ ) {
|
||||
fetch()->logPrint(FATAL, @_);
|
||||
fetch()->logPrint(FATAL, @_, caller);
|
||||
if ( $SIG{TERM} and ( $SIG{TERM} ne 'DEFAULT' ) ) {
|
||||
$SIG{TERM}();
|
||||
}
|
||||
# I think if we don't disconnect we will leave sockets around in TIME_WAIT
|
||||
ZoneMinder::Database::zmDbDisconnect();
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
sub Panic( @ ) {
|
||||
fetch()->logPrint(PANIC, @_);
|
||||
fetch()->logPrint(PANIC, @_, caller);
|
||||
confess($_[0]);
|
||||
}
|
||||
|
||||
|
|
|
@ -161,7 +161,6 @@ our $mem_data = {
|
|||
format => { type=>'uint8', seq=>$mem_seq++ },
|
||||
imagesize => { type=>'uint32', seq=>$mem_seq++ },
|
||||
epadding1 => { type=>'uint32', seq=>$mem_seq++ },
|
||||
epadding2 => { type=>'uint32', seq=>$mem_seq++ },
|
||||
startup_time => { type=>'time_t64', seq=>$mem_seq++ },
|
||||
last_write_time => { type=>'time_t64', seq=>$mem_seq++ },
|
||||
last_read_time => { type=>'time_t64', seq=>$mem_seq++ },
|
||||
|
@ -813,7 +812,7 @@ shared_data The general mapped memory section
|
|||
size The size, in bytes, of this section
|
||||
valid Flag indicating whether this section has been initialised
|
||||
active Flag indicating whether this monitor is active (enabled/disabled)
|
||||
signal Flag indicating whether this monitor is reciving a valid signal
|
||||
signal Flag indicating whether this monitor is receiving a valid signal
|
||||
state The current monitor state, see the STATE constants below
|
||||
last_write_index The last index, in the image buffer, that an image has been saved to
|
||||
last_read_index The last index, in the image buffer, that an image has been analysed from
|
||||
|
|
|
@ -456,7 +456,7 @@ sub transform {
|
|||
sub to_string {
|
||||
my $type = ref($_[0]);
|
||||
my $fields = eval '\%'.$type.'::fields';
|
||||
return $type . ': '. join(' ' , map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } keys %$fields );
|
||||
return $type . ': '. join(' ' , map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } sort { $a cmp $b } keys %$fields );
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# ==========================================================================
|
||||
#
|
||||
|
@ -53,64 +53,65 @@ $primary_key = 'Id';
|
|||
#__PACKAGE__->primary_key('Id');
|
||||
|
||||
sub find {
|
||||
shift if $_[0] eq 'ZoneMinder::Storage';
|
||||
my %sql_filters = @_;
|
||||
shift if $_[0] eq 'ZoneMinder::Storage';
|
||||
my %sql_filters = @_;
|
||||
|
||||
my $sql = 'SELECT * FROM Storage';
|
||||
my @sql_filters;
|
||||
my @sql_values;
|
||||
my $sql = 'SELECT * FROM Storage';
|
||||
my @sql_filters;
|
||||
my @sql_values;
|
||||
|
||||
if ( exists $sql_filters{Id} ) {
|
||||
push @sql_filters , ' Id=? ';
|
||||
push @sql_values, $sql_filters{Id};
|
||||
}
|
||||
if ( exists $sql_filters{Name} ) {
|
||||
push @sql_filters , ' Name = ? ';
|
||||
push @sql_values, $sql_filters{Name};
|
||||
}
|
||||
if ( exists $sql_filters{ServerId} ) {
|
||||
push @sql_filters, ' Id IN ( SELECT StorageId FROM Monitors WHERE ServerId=? )';
|
||||
push @sql_values, $sql_filters{ServerId};
|
||||
}
|
||||
if ( exists $sql_filters{Id} ) {
|
||||
push @sql_filters , ' Id=? ';
|
||||
push @sql_values, $sql_filters{Id};
|
||||
}
|
||||
if ( exists $sql_filters{Name} ) {
|
||||
push @sql_filters , ' Name = ? ';
|
||||
push @sql_values, $sql_filters{Name};
|
||||
}
|
||||
if ( exists $sql_filters{ServerId} ) {
|
||||
push @sql_filters, ' ServerId = ?';
|
||||
push @sql_values, $sql_filters{ServerId};
|
||||
}
|
||||
|
||||
|
||||
$sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters;
|
||||
$sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit};
|
||||
$sql .= ' WHERE ' . join(' AND ', @sql_filters) if @sql_filters;
|
||||
$sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit};
|
||||
|
||||
my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() );
|
||||
my $res = $sth->execute( @sql_values )
|
||||
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
||||
my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() );
|
||||
my $res = $sth->execute( @sql_values )
|
||||
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
||||
|
||||
my @results;
|
||||
my @results;
|
||||
|
||||
while( my $db_filter = $sth->fetchrow_hashref() ) {
|
||||
my $filter = new ZoneMinder::Storage( $$db_filter{Id}, $db_filter );
|
||||
push @results, $filter;
|
||||
} # end while
|
||||
return @results;
|
||||
while( my $db_filter = $sth->fetchrow_hashref() ) {
|
||||
my $filter = new ZoneMinder::Storage( $$db_filter{Id}, $db_filter );
|
||||
push @results, $filter;
|
||||
} # end while
|
||||
Debug("SQL: $sql returned " . @results . ' results');
|
||||
return @results;
|
||||
}
|
||||
|
||||
sub find_one {
|
||||
my @results = find(@_);
|
||||
return $results[0] if @results;
|
||||
my @results = find(@_);
|
||||
return $results[0] if @results;
|
||||
}
|
||||
|
||||
sub Path {
|
||||
if ( @_ > 1 ) {
|
||||
$_[0]{Path} = $_[1];
|
||||
}
|
||||
if ( ! ( $_[0]{Id} or $_[0]{Path} ) ) {
|
||||
$_[0]{Path} = ($Config{ZM_DIR_EVENTS}=~/^\//) ? $Config{ZM_DIR_EVENTS} : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS})
|
||||
}
|
||||
return $_[0]{Path};
|
||||
if ( @_ > 1 ) {
|
||||
$_[0]{Path} = $_[1];
|
||||
}
|
||||
if ( ! ( $_[0]{Id} or $_[0]{Path} ) ) {
|
||||
$_[0]{Path} = ($Config{ZM_DIR_EVENTS}=~/^\//) ? $Config{ZM_DIR_EVENTS} : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS})
|
||||
}
|
||||
return $_[0]{Path};
|
||||
} # end sub Path
|
||||
|
||||
sub Name {
|
||||
if ( @_ > 1 ) {
|
||||
$_[0]{Name} = $_[1];
|
||||
}
|
||||
return $_[0]{Name};
|
||||
if ( @_ > 1 ) {
|
||||
$_[0]{Name} = $_[1];
|
||||
}
|
||||
return $_[0]{Name};
|
||||
} # end sub Path
|
||||
|
||||
sub DoDelete {
|
||||
|
|
|
@ -67,6 +67,7 @@ my $level = 1;
|
|||
my $monitor_id = 0;
|
||||
my $version;
|
||||
my $force = 0;
|
||||
my $server_id = undef;
|
||||
my $storage_id = undef;
|
||||
|
||||
logInit();
|
||||
|
@ -78,6 +79,7 @@ GetOptions(
|
|||
level =>\$level,
|
||||
'monitor_id=i' =>\$monitor_id,
|
||||
report =>\$report,
|
||||
'server_id=i' =>\$server_id,
|
||||
'storage_id=i' =>\$storage_id,
|
||||
version =>\$version
|
||||
) or pod2usage(-exitstatus => -1);
|
||||
|
@ -181,13 +183,15 @@ MAIN: while( $loop ) {
|
|||
Term();
|
||||
}
|
||||
Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}");
|
||||
} elsif ( $Config{ZM_SERVER_ID} ) {
|
||||
@Storage_Areas = ZoneMinder::Storage->find( ServerId => $Config{ZM_SERVER_ID} );
|
||||
} elsif ( $server_id ) {
|
||||
@Storage_Areas = ZoneMinder::Storage->find( ServerId => $server_id );
|
||||
if ( ! @Storage_Areas ) {
|
||||
Error("No Storage Area found with ServerId =" . $Config{ZM_SERVER_ID});
|
||||
Error("No Storage Area found with ServerId =" . $server_id);
|
||||
Term();
|
||||
}
|
||||
Info("Auditing All Storage Areas on Server " . $Storage_Areas[0]->Server()->Name());
|
||||
foreach my $Storage ( @Storage_Areas ) {
|
||||
Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name() );
|
||||
}
|
||||
} else {
|
||||
@Storage_Areas = ZoneMinder::Storage->find();
|
||||
Info("Auditing All Storage Areas");
|
||||
|
@ -252,46 +256,127 @@ MAIN: while( $loop ) {
|
|||
foreach my $day_dir ( @day_dirs ) {
|
||||
Debug( "Checking day dir $day_dir" );
|
||||
( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint
|
||||
if ( ! chdir( $day_dir ) ) {
|
||||
Error( "Can't chdir to '$$Storage{Path}/$day_dir': $!" );
|
||||
if ( !chdir($day_dir) ) {
|
||||
Error("Can't chdir to '$$Storage{Path}/$day_dir': $!");
|
||||
next;
|
||||
}
|
||||
if ( ! opendir( DIR, '.' ) ) {
|
||||
Error( "Can't open directory '$$Storage{Path}/$day_dir': $!" );
|
||||
if ( ! opendir(DIR, '.') ) {
|
||||
Error("Can't open directory '$$Storage{Path}/$day_dir': $!");
|
||||
next;
|
||||
}
|
||||
my %event_ids_by_path;
|
||||
|
||||
my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR );
|
||||
closedir( DIR );
|
||||
Debug("Have " . @event_links . ' event links');
|
||||
closedir(DIR);
|
||||
|
||||
my $count = 0;
|
||||
foreach my $event_link ( @event_links ) {
|
||||
if ( $event_link =~ /[^\d\.]/ ) {
|
||||
# Event links start with a period and consist of the digits of the event id. Anything else is not an event link
|
||||
my ($event_id) = $event_link =~ /^\.(\d+)$/;
|
||||
if ( !$event_id ) {
|
||||
Warning("Non-event link found $event_link in $day_dir, skipping");
|
||||
next;
|
||||
}
|
||||
Debug( "Checking link $event_link" );
|
||||
( my $event = $event_link ) =~ s/^.*\.//;
|
||||
Debug("Checking link $event_link");
|
||||
#Event path is hour/minute/sec
|
||||
my $event_path = readlink( $event_link );
|
||||
my $event_path = readlink($event_link);
|
||||
|
||||
if ( !($event_path and -e $event_path) ) {
|
||||
aud_print( "Event link $day_dir/$event_link does not point to valid target" );
|
||||
aud_print("Event link $day_dir/$event_link does not point to valid target at $event_path");
|
||||
if ( confirm() ) {
|
||||
( $event_link ) = ( $event_link =~ /^(.*)$/ ); # De-taint
|
||||
unlink( $event_link );
|
||||
unlink($event_link);
|
||||
$cleaned = 1;
|
||||
}
|
||||
} else {
|
||||
Debug( "Checking link $event_link points to $event_path " );
|
||||
my $Event = $fs_events->{$event} = new ZoneMinder::Event();
|
||||
$$Event{Id} = $event;
|
||||
$$Event{Path} = join('/', $Storage->Path(), $day_dir,$event_path);
|
||||
$$Event{RelativePath} = join('/', $day_dir,$event_path);
|
||||
$event_ids_by_path{$event_path} = $event_id;
|
||||
|
||||
Debug("Checking link $event_link points to $event_path ");
|
||||
my $Event = $fs_events->{$event_id} = new ZoneMinder::Event();
|
||||
$$Event{Id} = $event_id;
|
||||
$$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_path);
|
||||
$$Event{RelativePath} = join('/', $day_dir, $event_path);
|
||||
$$Event{Scheme} = 'Deep';
|
||||
$Event->MonitorId( $monitor_dir );
|
||||
$Event->StorageId( $Storage->Id() );
|
||||
$Event->DiskSpace( undef );
|
||||
} # event path exists
|
||||
} # end foreach event_link
|
||||
|
||||
# Now check for events that have lost their link
|
||||
|
||||
my @time_dirs = glob('[0-9][0-9]/[0-9][0-9]/[0-9][0-9]');
|
||||
foreach my $event_dir ( @time_dirs ) {
|
||||
Debug("Checking time dir $event_dir");
|
||||
( $event_dir ) = ( $event_dir =~ /^(.*)$/ ); # De-taint
|
||||
|
||||
my $event_id = undef;
|
||||
|
||||
if ( ! opendir(DIR, $event_dir) ) {
|
||||
Error("Can't open directory '$$Storage{Path}/$day_dir': $!");
|
||||
next;
|
||||
}
|
||||
my @contents = readdir( DIR );
|
||||
Debug("Have " . @contents . " files in $day_dir/$event_dir");
|
||||
closedir(DIR);
|
||||
|
||||
my @mp4_files = grep( /^\d+\-video.mp4$/, @contents);
|
||||
foreach my $mp4_file ( @mp4_files ) {
|
||||
my ( $id ) = $mp4_file =~ /^([0-9]+)\-video\.mp4$/;
|
||||
if ( $id ) {
|
||||
$event_id = $id;
|
||||
Debug("Got event id from mp4 file $mp4_file => $event_id");
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $event_id ) {
|
||||
# Look for .id file
|
||||
my @hidden_files = grep( /^\.\d+$/, @contents);
|
||||
Debug("Have " . @hidden_files . ' hidden files');
|
||||
if ( @hidden_files ) {
|
||||
( $event_id ) = $hidden_files[0] =~ /^.(\d+)$/;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $event_id and ! $fs_events->{$event_id} ) {
|
||||
my $Event = $fs_events->{$event_id} = new ZoneMinder::Event();
|
||||
$$Event{Id} = $event_id;
|
||||
$$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_dir);
|
||||
$$Event{RelativePath} = join('/', $day_dir, $event_dir);
|
||||
$$Event{Scheme} = 'Deep';
|
||||
$Event->MonitorId( $monitor_dir );
|
||||
$Event->StorageId( $Storage->Id() );
|
||||
$Event->DiskSpace( undef );
|
||||
if ( ! $event_ids_by_path{$event_dir} ) {
|
||||
Warning("No event link found at ".$Event->LinkPath() ." for " . $Event->to_string());
|
||||
}
|
||||
} else {
|
||||
if ( $event_ids_by_path{$event_dir} ) {
|
||||
Debug("Have an event link, leaving dir alone.");
|
||||
next;
|
||||
}
|
||||
my ( undef, $year, $month, $day ) = split('/', $day_dir);
|
||||
$year += 2000;
|
||||
my ( $hour, $minute, $second ) = split('/', $event_dir);
|
||||
my $StartTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second);
|
||||
my $Event = ZoneMinder::Event->find_one(
|
||||
MonitorId=>$monitor_dir,
|
||||
StartTime=>$StartTime,
|
||||
);
|
||||
if ( $Event ) {
|
||||
Debug("Found event matching starttime on monitor $monitor_dir at $StartTime: " . $Event->to_string());
|
||||
next;
|
||||
}
|
||||
aud_print("Deleting event directories with no event id information at $day_dir/$event_dir");
|
||||
if ( confirm() ) {
|
||||
my $command = "rm -rf $event_dir";
|
||||
executeShellCommand( $command );
|
||||
$cleaned = 1;
|
||||
}
|
||||
} # end if able to find id
|
||||
} # end foreach event_dir without link
|
||||
chdir( $Storage->Path() );
|
||||
} # end foreach day dir
|
||||
}
|
||||
|
@ -299,7 +384,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(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.' );
|
||||
foreach my $event_dir ( @event_dirs ) {
|
||||
if ( ! -d $event_dir ) {
|
||||
Debug( "$event_dir is not a dir. Skipping" );
|
||||
|
@ -318,11 +403,12 @@ MAIN: while( $loop ) {
|
|||
$$Event{RelativePath} = $event_dir;
|
||||
$Event->MonitorId( $monitor_dir );
|
||||
$Event->StorageId( $Storage->Id() );
|
||||
$Event->StartTime( POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($$Event{Path})) ) );
|
||||
} # end foreach event
|
||||
}
|
||||
|
||||
if ( ! $$Storage{Scheme} ) {
|
||||
Debug("Storage Scheme not set on $$Storage{Name}");
|
||||
Error("Storage Scheme not set on $$Storage{Name}");
|
||||
if ( ! chdir( $monitor_dir ) ) {
|
||||
Error( "Can't chdir directory '$$Storage{Path}/$monitor_dir': $!" );
|
||||
next;
|
||||
|
@ -343,9 +429,9 @@ 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\n" );
|
||||
Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir" );
|
||||
|
||||
#delete_empty_directories( $monitor_dir );
|
||||
delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir);
|
||||
} # end foreach monitor
|
||||
|
||||
if ( $cleaned ) {
|
||||
|
@ -361,13 +447,19 @@ 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 ) {
|
||||
|
||||
my $Event = $fs_events->{$fs_event_id};
|
||||
|
||||
if ( ! defined( $db_events->{$fs_event_id} ) ) {
|
||||
# Long running zmaudits can find events that were created after we loaded all db events.
|
||||
# So do a secondary lookup
|
||||
if ( ZoneMinder::Event->find_one(Id=>$fs_event_id) ) {
|
||||
Debug("$$Event{Id} found in secondary lookup.");
|
||||
next;
|
||||
}
|
||||
my $age = $Event->age();
|
||||
|
||||
if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) {
|
||||
|
@ -405,7 +497,11 @@ MAIN: while( $loop ) {
|
|||
}
|
||||
}
|
||||
} # end foreach Storage Area
|
||||
redo MAIN if ( $cleaned );
|
||||
|
||||
if ( $cleaned ) {
|
||||
Debug("Events were deleted, starting again.");
|
||||
redo MAIN;
|
||||
}
|
||||
|
||||
$cleaned = 0;
|
||||
my $deleteMonitorSql = 'DELETE LOW_PRIORITY FROM Monitors WHERE Id = ?';
|
||||
|
@ -423,64 +519,88 @@ MAIN: while( $loop ) {
|
|||
|
||||
# Foreach database monitor and it's list of events.
|
||||
while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) ) {
|
||||
Debug("Checking db events for monitor $db_monitor");
|
||||
if ( ! $db_events ) {
|
||||
Debug("Skipping db events for $db_monitor because there are none");
|
||||
next;
|
||||
}
|
||||
|
||||
# If we found the monitor in the file system
|
||||
if ( my $fs_events = $fs_monitors->{$db_monitor} ) {
|
||||
next if ! $db_events;
|
||||
my $fs_events = $fs_monitors->{$db_monitor};
|
||||
|
||||
while ( my ( $db_event, $age ) = each( %$db_events ) ) {
|
||||
if ( ! defined( $fs_events->{$db_event} ) ) {
|
||||
my $Event = ZoneMinder::Event->find_one( Id=>$db_event );
|
||||
if ( ! $Event ) {
|
||||
Debug("Event $db_event is no longer in db. Filter probably deleted it while we were auditing.");
|
||||
next;
|
||||
while ( my ( $db_event, $age ) = each( %$db_events ) ) {
|
||||
if ( ! ($fs_events and defined( $fs_events->{$db_event} ) ) ) {
|
||||
Debug("Don't have an fs event for $db_event");
|
||||
my $Event = ZoneMinder::Event->find_one( Id=>$db_event );
|
||||
if ( ! $Event ) {
|
||||
Debug("Event $db_event is no longer in db. Filter probably deleted it while we were auditing.");
|
||||
next;
|
||||
}
|
||||
Debug("Event $db_event is not in fs. Should have been at ".$Event->Path());
|
||||
if ( $Event->Archived() ) {
|
||||
Warning("Event $$Event{Id} is Archived. Taking no further action on it.");
|
||||
next;
|
||||
}
|
||||
if ( ! $Event->StartTime() ) {
|
||||
Info("Event $$Event{Id} has no start time.");
|
||||
if ( confirm() ) {
|
||||
$Event->delete();
|
||||
$cleaned = 1;
|
||||
}
|
||||
Debug("Event $db_event is not in fs. Should have been at ".$Event->Path());
|
||||
if ( ! $Event->StartTime() ) {
|
||||
Info("Event $$Event{Id} has no start time. deleting it.");
|
||||
next;
|
||||
}
|
||||
if ( ! $Event->EndTime() ) {
|
||||
if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) {
|
||||
Info("Event $$Event{Id} has no end time and is $age seconds old. deleting it.");
|
||||
if ( confirm() ) {
|
||||
$Event->delete();
|
||||
$cleaned = 1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ( ! $Event->EndTime() ) {
|
||||
if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) {
|
||||
Info("Event $$Event{Id} has no end time and is $age seconds old. deleting it.");
|
||||
if ( confirm() ) {
|
||||
$Event->delete();
|
||||
$cleaned = 1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
}
|
||||
if ( $Event->check_for_in_filesystem() ) {
|
||||
Debug("Database event $$Event{Id} apparently exists at " . $Event->Path() );
|
||||
} else {
|
||||
if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) {
|
||||
aud_print( "Database event '$db_monitor/$db_event' does not exist at " . $Event->Path().' in filesystem, deleting' );
|
||||
if ( confirm() ) {
|
||||
$Event->delete();
|
||||
$cleaned = 1;
|
||||
}
|
||||
} else {
|
||||
aud_print( "Database event '".$Event->Path()." monitor:$db_monitor event:$db_event' does not exist in filesystem but too young to delete age: $age > MIN $Config{ZM_AUDIT_MIN_AGE}.\n" );
|
||||
}
|
||||
if ( $Event->check_for_in_filesystem() ) {
|
||||
Debug("Database event $$Event{Id} apparently exists at " . $Event->Path() );
|
||||
} else {
|
||||
if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) {
|
||||
aud_print( "Database event '$db_monitor/$db_event' does not exist at " . $Event->Path().' in filesystem, deleting' );
|
||||
if ( confirm() ) {
|
||||
$Event->delete();
|
||||
$cleaned = 1;
|
||||
}
|
||||
} # end if exists in filesystem
|
||||
} # end if ! in fs_events
|
||||
} # foreach db_event
|
||||
#} else {
|
||||
#my $Monitor = new ZoneMinder::Monitor( $db_monitor );
|
||||
#my $Storage = $Monitor->Storage();
|
||||
#aud_print( "Database monitor '$db_monitor' does not exist in filesystem, should have been at ".$Storage->Path().'/'.$Monitor->Id()."\n" );
|
||||
#if ( confirm() )
|
||||
#{
|
||||
# We don't actually do this in case it's new
|
||||
#my $res = $deleteMonitorSth->execute( $db_monitor )
|
||||
# or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() );
|
||||
#$cleaned = 1;
|
||||
#}
|
||||
}
|
||||
} else {
|
||||
aud_print( "Database event '".$Event->Path()." monitor:$db_monitor event:$db_event' does not exist in filesystem but too young to delete age: $age > MIN $Config{ZM_AUDIT_MIN_AGE}.\n" );
|
||||
}
|
||||
} # end if exists in filesystem
|
||||
} else {
|
||||
Debug("Found fs event for $db_event, $age at " . $$fs_events{$db_event}->Path());
|
||||
my $Event = new ZoneMinder::Event( $db_event );
|
||||
if ( ! $Event->check_for_in_filesystem() ) {
|
||||
Warning("Not found at " . $Event->Path() . ' was found at ' . $$fs_events{$db_event}->Path() );
|
||||
Warning($Event->to_string());
|
||||
Warning($$fs_events{$db_event}->to_string());
|
||||
if ( $$fs_events{$db_event}->Scheme() ne $Event->Scheme() ) {
|
||||
Info("Updating scheme on event $$Event{Id} from $$Event{Scheme} to $$fs_events{$db_event}{Scheme}");
|
||||
$Event->Scheme($$fs_events{$db_event}->Scheme());
|
||||
}
|
||||
if ( $$fs_events{$db_event}->StorageId() != $Event->StorageId() ) {
|
||||
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() ) {
|
||||
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 ! in fs_events
|
||||
} # foreach db_event
|
||||
} # end foreach db_monitor
|
||||
if ( $cleaned ) {
|
||||
Debug("Have done some cleaning, restarting.");
|
||||
|
@ -701,17 +821,18 @@ FROM Frames WHERE EventId=?';
|
|||
if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^(.*)s$/ ) {
|
||||
$Config{ZM_LOG_DATABASE_LIMIT} = $1;
|
||||
}
|
||||
my $deleteLogByTimeSql =
|
||||
'DELETE low_priority FROM Logs
|
||||
WHERE TimeKey < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.')';
|
||||
my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql )
|
||||
or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() );
|
||||
$res = $deleteLogByTimeSth->execute()
|
||||
or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() );
|
||||
if ( $deleteLogByTimeSth->rows() ){
|
||||
aud_print( 'Deleted '.$deleteLogByTimeSth->rows()
|
||||
." log table entries by time\n" );
|
||||
}
|
||||
my $deleted_rows;
|
||||
do {
|
||||
my $deleteLogByTimeSql =
|
||||
'DELETE FROM Logs
|
||||
WHERE TimeKey < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.') LIMIT 10';
|
||||
my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql )
|
||||
or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() );
|
||||
$res = $deleteLogByTimeSth->execute()
|
||||
or Fatal( "Can't execute: ".$deleteLogByTimeSth->errstr() );
|
||||
$deleted_rows = $deleteLogByTimeSth->rows();
|
||||
aud_print( "Deleted $deleted_rows log table entries by time\n" );
|
||||
} while ( $deleted_rows );
|
||||
}
|
||||
} # end if ZM_LOG_DATABASE_LIMIT
|
||||
$loop = $continuous;
|
||||
|
@ -839,30 +960,71 @@ sub deleteSwapImage {
|
|||
}
|
||||
}
|
||||
|
||||
sub delete_empty_directories {
|
||||
# Deletes empty sub directories of the given path.
|
||||
# Does not delete the path if empty. Is not meant to be recursive.
|
||||
sub delete_empty_subdirs {
|
||||
my $DIR;
|
||||
Debug("delete_empty_directories $_[0]");
|
||||
if ( ! opendir( $DIR, $_[0] ) ) {
|
||||
Error( "delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" );
|
||||
if ( !opendir($DIR, $_[0]) ) {
|
||||
Error("delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" );
|
||||
return;
|
||||
}
|
||||
my @contents = map { $_ eq '.' or $_ eq '..' ? () : $_ } readdir( $DIR );
|
||||
my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR );
|
||||
Debug("delete_empty_subdirectories $_[0] has " . @contents .' entries:' . ( @contents < 2 ? join(',',@contents) : '' ));
|
||||
my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents;
|
||||
Debug("Have " . @dirs . " dirs");
|
||||
foreach ( @dirs ) {
|
||||
delete_empty_directories( $_[0].'/'.$_ );
|
||||
}
|
||||
closedir($DIR);
|
||||
}
|
||||
|
||||
sub delete_empty_directories {
|
||||
my $DIR;
|
||||
if ( !opendir($DIR, $_[0]) ) {
|
||||
Error("delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" );
|
||||
return;
|
||||
}
|
||||
my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR );
|
||||
#Debug("delete_empty_directories $_[0] has " . @contents .' entries:' . ( @contents <= 2 ? join(',',@contents) : '' ));
|
||||
my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents;
|
||||
if ( @dirs ) {
|
||||
Debug("Have " . @dirs . " dirs");
|
||||
foreach ( @dirs ) {
|
||||
delete_empty_directories( $_[0].'/'.$_ );
|
||||
}
|
||||
#Reload, since we may now be empty
|
||||
rewinddir $DIR;
|
||||
@contents = map { $_ eq '.' or $_ eq '..' ? () : $_ } readdir( $DIR );
|
||||
@contents = map { ($_ eq '.' or $_ eq '..') ? () : $_ } readdir( $DIR );
|
||||
}
|
||||
closedir($DIR);
|
||||
if ( ! @contents ) {
|
||||
( my $dir ) = ( $_[0] =~ /^(.*)$/ );
|
||||
unlink $dir;
|
||||
Debug("Unlinking $dir because it's empty");
|
||||
if ( ! rmdir $dir ) {
|
||||
Error("Unable to unlink $dir: $!");
|
||||
}
|
||||
}
|
||||
closedir( $DIR );
|
||||
} # end sub delete_empty_directories
|
||||
|
||||
sub time_of_youngest_file {
|
||||
my $dir = shift;
|
||||
|
||||
if ( ! opendir(DIR, $dir) ) {
|
||||
Error("Can't open directory '$dir': $!");
|
||||
return;
|
||||
}
|
||||
my $youngest = (stat($dir))[9];
|
||||
Debug("stat of $dir is $youngest");
|
||||
foreach my $file ( readdir( DIR ) ) {
|
||||
next if $file =~ /^\./;
|
||||
$_ = (stat($dir))[9];
|
||||
$youngest = $_ if $_ and ( $_ < $youngest );
|
||||
#Debug("stat of $dir is $_ < $youngest");
|
||||
}
|
||||
Debug("stat of $dir is $youngest");
|
||||
return $youngest;
|
||||
} # end sub time_of_youngest_file
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
|
|
|
@ -21,6 +21,195 @@
|
|||
#
|
||||
# ==========================================================================
|
||||
|
||||
use strict;
|
||||
|
||||
@EXTRA_PERL_LIB@
|
||||
use ZoneMinder;
|
||||
use Getopt::Long;
|
||||
use autouse 'Pod::Usage'=>qw(pod2usage);
|
||||
use POSIX qw/strftime EPIPE/;
|
||||
use Socket;
|
||||
#use Data::Dumper;
|
||||
use Module::Load::Conditional qw{can_load};;
|
||||
|
||||
use constant MAX_CONNECT_DELAY => 15;
|
||||
use constant MAX_COMMAND_WAIT => 1800;
|
||||
|
||||
$| = 1;
|
||||
|
||||
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
|
||||
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
||||
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
||||
|
||||
logInit();
|
||||
|
||||
my $arg_string = join( " ", @ARGV );
|
||||
|
||||
my $id;
|
||||
my %options;
|
||||
|
||||
GetOptions(
|
||||
'id=i' =>\$id,
|
||||
'command=s' =>\$options{command},
|
||||
'xcoord=i' =>\$options{xcoord},
|
||||
'ycoord=i' =>\$options{ycoord},
|
||||
'speed=i' =>\$options{speed},
|
||||
'step=i' =>\$options{step},
|
||||
'panspeed=i' =>\$options{panspeed},
|
||||
'tiltspeed=i' =>\$options{tiltspeed},
|
||||
'panstep=i' =>\$options{panstep},
|
||||
'tiltstep=i' =>\$options{tiltstep},
|
||||
'preset=i' =>\$options{preset},
|
||||
'autostop' =>\$options{autostop},
|
||||
) or pod2usage(-exitstatus => -1);
|
||||
|
||||
if ( !$id || !$options{command} ) {
|
||||
print( STDERR "Please give a valid monitor id and command\n" );
|
||||
pod2usage(-exitstatus => -1);
|
||||
}
|
||||
|
||||
( $id ) = $id =~ /^(\w+)$/;
|
||||
|
||||
Debug("zmcontrol: arg string: $arg_string");
|
||||
|
||||
my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock';
|
||||
|
||||
socket(CLIENT, PF_UNIX, SOCK_STREAM, 0)
|
||||
or Fatal("Can't open socket: $!");
|
||||
|
||||
my $saddr = sockaddr_un($sock_file);
|
||||
my $server_up = connect(CLIENT, $saddr);
|
||||
if ( !$server_up ) {
|
||||
# The server isn't there
|
||||
my $monitor = zmDbGetMonitorAndControl($id);
|
||||
if ( !$monitor ) {
|
||||
Fatal("Unable to load control data for monitor $id");
|
||||
}
|
||||
my $protocol = $monitor->{Protocol};
|
||||
|
||||
if ( -x $protocol ) {
|
||||
# Protocol is actually a script!
|
||||
# Holdover from previous versions
|
||||
my $command .= $protocol.' '.$arg_string;
|
||||
Debug($command);
|
||||
|
||||
my $output = qx($command);
|
||||
my $status = $? >> 8;
|
||||
if ( $status || logDebugging() ) {
|
||||
chomp($output);
|
||||
Debug("Output: $output");
|
||||
}
|
||||
if ( $status ) {
|
||||
Error("Command '$command' exited with status: $status");
|
||||
exit($status);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
Info("Starting control server $id/$protocol");
|
||||
close(CLIENT);
|
||||
|
||||
if ( ! can_load( modules => { "ZoneMinder::Control::$protocol" => undef } ) ) {
|
||||
Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR");
|
||||
}
|
||||
|
||||
if ( my $cpid = fork() ) {
|
||||
logReinit();
|
||||
|
||||
# Parent process just sleep and fall through
|
||||
socket(CLIENT, PF_UNIX, SOCK_STREAM, 0)
|
||||
or die("Can't open socket: $!");
|
||||
my $attempts = 0;
|
||||
while ( !connect(CLIENT, $saddr) ) {
|
||||
$attempts++;
|
||||
Fatal("Can't connect: $! after $attempts attempts to $sock_file") if $attempts > MAX_CONNECT_DELAY;
|
||||
sleep(1);
|
||||
}
|
||||
} elsif ( defined($cpid) ) {
|
||||
close(STDOUT);
|
||||
close(STDERR);
|
||||
|
||||
setpgrp();
|
||||
|
||||
logReinit();
|
||||
|
||||
Info("Control server $id/$protocol starting at "
|
||||
.strftime('%y/%m/%d %H:%M:%S', localtime())
|
||||
);
|
||||
|
||||
$0 = $0." --id $id";
|
||||
|
||||
my $control = "ZoneMinder::Control::$protocol"->new($id);
|
||||
my $control_key = $control->getKey();
|
||||
$control->loadMonitor();
|
||||
|
||||
$control->open();
|
||||
|
||||
socket(SERVER, PF_UNIX, SOCK_STREAM, 0)
|
||||
or Fatal("Can't open socket: $!");
|
||||
unlink($sock_file);
|
||||
bind(SERVER, $saddr) or Fatal("Can't bind: $!");
|
||||
listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!");
|
||||
|
||||
my $rin = '';
|
||||
vec( $rin, fileno(SERVER), 1 ) = 1;
|
||||
my $win = $rin;
|
||||
my $ein = $win;
|
||||
my $timeout = MAX_COMMAND_WAIT;
|
||||
while( 1 ) {
|
||||
my $nfound = select(my $rout = $rin, undef, undef, $timeout);
|
||||
if ( $nfound > 0 ) {
|
||||
if ( vec( $rout, fileno(SERVER), 1 ) ) {
|
||||
my $paddr = accept(CLIENT, SERVER);
|
||||
my $message = <CLIENT>;
|
||||
|
||||
next if !$message;
|
||||
|
||||
my $params = jsonDecode($message);
|
||||
#Debug( Dumper( $params ) );
|
||||
|
||||
my $command = $params->{command};
|
||||
close( CLIENT );
|
||||
if ( $command eq 'quit' ) {
|
||||
last;
|
||||
}
|
||||
$control->$command($params);
|
||||
} else {
|
||||
Fatal('Bogus descriptor');
|
||||
}
|
||||
} elsif ( $nfound < 0 ) {
|
||||
if ( $! == EPIPE ) {
|
||||
Error("Can't select: $!");
|
||||
} else {
|
||||
Fatal("Can't select: $!");
|
||||
}
|
||||
} else {
|
||||
#print( "Select timed out\n" );
|
||||
last;
|
||||
}
|
||||
} # end while forever
|
||||
Info("Control server $id/$protocol exiting");
|
||||
unlink($sock_file);
|
||||
$control->close();
|
||||
exit(0);
|
||||
} else {
|
||||
Fatal("Can't fork: $!");
|
||||
}
|
||||
} # end if !server up
|
||||
|
||||
# The server is there, connect to it
|
||||
#print( "Writing commands\n" );
|
||||
CLIENT->autoflush();
|
||||
|
||||
my $message = jsonEncode(\%options);
|
||||
print(CLIENT $message);
|
||||
shutdown(CLIENT, 1);
|
||||
|
||||
exit(0);
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
zmcontrol.pl - ZoneMinder control script
|
||||
|
@ -47,214 +236,3 @@ FIXME FIXME
|
|||
--preset [ arg ] -
|
||||
|
||||
=cut
|
||||
use strict;
|
||||
|
||||
@EXTRA_PERL_LIB@
|
||||
use ZoneMinder;
|
||||
use Getopt::Long;
|
||||
use autouse 'Pod::Usage'=>qw(pod2usage);
|
||||
use POSIX qw/strftime EPIPE/;
|
||||
use Socket;
|
||||
#use Data::Dumper;
|
||||
use Module::Load::Conditional qw{can_load};;
|
||||
|
||||
use constant MAX_CONNECT_DELAY => 10;
|
||||
use constant MAX_COMMAND_WAIT => 1800;
|
||||
|
||||
$| = 1;
|
||||
|
||||
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
|
||||
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
||||
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
||||
|
||||
logInit();
|
||||
|
||||
my $arg_string = join( " ", @ARGV );
|
||||
|
||||
my $id;
|
||||
my %options;
|
||||
|
||||
GetOptions(
|
||||
'id=i' =>\$id,
|
||||
'command=s' =>\$options{command},
|
||||
'xcoord=i' =>\$options{xcoord},
|
||||
'ycoord=i' =>\$options{ycoord},
|
||||
'speed=i' =>\$options{speed},
|
||||
'step=i' =>\$options{step},
|
||||
'panspeed=i' =>\$options{panspeed},
|
||||
'tiltspeed=i' =>\$options{tiltspeed},
|
||||
'panstep=i' =>\$options{panstep},
|
||||
'tiltstep=i' =>\$options{tiltstep},
|
||||
'preset=i' =>\$options{preset},
|
||||
'autostop' =>\$options{autostop},
|
||||
) or pod2usage(-exitstatus => -1);
|
||||
|
||||
if ( !$id || !$options{command} )
|
||||
{
|
||||
print( STDERR "Please give a valid monitor id and command\n" );
|
||||
pod2usage(-exitstatus => -1);
|
||||
}
|
||||
|
||||
( $id ) = $id =~ /^(\w+)$/;
|
||||
|
||||
Debug( $arg_string );
|
||||
|
||||
my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock';
|
||||
|
||||
socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 )
|
||||
or Fatal( "Can't open socket: $!" );
|
||||
|
||||
my $saddr = sockaddr_un( $sock_file );
|
||||
my $server_up = connect( CLIENT, $saddr );
|
||||
if ( !$server_up )
|
||||
{
|
||||
# The server isn't there
|
||||
my $monitor = zmDbGetMonitorAndControl( $id );
|
||||
if ( !$monitor )
|
||||
{
|
||||
Fatal( "Unable to load control data for monitor $id" );
|
||||
}
|
||||
my $protocol = $monitor->{Protocol};
|
||||
|
||||
if ( -x $protocol )
|
||||
{
|
||||
# Protocol is actually a script!
|
||||
# Holdover from previous versions
|
||||
my $command .= $protocol.' '.$arg_string;
|
||||
Debug( $command."\n" );
|
||||
|
||||
my $output = qx($command);
|
||||
my $status = $? >> 8;
|
||||
if ( $status || logDebugging() )
|
||||
{
|
||||
chomp( $output );
|
||||
Debug( "Output: $output\n" );
|
||||
}
|
||||
if ( $status )
|
||||
{
|
||||
Error( "Command '$command' exited with status: $status\n" );
|
||||
exit( $status );
|
||||
}
|
||||
exit( 0 );
|
||||
}
|
||||
|
||||
Info( "Starting control server $id/$protocol" );
|
||||
close( CLIENT );
|
||||
|
||||
if ( ! can_load( modules => { "ZoneMinder::Control::$protocol" => undef } ) ) {
|
||||
Fatal("Can't load ZoneMinder::Control::$protocol");
|
||||
}
|
||||
|
||||
if ( my $cpid = fork() )
|
||||
{
|
||||
logReinit();
|
||||
|
||||
# Parent process just sleep and fall through
|
||||
socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 )
|
||||
or die( "Can't open socket: $!" );
|
||||
my $attempts = 0;
|
||||
while (!connect( CLIENT, $saddr ))
|
||||
{
|
||||
$attempts++;
|
||||
Fatal( "Can't connect: $! after $attempts attempts to $sock_file" ) if ($attempts > MAX_CONNECT_DELAY);
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
elsif ( defined($cpid) )
|
||||
{
|
||||
close( STDOUT );
|
||||
close( STDERR );
|
||||
|
||||
setpgrp();
|
||||
|
||||
logReinit();
|
||||
|
||||
Info( "Control server $id/$protocol starting at "
|
||||
.strftime( '%y/%m/%d %H:%M:%S', localtime() )
|
||||
);
|
||||
|
||||
$0 = $0." --id $id";
|
||||
|
||||
my $control = "ZoneMinder::Control::$protocol"->new( $id );
|
||||
my $control_key = $control->getKey();
|
||||
$control->loadMonitor();
|
||||
|
||||
$control->open();
|
||||
|
||||
socket( SERVER, PF_UNIX, SOCK_STREAM, 0 )
|
||||
or Fatal( "Can't open socket: $!" );
|
||||
unlink( $sock_file );
|
||||
bind( SERVER, $saddr ) or Fatal( "Can't bind: $!" );
|
||||
listen( SERVER, SOMAXCONN ) or Fatal( "Can't listen: $!" );
|
||||
|
||||
my $rin = '';
|
||||
vec( $rin, fileno(SERVER), 1 ) = 1;
|
||||
my $win = $rin;
|
||||
my $ein = $win;
|
||||
my $timeout = MAX_COMMAND_WAIT;
|
||||
while( 1 )
|
||||
{
|
||||
my $nfound = select( my $rout = $rin, undef, undef, $timeout );
|
||||
if ( $nfound > 0 )
|
||||
{
|
||||
if ( vec( $rout, fileno(SERVER), 1 ) )
|
||||
{
|
||||
my $paddr = accept( CLIENT, SERVER );
|
||||
my $message = <CLIENT>;
|
||||
|
||||
next if ( !$message );
|
||||
|
||||
my $params = jsonDecode( $message );
|
||||
#Debug( Dumper( $params ) );
|
||||
|
||||
my $command = $params->{command};
|
||||
close( CLIENT );
|
||||
if ( $command eq 'quit' ) {
|
||||
last;
|
||||
}
|
||||
$control->$command( $params );
|
||||
}
|
||||
else
|
||||
{
|
||||
Fatal( "Bogus descriptor" );
|
||||
}
|
||||
}
|
||||
elsif ( $nfound < 0 )
|
||||
{
|
||||
if ( $! == EPIPE )
|
||||
{
|
||||
Error( "Can't select: $!" );
|
||||
}
|
||||
else
|
||||
{
|
||||
Fatal( "Can't select: $!" );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#print( "Select timed out\n" );
|
||||
last;
|
||||
}
|
||||
}
|
||||
Info( "Control server $id/$protocol exiting at "
|
||||
.strftime( '%y/%m/%d %H:%M:%S', localtime() )
|
||||
);
|
||||
unlink( $sock_file );
|
||||
$control->close();
|
||||
exit( 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
Fatal( "Can't fork: $!" );
|
||||
}
|
||||
}
|
||||
|
||||
# The server is there, connect to it
|
||||
#print( "Writing commands\n" );
|
||||
CLIENT->autoflush();
|
||||
|
||||
my $message = jsonEncode( \%options );
|
||||
print( CLIENT $message );
|
||||
shutdown( CLIENT, 1 );
|
||||
|
||||
exit( 0 );
|
||||
|
|
|
@ -272,6 +272,8 @@ sub run {
|
|||
."\n"
|
||||
);
|
||||
|
||||
# We don't want to leave killall zombies, so ignore SIGCHLD
|
||||
$SIG{CHLD} = 'IGNORE';
|
||||
# Tell any existing processes to die, wait 1 second between TERM and KILL
|
||||
killAll(1);
|
||||
|
||||
|
@ -370,7 +372,6 @@ sub run {
|
|||
restartPending();
|
||||
check_for_processes_to_kill() if %terminating_processes;
|
||||
reaper() if %pids_to_reap;
|
||||
|
||||
} # end while
|
||||
|
||||
dPrint(ZoneMinder::Logger::INFO, 'Server exiting at '
|
||||
|
@ -431,7 +432,11 @@ sub start {
|
|||
my $sigset = POSIX::SigSet->new;
|
||||
my $blockset = POSIX::SigSet->new(SIGCHLD);
|
||||
sigprocmask(SIG_BLOCK, $blockset, $sigset) or Fatal("Can't block SIGCHLD: $!");
|
||||
# Apparently the child closing the db connection can affect the parent.
|
||||
zmDbDisconnect();
|
||||
if ( my $cpid = fork() ) {
|
||||
|
||||
$dbh = zmDbConnect(1);
|
||||
# This logReinit is required. Not sure why.
|
||||
logReinit();
|
||||
|
||||
|
@ -477,7 +482,7 @@ sub start {
|
|||
logTerm();
|
||||
zmDbDisconnect();
|
||||
|
||||
my $fd = 0;
|
||||
my $fd = 3; # leave stdin,stdout,stderr open. Closing them causes problems with libx264
|
||||
while( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) {
|
||||
POSIX::close($fd++);
|
||||
}
|
||||
|
@ -510,7 +515,7 @@ sub send_stop {
|
|||
.strftime('%y/%m/%d %H:%M:%S', localtime())
|
||||
."\n"
|
||||
);
|
||||
sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n";
|
||||
sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n";
|
||||
return();
|
||||
}
|
||||
|
||||
|
@ -643,7 +648,6 @@ sub chld_sig_handler {
|
|||
sub reaper {
|
||||
foreach my $cpid ( keys %pids_to_reap ) {
|
||||
my $process = $pid_hash{$cpid};
|
||||
Debug("Reaping pricess $cpid");
|
||||
delete $pid_hash{$cpid};
|
||||
my $reap_info = $pids_to_reap{$cpid};
|
||||
my ( $status, $stopped ) = @$reap_info{'status','stopped'};
|
||||
|
@ -709,7 +713,6 @@ Debug("Reaping pricess $cpid");
|
|||
} else {
|
||||
delete $cmd_hash{$$process{command}};
|
||||
}
|
||||
Debug("Reaping pricess $cpid");
|
||||
} # end foreach pid_to_reap
|
||||
} # end sub reaper
|
||||
|
||||
|
|
|
@ -69,17 +69,20 @@ use Time::HiRes qw/gettimeofday/;
|
|||
use Getopt::Long;
|
||||
use autouse 'Pod::Usage'=>qw(pod2usage);
|
||||
use autouse 'Data::Dumper'=>qw(Dumper);
|
||||
use File::Path qw(make_path);
|
||||
|
||||
my $daemon = 0;
|
||||
my $filter_name = '';
|
||||
my $filter_id;
|
||||
my $version = 0;
|
||||
my $zm_terminate = 0;
|
||||
|
||||
GetOptions(
|
||||
'filter=s' =>\$filter_name,
|
||||
'filter_id=s' =>\$filter_id,
|
||||
'version' =>\$version
|
||||
) or pod2usage(-exitstatus => -1);
|
||||
daemon =>\$daemon,
|
||||
'filter=s' =>\$filter_name,
|
||||
'filter_id=s' =>\$filter_id,
|
||||
version =>\$version
|
||||
) or pod2usage(-exitstatus => -1);
|
||||
|
||||
if ( $version ) {
|
||||
print ZoneMinder::Base::ZM_VERSION . "\n";
|
||||
|
@ -96,15 +99,21 @@ use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|)
|
|||
|
||||
logInit($filter_id?(id=>'zmfilter_'.$filter_id):());
|
||||
sub HupHandler {
|
||||
Info("Received HUP, reloading");
|
||||
# This idea at this time is to just exit, freeing up the memory.
|
||||
# zmfilter.pl will be respawned by zmdc.
|
||||
TermHandler();
|
||||
return;
|
||||
|
||||
Info('Received HUP, reloading');
|
||||
ZoneMinder::Object::init_cache();
|
||||
&ZoneMinder::Logger::logHupHandler();
|
||||
}
|
||||
sub TermHandler {
|
||||
Info("Received TERM, exiting");
|
||||
Info('Received TERM, exiting');
|
||||
$zm_terminate = 1;
|
||||
}
|
||||
sub Term {
|
||||
exit( 0 );
|
||||
exit(0);
|
||||
}
|
||||
$SIG{HUP} = \&HupHandler;
|
||||
$SIG{TERM} = \&TermHandler;
|
||||
|
@ -154,7 +163,7 @@ my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL};
|
|||
my $event_id = 0;
|
||||
|
||||
if ( ! EVENT_PATH ) {
|
||||
Error("No event path defined. Config was $Config{ZM_DIR_EVENTS}\n");
|
||||
Error("No event path defined. Config was $Config{ZM_DIR_EVENTS}");
|
||||
die;
|
||||
}
|
||||
|
||||
|
@ -165,15 +174,15 @@ chdir( EVENT_PATH );
|
|||
my $dbh = zmDbConnect();
|
||||
|
||||
if ( $filter_name ) {
|
||||
Info("Scanning for events using filter '$filter_name'\n");
|
||||
Info("Scanning for events using filter '$filter_name'");
|
||||
} elsif ( $filter_id ) {
|
||||
Info("Scanning for events using filter id '$filter_id'\n");
|
||||
Info("Scanning for events using filter id '$filter_id'");
|
||||
} else {
|
||||
Info("Scanning for events using all filters\n");
|
||||
Info("Scanning for events using all filters");
|
||||
}
|
||||
|
||||
if ( ! ( $filter_name or $filter_id ) ) {
|
||||
Debug("Sleeping due to start delay: " . START_DELAY . ' seconds...');
|
||||
Debug('Sleeping due to start delay: ' . START_DELAY . ' seconds...');
|
||||
sleep(START_DELAY);
|
||||
}
|
||||
|
||||
|
@ -183,7 +192,7 @@ my $last_action = 0;
|
|||
while( !$zm_terminate ) {
|
||||
my $now = time;
|
||||
if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) {
|
||||
Debug("Reloading filters\n");
|
||||
Debug("Reloading filters");
|
||||
$last_action = $now;
|
||||
@filters = getFilters({ Name=>$filter_name, Id=>$filter_id });
|
||||
}
|
||||
|
@ -201,9 +210,9 @@ while( !$zm_terminate ) {
|
|||
}
|
||||
}
|
||||
|
||||
last if $filter_name or $filter_id or $zm_terminate;
|
||||
last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate;
|
||||
|
||||
Debug("Sleeping for $delay seconds\n");
|
||||
Debug("Sleeping for $delay seconds");
|
||||
sleep($delay);
|
||||
}
|
||||
|
||||
|
@ -265,8 +274,7 @@ sub checkFilter {
|
|||
Info(
|
||||
join(' ',
|
||||
'Checking filter', $filter->{Name},
|
||||
join( ', ',
|
||||
|
||||
join(', ',
|
||||
($filter->{AutoDelete}?'delete':()),
|
||||
($filter->{AutoArchive}?'archive':()),
|
||||
($filter->{AutoVideo}?'video':()),
|
||||
|
@ -285,48 +293,48 @@ sub checkFilter {
|
|||
last if $zm_terminate;
|
||||
my $Event = new ZoneMinder::Event($$event{Id}, $event);
|
||||
|
||||
Debug("Checking event $event->{Id}");
|
||||
Debug("Checking event $Event->{Id}");
|
||||
my $delete_ok = !undef;
|
||||
$dbh->ping();
|
||||
if ( $filter->{AutoArchive} ) {
|
||||
Info("Archiving event $event->{Id}");
|
||||
Info("Archiving event $Event->{Id}");
|
||||
# Do it individually to avoid locking up the table for new events
|
||||
my $sql = 'UPDATE Events SET Archived = 1 WHERE Id = ?';
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute( $event->{Id} )
|
||||
my $res = $sth->execute( $Event->{Id} )
|
||||
or Error("Unable to execute '$sql': ".$dbh->errstr());
|
||||
}
|
||||
if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) {
|
||||
if ( !$event->{Videoed} ) {
|
||||
$delete_ok = undef if !generateVideo($filter, $event);
|
||||
if ( !$Event->{Videoed} ) {
|
||||
$delete_ok = undef if !generateVideo($filter, $Event);
|
||||
}
|
||||
}
|
||||
if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) {
|
||||
if ( !$event->{Emailed} ) {
|
||||
if ( !$Event->{Emailed} ) {
|
||||
$delete_ok = undef if !sendEmail($filter, $Event);
|
||||
}
|
||||
}
|
||||
if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) {
|
||||
if ( !$event->{Messaged} ) {
|
||||
if ( !$Event->{Messaged} ) {
|
||||
$delete_ok = undef if !sendMessage($filter, $Event);
|
||||
}
|
||||
}
|
||||
if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) {
|
||||
if ( !$event->{Uploaded} ) {
|
||||
$delete_ok = undef if !uploadArchFile($filter, $event);
|
||||
if ( !$Event->{Uploaded} ) {
|
||||
$delete_ok = undef if !uploadArchFile($filter, $Event);
|
||||
}
|
||||
}
|
||||
if ( $filter->{AutoExecute} ) {
|
||||
if ( !$event->{Executed} ) {
|
||||
$delete_ok = undef if !executeCommand($filter, $event);
|
||||
if ( !$Event->{Executed} ) {
|
||||
$delete_ok = undef if !executeCommand($filter, $Event);
|
||||
}
|
||||
}
|
||||
if ( $filter->{AutoDelete} ) {
|
||||
if ( $delete_ok ) {
|
||||
$Event->delete();
|
||||
} else {
|
||||
Error("Unable toto delete event $event->{Id} as previous operations failed");
|
||||
Error("Unable to delete event $Event->{Id} as previous operations failed");
|
||||
}
|
||||
} # end if AutoDelete
|
||||
|
||||
|
@ -357,11 +365,11 @@ sub checkFilter {
|
|||
|
||||
sub generateVideo {
|
||||
my $filter = shift;
|
||||
my $event = shift;
|
||||
my $Event = shift;
|
||||
my $phone = shift;
|
||||
|
||||
my $rate = $event->{DefaultRate}/100;
|
||||
my $scale = $event->{DefaultScale}/100;
|
||||
my $rate = $Event->{DefaultRate}/100;
|
||||
my $scale = $Event->{DefaultScale}/100;
|
||||
my $format;
|
||||
|
||||
my @ffmpeg_formats = split(/\s+/, $Config{ZM_FFMPEG_FORMATS});
|
||||
|
@ -383,10 +391,10 @@ sub generateVideo {
|
|||
$format = $ffmpeg_formats[0];
|
||||
}
|
||||
|
||||
my $command = join( '',
|
||||
my $command = join('',
|
||||
$Config{ZM_PATH_BIN},
|
||||
'/zmvideo.pl -e ',
|
||||
$event->{Id},
|
||||
$Event->{Id},
|
||||
' -r ',
|
||||
$rate,
|
||||
' -s ',
|
||||
|
@ -395,13 +403,13 @@ sub generateVideo {
|
|||
$format,
|
||||
);
|
||||
my $output = qx($command);
|
||||
chomp( $output );
|
||||
chomp($output);
|
||||
my $status = $? >> 8;
|
||||
if ( $status || logDebugging() ) {
|
||||
Debug("Output: $output\n");
|
||||
Debug("Output: $output");
|
||||
}
|
||||
if ( $status ) {
|
||||
Error("Video generation '$command' failed with status: $status\n");
|
||||
Error("Video generation '$command' failed with status: $status");
|
||||
if ( wantarray() ) {
|
||||
return( undef, undef );
|
||||
}
|
||||
|
@ -410,8 +418,8 @@ sub generateVideo {
|
|||
my $sql = 'UPDATE Events SET Videoed = 1 WHERE Id = ?';
|
||||
my $sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute($event->{Id})
|
||||
or Fatal("Unable toexecute '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute($Event->{Id})
|
||||
or Fatal("Unable to execute '$sql': ".$dbh->errstr());
|
||||
if ( wantarray() ) {
|
||||
return( $format, $output );
|
||||
}
|
||||
|
@ -446,7 +454,7 @@ sub generateImage {
|
|||
chomp( $output );
|
||||
my $status = $? >> 8;
|
||||
if ( $status || logDebugging() ) {
|
||||
Debug("Output: $output\n");
|
||||
Debug("Output: $output");
|
||||
}
|
||||
if ( $status ) {
|
||||
Error("Failed $command status $status");
|
||||
|
@ -460,22 +468,30 @@ sub generateImage {
|
|||
|
||||
sub uploadArchFile {
|
||||
my $filter = shift;
|
||||
my $event = shift;
|
||||
my $Event = shift;
|
||||
|
||||
if ( ! $Config{ZM_UPLOAD_HOST} ) {
|
||||
Error('Cannot upload archive as no upload host defined');
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
my $archFile = $event->{MonitorName}.'-'.$event->{Id};
|
||||
my $archImagePath = getEventPath($event)
|
||||
# Try to make the path to the upload folder if it doesn't already exist
|
||||
make_path( $Config{ZM_UPLOAD_LOC_DIR} );
|
||||
|
||||
# Complain if the upload folder is still not writable
|
||||
if ( ! -w $Config{ZM_UPLOAD_LOC_DIR} ) {
|
||||
Error("Upload folder either does not exist or is not writable: $Config{ZM_UPLOAD_LOC_DIR}");
|
||||
return(0);
|
||||
}
|
||||
|
||||
my $archFile = $Event->{MonitorName}.'-'.$Event->{Id};
|
||||
my $archImagePath = $Event->Path()
|
||||
.'/'
|
||||
.(
|
||||
( $Config{ZM_UPLOAD_ARCH_ANALYSE} )
|
||||
? '{*analyse,*capture}'
|
||||
: '*capture'
|
||||
? '{*analyse.jpg,*capture.jpg,snapshot.jpg,*.mp4}'
|
||||
: '{*capture.jpg,snapshot.jpg,*.mp4}'
|
||||
)
|
||||
.'.jpg'
|
||||
;
|
||||
my @archImageFiles = glob($archImagePath);
|
||||
my $archLocPath;
|
||||
|
@ -485,11 +501,11 @@ sub uploadArchFile {
|
|||
$archFile .= '.zip';
|
||||
$archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile;
|
||||
my $zip = Archive::Zip->new();
|
||||
Info("Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n");
|
||||
Info("Creating upload file '$archLocPath', ".int(@archImageFiles).' files');
|
||||
|
||||
my $status = &AZ_OK;
|
||||
foreach my $imageFile ( @archImageFiles ) {
|
||||
Debug("Adding $imageFile\n");
|
||||
Debug("Adding $imageFile");
|
||||
my $member = $zip->addFile($imageFile);
|
||||
if ( !$member ) {
|
||||
Error("Unable toto add image file $imageFile to zip archive $archLocPath");
|
||||
|
@ -517,7 +533,7 @@ sub uploadArchFile {
|
|||
$archFile .= '.tar';
|
||||
}
|
||||
$archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile;
|
||||
Info("Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n");
|
||||
Info("Creating upload file '$archLocPath', ".int(@archImageFiles).' files');
|
||||
|
||||
if ( $archError = !Archive::Tar->create_archive(
|
||||
$archLocPath,
|
||||
|
@ -533,7 +549,7 @@ sub uploadArchFile {
|
|||
return( 0 );
|
||||
} else {
|
||||
if ( $Config{ZM_UPLOAD_PROTOCOL} eq 'ftp' ) {
|
||||
Info('Uploading to '.$Config{ZM_UPLOAD_HOST}." using FTP");
|
||||
Info('Uploading to '.$Config{ZM_UPLOAD_HOST}.' using FTP');
|
||||
my $ftp = Net::FTP->new(
|
||||
$Config{ZM_UPLOAD_HOST},
|
||||
Timeout=>$Config{ZM_UPLOAD_TIMEOUT},
|
||||
|
@ -541,26 +557,27 @@ sub uploadArchFile {
|
|||
Debug=>$Config{ZM_UPLOAD_DEBUG}
|
||||
);
|
||||
if ( !$ftp ) {
|
||||
Error("Unable tocreate FTP connection: $@");
|
||||
Error("Unable to create FTP connection: $@");
|
||||
return 0;
|
||||
}
|
||||
$ftp->login($Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS})
|
||||
or Error("FTP - Unable tologin");
|
||||
or Error("FTP - Unable to login");
|
||||
$ftp->binary()
|
||||
or Error("FTP - Unable togo binary");
|
||||
or Error("FTP - Unable to go binary");
|
||||
$ftp->cwd($Config{ZM_UPLOAD_REM_DIR})
|
||||
or Error("FTP - Unable tocwd")
|
||||
or Error("FTP - Unable to cwd")
|
||||
if ( $Config{ZM_UPLOAD_REM_DIR} );
|
||||
$ftp->put( $archLocPath )
|
||||
or Error("FTP - Unable toupload '$archLocPath'");
|
||||
or Error("FTP - Unable to upload '$archLocPath'");
|
||||
$ftp->quit()
|
||||
or Error("FTP - Unable toquit");
|
||||
or Error("FTP - Unable to quit");
|
||||
} else {
|
||||
my $host = $Config{ZM_UPLOAD_HOST};
|
||||
$host .= ':'.$Config{ZM_UPLOAD_PORT} if $Config{ZM_UPLOAD_PORT};
|
||||
Info('Uploading to '.$host." using SFTP\n");
|
||||
Info('Uploading to '.$host.' using SFTP');
|
||||
my %sftpOptions = (
|
||||
host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER}
|
||||
host=>$Config{ZM_UPLOAD_HOST},
|
||||
user=>$Config{ZM_UPLOAD_USER},
|
||||
($Config{ZM_UPLOAD_PASS} ? (password=>$Config{ZM_UPLOAD_PASS}) : ()),
|
||||
($Config{ZM_UPLOAD_PORT} ? (port=>$Config{ZM_UPLOAD_PORT}) : ()),
|
||||
($Config{ZM_UPLOAD_TIMEOUT} ? (timeout=>$Config{ZM_UPLOAD_TIMEOUT}) : ()),
|
||||
|
@ -573,7 +590,7 @@ sub uploadArchFile {
|
|||
$sftpOptions{more} = [@more_ssh_args];
|
||||
my $sftp = Net::SFTP::Foreign->new($Config{ZM_UPLOAD_HOST}, %sftpOptions);
|
||||
if ( $sftp->error ) {
|
||||
Error("Unable tocreate SFTP connection: ".$sftp->error);
|
||||
Error("Unable to create SFTP connection: ".$sftp->error);
|
||||
return 0;
|
||||
}
|
||||
$sftp->setcwd($Config{ZM_UPLOAD_REM_DIR})
|
||||
|
@ -585,9 +602,9 @@ sub uploadArchFile {
|
|||
unlink($archLocPath);
|
||||
my $sql = 'UPDATE Events SET Uploaded = 1 WHERE Id = ?';
|
||||
my $sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Unable toprepare '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute($event->{Id})
|
||||
or Fatal("Unable toexecute '$sql': ".$dbh->errstr());
|
||||
or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute($Event->{Id})
|
||||
or Fatal("Unable to execute '$sql': ".$dbh->errstr());
|
||||
}
|
||||
return 1;
|
||||
} # end sub uploadArchFile
|
||||
|
@ -615,9 +632,9 @@ sub substituteTags {
|
|||
WHERE EventId = ? AND Type = 'Alarm'
|
||||
ORDER BY FrameId`;
|
||||
my $sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Unable toprepare '$sql': ".$dbh->errstr());
|
||||
or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute($Event->{Id})
|
||||
or Fatal( "Unable toexecute '$sql': ".$dbh->errstr());
|
||||
or Fatal("Unable to execute '$sql': ".$dbh->errstr());
|
||||
my $rows = 0;
|
||||
while( my $frame = $sth->fetchrow_hashref() ) {
|
||||
if ( !$first_alarm_frame ) {
|
||||
|
@ -706,13 +723,17 @@ sub substituteTags {
|
|||
}
|
||||
} # end if $first_alarm_frame
|
||||
|
||||
if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} ) {
|
||||
if ( $attachments_ref ) {
|
||||
if ( $text =~ s/%EV%//g ) {
|
||||
my ( $format, $path ) = generateVideo($filter, $Event);
|
||||
if ( !$format ) {
|
||||
return undef;
|
||||
if ( $$Event{DefaultVideo} ) {
|
||||
push @$attachments_ref, { type=>'video/mp4', path=>join('/',$Event->Path(), $Event->DefaultVideo()) };
|
||||
} elsif ( $Config{ZM_OPT_FFMPEG} ) {
|
||||
my ( $format, $path ) = generateVideo($filter, $Event);
|
||||
if ( !$format ) {
|
||||
return undef;
|
||||
}
|
||||
push( @$attachments_ref, { type=>"video/$format", path=>$path } );
|
||||
}
|
||||
push( @$attachments_ref, { type=>"video/$format", path=>$path } );
|
||||
}
|
||||
if ( $text =~ s/%EVM%//g ) {
|
||||
my ( $format, $path ) = generateVideo($filter, $Event, 1);
|
||||
|
@ -785,7 +806,7 @@ sub sendEmail {
|
|||
$ssmtp_location = qx('which ssmtp');
|
||||
}
|
||||
if ( !$ssmtp_location ) {
|
||||
Debug('Unable tofind ssmtp, trying MIME::Lite->send');
|
||||
Debug('Unable to find ssmtp, trying MIME::Lite->send');
|
||||
MIME::Lite->send('smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60);
|
||||
$mail->send();
|
||||
} else {
|
||||
|
@ -817,16 +838,16 @@ sub sendEmail {
|
|||
}
|
||||
};
|
||||
if ( $@ ) {
|
||||
Error("Unable tosend email: $@");
|
||||
Error("Unable to send email: $@");
|
||||
return 0;
|
||||
} else {
|
||||
Info('Notification email sent');
|
||||
}
|
||||
my $sql = 'UPDATE Events SET Emailed = 1 WHERE Id = ?';
|
||||
my $sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Unable toprepare '$sql': ".$dbh->errstr());
|
||||
or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute($Event->{Id})
|
||||
or Fatal("Unable toexecute '$sql': ".$dbh->errstr());
|
||||
or Fatal("Unable to execute '$sql': ".$dbh->errstr());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -887,7 +908,7 @@ sub sendMessage {
|
|||
$ssmtp_location = qx('which ssmtp');
|
||||
}
|
||||
if ( !$ssmtp_location ) {
|
||||
Debug('Unable tofind ssmtp, trying MIME::Lite->send');
|
||||
Debug('Unable to find ssmtp, trying MIME::Lite->send');
|
||||
MIME::Lite->send(smtp=>$Config{ZM_EMAIL_HOST}, Timeout=>60);
|
||||
$mail->send();
|
||||
} else {
|
||||
|
@ -922,29 +943,29 @@ sub sendMessage {
|
|||
}
|
||||
};
|
||||
if ( $@ ) {
|
||||
Error("Unable tosend email: $@");
|
||||
Error("Unable to send email: $@");
|
||||
return 0;
|
||||
} else {
|
||||
Info('Notification message sent');
|
||||
}
|
||||
my $sql = 'UPDATE Events SET Messaged = 1 WHERE Id = ?';
|
||||
my $sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Unable toprepare '$sql': ".$dbh->errstr());
|
||||
or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute($Event->{Id})
|
||||
or Fatal("Unable toexecute '$sql': ".$dbh->errstr());
|
||||
or Fatal("Unable to execute '$sql': ".$dbh->errstr());
|
||||
|
||||
return 1;
|
||||
} # end sub sendMessage
|
||||
|
||||
sub executeCommand {
|
||||
my $filter = shift;
|
||||
my $event = shift;
|
||||
my $Event = shift;
|
||||
|
||||
my $event_path = getEventPath($event);
|
||||
my $event_path = $Event->Path();
|
||||
|
||||
my $command = $filter->{AutoExecuteCmd};
|
||||
$command .= " $event_path";
|
||||
$command = substituteTags($command, $filter, $event);
|
||||
$command = substituteTags($command, $filter, $Event);
|
||||
|
||||
Info("Executing '$command'");
|
||||
my $output = qx($command);
|
||||
|
@ -960,7 +981,7 @@ sub executeCommand {
|
|||
my $sql = 'UPDATE Events SET Executed = 1 WHERE Id = ?';
|
||||
my $sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute( $event->{Id} )
|
||||
my $res = $sth->execute( $Event->{Id} )
|
||||
or Fatal("Unable to execute '$sql': ".$dbh->errstr());
|
||||
}
|
||||
return( 1 );
|
||||
|
|
|
@ -41,7 +41,7 @@ use autouse 'Pod::Usage'=>qw(pod2usage);
|
|||
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
|
||||
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
||||
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
||||
my $store_state=""; # PP - will remember state name passed
|
||||
my $store_state=''; # PP - will remember state name passed
|
||||
|
||||
logInit();
|
||||
|
||||
|
@ -53,28 +53,27 @@ if ( $command eq 'version' ) {
|
|||
|
||||
my $state;
|
||||
|
||||
my $dbh;
|
||||
|
||||
my $dbh = zmDbConnect();
|
||||
Info("Command: $command");
|
||||
if ( !$command || $command !~ /^(?:start|stop|restart|status|logrot|version)$/ ) {
|
||||
if ( $command ) {
|
||||
$dbh = zmDbConnect();
|
||||
# Check to see if it's a valid run state
|
||||
my $sql = 'SELECT * FROM States WHERE Name=?';
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
my $res = $sth->execute( $command )
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
my $sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute($command)
|
||||
or Fatal("Can't execute: ".$sth->errstr());
|
||||
if ( $state = $sth->fetchrow_hashref() ) {
|
||||
$state->{Name} = $command;
|
||||
#$state->{Name} = $command;
|
||||
$state->{Definitions} = [];
|
||||
foreach( split( /,/, $state->{Definition} ) ) {
|
||||
my ( $id, $function, $enabled ) = split( /:/, $_ );
|
||||
foreach( split(',', $state->{Definition}) ) {
|
||||
my ( $id, $function, $enabled ) = split(':', $_);
|
||||
push( @{$state->{Definitions}},
|
||||
{ Id=>$id, Function=>$function, Enabled=>$enabled }
|
||||
);
|
||||
}
|
||||
$store_state=$command; # PP - Remember the name that was passed to search in DB
|
||||
$command = 'state';
|
||||
$store_state = $command; # PP - Remember the name that was passed to search in DB
|
||||
$command = 'state';
|
||||
} else {
|
||||
$command = undef;
|
||||
}
|
||||
|
@ -82,64 +81,66 @@ if ( !$command || $command !~ /^(?:start|stop|restart|status|logrot|version)$/ )
|
|||
if ( !$command ) {
|
||||
pod2usage(-exitstatus => -1);
|
||||
}
|
||||
}
|
||||
$dbh = zmDbConnect() if ! $dbh;
|
||||
} # end if not one of the usual commands
|
||||
|
||||
# PP - Sane state check
|
||||
Debug("StartisActiveSSantiyCheck");
|
||||
isActiveSanityCheck();
|
||||
Debug("Done isActiveSSantiyCheck");
|
||||
|
||||
# Move to the right place
|
||||
chdir( $Config{ZM_PATH_WEB} )
|
||||
or Fatal( "Can't chdir to '".$Config{ZM_PATH_WEB}."': $!" );
|
||||
chdir($Config{ZM_PATH_WEB})
|
||||
or Fatal("Can't chdir to '$Config{ZM_PATH_WEB}': $!");
|
||||
|
||||
my $dbg_id = '';
|
||||
my $dbg_id = '';
|
||||
|
||||
Info( "Command: $command\n" );
|
||||
Info("Command: $command");
|
||||
|
||||
my $retval = 0;
|
||||
my $retval = 0;
|
||||
|
||||
if ( $command eq 'state' ) {
|
||||
Info( "Updating DB: $state->{Name}\n" );
|
||||
my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=? ORDER BY Id ASC' : 'SELECT * FROM Monitors ORDER BY Id ASC';
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID}: () )
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
while( my $monitor = $sth->fetchrow_hashref() ) {
|
||||
foreach my $definition ( @{$state->{Definitions}} ) {
|
||||
if ( $monitor->{Id} =~ /^$definition->{Id}$/ ) {
|
||||
$monitor->{NewFunction} = $definition->{Function};
|
||||
$monitor->{NewEnabled} = $definition->{Enabled};
|
||||
}
|
||||
}
|
||||
#next if ( !$monitor->{NewFunction} );
|
||||
$monitor->{NewFunction} = 'None'
|
||||
if ( !$monitor->{NewFunction} );
|
||||
$monitor->{NewEnabled} = 0
|
||||
if ( !$monitor->{NewEnabled} );
|
||||
if ( $monitor->{Function} ne $monitor->{NewFunction}
|
||||
|| $monitor->{Enabled} ne $monitor->{NewEnabled}
|
||||
) {
|
||||
my $sql = 'UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?';
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
my $res = $sth->execute( $monitor->{NewFunction}, $monitor->{NewEnabled}, $monitor->{Id} )
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
if ( $command eq 'state' ) {
|
||||
Info("Updating DB: $state->{Name}");
|
||||
my $sql = 'SELECT * FROM Monitors' . ($Config{ZM_SERVER_ID} ? ' WHERE ServerId=?' : '' ) .' ORDER BY Id ASC';
|
||||
my $sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute($Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID}: ())
|
||||
or Fatal("Can't execute: ".$sth->errstr());
|
||||
while( my $monitor = $sth->fetchrow_hashref() ) {
|
||||
foreach my $definition ( @{$state->{Definitions}} ) {
|
||||
if ( $monitor->{Id} =~ /^$definition->{Id}$/ ) {
|
||||
$monitor->{NewFunction} = $definition->{Function};
|
||||
$monitor->{NewEnabled} = $definition->{Enabled};
|
||||
}
|
||||
}
|
||||
$sth->finish();
|
||||
#next if ( !$monitor->{NewFunction} );
|
||||
$monitor->{NewFunction} = 'None'
|
||||
if ( !$monitor->{NewFunction} );
|
||||
$monitor->{NewEnabled} = 0
|
||||
if ( !$monitor->{NewEnabled} );
|
||||
if ( $monitor->{Function} ne $monitor->{NewFunction}
|
||||
|| $monitor->{Enabled} ne $monitor->{NewEnabled}
|
||||
) {
|
||||
my $sql = 'UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?';
|
||||
my $sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute($monitor->{NewFunction}, $monitor->{NewEnabled}, $monitor->{Id})
|
||||
or Fatal("Can't execute: ".$sth->errstr());
|
||||
} # end if change of function or enablement
|
||||
} # end foreach monitor
|
||||
$sth->finish();
|
||||
|
||||
# PP - Now mark a specific state as active
|
||||
resetStates();
|
||||
Info ("Marking $store_state as Enabled");
|
||||
$sql = "UPDATE States SET IsActive = '1' WHERE Name = ?";
|
||||
$sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
$res = $sth->execute( $store_state )
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
# PP - Now mark a specific state as active
|
||||
resetStates();
|
||||
Info("Marking $store_state as Enabled");
|
||||
$sql = 'UPDATE States SET IsActive = 1 WHERE Name = ?';
|
||||
$sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
|
||||
$res = $sth->execute($store_state)
|
||||
or Fatal("Can't execute: ".$sth->errstr());
|
||||
|
||||
# PP - zero out other states isActive
|
||||
$command = 'restart';
|
||||
}
|
||||
# PP - zero out other states isActive
|
||||
$command = 'restart';
|
||||
} # end if command = state
|
||||
|
||||
# Check if we are running systemd and if we have been called by the system
|
||||
if ( $command =~ /^(start|stop|restart)$/ ) {
|
||||
|
@ -154,6 +155,7 @@ if ( $command =~ /^(start|stop|restart)$/ ) {
|
|||
|
||||
if ( $command =~ /^(?:stop|restart)$/ ) {
|
||||
my $status = runCommand('zmdc.pl check');
|
||||
Debug("zmdc.pl check = $status");
|
||||
|
||||
if ( $status eq 'running' ) {
|
||||
runCommand('zmdc.pl shutdown');
|
||||
|
@ -163,20 +165,19 @@ if ( $command =~ /^(?:stop|restart)$/ ) {
|
|||
}
|
||||
}
|
||||
|
||||
#runCommand( "zmupdate.pl -f" );
|
||||
|
||||
if ( $command =~ /^(?:start|restart)$/ ) {
|
||||
my $status = runCommand('zmdc.pl check');
|
||||
Debug("zmdc.pl check = $status");
|
||||
|
||||
if ( $status eq 'stopped' ) {
|
||||
if ( $Config{ZM_DYN_DB_VERSION}
|
||||
and ( $Config{ZM_DYN_DB_VERSION} ne ZM_VERSION )
|
||||
) {
|
||||
Fatal( 'Version mismatch, system is version '.ZM_VERSION
|
||||
Fatal('Version mismatch, system is version '.ZM_VERSION
|
||||
.', database is '.$Config{ZM_DYN_DB_VERSION}
|
||||
.', please run zmupdate.pl to update.'
|
||||
);
|
||||
exit( -1 );
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
# Recreate the temporary directory if it's been wiped
|
||||
|
@ -196,8 +197,8 @@ if ( $command =~ /^(?:start|restart)$/ ) {
|
|||
my @values;
|
||||
if ( $Config{ZM_SERVER_ID} ) {
|
||||
require ZoneMinder::Server;
|
||||
Info("Multi-server configuration detected. Starting up services for server $Config{ZM_SERVER_ID}\n");
|
||||
$Server = new ZoneMinder::Server( $Config{ZM_SERVER_ID} );
|
||||
Info("Multi-server configuration detected. Starting up services for server $Config{ZM_SERVER_ID}");
|
||||
$Server = new ZoneMinder::Server($Config{ZM_SERVER_ID});
|
||||
$sql = 'SELECT * FROM Monitors WHERE ServerId=?';
|
||||
@values = ( $Config{ZM_SERVER_ID} );
|
||||
} else {
|
||||
|
@ -206,46 +207,49 @@ if ( $command =~ /^(?:start|restart)$/ ) {
|
|||
}
|
||||
|
||||
{
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
my $res = $sth->execute( @values )
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
while( my $monitor = $sth->fetchrow_hashref() ) {
|
||||
if ( $monitor->{Function} ne 'None' && $monitor->{Type} ne 'WebSite' ) {
|
||||
if ( $monitor->{Type} eq 'Local' ) {
|
||||
runCommand( "zmdc.pl start zmc -d $monitor->{Device}" );
|
||||
} else {
|
||||
runCommand( "zmdc.pl start zmc -m $monitor->{Id}" );
|
||||
}
|
||||
if ( $monitor->{Function} ne 'Monitor' ) {
|
||||
runCommand( "zmdc.pl start zma -m $monitor->{Id}" );
|
||||
}
|
||||
if ( $Config{ZM_OPT_CONTROL} ) {
|
||||
if ( $monitor->{Function} eq 'Modect' || $monitor->{Function} eq 'Mocord' ) {
|
||||
if ( $monitor->{Controllable} && $monitor->{TrackMotion} ) {
|
||||
runCommand( "zmdc.pl start zmtrack.pl -m $monitor->{Id}" );
|
||||
}
|
||||
my $sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
|
||||
my $res = $sth->execute(@values)
|
||||
or Fatal("Can't execute: ".$sth->errstr());
|
||||
while( my $monitor = $sth->fetchrow_hashref() ) {
|
||||
if ( $monitor->{Function} ne 'None' && $monitor->{Type} ne 'WebSite' ) {
|
||||
if ( $monitor->{Type} eq 'Local' ) {
|
||||
runCommand("zmdc.pl start zmc -d $monitor->{Device}");
|
||||
} else {
|
||||
runCommand("zmdc.pl start zmc -m $monitor->{Id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$sth->finish();
|
||||
if ( $monitor->{Function} ne 'Monitor' ) {
|
||||
runCommand("zmdc.pl start zma -m $monitor->{Id}");
|
||||
}
|
||||
if ( $Config{ZM_OPT_CONTROL} ) {
|
||||
if ( $monitor->{Controllable} && $monitor->{TrackMotion} ) {
|
||||
if ( $monitor->{Function} eq 'Modect' || $monitor->{Function} eq 'Mocord' ) {
|
||||
runCommand( "zmdc.pl start zmtrack.pl -m $monitor->{Id}" );
|
||||
} else {
|
||||
Warning(' Monitor is set to track motion, but does not have motion detection enabled.');
|
||||
} # end if Has motion enabled
|
||||
} # end if track motion
|
||||
} # end if ZM_OPT_CONTROL
|
||||
} # end if function is not none or Website
|
||||
} # end foreach monitor
|
||||
$sth->finish();
|
||||
}
|
||||
|
||||
{
|
||||
my $sql = 'SELECT Id FROM Filters WHERE Background=1';
|
||||
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() );
|
||||
if ( $sth->rows ) {
|
||||
while( my $filter = $sth->fetchrow_hashref() ) {
|
||||
# This is now started unconditionally
|
||||
runCommand("zmdc.pl start zmfilter.pl --filter_id=$$filter{Id}");
|
||||
my $sql = 'SELECT Id FROM Filters WHERE Background=1';
|
||||
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());
|
||||
if ( $sth->rows ) {
|
||||
while( my $filter = $sth->fetchrow_hashref() ) {
|
||||
# This is now started unconditionally
|
||||
runCommand("zmdc.pl start zmfilter.pl --filter_id=$$filter{Id} --daemon");
|
||||
}
|
||||
} else {
|
||||
runCommand('zmdc.pl start zmfilter.pl');
|
||||
}
|
||||
} else {
|
||||
runCommand('zmdc.pl start zmfilter.pl');
|
||||
}
|
||||
$sth->finish();
|
||||
$sth->finish();
|
||||
}
|
||||
|
||||
if ( $Config{ZM_RUN_AUDIT} ) {
|
||||
|
@ -283,102 +287,88 @@ if ( $command =~ /^(?:start|restart)$/ ) {
|
|||
} else {
|
||||
$retval = 1;
|
||||
}
|
||||
}
|
||||
} # end if command is start or restart
|
||||
|
||||
if ( $command eq 'status' ) {
|
||||
my $status = runCommand('zmdc.pl check');
|
||||
|
||||
print( STDOUT $status."\n" );
|
||||
print(STDOUT $status."\n");
|
||||
} elsif ( $command eq 'logrot' ) {
|
||||
runCommand('zmdc.pl logrot');
|
||||
}
|
||||
|
||||
exit( $retval );
|
||||
exit($retval);
|
||||
|
||||
# PP - Make sure isActive is on and only one
|
||||
sub isActiveSanityCheck {
|
||||
|
||||
Info ('Sanity checking States table...');
|
||||
Info('Sanity checking States table...');
|
||||
$dbh = zmDbConnect() if ! $dbh;
|
||||
|
||||
# PP - First, make sure default exists and there is only one
|
||||
my $sql = "SELECT Name FROM States WHERE Name='default'";
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
my $sql = q`SELECT Name FROM States WHERE Name='default'`;
|
||||
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() );
|
||||
|
||||
if ($sth->rows != 1) {
|
||||
# PP - no row, or too many rows. Either case is an error
|
||||
Info( 'Fixing States table - either no default state or duplicate default states' );
|
||||
$sql = "DELETE FROM States WHERE Name='default'";
|
||||
$sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
$res = $sth->execute()
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
$sql = "INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');";
|
||||
$sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
$res = $sth->execute()
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
}
|
||||
|
||||
|
||||
# PP - Now make sure no two states have IsActive=1
|
||||
$sql = "SELECT Name FROM States WHERE IsActive = '1'";
|
||||
$sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
$res = $sth->execute()
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
or Fatal("Can't execute: ".$sth->errstr());
|
||||
|
||||
if ( $sth->rows != 1 ) {
|
||||
Info( 'Fixing States table so only one run state is active' );
|
||||
resetStates();
|
||||
$sql = "UPDATE States SET IsActive='1' WHERE Name='default'";
|
||||
$sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
# PP - no row, or too many rows. Either case is an error
|
||||
Info( 'Fixing States table - either no default state or duplicate default states' );
|
||||
if ( $sth->rows ) {
|
||||
$sql = q`DELETE FROM States WHERE Name='default'`;
|
||||
$sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
$res = $sth->execute()
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
}
|
||||
$sql = q`INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');`;
|
||||
$sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
|
||||
$res = $sth->execute()
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
or Fatal("Can't execute: ".$sth->errstr());
|
||||
}
|
||||
}
|
||||
|
||||
# PP - Now make sure no two states have IsActive=1
|
||||
$sql = 'SELECT Name FROM States WHERE IsActive = 1';
|
||||
$sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
|
||||
$res = $sth->execute()
|
||||
or Fatal("Can't execute: ".$sth->errstr());
|
||||
|
||||
if ( $sth->rows != 1 ) {
|
||||
Info('Fixing States table so only one run state is active');
|
||||
resetStates();
|
||||
$sql = q`UPDATE States SET IsActive=1 WHERE Name='default'`;
|
||||
$sth = $dbh->prepare_cached($sql)
|
||||
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
|
||||
$res = $sth->execute()
|
||||
or Fatal("Can't execute: ".$sth->errstr());
|
||||
}
|
||||
} # end sub isActiveSanityCheck
|
||||
|
||||
# PP - zeroes out isActive for all states
|
||||
sub resetStates {
|
||||
$dbh = zmDbConnect() if ! $dbh;
|
||||
my $sql = "UPDATE States SET IsActive='0'";
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
my $sql = 'UPDATE States SET IsActive=0';
|
||||
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() );
|
||||
|
||||
or Fatal("Can't execute: ".$sth->errstr());
|
||||
}
|
||||
|
||||
sub systemdRunning {
|
||||
my $result = 0;
|
||||
|
||||
my $output = qx(ps -o comm="" -p 1);
|
||||
chomp( $output );
|
||||
|
||||
if ( $output =~ /systemd/ ) {
|
||||
$result = 1;
|
||||
}
|
||||
|
||||
return $result;
|
||||
return scalar ( $output =~ /systemd/ );
|
||||
}
|
||||
|
||||
sub calledBysystem {
|
||||
my $result = 0;
|
||||
my $ppid = getppid();
|
||||
|
||||
my $output = qx(ps -o comm="" -p $ppid);
|
||||
chomp( $output );
|
||||
#chomp( $output );
|
||||
|
||||
if ($output =~ /^(?:systemd|init)$/) {
|
||||
$result = 1;
|
||||
}
|
||||
|
||||
return $result;
|
||||
return ($output =~ /^(?:systemd|init)$/);
|
||||
}
|
||||
|
||||
sub verifyFolder {
|
||||
|
@ -386,24 +376,22 @@ sub verifyFolder {
|
|||
|
||||
# Recreate the temporary directory if it's been wiped
|
||||
if ( !-e $folder ) {
|
||||
Debug( "Recreating directory '$folder'" );
|
||||
mkdir( $folder, 0774 )
|
||||
Debug("Recreating directory '$folder'");
|
||||
mkdir($folder, 0774)
|
||||
or Fatal( "Can't create missing temporary directory '$folder': $!" );
|
||||
my ( $runName ) = getpwuid( $> );
|
||||
my ( $runName ) = getpwuid($>);
|
||||
if ( $runName ne $Config{ZM_WEB_USER} ) {
|
||||
# Not running as web user, so should be root in which case
|
||||
# chown the directory
|
||||
my ( $webName, $webPass, $webUid, $webGid ) = getpwnam( $Config{ZM_WEB_USER} )
|
||||
or Fatal( "Can't get user details for web user '"
|
||||
.$Config{ZM_WEB_USER}."': $!"
|
||||
my ( $webName, $webPass, $webUid, $webGid ) = getpwnam($Config{ZM_WEB_USER})
|
||||
or Fatal("Can't get details for web user '$Config{ZM_WEB_USER}': $!");
|
||||
chown($webUid, $webGid, $folder)
|
||||
or Fatal("Can't change ownership of '$folder' to '"
|
||||
.$Config{ZM_WEB_USER}.':'.$Config{ZM_WEB_GROUP}."': $!"
|
||||
);
|
||||
chown( $webUid, $webGid, "$folder" )
|
||||
or Fatal( "Can't change ownership of directory '$folder' to '"
|
||||
.$Config{ZM_WEB_USER}.":".$Config{ZM_WEB_GROUP}."': $!"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} # end if runName ne ZM_WEB_USER
|
||||
} # end if folder doesn't exist
|
||||
} # end sub verifyFolder
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
|
|
@ -1,4 +1,63 @@
|
|||
#!/usr/bin/perl -wT
|
||||
use strict;
|
||||
use bytes;
|
||||
|
||||
# ==========================================================================
|
||||
#
|
||||
# These are the elements you can edit to suit your installation
|
||||
#
|
||||
# ==========================================================================
|
||||
|
||||
use constant START_DELAY => 30; # To give everything else time to start
|
||||
|
||||
# ==========================================================================
|
||||
#
|
||||
# Don't change anything below here
|
||||
#
|
||||
# ==========================================================================
|
||||
|
||||
@EXTRA_PERL_LIB@
|
||||
use ZoneMinder;
|
||||
use POSIX;
|
||||
use DBI;
|
||||
use autouse 'Data::Dumper'=>qw(Dumper);
|
||||
|
||||
$| = 1;
|
||||
|
||||
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
|
||||
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
||||
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
||||
|
||||
logInit();
|
||||
logSetSignal();
|
||||
|
||||
Info("Stats Daemon starting in ".START_DELAY." seconds");
|
||||
sleep(START_DELAY);
|
||||
|
||||
my $dbh = zmDbConnect();
|
||||
|
||||
while( 1 ) {
|
||||
while ( ! ( $dbh and $dbh->ping() ) ) {
|
||||
Info("Reconnecting to db");
|
||||
if ( ! ( $dbh = zmDbConnect() ) ) {
|
||||
#What we do here is not that important, so just skip this interval
|
||||
sleep($Config{ZM_STATS_UPDATE_INTERVAL});
|
||||
}
|
||||
}
|
||||
|
||||
$dbh->do('DELETE FROM Events_Hour WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 hour)') or Error($dbh->errstr());
|
||||
$dbh->do('DELETE FROM Events_Day WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 day)') or Error($dbh->errstr());
|
||||
$dbh->do('DELETE FROM Events_Week WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 week)') or Error($dbh->errstr());
|
||||
$dbh->do('DELETE FROM Events_Month WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 month)') or Error($dbh->errstr());
|
||||
|
||||
sleep($Config{ZM_STATS_UPDATE_INTERVAL});
|
||||
} # end while (1)
|
||||
|
||||
Info("Stats Daemon exiting");
|
||||
exit();
|
||||
1;
|
||||
__END__
|
||||
|
||||
#
|
||||
# ==========================================================================
|
||||
#
|
||||
|
@ -23,7 +82,7 @@
|
|||
|
||||
=head1 NAME
|
||||
|
||||
zmwatch.pl - ZoneMinder Stats Updating Script
|
||||
zmstats.pl - ZoneMinder Stats Updating Script
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
|
@ -34,64 +93,3 @@ zmstats.pl
|
|||
This does background updating various stats in the db like event counts, diskspace, etc.
|
||||
|
||||
=cut
|
||||
use strict;
|
||||
use bytes;
|
||||
|
||||
# ==========================================================================
|
||||
#
|
||||
# These are the elements you can edit to suit your installation
|
||||
#
|
||||
# ==========================================================================
|
||||
|
||||
use constant START_DELAY => 30; # To give everything else time to start
|
||||
|
||||
# ==========================================================================
|
||||
#
|
||||
# Don't change anything below here
|
||||
#
|
||||
# ==========================================================================
|
||||
|
||||
@EXTRA_PERL_LIB@
|
||||
use ZoneMinder;
|
||||
use ZoneMinder::Storage;
|
||||
use POSIX;
|
||||
use DBI;
|
||||
use autouse 'Data::Dumper'=>qw(Dumper);
|
||||
|
||||
$| = 1;
|
||||
|
||||
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
|
||||
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
||||
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
||||
|
||||
logInit();
|
||||
logSetSignal();
|
||||
|
||||
Info( "Stats Daemon starting in ".START_DELAY." seconds\n" );
|
||||
sleep( START_DELAY );
|
||||
|
||||
my $dbh = zmDbConnect();
|
||||
|
||||
my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=?' : 'SELECT * FROM Monitors';
|
||||
my $sth = $dbh->prepare_cached( $sql )
|
||||
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
|
||||
|
||||
while( 1 ) {
|
||||
while ( ! ( $dbh and $dbh->ping() ) ) {
|
||||
Info("Reconnecting to db");
|
||||
$dbh = zmDbConnect();
|
||||
}
|
||||
|
||||
$dbh->do('DELETE FROM Events_Hour WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 hour)') or Error($dbh->errstr());
|
||||
$dbh->do('DELETE FROM Events_Day WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 day)') or Error($dbh->errstr());
|
||||
$dbh->do('DELETE FROM Events_Week WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 week)') or Error($dbh->errstr());
|
||||
$dbh->do('DELETE FROM Events_Month WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 month)') or Error($dbh->errstr());
|
||||
|
||||
sleep( $Config{ZM_STATS_UPDATE_INTERVAL} );
|
||||
} # end while (1)
|
||||
|
||||
Info( "Stats Daemon exiting\n" );
|
||||
exit();
|
||||
1;
|
||||
__END__
|
||||
|
|
|
@ -33,6 +33,7 @@ use LWP::UserAgent;
|
|||
use Sys::MemInfo qw(totalmem);
|
||||
use Sys::CPU qw(cpu_count);
|
||||
use POSIX qw(strftime uname);
|
||||
use JSON::MaybeXS;
|
||||
|
||||
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
|
||||
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
||||
|
@ -87,7 +88,7 @@ while( 1 ) {
|
|||
# We should keep *BSD systems in mind when calling system commands
|
||||
my %telemetry;
|
||||
$telemetry{uuid} = getUUID($dbh);
|
||||
$telemetry{ip} = getIP();
|
||||
($telemetry{city}, $telemetry{region}, $telemetry{country}, $telemetry{latitude}, $telemetry{longitude}) = getGeo();
|
||||
$telemetry{timestamp} = strftime( '%Y-%m-%dT%H:%M:%S%z', localtime() );
|
||||
$telemetry{monitor_count} = countQuery($dbh,'Monitors');
|
||||
$telemetry{event_count} = countQuery($dbh,'Events');
|
||||
|
@ -203,22 +204,25 @@ sub getUUID {
|
|||
return $uuid;
|
||||
}
|
||||
|
||||
# Retrieves the local server's external IP address
|
||||
sub getIP {
|
||||
my $ipaddr = '0.0.0.0';
|
||||
# Retrieve this server's general location information from a GeoIP database
|
||||
sub getGeo {
|
||||
my $unknown = 'Unknown';
|
||||
my $endpoint = 'https://ipinfo.io/geo';
|
||||
my $ua = LWP::UserAgent->new;
|
||||
my $server_endpoint = 'https://wiki.zoneminder.com/ip.php';
|
||||
|
||||
my $req = HTTP::Request->new(GET => $server_endpoint);
|
||||
my $req = HTTP::Request->new(GET => $endpoint);
|
||||
my $resp = $ua->request($req);
|
||||
my $resp_msg = $resp->decoded_content;
|
||||
my $resp_code = $resp->code;
|
||||
|
||||
if ($resp->is_success) {
|
||||
$ipaddr = $resp->decoded_content;
|
||||
my $content = decode_json( $resp_msg );
|
||||
(my $latitude, my $longitude) = split /,/, $content->{loc};
|
||||
return ($content->{city}, $content->{region}, $content->{country}, $latitude, $longitude);
|
||||
} else {
|
||||
Warning("Geoip data retrieval returned HTTP POST error code: $resp_code");
|
||||
Debug("Geoip data retrieval failure response message: $resp_msg");
|
||||
return ($unknown, $unknown, $unknown, $unknown);
|
||||
}
|
||||
|
||||
Debug("Found external ip address of: $ipaddr");
|
||||
|
||||
return $ipaddr;
|
||||
}
|
||||
|
||||
# As the name implies, just your average mysql count query
|
||||
|
|
|
@ -88,13 +88,13 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
|||
logInit();
|
||||
logSetSignal();
|
||||
|
||||
Info( "Trigger daemon starting\n" );
|
||||
Info( "Trigger daemon starting" );
|
||||
|
||||
my $dbh = zmDbConnect();
|
||||
|
||||
my $base_rin = '';
|
||||
foreach my $connection ( @connections ) {
|
||||
Info( "Opening connection '$connection->{name}'\n" );
|
||||
Info( "Opening connection '$connection->{name}'" );
|
||||
$connection->open();
|
||||
}
|
||||
|
||||
|
@ -109,7 +109,7 @@ foreach my $connection ( @in_select_connections ) {
|
|||
my %spawned_connections;
|
||||
my %monitors;
|
||||
my $monitor_reload_time = 0;
|
||||
my $needsReload = 0;
|
||||
my @needsReload;
|
||||
loadMonitors();
|
||||
|
||||
$! = undef;
|
||||
|
@ -127,14 +127,14 @@ while( 1 ) {
|
|||
|
||||
my $nfound = select( my $rout = $rin, undef, my $eout = $ein, $timeout );
|
||||
if ( $nfound > 0 ) {
|
||||
Debug( "Got input from $nfound connections\n" );
|
||||
Debug( "Got input from $nfound connections" );
|
||||
foreach my $connection ( @in_select_connections ) {
|
||||
if ( vec( $rout, $connection->fileno(), 1 ) ) {
|
||||
Debug( 'Got input from connection '
|
||||
.$connection->name()
|
||||
.' ('
|
||||
.$connection->fileno()
|
||||
.")\n"
|
||||
.")"
|
||||
);
|
||||
if ( $connection->spawns() ) {
|
||||
my $new_connection = $connection->accept();
|
||||
|
@ -143,7 +143,7 @@ while( 1 ) {
|
|||
.$new_connection->fileno()
|
||||
.'), '
|
||||
.int(keys(%spawned_connections))
|
||||
." spawned connections\n"
|
||||
." spawned connections"
|
||||
);
|
||||
} else {
|
||||
my $messages = $connection->getMessages();
|
||||
|
@ -162,7 +162,7 @@ while( 1 ) {
|
|||
.$connection->name()
|
||||
.' ('
|
||||
.$connection->fileno()
|
||||
.")\n"
|
||||
.")"
|
||||
);
|
||||
my $messages = $connection->getMessages();
|
||||
if ( defined($messages) ) {
|
||||
|
@ -175,7 +175,7 @@ while( 1 ) {
|
|||
.$connection->fileno()
|
||||
.'), '
|
||||
.int(keys(%spawned_connections))
|
||||
." spawned connections\n"
|
||||
." spawned connections"
|
||||
);
|
||||
$connection->close();
|
||||
}
|
||||
|
@ -206,7 +206,7 @@ while( 1 ) {
|
|||
if ( ! zmMemVerify($monitor) ) {
|
||||
# Our attempt to verify the memory handle failed. We should reload the monitors.
|
||||
# Don't need to zmMemInvalidate because the monitor reload will do it.
|
||||
$needsReload = 1;
|
||||
push @needsReload, $monitor;
|
||||
next;
|
||||
}
|
||||
|
||||
|
@ -217,8 +217,8 @@ while( 1 ) {
|
|||
]
|
||||
);
|
||||
|
||||
#print( "$monitor->{Id}: S:$state, LE:$last_event\n" );
|
||||
#print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}\n" );
|
||||
#print( "$monitor->{Id}: S:$state, LE:$last_event" );
|
||||
#print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}" );
|
||||
if ( $state == STATE_ALARM || $state == STATE_ALERT ) {
|
||||
# In alarm state
|
||||
if ( !defined($monitor->{LastEvent})
|
||||
|
@ -261,14 +261,14 @@ while( 1 ) {
|
|||
}
|
||||
|
||||
if ( my @action_times = keys(%actions) ) {
|
||||
Debug( "Checking for timed actions\n" );
|
||||
Debug( "Checking for timed actions" );
|
||||
my $now = time();
|
||||
foreach my $action_time ( sort( grep { $_ < $now } @action_times ) ) {
|
||||
Info( "Found actions expiring at $action_time\n" );
|
||||
Info( "Found actions expiring at $action_time" );
|
||||
foreach my $action ( @{$actions{$action_time}} ) {
|
||||
my $connection = $action->{connection};
|
||||
my $message = $action->{message};
|
||||
Info( "Found action '$message'\n" );
|
||||
Info( "Found action '$message'" );
|
||||
handleMessage( $connection, $message );
|
||||
}
|
||||
delete( $actions{$action_time} );
|
||||
|
@ -293,23 +293,41 @@ while( 1 ) {
|
|||
}
|
||||
}
|
||||
|
||||
# If necessary reload monitors
|
||||
if ( $needsReload || ((time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL )) {
|
||||
# Reload all monitors from the dB every MONITOR_RELOAD_INTERVAL
|
||||
if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) {
|
||||
foreach my $monitor ( values(%monitors) ) {
|
||||
# Free up any used memory handle
|
||||
zmMemInvalidate( $monitor );
|
||||
zmMemInvalidate( $monitor ); # Free up any used memory handle
|
||||
}
|
||||
loadMonitors();
|
||||
$needsReload = 0;
|
||||
@needsReload = (); # We just reloaded all monitors so no need reload a specific monitor
|
||||
# If we have NOT just reloaded all monitors, reload a specific monitor if its shared mem changed
|
||||
} elsif ( @needsReload ) {
|
||||
foreach my $monitor ( @needsReload ) {
|
||||
loadMonitor($monitor);
|
||||
}
|
||||
@needsReload = ();
|
||||
}
|
||||
|
||||
# zmDbConnect will ping and reconnect if neccessary
|
||||
$dbh = zmDbConnect();
|
||||
} # end while ( 1 )
|
||||
Info( "Trigger daemon exiting\n" );
|
||||
Info( "Trigger daemon exiting" );
|
||||
exit;
|
||||
|
||||
sub loadMonitor {
|
||||
my $monitor = shift;
|
||||
|
||||
Debug( "Loading monitor $monitor" );
|
||||
zmMemInvalidate( $monitor );
|
||||
|
||||
if ( zmMemVerify( $monitor ) ) { # This will re-init shared memory
|
||||
$monitor->{LastState} = zmGetMonitorState( $monitor );
|
||||
$monitor->{LastEvent} = zmGetLastEvent( $monitor );
|
||||
}
|
||||
}
|
||||
|
||||
sub loadMonitors {
|
||||
Debug( "Loading monitors\n" );
|
||||
Debug( "Loading monitors" );
|
||||
$monitor_reload_time = time();
|
||||
|
||||
my %new_monitors = ();
|
||||
|
@ -323,14 +341,10 @@ sub loadMonitors {
|
|||
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
|
||||
or Fatal( "Can't execute: ".$sth->errstr() );
|
||||
while( my $monitor = $sth->fetchrow_hashref() ) {
|
||||
# Check shared memory ok
|
||||
if ( !zmMemVerify( $monitor ) ) {
|
||||
zmMemInvalidate( $monitor );
|
||||
next;
|
||||
if ( zmMemVerify( $monitor ) ) { # This will re-init shared memory
|
||||
$monitor->{LastState} = zmGetMonitorState( $monitor );
|
||||
$monitor->{LastEvent} = zmGetLastEvent( $monitor );
|
||||
}
|
||||
|
||||
$monitor->{LastState} = zmGetMonitorState( $monitor );
|
||||
$monitor->{LastEvent} = zmGetLastEvent( $monitor );
|
||||
$new_monitors{$monitor->{Id}} = $monitor;
|
||||
} # end while fetchrow
|
||||
%monitors = %new_monitors;
|
||||
|
@ -348,14 +362,14 @@ sub handleMessage {
|
|||
|
||||
my $monitor = $monitors{$id};
|
||||
if ( !$monitor ) {
|
||||
Warning( "Can't find monitor '$id' for message '$message'\n" );
|
||||
Warning( "Can't find monitor '$id' for message '$message'" );
|
||||
return;
|
||||
}
|
||||
Debug( "Found monitor for id '$id'\n" );
|
||||
Debug( "Found monitor for id '$id'" );
|
||||
|
||||
next if ( !zmMemVerify( $monitor ) );
|
||||
|
||||
Debug( "Handling action '$action'\n" );
|
||||
Debug( "Handling action '$action'" );
|
||||
if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ ) {
|
||||
my $state = $1;
|
||||
my $delay = $2;
|
||||
|
@ -366,7 +380,7 @@ sub handleMessage {
|
|||
}
|
||||
# Force a reload
|
||||
$monitor_reload_time = 0;
|
||||
Info( "Set monitor to $state\n" );
|
||||
Info( "Set monitor to $state" );
|
||||
if ( $delay ) {
|
||||
my $action_text = $id.'|'.( ($state eq 'enable')
|
||||
? 'disable'
|
||||
|
@ -383,7 +397,7 @@ sub handleMessage {
|
|||
if ( $trigger eq 'on' ) {
|
||||
zmTriggerEventOn( $monitor, $score, $cause, $text );
|
||||
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
|
||||
Info( "Trigger '$trigger' '$cause'\n" );
|
||||
Info( "Trigger '$trigger' '$cause'" );
|
||||
if ( $delay ) {
|
||||
my $action_text = $id.'|cancel';
|
||||
handleDelay($delay, $connection, $action_text);
|
||||
|
@ -396,7 +410,7 @@ sub handleMessage {
|
|||
my $last_event = zmGetLastEvent( $monitor );
|
||||
zmTriggerEventOff( $monitor );
|
||||
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
|
||||
Info( "Trigger '$trigger'\n" );
|
||||
Info( "Trigger '$trigger'" );
|
||||
# Wait til it's finished
|
||||
while( zmInAlarm( $monitor )
|
||||
&& ($last_event == zmGetLastEvent( $monitor ))
|
||||
|
@ -410,12 +424,12 @@ sub handleMessage {
|
|||
} elsif( $action eq 'cancel' ) {
|
||||
zmTriggerEventCancel( $monitor );
|
||||
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
|
||||
Info( "Cancelled event\n" );
|
||||
Info( "Cancelled event" );
|
||||
} elsif( $action eq 'show' ) {
|
||||
zmTriggerShowtext( $monitor, $showtext );
|
||||
Info( "Updated show text to '$showtext'\n" );
|
||||
Info( "Updated show text to '$showtext'" );
|
||||
} else {
|
||||
Error( "Unrecognised action '$action' in message '$message'\n" );
|
||||
Error( "Unrecognised action '$action' in message '$message'" );
|
||||
}
|
||||
} # end sub handleMessage
|
||||
|
||||
|
@ -430,7 +444,7 @@ sub handleDelay {
|
|||
$action_array = $actions{$action_time} = [];
|
||||
}
|
||||
push( @$action_array, { connection=>$connection, message=>$action_text } );
|
||||
Debug( "Added timed event '$action_text', expires at $action_time (+$delay secs)\n" );
|
||||
Debug( "Added timed event '$action_text', expires at $action_time (+$delay secs)" );
|
||||
}
|
||||
1;
|
||||
__END__
|
||||
|
|
|
@ -353,8 +353,8 @@ if ( $version ) {
|
|||
$version = $detaint_version;
|
||||
|
||||
if ( ZM_VERSION eq $version ) {
|
||||
print( "\nDatabase already at version $version, update aborted.\n\n" );
|
||||
exit( 0 );
|
||||
print("\nDatabase already at version $version, update skipped.\n\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
print( "\nInitiating database upgrade to version ".ZM_VERSION." from version $version\n" );
|
||||
|
@ -922,6 +922,11 @@ if ( $version ) {
|
|||
zmDbDisconnect();
|
||||
die( "Can't find upgrade from version '$version'" );
|
||||
}
|
||||
# Re-enable the privacy popup after each upgrade
|
||||
my $sql = "update Config set Value = 1 where Name = 'ZM_SHOW_PRIVACY'";
|
||||
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() );
|
||||
$sth->finish();
|
||||
print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" );
|
||||
}
|
||||
zmDbDisconnect();
|
||||
|
|
|
@ -173,6 +173,12 @@ my $cwd = getcwd;
|
|||
|
||||
my $video_name;
|
||||
my @event_ids;
|
||||
|
||||
# Fail if the path to a valid ffmpeg binary is not set
|
||||
if ( ! -x $Config{ZM_PATH_FFMPEG} ) {
|
||||
Fatal("Ffmpeg binary not found or not executable. Verify ZM_PATH_FFMPEG points to ffmpeg, avconv, or a compatible binary.");
|
||||
}
|
||||
|
||||
if ( $event_id ) {
|
||||
@event_ids = ( $event_id );
|
||||
|
||||
|
|
|
@ -78,6 +78,11 @@ my $sth = $dbh->prepare_cached($sql)
|
|||
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
|
||||
|
||||
while( 1 ) {
|
||||
while ( ! ( $dbh and $dbh->ping() ) ) {
|
||||
if ( ! ( $dbh = zmDbConnect() ) ) {
|
||||
sleep($Config{ZM_WATCH_CHECK_INTERVAL});
|
||||
}
|
||||
}
|
||||
|
||||
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
|
||||
or Fatal('Can\'t execute: '.$sth->errstr());
|
||||
|
@ -98,7 +103,7 @@ while( 1 ) {
|
|||
Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time.");
|
||||
if ( !$capture_time ) {
|
||||
my $startup_time = zmGetStartupTime($monitor);
|
||||
if ( $now - $startup_time > $Config{ZM_WATCH_MAX_DELAY} ) {
|
||||
if ( ( $now - $startup_time ) > $Config{ZM_WATCH_MAX_DELAY} ) {
|
||||
Info(
|
||||
"Restarting capture daemon for $$monitor{Name}, no image since startup. ".
|
||||
"Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}"
|
||||
|
@ -111,11 +116,12 @@ while( 1 ) {
|
|||
}
|
||||
}
|
||||
if ( ! $restart ) {
|
||||
my $max_image_delay = ( $monitor->{MaxFPS}
|
||||
&&($monitor->{MaxFPS}>0)
|
||||
&&($monitor->{MaxFPS}<1)
|
||||
) ? (3/$monitor->{MaxFPS})
|
||||
: $Config{ZM_WATCH_MAX_DELAY}
|
||||
my $max_image_delay = (
|
||||
$monitor->{MaxFPS}
|
||||
&&($monitor->{MaxFPS}>0)
|
||||
&&($monitor->{MaxFPS}<1)
|
||||
) ? (3/$monitor->{MaxFPS})
|
||||
: $Config{ZM_WATCH_MAX_DELAY}
|
||||
;
|
||||
my $image_delay = $now-$capture_time;
|
||||
Debug("Monitor $monitor->{Id} last captured $image_delay seconds ago, max is $max_image_delay");
|
||||
|
|
|
@ -33,8 +33,7 @@ class Camera;
|
|||
// Abstract base class for cameras. This is intended just to express
|
||||
// common attributes
|
||||
//
|
||||
class Camera
|
||||
{
|
||||
class Camera {
|
||||
protected:
|
||||
typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC } SourceType;
|
||||
|
||||
|
@ -53,46 +52,48 @@ protected:
|
|||
int contrast;
|
||||
bool capture;
|
||||
bool record_audio;
|
||||
unsigned int bytes;
|
||||
|
||||
unsigned int bytes;
|
||||
|
||||
public:
|
||||
Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
|
||||
virtual ~Camera();
|
||||
|
||||
unsigned int getId() const { return( monitor_id ); }
|
||||
unsigned int getId() const { return monitor_id; }
|
||||
Monitor *getMonitor();
|
||||
void setMonitor( Monitor *p_monitor );
|
||||
SourceType Type() const { return( type ); }
|
||||
bool IsLocal() const { return( type == LOCAL_SRC ); }
|
||||
bool IsRemote() const { return( type == REMOTE_SRC ); }
|
||||
bool IsFile() const { return( type == FILE_SRC ); }
|
||||
bool IsFfmpeg() const { return( type == FFMPEG_SRC ); }
|
||||
bool IsLibvlc() const { return( type == LIBVLC_SRC ); }
|
||||
bool IscURL() const { return( type == CURL_SRC ); }
|
||||
unsigned int Width() const { return( width ); }
|
||||
unsigned int Height() const { return( height ); }
|
||||
unsigned int Colours() const { return( colours ); }
|
||||
unsigned int SubpixelOrder() const { return( subpixelorder ); }
|
||||
unsigned int Pixels() const { return( pixels ); }
|
||||
unsigned int ImageSize() const { return( imagesize ); }
|
||||
SourceType Type() const { return type; }
|
||||
bool IsLocal() const { return type == LOCAL_SRC; }
|
||||
bool IsRemote() const { return type == REMOTE_SRC; }
|
||||
bool IsFile() const { return type == FILE_SRC; }
|
||||
bool IsFfmpeg() const { return type == FFMPEG_SRC; }
|
||||
bool IsLibvlc() const { return type == LIBVLC_SRC; }
|
||||
bool IscURL() const { return type == CURL_SRC; }
|
||||
unsigned int Width() const { return width; }
|
||||
unsigned int Height() const { return height; }
|
||||
unsigned int Colours() const { return colours; }
|
||||
unsigned int SubpixelOrder() const { return subpixelorder; }
|
||||
unsigned int Pixels() const { return pixels; }
|
||||
unsigned int ImageSize() const { return imagesize; }
|
||||
unsigned int Bytes() const { return bytes; };
|
||||
|
||||
virtual int Brightness( int/*p_brightness*/=-1 ) { return( -1 ); }
|
||||
virtual int Hue( int/*p_hue*/=-1 ) { return( -1 ); }
|
||||
virtual int Colour( int/*p_colour*/=-1 ) { return( -1 ); }
|
||||
virtual int Contrast( int/*p_contrast*/=-1 ) { return( -1 ); }
|
||||
virtual int Brightness( int/*p_brightness*/=-1 ) { return -1; }
|
||||
virtual int Hue( int/*p_hue*/=-1 ) { return -1; }
|
||||
virtual int Colour( int/*p_colour*/=-1 ) { return -1; }
|
||||
virtual int Contrast( int/*p_contrast*/=-1 ) { return -1; }
|
||||
|
||||
bool CanCapture() const { return( capture ); }
|
||||
bool CanCapture() const { return capture; }
|
||||
|
||||
bool SupportsNativeVideo() const { return( (type == FFMPEG_SRC )||(type == REMOTE_SRC)); }
|
||||
bool SupportsNativeVideo() const {
|
||||
return (type == FFMPEG_SRC);
|
||||
//return (type == FFMPEG_SRC )||(type == REMOTE_SRC);
|
||||
}
|
||||
|
||||
virtual int PrimeCapture() { return( 0 ); }
|
||||
virtual int PreCapture()=0;
|
||||
virtual int Capture( Image &image )=0;
|
||||
virtual int PostCapture()=0;
|
||||
virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) = 0;
|
||||
virtual int Close()=0;
|
||||
virtual int PrimeCapture() { return 0; }
|
||||
virtual int PreCapture() = 0;
|
||||
virtual int Capture(Image &image) = 0;
|
||||
virtual int PostCapture() = 0;
|
||||
virtual int CaptureAndRecord(Image &image, timeval recording, char* event_directory) = 0;
|
||||
virtual int Close() = 0;
|
||||
};
|
||||
|
||||
#endif // ZM_CAMERA_H
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue