Merge branch 'storageareas' of github.com:/connortechnology/ZoneMinder into storageareas

This commit is contained in:
Isaac Connor 2018-11-06 15:27:17 -05:00
commit be0767b4cd
108 changed files with 4640 additions and 1827 deletions

View File

@ -42,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.
@ -55,7 +55,7 @@ Please visit our [ReadtheDocs site](https://zoneminder.readthedocs.org/en/stable
### 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.
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 pacakging script:
```bash
./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes
```

View File

@ -781,6 +781,7 @@ INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,1
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);
--
@ -808,6 +809,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);

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

@ -0,0 +1,5 @@
--
-- This updates a 1.32.0 database to 1.32.1
--
-- No changes required
--

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

@ -0,0 +1,5 @@
--
-- This updates a 1.32.1 database to 1.32.2
--
-- No changes required
--

9
db/zm_update-1.32.3.sql Normal file
View File

@ -0,0 +1,9 @@
--
-- This updates a 1.32.2 database to 1.32.3
--
--
-- Add some additional monitor preset values
--
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);

View File

@ -72,7 +72,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
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:
@ -129,7 +129,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
@ -151,6 +151,10 @@ Upgrades
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 Apache 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:

View File

@ -72,7 +72,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
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:
@ -129,7 +129,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
@ -147,10 +147,14 @@ Upgrades
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
may also be a file called "zoneminder.conf.rpmnew". If an 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 Apache 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:

View File

@ -25,7 +25,7 @@
%global _hardened_build 1
Name: zoneminder
Version: 1.32.0
Version: 1.32.2
Release: 1%{?dist}
Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons
@ -320,6 +320,17 @@ EOF
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/run/zoneminder
%changelog
* 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

View File

@ -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)

View File

@ -13,6 +13,13 @@ 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.
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
@ -142,6 +149,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
^^^^^^^^^^^^^^

View File

@ -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 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

View File

@ -1,10 +1,14 @@
# This configuration is only needed for compatibility with zmninja on iOS
# 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.
#zmNinja header permissions. Tweak to your needs
# 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"

View File

@ -10,6 +10,7 @@ configure_file(zmdc.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" @ONLY)
configure_file(zmfilter.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" @ONLY)
configure_file(zmonvif-probe.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" @ONLY)
configure_file(zmpkg.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" @ONLY)
configure_file(zmrecover.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmrecover.pl" @ONLY)
configure_file(zmtrack.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" @ONLY)
configure_file(zmtrigger.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" @ONLY)
configure_file(zmupdate.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" @ONLY)
@ -35,7 +36,7 @@ FOREACH(PERLSCRIPT ${perlscripts})
ENDFOREACH(PERLSCRIPT ${perlscripts})
# Install the perl scripts
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmstats.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmrecover.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmstats.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
if(NOT ZM_NO_X10)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
endif(NOT ZM_NO_X10)

View File

@ -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

View File

@ -31,12 +31,16 @@ use warnings;
require ZoneMinder::Base;
require ZoneMinder::Object;
require ZoneMinder::Storage;
require ZoneMinder::Frame;
require Date::Manip;
require File::Find;
require File::Path;
require File::Copy;
require File::Basename;
require Number::Bytes::Human;
require Date::Parse;
require POSIX;
use Date::Format qw(time2str);
#our @ISA = qw(ZoneMinder::Object);
use parent qw(ZoneMinder::Object);
@ -50,9 +54,8 @@ use parent qw(ZoneMinder::Object);
use ZoneMinder::Config qw(:all);
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Database qw(:all);
require Date::Parse;
use vars qw/ $table $primary_key %fields $serial @identified_by/;
use vars qw/ $table $primary_key %fields $serial @identified_by %defaults/;
$table = 'Events';
@identified_by = ('Id');
$serial = $primary_key = 'Id';
@ -84,8 +87,18 @@ $serial = $primary_key = 'Id';
Orientation
DiskSpace
);
%defaults = (
Cause => q`'Unknown'`,
DefaultVideo => q`''`,
TotScore => '0',
Archived => '0',
Videoed => '0',
Uploaded => '0',
Emailed => '0',
Messaged => '0',
Executed => '0',
);
use POSIX;
sub Time {
if ( @_ > 1 ) {
@ -99,56 +112,10 @@ sub Time {
return $_[0]{Time};
}
sub Name {
if ( @_ > 1 ) {
$_[0]{Name} = $_[1];
}
return $_[0]{Name};
} # end sub Name
sub find {
shift if $_[0] eq 'ZoneMinder::Event';
my %sql_filters = @_;
my $sql = 'SELECT * FROM Events';
my @sql_filters;
my @sql_values;
if ( exists $sql_filters{Name} ) {
push @sql_filters , ' Name = ? ';
push @sql_values, $sql_filters{Name};
}
if ( exists $sql_filters{Id} ) {
push @sql_filters , ' Id = ? ';
push @sql_values, $sql_filters{Id};
}
$sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters;
$sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit};
my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql )
or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() );
my $res = $sth->execute( @sql_values )
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
my @results;
while( my $db_filter = $sth->fetchrow_hashref() ) {
my $filter = new ZoneMinder::Event( $$db_filter{Id}, $db_filter );
push @results, $filter;
} # end while
$sth->finish();
return @results;
}
sub find_one {
my @results = find(@_);
return $results[0] if @results;
}
sub getPath {
return Path( @_ );
}
sub Path {
my $event = shift;
@ -168,6 +135,9 @@ 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+$/ ) {
@ -182,16 +152,15 @@ sub Scheme {
sub RelativePath {
my $event = shift;
if ( @_ ) {
$$event{RelativePath} = $_[0];
}
$$event{RelativePath} = shift if @_;
if ( ! $$event{RelativePath} ) {
if ( $$event{Scheme} eq 'Deep' ) {
if ( $event->Time() ) {
$$event{RelativePath} = join('/',
$event->{MonitorId},
strftime( '%y/%m/%d/%H/%M/%S',
POSIX::strftime( '%y/%m/%d/%H/%M/%S',
localtime($event->Time())
),
);
@ -203,7 +172,7 @@ sub RelativePath {
if ( $event->Time() ) {
$$event{RelativePath} = join('/',
$event->{MonitorId},
strftime( '%Y-%m-%d', localtime($event->Time())),
POSIX::strftime('%Y-%m-%d', localtime($event->Time())),
$event->{Id},
);
} else {
@ -223,16 +192,15 @@ sub RelativePath {
sub LinkPath {
my $event = shift;
if ( @_ ) {
$$event{LinkPath} = $_[0];
}
$$event{LinkPath} = shift if @_;
if ( ! $$event{LinkPath} ) {
if ( $$event{Scheme} eq 'Deep' ) {
if ( $event->Time() ) {
$$event{LinkPath} = join('/',
$event->{MonitorId},
strftime( '%y/%m/%d',
POSIX::strftime( '%y/%m/%d',
localtime($event->Time())
),
'.'.$$event{Id}
@ -351,19 +319,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;
@ -373,14 +341,14 @@ 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");
Warning("Can't delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from $caller:$line");
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");
Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}");
$ZoneMinder::Database::dbh->ping();
$ZoneMinder::Database::dbh->begin_work();
@ -697,6 +665,92 @@ Debug("Done deleting files, returning");
return $error;
} # end sub MoveTo
# Assumes $path is absolute
#
sub recover_timestamps {
my ( $Event, $path ) = @_;
$path = $Event->Path() if ! $path;
if ( ! opendir(DIR, $path) ) {
Error("Can't open directory '$path': $!");
next;
}
my @contents = readdir(DIR);
Debug('Have ' . @contents . " files in $path");
closedir(DIR);
my @mp4_files = grep( /^\d+\-video\.mp4$/, @contents);
if ( @mp4_files ) {
$$Event{DefaultVideo} = $mp4_files[0];
}
my @analyse_jpgs = grep( /^\d+\-analyse\.jpg$/, @contents);
if ( @analyse_jpgs ) {
$$Event{Save_JPEGs} |= 2;
}
my @capture_jpgs = grep( /^\d+\-capture\.jpg$/, @contents);
if ( @capture_jpgs ) {
$$Event{Frames} = scalar @capture_jpgs;
$$Event{Save_JPEGs} |= 1;
# can get start and end times from stat'ing first and last jpg
@capture_jpgs = sort { $a cmp $b } @capture_jpgs;
my $first_file = "$path/$capture_jpgs[0]";
( $first_file ) = $first_file =~ /^(.*)$/;
my $first_timestamp = (stat($first_file))[9];
my $last_file = "$path/$capture_jpgs[@capture_jpgs-1]";
( $last_file ) = $last_file =~ /^(.*)$/;
my $last_timestamp = (stat($last_file))[9];
my $duration = $last_timestamp - $first_timestamp;
$Event->Length($duration);
$Event->StartTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) );
$Event->EndTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $last_timestamp) );
Debug("From capture Jpegs have duration $duration = $last_timestamp - $first_timestamp : $$Event{StartTime} to $$Event{EndTime}");
$ZoneMinder::Database::dbh->begin_work();
foreach my $jpg ( @capture_jpgs ) {
my ( $id ) = $jpg =~ /^(\d+)\-capture\.jpg$/;
if ( ! ZoneMinder::Frame->find_one( EventId=>$$Event{Id}, FrameId=>$id ) ) {
my $file = "$path/$jpg";
( $file ) = $file =~ /^(.*)$/;
my $timestamp = (stat($file))[9];
my $Frame = new ZoneMinder::Frame();
$Frame->save({
EventId=>$$Event{Id}, FrameId=>$id,
TimeStamp=>Date::Format::time2str('%Y-%m-%d %H:%M:%S',$timestamp),
Delta => $timestamp - $first_timestamp,
Type=>'Normal',
Score=>0,
});
}
}
$ZoneMinder::Database::dbh->commit();
} elsif ( @mp4_files ) {
my $file = "$path/$mp4_files[0]";
( $file ) = $file =~ /^(.*)$/;
my $first_timestamp = (stat($file))[9];
my $output = `ffprobe $file 2>&1`;
my ($duration) = $output =~ /Duration: [:\.0-9]+/gm;
Debug("From mp4 have duration $duration, start: $first_timestamp");
my ( $h, $m, $s, $u );
if ( $duration =~ m/(\d+):(\d+):(\d+)\.(\d+)/ ) {
( $h, $m, $s, $u ) = ($1, $2, $3, $4 );
Debug("( $h, $m, $s, $u ) from /^(\\d{2}):(\\d{2}):(\\d{2})\.(\\d+)/");
}
my $seconds = ($h*60*60)+($m*60)+$s;
$Event->Length($seconds.'.'.$u);
$Event->StartTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) );
$Event->EndTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp+$seconds) );
}
if ( @mp4_files ) {
$Event->DefaultVideo($mp4_files[0]);
}
}
1;
__END__

View File

@ -30,6 +30,7 @@ use warnings;
require ZoneMinder::Base;
require Date::Manip;
require POSIX;
use parent qw(ZoneMinder::Object);
@ -48,8 +49,6 @@ use ZoneMinder::Database qw(:all);
require ZoneMinder::Storage;
require ZoneMinder::Server;
use POSIX;
sub Name {
if ( @_ > 1 ) {
$_[0]{Name} = $_[1];
@ -435,7 +434,7 @@ sub DateTimeToSQL {
Error( "Unable to parse date string '$dt_str'\n" );
return( undef );
}
return( strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) );
return( POSIX::strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) );
}
1;

View File

@ -549,7 +549,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 );

View File

@ -91,7 +91,7 @@ use ZoneMinder::Config qw(:all);
use DBI;
use Carp;
use POSIX;
require POSIX;
use IO::Handle;
use Data::Dumper;
use Time::HiRes qw/gettimeofday/;
@ -539,7 +539,7 @@ sub logPrint {
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))
, POSIX::strftime('%x %H:%M:%S', localtime($seconds))
, $microseconds
, $this->{id}
, $$
@ -704,6 +704,8 @@ sub Fatal( @ ) {
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);
}

View File

@ -247,11 +247,12 @@ sub zmMemInit {
sub zmMemVerify {
my $monitor = shift;
if ( !zmMemAttach( $monitor, $mem_size ) ) {
return( undef );
if ( !zmMemAttach($monitor, $mem_size) ) {
return undef;
}
my $sd_size = zmMemRead( $monitor, 'shared_data:size', 1 );
my $sd_size = zmMemRead($monitor, 'shared_data:size', 1);
if ( $sd_size != $mem_data->{shared_data}->{size} ) {
if ( $sd_size ) {
Error( "Shared data size conflict in shared_data for monitor "
@ -269,9 +270,9 @@ sub zmMemVerify {
.", got ".$sd_size
);
}
return( undef );
return undef;
}
my $td_size = zmMemRead( $monitor, 'trigger_data:size', 1 );
my $td_size = zmMemRead($monitor, 'trigger_data:size', 1);
if ( $td_size != $mem_data->{trigger_data}->{size} ) {
if ( $td_size ) {
Error( "Shared data size conflict in trigger_data for monitor "
@ -290,14 +291,17 @@ sub zmMemVerify {
.$td_size
);
}
return( undef );
return undef;
}
if ( !zmMemRead($monitor, 'shared_data:valid',1) ) {
Error( "Shared data not valid for monitor $$monitor{Id}" );
return( undef );
my $valid = zmMemRead($monitor, 'shared_data:valid',1);
if ( !$valid ) {
Error("Shared data not valid for monitor $$monitor{Id}");
return undef;
} else {
Debug("Shared data appears vaild for monitor $$monitor{Id}: $valid");
}
return( !undef );
return !undef;
}
sub zmMemRead {

View File

@ -75,55 +75,50 @@ sub zmMemKey {
sub zmMemAttach {
my ( $monitor, $size ) = @_;
if ( ! $size ) {
Error( "No size passed to zmMemAttach for monitor $$monitor{Id}\n" );
if ( !$size ) {
Error("No size passed to zmMemAttach for monitor $$monitor{Id}");
return undef;
}
if ( !defined($monitor->{MMapAddr}) ) {
if ( defined($monitor->{MMapAddr}) ) {
Debug("zmMemAttach already attached at $monitor->{MMapAddr}");
return !undef;
}
my $mmap_file = $Config{ZM_PATH_MAP}.'/zm.mmap.'.$monitor->{Id};
if ( ! -e $mmap_file ) {
Error( sprintf( "Memory map file '%s' does not exist. zmc might not be running."
, $mmap_file
)
);
Error("Memory map file '$mmap_file' does not exist. zmc might not be running.");
return undef;
}
my $mmap_file_size = -s $mmap_file;
if ( $mmap_file_size < $size ) {
Error( sprintf( "Memory map file '%s' should have been %d but was instead %d"
, $mmap_file
, $size
, $mmap_file_size
)
);
Error("Memory map file '$mmap_file' should have been $size but was instead $mmap_file_size");
return undef;
}
my $MMAP;
if ( !open( $MMAP, '+<', $mmap_file ) ) {
Error( sprintf( "Can't open memory map file '%s': $!", $mmap_file ) );
if ( !open($MMAP, '+<', $mmap_file) ) {
Error("Can't open memory map file '$mmap_file': $!");
return undef;
}
my $mmap = undef;
my $mmap_addr = mmap( $mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, $MMAP );
my $mmap_addr = mmap($mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, $MMAP);
if ( !$mmap_addr || !$mmap ) {
Error( sprintf( "Can't mmap to file '%s': $!\n", $mmap_file ) );
close( $MMAP );
Error("Can't mmap to file '$mmap_file': $!");
close($MMAP);
return undef;
}
$monitor->{MMapHandle} = $MMAP;
$monitor->{MMapAddr} = $mmap_addr;
$monitor->{MMap} = \$mmap;
}
return !undef;
}
} # end sub zmMemAttach
sub zmMemDetach {
my $monitor = shift;
if ( $monitor->{MMap} ) {
if ( ! munmap( ${$monitor->{MMap}} ) ) {
if ( ! munmap(${$monitor->{MMap}}) ) {
Warn( "Unable to munmap for monitor $$monitor{Id}\n");
}
delete $monitor->{MMap};
@ -132,7 +127,7 @@ sub zmMemDetach {
delete $monitor->{MMapAddr};
}
if ( $monitor->{MMapHandle} ) {
close( $monitor->{MMapHandle} );
close($monitor->{MMapHandle});
delete $monitor->{MMapHandle};
}
}
@ -144,13 +139,10 @@ sub zmMemGet {
my $mmap = $monitor->{MMap};
if ( !$mmap || !$$mmap ) {
Error( sprintf( "Can't read from mapped memory for monitor '%d', gone away?"
, $monitor->{Id}
)
);
Error("Can't read from mapped memory for monitor '$$monitor{Id}', gone away?");
return undef;
}
my $data = substr( $$mmap, $offset, $size );
my $data = substr($$mmap, $offset, $size);
return $data;
}
@ -162,23 +154,20 @@ sub zmMemPut {
my $mmap = $monitor->{MMap};
if ( !$mmap || !$$mmap ) {
Error( sprintf( "Can't write mapped memory for monitor '%d', gone away?"
, $monitor->{Id}
)
);
return( undef );
Error("Can't write mapped memory for monitor '$$monitor{Id}', gone away?");
return undef;
}
substr( $$mmap, $offset, $size ) = $data;
return( !undef );
substr($$mmap, $offset, $size) = $data;
return !undef;
}
sub zmMemClean {
Debug( "Removing memory map files\n" );
Debug("Removing memory map files");
my $mapPath = $Config{ZM_PATH_MAP}.'/zm.mmap.*';
foreach my $mapFile( glob( $mapPath ) ) {
( $mapFile ) = $mapFile =~ /^(.+)$/;
Debug( "Removing memory map file '$mapFile'\n" );
unlink( $mapFile );
Debug("Removing memory map file '$mapFile'");
unlink($mapFile);
}
}

View File

@ -36,17 +36,6 @@ require ZoneMinder::Server;
#our @ISA = qw(Exporter ZoneMinder::Base);
use parent qw(ZoneMinder::Object);
# ==========================================================================
#
# General Utility Functions
#
# ==========================================================================
use ZoneMinder::Config qw(:all);
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Database qw(:all);
use POSIX;
use vars qw/ $table $primary_key /;
$table = 'Monitors';
$primary_key = 'Id';

View File

@ -27,6 +27,8 @@ package ZoneMinder::Object;
use 5.006;
use strict;
use warnings;
use Time::HiRes qw{ gettimeofday tv_interval };
use Carp qw( cluck );
require ZoneMinder::Base;
@ -167,17 +169,6 @@ sub lock_and_load {
} # end sub lock_and_load
sub AUTOLOAD {
my ( $self, $newvalue ) = @_;
my $type = ref($_[0]);
my $name = $AUTOLOAD;
$name =~ s/.*://;
if ( @_ > 1 ) {
return $_[0]{$name} = $_[1];
}
return $_[0]{$name};
}
sub save {
my ( $self, $data, $force_insert ) = @_;
@ -187,7 +178,12 @@ sub save {
$log->error("No type in Object::save. self:$self from $caller:$line");
}
my $local_dbh = eval '$'.$type.'::dbh';
$local_dbh = $ZoneMinder::Database::dbh if ! $local_dbh;
if ( ! $local_dbh ) {
$local_dbh = $ZoneMinder::Database::dbh;
if ( $debug or DEBUG_ALL ) {
$log->debug("Using global dbh");
}
}
$self->set( $data ? $data : {} );
if ( $debug or DEBUG_ALL ) {
if ( $data ) {
@ -196,7 +192,6 @@ sub save {
}
}
}
#$debug = 0;
my $table = eval '$'.$type.'::table';
my $fields = eval '\%'.$type.'::fields';
@ -297,6 +292,7 @@ $log->debug("No serial") if $debug;
if ( $need_serial ) {
if ( $serial ) {
$log->debug("Getting auto_increments");
my $s = qq{SELECT `auto_increment` FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '$table'};
@$self{@identified_by} = @sql{@$fields{@identified_by}} = $local_dbh->selectrow_array( $s );
#@$self{@identified_by} = @sql{@$fields{@identified_by}} = $local_dbh->selectrow_array( q{SELECT nextval('} . $serial . q{')} );
@ -345,7 +341,7 @@ $log->debug("No serial") if $debug;
} # end if
} # end if
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
$self->load();
#$self->load();
#if ( $$fields{id} ) {
#if ( ! $ZoneMinder::Object::cache{$type}{$$self{id}} ) {
#$ZoneMinder::Object::cache{$type}{$$self{id}} = $self;
@ -368,6 +364,7 @@ sub set {
$log->warn("ZoneMinder::Object::set called on an object ($type) with no fields".$@);
} # end if
my %defaults = eval('%'.$type.'::defaults');
if ( ref $params ne 'HASH' ) {
my ( $caller, undef, $line ) = caller;
$log->error("$type -> set called with non-hash params from $caller $line");
@ -456,7 +453,420 @@ sub transform {
sub to_string {
my $type = ref($_[0]);
my $fields = eval '\%'.$type.'::fields';
return $type . ': '. join(' ' , map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } keys %$fields );
if ( $fields and %{$fields} ) {
return $type . ': '. join(' ', map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } sort { $a cmp $b } keys %$fields );
}
return $type . ': '. join(' ', map { $_ .' => '.(defined $_[0]{$_} ? $_[0]{$_} : 'undef') } sort { $a cmp $b } keys %{$_[0]} );
}
# We make this a separate function so that we can use it to generate the sql statements for each value in an OR
sub find_operators {
my ( $field, $type, $operator, $value ) = @_;
$log->debug("find_operators: field($field) type($type) op($operator) value($value)") if DEBUG_ALL;
my $add_placeholder = ( ! ( $field =~ /\?/ ) ) ? 1 : 0;
if ( sets::isin( $operator, [ '=', '!=', '<', '>', '<=', '>=', '<<=' ] ) ) {
return ( $field.$type.' ' . $operator . ( $add_placeholder ? ' ?' : '' ), $value );
} elsif ( $operator eq 'not' ) {
return ( '( NOT ' . $field.$type.')', $value );
} elsif ( sets::isin( $operator, [ '&&', '<@', '@>' ] ) ) {
if ( ref $value eq 'ARRAY' ) {
if ( $field =~ /^\(/ ) {
return ( 'ARRAY('.$field.$type.') ' . $operator . ' ?', $value );
} else {
return ( $field.$type.' ' . $operator . ' ?', $value );
} # emd of
} else {
return ( $field.$type.' ' . $operator . ' ?', [ $value ] );
} # end if
} elsif ( $operator eq 'exists' ) {
return ( $value ? '' : 'NOT ' ) . 'EXISTS ' . $field.$type;
} elsif ( sets::isin( $operator, [ 'in', 'not in' ] ) ) {
if ( ref $value eq 'ARRAY' ) {
return ( $field.$type.' ' . $operator . ' ('. join(',', map { '?' } @{$value} ) . ')', @{$value} );
} else {
return ( $field.$type.' ' . $operator . ' (?)', $value );
} # end if
} elsif ( $operator eq 'contains' ) {
return ( '? IN '.$field.$type, $value );
} elsif ( $operator eq 'does not contain' ) {
return ( '? NOT IN '.$field.$type, $value );
} elsif ( sets::isin( $operator, [ 'like','ilike' ] ) ) {
return $field.'::text ' . $operator . ' ?', $value;
} elsif ( $operator eq 'null_or_<=' ) {
return '('.$field.$type.' IS NULL OR '.$field.$type.' <= ?)', $value;
} elsif ( $operator eq 'is null or <=' ) {
return '('.$field.$type.' IS NULL OR '.$field.$type.' <= ?)', $value;
} elsif ( $operator eq 'null_or_>=' ) {
return '('.$field.$type.' IS NULL OR '.$field.$type.' >= ?)', $value;
} elsif ( $operator eq 'is null or >=' ) {
return '('.$field.$type.' IS NULL OR '.$field.$type.' >= ?)', $value;
} elsif ( $operator eq 'null_or_>' or $operator eq 'is null or >' ) {
return '('.$field.$type.' IS NULL OR '.$field.$type.' > ?)', $value;
} elsif ( $operator eq 'null_or_<' or $operator eq 'is null or <' ) {
return '('.$field.$type.' IS NULL OR '.$field.$type.' < ?)', $value;
} elsif ( $operator eq 'null_or_=' or $operator eq 'is null or =' ) {
return '('.$field.$type.' IS NULL OR '.$field.$type.' = ?)', $value;
} elsif ( $operator eq 'null or in' or $operator eq 'is null or in' ) {
return '('.$field.$type.' IS NULL OR '.$field.$type.' IN ('.join(',', map { '?' } @{$value} ) . '))', @{$value};
} elsif ( $operator eq 'null or not in' ) {
return '('.$field.$type.' IS NULL OR '.$field.$type.' NOT IN ('.join(',', map { '?' } @{$value} ) . '))', @{$value};
} elsif ( $operator eq 'exists' ) {
return ( $value ? ' EXISTS ' : 'NOT EXISTS ' ).$field;
} elsif ( $operator eq 'lc' ) {
return 'lower('.$field.$type.') = ?', $value;
} elsif ( $operator eq 'uc' ) {
return 'upper('.$field.$type.') = ?', $value;
} elsif ( $operator eq 'trunc' ) {
return 'trunc('.$field.$type.') = ?', $value;
} elsif ( $operator eq 'any' ) {
if ( ref $value eq 'ARRAY' ) {
return '(' . join(',', map { '?' } @{$value} ).") = ANY($field)", @{$value};
} else {
return "? = ANY($field)", $value;
} # end if
} elsif ( $operator eq 'not any' ) {
if ( ref $value eq 'ARRAY' ) {
return '(' . join(',', map { '?' } @{$value} ).") != ANY($field)", @{$value};
} else {
return "? != ANY($field)", $value;
} # end if
} elsif ( $operator eq 'is null' ) {
if ( $value ) {
return $field.$type. ' is null';
} else {
return $field.$type. ' is not null';
} # end if
} elsif ( $operator eq 'is not null' ) {
if ( $value ) {
return $field.$type. ' is not null';
} else {
return $field.$type. ' is null';
} # end if
} else {
$log->warn("find_operators: op not found field($field) type($type) op($operator) value($value)");
} # end if
return;
} # end sub find_operators
sub get_fields_values {
my ( $object_type, $search, $param_keys ) = @_;
my @used_fields;
my @where;
my @values;
no strict 'refs';
foreach my $k ( @$param_keys ) {
if ( $k eq 'or' ) {
my $or_ref = ref $$search{or};
if ( $or_ref eq 'HASH' ) {
my @keys = keys %{$$search{or}};
if ( @keys ) {
my ( $where, $values, $used_fields ) = get_fields_values( $object_type, $$search{or}, \@keys );
push @where, '('.join(' OR ', @{$where} ).')';
push @values, @{$values};
} else {
$log->error("No keys in or");
}
} elsif ( $or_ref eq 'ARRAY' ) {
my %s = @{$$search{or}};
my ( $where, $values, $used_fields ) = get_fields_values( $object_type, \%s, [ keys %s ] );
push @where, '('.join(' OR ', @{$where} ).')';
push @values, @{$values};
} else {
$log->error("Deprecated use of or $or_ref for $$search{or}");
} # end if
push @used_fields, $k;
next;
} elsif ( $k eq 'and' ) {
my $and_ref = ref $$search{and};
if ( $and_ref eq 'HASH' ) {
my @keys = keys %{$$search{and}};
if ( @keys ) {
my ( $where, $values, $used_fields ) = get_fields_values( $object_type, $$search{and}, \@keys );
push @where, '('.join(' AND ', @{$where} ).')';
push @values, @{$values};
} else {
$log->error("No keys in and");
}
} elsif ( $and_ref eq 'ARRAY' and @{$$search{and}} ) {
my @sub_where;
for( my $p_index = 0; $p_index < @{$$search{and}}; $p_index += 2 ) {
my %p = ( $$search{and}[$p_index], $$search{and}[$p_index+1] );
my ( $where, $values, $used_fields ) = get_fields_values( $object_type, \%p, [ keys %p ] );
push @sub_where, @{$where};
push @values, @{$values};
}
push @where, '('.join(' AND ', @sub_where ).')';
} else {
$log->error("incorrect ref of and $and_ref");
}
push @used_fields, $k;
next;
}
my ( $field, $type, $function ) = $k =~ /^([_\+\w\-]+)(::\w+\[?\]?)?[\s_]*(.*)?$/;
$type = '' if ! defined $type;
$log->debug("$object_type param $field($type) func($function) " . ( ref $$search{$k} eq 'ARRAY' ? join(',',@{$$search{$k}}) : $$search{$k} ) ) if DEBUG_ALL;
foreach ( 'find_fields', 'fields' ) {
my $fields = \%{$object_type.'::'.$_};
if ( ! $fields ) {
$log->debug("No $fields in $object_type") if DEBUG_ALL;
next;
} # end if
if ( ! $$fields{$field} ) {
#$log->debug("No $field in $_ for $object_type") if DEBUG_ALL;
next;
} # end if
# This allows mainly for find_fields to reference multiple values, opinion in Project, value
foreach my $db_field ( ref $$fields{$field} eq 'ARRAY' ? @{$$fields{$field}} : $$fields{$field} ) {
if ( ! $function ) {
$db_field .= $type;
if ( ref $$search{$k} eq 'ARRAY' ) {
$log->debug("Have array for $k $$search{$k}") if DEBUG_ALL;
if ( ! ( $db_field =~ /\?/ ) ) {
if ( @{$$search{$k}} != 1 ) {
push @where, $db_field .' IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')';
} else {
push @where, $db_field.'=?';
} # end if
} else {
$log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL;
$db_field =~ s/=/IN/g;
my $question_replacement = '('.join(',', map {'?'} @{$$search{$k}} ) . ')';
$db_field =~ s/\?/$question_replacement/;
push @where, $db_field;
}
push @values, @{$$search{$k}};
} elsif ( ref $$search{$k} eq 'HASH' ) {
foreach my $p_k ( keys %{$$search{$k}} ) {
my $v = $$search{$k}{$p_k};
if ( ref $v eq 'ARRAY' ) {
push @where, $db_field.' IN ('.join(',', map {'?'} @{$v} ) . ')';
push @values, $p_k, @{$v};
} else {
push @where, $db_field.'=?';
push @values, $p_k, $v;
} # end if
} # end foreach p_k
} elsif ( ! defined $$search{$k} ) {
push @where, $db_field.' IS NULL';
} else {
if ( ! ( $db_field =~ /\?/ ) ) {
push @where, $db_field .'=?';
} else {
push @where, $db_field;
}
push @values, $$search{$k};
} # end if
push @used_fields, $k;
} else {
#my @w =
#ref $search{$k} eq 'ARRAY' ?
#map { find_operators( $field, $type, $function, $_ ); } @{$search{$k}} :
my ( $w, @v ) = find_operators( $db_field, $type, $function, $$search{$k} );
if ( $w ) {
#push @where, '(' . join(' OR ', @w ) . ')';
push @where, $w;
push @values, @v if @v;
push @used_fields, $k;
} # end if @w
} # end if has function or not
} # end foreach db_field
} # end foreach find_field
} # end foreach k
return ( \@where, \@values, \@used_fields );
}
sub find {
no strict 'refs';
my $object_type = shift;
my $debug = ${$object_type.'::debug'};
$debug = DEBUG_ALL if ! $debug;
my $starttime = [gettimeofday] if $debug;
my $params;
if ( @_ == 1 ) {
$params = $_[0];
if ( ref $params ne 'HASH' ) {
$log->error("params $params was not a has");
} # end if
} else {
$params = { @_ };
} # end if
my $local_dbh = ${$object_type.'::dbh'};
if ( $$params{dbh} ) {
$local_dbh = $$params{dbh};
delete $$params{dbh};
} elsif ( ! $local_dbh ) {
$local_dbh = $dbh if ! $local_dbh;
} # end if
my $sql = find_sql( $object_type, $params);
my $do_cache = $$sql{columns} ne '*' ? 0 : 1;
#$log->debug( 'find prepare: ' . sprintf('%.4f', tv_interval($starttime)*1000) ." useconds") if $debug;
my $data = $local_dbh->selectall_arrayref($$sql{sql}, { Slice => {} }, @{$$sql{values}});
if ( ! $data ) {
$log->error('Error ' . $local_dbh->errstr() . " loading $object_type ($$sql{sql}) (". join(',', map { ref $_ eq 'ARRAY' ? 'ARRAY('.join(',',@$_).')' : $_ } @{$$sql{values}} ) . ')' );
return ();
#} elsif ( ( ! @$data ) and $debug ) {
#$log->debug("No $type ($sql) (@values) " );
} elsif ( $debug ) {
$log->debug("Loading Debug:$debug $object_type ($$sql{sql}) (".join(',', map { ref $_ eq 'ARRAY' ? join(',', @{$_}) : $_ } @{$$sql{values}}).') # of results:' . @$data . ' in ' . sprintf('%.4f', tv_interval($starttime)*1000) .' useconds' );
} # end if
my $fields = \%{$object_type.'::fields'};
my $primary_key = ${$object_type.'::primary_key'};
if ( ! $primary_key ) {
Error( 'NO primary_key for type ' . $object_type );
return;
} # end if
if ( ! ($fields and keys %{$fields}) ) {
return map { new($object_type, $$_{$primary_key}, $_ ) } @$data;
} elsif ( $$fields{$primary_key} ) {
return map { new($object_type, $_->{$$fields{$primary_key}}, $_) } @$data;
} else {
my @identified_by = eval '@'.$object_type.'::identified_by';
if ( ! @identified_by ) {
$log->debug("Multi key object $object_type but no identified by $fields") if $debug;
} # end if
return map { new($object_type, \@identified_by, $_, !$do_cache) } @$data;
} # end if
} # end sub find
sub find_one {
my $object_type = shift;
my $params;
if ( @_ == 1 ) {
$params = $_[0];
} else {
%{$params} = @_;
} # end if
$$params{limit}=1;
my @Results = $object_type->find(%$params);
my ( $caller, undef, $line ) = caller;
$log->debug("returning to $caller:$line from find_one") if DEBUG_ALL;
return $Results[0] if @Results;
} # end sub find_one
sub find_sql {
no strict 'refs';
my $object_type = shift;
my $debug = ${$object_type.'::debug'};
$debug = DEBUG_ALL if ! $debug;
my $params;
if ( @_ == 1 ) {
$params = $_[0];
if ( ref $params ne 'HASH' ) {
$log->error("params $params was not a has");
} # end if
} else {
$params = { @_ };
} # end if
my %sql = (
( distinct => ( exists $$params{distinct} ? 1:0 ) ),
( columns => ( exists $$params{columns} ? $$params{columns} : '*' ) ),
( table => ( exists $$params{table} ? $$params{table} : ${$object_type.'::table'} )),
'group by'=> $$params{'group by'},
limit => $$params{limit},
offset => $$params{offset},
);
if ( exists $$params{order} ) {
$sql{order} = $$params{order};
} else {
my $order = eval '$'.$object_type.'::default_sort';
#$log->debug("default sort: $object_type :: default_sort = $order") if DEBUG_ALL;
$sql{order} = $order if $order;
} # end if
delete @$params{'distinct','columns','table','group by','limit','offset','order'};
my @where;
my @values;
if ( exists $$params{custom} ) {
push @where, '(' . (shift @{$$params{custom}}) . ')';
push @values, @{$$params{custom}};
delete $$params{custom};
} # end if
my @param_keys = keys %$params;
# no operators, just which fields are being searched on. Mostly just useful for detetion of the deleted field.
my %used_fields;
# We use this search hash so that we can mash it up and leave the params hash alone
my %search;
@search{@param_keys} = @$params{@param_keys};
my ( $where, $values, $used_fields ) = get_fields_values( $object_type, \%search, \@param_keys );
delete @search{@{$used_fields}};
@used_fields{ @{$used_fields} } = @{$used_fields};
push @where, @{$where};
push @values, @{$values};
my $fields = \%{$object_type.'::fields'};
#optimise this
if ( $$fields{deleted} and ! $used_fields{deleted} ) {
push @where, 'deleted=?';
push @values, 0;
} # end if
$sql{where} = \@where;
$sql{values} = \@values;
$sql{used_fields} = \%used_fields;
foreach my $k ( keys %search ) {
$log->error("Extra parameters in $object_type ::find $k => $search{$k}");
Carp::cluck("Extra parameters in $object_type ::find $k => $search{$k}");
} # end foreach
$sql{sql} = join( ' ',
( 'SELECT', ( $sql{distinct} ? ('DISTINCT') : () ) ),
( $sql{columns}, 'FROM', $sql{table} ),
( @{$sql{where}} ? ('WHERE', join(' AND ', @{$sql{where}})) : () ),
( $sql{order} ? ( 'ORDER BY', $sql{order} ) : () ),
( $sql{'group by'} ? ( 'GROUP BY', $sql{'group by'} ) : () ),
( $sql{limit} ? ( 'LIMIT', $sql{limit}) : () ),
( $sql{offset} ? ( 'OFFSET', $sql{offset} ) : () ),
);
#$log->debug("Loading Debug:$debug $object_type ($sql) (".join(',', map { ref $_ eq 'ARRAY' ? join(',', @{$_}) : $_ } @values).')' ) if $debug;
return \%sql;
} # end sub find_sql
sub AUTOLOAD {
my $type = ref($_[0]);
Carp::cluck("No type in autoload") if ! $type;
if ( DEBUG_ALL ) {
Carp::cluck("Using AUTOLOAD $AUTOLOAD");
}
my $name = $AUTOLOAD;
$name =~ s/.*://;
if ( @_ > 1 ) {
return $_[0]{$name} = $_[1];
}
return $_[0]{$name};
}
sub DESTROY {
}
1;

View File

@ -485,7 +485,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++);
}

View File

@ -69,6 +69,7 @@ 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 = '';
@ -162,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;
}
@ -173,11 +174,11 @@ 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 ) ) {
@ -191,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 });
}
@ -211,7 +212,7 @@ while( !$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);
}
@ -292,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
@ -364,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});
@ -393,7 +394,7 @@ sub generateVideo {
my $command = join('',
$Config{ZM_PATH_BIN},
'/zmvideo.pl -e ',
$event->{Id},
$Event->{Id},
' -r ',
$rate,
' -s ',
@ -405,10 +406,10 @@ sub generateVideo {
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 );
}
@ -417,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 );
}
@ -453,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");
@ -467,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 = $event->Path()
# 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;
@ -492,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");
@ -524,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,
@ -540,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},
@ -548,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}) : ()),
@ -580,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})
@ -592,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
@ -622,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 ) {
@ -796,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 {
@ -828,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;
}
@ -898,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 {
@ -933,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 = $event->Path();
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);
@ -971,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 );

View File

@ -44,7 +44,6 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
my $store_state=''; # PP - will remember state name passed
logInit();
Info("Aftere LogInit");
my $command = $ARGV[0]||'';
if ( $command eq 'version' ) {

475
scripts/zmrecover.pl.in Normal file
View File

@ -0,0 +1,475 @@
#!/usr/bin/perl -wT
use strict;
use bytes;
# ==========================================================================
#
# These are the elements you can edit to suit your installation
#
# ==========================================================================
use constant RECOVER_TAG => '(r)'; # Tag to append to event name when recovered
use constant RECOVER_TEXT => 'Recovered.'; # Text to append to event notes when recovered
# ==========================================================================
#
# You shouldn't need to change anything from here downwards
#
# ==========================================================================
@EXTRA_PERL_LIB@
use ZoneMinder;
use DBI;
use POSIX;
use File::Find;
use Time::HiRes qw/gettimeofday/;
use Getopt::Long;
use autouse 'Pod::Usage'=>qw(pod2usage);
use constant ZM_RECOVER_PID => '@ZM_RUNDIR@/zmrecover.pid';
$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 $report = 0;
my $interactive = 1;
my $monitor_id = 0;
my $version;
my $force = 0;
my $server_id = undef;
my $storage_id = undef;
logInit();
GetOptions(
force =>\$force,
interactive =>\$interactive,
'monitor_id=i' =>\$monitor_id,
report =>\$report,
'server_id=i' =>\$server_id,
'storage_id=i' =>\$storage_id,
version =>\$version
) or pod2usage(-exitstatus => -1);
if ( $version ) {
print( ZoneMinder::Base::ZM_VERSION . "\n");
exit(0);
}
if ( ($report + $interactive) > 1 ) {
print( STDERR "Error, only one option may be specified\n" );
pod2usage(-exitstatus => -1);
}
if ( -e ZM_RECOVER_PID ) {
local $/ = undef;
open FILE, ZM_RECOVER_PID or die "Couldn't open file: $!";
binmode FILE;
my $pid = <FILE>;
close FILE;
if ( $force ) {
Error("zmrecover.pl appears to already be running at pid $pid. Continuing." );
} else {
Fatal("zmrecover.pl appears to already be running at pid $pid. If not, please delete " .
ZM_RECOVER_PID . " or use the --force command line option." );
}
} # end if ZM_RECOVER_PID exists
if ( open(my $PID, '>', ZM_RECOVER_PID) ) {
print($PID $$);
close($PID);
} else {
Error( "Can't open pid file at " . ZM_PID );
}
sub HupHandler {
Info('Received HUP, reloading');
&ZoneMinder::Logger::logHupHandler();
}
sub TermHandler {
Info('Received TERM, exiting');
Term();
}
sub Term {
unlink ZM_RECOVER_PID;
exit(0);
}
$SIG{HUP} = \&HupHandler;
$SIG{TERM} = \&TermHandler;
$SIG{INT} = \&TermHandler;
my $dbh = zmDbConnect();
if ( ! $dbh ) {
Error('Unable to connect to database');
Term();
} # end if
$| = 1;
require ZoneMinder::Monitor;
require ZoneMinder::Storage;
require ZoneMinder::Event;
my @Storage_Areas;
if ( defined $storage_id ) {
@Storage_Areas = ZoneMinder::Storage->find( Id=>$storage_id );
if ( !@Storage_Areas ) {
Error("No Storage Area found with Id $storage_id");
Term();
}
Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}");
} elsif ( $server_id ) {
@Storage_Areas = ZoneMinder::Storage->find( ServerId => $server_id );
if ( ! @Storage_Areas ) {
Error("No Storage Area found with ServerId =" . $server_id);
Term();
}
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");
}
my @Monitors = ZoneMinder::Monitor->find();
Debug("@Monitors");
foreach my $Monitor ( @Monitors ) {
Debug("Monitor " . $Monitor->to_string() )
}
my %Monitors = map { $$_{Id} => $_ } @Monitors;
#ZoneMinder::Monitor->find(
# ($monitor_id ? ( Id=>$monitor_id ) : () ),
#);
Debug("Found " . (keys %Monitors) . " monitors");
foreach my $id ( keys %Monitors ) {
Debug("Monitor $id $Monitors{$id}{Name}");
}
foreach my $Storage ( @Storage_Areas ) {
Debug('Checking events in ' . $Storage->Path() );
if ( ! chdir($Storage->Path()) ) {
Error('Unable to change dir to ' . $Storage->Path());
next;
} # end if
# Please note that this glob will take all files beginning with a digit.
foreach my $monitor ( glob('[0-9]*') ) {
if ( $monitor =~ /\D/ ) {
Debug("Weird non digit characters in $monitor");
next;
}
# De-taint
( my $monitor_dir ) = ( $monitor =~ /^(\d+)$/ );
if ( $monitor_id and ( $monitor_id != $monitor_dir ) ) {
Debug("Skipping monitor $monitor_dir because we are only interested in monitor $monitor_id");
next;
}
if ( ! $Monitors{$monitor_dir} ) {
Warning("There is no monitor in the database for $$Storage{Path}/$monitor_dir. Skipping it.");
next;
}
my $Monitor = $Monitors{$monitor_dir};
Debug("Found filesystem monitor '$monitor_dir'");
{
my @day_dirs = glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]");
Debug(qq`Checking for Deep Events under $$Storage{Path} using glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]") returned `. scalar @day_dirs . ' days with events');
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': $!");
next;
}
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 );
Debug('Have ' . (scalar @event_links) . ' event links');
closedir(DIR);
my $count = 0;
foreach my $event_link ( @event_links ) {
# 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");
#Event path is hour/minute/sec
my $event_path = readlink($event_link);
if ( !($event_path and -e $event_path) ) {
Warning("Event link $day_dir/$event_link does not point to valid target at $event_path");
next;
}
if ( ! ZoneMinder::Event->find_one(Id=>$event_id) ) {
Info("Event not found in db for event data found at $$Storage{Path}/$day_dir/$event_path with Id=$event_id");
if ( confirm() ) {
my $Event = 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{Name} = "Event $event_id recovered";
$Event->MonitorId( $monitor_dir );
$Event->StorageId( $Storage->Id() );
$Event->DiskSpace( undef );
$Event->Width( $Monitor->Width() );
$Event->Height( $Monitor->Height() );
$Event->Orientation( $Monitor->Orientation() );
$Event->recover_timestamps();
$Event->save({}, 1);
Debug("Event resurrected as " . $Event->to_string() );
next;
} # end if resurrection
} # event path exists
} # end foreach event_link
# Now check for events that have lost their link. We can determine event Id from .mp4
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;
}
} # end foreach mp4
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 ! ZoneMinder::Event->find_one(Id=>$event_id) ) {
Info("Event not found in db for event data found at $$Storage{Path}/$monitor_dir/$day_dir/$event_dir");
if ( confirm() ) {
my $Event = 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{Name} = "Event $event_id recovered";
$Event->MonitorId( $monitor_dir );
$Event->Width( $Monitor->Width() );
$Event->Height( $Monitor->Height() );
$Event->Orientation( $Monitor->Orientation() );
$Event->StorageId( $Storage->Id() );
$Event->DiskSpace( undef );
$Event->recover_timestamps();
$Event->save({}, 1);
Debug("Event resurrected as " . $Event->to_string() );
next;
}
} # end if event found
# Search in db for given timestamp?
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;
}
} # end foreach event_dir without link
chdir( $Storage->Path() );
} # end foreach day dir
}
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." );
foreach my $event_dir ( @event_dirs ) {
if ( ! -d $event_dir ) {
Debug("$event_dir is not a dir. Skipping");
next;
}
my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/;
if ( !$event_id ) {
Debug("Unable to parse date/event_id from $event_dir");
next;
}
my $Event = ZoneMinder::Event->find_one(Id=>$event_id);
if ( $Event ) {
Debug('Found event in the db, moving on.');
next;
}
$Event = new ZoneMinder::Event();
$$Event{Id} = $event_id;
$$Event{Path} = join('/', $Storage->Path(), $event_dir );
Debug("Have event $$Event{Id} at $$Event{Path}");
if ( confirm() ) {
$$Event{Scheme} = 'Medium';
$$Event{RelativePath} = $event_dir;
$$Event{Name} = "Event $event_id recovered";
$Event->MonitorId( $monitor_dir );
$Event->Width( $Monitor->Width() );
$Event->Height( $Monitor->Height() );
$Event->Orientation( $Monitor->Orientation() );
$Event->StorageId( $Storage->Id() );
$Event->recover_timestamps();
$Event->save({}, 1);
Debug("Event resurrected as " . $Event->to_string() );
}
} # end foreach event
} # end search for Medium
# Shallow
Debug("Checking for ShallowScheme Events under $$Storage{Path}/$monitor_dir");
if ( ! chdir($monitor_dir) ) {
Error("Can't chdir directory '$$Storage{Path}/$monitor_dir': $!");
next;
}
if ( ! opendir(DIR, '.') ) {
Error("Can't open directory '$$Storage{Path}/$monitor_dir': $!");
next;
}
my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR );
closedir(DIR);
foreach my $event ( @temp_events ) {
my $Event = ZoneMinder::Event->find_one(Id=>$event);
if ( $Event ) {
Debug("Found an event in db for $event");
next;
}
$$Event{Id} = $event;
$$Event{Path} = join('/', $Storage->Path(), $event );
Debug("Have event $$Event{Id} at $$Event{Path}");
if ( confirm() ) {
$$Event{Scheme} = 'Shallow';
$$Event{Name} = "Event $event recovered";
#$$Event{Path} = $event_path;
$Event->MonitorId( $monitor_dir );
$Event->Width( $Monitor->Width() );
$Event->Height( $Monitor->Height() );
$Event->Orientation( $Monitor->Orientation() );
$Event->StorageId( $Storage->Id() );
$Event->recover_timestamps();
$Event->save({}, 1);
Debug("Event resurrected as " . $Event->to_string() );
}
} # end foreach event
chdir( $Storage->Path() );
} # end foreach monitor
} # end foreach Storage Area
Term();
sub confirm {
my $prompt = shift || 'resurrect';
my $action = shift || 'resurrecting';
my $yesno = 0;
if ( $report ) {
print( "\n" );
} elsif ( $interactive ) {
print(", $prompt Y/n/q: ");
my $char = <>;
chomp( $char );
if ( $char eq 'q' ) {
Term();
}
if ( !$char ) {
$char = 'y';
}
$yesno = ( $char =~ /[yY]/ );
} else {
Info($action);
$yesno = 1;
}
return $yesno;
}
1;
__END__
=head1 NAME
zmrecover.pl - ZoneMinder event file system and database recovery checker
=head1 SYNOPSIS
zmrecover.pl [-r,-report|-i,-interactive]
=head1 DESCRIPTION
This script checks for consistency between the event filesystem and
the database. If events are found in one and not the other they are
deleted (optionally). Additionally any monitor event directories that
do not correspond to a database monitor are similarly disposed of.
However monitors in the database that don't have a directory are left
alone as this is valid if they are newly created and have no events
yet.
=head1 OPTIONS
-i, --interactive - Ask before applying any changes
-m, --monitor_id - Only consider the given monitor
-r, --report - Just report don't actually do anything
-s, --storage_id - Specify a storage area to recover instead of all
-v, --version - Print the installed version of ZoneMinder
=head1 COPYRIGHT
ZoneMinder Recover Script
Copyright (C) 2018 ZoneMinder LLC
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=cut

