Merge branch 'master' into add_export_to_filter

This commit is contained in:
Isaac Connor 2018-09-25 14:54:06 -04:00
commit 01f1ffb591
142 changed files with 5710 additions and 3543 deletions

View File

@ -1,6 +1,6 @@
You should only file an issue if you found a bug. Feature and enhancement requests, general discussions and support questions should occur in one of the following areas:
- The ZoneMinder IRC channel - irc.freenode.net #zoneminder
- The [ZoneMinder-Chat Slack channel](https://zoneminder-chat.herokuapp.com/)
- The [ZoneMinder Forum](https://forums.zoneminder.com/)
**Do not post feature or enhancement requests, general discussions or support questions here.**

View File

@ -3,6 +3,8 @@ ZoneMinder
[![Build Status](https://travis-ci.org/ZoneMinder/zoneminder.png)](https://travis-ci.org/ZoneMinder/zoneminder) [![Bountysource](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received)
[![Join Slack](https://github.com/ozonesecurity/ozonebase/blob/master/img/slacksm.png?raw=true)](https://zoneminder-chat.herokuapp.com)
All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org
## Overview

View File

@ -50,4 +50,4 @@ ZM_PATH_SWAP=@ZM_TMPDIR@
# Full path to optional arp binary
# ZoneMinder will find the arp binary automatically on most systems
ZM_PATH_ARP=@ZM_PATH_ARP@
ZM_PATH_ARP="@ZM_PATH_ARP@"

View File

@ -517,6 +517,7 @@ CREATE TABLE `Monitors` (
`ArchivedEvents` int(10) default NULL,
`ArchivedEventDiskSpace` bigint default NULL,
`ZoneCount` TINYINT NOT NULL DEFAULT 0,
`Refresh` int(10) unsigned default NULL,
PRIMARY KEY (`Id`)
) ENGINE=@ZM_MYSQL_ENGINE@;
@ -580,7 +581,7 @@ DROP TABLE IF EXISTS `Stats`;
CREATE TABLE `Stats` (
`MonitorId` int(10) unsigned NOT NULL default '0',
`ZoneId` int(10) unsigned NOT NULL default '0',
`EventId` int(10) unsigned NOT NULL default '0',
`EventId` BIGINT UNSIGNED NOT NULL,
`FrameId` int(10) unsigned NOT NULL default '0',
`PixelDiff` tinyint(3) unsigned NOT NULL default '0',
`AlarmPixels` int(10) unsigned NOT NULL default '0',
@ -778,6 +779,10 @@ INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-411','Ffmpeg','Reolink',0,0,1,1
INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-420','Ffmpeg','Reolink',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'Dericam P2','Ffmpeg','DericamP2',0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'Dahua','Remote','Dahua',0,0,0,1,0,0,1,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,1,0,1,0,0,0,0,1,1,8,0,0,1,0,0,0,0,1,1,8,0,0,0,0);
--
-- Add some monitor preset values
--
@ -819,6 +824,7 @@ INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg','Remote','http',0,0,'
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/GetData.cgi',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Gadspot IP, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,5.0,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'IP Webcam by Pavel Khlebovich 1920x1080','Remote','/dev/video<?>','0',255,'http','simple','<ip-address>','8080','/video','',1920,1080,0,NULL,0,'0','','',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'VEO Observer, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/Jpeg/CamImg.jpg',NULL,NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Blue Net Video Server, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/cgi-bin/image.cgi?control=0&id=admin&passwd=admin',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,'ACTi IP, mpeg4, unicast','Remote',NULL,NULL,NULL,'rtsp','rtpUni','<ip-address>',7070,'','/track',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);

View File

@ -5,7 +5,7 @@
-- Add Refresh column to Monitors table
--
ALTER TABLE `zm`.`Monitors`
ALTER TABLE `Monitors`
CHANGE COLUMN `Type` `Type` ENUM('Local', 'Remote', 'File', 'Ffmpeg', 'Libvlc', 'cURL', 'WebSite') NOT NULL DEFAULT 'Local' ;
SET @s = (SELECT IF(
@ -16,7 +16,7 @@ SET @s = (SELECT IF(
AND column_name = 'Refresh'
) > 0,
"SELECT 'Column Refresh exists in Monitors'",
"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL"
"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL AFTER `ZoneCount`"
));
PREPARE stmt FROM @s;

23
db/zm_update-1.31.45.sql Normal file
View File

@ -0,0 +1,23 @@
--
-- This updates a 1.31.44 database to 1.31.45
--
-- Add WebSite enum to Monitor.Type
-- Add Refresh column to Monitors table
-- This is the same as the update to 1.31.43, but due to Refresh not being added to zm_create.sql.in we need to have it
-- again in order to fix people who did a fresh install from 1.31.43 or 1.31.44.
--
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Monitors'
AND table_schema = DATABASE()
AND column_name = 'Refresh'
) > 0,
"SELECT 'Column Refresh exists in Monitors'",
"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL AFTER `ZoneCount`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

2
db/zm_update-1.31.46.sql Normal file
View File

@ -0,0 +1,2 @@
ALTER TABLE Stats MODIFY COLUMN EventId bigint unsigned NOT NULL;

2
db/zm_update-1.31.47.sql Normal file
View File

@ -0,0 +1,2 @@
ALTER TABLE Frames MODIFY COLUMN EventId bigint unsigned NOT NULL;

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

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

View File

@ -37,11 +37,13 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
, libphp-serialization-perl
, libdate-manip-perl, libmime-lite-perl, libmime-tools-perl, libdbd-mysql-perl
, libwww-perl, libarchive-tar-perl, libarchive-zip-perl, libdevice-serialport-perl
, libmodule-load-perl, libsys-mmap-perl, libjson-any-perl
, libmodule-load-perl, libsys-mmap-perl, libjson-any-perl, libjson-maybexs-perl
, libnet-sftp-foreign-perl, libio-pty-perl, libexpect-perl
, libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl, libio-socket-multicast-perl, libdigest-sha-perl
, libsys-cpu-perl, libsys-meminfo-perl
, libdata-uuid-perl
,libnumber-bytes-human-perl
,libfile-slurp-perl
, libpcre3
, ffmpeg | libav-tools, libavdevice53 | libavdevice55 | libavdevice57
, rsyslog | system-log-daemon

View File

@ -1,4 +1,4 @@
# CMakeLists.txt for the Redhat/CentOS Target Distro.
# CMakeLists.txt for the Redhat Target Distros.
# Display a message to show the RHEL build options are being processed.
if(ZM_TARGET_DISTRO MATCHES "^el")
@ -20,11 +20,16 @@ if(ZM_WEB_USER STREQUAL "nginx")
configure_file(nginx/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
configure_file(nginx/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY)
configure_file(nginx/zoneminder.php-fpm.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.php-fpm.conf @ONLY)
configure_file(nginx/README.Fedora ${CMAKE_CURRENT_SOURCE_DIR}/readme/README.Fedora COPYONLY)
configure_file(nginx/README.Fedora ${CMAKE_CURRENT_SOURCE_DIR}/readme/README COPYONLY)
else(ZM_WEB_USER STREQUAL "nginx")
configure_file(systemd/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY)
configure_file(apache/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
configure_file(systemd/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY)
if( ZM_TARGET_DISTRO MATCHES "^fc")
configure_file(readme/README.Fedora ${CMAKE_CURRENT_SOURCE_DIR}/readme/README COPYONLY)
else( ZM_TARGET_DISTRO MATCHES "^fc")
configure_file(readme/README.Redhat7 ${CMAKE_CURRENT_SOURCE_DIR}/readme/README COPYONLY)
endif( ZM_TARGET_DISTRO MATCHES "^fc")
endif(ZM_WEB_USER STREQUAL "nginx")
# Create several empty folders

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

@ -22,11 +22,10 @@
%global with_apcu_bc 1
%endif
%global readme_suffix %{?rhel:Redhat%{?rhel}}%{!?rhel:Fedora}
%global _hardened_build 1
Name: zoneminder
Version: 1.31.44
Version: 1.32.0
Release: 1%{?dist}
Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons
@ -203,8 +202,8 @@ fi
%{_bindir}/gpasswd -a %{zmuid_final} dialout >/dev/null 2>&1 || :
# Warn the end user to read the README file
echo -e "\nVERY IMPORTANT: Before starting ZoneMinder, read README.%{readme_suffix} to finish the\ninstallation or upgrade!\n"
echo -e "\nThe README file is located here: %{_docdir}/%{name}\n"
echo -e "\nVERY IMPORTANT: Before starting ZoneMinder, you must read the README file\nto finish the installation or upgrade!"
echo -e "\nThe README file is located here: %{_pkgdocdir}/README\n"
%if 0%{?with_nginx}
# Nginx does not create an SSL certificate like the apache package does so lets do that here
@ -252,7 +251,7 @@ EOF
%files
%license COPYING
%doc AUTHORS README.md distros/redhat/readme/README.%{readme_suffix} distros/redhat/readme/README.https
%doc AUTHORS README.md distros/redhat/readme/README distros/redhat/readme/README.https
# We want these two folders to have "normal" read permission
# compared to the folder contents
@ -321,11 +320,32 @@ EOF
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/run/zoneminder
%changelog
* Sun Apr 22 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.31.42-1
- Remove support for sysvinit a.k.a. el6
- use desktop-file-install for new zoneminder.desktop file
- add new web cache folder
- 1.31.42 development snapshot
* Wed Sep 12 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.32.0-1
- 1.32.0 release
- remove el6 (sys v init) support
- Make README name consistent across all supported distros
- remove jscalendar
- add requires php-json, zip
- support zm/conf.d folder
- support zm cache (busting) folder
* Sun Aug 19 2018 Leigh Scott <leigh123linux@googlemail.com> - 1.30.4-9
- Rebuilt for Fedora 29 Mass Rebuild binutils issue
* Fri Jul 27 2018 RPM Fusion Release Engineering <leigh123linux@gmail.com> - 1.30.4-8
- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
* Thu Mar 08 2018 RPM Fusion Release Engineering <leigh123linux@googlemail.com> - 1.30.4-7
- Rebuilt for new ffmpeg snapshot
* Thu Mar 01 2018 RPM Fusion Release Engineering <leigh123linux@googlemail.com> - 1.30.4-6
- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild
* Thu Jan 18 2018 Leigh Scott <leigh123linux@googlemail.com> - 1.30.4-5
- Rebuilt for ffmpeg-3.5 git
* Thu Aug 31 2017 RPM Fusion Release Engineering <kwizart@rpmfusion.org> - 1.30.4-4
- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild
* Tue May 09 2017 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.30.4-1
- modify autosetup macro parameters

View File

@ -50,6 +50,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,libdevice-serialport-perl
,libimage-info-perl
,libjson-any-perl
,libjson-maybexs-perl
,libsys-mmap-perl [!hurd-any]
,liburi-encode-perl
,libwww-perl

View File

@ -32,7 +32,7 @@ Package: libzoneminder-perl
Section: perl
Architecture: all
Depends: ${misc:Depends}, ${perl:Depends}, libdbi-perl,
libdevice-serialport-perl, libimage-info-perl, libjson-any-perl,
libdevice-serialport-perl, libimage-info-perl, libjson-any-perl, libjson-maybexs-perl,
libsys-mmap-perl, liburi-encode-perl, libwww-perl
Description: Perl libraries for ZoneMinder
ZoneMinder is a video camera security and surveillance solution.

View File

@ -45,7 +45,7 @@ Package: libzoneminder-perl
Section: perl
Architecture: all
Depends: ${misc:Depends}, ${perl:Depends}, libdbi-perl,
libdevice-serialport-perl, libimage-info-perl, libjson-any-perl,
libdevice-serialport-perl, libimage-info-perl, libjson-any-perl, libjson-maybexs-perl,
libsys-mmap-perl, liburi-encode-perl, libwww-perl
Description: Perl libraries for ZoneMinder
ZoneMinder is a video camera security and surveillance solution.

View File

@ -53,6 +53,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,libdevice-serialport-perl
,libimage-info-perl
,libjson-any-perl
,libjson-maybexs-perl
,libsys-mmap-perl [!hurd-any]
,liburi-encode-perl
,libwww-perl

View File

@ -22,7 +22,7 @@ if [ "$1" = "configure" ]; then
if [ "$ZM_DB_HOST" = "localhost" ]; then
if [ -e "/lib/systemd/system/mysql.service" ] || [ -e "/lib/systemd/system/mariadb.service" ]; then
if [ -e "/lib/systemd/system/mysql.service" ] || [ -e "/lib/systemd/system/mariadb.service" ] || [ -e "/etc/init.d/mysql" ]; then
# Ensure zoneminder is stopped
deb-systemd-invoke stop zoneminder.service || exit $?
@ -68,6 +68,7 @@ if [ "$1" = "configure" ]; then
# Add any new PTZ control configurations to the database (will not overwrite)
zmcamtool.pl --import >/dev/null 2>&1
echo "Done Updating; starting ZoneMinder."
else
echo 'NOTE: MySQL/MariaDB not running; please start mysql and run dpkg-reconfigure zoneminder when it is running.'
fi
@ -78,7 +79,6 @@ if [ "$1" = "configure" ]; then
else
echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)."
fi
echo "Done Updating; starting ZoneMinder."
deb-systemd-invoke restart zoneminder.service
fi

View File

@ -29,13 +29,13 @@ This means if you plan to use cuRL to experiment with these APIs, you first need
::
curl -XPOST -d "user=XXXX&pass=YYYY" -c cookies.txt http://yourzmip/zm/api/login.json
curl -XPOST -d "user=XXXX&pass=YYYY" -c cookies.txt http://yourzmip/zm/api/host/login.json
Staring ZM 1.32.0, you also have a `logout` API that basically clears your session. It looks like this:
::
curl -b cookies.txt http://yourzmip/zm/api/logout.json
curl -b cookies.txt http://yourzmip/zm/api/host/logout.json
**Login process for older versions of ZoneMinder**

View File

@ -150,7 +150,7 @@ Orientation
WebSite
^^^^^^^
This Source Type allows one to configure an arbitrary website as a non-reocrdable, fully interactive, monitor in ZoneMinder. Note that sites with self-signed certificates will not display until the end user first manually navigates to the site and accpets the unsigned certificate. Also note that some sites will set an X-Frame option in the header, which discourages their site from being displayed within a frame. ZoneMinder will detect this condition and present a warning in the log. When this occurs, the end user can choose to install a browser plugin or extension to workaround this issue.
This Source Type allows one to configure an arbitrary website as a non-recordable, fully interactive, monitor in ZoneMinder. Note that sites with self-signed certificates will not display until the end user first manually navigates to the site and accpets the unsigned certificate. Also note that some sites will set an X-Frame option in the header, which discourages their site from being displayed within a frame. ZoneMinder will detect this condition and present a warning in the log. When this occurs, the end user can choose to install a browser plugin or extension to workaround this issue.
Website URL
Enter the full http or https url to the desired website.

View File

@ -15,7 +15,7 @@ AUTH_HASH_SECRET - When ZoneMinder is running in hashed authenticated mode it is
AUTH_HASH_IPS - When ZoneMinder is running in hashed authenticated mode it can optionally include the requesting IP address in the resultant hash. This adds an extra level of security as only requests from that address may use that authentication key. However in some circumstances, such as access over mobile networks, the requesting address can change for each request which will cause most requests to fail. This option allows you to control whether IP addresses are included in the authentication hash on your system. If you experience intermitent problems with authentication, switching this option off may help.
AUTH_HASH_LOGINS - The normal process for logging into ZoneMinder is via the login screen with username and password. In some circumstances it may be desirable to allow access directly to one or more pages, for instance from a third party application. If this option is enabled then adding an 'auth' parameter to any request will include a shortcut login bypassing the login screen, if not already logged in. As authentication hashes are time and, optionally, IP limited this can allow short-term access to ZoneMinder screens from other web pages etc. In order to use this the calling application will hae to generate the authentication hash itself and ensure it is valid. If you use this option you should ensure that you have modified the ZM_AUTH_HASH_SECRET to somethign unique to your system.
AUTH_HASH_LOGINS - The normal process for logging into ZoneMinder is via the login screen with username and password. In some circumstances it may be desirable to allow access directly to one or more pages, for instance from a third party application. If this option is enabled then adding an 'auth' parameter to any request will include a shortcut login bypassing the login screen, if not already logged in. As authentication hashes are time and, optionally, IP limited, this can allow short-term access to ZoneMinder screens from other web pages etc. In order to use this, the calling application will have to generate the authentication hash itself and ensure it is valid. If you use this option you should ensure that you have modified the ZM_AUTH_HASH_SECRET to something unique to your system.
OPT_FAST_DELETE - Normally an event created as the result of an alarm consists of entries in one or more database tables plus the various files associated with it. When deleting events in the browser it can take a long time to remove all of this if your are trying to do a lot of events at once. It is recommended that you set this option which means that the browser client only deletes the key entries in the events table, which means the events will no longer appear in the listing, and leaves the zmaudit daemon to clear up the rest later.
@ -38,6 +38,7 @@ OPT_CONTROL - ZoneMinder includes limited support for controllable cameras. A nu
OPT_TRIGGERS - ZoneMinder can interact with external systems which prompt or cancel alarms. This is done via the zmtrigger.pl script. This option indicates whether you want to use these external triggers. Most people will say no here.
CHECK_FOR_UPDATES - From ZoneMinder version 1.17.0 onwards new versions are expected to be more frequent. To save checking manually for each new version ZoneMinder can check with the zoneminder.com website to determine the most recent release. These checks are infrequent, about once per week, and no personal or system information is transmitted other than your current version number. If you do not wish these checks to take place or your ZoneMinder system has no internet access you can switch these check off with this configuration variable
UPDATE_CHECK_PROXY - If you use a proxy to access the internet then ZoneMinder needs to know so it can access zoneminder.com to check for updates. If you do use a proxy enter the full proxy url here in the form of http://<proxy host>:<proxy port>/
SHM_KEY - ZoneMinder uses shared memory to speed up communication between modules. To identify the right area to use shared memory keys are used. This option controls what the base key is, each monitor will have it's Id or'ed with this to get the actual key used. You will not normally need to change this value unless it clashes with another instance of ZoneMinder on the same machine. Only the first four hex digits are used, the lower four will be masked out and ignored.

17
misc/apache-cors.conf Normal file
View File

@ -0,0 +1,17 @@
# This configuration is only needed for compatibility with zmninja on iOS
# 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
Header always set Access-Control-Allow-Credentials true
#zmNinja's WKWebView will set the origin header as localhost:8080
Header always set Access-Control-Allow-Origin "http://localhost:8080"
Header always set Access-Control-Request-Methods "Authorization"
Header always set Access-Control-Methods "OPTIONS,GET,POST,DELETE,PUT"
Header always set Access-Control-Allow-Headers "X-Requested-With, Content-Type, Authorization, Origin, Accept, client-security-token"
Header always set Access-Control-Expose-Headers "Content-Security-Policy, Location"
Header always set Access-Control-Max-Age "1000"
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]

View File

@ -1,27 +1,3 @@
# ==========================================================================
#
# ZoneMinder Base Module, $Date$, $Revision$
# Copyright (C) 2001-2008 Philip Coombes
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================
#
# This module contains the common definitions and functions used by the rest
# of the ZoneMinder scripts
#
package ZoneMinder::Base;
use 5.006;
@ -82,11 +58,18 @@ Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2001-2008 Philip Coombes
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.3 or,
at your option, any later version of Perl 5 you may have available.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=cut

View File

@ -149,7 +149,7 @@ BEGIN {
foreach my $str ( <$CONFIG> ) {
next if ( $str =~ /^\s*$/ );
next if ( $str =~ /^\s*#/ );
my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*(.*?)\s*$/;
my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/;
if ( ! $name ) {
print( STDERR "Warning, bad line in $config_file: $str\n" );
next;

View File

@ -409,8 +409,8 @@ our @options = (
that is used to get notifications for alarms detected by ZoneMinder
in real time. zmNinja requires this server for push notifications to
mobile phones. This option only enables the server if its already installed.
Please visit https://github.com/pliablepixels/zmeventserver for installation
instructions.
Please visit the [zmeventserver project site](https://github.com/pliablepixels/zmeventserver)
for installation instructions.
`,
type => $types{boolean},
category => 'system',
@ -442,7 +442,7 @@ our @options = (
description => 'Your recaptcha site-key',
help => q`You need to generate your keys from
the Google reCaptcha website.
Please refer to https://www.google.com/recaptcha/
Please refer to the [recaptcha project site](https://www.google.com/recaptcha/)
for more details.
`,
requires => [
@ -457,7 +457,7 @@ our @options = (
description => 'Your recaptcha secret-key',
help => q`You need to generate your keys from
the Google reCaptcha website.
Please refer to https://www.google.com/recaptcha/
Please refer to the [recaptcha project site](https://www.google.com/recaptcha/)
for more details.
`,
requires => [
@ -674,9 +674,9 @@ our @options = (
ZoneMinder uses to view image streams on browsers such as
Internet Explorer that don't natively support this format. If
you use this browser it is highly recommended to install this
from http://www.charliemouse.com/code/cambozola/ however if it
is not installed still images at a lower refresh rate can still
be viewed.
from the [cambozola project site](http://www.charliemouse.com/code/cambozola/).
However, if it is not installed still images at a lower refresh rate can
still be viewed.
`,
type => $types{boolean},
category => 'images',
@ -690,9 +690,9 @@ our @options = (
ZoneMinder uses to view image streams on browsers such as
Internet Explorer that don't natively support this format. If
you use this browser it is highly recommended to install this
from http://www.charliemouse.com/code/cambozola/ however if it
is not installed still images at a lower refresh rate can still
be viewed. Leave this as 'cambozola.jar' if cambozola is
from the [cambozola project site](http://www.charliemouse.com/code/cambozola/).
However if it is not installed still images at a lower refresh rate can
still be viewed. Leave this as 'cambozola.jar' if cambozola is
installed in the same directory as the ZoneMinder web client
files.
`,
@ -2721,7 +2721,8 @@ our @options = (
This is being done for the sole purpoase of creating a better
product for our target audience. This script is intended to be
completely transparent to the end user, and can be disabled from
the web console under Options.
the web console under Options. For more details on what information
we collect, please refer to our [privacy](?view=privacy) statement.
`,
type => $types{boolean},
category => 'system',
@ -3882,6 +3883,15 @@ our @options = (
readonly => 1,
category => 'dynamic',
},
{
name => 'ZM_SHOW_PRIVACY',
default => 'yes',
description => 'Present the privacy statment',
help => '',
type => $types{boolean},
readonly => 1,
category => 'dynamic',
},
{
name => 'ZM_SSMTP_MAIL',
default => 'no',
@ -3898,7 +3908,7 @@ our @options = (
SSMTP is a lightweight and efficient method to send email.
The SSMTP application is not installed by default.
NEW_MAIL_MODULES must also be enabled.
Please visit: http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder
Please visit the ZoneMinder [SSMTP Wiki page](http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder)
for setup and configuration help.
`,
type => $types{boolean},

View File

@ -0,0 +1,362 @@
package ZoneMinder::Control::Dahua;
use 5.8.0;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
our $REALM = '';
our $USERNAME = '';
our $PASSWORD = '';
our $ADDRESS = '';
our $PROTOCOL = 'http://';
use Time::HiRes qw(usleep);
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use ZoneMinder::Database qw(zmDbConnect);
sub new
{
my $class = shift;
my $id = shift;
my $self = ZoneMinder::Control->new( $id );
bless( $self, $class );
srand( time() );
return $self;
}
our $AUTOLOAD;
sub AUTOLOAD
{
my $self = shift;
my $class = ref($self) || croak( "$self not object" );
my $name = $AUTOLOAD;
$name =~ s/.*://;
if ( exists($self->{$name}) )
{
return( $self->{$name} );
}
Fatal( "Can't access $name member of object of class $class" );
}
sub open
{
my $self = shift;
$self->loadMonitor();
# The Dahua camera firmware API supports the concept of having multiple
# channels on a single IP controller.
# As most cameras only have a single channel, and there is no similar
# information model in Zoneminder, I'm hardcoding the first and default
# channel "0", here.
$self->{dahua_channel_number} = "0";
if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?<PROTOCOL>https?:\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>.*)$/ ) ) {
$PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL};
$USERNAME = $+{USERNAME} if $+{USERNAME};
$PASSWORD = $+{PASSWORD} if $+{PASSWORD};
$ADDRESS = $+{ADDRESS} if $+{ADDRESS};
} else {
Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress});
$ADDRESS = $self->{Monitor}->{ControlAddress};
}
if ( !($ADDRESS =~ /:/) ) {
Error('You generally need to also specify the port. I will append :80');
$ADDRESS .= ':80';
}
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent("ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION);
$self->{state} = 'closed';
# credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string)
Debug("sendCmd credentials control address:'".$ADDRESS
."' realm:'" . $REALM
. "' username:'" . $USERNAME
. "' password:'".$PASSWORD
."'"
);
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
# Detect REALM
my $get_config_url = $PROTOCOL . $ADDRESS . "/cgi-bin/configManager.cgi?action=getConfig&name=Ptz";
my $req = HTTP::Request->new(GET=>$get_config_url);
my $res = $self->{ua}->request($req);
if ($res->is_success) {
$self->{state} = 'open';
return;
}
if ( $res->status_line() eq '401 Unauthorized' ) {
my $headers = $res->headers();
foreach my $k (keys %$headers) {
Debug("Initial Header $k => $$headers{$k}");
}
if ($$headers{'www-authenticate'}) {
my ($auth, $tokens) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
if ($tokens =~ /\w+="([^"]+)"/i) {
if ($REALM ne $1) {
$REALM = $1;
Debug("Changing REALM to '" . $REALM . "'");
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
my $req = HTTP::Request->new(GET=>$get_config_url);
$res = $self->{ua}->request($req);
if ($res->is_success()) {
$self->{state} = 'open';
return;
}
Debug('Authentication still failed after updating REALM' . $res->status_line);
$headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
} # end foreach
} else {
Error('Authentication failed, not a REALM problem');
}
} else {
Error('Failed to match realm in tokens');
} # end if
} else {
Error('No WWW-Authenticate Header');
} # end if headers
} # end if $res->status_line() eq '401 Unauthorized'
}
sub close
{
my $self = shift;
$self->{state} = 'closed';
}
sub printMsg
{
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendGetRequest {
my $self = shift;
my $url_path = shift;
my $result = undef;
my $url = $PROTOCOL . $ADDRESS . $url_path;
my $req = HTTP::Request->new(GET=>$url);
my $res = $self->{ua}->request($req);
if ($res->is_success) {
$result = !undef;
} else {
if ($res->status_line() eq '401 Unauthorized') {
Debug("Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD);
Debug("Content was " . $res->content() );
my $res = $self->{ua}->request($req);
if ($res->is_success) {
$result = !undef;
} else {
Error("Content was " . $res->content() );
}
}
if ( ! $result ) {
Error("Error check failed: '".$res->status_line());
}
}
return($result);
}
sub sendPtzCommand
{
my $self = shift;
my $action = shift;
my $command_code = shift;
my $arg1 = shift;
my $arg2 = shift;
my $arg3 = shift;
my $channel = $self->{dahua_channel_number};
my $url_path = "/cgi-bin/ptz.cgi?";
$url_path .= "action=" . $action . "&";
$url_path .= "channel=" . $channel . "&";
$url_path .= "code=" . $command_code . "&";
$url_path .= "arg1=" . $arg1 . "&";
$url_path .= "arg2=" . $arg2 . "&";
$url_path .= "arg3=" . $arg3;
$self->sendGetRequest($url_path);
}
sub sendMomentaryPtzCommand
{
my $self = shift;
my $command_code = shift;
my $arg1 = shift;
my $arg2 = shift;
my $arg3 = shift;
my $duration_ms = shift;
$self->sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3);
my $duration_ns = $duration_ms * 1000;
usleep($duration_ns);
$self->sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3);
}
sub moveRelUpLeft
{
my $self = shift;
Debug("Move Up Left");
$self->sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500);
}
sub moveRelUp
{
my $self = shift;
Debug("Move Up");
$self->sendMomentaryPtzCommand("Up", 0, 4, 0, 500);
}
sub moveRelUpRight
{
my $self = shift;
Debug("Move Up Right");
$self->sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500);
}
sub moveRelLeft
{
my $self = shift;
Debug("Move Left");
$self->sendMomentaryPtzCommand("Left", 0, 4, 0, 500);
}
sub moveRelRight
{
my $self = shift;
Debug("Move Right");
$self->sendMomentaryPtzCommand("Right", 0, 4, 0, 500);
}
sub moveRelDownLeft
{
my $self = shift;
Debug("Move Down Left");
$self->sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500);
}
sub moveRelDown
{
my $self = shift;
Debug("Move Down");
$self->sendMomentaryPtzCommand("Down", 0, 4, 0, 500);
}
sub moveRelDownRight
{
my $self = shift;
Debug("Move Down Right");
$self->sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500);
}
sub zoomRelTele
{
my $self = shift;
Debug("Zoom Relative Tele");
$self->sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500);
}
sub zoomRelWide
{
my $self = shift;
Debug("Zoom Relative Wide");
$self->sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500);
}
sub presetClear
{
my $self = shift;
my $params = shift;
my $preset_id = $self->getParam($params, 'preset');
$self->sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0);
}
sub presetSet
{
my $self = shift;
my $params = shift;
my $preset_id = $self->getParam($params, 'preset');
my $dbh = zmDbConnect(1);
my $sql = 'SELECT * FROM ControlPresets WHERE MonitorId = ? AND Preset = ?';
my $sth = $dbh->prepare($sql)
or Fatal("Can't prepare sql '$sql': " . $dbh->errstr());
my $res = $sth->execute($self->{Monitor}->{Id}, $preset_id)
or Fatal("Can't execute sql '$sql': " . $sth->errstr());
my $control_preset_row = $sth->fetchrow_hashref();
my $new_label_name = $control_preset_row->{'Label'};
$self->sendPtzCommand("start", "SetPreset", 0, $preset_id, 0);
$self->sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0);
}
sub presetGoto
{
my $self = shift;
my $params = shift;
my $preset_id = $self->getParam($params, 'preset');
$self->sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0);
}
1;
__END__
=head1 NAME
ZoneMinder::Control::Dahua - Perl module for Dahua cameras
=head1 SYNOPSIS
use ZoneMinder::Control::Dahua;
place this in /usr/share/perl5/ZoneMinder/Control
=head1 DESCRIPTION
This module is an implementation of the Dahua IP camera HTTP control API.
=head2 EXPORT
None by default.
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2018 ZoneMinder LLC
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=cut

View File

@ -0,0 +1,475 @@
# ==========================================================================
#
# ZoneMinder Dericam P2 Control Protocol Module
# Copyright (C) Roman Dissertori
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================
#
# This module contains the implementation of the Dericam P2 device control protocol
#
package ZoneMinder::Control::DericamP2;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
our %CamParams = ();
# ==========================================================================
#
# Dericam P2 Control Protocol
#
# On ControlAddress use the format :
# USERNAME:PASSWORD@ADDRESS:PORT
# eg : admin:@10.1.2.1:80
# zoneminder:zonepass@10.0.100.1:40000
#
# ==========================================================================
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use Time::HiRes qw( usleep );
sub open
{
my $self = shift;
$self->loadMonitor();
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION );
$self->{state} = 'open';
}
sub printMsg
{
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendCmd
{
my $self = shift;
my $cmd = shift;
my $result = undef;
printMsg( $cmd, "Tx" );
my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" );
Info( "http://".$self->{Monitor}->{ControlAddress}."/$cmd".$self->{Monitor}->{ControlDevice} );
my $res = $self->{ua}->request($req);
if ( $res->is_success )
{
$result = !undef;
}
else
{
Error( "Error check failed:'".$res->status_line()."'" );
}
return( $result );
}
sub getCamParams
{
my $self = shift;
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=getimageattr";
my $req = $self->sendCmd( $cmd );
my $res = $self->{ua}->request($req);
if ( $res->is_success )
{
# Parse results setting values in %FCParams
my $content = $res->decoded_content;
while ($content =~ s/var\s+([^=]+)=([^;]+);//ms) {
$CamParams{$1} = $2;
}
}
else
{
Error( "Error check failed:'".$res->status_line()."'" );
}
}
#autoStop
#This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab
sub autoStop
{
my $self = shift;
my $stop_command = shift;
my $autostop = shift;
if( $stop_command && $autostop)
{
Debug( "Auto Stop" );
usleep( $autostop );
my $cmd = "decoder_control.cgi?command=".$stop_command;
$self->sendCmd( $cmd );
}
}
# Reset the Camera
sub reset
{
my $self = shift;
Debug( "Camera Reset" );
# Move to default position
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=home";
$self->sendCmd( $cmd );
# Reset all other values to default
$cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-image_type=1&-default=on";
$self->sendCmd( $cmd );
}
# Reboot Camera (on Sleep button)
sub sleep
{
my $self = shift;
Debug( "Camera Reboot" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=sysreboot";
$self->sendCmd( $cmd );
}
# Stop the Camera
sub moveStop
{
my $self = shift;
Debug( "Camera Stop" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-act=stop";
$self->sendCmd( $cmd );
}
#Up Arrow
sub moveConUp
{
my $self = shift;
my $stop_command = "1";
Debug( "Move Up" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=up&-speed=45";
$self->sendCmd( $cmd );
#$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} );
}
#Down Arrow
sub moveConDown
{
my $self = shift;
my $stop_command = "3";
Debug( "Move Down" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=down&-speed=45";
$self->sendCmd( $cmd );
#$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} );
}
#Left Arrow
sub moveConLeft
{
my $self = shift;
my $stop_command = "5";
Debug( "Move Left" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=left&-speed=45";
$self->sendCmd( $cmd );
#$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} );
}
#Right Arrow
sub moveConRight
{
my $self = shift;
my $stop_command = "7";
Debug( "Move Right" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=right&-speed=45";
$self->sendCmd( $cmd );
#$self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} );
}
#Zoom In
sub zoomConTele
{
my $self = shift;
Debug( "Zoom Tele" );
my $cmd = "decoder_control.cgi?command=18";
$self->sendCmd( $cmd );
}
#Zoom Out
sub zoomConWide
{
my $self = shift;
Debug( "Zoom Wide" );
my $cmd = "decoder_control.cgi?command=16";
$self->sendCmd( $cmd );
}
#Diagonally Up Right Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConUpRight
{
my $self = shift;
Debug( "Move Diagonally Up Right" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=upright&-speed=45";
$self->sendCmd( $cmd );
}
#Diagonally Down Right Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConDownRight
{
my $self = shift;
Debug( "Move Diagonally Down Right" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=downright&-speed=45";
$self->sendCmd( $cmd );
}
#Diagonally Up Left Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConUpLeft
{
my $self = shift;
Debug( "Move Diagonally Up Left" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=upleft&-speed=45";
$self->sendCmd( $cmd );
}
#Diagonally Down Left Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConDownLeft
{
my $self = shift;
Debug( "Move Diagonally Down Left" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=downnleft&-speed=45";
$self->sendCmd( $cmd );
}
#Set Camera Preset
#Presets must be translated into values internal to the camera
#Those values are: 30,32,34,36,38,40,42,44 for presets 1-8 respectively
sub presetSet
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
Debug( "Set Preset $preset" );
if (( $preset >= 1 ) && ( $preset <= 8 )) {
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=preset&-act=set&-number=".(($preset*2) + 28);
$self->sendCmd( $cmd );
}
}
#Recall Camera Preset
#Presets must be translated into values internal to the camera
#Those values are: 31,33,35,37,39,41,43,45 for presets 1-8 respectively
sub presetGoto
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
Debug( "Goto Preset $preset" );
if (( $preset >= 1 ) && ( $preset <= 8 )) {
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=preset&-act=goto&-number=".(($preset*2) + 29);
$self->sendCmd( $cmd );
}
if ( $preset == 9 ) {
$self->horizontalPatrol();
}
if ( $preset == 10 ) {
$self->verticalPatrol();
}
}
#Horizontal Patrol
sub horizontalPatrol
{
my $self = shift;
Debug( "Horizontal Patrol" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=hscan";
$self->sendCmd( $cmd );
}
#Vertical Patrol
sub verticalPatrol
{
my $self = shift;
Debug( "Vertical Patrol" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=ptzctrl&-step=0&-act=vscan";
$self->sendCmd( $cmd );
}
# Increase Brightness
sub irisAbsOpen
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'brightness'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'brightness'} += $step;
$CamParams{'brightness'} = 100 if ($CamParams{'brightness'} > 100);
Debug( "Increase Brightness" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-brightness=".$CamParams{'brightness'};
$self->sendCmd( $cmd );
}
# Decrease Brightness
sub irisAbsClose
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'brightness'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'brightness'} -= $step;
$CamParams{'brightness'} = 0 if ($CamParams{'brightness'} < 0);
Debug( "Decrease Brightness" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-brightness=".$CamParams{'brightness'};
$self->sendCmd( $cmd );
}
# Increase Contrast
sub whiteAbsIn
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'contrast'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'contrast'} += $step;
$CamParams{'contrast'} = 100 if ($CamParams{'contrast'} > 100);
Debug( "Increase Contrast" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-contrast=".$CamParams{'contrast'};
$self->sendCmd( $cmd );
}
# Decrease Contrast
sub whiteAbsOut
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'contrast'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'contrast'} -= $step;
$CamParams{'contrast'} = 0 if ($CamParams{'contrast'} < 0);
Debug( "Decrease Contrast" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-contrast=".$CamParams{'contrast'};
$self->sendCmd( $cmd );
}
#TODO Saturation cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=44 [0-255]
sub satIncrease
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'saturation'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'saturation'} += $step;
$CamParams{'saturation'} = 255 if ($CamParams{'saturation'} > 255);
Debug( "Increase Saturation" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=".$CamParams{'saturation'};
$self->sendCmd( $cmd );
}
sub satDecrease
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'saturation'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'saturation'} -= $step;
$CamParams{'saturation'} = 0 if ($CamParams{'saturation'} < 0);
Debug( "Decrease Saturation" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-saturation=".$CamParams{'saturation'};
$self->sendCmd( $cmd );
}
#TODO Sharpness cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=37 [0-100]
sub sharpIncrease
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'sharpness'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'sharpness'} += $step;
$CamParams{'sharpness'} = 100 if ($CamParams{'sharpness'} > 100);
Debug( "Increase Sharpness" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=".$CamParams{'sharpness'};
$self->sendCmd( $cmd );
}
sub sharpDecrease
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'sharpness'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'sharpness'} -= $step;
$CamParams{'sharpness'} = 0 if ($CamParams{'sharpness'} < 0);
Debug( "Decrease Sharpness" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-sharpness=".$CamParams{'sharpness'};
$self->sendCmd( $cmd );
}
#TODO Hue cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=37 [0-100]
sub hueIncrease
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'hue'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'hue'} += $step;
$CamParams{'hue'} = 100 if ($CamParams{'hue'} > 100);
Debug( "Increase Hue" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=".$CamParams{'hue'};
$self->sendCmd( $cmd );
}
sub hueDecrease
{
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'hue'});
my $step = $self->getParam( $params, 'step' );
$CamParams{'hue'} -= $step;
$CamParams{'hue'} = 0 if ($CamParams{'hue'} < 0);
Debug( "Decrease Hue" );
my $cmd = "cgi-bin/hi3510/param.cgi?cmd=setimageattr&-hue=".$CamParams{'hue'};
$self->sendCmd( $cmd );
}
1;

View File

@ -74,39 +74,36 @@ use ZoneMinder::Config qw(:all);
use Time::HiRes qw( usleep );
sub open
{
sub open {
my $self = shift;
$self->loadMonitor();
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION );
$self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION);
$self->{state} = 'open';
}
sub printMsg
{
sub printMsg {
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
Debug($msg.'['.$msg_len.']');
}
sub sendCmd
{
sub sendCmd {
my $self = shift;
my $cmd = shift;
my $msg = shift;
my $content_type = shift;
my $result = undef;
printMsg( $cmd, "Tx" );
printMsg($cmd, 'Tx');
my $server_endpoint = "http://".$self->{Monitor}->{ControlAddress}."/$cmd";
my $server_endpoint = 'http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd;
my $req = HTTP::Request->new(POST => $server_endpoint);
$req->header('content-type' => $content_type);
$req->header('Host' => $self->{Monitor}->{ControlAddress});
@ -122,14 +119,13 @@ sub sendCmd
} else {
Error("After sending PTZ command, camera returned the following error:'".$res->status_line()."'");
}
return( $result );
return $result;
}
sub getCamParams
{
sub getCamParams {
my $self = shift;
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken></GetImagingSettings></s:Body></s:Envelope>';
my $server_endpoint = "http://".$self->{Monitor}->{ControlAddress}."/onvif/imaging";
my $server_endpoint = 'http://'.$self->{Monitor}->{ControlAddress}.'/onvif/imaging';
my $req = HTTP::Request->new(POST => $server_endpoint);
$req->header('content-type' => 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/GetImagingSettings"');
$req->header('Host' => $self->{Monitor}->{ControlAddress});
@ -150,22 +146,19 @@ sub getCamParams
if ( $content =~ /.*<tt:(Contrast)>(.+)<\/tt:Contrast>.*/ ) {
$CamParams{$1} = $2;
}
}
else
{
} else {
Error("Unable to retrieve camera image settings:'".$res->status_line()."'");
}
}
#autoStop
#This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab
sub autoStop
{
sub autoStop {
my $self = shift;
my $autostop = shift;
if ( $autostop ) {
Debug( "Auto Stop" );
Debug('Auto Stop');
my $cmd = 'onvif/PTZ';
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
@ -175,20 +168,18 @@ sub autoStop
}
# Reset the Camera
sub reset
{
Debug( "Camera Reset" );
sub reset {
Debug('Camera Reset');
my $self = shift;
my $cmd = "";
my $cmd = '';
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SystemReboot xmlns="http://www.onvif.org/ver10/device/wsdl"/></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/SystemReboot"';
$self->sendCmd($cmd, $msg, $content_type);
}
#Up Arrow
sub moveConUp
{
Debug( "Move Up" );
sub moveConUp {
Debug('Move Up');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -198,9 +189,8 @@ sub moveConUp
}
#Down Arrow
sub moveConDown
{
Debug( "Move Down" );
sub moveConDown {
Debug('Move Down');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -210,9 +200,8 @@ sub moveConDown
}
#Left Arrow
sub moveConLeft
{
Debug( "Move Left" );
sub moveConLeft {
Debug('Move Left');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.49" y="0" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -222,9 +211,8 @@ sub moveConLeft
}
#Right Arrow
sub moveConRight
{
Debug( "Move Right" );
sub moveConRight {
Debug('Move Right');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.49" y="0" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -234,9 +222,8 @@ sub moveConRight
}
#Zoom In
sub zoomConTele
{
Debug( "Zoom Tele" );
sub zoomConTele {
Debug('Zoom Tele');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><Zoom x="0.49" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -246,9 +233,8 @@ sub zoomConTele
}
#Zoom Out
sub zoomConWide
{
Debug( "Zoom Wide" );
sub zoomConWide {
Debug('Zoom Wide');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><Zoom x="-0.49" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -259,9 +245,8 @@ sub zoomConWide
#Diagonally Up Right Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConUpRight
{
Debug( "Move Diagonally Up Right" );
sub moveConUpRight {
Debug('Move Diagonally Up Right');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.5" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -272,9 +257,8 @@ sub moveConUpRight
#Diagonally Down Right Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConDownRight
{
Debug( "Move Diagonally Down Right" );
sub moveConDownRight {
Debug('Move Diagonally Down Right');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.5" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -285,9 +269,8 @@ sub moveConDownRight
#Diagonally Up Left Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConUpLeft
{
Debug( "Move Diagonally Up Left" );
sub moveConUpLeft {
Debug('Move Diagonally Up Left');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.5" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -298,9 +281,8 @@ sub moveConUpLeft
#Diagonally Down Left Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConDownLeft
{
Debug( "Move Diagonally Down Left" );
sub moveConDownLeft {
Debug('Move Diagonally Down Left');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.5" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
@ -310,9 +292,8 @@ sub moveConDownLeft
}
#Stop
sub moveStop
{
Debug( "Move Stop" );
sub moveStop {
Debug('Move Stop');
my $self = shift;
my $cmd = 'onvif/PTZ';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
@ -321,8 +302,7 @@ sub moveStop
}
#Set Camera Preset
sub presetSet
{
sub presetSet {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
@ -334,8 +314,7 @@ sub presetSet
}
#Recall Camera Preset
sub presetGoto
{
sub presetGoto {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
@ -348,45 +327,42 @@ sub presetGoto
#Horizontal Patrol
#To be determined if this camera supports this feature
sub horizontalPatrol
{
Debug( "Horizontal Patrol" );
sub horizontalPatrol {
Debug('Horizontal Patrol');
my $self = shift;
my $cmd = '';
my $msg ='';
my $content_type = '';
# $self->sendCmd( $cmd, $msg, $content_type );
Error( "PTZ Command not implemented in control script." );
Error('PTZ Command not implemented in control script.');
}
#Horizontal Patrol Stop
#To be determined if this camera supports this feature
sub horizontalPatrolStop
{
Debug( "Horizontal Patrol Stop" );
sub horizontalPatrolStop {
Debug('Horizontal Patrol Stop');
my $self = shift;
my $cmd = '';
my $msg ='';
my $content_type = '';
# $self->sendCmd( $cmd, $msg, $content_type );
Error( "PTZ Command not implemented in control script." );
Error('PTZ Command not implemented in control script.');
}
# Increase Brightness
sub irisAbsOpen
{
Debug( "Iris $CamParams{'Brightness'}" );
sub irisAbsOpen {
Debug("Iris $CamParams{Brightness}");
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'Brightness'});
$self->getCamParams() unless($CamParams{Brightness});
my $step = $self->getParam($params, 'step');
my $max = 100;
$CamParams{'Brightness'} += $step;
$CamParams{'Brightness'} = $max if ($CamParams{'Brightness'} > $max);
$CamParams{Brightness} += $step;
$CamParams{Brightness} = $max if ($CamParams{Brightness} > $max);
my $cmd = 'onvif/imaging';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Brightness'}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{Brightness}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
$self->sendCmd( $cmd, $msg, $content_type );
}
@ -394,57 +370,55 @@ sub irisAbsOpen
# Decrease Brightness
sub irisAbsClose
{
Debug( "Iris $CamParams{'Brightness'}" );
Debug( "Iris $CamParams{Brightness}" );
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'brightness'});
$self->getCamParams() unless($CamParams{brightness});
my $step = $self->getParam( $params, 'step' );
my $min = 0;
$CamParams{'Brightness'} -= $step;
$CamParams{'Brightness'} = $min if ($CamParams{'Brightness'} < $min);
$CamParams{Brightness} -= $step;
$CamParams{Brightness} = $min if ($CamParams{Brightness} < $min);
my $cmd = 'onvif/imaging';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Brightness'}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{Brightness}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
$self->sendCmd( $cmd, $msg, $content_type );
}
# Increase Contrast
sub whiteAbsIn
{
Debug( "Iris $CamParams{'Contrast'}" );
sub whiteAbsIn {
Debug("Iris $CamParams{Contrast}");
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'Contrast'});
$self->getCamParams() unless($CamParams{Contrast});
my $step = $self->getParam( $params, 'step' );
my $max = 100;
$CamParams{'Contrast'} += $step;
$CamParams{'Contrast'} = $max if ($CamParams{'Contrast'} > $max);
$CamParams{Contrast} += $step;
$CamParams{Contrast} = $max if ($CamParams{Contrast} > $max);
my $cmd = 'onvif/imaging';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Contrast'}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{Contrast}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
}
# Decrease Contrast
sub whiteAbsOut
{
Debug( "Iris $CamParams{'Contrast'}" );
sub whiteAbsOut {
Debug("Iris $CamParams{Contrast}");
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'Contrast'});
$self->getCamParams() unless($CamParams{Contrast});
my $step = $self->getParam($params, 'step');
my $min = 0;
$CamParams{'Contrast'} -= $step;
$CamParams{'Contrast'} = $min if ($CamParams{'Contrast'} < $min);
$CamParams{Contrast} -= $step;
$CamParams{Contrast} = $min if ($CamParams{Contrast} < $min);
my $cmd = 'onvif/imaging';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Contrast'}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{Contrast}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
}
1;
__END__

View File

@ -1,504 +0,0 @@
# =========================================================================
#
# ZoneMinder Trendnet TV-IP862IC IP Control Protocol Module, $Date: $, $Revision: $
# Copyright (C) 2014 Vincent Giovannone
#
#
# ==========================================================================
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================
#
# This module contains the implementation of the Trendnet TV-IP672PI IP camera control
# protocol. Also works or TV-IP862IC
#
# For Zoneminder 1.26+
#
# Under control capability:
#
# * Main: name it (suggest TVIP672PI), type is FFMPEG (or remote if you're using MJPEG), protocol is TVIP672PI
# * Main (more): Can wake, can sleep, can reset
# * Move: Can move, can move diagonally, can move mapped, can move relative
# * Pan: Can pan
# * Tilt: Can tilt
# * Presets: Has presets, num presets 20, has home preset (don't set presets via camera's web server, only set via ZM.)
#
# Under control tab in the monitor itself:
#
# * Controllable
# * Control type is the name you gave it in control capability above
# * Control device is the password you use to authenticate to the camera (see further below if you need to change the username from "admin")
# * Control address is the camera's ip address AND web port. example: 192.168.1.1:80
#
#
# If using with anything but a TV-IP672PI (ex: TV-IP672WI), YOU MUST MATCH THE REALM TO MATCH YOUR CAMERA FURTHER DOWN!
#
#
# Due to how the TVIP672 represents presets internally, you MUST define the presets in order... i.e. 1,2,3,4... not 1,10,3,4.
# (see much further down for why, if you care...)
#
package ZoneMinder::Control::TVIP862;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
#
# ******** YOU MUST CHANGE THE FOLLOWING LINES TO MATCH YOUR CAMERA! **********
#
# I assume that "TV-IP672WI" would work for the TV-IP672WI, but can't test since I don't own one.
#
# TV-IP672PI works for the PI version, of course.
#
# Finally, the username is the username you'd like to authenticate as.
#
our $REALM = 'TV-IP862IC';
our $USERNAME = 'admin';
our $PASSWORD = '';
our $ADDRESS = '';
# ==========================================================================
#
# Trendnet TV-IP672PI Control Protocol
#
# ==========================================================================
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
sub open
{
my $self = shift;
$self->loadMonitor();
my ( $protocol, $username, $password, $address )
= $self->{Monitor}->{ControlAddress} =~ /^(https?:\/\/)?([^:]+):([^\/@]+)@(.*)$/;
if ( $username ) {
$USERNAME = $username;
$PASSWORD = $password;
$ADDRESS = $address;
} else {
Error( "Failed to parse auth from address");
$ADDRESS = $self->{Monitor}->{ControlAddress};
}
if ( ! $ADDRESS =~ /:/ ) {
Error( "You generally need to also specify the port. I will append :80" );
$ADDRESS .= ':80';
}
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION );
$self->{state} = 'open';
# credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string)
Debug ( "sendCmd credentials control address:'".$ADDRESS
."' realm:'" . $REALM
. "' username:'" . $USERNAME
. "' password:'".$PASSWORD
."'"
);
$self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD);
# Detect REALM
my $req = HTTP::Request->new( GET=>"http://".$ADDRESS."/cgi/ptdc.cgi" );
my $res = $self->{ua}->request($req);
if ( ! $res->is_success ) {
Debug("Need newer REALM");
if ( $res->status_line() eq '401 Unauthorized' ) {
my $headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
} # end foreach
if ( $$headers{'www-authenticate'} ) {
my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
if ( $tokens =~ /\w+="([^"]+)"/i ) {
$REALM = $1;
Debug( "Changing REALM to $REALM" );
$self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD);
} # end if
} else {
Debug("No headers line");
} # end if headers
} # end if $res->status_line() eq '401 Unauthorized'
} # end if ! $res->is_success
}
sub printMsg
{
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendCmd
{
# This routine is used for all moving, which are all GET commands...
my $self = shift;
my $cmd = shift;
my $result = undef;
my $url = "http://".$ADDRESS."/cgi/ptdc.cgi?command=".$cmd;
my $req = HTTP::Request->new( GET=>$url );
Debug ("sendCmd command: " . $url );
my $res = $self->{ua}->request($req);
if ( $res->is_success ) {
$result = !undef;
} else {
if ( $res->status_line() eq '401 Unauthorized' ) {
Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
Error("Content was " . $res->content() );
my $res = $self->{ua}->request($req);
if ( $res->is_success ) {
$result = !undef;
} else {
Error("Content was " . $res->content() );
}
}
if ( ! $result ) {
Error( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" );
}
}
return( $result );
}
sub sendCmdPost
{
#
# This routine is used for setting/clearing presets and IR commands, which are POST commands...
#
my $self = shift;
my $url = shift;
my $cmd = shift;
my $result = undef;
if ($url eq undef)
{
Error ("url passed to sendCmdPost is undefined.");
return(-1);
}
Debug ("sendCmdPost url: " . $url . " cmd: " . $cmd);
my $req = HTTP::Request->new(POST => "http://".$ADDRESS.$url);
$req->content_type('application/x-www-form-urlencoded');
$req->content($cmd);
Debug ( "sendCmdPost credentials control address:'".$ADDRESS."' realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD."'");
my $res = $self->{ua}->request($req);
if ( $res->is_success )
{
$result = !undef;
}
else
{
Error( "sendCmdPost Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" );
if ( $res->status_line() eq '401 Unauthorized' ) {
Error( "sendCmdPost Error check failed: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
} else {
Error( "sendCmdPost Error check failed: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
} # endif
}
return( $result );
}
sub move
{
my $self = shift;
my $panSteps = shift;
my $tiltSteps = shift;
my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps";
$self->sendCmd( $cmd );
}
sub moveRelUpLeft
{
my $self = shift;
Debug( "Move Up Left" );
$self->move(-3, 3);
}
sub moveRelUp
{
my $self = shift;
Debug( "Move Up" );
$self->move(0, 3);
}
sub moveRelUpRight
{
my $self = shift;
Debug( "Move Up Right" );
$self->move(3, 3);
}
sub moveRelLeft
{
my $self = shift;
Debug( "Move Left" );
$self->move(-3, 0);
}
sub moveRelRight
{
my $self = shift;
Debug( "Move Right" );
$self->move(3, 0);
}
sub moveRelDownLeft
{
my $self = shift;
Debug( "Move Down Left" );
$self->move(-3, -3);
}
sub moveRelDown
{
my $self = shift;
Debug( "Move Down" );
$self->move(0, -3);
}
sub moveRelDownRight
{
my $self = shift;
Debug( "Move Down Right" );
$self->move(3, -3);
}
# moves the camera to center on the point that the user clicked on in the video image.
# This isn't mega accurate but good enough for most purposes
sub moveMap
{
# If the camera moves too much, increase hscale and vscale. (...if it doesn't move enough, try decreasing!)
# They scale the movement and are here to compensate for manufacturing variation.
# It's never going to be perfect, so just get somewhere in the ballpark and call it a day.
# (Don't forget to kill the zmcontrol process while tweaking!)
# 1280x800
my $hscale = 31;
my $vscale = 25;
# 1280x800 with fisheye
#my $hscale = 15;
#my $vscale = 15;
# 640x400
#my $hscale = 14;
#my $vscale = 12;
my $self = shift;
my $params = shift;
my $xcoord = $self->getParam( $params, 'xcoord' );
my $ycoord = $self->getParam( $params, 'ycoord' );
my $hor = ($xcoord - ($self->{Monitor}->{Width} / 2))/$hscale;
my $ver = ($ycoord - ($self->{Monitor}->{Height} / 2))/$vscale;
$hor = int($hor);
$ver = -1 * int($ver);
Debug( "Move Map to $xcoord,$ycoord, hor=$hor, ver=$ver" );
$self->move( $hor, $ver );
}
# **** PRESETS ****
#
# OK, presets work a little funky but they DO work, provided you define them in order and don't skip any.
#
# The problem is that when you load the web page for this camera, it gives a list of preset names tied to index numbers.
# So let's say you have four presets... A, B, C, and D, and defined them in that order.
# So A is index 0, B is index 1, C is index 2, D is index 3. When you tell the camera to go to a preset, you actually tell it by number, not by name.
# (So "Go to D" is really "go to index 3".)
#
# Now let's say somebody deletes C via the camera's web GUI. The camera re-numbers the existing presets A=0, B=1, D=2.
# There's really no easy way for ZM to discover this re-numbering, so zoneminder would still send "go to preset 3" thinking
# it's telling the camera to go to point D. In actuality it's telling the camera to go to a preset that no longer exists.
#
# As long as you define your presets in order (i.e. define preset 1, then preset 2, then preset 3, etc.) everything will work just
# fine in ZoneMinder.
#
# (Home preset needs to be set via the camera's web gui, and is unaffected by any of this.)
#
# So that's the limitation: DEFINE YOUR PRESETS IN ORDER THROUGH (and only through!) ZM AND DON'T SKIP ANY.
#
sub presetClear
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
my $cmd = "presetName=$preset&command=del";
my $url = "/eng/admin/cam_control.cgi";
Debug ("presetClear: " . $preset . " cmd: " . $cmd);
$self->sendCmdPost($url,$cmd);
}
sub presetSet
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
my $cmd = "presetName=$preset&command=add";
my $url = "/eng/admin/cam_control.cgi";
Debug ("presetSet " . $preset . " cmd: " . $cmd);
$self->sendCmdPost ($url,$cmd);
}
sub presetGoto
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
$preset = $preset - 1;
Debug( "Goto Preset $preset" );
my $cmd = "goto_preset_position&index=$preset";
$self->sendCmd( $cmd );
}
sub presetHome
{
my $self = shift;
Debug( "Home Preset" );
my $cmd = "go_home";
$self->sendCmd( $cmd );
}
#
# **** IR CONTROLS ****
#
#
# Wake: Force IR on, always. (always night mode)
#
# Sleep: Force IR off, always. (always day mode)
#
# Reset: Automatic IR mode. (day/night mode determined by camera)
#
sub wake
{
# force IR on ("always night mode")
my $self = shift;
my $url = "/eng/admin/adv_audiovideo.cgi";
my $cmd = "irMode=3";
Debug("Wake -- IR on");
$self->sendCmdPost ($url,$cmd);
}
sub sleep
{
# force IR off ("always day mode")
my $self=shift;
my $url = "/eng/admin/adv_audiovideo.cgi";
my $cmd = "irMode=2";
Debug("Sleep -- IR off");
$self->sendCmdPost ($url,$cmd);
}
sub reset
{
# IR auto
my $self=shift;
my $url = "/eng/admin/adv_audiovideo.cgi";
my $cmd = "irMode=0";
Debug("Reset -- IR auto");
$self->sendCmdPost ($url,$cmd);
}
1;
__END__
# Below is stub documentation for your module. You'd better edit it!
=head1 NAME
ZoneMinder::Database - Perl extension for Trendnet TVIP672
=head1 SYNOPSIS
use ZoneMinder::Database;
stuff this in /usr/share/perl5/ZoneMinder/Control , then eat a sandwich
=head1 DESCRIPTION
Stub documentation for Trendnet TVIP672, created by Vince.
=head2 EXPORT
None by default.
=head1 SEE ALSO
Read the comments at the beginning of this file to see the usage for zoneminder 1.25.0
=head1 AUTHOR
Vincent Giovannone, I'd rather you not email me.
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2014 by Vincent Giovannone
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.3 or,
at your option, any later version of Perl 5 you may have available.
=cut

View File

@ -0,0 +1,431 @@
package ZoneMinder::Control::Trendnet;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
# You do not need to change the REALM, but you can get slightly faster response
# by setting so that the first auth request succeeds.
#
# The username and password should be passed in the ControlAddress field but you
# can set them here if you want.
#
our $REALM = '';
our $PROTOCOL = 'http://';
our $USERNAME = 'admin';
our $PASSWORD = '';
our $ADDRESS = '';
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
sub open {
my $self = shift;
$self->loadMonitor();
if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?<PROTOCOL>https?:\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>.*)$/ ) ) {
$PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL};
$USERNAME = $+{USERNAME} if $+{USERNAME};
$PASSWORD = $+{PASSWORD} if $+{PASSWORD};
$ADDRESS = $+{ADDRESS} if $+{ADDRESS};
} else {
Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress});
$ADDRESS = $self->{Monitor}->{ControlAddress};
}
if ( !($ADDRESS =~ /:/) ) {
Error('You generally need to also specify the port. I will append :80');
$ADDRESS .= ':80';
}
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent('ZoneMinder Control Agent/'.$ZoneMinder::Base::ZM_VERSION);
$self->{state} = 'closed';
# credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string)
Debug ( "sendCmd credentials control address:'".$ADDRESS
."' realm:'" . $REALM
. "' username:'" . $USERNAME
. "' password:'".$PASSWORD
."'"
);
$self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD);
# Detect REALM
my $res = $self->{ua}->get($PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi');
if ( $res->is_success ) {
$self->{state} = 'open';
return;
}
if ( $res->status_line() eq '401 Unauthorized' ) {
my $headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
}
if ( $$headers{'www-authenticate'} ) {
my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
if ( $tokens =~ /\w+="([^"]+)"/i ) {
if ( $REALM ne $1 ) {
$REALM = $1;
Debug("Changing REALM to $REALM");
$self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD);
$res = $self->{ua}->get($PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi');
if ( $res->is_success() ) {
$self->{state} = 'open';
return;
}
Error('Authentication still failed after updating REALM' . $res->status_line);
$headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
} # end foreach
} else {
Error('Authentication failed, not a REALM problem');
}
} else {
Error('Failed to match realm in tokens');
} # end if
} else {
Debug('No headers line');
} # end if headers
} # end if $res->status_line() eq '401 Unauthorized'
} # end sub open
sub printMsg {
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug($msg.'['.$msg_len.']');
}
sub sendCmd {
# This routine is used for all moving, which are all GET commands...
my $self = shift;
my $cmd = shift;
my $url = $PROTOCOL.$ADDRESS.'/cgi/ptdc.cgi?command='.$cmd;
my $res = $self->{ua}->get($url);
Debug('sendCmd command: ' . $url);
if ( $res->is_success ) {
return !undef;
}
Error("Error check failed: '".$res->status_line()."' cmd:'".$cmd."'");
return;
}
sub sendCmdPost {
#
# This routine is used for setting/clearing presets and IR commands, which are POST commands...
#
my $self = shift;
my $url = shift;
my $form = shift;
my $result = undef;
if ( $url eq undef ) {
Error('url passed to sendCmdPost is undefined.');
return -1;
}
#Debug('sendCmdPost url: ' . $url . ' cmd: ' . $cmd);
my $res;
$res = $self->{ua}->post(
$PROTOCOL.$ADDRESS.$url,
Referer=>$PROTOCOL.$ADDRESS.$url,
Content=>$form
);
Debug("sendCmdPost credentials control to: $PROTOCOL$ADDRESS$url realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD."'");
if ( $res->is_success ) {
return !undef;
}
Error("sendCmdPost Error check failed: '".$res->status_line()."' cmd:");
return $result;
} # end sub sendCmdPost
sub move {
my $self = shift;
my $panSteps = shift;
my $tiltSteps = shift;
my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps";
$self->sendCmd($cmd);
}
sub moveRelUpLeft {
my $self = shift;
Debug('Move Up Left');
$self->move(-3, 3);
}
sub moveRelUp {
my $self = shift;
Debug('Move Up');
$self->move(0, 3);
}
sub moveRelUpRight {
my $self = shift;
Debug('Move Up Right');
$self->move(3, 3);
}
sub moveRelLeft {
my $self = shift;
Debug('Move Left');
$self->move(-3, 0);
}
sub moveRelRight {
my $self = shift;
Debug('Move Right');
$self->move(3, 0);
}
sub moveRelDownLeft {
my $self = shift;
Debug('Move Down Left');
$self->move(-3, -3);
}
sub moveRelDown {
my $self = shift;
Debug('Move Down');
$self->move(0, -3);
}
sub moveRelDownRight {
my $self = shift;
Debug('Move Down Right');
$self->move(3, -3);
}
# moves the camera to center on the point that the user clicked on in the video image.
# This isn't mega accurate but good enough for most purposes
sub moveMap {
# If the camera moves too much, increase hscale and vscale. (...if it doesn't move enough, try decreasing!)
# They scale the movement and are here to compensate for manufacturing variation.
# It's never going to be perfect, so just get somewhere in the ballpark and call it a day.
# (Don't forget to kill the zmcontrol process while tweaking!)
# 1280x800
my $hscale = 31;
my $vscale = 25;
# 1280x800 with fisheye
#my $hscale = 15;
#my $vscale = 15;
# 640x400
#my $hscale = 14;
#my $vscale = 12;
my $self = shift;
my $params = shift;
my $xcoord = $self->getParam( $params, 'xcoord' );
my $ycoord = $self->getParam( $params, 'ycoord' );
my $hor = ($xcoord - ($self->{Monitor}->{Width} / 2))/$hscale;
my $ver = ($ycoord - ($self->{Monitor}->{Height} / 2))/$vscale;
$hor = int($hor);
$ver = -1 * int($ver);
Debug("Move Map to $xcoord,$ycoord, hor=$hor, ver=$ver");
$self->move($hor, $ver);
}
# **** PRESETS ****
#
# OK, presets work a little funky but they DO work, provided you define them
# in order and don't skip any.
#
# The problem is that when you load the web page for this camera, it gives a
# list of preset names tied to index numbers.
# So let's say you have four presets... A, B, C, and D, and defined them in
# that order.
# So A is index 0, B is index 1, C is index 2, D is index 3. When you tell
# the camera to go to a preset, you actually tell it by number, not by name.
# (So "Go to D" is really "go to index 3".)
#
# Now let's say somebody deletes C via the camera's web GUI. The camera
# re-numbers the existing presets A=0, B=1, D=2.
# There's really no easy way for ZM to discover this re-numbering, so
# zoneminder would still send "go to preset 3" thinking
# it's telling the camera to go to point D. In actuality it's telling the
# camera to go to a preset that no longer exists.
#
# As long as you define your presets in order (i.e. define preset 1, then
# preset 2, then preset 3, etc.) everything will work just
# fine in ZoneMinder.
#
# (Home preset needs to be set via the camera's web gui, and is unaffected by
# any of this.)
#
# So that's the limitation: DEFINE YOUR PRESETS IN ORDER THROUGH (and only
# through!) ZM AND DON'T SKIP ANY.
#
sub presetClear {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
my $cmd = "presetName=$preset&command=del";
my $url = '/eng/admin/cam_control.cgi';
Debug('presetClear: ' . $preset . ' cmd: ' . $cmd);
$self->sendCmdPost($url,{presetName=>$preset, command=>'del'});
}
sub presetSet {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
my $cmd = "presetName=$preset&command=add";
my $url = '/eng/admin/cam_control.cgi';
Debug('presetSet ' . $preset . ' cmd: ' . $cmd);
$self->sendCmdPost($url,{presetName=>$preset, command=>'add', Submit=>'Add'});
}
sub presetGoto {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
$preset = $preset - 1;
Debug("Goto Preset $preset");
my $cmd = "goto_preset_position&index=$preset";
$self->sendCmd($cmd);
}
sub presetHome {
my $self = shift;
Debug('Home Preset');
my $cmd = 'go_home';
$self->sendCmd($cmd);
}
#
# **** IR CONTROLS ****
#
#
# Wake: Force IR on, always. (always night mode)
#
# Sleep: Force IR off, always. (always day mode)
#
# Reset: Automatic IR mode. (day/night mode determined by camera)
#
sub wake {
# force IR on ("always night mode")
my $self = shift;
my $url = '/eng/admin/adv_audiovideo.cgi';
my $cmd = 'irMode=3';
Debug('Wake -- IR on');
$self->sendCmdPost($url,$cmd);
}
sub sleep {
# force IR off ("always day mode")
my $self = shift;
my $url = '/eng/admin/adv_audiovideo.cgi';
my $cmd = 'irMode=2';
Debug('Sleep -- IR off');
$self->sendCmdPost($url,$cmd);
}
sub reset {
# IR auto
my $self=shift;
my $url = '/eng/admin/adv_audiovideo.cgi';
my $cmd = 'irMode=0';
Debug('Reset -- IR auto');
$self->sendCmdPost($url,$cmd);
}
1;
__END__
=head1 NAME
ZoneMinder::Control::Trendnet - Perl module for Trendnet cameras
=head1 SYNOPSIS
use ZoneMinder::Control::Trendnet;
place this in /usr/share/perl5/ZoneMinder/Control
=head1 DESCRIPTION
This module contains the implementation of the Trendnet # IP camera control
protocol. Has been tested with TV-IP862IC
Under control capability:
* Main: Can wake, can sleep, can reset
* Move: Can move, can move diagonally, can move mapped, can move relative
* Pan: Can pan
* Tilt: Can tilt
* Presets: Has presets, num presets 20, has home preset (don't set presets via camera's web server, only set via ZM.)
Under control tab in the monitor itself:
Controllable
Control type is the name you gave it in control capability above
Control address is the camera's ip address AND web port. example: 192.168.1.1:80
You can also put the authentication information here and change the
protocol to https using something like https://admin:password@192.168.1.1:80
=head2 EXPORT
None by default.
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2018 ZoneMinder LLC
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=cut

View File

@ -184,16 +184,19 @@ sub zmDbGetMonitor {
my $id = shift;
return( undef ) if ( !defined($id) );
if ( !defined($id) ) {
croak("Undefined id in zmDbgetMonitor");
return undef ;
}
my $sql = "select * from Monitors where Id = ?";
my $sql = 'SELECT * FROM Monitors WHERE Id = ?';
my $sth = $dbh->prepare_cached($sql)
or croak("Can't prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute($id)
or croak("Can't execute '$sql': ".$sth->errstr());
my $monitor = $sth->fetchrow_hashref();
return( $monitor );
return $monitor;
}
sub zmDbGetMonitorAndControl {
@ -241,55 +244,31 @@ if ( ! defined $ac ) {
} # end if
$d->{AutoCommit} = $ac;
} # end sub end_transaction
1;
__END__
# Below is stub documentation for your module. You'd better edit it!
=head1 NAME
ZoneMinder::Database - Perl extension for blah blah blah
ZoneMinder::Database - Perl module containing database functions used in ZM
=head1 SYNOPSIS
use ZoneMinder::Database;
blah blah blah
=head1 DESCRIPTION
Stub documentation for ZoneMinder, created by h2xs. It looks like the
author of the extension was negligent enough to leave the stub
unedited.
Blah blah blah.
=head2 EXPORT
None by default.
=head1 SEE ALSO
Mention other useful documentation such as the documentation of
related modules or operating system documentation (such as man pages
in UNIX), or any relevant external documentation such as RFCs or
standards.
If you have a mailing list set up for your module, mention it here.
If you have a web site set up for your module, mention it here.
zmDbConnect
zmDbDisconnect
zmDbGetMonitors
zmDbGetMonitor
zmDbGetMonitorAndControl
=head1 AUTHOR
Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2001-2008 Philip Coombes
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.3 or,
at your option, any later version of Perl 5 you may have available.
=cut

View File

@ -35,7 +35,6 @@ require Date::Manip;
require File::Find;
require File::Path;
require File::Copy;
require File::Slurp;
require File::Basename;
require Number::Bytes::Human;
@ -239,7 +238,7 @@ sub LinkPath {
'.'.$$event{Id}
);
} elsif ( $$event{Path} ) {
if ( ( $$event{Path} =~ /^(\d+\/\d{4}\/\d{2}\/\d{2})/ ) ) {
if ( ( $event->RelativePath() =~ /^(\d+\/\d{4}\/\d{2}\/\d{2})/ ) ) {
$$event{LinkPath} = $1.'/.'.$$event{Id};
} else {
Error("Unable to get LinkPath from Path for $$event{Id} $$event{Path}");
@ -255,6 +254,33 @@ sub LinkPath {
return $$event{LinkPath};
} # end sub LinkPath
sub createPath {
makePath($_[0]->Path());
}
sub createLinkPath {
my $LinkPath = $_[0]->LinkPath();
my $EventPath = $_[0]->EventPath();
if ( $LinkPath ) {
if ( !symlink($EventPath, $LinkPath) ) {
Error("Failed symlinking $EventPath to $LinkPath");
}
}
}
sub idPath {
return sprintf('%s/.%d', $_[0]->Path(), $_[0]->{Id});
}
sub createIdFile {
my $event = shift;
my $idFile = $event->idPath();
open( my $ID_FP, '>', $idFile )
or Error("Can't open $idFile: $!");
close($ID_FP);
setFileOwner($idFile);
}
sub GenerateVideo {
my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_;
@ -417,13 +443,13 @@ sub delete_files {
return;
}
my $event_path = $event->RelativePath();
Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path.");
Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path, scheme is $$event{Scheme}.");
if ( $event_path ) {
( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint
( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint
my $deleted = 0;
if ( $$Storage{Type} eq 's3fs' ) {
if ( $$Storage{Type} and ( $$Storage{Type} eq 's3fs' ) ) {
my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ );
eval {
require Net::Amazon::S3;
@ -453,7 +479,7 @@ sub delete_files {
if ( $event->Scheme() eq 'Deep' ) {
my $link_path = $event->LinkPath();
Debug("Deleting files for Event $$event{Id} from $storage_path/$link_path.");
Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path.");
if ( $link_path ) {
( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint
unlink($storage_path.'/'.$link_path) or Error( "Unable to unlink '$storage_path/$link_path': $!" );
@ -564,6 +590,7 @@ sub MoveTo {
my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$NewStorage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ );
eval {
require Net::Amazon::S3;
require File::Slurp;
my $s3 = Net::Amazon::S3->new( {
aws_access_key_id => $aws_id,
aws_secret_access_key => $aws_secret,

View File

@ -47,10 +47,8 @@ our %EXPORT_TAGS = (
getCmdFormat
runCommand
setFileOwner
getEventPath
createEventPath
createEvent
deleteEventFiles
makePath
jsonEncode
jsonDecode
@ -181,70 +179,14 @@ sub runCommand {
return( $output );
}
sub getEventPath {
my $event = shift;
my $Storage = new ZoneMinder::Storage( $$event{StorageId} );
my $event_path = join( '/',
$Storage->Path(),
$event->{MonitorId},
( $Config{ZM_USE_DEEP_STORAGE} ? strftime( "%y/%m/%d/%H/%M/%S", localtime($event->{Time}) ) : $event->{Id} ),
);
return( $event_path );
}
sub createEventPath {
#
# WARNING assumes running from events directory
#
my $event = shift;
my $Storage = new ZoneMinder::Storage( $$event{Id} );
my $eventPath = $Storage->Path() . '/'.$event->{MonitorId};
my $eventPath = $event->Path();
$event->createPath();
$event->createIdFile();
$event->createLinkPath();
if ( $Config{ZM_USE_DEEP_STORAGE} ) {
my @startTime = localtime( $event->{StartTime} );
my @datetimeParts = ();
$datetimeParts[0] = sprintf( "%02d", $startTime[5]-100 );
$datetimeParts[1] = sprintf( "%02d", $startTime[4]+1 );
$datetimeParts[2] = sprintf( "%02d", $startTime[3] );
$datetimeParts[3] = sprintf( "%02d", $startTime[2] );
$datetimeParts[4] = sprintf( "%02d", $startTime[1] );
$datetimeParts[5] = sprintf( "%02d", $startTime[0] );
my $datePath = join('/',@datetimeParts[0..2]);
my $timePath = join('/',@datetimeParts[3..5]);
makePath( $datePath, $eventPath );
$eventPath .= '/'.$datePath;
# Create event id symlink
my $idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} );
symlink( $timePath, $idFile )
or Fatal( "Can't symlink $idFile -> $eventPath: $!" );
makePath( $timePath, $eventPath );
$eventPath .= '/'.$timePath;
setFileOwner( $idFile ); # Must come after directory has been created
# Create empty id tag file
$idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} );
open( my $ID_FP, ">", $idFile )
or Fatal( "Can't open $idFile: $!" );
close( $ID_FP );
setFileOwner( $idFile );
} else {
makePath( $event->{Id}, $eventPath );
$eventPath .= '/'.$event->{Id};
my $idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} );
open( my $ID_FP, ">", $idFile )
or Fatal( "Can't open $idFile: $!" );
close( $ID_FP );
setFileOwner( $idFile );
}
return( $eventPath );
return $eventPath;
}
use Data::Dumper;
@ -481,53 +423,6 @@ sub updateEvent {
or Fatal( "Can't execute sql '$sql': ".$sth->errstr() );
}
sub deleteEventFiles {
#
# WARNING assumes running from events directory
#
my $event_id = shift;
my $monitor_id = shift;
$monitor_id = '*' if ( !defined($monitor_id) );
if ( $Config{ZM_USE_DEEP_STORAGE} ) {
my $link_path = $monitor_id."/*/*/*/.".$event_id;
#Debug( "LP1:$link_path" );
my @links = glob($link_path);
#Debug( "L:".$links[0].": $!" );
if ( @links ) {
( $link_path ) = ( $links[0] =~ /^(.*)$/ ); # De-taint
#Debug( "LP2:$link_path" );
( my $day_path = $link_path ) =~ s/\.\d+//;
#Debug( "DP:$day_path" );
my $event_path = $day_path.readlink( $link_path );
( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint
#Debug( "EP:$event_path" );
my $command = "/bin/rm -rf ".$event_path;
#Debug( "C:$command" );
executeShellCommand( $command );
unlink( $link_path ) or Error( "Unable to unlink '$link_path': $!" );
my @path_parts = split( /\//, $event_path );
for ( my $i = int(@path_parts)-2; $i >= 1; $i-- ) {
my $delete_path = join( '/', @path_parts[0..$i] );
#Debug( "DP$i:$delete_path" );
my @has_files = glob( $delete_path."/*" );
#Debug( "HF1:".$has_files[0] ) if ( @has_files );
last if ( @has_files );
@has_files = glob( $delete_path."/.[0-9]*" );
#Debug( "HF2:".$has_files[0] ) if ( @has_files );
last if ( @has_files );
my $command = "/bin/rm -rf ".$delete_path;
executeShellCommand( $command );
}
}
} else {
my $command = "/bin/rm -rf $monitor_id/$event_id";
executeShellCommand( $command );
}
}
sub makePath {
my $path = shift;
my $root = shift;

View File

@ -440,7 +440,7 @@ sub databaseLevel {
if ( defined($databaseLevel) ) {
$databaseLevel = $this->limit($databaseLevel);
if ( $this->{databaseLevel} != $databaseLevel ) {
if ( $databaseLevel > NOLOG and $this->{databaseLevel} <= NOLOG ) {
if ( ( $databaseLevel > NOLOG ) and ( $this->{databaseLevel} <= NOLOG ) ) {
if ( !$this->{dbh} ) {
$this->{dbh} = ZoneMinder::Database::zmDbConnect();
}
@ -527,6 +527,7 @@ sub logPrint {
my $this = shift;
my $level = shift;
my $string = shift;
my ($caller, undef, $line) = @_ ? @_ : caller;
if ( $level <= $this->{effectiveLevel} ) {
$string =~ s/[\r\n]+$//g;
@ -537,12 +538,14 @@ sub logPrint {
my ($seconds, $microseconds) = gettimeofday();
if ( $level <= $this->{fileLevel} or $level <= $this->{termLevel} ) {
my $message = sprintf(
'%s.%06d %s[%d].%s [%s]'
'%s.%06d %s[%d].%s [%s:%d] [%s]'
, strftime('%x %H:%M:%S', localtime($seconds))
, $microseconds
, $this->{id}
, $$
, $codes{$level}
, $caller
, $line
, $string
);
if ( $this->{trace} ) {
@ -557,11 +560,14 @@ sub logPrint {
if ( $level <= $this->{databaseLevel} ) {
if ( ! ( $this->{dbh} and $this->{dbh}->ping() ) ) {
$this->{sth} = undef;
# Turn this off because zDbConnect will do logging calls.
my $oldlevel = $this->{databaseLevel};
$this->{databaseLevel} = NOLOG;
if ( ! ( $this->{dbh} = ZoneMinder::Database::zmDbConnect() ) ) {
#print(STDERR "Can't log to database: ");
$this->{databaseLevel} = NOLOG;
return;
}
$this->{databaseLevel} = $oldlevel;
}
my $sql = 'INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, NULL )';
@ -660,39 +666,39 @@ sub Dump {
sub debug {
my $log = shift;
$log->logPrint(DEBUG, @_);
$log->logPrint(DEBUG, @_, caller);
}
sub Debug( @ ) {
fetch()->logPrint(DEBUG, @_);
fetch()->logPrint(DEBUG, @_, caller);
}
sub Info( @ ) {
fetch()->logPrint(INFO, @_);
fetch()->logPrint(INFO, @_, caller);
}
sub info {
my $log = shift;
$log->logPrint(INFO, @_);
$log->logPrint(INFO, @_, caller);
}
sub Warning( @ ) {
fetch()->logPrint(WARNING, @_);
fetch()->logPrint(WARNING, @_, caller);
}
sub warn {
my $log = shift;
$log->logPrint(WARNING, @_);
$log->logPrint(WARNING, @_, caller);
}
sub Error( @ ) {
fetch()->logPrint(ERROR, @_);
fetch()->logPrint(ERROR, @_, caller);
}
sub error {
my $log = shift;
$log->logPrint(ERROR, @_);
$log->logPrint(ERROR, @_, caller);
}
sub Fatal( @ ) {
fetch()->logPrint(FATAL, @_);
fetch()->logPrint(FATAL, @_, caller);
if ( $SIG{TERM} and ( $SIG{TERM} ne 'DEFAULT' ) ) {
$SIG{TERM}();
}
@ -700,7 +706,7 @@ sub Fatal( @ ) {
}
sub Panic( @ ) {
fetch()->logPrint(PANIC, @_);
fetch()->logPrint(PANIC, @_, caller);
confess($_[0]);
}

View File

@ -69,7 +69,7 @@ sub find {
push @sql_values, $sql_filters{Name};
}
if ( exists $sql_filters{ServerId} ) {
push @sql_filters, ' Id IN ( SELECT StorageId FROM Monitors WHERE ServerId=? )';
push @sql_filters, ' ServerId = ?';
push @sql_values, $sql_filters{ServerId};
}
@ -88,6 +88,7 @@ sub find {
my $filter = new ZoneMinder::Storage( $$db_filter{Id}, $db_filter );
push @results, $filter;
} # end while
Debug("SQL: $sql returned " . @results . ' results');
return @results;
}

View File

@ -67,6 +67,7 @@ my $level = 1;
my $monitor_id = 0;
my $version;
my $force = 0;
my $server_id = undef;
my $storage_id = undef;
logInit();
@ -78,6 +79,7 @@ GetOptions(
level =>\$level,
'monitor_id=i' =>\$monitor_id,
report =>\$report,
'server_id=i' =>\$server_id,
'storage_id=i' =>\$storage_id,
version =>\$version
) or pod2usage(-exitstatus => -1);
@ -181,13 +183,15 @@ MAIN: while( $loop ) {
Term();
}
Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}");
} elsif ( $Config{ZM_SERVER_ID} ) {
@Storage_Areas = ZoneMinder::Storage->find( ServerId => $Config{ZM_SERVER_ID} );
} elsif ( $server_id ) {
@Storage_Areas = ZoneMinder::Storage->find( ServerId => $server_id );
if ( ! @Storage_Areas ) {
Error("No Storage Area found with ServerId =" . $Config{ZM_SERVER_ID});
Error("No Storage Area found with ServerId =" . $server_id);
Term();
}
Info("Auditing All Storage Areas on Server " . $Storage_Areas[0]->Server()->Name());
foreach my $Storage ( @Storage_Areas ) {
Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name() );
}
} else {
@Storage_Areas = ZoneMinder::Storage->find();
Info("Auditing All Storage Areas");
@ -260,31 +264,37 @@ MAIN: while( $loop ) {
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 " . @event_links . ' event links');
closedir(DIR);
my $count = 0;
foreach my $event_link ( @event_links ) {
if ( $event_link =~ /[^\d\.]/ ) {
# Event links start with a period and consist of the digits of the event id. Anything else is not an event link
my ($event_id) = $event_link =~ /^\.(\d+)$/;
if ( !$event_id ) {
Warning("Non-event link found $event_link in $day_dir, skipping");
next;
}
Debug("Checking link $event_link");
( my $event = $event_link ) =~ s/^.*\.//;
#Event path is hour/minute/sec
my $event_path = readlink($event_link);
if ( !($event_path and -e $event_path) ) {
aud_print("Event link $day_dir/$event_link does not point to valid target");
aud_print("Event link $day_dir/$event_link does not point to valid target at $event_path");
if ( confirm() ) {
( $event_link ) = ( $event_link =~ /^(.*)$/ ); # De-taint
unlink($event_link);
$cleaned = 1;
}
} else {
$event_ids_by_path{$event_path} = $event_id;
Debug("Checking link $event_link points to $event_path ");
my $Event = $fs_events->{$event} = new ZoneMinder::Event();
$$Event{Id} = $event;
my $Event = $fs_events->{$event_id} = new ZoneMinder::Event();
$$Event{Id} = $event_id;
$$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_path);
$$Event{RelativePath} = join('/', $day_dir, $event_path);
$$Event{Scheme} = 'Deep';
@ -303,15 +313,34 @@ MAIN: while( $loop ) {
my $event_id = undef;
my @mp4_files = glob("$event_dir/[0-9]+\-video.mp4");
if ( ! opendir(DIR, $event_dir) ) {
Error("Can't open directory '$$Storage{Path}/$day_dir': $!");
next;
}
my @contents = readdir( DIR );
Debug("Have " . @contents . " files in $day_dir/$event_dir");
closedir(DIR);
my @mp4_files = grep( /^\d+\-video.mp4$/, @contents);
foreach my $mp4_file ( @mp4_files ) {
my ( $id ) = $mp4_file =~ /^([0-9]+)\-video\.mp4$/;
if ( $id ) {
$event_id = $id;
Debug("Got event id from mp4 file $mp4_file => $event_id");
last;
}
}
if ( $event_id ) {
if ( ! $event_id ) {
# Look for .id file
my @hidden_files = grep( /^\.\d+$/, @contents);
Debug("Have " . @hidden_files . ' hidden files');
if ( @hidden_files ) {
( $event_id ) = $hidden_files[0] =~ /^.(\d+)$/;
}
}
if ( $event_id and ! $fs_events->{$event_id} ) {
my $Event = $fs_events->{$event_id} = new ZoneMinder::Event();
$$Event{Id} = $event_id;
$$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_dir);
@ -320,7 +349,26 @@ MAIN: while( $loop ) {
$Event->MonitorId( $monitor_dir );
$Event->StorageId( $Storage->Id() );
$Event->DiskSpace( undef );
if ( ! $event_ids_by_path{$event_dir} ) {
Warning("No event link found at ".$Event->LinkPath() ." for " . $Event->to_string());
}
} else {
if ( $event_ids_by_path{$event_dir} ) {
Debug("Have an event link, leaving dir alone.");
next;
}
my ( undef, $year, $month, $day ) = split('/', $day_dir);
$year += 2000;
my ( $hour, $minute, $second ) = split('/', $event_dir);
my $StartTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second);
my $Event = ZoneMinder::Event->find_one(
MonitorId=>$monitor_dir,
StartTime=>$StartTime,
);
if ( $Event ) {
Debug("Found event matching starttime on monitor $monitor_dir at $StartTime: " . $Event->to_string());
next;
}
aud_print("Deleting event directories with no event id information at $day_dir/$event_dir");
if ( confirm() ) {
my $command = "rm -rf $event_dir";
@ -359,7 +407,7 @@ MAIN: while( $loop ) {
}
if ( ! $$Storage{Scheme} ) {
Debug("Storage Scheme not set on $$Storage{Name}");
Error("Storage Scheme not set on $$Storage{Name}");
if ( ! chdir( $monitor_dir ) ) {
Error( "Can't chdir directory '$$Storage{Path}/$monitor_dir': $!" );
next;
@ -382,7 +430,7 @@ MAIN: while( $loop ) {
} # if USE_DEEP_STORAGE
Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir\n" );
#delete_empty_directories( $monitor_dir );
delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir);
} # end foreach monitor
if ( $cleaned ) {
@ -473,6 +521,10 @@ MAIN: while( $loop ) {
next;
}
Debug("Event $db_event is not in fs. Should have been at ".$Event->Path());
if ( $Event->Archived() ) {
Warning("Event $$Event{Id} is Archived. Taking no further action on it.");
next;
}
if ( ! $Event->StartTime() ) {
Info("Event $$Event{Id} has no start time. deleting it.");
if ( confirm() ) {
@ -877,28 +929,50 @@ sub deleteSwapImage {
}
}
sub delete_empty_directories {
# Deletes empty sub directories of the given path.
# Does not delete the path if empty. Is not meant to be recursive.
sub delete_empty_subdirs {
my $DIR;
Debug("delete_empty_directories $_[0]");
if ( !opendir($DIR, $_[0]) ) {
Error("delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" );
return;
}
my @contents = map { $_ eq '.' or $_ eq '..' ? () : $_ } readdir( $DIR );
my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR );
Debug("delete_empty_subdirectories $_[0] has " . @contents .' entries:' . ( @contents < 2 ? join(',',@contents) : '' ));
my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents;
Debug("Have " . @dirs . " dirs");
foreach ( @dirs ) {
delete_empty_directories( $_[0].'/'.$_ );
}
closedir($DIR);
}
sub delete_empty_directories {
my $DIR;
if ( !opendir($DIR, $_[0]) ) {
Error("delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" );
return;
}
my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR );
Debug("delete_empty_directories $_[0] has " . @contents .' entries:' . ( @contents <= 2 ? join(',',@contents) : '' ));
my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents;
if ( @dirs ) {
Debug("Have " . @dirs . " dirs");
foreach ( @dirs ) {
delete_empty_directories( $_[0].'/'.$_ );
}
#Reload, since we may now be empty
rewinddir $DIR;
@contents = map { $_ eq '.' or $_ eq '..' ? () : $_ } readdir( $DIR );
}
if ( ! @contents ) {
( my $dir ) = ( $_[0] =~ /^(.*)$/ );
unlink $dir;
@contents = map { ($_ eq '.' or $_ eq '..') ? () : $_ } readdir( $DIR );
}
closedir($DIR);
if ( ! @contents ) {
( my $dir ) = ( $_[0] =~ /^(.*)$/ );
Debug("Unlinking $dir because it's empty");
if ( ! rmdir $dir ) {
Error("Unable to unlink $dir: $!");
}
}
} # end sub delete_empty_directories
1;

View File

@ -21,32 +21,6 @@
#
# ==========================================================================
=head1 NAME
zmcontrol.pl - ZoneMinder control script
=head1 SYNOPSIS
zmcontrol.pl --id {monitor_id} --command={command} [various options]
=head1 DESCRIPTION
FIXME FIXME
=head1 OPTIONS
--autostop -
--xcoord [ arg ] - X-coord
--ycoord [ arg ] - Y-coord
--speed [ arg ] - Speed
--step [ arg ] -
--panspeed [ arg ] -
--panstep [ arg ] -
--tiltspeed [ arg ] -
--tiltstep [ arg ] -
--preset [ arg ] -
=cut
use strict;
@EXTRA_PERL_LIB@
@ -58,7 +32,7 @@ use Socket;
#use Data::Dumper;
use Module::Load::Conditional qw{can_load};;
use constant MAX_CONNECT_DELAY => 10;
use constant MAX_CONNECT_DELAY => 15;
use constant MAX_COMMAND_WAIT => 1800;
$| = 1;
@ -89,15 +63,14 @@ GetOptions(
'autostop' =>\$options{autostop},
) or pod2usage(-exitstatus => -1);
if ( !$id || !$options{command} )
{
if ( !$id || !$options{command} ) {
print( STDERR "Please give a valid monitor id and command\n" );
pod2usage(-exitstatus => -1);
}
( $id ) = $id =~ /^(\w+)$/;
Debug( $arg_string );
Debug("zmcontrol: arg string: $arg_string");
my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock';
@ -106,33 +79,28 @@ socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 )
my $saddr = sockaddr_un($sock_file);
my $server_up = connect(CLIENT, $saddr);
if ( !$server_up )
{
if ( !$server_up ) {
# The server isn't there
my $monitor = zmDbGetMonitorAndControl($id);
if ( !$monitor )
{
if ( !$monitor ) {
Fatal("Unable to load control data for monitor $id");
}
my $protocol = $monitor->{Protocol};
if ( -x $protocol )
{
if ( -x $protocol ) {
# Protocol is actually a script!
# Holdover from previous versions
my $command .= $protocol.' '.$arg_string;
Debug( $command."\n" );
Debug($command);
my $output = qx($command);
my $status = $? >> 8;
if ( $status || logDebugging() )
{
if ( $status || logDebugging() ) {
chomp($output);
Debug( "Output: $output\n" );
Debug("Output: $output");
}
if ( $status )
{
Error( "Command '$command' exited with status: $status\n" );
if ( $status ) {
Error("Command '$command' exited with status: $status");
exit($status);
}
exit(0);
@ -142,26 +110,22 @@ if ( !$server_up )
close(CLIENT);
if ( ! can_load( modules => { "ZoneMinder::Control::$protocol" => undef } ) ) {
Fatal("Can't load ZoneMinder::Control::$protocol");
Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR");
}
if ( my $cpid = fork() )
{
if ( my $cpid = fork() ) {
logReinit();
# Parent process just sleep and fall through
socket(CLIENT, PF_UNIX, SOCK_STREAM, 0)
or die("Can't open socket: $!");
my $attempts = 0;
while (!connect( CLIENT, $saddr ))
{
while ( !connect(CLIENT, $saddr) ) {
$attempts++;
Fatal( "Can't connect: $! after $attempts attempts to $sock_file" ) if ($attempts > MAX_CONNECT_DELAY);
Fatal("Can't connect: $! after $attempts attempts to $sock_file") if $attempts > MAX_CONNECT_DELAY;
sleep(1);
}
}
elsif ( defined($cpid) )
{
} elsif ( defined($cpid) ) {
close(STDOUT);
close(STDERR);
@ -192,17 +156,14 @@ if ( !$server_up )
my $win = $rin;
my $ein = $win;
my $timeout = MAX_COMMAND_WAIT;
while( 1 )
{
while( 1 ) {
my $nfound = select(my $rout = $rin, undef, undef, $timeout);
if ( $nfound > 0 )
{
if ( vec( $rout, fileno(SERVER), 1 ) )
{
if ( $nfound > 0 ) {
if ( vec( $rout, fileno(SERVER), 1 ) ) {
my $paddr = accept(CLIENT, SERVER);
my $message = <CLIENT>;
next if ( !$message );
next if !$message;
my $params = jsonDecode($message);
#Debug( Dumper( $params ) );
@ -213,41 +174,28 @@ if ( !$server_up )
last;
}
$control->$command($params);
} else {
Fatal('Bogus descriptor');
}
else
{
Fatal( "Bogus descriptor" );
}
}
elsif ( $nfound < 0 )
{
if ( $! == EPIPE )
{
} elsif ( $nfound < 0 ) {
if ( $! == EPIPE ) {
Error("Can't select: $!");
}
else
{
} else {
Fatal("Can't select: $!");
}
}
else
{
} else {
#print( "Select timed out\n" );
last;
}
}
Info( "Control server $id/$protocol exiting at "
.strftime( '%y/%m/%d %H:%M:%S', localtime() )
);
} # end while forever
Info("Control server $id/$protocol exiting");
unlink($sock_file);
$control->close();
exit(0);
}
else
{
} else {
Fatal("Can't fork: $!");
}
}
} # end if !server up
# The server is there, connect to it
#print( "Writing commands\n" );
@ -258,3 +206,33 @@ print( CLIENT $message );
shutdown(CLIENT, 1);
exit(0);
1;
__END__
=head1 NAME
zmcontrol.pl - ZoneMinder control script
=head1 SYNOPSIS
zmcontrol.pl --id {monitor_id} --command={command} [various options]
=head1 DESCRIPTION
FIXME FIXME
=head1 OPTIONS
--autostop -
--xcoord [ arg ] - X-coord
--ycoord [ arg ] - Y-coord
--speed [ arg ] - Speed
--step [ arg ] -
--panspeed [ arg ] -
--panstep [ arg ] -
--tiltspeed [ arg ] -
--tiltstep [ arg ] -
--preset [ arg ] -
=cut

View File

@ -272,6 +272,8 @@ sub run {
."\n"
);
# We don't want to leave killall zombies, so ignore SIGCHLD
$SIG{CHLD} = 'IGNORE';
# Tell any existing processes to die, wait 1 second between TERM and KILL
killAll(1);

View File

@ -98,6 +98,11 @@ use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|)
logInit($filter_id?(id=>'zmfilter_'.$filter_id):());
sub HupHandler {
# This idea at this time is to just exit, freeing up the memory.
# zmfilter.pl will be respawned by zmdc.
TermHandler();
return;
Info('Received HUP, reloading');
ZoneMinder::Object::init_cache();
&ZoneMinder::Logger::logHupHandler();
@ -470,7 +475,7 @@ sub uploadArchFile {
}
my $archFile = $event->{MonitorName}.'-'.$event->{Id};
my $archImagePath = getEventPath($event)
my $archImagePath = $event->Path()
.'/'
.(
( $Config{ZM_UPLOAD_ARCH_ANALYSE} )
@ -708,14 +713,18 @@ sub substituteTags {
}
} # end if $first_alarm_frame
if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} ) {
if ( $attachments_ref ) {
if ( $text =~ s/%EV%//g ) {
if ( $$Event{DefaultVideo} ) {
push @$attachments_ref, { type=>'video/mp4', path=>join('/',$Event->Path(), $Event->DefaultVideo()) };
} elsif ( $Config{ZM_OPT_FFMPEG} ) {
my ( $format, $path ) = generateVideo($filter, $Event);
if ( !$format ) {
return undef;
}
push( @$attachments_ref, { type=>"video/$format", path=>$path } );
}
}
if ( $text =~ s/%EVM%//g ) {
my ( $format, $path ) = generateVideo($filter, $Event, 1);
if ( !$format ) {
@ -942,7 +951,7 @@ sub executeCommand {
my $filter = shift;
my $event = shift;
my $event_path = getEventPath($event);
my $event_path = $event->Path();
my $command = $filter->{AutoExecuteCmd};
$command .= " $event_path";

View File

@ -53,7 +53,6 @@ use constant START_DELAY => 30; # To give everything else time to start
@EXTRA_PERL_LIB@
use ZoneMinder;
use ZoneMinder::Storage;
use POSIX;
use DBI;
use autouse 'Data::Dumper'=>qw(Dumper);
@ -72,15 +71,13 @@ sleep( START_DELAY );
my $dbh = zmDbConnect();
my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=?' : 'SELECT * FROM Monitors';
my $sth = $dbh->prepare_cached( $sql )
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
while( 1 ) {
while ( ! ( $dbh and $dbh->ping() ) ) {
Info("Reconnecting to db");
$dbh = zmDbConnect();
if ( ! ( $dbh = zmDbConnect() ) ) {
#What we do here is not that important, so just skip this interval
sleep($Config{ZM_STATS_UPDATE_INTERVAL});
}
}
$dbh->do('DELETE FROM Events_Hour WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 hour)') or Error($dbh->errstr());

View File

@ -33,6 +33,7 @@ use LWP::UserAgent;
use Sys::MemInfo qw(totalmem);
use Sys::CPU qw(cpu_count);
use POSIX qw(strftime uname);
use JSON::MaybeXS;
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
@ -87,7 +88,7 @@ while( 1 ) {
# We should keep *BSD systems in mind when calling system commands
my %telemetry;
$telemetry{uuid} = getUUID($dbh);
$telemetry{ip} = getIP();
($telemetry{city}, $telemetry{region}, $telemetry{country}, $telemetry{latitude}, $telemetry{longitude}) = getGeo();
$telemetry{timestamp} = strftime( '%Y-%m-%dT%H:%M:%S%z', localtime() );
$telemetry{monitor_count} = countQuery($dbh,'Monitors');
$telemetry{event_count} = countQuery($dbh,'Events');
@ -203,22 +204,25 @@ sub getUUID {
return $uuid;
}
# Retrieves the local server's external IP address
sub getIP {
my $ipaddr = '0.0.0.0';
# Retrieve this server's general location information from a GeoIP database
sub getGeo {
my $unknown = 'Unknown';
my $endpoint = 'https://ipinfo.io/geo';
my $ua = LWP::UserAgent->new;
my $server_endpoint = 'https://wiki.zoneminder.com/ip.php';
my $req = HTTP::Request->new(GET => $server_endpoint);
my $req = HTTP::Request->new(GET => $endpoint);
my $resp = $ua->request($req);
my $resp_msg = $resp->decoded_content;
my $resp_code = $resp->code;
if ($resp->is_success) {
$ipaddr = $resp->decoded_content;
my $content = decode_json( $resp_msg );
(my $latitude, my $longitude) = split /,/, $content->{loc};
return ($content->{city}, $content->{region}, $content->{country}, $latitude, $longitude);
} else {
Warning("Geoip data retrieval returned HTTP POST error code: $resp_code");
Debug("Geoip data retrieval failure response message: $resp_msg");
return ($unknown, $unknown, $unknown, $unknown);
}
Debug("Found external ip address of: $ipaddr");
return $ipaddr;
}
# As the name implies, just your average mysql count query

View File

@ -922,6 +922,11 @@ if ( $version ) {
zmDbDisconnect();
die( "Can't find upgrade from version '$version'" );
}
# Re-enable the privacy popup after each upgrade
my $sql = "update Config set Value = 1 where Name = 'ZM_SHOW_PRIVACY'";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" );
}
zmDbDisconnect();

View File

@ -78,6 +78,11 @@ my $sth = $dbh->prepare_cached($sql)
or Fatal("Can't prepare '$sql': ".$dbh->errstr());
while( 1 ) {
while ( ! ( $dbh and $dbh->ping() ) ) {
if ( ! ( $dbh = zmDbConnect() ) ) {
sleep($Config{ZM_WATCH_CHECK_INTERVAL});
}
}
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
or Fatal('Can\'t execute: '.$sth->errstr());
@ -98,7 +103,7 @@ while( 1 ) {
Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time.");
if ( !$capture_time ) {
my $startup_time = zmGetStartupTime($monitor);
if ( $now - $startup_time > $Config{ZM_WATCH_MAX_DELAY} ) {
if ( ( $now - $startup_time ) > $Config{ZM_WATCH_MAX_DELAY} ) {
Info(
"Restarting capture daemon for $$monitor{Name}, no image since startup. ".
"Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}"
@ -111,7 +116,8 @@ while( 1 ) {
}
}
if ( ! $restart ) {
my $max_image_delay = ( $monitor->{MaxFPS}
my $max_image_delay = (
$monitor->{MaxFPS}
&&($monitor->{MaxFPS}>0)
&&($monitor->{MaxFPS}<1)
) ? (3/$monitor->{MaxFPS})

View File

@ -33,8 +33,7 @@ class Camera;
// Abstract base class for cameras. This is intended just to express
// common attributes
//
class Camera
{
class Camera {
protected:
typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC } SourceType;
@ -55,42 +54,41 @@ protected:
bool record_audio;
unsigned int bytes;
public:
Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
virtual ~Camera();
unsigned int getId() const { return( monitor_id ); }
unsigned int getId() const { return monitor_id; }
Monitor *getMonitor();
void setMonitor( Monitor *p_monitor );
SourceType Type() const { return( type ); }
bool IsLocal() const { return( type == LOCAL_SRC ); }
bool IsRemote() const { return( type == REMOTE_SRC ); }
bool IsFile() const { return( type == FILE_SRC ); }
bool IsFfmpeg() const { return( type == FFMPEG_SRC ); }
bool IsLibvlc() const { return( type == LIBVLC_SRC ); }
bool IscURL() const { return( type == CURL_SRC ); }
unsigned int Width() const { return( width ); }
unsigned int Height() const { return( height ); }
unsigned int Colours() const { return( colours ); }
unsigned int SubpixelOrder() const { return( subpixelorder ); }
unsigned int Pixels() const { return( pixels ); }
unsigned int ImageSize() const { return( imagesize ); }
SourceType Type() const { return type; }
bool IsLocal() const { return type == LOCAL_SRC; }
bool IsRemote() const { return type == REMOTE_SRC; }
bool IsFile() const { return type == FILE_SRC; }
bool IsFfmpeg() const { return type == FFMPEG_SRC; }
bool IsLibvlc() const { return type == LIBVLC_SRC; }
bool IscURL() const { return type == CURL_SRC; }
unsigned int Width() const { return width; }
unsigned int Height() const { return height; }
unsigned int Colours() const { return colours; }
unsigned int SubpixelOrder() const { return subpixelorder; }
unsigned int Pixels() const { return pixels; }
unsigned int ImageSize() const { return imagesize; }
unsigned int Bytes() const { return bytes; };
virtual int Brightness( int/*p_brightness*/=-1 ) { return( -1 ); }
virtual int Hue( int/*p_hue*/=-1 ) { return( -1 ); }
virtual int Colour( int/*p_colour*/=-1 ) { return( -1 ); }
virtual int Contrast( int/*p_contrast*/=-1 ) { return( -1 ); }
virtual int Brightness( int/*p_brightness*/=-1 ) { return -1; }
virtual int Hue( int/*p_hue*/=-1 ) { return -1; }
virtual int Colour( int/*p_colour*/=-1 ) { return -1; }
virtual int Contrast( int/*p_contrast*/=-1 ) { return -1; }
bool CanCapture() const { return( capture ); }
bool CanCapture() const { return capture; }
bool SupportsNativeVideo() const {
return (type == FFMPEG_SRC);
//return (type == FFMPEG_SRC )||(type == REMOTE_SRC);
}
virtual int PrimeCapture() { return( 0 ); }
virtual int PrimeCapture() { return 0; }
virtual int PreCapture() = 0;
virtual int Capture(Image &image) = 0;
virtual int PostCapture() = 0;

View File

@ -123,9 +123,9 @@ void process_configfile( char* configFile) {
if ( *line_ptr == '\0' || *line_ptr == '#' )
continue;
// Remove trailing white space
// Remove trailing white space and trailing quotes
char *temp_ptr = line_ptr+strlen(line_ptr)-1;
while ( *temp_ptr == ' ' || *temp_ptr == '\t' ) {
while ( *temp_ptr == ' ' || *temp_ptr == '\t' || *temp_ptr == '\'' || *temp_ptr == '\"') {
*temp_ptr-- = '\0';
temp_ptr--;
}
@ -147,8 +147,9 @@ void process_configfile( char* configFile) {
temp_ptr--;
} while ( *temp_ptr == ' ' || *temp_ptr == '\t' );
// Remove leading white space from the value part
// Remove leading white space and leading quotes from the value part
white_len = strspn( val_ptr, " \t" );
white_len += strspn( val_ptr, "\'\"" );
val_ptr += white_len;
if ( strcasecmp( name_ptr, "ZM_DB_HOST" ) == 0 )

View File

@ -120,9 +120,15 @@ Event::Event(
char id_file[PATH_MAX];
if ( storage->Scheme() == Storage::DEEP ) {
char *path_ptr = path;
path_ptr += snprintf(path_ptr, sizeof(path), "%s/%d", storage->Path(), monitor->Id());
// Try to make the Monitor Dir. Normally this would exist, but in odd cases might not.
if ( mkdir(path, 0755) ) {
if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path, strerror(errno));
}
if ( storage->Scheme() == Storage::DEEP ) {
int dt_parts[6];
dt_parts[0] = stime->tm_year-100;
@ -141,10 +147,9 @@ Event::Event(
errno = 0;
if ( mkdir(path, 0755) ) {
// FIXME This should not be fatal. Should probably move to a different storage area.
if ( errno != EEXIST ) {
if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path, strerror(errno));
}
}
if ( i == 2 )
strncpy(date_path, path, sizeof(date_path));
else if ( i >= 3 )
@ -155,29 +160,25 @@ Event::Event(
if ( symlink(time_path, id_file) < 0 )
Error("Can't symlink %s -> %s: %s", id_file, path, strerror(errno));
} else if ( storage->Scheme() == Storage::MEDIUM ) {
char *path_ptr = path;
path_ptr += snprintf(
path_ptr, sizeof(path), "%s/%d/%04d-%02d-%02d",
storage->Path(), monitor->Id(), stime->tm_year+1900, stime->tm_mon+1, stime->tm_mday
path_ptr, sizeof(path), "/%04d-%02d-%02d",
stime->tm_year+1900, stime->tm_mon+1, stime->tm_mday
);
if ( mkdir(path, 0755) ) {
// FIXME This should not be fatal. Should probably move to a different storage area.
if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path, strerror(errno));
}
path_ptr += snprintf(path_ptr, sizeof(path), "/%" PRIu64, id);
if ( mkdir(path, 0755) ) {
// FIXME This should not be fatal. Should probably move to a different storage area.
if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path, strerror(errno));
}
} else {
snprintf(path, sizeof(path), "%s/%d/%" PRIu64, storage->Path(), monitor->Id(), id);
path_ptr += snprintf(path, sizeof(path), "/%" PRIu64, id);
if ( mkdir(path, 0755) ) {
if ( errno != EEXIST ) {
if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path, strerror(errno));
}
}
// Create empty id tag file
snprintf(id_file, sizeof(id_file), "%s/.%" PRIu64, path, id);
@ -540,7 +541,7 @@ Debug(3, "Writing video");
if ( score < 0 )
score = 0;
bool db_frame = ( frame_type != BULK ) || ((frames%config.bulk_frame_interval)==0) || !frames;
bool db_frame = ( frame_type != BULK ) || (!frames) || ((frames%config.bulk_frame_interval)==0) ;
if ( db_frame ) {
Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, Event::frame_type_names[frame_type] );
@ -594,11 +595,12 @@ Debug(3, "Writing video");
max_score = score;
if ( alarm_image ) {
snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path, frames);
Debug(1, "Writing analysis frame %d", frames);
if ( monitor->GetOptSaveJPEGs() & 2 ) {
WriteFrameImage(alarm_image, timestamp, event_file, true);
snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path, frames);
Debug(1, "Writing analysis frame %d", frames);
if ( ! WriteFrameImage(alarm_image, timestamp, event_file, true) ) {
Error("Failed to write analysis frame image");
}
}
}
}

View File

@ -649,10 +649,8 @@ void Image::Assign( const Image &image ) {
(*fptr_imgbufcpy)(buffer, image.buffer, size);
}
Image *Image::HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p_subpixelorder, const Box *limits )
{
if ( colours != ZM_COLOUR_GRAY8 )
{
Image *Image::HighlightEdges( Rgb colour, unsigned int p_colours, unsigned int p_subpixelorder, const Box *limits ) {
if ( colours != ZM_COLOUR_GRAY8 ) {
Panic("Attempt to highlight image edges when colours = %d", colours);
}

View File

@ -160,16 +160,16 @@ public:
static void Initialise();
static void Deinitialise();
inline unsigned int Width() const { return( width ); }
inline unsigned int Height() const { return( height ); }
inline unsigned int Pixels() const { return( pixels ); }
inline unsigned int Colours() const { return( colours ); }
inline unsigned int SubpixelOrder() const { return( subpixelorder ); }
inline unsigned int Size() const { return( size ); }
inline unsigned int Width() const { return width; }
inline unsigned int Height() const { return height; }
inline unsigned int Pixels() const { return pixels; }
inline unsigned int Colours() const { return colours; }
inline unsigned int SubpixelOrder() const { return subpixelorder; }
inline unsigned int Size() const { return size; }
/* Internal buffer should not be modified from functions outside of this class */
inline const uint8_t* Buffer() const { return( buffer ); }
inline const uint8_t* Buffer( unsigned int x, unsigned int y= 0 ) const { return( &buffer[colours*((y*width)+x)] ); }
inline const uint8_t* Buffer() const { return buffer; }
inline const uint8_t* Buffer( unsigned int x, unsigned int y= 0 ) const { return &buffer[colours*((y*width)+x)]; }
/* Request writeable buffer */
uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder);
@ -196,7 +196,7 @@ public:
}
inline Image &operator=( const unsigned char *new_buffer ) {
(*fptr_imgbufcpy)(buffer, new_buffer, size);
return( *this );
return *this;
}
bool ReadRaw( const char *filename );

File diff suppressed because it is too large Load Diff

View File

@ -38,7 +38,7 @@
#endif
bool Logger::smInitialised = false;
Logger *Logger::smInstance = 0;
Logger *Logger::smInstance = NULL;
Logger::StringMap Logger::smCodes;
Logger::IntMap Logger::smSyslogPriorities;
@ -57,9 +57,9 @@ static void subtractTime( struct timeval * const tp1, struct timeval * const tp2
void Logger::usrHandler( int sig ) {
Logger *logger = fetch();
if ( sig == SIGUSR1 )
if ( sig == SIGUSR1 ) {
logger->level(logger->level()+1);
else if ( sig == SIGUSR2 )
} else if ( sig == SIGUSR2 )
logger->level(logger->level()-1);
Info("Logger - Level changed to %d", logger->level());
}
@ -133,11 +133,11 @@ void Logger::initialise(const std::string &id, const Options &options) {
std::string tempLogFile;
if ( (envPtr = getTargettedEnv("LOG_FILE")) )
if ( (envPtr = getTargettedEnv("LOG_FILE")) ) {
tempLogFile = envPtr;
else if ( options.mLogFile.size() )
} else if ( options.mLogFile.size() ) {
tempLogFile = options.mLogFile;
else {
} else {
if ( options.mLogPath.size() ) {
mLogPath = options.mLogPath;
}
@ -335,6 +335,10 @@ Logger::Level Logger::level(Logger::Level level) {
mEffectiveLevel = mSyslogLevel;
if ( mEffectiveLevel > mLevel)
mEffectiveLevel = mLevel;
// DEBUG levels should flush
if ( mLevel > INFO )
mFlush = true;
}
return mLevel;
}
@ -540,7 +544,13 @@ void Logger::logPrint( bool hex, const char * const filepath, const int line, co
if ( ! db_mutex.trylock() ) {
mysql_real_escape_string( &dbconn, escapedString, syslogStart, strlen(syslogStart) );
snprintf( sql, sizeof(sql), "insert into Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) values ( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line );
snprintf(sql, sizeof(sql),
"INSERT INTO Logs "
"( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line )"
" VALUES "
"( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )",
timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line
);
if ( mysql_query(&dbconn, sql) ) {
Level tempDatabaseLevel = mDatabaseLevel;
databaseLevel(NOLOG);

View File

@ -423,16 +423,10 @@ Monitor::Monitor(
snprintf(monitor_dir, sizeof(monitor_dir), "%s/%d", storage->Path(), id);
if ( purpose == CAPTURE ) {
struct stat statbuf;
if ( stat(monitor_dir, &statbuf) ) {
if ( errno == ENOENT || errno == ENOTDIR ) {
if ( mkdir(monitor_dir, 0755) ) {
if ( errno != EEXIST ) {
Error("Can't mkdir %s: %s", monitor_dir, strerror(errno));
}
} else {
Warning("Error stat'ing %s, may be fatal. error is %s", monitor_dir, strerror(errno));
}
}
if ( ! this->connect() ) {
@ -609,7 +603,8 @@ bool Monitor::connect() {
next_buffer.image = new Image( width, height, camera->Colours(), camera->SubpixelOrder());
next_buffer.timestamp = new struct timeval;
}
if ( ( purpose == ANALYSIS ) && analysis_fps ) {
if ( purpose == ANALYSIS ) {
if ( analysis_fps ) {
// Size of pre event buffer must be greater than pre_event_count
// if alarm_frame_count > 1, because in this case the buffer contains
// alarmed images that must be discarded when event is created
@ -620,6 +615,7 @@ bool Monitor::connect() {
*pre_event_buffer[i].timestamp = {0,0};
pre_event_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder());
}
}
timestamps = new struct timeval *[pre_event_count];
images = new Image *[pre_event_count];
@ -1577,8 +1573,8 @@ Error("Creating new event when one exists");
else
pre_index = ((index + image_buffer_count) - pre_event_count)%image_buffer_count;
Debug(4,"Resulting pre_index(%d) from index(%d) + image_buffer_count(%d) - pre_event_count(%d) % %d",
pre_index, index, image_buffer_count, pre_event_count, image_buffer_count);
Debug(4,"Resulting pre_index(%d) from index(%d) + image_buffer_count(%d) - pre_event_count(%d)",
pre_index, index, image_buffer_count, pre_event_count);
// Seek forward the next filled slot in to the buffer (oldest data)
// from the current position
@ -1623,7 +1619,6 @@ Error("Creating new event when one exists");
pre_index = (pre_index + 1)%image_buffer_count;
}
}
event->AddFrames( pre_event_images, images, timestamps );
}
if ( alarm_frame_count ) {
@ -1736,6 +1731,7 @@ Error("Creating new event when one exists");
}
shared_data->state = state = IDLE;
last_section_mod = 0;
trigger_data->trigger_state = TRIGGER_CANCEL;
} // end if ( trigger_data->trigger_state != TRIGGER_OFF )
if ( (!signal_change && signal) && (function == MODECT || function == MOCORD) ) {
@ -2388,7 +2384,9 @@ int Monitor::Capture() {
}
if ( captureResult < 0 ) {
Warning("Return from Capture (%d), signal loss", captureResult);
Info("Return from Capture (%d), signal loss", captureResult);
// Tell zma to end the event. zma will reset TRIGGER
trigger_data->trigger_state = TRIGGER_OFF;
// Unable to capture image for temporary reason
// Fake a signal loss image
Rgb signalcolor;
@ -2474,9 +2472,7 @@ int Monitor::Capture() {
//Info( "%d -> %d -> %lf -> %lf", now-last_fps_time, fps_report_interval/(now-last_fps_time), double(fps_report_interval)/(now-last_fps_time), fps );
Info("%s: images:%d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec", name, image_count, new_fps, new_capture_bandwidth);
last_fps_time = now;
if ( new_fps != fps ) {
fps = new_fps;
db_mutex.lock();
static char sql[ZM_SQL_SML_BUFSIZ];
snprintf(sql, sizeof(sql),
@ -2486,7 +2482,7 @@ int Monitor::Capture() {
Error("Can't run query: %s", mysql_error(&dbconn));
}
db_mutex.unlock();
} // end if new_fps != fps
Debug(4,sql);
} // end if time has changed since last update
} // end if it might be time to report the fps
} // end if captureResult

View File

@ -290,6 +290,8 @@ protected:
bool last_signal;
bool last_signal;
double fps;
unsigned int last_camera_bytes;

View File

@ -482,7 +482,7 @@ void MonitorStream::runStream() {
swap_path = staticConfig.PATH_SWAP;
Debug( 3, "Checking swap path folder: %s", swap_path.c_str() );
if ( checkSwapPath(swap_path.c_str(), false) ) {
if ( checkSwapPath(swap_path.c_str(), true) ) {
swap_path += stringtf("/zmswap-m%d", monitor->Id());
Debug(4, "Checking swap path subfolder: %s", swap_path.c_str());
@ -633,7 +633,7 @@ Debug(2, "Have checking command Queue for connkey: %d", connkey );
// Send the next frame
Monitor::Snapshot *snap = &monitor->image_buffer[index];
//Debug(2, "sending Frame.");
Debug(2, "sending Frame.");
if ( !sendFrame(snap->image, snap->timestamp) ) {
Debug(2, "sendFrame failed, quiting.");
zm_terminate = true;
@ -687,7 +687,7 @@ Debug(2, "Have checking command Queue for connkey: %d", connkey );
} // end if buffered playback
frame_count++;
} else {
Debug(5,"Waiting for capture");
Debug(4,"Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index);
} // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index )
unsigned long sleep_time = (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2)));

View File

@ -267,7 +267,20 @@ bool StreamBase::sendTextFrame( const char *frame_text ) {
void StreamBase::openComms() {
if ( connkey > 0 ) {
unsigned int length = snprintf(sock_path_lock, sizeof(sock_path_lock), "%s/zms-%06d.lock", staticConfig.PATH_SOCKS.c_str(), connkey);
// Have to mkdir because systemd is now chrooting and the dir may not exist
if ( mkdir(staticConfig.PATH_SOCKS.c_str(), 0755) ) {
if ( errno != EEXIST ) {
Error("Can't mkdir ZM_PATH_SOCKS %s: %s.", staticConfig.PATH_SOCKS.c_str(), strerror(errno));
}
}
unsigned int length = snprintf(
sock_path_lock,
sizeof(sock_path_lock),
"%s/zms-%06d.lock",
staticConfig.PATH_SOCKS.c_str(),
connkey
);
if ( length >= sizeof(sock_path_lock) ) {
Warning("Socket lock path was truncated.");
}
@ -292,7 +305,13 @@ void StreamBase::openComms() {
Debug(1, "Have socket %d", sd);
}
length = snprintf(loc_sock_path, sizeof(loc_sock_path), "%s/zms-%06ds.sock", staticConfig.PATH_SOCKS.c_str(), connkey);
length = snprintf(
loc_sock_path,
sizeof(loc_sock_path),
"%s/zms-%06ds.sock",
staticConfig.PATH_SOCKS.c_str(),
connkey
);
if ( length >= sizeof(loc_sock_path) ) {
Warning("Socket path was truncated.");
length = sizeof(loc_sock_path)-1;

View File

@ -178,7 +178,6 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
}
Monitor::Orientation orientation = monitor->getOrientation();
Debug(3, "Have orientation");
if (orientation) {
if (orientation == Monitor::ROTATE_0) {
} else if (orientation == Monitor::ROTATE_90) {
@ -336,14 +335,14 @@ bool VideoStore::open() {
} else if (av_dict_count(opts) != 0) {
Warning("some options not set\n");
}
if (opts) av_dict_free(&opts);
if (ret < 0) {
Error("Error occurred when writing out file header to %s: %s\n",
filename, av_make_error_string(ret).c_str());
return false;
}
if (opts) av_dict_free(&opts);
return true;
}
} // end VideoStore::open()
VideoStore::~VideoStore() {
@ -353,6 +352,9 @@ VideoStore::~VideoStore() {
// The codec queues data. We need to send a flush command and out
// whatever we get. Failures are not fatal.
AVPacket pkt;
// Without these we seg fault I don't know why.
pkt.data = NULL;
pkt.size = 0;
av_init_packet(&pkt);
while (1) {
@ -402,8 +404,10 @@ VideoStore::~VideoStore() {
} // end if audio_out_codec
// Flush Queues
Debug(1,"Flushing interleaved queues");
av_interleaved_write_frame(oc, NULL);
Debug(1,"Writing trailer");
/* Write the trailer before close */
if (int rc = av_write_trailer(oc)) {
Error("Error writing trailer %s", av_err2str(rc));
@ -411,7 +415,7 @@ VideoStore::~VideoStore() {
Debug(3, "Sucess Writing trailer");
}
// WHen will be not using a file ?
// When will we not be using a file ?
if ( !(out_format->flags & AVFMT_NOFILE) ) {
/* Close the out file. */
Debug(2, "Closing");
@ -507,6 +511,7 @@ bool VideoStore::setup_resampler() {
}
Debug(2, "Have audio out codec");
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
// audio_out_ctx = audio_out_stream->codec;
audio_out_ctx = avcodec_alloc_context3(audio_out_codec);
@ -517,6 +522,12 @@ bool VideoStore::setup_resampler() {
}
Debug(2, "Have audio_out_ctx");
// Now copy them to the out stream
audio_out_stream = avformat_new_stream(oc, NULL);
#else
audio_out_stream = avformat_new_stream(oc, NULL);
audio_out_ctx = audio_out_stream->codec;
#endif
/* put sample parameters */
audio_out_ctx->bit_rate = audio_in_ctx->bit_rate;
@ -559,8 +570,6 @@ bool VideoStore::setup_resampler() {
audio_out_ctx->time_base =
(AVRational){1, audio_out_ctx->sample_rate};
// Now copy them to the out stream
audio_out_stream = avformat_new_stream(oc, audio_out_codec);
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
ret = avcodec_parameters_from_context(audio_out_stream->codecpar,

View File

@ -236,7 +236,9 @@ bool Zone::CheckAlarms(const Image *delta_image) {
if ( pixel_diff_count && alarm_pixels )
pixel_diff = pixel_diff_count/alarm_pixels;
Debug(5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d", alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff);
Debug(5, "Got %d alarmed pixels, need %d -> %d, avg pixel diff %d",
alarm_pixels, min_alarm_pixels, max_alarm_pixels, pixel_diff);
if ( alarm_pixels ) {
if ( min_alarm_pixels && (alarm_pixels < (unsigned int)min_alarm_pixels) ) {
@ -252,7 +254,11 @@ bool Zone::CheckAlarms(const Image *delta_image) {
return false;
}
if (max_alarm_pixels != 0)
score = (100*alarm_pixels)/max_alarm_pixels;
else
score = (100*alarm_pixels)/polygon.Area();
if ( score < 1 )
score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */
Debug(5, "Current score is %d", score);
@ -312,7 +318,8 @@ bool Zone::CheckAlarms(const Image *delta_image) {
if ( config.record_diag_images )
diff_image->WriteJpeg(diag_path);
Debug(5, "Got %d filtered pixels, need %d -> %d", alarm_filter_pixels, min_filter_pixels, max_filter_pixels);
Debug(5, "Got %d filtered pixels, need %d -> %d",
alarm_filter_pixels, min_filter_pixels, max_filter_pixels);
if ( alarm_filter_pixels ) {
if ( min_filter_pixels && (alarm_filter_pixels < min_filter_pixels) ) {
@ -328,7 +335,11 @@ bool Zone::CheckAlarms(const Image *delta_image) {
return false;
}
score = (100*alarm_filter_pixels)/(polygon.Area());
if (max_filter_pixels != 0)
score = (100*alarm_filter_pixels)/max_filter_pixels;
else
score = (100*alarm_filter_pixels)/polygon.Area();
if ( score < 1 )
score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */
Debug(5, "Current score is %d", score);
@ -438,7 +449,8 @@ bool Zone::CheckAlarms(const Image *delta_image) {
alarm_blobs--;
Debug(6, "Merging blob %d with %d at %d,%d, %d current blobs", bss->tag, bsm->tag, x, y, alarm_blobs);
Debug(6, "Merging blob %d with %d at %d,%d, %d current blobs",
bss->tag, bsm->tag, x, y, alarm_blobs);
// Clear out the old blob
bss->tag = 0;
@ -476,7 +488,11 @@ bool Zone::CheckAlarms(const Image *delta_image) {
BlobStats *bs = &blob_stats[i];
// See if we can recycle one first, only if it's at least two rows up
if ( bs->count && bs->hi_y < (int)(y-1) ) {
if ( (min_blob_pixels && bs->count < min_blob_pixels) || (max_blob_pixels && bs->count > max_blob_pixels) ) {
if (
(min_blob_pixels && bs->count < min_blob_pixels)
||
(max_blob_pixels && bs->count > max_blob_pixels)
) {
if ( config.create_analysis_images || config.record_diag_images ) {
for ( int sy = bs->lo_y; sy <= bs->hi_y; sy++ ) {
spdiff = diff_buff + ((diff_width * sy) + bs->lo_x);
@ -590,7 +606,11 @@ bool Zone::CheckAlarms(const Image *delta_image) {
return false;
}
score = (100*alarm_blob_pixels)/(polygon.Area());
if (max_blob_pixels != 0)
score = (100*alarm_blob_pixels)/(max_blob_pixels);
else
score = (100*alarm_blob_pixels)/polygon.Area();
if ( score < 1 )
score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */
Debug(5, "Current score is %d", score);

View File

@ -181,5 +181,5 @@ int main( int argc, char *argv[] ) {
Image::Deinitialise();
logTerm();
zmDbClose();
return( 0 );
return 0;
}

View File

@ -300,19 +300,22 @@ int main(int argc, char *argv[]) {
if ( next_delays[i] <= min_delay || next_delays[i] <= 0 ) {
if ( monitors[i]->PreCapture() < 0 ) {
Error("Failed to pre-capture monitor %d %d (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
Error("Failed to pre-capture monitor %d %d (%d/%d)",
monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
monitors[i]->Close();
result = -1;
break;
}
if ( monitors[i]->Capture() < 0 ) {
Error("Failed to capture image from monitor %d %s (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
Info("Failed to capture image from monitor %d %s (%d/%d)",
monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
monitors[i]->Close();
result = -1;
break;
}
if ( monitors[i]->PostCapture() < 0 ) {
Error("Failed to post-capture monitor %d %s (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
Error("Failed to post-capture monitor %d %s (%d/%d)",
monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
monitors[i]->Close();
result = -1;
break;

View File

@ -93,7 +93,8 @@ if [ "$RELEASE" != "" ]; then
else
GITHUB_FORK="ZoneMinder";
fi
BRANCH="release-$RELEASE"
# We use a tag instead of a branch atm.
BRANCH=$RELEASE
else
if [ "$GITHUB_FORK" == "" ]; then
echo "Defaulting to ZoneMinder upstream git"
@ -188,6 +189,12 @@ if [ "$URGENCY" = "" ]; then
URGENCY="medium"
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
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
@ -221,12 +228,6 @@ zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
EOF
fi;
rm -rf .git
rm .gitignore
cd ../
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
cd $DIRECTORY.orig
if [ $TYPE == "binary" ]; then
# Auto-install all ZoneMinder's depedencies using the Debian control file
sudo apt-get install devscripts equivs
@ -287,7 +288,8 @@ else
SC="zoneminder_${VERSION}-${DISTRO}${PACKAGE_VERSION}_source.changes";
PPA="";
if [ "$RELEASE" != "" ]; then
PPA="ppa:iconnor/zoneminder";
IFS='.' read -r -a VERSION <<< "$RELEASE"
PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}"
else
if [ "$BRANCH" == "" ]; then
PPA="ppa:iconnor/zoneminder-master";

View File

@ -154,7 +154,7 @@ movecrud () {
echo "Unpacking CakePHP-Enum-Behavior plugin..."
tar -xzf build/cakephp-enum-behavior-${CEBVER}.tar.gz
rmdir web/api/app/Plugin/CakePHP-Enum-Behavior
mv -f crud-${CEBVER} web/api/app/Plugin/CakePHP-Enum-Behavior
mv -f CakePHP-Enum-Behavior-${CEBVER} web/api/app/Plugin/CakePHP-Enum-Behavior
fi
}

View File

@ -14,9 +14,17 @@ $| = 1;
my @monitors;
my $dbh = zmDbConnect();
my $sql = "SELECT * FROM Monitors";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute() or die( "Can't execute '$sql': ".$sth->errstr() );
my $sql = "SELECT * FROM Monitors
WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )".
( $Config{ZM_SERVER_ID} ? 'AND ServerId=?' : '' )
;
my $sth = $dbh->prepare_cached( $sql )
or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute()
or die( "Can't execute '$sql': ".$sth->errstr() );
while ( my $monitor = $sth->fetchrow_hashref() ) {
push( @monitors, $monitor );
@ -24,6 +32,12 @@ while ( my $monitor = $sth->fetchrow_hashref() ) {
while (1) {
foreach my $monitor (@monitors) {
# Check shared memory ok
if ( !zmMemVerify( $monitor ) ) {
zmMemInvalidate( $monitor );
next;
}
my $monitorState = zmGetMonitorState($monitor);
printState($monitor->{Id}, $monitor->{Name}, $monitorState);
}

View File

@ -1 +1 @@
1.31.44
1.32.0

View File

@ -93,7 +93,7 @@ Info("Testing connection to " . $url_bits['host'].':'.$port);
foreach ( $available_streams as &$stream ) {
# check for existence in db.
$stream['url'] = unparse_url( $stream, array('path'=>'/','query'=>'action=stream') );
$monitors = Monitor::find_all( array('Path'=>$stream['url']) );
$monitors = Monitor::find( array('Path'=>$stream['url']) );
if ( count($monitors) ) {
Info("Found monitors matching " . $stream['url'] );
$stream['Monitor'] = $monitors[0];
@ -135,7 +135,7 @@ if ( canEdit( 'Monitors' ) ) {
if ( 0 ) {
// Shortcut test
$monitors = Monitor::find_all( array( 'Path'=>$_REQUEST['url'] ) );
$monitors = Monitor::find( array('Path'=>$_REQUEST['url']) );
if ( count( $monitors ) ) {
Info("Monitor found for " . $_REQUEST['url']);
$url_bits['url'] = $_REQUEST['url'];

View File

@ -33,7 +33,7 @@ switch ( $_REQUEST['task'] ) {
if ( !canView('System') )
ajaxError('Insufficient permissions to view log entries');
$servers = Server::find_all();
$servers = Server::find();
$servers_by_Id = array();
# There is probably a better way to do this.
foreach ( $servers as $server ) {
@ -59,7 +59,7 @@ switch ( $_REQUEST['task'] ) {
$sortField = $_REQUEST['sortField'];
}
}
$sortOrder = (isset($_REQUEST['sortOrder']) and $_REQUEST['sortOrder']) == 'asc' ? 'asc':'desc';
$sortOrder = (isset($_REQUEST['sortOrder']) and ($_REQUEST['sortOrder'] == 'asc')) ? 'asc' : 'desc';
$filter = isset($_REQUEST['filter']) ? $_REQUEST['filter'] : array();
$total = dbFetchOne('SELECT count(*) AS Total FROM Logs', 'Total');
@ -93,7 +93,10 @@ switch ( $_REQUEST['task'] ) {
$sql .= ' ORDER BY '.$sortField.' '.$sortOrder.' LIMIT '.$limit;
$logs = array();
foreach ( dbFetchAll($sql, NULL, $values) as $log ) {
$log['DateTime'] = preg_replace('/^\d+/', strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])), $log['TimeKey']);
$log['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey']));
#Warning("TimeKey: " . $log['TimeKey'] . 'Intval:'.intval($log['TimeKey']).' DateTime:'.$log['DateTime']);
#$log['DateTime'] = preg_replace('/^\d+/', strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])), $log['TimeKey']);
$log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : '';
$log['Message'] = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $log['Message']);
foreach( $filterFields as $field ) {
@ -109,7 +112,7 @@ switch ( $_REQUEST['task'] ) {
} else if ( $field == 'ServerId' ) {
$options['ServerId'][$value] = ( $value and isset($servers_by_Id[$value]) ) ? $servers_by_Id[$value]->Name() : '';
} else if ( isset($log[$field]) ) {
$options[$field][$log[$field]] = $log[$field];
$options[$field][$log[$field]] = $value;
}
}
$logs[] = $log;
@ -122,7 +125,7 @@ switch ( $_REQUEST['task'] ) {
'available' => isset($available) ? $available : $total,
'logs' => $logs,
'state' => logState(),
'options' => $options
'options' => $options,
) );
break;
}
@ -150,7 +153,7 @@ switch ( $_REQUEST['task'] ) {
}
$sortOrder = (isset($_POST['sortOrder']) and $_POST['sortOrder']) == 'asc' ? 'asc':'desc';
$servers = Server::find_all();
$servers = Server::find();
$servers_by_Id = array();
# There is probably a better way to do this.
foreach ( $servers as $server ) {
@ -347,7 +350,7 @@ switch ( $_REQUEST['task'] ) {
</logexport>' );
break;
}
$exportExt = "xml";
$exportExt = 'xml';
break;
}
fclose( $exportFP );
@ -363,10 +366,10 @@ switch ( $_REQUEST['task'] ) {
ajaxError('Insufficient permissions to download logs');
if ( empty($_REQUEST['key']) )
Fatal( "No log export key given" );
Fatal('No log export key given');
$exportKey = $_REQUEST['key'];
if ( empty($_REQUEST['format']) )
Fatal( "No log export format given" );
Fatal('No log export format given');
$format = $_REQUEST['format'];
switch( $format ) {
@ -389,15 +392,15 @@ switch ( $_REQUEST['task'] ) {
$exportFile = "zm-log.$exportExt";
$exportPath = ZM_PATH_SWAP."/zm-log-$exportKey.$exportExt";
header( "Pragma: public" );
header( "Expires: 0" );
header( "Cache-Control: must-revalidate, post-check=0, pre-check=0" );
header( "Cache-Control: private", false ); // required by certain browsers
header( "Content-Description: File Transfer" );
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: private', false ); // required by certain browsers
header('Content-Description: File Transfer');
header('Content-Disposition: attachment; filename="'.$exportFile.'"' );
header( "Content-Transfer-Encoding: binary" );
header( "Content-Type: application/force-download" );
header( "Content-Length: ".filesize($exportPath) );
header('Content-Transfer-Encoding: binary');
header('Content-Type: application/force-download');
header('Content-Length: '.filesize($exportPath));
readfile($exportPath);
exit(0);
break;

View File

@ -60,30 +60,47 @@ class AppController extends Controller {
// for role and deny API access in future
// Also checking to do this only if ZM_OPT_USE_AUTH is on
public function beforeFilter() {
$this->loadModel('Config');
$options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_API'));
$config = $this->Config->find('first', $options);
$zmOptApi = $config['Config']['Value'];
if ( $zmOptApi != '1' ) {
if ( ! ZM_OPT_USE_API ) {
throw new UnauthorizedException(__('API Disabled'));
return;
}
# For use throughout the app. If not logged in, this will be null.
global $user;
$user = $this->Session->read('user');
if ( ZM_OPT_USE_AUTH ) {
require_once '../../../includes/auth.php';
$mUser = $this->request->query('user') ? $this->request->query('user') : $this->request->data('user');
$mPassword = $this->request->query('pass') ? $this->request->query('pass') : $this->request->data('pass');
$mAuth = $this->request->query('auth') ? $this->request->query('auth') : $this->request->data('auth');
if ( $mUser and $mPassword ) {
$user = userLogin($mUser, $mPassword);
if ( !$user ) {
throw new UnauthorizedException(__('User not found or incorrect password'));
return;
}
} else if ( $mAuth ) {
$user = getAuthUser($mAuth);
if ( !$user ) {
throw new UnauthorizedException(__('Invalid Auth Key'));
return;
}
}
// We need to reject methods that are not authenticated
// besides login and logout
if (
strcasecmp($this->params->action, 'login')
&&
strcasecmp($this->params->action,"logout")
) {
if ( !$this->Session->read('user.Username') ) {
if ( strcasecmp($this->params->action, 'logout') ) {
if ( !( $user and $user['Username'] ) ) {
throw new UnauthorizedException(__('Not Authenticated'));
return;
} else if ( !$this->Session->read('user.Enabled') ) {
} else if ( !( $user and $user['Enabled'] ) ) {
throw new UnauthorizedException(__('User is not enabled'));
return;
}
}
} # end if ! login or logout
} # end if ZM_OPT_AUTH
} # end function beforeFilter()
}

View File

@ -1,5 +1,6 @@
<?php
App::uses('AppController', 'Controller');
/**
* Events Controller
*
@ -16,8 +17,9 @@ class EventsController extends AppController {
public function beforeFilter() {
parent::beforeFilter();
$canView = $this->Session->Read('eventPermission');
if ( $canView == 'None' ) {
global $user;
$canView = (!$user) || ($user['Events'] != 'None');
if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
@ -32,9 +34,10 @@ class EventsController extends AppController {
public function index() {
$this->Event->recursive = -1;
$allowedMonitors = preg_split('@,@', $this->Session->Read('allowedMonitors'), NULL, PREG_SPLIT_NO_EMPTY);
global $user;
$allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null;
if ( !empty($allowedMonitors) ) {
if ( $allowedMonitors ) {
$mon_options = array('Event.MonitorId' => $allowedMonitors);
} else {
$mon_options = '';
@ -91,7 +94,7 @@ class EventsController extends AppController {
}
$this->set(compact('events'));
}
} // end public function index()
/**
* view method
@ -108,9 +111,10 @@ class EventsController extends AppController {
throw new NotFoundException(__('Invalid event'));
}
$allowedMonitors = preg_split('@,@', $this->Session->Read('allowedMonitors'), NULL, PREG_SPLIT_NO_EMPTY);
global $user;
$allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null;
if ( !empty($allowedMonitors) ) {
if ( $allowedMonitors ) {
$mon_options = array('Event.MonitorId' => $allowedMonitors);
} else {
$mon_options = '';
@ -149,7 +153,9 @@ class EventsController extends AppController {
*/
public function add() {
if ( $this->Session->Read('eventPermission') != 'Edit' ) {
global $user;
$canEdit = (!$user) || ($user['Events'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -173,7 +179,9 @@ class EventsController extends AppController {
*/
public function edit($id = null) {
if ( $this->Session->Read('eventPermission') != 'Edit' ) {
global $user;
$canEdit = (!$user) || ($user['Events'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -204,7 +212,9 @@ class EventsController extends AppController {
* @return void
*/
public function delete($id = null) {
if ( $this->Session->Read('eventPermission') != 'Edit' ) {
global $user;
$canEdit = (!$user) || ($user['Events'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -259,7 +269,7 @@ class EventsController extends AppController {
$moreconditions = $moreconditions . ' AND '.$name.$param;
}
$query = $this->Event->query("select MonitorId, COUNT(*) AS Count from Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;");
$query = $this->Event->query("SELECT MonitorId, COUNT(*) AS Count FROM Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;");
foreach ($query as $result) {
$results[$result['Events']['MonitorId']] = $result[0]['Count'];
@ -336,7 +346,7 @@ class EventsController extends AppController {
$thumbData['Width'] = (int)$thumbWidth;
$thumbData['Height'] = (int)$thumbHeight;
return( $thumbData );
return $thumbData;
}
public function archive($id = null) {

View File

@ -30,94 +30,8 @@ class HostController extends AppController {
));
}
function login() {
$options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH'));
$config = $this->Config->find('first', $options);
$zmOptAuth = $config['Config']['Value'];
if ( $zmOptAuth == '1' ) {
require_once "../../../includes/auth.php";
global $user;
$user = $this->Session->read('user');
$mUser = $this->request->data('user');
$mPassword = $this->request->data('pass');
$mAuth = $this->request->data('auth');
if ( $mUser and $mPassword) {
$user = userLogin($mUser, $mPassword);
if ( !$user ) {
throw new UnauthorizedException(__('User not found or incorrect password'));
return;
}
}
elseif ( $mAuth ) {
$user = getAuthUser($mAuth);
if ( ! $user ) {
throw new UnauthorizedException(__('User not found or incorrect password'));
return;
}
}
else {
throw new UnauthorizedException(__('missing credentials'));
}
if ( 0 and $user ) {
# We have to redo the session variables because cakephp's Session code will overwrite the normal php session
# Actually I'm not sure that is true. Getting indeterminate behaviour
Logger::Debug("user.Username: " . $this->Session->read('user.Username'));
if ( ! $this->Session->Write('user', $user) )
$this->log("Error writing session var user");
Logger::Debug("user.Username: " . $this->Session->read('user.Username'));
if ( ! $this->Session->Write('user.Username', $user['Username']) )
$this->log("Error writing session var user.Username");
if ( ! $this->Session->Write('password', $user['Password']) )
$this->log("Error writing session var user.Username");
if ( ! $this->Session->Write('user.Enabled', $user['Enabled']) )
$this->log("Error writing session var user.Enabled");
if ( ! $this->Session->Write('remoteAddr', $_SERVER['REMOTE_ADDR']) )
$this->log("Error writing session var remoteAddr");
}
// I don't think this is really needed - the Username part
// Enabled check is ok
if ( !$user['Username'] ) {
throw new UnauthorizedException(__('Not Authenticated'));
return;
} else if ( !$user['Enabled'] ) {
throw new UnauthorizedException(__('User is not enabled'));
return;
}
$this->Session->Write('allowedMonitors',$user['MonitorIds']);
$this->Session->Write('streamPermission',$user['Stream']);
$this->Session->Write('eventPermission',$user['Events']);
$this->Session->Write('controlPermission',$user['Control']);
$this->Session->Write('systemPermission',$user['System']);
$this->Session->Write('monitorPermission',$user['Monitors']);
} else {
// if auth is not on, you can do everything
//$userMonitors = $this->User->find('first', $options);
$this->Session->Write('allowedMonitors','');
$this->Session->Write('streamPermission','View');
$this->Session->Write('eventPermission','Edit');
$this->Session->Write('controlPermission','Edit');
$this->Session->Write('systemPermission','Edit');
$this->Session->Write('monitorPermission','Edit');
}
$cred = $this->_getCredentials();
$ver = $this->_getVersion();
$this->set(array(
@ -130,8 +44,7 @@ class HostController extends AppController {
'version',
'apiversion'
)));
}
} // end function login()
// clears out session
function logout() {
@ -143,7 +56,7 @@ class HostController extends AppController {
'_serialize' => array('result')
));
}
} // end function logout()
private function _getCredentials() {
$credentials = '';
@ -167,8 +80,7 @@ class HostController extends AppController {
}
}
return array($credentials, $appendPassword);
}
} // end function _getCredentials
function getCredentials() {
// ignore debug warnings from other functions
@ -181,8 +93,6 @@ class HostController extends AppController {
) );
}
// If $mid is set, only return disk usage for that monitor
// Else, return an array of total disk usage, and per-monitor
// usage.

View File

@ -21,8 +21,10 @@ class MonitorsController extends AppController {
public function beforeFilter() {
parent::beforeFilter();
$canView = $this->Session->Read('monitorPermission');
if ($canView == 'None') {
global $user;
# We already tested for auth in appController, so we just need to test for specific permission
$canView = (!$user) || ($user['Monitors'] != 'None');
if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
@ -44,8 +46,9 @@ class MonitorsController extends AppController {
$conditions = array();
}
$allowedMonitors=preg_split ('@,@', $this->Session->Read('allowedMonitors'),NULL, PREG_SPLIT_NO_EMPTY);
if (!empty($allowedMonitors)) {
global $user;
$allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null;
if ( $allowedMonitors ) {
$conditions['Monitor.Id' ] = $allowedMonitors;
}
$find_array = array('conditions'=>$conditions,'contain'=>array('Group'));
@ -88,8 +91,9 @@ class MonitorsController extends AppController {
if ( !$this->Monitor->exists($id) ) {
throw new NotFoundException(__('Invalid monitor'));
}
$allowedMonitors=preg_split('@,@', $this->Session->Read('allowedMonitors'), NULL, PREG_SPLIT_NO_EMPTY);
if ( !empty($allowedMonitors) ) {
global $user;
$allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null;
if ( $allowedMonitors ) {
$restricted = array('Monitor.' . $this->Monitor->primaryKey => $allowedMonitors);
} else {
$restricted = '';
@ -115,7 +119,9 @@ class MonitorsController extends AppController {
public function add() {
if ( $this->request->is('post') ) {
if ( $this->Session->Read('systemPermission') != 'Edit' ) {
global $user;
$canAdd = (!$user) || ($user['System'] == 'Edit' );
if ( !$canAdd ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -148,7 +154,9 @@ class MonitorsController extends AppController {
if ( !$this->Monitor->exists($id) ) {
throw new NotFoundException(__('Invalid monitor'));
}
if ( $this->Session->Read('monitorPermission') != 'Edit' ) {
global $user;
$canEdit = (!$user) || ($user['Monitors'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -215,7 +223,7 @@ class MonitorsController extends AppController {
}
public function sourceTypes() {
$sourceTypes = $this->Monitor->query("describe Monitors Type;");
$sourceTypes = $this->Monitor->query('describe Monitors Type;');
preg_match('/^enum\((.*)\)$/', $sourceTypes[0]['COLUMNS']['Type'], $matches);
foreach( explode(',', $matches[1]) as $value ) {
@ -264,7 +272,6 @@ class MonitorsController extends AppController {
$config = $this->Config->find('first', $options);
$zmOptAuth = $config['Config']['Value'];
$options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_RELAY'));
$config = $this->Config->find('first', $options);
$zmAuthRelay = $config['Config']['Value'];
@ -372,5 +379,4 @@ class MonitorsController extends AppController {
$status = exec( $shellcmd );
}
}
} // end class MonitorsController

View File

@ -8,7 +8,6 @@ App::uses('AppController', 'Controller');
*/
class ServersController extends AppController {
/**
* Components
*
@ -16,18 +15,16 @@ class ServersController extends AppController {
*/
public $components = array('Paginator', 'RequestHandler');
public function beforeFilter() {
parent::beforeFilter();
$canView = $this->Session->Read('streamPermission');
if ($canView =='None') {
global $user;
$canView = (!$user) || ($user['System'] != 'None');
if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
}
/**
* index method
*
@ -78,8 +75,9 @@ public function beforeFilter() {
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;
}
@ -103,14 +101,16 @@ public function beforeFilter() {
public function edit($id = null) {
$this->Server->id = $id;
if (!$this->Server->exists($id)) {
throw new NotFoundException(__('Invalid server'));
}
if ($this->Session->Read('systemPermission') != 'Edit')
{
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
if ( !$this->Server->exists($id) ) {
throw new NotFoundException(__('Invalid server'));
}
if ( $this->Server->save($this->request->data) ) {
$message = 'Saved';
} else {
@ -133,15 +133,17 @@ public function beforeFilter() {
* @return void
*/
public function delete($id = null) {
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
$this->Server->id = $id;
if ( !$this->Server->exists() ) {
throw new NotFoundException(__('Invalid server'));
}
if ($this->Session->Read('systemPermission') != 'Edit')
{
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
$this->request->allowMethod('post', 'delete');
#$this->daemonControl($this->Server->id, 'stop');

View File

@ -13,16 +13,14 @@ public $components = array('RequestHandler');
public function beforeFilter() {
parent::beforeFilter();
$canView = $this->Session->Read('systemPermission');
if ($canView =='None')
{
global $user;
$canView = (!$user) || ($user['System'] != 'None');
if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
}
/**
* index method
*
@ -86,8 +84,9 @@ public function beforeFilter() {
throw new NotFoundException(__('Invalid state'));
}
if ($this->Session->Read('systemPermission') != 'Edit')
{
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -111,8 +110,9 @@ public function beforeFilter() {
*/
public function delete($id = null) {
$this->State->id = $id;
if ($this->Session->Read('systemPermission') != 'Edit')
{
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}
@ -129,8 +129,9 @@ public function beforeFilter() {
}
public function change() {
if ($this->Session->Read('systemPermission') != 'Edit')
{
global $user;
$canEdit = (!$user) || ($user['System'] == 'Edit');
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient privileges'));
return;
}

View File

@ -16,8 +16,10 @@ class ZonesController extends AppController {
public function beforeFilter() {
parent::beforeFilter();
$canView = $this->Session->Read('monitorPermission');
if ( $canView =='None' ) {
global $user;
$canView = (!$user) || $user['Monitors'] != 'None';
if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
@ -38,12 +40,12 @@ class ZonesController extends AppController {
'_serialize' => array('zones')
));
}
public function index() {
$this->Zone->recursive = -1;
$allowedMonitors = preg_split('@,@', $this->Session->Read('allowedMonitors'), NULL, PREG_SPLIT_NO_EMPTY);
if ( !empty($allowedMonitors) ) {
global $user;
$allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'],NULL, PREG_SPLIT_NO_EMPTY) : null;
if ( $allowedMonitors ) {
$mon_options = array('Zones.MonitorId' => $allowedMonitors);
} else {
$mon_options = '';
@ -62,6 +64,14 @@ class ZonesController extends AppController {
*/
public function add() {
if ( $this->request->is('post') ) {
global $user;
$canEdit = (!$user) || $user['Monitors'] == 'Edit';
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
$this->Zone->create();
if ( $this->Zone->save($this->request->data) ) {
return $this->flash(__('The zone has been saved.'), array('action' => 'index'));
@ -85,6 +95,12 @@ class ZonesController extends AppController {
throw new NotFoundException(__('Invalid zone'));
}
if ( $this->request->is(array('post', 'put')) ) {
global $user;
$canEdit = (!$user) || $user['Monitors'] == 'Edit';
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
if ( $this->Zone->save($this->request->data) ) {
return $this->flash(__('The zone has been saved.'), array('action' => 'index'));
}
@ -109,6 +125,12 @@ class ZonesController extends AppController {
throw new NotFoundException(__('Invalid zone'));
}
$this->request->allowMethod('post', 'delete');
global $user;
$canEdit = (!$user) || $user['Monitors'] == 'Edit';
if ( !$canEdit ) {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
if ( $this->Zone->delete() ) {
return $this->flash(__('The zone has been deleted.'), array('action' => 'index'));
} else {
@ -144,4 +166,4 @@ class ZonesController extends AppController {
'_serialize' => array('status')
));
}
}
} // end class

View File

@ -110,7 +110,7 @@ class Monitor extends AppModel {
);
public $actsAs = array(
'CakePHP-Enum-Behavior.Enum' => array(
'Type' => array('Local','Remote','File','Ffmpeg','Libvlc','cURL'),
'Type' => array('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite'),
'Function' => array('None','Monitor','Modect','Record','Mocord','Nodect'),
'Orientation' => array('0','90','180','270','hori','vert'),
'OutputCodec' => array('h264','mjpeg','mpeg1','mpeg2'),

@ -1 +1 @@
Subproject commit ea90c0cd7f6e24333a90885e563b5d30b793db29
Subproject commit 7108489f218c54d36d235d3af91d6da2f8311237

@ -1 +1 @@
Subproject commit 0bd63fb464957080ead342db58ca9e01532cf1ef
Subproject commit c3976f1478c681b0bbc132ec3a3e82c3984eeed5

View File

@ -12,6 +12,9 @@ private $defaults = array(
'CanMoveRel' => 0,
'CanMoveCon' => 0,
'CanPan' => 0,
'CanReset' => 0,
'CanSleep' => 0,
'CanWake' => 0,
'MinPanRange' => NULL,
'MaxPanRange' => NULL,
'MinPanStep' => NULL,
@ -103,7 +106,7 @@ private $defaults = array(
if ( $IdOrRow ) {
$row = NULL;
if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) {
$row = dbFetchOne( 'SELECT * FROM Control WHERE Id=?', NULL, array( $IdOrRow ) );
$row = dbFetchOne( 'SELECT * FROM Controls WHERE Id=?', NULL, array( $IdOrRow ) );
if ( ! $row ) {
Error("Unable to load Control record for Id=" . $IdOrRow );
}
@ -163,8 +166,7 @@ private $defaults = array(
}
}
}
public static function find_all( $parameters = null, $options = null ) {
$filters = array();
public static function find( $parameters = null, $options = null ) {
$sql = 'SELECT * FROM Controls ';
$values = array();
@ -186,15 +188,37 @@ private $defaults = array(
}
$sql .= implode(' AND ', $fields );
}
if ( $options and isset($options['order']) ) {
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(".$options['limit'].") passed to Control::find from $file:$line");
return;
}
}
}
$controls = array();
$result = dbQuery($sql, $values);
$results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Control');
foreach ( $results as $row => $obj ) {
$filters[] = $obj;
$controls[] = $obj;
}
return $filters;
return $controls;
}
public static function find_one( $parameters = array() ) {
$results = Control::find( $parameters, array('limit'=>1) );
if ( ! sizeof($results) ) {
return;
}
return $results[0];
}
public function save( $new_values = null ) {

View File

@ -84,7 +84,12 @@ class Event {
}
public function Monitor() {
return new Monitor( isset($this->{'MonitorId'}) ? $this->{'MonitorId'} : NULL );
if ( isset($this->{'MonitorId'}) ) {
$Monitor = Monitor::find_one(array('Id'=>$this->{'MonitorId'}));
if ( $Monitor )
return $Monitor;
}
return new Monitor();
}
public function __call( $fn, array $args){
@ -95,9 +100,13 @@ class Event {
return $this->{$fn};
$backTrace = debug_backtrace();
$file = $backTrace[0]['file'];
$line = $backTrace[0]['line'];
Warning("Unknown function call Event->$fn from $file:$line");
$file = $backTrace[1]['file'];
$line = $backTrace[1]['line'];
Warning("Unknown function call Event->$fn from $file:$line");
Warning(print_r( $this, true ));
}
}
@ -486,7 +495,7 @@ class Event {
isset($event_cache[$parameters['Id']]) ) {
return $event_cache[$parameters['Id']];
}
$results = Event::find_all( $parameters, $options );
$results = Event::find( $parameters, $options );
if ( count($results) > 1 ) {
Error("Event Returned more than 1");
return $results[0];
@ -497,8 +506,7 @@ class Event {
}
}
public static function find_all( $parameters = null, $options = null ) {
$filters = array();
public static function find( $parameters = null, $options = null ) {
$sql = 'SELECT * FROM Events ';
$values = array();
@ -520,13 +528,27 @@ class Event {
}
$sql .= implode(' AND ', $fields );
}
if ( $options and isset($options['order']) ) {
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(".$options['limit'].") passed to Event::find from $file:$line");
return array();
}
}
}
$filters = array();
$result = dbQuery($sql, $values);
$results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Event');
foreach ( $results as $row => $obj ) {
$filters[] = $obj;
$results = $result->fetchALL();
foreach ( $results as $row ) {
$filters[] = new Event($row);
}
return $filters;
}

View File

@ -11,6 +11,7 @@ public $defaults = array(
'AutoDelete' => 0,
'AutoArchive' => 0,
'AutoVideo' => 0,
'AutoUpload' => 0,
'AutoMessage' => 0,
'AutoMove' => 0,
'AutoMoveTo' => 0,
@ -111,15 +112,59 @@ public $defaults = array(
return $this->defaults{'limit'};
}
public static function find_all() {
public static function find( $parameters = null, $options = null ) {
$filters = array();
$result = dbQuery( 'SELECT * FROM Filters ORDER BY Name');
$sql = 'SELECT * FROM Filters ';
$values = array();
if ( $parameters ) {
$fields = array();
$sql .= 'WHERE ';
foreach ( $parameters as $field => $value ) {
if ( $value == null ) {
$fields[] = $field.' IS NULL';
} else if ( is_array( $value ) ) {
$func = function(){return '?';};
$fields[] = $field.' IN ('.implode(',', array_map($func, $value)). ')';
$values += $value;
} else {
$fields[] = $field.'=?';
$values[] = $value;
}
}
$sql .= implode(' AND ', $fields);
}
if ( $options ) {
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(".$options['limit'].") passed to Filter::find from $file:$line");
return array();
}
}
}
$result = dbQuery($sql, $values);
$results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Filter');
foreach ( $results as $row => $obj ) {
$filters[] = $obj;
}
return $filters;
} # end find()
public static function find_one( $parameters = array() ) {
$results = Filter::find($parameters, array('limit'=>1));
if ( ! sizeof($results) ) {
return;
}
return $results[0];
} # end find_one()
public function delete() {
dbQuery('DELETE FROM Filters WHERE Id = ?', array($this->{'Id'}));
@ -140,9 +185,7 @@ public $defaults = array(
$this->{$k} = $v;
}
}
}
} # end class
} # end function set
} # end class Filter
?>

View File

@ -58,27 +58,7 @@ class Group {
}
}
public static function find_one($parameters = null, $options = null) {
global $group_cache;
if (
( count($parameters) == 1 ) and
isset($parameters['Id']) and
isset($group_cache[$parameters['Id']]) ) {
return $group_cache[$parameters['Id']];
}
$results = Group::find_all($parameters, $options);
if ( count($results) > 1 ) {
Error("Group::find_one Returned more than 1");
return $results[0];
} else if ( count($results) ) {
return $results[0];
} else {
return null;
}
}
public static function find_all( $parameters = null ) {
$filters = array();
public static function find( $parameters = null, $options = null ) {
$sql = 'SELECT * FROM Groups ';
$values = array();
@ -98,14 +78,49 @@ class Group {
}
}
$sql .= implode(' AND ', $fields);
} # end if parameters
if ( $options ) {
if ( isset($options['order']) ) {
$sql .= ' ORDER BY ' . $options['order'];
}
$sql .= ' ORDER BY Name';
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(".$options['limit'].") passed to Group::find from $file:$line");
return array();
}
}
} # 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 ) {
$filters[] = $obj;
$groups[] = $obj;
}
return $groups;
} # end find()
public static function find_one($parameters = null, $options = null) {
global $group_cache;
if (
( count($parameters) == 1 ) and
isset($parameters['Id']) and
isset($group_cache[$parameters['Id']]) ) {
return $group_cache[$parameters['Id']];
}
$results = Group::find($parameters, $options);
if ( count($results) > 1 ) {
Error("Group::find_one Returned more than 1");
return $results[0];
} else if ( count($results) ) {
return $results[0];
} else {
return null;
}
return $filters;
}
public function delete() {
@ -182,7 +197,7 @@ class Group {
public static function get_dropdown_options() {
$Groups = array();
foreach ( Group::find_all( ) as $Group ) {
foreach ( Group::find( ) as $Group ) {
$Groups[$Group->Id()] = $Group;
}

View File

@ -2,6 +2,8 @@
require_once('database.php');
require_once('Server.php');
$monitor_cache = array();
class Monitor {
private $defaults = array(
@ -9,17 +11,19 @@ private $defaults = array(
'Name' => '',
'StorageId' => 0,
'ServerId' => 0,
'Type' => 'Ffmpeg',
'Function' => 'None',
'Enabled' => 1,
'LinkedMonitors' => null,
'Width' => null,
'Height' => null,
'Orientation' => null,
'AnalysisFPSLimit' => null,
'ZoneCount' => 0,
'Triggers' => null,
'Type' => 'Ffmpeg',
'MaxFPS' => null,
'AlarmMaxFPS' => null,
'Refresh' => null,
);
private $status_fields = array(
'AnalysisFPS' => null,
@ -163,6 +167,8 @@ private $control_fields = array(
}
}
}
global $monitor_cache;
$monitor_cache[$row['Id']] = $this;
} else {
Error('No row for Monitor ' . $IdOrRow);
@ -276,8 +282,7 @@ private $control_fields = array(
} # end if method_exists
} # end foreach $data as $k=>$v
}
public static function find_all( $parameters = null, $options = null ) {
$filters = array();
public static function find( $parameters = null, $options = null ) {
$sql = 'SELECT * FROM Monitors ';
$values = array();
@ -299,16 +304,45 @@ private $control_fields = array(
}
$sql .= implode(' AND ', $fields);
}
if ( $options and isset($options['order']) ) {
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(".$options['limit'].") passed to Control::find from $file:$line");
return array();
}
}
}
$monitors = array();
$result = dbQuery($sql, $values);
$results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Monitor');
foreach ( $results as $row => $obj ) {
$filters[] = $obj;
$results = $result->fetchALL();
foreach ( $results as $row ) {
$monitors[] = new Monitor($row);
}
return $filters;
return $monitors;
} # end find
public static function find_one( $parameters = array() ) {
global $monitor_cache;
if (
( count($parameters) == 1 ) and
isset($parameters['Id']) and
isset($monitor_cache[$parameters['Id']]) ) {
return $monitor_cache[$parameters['Id']];
}
$results = Monitor::find( $parameters, array('limit'=>1) );
if ( ! sizeof($results) ) {
return;
}
return $results[0];
} # end find_one
public function save($new_values = null) {
@ -487,8 +521,12 @@ private $control_fields = array(
$source = preg_replace( '/^.*\//', '', $this->{'Path'} );
} elseif ( $this->{'Type'} == 'Ffmpeg' || $this->{'Type'} == 'Libvlc' || $this->{'Type'} == 'WebSite' ) {
$url_parts = parse_url( $this->{'Path'} );
if ( ZM_WEB_FILTER_SOURCE == "Hostname" ) { # Filter out everything but the hostname
if ( ZM_WEB_FILTER_SOURCE == 'Hostname' ) { # Filter out everything but the hostname
if ( isset($url_parts['host']) ) {
$source = $url_parts['host'];
} else {
$source = $this->{'Path'};
}
} elseif ( ZM_WEB_FILTER_SOURCE == "NoCredentials" ) { # Filter out sensitive and common items
unset($url_parts['user']);
unset($url_parts['pass']);
@ -507,5 +545,10 @@ private $control_fields = array(
}
return $source;
} // end function Source
public function Url() {
return $this->Server()->Url() .':'. ( ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : $_SERVER['SERVER_PORT'] );
}
} // end class Monitor
?>

View File

@ -1,6 +1,8 @@
<?php
require_once('database.php');
$server_cache = array();
class Server {
private $defaults = array(
'Id' => null,
@ -10,7 +12,10 @@ class Server {
'zmstats' => 1,
'zmtrigger' => 0,
);
public function __construct( $IdOrRow = NULL ) {
global $server_cache;
$row = NULL;
if ( $IdOrRow ) {
if ( is_integer($IdOrRow) or ctype_digit($IdOrRow) ) {
@ -26,44 +31,12 @@ class Server {
foreach ($row as $k => $v) {
$this->{$k} = $v;
}
$server_cache[$row['Id']] = $this;
} else {
$this->{'Name'} = '';
$this->{'Hostname'} = '';
}
}
public static function find_all( $parameters = null, $options = null ) {
$filters = array();
$sql = 'SELECT * FROM Servers ';
$values = array();
if ( $parameters ) {
$fields = array();
$sql .= 'WHERE ';
foreach ( $parameters as $field => $value ) {
if ( $value == null ) {
$fields[] = $field.' IS NULL';
} else if ( is_array( $value ) ) {
$func = function(){return '?';};
$fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')';
$values += $value;
} else {
$fields[] = $field.'=?';
$values[] = $value;
}
}
$sql .= implode(' AND ', $fields );
}
if ( $options and isset($options['order']) ) {
$sql .= ' ORDER BY ' . $options['order'];
}
$result = dbQuery($sql, $values);
$results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Server');
foreach ( $results as $row => $obj ) {
$filters[] = $obj;
}
return $filters;
}
public function Url() {
if ( $this->Id() ) {
@ -96,34 +69,61 @@ class Server {
}
}
}
public static function find( $parameters = array(), $limit = NULL ) {
public static function find( $parameters = null, $options = null ) {
$filters = array();
$sql = 'SELECT * FROM Servers ';
$values = array();
if ( sizeof($parameters) ) {
$sql .= ' WHERE ' . implode( ' AND ', array_map(
function($v){ return $v.'=?'; },
array_keys( $parameters )
) );
$values = array_values( $parameters );
if ( $parameters ) {
$fields = array();
$sql .= 'WHERE ';
foreach ( $parameters as $field => $value ) {
if ( $value == null ) {
$fields[] = $field.' IS NULL';
} else if ( is_array( $value ) ) {
$func = function(){return '?';};
$fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')';
$values += $value;
} else {
$fields[] = $field.'=?';
$values[] = $value;
}
if ( is_integer( $limit ) or ctype_digit( $limit ) ) {
$sql .= ' LIMIT ' . $limit;
}
$sql .= implode(' AND ', $fields );
}
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 Server::find from $file:$line");
return;
Error("Invalid value for limit(".$options['limit'].") passed to Server::find from $file:$line");
return array();
}
}
}
$results = dbFetchAll( $sql, NULL, $values );
if ( $results ) {
return array_map( function($id){ return new Server($id); }, $results );
}
return array();
}
public static function find_one( $parameters = array() ) {
$results = Server::find( $parameters, 1 );
global $server_cache;
if (
( count($parameters) == 1 ) and
isset($parameters['Id']) and
isset($server_cache[$parameters['Id']]) ) {
return $server_cache[$parameters['Id']];
}
$results = Server::find( $parameters, array('limit'=>1) );
if ( ! sizeof($results) ) {
return;
}

View File

@ -3,6 +3,18 @@ require_once('database.php');
$storage_cache = array();
class Storage {
private $defaults = array(
'Id' => null,
'Path' => '',
'Name' => '',
'Type' => 'local',
'Url' => '',
'DiskSpace' => null,
'Scheme' => 'Medium',
'ServerId' => 0,
'DoDelete' => 1,
);
public function __construct( $IdOrRow = NULL ) {
global $storage_cache;
@ -11,7 +23,7 @@ class Storage {
if ( is_integer($IdOrRow) or is_numeric($IdOrRow) ) {
$row = dbFetchOne('SELECT * FROM Storage WHERE Id=?', NULL, array($IdOrRow));
if ( ! $row ) {
Error("Unable to load Storage record for Id=" . $IdOrRow );
Error('Unable to load Storage record for Id=' . $IdOrRow);
}
} else if ( is_array($IdOrRow) ) {
$row = $IdOrRow;
@ -40,7 +52,6 @@ class Storage {
$this->{'Path'} = ZM_DIR_EVENTS;
}
return $this->{'Path'};
}
return $this->{'Name'};
}
@ -57,15 +68,21 @@ class Storage {
if ( count($args) ) {
$this->{$fn} = $args[0];
}
if ( array_key_exists( $fn, $this ) ) {
if ( array_key_exists($fn, $this) )
return $this->{$fn};
if ( array_key_exists( $fn, $this->defaults ) )
return $this->defaults{$fn};
$backTrace = debug_backtrace();
$file = $backTrace[0]['file'];
$line = $backTrace[0]['line'];
Warning("Unknown function call Storage->$fn from $file:$line");
$file = $backTrace[1]['file'];
$line = $backTrace[1]['line'];
Warning("Unknown function call Storage->$fn from $file:$line");
}
}
public static function find_one( $parameters = null, $options = null ) {
global $storage_cache;
if (
@ -74,7 +91,8 @@ class Storage {
isset($storage_cache[$parameters['Id']]) ) {
return $storage_cache[$parameters['Id']];
}
$results = Storage::find_all( $parameters, $options );
$results = Storage::find($parameters, $options);
if ( count($results) > 1 ) {
Error("Storage Returned more than 1");
return $results[0];
@ -84,8 +102,7 @@ class Storage {
return null;
}
}
public static function find_all( $parameters = null, $options = null ) {
$filters = array();
public static function find( $parameters = null, $options = null ) {
$sql = 'SELECT * FROM Storage ';
$values = array();
@ -106,23 +123,38 @@ class Storage {
}
}
$sql .= implode(' AND ', $fields);
}
if ( $options and isset($options['order']) ) {
} # end if parameters
if ( $options ) {
if ( isset($options['order']) ) {
$sql .= ' ORDER BY ' . $options['order'];
} # end if options
if ( isset($options['limit']) ) {
if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) {
$sql .= ' LIMIT ' . $option['limit'];
} else {
$backTrace = debug_backtrace();
$file = $backTrace[1]['file'];
$line = $backTrace[1]['line'];
Error("Invalid value for limit(".$options['limit'].") passed to Control::find from $file:$line");
return array();
}
} # end if limit
} # end if options
$storage_areas = array();
$result = dbQuery($sql, $values);
if ( $result ) {
$results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Storage');
foreach ( $results as $row => $obj ) {
$filters[] = $obj;
$results = $result->fetchALL();
foreach ( $results as $row ) {
$storage_areas[] = new Storage($row);
}
}
return $filters;
}
return $storage_areas;
} # end find()
public function disk_usage_percent() {
$path = $this->Path();
if ( ! $path ) {
Warning("Storage::disk_usage_percent: path is empty");
Warning('Storage::disk_usage_percent: path is empty');
return 0;
} else if ( ! file_exists($path) ) {
Warning("Storage::disk_usage_percent: path $path does not exist");
@ -131,7 +163,7 @@ class Storage {
$total = $this->disk_total_space();
if ( ! $total ) {
Error("disk_total_space returned false for " . $path );
Error('disk_total_space returned false for ' . $path );
return 0;
}
$used = $this->disk_used_space();
@ -139,6 +171,7 @@ class Storage {
//Logger::Debug("Used $usage = round( ( $used / $total ) * 100 )");
return $usage;
}
public function disk_total_space() {
if ( !array_key_exists('disk_total_space', $this) ) {
$path = $this->Path();
@ -175,7 +208,7 @@ class Storage {
if ( (! array_key_exists('DiskSpace', $this)) or (!$this->{'DiskSpace'}) ) {
$used = dbFetchOne('SELECT SUM(DiskSpace) AS DiskSpace FROM Events WHERE StorageId=? AND DiskSpace IS NOT NULL', 'DiskSpace', array($this->Id()) );
foreach ( Event::find_all( array('StorageId'=>$this->Id(), 'DiskSpace'=>null) ) as $Event ) {
foreach ( Event::find(array('StorageId'=>$this->Id(), 'DiskSpace'=>null)) as $Event ) {
$Event->Storage($this); // Prevent further db hit
$used += $Event->DiskSpace();
}

View File

@ -210,11 +210,13 @@ if ( canView('Events') ) {
dbQuery('UPDATE Events SET Name=? WHERE Id=?', array($_REQUEST['eventName'], $_REQUEST['eid']));
} else if ( $action == 'eventdetail' ) {
if ( !empty($_REQUEST['eid']) ) {
dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['eid'] ) );
dbQuery('UPDATE Events SET Cause=?, Notes=? WHERE Id=?',
array($_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['eid']) );
} else {
$dbConn->beginTransaction();
foreach( getAffectedIds('markEid') as $markEid ) {
dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid ) );
dbQuery('UPDATE Events SET Cause=?, Notes=? WHERE Id=?',
array($_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid) );
}
$dbConn->commit();
}
@ -265,7 +267,9 @@ if ( !empty($_REQUEST['mid']) && canView( 'Control', $_REQUEST['mid'] ) ) {
$zmuOutput = exec($zmuCommand);
list($brightness, $contrast, $hue, $colour) = explode(' ', $zmuOutput);
dbQuery( 'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?', array($brightness, $contrast, $hue, $colour, $mid));
dbQuery(
'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?',
array($brightness, $contrast, $hue, $colour, $mid));
}
}
@ -282,8 +286,8 @@ if ( canEdit('Control') ) {
} elseif ( $action == 'delete' ) {
if ( isset($_REQUEST['markCids']) ) {
foreach( $_REQUEST['markCids'] as $markCid ) {
dbQuery( 'delete from Controls where Id = ?', array($markCid) );
dbQuery( 'update Monitors set Controllable = 0, ControlId = 0 where ControlId = ?', array($markCid) );
dbQuery('DELETE FROM Controls WHERE Id = ?', array($markCid));
dbQuery('UPDATE Monitors SET Controllable = 0, ControlId = 0 WHERE ControlId = ?', array($markCid));
$refreshParent = true;
}
}
@ -310,7 +314,6 @@ if ( isset($_REQUEST['object']) and $_REQUEST['object'] == 'Monitor' ) {
$Monitor->zmaControl('start');
}
}
} // end foreach mid
$refreshParent = true;
} // end if action == save
@ -328,11 +331,12 @@ if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) {
$oldFunction = $monitor['Function'];
$oldEnabled = $monitor['Enabled'];
if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) {
dbQuery( 'UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?', array( $newFunction, $newEnabled, $mid ) );
dbQuery('UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?',
array($newFunction, $newEnabled, $mid));
$monitor['Function'] = $newFunction;
$monitor['Enabled'] = $newEnabled;
if ( daemonCheck() && $monitor['Type'] != 'WebSite' ) {
if ( daemonCheck() && ($monitor['Type'] != 'WebSite') ) {
$restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled);
zmaControl($monitor, 'stop');
zmcControl($monitor, $restart?'restart':'');
@ -373,16 +377,16 @@ if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) {
} else {
dbQuery('INSERT INTO Zones SET MonitorId=?, '.implode(', ', $changes), array($mid));
}
if ( daemonCheck() && $monitor['Type'] != 'WebSite' ) {
if ( daemonCheck() && ($monitor['Type'] != 'WebSite') ) {
if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) {
zmaControl($monitor, 'stop');
zmcControl($monitor, 'restart');
zmaControl($monitor, 'start');
} else {
zmaControl( $mid, 'restart' );
zmaControl($monitor, 'restart');
}
}
if ( $_REQUEST['newZone']['Type'] == 'Privacy' && $monitor['Controllable'] ) {
if ( ($_REQUEST['newZone']['Type'] == 'Privacy') && $monitor['Controllable'] ) {
require_once('control_functions.php');
sendControlCommand($mid, 'quit');
}
@ -401,19 +405,19 @@ if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) {
}
}
if ( $changes > 0 ) {
if ( daemonCheck() && $monitor['Type'] != 'WebSite' ) {
if ( daemonCheck() && ($monitor['Type'] != 'WebSite') ) {
zmaControl($mid, 'restart');
}
$refreshParent = true;
}
$view = 'none';
} elseif ( $action == 'sequence' && isset($_REQUEST['smid']) ) {
} elseif ( ($action == 'sequence') && isset($_REQUEST['smid']) ) {
$smid = validInt($_REQUEST['smid']);
$monitor = dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($mid) );
$smonitor = dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($smid) );
$monitor = dbFetchOne('SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid));
$smonitor = dbFetchOne('SELECT * FROM Monitors WHERE Id = ?', NULL, array($smid));
dbQuery( 'update Monitors set Sequence=? where Id=?', array( $smonitor['Sequence'], $monitor['Id'] ) );
dbQuery( 'update Monitors set Sequence=? WHERE Id=?', array( $monitor['Sequence'], $smonitor['Id'] ) );
dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($smonitor['Sequence'], $monitor['Id']));
dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($monitor['Sequence'], $smonitor['Id']));
$refreshParent = true;
fixSequences();
@ -421,8 +425,8 @@ if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) {
if ( isset($_REQUEST['markZids']) ) {
$deletedZid = 0;
foreach ( $_REQUEST['markZids'] as $markZid ) {
$zone = dbFetchOne( 'select * from Zones where Id=?', NULL, array($markZid) );
dbQuery( 'delete from Zones WHERE MonitorId=? AND Id=?', array( $mid, $markZid) );
$zone = dbFetchOne('SELECT * FROM Zones WHERE Id=?', NULL, array($markZid));
dbQuery('DELETE FROM Zones WHERE MonitorId=? AND Id=?', array($mid, $markZid));
$deletedZid = 1;
}
if ( $deletedZid ) {
@ -476,11 +480,12 @@ if ( canEdit( 'Monitors' ) ) {
);
if ( $_REQUEST['newMonitor']['ServerId'] == 'auto' ) {
$_REQUEST['newMonitor']['ServerId'] = dbFetchOne('SELECT Id FROM Servers WHERE Status=\'Running\' ORDER BY FreeMem DESC, CpuLoad ASC LIMIT 1', 'Id');
Logger::Debug("Auto selecting server: Got " . $_REQUEST['newMonitor']['ServerId'] );
$_REQUEST['newMonitor']['ServerId'] = dbFetchOne(
'SELECT Id FROM Servers WHERE Status=\'Running\' ORDER BY FreeMem DESC, CpuLoad ASC LIMIT 1', 'Id');
Logger::Debug('Auto selecting server: Got ' . $_REQUEST['newMonitor']['ServerId'] );
if ( ( ! $_REQUEST['newMonitor'] ) and defined('ZM_SERVER_ID') ) {
$_REQUEST['newMonitor']['ServerId'] = ZM_SERVER_ID;
Logger::Debug("Auto selecting server to " . ZM_SERVER_ID);
Logger::Debug('Auto selecting server to ' . ZM_SERVER_ID);
}
}
@ -537,12 +542,14 @@ if ( canEdit( 'Monitors' ) ) {
$changes = getFormChanges($zone, $newZone, $types);
if ( count($changes) ) {
dbQuery( 'update Zones set '.implode( ', ', $changes ).' WHERE MonitorId=? AND Id=?', array( $mid, $zone['Id'] ) );
}
}
dbQuery('UPDATE Zones SET '.implode(', ', $changes).' WHERE MonitorId=? AND Id=?',
array($mid, $zone['Id']));
}
} // end foreach zone
} // end if width and height
$restart = true;
} else if ( ! $user['MonitorIds'] ) { // Can only create new monitors if we are not restricted to specific monitors
} else if ( ! $user['MonitorIds'] ) {
// Can only create new monitors if we are not restricted to specific monitors
# FIXME This is actually a race condition. Should lock the table.
$maxSeq = dbFetchOne('SELECT MAX(Sequence) AS MaxSequence FROM Monitors', 'MaxSequence');
$changes[] = 'Sequence = '.($maxSeq+1);
@ -550,7 +557,7 @@ if ( canEdit( 'Monitors' ) ) {
if ( dbQuery('INSERT INTO Monitors SET '.implode(', ', $changes)) ) {
$mid = dbInsertId();
$zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height'];
dbQuery( "insert into Zones set MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) );
dbQuery("INSERT INTO Zones SET MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) );
//$view = 'none';
$Storage = new Storage($_REQUEST['newMonitor']['StorageId']);
mkdir($Storage->Path().'/'.$mid, 0755);
@ -558,17 +565,17 @@ if ( canEdit( 'Monitors' ) ) {
symlink($mid, $Storage->Path().'/'.$saferName);
} else {
Error("Error saving new Monitor.");
Error('Error saving new Monitor.');
return;
}
} else {
Error("Users with Monitors restrictions cannot create new monitors.");
Error('Users with Monitors restrictions cannot create new monitors.');
return;
}
$restart = true;
} else {
Logger::Debug("No action due to no changes to Monitor");
Logger::Debug('No action due to no changes to Monitor');
} # end if count(changes)
if (
@ -593,17 +600,17 @@ if ( canEdit( 'Monitors' ) ) {
if ( count($x10Changes) ) {
if ( $x10Monitor && isset($_REQUEST['newX10Monitor']) ) {
dbQuery( 'update TriggersX10 set '.implode( ', ', $x10Changes ).' where MonitorId=?', array($mid) );
dbQuery('UPDATE TriggersX10 SET '.implode(', ', $x10Changes).' WHERE MonitorId=?', array($mid));
} elseif ( !$user['MonitorIds'] ) {
if ( !$x10Monitor ) {
dbQuery( 'insert into TriggersX10 set MonitorId = ?, '.implode( ', ', $x10Changes ), array( $mid ) );
dbQuery('INSERT INTO TriggersX10 SET MonitorId = ?, '.implode(', ', $x10Changes), array($mid));
} else {
dbQuery( 'delete from TriggersX10 where MonitorId = ?', array($mid) );
dbQuery('DELETE FROM TriggersX10 WHERE MonitorId = ?', array($mid));
}
}
$restart = true;
}
}
} # end if has x10Changes
} # end if ZM_OPT_X10
if ( $restart ) {
@ -647,9 +654,11 @@ if ( canEdit( 'Devices' ) ) {
setDeviceStatusX10($_REQUEST['key'], $_REQUEST['command']);
} else if ( isset($_REQUEST['newDevice']) ) {
if ( isset($_REQUEST['did']) ) {
dbQuery( 'update Devices set Name=?, KeyString=? where Id=?', array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'], $_REQUEST['did']) );
dbQuery('UPDATE Devices SET Name=?, KeyString=? WHERE Id=?',
array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'], $_REQUEST['did']) );
} else {
dbQuery( 'insert into Devices set Name=?, KeyString=?', array( $_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'] ) );
dbQuery('INSERT INTO Devices SET Name=?, KeyString=?',
array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString']) );
}
$refreshParent = true;
$view = 'none';
@ -657,7 +666,7 @@ if ( canEdit( 'Devices' ) ) {
} elseif ( $action == 'delete' ) {
if ( isset($_REQUEST['markDids']) ) {
foreach( $_REQUEST['markDids'] as $markDid ) {
dbQuery( 'delete from Devices where Id=?', array($markDid) );
dbQuery('DELETE FROM Devices WHERE Id=?', array($markDid));
$refreshParent = true;
}
}
@ -665,7 +674,7 @@ if ( canEdit( 'Devices' ) ) {
} // end if canedit devices
// Group view actions
if ( canView( 'Groups' ) && $action == 'setgroup' ) {
if ( canView('Groups') && ($action == 'setgroup') ) {
if ( !empty($_REQUEST['gid']) ) {
setcookie('zmGroup', validInt($_REQUEST['gid']), time()+3600*24*30*12*10);
} else {
@ -675,19 +684,31 @@ if ( canView( 'Groups' ) && $action == 'setgroup' ) {
}
// Group edit actions
# Should probably verify that each monitor id is a valid monitor, that we have access to. However at the moment, you have to have System permissions to do this
# Should probably verify that each monitor id is a valid monitor, that we have access to.
# However at the moment, you have to have System permissions to do this
if ( canEdit('Groups') ) {
if ( $action == 'group' ) {
$monitors = empty($_POST['newGroup']['MonitorIds']) ? '' : implode(',', $_POST['newGroup']['MonitorIds']);
$group_id = null;
if ( !empty($_POST['gid']) ) {
$group_id = $_POST['gid'];
dbQuery( 'UPDATE Groups SET Name=?, ParentId=? WHERE Id=?',
array($_POST['newGroup']['Name'], ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), $group_id) );
dbQuery(
'UPDATE Groups SET Name=?, ParentId=? WHERE Id=?',
array(
$_POST['newGroup']['Name'],
( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ),
$group_id,
)
);
dbQuery('DELETE FROM Groups_Monitors WHERE GroupId=?', array($group_id));
} else {
dbQuery( 'INSERT INTO Groups (Name,ParentId) VALUES (?,?)',
array( $_POST['newGroup']['Name'], ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ) ) );
dbQuery(
'INSERT INTO Groups (Name,ParentId) VALUES (?,?)',
array(
$_POST['newGroup']['Name'],
( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ),
)
);
$group_id = dbInsertId();
}
if ( $group_id ) {
@ -738,19 +759,24 @@ if ( canEdit( 'System' ) ) {
} else if ( $_REQUEST['object'] == 'server' ) {
if ( $action == 'Save' ) {
if ( !empty($_REQUEST['id']) )
$dbServer = dbFetchOne( 'SELECT * FROM Servers WHERE Id=?', NULL, array($_REQUEST['id']) );
else
if ( !empty($_REQUEST['id']) ) {
$dbServer = dbFetchOne(
'SELECT * FROM Servers WHERE Id=?',
NULL,
array($_REQUEST['id']) );
} else {
$dbServer = array();
}
$types = array();
$changes = getFormChanges($dbServer, $_REQUEST['newServer'], $types);
if ( count($changes) ) {
if ( !empty($_REQUEST['id']) ) {
dbQuery( 'UPDATE Servers SET '.implode( ', ', $changes ).' WHERE Id = ?', array($_REQUEST['id']) );
dbQuery('UPDATE Servers SET '.implode(', ', $changes).' WHERE Id = ?',
array($_REQUEST['id']) );
} else {
dbQuery( 'INSERT INTO Servers set '.implode( ', ', $changes ) );
dbQuery('INSERT INTO Servers SET '.implode(', ', $changes));
}
$refreshParent = true;
}
@ -804,7 +830,7 @@ if ( canEdit( 'System' ) ) {
}
case 'ignore' :
{
dbQuery( "update Config set Value = '".ZM_DYN_LAST_VERSION."' where Name = 'ZM_DYN_CURR_VERSION'" );
dbQuery("UPDATE Config SET Value = '".ZM_DYN_LAST_VERSION."' WHERE Name = 'ZM_DYN_CURR_VERSION'");
break;
}
case 'hour' :
@ -819,12 +845,12 @@ if ( canEdit( 'System' ) ) {
} elseif ( $option == 'week' ) {
$nextReminder += 7*24*60*60;
}
dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_NEXT_REMINDER'" );
dbQuery("UPDATE Config SET Value = '".$nextReminder."' WHERE Name = 'ZM_DYN_NEXT_REMINDER'");
break;
}
case 'never' :
{
dbQuery( "update Config set Value = '0' where Name = 'ZM_CHECK_FOR_UPDATES'" );
dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_CHECK_FOR_UPDATES'");
break;
}
}
@ -852,17 +878,38 @@ if ( canEdit( 'System' ) ) {
} elseif ( $option == 'month' ) {
$nextReminder += 30*24*60*60;
}
dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_DONATE_REMINDER_TIME'" );
dbQuery("UPDATE Config SET Value = '".$nextReminder."' WHERE Name = 'ZM_DYN_DONATE_REMINDER_TIME'");
break;
}
case 'never' :
case 'already' :
{
dbQuery( "update Config set Value = '0' where Name = 'ZM_DYN_SHOW_DONATE_REMINDER'" );
dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_DYN_SHOW_DONATE_REMINDER'");
break;
}
} // end switch option
}
if ( ($action == 'privacy') && isset($_REQUEST['option']) ) {
switch( $_REQUEST['option'] ) {
case 'decline' :
{
dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_SHOW_PRIVACY'");
dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_TELEMETRY_DATA'");
$redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console';
break;
}
case 'accept' :
{
dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_SHOW_PRIVACY'");
dbQuery("UPDATE Config SET Value = '1' WHERE Name = 'ZM_TELEMETRY_DATA'");
$redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console';
break;
}
default: # Enable the privacy statement if we somehow submit something other than accept or decline
dbQuery("UPDATE Config SET Value = '1' WHERE Name = 'ZM_SHOW_PRIVACY'");
} // end switch option
return;
}
if ( $action == 'options' && isset($_REQUEST['tab']) ) {
$configCat = $configCats[$_REQUEST['tab']];
$changed = false;
@ -902,9 +949,10 @@ if ( canEdit( 'System' ) ) {
$redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=options&tab='.$_REQUEST['tab'];
}
loadConfig(false);
return;
} elseif ( $action == 'user' ) {
if ( !empty($_REQUEST['uid']) )
$dbUser = dbFetchOne( "SELECT * FROM Users WHERE Id=?", NULL, array($_REQUEST['uid']) );
$dbUser = dbFetchOne('SELECT * FROM Users WHERE Id=?', NULL, array($_REQUEST['uid']));
else
$dbUser = array();
@ -918,12 +966,12 @@ if ( canEdit( 'System' ) ) {
if ( count($changes) ) {
if ( !empty($_REQUEST['uid']) ) {
dbQuery( 'update Users set '.implode( ', ', $changes ).' where Id = ?', array($_REQUEST['uid']) );
dbQuery('UPDATE Users SET '.implode(', ', $changes).' WHERE Id = ?', array($_REQUEST['uid']));
# If we are updating the logged in user, then update our session user data.
if ( $user and ( $dbUser['Username'] == $user['Username'] ) )
userLogin($dbUser['Username'], $dbUser['Password']);
} else {
dbQuery( 'insert into Users set '.implode( ', ', $changes ) );
dbQuery('INSERT INTO Users SET '.implode(', ', $changes));
}
$refreshParent = true;
}
@ -938,22 +986,21 @@ if ( canEdit( 'System' ) ) {
if ( !empty($_REQUEST['runState']) || !empty($_REQUEST['newState']) ) {
$sql = 'SELECT Id,Function,Enabled FROM Monitors ORDER BY Id';
$definitions = array();
foreach( dbFetchAll( $sql ) as $monitor )
{
foreach( dbFetchAll($sql) as $monitor ) {
$definitions[] = $monitor['Id'].':'.$monitor['Function'].':'.$monitor['Enabled'];
}
$definition = join(',', $definitions);
if ( $_REQUEST['newState'] )
$_REQUEST['runState'] = $_REQUEST['newState'];
dbQuery( 'replace into States set Name=?, Definition=?', array( $_REQUEST['runState'],$definition) );
dbQuery('REPLACE INTO States SET Name=?, Definition=?', array($_REQUEST['runState'],$definition));
}
} elseif ( $action == 'delete' ) {
if ( isset($_REQUEST['runState']) )
dbQuery( 'delete from States where Name=?', array($_REQUEST['runState']) );
dbQuery('DELETE FROM States WHERE Name=?', array($_REQUEST['runState']));
if ( isset($_REQUEST['markUids']) ) {
foreach( $_REQUEST['markUids'] as $markUid )
dbQuery( 'delete from Users where Id = ?', array($markUid) );
dbQuery('DELETE FROM Users WHERE Id = ?', array($markUid));
if ( $markUid == $user['Id'] )
userLogout();
}
@ -972,7 +1019,7 @@ if ( canEdit( 'System' ) ) {
else
unset($changes['Password']);
if ( count($changes) ) {
dbQuery( 'update Users set '.implode( ', ', $changes ).' where Id=?', array($uid) );
dbQuery('UPDATE Users SET '.implode(', ', $changes).' WHERE Id=?', array($uid));
$refreshParent = true;
}
$view = 'none';

View File

@ -212,7 +212,7 @@ function process_configfile($configFile) {
continue;
elseif ( preg_match( '/^\s*#/', $str ))
continue;
elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*(.*?)\s*$/', $str, $matches ))
elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/', $str, $matches ))
$configvals[$matches[1]] = $matches[2];
}
fclose( $cfg );

View File

@ -139,9 +139,9 @@ function dbQuery( $sql, $params=NULL ) {
}
if ( defined('ZM_DB_DEBUG') ) {
if ( $params )
Warning("SQL: $sql" . implode(',',$params) . ' rows: '.$result->rowCount() );
Logger::Debug("SQL: $sql" . implode(',',$params) . ' rows: '.$result->rowCount() );
else
Warning("SQL: $sql: rows:" . $result->rowCount() );
Logger::Debug("SQL: $sql: rows:" . $result->rowCount() );
}
} catch(PDOException $e) {
Error( "SQL-ERR '".$e->getMessage()."', statement was '".$sql."' params:" . ($params?implode(',',$params):'') );

View File

@ -47,10 +47,11 @@ function CORSHeaders() {
}
foreach( $servers as $row ) {
$Server = new Server($row);
if ( $_SERVER['HTTP_ORIGIN'] == $Server->Url() ) {
if ( preg_match('/^'.preg_quote($Server->Url(),'/').'/', $_SERVER['HTTP_ORIGIN']) ) {
$valid = true;
header('Access-Control-Allow-Origin: ' . $Server->Url() );
header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
header('Access-Control-Allow-Headers: x-requested-with,x-request');
break;
}
}
if ( !$valid ) {
@ -436,16 +437,34 @@ function htmlSelect( $name, $contents, $values, $behaviours=false ) {
}
function htmlOptions($contents, $values) {
$html = '';
foreach ( $contents as $value=>$text ) {
if ( is_array( $text ) )
$text = $text['Name'];
else if ( is_object( $text ) )
$text = $text->Name();
$selected = is_array( $values ) ? in_array( $value, $values ) : !strcmp($value, $values);
$html .= "<option value=\"$value\"".($selected?" selected=\"selected\"":'').">$text</option>";
$options_html = '';
foreach ( $contents as $value=>$option ) {
$disabled = 0;
$text = '';
if ( is_array($option) ) {
if ( isset($option['Name']) )
$text = $option['Name'];
else if ( isset($option['text']) )
$text = $option['text'];
if ( isset($option['disabled']) ) {
$disabled = $option['disabled'];
Error("Setting to disabled");
}
return $html;
} else if ( is_object($option) ) {
$text = $option->Name();
} else {
$text = $option;
}
$selected = is_array($values) ? in_array($value, $values) : !strcmp($value, $values);
$options_html .= "<option value=\"$value\"".
($selected?' selected="selected"':'').
($disabled?' disabled="disabled"':'').
">$text</option>";
}
return $options_html;
}
function truncText( $text, $length, $deslash=1 ) {
@ -892,7 +911,7 @@ function reScale( $dimension, $dummy ) {
$new_dimension = $dimension;
for ( $i = 1; $i < func_num_args(); $i++ ) {
$scale = func_get_arg( $i );
if ( !empty($scale) && $scale != SCALE_BASE )
if ( !empty($scale) && ($scale != 'auto') && ($scale != SCALE_BASE) )
$new_dimension = (int)(($new_dimension*$scale)/SCALE_BASE);
}
return( $new_dimension );

View File

@ -49,8 +49,14 @@ function loadLanguage( $prefix="" )
return( false );
}
if ( $langFile = loadLanguage() )
if ( $langFile = loadLanguage() ) {
require_once( $langFile );
require_once( 'lang/default.php' );
foreach ($DLANG as $key => $value) {
if ( ! array_key_exists( $key, $SLANG ) )
$SLANG[$key] = $DLANG[$key];
}
}
//

View File

@ -406,7 +406,7 @@ class Logger {
$result = $stmt->execute( array( sprintf( '%d.%06d', $time['sec'], $time['usec'] ), $this->id, getmypid(), $level, $code, $string, $file, $line ) );
} catch(PDOException $ex) {
$this->databaseLevel = self::NOLOG;
Fatal( "Can't write log entry '$sql': ". $ex->getMessage() );
Error("Can't write log entry '$sql': ". $ex->getMessage());
}
}
// This has to be last as trigger_error can be fatal

View File

@ -205,7 +205,16 @@ isset($view) || $view = NULL;
isset($request) || $request = NULL;
isset($action) || $action = NULL;
if ( ZM_ENABLE_CSRF_MAGIC && $action != 'login' && $view != 'view_video' && $request != 'control' && $view != 'frames' && $view != 'archive' ) {
Logger::Debug("View: $view Request: $request Action: $action");
if (
ZM_ENABLE_CSRF_MAGIC &&
( $action != 'login' ) &&
( $view != 'view_video' ) &&
( $view != 'image' ) &&
( $request != 'control' ) &&
( $view != 'frames' ) &&
( $view != 'archive' )
) {
require_once( 'includes/csrf/csrf-magic.php' );
#Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\"");
csrf_check();
@ -216,12 +225,15 @@ require_once( 'includes/actions.php' );
# If I put this here, it protects all views and popups, but it has to go after actions.php because actions.php does the actual logging in.
if ( ZM_OPT_USE_AUTH and !isset($user) ) {
Logger::Debug("Redirecting to login" );
Logger::Debug('Redirecting to login');
$view = 'login';
$request = null;
} else if ( ZM_SHOW_PRIVACY && ($action != 'privacy') && ($view !='options') && (!$request) && canEdit('System') ) {
Logger::Debug('Redirecting to privacy');
$view = 'privacy';
$request = null;
}
if ( $redirect ) {
header('Location: '.$redirect);
return;

View File

@ -82,6 +82,8 @@ $SLANG = array(
'Actual' => 'Actual',
'AddNewControl' => '新增控制',
'AddNewMonitor' => '新增監視',
'AddNewServer' => 'Add New Server', // Added - 2018-08-30
'AddNewStorage' => 'Add New Storage', // Added - 2018-08-30
'AddNewUser' => '新增使用者',
'AddNewZone' => '新增監視區',
'Alarm' => '警報',
@ -109,22 +111,32 @@ $SLANG = array(
'AttrArchiveStatus' => 'Archive Status',
'AttrAvgScore' => 'Average Score',
'AttrCause' => 'Cause',
'AttrDate' => 'Date',
'AttrDateTime' => 'Date/Time',
'AttrDiskBlocks' => 'Disk Blocks',
'AttrDiskPercent' => 'Disk Percent',
'AttrDiskSpace' => 'Disk Space', // Added - 2018-08-30
'AttrDuration' => 'Duration',
'AttrEndDate' => 'End Date', // Added - 2018-08-30
'AttrEndDateTime' => 'End Date/Time', // Added - 2018-08-30
'AttrEndTime' => 'End Time', // Added - 2018-08-30
'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30
'AttrFilterServer' => 'Server Filter is Running On', // Added - 2018-08-30
'AttrFrames' => 'Frames',
'AttrId' => 'Id',
'AttrMaxScore' => 'Max. Score',
'AttrMonitorId' => 'Monitor Id',
'AttrMonitorName' => 'Monitor Name',
'AttrMonitorServer' => 'Server Monitor is Running On', // Added - 2018-08-30
'AttrName' => 'Name',
'AttrNotes' => 'Notes',
'AttrStartDate' => 'Start Date', // Added - 2018-08-30
'AttrStartDateTime' => 'Start Date/Time', // Added - 2018-08-30
'AttrStartTime' => 'Start Time', // Added - 2018-08-30
'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30
'AttrStateId' => 'Run State', // Added - 2018-08-30
'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30
'AttrStorageServer' => 'Server Hosting Storage', // Added - 2018-08-30
'AttrSystemLoad' => 'System Load',
'AttrTime' => 'Time',
'AttrTotalScore' => 'Total Score',
'AttrWeekday' => 'Weekday',
'Auto' => '自動',
'AutoStopTimeout' => '時間過自動停止',
'Available' => 'Available', // Added - 2009-03-31
@ -157,9 +169,11 @@ $SLANG = array(
'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',
'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"', // Added - 2018-08-30
'BadStreamReplayBuffer'=> 'Stream replay buffer must be an integer of zero or more',
'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.', // Added - 2018-08-30
'BadWidth' => 'Width must be set to a valid value',
'Bandwidth' => '頻寬',
'BandwidthHead' => 'Bandwidth', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing
@ -223,10 +237,12 @@ $SLANG = array(
'ChooseLogSelection' => 'Choose a log selection', // Added - 2011-06-17
'ChoosePreset' => 'Choose Preset',
'Clear' => 'Clear', // Added - 2011-06-16
'CloneMonitor' => 'Clone', // Added - 2018-08-30
'Close' => '關閉',
'Colour' => 'Colour',
'Command' => 'Command',
'Component' => 'Component', // Added - 2011-06-16
'ConcurrentFilter' => 'Run filter concurrently', // Added - 2018-08-30
'Config' => 'Config',
'ConfiguredFor' => '配置為',
'ConfirmDeleteEvents' => 'Are you sure you wish to delete the selected events?',
@ -284,9 +300,11 @@ $SLANG = array(
'DonateRemindWeek' => 'Not yet, remind again in 1 week',
'DonateYes' => 'Yes, I\'d like to donate now',
'Download' => '下載',
'DownloadVideo' => 'Download Video', // Added - 2018-08-30
'DuplicateMonitorName' => 'Duplicate Monitor Name', // Added - 2009-03-31
'Duration' => '歷時',
'Edit' => '編輯',
'EditLayout' => 'Edit Layout', // Added - 2018-08-30
'Email' => 'Email',
'EnableAlarms' => '啟動警報',
'Enabled' => '啟用',
@ -303,6 +321,7 @@ $SLANG = array(
'Events' => '事件',
'Exclude' => '不包含',
'Execute' => 'Execute',
'Exif' => 'Embed EXIF data into image', // Added - 2018-08-30
'Export' => '輸出',
'ExportDetails' => '輸出事件細項',
'ExportFailed' => '輸出失敗',
@ -332,8 +351,10 @@ $SLANG = array(
'FilterExecuteEvents' => '自動執行符合指令',
'FilterLog' => 'Filter log', // Added - 2015-04-18
'FilterMessageEvents' => '自動發出符合訊息',
'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
'FilterPx' => 'Filter Px',
'FilterUnset' => '您必需設定濾鏡的寬度和高度',
'FilterUpdateDiskSpace'=> 'Update used disk space', // Added - 2018-08-30
'FilterUploadEvents' => '自動上傳符合項目',
'FilterVideoEvents' => '自動產生符合的影像檔',
'Filters' => '濾鏡',
@ -358,6 +379,7 @@ $SLANG = array(
'Function' => '功能',
'Gain' => 'Gain',
'General' => '一般',
'GenerateDownload' => 'Generate Download', // Added - 2018-08-30
'GenerateVideo' => '輸出影片',
'GeneratingVideo' => '輸出影片中',
'GoToZoneMinder' => 'Go to ZoneMinder.com',
@ -378,6 +400,7 @@ $SLANG = array(
'High' => '高',
'HighBW' => 'High&nbsp;B/W',
'Home' => 'Home',
'Hostname' => 'Hostname', // Added - 2018-08-30
'Hour' => '時',
'Hue' => 'Hue',
'Id' => 'Id',
@ -402,6 +425,7 @@ $SLANG = array(
'Line' => 'Line', // Added - 2011-06-16
'LinkedMonitors' => 'Linked Monitors',
'List' => '列出',
'ListMatches' => 'List Matches', // Added - 2018-08-30
'Load' => '載入',
'Local' => 'Local',
'Log' => 'Log', // Added - 2011-06-16
@ -488,6 +512,7 @@ $SLANG = array(
'MonitorProbeIntro' => 'The list below shows detected analog and network 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/>', // Added - 2009-03-31
'Monitors' => '監視',
'Montage' => '全部顯示',
'MontageReview' => 'Montage Review', // Added - 2018-08-30
'Month' => '月',
'More' => 'More', // Added - 2011-06-16
'MotionFrameSkip' => 'Motion Frame Skip',
@ -514,6 +539,7 @@ $SLANG = array(
'Next' => '下一步',
'No' => 'No',
'NoDetectedCameras' => 'No Detected Cameras', // Added - 2009-03-31
'NoDetectedProfiles' => 'No Detected Profiles', // Added - 2018-08-30
'NoFramesRecorded' => 'There are no frames recorded for this event',
'NoGroup' => 'No Group', // Added - 2009-02-08
'NoSavedFilters' => 'NoSavedFilters',
@ -532,6 +558,8 @@ $SLANG = array(
'OpGt' => 'greater than',
'OpGtEq' => 'greater than or equal to',
'OpIn' => 'in set',
'OpIs' => 'is', // Added - 2018-08-30
'OpIsNot' => 'is not', // Added - 2018-08-30
'OpLt' => 'less than',
'OpLtEq' => 'less than or equal to',
'OpMatches' => 'matches',
@ -541,6 +569,7 @@ $SLANG = array(
'Open' => 'Open',
'OptionHelp' => 'OptionHelp',
'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.',
'OptionalEncoderParam' => 'Optional Encoder Parameters', // Added - 2018-08-30
'Options' => '銓垣專用',//進階選項
'OrEnterNewName' => 'or enter new name',
'Order' => '順序',
@ -578,9 +607,13 @@ $SLANG = array(
'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/>', // Added - 2015-04-18
'Progress' => 'Progress', // Added - 2015-04-18
'Protocol' => 'Protocol',
'RTSPDescribe' => 'Use RTSP Response Media URL', // Added - 2018-08-30
'RTSPTransport' => 'RTSP Transport Protocol', // Added - 2018-08-30
'Rate' => 'Rate',
'Real' => 'Real',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // Added - 2018-08-30
'Record' => '錄影',
'RecordAudio' => 'Whether to store the audio stream when saving an event.', // Added - 2018-08-30
'RefImageBlendPct' => '參考影像混合 %ge',
'Refresh' => '更新',
'Remote' => 'Remote',
@ -596,6 +629,7 @@ $SLANG = array(
'ReplayAll' => 'All Events',
'ReplayGapless' => 'Gapless Events',
'ReplaySingle' => 'Single Event',
'ReportEventAudit' => 'Audit Events Report', // Added - 2018-08-30
'Reset' => 'Reset',
'ResetEventCounts' => 'Reset Event Counts',
'Restart' => '重新啟動',
@ -614,6 +648,7 @@ $SLANG = array(
'Save' => '存檔',
'SaveAs' => '儲存為',
'SaveFilter' => 'Save Filter',
'SaveJPEGs' => 'Save JPEGs', // Added - 2018-08-30
'Scale' => 'Scale',
'Score' => '分數',
'Secs' => 'Secs',
@ -630,6 +665,7 @@ $SLANG = array(
'ShowFilterWindow' => '顯示過濾視窗',
'ShowTimeline' => 'Show Timeline',
'SignalCheckColour' => 'Signal Check Colour',
'SignalCheckPoints' => 'Signal Check Points', // Added - 2018-08-30
'Size' => 'Size',
'SkinDescription' => 'Change the default skin for this computer', // Added - 2011-01-30
'Sleep' => 'Sleep',
@ -649,6 +685,10 @@ $SLANG = array(
'State' => 'State',
'Stats' => 'Stats',
'Status' => 'Status',
'StatusConnected' => 'Capturing', // Added - 2018-08-30
'StatusNotRunning' => 'Not Running', // Added - 2018-08-30
'StatusRunning' => 'Not Capturing', // Added - 2018-08-30
'StatusUnknown' => 'Unknown', // Added - 2018-08-30
'Step' => 'Step',
'StepBack' => 'Step Back',
'StepForward' => 'Step Forward',
@ -659,6 +699,8 @@ $SLANG = array(
'Stills' => '靜止',
'Stop' => '停止',
'Stopped' => '已停止',
'StorageArea' => 'Storage Area', // Added - 2018-08-30
'StorageScheme' => 'Scheme', // Added - 2018-08-30
'Stream' => '串流',
'StreamReplayBuffer' => 'Stream Replay Image Buffer',
'Submit' => 'Submit',
@ -678,6 +720,7 @@ $SLANG = array(
'TimelineTip4' => 'Use the controls below to zoom out or navigate back and forward through the time range.', // Added 2013.08.15.
'Timestamp' => '時間格式',
'TimestampLabelFormat' => '時間標示格式',
'TimestampLabelSize' => 'Font Size', // Added - 2018-08-30
'TimestampLabelX' => '時間標示 X',
'TimestampLabelY' => '時間標示 Y',
'Today' => 'Today',
@ -724,6 +767,7 @@ $SLANG = array(
'VideoGenParms' => '輸出影片參數',
'VideoGenSucceeded' => 'Video Generation Succeeded!', // Added - 2009-02-08
'VideoSize' => '影片尺寸',
'VideoWriter' => 'Video Writer', // Added - 2018-08-30
'View' => '檢視',
'ViewAll' => '全部檢視',
'ViewEvent' => 'View Event', // Added - 2009-02-08
@ -733,6 +777,7 @@ $SLANG = array(
'Watch' => 'Watch',
'Web' => 'Web',
'WebColour' => 'Web Colour', // Added - 2009-02-08
'WebSiteUrl' => 'Website URL', // Added - 2018-08-30
'Week' => '週',
'White' => 'White',
'WhiteBalance' => 'White Balance',

View File

@ -78,6 +78,8 @@ $SLANG = array(
'Actual' => '实际',
'AddNewControl' => '新建控制',
'AddNewMonitor' => '新建监视器',
'AddNewServer' => 'Add New Server', // Added - 2018-08-30
'AddNewStorage' => 'Add New Storage', // Added - 2018-08-30
'AddNewUser' => '新建用户',
'AddNewZone' => '新建区域',
'Alarm' => '报警',
@ -105,22 +107,32 @@ $SLANG = array(
'AttrArchiveStatus' => '存档状态',
'AttrAvgScore' => '平均分数',
'AttrCause' => '原因',
'AttrDate' => '日期',
'AttrDateTime' => '日期/时间',
'AttrDiskBlocks' => '磁碟区块',
'AttrDiskPercent' => '磁碟百分比',
'AttrDiskSpace' => 'Disk Space', // Added - 2018-08-30
'AttrDuration' => '过程',
'AttrEndDate' => 'End Date', // Added - 2018-08-30
'AttrEndDateTime' => 'End Date/Time', // Added - 2018-08-30
'AttrEndTime' => 'End Time', // Added - 2018-08-30
'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30
'AttrFilterServer' => 'Server Filter is Running On', // Added - 2018-08-30
'AttrFrames' => '帧',
'AttrId' => 'Id',
'AttrMaxScore' => '最大分数',
'AttrMonitorId' => '监视器 Id',
'AttrMonitorName' => '监视器名称',
'AttrMonitorServer' => 'Server Monitor is Running On', // Added - 2018-08-30
'AttrName' => '名称',
'AttrNotes' => '备注',
'AttrStartDate' => 'Start Date', // Added - 2018-08-30
'AttrStartDateTime' => 'Start Date/Time', // Added - 2018-08-30
'AttrStartTime' => 'Start Time', // Added - 2018-08-30
'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30
'AttrStateId' => 'Run State', // Added - 2018-08-30
'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30
'AttrStorageServer' => 'Server Hosting Storage', // Added - 2018-08-30
'AttrSystemLoad' => '系统负载',
'AttrTime' => '时间',
'AttrTotalScore' => '总分数',
'AttrWeekday' => '星期',
'Auto' => '自动',
'AutoStopTimeout' => '超时自动停止',
'Available' => 'Available', // Added - 2009-03-31
@ -153,9 +165,11 @@ $SLANG = array(
'BadRefBlendPerc' => '参考混合百分比必须设为一个正整数',
'BadSectionLength' => '节长度必须设为30的整数倍',
'BadSignalCheckColour' => '信号检查颜色必须设为有效的RGB颜色字符',
'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"', // Added - 2018-08-30
'BadStreamReplayBuffer' => '流重放缓冲必须为零或更多整数',
'BadWarmupCount' => '预热帪必须设为零或更多整数',
'BadWebColour' => 'Web颜色必须设为有效Web颜色字符',
'BadWebSitePath' => 'Please enter a complete website url, including the http:// or https:// prefix.', // Added - 2018-08-30
'BadWidth' => '宽度必须设为有效值',
'Bandwidth' => '带宽',
'BandwidthHead' => 'Bandwidth', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing
@ -219,10 +233,12 @@ $SLANG = array(
'ChooseLogSelection' => 'Choose a log selection', // Added - 2011-06-17
'ChoosePreset' => '选择预置',
'Clear' => 'Clear', // Added - 2011-06-16
'CloneMonitor' => 'Clone', // Added - 2018-08-30
'Close' => '关闭',
'Colour' => '彩色',
'Command' => '命令',
'Component' => 'Component', // Added - 2011-06-16
'ConcurrentFilter' => 'Run filter concurrently', // Added - 2018-08-30
'Config' => '配置',
'ConfiguredFor' => '配置标的',
'ConfirmDeleteEvents' => '确认希望删除所选事件?',
@ -280,9 +296,11 @@ $SLANG = array(
'DonateRemindWeek' => '现在不1星期内再次提醒我',
'DonateYes' => '好,我现在就捐款',
'Download' => '下载',
'DownloadVideo' => 'Download Video', // Added - 2018-08-30
'DuplicateMonitorName' => 'Duplicate Monitor Name', // Added - 2009-03-31
'Duration' => 'Duration',
'Edit' => '编辑',
'EditLayout' => 'Edit Layout', // Added - 2018-08-30
'Email' => 'Email',
'EnableAlarms' => '启动报警',
'Enabled' => '已启动',
@ -299,6 +317,7 @@ $SLANG = array(
'Events' => '事件',
'Exclude' => '排除',
'Execute' => '执行',
'Exif' => 'Embed EXIF data into image', // Added - 2018-08-30
'Export' => '导出',
'ExportDetails' => '导出时间详情',
'ExportFailed' => '导出失败',
@ -328,8 +347,10 @@ $SLANG = array(
'FilterExecuteEvents' => '执行全部匹配项命令',
'FilterLog' => 'Filter log', // Added - 2015-04-18
'FilterMessageEvents' => '全部匹配项的信息详情',
'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
'FilterPx' => '过滤器像素',
'FilterUnset' => '您必须指定过滤器宽度和高度',
'FilterUpdateDiskSpace'=> 'Update used disk space', // Added - 2018-08-30
'FilterUploadEvents' => '上传全部匹配项',
'FilterVideoEvents' => '为全部匹配项创建视频',
'Filters' => '过滤器',
@ -354,6 +375,7 @@ $SLANG = array(
'Function' => '功能',
'Gain' => '增益',
'General' => '一般',
'GenerateDownload' => 'Generate Download', // Added - 2018-08-30
'GenerateVideo' => '创建视频',
'GeneratingVideo' => '正在创建视频',
'GoToZoneMinder' => '访问 ZoneMinder.com',
@ -374,6 +396,7 @@ $SLANG = array(
'High' => '高',
'HighBW' => '高&nbsp;B/W',
'Home' => '主页',
'Hostname' => 'Hostname', // Added - 2018-08-30
'Hour' => '小时',
'Hue' => '色调',
'Id' => 'Id',
@ -398,6 +421,7 @@ $SLANG = array(
'Line' => 'Line', // Added - 2011-06-16
'LinkedMonitors' => '管理监视器',
'List' => '列表',
'ListMatches' => 'List Matches', // Added - 2018-08-30
'Load' => '加载',
'Local' => '本地',
'Log' => 'Log', // Added - 2011-06-16
@ -484,6 +508,7 @@ $SLANG = array(
'MonitorProbeIntro' => 'The list below shows detected analog and network 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/>', // Added - 2009-03-31
'Monitors' => '监视器',
'Montage' => '镜头组接',
'MontageReview' => 'Montage Review', // Added - 2018-08-30
'Month' => '月',
'More' => 'More', // Added - 2011-06-16
'MotionFrameSkip' => 'Motion Frame Skip',
@ -510,6 +535,7 @@ $SLANG = array(
'Next' => '下一个',
'No' => '不',
'NoDetectedCameras' => 'No Detected Cameras', // Added - 2009-03-31
'NoDetectedProfiles' => 'No Detected Profiles', // Added - 2018-08-30
'NoFramesRecorded' => '该事件没有相关帧的记录',
'NoGroup' => '无组',
'NoSavedFilters' => '没有保存过滤器',
@ -528,6 +554,8 @@ $SLANG = array(
'OpGt' => '大于',
'OpGtEq' => '大于等于',
'OpIn' => '在集',
'OpIs' => 'is', // Added - 2018-08-30
'OpIsNot' => 'is not', // Added - 2018-08-30
'OpLt' => '小于',
'OpLtEq' => '小于等于',
'OpMatches' => '匹配',
@ -537,6 +565,7 @@ $SLANG = array(
'Open' => '打开',
'OptionHelp' => '选项帮助',
'OptionRestartWarning' => '这些改动在系统运行时可以不会完全生效.\n 当你设置完毕改动后\n请确认\n您重新启动 ZoneMinder.',
'OptionalEncoderParam' => 'Optional Encoder Parameters', // Added - 2018-08-30
'Options' => '选项',
'OrEnterNewName' => '或输入新名词',
'Order' => '次序',
@ -574,9 +603,13 @@ $SLANG = array(
'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/>', // Added - 2015-04-18
'Progress' => 'Progress', // Added - 2015-04-18
'Protocol' => '协议',
'RTSPDescribe' => 'Use RTSP Response Media URL', // Added - 2018-08-30
'RTSPTransport' => 'RTSP Transport Protocol', // Added - 2018-08-30
'Rate' => '速率',
'Real' => '实际',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // Added - 2018-08-30
'Record' => '记录',
'RecordAudio' => 'Whether to store the audio stream when saving an event.', // Added - 2018-08-30
'RefImageBlendPct' => '参考影像混合 %ge',
'Refresh' => '刷新',
'Remote' => '远程',
@ -592,6 +625,7 @@ $SLANG = array(
'ReplayAll' => '全部事件',
'ReplayGapless' => '无间隙事件',
'ReplaySingle' => '单一事件',
'ReportEventAudit' => 'Audit Events Report', // Added - 2018-08-30
'Reset' => '重置',
'ResetEventCounts' => '重置事件数',
'Restart' => '重启动',
@ -610,6 +644,7 @@ $SLANG = array(
'Save' => '保存',
'SaveAs' => '另存为',
'SaveFilter' => '存储过滤器',
'SaveJPEGs' => 'Save JPEGs', // Added - 2018-08-30
'Scale' => '比例',
'Score' => '分数',
'Secs' => '秒',
@ -626,6 +661,7 @@ $SLANG = array(
'ShowFilterWindow' => '显示过滤器视窗',
'ShowTimeline' => '显示时间轴',
'SignalCheckColour' => '型号检查颜色',
'SignalCheckPoints' => 'Signal Check Points', // Added - 2018-08-30
'Size' => '大小',
'SkinDescription' => 'Change the default skin for this computer', // Added - 2011-01-30
'Sleep' => '睡眠',
@ -645,6 +681,10 @@ $SLANG = array(
'State' => '状态',
'Stats' => '统计',
'Status' => '状况',
'StatusConnected' => 'Capturing', // Added - 2018-08-30
'StatusNotRunning' => 'Not Running', // Added - 2018-08-30
'StatusRunning' => 'Not Capturing', // Added - 2018-08-30
'StatusUnknown' => 'Unknown', // Added - 2018-08-30
'Step' => '步进',
'StepBack' => '单步后退',
'StepForward' => '单步前进',
@ -655,6 +695,8 @@ $SLANG = array(
'Stills' => '静止',
'Stop' => '停止',
'Stopped' => '已停止',
'StorageArea' => 'Storage Area', // Added - 2018-08-30
'StorageScheme' => 'Scheme', // Added - 2018-08-30
'Stream' => '流',
'StreamReplayBuffer' => '流重放影像缓冲',
'Submit' => '发送',
@ -674,6 +716,7 @@ $SLANG = array(
'TimelineTip4' => 'Use the controls below to zoom out or navigate back and forward through the time range.', // Added 2013.08.15.
'Timestamp' => '时间戳',
'TimestampLabelFormat' => '时间戳标签格式',
'TimestampLabelSize' => 'Font Size', // Added - 2018-08-30
'TimestampLabelX' => '时间戳标签 X',
'TimestampLabelY' => '时间戳标签 Y',
'Today' => '今天',
@ -720,6 +763,7 @@ $SLANG = array(
'VideoGenParms' => '视频产生参数',
'VideoGenSucceeded' => '视频产生成功!',
'VideoSize' => '视频尺寸',
'VideoWriter' => 'Video Writer', // Added - 2018-08-30
'View' => '查看',
'ViewAll' => '查看全部',
'ViewEvent' => '查看事件',
@ -729,6 +773,7 @@ $SLANG = array(
'Watch' => '观察',
'Web' => 'Web',
'WebColour' => 'Web颜色',
'WebSiteUrl' => 'Website URL', // Added - 2018-08-30
'Week' => '周',
'White' => '白',
'WhiteBalance' => '白平衡',

View File

@ -78,6 +78,8 @@ $SLANG = array(
'Actual' => 'Skutečná',
'AddNewControl' => 'Přidat nové řízení',
'AddNewMonitor' => 'Přidat kameru',
'AddNewServer' => 'Add New Server', // Added - 2018-08-30
'AddNewStorage' => 'Add New Storage', // Added - 2018-08-30
'AddNewUser' => 'Přidat uživatele',
'AddNewZone' => 'Přidat zónu',
'Alarm' => 'Alarm',
@ -105,22 +107,32 @@ $SLANG = array(
'AttrArchiveStatus' => 'Archiv status',
'AttrAvgScore' => 'Prům. skóre',
'AttrCause' => 'Příčina',
'AttrDate' => 'Datum',
'AttrDateTime' => 'Datum/Čas',
'AttrDiskBlocks' => 'Bloky disku',
'AttrDiskPercent' => 'Zaplnění disku',
'AttrDiskSpace' => 'Disk Space', // Added - 2018-08-30
'AttrDuration' => 'Průběh',
'AttrEndDate' => 'End Date', // Added - 2018-08-30
'AttrEndDateTime' => 'End Date/Time', // Added - 2018-08-30
'AttrEndTime' => 'End Time', // Added - 2018-08-30
'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30
'AttrFilterServer' => 'Server Filter is Running On', // Added - 2018-08-30
'AttrFrames' => 'Snímky',
'AttrId' => 'Id',
'AttrMaxScore' => 'Max. skóre',
'AttrMonitorId' => 'Kamera Id',
'AttrMonitorName' => 'Jméno kamery',
'AttrMonitorServer' => 'Server Monitor is Running On', // Added - 2018-08-30
'AttrName' => 'Jméno',
'AttrNotes' => 'Notes',
'AttrStartDate' => 'Start Date', // Added - 2018-08-30
'AttrStartDateTime' => 'Start Date/Time', // Added - 2018-08-30
'AttrStartTime' => 'Start Time', // Added - 2018-08-30
'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30
'AttrStateId' => 'Run State', // Added - 2018-08-30
'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30
'AttrStorageServer' => 'Server Hosting Storage', // Added - 2018-08-30
'AttrSystemLoad' => 'System Load',
'AttrTime' => 'Čas',
'AttrTotalScore' => 'Celkové skóre',
'AttrWeekday' => 'Den v týdnu',
'Auto' => 'Auto',
'AutoStopTimeout' => 'Časový limit pro vypršení',
'Available' => 'Available', // Added - 2009-03-31
@ -153,9 +165,11 @@ $SLANG = array(
'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',
'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"', // Added - 2018-08-30
'BadStreamReplayBuffer'=> 'Stream replay buffer must be an integer of zero or more',
'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.', // Added - 2018-08-30
'BadWidth' => 'Width must be set to a valid value',
'Bandwidth' => 'Rychlost sítě',
'BandwidthHead' => 'Bandwidth', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing
@ -219,10 +233,12 @@ $SLANG = array(
'ChooseLogSelection' => 'Choose a log selection', // Added - 2011-06-17
'ChoosePreset' => 'Choose Preset',
'Clear' => 'Clear', // Added - 2011-06-16
'CloneMonitor' => 'Clone', // Added - 2018-08-30
'Close' => 'Zavřít',
'Colour' => 'Barva',
'Command' => 'Příkaz',
'Component' => 'Component', // Added - 2011-06-16
'ConcurrentFilter' => 'Run filter concurrently', // Added - 2018-08-30
'Config' => 'Nastavení',
'ConfiguredFor' => 'Nastaveno pro',
'ConfirmDeleteEvents' => 'Are you sure you wish to delete the selected events?',
@ -280,9 +296,11 @@ $SLANG = array(
'DonateRemindWeek' => 'Nyní ne, připomenout za týden',
'DonateYes' => 'Ano, chcit podpořit ZoneMinder nyní',
'Download' => 'Stáhnout',
'DownloadVideo' => 'Download Video', // Added - 2018-08-30
'DuplicateMonitorName' => 'Duplicate Monitor Name', // Added - 2009-03-31
'Duration' => 'Průběh',
'Edit' => 'Editovat',
'EditLayout' => 'Edit Layout', // Added - 2018-08-30
'Email' => 'Email',
'EnableAlarms' => 'Povolit alarmy',
'Enabled' => 'Povoleno',
@ -299,6 +317,7 @@ $SLANG = array(
'Events' => 'Záznamy',
'Exclude' => 'Vyjmout',
'Execute' => 'Execute',
'Exif' => 'Embed EXIF data into image', // Added - 2018-08-30
'Export' => 'Exportovat',
'ExportDetails' => 'Exportovat detaily záznamu',
'ExportFailed' => 'Chyba při exportu',
@ -328,8 +347,10 @@ $SLANG = array(
'FilterExecuteEvents' => 'Spustit příkaz na všech nalezených',
'FilterLog' => 'Filter log', // Added - 2015-04-18
'FilterMessageEvents' => 'Podat zprávu o všech nalezených',
'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
'FilterPx' => 'Filtr Px',
'FilterUnset' => 'You must specify a filter width and height',
'FilterUpdateDiskSpace'=> 'Update used disk space', // Added - 2018-08-30
'FilterUploadEvents' => 'Uploadovat nalezené',
'FilterVideoEvents' => 'Create video for all matches',
'Filters' => 'Filtry',
@ -354,6 +375,7 @@ $SLANG = array(
'Function' => 'Funkce',
'Gain' => 'Zisk',
'General' => 'General',
'GenerateDownload' => 'Generate Download', // Added - 2018-08-30
'GenerateVideo' => 'Generovat video',
'GeneratingVideo' => 'Generuji video',
'GoToZoneMinder' => 'Jít na ZoneMinder.com',
@ -374,6 +396,7 @@ $SLANG = array(
'High' => 'Rychlá',
'HighBW' => 'Rychlá&nbsp;B/W',
'Home' => 'Domů',
'Hostname' => 'Hostname', // Added - 2018-08-30
'Hour' => 'Hodina',
'Hue' => 'Odstín',
'Id' => 'Id',
@ -398,6 +421,7 @@ $SLANG = array(
'Line' => 'Line', // Added - 2011-06-16
'LinkedMonitors' => 'Linked Monitors',
'List' => 'Seznam',
'ListMatches' => 'List Matches', // Added - 2018-08-30
'Load' => 'Load',
'Local' => 'Lokální',
'Log' => 'Log', // Added - 2011-06-16
@ -484,6 +508,7 @@ $SLANG = array(
'MonitorProbeIntro' => 'The list below shows detected analog and network 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/>', // Added - 2009-03-31
'Monitors' => 'Kamery',
'Montage' => 'Sestřih',
'MontageReview' => 'Montage Review', // Added - 2018-08-30
'Month' => 'Měsíc',
'More' => 'More', // Added - 2011-06-16
'MotionFrameSkip' => 'Motion Frame Skip',
@ -510,6 +535,7 @@ $SLANG = array(
'Next' => 'Další',
'No' => 'Ne',
'NoDetectedCameras' => 'No Detected Cameras', // Added - 2009-03-31
'NoDetectedProfiles' => 'No Detected Profiles', // Added - 2018-08-30
'NoFramesRecorded' => 'Pro tento snímek nejsou žádné záznamy',
'NoGroup' => 'No Group',
'NoSavedFilters' => 'Žádné uložené filtry',
@ -528,6 +554,8 @@ $SLANG = array(
'OpGt' => 'větší',
'OpGtEq' => 'větší nebo rovno',
'OpIn' => 'nin set',
'OpIs' => 'is', // Added - 2018-08-30
'OpIsNot' => 'is not', // Added - 2018-08-30
'OpLt' => 'menší',
'OpLtEq' => 'menší nebo rovno',
'OpMatches' => 'obsahuje',
@ -537,6 +565,7 @@ $SLANG = array(
'Open' => 'Otevřít',
'OptionHelp' => 'MožnostHelp',
'OptionRestartWarning' => 'Tyto změny se neprojeví\ndokud systém běží. Jakmile\ndokončíte provádění změn prosím\nrestartujte ZoneMinder.',
'OptionalEncoderParam' => 'Optional Encoder Parameters', // Added - 2018-08-30
'Options' => 'Možnosti',
'OrEnterNewName' => 'nebo vložte nové jméno',
'Order' => 'Pořadí',
@ -574,9 +603,13 @@ $SLANG = array(
'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/>', // Added - 2015-04-18
'Progress' => 'Progress', // Added - 2015-04-18
'Protocol' => 'Protocol',
'RTSPDescribe' => 'Use RTSP Response Media URL', // Added - 2018-08-30
'RTSPTransport' => 'RTSP Transport Protocol', // Added - 2018-08-30
'Rate' => 'Rychlost',
'Real' => 'Skutečná',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // Added - 2018-08-30
'Record' => 'Nahrávat',
'RecordAudio' => 'Whether to store the audio stream when saving an event.', // Added - 2018-08-30
'RefImageBlendPct' => 'Reference Image Blend %ge',
'Refresh' => 'Obnovit',
'Remote' => 'Síťová',
@ -592,6 +625,7 @@ $SLANG = array(
'ReplayAll' => 'All Events',
'ReplayGapless' => 'Gapless Events',
'ReplaySingle' => 'Single Event',
'ReportEventAudit' => 'Audit Events Report', // Added - 2018-08-30
'Reset' => 'Reset',
'ResetEventCounts' => 'Resetovat počty záznamů',
'Restart' => 'Restartovat',
@ -610,6 +644,7 @@ $SLANG = array(
'Save' => 'Uložit',
'SaveAs' => 'Uložit jako',
'SaveFilter' => 'Uložit filtr',
'SaveJPEGs' => 'Save JPEGs', // Added - 2018-08-30
'Scale' => 'Velikost',
'Score' => 'Skóre',
'Secs' => 'Délka(s)',
@ -626,6 +661,7 @@ $SLANG = array(
'ShowFilterWindow' => 'Zobrazit filtr',
'ShowTimeline' => 'Zobrazit časovou linii ',
'SignalCheckColour' => 'Signal Check Colour',
'SignalCheckPoints' => 'Signal Check Points', // Added - 2018-08-30
'Size' => 'Velikost',
'SkinDescription' => 'Change the default skin for this computer', // Added - 2011-01-30
'Sleep' => 'Spát',
@ -645,6 +681,10 @@ $SLANG = array(
'State' => 'Stav',
'Stats' => 'Statistiky',
'Status' => 'Status',
'StatusConnected' => 'Capturing', // Added - 2018-08-30
'StatusNotRunning' => 'Not Running', // Added - 2018-08-30
'StatusRunning' => 'Not Capturing', // Added - 2018-08-30
'StatusUnknown' => 'Unknown', // Added - 2018-08-30
'Step' => 'Krok',
'StepBack' => 'Step Back',
'StepForward' => 'Step Forward',
@ -655,6 +695,8 @@ $SLANG = array(
'Stills' => 'Snímky',
'Stop' => 'Zastavit',
'Stopped' => 'Zastaven',
'StorageArea' => 'Storage Area', // Added - 2018-08-30
'StorageScheme' => 'Scheme', // Added - 2018-08-30
'Stream' => 'Stream',
'StreamReplayBuffer' => 'Stream Replay Image Buffer',
'Submit' => 'Potvrdit',
@ -674,6 +716,7 @@ $SLANG = array(
'TimelineTip4' => 'Use the controls below to zoom out or navigate back and forward through the time range.', // Added 2013.08.15.
'Timestamp' => 'Razítko',
'TimestampLabelFormat' => 'Formát časového razítka',
'TimestampLabelSize' => 'Font Size', // Added - 2018-08-30
'TimestampLabelX' => 'Časové razítko X',
'TimestampLabelY' => 'Časové razítko Y',
'Today' => 'Dnes',
@ -720,6 +763,7 @@ $SLANG = array(
'VideoGenParms' => 'Parametry generování videa',
'VideoGenSucceeded' => 'Video vygenerováno úspěšně!',
'VideoSize' => 'Velikost videa',
'VideoWriter' => 'Video Writer', // Added - 2018-08-30
'View' => 'Zobrazit',
'ViewAll' => 'Zobrazit všechny',
'ViewEvent' => 'Zobrazit záznam',
@ -729,6 +773,7 @@ $SLANG = array(
'Watch' => 'Sledovat',
'Web' => 'Web',
'WebColour' => 'Webová barva',
'WebSiteUrl' => 'Website URL', // Added - 2018-08-30
'Week' => 'Týden',
'White' => 'Bílá',
'WhiteBalance' => 'Vyvážení bílé',

View File

@ -80,6 +80,8 @@ $SLANG = array(
'Actual' => 'Original',
'AddNewControl' => 'Neues Steuerelement hinzufügen',
'AddNewMonitor' => 'Neuer Monitor',
'AddNewServer' => 'Add New Server', // Added - 2018-08-30
'AddNewStorage' => 'Add New Storage', // Added - 2018-08-30
'AddNewUser' => 'Neuer Benutzer',
'AddNewZone' => 'Neue Zone',
'Alarm' => 'Alarm',
@ -107,22 +109,32 @@ $SLANG = array(
'AttrArchiveStatus' => 'Archivstatus',
'AttrAvgScore' => 'Mittlere Wertung',
'AttrCause' => 'Grund',
'AttrDate' => 'Datum',
'AttrDateTime' => 'Datum/Zeit',
'AttrDiskBlocks' => 'Disk-Blöcke',
'AttrDiskPercent' => 'Disk-Prozent',
'AttrDiskSpace' => 'Disk Space', // Added - 2018-08-30
'AttrDuration' => 'Dauer',
'AttrEndDate' => 'End Date', // Added - 2018-08-30
'AttrEndDateTime' => 'End Date/Time', // Added - 2018-08-30
'AttrEndTime' => 'End Time', // Added - 2018-08-30
'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30
'AttrFilterServer' => 'Server Filter is Running On', // Added - 2018-08-30
'AttrFrames' => 'Bilder',
'AttrId' => 'ID',
'AttrMaxScore' => 'Maximale Wertung',
'AttrMonitorId' => 'Monitor-ID',
'AttrMonitorName' => 'Monitorname',
'AttrMonitorServer' => 'Server Monitor is Running On', // Added - 2018-08-30
'AttrName' => 'Name',
'AttrNotes' => 'Bemerkungen',
'AttrStartDate' => 'Start Date', // Added - 2018-08-30
'AttrStartDateTime' => 'Start Date/Time', // Added - 2018-08-30
'AttrStartTime' => 'Start Time', // Added - 2018-08-30
'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30
'AttrStateId' => 'Run State', // Added - 2018-08-30
'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30
'AttrStorageServer' => 'Server Hosting Storage', // Added - 2018-08-30
'AttrSystemLoad' => 'Systemlast',
'AttrTime' => 'Zeit',
'AttrTotalScore' => 'Gesamtwertung',
'AttrWeekday' => 'Wochentag',
'Auto' => 'Auto',
'AutoStopTimeout' => 'Auto-Stopp-Zeitüberschreitung',
'Available' => 'Verfügbar', // Added - 2009-03-31
@ -155,9 +167,11 @@ $SLANG = array(
'BadRefBlendPerc' => 'Der Referenz-Blenden-Prozentwert muss ganzzahlig 0 oder größer sein',
'BadSectionLength' => 'Die Bereichslänge muss ganzzahlig 0 oder größer sein',
'BadSignalCheckColour' => 'Die Signalprüffarbe muss auf einen gültigen Farbwert eingestellt sein',
'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"', // Added - 2018-08-30
'BadStreamReplayBuffer'=> 'Der Wiedergabestrompuffer muss eine ganze Zahl von null oder mehr betragen',
'BadWarmupCount' => 'Die Anzahl der Vorwärmbilder muss ganzzahlig 0 oder größer sein',
'BadWebColour' => 'Die Webfarbe muss auf einen gültigen Farbwert eingestellt sein',
'BadWebSitePath' => 'Please enter a complete website url, including the http:// or https:// prefix.', // Added - 2018-08-30
'BadWidth' => 'Die Breite muss auf einen gültigen Wert eingestellt sein',
'Bandwidth' => 'Bandbreite',
'BandwidthHead' => 'Bandbreite', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing
@ -221,10 +235,12 @@ $SLANG = array(
'ChooseLogSelection' => 'Log-Auswahl', // Added - 2011-06-17
'ChoosePreset' => 'Voreinstellung auswählen',
'Clear' => 'Leeren', // Added - 2011-06-16
'CloneMonitor' => 'Clone', // Added - 2018-08-30
'Close' => 'Schließen',
'Colour' => 'Farbe',
'Command' => 'Kommando',
'Component' => 'Komponente', // Added - 2011-06-16
'ConcurrentFilter' => 'Run filter concurrently', // Added - 2018-08-30
'Config' => 'Konfig.',
'ConfiguredFor' => 'Konfiguriert für',
'ConfirmDeleteEvents' => 'Sind Sie sicher, dass Sie die ausgewählten Ereignisse löschen wollen?',
@ -282,9 +298,11 @@ $SLANG = array(
'DonateRemindWeek' => 'Noch nicht, erinnere mich in einer Woche noch mal.',
'DonateYes' => 'Ja, ich möchte jetzt spenden.',
'Download' => 'Download',
'DownloadVideo' => 'Download Video', // Added - 2018-08-30
'DuplicateMonitorName' => 'Monitornamen Duplizieren', // Added - 2009-03-31
'Duration' => 'Dauer',
'Edit' => 'Bearbeiten',
'EditLayout' => 'Edit Layout', // Added - 2018-08-30
'Email' => 'E-Mail',
'EnableAlarms' => 'Alarme aktivieren',
'Enabled' => 'Aktiviert',
@ -301,6 +319,7 @@ $SLANG = array(
'Events' => 'Ereignisse',
'Exclude' => 'Ausschluss;',
'Execute' => 'Ausführen',
'Exif' => 'Embed EXIF data into image', // Added - 2018-08-30
'Export' => 'Exportieren',
'ExportDetails' => 'Exportiere Ereignis-Details',
'ExportFailed' => 'Exportieren fehlgeschlagen',
@ -330,8 +349,10 @@ $SLANG = array(
'FilterExecuteEvents' => 'Ausführen bei allen Treffern',
'FilterLog' => 'Log filtern', // Added - 2015-04-18
'FilterMessageEvents' => 'Detaillierte Nachricht zu allen Treffern',
'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
'FilterPx' => 'Filter-Pixel',
'FilterUnset' => 'Sie müssen eine Breite und Höhe für das Filter angeben',
'FilterUpdateDiskSpace'=> 'Update used disk space', // Added - 2018-08-30
'FilterUploadEvents' => 'Hochladen aller Treffer',
'FilterVideoEvents' => 'Video für alle Treffer erstellen',
'Filters' => 'Filter',
@ -356,6 +377,7 @@ $SLANG = array(
'Function' => 'Funktion',
'Gain' => 'Verstärkung',
'General' => 'Allgemeines',
'GenerateDownload' => 'Generate Download', // Added - 2018-08-30
'GenerateVideo' => 'Erzeuge Video',
'GeneratingVideo' => 'Erzeuge Video...',
'GoToZoneMinder' => 'Besuche ZoneMinder.com',
@ -376,6 +398,7 @@ $SLANG = array(
'High' => 'hohe',
'HighBW' => 'Hohe B/W',
'Home' => 'Home',
'Hostname' => 'Hostname', // Added - 2018-08-30
'Hour' => 'Stunde',
'Hue' => 'Farbton',
'Id' => 'ID',
@ -400,6 +423,7 @@ $SLANG = array(
'Line' => 'Zeile', // Added - 2011-06-16
'LinkedMonitors' => 'Verbundene Monitore',
'List' => 'Liste',
'ListMatches' => 'List Matches', // Added - 2018-08-30
'Load' => 'Last',
'Local' => 'Lokal',
'Log' => 'Log', // Added - 2011-06-16
@ -486,6 +510,7 @@ $SLANG = array(
'MonitorProbeIntro' => 'Die nachfolgende Liste zeigt erkannte Analog- und Netzwerkkameras, ob sie bereits genutzt werden und ob sie zur Auswahl verfügbar sind.<br/><br/>Wähle den gewünschten Eintrag aus der folgenden Liste.<br/><br/>Bitte Beachten: Nicht alle Kameras können erkannt werden. Die Auswahl einer Kamera kann bereits eingetragene Werte im aktuellen Monitor überschreiben.<br/><br/>', // Added - 2009-03-31
'Monitors' => 'Monitore',
'Montage' => 'Montage',
'MontageReview' => 'Montage Review', // Added - 2018-08-30
'Month' => 'Monat',
'More' => 'Mehr', // Added - 2011-06-16
'MotionFrameSkip' => 'Motion Frame Skip',
@ -512,6 +537,7 @@ $SLANG = array(
'Next' => 'Nächstes',
'No' => 'Nein',
'NoDetectedCameras' => 'Keine Kameras erkannt', // Added - 2009-03-31
'NoDetectedProfiles' => 'No Detected Profiles', // Added - 2018-08-30
'NoFramesRecorded' => 'Es gibt keine Aufnahmen von diesem Ereignis.',
'NoGroup' => 'Keine Gruppe',
'NoSavedFilters' => 'Keine gespeicherten Filter',
@ -530,6 +556,8 @@ $SLANG = array(
'OpGt' => 'groesser als',
'OpGtEq' => 'groesser oder gleich wie',
'OpIn' => 'in Satz',
'OpIs' => 'is', // Added - 2018-08-30
'OpIsNot' => 'is not', // Added - 2018-08-30
'OpLt' => 'kleiner als',
'OpLtEq' => 'kleiner oder gleich wie',
'OpMatches' => 'zutreffend',
@ -539,6 +567,7 @@ $SLANG = array(
'Open' => 'öffnen',
'OptionHelp' => 'Hilfe',
'OptionRestartWarning' => 'Veränderungen werden erst nach einem Neustart des Programms aktiv.\nFür eine sofortige änderung starten Sie das Programm bitte neu.',
'OptionalEncoderParam' => 'Optional Encoder Parameters', // Added - 2018-08-30
'Options' => 'Optionen',
'OrEnterNewName' => 'oder neuen Namen eingeben',
'Order' => 'Reihenfolge',
@ -576,9 +605,13 @@ $SLANG = array(
'ProfileProbeIntro' => 'Die folgende Liste zeigt die verfügbaren Streamingprofile der ausgewählten Kamera.<br/><br/>Wähle den gewünschten Eintrag aus der folgenden Liste.<br/><br/>Bitte Beachten: Zoneminder kann keine zusätzlichen Profile konfigurieren. Die Auswahl einer Kamera kann bereits eingetragene Werte im aktuellen Monitor überschreiben.<br/><br/>', // Added - 2015-04-18
'Progress' => 'Fortschritt', // Added - 2015-04-18
'Protocol' => 'Protokoll',
'RTSPDescribe' => 'Use RTSP Response Media URL', // Added - 2018-08-30
'RTSPTransport' => 'RTSP Transport Protocol', // Added - 2018-08-30
'Rate' => 'Abspielgeschwindigkeit',
'Real' => 'Real',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // Added - 2018-08-30
'Record' => 'Aufnahme',
'RecordAudio' => 'Whether to store the audio stream when saving an event.', // Added - 2018-08-30
'RefImageBlendPct' => 'Referenz-Bildblende',
'Refresh' => 'Aktualisieren',
'Remote' => 'Remote',
@ -594,6 +627,7 @@ $SLANG = array(
'ReplayAll' => 'Alle Ereignisse',
'ReplayGapless' => 'Lückenlose Ereignisse',
'ReplaySingle' => 'Einzelereignis',
'ReportEventAudit' => 'Audit Events Report', // Added - 2018-08-30
'Reset' => 'Zurücksetzen',
'ResetEventCounts' => 'Lösche Ereignispunktzahl',
'Restart' => 'Neustart',
@ -612,6 +646,7 @@ $SLANG = array(
'Save' => 'Speichern',
'SaveAs' => 'Speichere als',
'SaveFilter' => 'Speichere Filter',
'SaveJPEGs' => 'Save JPEGs', // Added - 2018-08-30
'Scale' => 'Skalierung',
'Score' => 'Wertung',
'Secs' => 'Sekunden',
@ -628,6 +663,7 @@ $SLANG = array(
'ShowFilterWindow' => 'Zeige Filterfenster',
'ShowTimeline' => 'Zeige Zeitstrahl',
'SignalCheckColour' => 'Farbe des Signalchecks',
'SignalCheckPoints' => 'Signal Check Points', // Added - 2018-08-30
'Size' => 'Größe',
'SkinDescription' => 'Wähle den standard Skin für diesen Computer.', // Added - 2011-01-30
'Sleep' => 'Schlaf',
@ -647,6 +683,10 @@ $SLANG = array(
'State' => 'Status',
'Stats' => 'Statistik',
'Status' => 'Status',
'StatusConnected' => 'Capturing', // Added - 2018-08-30
'StatusNotRunning' => 'Not Running', // Added - 2018-08-30
'StatusRunning' => 'Not Capturing', // Added - 2018-08-30
'StatusUnknown' => 'Unknown', // Added - 2018-08-30
'Step' => 'Stufe',
'StepBack' => 'Einen Schritt rückwärts',
'StepForward' => 'Einen Schritt vorwärts',
@ -657,6 +697,8 @@ $SLANG = array(
'Stills' => 'Standbilder',
'Stop' => 'Stop',
'Stopped' => 'Gestoppt',
'StorageArea' => 'Storage Area', // Added - 2018-08-30
'StorageScheme' => 'Scheme', // Added - 2018-08-30
'Stream' => 'Stream',
'StreamReplayBuffer' => 'Stream-Wiedergabe-Bildpuffer',
'Submit' => 'Absenden',
@ -676,9 +718,9 @@ $SLANG = array(
'TimelineTip4' => 'Verwenden Sie die Steuerelemente unten, um zu Zoomen oder navigieren Sie vorwärts und rückwärts durch die Zeit.', // Added 2013.08.15.
'Timestamp' => 'Zeitstempel',
'TimestampLabelFormat' => 'Format des Zeitstempels',
'TimestampLabelSize' => 'Schriftgröße',
'TimestampLabelX' => 'Zeitstempel-X',
'TimestampLabelY' => 'Zeitstempel-Y',
'TimestampLabelSize' => 'Schriftgröße',
'Today' => 'Heute',
'Tools' => 'Werkzeuge',
'Total' => 'Insgesamt', // Added - 2011-06-16
@ -723,6 +765,7 @@ $SLANG = array(
'VideoGenParms' => 'Parameter der Videoerzeugung',
'VideoGenSucceeded' => 'Videoerzeugung erfolgreich!',
'VideoSize' => 'Videogröße',
'VideoWriter' => 'Video Writer', // Added - 2018-08-30
'View' => 'Ansicht',
'ViewAll' => 'Alles ansehen',
'ViewEvent' => 'Zeige Ereignis',
@ -732,6 +775,7 @@ $SLANG = array(
'Watch' => 'Beobachte',
'Web' => 'Web',
'WebColour' => 'Webfarbe',
'WebSiteUrl' => 'Website URL', // Added - 2018-08-30
'Week' => 'Woche',
'White' => 'Weiß',
'WhiteBalance' => 'Weiß-Abgleich',

44
web/lang/default.php Normal file
View File

@ -0,0 +1,44 @@
<?php
//
// ZoneMinder DEFAULT 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.
//
// The translate function will return the literal name of the key if the key
// name does not exist in the language file.
// This file provides a means to return a result, other than the key name, if
// it does not exist in the language file.
// Simple String Replacements
$DLANG = array(
'Privacy' => 'Privacy',
'PrivacyAbout' => 'About',
'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' => 'Contact',
'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' => 'Cookies',
'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 dont 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.',
);
?>

View File

@ -72,15 +72,6 @@ header( "Content-Type: text/html; charset=utf-8" );
// Simple String Replacements
$SLANG = array(
'SystemLog' => 'System Log',
'DateTime' => 'Dato/Tid',
'Component' => 'Komponent',
'Pid' => 'PID',
'Level' => 'Niveau',
'Message' => 'Meddelelse',
'Line' => 'Linie',
'More' => 'Mere',
'Clear' => 'Slet',
'24BitColour' => '24 bit farve',
'32BitColour' => '32 bit farve',
'8BitGrey' => '8 bit gråskala',
@ -89,6 +80,7 @@ $SLANG = array(
'AddNewControl' => 'Tilføj Ny Kontrol',
'AddNewMonitor' => 'Tilføj Ny Monitor',
'AddNewServer' => 'Tilføj Ny Server',
'AddNewStorage' => 'Add New Storage', // Added - 2018-08-30
'AddNewUser' => 'Tilføj Ny Bruger',
'AddNewZone' => 'Tilføj Ny Zone',
'Alarm' => 'Alarm',
@ -98,8 +90,8 @@ $SLANG = array(
'AlarmLimits' => 'Alarm Grænser',
'AlarmMaximumFPS' => 'Alarm Maksimum FPS',
'AlarmPx' => 'Alarm Px',
'AlarmRefImageBlendPct' => 'Alarm Reference Billede Blandings %',
'AlarmRGBUnset' => 'Du skal vælge en alarm RGB farve',
'AlarmRefImageBlendPct' => 'Alarm Reference Billede Blandings %',
'Alert' => 'Advarsel',
'All' => 'Alle',
'AnalysisFPS' => 'Analyse FPS',
@ -107,37 +99,45 @@ $SLANG = array(
'Apply' => 'Udfør',
'ApplyingStateChange' => 'Udfører tilstandsændring',
'ArchArchived' => 'Kun arkiverede',
'ArchUnarchived' => 'Kun ikke-arkiverede',
'Archive' => 'Arkivér',
'Archived' => 'Arkiverede',
'ArchUnarchived' => 'Kun ikke-arkiverede',
'Area' => 'Område',
'AreaUnits' => 'Område (px/%)',
'AttrAlarmFrames' => 'Alarm Rammer',
'AttrArchiveStatus' => 'Arkiverings Status',
'AttrAvgScore' => 'Middel Score',
'AttrCause' => 'Årsag',
'AttrDate' => 'Dato',
'AttrDateTime' => 'Dato/Tid',
'AttrDiskBlocks' => 'Disk Blokke',
'AttrDiskPercent' => 'Disk Procent',
'AttrDiskSpace' => 'Disk Space', // Added - 2018-08-30
'AttrDuration' => 'Varighed',
'AttrEndDate' => 'End Date', // Added - 2018-08-30
'AttrEndDateTime' => 'End Date/Time', // Added - 2018-08-30
'AttrEndTime' => 'End Time', // Added - 2018-08-30
'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30
'AttrFilterServer' => 'Server Filter is Running On', // Added - 2018-08-30
'AttrFrames' => 'Rammer',
'AttrId' => 'Id',
'AttrMaxScore' => 'Max. Score',
'AttrMonitorId' => 'Monitor Id',
'AttrMonitorName' => 'Monitor Navn',
'AttrServer' => 'Server',
'AttrMonitorServer' => 'Server Monitor is Running On', // Added - 2018-08-30
'AttrName' => 'Navn',
'AttrNotes' => 'Noter',
'AttrStartDate' => 'Start Date', // Added - 2018-08-30
'AttrStartDateTime' => 'Start Date/Time', // Added - 2018-08-30
'AttrStartTime' => 'Start Time', // Added - 2018-08-30
'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30
'AttrStateId' => 'Run State', // Added - 2018-08-30
'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30
'AttrStorageServer' => 'Server Hosting Storage', // Added - 2018-08-30
'AttrSystemLoad' => 'System Belastning',
'AttrTime' => 'Tid',
'AttrTotalScore' => 'Total Score',
'AttrWeekday' => 'Ugedag',
'Auto' => 'Auto',
'AutoStopTimeout' => 'Auto Stop Timeout',
'Available' => 'Tilgængelig',
'AvgBrScore' => 'Middel<br/>Score',
'Available' => 'Tilgængelig',
'Background' => 'Baggrund',
'BackgroundFilter' => 'Kør filteret i baggrunden',
'BadAlarmFrameCount' => 'Antal alarm rammer skal være et positivt heltal',
@ -145,20 +145,20 @@ $SLANG = array(
'BadAnalysisFPS' => 'Analyse FPS skal være et positivt heltal eller flydende tal',
'BadAnalysisUpdateDelay'=> 'Analyse opdaterings forsinkelse skal være et heltal på 0 eller mere',
'BadChannel' => 'Kanal skal sættes til et heltal på 0 eller mere',
'BadColours' => 'Målfarven skal sættes til en gyldig værdi',
'BadDevice' => 'Enhed skal sættes til en gyldig værdi',
'BadFormat' => 'Format skal sættes til en gyldig værdi',
'BadFPSReportInterval' => 'Antal FPS report interval buffere skal være et heltal på 0 eller mere',
'BadFormat' => 'Format skal sættes til en gyldig værdi',
'BadFrameSkip' => 'Antal Frame skip skal være et heltal på 0 eller mere',
'BadMotionFrameSkip' => 'Antal Motion Frame skip skal være et heltal på 0 eller mere',
'BadHeight' => 'Højde skal sættes til en gyldig værdi',
'BadHost' => 'Host skal vare en gyldig IP adresse eller hostname, inkludér ikke http://',
'BadImageBufferCount' => 'Billed buffer størrelse skal være et heltal på 10 eller mere',
'BadLabelX' => 'Mærkat X co-ordinaten skal sættes til et heltal på 0 eller mere',
'BadLabelY' => 'Mærkat Y co-ordinaten skal sættes til et heltal på 0 eller mere',
'BadMaxFPS' => 'Maximum FPS skal være et positivt heltal eller flydende tal',
'BadMotionFrameSkip' => 'Antal Motion Frame skip skal være et heltal på 0 eller mere',
'BadNameChars' => 'Navne kan kun indeholde alfanumeriske tegn samt mellemrum, bindestreg og understregning',
'BadPalette' => 'Palette skal sættes til en gyldig værdi',
'BadColours' => 'Målfarven skal sættes til en gyldig værdi',
'BadPath' => 'Sti skal sættes til en gyldig værdi',
'BadPort' => 'Port skal sættes til et gyldigt nummer',
'BadPostEventCount' => 'Antal rammer efter hændelsen skal være et heltal på 0 eller mere',
@ -166,39 +166,40 @@ $SLANG = array(
'BadRefBlendPerc' => 'Reference blandings procentdelen skal være et positivt heltal',
'BadSectionLength' => 'Sektionslængden skal være et heltal på 30 eller mere',
'BadSignalCheckColour' => 'Signal check farve skal være en gyldig RGB farve streng',
'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"', // Added - 2018-08-30
'BadStreamReplayBuffer' => 'Videostrøm genspilsbufferen skal sættes til et heltal på 0 eller mere',
'BadWarmupCount' => 'Opvarmnings rammer skal være et heltal på 0 eller mere',
'BadWebColour' => 'Web farve skal være en gyldigt web farve streng',
'BadWebSitePath' => 'Please enter a complete website url, including the http:// or https:// prefix.', // Added - 2018-08-30
'BadWidth' => 'Bredde skal sættes til en gyldig værdi',
'Bandwidth' => 'Båndbredde',
'BandwidthHead' => 'Båndbredde', // 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 Størrelser',
'Blobs' => 'Blobs',
'Brightness' => 'Lysstyrke',
'Buffer' => 'Buffer',
'Buffers' => 'Buffere',
'CSSDescription' => 'SKift standard css for denne computer',
'CanAutoFocus' => 'Can Auto Focus',
'CanAutoGain' => 'Can Auto Gain',
'CanAutoIris' => 'Can Auto Iris',
'CanAutoWhite' => 'Can Auto White Bal.',
'CanAutoZoom' => 'Can Auto Zoom',
'Cancel' => 'Fortryd',
'CancelForcedAlarm' => 'Fortryd Tvungen Alarm',
'CanFocusAbs' => 'Can Focus Absolute',
'CanFocus' => 'Can Focus',
'CanFocusAbs' => 'Can Focus Absolute',
'CanFocusCon' => 'Can Focus Continuous',
'CanFocusRel' => 'Can Focus Relative',
'CanGainAbs' => 'Can Gain Absolute',
'CanGain' => 'Can Gain ',
'CanGainAbs' => 'Can Gain Absolute',
'CanGainCon' => 'Can Gain Continuous',
'CanGainRel' => 'Can Gain Relative',
'CanIrisAbs' => 'Can Iris Absolute',
'CanIris' => 'Can Iris',
'CanIrisAbs' => 'Can Iris Absolute',
'CanIrisCon' => 'Can Iris Continuous',
'CanIrisRel' => 'Can Iris Relative',
'CanMoveAbs' => 'Can Move Absolute',
'CanMove' => 'Can Move',
'CanMoveAbs' => 'Can Move Absolute',
'CanMoveCon' => 'Can Move Continuous',
'CanMoveDiag' => 'Can Move Diagonally',
'CanMoveMap' => 'Can Move Mapped',
@ -209,19 +210,21 @@ $SLANG = array(
'CanSleep' => 'Can Sleep',
'CanTilt' => 'Can Tilt',
'CanWake' => 'Can Wake',
'CanWhite' => 'Can White Balance',
'CanWhiteAbs' => 'Can White Bal. Absolute',
'CanWhiteBal' => 'Can White Bal.',
'CanWhite' => 'Can White Balance',
'CanWhiteCon' => 'Can White Bal. Continuous',
'CanWhiteRel' => 'Can White Bal. Relative',
'CanZoomAbs' => 'Can Zoom Absolute',
'CanZoom' => 'Can Zoom',
'CanZoomAbs' => 'Can Zoom Absolute',
'CanZoomCon' => 'Can Zoom Continuous',
'CanZoomRel' => 'Can Zoom Relative',
'Cancel' => 'Fortryd',
'CancelForcedAlarm' => 'Fortryd Tvungen Alarm',
'CaptureHeight' => 'Capture Højde',
'CaptureMethod' => 'Capture Metode',
'CaptureResolution' => 'Capture Opløsning',
'CapturePalette' => 'Capture Palette',
'CaptureResolution' => 'Capture Opløsning',
'CaptureWidth' => 'Capture Bredde',
'Cause' => 'Årsag',
'CheckMethod' => 'Alarm Check Metode',
@ -230,10 +233,13 @@ $SLANG = array(
'ChooseLogFormat' => 'Vælg et lognings format',
'ChooseLogSelection' => 'Vælg et lognings udvælgelse',
'ChoosePreset' => 'Vælg Forudindstilling',
'Clear' => 'Slet',
'CloneMonitor' => 'Klon Monitor',
'Close' => 'Luk',
'Colour' => 'Farve',
'Command' => 'Kommando',
'Component' => 'Komponent',
'ConcurrentFilter' => 'Run filter concurrently', // Added - 2018-08-30
'Config' => 'Konfigurer',
'ConfiguredFor' => 'Konfigureret for',
'ConfirmDeleteEvents' => 'Er du sikker på, at du vil slette de markerede hændelser?',
@ -244,62 +250,64 @@ $SLANG = array(
'ContactAdmin' => 'Venligst kontakt din administrator for detaljer.',
'Continue' => 'Fortsæt',
'Contrast' => 'Kontrast',
'Control' => 'Control',
'ControlAddress' => 'Control Address',
'ControlCap' => 'Control Capability',
'ControlCaps' => 'Control Capabilities',
'Control' => 'Control',
'ControlDevice' => 'Control Device',
'Controllable' => 'Controllable',
'ControlType' => 'Control Type',
'Controllable' => 'Controllable',
'Current' => 'Nuværende',
'Cycle' => 'Cyklisk',
'CycleWatch' => 'Cyklisk Overvågning',
'DateTime' => 'Dato/Tid',
'Day' => 'Dag',
'Debug' => 'Fejlfind',
'DefaultRate' => 'Standard Rate',
'DefaultScale' => 'Standard Skalering',
'DefaultView' => 'Standard Visning',
'Deinterlacing' => 'Deinterlacing',
'RTSPDescribe' => 'Brug RTSP Response Media URL',
'Delay' => 'Forsilkelse',
'Delete' => 'Slet',
'DeleteAndNext' => 'Slet &amp; Næste',
'DeleteAndPrev' => 'Slet &amp; Forrige',
'Delete' => 'Slet',
'DeleteSavedFilter' => 'Slet gemt filter',
'Description' => 'Beskrivelse',
'DetectedCameras' => 'Fundne Kameraer',
'DetectedProfiles' => 'Fundne Profiler',
'Device' => 'Enheds',
'DeviceChannel' => 'Enheds Kanal',
'DeviceFormat' => 'Enheds Format',
'DeviceNumber' => 'Enheds Number',
'DevicePath' => 'Sti Til Enhed',
'Device' => 'Enheds',
'Devices' => 'Enheder',
'Dimensions' => 'Dimensioner',
'DisableAlarms' => 'Deaktiver Alarmer',
'Disk' => 'Disk',
'Display' => 'Display',
'Displaying' => 'Displaying',
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
'Donate' => 'Venligst Donér',
'DonateAlready' => 'Nej, jeg har allerede doneret',
'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' => 'Venligst Donér',
'DonateRemindDay' => 'Ikke endnu, påmind igen on 1 dag',
'DonateRemindHour' => 'Ikke endnu, påmind igen on 1 time',
'DonateRemindMonth' => 'Ikke endnu, påmind igen on 1 måned',
'DonateRemindNever' => 'Nej, jeg ønsker ikke at donere, påmind ikke igen',
'DonateRemindWeek' => 'Ikke endnu, påmind igen on 1 uge',
'DonateYes' => 'Ja, jeg vil gerne donere nu',
'DoNativeMotionDetection'=> 'Do Native Motion Detection',
'Download' => 'Download',
'DownloadVideo' => 'Download Video', // Added - 2018-08-30
'DuplicateMonitorName' => 'Dupliket Monitor Navn',
'Duration' => 'Varighed',
'Edit' => 'Ret',
'EditLayout' => 'Edit Layout', // Added - 2018-08-30
'Email' => 'Email',
'EnableAlarms' => 'Aktivér Alarmer',
'Enabled' => 'Virksom',
'EnterNewFilterName' => 'Indtast nyt filternavn',
'ErrorBrackets' => 'Fejl, venligst check, at du har samme antal open og lukke klammer',
'Error' => 'Fejl',
'ErrorBrackets' => 'Fejl, venligst check, at du har samme antal open og lukke klammer',
'ErrorValidValue' => 'Fejl, venligst check at alle parametre har en gyldig værdi',
'Etc' => 'etc',
'Event' => 'Hændelse',
@ -310,9 +318,9 @@ $SLANG = array(
'Events' => 'Hændelser',
'Exclude' => 'Ekskluder',
'Execute' => 'Udfør',
'ExportDetails' => 'Exporter Hændelses Detaljer',
'Exif' => 'Indlejre EXIF data i billede',
'Export' => 'Exporter',
'ExportDetails' => 'Exporter Hændelses Detaljer',
'ExportFailed' => 'Export Mislykkedes',
'ExportFormat' => 'Export Fil Format',
'ExportFormatTar' => 'Tar',
@ -320,53 +328,55 @@ $SLANG = array(
'ExportFrames' => 'Exporter Ramme Detaljer',
'ExportImageFiles' => 'Exporter billed filer',
'ExportLog' => 'Export Log',
'Exporting' => 'Exporterer',
'ExportMiscFiles' => 'Exporter Andre Filer (hvis tilstede)',
'ExportOptions' => 'Export Indstillinger',
'ExportSucceeded' => 'Export Lykkedes',
'ExportVideoFiles' => 'Exporter Video Filer (hvis tilstede)',
'Exporting' => 'Exporterer',
'FPS' => 'fps',
'FPSReportInterval' => 'FPS Rapport Interval',
'FTP' => 'FTP',
'Far' => 'Fjern',
'FastForward' => 'Hurtigt Frem',
'Feed' => 'Feed',
'Ffmpeg' => 'Ffmpeg',
'File' => 'Fil',
'Filter' => 'Filter',
'FilterArchiveEvents' => 'Arkiver alle matchende',
'FilterDeleteEvents' => 'Slet alle matchende',
'FilterEmailEvents' => 'Email detaljer for alle matchende',
'FilterExecuteEvents' => 'Udfør kommando for alle matchende',
'FilterLog' => 'Filter log',
'FilterMessageEvents' => 'Meddel detaljer for alle matchende',
'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
'FilterPx' => 'Filter Px',
'Filter' => 'Filter',
'Filters' => 'Filtre',
'FilterUnset' => 'Du skal angive filter bredde og højde',
'FilterUpdateDiskSpace'=> 'Update used disk space', // Added - 2018-08-30
'FilterUploadEvents' => 'Upload alle match',
'FilterVideoEvents' => 'Opret video for alle match',
'Filters' => 'Filtre',
'First' => 'Første',
'FlippedHori' => 'Spejlet Horizontalt',
'FlippedVert' => 'Spejlet Vertikalt',
'FnNone' => 'None', // 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.
'FnModect' => 'Modect', // Added 2013.08.16.
'FnMonitor' => 'Monitor', // Added 2013.08.16.
'FnNodect' => 'Nodect', // Added 2013.08.16.
'FnNone' => 'None', // Added 2013.08.16.
'FnRecord' => 'Record', // Added 2013.08.16.
'Focus' => 'Focus',
'ForceAlarm' => 'Force Alarm',
'Format' => 'Format',
'FPS' => 'fps',
'FPSReportInterval' => 'FPS Rapport Interval',
'Frame' => 'Ramme',
'FrameId' => 'Ramme Id',
'FrameRate' => 'Billedhastighed',
'Frames' => 'Rammer',
'FrameSkip' => 'Spring over antal rammer',
'MotionFrameSkip' => 'Spring over antal bevægelsesrammer',
'FTP' => 'FTP',
'Frames' => 'Rammer',
'Func' => 'Funk',
'Function' => 'Funktion',
'Gain' => 'Gain',
'General' => 'Generelt',
'GenerateDownload' => 'Generate Download', // Added - 2018-08-30
'GenerateVideo' => 'Generer Video',
'GeneratingVideo' => 'Genererer Video',
'GoToZoneMinder' => 'Gå til ZoneMinder.com',
@ -384,8 +394,8 @@ $SLANG = array(
'HasTurboTilt' => 'Has Turbo Tilt',
'HasWhiteSpeed' => 'Has White Bal. Speed',
'HasZoomSpeed' => 'Has Zoom Speed',
'HighBW' => 'High&nbsp;B/W',
'High' => 'Høj',
'HighBW' => 'High&nbsp;B/W',
'Home' => 'Hjemme',
'Hostname' => 'Hostname',
'Hour' => 'Time',
@ -393,11 +403,11 @@ $SLANG = array(
'Id' => 'Id',
'Idle' => 'Afventende',
'Ignore' => 'Ignorer',
'ImageBufferSize' => 'Billed Buffer Størrelse (rammer)',
'Image' => 'Billede',
'ImageBufferSize' => 'Billed Buffer Størrelse (rammer)',
'Images' => 'Billeder',
'Include' => 'Inkluder',
'In' => 'I',
'Include' => 'Inkluder',
'Inverted' => 'Inverteret',
'Iris' => 'Blænde',
'KeyString' => 'Nøgle Streng',
@ -405,26 +415,30 @@ $SLANG = array(
'Language' => 'Sprog',
'Last' => 'Sidste',
'Layout' => 'Layout',
'Level' => 'Niveau',
'Libvlc' => 'Libvlc',
'LimitResultsPost' => 'resultater', // This is used at the end of the phrase 'Limit to first N results only'
'LimitResultsPre' => 'Begræns til kun de første', // This is used at the beginning of the phrase 'Limit to first N results only'
'Line' => 'Linie',
'LinkedMonitors' => 'Sammenkædede Monitorer',
'List' => 'Liste',
'ListMatches' => 'List Matches', // Added - 2018-08-30
'Load' => 'Belastning',
'Local' => 'Lokal',
'Log' => 'Log',
'Logs' => 'Logs',
'Logging' => 'Logning',
'LoggedInAs' => 'Logget ind som',
'Logging' => 'Logning',
'LoggingIn' => 'Logger ind',
'Login' => 'Logind',
'Logout' => 'Logud',
'LowBW' => 'Lav&nbsp;B/W',
'Logs' => 'Logs',
'Low' => 'Lav',
'LowBW' => 'Lav&nbsp;B/W',
'Main' => 'Hoved',
'Man' => 'Man',
'Manual' => 'Manuel',
'Mark' => 'Markér',
'Max' => 'Max',
'MaxBandwidth' => 'Max Båndbredde',
'MaxBrScore' => 'Max.<br/>Score',
'MaxFocusRange' => 'Max Focus Range',
@ -433,11 +447,9 @@ $SLANG = array(
'MaxGainRange' => 'Max Gain Range',
'MaxGainSpeed' => 'Max Gain Speed',
'MaxGainStep' => 'Max Gain Step',
'MaximumFPS' => 'Maximum FPS',
'MaxIrisRange' => 'Max Iris Range',
'MaxIrisSpeed' => 'Max Iris Speed',
'MaxIrisStep' => 'Max Iris Step',
'Max' => 'Max',
'MaxPanRange' => 'Max Pan Range',
'MaxPanSpeed' => 'Max Pan Speed',
'MaxPanStep' => 'Max Pan Step',
@ -450,8 +462,10 @@ $SLANG = array(
'MaxZoomRange' => 'Max Zoom Range',
'MaxZoomSpeed' => 'Max Zoom Speed',
'MaxZoomStep' => 'Max Zoom Step',
'MediumBW' => 'Medium&nbsp;B/W',
'MaximumFPS' => 'Maximum FPS',
'Medium' => 'Medium',
'MediumBW' => 'Medium&nbsp;B/W',
'Message' => 'Meddelelse',
'MinAlarmAreaLtMax' => 'Minimum alarm område skal være mindre end maksimum',
'MinAlarmAreaUnset' => 'Du skal angive det minimale antal alarm pixels',
'MinBlobAreaLtMax' => 'Minimum blob område skal være mindre end maksimum',
@ -487,22 +501,24 @@ $SLANG = array(
'MinZoomStep' => 'Min Zoom Step',
'Misc' => 'Diverse',
'Mode' => 'Mode',
'MonitorIds' => 'Monitor&nbsp;Ids',
'Monitor' => 'Monitor',
'MonitorPresetIntro' => 'Vælg en passende forudindstilling fra listen herunder.<br/><br/>Vær opmærksom på, at dette kan overskrive værdier, du allerede har angivet for den aktuelle monitor.<br/><br/>',
'MonitorIds' => 'Monitor&nbsp;Ids',
'MonitorPreset' => 'Monitor Forudindstillinger',
'MonitorProbeIntro' => 'Listen herunder viser fundne analoge og nætværks kameraer samt hvorvidt de allerede er i brug eller tilgængelige for udvælgelse.<br/><br/>Vælg det ønskede fra listen herunder.<br/><br/>Vær opmærksom på, at muligvis ikke alle kameraer er fundet og at valg af et kamera kan overskrive værdier, du allerede har angivet for den aktuelle monitor.<br/><br/>',
'MonitorPresetIntro' => 'Vælg en passende forudindstilling fra listen herunder.<br/><br/>Vær opmærksom på, at dette kan overskrive værdier, du allerede har angivet for den aktuelle monitor.<br/><br/>',
'MonitorProbe' => 'Monitor Probe',
'MonitorProbeIntro' => 'Listen herunder viser fundne analoge og nætværks kameraer samt hvorvidt de allerede er i brug eller tilgængelige for udvælgelse.<br/><br/>Vælg det ønskede fra listen herunder.<br/><br/>Vær opmærksom på, at muligvis ikke alle kameraer er fundet og at valg af et kamera kan overskrive værdier, du allerede har angivet for den aktuelle monitor.<br/><br/>',
'Monitors' => 'Monitorer',
'Montage' => 'Montage',
'MontageReview' => 'Montage Review',
'Month' => 'Måned',
'More' => 'Mere',
'MotionFrameSkip' => 'Spring over antal bevægelsesrammer',
'Move' => 'Bevæg',
'MtgDefault' => 'Standard', // Added 2013.08.15.
'Mtg2widgrd' => '2-bred gitter', // Added 2013.08.15.
'Mtg3widgrd' => '3-bred gitter', // Added 2013.08.15.
'Mtg4widgrd' => '4-bred gitter', // Added 2013.08.15.
'Mtg3widgrx' => '3-bred gitter, skaleret, forstørret ved alarm', // Added 2013.08.15.
'Mtg4widgrd' => '4-bred gitter', // Added 2013.08.15.
'MtgDefault' => 'Standard', // Added 2013.08.15.
'MustBeGe' => 'Skal være større end eller lig med',
'MustBeLe' => 'Skal være mindre end eller lig med',
'MustConfirmPassword' => 'Du skal bekræfte adgangskoden',
@ -511,53 +527,55 @@ $SLANG = array(
'Name' => 'Navn',
'Near' => 'Nær',
'Network' => 'Netværk',
'New' => 'Ny',
'NewGroup' => 'Ny Gruppe',
'NewLabel' => 'Ny Mærkat',
'New' => 'Ny',
'NewPassword' => 'Ny Adgangskode',
'NewState' => 'Ny Tilstand',
'NewUser' => 'Ny bruger',
'Next' => 'Næste',
'No' => 'Nej',
'NoDetectedCameras' => 'Ingen Detected Cameras',
'NoDetectedProfiles' => 'Ingen Fundne Profiler',
'NoFramesRecorded' => 'Der er ingen billeder optaget for denne hændelse',
'NoGroup' => 'Ingen gruppe',
'NoneAvailable' => 'Ingen tilgængelig',
'None' => 'Ingen',
'No' => 'Nej',
'Normal' => 'Normalt',
'NoSavedFilters' => 'IngenGemteFiltre',
'NoStatisticsRecorded' => 'Der er ingen statistik noteret for denne hændelse/ramme',
'None' => 'Ingen',
'NoneAvailable' => 'Ingen tilgængelig',
'Normal' => 'Normalt',
'Notes' => 'Noter',
'NumPresets' => 'Num Forudinst.',
'Off' => 'Fra',
'On' => 'Til',
'OnvifCredentialsIntro' => 'Venligst lever brugernavn og adgangskodefor de valgte kamera.<br/>Hvis der ikke er oprettet nogen bruger for kameraet, så vil brugeren givet her blive oprettet med den angivne adgangskode.<br/><br/>',
'OnvifProbe' => 'ONVIF',
'OnvifProbeIntro' => 'Listen nedenfor viser fundne ONVIF kameraer samt hvorvidt de allerede er i brug eller tilgængelige for udvælgelse.<br/><br/>Vælg det ønskede fra listen herunder.<br/><br/>Vær opmærksom på, at muligvis ikke alle kameraer er fundet og at valg af et kamera kan overskrive værdier, du allerede har angivet for den aktuelle monitor.<br/><br/>',
'OnvifCredentialsIntro' => 'Venligst lever brugernavn og adgangskodefor de valgte kamera.<br/>Hvis der ikke er oprettet nogen bruger for kameraet, så vil brugeren givet her blive oprettet med den angivne adgangskode.<br/><br/>',
'Open' => 'Åben',
'OpEq' => 'lig med',
'OpGtEq' => 'større end eller lig med',
'OpGt' => 'større end',
'OpGtEq' => 'større end eller lig med',
'OpIn' => 'indeholdt i',
'OpLtEq' => 'mindre end eller lig med',
'OpIs' => 'is', // Added - 2018-08-30
'OpIsNot' => 'is not', // Added - 2018-08-30
'OpLt' => 'mindre end',
'OpLtEq' => 'mindre end eller lig med',
'OpMatches' => 'matcher',
'OpNe' => 'ikke lig med',
'OpNotIn' => 'ikke indeholdt i',
'OpNotMatches' => 'matcher ikke',
'OptionalEncoderParam' => 'Optionelle Encoder Parametre',
'Open' => 'Åben',
'OptionHelp' => 'Indstillinger hjælp',
'OptionRestartWarning' => 'Disse ændringer har muligvis ikke fuld effekt\nmens systemet er kørende. Når du har\nafsluttet dine ændringer, skal du huske at\ngenstarte ZoneMinder.',
'OptionalEncoderParam' => 'Optionelle Encoder Parametre',
'Options' => 'Indstillinger',
'Order' => 'Rækkefølge',
'OrEnterNewName' => 'eller indtast nyt navn',
'Order' => 'Rækkefølge',
'Orientation' => 'Orientering',
'Out' => 'Ud',
'OverwriteExisting' => 'Overskriv Eksisterende',
'Paged' => 'Sidevis',
'PanLeft' => 'Pan Left',
'Pan' => 'Pan',
'PanLeft' => 'Pan Left',
'PanRight' => 'Pan Right',
'PanTilt' => 'Pan/Tilt',
'Parameter' => 'Parameter',
@ -565,14 +583,15 @@ $SLANG = array(
'PasswordsDifferent' => 'Den nye og den bekræftende adgangskode er forskellige',
'Paths' => 'Stier',
'Pause' => 'Pause',
'PhoneBW' => 'Telefon&nbsp;B/W',
'Phone' => 'Telefon',
'PhoneBW' => 'Telefon&nbsp;B/W',
'Pid' => 'PID',
'PixelDiff' => 'Pixel Forskel',
'Pixels' => 'pixels',
'PlayAll' => 'Afspil Alle',
'Play' => 'Afspil',
'Plugins' => 'Plugins',
'PlayAll' => 'Afspil Alle',
'PleaseWait' => 'Vent venligst',
'Plugins' => 'Plugins',
'Point' => 'Point',
'PostEventImageBuffer' => 'Antal Billeder Efter Hændelse',
'PreEventImageBuffer' => 'Antal Billeder Før Hændelse',
@ -585,30 +604,33 @@ $SLANG = array(
'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' => 'Position',
'Protocol' => 'Protokol',
'RTSPDescribe' => 'Brug RTSP Response Media URL',
'RTSPTransport' => 'RTSP Transport Protocol',
'Rate' => 'Rate',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // added Sep 24 2015 - PP
'RecordAudio' => 'Skal lydsporet gemmes sammen med en hændelse.',
'Real' => 'Naturtro',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // added Sep 24 2015 - PP
'Record' => 'Optag',
'RecordAudio' => 'Skal lydsporet gemmes sammen med en hændelse.',
'RefImageBlendPct' => 'Reference Billede Blandings %',
'Refresh' => 'Genindlæs',
'Remote' => 'Remote',
'RemoteHostName' => 'Remote Host Name',
'RemoteHostPath' => 'Remote Host Path',
'RemoteHostSubPath' => 'Remote Host SubPath',
'RemoteHostPort' => 'Remote Host Port',
'RemoteHostSubPath' => 'Remote Host SubPath',
'RemoteImageColours' => 'Remote Image Colours',
'RemoteMethod' => 'Remote Method',
'RemoteProtocol' => 'Remote Protocol',
'Remote' => 'Remote',
'Rename' => 'Omdøb',
'Replay' => 'Genafspil',
'ReplayAll' => 'Alle Hændelser',
'ReplayGapless' => 'Hændelser uafbrudt',
'Replay' => 'Genafspil',
'ReplaySingle' => 'Enkelt Hændelse',
'ResetEventCounts' => 'Nulstil Hændelses Tæller',
'ReportEventAudit' => 'Audit Events Report', // Added - 2018-08-30
'Reset' => 'Nulstil',
'Restarting' => 'Genstarter',
'ResetEventCounts' => 'Nulstil Hændelses Tæller',
'Restart' => 'Genstart',
'Restarting' => 'Genstarter',
'RestrictedCameraIds' => 'Restricted Camera Ids',
'RestrictedMonitors' => 'Restricted Monitors',
'ReturnDelay' => 'Return Delay',
@ -616,34 +638,33 @@ $SLANG = array(
'Rewind' => 'Hurtigt Tilbage',
'RotateLeft' => 'Roter til venstrte',
'RotateRight' => 'Roter til højre',
'RTSPTransport' => 'RTSP Transport Protocol',
'RunLocalUpdate' => 'Kør venligst zmupdate.pl for at opdatere',
'RunMode' => 'Driftsmåde',
'Running' => 'Kørende',
'RunState' => 'Drift Tilstand',
'Running' => 'Kørende',
'Save' => 'Gem',
'SaveAs' => 'Gem som',
'SaveFilter' => 'Gem Filter',
'SaveJPEGS' => 'Gem JPEGs',
'Save' => 'Gem',
'SaveJPEGs' => 'Save JPEGs', // Added - 2018-08-30
'Scale' => 'Skaler',
'Score' => 'Score',
'Secs' => 'Sek.',
'Sectionlength' => 'Sektions længde',
'SelectMonitors' => 'Vælg Monitorer',
'Select' => 'Vælg',
'SelectFormat' => 'Vælg Format',
'SelectLog' => 'Vælg Log',
'SelectMonitors' => 'Vælg Monitorer',
'SelfIntersecting' => 'Polygonens kanter må ikke krydses',
'Set' => 'Sæt',
'SetNewBandwidth' => 'Vælg ny båndbredde',
'SetPreset' => 'Set Preset',
'Set' => 'Sæt',
'Settings' => 'Indstillinger',
'ShowFilterWindow' => 'Vis Filter Vindue',
'ShowTimeline' => 'Vis Tidslinie',
'SignalCheckColour' => 'Signal Check Colour',
'SignalCheckPoints' => 'Signal Check Points', // Added - 2018-08-30
'Size' => 'Størrelse',
'SkinDescription' => 'SKift standard skin for denne computer',
'CSSDescription' => 'SKift standard css for denne computer',
'Sleep' => 'Sover',
'SortAsc' => 'Voksende',
'SortBy' => 'Sortér efter',
@ -652,46 +673,53 @@ $SLANG = array(
'SourceColours' => 'Kilde Farver',
'SourcePath' => 'Kilde Sti',
'SourceType' => 'Kilde Type',
'Speed' => 'Speed',
'SpeedHigh' => 'High Speed',
'SpeedLow' => 'Low Speed',
'SpeedMedium' => 'Medium Speed',
'Speed' => 'Speed',
'SpeedTurbo' => 'Turbo Speed',
'Start' => 'Start',
'State' => 'Tilstand',
'Stats' => 'Stats',
'Status' => 'Status',
'StatusConnected' => 'Capturing', // Added - 2018-08-30
'StatusNotRunning' => 'Not Running', // Added - 2018-08-30
'StatusRunning' => 'Not Capturing', // Added - 2018-08-30
'StatusUnknown' => 'Unknown', // Added - 2018-08-30
'Step' => 'Skridt',
'StepBack' => 'Skridt Tilbage',
'StepForward' => 'Skridt Frem',
'StepLarge' => 'Langt Skridt',
'StepMedium' => 'Medium Skridt',
'StepNone' => 'Ingen Skridt',
'StepSmall' => 'Lille Skridt',
'Step' => 'Skridt',
'Stills' => 'Stilbilleder',
'Stopped' => 'Stoppet',
'Stop' => 'Stop',
'StreamReplayBuffer' => 'Stream Replay Image Buffer',
'Stopped' => 'Stoppet',
'StorageArea' => 'Storage Area', // Added - 2018-08-30
'StorageScheme' => 'Scheme', // Added - 2018-08-30
'Stream' => 'Stream',
'StreamReplayBuffer' => 'Stream Replay Image Buffer',
'Submit' => 'Påtryk',
'System' => 'System',
'SystemLog' => 'System Log',
'TargetColorspace' => 'Target colorspace',
'Tele' => 'Tele',
'Thumbnail' => 'Thumbnail',
'Tilt' => 'Tilt',
'Time' => 'Tidspunkt',
'TimeDelta' => 'Tidsforskel',
'TimeStamp' => 'Tids stempel',
'Timeline' => 'Tidslinie',
'TimelineTip1' => 'Før musen over grafen for at vise snapshot billede og detaljer om hændelsen.', // Added 2013.08.15.
'TimelineTip2' => 'Klip på de farvede områder af grafen, eller billedet, for at se hændelsen.', // Added 2013.08.15.
'TimelineTip3' => 'Klik på baggrunden for at zoome ind på en mindre tidsperiode omkring dit klik.', // Added 2013.08.15.
'TimelineTip4' => 'Brug kontrollerne nedenfor for at zoome ud eller navigere frem eller tilbage i tiden.', // Added 2013.08.15.
'Timestamp' => 'Tidsstempel',
'TimestampLabelFormat' => 'Tidsstempel Mærkat Format',
'TimestampLabelSize' => 'Font Størrelse',
'TimestampLabelX' => 'Tidsstempel Mærkat X',
'TimestampLabelY' => 'Tidsstempel Mærkat Y',
'TimestampLabelSize' => 'Font Størrelse',
'Timestamp' => 'Tidsstempel',
'TimeStamp' => 'Tids stempel',
'Time' => 'Tidspunkt',
'Today' => 'Idag',
'Tools' => 'Værktøjer',
'Total' => 'Total',
@ -706,25 +734,29 @@ $SLANG = array(
'Undefined' => 'Udefineret',
'Units' => 'Enheder',
'Unknown' => 'Ukendt',
'Update' => 'Opdater',
'UpdateAvailable' => 'En opdatering til ZoneMinder er tilgængelig.',
'UpdateNotNecessary' => 'Ingen opdatering er nødvendig.',
'Update' => 'Opdater',
'Upload' => 'Upload',
'Updated' => 'Opdateret',
'UsedPlugins' => 'Anvendte Plugins',
'Upload' => 'Upload',
'UseFilter' => 'Anvend Filter',
'UseFilterExprsPost' => '&nbsp;filter&nbsp;udtryk', // This is used at the end of the phrase 'use N filter expressions'
'UseFilterExprsPre' => 'Anvend&nbsp;', // This is used at the beginning of the phrase 'use N filter expressions'
'UseFilter' => 'Anvend Filter',
'UsedPlugins' => 'Anvendte Plugins',
'User' => 'Bruger',
'Username' => 'Brugernavn',
'Users' => 'Brugere',
'User' => 'Bruger',
'V4L' => 'V4L',
'V4LCapturesPerFrame' => 'Captures Per Frame',
'V4LMultiBuffer' => 'Multi Buffering',
'Value' => 'Værdi',
'Version' => 'Version',
'VersionIgnore' => 'Ignorer denne værdi',
'VersionRemindDay' => 'Påmind igen om 1 dag',
'VersionRemindHour' => 'Påmind igen om 1 time',
'VersionRemindNever' => 'Påmind ikke om nye versioner',
'VersionRemindWeek' => 'Påmind igen om 1 uge',
'Version' => 'Version',
'Video' => 'Video',
'VideoFormat' => 'Video Format',
'VideoGenFailed' => 'Video Generering Fejlede!',
'VideoGenFiles' => 'Existerende Video Filer',
@ -733,47 +765,44 @@ $SLANG = array(
'VideoGenSucceeded' => 'Video Generering Succeeded!',
'VideoSize' => 'Video Størrelse',
'VideoWriter' => 'Video Skriver',
'Video' => 'Video',
'View' => 'Vis',
'ViewAll' => 'Vis Alle',
'ViewEvent' => 'Vis Hændelse',
'ViewPaged' => 'Vis Sidevis',
'View' => 'Vis',
'V4L' => 'V4L',
'V4LCapturesPerFrame' => 'Captures Per Frame',
'V4LMultiBuffer' => 'Multi Buffering',
'Wake' => 'Vågen',
'WarmupFrames' => 'Opvarmningsbilleder',
'Watch' => 'Ur',
'WebColour' => 'Web Farve',
'Web' => 'Web',
'WebColour' => 'Web Farve',
'WebSiteUrl' => 'Website URL', // Added - 2018-08-30
'Week' => 'Uge',
'WhiteBalance' => 'Hvidbalance',
'White' => 'Hvid',
'WhiteBalance' => 'Hvidbalance',
'Wide' => 'Bred',
'X' => 'X',
'X10' => 'X10',
'X10ActivationString' => 'X10 Activerings Streng',
'X10InputAlarmString' => 'X10 Input Alarm Streng',
'X10OutputAlarmString' => 'X10 Output Alarm Streng',
'X10' => 'X10',
'X' => 'X',
'Y' => 'Y',
'Yes' => 'Ja',
'YouNoPerms' => 'Du har ikke tilladelse til at tilgå denne ressurse.',
'Y' => 'Y',
'Zone' => 'Zone',
'ZoneAlarmColour' => 'Alarm Farve (Rød/Grøn/Blå)',
'ZoneArea' => 'Zone Område',
'ZoneExtendAlarmFrames' => 'Udvid Antal Alarm Rammer',
'ZoneFilterSize' => 'Filter Bredde/Højde (pixels)',
'ZoneMinderLog' => 'ZoneMinder Log',
'ZoneMinMaxAlarmArea' => 'Min/Max Alarmeret Område',
'ZoneMinMaxBlobArea' => 'Min/Max Blob Område',
'ZoneMinMaxBlobs' => 'Min/Max Blobs',
'ZoneMinMaxFiltArea' => 'Min/Max Filtreret Område',
'ZoneMinMaxPixelThres' => 'Min/Max Pixel Grænseværdi (0-255)',
'ZoneMinderLog' => 'ZoneMinder Log',
'ZoneOverloadFrames' => 'Antal Rammer At Ignorere Efter Overload',
'ZoneExtendAlarmFrames' => 'Udvid Antal Alarm Rammer',
'Zones' => 'Zoner',
'Zone' => 'Zone',
'Zoom' => 'Zoom',
'ZoomIn' => 'Zoom Ind',
'ZoomOut' => 'Zoom Ud',
'Zoom' => 'Zoom',
);
// Complex replacements with formatting and/or placements, must be passed through sprintf

View File

@ -601,6 +601,18 @@ $SLANG = array(
'Preset' => 'Preset',
'Presets' => 'Presets',
'Prev' => 'Prev',
'Privacy' => 'Privacy',
'PrivacyAbout' => 'About',
'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' => 'Contact',
'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' => 'Cookies',
'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 dont 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' => 'Probe',
'ProfileProbe' => 'Stream Probe',
'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/>',
@ -639,10 +651,13 @@ $SLANG = array(
'RotateLeft' => 'Rotate Left',
'RotateRight' => 'Rotate Right',
'RTSPTransport' => 'RTSP Transport Protocol',
'RunAudit' => 'Run Audit Process',
'RunLocalUpdate' => 'Please run zmupdate.pl to update',
'RunMode' => 'Run Mode',
'Running' => 'Running',
'RunState' => 'Run State',
'RunStats' => 'Run Stats Process',
'RunTrigger' => 'Run Trigger Process',
'SaveAs' => 'Save as',
'SaveFilter' => 'Save Filter',
'SaveJPEGs' => 'Save JPEGs',
@ -699,6 +714,7 @@ $SLANG = array(
'Stopped' => 'Stopped',
'Stop' => 'Stop',
'StorageArea' => 'Storage Area',
'StorageDoDelete' => 'Do Deletes',
'StorageScheme' => 'Scheme',
'StreamReplayBuffer' => 'Stream Replay Image Buffer',
'Stream' => 'Stream',

View File

@ -29,6 +29,8 @@ $SLANG = array(
'Actual' => 'Actual',
'AddNewControl' => 'Add New Control',
'AddNewMonitor' => 'Agregar Nuevo Monitor',
'AddNewServer' => 'Add New Server', // Added - 2018-08-30
'AddNewStorage' => 'Add New Storage', // Added - 2018-08-30
'AddNewUser' => 'Agregar Nuevo Usuario',
'AddNewZone' => 'Agregar Nueva Zona',
'Alarm' => 'Alarma',
@ -56,22 +58,32 @@ $SLANG = array(
'AttrArchiveStatus' => 'Estado Archivo',
'AttrAvgScore' => 'Puntaje Prom.',
'AttrCause' => 'Cause',
'AttrDate' => 'Fecha',
'AttrDateTime' => 'Fecha/Hora',
'AttrDiskBlocks' => 'Disk Blocks',
'AttrDiskPercent' => 'Disk Percent',
'AttrDiskSpace' => 'Disk Space', // Added - 2018-08-30
'AttrDuration' => 'Duración',
'AttrEndDate' => 'End Date', // Added - 2018-08-30
'AttrEndDateTime' => 'End Date/Time', // Added - 2018-08-30
'AttrEndTime' => 'End Time', // Added - 2018-08-30
'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30
'AttrFilterServer' => 'Server Filter is Running On', // Added - 2018-08-30
'AttrFrames' => 'Cuadros',
'AttrId' => 'Id',
'AttrMaxScore' => 'Puntaje Máximo',
'AttrMonitorId' => 'Monitor Id',
'AttrMonitorName' => 'Nombre Monitor',
'AttrMonitorServer' => 'Server Monitor is Running On', // Added - 2018-08-30
'AttrName' => 'Name',
'AttrNotes' => 'Notes',
'AttrStartDate' => 'Start Date', // Added - 2018-08-30
'AttrStartDateTime' => 'Start Date/Time', // Added - 2018-08-30
'AttrStartTime' => 'Start Time', // Added - 2018-08-30
'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30
'AttrStateId' => 'Run State', // Added - 2018-08-30
'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30
'AttrStorageServer' => 'Server Hosting Storage', // Added - 2018-08-30
'AttrSystemLoad' => 'System Load',
'AttrTime' => 'Hora',
'AttrTotalScore' => 'Puntaje Total',
'AttrWeekday' => 'Día Semana',
'Auto' => 'Auto',
'AutoStopTimeout' => 'Auto Stop Timeout',
'Available' => 'Available', // Added - 2009-03-31
@ -104,9 +116,11 @@ $SLANG = array(
'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',
'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"', // Added - 2018-08-30
'BadStreamReplayBuffer'=> 'Stream replay buffer must be an integer of zero or more',
'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.', // Added - 2018-08-30
'BadWidth' => 'Width must be set to a valid value',
'Bandwidth' => 'Velocidad',
'BandwidthHead' => 'Bandwidth', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing
@ -170,10 +184,12 @@ $SLANG = array(
'ChooseLogSelection' => 'Choose a log selection', // Added - 2011-06-17
'ChoosePreset' => 'Choose Preset',
'Clear' => 'Clear', // Added - 2011-06-16
'CloneMonitor' => 'Clone', // Added - 2018-08-30
'Close' => 'Cerrar',
'Colour' => 'Color',
'Command' => 'Command',
'Component' => 'Component', // Added - 2011-06-16
'ConcurrentFilter' => 'Run filter concurrently', // Added - 2018-08-30
'Config' => 'Config.',
'ConfiguredFor' => 'Configurado Para',
'ConfirmDeleteEvents' => 'Are you sure you wish to delete the selected events?',
@ -231,9 +247,11 @@ $SLANG = array(
'DonateRemindWeek' => 'Not yet, remind again in 1 week',
'DonateYes' => 'Yes, I\'d like to donate now',
'Download' => 'Download',
'DownloadVideo' => 'Download Video', // Added - 2018-08-30
'DuplicateMonitorName' => 'Duplicate Monitor Name', // Added - 2009-03-31
'Duration' => 'Duración',
'Edit' => 'Editar',
'EditLayout' => 'Edit Layout', // Added - 2018-08-30
'Email' => 'Email',
'EnableAlarms' => 'Enable Alarms',
'Enabled' => 'Habilitado',
@ -250,6 +268,7 @@ $SLANG = array(
'Events' => 'Eventos',
'Exclude' => 'Excluir',
'Execute' => 'Execute',
'Exif' => 'Embed EXIF data into image', // Added - 2018-08-30
'Export' => 'Export',
'ExportDetails' => 'Export Event Details',
'ExportFailed' => 'Export Failed',
@ -279,8 +298,10 @@ $SLANG = array(
'FilterExecuteEvents' => 'Ejecutar un comando en las coincidencias',
'FilterLog' => 'Filter log', // Added - 2015-04-18
'FilterMessageEvents' => 'Mandar un mensaje de los eventos',
'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
'FilterPx' => 'Filtro Px',
'FilterUnset' => 'You must specify a filter width and height',
'FilterUpdateDiskSpace'=> 'Update used disk space', // Added - 2018-08-30
'FilterUploadEvents' => 'Subir los eventos que coincidan',
'FilterVideoEvents' => 'Create video for all matches',
'Filters' => 'Filters',
@ -305,6 +326,7 @@ $SLANG = array(
'Function' => 'Función',
'Gain' => 'Gain',
'General' => 'General',
'GenerateDownload' => 'Generate Download', // Added - 2018-08-30
'GenerateVideo' => 'Crear Video',
'GeneratingVideo' => 'Creando Video',
'GoToZoneMinder' => 'Ir a Zoneminder.com',
@ -325,6 +347,7 @@ $SLANG = array(
'High' => 'Alta',
'HighBW' => 'Alta&nbsp;B/W',
'Home' => 'Home',
'Hostname' => 'Hostname', // Added - 2018-08-30
'Hour' => 'Hora',
'Hue' => 'Saturación',
'Id' => 'Id',
@ -349,6 +372,7 @@ $SLANG = array(
'Line' => 'Line', // Added - 2011-06-16
'LinkedMonitors' => 'Linked Monitors',
'List' => 'List',
'ListMatches' => 'List Matches', // Added - 2018-08-30
'Load' => 'Carga',
'Local' => 'Local',
'Log' => 'Log', // Added - 2011-06-16
@ -435,6 +459,7 @@ $SLANG = array(
'MonitorProbeIntro' => 'The list below shows detected analog and network 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/>', // Added - 2009-03-31
'Monitors' => 'Monitores',
'Montage' => 'Cámara Múltiple',
'MontageReview' => 'Montage Review', // Added - 2018-08-30
'Month' => 'Mes',
'More' => 'More', // Added - 2011-06-16
'MotionFrameSkip' => 'Motion Frame Skip',
@ -461,6 +486,7 @@ $SLANG = array(
'Next' => 'Siguiente',
'No' => 'No',
'NoDetectedCameras' => 'No Detected Cameras', // Added - 2009-03-31
'NoDetectedProfiles' => 'No Detected Profiles', // Added - 2018-08-30
'NoFramesRecorded' => 'No hay movimientos grabados para este evento',
'NoGroup' => 'No Group',
'NoSavedFilters' => 'FiltrosNoGuardados',
@ -479,6 +505,8 @@ $SLANG = array(
'OpGt' => 'mayor que',
'OpGtEq' => 'mayor o igual que',
'OpIn' => 'En sistema',
'OpIs' => 'is', // Added - 2018-08-30
'OpIsNot' => 'is not', // Added - 2018-08-30
'OpLt' => 'menor que',
'OpLtEq' => 'menor o igual que',
'OpMatches' => 'Coincide',
@ -488,6 +516,7 @@ $SLANG = array(
'Open' => 'Open',
'OptionHelp' => 'Ayuda',
'OptionRestartWarning' => 'Estos cambios no se guardaran completamente\nmientras el sistema se ejecute. Cuando termine\nde realizar los cambios asegurese de\nreiniciar Zoneminder.',
'OptionalEncoderParam' => 'Optional Encoder Parameters', // Added - 2018-08-30
'Options' => 'Opciones',
'OrEnterNewName' => 'o agregue nombre',
'Order' => 'Order',
@ -525,9 +554,13 @@ $SLANG = array(
'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/>', // Added - 2015-04-18
'Progress' => 'Progress', // Added - 2015-04-18
'Protocol' => 'Protocol',
'RTSPDescribe' => 'Use RTSP Response Media URL', // Added - 2018-08-30
'RTSPTransport' => 'RTSP Transport Protocol', // Added - 2018-08-30
'Rate' => 'Ritmo',
'Real' => 'Real',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // Added - 2018-08-30
'Record' => 'Registro',
'RecordAudio' => 'Whether to store the audio stream when saving an event.', // Added - 2018-08-30
'RefImageBlendPct' => 'Reference Image Blend %ge',
'Refresh' => 'Actualizar',
'Remote' => 'Remote',
@ -543,6 +576,7 @@ $SLANG = array(
'ReplayAll' => 'All Events',
'ReplayGapless' => 'Gapless Events',
'ReplaySingle' => 'Single Event',
'ReportEventAudit' => 'Audit Events Report', // Added - 2018-08-30
'Reset' => 'Reset',
'ResetEventCounts' => 'Borrar Contador Eventos',
'Restart' => 'Reiniciar',
@ -561,6 +595,7 @@ $SLANG = array(
'Save' => 'Guardar',
'SaveAs' => 'Guardar Como',
'SaveFilter' => 'Guardar Filtro',
'SaveJPEGs' => 'Save JPEGs', // Added - 2018-08-30
'Scale' => 'Escala',
'Score' => 'Res.',
'Secs' => 'Seg',
@ -577,6 +612,7 @@ $SLANG = array(
'ShowFilterWindow' => 'Abrir Filtro',
'ShowTimeline' => 'Show Timeline',
'SignalCheckColour' => 'Signal Check Colour',
'SignalCheckPoints' => 'Signal Check Points', // Added - 2018-08-30
'Size' => 'Size',
'SkinDescription' => 'Change the default skin for this computer', // Added - 2011-01-30
'Sleep' => 'Sleep',
@ -596,6 +632,10 @@ $SLANG = array(
'State' => 'Estado',
'Stats' => 'Est.',
'Status' => 'Estado',
'StatusConnected' => 'Capturing', // Added - 2018-08-30
'StatusNotRunning' => 'Not Running', // Added - 2018-08-30
'StatusRunning' => 'Not Capturing', // Added - 2018-08-30
'StatusUnknown' => 'Unknown', // Added - 2018-08-30
'Step' => 'Step',
'StepBack' => 'Step Back',
'StepForward' => 'Step Forward',
@ -606,6 +646,8 @@ $SLANG = array(
'Stills' => 'Fotos',
'Stop' => 'Desactivar',
'Stopped' => 'Apagado',
'StorageArea' => 'Storage Area', // Added - 2018-08-30
'StorageScheme' => 'Scheme', // Added - 2018-08-30
'Stream' => 'Stream',
'StreamReplayBuffer' => 'Stream Replay Image Buffer',
'Submit' => 'Submit',
@ -625,6 +667,7 @@ $SLANG = array(
'TimelineTip4' => 'Use the controls below to zoom out or navigate back and forward through the time range.', // Added 2013.08.15.
'Timestamp' => 'Etiqueta Hora',
'TimestampLabelFormat' => 'Formato Etiqueta Hora',
'TimestampLabelSize' => 'Font Size', // Added - 2018-08-30
'TimestampLabelX' => 'Eje X Etiqueta Hora',
'TimestampLabelY' => 'Eje Y Etiqueta Hora',
'Today' => 'Today',
@ -671,6 +714,7 @@ $SLANG = array(
'VideoGenParms' => 'Parametros Generacion Video',
'VideoGenSucceeded' => 'Video Generation Succeeded!',
'VideoSize' => 'Tamaño Video',
'VideoWriter' => 'Video Writer', // Added - 2018-08-30
'View' => 'Ver',
'ViewAll' => 'Ver Todo',
'ViewEvent' => 'View Event',
@ -680,6 +724,7 @@ $SLANG = array(
'Watch' => 'Monitor',
'Web' => 'Web',
'WebColour' => 'Web Colour',
'WebSiteUrl' => 'Website URL', // Added - 2018-08-30
'Week' => 'Semana',
'White' => 'White',
'WhiteBalance' => 'White Balance',

View File

@ -78,6 +78,8 @@ $SLANG = array(
'Actual' => 'Actual',
'AddNewControl' => 'Añadir nuevo control',
'AddNewMonitor' => 'Añadir nuevo monitor',
'AddNewServer' => 'Add New Server', // Added - 2018-08-30
'AddNewStorage' => 'Add New Storage', // Added - 2018-08-30
'AddNewUser' => 'Añadir nuevo usuario',
'AddNewZone' => 'Añadir nueva zona',
'Alarm' => 'Alarma',
@ -105,22 +107,32 @@ $SLANG = array(
'AttrArchiveStatus' => 'Estado de archivo',
'AttrAvgScore' => 'Promed. señal',
'AttrCause' => 'Causa',
'AttrDate' => 'Fecha',
'AttrDateTime' => 'Fecha/Hora',
'AttrDiskBlocks' => 'Bloques del disco',
'AttrDiskPercent' => 'Porcentaje del disco',
'AttrDiskSpace' => 'Disk Space', // Added - 2018-08-30
'AttrDuration' => 'Duración',
'AttrEndDate' => 'End Date', // Added - 2018-08-30
'AttrEndDateTime' => 'End Date/Time', // Added - 2018-08-30
'AttrEndTime' => 'End Time', // Added - 2018-08-30
'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30
'AttrFilterServer' => 'Server Filter is Running On', // Added - 2018-08-30
'AttrFrames' => 'Marcos',
'AttrId' => 'Id',
'AttrMaxScore' => 'Señal máxima',
'AttrMonitorId' => 'Id monitor',
'AttrMonitorName' => 'Nombre del monitor',
'AttrMonitorServer' => 'Server Monitor is Running On', // Added - 2018-08-30
'AttrName' => 'Nombre',
'AttrNotes' => 'Notas',
'AttrStartDate' => 'Start Date', // Added - 2018-08-30
'AttrStartDateTime' => 'Start Date/Time', // Added - 2018-08-30
'AttrStartTime' => 'Start Time', // Added - 2018-08-30
'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30
'AttrStateId' => 'Run State', // Added - 2018-08-30
'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30
'AttrStorageServer' => 'Server Hosting Storage', // Added - 2018-08-30
'AttrSystemLoad' => 'Carga del sistema',
'AttrTime' => 'Hora',
'AttrTotalScore' => 'Señal total',
'AttrWeekday' => 'Día de la semana',
'Auto' => 'Auto',
'AutoStopTimeout' => 'Autodetener tiempo de espera',
'Available' => 'Disponible',
@ -153,9 +165,11 @@ $SLANG = array(
'BadRefBlendPerc' => 'El porcentaje de la referencia de mezcla debe ser un entero positivo',
'BadSectionLength' => 'La duración de la sección debe ser un entero de 30 o más',
'BadSignalCheckColour' => 'El color de verificación de señal debe ser una cadena de color RGB válida',
'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"', // Added - 2018-08-30
'BadStreamReplayBuffer' => 'La secuencia de búfer de reproducción debe ser un entero de cero o más',
'BadWarmupCount' => 'Los marcos de calentamiento deben ser un entero de cero o más',
'BadWebColour' => 'El color web debe ser una cadena de color web válida',
'BadWebSitePath' => 'Please enter a complete website url, including the http:// or https:// prefix.', // Added - 2018-08-30
'BadWidth' => 'El ancho debe tener un valor válido',
'Bandwidth' => 'Ancho de banda',
'BandwidthHead' => 'Bandwidth', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing
@ -219,10 +233,12 @@ $SLANG = array(
'ChooseLogSelection' => 'Elegir selección de registro',
'ChoosePreset' => 'Elegir preprogramación',
'Clear' => 'Limpiar',
'CloneMonitor' => 'Clone', // Added - 2018-08-30
'Close' => 'Cerrar',
'Colour' => 'Color',
'Command' => 'Comando',
'Component' => 'Componente',
'ConcurrentFilter' => 'Run filter concurrently', // Added - 2018-08-30
'Config' => 'Config',
'ConfiguredFor' => 'Configurado para',
'ConfirmDeleteEvents' => '¿Seguro que desea borrar los eventos seleccionados?',
@ -280,9 +296,11 @@ $SLANG = array(
'DonateRemindWeek' => 'Aún no, recordarme de nuevo en 1 semana',
'DonateYes' => 'Sí, me gustaría hacer una donación ahora',
'Download' => 'Descargar',
'DownloadVideo' => 'Download Video', // Added - 2018-08-30
'DuplicateMonitorName' => 'Duplicar nombre de monitor',
'Duration' => 'Duración',
'Edit' => 'Editar',
'EditLayout' => 'Edit Layout', // Added - 2018-08-30
'Email' => 'Email',
'EnableAlarms' => 'Habilitar alarmas',
'Enabled' => 'Habilitado',
@ -299,6 +317,7 @@ $SLANG = array(
'Events' => 'Eventos',
'Exclude' => 'Excluir',
'Execute' => 'Ejecutar',
'Exif' => 'Embed EXIF data into image', // Added - 2018-08-30
'Export' => 'Exportar',
'ExportDetails' => 'Exportar detalles de evento',
'ExportFailed' => 'Fallo al exportar',
@ -328,8 +347,10 @@ $SLANG = array(
'FilterExecuteEvents' => 'Ejecutar comando para todas las coincidencias',
'FilterLog' => 'Filter log', // Added - 2015-04-18
'FilterMessageEvents' => 'Detalles de mensaje de todas las coincidencias',
'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
'FilterPx' => 'Filtrar Px',
'FilterUnset' => 'Debe especificar un ancho y un alto para el filtro',
'FilterUpdateDiskSpace'=> 'Update used disk space', // Added - 2018-08-30
'FilterUploadEvents' => 'Subir todas las coincidencias',
'FilterVideoEvents' => 'Create video for all matches', // Added - 2011-08-23
'Filters' => 'Filtros',
@ -354,6 +375,7 @@ $SLANG = array(
'Function' => 'Función',
'Gain' => 'Ganancia',
'General' => 'General',
'GenerateDownload' => 'Generate Download', // Added - 2018-08-30
'GenerateVideo' => 'Generate Video', // Added - 2011-08-23
'GeneratingVideo' => 'Generating Video', // Added - 2011-08-23
'GoToZoneMinder' => 'Ir a ZoneMinder.com',
@ -374,6 +396,7 @@ $SLANG = array(
'High' => 'Alto',
'HighBW' => 'Alto&nbsp;B/B',
'Home' => 'Inicio',
'Hostname' => 'Hostname', // Added - 2018-08-30
'Hour' => 'Hora',
'Hue' => 'Matiz',
'Id' => 'Id',
@ -398,6 +421,7 @@ $SLANG = array(
'Line' => 'Línea',
'LinkedMonitors' => 'Monitores enlazados',
'List' => 'Lista',
'ListMatches' => 'List Matches', // Added - 2018-08-30
'Load' => 'Carga',
'Local' => 'Local',
'Log' => 'Registro',
@ -484,6 +508,7 @@ $SLANG = array(
'MonitorProbeIntro' => 'La lista de debajo muestra las cámaras analógicas y en red detectadas y si ya están siendo usadas o están disponibles para seleccionar.<br/><br/>Seleccione la entrada deseada de la lista de debajo.<br/><br/>Por favor tenga en cuenta que podrían no detectarse todas las cámaras y que elegir una cámara aquí podría sobrescribir cualquier valor que ya hubiera configurado para el monitor actual.<br/><br/>',
'Monitors' => 'Monitores',
'Montage' => 'Montaje',
'MontageReview' => 'Montage Review', // Added - 2018-08-30
'Month' => 'Mes',
'More' => 'Más',
'MotionFrameSkip' => 'Motion Frame Skip',
@ -510,6 +535,7 @@ $SLANG = array(
'Next' => 'Siguiente',
'No' => 'No',
'NoDetectedCameras' => 'No se detectaron cámaras',
'NoDetectedProfiles' => 'No Detected Profiles', // Added - 2018-08-30
'NoFramesRecorded' => 'No hay marcos grabados para este evento',
'NoGroup' => 'Sin grupo',
'NoSavedFilters' => 'No hay filtros guardados',
@ -528,6 +554,8 @@ $SLANG = array(
'OpGt' => 'mayor que',
'OpGtEq' => 'mayor que o igual a',
'OpIn' => 'en conjunto',
'OpIs' => 'is', // Added - 2018-08-30
'OpIsNot' => 'is not', // Added - 2018-08-30
'OpLt' => 'menor que',
'OpLtEq' => 'menor que o igual a',
'OpMatches' => 'coincidencias',
@ -537,6 +565,7 @@ $SLANG = array(
'Open' => 'Abrir',
'OptionHelp' => 'Ayuda de la opción',
'OptionRestartWarning' => 'Estos cambios podrían no surtir un efecto completo mientras el sistema esté ejecutándose. Cuando haya terminado haciendo cambios por favor asegúrese de reiniciar ZoneMinder.',
'OptionalEncoderParam' => 'Optional Encoder Parameters', // Added - 2018-08-30
'Options' => 'Opciones',
'OrEnterNewName' => 'o introduzca un nuevo nombre',
'Order' => 'Orden',
@ -574,9 +603,13 @@ $SLANG = array(
'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/>', // Added - 2015-04-18
'Progress' => 'Progress', // Added - 2015-04-18
'Protocol' => 'Protocolo',
'RTSPDescribe' => 'Use RTSP Response Media URL', // Added - 2018-08-30
'RTSPTransport' => 'RTSP Transport Protocol', // Added - 2018-08-30
'Rate' => 'Valorar',
'Real' => 'Real',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // Added - 2018-08-30
'Record' => 'Grabar',
'RecordAudio' => 'Whether to store the audio stream when saving an event.', // Added - 2018-08-30
'RefImageBlendPct' => 'Referencia de mezcla de imagen %ge',
'Refresh' => 'Refrescar',
'Remote' => 'Remoto',
@ -592,6 +625,7 @@ $SLANG = array(
'ReplayAll' => 'Todos los eventos',
'ReplayGapless' => 'Eventos sin espacios',
'ReplaySingle' => 'Evento individual',
'ReportEventAudit' => 'Audit Events Report', // Added - 2018-08-30
'Reset' => 'Restablecer',
'ResetEventCounts' => 'Restablecer número de eventos',
'Restart' => 'Reiniciar',
@ -610,6 +644,7 @@ $SLANG = array(
'Save' => 'Guardar',
'SaveAs' => 'Guardar cómo',
'SaveFilter' => 'Guardar filtro',
'SaveJPEGs' => 'Save JPEGs', // Added - 2018-08-30
'Scale' => 'Escalar',
'Score' => 'Cuenta',
'Secs' => 'Segs',
@ -626,6 +661,7 @@ $SLANG = array(
'ShowFilterWindow' => 'Mostrar ventana de filtros',
'ShowTimeline' => 'Mostrar línea de tiempo',
'SignalCheckColour' => 'Color de comprobación de señal',
'SignalCheckPoints' => 'Signal Check Points', // Added - 2018-08-30
'Size' => 'Tamaño',
'SkinDescription' => 'Cambiar el tema por defecto para este ordenador',
'Sleep' => 'Dormir',
@ -645,6 +681,10 @@ $SLANG = array(
'State' => 'Estado',
'Stats' => 'Estadísticas',
'Status' => 'Estado',
'StatusConnected' => 'Capturing', // Added - 2018-08-30
'StatusNotRunning' => 'Not Running', // Added - 2018-08-30
'StatusRunning' => 'Not Capturing', // Added - 2018-08-30
'StatusUnknown' => 'Unknown', // Added - 2018-08-30
'Step' => 'Salto',
'StepBack' => 'Salto atrás',
'StepForward' => 'Salto adelante',
@ -655,6 +695,8 @@ $SLANG = array(
'Stills' => 'Fijas',
'Stop' => 'Detener',
'Stopped' => 'Detenido',
'StorageArea' => 'Storage Area', // Added - 2018-08-30
'StorageScheme' => 'Scheme', // Added - 2018-08-30
'Stream' => 'Corriente',
'StreamReplayBuffer' => 'Secuencia de búfer de reproducción',
'Submit' => 'Enviar',
@ -674,9 +716,9 @@ $SLANG = array(
'TimelineTip4' => 'Use the controls below to zoom out or navigate back and forward through the time range.', // Added 2013.08.15.
'Timestamp' => 'Marca de tiempo',
'TimestampLabelFormat' => 'Formato de hora multinacional',
'TimestampLabelSize' => 'Tamaño de fuente',
'TimestampLabelX' => 'Etiqueta de tiempo X',
'TimestampLabelY' => 'Etiqueta de tiempo Y',
'TimestampLabelSize' => 'Tamaño de fuente',
'Today' => 'Hoy',
'Tools' => 'Herramientas',
'Total' => 'Total',
@ -721,6 +763,7 @@ $SLANG = array(
'VideoGenParms' => 'Video Generation Parameters', // Added - 2011-08-23
'VideoGenSucceeded' => 'Video Generation Succeeded!', // Added - 2011-08-23
'VideoSize' => 'Video Size', // Added - 2011-08-23
'VideoWriter' => 'Video Writer', // Added - 2018-08-30
'View' => 'Ver',
'ViewAll' => 'Ver todos',
'ViewEvent' => 'Ver evento',
@ -730,6 +773,7 @@ $SLANG = array(
'Watch' => 'Observar',
'Web' => 'Web',
'WebColour' => 'Color web',
'WebSiteUrl' => 'Website URL', // Added - 2018-08-30
'Week' => 'Semana',
'White' => 'Blanco',
'WhiteBalance' => 'Balance de blancos',

View File

@ -85,6 +85,8 @@ $SLANG = array(
'Actual' => 'Aktuaalne',
'AddNewControl' => 'Lisa uus Kontroll',
'AddNewMonitor' => 'Lisa uus Monitor',
'AddNewServer' => 'Add New Server', // Added - 2018-08-30
'AddNewStorage' => 'Add New Storage', // Added - 2018-08-30
'AddNewUser' => 'Lisa uus Kasutaja',
'AddNewZone' => 'Lisa uus Tsoon',
'Alarm' => 'Alarm',
@ -112,22 +114,32 @@ $SLANG = array(
'AttrArchiveStatus' => 'Arhiivi Staatus',
'AttrAvgScore' => 'Keskm. Skoor',
'AttrCause' => 'Põhjus',
'AttrDate' => 'Kp.',
'AttrDateTime' => 'Kp/Kellaaeg',
'AttrDiskBlocks' => 'Ketta Blokk',
'AttrDiskPercent' => 'Ketta Protsent',
'AttrDiskSpace' => 'Disk Space', // Added - 2018-08-30
'AttrDuration' => 'Kestvus',
'AttrEndDate' => 'End Date', // Added - 2018-08-30
'AttrEndDateTime' => 'End Date/Time', // Added - 2018-08-30
'AttrEndTime' => 'End Time', // Added - 2018-08-30
'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30
'AttrFilterServer' => 'Server Filter is Running On', // Added - 2018-08-30
'AttrFrames' => 'Kaadrid',
'AttrId' => 'Id',
'AttrMaxScore' => 'Maks. Skoor',
'AttrMonitorId' => 'Monitori Id',
'AttrMonitorName' => 'Monitori Nimi',
'AttrMonitorServer' => 'Server Monitor is Running On', // Added - 2018-08-30
'AttrName' => 'Nimi',
'AttrNotes' => 'Märkmed',
'AttrStartDate' => 'Start Date', // Added - 2018-08-30
'AttrStartDateTime' => 'Start Date/Time', // Added - 2018-08-30
'AttrStartTime' => 'Start Time', // Added - 2018-08-30
'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30
'AttrStateId' => 'Run State', // Added - 2018-08-30
'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30
'AttrStorageServer' => 'Server Hosting Storage', // Added - 2018-08-30
'AttrSystemLoad' => 'Süsteemi Koormus',
'AttrTime' => 'Kellaaeg',
'AttrTotalScore' => 'Skoor Kokku',
'AttrWeekday' => 'Tööpäevad',
'Auto' => 'Auto',
'AutoStopTimeout' => 'Auto Stop Ajalimiit',
'Available' => 'Saadaval',
@ -160,9 +172,11 @@ $SLANG = array(
'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',
'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"', // Added - 2018-08-30
'BadStreamReplayBuffer' => 'Stream replay buffer must be an integer of zero or more',
'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.', // Added - 2018-08-30
'BadWidth' => 'Width must be set to a valid value',
'Bandwidth' => 'Ribalaius',
'BandwidthHead' => 'Ribalaius', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing
@ -226,10 +240,12 @@ $SLANG = array(
'ChooseLogSelection' => 'Choose a log selection', // Added - 2011-06-17
'ChoosePreset' => 'Choose Preset',
'Clear' => 'Clear', // Added - 2011-06-16
'CloneMonitor' => 'Clone', // Added - 2018-08-30
'Close' => 'Sule',
'Colour' => 'Värv',
'Command' => 'Käsk',
'Component' => 'Komponent', // Added - 2011-06-16
'ConcurrentFilter' => 'Run filter concurrently', // Added - 2018-08-30
'Config' => 'Seadistus',
'ConfiguredFor' => 'Seadistatud',
'ConfirmDeleteEvents' => 'Oled sa kindel kustamaks valitud sündmused?',
@ -287,9 +303,11 @@ $SLANG = array(
'DonateRemindWeek' => 'EI veel, tuleta meelde nädala pärast',
'DonateYes' => 'Jah, Ma soovin annetada',
'Download' => 'Lae alla',
'DownloadVideo' => 'Download Video', // Added - 2018-08-30
'DuplicateMonitorName' => 'Dubleeri Monitori Nimi',
'Duration' => 'Kestvus',
'Edit' => 'Muuda',
'EditLayout' => 'Edit Layout', // Added - 2018-08-30
'Email' => 'Email',
'EnableAlarms' => 'Luba Alarmid',
'Enabled' => 'Lubatud',
@ -306,6 +324,7 @@ $SLANG = array(
'Events' => 'Sündmuseid',
'Exclude' => 'Jäta välja',
'Execute' => 'Käivita',
'Exif' => 'Embed EXIF data into image', // Added - 2018-08-30
'Export' => 'Eksport',
'ExportDetails' => 'Ekspordi Sündmuste Detailid',
'ExportFailed' => 'Eksportimine Ebaõnnestus',
@ -335,8 +354,10 @@ $SLANG = array(
'FilterExecuteEvents' => 'Execute command on all matches',
'FilterLog' => 'Filter log', // Added - 2015-04-18
'FilterMessageEvents' => 'Message details of all matches',
'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
'FilterPx' => 'Filter Px',
'FilterUnset' => 'You must specify a filter width and height',
'FilterUpdateDiskSpace'=> 'Update used disk space', // Added - 2018-08-30
'FilterUploadEvents' => 'Upload all matches',
'FilterVideoEvents' => 'Create video for all matches',
'Filters' => 'Filtrid',
@ -361,6 +382,7 @@ $SLANG = array(
'Function' => 'Funktsioon',
'Gain' => 'Gain',
'General' => 'Peamine',
'GenerateDownload' => 'Generate Download', // Added - 2018-08-30
'GenerateVideo' => 'Genereeri Video',
'GeneratingVideo' => 'Genereerin Videot',
'GoToZoneMinder' => 'Mine ZoneMinder.com',
@ -381,6 +403,7 @@ $SLANG = array(
'High' => 'Suurim',
'HighBW' => 'High&nbsp;B/W',
'Home' => 'Koju',
'Hostname' => 'Hostname', // Added - 2018-08-30
'Hour' => 'Tunnis',
'Hue' => 'Hue',
'Id' => 'Id',
@ -405,6 +428,7 @@ $SLANG = array(
'Line' => 'Line', // Added - 2011-06-16
'LinkedMonitors' => 'Lingitud monitorid',
'List' => 'List',
'ListMatches' => 'List Matches', // Added - 2018-08-30
'Load' => 'Koormus',
'Local' => 'Local',
'Log' => 'Logi', // Added - 2011-06-16
@ -491,6 +515,7 @@ $SLANG = array(
'MonitorProbeIntro' => 'The list below shows detected analog and network 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/>',
'Monitors' => 'Monitors',
'Montage' => 'Montage',
'MontageReview' => 'Montage Review', // Added - 2018-08-30
'Month' => 'Kuus',
'More' => 'Veel', // Added - 2011-06-16
'MotionFrameSkip' => 'Motion Frame Skip',
@ -517,6 +542,7 @@ $SLANG = array(
'Next' => 'Järgmine',
'No' => 'Ei',
'NoDetectedCameras' => 'Ei leidnud kaameraid',
'NoDetectedProfiles' => 'No Detected Profiles', // Added - 2018-08-30
'NoFramesRecorded' => 'Ei ole kaadreid salvetatud selles sündmuses',
'NoGroup' => 'Ei krupp',
'NoSavedFilters' => 'EiSalvestatudFiltreid',
@ -535,6 +561,8 @@ $SLANG = array(
'OpGt' => 'Suurem kui',
'OpGtEq' => 'suurem kui või võrdne',
'OpIn' => 'in set',
'OpIs' => 'is', // Added - 2018-08-30
'OpIsNot' => 'is not', // Added - 2018-08-30
'OpLt' => 'vähem kui',
'OpLtEq' => 'vähem kui või võrdne',
'OpMatches' => 'klapib',
@ -544,6 +572,7 @@ $SLANG = array(
'Open' => 'Ava',
'OptionHelp' => 'Valik Aita',
'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.',
'OptionalEncoderParam' => 'Optional Encoder Parameters', // Added - 2018-08-30
'Options' => 'Seaded',
'OrEnterNewName' => 'või sisesta uus nimi',
'Order' => 'Järjekord',
@ -581,9 +610,13 @@ $SLANG = array(
'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/>', // Added - 2015-04-18
'Progress' => 'Progress', // Added - 2015-04-18
'Protocol' => 'Protocol',
'RTSPDescribe' => 'Use RTSP Response Media URL', // Added - 2018-08-30
'RTSPTransport' => 'RTSP Transport Protocol', // Added - 2018-08-30
'Rate' => 'Rate',
'Real' => 'Reaaalne',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // Added - 2018-08-30
'Record' => 'Salvesta',
'RecordAudio' => 'Whether to store the audio stream when saving an event.', // Added - 2018-08-30
'RefImageBlendPct' => 'Reference Image Blend %ge',
'Refresh' => 'Värskenda',
'Remote' => 'Remote',
@ -599,6 +632,7 @@ $SLANG = array(
'ReplayAll' => 'Kõik sündmused',
'ReplayGapless' => 'Lünkadeta sündmused',
'ReplaySingle' => 'Üksik sündmus',
'ReportEventAudit' => 'Audit Events Report', // Added - 2018-08-30
'Reset' => 'Reset',
'ResetEventCounts' => 'Reset Event Counts',
'Restart' => 'Taaskäivita',
@ -617,6 +651,7 @@ $SLANG = array(
'Save' => 'Salvesta',
'SaveAs' => 'Salvesta kui',
'SaveFilter' => 'Salvesta Filter',
'SaveJPEGs' => 'Save JPEGs', // Added - 2018-08-30
'Scale' => 'Skaala',
'Score' => 'Skoor',
'Secs' => 'Secs',
@ -633,6 +668,7 @@ $SLANG = array(
'ShowFilterWindow' => 'Näita Filtri Akent',
'ShowTimeline' => 'Näita Timeline',
'SignalCheckColour' => 'Signaali Kontroll Värv',
'SignalCheckPoints' => 'Signal Check Points', // Added - 2018-08-30
'Size' => 'Suurus',
'SkinDescription' => 'Vaheta veebilehe välimus selles arvutis', // Added - 2011-03-02
'Sleep' => 'Maga',
@ -652,6 +688,10 @@ $SLANG = array(
'State' => 'Olek',
'Stats' => 'Statistika',
'Status' => 'Staatus',
'StatusConnected' => 'Capturing', // Added - 2018-08-30
'StatusNotRunning' => 'Not Running', // Added - 2018-08-30
'StatusRunning' => 'Not Capturing', // Added - 2018-08-30
'StatusUnknown' => 'Unknown', // Added - 2018-08-30
'Step' => 'Samm',
'StepBack' => 'Samm tagasi',
'StepForward' => 'Samm edasi',
@ -662,6 +702,8 @@ $SLANG = array(
'Stills' => 'Stills',
'Stop' => 'Stop',
'Stopped' => 'Stopitud',
'StorageArea' => 'Storage Area', // Added - 2018-08-30
'StorageScheme' => 'Scheme', // Added - 2018-08-30
'Stream' => 'Striim',
'StreamReplayBuffer' => 'Striimi Replay Pildi Puhver',
'Submit' => 'Submit',
@ -681,6 +723,7 @@ $SLANG = array(
'TimelineTip4' => 'Use the controls below to zoom out or navigate back and forward through the time range.', // Added 2013.08.15.
'Timestamp' => 'Timestamp',
'TimestampLabelFormat' => 'Timestamp Label Format',
'TimestampLabelSize' => 'Font Size', // Added - 2018-08-30
'TimestampLabelX' => 'Timestamp Label X',
'TimestampLabelY' => 'Timestamp Label Y',
'Today' => 'Täna',
@ -727,6 +770,7 @@ $SLANG = array(
'VideoGenParms' => 'Video Genereerimise Parameetrid',
'VideoGenSucceeded' => 'Video Genereerimine Õnnestus!!!',
'VideoSize' => 'Video Suurus',
'VideoWriter' => 'Video Writer', // Added - 2018-08-30
'View' => 'Vaata',
'ViewAll' => 'View All',
'ViewEvent' => 'Vaata Sündmust',
@ -736,6 +780,7 @@ $SLANG = array(
'Watch' => 'Vaata',
'Web' => 'Veeb',
'WebColour' => 'Veebi värv',
'WebSiteUrl' => 'Website URL', // Added - 2018-08-30
'Week' => 'Nädalas',
'White' => 'White',
'WhiteBalance' => 'White Balance',

View File

@ -84,6 +84,8 @@ $SLANG = array(
'Actual' => 'Réel',
'AddNewControl' => 'Ajouter contrôle',
'AddNewMonitor' => 'Ajouter caméra',
'AddNewServer' => 'Add New Server', // Added - 2018-08-30
'AddNewStorage' => 'Add New Storage', // Added - 2018-08-30
'AddNewUser' => 'Ajouter utilisateur',
'AddNewZone' => 'Ajouter zone',
'Alarm' => 'Alarme',
@ -111,22 +113,32 @@ $SLANG = array(
'AttrArchiveStatus' => 'Etat Archive',
'AttrAvgScore' => 'Score moy.',
'AttrCause' => 'Cause',
'AttrDate' => 'Date',
'AttrDateTime' => 'Date/Heure',
'AttrDiskBlocks' => 'Blocs disque',
'AttrDiskPercent' => '% disque',
'AttrDiskSpace' => 'Disk Space', // Added - 2018-08-30
'AttrDuration' => 'Durée',
'AttrEndDate' => 'End Date', // Added - 2018-08-30
'AttrEndDateTime' => 'End Date/Time', // Added - 2018-08-30
'AttrEndTime' => 'End Time', // Added - 2018-08-30
'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30
'AttrFilterServer' => 'Server Filter is Running On', // Added - 2018-08-30
'AttrFrames' => 'Images',
'AttrId' => 'Id',
'AttrMaxScore' => 'Score max.',
'AttrMonitorId' => 'N°',
'AttrMonitorName' => 'Nom caméra',
'AttrMonitorServer' => 'Server Monitor is Running On', // Added - 2018-08-30
'AttrName' => 'Nom',
'AttrNotes' => 'Notes',
'AttrStartDate' => 'Start Date', // Added - 2018-08-30
'AttrStartDateTime' => 'Start Date/Time', // Added - 2018-08-30
'AttrStartTime' => 'Start Time', // Added - 2018-08-30
'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30
'AttrStateId' => 'Run State', // Added - 2018-08-30
'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30
'AttrStorageServer' => 'Server Hosting Storage', // Added - 2018-08-30
'AttrSystemLoad' => 'Charge système',
'AttrTime' => 'Heure',
'AttrTotalScore' => 'Score total',
'AttrWeekday' => 'Semaine',
'Auto' => 'Auto',
'AutoStopTimeout' => 'Temporisation arrêt',
'Available' => 'Disponibles', // Added - 2009-03-31
@ -159,9 +171,11 @@ $SLANG = array(
'BadRefBlendPerc' => 'Le pourcentage de fusion de l\'image de référence doit être un entier supérieur à 0 et inférieur à 100',
'BadSectionLength' => 'La longueur de la section doit être un entier supérieur ou égal à 30',
'BadSignalCheckColour' => 'La chaîne de caractères pour la couleur d\'état du signal est invalide',
'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"', // Added - 2018-08-30
'BadStreamReplayBuffer'=> 'Le tampon d\'images pour la relecture doit être un entier supérieur ou égal à 0',
'BadWarmupCount' => 'Le nombre d\'images tests doit être un entier supérieur ou égal à 0',
'BadWebColour' => 'La chaîne de caractères pour la couleur web est invalide',
'BadWebSitePath' => 'Please enter a complete website url, including the http:// or https:// prefix.', // Added - 2018-08-30
'BadWidth' => 'La valeur de la largeur est invalide',
'Bandwidth' => 'Débit',
'BandwidthHead' => 'Débit', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing
@ -225,10 +239,12 @@ $SLANG = array(
'ChooseLogSelection' => 'Choisir une sélection de journaux', // Added - 2011-06-17
'ChoosePreset' => 'Choisir préréglage',
'Clear' => 'Effacer', // Added - 2011-06-16
'CloneMonitor' => 'Clone', // Added - 2018-08-30
'Close' => 'Fermer',
'Colour' => 'Couleur',
'Command' => 'Commande',
'Component' => 'Composant', // Added - 2011-06-16
'ConcurrentFilter' => 'Run filter concurrently', // Added - 2018-08-30
'Config' => 'Config',
'ConfiguredFor' => 'Configuré pour',
'ConfirmDeleteEvents' => 'Etes-vous sûr de vouloir effacer le(s) événement(s) sélectionné(s)?',
@ -286,9 +302,11 @@ $SLANG = array(
'DonateRemindWeek' => 'Pas encore, me rappeler dans 1 semaine',
'DonateYes' => 'Oui, je souhaiterais faire un don maintenant',
'Download' => 'Télécharger',
'DownloadVideo' => 'Download Video', // Added - 2018-08-30
'DuplicateMonitorName' => 'Dupliquer le nom de la caméra', // Added - 2009-03-31
'Duration' => 'Durée',
'Edit' => 'Editer',
'EditLayout' => 'Edit Layout', // Added - 2018-08-30
'Email' => 'Email',
'EnableAlarms' => 'Activer les alarmes',
'Enabled' => 'Activé',
@ -305,6 +323,7 @@ $SLANG = array(
'Events' => 'Evénements',
'Exclude' => 'Exclure',
'Execute' => 'Exécuter',
'Exif' => 'Embed EXIF data into image', // Added - 2018-08-30
'Export' => 'Exporter',
'ExportDetails' => 'Exporter détails événements',
'ExportFailed' => 'Exportation échouée',
@ -334,8 +353,10 @@ $SLANG = array(
'FilterExecuteEvents' => 'Exécuter une commande',
'FilterLog' => 'Filtre', // Added - 2015-04-18
'FilterMessageEvents' => 'Envoyer les détails par message',
'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30
'FilterPx' => 'Filtre Px',
'FilterUnset' => 'Vous devez spécifier une largeur et une hauteur de filtre',
'FilterUpdateDiskSpace'=> 'Update used disk space', // Added - 2018-08-30
'FilterUploadEvents' => 'Transférer',
'FilterVideoEvents' => 'Créer vidéo',
'Filters' => 'Filtres',
@ -360,6 +381,7 @@ $SLANG = array(
'Function' => 'Mode',
'Gain' => 'Gain',
'General' => 'Général',
'GenerateDownload' => 'Generate Download', // Added - 2018-08-30
'GenerateVideo' => 'Générer vidéo',
'GeneratingVideo' => 'Génération vidéo',
'GoToZoneMinder' => 'Aller sur ZoneMinder.com',
@ -380,6 +402,7 @@ $SLANG = array(
'High' => 'Haut',
'HighBW' => 'Haut débit',
'Home' => 'Maison',
'Hostname' => 'Hostname', // Added - 2018-08-30
'Hour' => 'Heure',
'Hue' => 'Teinte',
'Id' => 'N°',
@ -404,6 +427,7 @@ $SLANG = array(
'Line' => 'Ligne', // Added - 2011-06-16
'LinkedMonitors' => 'Caméra(s) liée(s)',
'List' => 'Liste',
'ListMatches' => 'List Matches', // Added - 2018-08-30
'Load' => 'Charge',
'Local' => 'Local',
'Log' => 'Journal', // Added - 2011-06-16
@ -490,6 +514,7 @@ $SLANG = array(
'MonitorProbeIntro' => 'La liste ci-dessous montre les caméras détectées localement ou sur le réseau, qu\'elles soient déjà configurées ou non.<br/><br/>Sélectionnez la caméra désirée dans la liste.<br/><br/>Veuillez noter que toutes les caméras ne sont pas forcément détectées et que la sauvegarde entraînera l\'écrasement des paramètres déjà configurés pour la caméra en cours.<br/><br/>', // Added - 2009-03-31
'Monitors' => 'Caméras',
'Montage' => 'Montage',
'MontageReview' => 'Montage Review', // Added - 2018-08-30
'Month' => 'Mois',
'More' => 'Plus', // Added - 2011-06-16
'MotionFrameSkip' => 'Saut image en alarme',
@ -516,6 +541,7 @@ $SLANG = array(
'Next' => 'Suivant',
'No' => 'Non',
'NoDetectedCameras' => 'Pas de caméras détectées', // Added - 2009-03-31
'NoDetectedProfiles' => 'No Detected Profiles', // Added - 2018-08-30
'NoFramesRecorded' => 'Pas d\'images enregistrées pour cet événement',
'NoGroup' => 'Pas de groupe',
'NoSavedFilters' => 'Pas de filtres sauvegardés',
@ -534,6 +560,8 @@ $SLANG = array(
'OpGt' => 'sup. à',
'OpGtEq' => 'plus grand ou égal à',
'OpIn' => 'en lot',
'OpIs' => 'is', // Added - 2018-08-30
'OpIsNot' => 'is not', // Added - 2018-08-30
'OpLt' => 'inf. à',
'OpLtEq' => 'inf. ou égal à',
'OpMatches' => 'correspond',
@ -543,6 +571,7 @@ $SLANG = array(
'Open' => 'Ouvrir',
'OptionHelp' => 'Aide',
'OptionRestartWarning' => 'Ces changements peuvent nécessiter un redémarrage de ZoneMinder pour être pleinement opérationnels.',
'OptionalEncoderParam' => 'Optional Encoder Parameters', // Added - 2018-08-30
'Options' => 'Options',
'OrEnterNewName' => 'ou entrez nouv. nom',
'Order' => 'Ordre',
@ -580,9 +609,13 @@ $SLANG = array(
'ProfileProbeIntro' => 'La liste ci-dessous montre les profils de flux existants pour la caméra sélectionnée.<br/><br/>Sélectionnez le profil désiré dans la liste ci-dessous.<br/><br/>Veuillez noter que ZoneMinder ne peut pas configurer de profils additionels et que la sauvegarde entraînera l\'écrasement des paramètres déjà configurés pour la caméra en cours.<br/><br/>', // Added - 2015-04-18
'Progress' => 'Progression', // Added - 2015-04-18
'Protocol' => 'Protocole',
'RTSPDescribe' => 'Use RTSP Response Media URL', // Added - 2018-08-30
'RTSPTransport' => 'RTSP Transport Protocol', // Added - 2018-08-30
'Rate' => 'Vitesse',
'Real' => 'Réel',
'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // Added - 2018-08-30
'Record' => 'Enregistrer',
'RecordAudio' => 'Whether to store the audio stream when saving an event.', // Added - 2018-08-30
'RefImageBlendPct' => '% fusion image référence',
'Refresh' => 'Rafraîchir',
'Remote' => 'Distant',
@ -598,6 +631,7 @@ $SLANG = array(
'ReplayAll' => 'Tous les événements',
'ReplayGapless' => 'Rejouer sans blancs',
'ReplaySingle' => 'Rejouer seul',
'ReportEventAudit' => 'Audit Events Report', // Added - 2018-08-30
'Reset' => 'RàZ',
'ResetEventCounts' => 'RàZ compteur évts',
'Restart' => 'Redémarrer',
@ -616,6 +650,7 @@ $SLANG = array(
'Save' => 'Sauvegarder',
'SaveAs' => 'Sauvegarder sous',
'SaveFilter' => 'Sauvegarder filtre',
'SaveJPEGs' => 'Save JPEGs', // Added - 2018-08-30
'Scale' => 'Echelle',
'Score' => 'Score',
'Secs' => 'Secs',
@ -632,6 +667,7 @@ $SLANG = array(
'ShowFilterWindow' => 'Filtres',
'ShowTimeline' => 'Afficher chronologie',
'SignalCheckColour' => 'Couleur vérif. signal',
'SignalCheckPoints' => 'Signal Check Points', // Added - 2018-08-30
'Size' => 'Taille',
'SkinDescription' => 'Remplacer le skin par défaut', // Added - 2011-01-30
'Sleep' => 'Veille',
@ -651,6 +687,10 @@ $SLANG = array(
'State' => 'Etat',
'Stats' => 'Stats',
'Status' => 'Statut',
'StatusConnected' => 'Capturing', // Added - 2018-08-30
'StatusNotRunning' => 'Not Running', // Added - 2018-08-30
'StatusRunning' => 'Not Capturing', // Added - 2018-08-30
'StatusUnknown' => 'Unknown', // Added - 2018-08-30
'Step' => 'Pas',
'StepBack' => 'Reculer',
'StepForward' => 'Avancer',
@ -661,6 +701,8 @@ $SLANG = array(
'Stills' => 'Photos',
'Stop' => 'Arrêter',
'Stopped' => 'Arrêté',
'StorageArea' => 'Storage Area', // Added - 2018-08-30
'StorageScheme' => 'Scheme', // Added - 2018-08-30
'Stream' => 'Flux',
'StreamReplayBuffer' => 'Nb d\'image(s) pour relecture',
'Submit' => 'Soumettre',
@ -680,9 +722,9 @@ $SLANG = array(
'TimelineTip4' => 'Utilisez les contrôles ci-dessous pour faire un zoom arrière ou naviguer en arrière et avancer sur l\'intervalle de temps.', // Added 2013.08.15.
'Timestamp' => 'Horodatage',
'TimestampLabelFormat' => 'Format',
'TimestampLabelSize' => 'Taille de police',
'TimestampLabelX' => 'Coordonnée X',
'TimestampLabelY' => 'Coordonnée Y',
'TimestampLabelSize' => 'Taille de police',
'Today' => 'Aujourd\'hui',
'Tools' => 'Outils',
'Total' => 'Total', // Added - 2011-06-16
@ -727,6 +769,7 @@ $SLANG = array(
'VideoGenParms' => 'Paramètres génération vidéo',
'VideoGenSucceeded' => 'Vidéo générée avec succès !',
'VideoSize' => 'Taille vidéo',
'VideoWriter' => 'Video Writer', // Added - 2018-08-30
'View' => 'Voir',
'ViewAll' => 'Tout voir',
'ViewEvent' => 'Voir événement',
@ -736,6 +779,7 @@ $SLANG = array(
'Watch' => 'Regarder',
'Web' => 'Web',
'WebColour' => 'Couleur web',
'WebSiteUrl' => 'Website URL', // Added - 2018-08-30
'Week' => 'Semaine',
'White' => 'Blanc',
'WhiteBalance' => 'Balance des blancs',

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