View File

@ -1,39 +1,4 @@
#!/usr/bin/perl -wT
#
# ==========================================================================
#
# ZoneMinder WatchDog Script, $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.
#
# ==========================================================================
=head1 NAME
zmwatch.pl - ZoneMinder Stats Updating Script
=head1 SYNOPSIS
zmstats.pl
=head1 DESCRIPTION
This does background updating various stats in the db like event counts, diskspace, etc.
=cut
use strict;
use bytes;
@ -66,8 +31,8 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
logInit();
logSetSignal();
Info( "Stats Daemon starting in ".START_DELAY." seconds\n" );
sleep( START_DELAY );
Info("Stats Daemon starting in ".START_DELAY." seconds");
sleep(START_DELAY);
my $dbh = zmDbConnect();
@ -88,7 +53,43 @@ while( 1 ) {
sleep($Config{ZM_STATS_UPDATE_INTERVAL});
} # end while (1)
Info( "Stats Daemon exiting\n" );
Info("Stats Daemon exiting");
exit();
1;
__END__
#
# ==========================================================================
#
# ZoneMinder WatchDog Script, $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.
#
# ==========================================================================
=head1 NAME
zmstats.pl - ZoneMinder Stats Updating Script
=head1 SYNOPSIS
zmstats.pl
=head1 DESCRIPTION
This does background updating various stats in the db like event counts, diskspace, etc.
=cut

View File

@ -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 );
}
$new_monitors{$monitor->{Id}} = $monitor;
} # end while fetchrow
%monitors = %new_monitors;
@ -348,25 +362,25 @@ 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 ) );
next if ( !zmMemVerify($monitor) );
Debug( "Handling action '$action'\n" );
Debug("Handling action '$action'");
if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ ) {
my $state = $1;
my $delay = $2;
if ( $state eq 'enable' ) {
zmMonitorEnable( $monitor );
zmMonitorEnable($monitor);
} else {
zmMonitorDisable( $monitor );
zmMonitorDisable($monitor);
}
# 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'
@ -381,9 +395,9 @@ sub handleMessage {
my $delay = $2;
my $trigger_data;
if ( $trigger eq 'on' ) {
zmTriggerEventOn( $monitor, $score, $cause, $text );
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
Info( "Trigger '$trigger' '$cause'\n" );
zmTriggerEventOn($monitor, $score, $cause, $text);
zmTriggerShowtext($monitor, $showtext) if defined($showtext);
Info("Trigger '$trigger' '$cause'");
if ( $delay ) {
my $action_text = $id.'|cancel';
handleDelay($delay, $connection, $action_text);
@ -393,29 +407,29 @@ sub handleMessage {
my $action_text = $id.'|off|0|'.$cause.'|'.$text;
handleDelay($delay, $connection, $action_text);
} else {
my $last_event = zmGetLastEvent( $monitor );
zmTriggerEventOff( $monitor );
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
Info( "Trigger '$trigger'\n" );
my $last_event = zmGetLastEvent($monitor);
zmTriggerEventOff($monitor);
zmTriggerShowtext($monitor, $showtext) if defined($showtext);
Info("Trigger '$trigger'");
# Wait til it's finished
while( zmInAlarm( $monitor )
&& ($last_event == zmGetLastEvent( $monitor ))
while( zmInAlarm($monitor)
&& ($last_event == zmGetLastEvent($monitor))
) {
# Tenth of a second
usleep( 100000 );
usleep(100000);
}
zmTriggerEventCancel( $monitor );
zmTriggerEventCancel($monitor);
}
} # end if trigger is on or off
} elsif( $action eq 'cancel' ) {
zmTriggerEventCancel( $monitor );
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
Info( "Cancelled event\n" );
zmTriggerEventCancel($monitor);
zmTriggerShowtext($monitor, $showtext) if defined($showtext);
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,8 +444,9 @@ 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__
@ -506,13 +521,13 @@ B<id>|B<action>|B<score>|B<cause>|B<text>|B<showtext>
=back
Note that multiple messages can be sent at once and should be LF or CRLF
delimited. This script is not necessarily intended to be a solution in
itself, but is intended to be used as 'glue' to help ZoneMinder interface
with other systems. It will almost certainly require some customisation
before you can make any use of it. If all you want to do is generate alarms
from external sources then using the ZoneMinder::SharedMem perl module is
likely to be easier.
Note that multiple messages can be sent at once and should be LF or CRLF
delimited. This script is not necessarily intended to be a solution in
itself, but is intended to be used as 'glue' to help ZoneMinder interface
with other systems. It will almost certainly require some customisation
before you can make any use of it. If all you want to do is generate alarms
from external sources then using the ZoneMinder::SharedMem perl module is
likely to be easier.
=head1 EXAMPLES

View File

@ -923,10 +923,10 @@ if ( $version ) {
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();
#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();

View File

@ -109,7 +109,10 @@ bool EventStream::loadInitialEventData( uint64_t init_event_id, unsigned int ini
bool EventStream::loadEventData(uint64_t event_id) {
static char sql[ZM_SQL_MED_BUFSIZ];
snprintf(sql, sizeof(sql), "SELECT MonitorId, StorageId, Frames, unix_timestamp( StartTime ) AS StartTimestamp, (SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id) AS Duration, DefaultVideo, Scheme FROM Events WHERE Id = %" PRIu64, event_id);
snprintf(sql, sizeof(sql),
"SELECT MonitorId, StorageId, Frames, unix_timestamp( StartTime ) AS StartTimestamp, "
"(SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id) AS Duration, "
"DefaultVideo, Scheme, SaveJPEGs FROM Events WHERE Id = %" PRIu64, event_id);
if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn));
@ -151,6 +154,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
} else {
event_data->scheme = Storage::SHALLOW;
}
event_data->SaveJPEGs = dbrow[7] == NULL ? 0 : atoi(dbrow[7]);
mysql_free_result( result );
Storage * storage = new Storage(event_data->storage_id);
@ -241,6 +245,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
if ( event_data->video_file[0] ) {
char filepath[PATH_MAX];
snprintf(filepath, sizeof(filepath), "%s/%s", event_data->path, event_data->video_file);
Debug(1, "Loading video file from %s", filepath);
ffmpeg_input = new FFmpeg_Input();
if ( 0 > ffmpeg_input->Open( filepath ) ) {
Warning("Unable to open ffmpeg_input %s/%s", event_data->path, event_data->video_file);
@ -279,8 +284,8 @@ void EventStream::processCommand(const CmdMsg *msg) {
}
// If we are in single event mode and at the last frame, replay the current event
if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) {
Debug(1, "Was in single_mode, and last frame, so jumping to 1st frame");
if ( (mode == MODE_SINGLE || mode == MODE_NONE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) {
Debug(1, "Was in single or no replay mode, and at last frame, so jumping to 1st frame");
curr_frame_id = 1;
} else {
Debug(1, "mode is %s, current frame is %d, frame count is %d", (mode == MODE_SINGLE ? "single" : "not single" ), curr_frame_id, event_data->frame_count );
@ -501,7 +506,7 @@ void EventStream::checkEventLoaded() {
}
if ( reload_event ) {
if ( forceEventChange || mode != MODE_SINGLE ) {
if ( forceEventChange || ( mode != MODE_SINGLE && mode != MODE_NONE ) ) {
//Info( "SQL:%s", sql );
if ( mysql_query( &dbconn, sql ) ) {
Error( "Can't run query: %s", mysql_error( &dbconn ) );
@ -570,15 +575,18 @@ bool EventStream::sendFrame( int delta_us ) {
// This needs to be abstracted. If we are saving jpgs, then load the capture file. If we are only saving analysis frames, then send that.
// // This is also wrong, need to have this info stored in the event! FIXME
if ( monitor->GetOptSaveJPEGs() & 1 ) {
if ( event_data->SaveJPEGs & 1 ) {
snprintf( filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id );
} else if ( monitor->GetOptSaveJPEGs() & 2 ) {
} else if ( event_data->SaveJPEGs & 2 ) {
snprintf( filepath, sizeof(filepath), staticConfig.analyse_file_format, event_data->path, curr_frame_id );
if ( stat( filepath, &filestat ) < 0 ) {
Debug(1, "analyze file %s not found will try to stream from other", filepath);
snprintf( filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id );
if ( stat( filepath, &filestat ) < 0 ) {
Debug(1, "capture file %s not found either", filepath);
filepath[0] = 0;
}
}
} else if ( ! ffmpeg_input ) {
Fatal("JPEGS not saved.zms is not capable of streaming jpegs from mp4 yet");
@ -755,6 +763,12 @@ void EventStream::runStream() {
// commands may set send_frame to true
while(checkCommandQueue());
// Update modified time of the socket .lock file so that we can tell which ones are stale.
if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) {
touch(sock_path_lock);
last_comm_update = now;
}
if ( step != 0 )
curr_frame_id += step;
@ -812,7 +826,7 @@ void EventStream::runStream() {
step = 0;
send_frame = true;
} else if ( !send_frame ) {
// We are paused, and doing nothing
// We are paused, not stepping and doing nothing
double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent;
if ( actual_delta_time > MAX_STREAM_DELAY ) {
// Send keepalive
@ -828,9 +842,11 @@ void EventStream::runStream() {
curr_stream_time = frame_data->timestamp;
if ( !paused ) {
curr_frame_id += replay_rate>0?1:-1;
if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) )
curr_frame_id += (replay_rate>0) ? 1 : -1;
if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) {
Debug(2, "Have mode==MODE_SINGLE and at end of event, looping back to start");
curr_frame_id = 1;
}
if ( send_frame && type != STREAM_MPEG ) {
Debug( 3, "dUs: %d", delta_us );
if ( delta_us )

View File

@ -42,7 +42,7 @@ extern "C" {
class EventStream : public StreamBase {
public:
typedef enum { MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode;
typedef enum { MODE_NONE, MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode;
protected:
struct FrameData {
@ -65,6 +65,7 @@ class EventStream : public StreamBase {
FrameData *frames;
char video_file[PATH_MAX];
Storage::Schemes scheme;
int SaveJPEGs;
};
protected:

View File

@ -28,11 +28,14 @@ void FFMPEGInit() {
static bool bInit = false;
if ( !bInit ) {
//if ( logDebugging() )
//av_log_set_level( AV_LOG_DEBUG );
//else
if ( logDebugging() )
av_log_set_level( AV_LOG_DEBUG );
else
av_log_set_level( AV_LOG_QUIET );
#if LIBAVCODEC_VERSION_CHECK(58, 18, 0, 64, 0)
#else
av_register_all();
#endif
avformat_network_init();
bInit = true;
}
@ -225,8 +228,9 @@ static void zm_log_fps(double d, const char *postfix) {
Debug(1, "%3.2f %s", d, postfix);
} else if (v % (100 * 1000)) {
Debug(1, "%1.0f %s", d, postfix);
} else
} else {
Debug(1, "%1.0fk %s", d / 1000, postfix);
}
}
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
@ -355,9 +359,8 @@ int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt) {
#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100)
#else
unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ) {
dst->size = (src->size + FF_INPUT_BUFFER_PADDING_SIZE)/sizeof(uint64_t) + 1;
dst->data = reinterpret_cast<uint8_t*>(new uint64_t[dst->size]);
memcpy(dst->data, src->data, src->size );
av_new_packet(dst,src->size);
memcpy(dst->data, src->data, src->size);
dst->flags = src->flags;
return 0;
}

View File

@ -303,8 +303,8 @@ void zm_dump_codecpar ( const AVCodecParameters *par );
#define zm_av_packet_unref( packet ) av_packet_unref( packet )
#define zm_av_packet_ref( dst, src ) av_packet_ref( dst, src )
#else
unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src );
#define zm_av_packet_unref( packet ) av_free_packet( packet )
unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src );
#endif
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
#define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) avcodec_decode_video2( context, rawFrame, frameComplete, packet )

View File

@ -82,8 +82,35 @@ static AVPixelFormat get_format(AVCodecContext *avctx, const enum AVPixelFormat
}
#endif
FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) :
Camera( p_id, FFMPEG_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ),
FfmpegCamera::FfmpegCamera(
int p_id,
const std::string &p_path,
const std::string &p_method,
const std::string &p_options,
int p_width,
int p_height,
int p_colours,
int p_brightness,
int p_contrast,
int p_hue,
int p_colour,
bool p_capture,
bool p_record_audio
) :
Camera(
p_id,
FFMPEG_SRC,
p_width,
p_height,
p_colours,
ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours),
p_brightness,
p_contrast,
p_hue,
p_colour,
p_capture,
p_record_audio
),
mPath( p_path ),
mMethod( p_method ),
mOptions( p_options )
@ -113,6 +140,7 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri
videoStore = NULL;
video_last_pts = 0;
have_video_keyframe = false;
packetqueue = NULL;
#if HAVE_LIBSWSCALE
mConvertContext = NULL;
@ -134,10 +162,6 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri
FfmpegCamera::~FfmpegCamera() {
if ( videoStore ) {
delete videoStore;
videoStore = NULL;
}
Close();
if ( capture ) {
@ -155,12 +179,12 @@ void FfmpegCamera::Terminate() {
int FfmpegCamera::PrimeCapture() {
if ( mCanCapture ) {
Info( "Priming capture from %s, CLosing", mPath.c_str() );
Info("Priming capture from %s, Closing", mPath.c_str());
Close();
}
mVideoStreamId = -1;
mAudioStreamId = -1;
Info( "Priming capture from %s", mPath.c_str() );
Info("Priming capture from %s", mPath.c_str());
return OpenFfmpeg();
}
@ -170,7 +194,7 @@ int FfmpegCamera::PreCapture() {
if ( ! mCanCapture )
return OpenFfmpeg();
// Nothing to do here
return( 0 );
return 0;
}
int FfmpegCamera::Capture( Image &image ) {
@ -331,6 +355,8 @@ int FfmpegCamera::OpenFfmpeg() {
ret = av_dict_set(&opts, "rtsp_transport", "tcp", 0);
} else if ( method == "rtpRtspHttp" ) {
ret = av_dict_set(&opts, "rtsp_transport", "http", 0);
} else if ( method == "rtpUni" ) {
ret = av_dict_set(&opts, "rtsp_transport", "udp", 0);
} else {
Warning("Unknown method (%s)", method.c_str() );
}
@ -435,6 +461,7 @@ int FfmpegCamera::OpenFfmpeg() {
Debug(3, "Found video stream at index %d", mVideoStreamId);
Debug(3, "Found audio stream at index %d", mAudioStreamId);
packetqueue = new zm_packetqueue( mVideoStreamId > mAudioStreamId ? mVideoStreamId : mAudioStreamId );
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
mVideoCodecContext = avcodec_alloc_context3(NULL);
@ -606,7 +633,8 @@ int FfmpegCamera::OpenFfmpeg() {
return -1;
}
mConvertContext = sws_getContext(mVideoCodecContext->width,
mConvertContext = sws_getContext(
mVideoCodecContext->width,
mVideoCodecContext->height,
mVideoCodecContext->pix_fmt,
width, height,
@ -679,6 +707,10 @@ int FfmpegCamera::Close() {
delete videoStore;
videoStore = NULL;
}
if ( packetqueue ) {
delete packetqueue;
packetqueue = NULL;
}
return 0;
} // end FfmpegCamera::Close
@ -711,9 +743,15 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event
return -1;
}
if ( packet.pts < -100000 ) {
// Ignore packets that have crazy negative pts. They aren't supposed to happen.
Warning("Ignore packet because pts is massively negative");
dumpPacket(&packet,"Ignored packet");
continue;
}
int keyframe = packet.flags & AV_PKT_FLAG_KEY;
bytes += packet.size;
dumpPacket(&packet);
dumpPacket(&packet,"Captured Packet");
//Video recording
if ( recording.tv_sec ) {
@ -722,7 +760,8 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event
uint32_t video_writer_event_id = monitor->GetVideoWriterEventId();
if ( last_event_id != video_writer_event_id ) {
Debug(2, "Have change of event. last_event(%d), our current (%d)", last_event_id, video_writer_event_id );
Debug(2, "Have change of event. last_event(%d), our current (%d)",
last_event_id, video_writer_event_id);
if ( videoStore ) {
Info("Re-starting video storage module");
@ -731,7 +770,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event
// Also don't know how much it matters for audio.
if ( packet.stream_index == mVideoStreamId ) {
//Write the packet to our video store
int ret = videoStore->writeVideoFramePacket( &packet );
int ret = videoStore->writeVideoFramePacket(&packet);
if ( ret < 0 ) { //Less than zero and we skipped a frame
Warning("Error writing last packet to videostore.");
}
@ -789,14 +828,14 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event
ZMPacket *queued_packet;
// Clear all packets that predate the moment when the recording began
packetqueue.clear_unwanted_packets( &recording, mVideoStreamId );
packetqueue->clear_unwanted_packets( &recording, mVideoStreamId );
while ( ( queued_packet = packetqueue.popPacket() ) ) {
while ( ( queued_packet = packetqueue->popPacket() ) ) {
AVPacket *avp = queued_packet->av_packet();
packet_count += 1;
//Write the packet to our video store
Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue.size() );
Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue->size() );
if ( avp->stream_index == mVideoStreamId ) {
ret = videoStore->writeVideoFramePacket( avp );
have_video_keyframe = true;
@ -830,18 +869,22 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event
if ( packet.stream_index == mVideoStreamId ) {
if ( keyframe ) {
Debug(3, "Clearing queue");
packetqueue.clearQueue(monitor->GetPreEventCount(), mVideoStreamId);
packetqueue.queuePacket(&packet);
} else if ( packetqueue.size() ) {
if ( packetqueue->packet_count(mVideoStreamId) >= monitor->GetImageBufferCount() ) {
Warning("ImageBufferCount is too small. Needs to be at least %d. Either increase it or decrease time between keyframes", packetqueue->packet_count(mVideoStreamId) );
}
packetqueue->clearQueue(monitor->GetPreEventCount(), mVideoStreamId);
packetqueue->queuePacket(&packet);
} else if ( packetqueue->size() ) {
// it's a keyframe or we already have something in the queue
packetqueue.queuePacket(&packet);
packetqueue->queuePacket(&packet);
}
} else if ( packet.stream_index == mAudioStreamId ) {
// The following lines should ensure that the queue always begins with a video keyframe
//Debug(2, "Have audio packet, reocrd_audio is (%d) and packetqueue.size is (%d)", record_audio, packetqueue.size() );
if ( record_audio && packetqueue.size() ) {
if ( record_audio && packetqueue->size() ) {
// if it's audio, and we are doing audio, and there is already something in the queue
packetqueue.queuePacket(&packet);
packetqueue->queuePacket(&packet);
}
}
} // end if recording or not

View File

@ -79,7 +79,7 @@ class FfmpegCamera : public Camera {
#endif // HAVE_LIBAVFORMAT
VideoStore *videoStore;
zm_packetqueue packetqueue;
zm_packetqueue *packetqueue;
bool have_video_keyframe;
#if HAVE_LIBSWSCALE

File diff suppressed because it is too large Load Diff

View File

@ -274,6 +274,7 @@ void std_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result,
void std_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void std_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void std_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void neon32_armv7_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void neon32_armv7_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void neon32_armv7_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);

View File

@ -456,6 +456,7 @@ public:
void SetVideoWriterEventId( unsigned long long p_event_id ) { video_store_data->current_event = p_event_id; }
unsigned int GetPreEventCount() const { return pre_event_count; };
int GetImageBufferCount() const { return image_buffer_count; };
State GetState() const;
int GetImage( int index=-1, int scale=100 );
Snapshot *getSnapshot() const;

View File

@ -531,6 +531,12 @@ void MonitorStream::runStream() {
Debug(2, "Have checking command Queue for connkey: %d", connkey );
got_command = true;
}
// Update modified time of the socket .lock file so that we can tell which ones are stale.
if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) {
touch(sock_path_lock);
last_comm_update = now;
}
}
if ( paused ) {

View File

@ -21,27 +21,31 @@
#include "zm_ffmpeg.h"
#include <sys/time.h>
#define VIDEO_QUEUESIZE 200
#define AUDIO_QUEUESIZE 50
zm_packetqueue::zm_packetqueue(){
zm_packetqueue::zm_packetqueue( int p_max_stream_id ) {
max_stream_id = p_max_stream_id;
packet_counts = new int[max_stream_id+1];
for ( int i=0; i <= max_stream_id; ++i )
packet_counts[i] = 0;
}
zm_packetqueue::~zm_packetqueue() {
clearQueue();
delete[] packet_counts;
packet_counts = NULL;
}
bool zm_packetqueue::queuePacket( ZMPacket* zm_packet ) {
pktQueue.push_back( zm_packet );
bool zm_packetqueue::queuePacket(ZMPacket* zm_packet) {
pktQueue.push_back(zm_packet);
packet_counts[zm_packet->packet.stream_index] += 1;
return true;
}
bool zm_packetqueue::queuePacket( AVPacket* av_packet ) {
ZMPacket *zm_packet = new ZMPacket( av_packet );
bool zm_packetqueue::queuePacket(AVPacket* av_packet) {
pktQueue.push_back( zm_packet );
ZMPacket *zm_packet = new ZMPacket(av_packet);
pktQueue.push_back(zm_packet);
packet_counts[zm_packet->packet.stream_index] += 1;
return true;
}
@ -53,13 +57,14 @@ ZMPacket* zm_packetqueue::popPacket( ) {
ZMPacket *packet = pktQueue.front();
pktQueue.pop_front();
packet_counts[packet->packet.stream_index] -= 1;
return packet;
}
unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) {
unsigned int zm_packetqueue::clearQueue(unsigned int frames_to_keep, int stream_id) {
Debug(3, "Clearing all but %d frames, queue has %d", frames_to_keep, pktQueue.size() );
Debug(3, "Clearing all but %d frames, queue has %d", frames_to_keep, pktQueue.size());
frames_to_keep += 1;
if ( pktQueue.empty() ) {
@ -74,7 +79,8 @@ unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream
ZMPacket *zm_packet = *it;
AVPacket *av_packet = &(zm_packet->packet);
Debug(4, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
Debug(4, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)",
av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
// Want frames_to_keep video keyframes. Otherwise, we may not have enough
if ( ( av_packet->stream_index == stream_id) ) {
@ -88,16 +94,18 @@ unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream
ZMPacket *zm_packet = *it;
AVPacket *av_packet = &(zm_packet->packet);
Debug(5, "Looking for keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
Debug(5, "Looking for keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)",
av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
// Want frames_to_keep video keyframes. Otherwise, we may not have enough
if ( ( av_packet->stream_index == stream_id) && ( av_packet->flags & AV_PKT_FLAG_KEY ) ) {
Debug(4, "Found keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
Debug(4, "Found keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)",
av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep );
break;
}
}
if ( frames_to_keep ) {
Debug(3, "Hit end of queue, still need (%d) video frames", frames_to_keep );
Debug(3, "Hit end of queue, still need (%d) video frames", frames_to_keep);
}
if ( it != pktQueue.rend() ) {
// We want to keep this packet, so advance to the next
@ -105,22 +113,25 @@ unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream
}
unsigned int delete_count = 0;
while ( it != pktQueue.rend() ) {
Debug(4, "Deleting a packet from the front, count is (%d)", delete_count );
Debug(4, "Deleting a packet from the front, count is (%d)", delete_count);
packet = pktQueue.front();
pktQueue.pop_front();
packet_counts[packet->packet.stream_index] -= 1;
delete packet;
delete_count += 1;
}
Debug(3, "Deleted (%d) packets", delete_count );
packet = NULL; // tidy up for valgrind
Debug(3, "Deleted %d packets, %d remaining", delete_count, pktQueue.size());
return delete_count;
} // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id )
void zm_packetqueue::clearQueue() {
ZMPacket *packet = NULL;
while(!pktQueue.empty()) {
while (!pktQueue.empty()) {
packet = pktQueue.front();
packet_counts[packet->packet.stream_index] -= 1;
pktQueue.pop_front();
delete packet;
}
@ -130,18 +141,20 @@ unsigned int zm_packetqueue::size() {
return pktQueue.size();
}
int zm_packetqueue::packet_count( int stream_id ) {
return packet_counts[stream_id];
} // end int zm_packetqueue::packet_count( int stream_id )
void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId ) {
// Need to find the keyframe <= recording_started. Can get rid of audio packets.
if ( pktQueue.empty() ) {
if ( pktQueue.empty() )
return;
}
// Step 1 - find keyframe < recording_started.
// Step 2 - pop packets until we get to the packet in step 2
std::list<ZMPacket *>::reverse_iterator it;
Debug(3, "Looking for keyframe after start recording stream id (%d)", mVideoStreamId );
Debug(3, "Looking for keyframe after start recording stream id (%d)", mVideoStreamId);
for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) {
ZMPacket *zm_packet = *it;
AVPacket *av_packet = &(zm_packet->packet);
@ -175,9 +188,11 @@ void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVi
//while ( pktQueue.rend() != it ) {
packet = pktQueue.front();
pktQueue.pop_front();
packet_counts[packet->packet.stream_index] -= 1;
delete packet;
deleted_frames += 1;
}
packet = NULL; // tidy up for valgrind
zm_packet = pktQueue.front();
av_packet = &(zm_packet->packet);
@ -186,4 +201,4 @@ void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVi
} else {
Debug(1, "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() );
}
}
} // end void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId )

View File

@ -29,23 +29,25 @@
extern "C" {
#include <libavformat/avformat.h>
}
class zm_packetqueue {
public:
zm_packetqueue();
zm_packetqueue(int max_stream_id);
virtual ~zm_packetqueue();
bool queuePacket( AVPacket* packet, struct timeval *timestamp );
bool queuePacket( ZMPacket* packet );
bool queuePacket( AVPacket* packet );
ZMPacket * popPacket( );
bool queuePacket(AVPacket* packet, struct timeval *timestamp);
bool queuePacket(ZMPacket* packet);
bool queuePacket(AVPacket* packet);
ZMPacket * popPacket();
bool popVideoPacket(ZMPacket* packet);
bool popAudioPacket(ZMPacket* packet);
unsigned int clearQueue( unsigned int video_frames_to_keep, int stream_id );
void clearQueue( );
unsigned int clearQueue(unsigned int video_frames_to_keep, int stream_id);
void clearQueue();
unsigned int size();
void clear_unwanted_packets( timeval *recording, int mVideoStreamId );
void clear_unwanted_packets(timeval *recording, int mVideoStreamId);
int packet_count(int stream_id);
private:
std::list<ZMPacket *> pktQueue;
int max_stream_id;
int *packet_counts; /* packet count for each stream_id, to keep track of how many video vs audio packets are in the queue */
};

View File

@ -68,8 +68,8 @@ void RemoteCamera::Initialise() {
if( host.empty() )
Fatal( "No host specified for remote camera" );
if( port.empty() )
Fatal( "No port specified for remote camera" );
if ( port.empty() )
Fatal("No port specified for remote camera");
//if( path.empty() )
//Fatal( "No path specified for remote camera" );
@ -99,6 +99,12 @@ void RemoteCamera::Initialise() {
if ( ret != 0 ) {
Fatal( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) );
}
struct addrinfo *p = NULL;
int addr_count = 0;
for ( p = hp; p != NULL; p = p->ai_next ) {
addr_count++;
}
Debug(1, "%d addresses returned", addr_count);
}
int RemoteCamera::Read( int fd, char *buf, int size ) {

View File

@ -140,7 +140,7 @@ void RemoteCameraHttp::Initialise() {
} // end void RemoteCameraHttp::Initialise()
int RemoteCameraHttp::Connect() {
struct addrinfo *p;
struct addrinfo *p = NULL;
for ( p = hp; p != NULL; p = p->ai_next ) {
sd = socket( p->ai_family, p->ai_socktype, p->ai_protocol );

View File

@ -286,6 +286,18 @@ void StreamBase::openComms() {
}
Debug(1, "Trying to open the lock on %s", sock_path_lock);
// Under systemd, we get chrooted to something like /tmp/systemd-apache-blh/ so the dir may not exist.
if ( mkdir(staticConfig.PATH_SOCKS.c_str(), 0755) ) {
if ( errno != EEXIST ) {
Error("Can't mkdir %s: %s", staticConfig.PATH_SOCKS.c_str(), strerror(errno));
return;
} else {
Debug(3, "SOCKS dir %s already exists", staticConfig.PATH_SOCKS.c_str() );
}
} else {
Debug(3, "Success making SOCKS dir %s", staticConfig.PATH_SOCKS.c_str() );
}
lock_fd = open(sock_path_lock, O_CREAT|O_WRONLY, S_IRUSR | S_IWUSR);
if ( lock_fd <= 0 ) {
Error("Unable to open sock lock file %s: %s", sock_path_lock, strerror(errno));
@ -302,7 +314,7 @@ void StreamBase::openComms() {
if ( sd < 0 ) {
Fatal("Can't create socket: %s", strerror(errno));
} else {
Debug(1, "Have socket %d", sd);
Debug(3, "Have socket %d", sd);
}
length = snprintf(
@ -332,8 +344,10 @@ void StreamBase::openComms() {
snprintf(rem_sock_path, sizeof(rem_sock_path), "%s/zms-%06dw.sock", staticConfig.PATH_SOCKS.c_str(), connkey);
strncpy(rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path)-1);
rem_addr.sun_family = AF_UNIX;
gettimeofday(&last_comm_update, NULL);
} // end if connKey > 0
Debug(2, "comms open");
Debug(3, "comms open");
} // end void StreamBase::openComms()
void StreamBase::closeComms() {

View File

@ -85,6 +85,7 @@ protected:
int step;
struct timeval now;
struct timeval last_comm_update;
double base_fps;
double effective_fps;

View File

@ -24,6 +24,8 @@
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
#if defined(__arm__)
#include <sys/auxv.h>
#endif
@ -414,3 +416,22 @@ Warning("ZM Compiled without LIBCURL. UriDecoding not implemented.");
#endif
}
void touch(const char *pathname) {
int fd = open(pathname,
O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK,
0666);
if ( fd < 0 ) {
// Couldn't open that path.
Error("Couldn't open() path \"%s in touch", pathname);
return;
}
int rc = utimensat(AT_FDCWD,
pathname,
nullptr,
0);
if ( rc ) {
Error("Couldn't utimensat() path %s in touch", pathname);
return;
}
}

View File

@ -63,5 +63,5 @@ extern unsigned int neonversion;
char *timeval_to_string( struct timeval tv );
std::string UriDecode( const std::string &encoded );
void touch( const char *pathname );
#endif // ZM_UTILS_H

View File

@ -64,7 +64,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
}
// Couldn't deduce format from filename, trying from format name
if (!oc) {
if ( !oc ) {
avformat_alloc_output_context2(&oc, NULL, format, filename);
if (!oc) {
Error(
@ -108,7 +108,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
Debug(2, "Success creating video out stream");
}
if (!video_out_ctx->codec_tag) {
if ( !video_out_ctx->codec_tag ) {
video_out_ctx->codec_tag =
av_codec_get_tag(oc->oformat->codec_tag, video_in_ctx->codec_id);
Debug(2, "No codec_tag, setting to %d", video_out_ctx->codec_tag);
@ -127,9 +127,10 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
#else
video_out_stream =
avformat_new_stream(oc,(AVCodec *)(video_in_ctx->codec));
avformat_new_stream(oc, NULL);
//(AVCodec *)(video_in_ctx->codec));
//avformat_new_stream(oc,(const AVCodec *)(video_in_ctx->codec));
if (!video_out_stream) {
if ( !video_out_stream ) {
Fatal("Unable to create video out stream\n");
} else {
Debug(2, "Success creating video out stream");
@ -158,6 +159,9 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
// Just copy them from the in, no reason to choose different
video_out_ctx->time_base = video_in_ctx->time_base;
if ( ! (video_out_ctx->time_base.num && video_out_ctx->time_base.den) ) {
video_out_ctx->time_base = AV_TIME_BASE_Q;
}
video_out_stream->time_base = video_in_stream->time_base;
Debug(3,
@ -244,18 +248,18 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
// Copy params from instream to ctx
ret = avcodec_parameters_to_context(audio_out_ctx,
audio_in_stream->codecpar);
if (ret < 0) {
Error("Unable to copy audio params to ctx %s\n",
if ( ret < 0 ) {
Error("Unable to copy audio params to ctx %s",
av_make_error_string(ret).c_str());
}
ret = avcodec_parameters_from_context(audio_out_stream->codecpar,
audio_out_ctx);
if (ret < 0) {
Error("Unable to copy audio params to stream %s\n",
if ( ret < 0 ) {
Error("Unable to copy audio params to stream %s",
av_make_error_string(ret).c_str());
}
if (!audio_out_ctx->codec_tag) {
if ( !audio_out_ctx->codec_tag ) {
audio_out_ctx->codec_tag = av_codec_get_tag(
oc->oformat->codec_tag, audio_in_ctx->codec_id);
Debug(2, "Setting audio codec tag to %d",
@ -267,12 +271,12 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
ret = avcodec_copy_context(audio_out_ctx, audio_in_ctx);
audio_out_ctx->codec_tag = 0;
#endif
if (ret < 0) {
Error("Unable to copy audio ctx %s\n",
if ( ret < 0 ) {
Error("Unable to copy audio ctx %s",
av_make_error_string(ret).c_str());
audio_out_stream = NULL;
} else {
if (audio_out_ctx->channels > 1) {
if ( audio_out_ctx->channels > 1 ) {
Warning("Audio isn't mono, changing it.");
audio_out_ctx->channels = 1;
} else {
@ -282,7 +286,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
} // end if audio_out_stream
} // end if is AAC
if (audio_out_stream) {
if ( audio_out_stream ) {
if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0)
audio_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
@ -339,6 +343,9 @@ bool VideoStore::open() {
if (ret < 0) {
Error("Error occurred when writing out file header to %s: %s\n",
filename, av_make_error_string(ret).c_str());
/* free the stream */
avio_closep(&oc->pb);
//avformat_free_context(oc);
return false;
}
return true;
@ -412,7 +419,7 @@ VideoStore::~VideoStore() {
if (int rc = av_write_trailer(oc)) {
Error("Error writing trailer %s", av_err2str(rc));
} else {
Debug(3, "Sucess Writing trailer");
Debug(3, "Success Writing trailer");
}
// When will we not be using a file ?
@ -426,7 +433,7 @@ VideoStore::~VideoStore() {
} else {
Debug(3, "Not closing avio because we are not writing to a file.");
}
}
} // end if ( oc->pb )
// I wonder if we should be closing the file first.
// I also wonder if we really need to be doing all the ctx
// allocation/de-allocation constantly, or whether we can just re-use it.
@ -515,8 +522,8 @@ bool VideoStore::setup_resampler() {
// audio_out_ctx = audio_out_stream->codec;
audio_out_ctx = avcodec_alloc_context3(audio_out_codec);
if (!audio_out_ctx) {
Error("could not allocate codec ctx for AAC\n");
if ( !audio_out_ctx ) {
Error("could not allocate codec ctx for AAC");
audio_out_stream = NULL;
return false;
}
@ -539,6 +546,13 @@ bool VideoStore::setup_resampler() {
#else
audio_out_ctx->refcounted_frames = 1;
#endif
if ( ! audio_out_ctx->channel_layout ) {
Debug(3, "Correcting channel layout from (%d) to (%d)",
audio_out_ctx->channel_layout,
av_get_default_channel_layout(audio_out_ctx->channels)
);
audio_out_ctx->channel_layout = av_get_default_channel_layout(audio_out_ctx->channels);
}
if (audio_out_codec->supported_samplerates) {
int found = 0;

View File

@ -66,7 +66,7 @@ int main( int argc, const char *argv[] ) {
double maxfps = 10.0;
unsigned int bitrate = 100000;
unsigned int ttl = 0;
EventStream::StreamMode replay = EventStream::MODE_SINGLE;
EventStream::StreamMode replay = EventStream::MODE_NONE;
std::string username;
std::string password;
char auth[64] = "";
@ -137,8 +137,17 @@ int main( int argc, const char *argv[] ) {
} else if ( !strcmp( name, "ttl" ) ) {
ttl = atoi(value);
} else if ( !strcmp( name, "replay" ) ) {
replay = !strcmp( value, "gapless" )?EventStream::MODE_ALL_GAPLESS:EventStream::MODE_SINGLE;
replay = !strcmp( value, "all" )?EventStream::MODE_ALL:replay;
if ( !strcmp(value, "gapless") ) {
replay = EventStream::MODE_ALL_GAPLESS;
} else if ( !strcmp(value, "all") ) {
replay = EventStream::MODE_ALL;
} else if ( !strcmp(value, "none") ) {
replay = EventStream::MODE_NONE;
} else if ( !strcmp(value, "single") ) {
replay = EventStream::MODE_SINGLE;
} else {
Error("Unsupported value %s for replay, defaulting to none", value);
}
} else if ( !strcmp( name, "connkey" ) ) {
connkey = atoi(value);
} else if ( !strcmp( name, "buffer" ) ) {

View File

@ -23,7 +23,7 @@ case $i in
shift # past argument=value
;;
-d=*|--distro=*)
DISTRO="${i#*=}"
DISTROS="${i#*=}"
shift # past argument=value
;;
-i=*|--interactive=*)
@ -74,11 +74,15 @@ else
echo "Doing $TYPE build"
fi;
if [ "$DISTRO" == "" ]; then
DISTRO=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`;
echo "Defaulting to $DISTRO for distribution";
if [ "$DISTROS" == "" ]; then
if [ "$RELEASE" != "" ]; then
DISTROS="xenial,bionic,trusty"
else
DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`;
fi;
echo "Defaulting to $DISTROS for distribution";
else
echo "Building for $DISTRO";
echo "Building for $DISTROS";
fi;
# Release is a special mode... it uploads to the release ppa and cannot have a snapshot
@ -116,6 +120,18 @@ else
fi;
fi
PPA="";
if [ "$RELEASE" != "" ]; then
# We need to use our official tarball for the original source, so grab it and overwrite our generated one.
IFS='.' read -r -a VERSION <<< "$RELEASE"
PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}"
else
if [ "$BRANCH" == "" ]; then
PPA="ppa:iconnor/zoneminder-master";
else
PPA="ppa:iconnor/zoneminder-$BRANCH";
fi;
fi;
# Instead of cloning from github each time, if we have a fork lying around, update it and pull from there instead.
if [ ! -d "${GITHUB_FORK}_zoneminder_release" ]; then
@ -154,6 +170,11 @@ if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then
fi;
DIRECTORY="zoneminder_$VERSION";
if [ -d "$DIRECTORY.orig" ]; then
echo "$DIRECTORY.orig already exists. Please delete it."
exit 0;
fi;
echo "Doing $TYPE release $DIRECTORY";
mv "${GITHUB_FORK}_zoneminder_release" "$DIRECTORY.orig";
if [ $? -ne 0 ]; then
@ -161,42 +182,57 @@ if [ $? -ne 0 ]; then
echo "Setting up build dir failed.";
exit $?;
fi;
cd "$DIRECTORY.orig";
# Init submodules
git submodule init
git submodule update --init --recursive
if [ "$DISTRO" == "trusty" ] || [ "$DISTRO" == "precise" ]; then
mv distros/ubuntu1204 debian
else
if [ "$DISTRO" == "wheezy" ]; then
mv distros/debian debian
else
mv distros/ubuntu1604 debian
fi;
# Cleanup
rm -rf .git
rm .gitignore
cd ../
if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
fi;
if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then
IFS=',' ;for DISTRO in `echo "$DISTROS"`; do
echo "Generating package for $DISTRO";
cd $DIRECTORY.orig
if [ -e "debian" ]; then
rm -rf debian
fi;
# Generate Changlog
if [ "$DISTRO" == "trusty" ] || [ "$DISTRO" == "precise" ]; then
cp -Rpd distros/ubuntu1204 debian
else
if [ "$DISTRO" == "wheezy" ]; then
cp -Rpd distros/debian debian
else
cp -Rpd distros/ubuntu1604 debian
fi;
fi;
if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then
AUTHOR="$DEBFULLNAME <$DEBEMAIL>"
else
else
if [ -z `hostname -d` ] ; then
AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`.local>"
else
AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`>"
fi
fi
fi
if [ "$URGENCY" = "" ]; then
if [ "$URGENCY" = "" ]; then
URGENCY="medium"
fi;
fi;
rm -rf .git
rm .gitignore
cd ../
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
cd $DIRECTORY.orig
if [ "$SNAPSHOT" == "stable" ]; then
cat <<EOF > debian/changelog
if [ "$SNAPSHOT" == "stable" ]; then
cat <<EOF > debian/changelog
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
* Release $VERSION
@ -204,37 +240,37 @@ zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
-- $AUTHOR $DATE
EOF
cat <<EOF > debian/NEWS
cat <<EOF > debian/NEWS
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
* Release $VERSION
-- $AUTHOR $DATE
EOF
else
cat <<EOF > debian/changelog
else
cat <<EOF > debian/changelog
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
*
-- $AUTHOR $DATE
EOF
cat <<EOF > debian/changelog
cat <<EOF > debian/changelog
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
*
-- $AUTHOR $DATE
EOF
fi;
fi;
if [ $TYPE == "binary" ]; then
if [ $TYPE == "binary" ]; then
# Auto-install all ZoneMinder's depedencies using the Debian control file
sudo apt-get install devscripts equivs
sudo mk-build-deps -ir ./debian/control
echo "Status: $?"
DEBUILD=debuild
else
else
if [ $TYPE == "local" ]; then
# Auto-install all ZoneMinder's depedencies using the Debian control file
sudo apt-get install devscripts equivs
@ -245,27 +281,20 @@ else
# Source build, don't need build depends.
DEBUILD="debuild -S -sa"
fi;
fi;
if [ "$DEBSIGN_KEYID" != "" ]; then
fi;
if [ "$DEBSIGN_KEYID" != "" ]; then
DEBUILD="$DEBUILD -k$DEBSIGN_KEYID"
fi
$DEBUILD
if [ $? -ne 0 ]; then
echo "Error status code is: $?"
fi
eval $DEBUILD
if [ $? -ne 0 ]; then
echo "Error status code is: $?"
echo "Build failed.";
exit $?;
fi;
fi;
cd ../
if [ "$INTERACTIVE" != "no" ]; then
read -p "Do you want to keep the checked out version of Zoneminder (incase you want to modify it later) [y/N]"
[[ $REPLY == [yY] ]] && { mv "$DIRECTORY.orig" zoneminder_release; echo "The checked out copy is preserved in zoneminder_release"; } || { rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted"; }
echo "Done!"
else
rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted";
fi
cd ../
if [ $TYPE == "binary" ]; then
if [ $TYPE == "binary" ]; then
if [ "$INTERACTIVE" != "no" ]; then
read -p "Not doing dput since it's a binary release. Do you want to install it? (y/N)"
if [[ $REPLY == [yY] ]]; then
@ -284,19 +313,8 @@ if [ $TYPE == "binary" ]; then
fi;
fi;
fi;
else
else
SC="zoneminder_${VERSION}-${DISTRO}${PACKAGE_VERSION}_source.changes";
PPA="";
if [ "$RELEASE" != "" ]; then
IFS='.' read -r -a VERSION <<< "$RELEASE"
PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}"
else
if [ "$BRANCH" == "" ]; then
PPA="ppa:iconnor/zoneminder-master";
else
PPA="ppa:iconnor/zoneminder-$BRANCH";
fi;
fi;
dput="Y";
if [ "$INTERACTIVE" != "no" ]; then
@ -305,6 +323,15 @@ else
dput $PPA $SC
fi;
fi;
fi;
fi;
done; # foreach distro
if [ "$INTERACTIVE" != "no" ]; then
read -p "Do you want to keep the checked out version of Zoneminder (incase you want to modify it later) [y/N]"
[[ $REPLY == [yY] ]] && { mv "$DIRECTORY.orig" zoneminder_release; echo "The checked out copy is preserved in zoneminder_release"; } || { rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted"; }
echo "Done!"
else
rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted";
fi

View File

@ -1 +1 @@
1.32.0
1.32.2

View File

@ -39,7 +39,8 @@ if ( canView('Events') ) {
{
require_once(ZM_SKIN_PATH.'/includes/export_functions.php');
# We use session vars in here, so we need to restart the session because we stopped it in index.php to improve concurrency.
# We use session vars in here, so we need to restart the session
# because we stopped it in index.php to improve concurrency.
session_start();
if ( !empty($_REQUEST['exportDetail']) )
@ -72,6 +73,11 @@ if ( canView('Events') ) {
else
$exportFormat = '';
if ( !empty($_REQUEST['exportCompress']) )
$exportCompress = $_SESSION['export']['compress'] = $_REQUEST['exportCompress'];
else
$exportCompress = false;
session_write_close();
$exportIds = !empty($_REQUEST['eids'])?$_REQUEST['eids']:$_REQUEST['id'];
@ -83,7 +89,8 @@ if ( canView('Events') ) {
$exportImages,
$exportVideo,
$exportMisc,
$exportFormat
$exportFormat,
$exportCompress
) )
ajaxResponse(array('exportFile'=>$exportFile));
else
@ -100,7 +107,8 @@ if ( canView('Events') ) {
if ( $exportFile = exportEvents(
$exportIds,
(isset($_REQUEST['connkey'])?$_REQUEST['connkey']:''),
false,false, false, $exportVideo, false, $exportFormat, $exportStructure ) )
false,false, false,
$exportVideo, false, $exportFormat, $exportStructure ) )
ajaxResponse(array('exportFile'=>$exportFile));
else
ajaxError('Export Failed');

View File

@ -383,7 +383,7 @@ function getNearEvents() {
parseSort();
if ( $user['MonitorIds'] )
$midSql = ' and MonitorId in ('.join( ',', preg_split( '/["\'\s]*,["\'\s]*/', $user['MonitorIds'] ) ).')';
$midSql = ' AND MonitorId IN ('.join( ',', preg_split( '/["\'\s]*,["\'\s]*/', $user['MonitorIds'] ) ).')';
else
$midSql = '';
@ -392,32 +392,40 @@ function getNearEvents() {
$sortOrder = 'asc';
}
$sql = "SELECT E.Id AS Id, E.StartTime AS StartTime FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE $sortColumn ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." ORDER BY $sortColumn ".($sortOrder=='asc'?'desc':'asc') . ' LIMIT 2';
$sql = "SELECT E.Id AS Id, E.StartTime AS StartTime FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE $sortColumn ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql.' AND E.Id<'.$event['Id'] . " ORDER BY $sortColumn ".($sortOrder=='asc'?'desc':'asc');
if ( $sortColumn != 'E.Id' ) {
# When sorting by starttime, if we have two events with the same starttime (diffreent monitors) then we should sort secondly by Id
$sql .= ', E.Id DESC';
}
$sql .= ' LIMIT 1';
$result = dbQuery( $sql );
while ( $id = dbFetchNext( $result, 'Id' ) ) {
if ( $id == $eventId ) {
$prevEvent = dbFetchNext( $result );
break;
}
}
$sql = "SELECT E.Id AS Id, E.StartTime AS StartTime FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." ORDER BY $sortColumn $sortOrder LIMIT 2";
$sql = "SELECT E.Id AS Id, E.StartTime AS StartTime FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql.' AND E.Id>'.$event['Id'] . " ORDER BY $sortColumn $sortOrder";
if ( $sortColumn != 'E.Id' ) {
# When sorting by starttime, if we have two events with the same starttime (diffreent monitors) then we should sort secondly by Id
$sql .= ', E.Id ASC';
}
$sql .= ' LIMIT 1';
$result = dbQuery( $sql );
while ( $id = dbFetchNext( $result, 'Id' ) ) {
if ( $id == $eventId ) {
$nextEvent = dbFetchNext( $result );
break;
}
}
$result = array( 'EventId'=>$eventId );
$result['PrevEventId'] = empty($prevEvent)?0:$prevEvent['Id'];
$result['NextEventId'] = empty($nextEvent)?0:$nextEvent['Id'];
$result['PrevEventStartTime'] = empty($prevEvent)?0:$prevEvent['StartTime'];
$result['NextEventStartTime'] = empty($nextEvent)?0:$nextEvent['StartTime'];
$result['PrevEventDefVideoPath'] = empty($prevEvent)?0:(getEventDefaultVideoPath($prevEvent['Id']));
$result['NextEventDefVideoPath'] = empty($nextEvent)?0:(getEventDefaultVideoPath($nextEvent['Id']));
return( $result );
if ( $prevEvent ) {
$result['PrevEventId'] = $prevEvent['Id'];
$result['PrevEventStartTime'] = $prevEvent['StartTime'];
$result['PrevEventDefVideoPath'] = getEventDefaultVideoPath($prevEvent['Id']);
} else {
$result['PrevEventId'] = $result['PrevEventStartTime'] = $result['PrevEventDefVideoPath'] = 0;
}
if ( $nextEvent ) {
$result['NextEventId'] = $nextEvent['Id'];
$result['NextEventStartTime'] = $nextEvent['StartTime'];
$result['NextEventDefVideoPath'] = getEventDefaultVideoPath($nextEvent['Id']);
} else {
$result['NextEventId'] = $result['NextEventStartTime'] = $result['NextEventDefVideoPath'] = 0;
}
return $result;
}
?>

View File

@ -69,7 +69,7 @@ class HostController extends AppController {
$this->Session->read('user'); # this is needed for command line/curl to recognize a session
$zmAuthRelay = $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_RELAY')))['Config']['Value'];
if ( $zmAuthRelay == 'hashed' ) {
$zmAuthHashIps= $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_HASH_IPS')))['Config']['Value'];
$zmAuthHashIps = $this->Config->find('first',array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_HASH_IPS')))['Config']['Value'];
$credentials = 'auth='.generateAuthHash($zmAuthHashIps);
} else if ( $zmAuthRelay == 'plain' ) {
// user will need to append the store password here
@ -101,27 +101,25 @@ class HostController extends AppController {
$this->loadModel('Monitor');
// If $mid is passed, see if it is valid
if ($mid) {
if (!$this->Monitor->exists($mid)) {
if ( $mid ) {
if ( !$this->Monitor->exists($mid) ) {
throw new NotFoundException(__('Invalid monitor'));
}
}
$zm_dir_events = $this->Config->find('list', array(
'conditions' => array('Name' => 'ZM_DIR_EVENTS'),
'fields' => array('Name', 'Value')
));
$zm_dir_events = $zm_dir_events['ZM_DIR_EVENTS' ];
$zm_dir_events = ZM_DIR_EVENTS;
// Test to see if $zm_dir_events is relative or absolute
if ('/' === "" || strrpos($zm_dir_events, '/', -strlen($zm_dir_events)) !== TRUE) {
#if ('/' === "" || strrpos($zm_dir_events, '/', -strlen($zm_dir_events)) !== TRUE) {
if ( substr($zm_dir_events, 0, 1) != '/' ) {
// relative - so add the full path
$zm_dir_events = Configure::read('ZM_PATH_WEB') . '/' . $zm_dir_events;
$zm_dir_events = ZM_PATH_WEB . '/' . $zm_dir_events;
}
if ($mid) {
if ( $mid ) {
// Get disk usage for $mid
$usage = shell_exec ("du -sh0 $zm_dir_events/$mid | awk '{print $1}'");
Logger::Debug("Executing du -s0 $zm_dir_events/$mid | awk '{print $1}'");
$usage = shell_exec("du -s0 $zm_dir_events/$mid | awk '{print $1}'");
} else {
$monitors = $this->Monitor->find('all', array(
'fields' => array('Id', 'Name', 'WebColour')

View File

@ -207,7 +207,9 @@ class MonitorsController extends AppController {
if ( !$this->Monitor->exists() ) {
throw new NotFoundException(__('Invalid monitor'));
}
if ( $this->Session->Read('systemPermission') != 'Edit' ) {
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}

View File

@ -59,8 +59,9 @@ public function add() {
if ($this->request->is('post')) {
if ($this->Session->Read('systemPermission') != 'Edit')
{
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}

View File

@ -0,0 +1,157 @@
<?php
App::uses('AppController', 'Controller');
/**
* Storage Controller
*
* @property Storage $Storage
* @property PaginatorComponent $Paginator
*/
class StorageController extends AppController {
/**
* Components
*
* @var array
*/
public $components = array('Paginator', 'RequestHandler');
public function beforeFilter() {
parent::beforeFilter();
global $user;
$canView = (!$user) || ($user['System'] != 'None');
if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
}
/**
* index method
*
* @return void
*/
public function index() {
$this->Storage->recursive = -1;
$options = '';
$storage_areas = $this->Storage->find('all',$options);
$this->set(array(
'storage' => $storage_areas,
'_serialize' => array('storage')
));
}
/**
* view method
*
* @throws NotFoundException
* @param string $id
* @return void
*/
public function view($id = null) {
$this->Storage->recursive = 0;
if (!$this->Storage->exists($id)) {
throw new NotFoundException(__('Invalid storage area'));
}
$restricted = '';
$options = array('conditions' => array(
array('Storage.' . $this->Storage->primaryKey => $id),
$restricted
)
);
$storage = $this->Storage->find('first', $options);
$this->set(array(
'storage' => $storage,
'_serialize' => array('storage')
));
}
/**
* add method
*
* @return void
*/
public function add() {
if ( $this->request->is('post') ) {
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
$this->Storage->create();
if ( $this->Storage->save($this->request->data) ) {
# Might be nice to send it a start request
#$this->daemonControl($this->Storage->id, 'start', $this->request->data);
return $this->flash(__('The storage area has been saved.'), array('action' => 'index'));
}
}
}
/**
* edit method
*
* @throws NotFoundException
* @param string $id
* @return void
*/
public function edit($id = null) {
$this->Storage->id = $id;
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
if ( !$this->Storage->exists($id) ) {
throw new NotFoundException(__('Invalid storage area'));
}
if ( $this->Storage->save($this->request->data) ) {
$message = 'Saved';
} else {
$message = 'Error';
}
$this->set(array(
'message' => $message,
'_serialize' => array('message')
));
// - restart this storage area after change
#$this->daemonControl($this->Storage->id, 'restart', $this->request->data);
}
/**
* delete method
*
* @throws NotFoundException
* @param string $id
* @return void
*/
public function delete($id = null) {
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
$this->Storage->id = $id;
if ( !$this->Storage->exists() ) {
throw new NotFoundException(__('Invalid storage area'));
}
$this->request->allowMethod('post', 'delete');
#$this->daemonControl($this->Storage->id, 'stop');
if ( $this->Storage->delete() ) {
return $this->flash(__('The storage area has been deleted.'), array('action' => 'index'));
} else {
return $this->flash(__('The storage area could not be deleted. Please, try again.'), array('action' => 'index'));
}
}
}

View File

@ -116,8 +116,15 @@ class Monitor extends AppModel {
'OutputCodec' => array('h264','mjpeg','mpeg1','mpeg2'),
'OutputContainer' => array('auto','mp4','mkv'),
'DefaultView' => array('Events','Control'),
'Status' => array('Unknown','NotRunning','Running','NoSignal','Signal'),
#'Status' => array('Unknown','NotRunning','Running','NoSignal','Signal'),
)
);
public $hasOne = array(
'Monitor_Status' => array(
'className' => 'Monitor_Status',
'foreignKey' => 'MonitorId',
'joinTable' => 'Monitor_Status',
)
);
}

View File

@ -0,0 +1,59 @@
<?php
App::uses('AppModel', 'Model');
/**
* Monitor_Status Model
*
* @property Event $Event
* @property Zone $Zone
*/
class Monitor_Status extends AppModel {
/**
* Use table
*
* @var mixed False or table name
*/
public $useTable = 'Monitor_Status';
/**
* Primary key field
*
* @var string
*/
public $primaryKey = 'MonitorId';
/**
* Display field
*
* @var string
*/
public $displayField = 'Status';
public $recursive = -1;
/**
* Validation rules
*
* @var array
*/
public $validate = array(
'MonitorId' => array(
'numeric' => array(
'rule' => array('numeric'),
//'message' => 'Your custom message here',
//'allowEmpty' => false,
//'required' => false,
//'last' => false, // Stop validation after this rule
//'on' => 'create', // Limit validation to 'create' or 'update' operations
),
),
);
public $actsAs = array(
'CakePHP-Enum-Behavior.Enum' => array(
'Status' => array('Unknown','NotRunning','Running','NoSignal','Signal'),
)
);
//The Associations below have been created with all possible keys, those that are not needed can be removed
}

View File

@ -1168,6 +1168,9 @@ class CakeResponse {
if ($modifiedSince) {
$timeMatches = strtotime($this->modified()) === strtotime($modifiedSince);
}
if (!isset($etagMatches, $timeMatches)) {
return false;
}
$checks = compact('etagMatches', 'timeMatches');
if (empty($checks)) {
return false;

View File

@ -19,7 +19,7 @@
.overlayHeader {
float: left;
background-color: #dddddd;
background-color: #853131;
width: 100%;
border-bottom: 1px solid #666666;
color: black;

31
web/fonts/license.md Normal file
View File

@ -0,0 +1,31 @@
ZoneMinder uses certain 3rd party media assets/libraries for UI display purposes. Their licenses are listed in this file
### Material Design icons
Origin: https://github.com/google/material-design-icons
License: Apache 2.0 (https://github.com/google/material-design-icons/blob/master/LICENSE)
### Glyphicon halflings font
Origin: http://www.glyphicons.com/
License: MIT, As clarified below (http://www.glyphicons.com/license/)
```
License for GLYPHICONS Halflings in Bootstrap
GLYPHICONS Halflings font is also released as an extension of a Bootstrap www.getbootstrap.com for free and
it is released under the same license as Bootstrap. While you are not required to include attribution on your
Bootstrap-based projects, I would certainly appreciate any form of support, even a nice Tweet is enough.
Of course if you want, you can say thank you and support me by buying more icons on GLYPHICONS.com.
Jan Kovařík
```
ZoneMinder uses Bootstrap for UI and qualifies as a Bootstrap-based project.
Bootstrap is MIT licensed (https://github.com/twbs/bootstrap/blob/v4.1.3/LICENSE)

View File

@ -546,10 +546,12 @@ class Event {
}
$filters = array();
$result = dbQuery($sql, $values);
if ( $result ) {
$results = $result->fetchALL();
foreach ( $results as $row ) {
$filters[] = new Event($row);
}
}
return $filters;
}

View File

@ -55,7 +55,7 @@ class Frame {
#return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'Id'}.'&show='.$show.'&filename='.$this->Event()->MonitorId().'_'.$this->{'EventId'}.'_'.$this->{'FrameId'}.'.jpg';
} // end function getImageSrc
public static function find( $parameters = array(), $limit = NULL ) {
public static function find( $parameters = array(), $options = NULL ) {
$sql = 'SELECT * FROM Frames';
$values = array();
if ( sizeof($parameters) ) {
@ -65,17 +65,23 @@ class Frame {
) );
$values = array_values( $parameters );
}
if ( $limit ) {
if ( is_integer( $limit ) or ctype_digit( $limit ) ) {
$sql .= ' LIMIT ' . $limit;
if ( $options ) {
if ( isset($options['order']) ) {
$sql .= ' ORDER BY ' . $options['order'];
}
if ( isset($options['limit']) ) {
if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) {
$sql .= ' LIMIT ' . $options['limit'];
} else {
$backTrace = debug_backtrace();
$file = $backTrace[1]['file'];
$line = $backTrace[1]['line'];
Error("Invalid value for limit($limit) passed to Frame::find from $file:$line");
Error("Invalid value for limit(".$options['limit'].") passed to Frame::find from $file:$line");
return array();
}
}
}
$results = dbFetchAll($sql, NULL, $values);
if ( $results ) {
return array_map( function($id){ return new Frame($id); }, $results );
@ -83,8 +89,9 @@ class Frame {
return array();
}
public static function find_one( $parameters = array() ) {
$results = Frame::find( $parameters, 1 );
public static function find_one( $parameters = array(), $options = null ) {
$options['limit'] = 1;
$results = Frame::find($parameters, $options);
if ( ! sizeof($results) ) {
return;
}

View File

@ -95,13 +95,12 @@ class Group {
}
}
} # end if options
$groups = array();
$result = dbQuery($sql, $values);
$results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Group');
foreach ( $results as $row => $obj ) {
$groups[] = $obj;
$results = dbFetchAll($sql, NULL, $values);
if ( $results ) {
return array_map( function($row){ return new Group($row); }, $results );
}
return $groups;
return array();
} # end find()
public static function find_one($parameters = null, $options = null) {

View File

@ -152,10 +152,11 @@ private $control_fields = array(
}
if ( $this->{'Controllable'} ) {
$s = dbFetchOne('SELECT * FROM Controls WHERE Id=?', NULL, array($this->{'ControlId'}) );
if ( $s ) {
foreach ($s as $k => $v) {
if ( $k == 'Id' ) {
continue;
# The reason for these is that the name overlaps Monitor fields.
# The reason for these is that the name overlaps Monitor fields.
} else if ( $k == 'Protocol' ) {
$this->{'ControlProtocol'} = $v;
} else if ( $k == 'Name' ) {
@ -166,6 +167,9 @@ private $control_fields = array(
$this->{$k} = $v;
}
}
} else {
Warning('No Controls found for monitor '.$this->{'Id'} . ' ' . $this->{'Name'}.' althrough it is marked as controllable');
}
}
global $monitor_cache;
$monitor_cache[$row['Id']] = $this;
@ -381,7 +385,7 @@ private $control_fields = array(
} else if ( $this->ServerId() ) {
$Server = $this->Server();
$url = $Server->Url() . '/zm/api/monitors/'.$this->{'Id'}.'.json';
$url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/'.$this->{'Id'}.'.json';
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS );
@ -547,7 +551,7 @@ private $control_fields = array(
} // end function Source
public function Url() {
return $this->Server()->Url() .':'. ( ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : $_SERVER['SERVER_PORT'] );
return $this->Server()->Url( ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : null );
}
} // end class Monitor

View File

@ -38,13 +38,20 @@ class Server {
}
}
public function Url() {
public function Url( $port = null ) {
$url = ZM_BASE_PROTOCOL . '://';
if ( $this->Id() ) {
return ZM_BASE_PROTOCOL . '://'. $this->Hostname();
$url .= $this->Hostname();
} else {
return ZM_BASE_PROTOCOL . '://'. $_SERVER['SERVER_NAME'];
return '';
$url .= $_SERVER['SERVER_NAME'];
}
if ( $port ) {
$url .= ':'.$port;
} else {
$url .= ':'.$_SERVER['SERVER_PORT'];
}
$url .= $_SERVER['PHP_SELF'];
return $url;
}
public function Hostname() {
if ( isset( $this->{'Hostname'} ) and ( $this->{'Hostname'} != '' ) ) {

View File

@ -189,7 +189,7 @@ class Storage {
# This isn't a function like this in php, so we have to add up the space used in each event.
if ( ( !array_key_exists('disk_used_space', $this)) or !$this->{'disk_used_space'} ) {
if ( $this->{'Type'} == 's3fs' ) {
$this->{'disk_used_space'} = $this->disk_event_space();
$this->{'disk_used_space'} = $this->event_disk_space();
} else {
$path = $this->Path();
if ( file_exists($path) ) {

View File

@ -18,51 +18,6 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// PP - POST request handler for PHP which does not need extensions
// credit: http://wezfurlong.org/blog/2006/nov/http-post-from-php-without-curl/
function do_request($method, $url, $data=array(), $optional_headers = null) {
global $php_errormsg;
$params = array('http' => array(
'method' => $method,
'content' => $data
));
if ( $optional_headers !== null ) {
$params['http']['header'] = $optional_headers;
}
$ctx = stream_context_create($params);
$fp = @fopen($url, 'rb', false, $ctx);
if ( !$fp ) {
throw new Exception("Problem with $url, $php_errormsg");
}
$response = @stream_get_contents($fp);
if ( $response === false ) {
throw new Exception("Problem reading data from $url, $php_errormsg");
}
return $response;
}
function do_post_request($url, $data, $optional_headers = null) {
$params = array('http' => array(
'method' => 'POST',
'content' => $data
));
if ( $optional_headers !== null ) {
$params['http']['header'] = $optional_headers;
}
$ctx = stream_context_create($params);
$fp = @fopen($url, 'rb', false, $ctx);
if ( !$fp ) {
throw new Exception("Problem with $url, $php_errormsg");
}
$response = @stream_get_contents($fp);
if ( $response === false ) {
throw new Exception("Problem reading data from $url, $php_errormsg");
}
return $response;
}
function getAffectedIds( $name ) {
$names = $name.'s';
@ -88,52 +43,17 @@ if ( empty($action) ) {
return;
}
if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 'remote' || isset($_REQUEST['password']) ) ) {
// if true, a popup will display after login
// PP - lets validate reCaptcha if it exists
if ( defined('ZM_OPT_USE_GOOG_RECAPTCHA')
&& defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY')
&& defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY')
&& ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_GOOG_RECAPTCHA_SECRETKEY
&& ZM_OPT_GOOG_RECAPTCHA_SITEKEY )
{
$url = 'https://www.google.com/recaptcha/api/siteverify';
$fields = array (
'secret' => ZM_OPT_GOOG_RECAPTCHA_SECRETKEY,
'response' => $_REQUEST['g-recaptcha-response'],
'remoteip' => $_SERVER['REMOTE_ADDR']
);
$res = do_post_request($url, http_build_query($fields));
$responseData = json_decode($res,true);
// PP - credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php
// if recaptcha resulted in error, we might have to deny login
if ( isset($responseData['success']) && $responseData['success'] == false ) {
// PP - before we deny auth, let's make sure the error was not 'invalid secret'
// because that means the user did not configure the secret key correctly
// in this case, we prefer to let him login in and display a message to correct
// the key. Unfortunately, there is no way to check for invalid site key in code
// as it produces the same error as when you don't answer a recaptcha
if ( isset($responseData['error-codes']) && is_array($responseData['error-codes']) ) {
if ( !in_array('invalid-input-secret',$responseData['error-codes']) ) {
Error('reCaptcha authentication failed');
userLogout();
$view = 'login';
$refreshParent = true;
return;
} else {
//Let them login but show an error
echo '<script type="text/javascript">alert("'.translate('RecaptchaWarning').'"); </script>';
Error('Invalid recaptcha secret detected');
}
}
} // end if success==false
} // end if using reCaptcha
$username = validStr($_REQUEST['username']);
$password = isset($_REQUEST['password'])?validStr($_REQUEST['password']):'';
userLogin($username, $password);
$refreshParent = true;
// User login is automatically performed in includes/auth.php So we don't need to perform a login here,
// just handle redirects. This is the action that comes from the login view, so the logical thing to
// do on successful auth is redirect to console, otherwise loop back to login.
if ( !$user ) {
$view = 'login';
} else {
$view = 'console';
$redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console';
}
} else if ( $action == 'logout' ) {
userLogout();
$refreshParent = true;
@ -911,6 +831,22 @@ if ( canEdit('System') ) {
return;
}
if ( $action == 'options' && isset($_REQUEST['tab']) ) {
$config = array();
$configCat = array();
$configCats = array();
$result = $dbConn->query('SELECT * FROM Config ORDER BY Id ASC');
if ( !$result )
echo mysql_error();
while( $row = dbFetchNext($result) ) {
$config[$row['Name']] = $row;
if ( !($configCat = &$configCats[$row['Category']]) ) {
$configCats[$row['Category']] = array();
$configCat = &$configCats[$row['Category']];
}
$configCat[$row['Name']] = $row;
}
$configCat = $configCats[$_REQUEST['tab']];
$changed = false;
foreach ( $configCat as $name=>$value ) {

View File

@ -18,9 +18,50 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
function userLogin($username, $password='', $passwordHashed=false) {
function userLogin($username='', $password='', $passwordHashed=false) {
global $user;
if ( !$username and isset($_REQUEST['username']) )
$username = $_REQUEST['username'];
if ( !$password and isset($_REQUEST['password']) )
$password = $_REQUEST['password'];
// if true, a popup will display after login
// PP - lets validate reCaptcha if it exists
if ( defined('ZM_OPT_USE_GOOG_RECAPTCHA')
&& defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY')
&& defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY')
&& ZM_OPT_USE_GOOG_RECAPTCHA
&& ZM_OPT_GOOG_RECAPTCHA_SECRETKEY
&& ZM_OPT_GOOG_RECAPTCHA_SITEKEY )
{
$url = 'https://www.google.com/recaptcha/api/siteverify';
$fields = array (
'secret' => ZM_OPT_GOOG_RECAPTCHA_SECRETKEY,
'response' => $_REQUEST['g-recaptcha-response'],
'remoteip' => $_SERVER['REMOTE_ADDR']
);
$res = do_post_request($url, http_build_query($fields));
$responseData = json_decode($res,true);
// PP - credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php
// if recaptcha resulted in error, we might have to deny login
if ( isset($responseData['success']) && $responseData['success'] == false ) {
// PP - before we deny auth, let's make sure the error was not 'invalid secret'
// because that means the user did not configure the secret key correctly
// in this case, we prefer to let him login in and display a message to correct
// the key. Unfortunately, there is no way to check for invalid site key in code
// as it produces the same error as when you don't answer a recaptcha
if ( isset($responseData['error-codes']) && is_array($responseData['error-codes']) ) {
if ( !in_array('invalid-input-secret',$responseData['error-codes']) ) {
Error('reCaptcha authentication failed');
return null;
} else {
Error('Invalid recaptcha secret detected');
}
}
} // end if success==false
} // end if using reCaptcha
$sql = 'SELECT * FROM Users WHERE Enabled=1';
$sql_values = NULL;
if ( ZM_AUTH_TYPE == 'builtin' ) {
@ -36,7 +77,6 @@ function userLogin($username, $password='', $passwordHashed=false) {
}
$close_session = 0;
if ( !is_session_started() ) {
Logger::Debug("Starting session in userLogin");
session_start();
$close_session = 1;
}
@ -70,7 +110,6 @@ function userLogout() {
session_start();
unset($_SESSION['user']);
unset($user);
session_destroy();
}
@ -116,7 +155,7 @@ function generateAuthHash($useRemoteAddr, $force=false) {
$time = time();
$mintime = $time - ( ZM_AUTH_HASH_TTL * 1800 );
if ( $force or ( !isset($_SESSION['AuthHash']) ) or ( $_SESSION['AuthHashGeneratedAt'] < $mintime ) ) {
if ( $force or ( !isset($_SESSION['AuthHash'.$_SESSION['remoteAddr']]) ) or ( $_SESSION['AuthHashGeneratedAt'] < $mintime ) ) {
# Don't both regenerating Auth Hash if an hour hasn't gone by yet
$local_time = localtime();
$authKey = '';
@ -133,7 +172,7 @@ function generateAuthHash($useRemoteAddr, $force=false) {
session_start();
$close_session = 1;
}
$_SESSION['AuthHash'] = $auth;
$_SESSION['AuthHash'.$_SESSION['remoteAddr']] = $auth;
$_SESSION['AuthHashGeneratedAt'] = $time;
session_write_close();
} else {
@ -143,7 +182,7 @@ function generateAuthHash($useRemoteAddr, $force=false) {
#} else {
#Logger::Debug("Using cached auth " . $_SESSION['AuthHash'] ." beacuse generatedat:" . $_SESSION['AuthHashGeneratedAt'] . ' < now:'. $time . ' - ' . ZM_AUTH_HASH_TTL . ' * 1800 = '. $mintime);
} # end if AuthHash is not cached
return $_SESSION['AuthHash'];
return $_SESSION['AuthHash'.$_SESSION['remoteAddr']];
} # end if using AUTH and AUTH_RELAY
return '';
}
@ -179,4 +218,18 @@ function is_session_started() {
return FALSE;
}
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_HASH_LOGINS && empty($user) && ! empty($_REQUEST['auth']) ) {
if ( $authUser = getAuthUser($_REQUEST['auth']) ) {
userLogin($authUser['Username'], $authUser['Password'], true);
}
}
else if ( isset($_REQUEST['username']) and isset($_REQUEST['password']) ) {
userLogin($_REQUEST['username'], $_REQUEST['password'], false);
}
if ( !empty($user) ) {
// generate it once here, while session is open. Value will be cached in session and return when called later on
generateAuthHash(ZM_AUTH_HASH_IPS);
}
}
?>

View File

@ -158,29 +158,19 @@ $GLOBALS['defaultUser'] = array(
function loadConfig( $defineConsts=true ) {
global $config;
global $configCats;
global $dbConn;
$config = array();
$configCat = array();
$result = $dbConn->query( 'select * from Config order by Id asc' );
$result = $dbConn->query('SELECT Name,Value FROM Config');
if ( !$result )
echo mysql_error();
$monitors = array();
while( $row = dbFetchNext( $result ) ) {
if ( $defineConsts )
define( $row['Name'], $row['Value'] );
$config[$row['Name']] = $row;
if ( !($configCat = &$configCats[$row['Category']]) ) {
$configCats[$row['Category']] = array();
$configCat = &$configCats[$row['Category']];
}
$configCat[$row['Name']] = $row;
}
//print_r( $config );
//print_r( $configCats );
}
} # end function loadConfig
// For Human-readability, use ZM_SERVER_HOST or ZM_SERVER_NAME in zm.conf, and convert it here to a ZM_SERVER_ID
if ( ! defined('ZM_SERVER_ID') ) {

View File

@ -40,15 +40,16 @@ function CORSHeaders() {
# The following is left for future reference/use.
$valid = false;
$servers = dbFetchAll('SELECT * FROM Servers');
if ( sizeof($servers) <= 1 ) {
$Servers = Server::find();
if ( sizeof($Servers) < 1 ) {
# Only need CORSHeaders in the event that there are multiple servers in use.
# ICON: Might not be true. multi-port?
return;
}
foreach( $servers as $row ) {
$Server = new Server($row);
if ( preg_match('/^'.preg_quote($Server->Url(),'/').'/', $_SERVER['HTTP_ORIGIN']) ) {
foreach( $Servers as $Server ) {
if ( preg_match('/^(https?:\/\/)?'.preg_quote($Server->Hostname(),'/').'/', $_SERVER['HTTP_ORIGIN']) ) {
$valid = true;
Logger::Debug("Setting Access-Controll-Allow-Origin from " . $_SERVER['HTTP_ORIGIN']);
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
header('Access-Control-Allow-Headers: x-requested-with,x-request');
break;
@ -1908,13 +1909,13 @@ function logState() {
# This is an expensive request, as it has to hit every row of the Logs Table
$sql = 'SELECT Level, COUNT(Level) AS LevelCount FROM Logs WHERE Level < '.Logger::INFO.' AND TimeKey > unix_timestamp(now() - interval '.ZM_LOG_CHECK_PERIOD.' second) GROUP BY Level ORDER BY Level ASC';
$counts = dbFetchAll( $sql );
$counts = dbFetchAll($sql);
if ( $counts ) {
foreach ( $counts as $count ) {
if ( $count['Level'] <= Logger::PANIC )
$count['Level'] = Logger::FATAL;
if ( !($levelCount = $levelCounts[$count['Level']]) ) {
Error( "Unexpected Log level ".$count['Level'] );
Error('Unexpected Log level '.$count['Level']);
next;
}
if ( $levelCount[1] && $count['LevelCount'] >= $levelCount[1] ) {
@ -1924,7 +1925,8 @@ function logState() {
$state = 'alert';
}
}
return( $state );
}
return $state;
}
function isVector ( &$array ) {
@ -2272,4 +2274,75 @@ function unparse_url($parsed_url, $substitutions = array() ) {
$fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
return "$scheme$user$pass$host$port$path$query$fragment";
}
// PP - POST request handler for PHP which does not need extensions
// credit: http://wezfurlong.org/blog/2006/nov/http-post-from-php-without-curl/
function do_request($method, $url, $data=array(), $optional_headers = null) {
global $php_errormsg;
$params = array('http' => array(
'method' => $method,
'content' => $data
));
if ( $optional_headers !== null ) {
$params['http']['header'] = $optional_headers;
}
$ctx = stream_context_create($params);
$fp = @fopen($url, 'rb', false, $ctx);
if ( !$fp ) {
throw new Exception("Problem with $url, $php_errormsg");
}
$response = @stream_get_contents($fp);
if ( $response === false ) {
throw new Exception("Problem reading data from $url, $php_errormsg");
}
return $response;
}
function do_post_request($url, $data, $optional_headers = null) {
$params = array('http' => array(
'method' => 'POST',
'content' => $data
));
if ( $optional_headers !== null ) {
$params['http']['header'] = $optional_headers;
}
$ctx = stream_context_create($params);
$fp = @fopen($url, 'rb', false, $ctx);
if ( !$fp ) {
throw new Exception("Problem with $url, $php_errormsg");
}
$response = @stream_get_contents($fp);
if ( $response === false ) {
throw new Exception("Problem reading data from $url, $php_errormsg");
}
return $response;
}
// The following works around php not being built with semaphore functions.
if ( !function_exists('sem_get') ) {
function sem_get($key) {
return fopen(__FILE__ . '.sem.' . $key, 'w+');
}
function sem_acquire($sem_id) {
return flock($sem_id, LOCK_EX);
}
function sem_release($sem_id) {
return flock($sem_id, LOCK_UN);
}
}
if ( !function_exists('ftok') ) {
function ftok($filename = "", $proj = "") {
if ( empty($filename) || !file_exists($filename) ) {
return -1;
} else {
$filename = $filename . (string) $proj;
for($key = array(); sizeof($key) < strlen($filename); $key[] = ord(substr($filename, sizeof($key), 1)));
return dechex(array_sum($key));
}
}
}
?>

View File

@ -156,7 +156,6 @@ session_write_close();
require_once('includes/lang.php');
require_once('includes/functions.php');
require_once('includes/auth.php');
# Running is global but only do the daemonCheck if it is actually needed
$running = null;
@ -182,19 +181,8 @@ if ( isset($_REQUEST['request']) )
foreach ( getSkinIncludes('skin.php') as $includeFile )
require_once $includeFile;
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_HASH_LOGINS ) {
if ( empty($user) && ! empty($_REQUEST['auth']) ) {
if ( $authUser = getAuthUser($_REQUEST['auth']) ) {
userLogin($authUser['Username'], $authUser['Password'], true);
}
}
}
if ( !empty($user) ) {
// generate it once here, while session is open. Value will be cached in session and return when called later on
generateAuthHash(ZM_AUTH_HASH_IPS);
}
}
# User Login will be performed in auth.php
require_once('includes/auth.php');
if ( isset($_REQUEST['action']) ) {
$action = detaintPath($_REQUEST['action']);
@ -228,7 +216,7 @@ if ( ZM_OPT_USE_AUTH and !isset($user) ) {
Logger::Debug('Redirecting to login');
$view = 'login';
$request = null;
} else if ( ZM_SHOW_PRIVACY && ($action != 'privacy') && ($view !='options') && (!$request) && canEdit('System') ) {
} else if ( ZM_SHOW_PRIVACY && ($action != 'privacy') && ($view != 'options') && (!$request) && canEdit('System') ) {
Logger::Debug('Redirecting to privacy');
$view = 'privacy';
$request = null;

995
web/lang/ba_ba.php Normal file
View File

@ -0,0 +1,995 @@
<?php
//
// ZoneMinder web UK English language file, $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.
//
// ZoneMinder Bosnian Translation by Damir Merdan (merdan.damir@gmail.com)
// Notes for Translators
// 0. Get some credit, put your name in the line above (optional)
// 1. When composing the language tokens in your language you should try and keep to roughly the
// same length text if possible. Abbreviate where necessary as spacing is quite close in a number of places.
// 2. There are four types of string replacement
// a) Simple replacements are words or short phrases that are static and used directly. This type of
// replacement can be used 'as is'.
// b) Complex replacements involve some dynamic element being included and so may require substitution
// or changing into a different order. The token listed in this file will be passed through sprintf as
// a formatting string. If the dynamic element is a number you will usually need to use a variable
// replacement also as described below.
// c) Variable replacements are used in conjunction with complex replacements and involve the generation
// of a singular or plural noun depending on the number passed into the zmVlang function. See the
// the zmVlang section below for a further description of this.
// d) Optional strings which can be used to replace the prompts and/or help text for the Options section
// of the web interface. These are not listed below as they are quite large and held in the database
// so that they can also be used by the zmconfig.pl script. However you can build up your own list
// quite easily from the Config table in the database if necessary.
// 3. The tokens listed below are not used to build up phrases or sentences from single words. Therefore
// you can safely assume that a single word token will only be used in that context.
// 4. In new language files, or if you are changing only a few words or phrases it makes sense from a
// maintenance point of view to include the original language file and override the old definitions rather
// than copy all the language tokens across. To do this change the line below to whatever your base language
// is and uncomment it.
// require_once( 'zm_lang_en_gb.php' );
// You may need to change the character set here, if your web server does not already
// do this by default, uncomment this if required.
//
// Example
// header( "Content-Type: text/html; charset=iso-8859-1" );
// You may need to change your locale here if your default one is incorrect for the
// language described in this file, or if you have multiple languages supported.
// If you do need to change your locale, be aware that the format of this function
// is subtlely different in versions of PHP before and after 4.3.0, see
// http://uk2.php.net/manual/en/function.setlocale.php for details.
// Also be aware that changing the whole locale may affect some floating point or decimal
// arithmetic in the database, if this is the case change only the individual locale areas
// that don't affect this rather than all at once. See the examples below.
// Finally, depending on your setup, PHP may not enjoy have multiple locales in a shared
// threaded environment, if you get funny errors it may be this.
//
// Examples
// setlocale( 'LC_ALL', 'en_GB' ); All locale settings pre-4.3.0
// setlocale( LC_ALL, 'en_GB' ); All locale settings 4.3.0 and after
// setlocale( LC_CTYPE, 'en_GB' ); Character class settings 4.3.0 and after
// setlocale( LC_TIME, 'en_GB' ); Date and time formatting 4.3.0 and after
// Simple String Replacements
$SLANG = array(
'SystemLog' => 'Dnevnik',
'DateTime' => 'Datum/Vrijeme',
'Component' => 'Komponenta',
'Pid' => 'PID',
'Level' => 'Nivo',
'Message' => 'Poruka',
'Line' => 'Linija',
'More' => 'Više',
'Clear' => 'Očisti',
'24BitColour' => '24 bitne boje',
'32BitColour' => '32 bitne boje',
'8BitGrey' => '8 bit siva nijansa',
'Action' => 'Action',
'Actual' => 'Stvarno',
'AddNewControl' => 'Dodaj kontrolu',
'AddNewMonitor' => 'Dodaj monitor',
'AddNewServer' => 'Dodaj novi server',
'AddNewStorage' => 'Dodaj novi disk',
'AddNewUser' => 'Dodaj novog korisnika',
'AddNewZone' => 'Dodaj novu zonu',
'Alarm' => 'Alarm',
'AlarmBrFrames' => 'Alarm<br/>Sličice',
'AlarmFrame' => 'Alarm sličica',
'AlarmFrameCount' => 'Brzina snimanja alarma (u frejmovima)',
'AlarmLimits' => 'Alarm limiti',
'AlarmMaximumFPS' => 'Alarm Max SPS',
'AlarmPx' => 'Alarm Px',
'AlarmRefImageBlendPct' => 'Alarm Reference Image Blend %ge',
'AlarmRGBUnset' => 'Morate postaviti RGB boju za alarm',
'Alert' => 'Uzbuna',
'All' => 'Sve',
'AnalysisFPS' => 'Analiza frejmova',
'AnalysisUpdateDelay' => 'Analysis Update Delay',
'Apply' => 'Primjeni',
'ApplyingStateChange' => 'Primjenjujem promjenu stanja',
'ArchArchived' => 'Samo arhivirano',
'Archive' => 'Arhiva',
'Archived' => 'Ahivirano',
'ArchUnarchived' => 'Samo nearhivirano',
'Area' => 'Oblast',
'AreaUnits' => 'Oblast (px/%)',
'AttrAlarmFrames' => 'Alarm frejmovi',
'AttrArchiveStatus' => 'Status arhive',
'AttrAvgScore' => 'Prosj. score',
'AttrCause' => 'Uzrok',
'AttrStartDate' => 'Pocetni datum',
'AttrEndDate' => 'Krajnji datum',
'AttrStartDateTime' => 'Pocetni Datum/Vrijeme',
'AttrEndDateTime' => 'Krajnji Datum/Vrijeme',
'AttrDiskSpace' => 'Disk prostor',
'AttrDiskBlocks' => 'Disk blokovi',
'AttrDiskPercent' => 'Disk procentualno',
'AttrDuration' => 'Trajanje',
'AttrFrames' => 'Frejmovi',
'AttrId' => 'Id',
'AttrMaxScore' => 'Max. Score',
'AttrMonitorId' => 'ID Kamere',
'AttrMonitorName' => 'Naziv Kamere',
'AttrStorageArea' => 'Storage Area',
'AttrFilterServer' => 'Server Filter je pokrenut na',
'AttrMonitorServer' => 'Server Monitor je pokrenut na',
'AttrStorageServer' => 'Server Hosting Storage',
'AttrStateId' => 'Status',
'AttrName' => 'Naziv',
'AttrNotes' => 'Bilješke',
'AttrSystemLoad' => 'Opterećenje sistema',
'AttrStartTime' => 'Vrijeme početka',
'AttrEndTime' => 'Vrijeme završetka',
'AttrTotalScore' => 'Ukupan score',
'AttrStartWeekday' => 'Početni dan',
'AttrEndWeekday' => 'Krajnji dan',
'Auto' => 'Automatski',
'AutoStopTimeout' => 'Auto Stop Timeout',
'Available' => 'Dostupno',
'AvgBrScore' => 'Avg.<br/>Score',
'Available' => 'Dostupno',
'Background' => 'Pozadina',
'BackgroundFilter' => 'Pokreni filter u pozadini',
'BadAlarmFrameCount' => 'Brojač alarm frejmova mora biti tipa integer počevši od jedan ili više',
'BadAlarmMaxFPS' => 'Max FPS za alarm mora biti pozitivan cjeli broj ili broj sa pomičnim zarezom',
'BadAnalysisFPS' => 'Broj frejmova za analitiku mora pozitivan cjeli broj ili broj sa pomičnim zarezom',
'BadAnalysisUpdateDelay'=> 'Vrijeme zadrške analitike mora biti broj od nula ili više',
'BadChannel' => 'Kanal mora biti postavljen na cjeli broj nula ili više',
'BadDevice' => 'Uredaj mora biti postavljen na validnu vrijednost',
'BadFormat' => 'Format mora biti postavljen na validnu vrijenost',
'BadFPSReportInterval' => 'FPS report interval buffer count must be an integer of 0 or more',
'BadFrameSkip' => 'Frame skip count must be an integer of zero or more',
'BadMotionFrameSkip' => 'Motion Frame skip count must be an integer of zero or more',
'BadHeight' => 'Height must be set to a valid value',
'BadHost' => 'Host must be set to a valid ip address or hostname, do not include http://',
'BadImageBufferCount' => 'Image buffer size must be an integer of 10 or more',
'BadLabelX' => 'Label X co-ordinate must be set to an integer of zero or more',
'BadLabelY' => 'Label Y co-ordinate must be set to an integer of zero or more',
'BadMaxFPS' => 'Maximum FPS must be a positive integer or floating point value',
'BadNameChars' => 'Names may only contain alphanumeric characters plus spaces, hyphen and underscore',
'BadPalette' => 'Palette must be set to a valid value',
'BadColours' => 'Target colour must be set to a valid value',
'BadPath' => 'Path must be set to a valid value',
'BadPort' => 'Port must be set to a valid number',
'BadPostEventCount' => 'Post event image count must be an integer of zero or more',
'BadPreEventCount' => 'Pre event image count must be at least zero, and less than image buffer size',
'BadRefBlendPerc' => 'Reference blend percentage must be a positive integer',
'BadSectionLength' => 'Section length must be an integer of 30 or more',
'BadSignalCheckColour' => 'Signal check colour must be a valid RGB colour string',
'BadStreamReplayBuffer' => 'Stream replay buffer must be an integer of zero or more',
'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"',
'BadWarmupCount' => 'Warmup frames must be an integer of zero or more',
'BadWebColour' => 'Web colour must be a valid web colour string',
'BadWebSitePath' => 'Please enter a complete website url, including the http:// or https:// prefix.',
'BadWidth' => 'Width must be set to a valid value',
'Bandwidth' => 'Propusnost',
'BandwidthHead' => 'propusnost', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing
'BlobPx' => 'Blob Px',
'Blobs' => 'Blobs',
'BlobSizes' => 'Blob velicine',
'Brightness' => 'Svjetloća',
'Buffer' => 'Bufer',
'Buffers' => 'Buferi',
'CanAutoFocus' => 'Podržava Auto fokusiranje',
'CanAutoGain' => 'Podržava Auto pojačanje',
'CanAutoIris' => 'Podržava Auto blenda',
'CanAutoWhite' => 'Podržava Auto balans bijel.',
'CanAutoZoom' => 'Podržava Auto zum',
'Cancel' => 'otkaži',
'CancelForcedAlarm' => 'Otkaži prisilni alarm',
'CanFocusAbs' => 'Podržava Abs fokus',
'CanFocus' => 'Podržava Fokus',
'CanFocusCon' => 'Podržava Kontinuirani fokus',
'CanFocusRel' => 'Podržava Relativni fokus',
'CanGainAbs' => 'Podržava Aps. pojačanje',
'CanGain' => 'Podržava Pojačanje ',
'CanGainCon' => 'Podržava Kontinuirano pojačanje',
'CanGainRel' => 'Podržava Relativno pojačanje',
'CanIrisAbs' => 'Podržava Aps. blenda',
'CanIris' => 'Podržava Blenda',
'CanIrisCon' => 'Podržava kontinuirana blenda',
'CanIrisRel' => 'Podržava Relativna blenda',
'CanMoveAbs' => 'Podržava Aps. kretanje',
'CanMove' => 'Podržava Kretanje',
'CanMoveCon' => 'Podržava Kontinuirano kretanje',
'CanMoveDiag' => 'Podržava Dijagonalno kretanje',
'CanMoveMap' => 'Podržava Mapirano kretanje',
'CanMoveRel' => 'Podržava Relativno kretanje',
'CanPan' => 'Podržava Pomak' ,
'CanReset' => 'PodržavaReset',
'CanSetPresets' => 'Podržava presetove',
'CanSleep' => 'Podržava Sleep',
'CanTilt' => 'Podržava nagib',
'CanWake' => 'Podržava Wake',
'CanWhiteAbs' => 'Podržava Aps. balans bijele boje',
'CanWhiteBal' => 'Podržava balans bijel.',
'CanWhite' => 'Podržava bijelu',
'CanWhiteCon' => 'Podržava kont. balans bijele boje',
'CanWhiteRel' => 'Podržava relativ. balans bijele boje',
'CanZoomAbs' => 'Podržava Aps. zoom',
'CanZoom' => 'Podržava Zoom',
'CanZoomCon' => 'Podržava kontinuirani Zoom',
'CanZoomRel' => 'Podržava Relativni zoom',
'CaptureHeight' => 'Visina slike',
'CaptureMethod' => 'Metoda snimanja',
'CaptureResolution' => 'Snimi rezoluciju',
'CapturePalette' => 'Paleta boja',
'CaptureWidth' => 'Širina slike',
'Cause' => 'Uzrok',
'CheckMethod' => 'Metoda provjere alarma',
'ChooseDetectedCamera' => 'Odaberi otkrivenu kameru',
'ChooseFilter' => 'Odaberi filter',
'ChooseLogFormat' => 'Odaberi dugi format',
'ChooseLogSelection' => 'Odaberi dugu selekciju',
'ChoosePreset' => 'Odaberi preset',
'CloneMonitor' => 'Kloniraj',
'Close' => 'Zatvori',
'Colour' => 'Bojs',
'Command' => 'Komanda',
'ConcurrentFilter' => 'Istovremeno pokreni filter',
'Config' => 'Postavke',
'ConfiguredFor' => 'Podešeno za',
'ConfirmDeleteEvents' => 'Sigurni ste da želite izbrisati odabrane događaje?',
'ConfirmPassword' => 'Potvrdi lozinku',
'ConjAnd' => 'i',
'ConjOr' => 'ili',
'Console' => 'Konzola',
'ContactAdmin' => 'Molimo konkatirajte svog administratora za detalje.',
'Continue' => 'Nastavi',
'Contrast' => 'Kontrast',
'ControlAddress' => 'Kontrolna adresa',
'ControlCap' => 'Control Capability',
'ControlCaps' => 'Control Capabilities',
'Control' => 'PTZ kontole',
'ControlDevice' => 'Kontroliši uređaj',
'Controllable' => 'Moguće kontrolisati',
'ControlType' => 'Tipa kontrole',
'Current' => 'Tekuće',
'Cycle' => 'Kruži',
'CycleWatch' => 'Kružni prikaz',
'Day' => 'Dan',
'Debug' => 'Debug',
'DefaultRate' => 'Podrazumjevana stopa',
'DefaultScale' => 'Podrazumjevani razmjer',
'DefaultView' => 'Podrazumjevani prikaz',
'Deinterlacing' => 'Deinterlacing',
'RTSPDescribe' => 'Use RTSP Response Media URL',
'Delay' => 'Zadrška',
'DeleteAndNext' => 'Izbriši &amp; Sljedeće',
'DeleteAndPrev' => 'Izbriši &amp; Preth',
'Delete' => 'Izbriši',
'DeleteSavedFilter' => 'Izbriši spremljeni filter',
'Description' => 'Opis',
'DetectedCameras' => 'Detektovane kamere:',
'DetectedProfiles' => 'Otkriveni profili',
'DeviceChannel' => 'Kanal',
'DeviceFormat' => 'Sistem boja',
'DeviceNumber' => 'Broj uređaja',
'DevicePath' => 'Putanja uređaja',
'Device' => 'Uređaj',
'Devices' => 'Uređaji',
'Dimensions' => 'Dimenzije',
'DisableAlarms' => 'Onemogući alarme',
'Disk' => 'Disk',
'Display' => 'Prikaz',
'Displaying' => 'Prikazujem',
'DonateAlready' => 'Ne, već sam napravio donaciju.',
'DonateEnticement' => 'You\'ve been running ZoneMinder for a while now and hopefully are finding it a useful addition to your home or workplace security. Although ZoneMinder is, and will remain, free and open source, it costs money to develop and support. If you would like to help support future development and new features then please consider donating. Donating is, of course, optional but very much appreciated and you can donate as much or as little as you like.<br/><br/>If you would like to donate please select the option below or go to http://www.zoneminder.com/donate.html in your browser.<br/><br/>Thank you for using ZoneMinder and don\'t forget to visit the forums on ZoneMinder.com for support or suggestions about how to make your ZoneMinder experience even better.',
'Donate' => 'Molimo donirajte',
'DonateRemindDay' => 'Ne još, podsjetime za 1 dan',
'DonateRemindHour' => 'Ne još, podsjetime za 1 sat',
'DonateRemindMonth' => 'Ne još, podsjeti me za jedan mjesec',
'DonateRemindNever' => 'Ne, ne želim donirati, nemoj me više podsjećati.',
'DonateRemindWeek' => 'Ne još, podsjeti me za sedam dana.',
'DonateYes' => 'Da, želim da doniram sada.',
'DoNativeMotionDetection'=> 'Nativna detekcija pokreta',
'Download' => 'Preuzmi',
'DuplicateMonitorName' => 'Dupliciraj ime monitora',
'Duration' => 'Trajanje',
'Edit' => 'Uredi',
'EditLayout' => 'Uredi raspored',
'Email' => 'Email',
'EnableAlarms' => 'Omogući alarme',
'Enabled' => 'Omogućeno',
'EnterNewFilterName' => 'Unesi novo ime za filter',
'ErrorBrackets' => 'Greška, provjerite da li imate jednak broj otvorenih i zatvorenih zagrada.',
'Error' => 'Greška',
'ErrorValidValue' => 'Greška, osigurajte se da svi pojmovi imaju valide vrijednosti',
'Etc' => 'itd',
'Event' => 'Događaj',
'EventFilter' => 'Filter događaja',
'EventId' => 'ID događaja',
'EventName' => 'Naziv događaja',
'EventPrefix' => 'Prefiks događaja',
'Events' => 'Događaji',
'Exclude' => 'Isključi',
'Execute' => 'Izvrši',
'ExportDetails' => 'Izvezi detalje o događaju',
'Exif' => 'Umetni EXIF podatke u sliku',
'Export' => 'Izvezi',
'DownloadVideo' => 'Preuzmi video',
'GenerateDownload' => 'Generiši preuzimanje',
'ExportFailed' => 'Izvoz nije uspio',
'ExportFormat' => 'Format za izvoz',
'ExportFormatTar' => 'Tar',
'ExportFormatZip' => 'Zip',
'ExportFrames' => 'Izvezi detalje frejma',
'ExportImageFiles' => 'Izvezi slike',
'ExportLog' => 'Izvezi zapisnik',
'Exporting' => 'Izvozim',
'ExportMiscFiles' => 'Izvezi druge fajlove (ukoliko postoje)',
'ExportOptions' => 'Opcije izvoženja',
'ExportSucceeded' => 'Izvoz uspio',
'ExportVideoFiles' => 'Izvezi video fileove (ukoliko postoje)',
'Far' => 'Far',
'FastForward' => 'Naprijed',
'Feed' => 'Feed',
'Ffmpeg' => 'Ffmpeg',
'File' => 'File',
'FilterArchiveEvents' => 'Arhiviraj pronađeno',
'FilterUpdateDiskSpace' => 'Ažuriraj korišteni prostor na disku',
'FilterDeleteEvents' => 'Izbriši sve pronađeno',
'FilterMoveEvents' => 'Premjesti pronađeno',
'FilterEmailEvents' => 'Pošalji detalje mailom',
'FilterExecuteEvents' => 'Izvrši sljededeću komandu',
'FilterLog' => 'Filtriraj zapis',
'FilterMessageEvents' => 'Message details of all matches',
'FilterPx' => 'Filter Px',
'Filter' => 'Filter',
'Filters' => 'Filteri',
'FilterUnset' => 'Morate navesti širinu i visinu filtera',
'FilterUploadEvents' => 'Učitaj sve događaje',
'FilterVideoEvents' => 'Napravi video',
'First' => 'Prvi',
'FlippedHori' => 'Zaokrenuto horizontalno',
'FlippedVert' => 'Zaokrenuto vertikalno',
'FnNone' => 'nijedan', // Added 2013.08.16.
'FnMonitor' => 'Monitor', // Added 2013.08.16.
'FnModect' => 'Modect', // Added 2013.08.16.
'FnRecord' => 'Record', // Added 2013.08.16.
'FnMocord' => 'Mocord', // Added 2013.08.16.
'FnNodect' => 'Nodect', // Added 2013.08.16.
'Focus' => 'Fokus',
'ForceAlarm' => 'Prisilni alarm',
'Format' => 'Format',
'FPS' => 'fps',
'FPSReportInterval' => 'FPS Report Interval',
'Frame' => 'Frame',
'FrameId' => 'Frame Id',
'FrameRate' => 'Frame Rate',
'Frames' => 'Frejmovi',
'FrameSkip' => 'Preskoči frejm',
'MotionFrameSkip' => 'Motion Frame Skip',
'FTP' => 'FTP',
'Func' => 'Func',
'Function' => 'Funkcija',
'Gain' => 'Pojačanje',
'General' => 'Opšte',
'GenerateVideo' => 'Generiši video',
'GeneratingVideo' => 'Generiši video',
'GoToZoneMinder' => 'Idi na ZoneMinder.com',
'Grey' => 'Siva',
'Group' => 'Grupa',
'Groups' => 'Grupe',
'HasFocusSpeed' => 'Posjeduje brzo fokusiranja',
'HasGainSpeed' => 'Posjeduje brzo pojačanja',
'HasHomePreset' => 'Has Home Preset',
'HasIrisSpeed' => 'Posjeduje brzu blendu',
'HasPanSpeed' => 'Posjeduje brzi pomak',
'HasPresets' => 'Posjeduje pre-setove',
'HasTiltSpeed' => 'Posjeduje brzi nagiba',
'HasTurboPan' => 'Posjeduje turbo pomak',
'HasTurboTilt' => 'Posjeduje turbo nagib',
'HasWhiteSpeed' => 'Posjeduje brzo podeš.bijele',
'HasZoomSpeed' => 'Posjeduje brzi zoom',
'HighBW' => 'High&nbsp;B/W',
'High' => 'veliku',
'Home' => 'Početna',
'Hostname' => 'Hostname',
'Hour' => 'Sat',
'Hue' => 'Nijansa',
'Id' => 'Id',
'Idle' => 'Na čekanju',
'Ignore' => 'Zanemari',
'ImageBufferSize' => 'Veličina slikovnog bufera (u frejmovima)',
'Image' => 'Slika',
'Images' => 'Slike',
'Include' => 'Uključi',
'In' => 'U',
'Inverted' => 'Invertirano',
'Iris' => 'Blenda',
'KeyString' => 'Key String',
'Label' => 'Oznaka',
'Language' => 'Jezik',
'Last' => 'Zadnje',
'Layout' => 'Raspored',
'Libvlc' => 'Libvlc',
'LimitResultsPost' => 'results only', // This is used at the end of the phrase 'Limit to first N results only'
'LimitResultsPre' => 'Limit to first', // This is used at the beginning of the phrase 'Limit to first N results only'
'LinkedMonitors' => 'Povezani monitori',
'List' => 'Popis',
'ListMatches' => 'Prikaži pronađeno',
'Load' => 'Opterećenje',
'Local' => 'Lokalno',
'Log' => 'Zapis',
'Logs' => 'Zapisi',
'Logging' => 'Dnevnik događaja',
'LoggedInAs' => 'Prijavljen kao',
'LoggingIn' => 'Prijavljujem',
'Login' => 'prijava',
'Logout' => 'odjava',
'LowBW' => 'Low&nbsp;B/W',
'Low' => 'nisku',
'Main' => 'Glavno',
'Man' => 'Man',
'Manual' => 'Ručno',
'Mark' => 'Označi',
'MaxBandwidth' => 'Max propusnost',
'MaxBrScore' => 'Max.<br/>Score',
'MaxFocusRange' => 'Max raspon fokusa',
'MaxFocusSpeed' => 'Max brzina fokusa',
'MaxFocusStep' => 'Max korak fokusa',
'MaxGainRange' => 'Max raspon pojačanja',
'MaxGainSpeed' => 'Max brzina pojačanja',
'MaxGainStep' => 'Max korak pojačanja',
'MaximumFPS' => 'Maximum FPS',
'MaxIrisRange' => 'Max raspon blende',
'MaxIrisSpeed' => 'Max brzina blende',
'MaxIrisStep' => 'Max korak blende',
'Max' => 'Max',
'MaxPanRange' => 'Max raspon pomaka',
'MaxPanSpeed' => 'Max brzina pomaka',
'MaxPanStep' => 'Max korak pomaka',
'MaxTiltRange' => 'Max raspon nagiba',
'MaxTiltSpeed' => 'Max brzina nagiba',
'MaxTiltStep' => 'Max korak nagiba',
'MaxWhiteRange' => 'Max raspon bijele',
'MaxWhiteSpeed' => 'Max brzina bijele',
'MaxWhiteStep' => 'Max korak bijele',
'MaxZoomRange' => 'Max raspon zumiranja',
'MaxZoomSpeed' => 'Max brzina zumiranja',
'MaxZoomStep' => 'Max korak zumiranja',
'MediumBW' => 'Medium&nbsp;B/W',
'Medium' => 'srednju',
'MinAlarmAreaLtMax' => 'Min područje alarma mora biti manje od maksimalnog',
'MinAlarmAreaUnset' => 'Morate zadati minimalni broj alarm piksela',
'MinBlobAreaLtMax' => 'Min blob područje mora biti manje od maksimalnog',
'MinBlobAreaUnset' => 'Morate zadati minimalni broj blob piksela',
'MinBlobLtMinFilter' => 'Min blob oblast mora biti manja ili jednaka minimalnoj oblasti filtera',
'MinBlobsLtMax' => 'Min blob mora biti manji od maksimalne',
'MinBlobsUnset' => 'morate zadati minimalni broj blob-ova',
'MinFilterAreaLtMax' => 'Minimalna oblast filtera mora biti manja od maksimalne',
'MinFilterAreaUnset' => 'Morate zadati minimalni broj filter piksela',
'MinFilterLtMinAlarm' => 'Min oblast filtera mora biti manja ili jednaka minimalnoj oblasti alarmne oblasti',
'MinFocusRange' => 'Min raspon fokusiranja',
'MinFocusSpeed' => 'Min brzina fokusiranja',
'MinFocusStep' => 'Min korak fokusiranja',
'MinGainRange' => 'Min raspon pojačanja',
'MinGainSpeed' => 'Min brzina pojačanja',
'MinGainStep' => 'Min korak pojačanja',
'MinIrisRange' => 'Min raspon blende',
'MinIrisSpeed' => 'Min brzina blende',
'MinIrisStep' => 'Min korak blende',
'MinPanRange' => 'Min raspon pomaka',
'MinPanSpeed' => 'Min brzina pomaka',
'MinPanStep' => 'Min korak pomaka',
'MinPixelThresLtMax' => 'Min prag piksela mora biti manji od maksimalnog',
'MinPixelThresUnset' => 'Morate zadati minimalni prag piksela',
'MinTiltRange' => 'Min Tilt Range',
'MinTiltSpeed' => 'Min Tilt Speed',
'MinTiltStep' => 'Min Tilt Step',
'MinWhiteRange' => 'Min raspon bijelog balansa',
'MinWhiteSpeed' => 'Min brzina bijelog balansa',
'MinWhiteStep' => 'Min White Bal. Step',
'MinZoomRange' => 'Min raspon zumiranja',
'MinZoomSpeed' => 'Min brzina zumiranja',
'MinZoomStep' => 'Min korak zumiranja',
'Misc' => 'Razno',
'Mode' => 'Modus',
'MonitorIds' => 'Monitor&nbsp;Ids',
'Monitor' => 'Monitor',
'MonitorPresetIntro' => 'Odaberite odgovarajuće pre-setove sa popisa.<br/><br/>Imajte u vidu da ovo može prepisati bilo koju vrijednost koja postoji za odabrane monitore.<br/><br/>',
'MonitorPreset' => 'Monitor Preset',
'MonitorProbeIntro' => 'Donji popis prikazuje otkrivene analogne i mrežne kamere, te da li se iste već koriste i da li su dostupne.<br/><br/>Odaberite željenu kameru sa donjeg popisa.<br/><br/>Imajte u vidu da ovo može prepisati bilo koju vrijednost koja postoji za odabrane monitore.<br/><br/>',
'MonitorProbe' => 'Detektuj kameru',
'Monitors' => 'Monitori',
'Montage' => 'Montage',
'MontageReview' => 'Montage pregled',
'Month' => 'Mjesec',
'Move' => 'Pomjeri',
'MtgDefault' => 'Podrazumjevano', // Added 2013.08.15.
'Mtg2widgrd' => '2-struka rešetka', // Added 2013.08.15.
'Mtg3widgrd' => '3-struka rešetka', // Added 2013.08.15.
'Mtg4widgrd' => '4-struka rešetka', // Added 2013.08.15.
'Mtg3widgrx' => '3-wide grid, scaled, enlarge on alarm', // Added 2013.08.15.
'MustBeGe' => 'mora biti veće ili jednako',
'MustBeLe' => 'mora biti manje ili jednako',
'MustConfirmPassword' => 'Morate potvrditi lozinku',
'MustSupplyPassword' => 'Morate unjeti lozinku',
'MustSupplyUsername' => 'Morate unjeti korisničko ime',
'Name' => 'Ime',
'Near' => 'Blizu',
'Network' => 'Mreža',
'NewGroup' => 'Nova grupa',
'NewLabel' => 'Nova oznaka',
'New' => 'Novo',
'NewPassword' => 'Nova lozinka',
'NewState' => 'Novi radni modus',
'NewUser' => 'Novi korisnik',
'Next' => 'Sljedeće',
'NoDetectedCameras' => 'Nema otkrivenih kamera',
'NoDetectedProfiles' => 'Nema otkrivenih profila',
'NoFramesRecorded' => 'Nije ništa snimljeno za ovaj događaj',
'NoGroup' => 'Nema grupe',
'NoneAvailable' => 'Nijedno dostupno',
'None' => 'Nijedno',
'No' => 'Ne',
'Normal' => 'Normalno',
'NoSavedFilters' => 'NemaSnimljenihFiltera',
'NoStatisticsRecorded' => 'Nema snimljenih statistika za ovaj događaj',
'Notes' => 'Bilješke',
'NumPresets' => 'Num Presets',
'Off' => 'Isključeno',
'On' => 'Uključeno',
'OnvifProbe' => 'ONVIF detekcija',
'OnvifProbeIntro' => 'The list below shows detected ONVIF cameras and whether they are already being used or available for selection.<br/><br/>Select the desired entry from the list below.<br/><br/>Please note that not all cameras may be detected and that choosing a camera here may overwrite any values you already have configured for the current monitor.<br/><br/>',
'OnvifCredentialsIntro' => 'Please supply user name and password for the selected camera.<br/>If no user has been created for the camera then the user given here will be created with the given password.<br/><br/>',
'Open' => 'Otvori',
'OpEq' => 'jednako',
'OpGtEq' => 'veće ili jednako od',
'OpGt' => 'veće ',
'OpIn' => 'in set',
'OpLtEq' => 'manje ili jednako od',
'OpLt' => 'manje od',
'OpMatches' => 'matches',
'OpNe' => 'nije jednako',
'OpNotIn' => 'nije u ',
'OpNotMatches' => 'ne poklapa se',
'OpIs' => 'je',
'OpIsNot' => 'nije',
'OptionalEncoderParam' => 'Opcionalni parametri enkodera',
'OptionHelp' => 'Option Help',
'OptionRestartWarning' => 'These changes may not come into effect fully\nwhile the system is running. When you have\nfinished making your changes please ensure that\nyou restart ZoneMinder.',
'Options' => 'Opcije',
'Order' => 'Redosljed',
'OrEnterNewName' => 'ili unesi novo ime',
'Orientation' => 'Orijentacija',
'Out' => 'Izlaz',
'OverwriteExisting' => 'Prepiši preko postojećeg',
'Paged' => 'stranično',
'PanLeft' => 'Pomak lijevo',
'Pan' => 'Pomak',
'PanRight' => 'Pomak desno',
'PanTilt' => 'Pomak/Nagib',
'Parameter' => 'Parametar',
'Password' => 'Lozinka',
'PasswordsDifferent' => 'Nova i potvrđena lozinka se razlikuju',
'Paths' => 'Putanje',
'Pause' => 'Pauza',
'PhoneBW' => 'Telefon&nbsp;B/W',
'Phone' => 'Telefon',
'PixelDiff' => 'Piksel razli.',
'Pixels' => 'pikseli',
'PlayAll' => 'play all',
'Play' => 'Play',
'Plugins' => 'Plugini',
'PleaseWait' => 'Molim čekati',
'Point' => 'Point',
'PostEventImageBuffer' => 'Br. frejmova poslije događaja',
'PreEventImageBuffer' => 'Br. frejmova prije događaja',
'PreserveAspect' => 'Zadrži omjer',
'Preset' => 'Preset',
'Presets' => 'Presets',
'Prev' => 'Preth',
'Privacy' => 'Privatnost',
'PrivacyAbout' => 'O',
'PrivacyAboutText' => 'Since 2002, ZoneMinder has been the premier free and open-source Video Management System (VMS) solution for Linux platforms. ZoneMinder is supported by the community and is managed by those who choose to volunteer their spare time to the project. The best way to improve ZoneMinder is to get involved.',
'PrivacyContact' => 'Konakt',
'PrivacyContactText' => 'Please contact us <a href="https://zoneminder.com/contact/">here</a> for any questions regarding our privacy policy or to have your information removed.<br><br>For support, there are three primary ways to engage with the community:<ul><li>The ZoneMinder <a href="https://forums.zoneminder.com/">user forum</a></li><li>The ZoneMinder <a href="https://zoneminder-chat.herokuapp.com/">Slack channel</a></li><li>The ZoneMinder <a href="https://github.com/ZoneMinder/zoneminder/issues">Github forum</a></li></ul><p>Our Github forum is only for bug reporting. Please use our user forum or slack channel for all other questions or comments.</p>',
'PrivacyCookies' => 'Kolačići',
'PrivacyCookiesText' => 'Whether you use a web browser or a mobile app to communicate with the ZoneMinder server, a ZMSESSID cookie is created on the client to uniquely identify a session with the ZoneMinder server. ZmCSS and zmSkin cookies are created to remember your style and skin choices.',
'PrivacyTelemetry' => 'Telemetry',
'PrivacyTelemetryText' => 'Because ZoneMinder is open-source, anyone can install it without registering. This makes it difficult to answer questions such as: how many systems are out there, what is the largest system out there, what kind of systems are out there, or where are these systems located? Knowing the answers to these questions, helps users who ask us these questions, and it helps us set priorities based on the majority user base.',
'PrivacyTelemetryList' => 'The ZoneMinder Telemetry daemon collects the following data about your system:<ul><li>A unique identifier (UUID) <li>City based location is gathered by querying <a href="https://ipinfo.io/geo">ipinfo.io</a>. City, region, country, latitude, and longitude parameters are saved. The latitude and longitude coordinates are accurate down to the city or town level only!<li>Current time<li>Total number of monitors<li>Total number of events<li>System architecture<li>Operating system kernel, distro, and distro version<li>Version of ZoneMinder<li>Total amount of memory<li>Number of cpu cores</ul>',
'PrivacyMonitorList' => 'The following configuration parameters from each monitor are collected:<ul><li>Id<li>Name<li>Type<li>Function<li>Width<li>Height<li>Colours<li>MaxFPS<li>AlarmMaxFPS</ul>',
'PrivacyConclusionText' => 'We are <u>NOT</u> collecting any image specific data from your cameras. We don<6F>t know what your cameras are watching. This data will not be sold or used for any purpose not stated herein. By clicking accept, you agree to send us this data to help make ZoneMinder a better product. By clicking decline, you can still freely use ZoneMinder and all its features.',
'Probe' => 'Detektuj kameru',
'ProfileProbe' => 'Stream proba',
'ProfileProbeIntro' => 'The list below shows the existing stream profiles of the selected camera .<br/><br/>Select the desired entry from the list below.<br/><br/>Please note that ZoneMinder cannot configure additional profiles and that choosing a camera here may overwrite any values you already have configured for the current monitor.<br/><br/>',
'Progress' => 'Napredak',
'Protocol' => 'Protkol',
'Rate' => 'Stopa',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // added Sep 24 2015 - PP
'RecordAudio' => 'Whether to store the audio stream when saving an event.',
'Real' => 'Stvarno',
'Record' => 'Snimaj',
'RefImageBlendPct' => 'Reference Image Blend %ge',
'Refresh' => 'Osvježi',
'RemoteHostName' => 'Naziv uređaja',
'RemoteHostPath' => 'Putanja',
'RemoteHostSubPath' => 'Pod-putanja',
'RemoteHostPort' => 'Port',
'RemoteImageColours' => 'Boje slike',
'RemoteMethod' => 'Metoda',
'RemoteProtocol' => 'Protokol',
'Remote' => 'Udaljeno',
'Rename' => 'Preimenuj',
'ReplayAll' => 'Svi događaji',
'ReplayGapless' => 'Gapless Events',
'Replay' => 'Ponovo odigraj',
'ReplaySingle' => 'Jedan događaj',
'ReportEventAudit' => 'Audit Events Report',
'ResetEventCounts' => 'Resetiraj događaje',
'Reset' => 'Reset',
'Restarting' => 'Restartiram',
'Restart' => 'Restaruj',
'RestrictedCameraIds' => 'Restricted Camera Ids',
'RestrictedMonitors' => 'Ograničeni monitori',
'ReturnDelay' => 'Vrati kašnjenje',
'ReturnLocation' => 'Vrati lokaciju',
'Rewind' => 'Premotaj',
'RotateLeft' => 'Rotoraj ulijevo',
'RotateRight' => 'Rotiraj udesno',
'RTSPTransport' => 'RTSP Transport Protocol',
'RunAudit' => 'Run Audit Process',
'RunLocalUpdate' => 'Pokrenite zmupdate.pl za ažuriranje',
'RunMode' => 'Modus rada',
'Running' => 'Pokrenuto',
'RunState' => 'Radni modus',
'RunStats' => 'Pokreni stats proces',
'RunTrigger' => 'Pokreni triger proces',
'SaveAs' => 'Spremi kao',
'SaveFilter' => 'Spremi Filter',
'SaveJPEGs' => 'Spremi JPEGs',
'Save' => 'Spremi',
'Scale' => 'Razmjer',
'Score' => 'Zbir',
'Secs' => 'Secs',
'Sectionlength' => 'Odaberi dužinu',
'SelectMonitors' => 'SOdaberi monitore',
'Select' => 'Odaberi',
'SelectFormat' => 'Odaberi format',
'SelectLog' => 'Odaberi zapis',
'SelfIntersecting' => 'Polygon edges must not intersect',
'SetNewBandwidth' => 'Postavi propusnost na',
'SetPreset' => 'Postavi pozicije',
'Set' => 'Postavi',
'Settings' => 'Postavke',
'ShowFilterWindow' => 'Prikaži prozor za filter',
'ShowTimeline' => 'Prikaži vremensku liniju',
'SignalCheckColour' => 'Signal Check Colour',
'SignalCheckPoints' => 'Signal Check Points',
'Size' => 'Veličina',
'SkinDescription' => 'Izmjeni izgled za ovu sesiju',
'CSSDescription' => 'Izmjeni css za ovu sesiju',
'Sleep' => 'Sleep',
'SortAsc' => 'Rastuće',
'SortBy' => 'Sortiraj po',
'SortDesc' => 'Padajuće',
'Source' => 'Izvor',
'SourceColours' => 'Source Colours',
'SourcePath' => 'Putanja izvora ',
'SourceType' => 'Izvor videa',
'SpeedHigh' => 'Velika brzina',
'SpeedLow' => 'Niska brzina',
'SpeedMedium' => 'Srednja brzina',
'Speed' => 'brzina',
'SpeedTurbo' => 'Turbo brzina',
'Start' => 'Start',
'State' => 'Stanje',
'Stats' => 'Statistka',
'Status' => 'Status',
'StatusUnknown' => 'Nepoznato',
'StatusConnected' => 'Snimam',
'StatusNotRunning' => 'Nije pokrenuto',
'StatusRunning' => 'Ne snima',
'StepBack' => 'Korak nazad',
'StepForward' => 'Korak naprijed',
'StepLarge' => 'Veliki korak',
'StepMedium' => 'Srednji korak',
'StepNone' => 'Bez koraka',
'StepSmall' => 'Mali korak',
'Step' => 'Korak',
'Stills' => 'Stills',
'Stopped' => 'Zaustavljeno',
'Stop' => 'Zaustavi',
'StorageArea' => 'Storage Area',
'StorageDoDelete' => 'Brisanja',
'StorageScheme' => 'Šema',
'StreamReplayBuffer' => 'Stream Replay Image Buffer',
'Stream' => 'Stream',
'Submit' => 'Pošalji',
'System' => 'Sistem',
'TargetColorspace' => 'Rezolucija boja',
'Tele' => 'Udaljeno',
'Thumbnail' => 'Sličica',
'Tilt' => 'Tilt',
'TimeDelta' => 'Vremenska razlika',
'Timeline' => 'Vremenska linija',
'TimelineTip1' => 'Pass your mouse over the graph to view a snapshot image and event details.', // Added 2013.08.15.
'TimelineTip2' => 'Click on the coloured sections of the graph, or the image, to view the event.', // Added 2013.08.15.
'TimelineTip3' => 'Click on the background to zoom in to a smaller time period based around your click.', // Added 2013.08.15.
'TimelineTip4' => 'Use the controls below to zoom out or navigate back and forward through the time range.', // Added 2013.08.15.
'TimestampLabelFormat' => 'Timestamp format oznake',
'TimestampLabelX' => 'Timestamp oznaka X',
'TimestampLabelY' => 'Timestamp oznaka Y',
'TimestampLabelSize' => 'Veličina fonta',
'Timestamp' => 'Timestamp',
'TimeStamp' => 'Vremenski pečat',
'Time' => 'Vrijeme',
'Today' => 'Danas',
'Tools' => 'Alati',
'Total' => 'Ukupno',
'TotalBrScore' => 'Total<br/>Score',
'TrackDelay' => 'Kašnjenje',
'TrackMotion' => 'Prati pokret',
'Triggers' => 'Okidači',
'TurboPanSpeed' => 'Turbo Pan brzina',
'TurboTiltSpeed' => 'Turbo Tilt brzina',
'Type' => 'Tip',
'Unarchive' => 'Dearhiviraj',
'Undefined' => 'Nedefinisano',
'Units' => 'Mjere',
'Unknown' => 'Nepoznato',
'UpdateAvailable' => 'Dostupno je novo ažurranje za Zoneminder .',
'UpdateNotNecessary' => 'Ažuriranje nije potrebno.',
'Update' => 'Ažuiriaj',
'Upload' => 'Upload',
'Updated' => 'Ažurirano',
'UsedPlugins' => 'Korišteni plugini ',
'UseFilterExprsPost' => '&nbsp;filter&nbsp;expressions', // This is used at the end of the phrase 'use N filter expressions'
'UseFilterExprsPre' => 'Use&nbsp;', // This is used at the beginning of the phrase 'use N filter expressions'
'UseFilter' => 'Koristi filter',
'Username' => 'Korisničko ime',
'Users' => 'Korisnici',
'User' => 'Korisnik',
'Value' => 'Vrijednost',
'VersionIgnore' => 'Ignoriši ovu verziju',
'VersionRemindDay' => 'Podsjeti me za jedan dan',
'VersionRemindHour' => 'Podsjeti me za jedan sat',
'VersionRemindNever' => 'Ne podsjecaj me na nove verzije',
'VersionRemindWeek' => 'Podsjeti me za sedam dana',
'Version' => 'Verzija',
'VideoFormat' => 'Video Format',
'VideoGenFailed' => 'Generisanje videa nije uspjelo!',
'VideoGenFiles' => 'Postojece video datoteke',
'VideoGenNoFiles' => 'Video datoteke nisu pronadjene',
'VideoGenParms' => 'Parametri za generisanje videa',
'VideoGenSucceeded' => 'Generisanje videa uspjelo!',
'VideoSize' => 'Velicina videa',
'VideoWriter' => 'Video pisac',
'Video' => 'Video',
'ViewAll' => 'Pregledaj sve',
'ViewEvent' => 'Pregled događaja',
'ViewPaged' => 'Stanični pregled',
'View' => 'Pregled',
'V4L' => 'V4L',
'V4LCapturesPerFrame' => 'Snimci po frejmu',
'V4LMultiBuffer' => 'Višestr. bafer',
'Wake' => 'Budi',
'WarmupFrames' => 'Warmup frejmovi',
'Watch' => 'Gledaj',
'WebColour' => 'Web boja',
'Web' => 'Web',
'WebSiteUrl' => 'URL web stranice',
'Week' => 'Sedmica',
'WhiteBalance' => 'Balans bijele',
'White' => 'Bijelo',
'Wide' => 'Široko',
'X10ActivationString' => 'X10 znakovni niz za aktiviranje',
'X10InputAlarmString' => 'X10 ulazni znakovni niz za alarm',
'X10OutputAlarmString' => 'X10 izlazni znakovni niz za alarm',
'X10' => 'X10',
'X' => 'X',
'Yes' => 'Da',
'YouNoPerms' => 'Nemate potrebne dozvole za pristup ovom resursu.',
'Y' => 'Y',
'ZoneAlarmColour' => 'Boja alarma (Red/Green/Blue)',
'ZoneArea' => 'Oblast zone',
'ZoneFilterSize' => 'Filter Width/Height (pixels)',
'ZoneMinderLog' => 'ZoneMinder zapisnik',
'ZoneMinMaxAlarmArea' => 'Min/Max alarmirana oblast',
'ZoneMinMaxBlobArea' => 'Min/Max blob oblast',
'ZoneMinMaxBlobs' => 'Min/Max Blobovi',
'ZoneMinMaxFiltArea' => 'Min/Max filtrirane oblasti',
'ZoneMinMaxPixelThres' => 'Min/Max Pixel Threshold (0-255)',
'ZoneOverloadFrames' => 'Overload Frame Ignore Count',
'ZoneExtendAlarmFrames' => 'Extend Alarm Frame Count',
'Zones' => 'Zone',
'Zone' => 'Zona',
'ZoomIn' => 'Zoom In',
'ZoomOut' => 'Zoom Out',
'Zoom' => 'Zumiranje',
);
// Complex replacements with formatting and/or placements, must be passed through sprintf
$CLANG = array(
'CurrentLogin' => 'Prijavljeni ste kao \'%1$s\'',
'EventCount' => '%1$s %2$s', // For example '37 Events' (from Vlang below)
'LastEvents' => 'Last %1$s %2$s', // For example 'Last 37 Events' (from Vlang below)
'LatestRelease' => 'Zadnja verzija servera je v%1$s, vi imate v%2$s.',
'MonitorCount' => '%1$s %2$s', // For example '4 Monitors' (from Vlang below)
'MonitorFunction' => 'Monitor %1$s Function',
'RunningRecentVer' => 'Koristite najnoviju verziju Zoneminder servera, v%s.',
'VersionMismatch' => 'Version mismatch, system is version %1$s, database is %2$s.',
);
// The next section allows you to describe a series of word ending and counts used to
// generate the correctly conjugated forms of words depending on a count that is associated
// with that word.
// This intended to allow phrases such a '0 potatoes', '1 potato', '2 potatoes' etc to
// conjugate correctly with the associated count.
// In some languages such as English this is fairly simple and can be expressed by assigning
// a count with a singular or plural form of a word and then finding the nearest (lower) value.
// So '0' of something generally ends in 's', 1 of something is singular and has no extra
// ending and 2 or more is a plural and ends in 's' also. So to find the ending for '187' of
// something you would find the nearest lower count (2) and use that ending.
//
// So examples of this would be
// $zmVlangPotato = array( 0=>'Potatoes', 1=>'Potato', 2=>'Potatoes' );
// $zmVlangSheep = array( 0=>'Sheep' );
//
// where you can have as few or as many entries in the array as necessary
// If your language is similar in form to this then use the same format and choose the
// appropriate zmVlang function below.
// If however you have a language with a different format of plural endings then another
// approach is required . For instance in Russian the word endings change continuously
// depending on the last digit (or digits) of the numerator. In this case then zmVlang
// arrays could be written so that the array index just represents an arbitrary 'type'
// and the zmVlang function does the calculation about which version is appropriate.
//
// So an example in Russian might be (using English words, and made up endings as I
// don't know any Russian!!)
// 'Potato' => array( 1=>'Potati', 2=>'Potaton', 3=>'Potaten' ),
//
// and the zmVlang function decides that the first form is used for counts ending in
// 0, 5-9 or 11-19 and the second form when ending in 1 etc.
//
// Variable arrays expressing plurality, see the zmVlang description above
$VLANG = array(
'Event' => array( 0=>'Events', 1=>'Event', 2=>'Events' ),
'Monitor' => array( 0=>'Monitors', 1=>'Monitor', 2=>'Monitors' ),
);
// You will need to choose or write a function that can correlate the plurality string arrays
// with variable counts. This is used to conjugate the Vlang arrays above with a number passed
// in to generate the correct noun form.
//
// In languages such as English this is fairly simple
// Note this still has to be used with printf etc to get the right formatting
function zmVlang( $langVarArray, $count )
{
krsort( $langVarArray );
foreach ( $langVarArray as $key=>$value )
{
if ( abs($count) >= $key )
{
return( $value );
}
}
die( 'Error, unable to correlate variable language string' );
}
// This is an version that could be used in the Russian example above
// The rules are that the first word form is used if the count ends in
// 0, 5-9 or 11-19. The second form is used then the count ends in 1
// (not including 11 as above) and the third form is used when the
// count ends in 2-4, again excluding any values ending in 12-14.
//
// function zmVlang( $langVarArray, $count )
// {
// $secondlastdigit = substr( $count, -2, 1 );
// $lastdigit = substr( $count, -1, 1 );
// // or
// // $secondlastdigit = ($count/10)%10;
// // $lastdigit = $count%10;
//
// // Get rid of the special cases first, the teens
// if ( $secondlastdigit == 1 && $lastdigit != 0 )
// {
// return( $langVarArray[1] );
// }
// switch ( $lastdigit )
// {
// case 0 :
// case 5 :
// case 6 :
// case 7 :
// case 8 :
// case 9 :
// {
// return( $langVarArray[1] );
// break;
// }
// case 1 :
// {
// return( $langVarArray[2] );
// break;
// }
// case 2 :
// case 3 :
// case 4 :
// {
// return( $langVarArray[3] );
// break;
// }
// }
// die( 'Error, unable to correlate variable language string' );
// }
// This is an example of how the function is used in the code which you can uncomment and
// use to test your custom function.
//$monitors = array();
//$monitors[] = 1; // Choose any number
//echo sprintf( $CLANG['MonitorCount'], count($monitors), zmVlang( $VLANG['VlangMonitor'], count($monitors) ) );
// In this section you can override the default prompt and help texts for the options area
// These overrides are in the form show below where the array key represents the option name minus the initial ZM_
// So for example, to override the help text for ZM_LANG_DEFAULT do
$OLANG = array(
'OPTIONS_FFMPEG' => array(
'Help' => "Parameters in this field are passed on to FFmpeg. Multiple parameters can be separated by ,~~ ".
"Examples (do not enter quotes)~~~~".
"\"allowed_media_types=video\" Set datatype to request fromcam (audio, video, data)~~~~".
"\"reorder_queue_size=nnn\" Set number of packets to buffer for handling of reordered packets~~~~".
"\"loglevel=debug\" Set verbosity of FFmpeg (quiet, panic, fatal, error, warning, info, verbose, debug)"
),
'OPTIONS_RTSPTrans' => array(
'Help' => "This sets the RTSP Transport Protocol for FFmpeg.~~ ".
"TCP - Use TCP (interleaving within the RTSP control channel) as transport protocol.~~".
"UDP - Use UDP as transport protocol. Higher resolution cameras have experienced some 'smearing' while using UDP, if so try TCP~~".
"UDP Multicast - Use UDP Multicast as transport protocol~~".
"HTTP - Use HTTP tunneling as transport protocol, which is useful for passing proxies.~~"
),
'OPTIONS_LIBVLC' => array(
'Help' => "Parameters in this field are passed on to libVLC. Multiple parameters can be separated by ,~~ ".
"Examples (do not enter quotes)~~~~".
"\"--rtp-client-port=nnn\" Set local port to use for rtp data~~~~".
"\"--verbose=2\" Set verbosity of libVLC"
),
'OPTIONS_EXIF' => array(
'Help' => "Enable this option to embed EXIF data into each jpeg frame."
),
'OPTIONS_RTSPDESCRIBE' => array(
'Help' => "Sometimes, during the initial RTSP handshake, the camera will send an updated media URL. ".
"Enable this option to tell ZoneMinder to use this URL. Disable this option to ignore the ".
"value from the camera and use the value as entered in the monitor configuration~~~~".
"Generally this should be enabled. However, there are cases where the camera can get its".
"own URL incorrect, such as when the camera is streaming through a firewall"),
'OPTIONS_MAXFPS' => array(
'Help' => "This field has certain limitations when used for non-local devices.~~ ".
"Failure to adhere to these limitations will cause a delay in live video, irregular frame skipping, ".
"and missed events~~".
"For streaming IP cameras, do not use this field to reduce the frame rate. Set the frame rate in the".
" camera, instead. You can, however, use a value that is slightly higher than the frame rate in the camera. ".
"In this case, this helps keep the cpu from being overtaxed in the event of a network problem.~~".
"Some, mostly older, IP cameras support snapshot mode. In this case ZoneMinder is actively polling the camera ".
"for new images. In this case, it is safe to use the field."
),
// 'LANG_DEFAULT' => array(
// 'Prompt' => "This is a new prompt for this option",
// 'Help' => "This is some new help for this option which will be displayed in the popup window when the ? is clicked"
// ),
);
?>

View File

@ -41,4 +41,3 @@ $DLANG = array(
);
?>

View File

@ -237,6 +237,7 @@ $SLANG = array(
'Cause' => 'Cause',
'CheckMethod' => 'Alarm Check Method',
'ChooseDetectedCamera' => 'Choose Detected Camera',
'ChooseDetectedProfile' => 'Choose Detected Profile',
'ChooseFilter' => 'Choose Filter',
'ChooseLogFormat' => 'Choose a log format',
'ChooseLogSelection' => 'Choose a log selection',
@ -323,6 +324,7 @@ $SLANG = array(
'Events' => 'Events',
'Exclude' => 'Exclude',
'Execute' => 'Execute',
'ExportCompress' => 'Use Compression',
'ExportDetails' => 'Export Event Details',
'ExportMatches' => 'Export Matches',
'Exif' => 'Embed EXIF data into image',

View File

@ -1,2 +1,3 @@
User-agent: *
Disallow: /
###

View File

@ -341,17 +341,17 @@ fieldset > legend {
/*
* Behavior classes
*/
.alarm, .errorText, .error {
color: #ff3f34;
}
.alert, .warnText, .warning {
color: #ffa801;
}
.ok, .infoText {
color: #0fb9b1;
}
.alert, .warnText, .warning, .disabledText {
color: #ffa801;
}
.alarm, .errorText, .error {
color: #ff3f34;
}
.fakelink {
color: #7f7fb2;

View File

@ -21,7 +21,7 @@
}
.ptzControls input[type=image] {
border: 0px;
border: 0;
}
.ptzControls .controlsPanel .arrowControl {
@ -115,7 +115,7 @@
}
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .centerBtn {
background: url("../skins/classic/graphics/graphics/center.png") no-repeat 0 0;
background: url("../skins/classic/graphics/center.png") no-repeat 0 0;
}
.ptzControls .controlsPanel .pantiltPanel .pantiltControls .rightBtn {

View File

@ -25,7 +25,6 @@ function exportHeader($title) {
<meta charset="utf-8">
<title><?php echo $title ?></title>
<style type="text/css">
<!--
<?php include(ZM_PATH_WEB.'/'.ZM_SKIN_PATH.'/css/'.ZM_SKIN_NAME.'/export.css'); ?>
ul.tabs {
@ -67,7 +66,6 @@ html ul.tabs li.active, html ul.tabs li.active a:hover {
background: #dddddd;
border-bottom: 1px solid #e0e0e0;
}
-->
</style>
<script type="text/javascript" src="<?php echo ($title == translate('Images').' Master' ? '' : '../') ?>jquery.js"></script>
<!--<script type="text/javascript" src="<?php echo ($title == translate('Images').' Master' ? '' : '../') ?>video.js"></script>-->
@ -94,7 +92,6 @@ html ul.tabs li.active, html ul.tabs li.active a:hover {
});
});
// ]]>
</script>
</head>
<?php
@ -144,8 +141,8 @@ function exportEventFrames($event, $exportDetail, $exportImages) {
exportHeader(translate('Frames').' '.$event->Id());
$otherlinks = '';
if( $exportDetail ) $otherlinks .= '<a href="zmEventDetail.html">'.translate('Event').'</a>,';
if( $exportImages ) $otherlinks .= '<a href="zmEventImages.html">'.translate('Images').'</a>,';
if ( $exportDetail ) $otherlinks .= '<a href="zmEventDetail.html">'.translate('Event').'</a>,';
if ( $exportImages ) $otherlinks .= '<a href="zmEventImages.html">'.translate('Images').'</a>,';
$otherlinks = substr($otherlinks,0,-1);
?>
<body>
@ -265,7 +262,7 @@ function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist) {
</video>
</div><!--videoFeed-->
<?php
} else { // end if DefaultVideo
} else { // end if DefaultVideo
?>
<ilayer id="slidensmain" width=&{slidewidth}; height=&{slideheight}; bgColor=&{slidebgcolor}; visibility=hide>
<layer id="slidenssub" width="&{slidewidth};" left="auto" top="auto"></layer>
@ -587,6 +584,33 @@ else if (document.layers) window.onload=start_slider;
return ob_get_clean();
}
function eventlist_html($Event) {
?>
<div class="event">
<?php
if ( $Event->SaveJPEGs() ) {
?>
<a href="#" onclick="switchevent('<?php echo $Event->Id(); ?>/zmEventImages.html');return false;">
<?php if ( ZM_WEB_LIST_THUMBS ) { ?>
<img width="<?php echo ZM_WEB_LIST_THUMB_WIDTH ?>" src="<?php echo $Event->Id(); ?>/snapshot.jpg" alt="<?php echo $Event->Id()?>"/>
<?php } else { echo $Event->Id(); } ?>
</a>
<?php
} # end if has jpegs
if ( $Event->DefaultVideo() ) {
if ( ZM_WEB_LIST_THUMBS ) {
?>
<a href="<?php echo $Event->Id().'/'.$Event->DefaultVideo() ?>">
<img width="<?php echo ZM_WEB_LIST_THUMB_WIDTH ?>" src="<?php echo $Event->Id(); ?>/snapshot.jpg" alt="<?php echo $Event->Id()?>"/>
</a>
<?php
}
}
?>
</div><!--event-->
<?php
}
function exportEventImagesMaster($eids) {
ob_start();
exportHeader(translate('Images').' Master');
@ -631,25 +655,20 @@ function exportEventImagesMaster($eids) {
<?php
foreach($eids as $eid) {
$Event = new Event($eid);
if ( $Event->SaveJPEGs() ) {
?>
<div><a href="#" onclick="switchevent('<?php echo $eid; ?>/zmEventImages.html');return false;"><?php echo $eid; ?></a></div>
<?php
} # end if saveJPEGs
eventlist_html($Event);
} # end foreach event id
?>
</div>
<?php
foreach ($monitors as $monitor) {
echo "<div class=\"tab_content\" id=\"tab$monitor\">";
echo '<h2>Monitor: ' . $monitorNames[$monitor] . ' </h2>';
foreach ($monitors as $monitor_id) {
echo "<div class=\"tab_content\" id=\"tab$monitor_id\">";
echo '<h2>Monitor: ' . $monitorNames[$monitor_id] . ' </h2>';
foreach ( $eids as $eid ) {
if ( $eventMonitorId[$eid] == $monitor ) {
?>
<div><a href="#" onclick="switchevent('<?php echo $eid; ?>/zmEventImages.html');return false;"><?php echo $eid; ?></a></div>
<?php
}
}
$Event = new Event($eid);
if ( $Event->MonitorId() == $monitor_id ) {
eventlist_html($Event);
} # end if its the right monitor
} # end foreach event
echo '</div>';
} # end foreach monitor
?>
@ -826,6 +845,7 @@ function exportEvents(
$exportVideo,
$exportMisc,
$exportFormat,
$exportCompressed,
$exportStructure = false
) {
@ -912,28 +932,30 @@ function exportEvents(
chdir(ZM_DIR_EXPORTS);
$archive = '';
if ( $exportFormat == 'tar' ) {
$archive = ZM_DIR_EXPORTS.'/'.$export_root.($connkey?'_'.$connkey:'').'.tar.gz';
@unlink($archive);
$command = 'nice -10 tar --create --gzip --dereference --file='.escapeshellarg($archive).' zmExport_'.$connkey.'/';
#$command = 'nice -10 tar --create --gzip --file='.escapeshellarg($archive).' --files-from='.escapeshellarg($listFile);
$archive = ZM_DIR_EXPORTS.'/'.$export_root.($connkey?'_'.$connkey:'').'.tar';
$command = 'tar --create --dereference';
if ( $exportCompressed ) {
$archive .= '.gz';
$command .= ' --gzip';
$exportFormat .= '.gz';
}
if ( $exportStructure == 'flat' ) {
//strip file paths if we
$command .= " --xform='s#^.+/##x'";
}
$command .= ' --file='.escapeshellarg($archive);
} elseif ( $exportFormat == 'zip' ) {
$archive = ZM_DIR_EXPORTS.'/'.$export_root.($connkey?'_'.$connkey:'').'.zip';
@unlink($archive);
if ( $exportStructure == 'flat' ) {
$command = 'nice -10 zip -j '.escapeshellarg($archive).' zmExport_'.$connkey.'/';
#$command = 'cat '.escapeshellarg($listFile).' | nice -10 zip -q -j '.escapeshellarg($archive).' -@';
} else {
$command = 'nice -10 zip -r '.escapeshellarg($archive).' zmExport_' . $connkey.'/';
#$command = 'cat '.escapeshellarg($listFile).' | nice -10 zip -q '.escapeshellarg($archive).' -@';
}
$command = 'zip ';
$command .= ($exportStructure == 'flat' ? ' -j ' : ' -r ' ).escapeshellarg($archive);
$command .= $exportCompressed ? ' -9' : ' -0';
} else {
Error("No exportFormat specified.");
return false;
} // if $exportFormat
@unlink($archive);
$command .= ' zmExport_' . $connkey.'/';
Logger::Debug("Command is $command");
exec($command, $output, $status);
if ( $status ) {

View File

@ -124,6 +124,7 @@ echo output_link_if_exists( array(
jQuery("#flip").click(function(){
jQuery("#panel").slideToggle("slow");
jQuery("#flip").toggleClass('glyphicon-menu-down').toggleClass('glyphicon-menu-up');
Cookie.write( 'zmHeaderFlip', jQuery('#flip').hasClass('glyphicon-menu-up') ? 'up' : 'down', { duration: 10*365 } );
});
});
</script>
@ -208,6 +209,9 @@ echo output_link_if_exists( array(
} // end function xhtmlHeaders( $file, $title )
function getNavBarHTML($reload = null) {
# Provide a facility to turn off the headers if you put headers=0 into the url
if ( isset($_REQUEST['navbar']) and $_REQUEST['navbar']=='0' )
return '';
$versionClass = (ZM_DYN_DB_VERSION&&(ZM_DYN_DB_VERSION!=ZM_VERSION))?'errorText':'';
global $running;
@ -303,7 +307,7 @@ if (isset($_REQUEST['filter']['Query']['terms']['attr'])) {
}
?>
<li><a href="?view=report_event_audit"<?php echo $view=='report_event_audit'?' class="selected"':''?>><?php echo translate('ReportEventAudit') ?></a></li>
<li><a href="#"><span id="flip" class="glyphicon glyphicon-menu-up pull-right"></span></a></li>
<li><a href="#"><span id="flip" class="glyphicon glyphicon-menu-<?php echo ( isset($_COOKIE['zmHeaderFlip']) and $_COOKIE['zmHeaderFlip'] == 'down') ? 'down' : 'up' ?> pull-right"></span></a></li>
</ul>
<?php } // end if canView('Monitors') ?>
@ -321,7 +325,7 @@ if (isset($_REQUEST['filter']['Query']['terms']['attr'])) {
</div>
</div><!-- End .navbar-collapse -->
</div> <!-- End .container-fluid -->
<div id="panel">
<div id="panel"<?php echo ( isset($_COOKIE['zmHeaderFlip']) and $_COOKIE['zmHeaderFlip'] == 'down' ) ? 'style="display:none;"' : '' ?>>
<?php
}//end reload null. Runs on full page load
@ -341,7 +345,7 @@ if ($reload == 'reload') ob_start();
<?php
$connections = dbFetchOne( "SHOW status WHERE variable_name='threads_connected'", 'Value' );
$max_connections = dbFetchOne( "SHOW variables WHERE variable_name='max_connections'", 'Value' );
$percent_used = 100 * $connections / $max_connections;
$percent_used = $max_connections ? 100 * $connections / $max_connections : 100;
echo '<li'. ( $percent_used > 90 ? ' class="warning"' : '' ).'>'.translate('DB').':'.$connections.'/'.$max_connections.'</li>';
?>
<li><?php echo translate('Storage') ?>:
@ -378,13 +382,13 @@ if ($reload == 'reload') ob_start();
<?php } ?>
<!-- End .footer/reload --></div>
<?php
if ($reload == 'reload') return( ob_get_clean() );
if ($reload == 'reload') return ob_get_clean();
} // end if (!ZM_OPT_USE_AUTH) or $user )
?>
</div>
</div><!-- End .navbar .navbar-default -->
<?php
return( ob_get_clean() );
return ob_get_clean();
} // end function getNavBarHTML()
function xhtmlFooter() {

View File

@ -195,11 +195,12 @@ if ( currentView != 'none' && currentView != 'login' ) {
$j.getJSON(thisUrl + '?view=request&request=status&entity=navBar')
.done(setNavBar)
.fail(function( jqxhr, textStatus, error ) {
var err = textStatus + ", " + error;
console.log( "Request Failed: " + err );
console.log( "Request Failed: " + textStatus + ", " + error);
if ( textStatus != "timeout" ) {
// The idea is that this should only fail due to auth, so reload the page
// which should go to login if it can't stay logged in.
window.location.href = thisUrl+'?view='+currentView;
window.location.reload( true );
}
});
}

View File

@ -67,5 +67,5 @@ var imagePrefix = "<?php echo "?view=image&eid=" ?>";
var auth_hash;
<?php if ( ZM_OPT_USE_AUTH && ZM_AUTH_HASH_LOGINS ) { ?>
auth_hash = '<?php echo isset($_SESSION['AuthHash']) ? $_SESSION['AuthHash'] : ''; ?>';
auth_hash = '<?php echo generateAuthHash(ZM_AUTH_HASH_IPS) ?>';
<?php } ?>

View File

@ -56,7 +56,7 @@ foreach ( Group::find() as $G ) {
$groupSql = '';
if ( count($GroupsById) ) {
$html .= '<span id="groupControl"><label>'. translate('Group') .':</label>';
$html .= '<span id="groupControl"><label>'. translate('Group') .'</label>';
# This will end up with the group_id of the deepest selection
$group_id = isset($_SESSION['Group']) ? $_SESSION['Group'] : null;
$html .= Group::get_group_dropdown();
@ -114,7 +114,7 @@ $html .= htmlSelect('Function[]', $Functions,
$html .= '</span>';
if ( count($ServersById) > 1 ) {
$html .= '<span class="ServerFilter"><label>'. translate('Server').':</label>';
$html .= '<span class="ServerFilter"><label>'. translate('Server').'</label>';
$html .= htmlSelect('ServerId[]', $ServersById,
(isset($_SESSION['ServerId'])?$_SESSION['ServerId']:''),
array(
@ -128,7 +128,7 @@ if ( count($ServersById) > 1 ) {
} # end if have Servers
if ( count($StorageById) > 1 ) {
$html .= '<span class="StorageFilter"><label>'.translate('Storage').':</label>';
$html .= '<span class="StorageFilter"><label>'.translate('Storage').'</label>';
$html .= htmlSelect('StorageId[]', $StorageById,
(isset($_SESSION['StorageId'])?$_SESSION['StorageId']:''),
array(
@ -140,7 +140,7 @@ if ( count($StorageById) > 1 ) {
$html .= '</span>';
} # end if have Storage Areas
$html .= '<span class="StatusFilter"><label>'. translate('Status') . ':</label>';
$html .= '<span class="StatusFilter"><label>'. translate('Status') . '</label>';
$status_options = array(
'Unknown' => translate('StatusUnknown'),
'NotRunning' => translate('StatusNotRunning'),
@ -157,7 +157,7 @@ $html .= htmlSelect( 'Status[]', $status_options,
) );
$html .= '</span>';
$html .= '<span class="SourceFilter"><label>'.translate('Source').':</label>';
$html .= '<span class="SourceFilter"><label>'.translate('Source').'</label>';
$html .= '<input type="text" name="Source" value="'.(isset($_SESSION['Source'])?$_SESSION['Source']:'').'" onkeydown="if(event&&event.keyCode==13){this.form.submit();}" placeholder="text or regular expression"/>';
$html .= '</span>';
@ -232,7 +232,7 @@ $html .= htmlSelect( 'Status[]', $status_options,
$displayMonitors[] = $monitors[$i];
} # end foreach monitor
$html .= '<span class="MonitorFilter"><label>'.translate('Monitor').':</label>';
$html .= '<span class="MonitorFilter"><label>'.translate('Monitor').'</label>';
$html .= htmlSelect('MonitorId[]', $monitors_dropdown, $selected_monitor_ids,
array(
'onchange'=>'this.form.submit();',

View File

@ -217,7 +217,7 @@ ob_start();
echo '<th class="colEvents">'. $j .'</th>';
}
?>
<th class="colZones"><a href="<?php echo $_SERVER['PHP_SELF'] ?>?view=zones_overview"><?php echo translate('Zones') ?></a></th>
<th class="colZones"><?php echo translate('Zones') ?></th>
<?php if ( canEdit('Monitors') ) { ?>
<th class="colMark"><input type="checkbox" name="toggleCheck" value="1" onclick="toggleCheckbox(this, 'markMids[]');setButtonStates(this);"/> <?php echo translate('All') ?></th>
<?php } ?>
@ -251,15 +251,19 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) {
}
}
if ( $monitor['Function'] == 'None' )
$fclass = 'errorText';
$function_class = 'errorText';
else
$fclass = 'infoText';
if ( !$monitor['Enabled'] )
$fclass .= ' disabledText';
$function_class = 'infoText';
$scale = max(reScale(SCALE_BASE, $monitor['DefaultScale'], ZM_WEB_DEFAULT_SCALE), SCALE_BASE);
$stream_available = canView('Stream') and $monitor['Type']=='WebSite' or ($monitor['CaptureFPS'] && $monitor['Function'] != 'None');
$dot_class=$source_class;
if ( $fclass != 'infoText' ) $dot_class=$fclass;
$dot_class = $source_class;
if ( $function_class != 'infoText' ) {
$dot_class = $function_class;
} else if ( !$monitor['Enabled'] ) {
$dot_class .= ' warnText';
}
if ( ZM_WEB_ID_ON_CONSOLE ) {
?>
@ -281,7 +285,7 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) {
?>
</div></td>
<td class="colFunction">
<?php echo makePopupLink( '?view=function&amp;mid='.$monitor['Id'], 'zmFunction', 'function', '<span class="'.$fclass.'">'.translate('Fn'.$monitor['Function']).( empty($monitor['Enabled']) ? ', disabled' : '' ) .'</span>', canEdit( 'Monitors' ) ) ?><br/>
<?php echo makePopupLink( '?view=function&amp;mid='.$monitor['Id'], 'zmFunction', 'function', '<span class="'.$function_class.'">'.translate('Fn'.$monitor['Function']).( empty($monitor['Enabled']) ? ', <span class="disabledText">disabled</span>' : '' ) .'</span>', canEdit('Monitors') ) ?><br/>
<?php echo translate('Status'.$monitor['Status']) ?><br/>
<div class="small text-nowrap text-muted">
<?php

View File

@ -174,7 +174,6 @@ if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT ) {
outputVideoStream( "evtStream", $streamSrc, reScale( $Event->Width(), $scale ), reScale( $Event->Height(), $scale ), ZM_MPEG_LIVE_FORMAT );
} else {
$streamSrc = $Event->getStreamSrc( array( 'mode'=>'jpeg', 'frame'=>$fid, 'scale'=>$scale, 'rate'=>$rate, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>$replayMode) );
Warning("Streamsrc: $streamSrc");
if ( canStreamNative() ) {
outputImageStream( 'evtStream', $streamSrc, reScale( $Event->Width(), $scale ), reScale( $Event->Height(), $scale ), validHtmlStr($Event->Name()) );
} else {

View File

@ -44,7 +44,7 @@ if ( $_REQUEST['filter']['sql'] ) {
$countSql .= $_REQUEST['filter']['sql'];
$eventsSql .= $_REQUEST['filter']['sql'];
}
$eventsSql .= " ORDER BY $sortColumn $sortOrder";
$eventsSql .= " ORDER BY $sortColumn $sortOrder,Id $sortOrder";
$page = isset($_REQUEST['page']) ? validInt($_REQUEST['page']) : 0;
$limit = isset($_REQUEST['limit']) ? validInt($_REQUEST['limit']) : 0;
@ -191,7 +191,8 @@ while ( $event_row = dbFetchNext($results) ) {
<td class="colId"><a href="?view=event&amp;eid=<?php echo $event->Id().$filterQuery.$sortQuery.'&amp;page=1"> '.$event->Id().($event->Archived()?'*':'') ?></a></td>
<td class="colName"><a href="?view=event&amp;eid=<?php echo $event->Id().$filterQuery.$sortQuery.'&amp;page=1"> '.validHtmlStr($event->Name()).($event->Archived()?'*':'') ?></a></td>
<td class="colMonitorName"><?php echo makePopupLink( '?view=monitor&amp;mid='.$event->MonitorId(), 'zmMonitor'.$event->Monitorid(), 'monitor', $event->MonitorName(), canEdit( 'Monitors' ) ) ?></td>
<td class="colCause"><?php echo makePopupLink( '?view=eventdetail&amp;eid='.$event->Id(), 'zmEventDetail', 'eventdetail', validHtmlStr($event->Cause()), canEdit( 'Events' ), 'title="'.htmlspecialchars($event->Notes()).'"' ) ?></td>
<td class="colCause"><?php echo makePopupLink( '?view=eventdetail&amp;eid='.$event->Id(), 'zmEventDetail', 'eventdetail', validHtmlStr($event->Cause()), canEdit( 'Events' ), 'title="'.htmlspecialchars($event->Notes()).'"' ) ?>
<?php if ($event->Notes() && ($event->Notes() != 'Forced Web: ')) echo "<br/><div class=\"small text-nowrap text-muted\">".$event->Notes()."</div>" ?></td>
<td class="colTime"><?php echo strftime(STRF_FMT_DATETIME_SHORTER, strtotime($event->StartTime())) .
( $event->EndTime() ? ' until ' . strftime(STRF_FMT_DATETIME_SHORTER, strtotime($event->EndTime()) ) : '' ) ?>
</td>

View File

@ -36,18 +36,21 @@ if ( isset($_SESSION['export']) ) {
$_REQUEST['exportMisc'] = $_SESSION['export']['misc'];
if ( isset($_SESSION['export']['format']) )
$_REQUEST['exportFormat'] = $_SESSION['export']['format'];
if ( isset($_SESSION['export']['compress']) )
$_REQUEST['exportCompress'] = $_SESSION['export']['compress'];
} else {
$_REQUEST['exportDetail'] =
$_REQUEST['exportFrames'] =
$_REQUEST['exportImages'] =
$_REQUEST['exportVideo'] =
$_REQUEST['exportMisc'] = 1;
$_REQUEST['exportCompress'] = 0;
}
$focusWindow = true;
$connkey = isset($_REQUEST['connkey']) ? $_REQUEST['connkey'] : generateConnKey();
xhtmlHeaders(__FILE__, translate('Export') );
xhtmlHeaders(__FILE__, translate('Export'));
?>
<body>
<div id="page">
@ -74,11 +77,11 @@ if ( $user['MonitorIds'] ) {
}
if ( isset($_REQUEST['eid']) and $_REQUEST['eid'] ) {
Logger::Debug("Loading events by single eid");
Logger::Debug('Loading events by single eid');
$eventsSql .= ' AND E.Id=?';
$eventsValues[] = $_REQUEST['eid'];
} elseif ( isset($_REQUEST['eids']) and count($_REQUEST['eids']) > 0 ) {
Logger::Debug("Loading events by eids");
Logger::Debug('Loading events by eids');
$eventsSql .= ' AND E.Id IN ('.implode(',', array_map(function(){return '?';}, $_REQUEST['eids'])). ')';
$eventsValues += $_REQUEST['eids'];
} else if ( isset($_REQUEST['filter']) ) {
@ -100,17 +103,17 @@ $disk_space_total = 0;
<table id="contentTable" class="major">
<thead>
<tr>
<th class="colId"><a href="<?php echo sortHeader('Id') ?>"><?php echo translate('Id') ?><?php echo sortTag( 'Id' ) ?></a></th>
<th class="colName"><a href="<?php echo sortHeader('Name') ?>"><?php echo translate('Name') ?><?php echo sortTag( 'Name' ) ?></a></th>
<th class="colMonitor"><a href="<?php echo sortHeader('MonitorName') ?>"><?php echo translate('Monitor') ?><?php echo sortTag( 'MonitorName' ) ?></a></th>
<th class="colCause"><a href="<?php echo sortHeader('Cause') ?>"><?php echo translate('Cause') ?><?php echo sortTag( 'Cause' ) ?></a></th>
<th class="colTime"><a href="<?php echo sortHeader('StartTime') ?>"><?php echo translate('Time') ?><?php echo sortTag( 'StartTime' ) ?></a></th>
<th class="colDuration"><a href="<?php echo sortHeader('Length') ?>"><?php echo translate('Duration') ?><?php echo sortTag( 'Length' ) ?></a></th>
<th class="colFrames"><a href="<?php echo sortHeader('Frames') ?>"><?php echo translate('Frames') ?><?php echo sortTag( 'Frames' ) ?></a></th>
<th class="colAlarmFrames"><a href="<?php echo sortHeader('AlarmFrames') ?>"><?php echo translate('AlarmBrFrames') ?><?php echo sortTag( 'AlarmFrames' ) ?></a></th>
<th class="colTotScore"><a href="<?php echo sortHeader('TotScore') ?>"><?php echo translate('TotalBrScore') ?><?php echo sortTag( 'TotScore' ) ?></a></th>
<th class="colAvgScore"><a href="<?php echo sortHeader('AvgScore') ?>"><?php echo translate('AvgBrScore') ?><?php echo sortTag( 'AvgScore' ) ?></a></th>
<th class="colMaxScore"><a href="<?php echo sortHeader('MaxScore') ?>"><?php echo translate('MaxBrScore') ?><?php echo sortTag( 'MaxScore' ) ?></a></th>
<th class="colId"><a href="<?php echo sortHeader('Id') ?>"><?php echo translate('Id') ?><?php echo sortTag('Id') ?></a></th>
<th class="colName"><a href="<?php echo sortHeader('Name') ?>"><?php echo translate('Name') ?><?php echo sortTag('Name') ?></a></th>
<th class="colMonitor"><a href="<?php echo sortHeader('MonitorName') ?>"><?php echo translate('Monitor') ?><?php echo sortTag('MonitorName') ?></a></th>
<th class="colCause"><a href="<?php echo sortHeader('Cause') ?>"><?php echo translate('Cause') ?><?php echo sortTag('Cause') ?></a></th>
<th class="colTime"><a href="<?php echo sortHeader('StartTime') ?>"><?php echo translate('Time') ?><?php echo sortTag('StartTime') ?></a></th>
<th class="colDuration"><a href="<?php echo sortHeader('Length') ?>"><?php echo translate('Duration') ?><?php echo sortTag('Length') ?></a></th>
<th class="colFrames"><a href="<?php echo sortHeader('Frames') ?>"><?php echo translate('Frames') ?><?php echo sortTag('Frames') ?></a></th>
<th class="colAlarmFrames"><a href="<?php echo sortHeader('AlarmFrames') ?>"><?php echo translate('AlarmBrFrames') ?><?php echo sortTag('AlarmFrames') ?></a></th>
<th class="colTotScore"><a href="<?php echo sortHeader('TotScore') ?>"><?php echo translate('TotalBrScore') ?><?php echo sortTag('TotScore') ?></a></th>
<th class="colAvgScore"><a href="<?php echo sortHeader('AvgScore') ?>"><?php echo translate('AvgBrScore') ?><?php echo sortTag('AvgScore') ?></a></th>
<th class="colMaxScore"><a href="<?php echo sortHeader('MaxScore') ?>"><?php echo translate('MaxBrScore') ?><?php echo sortTag('MaxScore') ?></a></th>
<?php
if ( ZM_WEB_EVENT_DISK_SPACE ) {
?>
@ -122,14 +125,15 @@ $disk_space_total = 0;
</thead>
<tbody>
<?php
$event_count = 0;
while ( $event_row = dbFetchNext($results) ) {
$event = new Event($event_row);
$scale = max( reScale( SCALE_BASE, $event->DefaultScale(), ZM_WEB_DEFAULT_SCALE ), SCALE_BASE );
echo '<input type="hidden" name="eids[]" value="'.validInt($event->Id())."\"/>\n";
?>
<tr<?php echo $event->Archived() ? ' class="archived"' : '' ?>>
<td class="colId"><a href="?view=event&amp;eid=<?php echo $event->Id().$filterQuery.$sortQuery.'&amp;page=1"> '.$event->Id().($event->Archived()?'*':'') ?></a></td>
<td class="colName"><a href="?view=event&amp;eid=<?php echo $event->Id().$filterQuery.$sortQuery.'&amp;page=1"> '.validHtmlStr($event->Name()).($event->Archived()?'*':'') ?></a></td>
<td class="colId"><a href="?view=event&amp;eid=<?php echo $event->Id().$filterQuery.$sortQuery ?>&amp;page=1"><?php echo $event->Id().($event->Archived()?'*':'') ?></a></td>
<td class="colName"><a href="?view=event&amp;eid=<?php echo $event->Id().$filterQuery.$sortQuery ?>&amp;page=1"><?php echo validHtmlStr($event->Name()).($event->Archived()?'*':'') ?></a></td>
<td class="colMonitorName"><?php echo makePopupLink( '?view=monitor&amp;mid='.$event->MonitorId(), 'zmMonitor'.$event->Monitorid(), 'monitor', $event->MonitorName(), canEdit( 'Monitors' ) ) ?></td>
<td class="colCause"><?php echo makePopupLink( '?view=eventdetail&amp;eid='.$event->Id(), 'zmEventDetail', 'eventdetail', validHtmlStr($event->Cause()), canEdit( 'Events' ), 'title="'.htmlspecialchars($event->Notes()).'"' ) ?></td>
<td class="colTime"><?php echo strftime(STRF_FMT_DATETIME_SHORTER, strtotime($event->StartTime())) .
@ -146,6 +150,7 @@ while ( $event_row = dbFetchNext($results) ) {
<?php
if ( ZM_WEB_EVENT_DISK_SPACE ) {
$disk_space_total += $event->DiskSpace();
$event_count += 1;
?>
<td class="colDiskSpace"><?php echo human_filesize($event->DiskSpace()) ?></td>
<?php
@ -154,6 +159,10 @@ while ( $event_row = dbFetchNext($results) ) {
} # end foreach event
?>
</tbody>
<tfoot>
<td colspan="11"><?php echo $event_count ?> events</td>
<td class="colDiskSpace"><?php echo human_filesize($disk_space_total);?></td>
</tfoot>
</table>
<table id="contentTable" class="minor">
@ -185,6 +194,15 @@ while ( $event_row = dbFetchNext($results) ) {
<input type="radio" id="exportFormatZip" name="exportFormat" value="zip"<?php if ( isset($_REQUEST['exportFormat']) && $_REQUEST['exportFormat'] == "zip" ) { ?> checked="checked"<?php } ?> onclick="configureExportButton( this )"/><label for="exportFormatZip"><?php echo translate('ExportFormatZip') ?></label>
</td>
</tr>
<tr>
<th scope="row"><?php echo translate('ExportCompress') ?></th>
<td>
<input type="radio" id="exportCompress1" name="exportCompress" value="1"<?php echo ( isset($_REQUEST['exportCompress']) && $_REQUEST['exportCompress'] ) ? ' checked="checked"' : '' ?> onclick="configureExportButton(this)"/>
<label for="exportCompress1"><?php echo translate('Yes') ?></label>
<input type="radio" id="exportCompress0" name="exportCompress" value="0"<?php echo ( isset($_REQUEST['exportCompress']) && $_REQUEST['exportCompress'] ) ? '' : ' checked="checked"' ?> onclick="configureExportButton(this)"/>
<label for="exportCompress0"><?php echo translate('No') ?></label>
</td>
</tr>
</tbody>
</table>
<button type="button" id="exportButton" name="exportButton" value="Export" onclick="exportEvent(this.form);" disabled="disabled"><?php echo translate('Export') ?></button>

View File

@ -253,7 +253,7 @@ for ( $i=0; $i < count($terms); $i++ ) {
<td><?php echo htmlSelect( "filter[Query][terms][$i][op]", $opTypes, $term['op'] ); ?></td>
<td>
<input type="text" name="filter[Query][terms][<?php echo $i ?>][val]" id="filter[Query][terms][<?php echo $i ?>][val]" value="<?php echo isset($term['val'])?validHtmlStr(str_replace('T', ' ', $term['val'])):'' ?>"/>
<script type="text/javascript">$j("[name$='\\[<?php echo $i ?>\\]\\[val\\]']").timepicker({timeFormat: "HH:mm:ss", constrainInput: falsepi}); </script>
<script type="text/javascript">$j("[name$='\\[<?php echo $i ?>\\]\\[val\\]']").timepicker({timeFormat: "HH:mm:ss", constrainInput: false}); </script>
</td>
<?php
} elseif ( $term['attr'] == 'StateId' ) {
@ -411,8 +411,8 @@ if ( ZM_OPT_MESSAGE ) {
<hr/>
<div id="contentButtons">
<button type="submit" onclick="submitToEvents(this);"><?php echo translate('ListMatches') ?></button>
<button type="submit" onclick="submitToExport(this);"><?php echo translate('ExportMatches') ?></button>
<button type="submit" name="executeButton" id="executeButton" onclick="executeFilter( this );"><?php echo translate('Execute') ?></button>
<button type="button" onclick="submitToExport(this);"><?php echo translate('ExportMatches') ?></button>
<button type="submit" name="executeButton" id="executeButton" onclick="executeFilter(this);"><?php echo translate('Execute') ?></button>
<?php
if ( canEdit('Events') ) {
?>
@ -426,7 +426,7 @@ if ( canEdit('Events') ) {
}
}
?>
<button type="button" value="Reset" onclick="resetFilter( this );"><?php echo translate('Reset') ?></button>
<button type="button" value="Reset" onclick="resetFilter(this);"><?php echo translate('Reset') ?></button>
</div>
</form>
</div><!--content-->

View File

@ -42,7 +42,7 @@ function vjsReplay() {
$j.ajaxSetup ({timeout: AJAX_TIMEOUT }); //sets timeout for all getJSON.
var cueFrames = null; //make cueFrames availaible even if we don't send another ajax query
var cueFrames = null; //make cueFrames available even if we don't send another ajax query
function initialAlarmCues (eventId) {
$j.getJSON(thisUrl + '?view=request&request=status&entity=frames&id=' + eventId, setAlarmCues); //get frames data for alarmCues and inserts into html
@ -55,7 +55,12 @@ function setAlarmCues (data) {
}
function renderAlarmCues (containerEl) {
if (cueFrames) {
if ( !( cueFrames && cueFrames.length ) ) {
console.log("No cue frames for event");
return;
}
// This uses the Delta of the last frame to get the length of the event. I can't help but wonder though
// if we shouldn't just use the event length endtime-starttime
var cueRatio = containerEl.width() / (cueFrames[cueFrames.length - 1].Delta * 100);
var minAlarm = Math.ceil(1/cueRatio);
var spanTimeStart = 0;
@ -64,7 +69,8 @@ function renderAlarmCues (containerEl) {
var alarmHtml = "";
var pixSkew = 0;
var skip = 0;
for (let i = 0; i < cueFrames.length; i++) {
var num_cueFrames = cueFrames.length;
for ( let i = 0; i < num_cueFrames; i++ ) {
skip = 0;
frame = cueFrames[i];
if (frame.Type == "Alarm" && alarmed == 0) { //From nothing to alarm. End nothing and start alarm.
@ -84,7 +90,10 @@ function renderAlarmCues (containerEl) {
} else if (frame.Type !== "Alarm" && alarmed == 1) { //from alarm to nothing. End alarm and start nothing.
futNone = 0;
indexPlus = i+1;
if (((frame.Delta * 100) - spanTimeStart) < minAlarm && indexPlus < cueFrames.length) continue; //alarm is too short and there is more event
if (((frame.Delta * 100) - spanTimeStart) < minAlarm && indexPlus < num_cueFrames) {
//alarm is too short and there is more event
continue;
}
while (futNone < minAlarm) { //check ahead to see if there's enough for a nonespan
if (indexPlus >= cueFrames.length) break; //check if end of event.
futNone = (cueFrames[indexPlus].Delta *100) - (frame.Delta *100);
@ -118,7 +127,6 @@ function renderAlarmCues (containerEl) {
}
}
return alarmHtml;
}
}
function setButtonState( element, butClass ) {
@ -147,7 +155,7 @@ function changeScale() {
} else {
eventViewer = $j(vid ? '#videoobj' : '#evtStream');
}
if (scale == "auto") {
if ( scale == "auto" ) {
let newSize = scaleToFit(eventData.Width, eventData.Height, eventViewer, bottomEl);
newWidth = newSize.width;
newHeight = newSize.height;
@ -157,21 +165,22 @@ function changeScale() {
newWidth = eventData.Width * scale / SCALE_BASE;
newHeight = eventData.Height * scale / SCALE_BASE;
}
if (!(streamMode == 'stills')) eventViewer.width(newWidth); //stills handles its own width
if ( !(streamMode == 'stills') )
eventViewer.width(newWidth); //stills handles its own width
eventViewer.height(newHeight);
if ( !vid ) { // zms needs extra sizing
streamScale(scale == "auto" ? autoScale : scale);
drawProgressBar();
}
if (streamMode == 'stills') {
if ( streamMode == 'stills' ) {
slider.autosize();
alarmCue.html(renderAlarmCues($j('#thumbsSliderPanel')));
} else {
alarmCue.html(renderAlarmCues(eventViewer));//just re-render alarmCues. skip ajax call
}
if (scale == "auto") {
if ( scale == "auto" ) {
Cookie.write('zmEventScaleAuto', 'auto', {duration: 10*365});
}else{
} else {
Cookie.write('zmEventScale'+eventData.MonitorId, scale, {duration: 10*365});
Cookie.dispose('zmEventScaleAuto');
}
@ -193,7 +202,7 @@ var lastEventId = 0;
var zmsBroke = false; //Use alternate navigation if zms has crashed
function getCmdResponse( respObj, respText ) {
if ( checkStreamForErrors( "getCmdResponse", respObj ) ) {
if ( checkStreamForErrors("getCmdResponse", respObj) ) {
console.log('Got an error from getCmdResponse');
console.log(respObj);
console.log(respText);

View File

@ -97,7 +97,8 @@ function saveFilter( element ) {
form.target = window.name;
form.elements['action'].value = element.value;
form.action = thisUrl + '?view=filter';
form.submit();
//form.submit();
// Submit is done by the button type="submit"
}
function deleteFilter( element, name ) {

View File

@ -1,51 +1,53 @@
var requestQueue = new Request.Queue( { concurrent: monitorData.length, stopOnFailure: false } );
var requestQueue = new Request.Queue({
concurrent: monitorData.length,
stopOnFailure: false
});
function Monitor( monitorData ) {
function Monitor(monitorData) {
this.id = monitorData.id;
this.connKey = monitorData.connKey;
this.server_url = monitorData.server_url;
this.url = monitorData.url;
this.status = null;
this.alarmState = STATE_IDLE;
this.lastAlarmState = STATE_IDLE;
this.streamCmdParms = this.server_url+'?view=request&request=stream&connkey='+this.connKey;
this.streamCmdParms = 'view=request&request=stream&connkey='+this.connKey;
this.onclick = monitorData.onclick;
if ( auth_hash )
this.streamCmdParms += '&auth='+auth_hash;
this.streamCmdTimer = null;
this.type = monitorData.type;
this.refresh = monitorData.refresh;
this.start = function( delay ) {
this.start = function(delay) {
if ( this.streamCmdQuery )
this.streamCmdTimer = this.streamCmdQuery.delay( delay, this );
this.streamCmdTimer = this.streamCmdQuery.delay(delay, this);
else
console.log("No streamCmdQuery");
};
this.setStateClass = function( element, stateClass ) {
if ( !element.hasClass( stateClass ) ) {
this.setStateClass = function(element, stateClass) {
if ( !element.hasClass(stateClass) ) {
if ( stateClass != 'alarm' )
element.removeClass( 'alarm' );
element.removeClass('alarm');
if ( stateClass != 'alert' )
element.removeClass( 'alert' );
element.removeClass('alert');
if ( stateClass != 'idle' )
element.removeClass( 'idle' );
element.addClass( stateClass );
element.removeClass('idle');
element.addClass(stateClass);
}
};
this.onError = function( text, error ) {
this.onError = function(text, error) {
console.log('onerror: ' + text + ' error:'+error);
// Requeue, but want to wait a while.
var streamCmdTimeout = 1000*statusRefreshTimeout;
this.streamCmdTimer = this.streamCmdQuery.delay( streamCmdTimeout, this );
var streamCmdTimeout = 10*statusRefreshTimeout;
this.streamCmdTimer = this.streamCmdQuery.delay(streamCmdTimeout, this);
};
this.onFailure = function( xhr ) {
this.onFailure = function(xhr) {
console.log('onFailure: ' + this.connKey);
console.log(xhr );
console.log(xhr);
if ( ! requestQueue.hasNext("cmdReq"+this.id) ) {
console.log("Not requeuing because there is one already");
requestQueue.addRequest( "cmdReq"+this.id, this.streamCmdReq );
requestQueue.addRequest("cmdReq"+this.id, this.streamCmdReq);
}
if ( 0 ) {
// Requeue, but want to wait a while.
@ -58,9 +60,9 @@ function Monitor( monitorData ) {
console.log("done failure");
};
this.getStreamCmdResponse = function( respObj, respText ) {
this.getStreamCmdResponse = function(respObj, respText) {
if ( this.streamCmdTimer )
this.streamCmdTimer = clearTimeout( this.streamCmdTimer );
this.streamCmdTimer = clearTimeout(this.streamCmdTimer);
var stream = $j('#liveStream'+this.id)[0];
@ -78,11 +80,11 @@ function Monitor( monitorData ) {
stateClass = "idle";
if ( (!COMPACT_MONTAGE) && (this.type != 'WebSite') ) {
$('fpsValue'+this.id).set( 'text', this.status.fps );
$('stateValue'+this.id).set( 'text', stateStrings[this.alarmState] );
this.setStateClass( $('monitorState'+this.id), stateClass );
$('fpsValue'+this.id).set('text', this.status.fps);
$('stateValue'+this.id).set('text', stateStrings[this.alarmState]);
this.setStateClass($('monitorState'+this.id), stateClass);
}
this.setStateClass( $('monitor'+this.id), stateClass );
this.setStateClass($('monitor'+this.id), stateClass);
/*Stream could be an applet so can't use moo tools*/
stream.className = stateClass;
@ -96,7 +98,7 @@ function Monitor( monitorData ) {
if ( newAlarm ) {
if ( false && SOUND_ON_ALARM ) {
// Enable the alarm sound
$('alarmSound').removeClass( 'hidden' );
$('alarmSound').removeClass('hidden');
}
if ( POPUP_ON_ALARM ) {
windowToFront();
@ -105,83 +107,84 @@ function Monitor( monitorData ) {
if ( false && SOUND_ON_ALARM ) {
if ( oldAlarm ) {
// Disable alarm sound
$('alarmSound').addClass( 'hidden' );
$('alarmSound').addClass('hidden');
}
}
if ( this.status.auth ) {
if ( this.status.auth != auth_hash ) {
// Try to reload the image stream.
if ( stream )
stream.src = stream.src.replace( /auth=\w+/i, 'auth='+this.status.auth );
console.log("Changed auth from " + auth_hash + " to " + this.status.auth );
stream.src = stream.src.replace(/auth=\w+/i, 'auth='+this.status.auth);
console.log("Changed auth from " + auth_hash + " to " + this.status.auth);
auth_hash = this.status.auth;
}
} // end if have a new auth hash
} // end if has state
} else {
console.error( respObj.message );
console.error(respObj.message);
// Try to reload the image stream.
if ( stream ) {
if ( stream.src ) {
console.log('Reloading stream: ' + stream.src );
console.log('Reloading stream: ' + stream.src);
stream.src = stream.src.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) ));
} else {
}
} else {
console.log( 'No stream to reload?' );
console.log('No stream to reload?');
}
} // end if Ok or not
var streamCmdTimeout = statusRefreshTimeout;
// The idea here is if we are alarmed, do updates faster. However, there is a timeout in the php side which isn't getting modified, so this may cause a problem. Also the server may only be able to update so fast.
// The idea here is if we are alarmed, do updates faster.
// However, there is a timeout in the php side which isn't getting modified,
// so this may cause a problem. Also the server may only be able to update so fast.
//if ( this.alarmState == STATE_ALARM || this.alarmState == STATE_ALERT ) {
//streamCmdTimeout = streamCmdTimeout/5;
//}
this.streamCmdTimer = this.streamCmdQuery.delay( streamCmdTimeout, this );
this.streamCmdTimer = this.streamCmdQuery.delay(streamCmdTimeout, this);
this.lastAlarmState = this.alarmState;
};
this.streamCmdQuery = function( resent ) {
this.streamCmdQuery = function(resent) {
if ( resent ) {
console.log( this.connKey+": Resending" );
console.log(this.connKey+": timeout: Resending");
this.streamCmdReq.cancel();
}
//console.log("Starting CmdQuery for " + this.connKey );
if ( this.type != 'WebSite' ) {
this.streamCmdReq.send( this.streamCmdParms+"&command="+CMD_QUERY );
this.streamCmdReq.send(this.streamCmdParms+"&command="+CMD_QUERY);
}
};
if ( this.type != 'WebSite' ) {
this.streamCmdReq = new Request.JSON( {
url: this.server_url,
url: this.url,
method: 'get',
timeout: 1000+AJAX_TIMEOUT,
onSuccess: this.getStreamCmdResponse.bind( this ),
onTimeout: this.streamCmdQuery.bind( this, true ),
timeout: AJAX_TIMEOUT,
onSuccess: this.getStreamCmdResponse.bind(this),
onTimeout: this.streamCmdQuery.bind(this, true),
onError: this.onError.bind(this),
onFailure: this.onFailure.bind(this),
link: 'cancel'
} );
console.log("queueing for " + this.id + " " + this.connKey );
requestQueue.addRequest( "cmdReq"+this.id, this.streamCmdReq );
console.log("queueing for " + this.id + " " + this.connKey + " timeout is: " + AJAX_TIMEOUT);
requestQueue.addRequest("cmdReq"+this.id, this.streamCmdReq);
}
} // end function Monitor
}
function selectLayout( element ) {
function selectLayout(element) {
layout = $j(element).val();
if ( layout_id = parseInt(layout) ) {
layout = layouts[layout];
console.log(layout);
for ( var i = 0; i < monitors.length; i++ ) {
for ( var i = 0, length = monitors.length; i < length; i++ ) {
monitor = monitors[i];
// Need to clear the current positioning, and apply the new
monitor_frame = $j('#monitorFrame'+monitor.id);
if ( ! monitor_frame ) {
console.log("Error finding frame for " + monitor.id );
console.log("Error finding frame for " + monitor.id);
continue;
}
@ -199,7 +202,7 @@ function selectLayout( element ) {
styles = layout.Positions['mId'+monitor.id];
for ( style in styles ) {
monitor_frame.css(style, styles[style]);
console.log("Applying " + style + ' : ' + styles[style] );
console.log("Applying " + style + ' : ' + styles[style]);
}
} else {
console.log("No Monitor styles to apply");
@ -209,21 +212,20 @@ function selectLayout( element ) {
if ( ! layout ) {
return;
}
Cookie.write( 'zmMontageLayout', layout_id, { duration: 10*365 } );
Cookie.write('zmMontageLayout', layout_id, { duration: 10*365 });
if ( layouts[layout_id].Name != 'Freeform' ) { // 'montage_freeform.css' ) {
Cookie.write( 'zmMontageScale', '', { duration: 10*365 } );
$('scale').set('value', '' );
$('scale').set('value', '');
$('width').set('value', '');
if ( 1 ) {
for ( var x = 0; x < monitors.length; x++ ) {
var monitor = monitors[x];
var streamImg = $( 'liveStream'+monitor.id );
for ( var i = 0, length = monitors.length; i < length; i++ ) {
var monitor = monitors[i];
var streamImg = $('liveStream'+monitor.id);
if ( streamImg ) {
if ( streamImg.nodeName == 'IMG' ) {
var src = streamImg.src;
src = src.replace(/width=[\.\d]+/i,'width=0' );
if ( src != streamImg.src ) {
streamImg.src='';
streamImg.src = '';
streamImg.src = src;
}
} else if ( streamImg.nodeName == 'APPLET' || streamImg.nodeName == 'OBJECT' ) {
@ -236,37 +238,39 @@ if ( 1 ) {
zonesSVG.style.width = '';
}
} // end foreach monitor
}
}
}
} // end function selectLayout(element)
function changeSize() {
var width = $('width').get('value');
var height = $('height').get('value');
for ( var x = 0; x < monitors.length; x++ ) {
var monitor = monitors[x];
for ( var i = 0, length = monitors.length; i < length; i++ ) {
var monitor = monitors[i];
// Scale the frame
monitor_frame = $j('#monitorFrame'+monitor.id);
if ( ! monitor_frame ) {
console.log("Error finding frame for " + monitor.id );
if ( !monitor_frame ) {
console.log("Error finding frame for " + monitor.id);
continue;
}
monitor_frame.css('width',width?width+'px':'');
monitor_frame.css('height',height?height+'px':'');
if ( width )
monitor_frame.css('width', width+'px');
if ( height )
monitor_frame.css('height', height+'px');
/*Stream could be an applet so can't use moo tools*/
var streamImg = $( 'liveStream'+monitor.id );
var streamImg = $('liveStream'+monitor.id);
if ( streamImg ) {
if ( streamImg.nodeName == 'IMG' ) {
var src = streamImg.src;
streamImg.src='';
src = src.replace(/width=[\.\d]+/i,'width='+width );
src = src.replace(/height=[\.\d]+/i,'height='+height );
streamImg.src = '';
src = src.replace(/width=[\.\d]+/i,'width='+width);
src = src.replace(/height=[\.\d]+/i,'height='+height);
src = src.replace(/rand=\d+/i,'rand='+Math.floor((Math.random() * 1000000) ));
streamImg.src = src;
}
streamImg.style.width = width? width + "px" : null;
streamImg.style.width = width ? width + "px" : null;
streamImg.style.height = height ? height + "px" : null;
//streamImg.style.height = '';
}
@ -276,10 +280,10 @@ function changeSize() {
zonesSVG.style.height = height + "px";
}
}
$('scale').set('value', '' );
Cookie.write( 'zmMontageScale', '', { duration: 10*365 } );
Cookie.write( 'zmMontageWidth', width, { duration: 10*365 } );
Cookie.write( 'zmMontageHeight', height, { duration: 10*365 } );
$('scale').set('value', '');
Cookie.write('zmMontageScale', '', { duration: 10*365 });
Cookie.write('zmMontageWidth', width, { duration: 10*365 });
Cookie.write('zmMontageHeight', height, { duration: 10*365 });
selectLayout('#zmMontageLayout');
} // end function changeSize()
@ -287,22 +291,22 @@ function changeScale() {
var scale = $('scale').get('value');
$('width').set('value', '');
$('height').set('value', '');
Cookie.write( 'zmMontageScale', scale, { duration: 10*365 } );
Cookie.write( 'zmMontageWidth', '', { duration: 10*365 } );
Cookie.write( 'zmMontageHeight', '', { duration: 10*365 } );
if ( ! scale ) {
Cookie.write('zmMontageScale', scale, { duration: 10*365 });
Cookie.write('zmMontageWidth', '', { duration: 10*365 });
Cookie.write('zmMontageHeight', '', { duration: 10*365 });
if ( !scale ) {
selectLayout('#zmMontageLayout');
return;
}
for ( var x = 0; x < monitors.length; x++ ) {
var monitor = monitors[x];
var newWidth = ( monitorData[x].width * scale ) / SCALE_BASE;
var newHeight = ( monitorData[x].height * scale ) / SCALE_BASE;
for ( var i = 0, length = monitors.length; i < length; i++ ) {
var monitor = monitors[i];
var newWidth = ( monitorData[i].width * scale ) / SCALE_BASE;
var newHeight = ( monitorData[i].height * scale ) / SCALE_BASE;
// Scale the frame
monitor_frame = $j('#monitorFrame'+monitor.id);
if ( ! monitor_frame ) {
console.log("Error finding frame for " + monitor.id );
if ( !monitor_frame ) {
console.log("Error finding frame for " + monitor.id);
continue;
}
if ( width )
@ -310,16 +314,16 @@ function changeScale() {
if ( height )
monitor_frame.css('height',height+'px');
/*Stream could be an applet so can't use moo tools*/
var streamImg = $j('#liveStream'+monitor.id )[0];
var streamImg = $j('#liveStream'+monitor.id)[0];
if ( streamImg ) {
if ( streamImg.nodeName == 'IMG' ) {
var src = streamImg.src;
streamImg.src='';
streamImg.src = '';
//src = src.replace(/rand=\d+/i,'rand='+Math.floor((Math.random() * 1000000) ));
src = src.replace(/scale=[\.\d]+/i,'scale='+ scale );
src = src.replace(/width=[\.\d]+/i,'width='+newWidth );
src = src.replace(/height=[\.\d]+/i,'height='+newHeight );
src = src.replace(/scale=[\.\d]+/i, 'scale='+scale);
src = src.replace(/width=[\.\d]+/i, 'width='+newWidth);
src = src.replace(/height=[\.\d]+/i, 'height='+newHeight);
streamImg.src = src;
}
streamImg.style.width = newWidth + "px";
@ -342,10 +346,10 @@ function edit_layout(button) {
// Turn off the onclick on the image.
for ( var i = 0; i < monitors.length; i++ ) {
for ( var i = 0, length = monitors.length; i < length; i++ ) {
var monitor = monitors[i];
monitor_feed = $j('#imageFeed'+monitor.id)[0];
monitor_feed.click('');
monitor_feed.onclick = '';
};
$j('#monitors .monitorFrame').draggable({
@ -360,7 +364,7 @@ function save_layout(button) {
var form = button.form;
// In fixed positioning, order doesn't matter. In floating positioning, it does.
var Positions = {};
for ( var i = 0; i < monitors.length; i++ ) {
for ( var i = 0, length = monitors.length; i < lenth; i++ ) {
var monitor = monitors[i];
monitor_frame = $j('#monitorFrame'+monitor.id);
@ -375,16 +379,17 @@ function save_layout(button) {
float: monitor_frame.css('float'),
};
} // end foreach monitor
form.Positions.value = JSON.stringify( Positions );
form.Positions.value = JSON.stringify(Positions);
form.submit();
}
} // end function save_layout
function cancel_layout(button) {
$j('#SaveLayout').hide();
$j('#EditLayout').show();
for ( var i = 0; i < monitors.length; i++ ) {
for ( var i = 0, length = monitors.length; i < length; i++ ) {
var monitor = monitors[i];
monitor_feed = $j('#imageFeed'+monitor.id);
monitor_feed.click( monitor.onclick );
monitor_feed.click(monitor.onclick);
};
selectLayout('#zmMontageLayout');
}
@ -400,28 +405,28 @@ function initPage() {
jQuery("#hdrbutton").click(function(){
jQuery("#flipMontageHeader").slideToggle("slow");
jQuery("#hdrbutton").toggleClass('glyphicon-menu-down').toggleClass('glyphicon-menu-up');
Cookie.write( 'zmMontageHeaderFlip', jQuery('#hdrbutton').hasClass('glyphicon-menu-up') ? 'up' : 'down', { duration: 10*365 } );
});
});
if ( Cookie.read('zmMontageHeaderFlip') == 'down' ) {
// The chosen dropdowns require the selects to be visible, so once chosen has initialized, we can hide the header
jQuery("#flipMontageHeader").slideToggle("fast");
jQuery("#hdrbutton").toggleClass('glyphicon-menu-down').toggleClass('glyphicon-menu-up');
}
for ( var i = 0; i < monitorData.length; i++ ) {
for ( var i = 0, length = monitorData.length; i < length; i++ ) {
monitors[i] = new Monitor(monitorData[i]);
// Start the fps and status updates. give a random delay so that we don't assault the server
var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout );
monitors[i].start(delay);
var interval = monitors[i].refresh;
monitors[i].start( delay );
if ( monitors[i].type == 'WebSite' && interval > 0 ) {
setInterval(reloadWebSite, interval*1000, i);
}
}
selectLayout('#zmMontageLayout');
for ( var i = 0; i < monitorData.length; i++ ) {
if ( monitors[i].type == 'WebSite' )
continue;
var delay = Math.round( (Math.random()+0.75)*statusRefreshTimeout );
console.log("Delay for monitor " + monitorData[i].id + " is " + delay );
monitors[i].streamCmdQuery.delay( delay, monitors[i] );
//monitors[i].zm_startup(delay);
}
}
// Kick everything off
window.addEvent( 'domready', initPage );
window.addEvent('domready', initPage);

View File

@ -35,7 +35,7 @@ monitorData[monitorData.length] = {
'connKey': <?php echo $monitor->connKey() ?>,
'width': <?php echo $monitor->Width() ?>,
'height':<?php echo $monitor->Height() ?>,
'server_url': '<?php echo $monitor->Url() ?>',
'url': '<?php echo $monitor->Url() ?>',
'onclick': function(){createPopup( '?view=watch&mid=<?php echo $monitor->Id() ?>', 'zmWatch<?php echo $monitor->Id() ?>', 'watch', <?php echo reScale( $monitor->Width(), $monitor->PopupScale() ); ?>, <?php echo reScale( $monitor->Height(), $monitor->PopupScale() ); ?> );},
'type': '<?php echo $monitor->Type() ?>',
'refresh': '<?php echo $monitor->Refresh() ?>'

View File

@ -53,7 +53,7 @@ if ( !$liveMode ) {
$next_frames = array();
if ( $result = dbQuery($frameSql) ) {
if ( $result = dbQuery($framesSql) ) {
$next_frame = null;
while( $frame = $result->fetch(PDO::FETCH_ASSOC) ) {
$event_id = $frame['EventId'];

View File

@ -110,7 +110,7 @@ if ( monitorType != 'WebSite' ) {
if ( auth_hash )
streamCmdParms += '&auth='+auth_hash;
var streamCmdReq = new Request.JSON( {
url: monitorUrl+thisUrl,
url: monitorUrl,
method: 'get',
timeout: AJAX_TIMEOUT,
link: 'chain',
@ -366,7 +366,7 @@ if ( monitorType != 'WebSite' ) {
var statusCmdParms = "view=request&request=status&entity=monitor&id="+monitorId+"&element[]=Status&element[]=FrameRate";
if ( auth_hash )
statusCmdParms += '&auth='+auth_hash;
var statusCmdReq = new Request.JSON( { url: monitorUrl+thisUrl, method: 'get', data: statusCmdParms, timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getStatusCmdResponse } );
var statusCmdReq = new Request.JSON( { url: monitorUrl, method: 'get', data: statusCmdParms, timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getStatusCmdResponse } );
var statusCmdTimer = null;
}
@ -396,8 +396,8 @@ if ( monitorType != 'WebSite' ) {
if ( auth_hash )
alarmCmdParms += '&auth='+auth_hash;
var alarmCmdReq = new Request.JSON( {
url: monitorUrl+thisUrl,
method: 'post',
url: monitorUrl,
method: 'get',
timeout: AJAX_TIMEOUT,
link: 'cancel',
onSuccess: getAlarmCmdResponse,
@ -454,7 +454,15 @@ if ( monitorType != 'WebSite' ) {
var eventCmdParms = "view=request&request=status&entity=events&id="+monitorId+"&count="+maxDisplayEvents+"&sort=Id%20desc";
if ( auth_hash )
eventCmdParms += '&auth='+auth_hash;
var eventCmdReq = new Request.JSON( { url: thisUrl, method: 'post', timeout: AJAX_TIMEOUT, data: eventCmdParms, link: 'cancel', onSuccess: getEventCmdResponse, onTimeout: eventCmdQuery } );
var eventCmdReq = new Request.JSON( {
url: monitorUrl,
method: 'get',
timeout: AJAX_TIMEOUT,
data: eventCmdParms,
link: 'cancel',
onSuccess: getEventCmdResponse,
onTimeout: eventCmdQuery
} );
var eventCmdTimer = null;
var eventCmdFirst = true;
}
@ -564,7 +572,7 @@ if ( monitorType != 'WebSite' ) {
var controlParms = "view=request&request=control&id="+monitorId;
if ( auth_hash )
controlParms += '&auth='+auth_hash;
var controlReq = new Request.JSON( { url: thisUrl, method: 'post', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getControlResponse } );
var controlReq = new Request.JSON( { url: monitorUrl, method: 'post', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getControlResponse } );
}
function getControlResponse( respObj, respText ) {

View File

@ -49,9 +49,9 @@ function validateForm( form ) {
}
if ( errors.length ) {
alert( errors.join( "\n" ) );
return( false );
return false;
}
return( true );
return true;
}
function submitForm( form ) {
@ -324,7 +324,7 @@ function limitPointValue( point, loVal, hiVal ) {
}
function updateArea( ) {
area = Polygon_calcArea( zone['Points'] );
area = Polygon_calcArea(zone['Points']);
zone.Area = area;
var form = $('zoneForm');
form.elements['newZone[Area]'].value = area;
@ -333,7 +333,7 @@ function updateArea( ) {
} else if ( form.elements['newZone[Units]'].value == 'Pixels' ) {
form.elements['newZone[TempArea]'].value = area;
} else {
alert("Unknown units: " + form.elements['newZone[Units]'].value );
alert("Unknown units: " + form.elements['newZone[Units]'].value);
}
}
@ -368,15 +368,23 @@ function saveChanges( element ) {
if ( form.elements['newZone[Type]'].value == 'Privacy' ) {
alert( 'Capture process for this monitor will be restarted for the Privacy zone changes to take effect.' );
}
return( true );
return true;
}
return( false );
return false;
}
function drawZonePoints() {
$('imageFrame').getElements( 'div.zonePoint' ).each( function( element ) { element.destroy(); } );
for ( var i = 0; i < zone['Points'].length; i++ ) {
var div = new Element( 'div', { 'id': 'point'+i, 'class': 'zonePoint', 'title': 'Point '+(i+1), 'styles': { 'left': zone['Points'][i].x, 'top': zone['Points'][i].y } } );
var div = new Element( 'div', {
'id': 'point'+i,
'class': 'zonePoint',
'title': 'Point '+(i+1),
'styles': {
'left': zone['Points'][i].x,
'top': zone['Points'][i].y
}
} );
div.addEvent( 'mouseover', highlightOn.pass( i ) );
div.addEvent( 'mouseout', highlightOff.pass( i ) );
div.inject( $('imageFrame') );
@ -410,9 +418,11 @@ function drawZonePoints() {
cell.inject( row );
cell = new Element( 'td' );
new Element( 'a', { 'href': '#', 'events': { 'click': addPoint.pass( i ) } } ).set( 'text', '+' ).inject( cell );
if ( zone['Points'].length > 3 )
new Element( 'a', { 'id': 'delete'+i, 'href': '#', 'events': { 'click': delPoint.pass( i ) } } ).set( 'text', '-' ).inject( cell );
new Element( 'button', { 'type': 'button', 'events': { 'click': addPoint.pass( i ) } } ).set( 'text', '+' ).inject( cell );
if ( zone['Points'].length > 3 ) {
cell.appendText(' ');
new Element( 'button', { 'id': 'delete'+i, 'type': 'button', 'events': { 'click': delPoint.pass( i ) } } ).set( 'text', '-' ).inject( cell );
}
cell.inject( row );
row.inject( tables[i%tables.length].getElement( 'tbody' ) );
@ -471,7 +481,13 @@ function setAlarmState( currentAlarmState ) {
}
var streamCmdParms = "view=request&request=stream&connkey="+connKey;
var streamCmdReq = new Request.JSON( { url: monitorUrl+thisUrl, method: 'post', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getStreamCmdResponse } );
var streamCmdReq = new Request.JSON( {
url: monitorUrl,
method: 'get',
timeout: AJAX_TIMEOUT,
link: 'cancel',
onSuccess: getStreamCmdResponse
} );
var streamCmdTimer = null;
var streamStatus;
@ -546,7 +562,14 @@ function streamCmdQuery() {
}
var statusCmdParms = "view=request&request=status&entity=monitor&id="+monitorId+"&element[]=Status&element[]=FrameRate";
var statusCmdReq = new Request.JSON( { url: monitorUrl+thisUrl, method: 'post', data: statusCmdParms, timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getStatusCmdResponse } );
var statusCmdReq = new Request.JSON( {
url: monitorUrl,
method: 'get',
data: statusCmdParms,
timeout: AJAX_TIMEOUT,
link: 'cancel',
onSuccess: getStatusCmdResponse
} );
var statusCmdTimer = null;
function getStatusCmdResponse( respObj, respText ) {
@ -652,11 +675,13 @@ function initPage() {
// Imported from watch.js and modified for new zone edit view
//
var delay = (Math.random()+0.1)*statusRefreshTimeout;
//console.log("Delay for status updates is: " + delay );
if ( streamMode == "single" ) {
statusCmdTimer = statusCmdQuery.delay( (Math.random()+0.1)*statusRefreshTimeout );
statusCmdTimer = statusCmdQuery.delay( delay );
watchdogCheck.pass('status').periodical(statusRefreshTimeout*2);
} else {
streamCmdTimer = streamCmdQuery.delay( (Math.random()+0.1)*statusRefreshTimeout );
streamCmdTimer = streamCmdQuery.delay( delay );
watchdogCheck.pass('stream').periodical(statusRefreshTimeout*2);
}

View File

@ -101,7 +101,7 @@ var streamMode = "<?php echo $streamMode ?>";
var connKey = '<?php echo $connkey ?>';
var monitorId = <?php echo $monitor->Id() ?>;
var monitorUrl = '<?php echo ( $monitor->Server()->Url() ) ?>';
var monitorUrl = '<?php echo ( $monitor->Url() ) ?>';
var streamSrc = "<?php echo preg_replace( '/&amp;/', '&', $streamSrc ) ?>";

View File

@ -1,5 +1,5 @@
var streamCmdParms = "view=request&request=stream&connkey="+connKey;
var streamCmdReq = new Request.JSON( { url: monitorUrl+thisUrl, method: 'post', timeout: AJAX_TIMEOUT, link: 'cancel' } );
var streamCmdReq = new Request.JSON( { url: monitorUrl, method: 'post', timeout: AJAX_TIMEOUT, link: 'cancel' } );
function streamCmdQuit( action ) {
if ( action )

View File

@ -1,4 +1,4 @@
var connKey = '<?php echo $connkey ?>';
var monitorUrl = '<?php echo ( $monitor->Server()->Url() ) ?>';
var monitorUrl = '<?php echo ( $monitor->Url() ) ?>';
var CMD_QUIT = <?php echo CMD_QUIT ?>;

View File

@ -45,16 +45,15 @@ xhtmlHeaders(__FILE__, translate('SystemLog') );
<tr class="row">
<td class="col text-center">
<div class="btn-group">
<button type="button" class="btn btn-sm" onclick="expandLog()"> <?php echo translate('More') ?></button>
<button type="button" class="btn btn-sm" onclick="clearLog()"> <?php echo translate('Clear') ?></button>
<button type="button" class="btn btn-sm" onclick="refreshLog()"> <?php echo translate('Refresh') ?></button>
<button type="button" class="btn btn-sm" onclick="exportLog()"> <?php echo translate('Export') ?></button>
<button type="button" class="btn btn-sm" onclick="closeWindow()"> <?php echo translate('Close') ?></button>
<button type="button" onclick="expandLog()"><?php echo translate('More') ?></button>
<button type="button" onclick="clearLog()"><?php echo translate('Clear') ?></button>
<button type="button" onclick="refreshLog()"><?php echo translate('Refresh') ?></button>
<button type="button" onclick="exportLog()"><?php echo translate('Export') ?></button>
<button type="button" onclick="closeWindow()"><?php echo translate('Close') ?></button>
</div> <!--btn-->
</td>
</tr>
</table>
</div> <!--header-->
<div id="content">
<div id="filters">
@ -89,7 +88,7 @@ xhtmlHeaders(__FILE__, translate('SystemLog') );
</td>
</tr>
</table>
<input type="reset" value="<?php echo translate('Reset') ?>" onclick="resetLog()"/>
<button type="reset" onclick="resetLog()"><?php echo translate('Reset') ?></button>
</div>
<form name="logForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>">
<input type="hidden" name="view" value="<?php echo $view ?>"/>
@ -137,8 +136,8 @@ xhtmlHeaders(__FILE__, translate('SystemLog') );
<div id="exportError">
<?php echo translate('ExportFailed') ?>: <span id="exportErrorText"></span>
</div>
<input type="button" id="exportButton" value="<?php echo translate('Export') ?>" onclick="exportRequest()"/>
<input type="button" value="<?php echo translate('Cancel') ?>" class="overlayCloser"/>
<button type="button" id="exportButton" value="Export" onclick="exportRequest()"><?php echo translate('Export') ?></button>
<button type="button" value="Cancel" class="overlayCloser"><?php echo translate('Cancel') ?></button>
</form>
</div>
</div>

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