diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Base.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Base.pm.in index 70140ea66..4e462a86d 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Base.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Base.pm.in @@ -58,7 +58,7 @@ ZoneMinder::Base - Base perl module for ZoneMinder =head1 SYNOPSIS - use ZoneMinder::Base; +use ZoneMinder::Base; =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index f795ec0fa..bf8e889dd 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -46,14 +46,14 @@ our @EXPORT_CONFIG = qw( %Config ); # Get populated by BEGIN our %EXPORT_TAGS = ( functions => [ qw( - zmConfigLoad - loadConfigFromDB - saveConfigToDB - ) ], + zmConfigLoad + loadConfigFromDB + saveConfigToDB + ) ], constants => [ qw( - ZM_PID - ) ] -); + ZM_PID + ) ] + ); push( @{$EXPORT_TAGS{config}}, @EXPORT_CONFIG ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; @@ -71,69 +71,69 @@ use Carp; # Load the config from the database into the symbol table BEGIN { - my $config_file = ZM_CONFIG; - open( my $CONFIG, "<", $config_file ) - or croak( "Can't open config file '$config_file': $!" ); - foreach my $str ( <$CONFIG> ) - { - next if ( $str =~ /^\s*$/ ); - next if ( $str =~ /^\s*#/ ); - my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*(.*?)\s*$/; - if ( ! $name ) { - print( STDERR "Warning, bad line in $config_file: $str\n" ); - next; - } # end if - $name =~ tr/a-z/A-Z/; - $Config{$name} = $value; - } - close( $CONFIG ); + my $config_file = ZM_CONFIG; + open( my $CONFIG, "<", $config_file ) + or croak( "Can't open config file '$config_file': $!" ); + foreach my $str ( <$CONFIG> ) + { + next if ( $str =~ /^\s*$/ ); + next if ( $str =~ /^\s*#/ ); + my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*(.*?)\s*$/; + if ( ! $name ) { + print( STDERR "Warning, bad line in $config_file: $str\n" ); + next; + } # end if + $name =~ tr/a-z/A-Z/; + $Config{$name} = $value; + } + close( $CONFIG ); - use DBI; - my $socket; - my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); + use DBI; + my $socket; + my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); - if ( defined($portOrSocket) ) + if ( defined($portOrSocket) ) + { + if ( $portOrSocket =~ /^\// ) { - if ( $portOrSocket =~ /^\// ) - { - $socket = ";mysql_socket=".$portOrSocket; - } - else - { - $socket = ";host=".$host.";port=".$portOrSocket; - } + $socket = ";mysql_socket=".$portOrSocket; } else { - $socket = ";host=".$Config{ZM_DB_HOST}; + $socket = ";host=".$host.";port=".$portOrSocket; } - my $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .$socket - , $Config{ZM_DB_USER} - , $Config{ZM_DB_PASS} - ) or croak( "Can't connect to db" ); - my $sql = 'select * from Config'; - my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() or croak( "Can't execute: ".$sth->errstr() ); - while( my $config = $sth->fetchrow_hashref() ) { - $Config{$config->{Name}} = $config->{Value}; + } + else + { + $socket = ";host=".$Config{ZM_DB_HOST}; + } + my $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} + .$socket + , $Config{ZM_DB_USER} + , $Config{ZM_DB_PASS} + ) or croak( "Can't connect to db" ); + my $sql = 'select * from Config'; + my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() or croak( "Can't execute: ".$sth->errstr() ); + while( my $config = $sth->fetchrow_hashref() ) { + $Config{$config->{Name}} = $config->{Value}; + } + $sth->finish(); +#$dbh->disconnect(); + if ( ! exists $Config{ZM_SERVER_ID} ) { + $Config{ZM_SERVER_ID} = undef; + $sth = $dbh->prepare_cached( 'SELECT * FROM Servers WHERE Name=?' ); + if ( $Config{ZM_SERVER_NAME} ) { + $res = $sth->execute( $Config{ZM_SERVER_NAME} ); + my $result = $sth->fetchrow_hashref(); + $Config{ZM_SERVER_ID} = $$result{Id}; + } elsif ( $Config{ZM_SERVER_HOST} ) { + $res = $sth->execute( $Config{ZM_SERVER_HOST} ); + my $result = $sth->fetchrow_hashref(); + $Config{ZM_SERVER_ID} = $$result{Id}; } $sth->finish(); - #$dbh->disconnect(); - if ( ! exists $Config{ZM_SERVER_ID} ) { - $Config{ZM_SERVER_ID} = undef; - $sth = $dbh->prepare_cached( 'SELECT * FROM Servers WHERE Name=?' ); - if ( $Config{ZM_SERVER_NAME} ) { - $res = $sth->execute( $Config{ZM_SERVER_NAME} ); - my $result = $sth->fetchrow_hashref(); - $Config{ZM_SERVER_ID} = $$result{Id}; - } elsif ( $Config{ZM_SERVER_HOST} ) { - $res = $sth->execute( $Config{ZM_SERVER_HOST} ); - my $result = $sth->fetchrow_hashref(); - $Config{ZM_SERVER_ID} = $$result{Id}; - } - $sth->finish(); - } + } } sub loadConfigFromDB { @@ -315,7 +315,7 @@ 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. +at your option, any later version of Perl 5 you may have available. - =cut +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 27f9a80d5..15076197f 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -41,12 +41,12 @@ our @ISA = qw(Exporter ZoneMinder::Base); # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( - 'data' => [ qw( - %types - @options - %options_hash - ) ] -); + data => [ qw( + %types + @options + %options_hash + ) ] + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'data'} } ); @@ -67,3836 +67,3836 @@ our $configInitialised = 0; sub INIT { - initialiseConfig(); + initialiseConfig(); } # Types our %types = ( - string => { - db_type =>"string", - hint =>"string", - pattern =>qr|^(.+)$|, - format =>q( $1 ) - }, - alphanum => { - db_type =>"string", - hint =>"alphanumeric", - pattern =>qr|^([a-zA-Z0-9-_]+)$|, - format =>q( $1 ) - }, - text => { - db_type =>"text", - hint =>"free text", - pattern =>qr|^(.+)$|, - format =>q( $1 ) - }, - boolean => { - db_type =>"boolean", - hint =>"yes|no", - pattern =>qr|^([yn])|i, - check =>q( $1 ), - format =>q( ($1 =~ /^y/) - ? "yes" - : "no" - ) - }, - integer => { - db_type =>"integer", - hint =>"integer", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - decimal => { - db_type =>"decimal", - hint =>"decimal", - pattern =>qr|^(\d+(?:\.\d+)?)$|, - format =>q( $1 ) - }, - hexadecimal => { - db_type =>"hexadecimal", - hint =>"hexadecimal", - pattern =>qr|^(?:0x)?([0-9a-f]{1,8})$|, - format =>q( "0x".$1 ) - }, - tristate => { - db_type =>"string", - hint =>"auto|yes|no", - pattern =>qr|^([ayn])|i, check=>q( $1 ), - format =>q( ($1 =~ /^y/) - ? "yes" - : ($1 =~ /^n/ ? "no" : "auto" ) - ) - }, - abs_path => { - db_type =>"string", - hint =>"/absolute/path/to/somewhere", - pattern =>qr|^((?:/[^/]*)+?)/?$|, - format =>q( $1 ) - }, - rel_path => { - db_type =>"string", - hint =>"relative/path/to/somewhere", - pattern =>qr|^((?:[^/].*)?)/?$|, - format =>q( $1 ) - }, - directory => { - db_type =>"string", - hint =>"directory", - pattern =>qr|^([a-zA-Z0-9-_.]+)$|, - format =>q( $1 ) - }, - file => { - db_type =>"string", - hint =>"filename", - pattern =>qr|^([a-zA-Z0-9-_.]+)$|, - format =>q( $1 ) - }, - hostname => { - db_type =>"string", - hint =>"host.your.domain", - pattern =>qr|^([a-zA-Z0-9_.-]+)$|, - format =>q( $1 ) - }, - url => { - db_type =>"string", - hint =>"http://host.your.domain/", - pattern =>qr|^(?:http://)?(.+)$|, - format =>q( "http://".$1 ) - }, - email => { - db_type =>"string", - hint =>"your.name\@your.domain", - pattern =>qr|^([a-zA-Z0-9_.-]+)\@([a-zA-Z0-9_.-]+)$|, - format =>q( $1\@$2 ) - }, -); + string => { + db_type => "string", + hint => "string", + pattern => qr|^(.+)$|, + format => q( $1 ) + }, + alphanum => { + db_type => "string", + hint => "alphanumeric", + pattern => qr|^([a-zA-Z0-9-_]+)$|, + format => q( $1 ) + }, + text => { + db_type => "text", + hint => "free text", + pattern => qr|^(.+)$|, + format => q( $1 ) + }, + boolean => { + db_type => "boolean", + hint => "yes|no", + pattern => qr|^([yn])|i, + check => q( $1 ), + format => q( ($1 =~ /^y/) + ? "yes" + : "no" + ) + }, + integer => { + db_type => "integer", + hint => "integer", + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + decimal => { + db_type => "decimal", + hint => "decimal", + pattern => qr|^(\d+(?:\.\d+)?)$|, + format => q( $1 ) + }, + hexadecimal => { + db_type => "hexadecimal", + hint => "hexadecimal", + pattern => qr|^(?:0x)?([0-9a-f]{1,8})$|, + format => q( "0x".$1 ) + }, + tristate => { + db_type => "string", + hint => "auto|yes|no", + pattern => qr|^([ayn])|i, check=>q( $1 ), + format => q( ($1 =~ /^y/) + ? "yes" + : ($1 =~ /^n/ ? "no" : "auto" ) + ) + }, + abs_path => { + db_type => "string", + hint => "/absolute/path/to/somewhere", + pattern => qr|^((?:/[^/]*)+?)/?$|, + format => q( $1 ) + }, + rel_path => { + db_type => "string", + hint => "relative/path/to/somewhere", + pattern => qr|^((?:[^/].*)?)/?$|, + format => q( $1 ) + }, + directory => { + db_type => "string", + hint => "directory", + pattern => qr|^([a-zA-Z0-9-_.]+)$|, + format => q( $1 ) + }, + file => { + db_type => "string", + hint => "filename", + pattern => qr|^([a-zA-Z0-9-_.]+)$|, + format => q( $1 ) + }, + hostname => { + db_type => "string", + hint => "host.your.domain", + pattern => qr|^([a-zA-Z0-9_.-]+)$|, + format => q( $1 ) + }, + url => { + db_type => "string", + hint => "http://host.your.domain/", + pattern => qr|^(?:http://)?(.+)$|, + format => q( "http://".$1 ) + }, + email => { + db_type => "string", + hint => "your.name\@your.domain", + pattern => qr|^([a-zA-Z0-9_.-]+)\@([a-zA-Z0-9_.-]+)$|, + format => q( $1\@$2 ) + }, + ); sub qqq { ## Un-pad paragraph of text. - local $_ = shift; - s{\n?^\s*}{ }mg; - return $_; + local $_ = shift; + s{\n?^\s*}{ }mg; + return $_; } our @options = ( - { - name => "ZM_SKIN_DEFAULT", - default => "classic", - description => "Default skin used by web interface", + { + name => "ZM_SKIN_DEFAULT", + default => "classic", + description => "Default skin used by web interface", - help => qqq(" - ZoneMinder allows the use of many different web interfaces. - This option allows you to set the default skin used by the - website. Users can change their skin later, this merely sets - the default. - "), - type => $types{string}, - category => "system", + help => qqq(" + ZoneMinder allows the use of many different web interfaces. + This option allows you to set the default skin used by the + website. Users can change their skin later, this merely sets + the default. + "), + type => $types{string}, + category => "system", + }, + { + name => "ZM_CSS_DEFAULT", + default => "classic", + description => "Default set of css files used by web interface", + help => qqq(" + ZoneMinder allows the use of many different web interfaces, and + some skins allow the use of different set of CSS files to + control the appearance. This option allows you to set the + default set of css files used by the website. Users can change + their css later, this merely sets the default. + "), + type => $types{string}, + category => "system", + }, + { + name => "ZM_LANG_DEFAULT", + default => "en_gb", + description => "Default language used by web interface", + help => qqq(" + ZoneMinder allows the web interface to use languages other than + English if the appropriate language file has been created and + is present. This option allows you to change the default + language that is used from the shipped language, British + English, to another language + "), + type => $types{string}, + category => "system", + }, + { + name => "ZM_OPT_USE_AUTH", + default => "no", + description => "Authenticate user logins to ZoneMinder", + help => qqq(" + ZoneMinder can run in two modes. The simplest is an entirely + unauthenticated mode where anyone can access ZoneMinder and + perform all tasks. This is most suitable for installations + where the web server access is limited in other ways. The other + mode enables user accounts with varying sets of permissions. + Users must login or authenticate to access ZoneMinder and are + limited by their defined permissions. + "), + type => $types{boolean}, + category => "system", + }, + { + name => "ZM_AUTH_TYPE", + default => "builtin", + description => "What is used to authenticate ZoneMinder users", + help => qqq(" + ZoneMinder can use two methods to authenticate users when + running in authenticated mode. The first is a builtin method + where ZoneMinder provides facilities for users to log in and + maintains track of their identity. The second method allows + interworking with other methods such as http basic + authentication which passes an independently authentication + 'remote' user via http. In this case ZoneMinder would use the + supplied user without additional authentication provided such a + user is configured ion ZoneMinder. + "), + requires => [ { name=>"ZM_OPT_USE_AUTH", value=>"yes" } ], + type => { + db_type => "string", + hint => "builtin|remote", + pattern => qr|^([br])|i, + format => q( $1 =~ /^b/ ? "builtin" : "remote" ) }, - { - name => "ZM_CSS_DEFAULT", - default => "classic", - description => "Default set of css files used by web interface", - help => qqq(" - ZoneMinder allows the use of many different web interfaces, and - some skins allow the use of different set of CSS files to - control the appearance. This option allows you to set the - default set of css files used by the website. Users can change - their css later, this merely sets the default. - "), - type => $types{string}, - category => "system", + category => "system", + }, + { + name => "ZM_AUTH_RELAY", + default => "hashed", + description => "Method used to relay authentication information", + help => qqq(" + When ZoneMinder is running in authenticated mode it can pass + user details between the web pages and the back end processes. + There are two methods for doing this. This first is to use a + time limited hashed string which contains no direct username or + password details, the second method is to pass the username and + passwords around in plaintext. This method is not recommend + except where you do not have the md5 libraries available on + your system or you have a completely isolated system with no + external access. You can also switch off authentication + relaying if your system is isolated in other ways. + "), + requires => [ { name=>"ZM_OPT_USE_AUTH", value=>"yes" } ], + type => { + db_type => "string", + hint => "hashed|plain|none", + pattern => qr|^([hpn])|i, + format => q( ($1 =~ /^h/) + ? "hashed" + : ($1 =~ /^p/ ? "plain" : "none" ) + ) }, - { - name => "ZM_LANG_DEFAULT", - default => "en_gb", - description => "Default language used by web interface", - help => qqq(" - ZoneMinder allows the web interface to use languages other than - English if the appropriate language file has been created and - is present. This option allows you to change the default - language that is used from the shipped language, British - English, to another language - "), - type => $types{string}, - category => "system", - }, - { - name => "ZM_OPT_USE_AUTH", - default => "no", - description => "Authenticate user logins to ZoneMinder", - help => qqq(" - ZoneMinder can run in two modes. The simplest is an entirely - unauthenticated mode where anyone can access ZoneMinder and - perform all tasks. This is most suitable for installations - where the web server access is limited in other ways. The other - mode enables user accounts with varying sets of permissions. - Users must login or authenticate to access ZoneMinder and are - limited by their defined permissions. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_AUTH_TYPE", - default => "builtin", - description => "What is used to authenticate ZoneMinder users", - help => qqq(" - ZoneMinder can use two methods to authenticate users when - running in authenticated mode. The first is a builtin method - where ZoneMinder provides facilities for users to log in and - maintains track of their identity. The second method allows - interworking with other methods such as http basic - authentication which passes an independently authentication - 'remote' user via http. In this case ZoneMinder would use the - supplied user without additional authentication provided such a - user is configured ion ZoneMinder. - "), - requires => [ { name=>"ZM_OPT_USE_AUTH", value=>"yes" } ], - type => { - db_type =>"string", - hint =>"builtin|remote", - pattern =>qr|^([br])|i, - format =>q( $1 =~ /^b/ ? "builtin" : "remote" ) - }, - category => "system", - }, - { - name => "ZM_AUTH_RELAY", - default => "hashed", - description => "Method used to relay authentication information", - help => qqq(" - When ZoneMinder is running in authenticated mode it can pass - user details between the web pages and the back end processes. - There are two methods for doing this. This first is to use a - time limited hashed string which contains no direct username or - password details, the second method is to pass the username and - passwords around in plaintext. This method is not recommend - except where you do not have the md5 libraries available on - your system or you have a completely isolated system with no - external access. You can also switch off authentication - relaying if your system is isolated in other ways. - "), - requires => [ { name=>"ZM_OPT_USE_AUTH", value=>"yes" } ], - type => { - db_type =>"string", - hint =>"hashed|plain|none", - pattern =>qr|^([hpn])|i, - format =>q( ($1 =~ /^h/) - ? "hashed" - : ($1 =~ /^p/ ? "plain" : "none" ) - ) - }, - category => "system", - }, - { - name => "ZM_AUTH_HASH_SECRET", - default => "...Change me to something unique...", - description => "Secret for encoding hashed authentication information", - help => qqq(" - When ZoneMinder is running in hashed authenticated mode it is - necessary to generate hashed strings containing encrypted - sensitive information such as usernames and password. Although - these string are reasonably secure the addition of a random - secret increases security substantially. - "), - requires => [ - { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, - { name=>"ZM_AUTH_RELAY", value=>"hashed" } - ], - type => $types{string}, - category => "system", - }, - { - name => "ZM_AUTH_HASH_IPS", - default => "yes", - description => "Include IP addresses in the authentication hash", - help => qqq(" - 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. - "), - requires => [ - { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, - { name=>"ZM_AUTH_RELAY", value=>"hashed" } - ], - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_AUTH_HASH_LOGINS", - default => "no", - description => "Allow login by authentication hash", - help => qqq(" - 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. - "), - requires => [ - { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, - { name=>"ZM_AUTH_RELAY", value=>"hashed" } - ], - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_OPT_USE_API", - default => "yes", - description => "Enable ZoneMinder APIs", - help => qqq(" - ZoneMinder now features a new API using which 3rd party - applications can interact with ZoneMinder data. It is - STRONGLY recommended that you enable authentication along - with APIs. Note that the APIs return sensitive data like - Monitor access details which are configured as JSON objects. - Which is why we recommend you enabling authentication, especially - if you are exposing your ZM instance on the Internet. - "), - type => $types{boolean}, - category => "system", - }, -# PP - Google reCaptcha settings - { - name => "ZM_OPT_USE_GOOG_RECAPTCHA", - default => "no", - description => "Add Google reCaptcha to login page", - help => qqq(" - This option allows you to include a google - reCaptcha validation at login. This means in addition to providing - a valid usernane and password, you will also have to - pass the reCaptcha test. Please note that enabling this - option results in the zoneminder login page reach out - to google servers for captcha validation. Also please note - that enabling this option will break 3rd party clients - like zmNinja and zmView as they also need to login to ZoneMinder - and they will fail the reCaptcha test. - "), - requires => [ - {name=>"ZM_OPT_USE_AUTH", value=>"yes"} - ], - type => $types {boolean}, - category => "system", - }, - - { - name => "ZM_OPT_GOOG_RECAPTCHA_SITEKEY", - default => "...Insert your recaptcha site-key here...", - description => "Your recaptcha site-key", - help => qqq("You need to generate your keys from - the Google reCaptcha website. - Please refer to https://www.google.com/recaptcha/ - for more details. - "), - requires => [ - {name=>"ZM_OPT_USE_GOOG_RECAPTCHA", value=>"yes"} - ], - type => $types {string}, - category => "system", - }, - { - name => "ZM_OPT_GOOG_RECAPTCHA_SECRETKEY", - default => "...Insert your recaptcha secret-key here...", - description => "Your recaptcha secret-key", - help => qqq("You need to generate your keys from - the Google reCaptcha website. - Please refer to https://www.google.com/recaptcha/ - for more details. - "), - requires => [ - {name=>"ZM_OPT_USE_GOOG_RECAPTCHA", value=>"yes"} - ], - type => $types {string}, - category => "system", - }, - + category => "system", + }, + { + name => "ZM_AUTH_HASH_SECRET", + default => "...Change me to something unique...", + description => "Secret for encoding hashed authentication information", + help => qqq(" + When ZoneMinder is running in hashed authenticated mode it is + necessary to generate hashed strings containing encrypted + sensitive information such as usernames and password. Although + these string are reasonably secure the addition of a random + secret increases security substantially. + "), + requires => [ + { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, + { name=>"ZM_AUTH_RELAY", value=>"hashed" } + ], + type => $types{string}, + category => "system", + }, + { + name => "ZM_AUTH_HASH_IPS", + default => "yes", + description => "Include IP addresses in the authentication hash", + help => qqq(" + 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. + "), + requires => [ + { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, + { name=>"ZM_AUTH_RELAY", value=>"hashed" } + ], + type => $types{boolean}, + category => "system", + }, + { + name => "ZM_AUTH_HASH_LOGINS", + default => "no", + description => "Allow login by authentication hash", + help => qqq(" + 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. + "), + requires => [ + { name=>"ZM_OPT_USE_AUTH", value=>"yes" }, + { name=>"ZM_AUTH_RELAY", value=>"hashed" } + ], + type => $types{boolean}, + category => "system", + }, + { + name => "ZM_OPT_USE_API", + default => "yes", + description => "Enable ZoneMinder APIs", + help => qqq(" + ZoneMinder now features a new API using which 3rd party + applications can interact with ZoneMinder data. It is + STRONGLY recommended that you enable authentication along + with APIs. Note that the APIs return sensitive data like + Monitor access details which are configured as JSON objects. + Which is why we recommend you enabling authentication, especially + if you are exposing your ZM instance on the Internet. + "), + type => $types{boolean}, + category => "system", + }, + # PP - Google reCaptcha settings + { + name => "ZM_OPT_USE_GOOG_RECAPTCHA", + default => "no", + description => "Add Google reCaptcha to login page", + help => qqq(" + This option allows you to include a google + reCaptcha validation at login. This means in addition to providing + a valid usernane and password, you will also have to + pass the reCaptcha test. Please note that enabling this + option results in the zoneminder login page reach out + to google servers for captcha validation. Also please note + that enabling this option will break 3rd party clients + like zmNinja and zmView as they also need to login to ZoneMinder + and they will fail the reCaptcha test. + "), + requires => [ + {name=>"ZM_OPT_USE_AUTH", value=>"yes"} + ], + type => $types{boolean}, + category => "system", + }, - { - name => "ZM_DIR_EVENTS", - default => "events", - description => "Directory where events are stored", - help => qqq(" - This is the path to the events directory where all the event - images and other miscellaneous files are stored. CAUTION: The - directory you specify here cannot be outside the web root. This - is a common mistake. Most users should never change this value. - If you intend to record events to a second disk or network - share, then you should mount the drive or share directly to the - ZoneMinder events folder or follow the instructions in the - ZoneMinder Wiki titled Using a dedicated Hard Drive. - "), - type => $types{directory}, - category => "paths", - }, - { - name => "ZM_USE_DEEP_STORAGE", - default => "yes", - description => "Use a deep filesystem hierarchy for events", - help => qqq(" - This option is now the default for new ZoneMinder systems and - should not be changed. Previous versions of ZoneMinder stored - all events for a monitor under one folder. Enabling - USE_DEEP_STORAGE causes ZoneMinder to store events under a - folder structure that follows year/month/day/hour/min/second. - Storing events this way avoids the limitation of storing more - than 32k files in a single folder inherent in some filesystems. - It is important to note that you cannot simply change this - option. You must stop zoneminder, enable USE_DEEP_STORAGE, and - then run \"sudo zmupdate.pl --migrate-events\". FAILURE TO DO - SO WILL RESULT IN LOSS OF YOUR DATA! Consult the ZoneMinder - WiKi for further details. - "), - type => $types{boolean}, - category => "hidden", - }, - { - name => "ZM_DIR_IMAGES", - default => "images", - description => "Directory where the images that the ZoneMinder client generates are stored", - help => qqq(" - ZoneMinder generates a myriad of images, mostly of which are - associated with events. For those that aren't this is where - they go. CAUTION: The directory you specify here cannot be - outside the web root. This is a common mistake. Most users - should never change this value. If you intend to save images to - a second disk or network share, then you should mount the drive - or share directly to the ZoneMinder images folder or follow the - instructions in the ZoneMinder Wiki titled Using a dedicated - Hard Drive. - "), - type => $types{directory}, - category => "paths", - }, - { - name => "ZM_DIR_SOUNDS", - default => "sounds", - description => "Directory to the sounds that the ZoneMinder client can use", - help => qqq(" - ZoneMinder can optionally play a sound file when an alarm is - detected. This indicates where to look for this file. CAUTION: - The directory you specify here cannot be outside the web root. - Most users should never change this value. - "), - type => $types{directory}, - category => "paths", - }, - { - name => "ZM_PATH_ZMS", - default => "/cgi-bin/nph-zms", - description => "Web path to zms streaming server", - help => qqq(" - The ZoneMinder streaming server is required to send streamed - images to your browser. It will be installed into the cgi-bin - path given at configuration time. This option determines what - the web path to the server is rather than the local path on - your machine. Ordinarily the streaming server runs in - parser-header mode however if you experience problems with - streaming you can change this to non-parsed-header (nph) mode - by changing 'zms' to 'nph-zms'. - "), - type => $types{rel_path}, - category => "paths", - }, - { - name => "ZM_COLOUR_JPEG_FILES", - default => "no", - description => "Colourise greyscale JPEG files", - help => qqq(" - Cameras that capture in greyscale can write their captured - images to jpeg files with a corresponding greyscale colour - space. This saves a small amount of disk space over colour - ones. However some tools such as ffmpeg either fail to work - with this colour space or have to convert it beforehand. - Setting this option to yes uses up a little more space but - makes creation of MPEG files much faster. - "), - type => $types{boolean}, - category => "images", - }, - { - name => "ZM_ADD_JPEG_COMMENTS", - default => "no", - description => "Add jpeg timestamp annotations as file header comments", - help => qqq(" - JPEG files may have a number of extra fields added to the file - header. The comment field may have any kind of text added. This - options allows you to have the same text that is used to - annotate the image additionally included as a file header - comment. If you archive event images to other locations this - may help you locate images for particular events or times if - you use software that can read comment headers. - "), - type => $types{boolean}, - category => "images", - }, - { - name => "ZM_JPEG_FILE_QUALITY", - default => "70", - description => "Set the JPEG quality setting for the saved event files (1-100)", - help => qqq(" - When ZoneMinder detects an event it will save the images - associated with that event to files. These files are in the - JPEG format and can be viewed or streamed later. This option - specifies what image quality should be used to save these - files. A higher number means better quality but less - compression so will take up more disk space and take longer to - view over a slow connection. By contrast a low number means - smaller, quicker to view, files but at the price of lower - quality images. This setting applies to all images written - except if the capture image has caused an alarm and the alarm - file quality option is set at a higher value when that is used - instead. - "), - type => $types{integer}, - category => "images", - }, - { - name => "ZM_JPEG_ALARM_FILE_QUALITY", - default => "0", - description => "Set the JPEG quality setting for the saved event files during an alarm (1-100)", - help => qqq(" - This value is equivalent to the regular jpeg file quality - setting above except that it only applies to images saved while - in an alarm state and then only if this value is set to a - higher quality setting than the ordinary file setting. If set - to a lower value then it is ignored. Thus leaving it at the - default of 0 effectively means to use the regular file quality - setting for all saved images. This is to prevent acccidentally - saving important images at a worse quality setting. - "), - type => $types{integer}, - category => "images", - }, - # Deprecated, now stream quality - { - name => "ZM_JPEG_IMAGE_QUALITY", - default => "70", - description => "Set the JPEG quality setting for the streamed 'live' images (1-100)", - help => qqq(" - When viewing a 'live' stream for a monitor ZoneMinder will grab - an image from the buffer and encode it into JPEG format before - sending it. This option specifies what image quality should be - used to encode these images. A higher number means better - quality but less compression so will take longer to view over a - slow connection. By contrast a low number means quicker to view - images but at the price of lower quality images. This option - does not apply when viewing events or still images as these are - usually just read from disk and so will be encoded at the - quality specified by the previous options. - "), - type => $types{integer}, - category => "hidden", - }, - { - name => "ZM_JPEG_STREAM_QUALITY", - default => "70", - description => "Set the JPEG quality setting for the streamed 'live' images (1-100)", - help => qqq(" - When viewing a 'live' stream for a monitor ZoneMinder will grab - an image from the buffer and encode it into JPEG format before - sending it. This option specifies what image quality should be - used to encode these images. A higher number means better - quality but less compression so will take longer to view over a - slow connection. By contrast a low number means quicker to view - images but at the price of lower quality images. This option - does not apply when viewing events or still images as these are - usually just read from disk and so will be encoded at the - quality specified by the previous options. - "), - type => $types{integer}, - category => "images", - }, - { - name => "ZM_MPEG_TIMED_FRAMES", - default => "yes", - description => "Tag video frames with a timestamp for more realistic streaming", - help => qqq(" - When using streamed MPEG based video, either for live monitor - streams or events, ZoneMinder can send the streams in two ways. - If this option is selected then the timestamp for each frame, - taken from it's capture time, is included in the stream. This - means that where the frame rate varies, for instance around an - alarm, the stream will still maintain it's 'real' timing. If - this option is not selected then an approximate frame rate is - calculated and that is used to schedule frames instead. This - option should be selected unless you encounter problems with - your preferred streaming method. - "), - type => $types{boolean}, - category => "images", - }, - { - name => "ZM_MPEG_LIVE_FORMAT", - default => "swf", - description => "What format 'live' video streams are played in", - help => qqq(" - When using MPEG mode ZoneMinder can output live video. However - what formats are handled by the browser varies greatly between - machines. This option allows you to specify a video format - using a file extension format, so you would just enter the - extension of the file type you would like and the rest is - determined from that. The default of 'asf' works well under - Windows with Windows Media Player but I'm currently not sure - what, if anything, works on a Linux platform. If you find out - please let me know! If this option is left blank then live - streams will revert to being in motion jpeg format - "), - type => $types{string}, - category => "images", - }, - { - name => "ZM_MPEG_REPLAY_FORMAT", - default => "swf", - description => "What format 'replay' video streams are played in", - help => qqq(" - When using MPEG mode ZoneMinder can replay events in encoded - video format. However what formats are handled by the browser - varies greatly between machines. This option allows you to - specify a video format using a file extension format, so you - would just enter the extension of the file type you would like - and the rest is determined from that. The default of 'asf' - works well under Windows with Windows Media Player and 'mpg', - or 'avi' etc should work under Linux. If you know any more then - please let me know! If this option is left blank then live - streams will revert to being in motion jpeg format - "), - type => $types{string}, - category => "images", - }, - { - name => "ZM_RAND_STREAM", - default => "yes", - description => "Add a random string to prevent caching of streams", - help => qqq(" - Some browsers can cache the streams used by ZoneMinder. In - order to prevent his a harmless random string can be appended - to the url to make each invocation of the stream appear unique. - "), - type => $types{boolean}, - category => "images", - }, - { - name => "ZM_OPT_CAMBOZOLA", - default => "no", - description => "Is the (optional) cambozola java streaming client installed", - help => qqq(" - Cambozola is a handy low fat cheese flavoured Java applet that - ZoneMinder uses to view image streams on browsers such as - Internet Explorer that don't natively support this format. If - 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. - "), - type => $types{boolean}, - category => "images", - }, - { - name => "ZM_PATH_CAMBOZOLA", - default => "cambozola.jar", - description => "Web path to (optional) cambozola java streaming client", - help => qqq(" - Cambozola is a handy low fat cheese flavoured Java applet that - ZoneMinder uses to view image streams on browsers such as - Internet Explorer that don't natively support this format. If - 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 - installed in the same directory as the ZoneMinder web client - files. - "), - requires => [ { name=>"ZM_OPT_CAMBOZOLA", value=>"yes" } ], - type => $types{rel_path}, - category => "images", - }, - { - name => "ZM_RELOAD_CAMBOZOLA", - default => "0", - description => "After how many seconds should Cambozola be reloaded in live view", - help => qqq(" - Cambozola allows for the viewing of streaming MJPEG however it - caches the entire stream into cache space on the computer, - setting this to a number > 0 will cause it to automatically - reload after that many seconds to avoid filling up a hard - drive. - "), - type => $types{integer}, - category => "images", - }, - { - name => "ZM_TIMESTAMP_ON_CAPTURE", - default => "yes", - description => "Timestamp images as soon as they are captured", - help => qqq(" - ZoneMinder can add a timestamp to images in two ways. The - default method, when this option is set, is that each image is - timestamped immediately when captured and so the image held in - memory is marked right away. The second method does not - timestamp the images until they are either saved as part of an - event or accessed over the web. The timestamp used in both - methods will contain the same time as this is preserved along - with the image. The first method ensures that an image is - timestamped regardless of any other circumstances but will - result in all images being timestamped even those never saved - or viewed. The second method necessitates that saved images are - copied before being saved otherwise two timestamps perhaps at - different scales may be applied. This has the (perhaps) - desirable side effect that the timestamp is always applied at - the same resolution so an image that has scaling applied will - still have a legible and correctly scaled timestamp. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_CPU_EXTENSIONS", - default => "yes", - description => "Use advanced CPU extensions to increase performance", - help => qqq(" - When advanced processor extensions such as SSE2 or SSSE3 are - available, ZoneMinder can use them, which should increase - performance and reduce system load. Enabling this option on - processors that do not support the advanced processors - extensions used by ZoneMinder is harmless and will have no - effect. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_FAST_IMAGE_BLENDS", - default => "yes", - description => "Use a fast algorithm to blend the reference image", - help => qqq(" - To detect alarms ZoneMinder needs to blend the captured image - with the stored reference image to update it for comparison - with the next image. The reference blend percentage specified - for the monitor controls how much the new image affects the - reference image. There are two methods that are available for - this. If this option is set then fast calculation which does - not use any multiplication or division is used. This - calculation is extremely fast, however it limits the possible - blend percentages to 50%, 25%, 12.5%, 6.25%, 3.25% and 1.5%. - Any other blend percentage will be rounded to the nearest - possible one. The alternative is to switch this option off - and use standard blending instead, which is slower. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_OPT_ADAPTIVE_SKIP", - default => "yes", - description => "Should frame analysis try and be efficient in skipping frames", - help => qqq(" - In previous versions of ZoneMinder the analysis daemon would - attempt to keep up with the capture daemon by processing the - last captured frame on each pass. This would sometimes have the - undesirable side-effect of missing a chunk of the initial - activity that caused the alarm because the pre-alarm frames - would all have to be written to disk and the database before - processing the next frame, leading to some delay between the - first and second event frames. Setting this option enables a - newer adaptive algorithm where the analysis daemon attempts to - process as many captured frames as possible, only skipping - frames when in danger of the capture daemon overwriting yet to - be processed frames. This skip is variable depending on the - size of the ring buffer and the amount of space left in it. - Enabling this option will give you much better coverage of the - beginning of alarms whilst biasing out any skipped frames - towards the middle or end of the event. However you should be - aware that this will have the effect of making the analysis - daemon run somewhat behind the capture daemon during events and - for particularly fast rates of capture it is possible for the - adaptive algorithm to be overwhelmed and not have time to react - to a rapid build up of pending frames and thus for a buffer - overrun condition to occur. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_MAX_SUSPEND_TIME", - default => "30", - description => "Maximum time that a monitor may have motion detection suspended", - help => qqq(" - ZoneMinder allows monitors to have motion detection to be - suspended, for instance while panning a camera. Ordinarily this - relies on the operator resuming motion detection afterwards as - failure to do so can leave a monitor in a permanently suspended - state. This setting allows you to set a maximum time which a - camera may be suspended for before it automatically resumes - motion detection. This time can be extended by subsequent - suspend indications after the first so continuous camera - movement will also occur while the monitor is suspended. - "), - type => $types{integer}, - category => "config", - }, - # Deprecated, really no longer necessary - { - name => "ZM_OPT_REMOTE_CAMERAS", - default => "no", - description => "Are you going to use remote/networked cameras", - help => qqq(" - ZoneMinder can work with both local cameras, ie. those attached - physically to your computer and remote or network cameras. If - you will be using networked cameras select this option. - "), - type => $types{boolean}, - category => "hidden", - }, - # Deprecated, now set on a per monitor basis using the Method field - { - name => "ZM_NETCAM_REGEXPS", - default => "yes", - description => "Use regular expression matching with network cameras", - help => qqq(" - Traditionally ZoneMinder has used complex regular regular - expressions to handle the multitude of formats that network - cameras produce. In versions from 1.21.1 the default is to use - a simpler and faster built in pattern matching methodology. - This works well with most networks cameras but if you have - problems you can try the older, but more flexible, regular - expression based method by selecting this option. Note, to use - this method you must have libpcre installed on your system. - "), - requires => [ { name => "ZM_OPT_REMOTE_CAMERAS", value => "yes" } ], - type => $types{boolean}, - category => "hidden", - }, - { - name => "ZM_HTTP_VERSION", - default => "1.0", - description => "The version of HTTP that ZoneMinder will use to connect", - help => qqq(" - ZoneMinder can communicate with network cameras using either of - the HTTP/1.1 or HTTP/1.0 standard. A server will normally fall - back to the version it supports with no problem so this should - usually by left at the default. However it can be changed to - HTTP/1.0 if necessary to resolve particular issues. - "), - type => { - db_type =>"string", - hint =>"1.1|1.0", - pattern =>qr|^(1\.[01])$|, - format =>q( $1?$1:"" ) - }, - category => "network", - }, - { - name => "ZM_HTTP_UA", - default => "ZoneMinder", - description => "The user agent that ZoneMinder uses to identify itself", - help => qqq(" - When ZoneMinder communicates with remote cameras it will - identify itself using this string and it's version number. This - is normally sufficient, however if a particular cameras expects - only to communicate with certain browsers then this can be - changed to a different string identifying ZoneMinder as - Internet Explorer or Netscape etc. - "), - type => $types{string}, - category => "network", - }, - { - name => "ZM_HTTP_TIMEOUT", - default => "2500", - description => "How long ZoneMinder waits before giving up on images (milliseconds)", - help => qqq(" - When retrieving remote images ZoneMinder will wait for this - length of time before deciding that an image is not going to - arrive and taking steps to retry. This timeout is in - milliseconds (1000 per second) and will apply to each part of - an image if it is not sent in one whole chunk. - "), - type => $types{integer}, - category => "network", - }, - { - name => "ZM_MIN_RTP_PORT", - default => "40200", - description => "Minimum port that ZoneMinder will listen for RTP traffic on", - help => qqq(" - When ZoneMinder communicates with MPEG4 capable cameras using - RTP with the unicast method it must open ports for the camera - to connect back to for control and streaming purposes. This - setting specifies the minimum port number that ZoneMinder will - use. Ordinarily two adjacent ports are used for each camera, - one for control packets and one for data packets. This port - should be set to an even number, you may also need to open up a - hole in your firewall to allow cameras to connect back if you - wish to use unicasting. - "), - type => $types{integer}, - category => "network", - }, - { - name => "ZM_MAX_RTP_PORT", - default => "40499", - description => "Maximum port that ZoneMinder will listen for RTP traffic on", - help => qqq(" - When ZoneMinder communicates with MPEG4 capable cameras using - RTP with the unicast method it must open ports for the camera - to connect back to for control and streaming purposes. This - setting specifies the maximum port number that ZoneMinder will - use. Ordinarily two adjacent ports are used for each camera, - one for control packets and one for data packets. This port - should be set to an even number, you may also need to open up a - hole in your firewall to allow cameras to connect back if you - wish to use unicasting. You should also ensure that you have - opened up at least two ports for each monitor that will be - connecting to unicasting network cameras. - "), - type => $types{integer}, - category => "network", - }, - { - name => "ZM_OPT_FFMPEG", - default => "@OPT_FFMPEG@", - description => "Is the ffmpeg video encoder/decoder installed", - help => qqq(" - ZoneMinder can optionally encode a series of video images into - an MPEG encoded movie file for viewing, downloading or storage. - This option allows you to specify whether you have the ffmpeg - tools installed. Note that creating MPEG files can be fairly - CPU and disk intensive and is not a required option as events - can still be reviewed as video streams without it. - "), - type => $types{boolean}, - category => "images", - }, - { - name => "ZM_PATH_FFMPEG", - default => "@PATH_FFMPEG@", - description => "Path to (optional) ffmpeg mpeg encoder", - help => "This path should point to where ffmpeg has been installed.", - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], - type => $types{abs_path}, - category => "images", - }, - { - name => "ZM_FFMPEG_INPUT_OPTIONS", - default => "", - description => "Additional input options to ffmpeg", - help => qqq(" - Ffmpeg can take many options on the command line to control the - quality of video produced. This option allows you to specify - your own set that apply to the input to ffmpeg (options that - are given before the -i option). Check the ffmpeg documentation - for a full list of options which may be used here. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], - type => $types{string}, - category => "images", - }, - { - name => "ZM_FFMPEG_OUTPUT_OPTIONS", - default => "-r 25", - description => "Additional output options to ffmpeg", - help => qqq(" - Ffmpeg can take many options on the command line to control the - quality of video produced. This option allows you to specify - your own set that apply to the output from ffmpeg (options that - are given after the -i option). Check the ffmpeg documentation - for a full list of options which may be used here. The most - common one will often be to force an output frame rate - supported by the video encoder. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], - type => $types{string}, - category => "images", - }, - { - name => "ZM_FFMPEG_FORMATS", - default => "mpg mpeg wmv asf avi* mov swf 3gp**", - description => "Formats to allow for ffmpeg video generation", - help => qqq(" - Ffmpeg can generate video in many different formats. This - option allows you to list the ones you want to be able to - select. As new formats are supported by ffmpeg you can add them - here and be able to use them immediately. Adding a '*' after a - format indicates that this will be the default format used for - web video, adding '**' defines the default format for phone - video. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], - type => $types{string}, - category => "images", - }, - { - name => "ZM_FFMPEG_OPEN_TIMEOUT", - default => "10", - description => "Timeout in seconds when opening a stream.", - help => qqq(" - When Ffmpeg is opening a stream, it can take a long time before - failing; certain circumstances even seem to be able to lock - indefinitely. This option allows you to set a maximum time in - seconds to pass before closing the stream and trying to reopen - it again. - "), - requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], - type => $types{integer}, - category => "images", - }, - { - name => "ZM_LOG_LEVEL_SYSLOG", - default => "0", - description => "Save logging output to the system log", - help => qqq(" - ZoneMinder logging is now more more integrated between - components and allows you to specify the destination for - logging output and the individual levels for each. This option - lets you control the level of logging output that goes to the - system log. ZoneMinder binaries have always logged to the - system log but now scripts and web logging is also included. To - preserve the previous behaviour you should ensure this value is - set to Info or Warning. This option controls the maximum level - of logging that will be written, so Info includes Warnings and - Errors etc. To disable entirely, set this option to None. You - should use caution when setting this option to Debug as it can - affect severely affect system performance. If you want debug - you will also need to set a level and component below - "), - type => { - db_type =>"integer", - hint =>"None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "logging", - }, - { - name => "ZM_LOG_LEVEL_FILE", - default => "-5", - description => "Save logging output to component files", - help => qqq(" - ZoneMinder logging is now more more integrated between - components and allows you to specify the destination for - logging output and the individual levels for each. This option - lets you control the level of logging output that goes to - individual log files written by specific components. This is - how logging worked previously and although useful for tracking - down issues in specific components it also resulted in many - disparate log files. To preserve this behaviour you should - ensure this value is set to Info or Warning. This option - controls the maximum level of logging that will be written, so - Info includes Warnings and Errors etc. To disable entirely, set - this option to None. You should use caution when setting this - option to Debug as it can affect severely affect system - performance though file output has less impact than the other - options. If you want debug you will also need to set a level - and component below - "), - type => { - db_type =>"integer", - hint =>"None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "logging", - }, - { - name => "ZM_LOG_LEVEL_WEBLOG", - default => "-5", - description => "Save logging output to the weblog", - help => qqq(" - ZoneMinder logging is now more more integrated between - components and allows you to specify the destination for - logging output and the individual levels for each. This option - lets you control the level of logging output from the web - interface that goes to the httpd error log. Note that only web - logging from PHP and JavaScript files is included and so this - option is really only useful for investigating specific issues - with those components. This option controls the maximum level - of logging that will be written, so Info includes Warnings and - Errors etc. To disable entirely, set this option to None. You - should use caution when setting this option to Debug as it can - affect severely affect system performance. If you want debug - you will also need to set a level and component below - "), - type => { - db_type =>"integer", - hint =>"None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "logging", - }, - { - name => "ZM_LOG_LEVEL_DATABASE", - default => "0", - description => "Save logging output to the database", - help => qqq(" - ZoneMinder logging is now more more integrated between - components and allows you to specify the destination for - logging output and the individual levels for each. This option - lets you control the level of logging output that is written to - the database. This is a new option which can make viewing - logging output easier and more intuitive and also makes it - easier to get an overall impression of how the system is - performing. If you have a large or very busy system then it is - possible that use of this option may slow your system down if - the table becomes very large. Ensure you use the - LOG_DATABASE_LIMIT option to keep the table to a manageable - size. This option controls the maximum level of logging that - will be written, so Info includes Warnings and Errors etc. To - disable entirely, set this option to None. You should use - caution when setting this option to Debug as it can affect - severely affect system performance. If you want debug you will - also need to set a level and component below - "), - type => { - db_type =>"integer", - hint =>"None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "logging", - }, - { - name => "ZM_LOG_DATABASE_LIMIT", - default => "7 day", - description => "Maximum number of log entries to retain", - help => qqq(" - If you are using database logging then it is possible to - quickly build up a large number of entries in the Logs table. - This option allows you to specify how many of these entries are - kept. If you set this option to a number greater than zero then - that number is used to determine the maximum number of rows, - less than or equal to zero indicates no limit and is not - recommended. You can also set this value to time values such as - ' day' which will limit the log entries to those newer than - that time. You can specify 'hour', 'day', 'week', 'month' and - 'year', note that the values should be singular (no 's' at the - end). The Logs table is pruned periodically so it is possible - for more than the expected number of rows to be present briefly - in the meantime. - "), - type => $types{string}, - category => "logging", - }, - { - name => "ZM_LOG_DEBUG", - default => "no", - description => "Switch debugging on", - help => qqq(" - ZoneMinder components usually support debug logging available - to help with diagnosing problems. Binary components have - several levels of debug whereas more other components have only - one. Normally this is disabled to minimise performance - penalties and avoid filling logs too quickly. This option lets - you switch on other options that allow you to configure - additional debug information to be output. Components will pick - up this instruction when they are restarted. - "), - type => $types{boolean}, - category => "logging", - }, - { - name => "ZM_LOG_DEBUG_TARGET", - default => "", - description => "What components should have extra debug enabled", - help => qqq(" - There are three scopes of debug available. Leaving this option - blank means that all components will use extra debug (not - recommended). Setting this option to '_', e.g. _zmc, - will limit extra debug to that component only. Setting this - option to '__', e.g. '_zmc_m1' will limit - extra debug to that instance of the component only. This is - ordinarily what you probably want to do. To debug scripts use - their names without the .pl extension, e.g. '_zmvideo' and to - debug issues with the web interface use '_web'. You can specify - multiple targets by separating them with '|' characters. - "), - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], - type => $types{string}, - category => "logging", - }, - { - name => "ZM_LOG_DEBUG_LEVEL", - default => 1, - description => "What level of extra debug should be enabled", - help => qqq(" - There are 9 levels of debug available, with higher numbers - being more debug and level 0 being no debug. However not all - levels are used by all components. Also if there is debug at a - high level it is usually likely to be output at such a volume - that it may obstruct normal operation. For this reason you - should set the level carefully and cautiously until the degree - of debug you wish to see is present. Scripts and the web - interface only have one level so this is an on/off type option - for them. - "), - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], - type => { - db_type =>"integer", - hint =>"1|2|3|4|5|6|7|8|9", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "logging", - }, - { - name => "ZM_LOG_DEBUG_FILE", - default => "@ZM_LOGDIR@/zm_debug.log+", - description => "Where extra debug is output to", - help => qqq(" - This option allows you to specify a different target for debug - output. All components have a default log file which will - norally be in /tmp or /var/log and this is where debug will be - written to if this value is empty. Adding a path here will - temporarily redirect debug, and other logging output, to this - file. This option is a simple filename and you are debugging - several components then they will all try and write to the same - file with undesirable consequences. Appending a '+' to the - filename will cause the file to be created with a '.' - suffix containing your process id. In this way debug from each - run of a component is kept separate. This is the recommended - setting as it will also prevent subsequent runs from - overwriting the same log. You should ensure that permissions - are set up to allow writing to the file and directory specified - here. - "), - requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], - type => $types{string}, - category => "logging", - }, - { - name => "ZM_LOG_CHECK_PERIOD", - default => "900", - description => "Time period used when calculating overall system health", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to indicate what period of historical - events are used in this calculation. This value is expressed in - seconds and is ignored if LOG_LEVEL_DATABASE is set to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_LOG_ALERT_WAR_COUNT", - default => "1", - description => "Number of warnings indicating system alert state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many warnings must have - occurred within the defined time period to generate an overall - system alert state. A value of zero means warnings are not - considered. This value is ignored if LOG_LEVEL_DATABASE is set - to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_LOG_ALERT_ERR_COUNT", - default => "1", - description => "Number of errors indicating system alert state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many errors must have - occurred within the defined time period to generate an overall - system alert state. A value of zero means errors are not - considered. This value is ignored if LOG_LEVEL_DATABASE is set - to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_LOG_ALERT_FAT_COUNT", - default => "0", - description => "Number of fatal error indicating system alert state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many fatal errors - (including panics) must have occurred within the defined time - period to generate an overall system alert state. A value of - zero means fatal errors are not considered. This value is - ignored if LOG_LEVEL_DATABASE is set to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_LOG_ALARM_WAR_COUNT", - default => "100", - description => "Number of warnings indicating system alarm state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many warnings must have - occurred within the defined time period to generate an overall - system alarm state. A value of zero means warnings are not - considered. This value is ignored if LOG_LEVEL_DATABASE is set - to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_LOG_ALARM_ERR_COUNT", - default => "10", - description => "Number of errors indicating system alarm state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many errors must have - occurred within the defined time period to generate an overall - system alarm state. A value of zero means errors are not - considered. This value is ignored if LOG_LEVEL_DATABASE is set - to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_LOG_ALARM_FAT_COUNT", - default => "1", - description => "Number of fatal error indicating system alarm state", - help => qqq(" - When ZoneMinder is logging events to the database it can - retrospectively examine the number of warnings and errors that - have occurred to calculate an overall state of system health. - This option allows you to specify how many fatal errors - (including panics) must have occurred within the defined time - period to generate an overall system alarm state. A value of - zero means fatal errors are not considered. This value is - ignored if LOG_LEVEL_DATABASE is set to None. - "), - type => $types{integer}, - category => "logging", - }, - { - name => "ZM_RECORD_EVENT_STATS", - default => "yes", - description => "Record event statistical information, switch off if too slow", - help => qqq(" - This version of ZoneMinder records detailed information about - events in the Stats table. This can help in profiling what the - optimum settings are for Zones though this is tricky at - present. However in future releases this will be done more - easily and intuitively, especially with a large sample of - events. The default option of 'yes' allows this information to - be collected now in readiness for this but if you are concerned - about performance you can switch this off in which case no - Stats information will be saved. - "), - type => $types{boolean}, - category => "logging", - }, - { - name => "ZM_RECORD_DIAG_IMAGES", - default => "no", - description => "Record intermediate alarm diagnostic images, can be very slow", - help => qqq(" - In addition to recording event statistics you can also record - the intermediate diagnostic images that display the results of - the various checks and processing that occur when trying to - determine if an alarm event has taken place. There are several - of these images generated for each frame and zone for each - alarm or alert frame so this can have a massive impact on - performance. Only switch this setting on for debug or analysis - purposes and remember to switch it off again once no longer - required. - "), - type => $types{boolean}, - category => "logging", - }, - { - name => "ZM_DUMP_CORES", - default => "no", - description => "Create core files on unexpected process failure.", - help => qqq(" - When an unrecoverable error occurs in a ZoneMinder binary - process is has traditionally been trapped and the details - written to logs to aid in remote analysis. However in some - cases it is easier to diagnose the error if a core file, which - is a memory dump of the process at the time of the error, is - created. This can be interactively analysed in the debugger and - may reveal more or better information than that available from - the logs. This option is recommended for advanced users only - otherwise leave at the default. Note using this option to - trigger core files will mean that there will be no indication - in the binary logs that a process has died, they will just - stop, however the zmdc log will still contain an entry. Also - note that you may have to explicitly enable core file creation - on your system via the 'ulimit -c' command or other means - otherwise no file will be created regardless of the value of - this option. - "), - type => $types{boolean}, - category => "logging", - }, - { - name => "ZM_PATH_MAP", - default => "/dev/shm", - description => "Path to the mapped memory files that that ZoneMinder can use", - help => qqq(" - ZoneMinder has historically used IPC shared memory for shared - data between processes. This has it's advantages and - limitations. This version of ZoneMinder can use an alternate - method, mapped memory, instead with can be enabled with the - --enable--mmap directive to configure. This requires less - system configuration and is generally more flexible. However it - requires each shared data segment to map onto a filesystem - file. This option indicates where those mapped files go. You - should ensure that this location has sufficient space for these - files and for the best performance it should be a tmpfs file - system or ramdisk otherwise disk access may render this method - slower than the regular shared memory one. - "), - type => $types{abs_path}, - category => "paths", - }, - { - name => "ZM_PATH_SOCKS", - default => "@ZM_SOCKDIR@", - description => "Path to the various Unix domain socket files that ZoneMinder uses", - help => qqq(" - ZoneMinder generally uses Unix domain sockets where possible. - This reduces the need for port assignments and prevents - external applications from possibly compromising the daemons. - However each Unix socket requires a .sock file to be created. - This option indicates where those socket files go. - "), - type => $types{abs_path}, - category => "paths", - }, - { - name => "ZM_PATH_LOGS", - default => "@ZM_LOGDIR@", - description => "Path to the various logs that the ZoneMinder daemons generate", - help => qqq(" - There are various daemons that are used by ZoneMinder to - perform various tasks. Most generate helpful log files and this - is where they go. They can be deleted if not required for - debugging. - "), - type => $types{abs_path}, - category => "paths", - }, - { - name => "ZM_PATH_SWAP", - default => "@ZM_TMPDIR@", - description => "Path to location for temporary swap images used in streaming", - help => qqq(" - Buffered playback requires temporary swap images to be stored - for each instance of the streaming daemons. This option - determines where these images will be stored. The images will - actually be stored in sub directories beneath this location and - will be automatically cleaned up after a period of time. - "), - type => $types{abs_path}, - category => "paths", - }, - { - name => "ZM_PATH_ARP", - default => "", - description => "Path to a supported ARP tool", - help => qqq(" - The camera probe function uses Address Resolution Protocol in - order to find known devices on the network. Optionally supply - the full path to \"ip neigh\", \"arp -a\", or any other tool on - your system that returns ip/mac address pairs. If this field is - left empty, ZoneMinder will search for the command \"arp\" and - attempt to use that. - "), - type => $types{abs_path}, - category => "paths", - }, - { - name => "ZM_WEB_TITLE_PREFIX", - default => "ZM", - description => "The title prefix displayed on each window", - help => qqq(" - If you have more than one installation of ZoneMinder it can be - helpful to display different titles for each one. Changing this - option allows you to customise the window titles to include - further information to aid identification. - "), - type => $types{string}, - category => "web", - }, - { - name => "ZM_WEB_CONSOLE_BANNER", - default => "", - description => "Arbitrary text message near the top of the console", - help => qqq(" - Allows the administrator to place an arbitrary text message - near the top of the web console. This is useful for the developers - to display a message which indicates the running instance of - ZoneMinder is a development snapshot, but it can also be used for - any other purpose as well. - "), - type => $types{string}, - category => "web", - }, - { - name => "ZM_WEB_RESIZE_CONSOLE", - default => "yes", - description => "Should the console window resize itself to fit", - help => qqq(" - Traditionally the main ZoneMinder web console window has - resized itself to shrink to a size small enough to list only - the monitors that are actually present. This is intended to - make the window more unobtrusize but may not be to everyones - tastes, especially if opened in a tab in browsers which support - this kind if layout. Switch this option off to have the console - window size left to the users preference - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_WEB_ID_ON_CONSOLE", - default => "no", - description => "Should the console list the monitor id", - help => qqq("Some find it useful to have the id always visible - on the console. This option will add a column listing it. - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_WEB_POPUP_ON_ALARM", - default => "yes", - description => "Should the monitor window jump to the top if an alarm occurs", - help => qqq(" - When viewing a live monitor stream you can specify whether you - want the window to pop to the front if an alarm occurs when the - window is minimised or behind another window. This is most - useful if your monitors are over doors for example when they - can pop up if someone comes to the doorway. - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_OPT_X10", - default => "no", - description => "Support interfacing with X10 devices", - help => qqq(" - If you have an X10 Home Automation setup in your home you can - use ZoneMinder to initiate or react to X10 signals if your - computer has the appropriate interface controller. This option - indicates whether X10 options will be available in the browser - client. - "), - type => $types{boolean}, - category => "x10", - }, - { - name => "ZM_X10_DEVICE", - default => "/dev/ttyS0", - description => "What device is your X10 controller connected on", - requires => [ { name => "ZM_OPT_X10", value => "yes" } ], - help => qqq(" - If you have an X10 controller device (e.g. XM10U) connected to - your computer this option details which port it is connected on, - the default of /dev/ttyS0 maps to serial or com port 1. - "), - type => $types{abs_path}, - category => "x10", - }, - { - name => "ZM_X10_HOUSE_CODE", - default => "A", - description => "What X10 house code should be used", - requires => [ { name => "ZM_OPT_X10", value => "yes" } ], - help => qqq(" - X10 devices are grouped together by identifying them as all - belonging to one House Code. This option details what that is. - It should be a single letter between A and P. - "), - type => { db_type=>"string", hint=>"A-P", pattern=>qr|^([A-P])|i, format=>q( uc($1) ) }, - category => "x10", - }, - { - name => "ZM_X10_DB_RELOAD_INTERVAL", - default => "60", - description => "How often (in seconds) the X10 daemon reloads the monitors from the database", - requires => [ { name => "ZM_OPT_X10", value => "yes" } ], - help => qqq(" - The zmx10 daemon periodically checks the database to find out - what X10 events trigger, or result from, alarms. This option - determines how frequently this check occurs, unless you change - this area frequently this can be a fairly large value. - "), - type => $types{integer}, - category => "x10", - }, - { - name => "ZM_WEB_SOUND_ON_ALARM", - default => "no", - description => "Should the monitor window play a sound if an alarm occurs", - help => qqq(" - When viewing a live monitor stream you can specify whether you - want the window to play a sound to alert you if an alarm - occurs. - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_WEB_ALARM_SOUND", - default => "", - description => "The sound to play on alarm, put this in the sounds directory", - help => qqq(" - You can specify a sound file to play if an alarm occurs whilst - you are watching a live monitor stream. So long as your browser - understands the format it does not need to be any particular - type. This file should be placed in the sounds directory - defined earlier. - "), - type => $types{file}, - requires => [ { name => "ZM_WEB_SOUND_ON_ALARM", value => "yes" } ], - category => "web", - }, - { - name => "ZM_WEB_COMPACT_MONTAGE", - default => "no", - description => "Compact the montage view by removing extra detail", - help => qqq(" - The montage view shows the output of all of your active - monitors in one window. This include a small menu and status - information for each one. This can increase the web traffic and - make the window larger than may be desired. Setting this option - on removes all this extraneous information and just displays - the images. - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_OPT_FAST_DELETE", - default => "no", - description => "Delete only event database records for speed", - help => qqq(" - 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. Note that this feature is less relevant - with modern hardware. Recommend this feature be left off. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_STRICT_VIDEO_CONFIG", - default => "yes", - description => "Allow errors in setting video config to be fatal", - help => qqq(" - With some video devices errors can be reported in setting the - various video attributes when in fact the operation was - successful. Switching this option off will still allow these - errors to be reported but will not cause them to kill the video - capture daemon. Note however that doing this will cause all - errors to be ignored including those which are genuine and - which may cause the video capture to not function correctly. - Use this option with caution. - "), - type => $types{boolean}, - category => "config", - }, - { - name => 'ZM_LD_PRELOAD', - default => '', - description => "Path to library to preload before launching daemons", - help => qqq("Some older cameras require the use of the v4l1 compat - library. This setting allows the setting of the path - to the library, so that it can be loaded by zmdc.pl - before launching zmc."), - type => $types{abs_path}, - category => 'config', - }, - { - name => "ZM_SIGNAL_CHECK_POINTS", - default => "10", - description => "How many points in a captured image to check for signal loss", - help => qqq(" - For locally attached video cameras ZoneMinder can check for - signal loss by looking at a number of random points on each - captured image. If all of these points are set to the same - fixed colour then the camera is assumed to have lost signal. - When this happens any open events are closed and a short one - frame signal loss event is generated, as is another when the - signal returns. This option defines how many points on each - image to check. Note that this is a maximum, any points found - to not have the check colour will abort any further checks so - in most cases on a couple of points will actually be checked. - Network and file based cameras are never checked. - "), - type => $types{integer}, - category => "config", - }, - { - name => "ZM_V4L_MULTI_BUFFER", - default => "yes", - description => "Use more than one buffer for Video 4 Linux devices", - help => qqq(" - Performance when using Video 4 Linux devices is usually best if - multiple buffers are used allowing the next image to be - captured while the previous one is being processed. If you have - multiple devices on a card sharing one input that requires - switching then this approach can sometimes cause frames from - one source to be mixed up with frames from another. Switching - this option off prevents multi buffering resulting in slower - but more stable image capture. This option is ignored for - non-local cameras or if only one input is present on a capture - chip. This option addresses a similar problem to the - ZM_CAPTURES_PER_FRAME option and you should normally change the - value of only one of the options at a time. If you have - different capture cards that need different values you can - ovveride them in each individual monitor on the source page. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_CAPTURES_PER_FRAME", - default => "1", - description => "How many images are captured per returned frame, for shared local cameras", - help => qqq(" - If you are using cameras attached to a video capture card which - forces multiple inputs to share one capture chip, it can - sometimes produce images with interlaced frames reversed - resulting in poor image quality and a distinctive comb edge - appearance. Increasing this setting allows you to force - additional image captures before one is selected as the - captured frame. This allows the capture hardware to 'settle - down' and produce better quality images at the price of lesser - capture rates. This option has no effect on (a) network - cameras, or (b) where multiple inputs do not share a capture - chip. This option addresses a similar problem to the - ZM_V4L_MULTI_BUFFER option and you should normally change the - value of only one of the options at a time. If you have - different capture cards that need different values you can - ovveride them in each individual monitor on the source page. - "), - type => $types{integer}, - category => "config", - }, - { - name => "ZM_FILTER_RELOAD_DELAY", - default => "300", - description => "How often (in seconds) filters are reloaded in zmfilter", - help => qqq(" - ZoneMinder allows you to save filters to the database which - allow events that match certain criteria to be emailed, deleted - or uploaded to a remote machine etc. The zmfilter daemon loads - these and does the actual operation. This option determines how - often the filters are reloaded from the database to get the - latest versions or new filters. If you don't change filters - very often this value can be set to a large value. - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_FILTER_EXECUTE_INTERVAL", - default => "60", - description => "How often (in seconds) to run automatic saved filters", - help => qqq(" - ZoneMinder allows you to save filters to the database which - allow events that match certain criteria to be emailed, deleted - or uploaded to a remote machine etc. The zmfilter daemon loads - these and does the actual operation. This option determines how - often the filters are executed on the saved event in the - database. If you want a rapid response to new events this - should be a smaller value, however this may increase the - overall load on the system and affect performance of other - elements. - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_OPT_UPLOAD", - default => "no", - description => "Should ZoneMinder support uploading events from filters", - help => qqq(" - In ZoneMinder you can create event filters that specify whether - events that match certain criteria should be uploaded to a - remote server for archiving. This option specifies whether this - functionality should be available - "), - type => $types{boolean}, - category => "upload", - }, - { - name => "ZM_UPLOAD_ARCH_FORMAT", - default => "tar", - description => "What format the uploaded events should be created in.", - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - help => qqq(" - Uploaded events may be stored in either .tar or .zip format, - this option specifies which. Note that to use this you will - need to have the Archive::Tar and/or Archive::Zip perl modules - installed. - "), - type => { - db_type =>"string", - hint =>"tar|zip", - pattern =>qr|^([tz])|i, - format =>q( $1 =~ /^t/ ? "tar" : "zip" ) - }, - category => "upload", - }, - { - name => "ZM_UPLOAD_ARCH_COMPRESS", - default => "no", - description => "Should archive files be compressed", - help => qqq(" - When the archive files are created they can be compressed. - However in general since the images are compressed already this - saves only a minimal amount of space versus utilising more CPU - in their creation. Only enable if you have CPU to waste and are - limited in disk space on your remote server or bandwidth. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{boolean}, - category => "upload", - }, - { - name => "ZM_UPLOAD_ARCH_ANALYSE", - default => "no", - description => "Include the analysis files in the archive", - help => qqq(" - When the archive files are created they can contain either just - the captured frames or both the captured frames and, for frames - that caused an alarm, the analysed image with the changed area - highlighted. This option controls files are included. Only - include analysed frames if you have a high bandwidth connection - to the remote server or if you need help in figuring out what - caused an alarm in the first place as archives with these files - in can be considerably larger. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{boolean}, - category => "upload", - }, - { - name => "ZM_UPLOAD_PROTOCOL", - default => "ftp", - description => "What protocol to use to upload events", - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - help => qqq(" - ZoneMinder can upload events to a remote server using either - FTP or SFTP. Regular FTP is widely supported but not - necessarily very secure whereas SFTP (Secure FTP) runs over an - ssh connection and so is encrypted and uses regular ssh ports. - Note that to use this you will need to have the appropriate - perl module, either Net::FTP or Net::SFTP installed depending - on your choice. - "), - type => { - db_type =>"string", - hint =>"ftp|sftp", - pattern =>qr|^([tz])|i, - format =>q( $1 =~ /^f/ ? "ftp" : "sftp" ) - }, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_HOST", - default => "", - description => "The remote server to upload to", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the name, or ip - address, of the server to use. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{hostname}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_HOST", - default => "", - description => "The remote server to upload events to", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the name, or ip address, - of the server to use. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{hostname}, - category => "upload", - }, - { - name => "ZM_UPLOAD_PORT", - default => "", - description => "The port on the remote upload server, if not the default (SFTP only)", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. If you are using the SFTP protocol then this - option allows you to specify a particular port to use for - connection. If this option is left blank then the default, port - 22, is used. This option is ignored for FTP uploads. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{integer}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_USER", - default => "", - description => "Your ftp username", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the username that - ZoneMinder should use to log in for ftp transfer. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{alphanum}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_USER", - default => "", - description => "Remote server username", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the username that - ZoneMinder should use to log in for transfer. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{alphanum}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_PASS", - default => "", - description => "Your ftp password", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the password that - ZoneMinder should use to log in for ftp transfer. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{string}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_PASS", - default => "", - description => "Remote server password", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the password that - ZoneMinder should use to log in for transfer. If you are using - certificate based logins for SFTP servers you can leave this - option blank. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{string}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_LOC_DIR", - default => "@ZM_TMPDIR@", - description => "The local directory in which to create upload files", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the local directory - that ZoneMinder should use for temporary upload files. These - are files that are created from events, uploaded and then - deleted. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{abs_path}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_LOC_DIR", - default => "@ZM_TMPDIR@", - description => "The local directory in which to create upload files", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the local directory that - ZoneMinder should use for temporary upload files. These are - files that are created from events, uploaded and then deleted. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{abs_path}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_REM_DIR", - default => "", - description => "The remote directory to upload to", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the remote directory - that ZoneMinder should use to upload event files to. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{rel_path}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_REM_DIR", - default => "", - description => "The remote directory to upload to", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the remote directory - that ZoneMinder should use to upload event files to. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{rel_path}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_TIMEOUT", - default => "120", - description => "How long to allow the transfer to take for each file", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates the maximum ftp - inactivity timeout (in seconds) that should be tolerated before - ZoneMinder determines that the transfer has failed and closes - down the connection. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{integer}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_TIMEOUT", - default => "120", - description => "How long to allow the transfer to take for each file", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. This option indicates the maximum inactivity - timeout (in seconds) that should be tolerated before ZoneMinder - determines that the transfer has failed and closes down the - connection. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{integer}, - category => "upload", - }, - { - name => "ZM_UPLOAD_STRICT", - default => "no", - description => "Require strict host key checking for SFTP uploads", - help => qqq(" - You can require SFTP uploads to verify the host key of the remote server - for protection against man-in-the-middle attacks. You will need to add the - server's key to the known_hosts file. On most systems, this will be - ~/.ssh/known_hosts, where ~ is the home directory of the web server running - ZoneMinder. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{boolean}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_PASSIVE", - default => "yes", - description => "Use passive ftp when uploading", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. This option indicates that ftp transfers - should be done in passive mode. This uses a single connection - for all ftp activity and, whilst slower than active transfers, - is more robust and likely to work from behind filewalls. This - option is ignored for SFTP transfers. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - help => qqq(" - If your computer is behind a firewall or proxy you may need to - set FTP to passive mode. In fact for simple transfers it makes - little sense to do otherwise anyway but you can set this to - 'No' if you wish. - "), - type => $types{boolean}, - category => "upload", - }, - { - name => "ZM_UPLOAD_FTP_DEBUG", - default => "no", - description => "Switch ftp debugging on", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote ftp server. If you are having (or expecting) troubles - with uploading events then setting this to 'yes' permits - additional information to be included in the zmfilter log file. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{boolean}, - category => "hidden", - }, - { - name => "ZM_UPLOAD_DEBUG", - default => "no", - description => "Switch upload debugging on", - help => qqq(" - You can use filters to instruct ZoneMinder to upload events to - a remote server. If you are having (or expecting) troubles with - uploading events then setting this to 'yes' permits additional - information to be generated by the underlying transfer modules - and included in the logs. - "), - requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], - type => $types{boolean}, - category => "upload", - }, - { - name => "ZM_OPT_EMAIL", - default => "no", - description => "Should ZoneMinder email you details of events that match corresponding filters", - help => qqq(" - In ZoneMinder you can create event filters that specify whether - events that match certain criteria should have their details - emailed to you at a designated email address. This will allow - you to be notified of events as soon as they occur and also to - quickly view the events directly. This option specifies whether - this functionality should be available. The email created with - this option can be any size and is intended to be sent to a - regular email reader rather than a mobile device. - "), - type => $types{boolean}, - category => "mail", - }, - { - name => "ZM_EMAIL_ADDRESS", - default => "", - description => "The email address to send matching event details to", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" - This option is used to define the email address that any events - that match the appropriate filters will be sent to. - "), - type => $types{email}, - category => "mail", - }, - { - name => "ZM_EMAIL_TEXT", - default => 'subject = "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)" -body = " -Hello, + { + name => "ZM_OPT_GOOG_RECAPTCHA_SITEKEY", + default => "...Insert your recaptcha site-key here...", + description => "Your recaptcha site-key", + help => qqq("You need to generate your keys from + the Google reCaptcha website. + Please refer to https://www.google.com/recaptcha/ + for more details. + "), + requires => [ + {name=>"ZM_OPT_USE_GOOG_RECAPTCHA", value=>"yes"} + ], + type => $types{string}, + category => "system", + }, + { + name => "ZM_OPT_GOOG_RECAPTCHA_SECRETKEY", + default => "...Insert your recaptcha secret-key here...", + description => "Your recaptcha secret-key", + help => qqq("You need to generate your keys from + the Google reCaptcha website. + Please refer to https://www.google.com/recaptcha/ + for more details. + "), + requires => [ + {name=>"ZM_OPT_USE_GOOG_RECAPTCHA", value=>"yes"} + ], + type => $types{string}, + category => "system", + }, -An alarm has been detected on your installation of the ZoneMinder. -The details are as follows :- + { + name => "ZM_DIR_EVENTS", + default => "events", + description => "Directory where events are stored", + help => qqq(" + This is the path to the events directory where all the event + images and other miscellaneous files are stored. CAUTION: The + directory you specify here cannot be outside the web root. This + is a common mistake. Most users should never change this value. + If you intend to record events to a second disk or network + share, then you should mount the drive or share directly to the + ZoneMinder events folder or follow the instructions in the + ZoneMinder Wiki titled Using a dedicated Hard Drive. + "), + type => $types{directory}, + category => "paths", + }, + { + name => "ZM_USE_DEEP_STORAGE", + default => "yes", + description => "Use a deep filesystem hierarchy for events", + help => qqq(" + This option is now the default for new ZoneMinder systems and + should not be changed. Previous versions of ZoneMinder stored + all events for a monitor under one folder. Enabling + USE_DEEP_STORAGE causes ZoneMinder to store events under a + folder structure that follows year/month/day/hour/min/second. + Storing events this way avoids the limitation of storing more + than 32k files in a single folder inherent in some filesystems. + It is important to note that you cannot simply change this + option. You must stop zoneminder, enable USE_DEEP_STORAGE, and + then run \"sudo zmupdate.pl --migrate-events\". FAILURE TO DO + SO WILL RESULT IN LOSS OF YOUR DATA! Consult the ZoneMinder + WiKi for further details. + "), + type => $types{boolean}, + category => "hidden", + }, + { + name => "ZM_DIR_IMAGES", + default => "images", + description => "Directory where the images that the ZoneMinder client generates are stored", + help => qqq(" + ZoneMinder generates a myriad of images, mostly of which are + associated with events. For those that aren't this is where + they go. CAUTION: The directory you specify here cannot be + outside the web root. This is a common mistake. Most users + should never change this value. If you intend to save images to + a second disk or network share, then you should mount the drive + or share directly to the ZoneMinder images folder or follow the + instructions in the ZoneMinder Wiki titled Using a dedicated + Hard Drive. + "), + type => $types{directory}, + category => "paths", + }, + { + name => "ZM_DIR_SOUNDS", + default => "sounds", + description => "Directory to the sounds that the ZoneMinder client can use", + help => qqq(" + ZoneMinder can optionally play a sound file when an alarm is + detected. This indicates where to look for this file. CAUTION: + The directory you specify here cannot be outside the web root. + Most users should never change this value. + "), + type => $types{directory}, + category => "paths", + }, + { + name => "ZM_PATH_ZMS", + default => "/cgi-bin/nph-zms", + description => "Web path to zms streaming server", + help => qqq(" + The ZoneMinder streaming server is required to send streamed + images to your browser. It will be installed into the cgi-bin + path given at configuration time. This option determines what + the web path to the server is rather than the local path on + your machine. Ordinarily the streaming server runs in + parser-header mode however if you experience problems with + streaming you can change this to non-parsed-header (nph) mode + by changing 'zms' to 'nph-zms'. + "), + type => $types{rel_path}, + category => "paths", + }, + { + name => "ZM_COLOUR_JPEG_FILES", + default => "no", + description => "Colourise greyscale JPEG files", + help => qqq(" + Cameras that capture in greyscale can write their captured + images to jpeg files with a corresponding greyscale colour + space. This saves a small amount of disk space over colour + ones. However some tools such as ffmpeg either fail to work + with this colour space or have to convert it beforehand. + Setting this option to yes uses up a little more space but + makes creation of MPEG files much faster. + "), + type => $types{boolean}, + category => "images", + }, + { + name => "ZM_ADD_JPEG_COMMENTS", + default => "no", + description => "Add jpeg timestamp annotations as file header comments", + help => qqq(" + JPEG files may have a number of extra fields added to the file + header. The comment field may have any kind of text added. This + options allows you to have the same text that is used to + annotate the image additionally included as a file header + comment. If you archive event images to other locations this + may help you locate images for particular events or times if + you use software that can read comment headers. + "), + type => $types{boolean}, + category => "images", + }, + { + name => "ZM_JPEG_FILE_QUALITY", + default => "70", + description => "Set the JPEG quality setting for the saved event files (1-100)", + help => qqq(" + When ZoneMinder detects an event it will save the images + associated with that event to files. These files are in the + JPEG format and can be viewed or streamed later. This option + specifies what image quality should be used to save these + files. A higher number means better quality but less + compression so will take up more disk space and take longer to + view over a slow connection. By contrast a low number means + smaller, quicker to view, files but at the price of lower + quality images. This setting applies to all images written + except if the capture image has caused an alarm and the alarm + file quality option is set at a higher value when that is used + instead. + "), + type => $types{integer}, + category => "images", + }, + { + name => "ZM_JPEG_ALARM_FILE_QUALITY", + default => "0", + description => "Set the JPEG quality setting for the saved event files during an alarm (1-100)", + help => qqq(" + This value is equivalent to the regular jpeg file quality + setting above except that it only applies to images saved while + in an alarm state and then only if this value is set to a + higher quality setting than the ordinary file setting. If set + to a lower value then it is ignored. Thus leaving it at the + default of 0 effectively means to use the regular file quality + setting for all saved images. This is to prevent acccidentally + saving important images at a worse quality setting. + "), + type => $types{integer}, + category => "images", + }, + # Deprecated, now stream quality + { + name => "ZM_JPEG_IMAGE_QUALITY", + default => "70", + description => "Set the JPEG quality setting for the streamed 'live' images (1-100)", + help => qqq(" + When viewing a 'live' stream for a monitor ZoneMinder will grab + an image from the buffer and encode it into JPEG format before + sending it. This option specifies what image quality should be + used to encode these images. A higher number means better + quality but less compression so will take longer to view over a + slow connection. By contrast a low number means quicker to view + images but at the price of lower quality images. This option + does not apply when viewing events or still images as these are + usually just read from disk and so will be encoded at the + quality specified by the previous options. + "), + type => $types{integer}, + category => "hidden", + }, + { + name => "ZM_JPEG_STREAM_QUALITY", + default => "70", + description => "Set the JPEG quality setting for the streamed 'live' images (1-100)", + help => qqq(" + When viewing a 'live' stream for a monitor ZoneMinder will grab + an image from the buffer and encode it into JPEG format before + sending it. This option specifies what image quality should be + used to encode these images. A higher number means better + quality but less compression so will take longer to view over a + slow connection. By contrast a low number means quicker to view + images but at the price of lower quality images. This option + does not apply when viewing events or still images as these are + usually just read from disk and so will be encoded at the + quality specified by the previous options. + "), + type => $types{integer}, + category => "images", + }, + { + name => "ZM_MPEG_TIMED_FRAMES", + default => "yes", + description => "Tag video frames with a timestamp for more realistic streaming", + help => qqq(" + When using streamed MPEG based video, either for live monitor + streams or events, ZoneMinder can send the streams in two ways. + If this option is selected then the timestamp for each frame, + taken from it's capture time, is included in the stream. This + means that where the frame rate varies, for instance around an + alarm, the stream will still maintain it's 'real' timing. If + this option is not selected then an approximate frame rate is + calculated and that is used to schedule frames instead. This + option should be selected unless you encounter problems with + your preferred streaming method. + "), + type => $types{boolean}, + category => "images", + }, + { + name => "ZM_MPEG_LIVE_FORMAT", + default => "swf", + description => "What format 'live' video streams are played in", + help => qqq(" + When using MPEG mode ZoneMinder can output live video. However + what formats are handled by the browser varies greatly between + machines. This option allows you to specify a video format + using a file extension format, so you would just enter the + extension of the file type you would like and the rest is + determined from that. The default of 'asf' works well under + Windows with Windows Media Player but I'm currently not sure + what, if anything, works on a Linux platform. If you find out + please let me know! If this option is left blank then live + streams will revert to being in motion jpeg format + "), + type => $types{string}, + category => "images", + }, + { + name => "ZM_MPEG_REPLAY_FORMAT", + default => "swf", + description => "What format 'replay' video streams are played in", + help => qqq(" + When using MPEG mode ZoneMinder can replay events in encoded + video format. However what formats are handled by the browser + varies greatly between machines. This option allows you to + specify a video format using a file extension format, so you + would just enter the extension of the file type you would like + and the rest is determined from that. The default of 'asf' + works well under Windows with Windows Media Player and 'mpg', + or 'avi' etc should work under Linux. If you know any more then + please let me know! If this option is left blank then live + streams will revert to being in motion jpeg format + "), + type => $types{string}, + category => "images", + }, + { + name => "ZM_RAND_STREAM", + default => "yes", + description => "Add a random string to prevent caching of streams", + help => qqq(" + Some browsers can cache the streams used by ZoneMinder. In + order to prevent his a harmless random string can be appended + to the url to make each invocation of the stream appear unique. + "), + type => $types{boolean}, + category => "images", + }, + { + name => "ZM_OPT_CAMBOZOLA", + default => "no", + description => "Is the (optional) cambozola java streaming client installed", + help => qqq(" + Cambozola is a handy low fat cheese flavoured Java applet that + ZoneMinder uses to view image streams on browsers such as + Internet Explorer that don't natively support this format. If + 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. + "), + type => $types{boolean}, + category => "images", + }, + { + name => "ZM_PATH_CAMBOZOLA", + default => "cambozola.jar", + description => "Web path to (optional) cambozola java streaming client", + help => qqq(" + Cambozola is a handy low fat cheese flavoured Java applet that + ZoneMinder uses to view image streams on browsers such as + Internet Explorer that don't natively support this format. If + 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 + installed in the same directory as the ZoneMinder web client + files. + "), + requires => [ { name=>"ZM_OPT_CAMBOZOLA", value=>"yes" } ], + type => $types{rel_path}, + category => "images", + }, + { + name => "ZM_RELOAD_CAMBOZOLA", + default => "0", + description => "After how many seconds should Cambozola be reloaded in live view", + help => qqq(" + Cambozola allows for the viewing of streaming MJPEG however it + caches the entire stream into cache space on the computer, + setting this to a number > 0 will cause it to automatically + reload after that many seconds to avoid filling up a hard + drive. + "), + type => $types{integer}, + category => "images", + }, + { + name => "ZM_TIMESTAMP_ON_CAPTURE", + default => "yes", + description => "Timestamp images as soon as they are captured", + help => qqq(" + ZoneMinder can add a timestamp to images in two ways. The + default method, when this option is set, is that each image is + timestamped immediately when captured and so the image held in + memory is marked right away. The second method does not + timestamp the images until they are either saved as part of an + event or accessed over the web. The timestamp used in both + methods will contain the same time as this is preserved along + with the image. The first method ensures that an image is + timestamped regardless of any other circumstances but will + result in all images being timestamped even those never saved + or viewed. The second method necessitates that saved images are + copied before being saved otherwise two timestamps perhaps at + different scales may be applied. This has the (perhaps) + desirable side effect that the timestamp is always applied at + the same resolution so an image that has scaling applied will + still have a legible and correctly scaled timestamp. + "), + type => $types{boolean}, + category => "config", + }, + { + name => "ZM_CPU_EXTENSIONS", + default => "yes", + description => "Use advanced CPU extensions to increase performance", + help => qqq(" + When advanced processor extensions such as SSE2 or SSSE3 are + available, ZoneMinder can use them, which should increase + performance and reduce system load. Enabling this option on + processors that do not support the advanced processors + extensions used by ZoneMinder is harmless and will have no + effect. + "), + type => $types{boolean}, + category => "config", + }, + { + name => "ZM_FAST_IMAGE_BLENDS", + default => "yes", + description => "Use a fast algorithm to blend the reference image", + help => qqq(" + To detect alarms ZoneMinder needs to blend the captured image + with the stored reference image to update it for comparison + with the next image. The reference blend percentage specified + for the monitor controls how much the new image affects the + reference image. There are two methods that are available for + this. If this option is set then fast calculation which does + not use any multiplication or division is used. This + calculation is extremely fast, however it limits the possible + blend percentages to 50%, 25%, 12.5%, 6.25%, 3.25% and 1.5%. + Any other blend percentage will be rounded to the nearest + possible one. The alternative is to switch this option off + and use standard blending instead, which is slower. + "), + type => $types{boolean}, + category => "config", + }, + { + name => "ZM_OPT_ADAPTIVE_SKIP", + default => "yes", + description => "Should frame analysis try and be efficient in skipping frames", + help => qqq(" + In previous versions of ZoneMinder the analysis daemon would + attempt to keep up with the capture daemon by processing the + last captured frame on each pass. This would sometimes have the + undesirable side-effect of missing a chunk of the initial + activity that caused the alarm because the pre-alarm frames + would all have to be written to disk and the database before + processing the next frame, leading to some delay between the + first and second event frames. Setting this option enables a + newer adaptive algorithm where the analysis daemon attempts to + process as many captured frames as possible, only skipping + frames when in danger of the capture daemon overwriting yet to + be processed frames. This skip is variable depending on the + size of the ring buffer and the amount of space left in it. + Enabling this option will give you much better coverage of the + beginning of alarms whilst biasing out any skipped frames + towards the middle or end of the event. However you should be + aware that this will have the effect of making the analysis + daemon run somewhat behind the capture daemon during events and + for particularly fast rates of capture it is possible for the + adaptive algorithm to be overwhelmed and not have time to react + to a rapid build up of pending frames and thus for a buffer + overrun condition to occur. + "), + type => $types{boolean}, + category => "config", + }, + { + name => "ZM_MAX_SUSPEND_TIME", + default => "30", + description => "Maximum time that a monitor may have motion detection suspended", + help => qqq(" + ZoneMinder allows monitors to have motion detection to be + suspended, for instance while panning a camera. Ordinarily this + relies on the operator resuming motion detection afterwards as + failure to do so can leave a monitor in a permanently suspended + state. This setting allows you to set a maximum time which a + camera may be suspended for before it automatically resumes + motion detection. This time can be extended by subsequent + suspend indications after the first so continuous camera + movement will also occur while the monitor is suspended. + "), + type => $types{integer}, + category => "config", + }, + # Deprecated, really no longer necessary + { + name => "ZM_OPT_REMOTE_CAMERAS", + default => "no", + description => "Are you going to use remote/networked cameras", + help => qqq(" + ZoneMinder can work with both local cameras, ie. those attached + physically to your computer and remote or network cameras. If + you will be using networked cameras select this option. + "), + type => $types{boolean}, + category => "hidden", + }, + # Deprecated, now set on a per monitor basis using the Method field + { + name => "ZM_NETCAM_REGEXPS", + default => "yes", + description => "Use regular expression matching with network cameras", + help => qqq(" + Traditionally ZoneMinder has used complex regular regular + expressions to handle the multitude of formats that network + cameras produce. In versions from 1.21.1 the default is to use + a simpler and faster built in pattern matching methodology. + This works well with most networks cameras but if you have + problems you can try the older, but more flexible, regular + expression based method by selecting this option. Note, to use + this method you must have libpcre installed on your system. + "), + requires => [ { name => "ZM_OPT_REMOTE_CAMERAS", value => "yes" } ], + type => $types{boolean}, + category => "hidden", + }, + { + name => "ZM_HTTP_VERSION", + default => "1.0", + description => "The version of HTTP that ZoneMinder will use to connect", + help => qqq(" + ZoneMinder can communicate with network cameras using either of + the HTTP/1.1 or HTTP/1.0 standard. A server will normally fall + back to the version it supports with no problem so this should + usually by left at the default. However it can be changed to + HTTP/1.0 if necessary to resolve particular issues. + "), + type => { + db_type => "string", + hint => "1.1|1.0", + pattern => qr|^(1\.[01])$|, + format => q( $1?$1:"" ) + }, + category => "network", + }, + { + name => "ZM_HTTP_UA", + default => "ZoneMinder", + description => "The user agent that ZoneMinder uses to identify itself", + help => qqq(" + When ZoneMinder communicates with remote cameras it will + identify itself using this string and it's version number. This + is normally sufficient, however if a particular cameras expects + only to communicate with certain browsers then this can be + changed to a different string identifying ZoneMinder as + Internet Explorer or Netscape etc. + "), + type => $types{string}, + category => "network", + }, + { + name => "ZM_HTTP_TIMEOUT", + default => "2500", + description => "How long ZoneMinder waits before giving up on images (milliseconds)", + help => qqq(" + When retrieving remote images ZoneMinder will wait for this + length of time before deciding that an image is not going to + arrive and taking steps to retry. This timeout is in + milliseconds (1000 per second) and will apply to each part of + an image if it is not sent in one whole chunk. + "), + type => $types{integer}, + category => "network", + }, + { + name => "ZM_MIN_RTP_PORT", + default => "40200", + description => "Minimum port that ZoneMinder will listen for RTP traffic on", + help => qqq(" + When ZoneMinder communicates with MPEG4 capable cameras using + RTP with the unicast method it must open ports for the camera + to connect back to for control and streaming purposes. This + setting specifies the minimum port number that ZoneMinder will + use. Ordinarily two adjacent ports are used for each camera, + one for control packets and one for data packets. This port + should be set to an even number, you may also need to open up a + hole in your firewall to allow cameras to connect back if you + wish to use unicasting. + "), + type => $types{integer}, + category => "network", + }, + { + name => "ZM_MAX_RTP_PORT", + default => "40499", + description => "Maximum port that ZoneMinder will listen for RTP traffic on", + help => qqq(" + When ZoneMinder communicates with MPEG4 capable cameras using + RTP with the unicast method it must open ports for the camera + to connect back to for control and streaming purposes. This + setting specifies the maximum port number that ZoneMinder will + use. Ordinarily two adjacent ports are used for each camera, + one for control packets and one for data packets. This port + should be set to an even number, you may also need to open up a + hole in your firewall to allow cameras to connect back if you + wish to use unicasting. You should also ensure that you have + opened up at least two ports for each monitor that will be + connecting to unicasting network cameras. + "), + type => $types{integer}, + category => "network", + }, + { + name => "ZM_OPT_FFMPEG", + default => "@OPT_FFMPEG@", + description => "Is the ffmpeg video encoder/decoder installed", + help => qqq(" + ZoneMinder can optionally encode a series of video images into + an MPEG encoded movie file for viewing, downloading or storage. + This option allows you to specify whether you have the ffmpeg + tools installed. Note that creating MPEG files can be fairly + CPU and disk intensive and is not a required option as events + can still be reviewed as video streams without it. + "), + type => $types{boolean}, + category => "images", + }, + { + name => "ZM_PATH_FFMPEG", + default => "@PATH_FFMPEG@", + description => "Path to (optional) ffmpeg mpeg encoder", + help => "This path should point to where ffmpeg has been installed.", + requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + type => $types{abs_path}, + category => "images", + }, + { + name => "ZM_FFMPEG_INPUT_OPTIONS", + default => "", + description => "Additional input options to ffmpeg", + help => qqq(" + Ffmpeg can take many options on the command line to control the + quality of video produced. This option allows you to specify + your own set that apply to the input to ffmpeg (options that + are given before the -i option). Check the ffmpeg documentation + for a full list of options which may be used here. + "), + requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + type => $types{string}, + category => "images", + }, + { + name => "ZM_FFMPEG_OUTPUT_OPTIONS", + default => "-r 25", + description => "Additional output options to ffmpeg", + help => qqq(" + Ffmpeg can take many options on the command line to control the + quality of video produced. This option allows you to specify + your own set that apply to the output from ffmpeg (options that + are given after the -i option). Check the ffmpeg documentation + for a full list of options which may be used here. The most + common one will often be to force an output frame rate + supported by the video encoder. + "), + requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + type => $types{string}, + category => "images", + }, + { + name => "ZM_FFMPEG_FORMATS", + default => "mpg mpeg wmv asf avi* mov swf 3gp**", + description => "Formats to allow for ffmpeg video generation", + help => qqq(" + Ffmpeg can generate video in many different formats. This + option allows you to list the ones you want to be able to + select. As new formats are supported by ffmpeg you can add them + here and be able to use them immediately. Adding a '*' after a + format indicates that this will be the default format used for + web video, adding '**' defines the default format for phone + video. + "), + requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + type => $types{string}, + category => "images", + }, + { + name => "ZM_FFMPEG_OPEN_TIMEOUT", + default => "10", + description => "Timeout in seconds when opening a stream.", + help => qqq(" + When Ffmpeg is opening a stream, it can take a long time before + failing; certain circumstances even seem to be able to lock + indefinitely. This option allows you to set a maximum time in + seconds to pass before closing the stream and trying to reopen + it again. + "), + requires => [ { name=>"ZM_OPT_FFMPEG", value=>"yes" } ], + type => $types{integer}, + category => "images", + }, + { + name => "ZM_LOG_LEVEL_SYSLOG", + default => "0", + description => "Save logging output to the system log", + help => qqq(" + ZoneMinder logging is now more more integrated between + components and allows you to specify the destination for + logging output and the individual levels for each. This option + lets you control the level of logging output that goes to the + system log. ZoneMinder binaries have always logged to the + system log but now scripts and web logging is also included. To + preserve the previous behaviour you should ensure this value is + set to Info or Warning. This option controls the maximum level + of logging that will be written, so Info includes Warnings and + Errors etc. To disable entirely, set this option to None. You + should use caution when setting this option to Debug as it can + affect severely affect system performance. If you want debug + you will also need to set a level and component below + "), + type => { + db_type => "integer", + hint => "None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + category => "logging", + }, + { + name => "ZM_LOG_LEVEL_FILE", + default => "-5", + description => "Save logging output to component files", + help => qqq(" + ZoneMinder logging is now more more integrated between + components and allows you to specify the destination for + logging output and the individual levels for each. This option + lets you control the level of logging output that goes to + individual log files written by specific components. This is + how logging worked previously and although useful for tracking + down issues in specific components it also resulted in many + disparate log files. To preserve this behaviour you should + ensure this value is set to Info or Warning. This option + controls the maximum level of logging that will be written, so + Info includes Warnings and Errors etc. To disable entirely, set + this option to None. You should use caution when setting this + option to Debug as it can affect severely affect system + performance though file output has less impact than the other + options. If you want debug you will also need to set a level + and component below + "), + type => { + db_type => "integer", + hint => "None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + category => "logging", + }, + { + name => "ZM_LOG_LEVEL_WEBLOG", + default => "-5", + description => "Save logging output to the weblog", + help => qqq(" + ZoneMinder logging is now more more integrated between + components and allows you to specify the destination for + logging output and the individual levels for each. This option + lets you control the level of logging output from the web + interface that goes to the httpd error log. Note that only web + logging from PHP and JavaScript files is included and so this + option is really only useful for investigating specific issues + with those components. This option controls the maximum level + of logging that will be written, so Info includes Warnings and + Errors etc. To disable entirely, set this option to None. You + should use caution when setting this option to Debug as it can + affect severely affect system performance. If you want debug + you will also need to set a level and component below + "), + type => { + db_type => "integer", + hint => "None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + category => "logging", + }, + { + name => "ZM_LOG_LEVEL_DATABASE", + default => "0", + description => "Save logging output to the database", + help => qqq(" + ZoneMinder logging is now more more integrated between + components and allows you to specify the destination for + logging output and the individual levels for each. This option + lets you control the level of logging output that is written to + the database. This is a new option which can make viewing + logging output easier and more intuitive and also makes it + easier to get an overall impression of how the system is + performing. If you have a large or very busy system then it is + possible that use of this option may slow your system down if + the table becomes very large. Ensure you use the + LOG_DATABASE_LIMIT option to keep the table to a manageable + size. This option controls the maximum level of logging that + will be written, so Info includes Warnings and Errors etc. To + disable entirely, set this option to None. You should use + caution when setting this option to Debug as it can affect + severely affect system performance. If you want debug you will + also need to set a level and component below + "), + type => { + db_type => "integer", + hint => "None=-5|Panic=-4|Fatal=-3|Error=-2|Warning=-1|Info=0|Debug=1", + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + category => "logging", + }, + { + name => "ZM_LOG_DATABASE_LIMIT", + default => "7 day", + description => "Maximum number of log entries to retain", + help => qqq(" + If you are using database logging then it is possible to + quickly build up a large number of entries in the Logs table. + This option allows you to specify how many of these entries are + kept. If you set this option to a number greater than zero then + that number is used to determine the maximum number of rows, + less than or equal to zero indicates no limit and is not + recommended. You can also set this value to time values such as + ' day' which will limit the log entries to those newer than + that time. You can specify 'hour', 'day', 'week', 'month' and + 'year', note that the values should be singular (no 's' at the + end). The Logs table is pruned periodically so it is possible + for more than the expected number of rows to be present briefly + in the meantime. + "), + type => $types{string}, + category => "logging", + }, + { + name => "ZM_LOG_DEBUG", + default => "no", + description => "Switch debugging on", + help => qqq(" + ZoneMinder components usually support debug logging available + to help with diagnosing problems. Binary components have + several levels of debug whereas more other components have only + one. Normally this is disabled to minimise performance + penalties and avoid filling logs too quickly. This option lets + you switch on other options that allow you to configure + additional debug information to be output. Components will pick + up this instruction when they are restarted. + "), + type => $types{boolean}, + category => "logging", + }, + { + name => "ZM_LOG_DEBUG_TARGET", + default => "", + description => "What components should have extra debug enabled", + help => qqq(" + There are three scopes of debug available. Leaving this option + blank means that all components will use extra debug (not + recommended). Setting this option to '_', e.g. _zmc, + will limit extra debug to that component only. Setting this + option to '__', e.g. '_zmc_m1' will limit + extra debug to that instance of the component only. This is + ordinarily what you probably want to do. To debug scripts use + their names without the .pl extension, e.g. '_zmvideo' and to + debug issues with the web interface use '_web'. You can specify + multiple targets by separating them with '|' characters. + "), + requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], + type => $types{string}, + category => "logging", + }, + { + name => "ZM_LOG_DEBUG_LEVEL", + default => 1, + description => "What level of extra debug should be enabled", + help => qqq(" + There are 9 levels of debug available, with higher numbers + being more debug and level 0 being no debug. However not all + levels are used by all components. Also if there is debug at a + high level it is usually likely to be output at such a volume + that it may obstruct normal operation. For this reason you + should set the level carefully and cautiously until the degree + of debug you wish to see is present. Scripts and the web + interface only have one level so this is an on/off type option + for them. + "), + requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], + type => { + db_type => "integer", + hint => "1|2|3|4|5|6|7|8|9", + pattern => qr|^(\d+)$|, + format => q( $1 ) + }, + category => "logging", + }, + { + name => "ZM_LOG_DEBUG_FILE", + default => "@ZM_LOGDIR@/zm_debug.log+", + description => "Where extra debug is output to", + help => qqq(" + This option allows you to specify a different target for debug + output. All components have a default log file which will + norally be in /tmp or /var/log and this is where debug will be + written to if this value is empty. Adding a path here will + temporarily redirect debug, and other logging output, to this + file. This option is a simple filename and you are debugging + several components then they will all try and write to the same + file with undesirable consequences. Appending a '+' to the + filename will cause the file to be created with a '.' + suffix containing your process id. In this way debug from each + run of a component is kept separate. This is the recommended + setting as it will also prevent subsequent runs from + overwriting the same log. You should ensure that permissions + are set up to allow writing to the file and directory specified + here. + "), + requires => [ { name => "ZM_LOG_DEBUG", value => "yes" } ], + type => $types{string}, + category => "logging", + }, + { + name => "ZM_LOG_CHECK_PERIOD", + default => "900", + description => "Time period used when calculating overall system health", + help => qqq(" + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to indicate what period of historical + events are used in this calculation. This value is expressed in + seconds and is ignored if LOG_LEVEL_DATABASE is set to None. + "), + type => $types{integer}, + category => "logging", + }, + { + name => "ZM_LOG_ALERT_WAR_COUNT", + default => "1", + description => "Number of warnings indicating system alert state", + help => qqq(" + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many warnings must have + occurred within the defined time period to generate an overall + system alert state. A value of zero means warnings are not + considered. This value is ignored if LOG_LEVEL_DATABASE is set + to None. + "), + type => $types{integer}, + category => "logging", + }, + { + name => "ZM_LOG_ALERT_ERR_COUNT", + default => "1", + description => "Number of errors indicating system alert state", + help => qqq(" + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many errors must have + occurred within the defined time period to generate an overall + system alert state. A value of zero means errors are not + considered. This value is ignored if LOG_LEVEL_DATABASE is set + to None. + "), + type => $types{integer}, + category => "logging", + }, + { + name => "ZM_LOG_ALERT_FAT_COUNT", + default => "0", + description => "Number of fatal error indicating system alert state", + help => qqq(" + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many fatal errors + (including panics) must have occurred within the defined time + period to generate an overall system alert state. A value of + zero means fatal errors are not considered. This value is + ignored if LOG_LEVEL_DATABASE is set to None. + "), + type => $types{integer}, + category => "logging", + }, + { + name => "ZM_LOG_ALARM_WAR_COUNT", + default => "100", + description => "Number of warnings indicating system alarm state", + help => qqq(" + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many warnings must have + occurred within the defined time period to generate an overall + system alarm state. A value of zero means warnings are not + considered. This value is ignored if LOG_LEVEL_DATABASE is set + to None. + "), + type => $types{integer}, + category => "logging", + }, + { + name => "ZM_LOG_ALARM_ERR_COUNT", + default => "10", + description => "Number of errors indicating system alarm state", + help => qqq(" + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many errors must have + occurred within the defined time period to generate an overall + system alarm state. A value of zero means errors are not + considered. This value is ignored if LOG_LEVEL_DATABASE is set + to None. + "), + type => $types{integer}, + category => "logging", + }, + { + name => "ZM_LOG_ALARM_FAT_COUNT", + default => "1", + description => "Number of fatal error indicating system alarm state", + help => qqq(" + When ZoneMinder is logging events to the database it can + retrospectively examine the number of warnings and errors that + have occurred to calculate an overall state of system health. + This option allows you to specify how many fatal errors + (including panics) must have occurred within the defined time + period to generate an overall system alarm state. A value of + zero means fatal errors are not considered. This value is + ignored if LOG_LEVEL_DATABASE is set to None. + "), + type => $types{integer}, + category => "logging", + }, + { + name => "ZM_RECORD_EVENT_STATS", + default => "yes", + description => "Record event statistical information, switch off if too slow", + help => qqq(" + This version of ZoneMinder records detailed information about + events in the Stats table. This can help in profiling what the + optimum settings are for Zones though this is tricky at + present. However in future releases this will be done more + easily and intuitively, especially with a large sample of + events. The default option of 'yes' allows this information to + be collected now in readiness for this but if you are concerned + about performance you can switch this off in which case no + Stats information will be saved. + "), + type => $types{boolean}, + category => "logging", + }, + { + name => "ZM_RECORD_DIAG_IMAGES", + default => "no", + description => "Record intermediate alarm diagnostic images, can be very slow", + help => qqq(" + In addition to recording event statistics you can also record + the intermediate diagnostic images that display the results of + the various checks and processing that occur when trying to + determine if an alarm event has taken place. There are several + of these images generated for each frame and zone for each + alarm or alert frame so this can have a massive impact on + performance. Only switch this setting on for debug or analysis + purposes and remember to switch it off again once no longer + required. + "), + type => $types{boolean}, + category => "logging", + }, + { + name => "ZM_DUMP_CORES", + default => "no", + description => "Create core files on unexpected process failure.", + help => qqq(" + When an unrecoverable error occurs in a ZoneMinder binary + process is has traditionally been trapped and the details + written to logs to aid in remote analysis. However in some + cases it is easier to diagnose the error if a core file, which + is a memory dump of the process at the time of the error, is + created. This can be interactively analysed in the debugger and + may reveal more or better information than that available from + the logs. This option is recommended for advanced users only + otherwise leave at the default. Note using this option to + trigger core files will mean that there will be no indication + in the binary logs that a process has died, they will just + stop, however the zmdc log will still contain an entry. Also + note that you may have to explicitly enable core file creation + on your system via the 'ulimit -c' command or other means + otherwise no file will be created regardless of the value of + this option. + "), + type => $types{boolean}, + category => "logging", + }, + { + name => "ZM_PATH_MAP", + default => "/dev/shm", + description => "Path to the mapped memory files that that ZoneMinder can use", + help => qqq(" + ZoneMinder has historically used IPC shared memory for shared + data between processes. This has it's advantages and + limitations. This version of ZoneMinder can use an alternate + method, mapped memory, instead with can be enabled with the + --enable--mmap directive to configure. This requires less + system configuration and is generally more flexible. However it + requires each shared data segment to map onto a filesystem + file. This option indicates where those mapped files go. You + should ensure that this location has sufficient space for these + files and for the best performance it should be a tmpfs file + system or ramdisk otherwise disk access may render this method + slower than the regular shared memory one. + "), + type => $types{abs_path}, + category => "paths", + }, + { + name => "ZM_PATH_SOCKS", + default => "@ZM_SOCKDIR@", + description => "Path to the various Unix domain socket files that ZoneMinder uses", + help => qqq(" + ZoneMinder generally uses Unix domain sockets where possible. + This reduces the need for port assignments and prevents + external applications from possibly compromising the daemons. + However each Unix socket requires a .sock file to be created. + This option indicates where those socket files go. + "), + type => $types{abs_path}, + category => "paths", + }, + { + name => "ZM_PATH_LOGS", + default => "@ZM_LOGDIR@", + description => "Path to the various logs that the ZoneMinder daemons generate", + help => qqq(" + There are various daemons that are used by ZoneMinder to + perform various tasks. Most generate helpful log files and this + is where they go. They can be deleted if not required for + debugging. + "), + type => $types{abs_path}, + category => "paths", + }, + { + name => "ZM_PATH_SWAP", + default => "@ZM_TMPDIR@", + description => "Path to location for temporary swap images used in streaming", + help => qqq(" + Buffered playback requires temporary swap images to be stored + for each instance of the streaming daemons. This option + determines where these images will be stored. The images will + actually be stored in sub directories beneath this location and + will be automatically cleaned up after a period of time. + "), + type => $types{abs_path}, + category => "paths", + }, + { + name => "ZM_PATH_ARP", + default => "", + description => "Path to a supported ARP tool", + help => qqq(" + The camera probe function uses Address Resolution Protocol in + order to find known devices on the network. Optionally supply + the full path to \"ip neigh\", \"arp -a\", or any other tool on + your system that returns ip/mac address pairs. If this field is + left empty, ZoneMinder will search for the command \"arp\" and + attempt to use that. + "), + type => $types{abs_path}, + category => "paths", + }, + { + name => "ZM_WEB_TITLE_PREFIX", + default => "ZM", + description => "The title prefix displayed on each window", + help => qqq(" + If you have more than one installation of ZoneMinder it can be + helpful to display different titles for each one. Changing this + option allows you to customise the window titles to include + further information to aid identification. + "), + type => $types{string}, + category => "web", + }, + { + name => "ZM_WEB_CONSOLE_BANNER", + default => "", + description => "Arbitrary text message near the top of the console", + help => qqq(" + Allows the administrator to place an arbitrary text message + near the top of the web console. This is useful for the developers + to display a message which indicates the running instance of + ZoneMinder is a development snapshot, but it can also be used for + any other purpose as well. + "), + type => $types{string}, + category => "web", + }, + { + name => "ZM_WEB_RESIZE_CONSOLE", + default => "yes", + description => "Should the console window resize itself to fit", + help => qqq(" + Traditionally the main ZoneMinder web console window has + resized itself to shrink to a size small enough to list only + the monitors that are actually present. This is intended to + make the window more unobtrusize but may not be to everyones + tastes, especially if opened in a tab in browsers which support + this kind if layout. Switch this option off to have the console + window size left to the users preference + "), + type => $types{boolean}, + category => "web", + }, + { + name => "ZM_WEB_ID_ON_CONSOLE", + default => "no", + description => "Should the console list the monitor id", + help => qqq("Some find it useful to have the id always visible + on the console. This option will add a column listing it. + "), + type => $types{boolean}, + category => "web", + }, + { + name => "ZM_WEB_POPUP_ON_ALARM", + default => "yes", + description => "Should the monitor window jump to the top if an alarm occurs", + help => qqq(" + When viewing a live monitor stream you can specify whether you + want the window to pop to the front if an alarm occurs when the + window is minimised or behind another window. This is most + useful if your monitors are over doors for example when they + can pop up if someone comes to the doorway. + "), + type => $types{boolean}, + category => "web", + }, + { + name => "ZM_OPT_X10", + default => "no", + description => "Support interfacing with X10 devices", + help => qqq(" + If you have an X10 Home Automation setup in your home you can + use ZoneMinder to initiate or react to X10 signals if your + computer has the appropriate interface controller. This option + indicates whether X10 options will be available in the browser + client. + "), + type => $types{boolean}, + category => "x10", + }, + { + name => "ZM_X10_DEVICE", + default => "/dev/ttyS0", + description => "What device is your X10 controller connected on", + requires => [ { name => "ZM_OPT_X10", value => "yes" } ], + help => qqq(" + If you have an X10 controller device (e.g. XM10U) connected to + your computer this option details which port it is connected on, + the default of /dev/ttyS0 maps to serial or com port 1. + "), + type => $types{abs_path}, + category => "x10", + }, + { + name => "ZM_X10_HOUSE_CODE", + default => "A", + description => "What X10 house code should be used", + requires => [ { name => "ZM_OPT_X10", value => "yes" } ], + help => qqq(" + X10 devices are grouped together by identifying them as all + belonging to one House Code. This option details what that is. + It should be a single letter between A and P. + "), + type => { db_type=>"string", hint=>"A-P", pattern=>qr|^([A-P])|i, format=>q( uc($1) ) }, + category => "x10", + }, + { + name => "ZM_X10_DB_RELOAD_INTERVAL", + default => "60", + description => "How often (in seconds) the X10 daemon reloads the monitors from the database", + requires => [ { name => "ZM_OPT_X10", value => "yes" } ], + help => qqq(" + The zmx10 daemon periodically checks the database to find out + what X10 events trigger, or result from, alarms. This option + determines how frequently this check occurs, unless you change + this area frequently this can be a fairly large value. + "), + type => $types{integer}, + category => "x10", + }, + { + name => "ZM_WEB_SOUND_ON_ALARM", + default => "no", + description => "Should the monitor window play a sound if an alarm occurs", + help => qqq(" + When viewing a live monitor stream you can specify whether you + want the window to play a sound to alert you if an alarm + occurs. + "), + type => $types{boolean}, + category => "web", + }, + { + name => "ZM_WEB_ALARM_SOUND", + default => "", + description => "The sound to play on alarm, put this in the sounds directory", + help => qqq(" + You can specify a sound file to play if an alarm occurs whilst + you are watching a live monitor stream. So long as your browser + understands the format it does not need to be any particular + type. This file should be placed in the sounds directory + defined earlier. + "), + type => $types{file}, + requires => [ { name => "ZM_WEB_SOUND_ON_ALARM", value => "yes" } ], + category => "web", + }, + { + name => "ZM_WEB_COMPACT_MONTAGE", + default => "no", + description => "Compact the montage view by removing extra detail", + help => qqq(" + The montage view shows the output of all of your active + monitors in one window. This include a small menu and status + information for each one. This can increase the web traffic and + make the window larger than may be desired. Setting this option + on removes all this extraneous information and just displays + the images. + "), + type => $types{boolean}, + category => "web", + }, + { + name => "ZM_OPT_FAST_DELETE", + default => "no", + description => "Delete only event database records for speed", + help => qqq(" + 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. Note that this feature is less relevant + with modern hardware. Recommend this feature be left off. + "), + type => $types{boolean}, + category => "system", + }, + { + name => "ZM_STRICT_VIDEO_CONFIG", + default => "yes", + description => "Allow errors in setting video config to be fatal", + help => qqq(" + With some video devices errors can be reported in setting the + various video attributes when in fact the operation was + successful. Switching this option off will still allow these + errors to be reported but will not cause them to kill the video + capture daemon. Note however that doing this will cause all + errors to be ignored including those which are genuine and + which may cause the video capture to not function correctly. + Use this option with caution. + "), + type => $types{boolean}, + category => "config", + }, + { + name => 'ZM_LD_PRELOAD', + default => '', + description => "Path to library to preload before launching daemons", + help => qqq("Some older cameras require the use of the v4l1 compat + library. This setting allows the setting of the path + to the library, so that it can be loaded by zmdc.pl + before launching zmc."), + type => $types{abs_path}, + category => 'config', + }, + { + name => "ZM_SIGNAL_CHECK_POINTS", + default => "10", + description => "How many points in a captured image to check for signal loss", + help => qqq(" + For locally attached video cameras ZoneMinder can check for + signal loss by looking at a number of random points on each + captured image. If all of these points are set to the same + fixed colour then the camera is assumed to have lost signal. + When this happens any open events are closed and a short one + frame signal loss event is generated, as is another when the + signal returns. This option defines how many points on each + image to check. Note that this is a maximum, any points found + to not have the check colour will abort any further checks so + in most cases on a couple of points will actually be checked. + Network and file based cameras are never checked. + "), + type => $types{integer}, + category => "config", + }, + { + name => "ZM_V4L_MULTI_BUFFER", + default => "yes", + description => "Use more than one buffer for Video 4 Linux devices", + help => qqq(" + Performance when using Video 4 Linux devices is usually best if + multiple buffers are used allowing the next image to be + captured while the previous one is being processed. If you have + multiple devices on a card sharing one input that requires + switching then this approach can sometimes cause frames from + one source to be mixed up with frames from another. Switching + this option off prevents multi buffering resulting in slower + but more stable image capture. This option is ignored for + non-local cameras or if only one input is present on a capture + chip. This option addresses a similar problem to the + ZM_CAPTURES_PER_FRAME option and you should normally change the + value of only one of the options at a time. If you have + different capture cards that need different values you can + ovveride them in each individual monitor on the source page. + "), + type => $types{boolean}, + category => "config", + }, + { + name => "ZM_CAPTURES_PER_FRAME", + default => "1", + description => "How many images are captured per returned frame, for shared local cameras", + help => qqq(" + If you are using cameras attached to a video capture card which + forces multiple inputs to share one capture chip, it can + sometimes produce images with interlaced frames reversed + resulting in poor image quality and a distinctive comb edge + appearance. Increasing this setting allows you to force + additional image captures before one is selected as the + captured frame. This allows the capture hardware to 'settle + down' and produce better quality images at the price of lesser + capture rates. This option has no effect on (a) network + cameras, or (b) where multiple inputs do not share a capture + chip. This option addresses a similar problem to the + ZM_V4L_MULTI_BUFFER option and you should normally change the + value of only one of the options at a time. If you have + different capture cards that need different values you can + ovveride them in each individual monitor on the source page. + "), + type => $types{integer}, + category => "config", + }, + { + name => "ZM_FILTER_RELOAD_DELAY", + default => "300", + description => "How often (in seconds) filters are reloaded in zmfilter", + help => qqq(" + ZoneMinder allows you to save filters to the database which + allow events that match certain criteria to be emailed, deleted + or uploaded to a remote machine etc. The zmfilter daemon loads + these and does the actual operation. This option determines how + often the filters are reloaded from the database to get the + latest versions or new filters. If you don't change filters + very often this value can be set to a large value. + "), + type => $types{integer}, + category => "system", + }, + { + name => "ZM_FILTER_EXECUTE_INTERVAL", + default => "60", + description => "How often (in seconds) to run automatic saved filters", + help => qqq(" + ZoneMinder allows you to save filters to the database which + allow events that match certain criteria to be emailed, deleted + or uploaded to a remote machine etc. The zmfilter daemon loads + these and does the actual operation. This option determines how + often the filters are executed on the saved event in the + database. If you want a rapid response to new events this + should be a smaller value, however this may increase the + overall load on the system and affect performance of other + elements. + "), + type => $types{integer}, + category => "system", + }, + { + name => "ZM_OPT_UPLOAD", + default => "no", + description => "Should ZoneMinder support uploading events from filters", + help => qqq(" + In ZoneMinder you can create event filters that specify whether + events that match certain criteria should be uploaded to a + remote server for archiving. This option specifies whether this + functionality should be available + "), + type => $types{boolean}, + category => "upload", + }, + { + name => "ZM_UPLOAD_ARCH_FORMAT", + default => "tar", + description => "What format the uploaded events should be created in.", + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + help => qqq(" + Uploaded events may be stored in either .tar or .zip format, + this option specifies which. Note that to use this you will + need to have the Archive::Tar and/or Archive::Zip perl modules + installed. + "), + type => { + db_type =>"string", + hint =>"tar|zip", + pattern =>qr|^([tz])|i, + format =>q( $1 =~ /^t/ ? "tar" : "zip" ) + }, + category => "upload", + }, + { + name => "ZM_UPLOAD_ARCH_COMPRESS", + default => "no", + description => "Should archive files be compressed", + help => qqq(" + When the archive files are created they can be compressed. + However in general since the images are compressed already this + saves only a minimal amount of space versus utilising more CPU + in their creation. Only enable if you have CPU to waste and are + limited in disk space on your remote server or bandwidth. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{boolean}, + category => "upload", + }, + { + name => "ZM_UPLOAD_ARCH_ANALYSE", + default => "no", + description => "Include the analysis files in the archive", + help => qqq(" + When the archive files are created they can contain either just + the captured frames or both the captured frames and, for frames + that caused an alarm, the analysed image with the changed area + highlighted. This option controls files are included. Only + include analysed frames if you have a high bandwidth connection + to the remote server or if you need help in figuring out what + caused an alarm in the first place as archives with these files + in can be considerably larger. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{boolean}, + category => "upload", + }, + { + name => "ZM_UPLOAD_PROTOCOL", + default => "ftp", + description => "What protocol to use to upload events", + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + help => qqq(" + ZoneMinder can upload events to a remote server using either + FTP or SFTP. Regular FTP is widely supported but not + necessarily very secure whereas SFTP (Secure FTP) runs over an + ssh connection and so is encrypted and uses regular ssh ports. + Note that to use this you will need to have the appropriate + perl module, either Net::FTP or Net::SFTP installed depending + on your choice. + "), + type => { + db_type =>"string", + hint =>"ftp|sftp", + pattern =>qr|^([tz])|i, + format =>q( $1 =~ /^f/ ? "ftp" : "sftp" ) + }, + category => "upload", + }, + { + name => "ZM_UPLOAD_FTP_HOST", + default => "", + description => "The remote server to upload to", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the name, or ip + address, of the server to use. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{hostname}, + category => "hidden", + }, + { + name => "ZM_UPLOAD_HOST", + default => "", + description => "The remote server to upload events to", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the name, or ip address, + of the server to use. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{hostname}, + category => "upload", + }, + { + name => "ZM_UPLOAD_PORT", + default => "", + description => "The port on the remote upload server, if not the default (SFTP only)", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote server. If you are using the SFTP protocol then this + option allows you to specify a particular port to use for + connection. If this option is left blank then the default, port + 22, is used. This option is ignored for FTP uploads. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{integer}, + category => "upload", + }, + { + name => "ZM_UPLOAD_FTP_USER", + default => "", + description => "Your ftp username", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the username that + ZoneMinder should use to log in for ftp transfer. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{alphanum}, + category => "hidden", + }, + { + name => "ZM_UPLOAD_USER", + default => "", + description => "Remote server username", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the username that + ZoneMinder should use to log in for transfer. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{alphanum}, + category => "upload", + }, + { + name => "ZM_UPLOAD_FTP_PASS", + default => "", + description => "Your ftp password", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the password that + ZoneMinder should use to log in for ftp transfer. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{string}, + category => "hidden", + }, + { + name => "ZM_UPLOAD_PASS", + default => "", + description => "Remote server password", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the password that + ZoneMinder should use to log in for transfer. If you are using + certificate based logins for SFTP servers you can leave this + option blank. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{string}, + category => "upload", + }, + { + name => "ZM_UPLOAD_FTP_LOC_DIR", + default => "@ZM_TMPDIR@", + description => "The local directory in which to create upload files", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the local directory + that ZoneMinder should use for temporary upload files. These + are files that are created from events, uploaded and then + deleted. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{abs_path}, + category => "hidden", + }, + { + name => "ZM_UPLOAD_LOC_DIR", + default => "@ZM_TMPDIR@", + description => "The local directory in which to create upload files", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the local directory that + ZoneMinder should use for temporary upload files. These are + files that are created from events, uploaded and then deleted. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{abs_path}, + category => "upload", + }, + { + name => "ZM_UPLOAD_FTP_REM_DIR", + default => "", + description => "The remote directory to upload to", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the remote directory + that ZoneMinder should use to upload event files to. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{rel_path}, + category => "hidden", + }, + { + name => "ZM_UPLOAD_REM_DIR", + default => "", + description => "The remote directory to upload to", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the remote directory + that ZoneMinder should use to upload event files to. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{rel_path}, + category => "upload", + }, + { + name => "ZM_UPLOAD_FTP_TIMEOUT", + default => "120", + description => "How long to allow the transfer to take for each file", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates the maximum ftp + inactivity timeout (in seconds) that should be tolerated before + ZoneMinder determines that the transfer has failed and closes + down the connection. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{integer}, + category => "hidden", + }, + { + name => "ZM_UPLOAD_TIMEOUT", + default => "120", + description => "How long to allow the transfer to take for each file", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote server. This option indicates the maximum inactivity + timeout (in seconds) that should be tolerated before ZoneMinder + determines that the transfer has failed and closes down the + connection. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{integer}, + category => "upload", + }, + { + name => "ZM_UPLOAD_STRICT", + default => "no", + description => "Require strict host key checking for SFTP uploads", + help => qqq(" + You can require SFTP uploads to verify the host key of the remote server + for protection against man-in-the-middle attacks. You will need to add the + server's key to the known_hosts file. On most systems, this will be + ~/.ssh/known_hosts, where ~ is the home directory of the web server running + ZoneMinder. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{boolean}, + category => "upload", + }, + { + name => "ZM_UPLOAD_FTP_PASSIVE", + default => "yes", + description => "Use passive ftp when uploading", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. This option indicates that ftp transfers + should be done in passive mode. This uses a single connection + for all ftp activity and, whilst slower than active transfers, + is more robust and likely to work from behind filewalls. This + option is ignored for SFTP transfers. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + help => qqq(" + If your computer is behind a firewall or proxy you may need to + set FTP to passive mode. In fact for simple transfers it makes + little sense to do otherwise anyway but you can set this to + 'No' if you wish. + "), + type => $types{boolean}, + category => "upload", + }, + { + name => "ZM_UPLOAD_FTP_DEBUG", + default => "no", + description => "Switch ftp debugging on", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote ftp server. If you are having (or expecting) troubles + with uploading events then setting this to 'yes' permits + additional information to be included in the zmfilter log file. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{boolean}, + category => "hidden", + }, + { + name => "ZM_UPLOAD_DEBUG", + default => "no", + description => "Switch upload debugging on", + help => qqq(" + You can use filters to instruct ZoneMinder to upload events to + a remote server. If you are having (or expecting) troubles with + uploading events then setting this to 'yes' permits additional + information to be generated by the underlying transfer modules + and included in the logs. + "), + requires => [ { name => "ZM_OPT_UPLOAD", value => "yes" } ], + type => $types{boolean}, + category => "upload", + }, + { + name => "ZM_OPT_EMAIL", + default => "no", + description => "Should ZoneMinder email you details of events that match corresponding filters", + help => qqq(" + In ZoneMinder you can create event filters that specify whether + events that match certain criteria should have their details + emailed to you at a designated email address. This will allow + you to be notified of events as soon as they occur and also to + quickly view the events directly. This option specifies whether + this functionality should be available. The email created with + this option can be any size and is intended to be sent to a + regular email reader rather than a mobile device. + "), + type => $types{boolean}, + category => "mail", + }, + { + name => "ZM_EMAIL_ADDRESS", + default => "", + description => "The email address to send matching event details to", + requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], + help => qqq(" + This option is used to define the email address that any events + that match the appropriate filters will be sent to. + "), + type => $types{email}, + category => "mail", + }, + { + name => "ZM_EMAIL_TEXT", + default => 'subject = "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)" + body = " + Hello, - Monitor : %MN% - Event Id : %EI% - Length : %EL% - Frames : %EF% (%EFA%) - Scores : t%EST% m%ESM% a%ESA% + An alarm has been detected on your installation of the ZoneMinder. -This alarm was matched by the %FN% filter and can be viewed at %EPS% + The details are as follows :- -ZoneMinder"', - description => "The text of the email used to send matching event details", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" - This option is used to define the content of the email that is - sent for any events that match the appropriate filters. - "), - type => $types{text}, - category => "hidden", - }, - { - name => "ZM_EMAIL_SUBJECT", - default => "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)", - description => "The subject of the email used to send matching event details", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" - This option is used to define the subject of the email that is - sent for any events that match the appropriate filters. - "), - type => $types{string}, - category => "mail", - }, - { - name => "ZM_EMAIL_BODY", - default => " -Hello, + Monitor : %MN% + Event Id : %EI% + Length : %EL% + Frames : %EF% (%EFA%) + Scores : t%EST% m%ESM% a%ESA% -An alarm has been detected on your installation of the ZoneMinder. + This alarm was matched by the %FN% filter and can be viewed at %EPS% -The details are as follows :- + ZoneMinder"', + description => "The text of the email used to send matching event details", + requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], + help => qqq(" + This option is used to define the content of the email that is + sent for any events that match the appropriate filters. + "), + type => $types{text}, + category => "hidden", + }, + { + name => "ZM_EMAIL_SUBJECT", + default => "ZoneMinder: Alarm - %MN%-%EI% (%ESM% - %ESA% %EFA%)", + description => "The subject of the email used to send matching event details", + requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], + help => qqq(" + This option is used to define the subject of the email that is + sent for any events that match the appropriate filters. + "), + type => $types{string}, + category => "mail", + }, + { + name => "ZM_EMAIL_BODY", + default => " + Hello, - Monitor : %MN% - Event Id : %EI% - Length : %EL% - Frames : %EF% (%EFA%) - Scores : t%EST% m%ESM% a%ESA% + An alarm has been detected on your installation of the ZoneMinder. -This alarm was matched by the %FN% filter and can be viewed at %EPS% + The details are as follows :- -ZoneMinder", - description => "The body of the email used to send matching event details", - requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], - help => qqq(" - This option is used to define the content of the email that is - sent for any events that match the appropriate filters. - "), - type => $types{text}, - category => "mail", - }, - { - name => "ZM_OPT_MESSAGE", - default => "no", - description => "Should ZoneMinder message you with details of events that match corresponding filters", - help => qqq(" - In ZoneMinder you can create event filters that specify whether - events that match certain criteria should have their details - sent to you at a designated short message email address. This - will allow you to be notified of events as soon as they occur. - This option specifies whether this functionality should be - available. The email created by this option will be brief and - is intended to be sent to an SMS gateway or a minimal mail - reader such as a mobile device or phone rather than a regular - email reader. - "), - type => $types{boolean}, - category => "mail", - }, - { - name => "ZM_MESSAGE_ADDRESS", - default => "", - description => "The email address to send matching event details to", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" - This option is used to define the short message email address - that any events that match the appropriate filters will be sent - to. - "), - type => $types{email}, - category => "mail", - }, - { - name => "ZM_MESSAGE_TEXT", - default => 'subject = "ZoneMinder: Alarm - %MN%-%EI%" -body = "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score."', - description => "The text of the message used to send matching event details", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" - This option is used to define the content of the message that - is sent for any events that match the appropriate filters. - "), - type => $types{text}, - category => "hidden", - }, - { - name => "ZM_MESSAGE_SUBJECT", - default => "ZoneMinder: Alarm - %MN%-%EI%", - description => "The subject of the message used to send matching event details", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" - This option is used to define the subject of the message that - is sent for any events that match the appropriate filters. - "), - type => $types{string}, - category => "mail", - }, - { - name => "ZM_MESSAGE_BODY", - default => "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.", - description => "The body of the message used to send matching event details", - requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], - help => qqq(" - This option is used to define the content of the message that - is sent for any events that match the appropriate filters. - "), - type => $types{text}, - category => "mail", - }, - { - name => "ZM_NEW_MAIL_MODULES", - default => "no", - description => "Use a newer perl method to send emails", - requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } - ], - help => qqq(" - Traditionally ZoneMinder has used the MIME::Entity perl module - to construct and send notification emails and messages. Some - people have reported problems with this module not being - present at all or flexible enough for their needs. If you are - one of those people this option allows you to select a new - mailing method using MIME::Lite and Net::SMTP instead. This - method was contributed by Ross Melin and should work for - everyone but has not been extensively tested so currently is - not selected by default. - "), - type => $types{boolean}, - category => "mail", - }, - { - name => "ZM_EMAIL_HOST", - default => "localhost", - description => "The host address of your SMTP mail server", - requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } - ], - help => qqq(" - If you have chosen SMTP as the method by which to send - notification emails or messages then this option allows you to - choose which SMTP server to use to send them. The default of - localhost may work if you have the sendmail, exim or a similar - daemon running however you may wish to enter your ISP's SMTP - mail server here. - "), - type => $types{hostname}, - category => "mail", - }, - { - name => "ZM_FROM_EMAIL", - default => "", - description => "The email address you wish your event notifications to originate from", - requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } - ], - help => qqq(" - The emails or messages that will be sent to you informing you - of events can appear to come from a designated email address to - help you with mail filtering etc. An address of something like - ZoneMinder\@your.domain is recommended. - "), - type => $types{email}, - category => "mail", - }, - { - name => "ZM_URL", - default => "", - description => "The URL of your ZoneMinder installation", - requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" } - ], - help => qqq(" - The emails or messages that will be sent to you informing you - of events can include a link to the events themselves for easy - viewing. If you intend to use this feature then set this option - to the url of your installation as it would appear from where - you read your email, e.g. http://host.your.domain/zm.php. - "), - type => $types{url}, - category => "mail", - }, - { - name => "ZM_MAX_RESTART_DELAY", - default => "600", - description => "Maximum delay (in seconds) for daemon restart attempts.", - help => qqq(" - The zmdc (zm daemon control) process controls when processeses - are started or stopped and will attempt to restart any that - fail. If a daemon fails frequently then a delay is introduced - between each restart attempt. If the daemon stills fails then - this delay is increased to prevent extra load being placed on - the system by continual restarts. This option controls what - this maximum delay is. - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_WATCH_CHECK_INTERVAL", - default => "10", - description => "How often to check the capture daemons have not locked up", - help => qqq(" - The zmwatch daemon checks the image capture performance of the - capture daemons to ensure that they have not locked up (rarely - a sync error may occur which blocks indefinitely). This option - determines how often the daemons are checked. - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_WATCH_MAX_DELAY", - default => "5", - description => "The maximum delay allowed since the last captured image", - help => qqq(" - The zmwatch daemon checks the image capture performance of the - capture daemons to ensure that they have not locked up (rarely - a sync error may occur which blocks indefinitely). This option - determines the maximum delay to allow since the last captured - frame. The daemon will be restarted if it has not captured any - images after this period though the actual restart may take - slightly longer in conjunction with the check interval value - above. - "), - type => $types{decimal}, - category => "system", - }, - { + Monitor : %MN% + Event Id : %EI% + Length : %EL% + Frames : %EF% (%EFA%) + Scores : t%EST% m%ESM% a%ESA% - name => "ZM_RUN_AUDIT", - default => "yes", - description => "Run zmaudit to check data consistency", - help => qqq(" - The zmaudit daemon exists to check that the saved information - in the database and on the filesystem match and are consistent - with each other. If an error occurs or if you are using 'fast - deletes' it may be that database records are deleted but files - remain. In this case, and similar, zmaudit will remove - redundant information to synchronise the two data stores. This - option controls whether zmaudit is run in the background and - performs these checks and fixes continuously. This is - recommended for most systems however if you have a very large - number of events the process of scanning the database and - filesystem may take a long time and impact performance. In this - case you may prefer to not have zmaudit running unconditionally - and schedule occasional checks at other, more convenient, - times. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_AUDIT_CHECK_INTERVAL", - default => "900", - description => "How often to check database and filesystem consistency", - help => qqq(" - The zmaudit daemon exists to check that the saved information - in the database and on the filesystem match and are consistent - with each other. If an error occurs or if you are using 'fast - deletes' it may be that database records are deleted but files - remain. In this case, and similar, zmaudit will remove - redundant information to synchronise the two data stores. The - default check interval of 900 seconds (15 minutes) is fine for - most systems however if you have a very large number of events - the process of scanning the database and filesystem may take a - long time and impact performance. In this case you may prefer - to make this interval much larger to reduce the impact on your - system. This option determines how often these checks are - performed. - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_AUDIT_MIN_AGE", - default => "86400", - description => "The minimum age in seconds event data must be in order to be deleted.", - help => qqq(" - The zmaudit daemon exists to check that the saved information - in the database and on the filesystem match and are consistent - with each other. Event files or db records that are younger than - this setting will not be deleted and a warning will be given. - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_FORCED_ALARM_SCORE", - default => "255", - description => "Score to give forced alarms", - help => qqq(" - The 'zmu' utility can be used to force an alarm on a monitor - rather than rely on the motion detection algorithms. This - option determines what score to give these alarms to - distinguish them from regular ones. It must be 255 or less. - "), - type => $types{integer}, - category => "config", - }, - { - name => "ZM_BULK_FRAME_INTERVAL", - default => "100", - description => "How often a bulk frame should be written to the database", - help => qqq(" - Traditionally ZoneMinder writes an entry into the Frames - database table for each frame that is captured and saved. This - works well in motion detection scenarios but when in a DVR - situation ('Record' or 'Mocord' mode) this results in a huge - number of frame writes and a lot of database and disk bandwidth - for very little additional information. Setting this to a - non-zero value will enabled ZoneMinder to group these non-alarm - frames into one 'bulk' frame entry which saves a lot of - bandwidth and space. The only disadvantage of this is that - timing information for individual frames is lost but in - constant frame rate situations this is usually not significant. - This setting is ignored in Modect mode and individual frames - are still written if an alarm occurs in Mocord mode also. - "), - type => $types{integer}, - category => "config", - }, - { - name => "ZM_EVENT_CLOSE_MODE", - default => "idle", - description => "When continuous events are closed.", - help => qqq(" - When a monitor is running in a continuous recording mode - (Record or Mocord) events are usually closed after a fixed - period of time (the section length). However in Mocord mode it - is possible that motion detection may occur near the end of a - section. This option controls what happens when an alarm occurs - in Mocord mode. The 'time' setting means that the event will be - closed at the end of the section regardless of alarm activity. - The 'idle' setting means that the event will be closed at the - end of the section if there is no alarm activity occurring at - the time otherwise it will be closed once the alarm is over - meaning the event may end up being longer than the normal - section length. The 'alarm' setting means that if an alarm - occurs during the event, the event will be closed once the - alarm is over regardless of when this occurs. This has the - effect of limiting the number of alarms to one per event and - the events will be shorter than the section length if an alarm - has occurred. - "), - type => $types{boolean}, - type => { - db_type =>"string", - hint =>"time|idle|alarm", - pattern =>qr|^([tia])|i, - format =>q( ($1 =~ /^t/) - ? "time" - : ($1 =~ /^i/ ? "idle" : "time" ) - ) - }, - category => "config", - }, - # Deprecated, superseded by event close mode - { - name => "ZM_FORCE_CLOSE_EVENTS", - default => "no", - description => "Close events at section ends.", - help => qqq(" - When a monitor is running in a continuous recording mode - (Record or Mocord) events are usually closed after a fixed - period of time (the section length). However in Mocord mode it - is possible that motion detection may occur near the end of a - section and ordinarily this will prevent the event being closed - until the motion has ceased. Switching this option on will - force the event closed at the specified time regardless of any - motion activity. - "), - type => $types{boolean}, - category => "hidden", - }, - { - name => "ZM_CREATE_ANALYSIS_IMAGES", - default => "yes", - description => "Create analysed alarm images with motion outlined", - help => qqq(" - By default during an alarm ZoneMinder records both the raw - captured image and one that has been analysed and had areas - where motion was detected outlined. This can be very useful - during zone configuration or in analysing why events occurred. - However it also incurs some overhead and in a stable system may - no longer be necessary. This parameter allows you to switch the - generation of these images off. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_WEIGHTED_ALARM_CENTRES", - default => "no", - description => "Use a weighted algorithm to calculate the centre of an alarm", - help => qqq(" - ZoneMinder will always calculate the centre point of an alarm - in a zone to give some indication of where on the screen it is. - This can be used by the experimental motion tracking feature or - your own custom extensions. In the alarmed or filtered pixels - mode this is a simple midpoint between the extents of the - detected pxiesl. However in the blob method this can instead be - calculated using weighted pixel locations to give more accurate - positioning for irregularly shaped blobs. This method, while - more precise is also slower and so is turned off by default. - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_EVENT_IMAGE_DIGITS", - default => "5", - description => "How many significant digits are used in event image numbering", - help => qqq(" - As event images are captured they are stored to the filesystem - with a numerical index. By default this index has three digits - so the numbers start 001, 002 etc. This works works for most - scenarios as events with more than 999 frames are rarely - captured. However if you have extremely long events and use - external applications then you may wish to increase this to - ensure correct sorting of images in listings etc. Warning, - increasing this value on a live system may render existing - events unviewable as the event will have been saved with the - previous scheme. Decreasing this value should have no ill - effects. - "), - type => $types{integer}, - category => "config", - }, - { - name => "ZM_DEFAULT_ASPECT_RATIO", - default => "4:3", - description => "The default width:height aspect ratio used in monitors", - help => qqq(" - When specifying the dimensions of monitors you can click a - checkbox to ensure that the width stays in the correct ratio to - the height, or vice versa. This setting allows you to indicate - what the ratio of these settings should be. This should be - specified in the format : and the - default of 4:3 normally be acceptable but 11:9 is another - common setting. If the checkbox is not clicked when specifying - monitor dimensions this setting has no effect. - "), - type => $types{string}, - category => "config", - }, - { - name => "ZM_USER_SELF_EDIT", - default => "no", - description => "Allow unprivileged users to change their details", - help => qqq(" - Ordinarily only users with system edit privilege are able to - change users details. Switching this option on allows ordinary - users to change their passwords and their language settings - "), - type => $types{boolean}, - category => "config", - }, - { - name => "ZM_OPT_FRAME_SERVER", - default => "no", - description => "Should analysis farm out the writing of images to disk", - #requires => [ { name => "ZM_OPT_ADAPTIVE_SKIP", value => "yes" } ], - help => qqq(" - In some circumstances it is possible for a slow disk to take so - long writing images to disk that it causes the analysis daemon - to fall behind especially during high frame rate events. - Setting this option to yes enables a frame server daemon (zmf) - which will be sent the images from the analysis daemon and will - do the actual writing of images itself freeing up the analysis - daemon to get on with other things. Should this transmission - fail or other permanent or transient error occur, this function - will fall back to the analysis daemon. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_FRAME_SOCKET_SIZE", - default => "0", - description => "Specify the frame server socket buffer size if non-standard", - requires => [ { name => "ZM_OPT_FRAME_SERVER", value => "yes" } ], - help => qqq(" - For large captured images it is possible for the writes from - the analysis daemon to the frame server to fail as the amount - to be written exceeds the default buffer size. While the images - are then written by the analysis daemon so no data is lost, it - defeats the object of the frame server daemon in the first - place. You can use this option to indicate that a larger buffer - size should be used. Note that you may have to change the - existing maximum socket buffer size on your system via sysctl - (or in /proc/sys/net/core/wmem_max) to allow this new size to - be set. Alternatively you can change the default buffer size on - your system in the same way in which case that will be used - with no change necessary in this option - "), - type => $types{integer}, - category => "system", - }, - { - name => "ZM_OPT_CONTROL", - default => "no", - description => "Support controllable (e.g. PTZ) cameras", - help => qqq(" - ZoneMinder includes limited support for controllable cameras. A - number of sample protocols are included and others can easily - be added. If you wish to control your cameras via ZoneMinder - then select this option otherwise if you only have static - cameras or use other control methods then leave this option - off. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_OPT_TRIGGERS", - default => "no", - description => "Interface external event triggers via socket or device files", - help => qqq(" - ZoneMinder can interact with external systems which prompt or - cancel alarms. This is done via the zmtrigger.pl script. This - option indicates whether you want to use these external - triggers. Most people will say no here. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_CHECK_FOR_UPDATES", - default => "yes", - description => "Check with zoneminder.com for updated versions", - help => qqq(" - 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 - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_TELEMETRY_DATA", - default => "yes", - description => "Send usage information to ZoneMinder", - help => qqq(" - Enable collection of usage information of the local system and send - it to the ZoneMinder development team. This data will be used to - determine things like who and where our customers are, how big their - systems are, the underlying hardware and operating system, etc. - This is being done for the sole purpoase of creating a better - product for our target audience. This script is intended to be - completely transparent to the end user, and can be disabled from - the web console under Options. - "), - type => $types{boolean}, - category => "system", - }, - { - name => "ZM_TELEMETRY_UUID", - default => "", - description => "Unique identifier for ZoneMinder telemetry", - help => qqq(" - This variable is auto-generated once by the system and is used to - uniquely identify it among all other ZoneMinder systems in - existence. - "), - type => $types{string}, - category => "dynamic", - }, - { - name => "ZM_TELEMETRY_LAST_UPLOAD", - default => "", - description => "When the last ZoneMinder telemetry upload ocurred", - help => "", - type => $types{integer}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_UPDATE_CHECK_PROXY", - default => "", - description => "Proxy url if required to access zoneminder.com", - help => qqq(" - If you use a proxy to access the internet then ZoneMinder needs - to know so it can access zoneminder.com to check for updates. - If you do use a proxy enter the full proxy url here in the form - of http://:/ - "), - type => $types{string}, - category => "system", - }, - { - name => "ZM_SHM_KEY", - default => "0x7a6d0000", - description => "Shared memory root key to use", - help => qqq(" - 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. - "), - type => $types{hexadecimal}, - category => "system", - }, - # Deprecated, really no longer necessary - { - name => "ZM_WEB_REFRESH_METHOD", - default => "javascript", - description => "What method windows should use to refresh themselves", - help => qqq(" - Many windows in Javascript need to refresh themselves to keep - their information current. This option determines what method - they should use to do this. Choosing 'javascript' means that - each window will have a short JavaScript statement in with a - timer to prompt the refresh. This is the most compatible - method. Choosing 'http' means the refresh instruction is put in - the HTTP header. This is a cleaner method but refreshes are - interrupted or cancelled when a link in the window is clicked - meaning that the window will no longer refresh and this would - have to be done manually. - "), - type => { - db_type =>"string", - hint =>"javascript|http", - pattern =>qr|^([jh])|i, - format =>q( $1 =~ /^j/ - ? "javascript" - : "http" - ) - }, - category => "hidden", - }, - { - name => "ZM_WEB_EVENT_SORT_FIELD", - default => "DateTime", - description => "Default field the event lists are sorted by", - help => qqq(" - Events in lists can be initially ordered in any way you want. - This option controls what field is used to sort them. You can - modify this ordering from filters or by clicking on headings in - the lists themselves. Bear in mind however that the 'Prev' and - 'Next' links, when scrolling through events, relate to the - ordering in the lists and so not always to time based ordering. - "), - type => { - db_type =>"string", - hint =>"Id|Name|Cause|MonitorName|DateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore", - pattern =>qr|.|, - format =>q( $1 ) - }, - category => "web", - }, - { - name => "ZM_WEB_EVENT_SORT_ORDER", - default => "asc", - description => "Default order the event lists are sorted by", - help => qqq(" - Events in lists can be initially ordered in any way you want. - This option controls what order (ascending or descending) is - used to sort them. You can modify this ordering from filters or - by clicking on headings in the lists themselves. Bear in mind - however that the 'Prev' and 'Next' links, when scrolling - through events, relate to the ordering in the lists and so not - always to time based ordering. - "), - type => { - db_type =>"string", - hint =>"asc|desc", - pattern =>qr|^([ad])|i, - format =>q( $1 =~ /^a/i ? "asc" : "desc" ) - }, - category => "web", - }, - { - name => "ZM_WEB_EVENTS_PER_PAGE", - default => "25", - description => "How many events to list per page in paged mode", - help => qqq(" - In the event list view you can either list all events or just a - page at a time. This option controls how many events are listed - per page in paged mode and how often to repeat the column - headers in non-paged mode. - "), - type => $types{integer}, - category => "web", - }, - { - name => "ZM_WEB_LIST_THUMBS", - default => "no", - description => "Display mini-thumbnails of event images in event lists", - help => qqq(" - Ordinarily the event lists just display text details of the - events to save space and time. By switching this option on you - can also display small thumbnails to help you identify events - of interest. The size of these thumbnails is controlled by the - following two options. - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_WEB_LIST_THUMB_WIDTH", - default => "48", - description => "The width of the thumbnails that appear in the event lists", - help => qqq(" - This options controls the width of the thumbnail images that - appear in the event lists. It should be fairly small to fit in - with the rest of the table. If you prefer you can specify a - height instead in the next option but you should only use one - of the width or height and the other option should be set to - zero. If both width and height are specified then width will be - used and height ignored. - "), - type => $types{integer}, - requires => [ { name => "ZM_WEB_LIST_THUMBS", value => "yes" } ], - category => "web", - }, - { - name => "ZM_WEB_LIST_THUMB_HEIGHT", - default => "0", - description => "The height of the thumbnails that appear in the event lists", - help => qqq(" - This options controls the height of the thumbnail images that - appear in the event lists. It should be fairly small to fit in - with the rest of the table. If you prefer you can specify a - width instead in the previous option but you should only use - one of the width or height and the other option should be set - to zero. If both width and height are specified then width will - be used and height ignored. - "), - type => $types{integer}, - requires => [ { name => "ZM_WEB_LIST_THUMBS", value => "yes" } ], - category => "web", - }, - { - name => "ZM_WEB_USE_OBJECT_TAGS", - default => "yes", - description => "Wrap embed in object tags for media content", - help => qqq(" - There are two methods of including media content in web pages. - The most common way is use the EMBED tag which is able to give - some indication of the type of content. However this is not a - standard part of HTML. The official method is to use OBJECT - tags which are able to give more information allowing the - correct media viewers etc to be loaded. However these are less - widely supported and content may be specifically tailored to a - particular platform or player. This option controls whether - media content is enclosed in EMBED tags only or whether, where - appropriate, it is additionally wrapped in OBJECT tags. - Currently OBJECT tags are only used in a limited number of - circumstances but they may become more widespread in the - future. It is suggested that you leave this option on unless - you encounter problems playing some content. - "), - type => $types{boolean}, - category => "web", - }, - { - name => "ZM_WEB_H_REFRESH_MAIN", - default => "60", - introduction => qqq(" - There are now a number of options that are grouped into - bandwidth categories, this allows you to configure the - ZoneMinder client to work optimally over the various access - methods you might to access the client.\n\nThe next few options - control what happens when the client is running in 'high' - bandwidth mode. You should set these options for when accessing - the ZoneMinder client over a local network or high speed link. - In most cases the default values will be suitable as a starting - point. - "), - description => "How often (in seconds) the main console window should refresh itself", - help => qqq(" - The main console window lists a general status and the event - totals for all monitors. This is not a trivial task and should - not be repeated too frequently or it may affect the performance - of the rest of the system. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_REFRESH_CYCLE", - default => "10", - description => "How often (in seconds) the cycle watch window swaps to the next monitor", - help => qqq(" - The cycle watch window is a method of continuously cycling - between images from all of your monitors. This option - determines how often to refresh with a new image. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_REFRESH_IMAGE", - default => "3", - description => "How often (in seconds) the watched image is refreshed (if not streaming)", - help => qqq(" - The live images from a monitor can be viewed in either streamed - or stills mode. This option determines how often a stills image - is refreshed, it has no effect if streaming is selected. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_REFRESH_STATUS", - default => "1", - description => "How often (in seconds) the status refreshes itself in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - one in the middle merely contains a monitor status which needs - to refresh fairly frequently to give a true indication. This - option determines that frequency. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_REFRESH_EVENTS", - default => "5", - description => "How often (in seconds) the event listing is refreshed in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - lower framme contains a listing of the last few events for easy - access. This option determines how often this is refreshed. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_CAN_STREAM", - default => "auto", - description => "Override the automatic detection of browser streaming capability", - help => qqq(" - If you know that your browser can handle image streams of the - type 'multipart/x-mixed-replace' but ZoneMinder does not detect - this correctly you can set this option to ensure that the - stream is delivered with or without the use of the Cambozola - plugin. Selecting 'yes' will tell ZoneMinder that your browser - can handle the streams natively, 'no' means that it can't and - so the plugin will be used while 'auto' lets ZoneMinder decide. - "), - type => $types{tristate}, - category => "highband", - }, - { - name => "ZM_WEB_H_STREAM_METHOD", - default => "jpeg", - description => "Which method should be used to send video streams to your browser.", - help => qqq(" - ZoneMinder can be configured to use either mpeg encoded video - or a series or still jpeg images when sending video streams. - This option defines which is used. If you choose mpeg you - should ensure that you have the appropriate plugins available - on your browser whereas choosing jpeg will work natively on - Mozilla and related browsers and with a Java applet on Internet - Explorer - "), - type => { - db_type =>"string", - hint =>"mpeg|jpeg", - pattern =>qr|^([mj])|i, - format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) - }, - category => "highband", - }, - { - name => "ZM_WEB_H_DEFAULT_SCALE", - default => "100", - description => "What the default scaling factor applied to 'live' or 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'live' or 'event' streams in - their native size. However if you have monitors with large - dimensions or a slow link you may prefer to reduce this size, - alternatively for small monitors you can enlarge it. This - options lets you specify what the default scaling factor will - be. It is expressed as a percentage so 100 is normal size, 200 - is double size etc. - "), - type => { - db_type =>"integer", - hint =>"25|33|50|75|100|150|200|300|400", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "highband", - }, - { - name => "ZM_WEB_H_DEFAULT_RATE", - default => "100", - description => "What the default replay rate factor applied to 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'event' streams at their - native rate, i.e. as close to real-time as possible. However if - you have long events it is often convenient to replay them at a - faster rate for review. This option lets you specify what the - default replay rate will be. It is expressed as a percentage so - 100 is normal rate, 200 is double speed etc. - "), - type => { - db_type =>"integer", - hint =>"25|50|100|150|200|400|1000|2500|5000|10000", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "highband", - }, - { - name => "ZM_WEB_H_VIDEO_BITRATE", - default => "150000", - description => "What the bitrate of the video encoded stream should be set to", - help => qqq(" - When encoding real video via the ffmpeg library a bit rate can - be specified which roughly corresponds to the available - bandwidth used for the stream. This setting effectively - corresponds to a 'quality' setting for the video. A low value - will result in a blocky image whereas a high value will produce - a clearer view. Note that this setting does not control the - frame rate of the video however the quality of the video - produced is affected both by this setting and the frame rate - that the video is produced at. A higher frame rate at a - particular bit rate result in individual frames being at a - lower quality. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_VIDEO_MAXFPS", - default => "30", - description => "What the maximum frame rate for streamed video should be", - help => qqq(" - When using streamed video the main control is the bitrate which - determines how much data can be transmitted. However a lower - bitrate at high frame rates results in a lower quality image. - This option allows you to limit the maximum frame rate to - ensure that video quality is maintained. An additional - advantage is that encoding video at high frame rates is a - processor intensive task when for the most part a very high - frame rate offers little perceptible improvement over one that - has a more manageable resource requirement. Note, this option - is implemented as a cap beyond which binary reduction takes - place. So if you have a device capturing at 15fps and set this - option to 10fps then the video is not produced at 10fps, but - rather at 7.5fps (15 divided by 2) as the final frame rate must - be the original divided by a power of 2. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_H_SCALE_THUMBS", - default => "no", - description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", - help => qqq(" - If unset, this option sends the whole image to the browser - which resizes it in the window. If set the image is scaled down - on the server before sending a reduced size image to the - browser to conserve bandwidth at the cost of cpu on the server. - Note that ZM can only perform the resizing if the appropriate - PHP graphics functionality is installed. This is usually - available in the php-gd package. - "), - type => $types{boolean}, - category => "highband", - }, - { - name => "ZM_WEB_H_EVENTS_VIEW", - default => "events", - description => "What the default view of multiple events should be.", - help => qqq(" - Stored events can be viewed in either an events list format or - in a timeline based one. This option sets the default view that - will be used. Choosing one view here does not prevent the other - view being used as it will always be selectable from whichever - view is currently being used. - "), - type => { - db_type =>"string", - hint =>"events|timeline", - pattern =>qr|^([lt])|i, - format =>q( $1 =~ /^e/ ? "events" : "timeline" ) - }, - category => "highband", - }, - { - name => "ZM_WEB_H_SHOW_PROGRESS", - default => "yes", - description => "Show the progress of replay in event view.", - help => qqq(" - When viewing events an event navigation panel and progress bar - is shown below the event itself. This allows you to jump to - specific points in the event, but can can also dynamically - update to display the current progress of the event replay - itself. This progress is calculated from the actual event - duration and is not directly linked to the replay itself, so on - limited bandwidth connections may be out of step with the - replay. This option allows you to turn off the progress - display, whilst still keeping the navigation aspect, where - bandwidth prevents it functioning effectively. - "), - type => $types{boolean}, - category => "highband", - }, - { - name => "ZM_WEB_H_AJAX_TIMEOUT", - default => "3000", - description => "How long to wait for Ajax request responses (ms)", - help => qqq(" - The newer versions of the live feed and event views use Ajax to - request information from the server and populate the views - dynamically. This option allows you to specify a timeout if - required after which requests are abandoned. A timeout may be - necessary if requests would overwise hang such as on a slow - connection. This would tend to consume a lot of browser memory - and make the interface unresponsive. Ordinarily no requests - should timeout so this setting should be set to a value greater - than the slowest expected response. This value is in - milliseconds but if set to zero then no timeout will be used. - "), - type => $types{integer}, - category => "highband", - }, - { - name => "ZM_WEB_M_REFRESH_MAIN", - default => "300", - description => "How often (in seconds) the main console window should refresh itself", - help => qqq(" - The main console window lists a general status and the event - totals for all monitors. This is not a trivial task and should - not be repeated too frequently or it may affect the performance - of the rest of the system. - "), - type => $types{integer}, - introduction => qqq(" - The next few options control what happens when the client is - running in 'medium' bandwidth mode. You should set these - options for when accessing the ZoneMinder client over a slower - cable or DSL link. In most cases the default values will be - suitable as a starting point. - "), - category => "medband", - }, - { - name => "ZM_WEB_M_REFRESH_CYCLE", - default => "20", - description => "How often (in seconds) the cycle watch window swaps to the next monitor", - help => qqq(" - The cycle watch window is a method of continuously cycling - between images from all of your monitors. This option - determines how often to refresh with a new image. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_M_REFRESH_IMAGE", - default => "10", - description => "How often (in seconds) the watched image is refreshed (if not streaming)", - help => qqq(" - The live images from a monitor can be viewed in either streamed - or stills mode. This option determines how often a stills image - is refreshed, it has no effect if streaming is selected. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_M_REFRESH_STATUS", - default => "5", - description => "How often (in seconds) the status refreshes itself in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - one in the middle merely contains a monitor status which needs - to refresh fairly frequently to give a true indication. This - option determines that frequency. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_M_REFRESH_EVENTS", - default => "60", - description => "How often (in seconds) the event listing is refreshed in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - lower framme contains a listing of the last few events for easy - access. This option determines how often this is refreshed. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_M_CAN_STREAM", - default => "auto", - description => "Override the automatic detection of browser streaming capability", - help => qqq(" - If you know that your browser can handle image streams of the - type 'multipart/x-mixed-replace' but ZoneMinder does not detect - this correctly you can set this option to ensure that the - stream is delivered with or without the use of the Cambozola - plugin. Selecting 'yes' will tell ZoneMinder that your browser - can handle the streams natively, 'no' means that it can't and - so the plugin will be used while 'auto' lets ZoneMinder decide. - "), - type => $types{tristate}, - category => "medband", - }, - { - name => "ZM_WEB_M_STREAM_METHOD", - default => "jpeg", - description => "Which method should be used to send video streams to your browser.", - help => qqq(" - ZoneMinder can be configured to use either mpeg encoded video - or a series or still jpeg images when sending video streams. - This option defines which is used. If you choose mpeg you - should ensure that you have the appropriate plugins available - on your browser whereas choosing jpeg will work natively on - Mozilla and related browsers and with a Java applet on Internet - Explorer - "), - type => { - db_type =>"string", - hint =>"mpeg|jpeg", - pattern =>qr|^([mj])|i, - format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) - }, - category => "medband", - }, - { - name => "ZM_WEB_M_DEFAULT_SCALE", - default => "100", - description => "What the default scaling factor applied to 'live' or 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'live' or 'event' streams in - their native size. However if you have monitors with large - dimensions or a slow link you may prefer to reduce this size, - alternatively for small monitors you can enlarge it. This - options lets you specify what the default scaling factor will - be. It is expressed as a percentage so 100 is normal size, 200 - is double size etc. - "), - type => { - db_type =>"integer", - hint =>"25|33|50|75|100|150|200|300|400", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "medband", - }, - { - name => "ZM_WEB_M_DEFAULT_RATE", - default => "100", - description => "What the default replay rate factor applied to 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'event' streams at their - native rate, i.e. as close to real-time as possible. However if - you have long events it is often convenient to replay them at a - faster rate for review. This option lets you specify what the - default replay rate will be. It is expressed as a percentage so - 100 is normal rate, 200 is double speed etc. - "), - type => { - db_type =>"integer", - hint =>"25|50|100|150|200|400|1000|2500|5000|10000", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "medband", - }, - { - name => "ZM_WEB_M_VIDEO_BITRATE", - default => "75000", - description => "What the bitrate of the video encoded stream should be set to", - help => qqq(" - When encoding real video via the ffmpeg library a bit rate can - be specified which roughly corresponds to the available - bandwidth used for the stream. This setting effectively - corresponds to a 'quality' setting for the video. A low value - will result in a blocky image whereas a high value will produce - a clearer view. Note that this setting does not control the - frame rate of the video however the quality of the video - produced is affected both by this setting and the frame rate - that the video is produced at. A higher frame rate at a - particular bit rate result in individual frames being at a - lower quality. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_M_VIDEO_MAXFPS", - default => "10", - description => "What the maximum frame rate for streamed video should be", - help => qqq(" - When using streamed video the main control is the bitrate which - determines how much data can be transmitted. However a lower - bitrate at high frame rates results in a lower quality image. - This option allows you to limit the maximum frame rate to - ensure that video quality is maintained. An additional - advantage is that encoding video at high frame rates is a - processor intensive task when for the most part a very high - frame rate offers little perceptible improvement over one that - has a more manageable resource requirement. Note, this option - is implemented as a cap beyond which binary reduction takes - place. So if you have a device capturing at 15fps and set this - option to 10fps then the video is not produced at 10fps, but - rather at 7.5fps (15 divided by 2) as the final frame rate must - be the original divided by a power of 2. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_M_SCALE_THUMBS", - default => "yes", - description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", - help => qqq(" - If unset, this option sends the whole image to the browser - which resizes it in the window. If set the image is scaled down - on the server before sending a reduced size image to the - browser to conserve bandwidth at the cost of cpu on the server. - Note that ZM can only perform the resizing if the appropriate - PHP graphics functionality is installed. This is usually - available in the php-gd package. - "), - type => $types{boolean}, - category => "medband", - }, - { - name => "ZM_WEB_M_EVENTS_VIEW", - default => "events", - description => "What the default view of multiple events should be.", - help => qqq(" - Stored events can be viewed in either an events list format or - in a timeline based one. This option sets the default view that - will be used. Choosing one view here does not prevent the other - view being used as it will always be selectable from whichever - view is currently being used. - "), - type => { - db_type =>"string", - hint =>"events|timeline", - pattern =>qr|^([lt])|i, - format =>q( $1 =~ /^e/ ? "events" : "timeline" ) - }, - category => "medband", - }, - { - name => "ZM_WEB_M_SHOW_PROGRESS", - default => "yes", - description => "Show the progress of replay in event view.", - help => qqq(" - When viewing events an event navigation panel and progress bar - is shown below the event itself. This allows you to jump to - specific points in the event, but can can also dynamically - update to display the current progress of the event replay - itself. This progress is calculated from the actual event - duration and is not directly linked to the replay itself, so on - limited bandwidth connections may be out of step with the - replay. This option allows you to turn off the progress - display, whilst still keeping the navigation aspect, where - bandwidth prevents it functioning effectively. - "), - type => $types{boolean}, - category => "medband", - }, - { - name => "ZM_WEB_M_AJAX_TIMEOUT", - default => "5000", - description => "How long to wait for Ajax request responses (ms)", - help => qqq(" - The newer versions of the live feed and event views use Ajax to - request information from the server and populate the views - dynamically. This option allows you to specify a timeout if - required after which requests are abandoned. A timeout may be - necessary if requests would overwise hang such as on a slow - connection. This would tend to consume a lot of browser memory - and make the interface unresponsive. Ordinarily no requests - should timeout so this setting should be set to a value greater - than the slowest expected response. This value is in - milliseconds but if set to zero then no timeout will be used. - "), - type => $types{integer}, - category => "medband", - }, - { - name => "ZM_WEB_L_REFRESH_MAIN", - default => "300", - description => "How often (in seconds) the main console window should refresh itself", - introduction => qqq(" - The next few options control what happens when the client is - running in 'low' bandwidth mode. You should set these options - for when accessing the ZoneMinder client over a modem or slow - link. In most cases the default values will be suitable as a - starting point. - "), - help => qqq(" - The main console window lists a general status and the event - totals for all monitors. This is not a trivial task and should - not be repeated too frequently or it may affect the performance - of the rest of the system. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_REFRESH_CYCLE", - default => "30", - description => "How often (in seconds) the cycle watch window swaps to the next monitor", - help => qqq(" - The cycle watch window is a method of continuously cycling - between images from all of your monitors. This option - determines how often to refresh with a new image. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_REFRESH_IMAGE", - default => "15", - description => "How often (in seconds) the watched image is refreshed (if not streaming)", - help => qqq(" - The live images from a monitor can be viewed in either streamed - or stills mode. This option determines how often a stills image - is refreshed, it has no effect if streaming is selected. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_REFRESH_STATUS", - default => "10", - description => "How often (in seconds) the status refreshes itself in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - one in the middle merely contains a monitor status which needs - to refresh fairly frequently to give a true indication. This - option determines that frequency. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_REFRESH_EVENTS", - default => "180", - description => "How often (in seconds) the event listing is refreshed in the watch window", - help => qqq(" - The monitor window is actually made from several frames. The - lower framme contains a listing of the last few events for easy - access. This option determines how often this is refreshed. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_CAN_STREAM", - default => "auto", - description => "Override the automatic detection of browser streaming capability", - help => qqq(" - If you know that your browser can handle image streams of the - type 'multipart/x-mixed-replace' but ZoneMinder does not detect - this correctly you can set this option to ensure that the - stream is delivered with or without the use of the Cambozola - plugin. Selecting 'yes' will tell ZoneMinder that your browser - can handle the streams natively, 'no' means that it can't and - so the plugin will be used while 'auto' lets ZoneMinder decide. - "), - type => $types{tristate}, - category => "lowband", - }, - { - name => "ZM_WEB_L_STREAM_METHOD", - default => "jpeg", - description => "Which method should be used to send video streams to your browser.", - help => qqq(" - ZoneMinder can be configured to use either mpeg encoded video - or a series or still jpeg images when sending video streams. - This option defines which is used. If you choose mpeg you - should ensure that you have the appropriate plugins available - on your browser whereas choosing jpeg will work natively on - Mozilla and related browsers and with a Java applet on Internet - Explorer - "), - type => { - db_type =>"string", - hint =>"mpeg|jpeg", - pattern =>qr|^([mj])|i, - format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) - }, - category => "lowband", - }, - { - name => "ZM_WEB_L_DEFAULT_SCALE", - default => "100", - description => "What the default scaling factor applied to 'live' or 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'live' or 'event' streams in - their native size. However if you have monitors with large - dimensions or a slow link you may prefer to reduce this size, - alternatively for small monitors you can enlarge it. This - options lets you specify what the default scaling factor will - be. It is expressed as a percentage so 100 is normal size, 200 - is double size etc. - "), - type => { - db_type =>"integer", - hint =>"25|33|50|75|100|150|200|300|400", - pattern =>qr|^(\d+)$|, - format =>q( $1 ) - }, - category => "lowband", - }, - { - name => "ZM_WEB_L_DEFAULT_RATE", - default => "100", - description => "What the default replay rate factor applied to 'event' views is (%)", - help => qqq(" - Normally ZoneMinder will display 'event' streams at their - native rate, i.e. as close to real-time as possible. However if - you have long events it is often convenient to replay them at a - faster rate for review. This option lets you specify what the - default replay rate will be. It is expressed as a percentage so - 100 is normal rate, 200 is double speed etc. - "), - type => { - db_type =>"integer", - hint =>"25|50|100|150|200|400|1000|2500|5000|10000", - pattern =>qr|^(\d+)$|, format=>q( $1 ) - }, - category => "lowband", - }, - { - name => "ZM_WEB_L_VIDEO_BITRATE", - default => "25000", - description => "What the bitrate of the video encoded stream should be set to", - help => qqq(" - When encoding real video via the ffmpeg library a bit rate can - be specified which roughly corresponds to the available - bandwidth used for the stream. This setting effectively - corresponds to a 'quality' setting for the video. A low value - will result in a blocky image whereas a high value will produce - a clearer view. Note that this setting does not control the - frame rate of the video however the quality of the video - produced is affected both by this setting and the frame rate - that the video is produced at. A higher frame rate at a - particular bit rate result in individual frames being at a - lower quality. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_VIDEO_MAXFPS", - default => "5", - description => "What the maximum frame rate for streamed video should be", - help => qqq(" - When using streamed video the main control is the bitrate which - determines how much data can be transmitted. However a lower - bitrate at high frame rates results in a lower quality image. - This option allows you to limit the maximum frame rate to - ensure that video quality is maintained. An additional - advantage is that encoding video at high frame rates is a - processor intensive task when for the most part a very high - frame rate offers little perceptible improvement over one that - has a more manageable resource requirement. Note, this option - is implemented as a cap beyond which binary reduction takes - place. So if you have a device capturing at 15fps and set this - option to 10fps then the video is not produced at 10fps, but - rather at 7.5fps (15 divided by 2) as the final frame rate must - be the original divided by a power of 2. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_WEB_L_SCALE_THUMBS", - default => "yes", - description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", - help => qqq(" - If unset, this option sends the whole image to the browser - which resizes it in the window. If set the image is scaled down - on the server before sending a reduced size image to the - browser to conserve bandwidth at the cost of cpu on the server. - Note that ZM can only perform the resizing if the appropriate - PHP graphics functionality is installed. This is usually - available in the php-gd package. - "), - type => $types{boolean}, - category => "lowband", - }, - { - name => "ZM_WEB_L_EVENTS_VIEW", - default => "events", - description => "What the default view of multiple events should be.", - help => qqq(" - Stored events can be viewed in either an events list format or - in a timeline based one. This option sets the default view that - will be used. Choosing one view here does not prevent the other - view being used as it will always be selectable from whichever - view is currently being used. - "), - type => { - db_type =>"string", - hint =>"events|timeline", - pattern =>qr|^([lt])|i, - format =>q( $1 =~ /^e/ ? "events" : "timeline" ) - }, - category => "lowband", - }, - { - name => "ZM_WEB_L_SHOW_PROGRESS", - default => "no", - description => "Show the progress of replay in event view.", - help => qqq(" - When viewing events an event navigation panel and progress bar - is shown below the event itself. This allows you to jump to - specific points in the event, but can can also dynamically - update to display the current progress of the event replay - itself. This progress is calculated from the actual event - duration and is not directly linked to the replay itself, so on - limited bandwidth connections may be out of step with the - replay. This option allows you to turn off the progress - display, whilst still keeping the navigation aspect, where - bandwidth prevents it functioning effectively. - "), - type => $types{boolean}, - category => "lowband", - }, - { - name => "ZM_WEB_L_AJAX_TIMEOUT", - default => "10000", - description => "How long to wait for Ajax request responses (ms)", - help => qqq(" - The newer versions of the live feed and event views use Ajax to - request information from the server and populate the views - dynamically. This option allows you to specify a timeout if - required after which requests are abandoned. A timeout may be - necessary if requests would overwise hang such as on a slow - connection. This would tend to consume a lot of browser memory - and make the interface unresponsive. Ordinarily no requests - should timeout so this setting should be set to a value greater - than the slowest expected response. This value is in - milliseconds but if set to zero then no timeout will be used. - "), - type => $types{integer}, - category => "lowband", - }, - { - name => "ZM_DYN_LAST_VERSION", - default => "", - description => "What the last version of ZoneMinder recorded from zoneminder.com is", - help => "", - type => $types{string}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_DYN_CURR_VERSION", - default => "@VERSION@", - description => qqq(" - What the effective current version of ZoneMinder is, might be - different from actual if versions ignored - "), - help => "", - type => $types{string}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_DYN_DB_VERSION", - default => "@VERSION@", - description => "What the version of the database is, from zmupdate", - help => "", - type => $types{string}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_DYN_LAST_CHECK", - default => "", - description => "When the last check for version from zoneminder.com was", - help => "", - type => $types{integer}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_DYN_NEXT_REMINDER", - default => "", - description => "When the earliest time to remind about versions will be", - help => "", - type => $types{string}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_DYN_DONATE_REMINDER_TIME", - default => 0, - description => "When the earliest time to remind about donations will be", - help => "", - type => $types{integer}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_DYN_SHOW_DONATE_REMINDER", - default => "yes", - description => "Remind about donations or not", - help => "", - type => $types{boolean}, - readonly => 1, - category => "dynamic", - }, - { - name => "ZM_SSMTP_MAIL", - default => "no", - description => qqq(" - Use a SSMTP mail server if available. - NEW_MAIL_MODULES must be enabled - "), - requires => [ - { name => "ZM_OPT_EMAIL", value => "yes" }, - { name => "ZM_OPT_MESSAGE", value => "yes" }, - { name => "ZM_NEW_MAIL_MODULES", value => "yes" } - ], - help => qqq(" - SSMTP is a lightweight and efficient method to send email. - The SSMTP application is not installed by default. - NEW_MAIL_MODULES must also be enabled. - Please visit: http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder - for setup and configuration help. - "), - type => $types{boolean}, - category => "mail", - }, - { - name => "ZM_SSMTP_PATH", - default => "", - description => "SSMTP executable path", - requires => [{ name => "ZM_SSMTP_MAIL", value => "yes" }], - help => qqq(" - Recommend setting the path to the SSMTP application. - If path is not defined. Zoneminder will try to determine - the path via shell command. Example path: /usr/sbin/ssmtp. - "), - type => $types{string}, - category => "mail", - }, -); + This alarm was matched by the %FN% filter and can be viewed at %EPS% + + ZoneMinder", + description => "The body of the email used to send matching event details", + requires => [ { name => "ZM_OPT_EMAIL", value => "yes" } ], + help => qqq(" + This option is used to define the content of the email that is + sent for any events that match the appropriate filters. + "), + type => $types{text}, + category => "mail", + }, + { + name => "ZM_OPT_MESSAGE", + default => "no", + description => "Should ZoneMinder message you with details of events that match corresponding filters", + help => qqq(" + In ZoneMinder you can create event filters that specify whether + events that match certain criteria should have their details + sent to you at a designated short message email address. This + will allow you to be notified of events as soon as they occur. + This option specifies whether this functionality should be + available. The email created by this option will be brief and + is intended to be sent to an SMS gateway or a minimal mail + reader such as a mobile device or phone rather than a regular + email reader. + "), + type => $types{boolean}, + category => "mail", + }, + { + name => "ZM_MESSAGE_ADDRESS", + default => "", + description => "The email address to send matching event details to", + requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], + help => qqq(" + This option is used to define the short message email address + that any events that match the appropriate filters will be sent + to. + "), + type => $types{email}, + category => "mail", + }, + { + name => "ZM_MESSAGE_TEXT", + default => 'subject = "ZoneMinder: Alarm - %MN%-%EI%" + body = "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score."', + description => "The text of the message used to send matching event details", + requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], + help => qqq(" + This option is used to define the content of the message that + is sent for any events that match the appropriate filters. + "), + type => $types{text}, + category => "hidden", + }, + { + name => "ZM_MESSAGE_SUBJECT", + default => "ZoneMinder: Alarm - %MN%-%EI%", + description => "The subject of the message used to send matching event details", + requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], + help => qqq(" + This option is used to define the subject of the message that + is sent for any events that match the appropriate filters. + "), + type => $types{string}, + category => "mail", + }, + { + name => "ZM_MESSAGE_BODY", + default => "ZM alarm detected - %EL% secs, %EF%/%EFA% frames, t%EST%/m%ESM%/a%ESA% score.", + description => "The body of the message used to send matching event details", + requires => [ { name => "ZM_OPT_MESSAGE", value => "yes" } ], + help => qqq(" + This option is used to define the content of the message that + is sent for any events that match the appropriate filters. + "), + type => $types{text}, + category => "mail", + }, + { + name => "ZM_NEW_MAIL_MODULES", + default => "no", + description => "Use a newer perl method to send emails", + requires => [ + { name => "ZM_OPT_EMAIL", value => "yes" }, + { name => "ZM_OPT_MESSAGE", value => "yes" } + ], + help => qqq(" + Traditionally ZoneMinder has used the MIME::Entity perl module + to construct and send notification emails and messages. Some + people have reported problems with this module not being + present at all or flexible enough for their needs. If you are + one of those people this option allows you to select a new + mailing method using MIME::Lite and Net::SMTP instead. This + method was contributed by Ross Melin and should work for + everyone but has not been extensively tested so currently is + not selected by default. + "), + type => $types{boolean}, + category => "mail", + }, + { + name => "ZM_EMAIL_HOST", + default => "localhost", + description => "The host address of your SMTP mail server", + requires => [ + { name => "ZM_OPT_EMAIL", value => "yes" }, + { name => "ZM_OPT_MESSAGE", value => "yes" } + ], + help => qqq(" + If you have chosen SMTP as the method by which to send + notification emails or messages then this option allows you to + choose which SMTP server to use to send them. The default of + localhost may work if you have the sendmail, exim or a similar + daemon running however you may wish to enter your ISP's SMTP + mail server here. + "), + type => $types{hostname}, + category => "mail", + }, + { + name => "ZM_FROM_EMAIL", + default => "", + description => "The email address you wish your event notifications to originate from", + requires => [ + { name => "ZM_OPT_EMAIL", value => "yes" }, + { name => "ZM_OPT_MESSAGE", value => "yes" } + ], + help => qqq(" + The emails or messages that will be sent to you informing you + of events can appear to come from a designated email address to + help you with mail filtering etc. An address of something like + ZoneMinder\@your.domain is recommended. + "), + type => $types{email}, + category => "mail", + }, + { + name => "ZM_URL", + default => "", + description => "The URL of your ZoneMinder installation", + requires => [ + { name => "ZM_OPT_EMAIL", value => "yes" }, + { name => "ZM_OPT_MESSAGE", value => "yes" } + ], + help => qqq(" + The emails or messages that will be sent to you informing you + of events can include a link to the events themselves for easy + viewing. If you intend to use this feature then set this option + to the url of your installation as it would appear from where + you read your email, e.g. http://host.your.domain/zm.php. + "), + type => $types{url}, + category => "mail", + }, + { + name => "ZM_MAX_RESTART_DELAY", + default => "600", + description => "Maximum delay (in seconds) for daemon restart attempts.", + help => qqq(" + The zmdc (zm daemon control) process controls when processeses + are started or stopped and will attempt to restart any that + fail. If a daemon fails frequently then a delay is introduced + between each restart attempt. If the daemon stills fails then + this delay is increased to prevent extra load being placed on + the system by continual restarts. This option controls what + this maximum delay is. + "), + type => $types{integer}, + category => "system", + }, + { + name => "ZM_WATCH_CHECK_INTERVAL", + default => "10", + description => "How often to check the capture daemons have not locked up", + help => qqq(" + The zmwatch daemon checks the image capture performance of the + capture daemons to ensure that they have not locked up (rarely + a sync error may occur which blocks indefinitely). This option + determines how often the daemons are checked. + "), + type => $types{integer}, + category => "system", + }, + { + name => "ZM_WATCH_MAX_DELAY", + default => "5", + description => "The maximum delay allowed since the last captured image", + help => qqq(" + The zmwatch daemon checks the image capture performance of the + capture daemons to ensure that they have not locked up (rarely + a sync error may occur which blocks indefinitely). This option + determines the maximum delay to allow since the last captured + frame. The daemon will be restarted if it has not captured any + images after this period though the actual restart may take + slightly longer in conjunction with the check interval value + above. + "), + type => $types{decimal}, + category => "system", + }, + { + + name => "ZM_RUN_AUDIT", + default => "yes", + description => "Run zmaudit to check data consistency", + help => qqq(" + The zmaudit daemon exists to check that the saved information + in the database and on the filesystem match and are consistent + with each other. If an error occurs or if you are using 'fast + deletes' it may be that database records are deleted but files + remain. In this case, and similar, zmaudit will remove + redundant information to synchronise the two data stores. This + option controls whether zmaudit is run in the background and + performs these checks and fixes continuously. This is + recommended for most systems however if you have a very large + number of events the process of scanning the database and + filesystem may take a long time and impact performance. In this + case you may prefer to not have zmaudit running unconditionally + and schedule occasional checks at other, more convenient, + times. + "), + type => $types{boolean}, + category => "system", + }, + { + name => "ZM_AUDIT_CHECK_INTERVAL", + default => "900", + description => "How often to check database and filesystem consistency", + help => qqq(" + The zmaudit daemon exists to check that the saved information + in the database and on the filesystem match and are consistent + with each other. If an error occurs or if you are using 'fast + deletes' it may be that database records are deleted but files + remain. In this case, and similar, zmaudit will remove + redundant information to synchronise the two data stores. The + default check interval of 900 seconds (15 minutes) is fine for + most systems however if you have a very large number of events + the process of scanning the database and filesystem may take a + long time and impact performance. In this case you may prefer + to make this interval much larger to reduce the impact on your + system. This option determines how often these checks are + performed. + "), + type => $types{integer}, + category => "system", + }, + { + name => "ZM_AUDIT_MIN_AGE", + default => "86400", + description => "The minimum age in seconds event data must be in order to be deleted.", + help => qqq(" + The zmaudit daemon exists to check that the saved information + in the database and on the filesystem match and are consistent + with each other. Event files or db records that are younger than + this setting will not be deleted and a warning will be given. + "), + type => $types{integer}, + category => "system", + }, + { + name => "ZM_FORCED_ALARM_SCORE", + default => "255", + description => "Score to give forced alarms", + help => qqq(" + The 'zmu' utility can be used to force an alarm on a monitor + rather than rely on the motion detection algorithms. This + option determines what score to give these alarms to + distinguish them from regular ones. It must be 255 or less. + "), + type => $types{integer}, + category => "config", + }, + { + name => "ZM_BULK_FRAME_INTERVAL", + default => "100", + description => "How often a bulk frame should be written to the database", + help => qqq(" + Traditionally ZoneMinder writes an entry into the Frames + database table for each frame that is captured and saved. This + works well in motion detection scenarios but when in a DVR + situation ('Record' or 'Mocord' mode) this results in a huge + number of frame writes and a lot of database and disk bandwidth + for very little additional information. Setting this to a + non-zero value will enabled ZoneMinder to group these non-alarm + frames into one 'bulk' frame entry which saves a lot of + bandwidth and space. The only disadvantage of this is that + timing information for individual frames is lost but in + constant frame rate situations this is usually not significant. + This setting is ignored in Modect mode and individual frames + are still written if an alarm occurs in Mocord mode also. + "), + type => $types{integer}, + category => "config", + }, + { + name => "ZM_EVENT_CLOSE_MODE", + default => "idle", + description => "When continuous events are closed.", + help => qqq(" + When a monitor is running in a continuous recording mode + (Record or Mocord) events are usually closed after a fixed + period of time (the section length). However in Mocord mode it + is possible that motion detection may occur near the end of a + section. This option controls what happens when an alarm occurs + in Mocord mode. The 'time' setting means that the event will be + closed at the end of the section regardless of alarm activity. + The 'idle' setting means that the event will be closed at the + end of the section if there is no alarm activity occurring at + the time otherwise it will be closed once the alarm is over + meaning the event may end up being longer than the normal + section length. The 'alarm' setting means that if an alarm + occurs during the event, the event will be closed once the + alarm is over regardless of when this occurs. This has the + effect of limiting the number of alarms to one per event and + the events will be shorter than the section length if an alarm + has occurred. + "), + type => $types{boolean}, + type => { + db_type =>"string", + hint =>"time|idle|alarm", + pattern =>qr|^([tia])|i, + format =>q( ($1 =~ /^t/) + ? "time" + : ($1 =~ /^i/ ? "idle" : "time" ) + ) + }, + category => "config", + }, + # Deprecated, superseded by event close mode + { + name => "ZM_FORCE_CLOSE_EVENTS", + default => "no", + description => "Close events at section ends.", + help => qqq(" + When a monitor is running in a continuous recording mode + (Record or Mocord) events are usually closed after a fixed + period of time (the section length). However in Mocord mode it + is possible that motion detection may occur near the end of a + section and ordinarily this will prevent the event being closed + until the motion has ceased. Switching this option on will + force the event closed at the specified time regardless of any + motion activity. + "), + type => $types{boolean}, + category => "hidden", + }, + { + name => "ZM_CREATE_ANALYSIS_IMAGES", + default => "yes", + description => "Create analysed alarm images with motion outlined", + help => qqq(" + By default during an alarm ZoneMinder records both the raw + captured image and one that has been analysed and had areas + where motion was detected outlined. This can be very useful + during zone configuration or in analysing why events occurred. + However it also incurs some overhead and in a stable system may + no longer be necessary. This parameter allows you to switch the + generation of these images off. + "), + type => $types{boolean}, + category => "config", + }, + { + name => "ZM_WEIGHTED_ALARM_CENTRES", + default => "no", + description => "Use a weighted algorithm to calculate the centre of an alarm", + help => qqq(" + ZoneMinder will always calculate the centre point of an alarm + in a zone to give some indication of where on the screen it is. + This can be used by the experimental motion tracking feature or + your own custom extensions. In the alarmed or filtered pixels + mode this is a simple midpoint between the extents of the + detected pxiesl. However in the blob method this can instead be + calculated using weighted pixel locations to give more accurate + positioning for irregularly shaped blobs. This method, while + more precise is also slower and so is turned off by default. + "), + type => $types{boolean}, + category => "config", + }, + { + name => "ZM_EVENT_IMAGE_DIGITS", + default => "5", + description => "How many significant digits are used in event image numbering", + help => qqq(" + As event images are captured they are stored to the filesystem + with a numerical index. By default this index has three digits + so the numbers start 001, 002 etc. This works works for most + scenarios as events with more than 999 frames are rarely + captured. However if you have extremely long events and use + external applications then you may wish to increase this to + ensure correct sorting of images in listings etc. Warning, + increasing this value on a live system may render existing + events unviewable as the event will have been saved with the + previous scheme. Decreasing this value should have no ill + effects. + "), + type => $types{integer}, + category => "config", + }, + { + name => "ZM_DEFAULT_ASPECT_RATIO", + default => "4:3", + description => "The default width:height aspect ratio used in monitors", + help => qqq(" + When specifying the dimensions of monitors you can click a + checkbox to ensure that the width stays in the correct ratio to + the height, or vice versa. This setting allows you to indicate + what the ratio of these settings should be. This should be + specified in the format : and the + default of 4:3 normally be acceptable but 11:9 is another + common setting. If the checkbox is not clicked when specifying + monitor dimensions this setting has no effect. + "), + type => $types{string}, + category => "config", + }, + { + name => "ZM_USER_SELF_EDIT", + default => "no", + description => "Allow unprivileged users to change their details", + help => qqq(" + Ordinarily only users with system edit privilege are able to + change users details. Switching this option on allows ordinary + users to change their passwords and their language settings + "), + type => $types{boolean}, + category => "config", + }, + { + name => "ZM_OPT_FRAME_SERVER", + default => "no", + description => "Should analysis farm out the writing of images to disk", + #requires => [ { name => "ZM_OPT_ADAPTIVE_SKIP", value => "yes" } ], + help => qqq(" + In some circumstances it is possible for a slow disk to take so + long writing images to disk that it causes the analysis daemon + to fall behind especially during high frame rate events. + Setting this option to yes enables a frame server daemon (zmf) + which will be sent the images from the analysis daemon and will + do the actual writing of images itself freeing up the analysis + daemon to get on with other things. Should this transmission + fail or other permanent or transient error occur, this function + will fall back to the analysis daemon. + "), + type => $types{boolean}, + category => "system", + }, + { + name => "ZM_FRAME_SOCKET_SIZE", + default => "0", + description => "Specify the frame server socket buffer size if non-standard", + requires => [ { name => "ZM_OPT_FRAME_SERVER", value => "yes" } ], + help => qqq(" + For large captured images it is possible for the writes from + the analysis daemon to the frame server to fail as the amount + to be written exceeds the default buffer size. While the images + are then written by the analysis daemon so no data is lost, it + defeats the object of the frame server daemon in the first + place. You can use this option to indicate that a larger buffer + size should be used. Note that you may have to change the + existing maximum socket buffer size on your system via sysctl + (or in /proc/sys/net/core/wmem_max) to allow this new size to + be set. Alternatively you can change the default buffer size on + your system in the same way in which case that will be used + with no change necessary in this option + "), + type => $types{integer}, + category => "system", + }, + { + name => "ZM_OPT_CONTROL", + default => "no", + description => "Support controllable (e.g. PTZ) cameras", + help => qqq(" + ZoneMinder includes limited support for controllable cameras. A + number of sample protocols are included and others can easily + be added. If you wish to control your cameras via ZoneMinder + then select this option otherwise if you only have static + cameras or use other control methods then leave this option + off. + "), + type => $types{boolean}, + category => "system", + }, + { + name => "ZM_OPT_TRIGGERS", + default => "no", + description => "Interface external event triggers via socket or device files", + help => qqq(" + ZoneMinder can interact with external systems which prompt or + cancel alarms. This is done via the zmtrigger.pl script. This + option indicates whether you want to use these external + triggers. Most people will say no here. + "), + type => $types{boolean}, + category => "system", + }, + { + name => "ZM_CHECK_FOR_UPDATES", + default => "yes", + description => "Check with zoneminder.com for updated versions", + help => qqq(" + 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 + "), + type => $types{boolean}, + category => "system", + }, + { + name => "ZM_TELEMETRY_DATA", + default => "yes", + description => "Send usage information to ZoneMinder", + help => qqq(" + Enable collection of usage information of the local system and send + it to the ZoneMinder development team. This data will be used to + determine things like who and where our customers are, how big their + systems are, the underlying hardware and operating system, etc. + This is being done for the sole purpoase of creating a better + product for our target audience. This script is intended to be + completely transparent to the end user, and can be disabled from + the web console under Options. + "), + type => $types{boolean}, + category => "system", + }, + { + name => "ZM_TELEMETRY_UUID", + default => "", + description => "Unique identifier for ZoneMinder telemetry", + help => qqq(" + This variable is auto-generated once by the system and is used to + uniquely identify it among all other ZoneMinder systems in + existence. + "), + type => $types{string}, + category => "dynamic", + }, + { + name => "ZM_TELEMETRY_LAST_UPLOAD", + default => "", + description => "When the last ZoneMinder telemetry upload ocurred", + help => "", + type => $types{integer}, + readonly => 1, + category => "dynamic", + }, + { + name => "ZM_UPDATE_CHECK_PROXY", + default => "", + description => "Proxy url if required to access zoneminder.com", + help => qqq(" + If you use a proxy to access the internet then ZoneMinder needs + to know so it can access zoneminder.com to check for updates. + If you do use a proxy enter the full proxy url here in the form + of http://:/ + "), + type => $types{string}, + category => "system", + }, + { + name => "ZM_SHM_KEY", + default => "0x7a6d0000", + description => "Shared memory root key to use", + help => qqq(" + 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. + "), + type => $types{hexadecimal}, + category => "system", + }, + # Deprecated, really no longer necessary + { + name => "ZM_WEB_REFRESH_METHOD", + default => "javascript", + description => "What method windows should use to refresh themselves", + help => qqq(" + Many windows in Javascript need to refresh themselves to keep + their information current. This option determines what method + they should use to do this. Choosing 'javascript' means that + each window will have a short JavaScript statement in with a + timer to prompt the refresh. This is the most compatible + method. Choosing 'http' means the refresh instruction is put in + the HTTP header. This is a cleaner method but refreshes are + interrupted or cancelled when a link in the window is clicked + meaning that the window will no longer refresh and this would + have to be done manually. + "), + type => { + db_type =>"string", + hint =>"javascript|http", + pattern =>qr|^([jh])|i, + format =>q( $1 =~ /^j/ + ? "javascript" + : "http" + ) + }, + category => "hidden", + }, + { + name => "ZM_WEB_EVENT_SORT_FIELD", + default => "DateTime", + description => "Default field the event lists are sorted by", + help => qqq(" + Events in lists can be initially ordered in any way you want. + This option controls what field is used to sort them. You can + modify this ordering from filters or by clicking on headings in + the lists themselves. Bear in mind however that the 'Prev' and + 'Next' links, when scrolling through events, relate to the + ordering in the lists and so not always to time based ordering. + "), + type => { + db_type =>"string", + hint =>"Id|Name|Cause|MonitorName|DateTime|Length|Frames|AlarmFrames|TotScore|AvgScore|MaxScore", + pattern =>qr|.|, + format =>q( $1 ) + }, + category => "web", + }, + { + name => "ZM_WEB_EVENT_SORT_ORDER", + default => "asc", + description => "Default order the event lists are sorted by", + help => qqq(" + Events in lists can be initially ordered in any way you want. + This option controls what order (ascending or descending) is + used to sort them. You can modify this ordering from filters or + by clicking on headings in the lists themselves. Bear in mind + however that the 'Prev' and 'Next' links, when scrolling + through events, relate to the ordering in the lists and so not + always to time based ordering. + "), + type => { + db_type =>"string", + hint =>"asc|desc", + pattern =>qr|^([ad])|i, + format =>q( $1 =~ /^a/i ? "asc" : "desc" ) + }, + category => "web", + }, + { + name => "ZM_WEB_EVENTS_PER_PAGE", + default => "25", + description => "How many events to list per page in paged mode", + help => qqq(" + In the event list view you can either list all events or just a + page at a time. This option controls how many events are listed + per page in paged mode and how often to repeat the column + headers in non-paged mode. + "), + type => $types{integer}, + category => "web", + }, + { + name => "ZM_WEB_LIST_THUMBS", + default => "no", + description => "Display mini-thumbnails of event images in event lists", + help => qqq(" + Ordinarily the event lists just display text details of the + events to save space and time. By switching this option on you + can also display small thumbnails to help you identify events + of interest. The size of these thumbnails is controlled by the + following two options. + "), + type => $types{boolean}, + category => "web", + }, + { + name => "ZM_WEB_LIST_THUMB_WIDTH", + default => "48", + description => "The width of the thumbnails that appear in the event lists", + help => qqq(" + This options controls the width of the thumbnail images that + appear in the event lists. It should be fairly small to fit in + with the rest of the table. If you prefer you can specify a + height instead in the next option but you should only use one + of the width or height and the other option should be set to + zero. If both width and height are specified then width will be + used and height ignored. + "), + type => $types{integer}, + requires => [ { name => "ZM_WEB_LIST_THUMBS", value => "yes" } ], + category => "web", + }, + { + name => "ZM_WEB_LIST_THUMB_HEIGHT", + default => "0", + description => "The height of the thumbnails that appear in the event lists", + help => qqq(" + This options controls the height of the thumbnail images that + appear in the event lists. It should be fairly small to fit in + with the rest of the table. If you prefer you can specify a + width instead in the previous option but you should only use + one of the width or height and the other option should be set + to zero. If both width and height are specified then width will + be used and height ignored. + "), + type => $types{integer}, + requires => [ { name => "ZM_WEB_LIST_THUMBS", value => "yes" } ], + category => "web", + }, + { + name => "ZM_WEB_USE_OBJECT_TAGS", + default => "yes", + description => "Wrap embed in object tags for media content", + help => qqq(" + There are two methods of including media content in web pages. + The most common way is use the EMBED tag which is able to give + some indication of the type of content. However this is not a + standard part of HTML. The official method is to use OBJECT + tags which are able to give more information allowing the + correct media viewers etc to be loaded. However these are less + widely supported and content may be specifically tailored to a + particular platform or player. This option controls whether + media content is enclosed in EMBED tags only or whether, where + appropriate, it is additionally wrapped in OBJECT tags. + Currently OBJECT tags are only used in a limited number of + circumstances but they may become more widespread in the + future. It is suggested that you leave this option on unless + you encounter problems playing some content. + "), + type => $types{boolean}, + category => "web", + }, + { + name => "ZM_WEB_H_REFRESH_MAIN", + default => "60", + introduction => qqq(" + There are now a number of options that are grouped into + bandwidth categories, this allows you to configure the + ZoneMinder client to work optimally over the various access + methods you might to access the client.\n\nThe next few options + control what happens when the client is running in 'high' + bandwidth mode. You should set these options for when accessing + the ZoneMinder client over a local network or high speed link. + In most cases the default values will be suitable as a starting + point. + "), + description => "How often (in seconds) the main console window should refresh itself", + help => qqq(" + The main console window lists a general status and the event + totals for all monitors. This is not a trivial task and should + not be repeated too frequently or it may affect the performance + of the rest of the system. + "), + type => $types{integer}, + category => "highband", + }, + { + name => "ZM_WEB_H_REFRESH_CYCLE", + default => "10", + description => "How often (in seconds) the cycle watch window swaps to the next monitor", + help => qqq(" + The cycle watch window is a method of continuously cycling + between images from all of your monitors. This option + determines how often to refresh with a new image. + "), + type => $types{integer}, + category => "highband", + }, + { + name => "ZM_WEB_H_REFRESH_IMAGE", + default => "3", + description => "How often (in seconds) the watched image is refreshed (if not streaming)", + help => qqq(" + The live images from a monitor can be viewed in either streamed + or stills mode. This option determines how often a stills image + is refreshed, it has no effect if streaming is selected. + "), + type => $types{integer}, + category => "highband", + }, + { + name => "ZM_WEB_H_REFRESH_STATUS", + default => "1", + description => "How often (in seconds) the status refreshes itself in the watch window", + help => qqq(" + The monitor window is actually made from several frames. The + one in the middle merely contains a monitor status which needs + to refresh fairly frequently to give a true indication. This + option determines that frequency. + "), + type => $types{integer}, + category => "highband", + }, + { + name => "ZM_WEB_H_REFRESH_EVENTS", + default => "5", + description => "How often (in seconds) the event listing is refreshed in the watch window", + help => qqq(" + The monitor window is actually made from several frames. The + lower framme contains a listing of the last few events for easy + access. This option determines how often this is refreshed. + "), + type => $types{integer}, + category => "highband", + }, + { + name => "ZM_WEB_H_CAN_STREAM", + default => "auto", + description => "Override the automatic detection of browser streaming capability", + help => qqq(" + If you know that your browser can handle image streams of the + type 'multipart/x-mixed-replace' but ZoneMinder does not detect + this correctly you can set this option to ensure that the + stream is delivered with or without the use of the Cambozola + plugin. Selecting 'yes' will tell ZoneMinder that your browser + can handle the streams natively, 'no' means that it can't and + so the plugin will be used while 'auto' lets ZoneMinder decide. + "), + type => $types{tristate}, + category => "highband", + }, + { + name => "ZM_WEB_H_STREAM_METHOD", + default => "jpeg", + description => "Which method should be used to send video streams to your browser.", + help => qqq(" + ZoneMinder can be configured to use either mpeg encoded video + or a series or still jpeg images when sending video streams. + This option defines which is used. If you choose mpeg you + should ensure that you have the appropriate plugins available + on your browser whereas choosing jpeg will work natively on + Mozilla and related browsers and with a Java applet on Internet + Explorer + "), + type => { + db_type =>"string", + hint =>"mpeg|jpeg", + pattern =>qr|^([mj])|i, + format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) + }, + category => "highband", + }, + { + name => "ZM_WEB_H_DEFAULT_SCALE", + default => "100", + description => "What the default scaling factor applied to 'live' or 'event' views is (%)", + help => qqq(" + Normally ZoneMinder will display 'live' or 'event' streams in + their native size. However if you have monitors with large + dimensions or a slow link you may prefer to reduce this size, + alternatively for small monitors you can enlarge it. This + options lets you specify what the default scaling factor will + be. It is expressed as a percentage so 100 is normal size, 200 + is double size etc. + "), + type => { + db_type =>"integer", + hint =>"25|33|50|75|100|150|200|300|400", + pattern =>qr|^(\d+)$|, + format =>q( $1 ) + }, + category => "highband", + }, + { + name => "ZM_WEB_H_DEFAULT_RATE", + default => "100", + description => "What the default replay rate factor applied to 'event' views is (%)", + help => qqq(" + Normally ZoneMinder will display 'event' streams at their + native rate, i.e. as close to real-time as possible. However if + you have long events it is often convenient to replay them at a + faster rate for review. This option lets you specify what the + default replay rate will be. It is expressed as a percentage so + 100 is normal rate, 200 is double speed etc. + "), + type => { + db_type =>"integer", + hint =>"25|50|100|150|200|400|1000|2500|5000|10000", + pattern =>qr|^(\d+)$|, + format =>q( $1 ) + }, + category => "highband", + }, + { + name => "ZM_WEB_H_VIDEO_BITRATE", + default => "150000", + description => "What the bitrate of the video encoded stream should be set to", + help => qqq(" + When encoding real video via the ffmpeg library a bit rate can + be specified which roughly corresponds to the available + bandwidth used for the stream. This setting effectively + corresponds to a 'quality' setting for the video. A low value + will result in a blocky image whereas a high value will produce + a clearer view. Note that this setting does not control the + frame rate of the video however the quality of the video + produced is affected both by this setting and the frame rate + that the video is produced at. A higher frame rate at a + particular bit rate result in individual frames being at a + lower quality. + "), + type => $types{integer}, + category => "highband", + }, + { + name => "ZM_WEB_H_VIDEO_MAXFPS", + default => "30", + description => "What the maximum frame rate for streamed video should be", + help => qqq(" + When using streamed video the main control is the bitrate which + determines how much data can be transmitted. However a lower + bitrate at high frame rates results in a lower quality image. + This option allows you to limit the maximum frame rate to + ensure that video quality is maintained. An additional + advantage is that encoding video at high frame rates is a + processor intensive task when for the most part a very high + frame rate offers little perceptible improvement over one that + has a more manageable resource requirement. Note, this option + is implemented as a cap beyond which binary reduction takes + place. So if you have a device capturing at 15fps and set this + option to 10fps then the video is not produced at 10fps, but + rather at 7.5fps (15 divided by 2) as the final frame rate must + be the original divided by a power of 2. + "), + type => $types{integer}, + category => "highband", + }, + { + name => "ZM_WEB_H_SCALE_THUMBS", + default => "no", + description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", + help => qqq(" + If unset, this option sends the whole image to the browser + which resizes it in the window. If set the image is scaled down + on the server before sending a reduced size image to the + browser to conserve bandwidth at the cost of cpu on the server. + Note that ZM can only perform the resizing if the appropriate + PHP graphics functionality is installed. This is usually + available in the php-gd package. + "), + type => $types{boolean}, + category => "highband", + }, + { + name => "ZM_WEB_H_EVENTS_VIEW", + default => "events", + description => "What the default view of multiple events should be.", + help => qqq(" + Stored events can be viewed in either an events list format or + in a timeline based one. This option sets the default view that + will be used. Choosing one view here does not prevent the other + view being used as it will always be selectable from whichever + view is currently being used. + "), + type => { + db_type =>"string", + hint =>"events|timeline", + pattern =>qr|^([lt])|i, + format =>q( $1 =~ /^e/ ? "events" : "timeline" ) + }, + category => "highband", + }, + { + name => "ZM_WEB_H_SHOW_PROGRESS", + default => "yes", + description => "Show the progress of replay in event view.", + help => qqq(" + When viewing events an event navigation panel and progress bar + is shown below the event itself. This allows you to jump to + specific points in the event, but can can also dynamically + update to display the current progress of the event replay + itself. This progress is calculated from the actual event + duration and is not directly linked to the replay itself, so on + limited bandwidth connections may be out of step with the + replay. This option allows you to turn off the progress + display, whilst still keeping the navigation aspect, where + bandwidth prevents it functioning effectively. + "), + type => $types{boolean}, + category => "highband", + }, + { + name => "ZM_WEB_H_AJAX_TIMEOUT", + default => "3000", + description => "How long to wait for Ajax request responses (ms)", + help => qqq(" + The newer versions of the live feed and event views use Ajax to + request information from the server and populate the views + dynamically. This option allows you to specify a timeout if + required after which requests are abandoned. A timeout may be + necessary if requests would overwise hang such as on a slow + connection. This would tend to consume a lot of browser memory + and make the interface unresponsive. Ordinarily no requests + should timeout so this setting should be set to a value greater + than the slowest expected response. This value is in + milliseconds but if set to zero then no timeout will be used. + "), + type => $types{integer}, + category => "highband", + }, + { + name => "ZM_WEB_M_REFRESH_MAIN", + default => "300", + description => "How often (in seconds) the main console window should refresh itself", + help => qqq(" + The main console window lists a general status and the event + totals for all monitors. This is not a trivial task and should + not be repeated too frequently or it may affect the performance + of the rest of the system. + "), + type => $types{integer}, + introduction => qqq(" + The next few options control what happens when the client is + running in 'medium' bandwidth mode. You should set these + options for when accessing the ZoneMinder client over a slower + cable or DSL link. In most cases the default values will be + suitable as a starting point. + "), + category => "medband", + }, + { + name => "ZM_WEB_M_REFRESH_CYCLE", + default => "20", + description => "How often (in seconds) the cycle watch window swaps to the next monitor", + help => qqq(" + The cycle watch window is a method of continuously cycling + between images from all of your monitors. This option + determines how often to refresh with a new image. + "), + type => $types{integer}, + category => "medband", + }, + { + name => "ZM_WEB_M_REFRESH_IMAGE", + default => "10", + description => "How often (in seconds) the watched image is refreshed (if not streaming)", + help => qqq(" + The live images from a monitor can be viewed in either streamed + or stills mode. This option determines how often a stills image + is refreshed, it has no effect if streaming is selected. + "), + type => $types{integer}, + category => "medband", + }, + { + name => "ZM_WEB_M_REFRESH_STATUS", + default => "5", + description => "How often (in seconds) the status refreshes itself in the watch window", + help => qqq(" + The monitor window is actually made from several frames. The + one in the middle merely contains a monitor status which needs + to refresh fairly frequently to give a true indication. This + option determines that frequency. + "), + type => $types{integer}, + category => "medband", + }, + { + name => "ZM_WEB_M_REFRESH_EVENTS", + default => "60", + description => "How often (in seconds) the event listing is refreshed in the watch window", + help => qqq(" + The monitor window is actually made from several frames. The + lower framme contains a listing of the last few events for easy + access. This option determines how often this is refreshed. + "), + type => $types{integer}, + category => "medband", + }, + { + name => "ZM_WEB_M_CAN_STREAM", + default => "auto", + description => "Override the automatic detection of browser streaming capability", + help => qqq(" + If you know that your browser can handle image streams of the + type 'multipart/x-mixed-replace' but ZoneMinder does not detect + this correctly you can set this option to ensure that the + stream is delivered with or without the use of the Cambozola + plugin. Selecting 'yes' will tell ZoneMinder that your browser + can handle the streams natively, 'no' means that it can't and + so the plugin will be used while 'auto' lets ZoneMinder decide. + "), + type => $types{tristate}, + category => "medband", + }, + { + name => "ZM_WEB_M_STREAM_METHOD", + default => "jpeg", + description => "Which method should be used to send video streams to your browser.", + help => qqq(" + ZoneMinder can be configured to use either mpeg encoded video + or a series or still jpeg images when sending video streams. + This option defines which is used. If you choose mpeg you + should ensure that you have the appropriate plugins available + on your browser whereas choosing jpeg will work natively on + Mozilla and related browsers and with a Java applet on Internet + Explorer + "), + type => { + db_type =>"string", + hint =>"mpeg|jpeg", + pattern =>qr|^([mj])|i, + format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) + }, + category => "medband", + }, + { + name => "ZM_WEB_M_DEFAULT_SCALE", + default => "100", + description => "What the default scaling factor applied to 'live' or 'event' views is (%)", + help => qqq(" + Normally ZoneMinder will display 'live' or 'event' streams in + their native size. However if you have monitors with large + dimensions or a slow link you may prefer to reduce this size, + alternatively for small monitors you can enlarge it. This + options lets you specify what the default scaling factor will + be. It is expressed as a percentage so 100 is normal size, 200 + is double size etc. + "), + type => { + db_type =>"integer", + hint =>"25|33|50|75|100|150|200|300|400", + pattern =>qr|^(\d+)$|, + format =>q( $1 ) + }, + category => "medband", + }, + { + name => "ZM_WEB_M_DEFAULT_RATE", + default => "100", + description => "What the default replay rate factor applied to 'event' views is (%)", + help => qqq(" + Normally ZoneMinder will display 'event' streams at their + native rate, i.e. as close to real-time as possible. However if + you have long events it is often convenient to replay them at a + faster rate for review. This option lets you specify what the + default replay rate will be. It is expressed as a percentage so + 100 is normal rate, 200 is double speed etc. + "), + type => { + db_type =>"integer", + hint =>"25|50|100|150|200|400|1000|2500|5000|10000", + pattern =>qr|^(\d+)$|, + format =>q( $1 ) + }, + category => "medband", + }, + { + name => "ZM_WEB_M_VIDEO_BITRATE", + default => "75000", + description => "What the bitrate of the video encoded stream should be set to", + help => qqq(" + When encoding real video via the ffmpeg library a bit rate can + be specified which roughly corresponds to the available + bandwidth used for the stream. This setting effectively + corresponds to a 'quality' setting for the video. A low value + will result in a blocky image whereas a high value will produce + a clearer view. Note that this setting does not control the + frame rate of the video however the quality of the video + produced is affected both by this setting and the frame rate + that the video is produced at. A higher frame rate at a + particular bit rate result in individual frames being at a + lower quality. + "), + type => $types{integer}, + category => "medband", + }, + { + name => "ZM_WEB_M_VIDEO_MAXFPS", + default => "10", + description => "What the maximum frame rate for streamed video should be", + help => qqq(" + When using streamed video the main control is the bitrate which + determines how much data can be transmitted. However a lower + bitrate at high frame rates results in a lower quality image. + This option allows you to limit the maximum frame rate to + ensure that video quality is maintained. An additional + advantage is that encoding video at high frame rates is a + processor intensive task when for the most part a very high + frame rate offers little perceptible improvement over one that + has a more manageable resource requirement. Note, this option + is implemented as a cap beyond which binary reduction takes + place. So if you have a device capturing at 15fps and set this + option to 10fps then the video is not produced at 10fps, but + rather at 7.5fps (15 divided by 2) as the final frame rate must + be the original divided by a power of 2. + "), + type => $types{integer}, + category => "medband", + }, + { + name => "ZM_WEB_M_SCALE_THUMBS", + default => "yes", + description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", + help => qqq(" + If unset, this option sends the whole image to the browser + which resizes it in the window. If set the image is scaled down + on the server before sending a reduced size image to the + browser to conserve bandwidth at the cost of cpu on the server. + Note that ZM can only perform the resizing if the appropriate + PHP graphics functionality is installed. This is usually + available in the php-gd package. + "), + type => $types{boolean}, + category => "medband", + }, + { + name => "ZM_WEB_M_EVENTS_VIEW", + default => "events", + description => "What the default view of multiple events should be.", + help => qqq(" + Stored events can be viewed in either an events list format or + in a timeline based one. This option sets the default view that + will be used. Choosing one view here does not prevent the other + view being used as it will always be selectable from whichever + view is currently being used. + "), + type => { + db_type =>"string", + hint =>"events|timeline", + pattern =>qr|^([lt])|i, + format =>q( $1 =~ /^e/ ? "events" : "timeline" ) + }, + category => "medband", + }, + { + name => "ZM_WEB_M_SHOW_PROGRESS", + default => "yes", + description => "Show the progress of replay in event view.", + help => qqq(" + When viewing events an event navigation panel and progress bar + is shown below the event itself. This allows you to jump to + specific points in the event, but can can also dynamically + update to display the current progress of the event replay + itself. This progress is calculated from the actual event + duration and is not directly linked to the replay itself, so on + limited bandwidth connections may be out of step with the + replay. This option allows you to turn off the progress + display, whilst still keeping the navigation aspect, where + bandwidth prevents it functioning effectively. + "), + type => $types{boolean}, + category => "medband", + }, + { + name => "ZM_WEB_M_AJAX_TIMEOUT", + default => "5000", + description => "How long to wait for Ajax request responses (ms)", + help => qqq(" + The newer versions of the live feed and event views use Ajax to + request information from the server and populate the views + dynamically. This option allows you to specify a timeout if + required after which requests are abandoned. A timeout may be + necessary if requests would overwise hang such as on a slow + connection. This would tend to consume a lot of browser memory + and make the interface unresponsive. Ordinarily no requests + should timeout so this setting should be set to a value greater + than the slowest expected response. This value is in + milliseconds but if set to zero then no timeout will be used. + "), + type => $types{integer}, + category => "medband", + }, + { + name => "ZM_WEB_L_REFRESH_MAIN", + default => "300", + description => "How often (in seconds) the main console window should refresh itself", + introduction => qqq(" + The next few options control what happens when the client is + running in 'low' bandwidth mode. You should set these options + for when accessing the ZoneMinder client over a modem or slow + link. In most cases the default values will be suitable as a + starting point. + "), + help => qqq(" + The main console window lists a general status and the event + totals for all monitors. This is not a trivial task and should + not be repeated too frequently or it may affect the performance + of the rest of the system. + "), + type => $types{integer}, + category => "lowband", + }, + { + name => "ZM_WEB_L_REFRESH_CYCLE", + default => "30", + description => "How often (in seconds) the cycle watch window swaps to the next monitor", + help => qqq(" + The cycle watch window is a method of continuously cycling + between images from all of your monitors. This option + determines how often to refresh with a new image. + "), + type => $types{integer}, + category => "lowband", + }, + { + name => "ZM_WEB_L_REFRESH_IMAGE", + default => "15", + description => "How often (in seconds) the watched image is refreshed (if not streaming)", + help => qqq(" + The live images from a monitor can be viewed in either streamed + or stills mode. This option determines how often a stills image + is refreshed, it has no effect if streaming is selected. + "), + type => $types{integer}, + category => "lowband", + }, + { + name => "ZM_WEB_L_REFRESH_STATUS", + default => "10", + description => "How often (in seconds) the status refreshes itself in the watch window", + help => qqq(" + The monitor window is actually made from several frames. The + one in the middle merely contains a monitor status which needs + to refresh fairly frequently to give a true indication. This + option determines that frequency. + "), + type => $types{integer}, + category => "lowband", + }, + { + name => "ZM_WEB_L_REFRESH_EVENTS", + default => "180", + description => "How often (in seconds) the event listing is refreshed in the watch window", + help => qqq(" + The monitor window is actually made from several frames. The + lower framme contains a listing of the last few events for easy + access. This option determines how often this is refreshed. + "), + type => $types{integer}, + category => "lowband", + }, + { + name => "ZM_WEB_L_CAN_STREAM", + default => "auto", + description => "Override the automatic detection of browser streaming capability", + help => qqq(" + If you know that your browser can handle image streams of the + type 'multipart/x-mixed-replace' but ZoneMinder does not detect + this correctly you can set this option to ensure that the + stream is delivered with or without the use of the Cambozola + plugin. Selecting 'yes' will tell ZoneMinder that your browser + can handle the streams natively, 'no' means that it can't and + so the plugin will be used while 'auto' lets ZoneMinder decide. + "), + type => $types{tristate}, + category => "lowband", + }, + { + name => "ZM_WEB_L_STREAM_METHOD", + default => "jpeg", + description => "Which method should be used to send video streams to your browser.", + help => qqq(" + ZoneMinder can be configured to use either mpeg encoded video + or a series or still jpeg images when sending video streams. + This option defines which is used. If you choose mpeg you + should ensure that you have the appropriate plugins available + on your browser whereas choosing jpeg will work natively on + Mozilla and related browsers and with a Java applet on Internet + Explorer + "), + type => { + db_type =>"string", + hint =>"mpeg|jpeg", + pattern =>qr|^([mj])|i, + format =>q( $1 =~ /^m/ ? "mpeg" : "jpeg" ) + }, + category => "lowband", + }, + { + name => "ZM_WEB_L_DEFAULT_SCALE", + default => "100", + description => "What the default scaling factor applied to 'live' or 'event' views is (%)", + help => qqq(" + Normally ZoneMinder will display 'live' or 'event' streams in + their native size. However if you have monitors with large + dimensions or a slow link you may prefer to reduce this size, + alternatively for small monitors you can enlarge it. This + options lets you specify what the default scaling factor will + be. It is expressed as a percentage so 100 is normal size, 200 + is double size etc. + "), + type => { + db_type =>"integer", + hint =>"25|33|50|75|100|150|200|300|400", + pattern =>qr|^(\d+)$|, + format =>q( $1 ) + }, + category => "lowband", + }, + { + name => "ZM_WEB_L_DEFAULT_RATE", + default => "100", + description => "What the default replay rate factor applied to 'event' views is (%)", + help => qqq(" + Normally ZoneMinder will display 'event' streams at their + native rate, i.e. as close to real-time as possible. However if + you have long events it is often convenient to replay them at a + faster rate for review. This option lets you specify what the + default replay rate will be. It is expressed as a percentage so + 100 is normal rate, 200 is double speed etc. + "), + type => { + db_type =>"integer", + hint =>"25|50|100|150|200|400|1000|2500|5000|10000", + pattern =>qr|^(\d+)$|, format=>q( $1 ) + }, + category => "lowband", + }, + { + name => "ZM_WEB_L_VIDEO_BITRATE", + default => "25000", + description => "What the bitrate of the video encoded stream should be set to", + help => qqq(" + When encoding real video via the ffmpeg library a bit rate can + be specified which roughly corresponds to the available + bandwidth used for the stream. This setting effectively + corresponds to a 'quality' setting for the video. A low value + will result in a blocky image whereas a high value will produce + a clearer view. Note that this setting does not control the + frame rate of the video however the quality of the video + produced is affected both by this setting and the frame rate + that the video is produced at. A higher frame rate at a + particular bit rate result in individual frames being at a + lower quality. + "), + type => $types{integer}, + category => "lowband", + }, + { + name => "ZM_WEB_L_VIDEO_MAXFPS", + default => "5", + description => "What the maximum frame rate for streamed video should be", + help => qqq(" + When using streamed video the main control is the bitrate which + determines how much data can be transmitted. However a lower + bitrate at high frame rates results in a lower quality image. + This option allows you to limit the maximum frame rate to + ensure that video quality is maintained. An additional + advantage is that encoding video at high frame rates is a + processor intensive task when for the most part a very high + frame rate offers little perceptible improvement over one that + has a more manageable resource requirement. Note, this option + is implemented as a cap beyond which binary reduction takes + place. So if you have a device capturing at 15fps and set this + option to 10fps then the video is not produced at 10fps, but + rather at 7.5fps (15 divided by 2) as the final frame rate must + be the original divided by a power of 2. + "), + type => $types{integer}, + category => "lowband", + }, + { + name => "ZM_WEB_L_SCALE_THUMBS", + default => "yes", + description => "Scale thumbnails in events, bandwidth versus cpu in rescaling", + help => qqq(" + If unset, this option sends the whole image to the browser + which resizes it in the window. If set the image is scaled down + on the server before sending a reduced size image to the + browser to conserve bandwidth at the cost of cpu on the server. + Note that ZM can only perform the resizing if the appropriate + PHP graphics functionality is installed. This is usually + available in the php-gd package. + "), + type => $types{boolean}, + category => "lowband", + }, + { + name => "ZM_WEB_L_EVENTS_VIEW", + default => "events", + description => "What the default view of multiple events should be.", + help => qqq(" + Stored events can be viewed in either an events list format or + in a timeline based one. This option sets the default view that + will be used. Choosing one view here does not prevent the other + view being used as it will always be selectable from whichever + view is currently being used. + "), + type => { + db_type =>"string", + hint =>"events|timeline", + pattern =>qr|^([lt])|i, + format =>q( $1 =~ /^e/ ? "events" : "timeline" ) + }, + category => "lowband", + }, + { + name => "ZM_WEB_L_SHOW_PROGRESS", + default => "no", + description => "Show the progress of replay in event view.", + help => qqq(" + When viewing events an event navigation panel and progress bar + is shown below the event itself. This allows you to jump to + specific points in the event, but can can also dynamically + update to display the current progress of the event replay + itself. This progress is calculated from the actual event + duration and is not directly linked to the replay itself, so on + limited bandwidth connections may be out of step with the + replay. This option allows you to turn off the progress + display, whilst still keeping the navigation aspect, where + bandwidth prevents it functioning effectively. + "), + type => $types{boolean}, + category => "lowband", + }, + { + name => "ZM_WEB_L_AJAX_TIMEOUT", + default => "10000", + description => "How long to wait for Ajax request responses (ms)", + help => qqq(" + The newer versions of the live feed and event views use Ajax to + request information from the server and populate the views + dynamically. This option allows you to specify a timeout if + required after which requests are abandoned. A timeout may be + necessary if requests would overwise hang such as on a slow + connection. This would tend to consume a lot of browser memory + and make the interface unresponsive. Ordinarily no requests + should timeout so this setting should be set to a value greater + than the slowest expected response. This value is in + milliseconds but if set to zero then no timeout will be used. + "), + type => $types{integer}, + category => "lowband", + }, + { + name => "ZM_DYN_LAST_VERSION", + default => "", + description => "What the last version of ZoneMinder recorded from zoneminder.com is", + help => "", + type => $types{string}, + readonly => 1, + category => "dynamic", + }, + { + name => "ZM_DYN_CURR_VERSION", + default => "@VERSION@", + description => qqq(" + What the effective current version of ZoneMinder is, might be + different from actual if versions ignored + "), + help => "", + type => $types{string}, + readonly => 1, + category => "dynamic", + }, + { + name => "ZM_DYN_DB_VERSION", + default => "@VERSION@", + description => "What the version of the database is, from zmupdate", + help => "", + type => $types{string}, + readonly => 1, + category => "dynamic", + }, + { + name => "ZM_DYN_LAST_CHECK", + default => "", + description => "When the last check for version from zoneminder.com was", + help => "", + type => $types{integer}, + readonly => 1, + category => "dynamic", + }, + { + name => "ZM_DYN_NEXT_REMINDER", + default => "", + description => "When the earliest time to remind about versions will be", + help => "", + type => $types{string}, + readonly => 1, + category => "dynamic", + }, + { + name => "ZM_DYN_DONATE_REMINDER_TIME", + default => 0, + description => "When the earliest time to remind about donations will be", + help => "", + type => $types{integer}, + readonly => 1, + category => "dynamic", + }, + { + name => "ZM_DYN_SHOW_DONATE_REMINDER", + default => "yes", + description => "Remind about donations or not", + help => "", + type => $types{boolean}, + readonly => 1, + category => "dynamic", + }, + { + name => "ZM_SSMTP_MAIL", + default => "no", + description => qqq(" + Use a SSMTP mail server if available. + NEW_MAIL_MODULES must be enabled + "), + requires => [ + { name => "ZM_OPT_EMAIL", value => "yes" }, + { name => "ZM_OPT_MESSAGE", value => "yes" }, + { name => "ZM_NEW_MAIL_MODULES", value => "yes" } + ], + help => qqq(" + SSMTP is a lightweight and efficient method to send email. + The SSMTP application is not installed by default. + NEW_MAIL_MODULES must also be enabled. + Please visit: http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder + for setup and configuration help. + "), + type => $types{boolean}, + category => "mail", + }, + { + name => "ZM_SSMTP_PATH", + default => "", + description => "SSMTP executable path", + requires => [{ name => "ZM_SSMTP_MAIL", value => "yes" }], + help => qqq(" + Recommend setting the path to the SSMTP application. + If path is not defined. Zoneminder will try to determine + the path via shell command. Example path: /usr/sbin/ssmtp. + "), + type => $types{string}, + category => "mail", + }, + ); our %options_hash = map { ( $_->{name}, $_ ) } @options; @@ -3904,25 +3904,25 @@ our %options_hash = map { ( $_->{name}, $_ ) } @options; # this module is 'require'd rather than 'use'd. See zmconfgen.pl. sub initialiseConfig { - return if ( $configInitialised ); + return if ( $configInitialised ); - # Do some initial data munging to finish the data structures - # Create option ids - my $option_id = 0; - foreach my $option ( @options ) +# Do some initial data munging to finish the data structures +# Create option ids + my $option_id = 0; + foreach my $option ( @options ) + { + if ( defined($option->{default}) ) { - if ( defined($option->{default}) ) - { - $option->{value} = $option->{default} - } - else - { - $option->{value} = ''; - } - #next if ( $option->{category} eq 'hidden' ); - $option->{id} = $option_id++; + $option->{value} = $option->{default} } - $configInitialised = 1; + else + { + $option->{value} = ''; + } +#next if ( $option->{category} eq 'hidden' ); + $option->{id} = $option_id++; + } + $configInitialised = 1; } 1; @@ -3934,11 +3934,11 @@ ZoneMinder::ConfigData - ZoneMinder Configuration Data module =head1 SYNOPSIS - use ZoneMinder::ConfigData; - use ZoneMinder::ConfigData qw(:all); +use ZoneMinder::ConfigData; +use ZoneMinder::ConfigData qw(:all); - loadConfigFromDB(); - saveConfigToDB(); +loadConfigFromDB(); +saveConfigToDB(); =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm index 0822dc3d3..9bd137c2e 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm @@ -45,17 +45,17 @@ our $AUTOLOAD; sub new { - my $class = shift; - my $id = shift; - my $self = {}; - $self->{name} = "PelcoD"; - if ( !defined($id) ) - { - Fatal( "No monitor defined when invoking protocol ".$self->{name} ); - } - $self->{id} = $id; - bless( $self, $class ); - return $self; + my $class = shift; + my $id = shift; + my $self = {}; + $self->{name} = "PelcoD"; + if ( !defined($id) ) + { + Fatal( "No monitor defined when invoking protocol ".$self->{name} ); + } + $self->{id} = $id; + bless( $self, $class ); + return $self; } sub DESTROY @@ -64,91 +64,91 @@ sub DESTROY 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} ); - } - croak( "Can't access $name member of object of class $class" ); + my $self = shift; + my $class = ref($self) || croak( "$self not object" ); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( exists($self->{$name}) ) + { + return( $self->{$name} ); + } + croak( "Can't access $name member of object of class $class" ); } sub getKey { - my $self = shift; - return( $self->{id} ); + my $self = shift; + return( $self->{id} ); } sub open { - my $self = shift; - Fatal( "No open method defined for protocol ".$self->{name} ); + my $self = shift; + Fatal( "No open method defined for protocol ".$self->{name} ); } sub close { - my $self = shift; - Fatal( "No close method defined for protocol ".$self->{name} ); + my $self = shift; + Fatal( "No close method defined for protocol ".$self->{name} ); } sub loadMonitor { - my $self = shift; - if ( !$self->{Monitor} ) + my $self = shift; + if ( !$self->{Monitor} ) + { + if ( !($self->{Monitor} = zmDbGetMonitor( $self->{id} )) ) { - if ( !($self->{Monitor} = zmDbGetMonitor( $self->{id} )) ) - { - Fatal( "Monitor id ".$self->{id}." not found or not controllable" ); - } - if ( defined($self->{Monitor}->{AutoStopTimeout}) ) - { - # Convert to microseconds. - $self->{Monitor}->{AutoStopTimeout} = int(1000000*$self->{Monitor}->{AutoStopTimeout}); - } + Fatal( "Monitor id ".$self->{id}." not found or not controllable" ); } + if ( defined($self->{Monitor}->{AutoStopTimeout}) ) + { +# Convert to microseconds. + $self->{Monitor}->{AutoStopTimeout} = int(1000000*$self->{Monitor}->{AutoStopTimeout}); + } + } } sub getParam { - my $self = shift; - my $params = shift; - my $name = shift; - my $default = shift; + my $self = shift; + my $params = shift; + my $name = shift; + my $default = shift; - if ( defined($params->{$name}) ) - { - return( $params->{$name} ); - } - elsif ( defined($default) ) - { - return( $default ); - } - Fatal( "Missing mandatory parameter '$name'" ); + if ( defined($params->{$name}) ) + { + return( $params->{$name} ); + } + elsif ( defined($default) ) + { + return( $default ); + } + Fatal( "Missing mandatory parameter '$name'" ); } sub executeCommand { - my $self = shift; - my $params = shift; + my $self = shift; + my $params = shift; - $self->loadMonitor(); + $self->loadMonitor(); - my $command = $params->{command}; - delete $params->{command}; + my $command = $params->{command}; + delete $params->{command}; - #if ( !defined($self->{$command}) ) - #{ - #Fatal( "Unsupported command '$command'" ); - #} - &{$self->{$command}}( $self, $params ); +#if ( !defined($self->{$command}) ) +#{ +#Fatal( "Unsupported command '$command'" ); +#} + &{$self->{$command}}( $self, $params ); } sub printMsg { - my $self = shift; - Fatal( "No printMsg method defined for protocol ".$self->{name} ); + my $self = shift; + Fatal( "No printMsg method defined for protocol ".$self->{name} ); } 1; @@ -161,8 +161,8 @@ ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS - use ZoneMinder::Database; - blah blah blah +use ZoneMinder::Database; +blah blah blah =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index fbbe0a027..20c3496c5 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -42,13 +42,13 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( 'functions' => [ qw( - zmDbConnect - zmDbDisconnect - zmDbGetMonitors - zmDbGetMonitor - zmDbGetMonitorAndControl - ) ] -); + zmDbConnect + zmDbDisconnect + zmDbGetMonitors + zmDbGetMonitor + zmDbGetMonitorAndControl + ) ] + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); @@ -72,48 +72,48 @@ our $dbh = undef; sub zmDbConnect { - my $force = shift; - if ( $force ) - { - zmDbDisconnect(); - } - if ( !defined( $dbh ) ) - { - my $socket; - my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); + my $force = shift; + if ( $force ) + { + zmDbDisconnect(); + } + if ( !defined( $dbh ) ) + { + my $socket; + my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); - if ( defined($portOrSocket) ) - { - if ( $portOrSocket =~ /^\// ) - { - $socket = ";mysql_socket=".$portOrSocket; - } - else - { - $socket = ";host=".$host.";port=".$portOrSocket; - } - } - else - { - $socket = ";host=".$Config{ZM_DB_HOST}; - } - $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .$socket - , $Config{ZM_DB_USER} - , $Config{ZM_DB_PASS} - ); - $dbh->trace( 0 ); + if ( defined($portOrSocket) ) + { + if ( $portOrSocket =~ /^\// ) + { + $socket = ";mysql_socket=".$portOrSocket; + } + else + { + $socket = ";host=".$host.";port=".$portOrSocket; + } } - return( $dbh ); + else + { + $socket = ";host=".$Config{ZM_DB_HOST}; + } + $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} + .$socket + , $Config{ZM_DB_USER} + , $Config{ZM_DB_PASS} + ); + $dbh->trace( 0 ); + } + return( $dbh ); } sub zmDbDisconnect { - if ( defined( $dbh ) ) - { - $dbh->disconnect(); - $dbh = undef; - } + if ( defined( $dbh ) ) + { + $dbh->disconnect(); + $dbh = undef; + } } use constant DB_MON_ALL => 0; # All monitors @@ -125,86 +125,86 @@ use constant DB_MON_PASSIVE => 5; # All monitors that are in nodect state sub zmDbGetMonitors { - zmDbConnect(); + zmDbConnect(); - my $function = shift || DB_MON_ALL; - my $sql = "select * from Monitors"; + my $function = shift || DB_MON_ALL; + my $sql = "select * from Monitors"; - if ( $function ) + if ( $function ) + { + if ( $function == DB_MON_CAPT ) { - if ( $function == DB_MON_CAPT ) - { - $sql .= " where Function >= 'Monitor'"; - } - elsif ( $function == DB_MON_ACTIVE ) - { - $sql .= " where Function > 'Monitor'"; - } - elsif ( $function == DB_MON_MOTION ) - { - $sql .= " where Function = 'Modect' or Function = 'Mocord'"; - } - elsif ( $function == DB_MON_RECORD ) - { - $sql .= " where Function = 'Record' or Function = 'Mocord'"; - } - elsif ( $function == DB_MON_PASSIVE ) - { - $sql .= " where Function = 'Nodect'"; - } + $sql .= " where Function >= 'Monitor'"; } - my $sth = $dbh->prepare_cached( $sql ) - or croak( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() - or croak( "Can't execute '$sql': ".$sth->errstr() ); - - my @monitors; - while( my $monitor = $sth->fetchrow_hashref() ) + elsif ( $function == DB_MON_ACTIVE ) { - push( @monitors, $monitor ); + $sql .= " where Function > 'Monitor'"; } - $sth->finish(); - return( \@monitors ); + elsif ( $function == DB_MON_MOTION ) + { + $sql .= " where Function = 'Modect' or Function = 'Mocord'"; + } + elsif ( $function == DB_MON_RECORD ) + { + $sql .= " where Function = 'Record' or Function = 'Mocord'"; + } + elsif ( $function == DB_MON_PASSIVE ) + { + $sql .= " where Function = 'Nodect'"; + } + } + my $sth = $dbh->prepare_cached( $sql ) + or croak( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute() + or croak( "Can't execute '$sql': ".$sth->errstr() ); + + my @monitors; + while( my $monitor = $sth->fetchrow_hashref() ) + { + push( @monitors, $monitor ); + } + $sth->finish(); + return( \@monitors ); } sub zmDbGetMonitor { - zmDbConnect(); + zmDbConnect(); - my $id = shift; + my $id = shift; - return( undef ) if ( !defined($id) ); + return( undef ) if ( !defined($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(); + 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 { - zmDbConnect(); + zmDbConnect(); - my $id = shift; + my $id = shift; - return( undef ) if ( !defined($id) ); + return( undef ) if ( !defined($id) ); - my $sql = "SELECT C.*,M.*,C.Protocol - FROM Monitors as M - INNER JOIN Controls as C on (M.ControlId = C.Id) - WHERE M.Id = ?" + my $sql = "SELECT C.*,M.*,C.Protocol + FROM Monitors as M + INNER JOIN Controls as C on (M.ControlId = C.Id) + WHERE M.Id = ?" ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $id ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - my $monitor = $sth->fetchrow_hashref(); + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $id ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $monitor = $sth->fetchrow_hashref(); - return( $monitor ); + return( $monitor ); } 1; @@ -217,8 +217,8 @@ ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS - use ZoneMinder::Database; - blah blah blah +use ZoneMinder::Database; +blah blah blah =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 67c117460..b7768946b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -43,8 +43,8 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( 'functions' => [ qw( - ) ] -); + ) ] + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); @@ -66,214 +66,214 @@ use ZoneMinder::Database qw(:all); use POSIX; sub new { - my ( $parent, $id, $data ) = @_; + my ( $parent, $id, $data ) = @_; - my $self = {}; - bless $self, $parent; - $$self{dbh} = $ZoneMinder::Database::dbh; + my $self = {}; + bless $self, $parent; + $$self{dbh} = $ZoneMinder::Database::dbh; #zmDbConnect(); - if ( ( $$self{Id} = $id ) or $data ) { + if ( ( $$self{Id} = $id ) or $data ) { #$log->debug("loading $parent $id") if $debug or DEBUG_ALL; - $self->load( $data ); - } - return $self; + $self->load( $data ); + } + return $self; } # end sub new sub load { - my ( $self, $data ) = @_; - my $type = ref $self; - if ( ! $data ) { + my ( $self, $data ) = @_; + my $type = ref $self; + if ( ! $data ) { #$log->debug("Object::load Loading from db $type"); - $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Events WHERE Id=?', {}, $$self{Id} ); - if ( ! $data ) { - Error( "Failure to load Event record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); - } else { - Debug( 3, "Loaded Event $$self{Id}" ); - } # end if - } # end if ! $data - if ( $data and %$data ) { - @$self{keys %$data} = values %$data; - } # end if + $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Events WHERE Id=?', {}, $$self{Id} ); + if ( ! $data ) { + Error( "Failure to load Event record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); + } else { + Debug( 3, "Loaded Event $$self{Id}" ); + } # end if + } # end if ! $data + if ( $data and %$data ) { + @$self{keys %$data} = values %$data; + } # end if } # end sub load sub Name { - if ( @_ > 1 ) { - $_[0]{Name} = $_[1]; - } - return $_[0]{Name}; + if ( @_ > 1 ) { + $_[0]{Name} = $_[1]; + } + return $_[0]{Name}; } # end sub Path sub find { - shift if $_[0] eq 'ZoneMinder::Event'; - my %sql_filters = @_; + shift if $_[0] eq 'ZoneMinder::Event'; + my %sql_filters = @_; - my $sql = 'SELECT * FROM Events'; - my @sql_filters; - my @sql_values; + my $sql = 'SELECT * FROM Events'; + my @sql_filters; + my @sql_values; - if ( exists $sql_filters{Name} ) { - push @sql_filters , ' Name = ? '; - push @sql_values, $sql_filters{Name}; - } + if ( exists $sql_filters{Name} ) { + push @sql_filters , ' Name = ? '; + push @sql_values, $sql_filters{Name}; + } - $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; - $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; + $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; + $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; - my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); - my $res = $sth->execute( @sql_values ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( @sql_values ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - my @results; + my @results; - while( my $db_filter = $sth->fetchrow_hashref() ) { - my $filter = new ZoneMinder::Event( $$db_filter{Id}, $db_filter ); - push @results, $filter; - } # end while - return @results; + while( my $db_filter = $sth->fetchrow_hashref() ) { + my $filter = new ZoneMinder::Event( $$db_filter{Id}, $db_filter ); + push @results, $filter; + } # end while + return @results; } sub find_one { - my @results = find(@_); - return $results[0] if @results; + my @results = find(@_); + return $results[0] if @results; } sub getEventPath { - my $event = shift; + my $event = shift; - my $event_path = ""; - if ( $Config{ZM_USE_DEEP_STORAGE} ) - { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.strftime( "%y/%m/%d/%H/%M/%S", - localtime($event->{Time}) - ) - ; - } - else - { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.$event->{Id} - ; - } + my $event_path = ""; + if ( $Config{ZM_USE_DEEP_STORAGE} ) + { + $event_path = $Config{ZM_DIR_EVENTS} + .'/'.$event->{MonitorId} + .'/'.strftime( "%y/%m/%d/%H/%M/%S", + localtime($event->{Time}) + ) + ; + } + else + { + $event_path = $Config{ZM_DIR_EVENTS} + .'/'.$event->{MonitorId} + .'/'.$event->{Id} + ; + } - if ( index($Config{ZM_DIR_EVENTS},'/') != 0 ){ - $event_path = $Config{ZM_PATH_WEB} - .'/'.$event_path - ; - } - return( $event_path ); + if ( index($Config{ZM_DIR_EVENTS},'/') != 0 ){ + $event_path = $Config{ZM_PATH_WEB} + .'/'.$event_path + ; + } + return( $event_path ); } sub GenerateVideo { - my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_; + my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_; - my $event_path = getEventPath( $self ); - chdir( $event_path ); - ( my $video_name = $self->{Name} ) =~ s/\s/_/g; + my $event_path = getEventPath( $self ); + chdir( $event_path ); + ( my $video_name = $self->{Name} ) =~ s/\s/_/g; - my @file_parts; - if ( $rate ) - { - my $file_rate = $rate; - $file_rate =~ s/\./_/; - $file_rate =~ s/_00//; - $file_rate =~ s/(_\d+)0+$/$1/; - $file_rate = 'r'.$file_rate; - push( @file_parts, $file_rate ); - } - elsif ( $fps ) - { - my $file_fps = $fps; - $file_fps =~ s/\./_/; - $file_fps =~ s/_00//; - $file_fps =~ s/(_\d+)0+$/$1/; - $file_fps = 'R'.$file_fps; - push( @file_parts, $file_fps ); - } + my @file_parts; + if ( $rate ) + { + my $file_rate = $rate; + $file_rate =~ s/\./_/; + $file_rate =~ s/_00//; + $file_rate =~ s/(_\d+)0+$/$1/; + $file_rate = 'r'.$file_rate; + push( @file_parts, $file_rate ); + } + elsif ( $fps ) + { + my $file_fps = $fps; + $file_fps =~ s/\./_/; + $file_fps =~ s/_00//; + $file_fps =~ s/(_\d+)0+$/$1/; + $file_fps = 'R'.$file_fps; + push( @file_parts, $file_fps ); + } - if ( $scale ) - { - my $file_scale = $scale; - $file_scale =~ s/\./_/; - $file_scale =~ s/_00//; - $file_scale =~ s/(_\d+)0+$/$1/; - $file_scale = 's'.$file_scale; - push( @file_parts, $file_scale ); - } - elsif ( $size ) - { - my $file_size = 'S'.$size; - push( @file_parts, $file_size ); - } - my $video_file = "$video_name-".$file_parts[0]."-".$file_parts[1].".$format"; - if ( $overwrite || !-s $video_file ) - { - Info( "Creating video file $video_file for event $self->{Id}\n" ); + if ( $scale ) + { + my $file_scale = $scale; + $file_scale =~ s/\./_/; + $file_scale =~ s/_00//; + $file_scale =~ s/(_\d+)0+$/$1/; + $file_scale = 's'.$file_scale; + push( @file_parts, $file_scale ); + } + elsif ( $size ) + { + my $file_size = 'S'.$size; + push( @file_parts, $file_size ); + } + my $video_file = "$video_name-".$file_parts[0]."-".$file_parts[1].".$format"; + if ( $overwrite || !-s $video_file ) + { + Info( "Creating video file $video_file for event $self->{Id}\n" ); - my $frame_rate = sprintf( "%.2f", $self->{Frames}/$self->{FullLength} ); - if ( $rate ) - { - if ( $rate != 1.0 ) - { - $frame_rate *= $rate; - } - } - elsif ( $fps ) - { - $frame_rate = $fps; - } + my $frame_rate = sprintf( "%.2f", $self->{Frames}/$self->{FullLength} ); + if ( $rate ) + { + if ( $rate != 1.0 ) + { + $frame_rate *= $rate; + } + } + elsif ( $fps ) + { + $frame_rate = $fps; + } - my $width = $self->{MonitorWidth}; - my $height = $self->{MonitorHeight}; - my $video_size = " ${width}x${height}"; + my $width = $self->{MonitorWidth}; + my $height = $self->{MonitorHeight}; + my $video_size = " ${width}x${height}"; - if ( $scale ) - { - if ( $scale != 1.0 ) - { - $width = int($width*$scale); - $height = int($height*$scale); - $video_size = " ${width}x${height}"; - } - } - elsif ( $size ) - { - $video_size = $size; - } - my $command = $Config{ZM_PATH_FFMPEG} - ." -y -r $frame_rate " - .$Config{ZM_FFMPEG_INPUT_OPTIONS} - ." -i %0" - .$Config{ZM_EVENT_IMAGE_DIGITS} - ."d-capture.jpg -s $video_size " + if ( $scale ) + { + if ( $scale != 1.0 ) + { + $width = int($width*$scale); + $height = int($height*$scale); + $video_size = " ${width}x${height}"; + } + } + elsif ( $size ) + { + $video_size = $size; + } + my $command = $Config{ZM_PATH_FFMPEG} + ." -y -r $frame_rate " + .$Config{ZM_FFMPEG_INPUT_OPTIONS} + ." -i %0" + .$Config{ZM_EVENT_IMAGE_DIGITS} + ."d-capture.jpg -s $video_size " #. " -f concat -i /tmp/event_files.txt" - ." -s $video_size " - .$Config{ZM_FFMPEG_OUTPUT_OPTIONS} - ." '$video_file' > ffmpeg.log 2>&1" - ; - Debug( $command."\n" ); - my $output = qx($command); + ." -s $video_size " + .$Config{ZM_FFMPEG_OUTPUT_OPTIONS} + ." '$video_file' > ffmpeg.log 2>&1" + ; + Debug( $command."\n" ); + my $output = qx($command); - my $status = $? >> 8; - if ( $status ) - { - Error( "Unable to generate video, check " - .$event_path."/ffmpeg.log for details" - ); - return; - } + my $status = $? >> 8; + if ( $status ) + { + Error( "Unable to generate video, check " + .$event_path."/ffmpeg.log for details" + ); + return; + } - Info( "Finished $video_file\n" ); - return $event_path.'/'.$video_file; - } else { - Info( "Video file $video_file already exists for event $self->{Id}\n" ); - return $event_path.'/'.$video_file; - } - return; + Info( "Finished $video_file\n" ); + return $event_path.'/'.$video_file; + } else { + Info( "Video file $video_file already exists for event $self->{Id}\n" ); + return $event_path.'/'.$video_file; + } + return; } # end sub GenerateVideo 1; @@ -286,8 +286,8 @@ ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS - use ZoneMinder::Event; - blah blah blah +use ZoneMinder::Event; +blah blah blah =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index b7806ec4a..473578a72 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -66,378 +66,378 @@ use ZoneMinder::Database qw(:all); use POSIX; sub new { - my ( $parent, $id, $data ) = @_; + my ( $parent, $id, $data ) = @_; - my $self = {}; - bless $self, $parent; - $$self{dbh} = $ZoneMinder::Database::dbh; + my $self = {}; + bless $self, $parent; + $$self{dbh} = $ZoneMinder::Database::dbh; #zmDbConnect(); - if ( ( $$self{Id} = $id ) or $data ) { + if ( ( $$self{Id} = $id ) or $data ) { #$log->debug("loading $parent $id") if $debug or DEBUG_ALL; - $self->load( $data ); - } - return $self; + $self->load( $data ); + } + return $self; } # end sub new sub load { - my ( $self, $data ) = @_; - my $type = ref $self; - if ( ! $data ) { + my ( $self, $data ) = @_; + my $type = ref $self; + if ( ! $data ) { #$log->debug("Object::load Loading from db $type"); - $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Filter WHERE Id=?', {}, $$self{Id} ); - if ( ! $data ) { - Error( "Failure to load Filter record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); - } else { - Debug( 3, "Loaded Filter $$self{Id}" ); - } # end if - } # end if ! $data - if ( $data and %$data ) { - @$self{keys %$data} = values %$data; - } # end if + $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Filter WHERE Id=?', {}, $$self{Id} ); + if ( ! $data ) { + Error( "Failure to load Filter record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); + } else { + Debug( 3, "Loaded Filter $$self{Id}" ); + } # end if + } # end if ! $data + if ( $data and %$data ) { + @$self{keys %$data} = values %$data; + } # end if } # end sub load sub Name { - if ( @_ > 1 ) { - $_[0]{Name} = $_[1]; - } - return $_[0]{Name}; + if ( @_ > 1 ) { + $_[0]{Name} = $_[1]; + } + return $_[0]{Name}; } # end sub Path sub find { - shift if $_[0] eq 'ZoneMinder::Filter'; - my %sql_filters = @_; + shift if $_[0] eq 'ZoneMinder::Filter'; + my %sql_filters = @_; - my $sql = 'SELECT * FROM Filters'; - my @sql_filters; - my @sql_values; + my $sql = 'SELECT * FROM Filters'; + my @sql_filters; + my @sql_values; - if ( exists $sql_filters{Name} ) { - push @sql_filters , ' Name = ? '; - push @sql_values, $sql_filters{Name}; - } + if ( exists $sql_filters{Name} ) { + push @sql_filters , ' Name = ? '; + push @sql_values, $sql_filters{Name}; + } - $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; - $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; + $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; + $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; - my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); - my $res = $sth->execute( @sql_values ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( @sql_values ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - my @results; + my @results; - while( my $db_filter = $sth->fetchrow_hashref() ) { - my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); - push @results, $filter; - } # end while - return @results; + while( my $db_filter = $sth->fetchrow_hashref() ) { + my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); + push @results, $filter; + } # end while + return @results; } sub find_one { - my @results = find(@_); - return $results[0] if @results; + my @results = find(@_); + return $results[0] if @results; } sub Execute { - my $self = $_[0]; + my $self = $_[0]; - my $sql = $self->Sql(); + my $sql = $self->Sql(); - if ( $self->{HasDiskPercent} ) - { - my $disk_percent = getDiskPercent(); - $sql =~ s/zmDiskPercent/$disk_percent/g; - } - if ( $self->{HasDiskBlocks} ) - { - my $disk_blocks = getDiskBlocks(); - $sql =~ s/zmDiskBlocks/$disk_blocks/g; - } - if ( $self->{HasSystemLoad} ) - { - my $load = getLoad(); - $sql =~ s/zmSystemLoad/$load/g; - } + if ( $self->{HasDiskPercent} ) + { + my $disk_percent = getDiskPercent(); + $sql =~ s/zmDiskPercent/$disk_percent/g; + } + if ( $self->{HasDiskBlocks} ) + { + my $disk_blocks = getDiskBlocks(); + $sql =~ s/zmDiskBlocks/$disk_blocks/g; + } + if ( $self->{HasSystemLoad} ) + { + my $load = getLoad(); + $sql =~ s/zmSystemLoad/$load/g; + } - my $sth = $$self{dbh}->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$$self{dbh}->errstr() ); - my $res = $sth->execute(); - if ( !$res ) - { - Error( "Can't execute filter '$sql', ignoring: ".$sth->errstr() ); - return; - } - my @results; + my $sth = $$self{dbh}->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$$self{dbh}->errstr() ); + my $res = $sth->execute(); + if ( !$res ) + { + Error( "Can't execute filter '$sql', ignoring: ".$sth->errstr() ); + return; + } + my @results; - while( my $event = $sth->fetchrow_hashref() ) { - push @results, $event; - } - $sth->finish(); - return @results; + while( my $event = $sth->fetchrow_hashref() ) { + push @results, $event; + } + $sth->finish(); + return @results; } sub Sql { - my $self = $_[0]; - if ( ! $$self{Sql} ) { - my $filter_expr = ZoneMinder::General::jsonDecode( $self->{Query} ); - my $sql = "SELECT E.Id, - E.MonitorId, - M.Name as MonitorName, - M.DefaultRate, - M.DefaultScale, - E.Name, - E.Cause, - E.Notes, - E.StartTime, - unix_timestamp(E.StartTime) as Time, - E.Length, - E.Frames, - E.AlarmFrames, - E.TotScore, - E.AvgScore, - E.MaxScore, - E.Archived, - E.Videoed, - E.Uploaded, - E.Emailed, - E.Messaged, - E.Executed - FROM Events as E - INNER JOIN Monitors as M on M.Id = E.MonitorId - "; - $self->{Sql} = ''; + my $self = $_[0]; + if ( ! $$self{Sql} ) { + my $filter_expr = ZoneMinder::General::jsonDecode( $self->{Query} ); + my $sql = "SELECT E.Id, + E.MonitorId, + M.Name as MonitorName, + M.DefaultRate, + M.DefaultScale, + E.Name, + E.Cause, + E.Notes, + E.StartTime, + unix_timestamp(E.StartTime) as Time, + E.Length, + E.Frames, + E.AlarmFrames, + E.TotScore, + E.AvgScore, + E.MaxScore, + E.Archived, + E.Videoed, + E.Uploaded, + E.Emailed, + E.Messaged, + E.Executed + FROM Events as E + INNER JOIN Monitors as M on M.Id = E.MonitorId + "; + $self->{Sql} = ''; - if ( $filter_expr->{terms} ) { - for ( my $i = 0; $i < @{$filter_expr->{terms}}; $i++ ) { - if ( exists($filter_expr->{terms}[$i]->{cnj}) ) { - $self->{Sql} .= " ".$filter_expr->{terms}[$i]->{cnj}." "; - } - if ( exists($filter_expr->{terms}[$i]->{obr}) ) { - $self->{Sql} .= " ".str_repeat( "(", $filter_expr->{terms}[$i]->{obr} )." "; - } - my $value = $filter_expr->{terms}[$i]->{val}; - my @value_list; - if ( $filter_expr->{terms}[$i]->{attr} ) { - if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) { - my ( $temp_attr_name ) = $filter_expr->{terms}[$i]->{attr} =~ /^Monitor(.+)$/; - $self->{Sql} .= "M.".$temp_attr_name; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) { - $self->{Sql} .= "E.StartTime"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) { - $self->{Sql} .= "to_days( E.StartTime )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) { - $self->{Sql} .= "extract( hour_second from E.StartTime )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Weekday' ) { - $self->{Sql} .= "weekday( E.StartTime )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskPercent' ) { - $self->{Sql} .= "zmDiskPercent"; - $self->{HasDiskPercent} = !undef; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskBlocks' ) { - $self->{Sql} .= "zmDiskBlocks"; - $self->{HasDiskBlocks} = !undef; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'SystemLoad' ) { - $self->{Sql} .= "zmSystemLoad"; - $self->{HasSystemLoad} = !undef; - } else { - $self->{Sql} .= "E.".$filter_expr->{terms}[$i]->{attr}; - } + if ( $filter_expr->{terms} ) { + for ( my $i = 0; $i < @{$filter_expr->{terms}}; $i++ ) { + if ( exists($filter_expr->{terms}[$i]->{cnj}) ) { + $self->{Sql} .= " ".$filter_expr->{terms}[$i]->{cnj}." "; + } + if ( exists($filter_expr->{terms}[$i]->{obr}) ) { + $self->{Sql} .= " ".str_repeat( "(", $filter_expr->{terms}[$i]->{obr} )." "; + } + my $value = $filter_expr->{terms}[$i]->{val}; + my @value_list; + if ( $filter_expr->{terms}[$i]->{attr} ) { + if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) { + my ( $temp_attr_name ) = $filter_expr->{terms}[$i]->{attr} =~ /^Monitor(.+)$/; + $self->{Sql} .= "M.".$temp_attr_name; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) { + $self->{Sql} .= "E.StartTime"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) { + $self->{Sql} .= "to_days( E.StartTime )"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) { + $self->{Sql} .= "extract( hour_second from E.StartTime )"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Weekday' ) { + $self->{Sql} .= "weekday( E.StartTime )"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskPercent' ) { + $self->{Sql} .= "zmDiskPercent"; + $self->{HasDiskPercent} = !undef; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskBlocks' ) { + $self->{Sql} .= "zmDiskBlocks"; + $self->{HasDiskBlocks} = !undef; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'SystemLoad' ) { + $self->{Sql} .= "zmSystemLoad"; + $self->{HasSystemLoad} = !undef; + } else { + $self->{Sql} .= "E.".$filter_expr->{terms}[$i]->{attr}; + } - ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; - foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { - if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) { - $value = "'$temp_value'"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Name' - || $filter_expr->{terms}[$i]->{attr} eq 'Cause' - || $filter_expr->{terms}[$i]->{attr} eq 'Notes' - ) { - $value = "'$temp_value'"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) { - $value = DateTimeToSQL( $temp_value ); - if ( !$value ) { - Error( "Error parsing date/time '$temp_value', " - ."skipping filter '$self->{Name}'\n" ); - return; - } - $value = "'$value'"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) { - $value = DateTimeToSQL( $temp_value ); - if ( !$value ) { - Error( "Error parsing date/time '$temp_value', " - ."skipping filter '$self->{Name}'\n" ); - return; - } - $value = "to_days( '$value' )"; - } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) { - $value = DateTimeToSQL( $temp_value ); - if ( !$value ) { - Error( "Error parsing date/time '$temp_value', " - ."skipping filter '$self->{Name}'\n" ); - return; - } - $value = "extract( hour_second from '$value' )"; - } else { - $value = $temp_value; - } - push( @value_list, $value ); - } # end foreach temp_value - } # end if has an attr - if ( $filter_expr->{terms}[$i]->{op} ) { - if ( $filter_expr->{terms}[$i]->{op} eq '=~' ) { - $self->{Sql} .= " regexp $value"; - } elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) { - $self->{Sql} .= " not regexp $value"; - } elsif ( $filter_expr->{terms}[$i]->{op} eq '=[]' ) { - $self->{Sql} .= " in (".join( ",", @value_list ).")"; - } elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) { - $self->{Sql} .= " not in (".join( ",", @value_list ).")"; - } else { - $self->{Sql} .= " ".$filter_expr->{terms}[$i]->{op}." $value"; - } - } # end if has an operator - if ( exists($filter_expr->{terms}[$i]->{cbr}) ) { - $self->{Sql} .= " ".str_repeat( ")", $filter_expr->{terms}[$i]->{cbr} )." "; - } - } # end foreach term - } # end if terms - - if ( $self->{Sql} ) - { - if ( $self->{AutoMessage} ) - { - # Include all events, including events that are still ongoing - # and have no EndTime yet - $sql .= " and ( ".$self->{Sql}." )"; - } - else - { - # Only include closed events (events with valid EndTime) - $sql .= " where not isnull(E.EndTime) and ( ".$self->{Sql}." )"; + ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; + foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { + if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) { + $value = "'$temp_value'"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Name' + || $filter_expr->{terms}[$i]->{attr} eq 'Cause' + || $filter_expr->{terms}[$i]->{attr} eq 'Notes' + ) { + $value = "'$temp_value'"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) { + $value = DateTimeToSQL( $temp_value ); + if ( !$value ) { + Error( "Error parsing date/time '$temp_value', " + ."skipping filter '$self->{Name}'\n" ); + return; + } + $value = "'$value'"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) { + $value = DateTimeToSQL( $temp_value ); + if ( !$value ) { + Error( "Error parsing date/time '$temp_value', " + ."skipping filter '$self->{Name}'\n" ); + return; + } + $value = "to_days( '$value' )"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) { + $value = DateTimeToSQL( $temp_value ); + if ( !$value ) { + Error( "Error parsing date/time '$temp_value', " + ."skipping filter '$self->{Name}'\n" ); + return; + } + $value = "extract( hour_second from '$value' )"; + } else { + $value = $temp_value; } + push( @value_list, $value ); + } # end foreach temp_value + } # end if has an attr + if ( $filter_expr->{terms}[$i]->{op} ) { + if ( $filter_expr->{terms}[$i]->{op} eq '=~' ) { + $self->{Sql} .= " regexp $value"; + } elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) { + $self->{Sql} .= " not regexp $value"; + } elsif ( $filter_expr->{terms}[$i]->{op} eq '=[]' ) { + $self->{Sql} .= " in (".join( ",", @value_list ).")"; + } elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) { + $self->{Sql} .= " not in (".join( ",", @value_list ).")"; + } else { + $self->{Sql} .= " ".$filter_expr->{terms}[$i]->{op}." $value"; + } + } # end if has an operator + if ( exists($filter_expr->{terms}[$i]->{cbr}) ) { + $self->{Sql} .= " ".str_repeat( ")", $filter_expr->{terms}[$i]->{cbr} )." "; } - my @auto_terms; - if ( $self->{AutoArchive} ) - { - push( @auto_terms, "E.Archived = 0" ) - } - if ( $self->{AutoVideo} ) - { - push( @auto_terms, "E.Videoed = 0" ) - } - if ( $self->{AutoUpload} ) - { - push( @auto_terms, "E.Uploaded = 0" ) - } - if ( $self->{AutoEmail} ) - { - push( @auto_terms, "E.Emailed = 0" ) - } - if ( $self->{AutoMessage} ) - { - push( @auto_terms, "E.Messaged = 0" ) - } - if ( $self->{AutoExecute} ) - { - push( @auto_terms, "E.Executed = 0" ) - } - if ( @auto_terms ) - { - $sql .= " and ( ".join( " or ", @auto_terms )." )"; - } - if ( !$filter_expr->{sort_field} ) - { - $filter_expr->{sort_field} = 'StartTime'; - $filter_expr->{sort_asc} = 0; - } - my $sort_column = ''; - if ( $filter_expr->{sort_field} eq 'Id' ) - { - $sort_column = "E.Id"; - } - elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) - { - $sort_column = "M.Name"; - } - elsif ( $filter_expr->{sort_field} eq 'Name' ) - { - $sort_column = "E.Name"; - } - elsif ( $filter_expr->{sort_field} eq 'StartTime' ) - { - $sort_column = "E.StartTime"; - } - elsif ( $filter_expr->{sort_field} eq 'Secs' ) - { - $sort_column = "E.Length"; - } - elsif ( $filter_expr->{sort_field} eq 'Frames' ) - { - $sort_column = "E.Frames"; - } - elsif ( $filter_expr->{sort_field} eq 'AlarmFrames' ) - { - $sort_column = "E.AlarmFrames"; - } - elsif ( $filter_expr->{sort_field} eq 'TotScore' ) - { - $sort_column = "E.TotScore"; - } - elsif ( $filter_expr->{sort_field} eq 'AvgScore' ) - { - $sort_column = "E.AvgScore"; - } - elsif ( $filter_expr->{sort_field} eq 'MaxScore' ) - { - $sort_column = "E.MaxScore"; - } - else - { - $sort_column = "E.StartTime"; - } - my $sort_order = $filter_expr->{sort_asc}?"asc":"desc"; - $sql .= " order by ".$sort_column." ".$sort_order; - if ( $filter_expr->{limit} ) - { - $sql .= " limit 0,".$filter_expr->{limit}; - } - Debug( "SQL:$sql\n" ); - $self->{Sql} = $sql; - } # end if has Sql - return $self->{Sql}; + } # end foreach term + } # end if terms + + if ( $self->{Sql} ) + { + if ( $self->{AutoMessage} ) + { +# Include all events, including events that are still ongoing +# and have no EndTime yet + $sql .= " and ( ".$self->{Sql}." )"; + } + else + { +# Only include closed events (events with valid EndTime) + $sql .= " where not isnull(E.EndTime) and ( ".$self->{Sql}." )"; + } + } + my @auto_terms; + if ( $self->{AutoArchive} ) + { + push( @auto_terms, "E.Archived = 0" ) + } + if ( $self->{AutoVideo} ) + { + push( @auto_terms, "E.Videoed = 0" ) + } + if ( $self->{AutoUpload} ) + { + push( @auto_terms, "E.Uploaded = 0" ) + } + if ( $self->{AutoEmail} ) + { + push( @auto_terms, "E.Emailed = 0" ) + } + if ( $self->{AutoMessage} ) + { + push( @auto_terms, "E.Messaged = 0" ) + } + if ( $self->{AutoExecute} ) + { + push( @auto_terms, "E.Executed = 0" ) + } + if ( @auto_terms ) + { + $sql .= " and ( ".join( " or ", @auto_terms )." )"; + } + if ( !$filter_expr->{sort_field} ) + { + $filter_expr->{sort_field} = 'StartTime'; + $filter_expr->{sort_asc} = 0; + } + my $sort_column = ''; + if ( $filter_expr->{sort_field} eq 'Id' ) + { + $sort_column = "E.Id"; + } + elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) + { + $sort_column = "M.Name"; + } + elsif ( $filter_expr->{sort_field} eq 'Name' ) + { + $sort_column = "E.Name"; + } + elsif ( $filter_expr->{sort_field} eq 'StartTime' ) + { + $sort_column = "E.StartTime"; + } + elsif ( $filter_expr->{sort_field} eq 'Secs' ) + { + $sort_column = "E.Length"; + } + elsif ( $filter_expr->{sort_field} eq 'Frames' ) + { + $sort_column = "E.Frames"; + } + elsif ( $filter_expr->{sort_field} eq 'AlarmFrames' ) + { + $sort_column = "E.AlarmFrames"; + } + elsif ( $filter_expr->{sort_field} eq 'TotScore' ) + { + $sort_column = "E.TotScore"; + } + elsif ( $filter_expr->{sort_field} eq 'AvgScore' ) + { + $sort_column = "E.AvgScore"; + } + elsif ( $filter_expr->{sort_field} eq 'MaxScore' ) + { + $sort_column = "E.MaxScore"; + } + else + { + $sort_column = "E.StartTime"; + } + my $sort_order = $filter_expr->{sort_asc}?"asc":"desc"; + $sql .= " order by ".$sort_column." ".$sort_order; + if ( $filter_expr->{limit} ) + { + $sql .= " limit 0,".$filter_expr->{limit}; + } + Debug( "SQL:$sql\n" ); + $self->{Sql} = $sql; + } # end if has Sql + return $self->{Sql}; } # end sub Sql sub getDiskPercent { - my $command = "df ."; - my $df = qx( $command ); - my $space = -1; - if ( $df =~ /\s(\d+)%/ms ) - { - $space = $1; - } - return( $space ); + my $command = "df ."; + my $df = qx( $command ); + my $space = -1; + if ( $df =~ /\s(\d+)%/ms ) + { + $space = $1; + } + return( $space ); } sub getDiskBlocks { - my $command = "df ."; - my $df = qx( $command ); - my $space = -1; - if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) - { - $space = $1; - } - return( $space ); + my $command = "df ."; + my $df = qx( $command ); + my $space = -1; + if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) + { + $space = $1; + } + return( $space ); } sub getLoad { - my $command = "uptime ."; - my $uptime = qx( $command ); - my $load = -1; - if ( $uptime =~ /load average:\s+([\d.]+)/ms ) - { - $load = $1; - Info( "Load: $load" ); - } - return( $load ); + my $command = "uptime ."; + my $uptime = qx( $command ); + my $load = -1; + if ( $uptime =~ /load average:\s+([\d.]+)/ms ) + { + $load = $1; + Info( "Load: $load" ); + } + return( $load ); } # @@ -445,8 +445,8 @@ sub getLoad # sub strtotime { - my $dt_str = shift; - return( Date::Manip::UnixDate( $dt_str, '%s' ) ); + my $dt_str = shift; + return( Date::Manip::UnixDate( $dt_str, '%s' ) ); } # @@ -454,22 +454,22 @@ sub strtotime # sub str_repeat { - my $string = shift; - my $count = shift; - return( ${string}x${count} ); + my $string = shift; + my $count = shift; + return( ${string}x${count} ); } # Formats a date into MySQL format sub DateTimeToSQL { - my $dt_str = shift; - my $dt_val = strtotime( $dt_str ); - if ( !$dt_val ) - { - Error( "Unable to parse date string '$dt_str'\n" ); - return( undef ); - } - return( strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) ); + my $dt_str = shift; + my $dt_val = strtotime( $dt_str ); + if ( !$dt_val ) + { + Error( "Unable to parse date string '$dt_str'\n" ); + return( undef ); + } + return( strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) ); } 1; @@ -482,8 +482,8 @@ ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS - use ZoneMinder::Filter; - blah blah blah +use ZoneMinder::Filter; +blah blah blah =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index 944781f6b..621408f7a 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -42,19 +42,19 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( 'functions' => [ qw( - executeShellCommand - getCmdFormat - runCommand - setFileOwner - getEventPath - createEventPath - createEvent - deleteEventFiles - makePath - jsonEncode - jsonDecode - ) ] -); + executeShellCommand + getCmdFormat + runCommand + setFileOwner + getEventPath + createEventPath + createEvent + deleteEventFiles + makePath + jsonEncode + jsonDecode + ) ] + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); @@ -78,83 +78,83 @@ use POSIX; # For running general shell commands sub executeShellCommand { - my $command = shift; - my $output = qx( $command ); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - Debug( "Command: $command\n" ); - chomp( $output ); - Debug( "Output: $output\n" ); - } - return( $status ); + my $command = shift; + my $output = qx( $command ); + my $status = $? >> 8; + if ( $status || logDebugging() ) + { + Debug( "Command: $command\n" ); + chomp( $output ); + Debug( "Output: $output\n" ); + } + return( $status ); } sub getCmdFormat { - Debug( "Testing valid shell syntax\n" ); + Debug( "Testing valid shell syntax\n" ); - my ( $name ) = getpwuid( $> ); - if ( $name eq $Config{ZM_WEB_USER} ) - { - Debug( "Running as '$name', su commands not needed\n" ); - return( "" ); - } + my ( $name ) = getpwuid( $> ); + if ( $name eq $Config{ZM_WEB_USER} ) + { + Debug( "Running as '$name', su commands not needed\n" ); + return( "" ); + } - my $null_command = "true"; + my $null_command = "true"; - my $prefix = "sudo -u ".$Config{ZM_WEB_USER}." "; - my $suffix = ""; - my $command = $prefix.$null_command.$suffix; + my $prefix = "sudo -u ".$Config{ZM_WEB_USER}." "; + my $suffix = ""; + my $command = $prefix.$null_command.$suffix; + Debug( "Testing \"$command\"\n" ); + my $output = qx($command); + my $status = $? >> 8; + if ( !$status ) + { + Debug( "Test ok, using format \"$prefix$suffix\"\n" ); + return( $prefix, $suffix ); + } + else + { + chomp( $output ); + Debug( "Test failed, '$output'\n" ); + + $prefix = "su ".$Config{ZM_WEB_USER}." --shell=/bin/sh --command='"; + $suffix = "'"; + $command = $prefix.$null_command.$suffix; Debug( "Testing \"$command\"\n" ); my $output = qx($command); my $status = $? >> 8; if ( !$status ) { - Debug( "Test ok, using format \"$prefix$suffix\"\n" ); - return( $prefix, $suffix ); + Debug( "Test ok, using format \"$prefix$suffix\"\n" ); + return( $prefix, $suffix ); } else { + chomp( $output ); + Debug( "Test failed, '$output'\n" ); + + $prefix = "su ".$Config{ZM_WEB_USER}." -c '"; + $suffix = "'"; + $command = $prefix.$null_command.$suffix; + Debug( "Testing \"$command\"\n" ); + $output = qx($command); + $status = $? >> 8; + if ( !$status ) + { + Debug( "Test ok, using format \"$prefix$suffix\"\n" ); + return( $prefix, $suffix ); + } + else + { chomp( $output ); Debug( "Test failed, '$output'\n" ); - - $prefix = "su ".$Config{ZM_WEB_USER}." --shell=/bin/sh --command='"; - $suffix = "'"; - $command = $prefix.$null_command.$suffix; - Debug( "Testing \"$command\"\n" ); - my $output = qx($command); - my $status = $? >> 8; - if ( !$status ) - { - Debug( "Test ok, using format \"$prefix$suffix\"\n" ); - return( $prefix, $suffix ); - } - else - { - chomp( $output ); - Debug( "Test failed, '$output'\n" ); - - $prefix = "su ".$Config{ZM_WEB_USER}." -c '"; - $suffix = "'"; - $command = $prefix.$null_command.$suffix; - Debug( "Testing \"$command\"\n" ); - $output = qx($command); - $status = $? >> 8; - if ( !$status ) - { - Debug( "Test ok, using format \"$prefix$suffix\"\n" ); - return( $prefix, $suffix ); - } - else - { - chomp( $output ); - Debug( "Test failed, '$output'\n" ); - } - } + } } - Error( "Unable to find valid 'su' syntax\n" ); - exit( -1 ); + } + Error( "Unable to find valid 'su' syntax\n" ); + exit( -1 ); } our $testedShellSyntax = 0; @@ -163,125 +163,125 @@ our ( $cmdPrefix, $cmdSuffix ); # For running ZM daemons etc sub runCommand { - if ( !$testedShellSyntax ) - { - # Determine the appropriate syntax for the su command - ( $cmdPrefix, $cmdSuffix ) = getCmdFormat(); - $testedShellSyntax = !undef; - } + if ( !$testedShellSyntax ) + { +# Determine the appropriate syntax for the su command + ( $cmdPrefix, $cmdSuffix ) = getCmdFormat(); + $testedShellSyntax = !undef; + } - my $command = shift; - $command = $Config{ZM_PATH_BIN}."/".$command; - if ( $cmdPrefix ) + my $command = shift; + $command = $Config{ZM_PATH_BIN}."/".$command; + if ( $cmdPrefix ) + { + $command = $cmdPrefix.$command.$cmdSuffix; + } + Debug( "Command: $command\n" ); + my $output = qx($command); + my $status = $? >> 8; + chomp( $output ); + if ( $status || logDebugging() ) + { + if ( $status ) { - $command = $cmdPrefix.$command.$cmdSuffix; + Error( "Unable to run \"$command\", output is \"$output\"\n" ); + exit( -1 ); } - Debug( "Command: $command\n" ); - my $output = qx($command); - my $status = $? >> 8; - chomp( $output ); - if ( $status || logDebugging() ) + else { - if ( $status ) - { - Error( "Unable to run \"$command\", output is \"$output\"\n" ); - exit( -1 ); - } - else - { - Debug( "Output: $output\n" ); - } + Debug( "Output: $output\n" ); } - return( $output ); + } + return( $output ); } sub getEventPath { - my $event = shift; + my $event = shift; - my $event_path = ""; - if ( $Config{ZM_USE_DEEP_STORAGE} ) - { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.strftime( "%y/%m/%d/%H/%M/%S", - localtime($event->{Time}) - ) - ; - } - else - { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.$event->{Id} - ; - } + my $event_path = ""; + if ( $Config{ZM_USE_DEEP_STORAGE} ) + { + $event_path = $Config{ZM_DIR_EVENTS} + .'/'.$event->{MonitorId} + .'/'.strftime( "%y/%m/%d/%H/%M/%S", + localtime($event->{Time}) + ) + ; + } + else + { + $event_path = $Config{ZM_DIR_EVENTS} + .'/'.$event->{MonitorId} + .'/'.$event->{Id} + ; + } - if ( index($Config{ZM_DIR_EVENTS},'/') != 0 ){ - $event_path = $Config{ZM_PATH_WEB} - .'/'.$event_path - ; - } - return( $event_path ); + if ( index($Config{ZM_DIR_EVENTS},'/') != 0 ){ + $event_path = $Config{ZM_PATH_WEB} + .'/'.$event_path + ; + } + return( $event_path ); } sub createEventPath { - # - # WARNING assumes running from events directory - # - my $event = shift; - my $eventRootPath = ($Config{ZM_DIR_EVENTS}=~m|/|) - ? $Config{ZM_DIR_EVENTS} - : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}); - my $eventPath = $eventRootPath.'/'.$event->{MonitorId}; +# +# WARNING assumes running from events directory +# + my $event = shift; + my $eventRootPath = ($Config{ZM_DIR_EVENTS}=~m|/|) + ? $Config{ZM_DIR_EVENTS} + : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}); + my $eventPath = $eventRootPath.'/'.$event->{MonitorId}; - if ( $Config{ZM_USE_DEEP_STORAGE} ) - { - my @startTime = localtime( $event->{StartTime} ); + 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 @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]); + my $datePath = join('/',@datetimeParts[0..2]); + my $timePath = join('/',@datetimeParts[3..5]); - makePath( $datePath, $eventPath ); - $eventPath .= '/'.$datePath; + 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: $!" ); +# 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 + 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}; +# 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 ); + 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 ); } use Data::Dumper; @@ -291,345 +291,345 @@ our ( $_ownerUid, $_ownerGid ); sub _checkProcessOwner { - if ( !defined($_setFileOwner) ) + if ( !defined($_setFileOwner) ) + { + my ( $processOwner ) = getpwuid( $> ); + if ( $processOwner ne $Config{ZM_WEB_USER} ) { - my ( $processOwner ) = getpwuid( $> ); - if ( $processOwner ne $Config{ZM_WEB_USER} ) - { - # Not running as web user, so should be root in which case chown - # the temporary directory - ( my $ownerName, my $ownerPass, $_ownerUid, $_ownerGid ) - = getpwnam( $Config{ZM_WEB_USER} ) - or Fatal( "Can't get user details for web user '" - .$Config{ZM_WEB_USER}."': $!" - ); - $_setFileOwner = 1; - } - else - { - $_setFileOwner = 0; - } +# Not running as web user, so should be root in which case chown +# the temporary directory + ( my $ownerName, my $ownerPass, $_ownerUid, $_ownerGid ) + = getpwnam( $Config{ZM_WEB_USER} ) + or Fatal( "Can't get user details for web user '" + .$Config{ZM_WEB_USER}."': $!" + ); + $_setFileOwner = 1; } - return( $_setFileOwner ); + else + { + $_setFileOwner = 0; + } + } + return( $_setFileOwner ); } sub setFileOwner { - my $file = shift; + my $file = shift; - if ( _checkProcessOwner() ) - { - chown( $_ownerUid, $_ownerGid, $file ) - or Fatal( "Can't change ownership of file '$file' to '" - .$Config{ZM_WEB_USER}.":".$Config{ZM_WEB_GROUP}."': $!" - ); - } + if ( _checkProcessOwner() ) + { + chown( $_ownerUid, $_ownerGid, $file ) + or Fatal( "Can't change ownership of file '$file' to '" + .$Config{ZM_WEB_USER}.":".$Config{ZM_WEB_GROUP}."': $!" + ); + } } our $_hasImageInfo = undef; sub _checkForImageInfo { - if ( !defined($_hasImageInfo) ) + if ( !defined($_hasImageInfo) ) + { + my $result = eval { - my $result = eval - { - require Image::Info; - Image::Info->import(); - }; - $_hasImageInfo = $@?0:1; - } - return( $_hasImageInfo ); + require Image::Info; + Image::Info->import(); + }; + $_hasImageInfo = $@?0:1; + } + return( $_hasImageInfo ); } sub createEvent { - my $event = shift; + my $event = shift; - Debug( "Creating event" ); - #print( Dumper( $event )."\n" ); + Debug( "Creating event" ); +#print( Dumper( $event )."\n" ); - _checkForImageInfo(); + _checkForImageInfo(); - my $dbh = zmDbConnect(); + my $dbh = zmDbConnect(); - if ( $event->{monitor} ) + if ( $event->{monitor} ) + { + $event->{MonitorId} = $event->{monitor}->{Id}; + } + elsif ( $event->{MonitorId} ) + { + my $sql = "select * from Monitors where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{MonitorId} ) + or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); + $event->{monitor} = $sth->fetchrow_hashref() + or Fatal( "Unable to create event, can't load monitor with id '" + .$event->{MonitorId}."'" + ); + $sth->finish(); + } + else + { + Fatal( "Unable to create event, no monitor or monitor id supplied" ); + } + $event->{Name} = "New Event" unless( $event->{Name} ); + $event->{Frames} = int(@{$event->{frames}}); + $event->{TotScore} = $event->{MaxScore} = 0; + + my $lastTimestamp = 0.0; + foreach my $frame ( @{$event->{frames}} ) + { + if ( !$event->{Width} ) { - $event->{MonitorId} = $event->{monitor}->{Id}; - } - elsif ( $event->{MonitorId} ) - { - my $sql = "select * from Monitors where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{MonitorId} ) - or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); - $event->{monitor} = $sth->fetchrow_hashref() - or Fatal( "Unable to create event, can't load monitor with id '" - .$event->{MonitorId}."'" - ); - $sth->finish(); - } - else - { - Fatal( "Unable to create event, no monitor or monitor id supplied" ); - } - $event->{Name} = "New Event" unless( $event->{Name} ); - $event->{Frames} = int(@{$event->{frames}}); - $event->{TotScore} = $event->{MaxScore} = 0; - - my $lastTimestamp = 0.0; - foreach my $frame ( @{$event->{frames}} ) - { - if ( !$event->{Width} ) + if ( $_hasImageInfo ) + { + my $imageInfo = Image::Info::image_info( $frame->{imagePath} ); + if ( $imageInfo->{error} ) { - if ( $_hasImageInfo ) - { - my $imageInfo = Image::Info::image_info( $frame->{imagePath} ); - if ( $imageInfo->{error} ) - { - Error( "Unable to extract image info from '" - .$frame->{imagePath}."': ".$imageInfo->{error} - ); - } - else - { - ( $event->{Width}, $event->{Height} ) = Image::Info::dim( $imageInfo ); - } - } + Error( "Unable to extract image info from '" + .$frame->{imagePath}."': ".$imageInfo->{error} + ); } - $frame->{Type} = $frame->{Score}>0?'Alarm':'Normal' unless( $frame->{Type} ); - $frame->{Delta} = $lastTimestamp?($frame->{TimeStamp}-$lastTimestamp):0.0; - $event->{StartTime} = $frame->{TimeStamp} unless ( $event->{StartTime} ); - $event->{TotScore} += $frame->{Score}; - $event->{MaxScore} = $frame->{Score} if ( $frame->{Score} > $event->{MaxScore} ); - $event->{AlarmFrames}++ if ( $frame->{Type} eq 'Alarm' ); - $event->{EndTime} = $frame->{TimeStamp}; - $lastTimestamp = $frame->{TimeStamp}; + else + { + ( $event->{Width}, $event->{Height} ) = Image::Info::dim( $imageInfo ); + } + } } - $event->{Width} = $event->{monitor}->{Width} unless( $event->{Width} ); - $event->{Height} = $event->{monitor}->{Height} unless( $event->{Height} ); - $event->{AvgScore} = $event->{TotScore}/int($event->{AlarmFrames}); - $event->{Length} = $event->{EndTime} - $event->{StartTime}; + $frame->{Type} = $frame->{Score}>0?'Alarm':'Normal' unless( $frame->{Type} ); + $frame->{Delta} = $lastTimestamp?($frame->{TimeStamp}-$lastTimestamp):0.0; + $event->{StartTime} = $frame->{TimeStamp} unless ( $event->{StartTime} ); + $event->{TotScore} += $frame->{Score}; + $event->{MaxScore} = $frame->{Score} if ( $frame->{Score} > $event->{MaxScore} ); + $event->{AlarmFrames}++ if ( $frame->{Type} eq 'Alarm' ); + $event->{EndTime} = $frame->{TimeStamp}; + $lastTimestamp = $frame->{TimeStamp}; + } + $event->{Width} = $event->{monitor}->{Width} unless( $event->{Width} ); + $event->{Height} = $event->{monitor}->{Height} unless( $event->{Height} ); + $event->{AvgScore} = $event->{TotScore}/int($event->{AlarmFrames}); + $event->{Length} = $event->{EndTime} - $event->{StartTime}; - my %formats = ( - StartTime => 'from_unixtime(?)', - EndTime => 'from_unixtime(?)', - ); + my %formats = ( + StartTime => 'from_unixtime(?)', + EndTime => 'from_unixtime(?)', + ); + + my ( @fields, @formats, @values ); + while ( my ( $field, $value ) = each( %$event ) ) + { + next unless $field =~ /^[A-Z]/; + push( @fields, $field ); + push( @formats, ($formats{$field} or '?') ); + push( @values, $event->{$field} ); + } + + my $sql = "INSERT INTO Events (".join(',',@fields) + .") VALUES (".join(',',@formats).")" + ; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( @values ) + or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); + $event->{Id} = $dbh->{mysql_insertid}; + Info( "Created event ".$event->{Id} ); + + if ( $event->{EndTime} ) + { + $event->{Name} = $event->{monitor}->{EventPrefix}.$event->{Id} + if ( $event->{Name} eq 'New Event' ); + my $sql = "update Events set Name = ? where Id = ?"; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Name}, $event->{Id} ) + or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); + } + + my $eventPath = createEventPath( $event ); + + my %frameFormats = ( + TimeStamp => 'from_unixtime(?)', + ); + my $frameId = 1; + foreach my $frame ( @{$event->{frames}} ) + { + $frame->{EventId} = $event->{Id}; + $frame->{FrameId} = $frameId++; my ( @fields, @formats, @values ); - while ( my ( $field, $value ) = each( %$event ) ) + while ( my ( $field, $value ) = each( %$frame ) ) { - next unless $field =~ /^[A-Z]/; - push( @fields, $field ); - push( @formats, ($formats{$field} or '?') ); - push( @values, $event->{$field} ); + next unless $field =~ /^[A-Z]/; + push( @fields, $field ); + push( @formats, ($frameFormats{$field} or '?') ); + push( @values, $frame->{$field} ); } - my $sql = "INSERT INTO Events (".join(',',@fields) - .") VALUES (".join(',',@formats).")" - ; + my $sql = "insert into Frames (".join(',',@fields) + .") values (".join(',',@formats).")" + ; my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); + or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); my $res = $sth->execute( @values ) - or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); - $event->{Id} = $dbh->{mysql_insertid}; - Info( "Created event ".$event->{Id} ); - - if ( $event->{EndTime} ) + or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); +#$frame->{FrameId} = $dbh->{mysql_insertid}; + if ( $frame->{imagePath} ) { - $event->{Name} = $event->{monitor}->{EventPrefix}.$event->{Id} - if ( $event->{Name} eq 'New Event' ); - my $sql = "update Events set Name = ? where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Name}, $event->{Id} ) - or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); - } - - my $eventPath = createEventPath( $event ); - - my %frameFormats = ( - TimeStamp => 'from_unixtime(?)', - ); - my $frameId = 1; - foreach my $frame ( @{$event->{frames}} ) - { - $frame->{EventId} = $event->{Id}; - $frame->{FrameId} = $frameId++; - - my ( @fields, @formats, @values ); - while ( my ( $field, $value ) = each( %$frame ) ) - { - next unless $field =~ /^[A-Z]/; - push( @fields, $field ); - push( @formats, ($frameFormats{$field} or '?') ); - push( @values, $frame->{$field} ); - } - - my $sql = "insert into Frames (".join(',',@fields) - .") values (".join(',',@formats).")" - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( @values ) - or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); - #$frame->{FrameId} = $dbh->{mysql_insertid}; - if ( $frame->{imagePath} ) - { - $frame->{capturePath} = sprintf( - "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS} - ."d-capture.jpg" - , $eventPath - , $frame->{FrameId} + $frame->{capturePath} = sprintf( + "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS} + ."d-capture.jpg" + , $eventPath + , $frame->{FrameId} + ); + rename( $frame->{imagePath}, $frame->{capturePath} ) + or Fatal( "Can't copy ".$frame->{imagePath} + ." to ".$frame->{capturePath}.": $!" ); - rename( $frame->{imagePath}, $frame->{capturePath} ) - or Fatal( "Can't copy ".$frame->{imagePath} - ." to ".$frame->{capturePath}.": $!" - ); - setFileOwner( $frame->{capturePath} ); - if ( 0 && $Config{ZM_CREATE_ANALYSIS_IMAGES} ) - { - $frame->{analysePath} = sprintf( - "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS} - ."d-analyse.jpg" - , $eventPath - , $frame->{FrameId} - ); - link( $frame->{capturePath}, $frame->{analysePath} ) - or Fatal( "Can't link ".$frame->{capturePath} - ." to ".$frame->{analysePath}.": $!" - ); - setFileOwner( $frame->{analysePath} ); - } - } + setFileOwner( $frame->{capturePath} ); + if ( 0 && $Config{ZM_CREATE_ANALYSIS_IMAGES} ) + { + $frame->{analysePath} = sprintf( + "%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS} + ."d-analyse.jpg" + , $eventPath + , $frame->{FrameId} + ); + link( $frame->{capturePath}, $frame->{analysePath} ) + or Fatal( "Can't link ".$frame->{capturePath} + ." to ".$frame->{analysePath}.": $!" + ); + setFileOwner( $frame->{analysePath} ); + } } + } } sub addEventImage { - my $event = shift; - my $frame = shift; + my $event = shift; + my $frame = shift; - # TBD +# TBD } sub updateEvent { - my $event = shift; + my $event = shift; - if ( !$event->{EventId} ) - { - Error( "Unable to update event, no event id supplied" ); - return( 0 ); - } + if ( !$event->{EventId} ) + { + Error( "Unable to update event, no event id supplied" ); + return( 0 ); + } - my $dbh = zmDbConnect(); + my $dbh = zmDbConnect(); - $event->{Name} = $event->{monitor}->{EventPrefix}.$event->{Id} - if ( $event->{Name} eq 'New Event' ); + $event->{Name} = $event->{monitor}->{EventPrefix}.$event->{Id} + if ( $event->{Name} eq 'New Event' ); - my %formats = ( - StartTime => 'from_unixtime(?)', - EndTime => 'from_unixtime(?)', - ); + my %formats = ( + StartTime => 'from_unixtime(?)', + EndTime => 'from_unixtime(?)', + ); - my ( @values, @sets ); - while ( my ( $field, $value ) = each( %$event ) ) - { - next if ( $field eq 'Id' ); - push( @values, $event->{$field} ); - push( @sets, $field." = ".($formats{$field} or '?') ); - } - my $sql = "update Events set ".join(',',@sets)." where Id = ?"; - push( @values, $event->{Id} ); + my ( @values, @sets ); + while ( my ( $field, $value ) = each( %$event ) ) + { + next if ( $field eq 'Id' ); + push( @values, $event->{$field} ); + push( @sets, $field." = ".($formats{$field} or '?') ); + } + my $sql = "update Events set ".join(',',@sets)." where Id = ?"; + push( @values, $event->{Id} ); - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( @values ) - or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare sql '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( @values ) + 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) ); +# +# 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} ) + 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 ) { - 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" ); + ( $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 ); + ( 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"; + 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; - $root = (( $path =~ m|^/| )?'':'.' ) unless( $root ); + my $path = shift; + my $root = shift; + $root = (( $path =~ m|^/| )?'':'.' ) unless( $root ); - Debug( "Creating path '$path' in $root'\n" ); - my @parts = split( '/', $path ); - my $fullPath = $root; - foreach my $dir ( @parts ) + Debug( "Creating path '$path' in $root'\n" ); + my @parts = split( '/', $path ); + my $fullPath = $root; + foreach my $dir ( @parts ) + { + $fullPath .= '/'.$dir; + if ( !-d $fullPath ) { - $fullPath .= '/'.$dir; - if ( !-d $fullPath ) - { - if ( -e $fullPath ) - { - Fatal( "Can't create '$fullPath', already exists as non directory" ); - } - else - { - Debug( "Creating '$fullPath'\n" ); - mkdir( $fullPath, 0755 ) or Fatal( "Can't mkdir '$fullPath': $!" ); - setFileOwner( $fullPath ); - } - } + if ( -e $fullPath ) + { + Fatal( "Can't create '$fullPath', already exists as non directory" ); + } + else + { + Debug( "Creating '$fullPath'\n" ); + mkdir( $fullPath, 0755 ) or Fatal( "Can't mkdir '$fullPath': $!" ); + setFileOwner( $fullPath ); + } } - return( $fullPath ); + } + return( $fullPath ); } our $testedJSON = 0; @@ -637,143 +637,143 @@ our $hasJSONAny = 0; sub _testJSON { - return if ( $testedJSON ); - my $result = eval - { - require JSON::Any; - JSON::Any->import(); - }; - $testedJSON = 1; - $hasJSONAny = 1 if ( $result ); + return if ( $testedJSON ); + my $result = eval + { + require JSON::Any; + JSON::Any->import(); + }; + $testedJSON = 1; + $hasJSONAny = 1 if ( $result ); } sub _getJSONType { - my $value = shift; - return( 'null' ) unless( defined($value) ); - return( 'integer' ) if ( $value =~ /^\d+$/ ); - return( 'double' ) if ( $value =~ /^\d+$/ ); - return( 'hash' ) if ( ref($value) eq 'HASH' ); - return( 'array' ) if ( ref($value) eq 'ARRAY' ); - return( 'string' ); + my $value = shift; + return( 'null' ) unless( defined($value) ); + return( 'integer' ) if ( $value =~ /^\d+$/ ); + return( 'double' ) if ( $value =~ /^\d+$/ ); + return( 'hash' ) if ( ref($value) eq 'HASH' ); + return( 'array' ) if ( ref($value) eq 'ARRAY' ); + return( 'string' ); } sub jsonEncode; sub jsonEncode { - my $value = shift; + my $value = shift; - _testJSON(); - if ( $hasJSONAny ) - { - my $string = eval { JSON::Any->objToJson( $value ) }; - Fatal( "Unable to encode object to JSON: $@" ) unless( $string ); - return( $string ); - } + _testJSON(); + if ( $hasJSONAny ) + { + my $string = eval { JSON::Any->objToJson( $value ) }; + Fatal( "Unable to encode object to JSON: $@" ) unless( $string ); + return( $string ); + } - my $type = _getJSONType($value); - if ( $type eq 'integer' || $type eq 'double' ) + my $type = _getJSONType($value); + if ( $type eq 'integer' || $type eq 'double' ) + { + return( $value ); + } + elsif ( $type eq 'boolean' ) + { + return( $value?'true':'false' ); + } + elsif ( $type eq 'string' ) + { + $value =~ s|(["\\/])|\\$1|g; + $value =~ s|\r?\n|\n|g; + return( '"'.$value.'"' ); + } + elsif ( $type eq 'null' ) + { + return( 'null' ); + } + elsif ( $type eq 'array' ) + { + return( '['.join( ',', map { jsonEncode( $_ ) } @$value ).']' ); + } + elsif ( $type eq 'hash' ) + { + my $result = '{'; + while ( my ( $subKey=>$subValue ) = each( %$value ) ) { - return( $value ); - } - elsif ( $type eq 'boolean' ) - { - return( $value?'true':'false' ); - } - elsif ( $type eq 'string' ) - { - $value =~ s|(["\\/])|\\$1|g; - $value =~ s|\r?\n|\n|g; - return( '"'.$value.'"' ); - } - elsif ( $type eq 'null' ) - { - return( 'null' ); - } - elsif ( $type eq 'array' ) - { - return( '['.join( ',', map { jsonEncode( $_ ) } @$value ).']' ); - } - elsif ( $type eq 'hash' ) - { - my $result = '{'; - while ( my ( $subKey=>$subValue ) = each( %$value ) ) - { - $result .= ',' if ( $result ne '{' ); - $result .= '"'.$subKey.'":'.jsonEncode( $subValue ); - } - return( $result.'}' ); - } - else - { - Fatal( "Unexpected type '$type'" ); + $result .= ',' if ( $result ne '{' ); + $result .= '"'.$subKey.'":'.jsonEncode( $subValue ); } + return( $result.'}' ); + } + else + { + Fatal( "Unexpected type '$type'" ); + } } sub jsonDecode { - my $value = shift; + my $value = shift; - _testJSON(); - if ( $hasJSONAny ) - { - my $object = eval { JSON::Any->jsonToObj( $value ) }; - Fatal( "Unable to decode JSON string '$value': $@" ) unless( $object ); - return( $object ); - } + _testJSON(); + if ( $hasJSONAny ) + { + my $object = eval { JSON::Any->jsonToObj( $value ) }; + Fatal( "Unable to decode JSON string '$value': $@" ) unless( $object ); + return( $object ); + } - my $comment = 0; - my $unescape = 0; - my $out = ''; - my @chars = split( //, $value ); - for ( my $i = 0; $i < @chars; $i++ ) + my $comment = 0; + my $unescape = 0; + my $out = ''; + my @chars = split( //, $value ); + for ( my $i = 0; $i < @chars; $i++ ) + { + if ( !$comment ) { - if ( !$comment ) - { - if ( $chars[$i] eq ':' ) - { - $out .= '=>'; - } - else - { - $out .= $chars[$i]; - } - } - elsif ( !$unescape ) - { - if ( $chars[$i] eq '\\' ) - { - $unescape = 1; - } - else - { - $out .= $chars[$i]; - } - } - else - { - if ( $chars[$i] ne '/' ) - { - $out .= '\\'; - } - $out .= $chars[$i]; - $unescape = 0; - } - if ( $chars[$i] eq '"' ) - { - $comment = !$comment; - } + if ( $chars[$i] eq ':' ) + { + $out .= '=>'; + } + else + { + $out .= $chars[$i]; + } } - $out =~ s/=>true/=>1/g; - $out =~ s/=>false/=>0/g; - $out =~ s/=>null/=>undef/g; - $out =~ s/`/'/g; - $out =~ s/qx/qq/g; - ( $out ) = $out =~ m/^({.+})$/; # Detaint and check it's a valid object syntax + elsif ( !$unescape ) + { + if ( $chars[$i] eq '\\' ) + { + $unescape = 1; + } + else + { + $out .= $chars[$i]; + } + } + else + { + if ( $chars[$i] ne '/' ) + { + $out .= '\\'; + } + $out .= $chars[$i]; + $unescape = 0; + } + if ( $chars[$i] eq '"' ) + { + $comment = !$comment; + } + } + $out =~ s/=>true/=>1/g; + $out =~ s/=>false/=>0/g; + $out =~ s/=>null/=>undef/g; + $out =~ s/`/'/g; + $out =~ s/qx/qq/g; + ( $out ) = $out =~ m/^({.+})$/; # Detaint and check it's a valid object syntax my $result = eval $out; - Fatal( $@ ) if ( $@ ); - return( $result ); + Fatal( $@ ) if ( $@ ); + return( $result ); } 1; @@ -786,8 +786,8 @@ ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS - use ZoneMinder::Database; - blah blah blah +use ZoneMinder::Database; +blah blah blah =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index d17f9683a..4c727915f 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -42,43 +42,43 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( 'constants' => [ qw( - DEBUG - INFO - WARNING - ERROR - FATAL - PANIC - NOLOG - ) ], + DEBUG + INFO + WARNING + ERROR + FATAL + PANIC + NOLOG + ) ], 'functions' => [ qw( - logInit - logReinit - logTerm - logSetSignal - logClearSignal - logDebugging - logLevel - logTermLevel - logDatabaseLevel - logFileLevel - logSyslogLevel - Mark - Dump - Debug - Info - Warning - Error - Fatal - Panic - ) ] -); -push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; + logInit + logReinit + logTerm + logSetSignal + logClearSignal + logDebugging + logLevel + logTermLevel + logDatabaseLevel + logFileLevel + logSyslogLevel + Mark + Dump + Debug + Info + Warning + Error + Fatal + Panic + ) ] + ); + push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); + our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); -our @EXPORT = qw(); + our @EXPORT = qw(); -our $VERSION = $ZoneMinder::Base::VERSION; + our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== # @@ -86,25 +86,25 @@ our $VERSION = $ZoneMinder::Base::VERSION; # # ========================================================================== -use ZoneMinder::Config qw(:all); + use ZoneMinder::Config qw(:all); -use DBI; -use Carp; -use POSIX; -use IO::Handle; -use Data::Dumper; -use Time::HiRes qw/gettimeofday/; -use Sys::Syslog; + use DBI; + use Carp; + use POSIX; + use IO::Handle; + use Data::Dumper; + use Time::HiRes qw/gettimeofday/; + use Sys::Syslog; -use constant { - DEBUG => 1, - INFO => 0, - WARNING => -1, - ERROR => -2, - FATAL => -3, - PANIC => -4, - NOLOG => -5 -}; + use constant { + DEBUG => 1, + INFO => 0, + WARNING => -1, + ERROR => -2, + FATAL => -3, + PANIC => -4, + NOLOG => -5 + }; our %codes = ( &DEBUG => "DBG", @@ -114,7 +114,7 @@ our %codes = ( &FATAL => "FAT", &PANIC => "PNC", &NOLOG => "OFF" -); + ); our %priorities = ( &DEBUG => "debug", @@ -123,668 +123,668 @@ our %priorities = ( &ERROR => "err", &FATAL => "err", &PANIC => "err" -); + ); our $logger; our $LOGFILE; sub new { - my $class = shift; - my $this = {}; + my $class = shift; + my $this = {}; - $this->{initialised} = undef; + $this->{initialised} = undef; - #$this->{id} = "zmundef"; - ( $this->{id} ) = $0 =~ m|^(?:.*/)?([^/]+?)(?:\.[^/.]+)?$|; - $this->{idRoot} = $this->{id}; - $this->{idArgs} = ""; +#$this->{id} = "zmundef"; + ( $this->{id} ) = $0 =~ m|^(?:.*/)?([^/]+?)(?:\.[^/.]+)?$|; + $this->{idRoot} = $this->{id}; + $this->{idArgs} = ""; - $this->{level} = INFO; - $this->{termLevel} = NOLOG; - $this->{databaseLevel} = NOLOG; - $this->{fileLevel} = NOLOG; - $this->{syslogLevel} = NOLOG; - $this->{effectiveLevel} = INFO; + $this->{level} = INFO; + $this->{termLevel} = NOLOG; + $this->{databaseLevel} = NOLOG; + $this->{fileLevel} = NOLOG; + $this->{syslogLevel} = NOLOG; + $this->{effectiveLevel} = INFO; - $this->{autoFlush} = 1; - $this->{hasTerm} = -t STDERR; + $this->{autoFlush} = 1; + $this->{hasTerm} = -t STDERR; - ( $this->{fileName} = $0 ) =~ s|^.*/||; - $this->{logPath} = $Config{ZM_PATH_LOGS}; - $this->{logFile} = $this->{logPath}."/".$this->{id}.".log"; + ( $this->{fileName} = $0 ) =~ s|^.*/||; + $this->{logPath} = $Config{ZM_PATH_LOGS}; + $this->{logFile} = $this->{logPath}."/".$this->{id}.".log"; - $this->{trace} = 0; + $this->{trace} = 0; - bless( $this, $class ); - return $this; + bless( $this, $class ); + return $this; } sub BEGIN { - # Fake the config variables that are used in case they are not defined yet - # Only really necessary to support upgrade from previous version - if ( !eval('defined($Config{ZM_LOG_DEBUG})') ) - { - no strict 'subs'; - no strict 'refs'; - my %dbgConfig = ( - ZM_LOG_LEVEL_DATABASE => 0, - ZM_LOG_LEVEL_FILE => 0, - ZM_LOG_LEVEL_SYSLOG => 0, - ZM_LOG_DEBUG => 0, - ZM_LOG_DEBUG_TARGET => "", - ZM_LOG_DEBUG_LEVEL => 1, - ZM_LOG_DEBUG_FILE => "" +# Fake the config variables that are used in case they are not defined yet +# Only really necessary to support upgrade from previous version + if ( !eval('defined($Config{ZM_LOG_DEBUG})') ) + { + no strict 'subs'; + no strict 'refs'; + my %dbgConfig = ( + ZM_LOG_LEVEL_DATABASE => 0, + ZM_LOG_LEVEL_FILE => 0, + ZM_LOG_LEVEL_SYSLOG => 0, + ZM_LOG_DEBUG => 0, + ZM_LOG_DEBUG_TARGET => "", + ZM_LOG_DEBUG_LEVEL => 1, + ZM_LOG_DEBUG_FILE => "" ); - while ( my ( $name, $value ) = each( %dbgConfig ) ) - { - *{$name} = sub { $value }; - } - use strict 'subs'; - use strict 'refs'; + while ( my ( $name, $value ) = each( %dbgConfig ) ) + { + *{$name} = sub { $value }; } + use strict 'subs'; + use strict 'refs'; + } } sub DESTROY { - my $this = shift; - $this->terminate(); + my $this = shift; + $this->terminate(); } sub initialise( @ ) { - my $this = shift; - my %options = @_; + my $this = shift; + my %options = @_; - $this->{id} = $options{id} if ( defined($options{id}) ); + $this->{id} = $options{id} if ( defined($options{id}) ); - $this->{logPath} = $options{logPath} if ( defined($options{logPath}) ); + $this->{logPath} = $options{logPath} if ( defined($options{logPath}) ); - my $tempLogFile; - $tempLogFile = $this->{logPath}."/".$this->{id}.".log"; - $tempLogFile = $options{logFile} if ( defined($options{logFile}) ); - if ( my $logFile = $this->getTargettedEnv('LOG_FILE') ) - { - $tempLogFile = $logFile; - } + my $tempLogFile; + $tempLogFile = $this->{logPath}."/".$this->{id}.".log"; + $tempLogFile = $options{logFile} if ( defined($options{logFile}) ); + if ( my $logFile = $this->getTargettedEnv('LOG_FILE') ) + { + $tempLogFile = $logFile; + } - my $tempLevel = INFO; - my $tempTermLevel = $this->{termLevel}; - my $tempDatabaseLevel = $this->{databaseLevel}; - my $tempFileLevel = $this->{fileLevel}; - my $tempSyslogLevel = $this->{syslogLevel}; + my $tempLevel = INFO; + my $tempTermLevel = $this->{termLevel}; + my $tempDatabaseLevel = $this->{databaseLevel}; + my $tempFileLevel = $this->{fileLevel}; + my $tempSyslogLevel = $this->{syslogLevel}; - $tempTermLevel = $options{termLevel} if ( defined($options{termLevel}) ); - if ( defined($options{databaseLevel}) ) - { - $tempDatabaseLevel = $options{databaseLevel}; - } - else - { - $tempDatabaseLevel = $Config{ZM_LOG_LEVEL_DATABASE}; - } - if ( defined($options{fileLevel}) ) - { - $tempFileLevel = $options{fileLevel}; - } - else - { - $tempFileLevel = $Config{ZM_LOG_LEVEL_FILE}; - } - if ( defined($options{syslogLevel}) ) - { - $tempSyslogLevel = $options{syslogLevel}; - } - else - { - $tempSyslogLevel = $Config{ZM_LOG_LEVEL_SYSLOG}; - } + $tempTermLevel = $options{termLevel} if ( defined($options{termLevel}) ); + if ( defined($options{databaseLevel}) ) + { + $tempDatabaseLevel = $options{databaseLevel}; + } + else + { + $tempDatabaseLevel = $Config{ZM_LOG_LEVEL_DATABASE}; + } + if ( defined($options{fileLevel}) ) + { + $tempFileLevel = $options{fileLevel}; + } + else + { + $tempFileLevel = $Config{ZM_LOG_LEVEL_FILE}; + } + if ( defined($options{syslogLevel}) ) + { + $tempSyslogLevel = $options{syslogLevel}; + } + else + { + $tempSyslogLevel = $Config{ZM_LOG_LEVEL_SYSLOG}; + } - if ( defined($ENV{'LOG_PRINT'}) ) + if ( defined($ENV{'LOG_PRINT'}) ) + { + $tempTermLevel = $ENV{'LOG_PRINT'}? DEBUG : NOLOG; + } + + my $level; + $tempLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL')) ); + + $tempTermLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_TERM')) ); + $tempDatabaseLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_DATABASE')) ); + $tempFileLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_FILE')) ); + $tempSyslogLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_SYSLOG')) ); + + if ( $Config{ZM_LOG_DEBUG} ) + { + foreach my $target ( split( /\|/, $Config{ZM_LOG_DEBUG_TARGET} ) ) { - $tempTermLevel = $ENV{'LOG_PRINT'}? DEBUG : NOLOG; - } - - my $level; - $tempLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL')) ); - - $tempTermLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_TERM')) ); - $tempDatabaseLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_DATABASE')) ); - $tempFileLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_FILE')) ); - $tempSyslogLevel = $level if ( defined($level = $this->getTargettedEnv('LOG_LEVEL_SYSLOG')) ); - - if ( $Config{ZM_LOG_DEBUG} ) - { - foreach my $target ( split( /\|/, $Config{ZM_LOG_DEBUG_TARGET} ) ) + if ( $target eq $this->{id} + || $target eq "_".$this->{id} + || $target eq $this->{idRoot} + || $target eq "_".$this->{idRoot} + || $target eq "" + ) + { + if ( $Config{ZM_LOG_DEBUG_LEVEL} > NOLOG ) { - if ( $target eq $this->{id} - || $target eq "_".$this->{id} - || $target eq $this->{idRoot} - || $target eq "_".$this->{idRoot} - || $target eq "" - ) - { - if ( $Config{ZM_LOG_DEBUG_LEVEL} > NOLOG ) - { - $tempLevel = $this->limit( $Config{ZM_LOG_DEBUG_LEVEL} ); - if ( $Config{ZM_LOG_DEBUG_FILE} ne "" ) - { - $tempLogFile = $Config{ZM_LOG_DEBUG_FILE}; - $tempFileLevel = $tempLevel; - } - } - } + $tempLevel = $this->limit( $Config{ZM_LOG_DEBUG_LEVEL} ); + if ( $Config{ZM_LOG_DEBUG_FILE} ne "" ) + { + $tempLogFile = $Config{ZM_LOG_DEBUG_FILE}; + $tempFileLevel = $tempLevel; + } } + } } + } - $this->logFile( $tempLogFile ); + $this->logFile( $tempLogFile ); - $this->termLevel( $tempTermLevel ); - $this->databaseLevel( $tempDatabaseLevel ); - $this->fileLevel( $tempFileLevel ); - $this->syslogLevel( $tempSyslogLevel ); + $this->termLevel( $tempTermLevel ); + $this->databaseLevel( $tempDatabaseLevel ); + $this->fileLevel( $tempFileLevel ); + $this->syslogLevel( $tempSyslogLevel ); - $this->level( $tempLevel ); + $this->level( $tempLevel ); - $this->{trace} = $options{trace} if ( defined($options{trace}) ); + $this->{trace} = $options{trace} if ( defined($options{trace}) ); - $this->{autoFlush} = $ENV{'LOG_FLUSH'}?1:0 if ( defined($ENV{'LOG_FLUSH'}) ); + $this->{autoFlush} = $ENV{'LOG_FLUSH'}?1:0 if ( defined($ENV{'LOG_FLUSH'}) ); - $this->{initialised} = !undef; + $this->{initialised} = !undef; - Debug( "LogOpts: level=".$codes{$this->{level}} - ."/".$codes{$this->{effectiveLevel}} - .", screen=".$codes{$this->{termLevel}} - .", database=".$codes{$this->{databaseLevel}} - .", logfile=".$codes{$this->{fileLevel}} - ."->".$this->{logFile} - .", syslog=".$codes{$this->{syslogLevel}} - ); + Debug( "LogOpts: level=".$codes{$this->{level}} + ."/".$codes{$this->{effectiveLevel}} + .", screen=".$codes{$this->{termLevel}} + .", database=".$codes{$this->{databaseLevel}} + .", logfile=".$codes{$this->{fileLevel}} + ."->".$this->{logFile} + .", syslog=".$codes{$this->{syslogLevel}} + ); } sub terminate { - my $this = shift; - return unless ( $this->{initialised} ); - $this->syslogLevel( NOLOG ); - $this->fileLevel( NOLOG ); - $this->databaseLevel( NOLOG ); - $this->termLevel( NOLOG ); + my $this = shift; + return unless ( $this->{initialised} ); + $this->syslogLevel( NOLOG ); + $this->fileLevel( NOLOG ); + $this->databaseLevel( NOLOG ); + $this->termLevel( NOLOG ); } sub reinitialise { - my $this = shift; + my $this = shift; - return unless ( $this->{initialised} ); + return unless ( $this->{initialised} ); - # Bit of a nasty hack to reopen connections to log files and the DB - my $syslogLevel = $this->syslogLevel(); - $this->syslogLevel( NOLOG ); - my $logfileLevel = $this->fileLevel(); - $this->fileLevel( NOLOG ); - my $databaseLevel = $this->databaseLevel(); - $this->databaseLevel( NOLOG ); - my $screenLevel = $this->termLevel(); - $this->termLevel( NOLOG ); +# Bit of a nasty hack to reopen connections to log files and the DB + my $syslogLevel = $this->syslogLevel(); + $this->syslogLevel( NOLOG ); + my $logfileLevel = $this->fileLevel(); + $this->fileLevel( NOLOG ); + my $databaseLevel = $this->databaseLevel(); + $this->databaseLevel( NOLOG ); + my $screenLevel = $this->termLevel(); + $this->termLevel( NOLOG ); - $this->syslogLevel( $syslogLevel ) if ( $syslogLevel > NOLOG ); - $this->fileLevel( $logfileLevel ) if ( $logfileLevel > NOLOG ); - $this->databaseLevel( $databaseLevel ) if ( $databaseLevel > NOLOG ); - $this->databaseLevel( $databaseLevel ) if ( $databaseLevel > NOLOG ); + $this->syslogLevel( $syslogLevel ) if ( $syslogLevel > NOLOG ); + $this->fileLevel( $logfileLevel ) if ( $logfileLevel > NOLOG ); + $this->databaseLevel( $databaseLevel ) if ( $databaseLevel > NOLOG ); + $this->databaseLevel( $databaseLevel ) if ( $databaseLevel > NOLOG ); } sub limit { - my $this = shift; - my $level = shift; - return( DEBUG ) if ( $level > DEBUG ); - return( NOLOG ) if ( $level < NOLOG ); - return( $level ); + my $this = shift; + my $level = shift; + return( DEBUG ) if ( $level > DEBUG ); + return( NOLOG ) if ( $level < NOLOG ); + return( $level ); } sub getTargettedEnv { - my $this = shift; - my $name = shift; - my $envName = $name."_".$this->{id}; - my $value; + my $this = shift; + my $name = shift; + my $envName = $name."_".$this->{id}; + my $value; + $value = $ENV{$envName} if ( defined($ENV{$envName}) ); + if ( !defined($value) && $this->{id} ne $this->{idRoot} ) + { + $envName = $name."_".$this->{idRoot}; $value = $ENV{$envName} if ( defined($ENV{$envName}) ); - if ( !defined($value) && $this->{id} ne $this->{idRoot} ) - { - $envName = $name."_".$this->{idRoot}; - $value = $ENV{$envName} if ( defined($ENV{$envName}) ); - } - if ( !defined($value) ) - { - $value = $ENV{$name} if ( defined($ENV{$name}) ); - } - if ( defined($value) ) - { - ( $value ) = $value =~ m/(.*)/; - } - return( $value ); + } + if ( !defined($value) ) + { + $value = $ENV{$name} if ( defined($ENV{$name}) ); + } + if ( defined($value) ) + { + ( $value ) = $value =~ m/(.*)/; + } + return( $value ); } sub fetch { - if ( !$logger ) - { - $logger = ZoneMinder::Logger->new(); - $logger->initialise( 'syslogLevel'=>INFO, 'databaseLevel'=>INFO ); - } - return( $logger ); + if ( !$logger ) + { + $logger = ZoneMinder::Logger->new(); + $logger->initialise( 'syslogLevel'=>INFO, 'databaseLevel'=>INFO ); + } + return( $logger ); } sub id { - my $this = shift; - my $id = shift; - if ( defined($id) && $this->{id} ne $id ) - { - # Remove whitespace - $id =~ s/\S//g; - # Replace non-alphanum with underscore - $id =~ s/[^a-zA-Z_]/_/g; + my $this = shift; + my $id = shift; + if ( defined($id) && $this->{id} ne $id ) + { +# Remove whitespace + $id =~ s/\S//g; +# Replace non-alphanum with underscore + $id =~ s/[^a-zA-Z_]/_/g; - if ( $this->{id} ne $id ) - { - $this->{id} = $this->{idRoot} = $id; - if ( $id =~ /^([^_]+)_(.+)$/ ) - { - $this->{idRoot} = $1; - $this->{idArgs} = $2; - } - } + if ( $this->{id} ne $id ) + { + $this->{id} = $this->{idRoot} = $id; + if ( $id =~ /^([^_]+)_(.+)$/ ) + { + $this->{idRoot} = $1; + $this->{idArgs} = $2; + } } - return( $this->{id} ); + } + return( $this->{id} ); } sub level { - my $this = shift; - my $level = shift; - if ( defined($level) ) - { - $this->{level} = $this->limit( $level ); - $this->{effectiveLevel} = NOLOG; - $this->{effectiveLevel} = $this->{termLevel} if ( $this->{termLevel} > $this->{effectiveLevel} ); - $this->{effectiveLevel} = $this->{databaseLevel} if ( $this->{databaseLevel} > $this->{effectiveLevel} ); - $this->{effectiveLevel} = $this->{fileLevel} if ( $this->{fileLevel} > $this->{effectiveLevel} ); - $this->{effectiveLevel} = $this->{syslogLevel} if ( $this->{syslogLevel} > $this->{level} ); - $this->{effectiveLevel} = $this->{level} if ( $this->{effectiveLevel} > $this->{level} ); - } - return( $this->{level} ); + my $this = shift; + my $level = shift; + if ( defined($level) ) + { + $this->{level} = $this->limit( $level ); + $this->{effectiveLevel} = NOLOG; + $this->{effectiveLevel} = $this->{termLevel} if ( $this->{termLevel} > $this->{effectiveLevel} ); + $this->{effectiveLevel} = $this->{databaseLevel} if ( $this->{databaseLevel} > $this->{effectiveLevel} ); + $this->{effectiveLevel} = $this->{fileLevel} if ( $this->{fileLevel} > $this->{effectiveLevel} ); + $this->{effectiveLevel} = $this->{syslogLevel} if ( $this->{syslogLevel} > $this->{level} ); + $this->{effectiveLevel} = $this->{level} if ( $this->{effectiveLevel} > $this->{level} ); + } + return( $this->{level} ); } sub debugOn { - my $this = shift; - return( $this->{effectiveLevel} >= DEBUG ); + my $this = shift; + return( $this->{effectiveLevel} >= DEBUG ); } sub trace { - my $this = shift; - $this->{trace} = $_[0] if ( @_ ); - return( $this->{trace} ); + my $this = shift; + $this->{trace} = $_[0] if ( @_ ); + return( $this->{trace} ); } sub termLevel { - my $this = shift; - my $termLevel = shift; - if ( defined($termLevel) ) + my $this = shift; + my $termLevel = shift; + if ( defined($termLevel) ) + { + $termLevel = NOLOG if ( !$this->{hasTerm} ); + $termLevel = $this->limit( $termLevel ); + if ( $this->{termLevel} != $termLevel ) { - $termLevel = NOLOG if ( !$this->{hasTerm} ); - $termLevel = $this->limit( $termLevel ); - if ( $this->{termLevel} != $termLevel ) - { - $this->{termLevel} = $termLevel; - } + $this->{termLevel} = $termLevel; } - return( $this->{termLevel} ); + } + return( $this->{termLevel} ); } sub databaseLevel { - my $this = shift; - my $databaseLevel = shift; - if ( defined($databaseLevel) ) + my $this = shift; + my $databaseLevel = shift; + if ( defined($databaseLevel) ) + { + $databaseLevel = $this->limit( $databaseLevel ); + if ( $this->{databaseLevel} != $databaseLevel ) { - $databaseLevel = $this->limit( $databaseLevel ); - if ( $this->{databaseLevel} != $databaseLevel ) + if ( $databaseLevel > NOLOG && $this->{databaseLevel} <= NOLOG ) + { + if ( !$this->{dbh} ) { - if ( $databaseLevel > NOLOG && $this->{databaseLevel} <= NOLOG ) - { - if ( !$this->{dbh} ) - { - my $socket; - my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); + my $socket; + my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); - if ( defined($portOrSocket) ) - { - if ( $portOrSocket =~ /^\// ) - { - $socket = ";mysql_socket=".$portOrSocket; - } - else - { - $socket = ";host=".$host.";port=".$portOrSocket; - } - } - else - { - $socket = ";host=".$Config{ZM_DB_HOST}; - } - $this->{dbh} = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .$socket - , $Config{ZM_DB_USER} - , $Config{ZM_DB_PASS} - ); - if ( !$this->{dbh} ) - { - $databaseLevel = NOLOG; - Error( "Unable to write log entries to DB, can't connect to database '" - .$Config{ZM_DB_NAME} - ."' on host '" - .$Config{ZM_DB_HOST} - ."'" - ); - } - else - { - $this->{dbh}->{AutoCommit} = 1; - Fatal( "Can't set AutoCommit on in database connection" ) - unless( $this->{dbh}->{AutoCommit} ); - $this->{dbh}->{mysql_auto_reconnect} = 1; - Fatal( "Can't set mysql_auto_reconnect on in database connection" ) - unless( $this->{dbh}->{mysql_auto_reconnect} ); - $this->{dbh}->trace( 0 ); - } - } - } - elsif ( $databaseLevel <= NOLOG && $this->{databaseLevel} > NOLOG ) + if ( defined($portOrSocket) ) + { + if ( $portOrSocket =~ /^\// ) { - if ( $this->{dbh} ) - { - $this->{dbh}->disconnect(); - undef($this->{dbh}); - } + $socket = ";mysql_socket=".$portOrSocket; } - $this->{databaseLevel} = $databaseLevel; + else + { + $socket = ";host=".$host.";port=".$portOrSocket; + } + } + else + { + $socket = ";host=".$Config{ZM_DB_HOST}; + } + $this->{dbh} = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} + .$socket + , $Config{ZM_DB_USER} + , $Config{ZM_DB_PASS} + ); + if ( !$this->{dbh} ) + { + $databaseLevel = NOLOG; + Error( "Unable to write log entries to DB, can't connect to database '" + .$Config{ZM_DB_NAME} + ."' on host '" + .$Config{ZM_DB_HOST} + ."'" + ); + } + else + { + $this->{dbh}->{AutoCommit} = 1; + Fatal( "Can't set AutoCommit on in database connection" ) + unless( $this->{dbh}->{AutoCommit} ); + $this->{dbh}->{mysql_auto_reconnect} = 1; + Fatal( "Can't set mysql_auto_reconnect on in database connection" ) + unless( $this->{dbh}->{mysql_auto_reconnect} ); + $this->{dbh}->trace( 0 ); + } } + } + elsif ( $databaseLevel <= NOLOG && $this->{databaseLevel} > NOLOG ) + { + if ( $this->{dbh} ) + { + $this->{dbh}->disconnect(); + undef($this->{dbh}); + } + } + $this->{databaseLevel} = $databaseLevel; } - return( $this->{databaseLevel} ); + } + return( $this->{databaseLevel} ); } sub fileLevel { - my $this = shift; - my $fileLevel = shift; - if ( defined($fileLevel) ) + my $this = shift; + my $fileLevel = shift; + if ( defined($fileLevel) ) + { + $fileLevel = $this->limit($fileLevel); + if ( $this->{fileLevel} != $fileLevel ) { - $fileLevel = $this->limit($fileLevel); - if ( $this->{fileLevel} != $fileLevel ) - { - $this->closeFile() if ( $this->{fileLevel} > NOLOG ); - $this->{fileLevel} = $fileLevel; - $this->openFile() if ( $this->{fileLevel} > NOLOG ); - } + $this->closeFile() if ( $this->{fileLevel} > NOLOG ); + $this->{fileLevel} = $fileLevel; + $this->openFile() if ( $this->{fileLevel} > NOLOG ); } - return( $this->{fileLevel} ); + } + return( $this->{fileLevel} ); } sub syslogLevel { - my $this = shift; - my $syslogLevel = shift; - if ( defined($syslogLevel) ) + my $this = shift; + my $syslogLevel = shift; + if ( defined($syslogLevel) ) + { + $syslogLevel = $this->limit($syslogLevel); + if ( $this->{syslogLevel} != $syslogLevel ) { - $syslogLevel = $this->limit($syslogLevel); - if ( $this->{syslogLevel} != $syslogLevel ) - { - $this->closeSyslog() if ( $syslogLevel <= NOLOG && $this->{syslogLevel} > NOLOG ); - $this->openSyslog() if ( $syslogLevel > NOLOG && $this->{syslogLevel} <= NOLOG ); - $this->{syslogLevel} = $syslogLevel; - } + $this->closeSyslog() if ( $syslogLevel <= NOLOG && $this->{syslogLevel} > NOLOG ); + $this->openSyslog() if ( $syslogLevel > NOLOG && $this->{syslogLevel} <= NOLOG ); + $this->{syslogLevel} = $syslogLevel; } - return( $this->{syslogLevel} ); + } + return( $this->{syslogLevel} ); } sub openSyslog { - my $this = shift; - openlog( $this->{id}, "pid", "local1" ); + my $this = shift; + openlog( $this->{id}, "pid", "local1" ); } sub closeSyslog { - my $this = shift; - #closelog(); + my $this = shift; +#closelog(); } sub logFile { - my $this = shift; - my $logFile = shift; - if ( $logFile =~ /^(.+)\+$/ ) - { - $this->{logFile} = $1.'.'.$$; - } - else - { - $this->{logFile} = $logFile; - } + my $this = shift; + my $logFile = shift; + if ( $logFile =~ /^(.+)\+$/ ) + { + $this->{logFile} = $1.'.'.$$; + } + else + { + $this->{logFile} = $logFile; + } } sub openFile { - my $this = shift; - if ( open( $LOGFILE, ">>", $this->{logFile} ) ) - { - $LOGFILE->autoflush() if ( $this->{autoFlush} ); + my $this = shift; + if ( open( $LOGFILE, ">>", $this->{logFile} ) ) + { + $LOGFILE->autoflush() if ( $this->{autoFlush} ); - my $webUid = (getpwnam( $Config{ZM_WEB_USER} ))[2]; - my $webGid = (getgrnam( $Config{ZM_WEB_GROUP} ))[2]; - if ( $> == 0 ) - { - chown( $webUid, $webGid, $this->{logFile} ) - or Fatal( "Can't change permissions on log file '" - .$this->{logFile}."': $!" - ) - } - } - else + my $webUid = (getpwnam( $Config{ZM_WEB_USER} ))[2]; + my $webGid = (getgrnam( $Config{ZM_WEB_GROUP} ))[2]; + if ( $> == 0 ) { - $this->fileLevel( NOLOG ); - Error( "Can't open log file '".$this->{logFile}."': $!" ); + chown( $webUid, $webGid, $this->{logFile} ) + or Fatal( "Can't change permissions on log file '" + .$this->{logFile}."': $!" + ) } + } + else + { + $this->fileLevel( NOLOG ); + Error( "Can't open log file '".$this->{logFile}."': $!" ); + } } sub closeFile { - my $this = shift; - close( $LOGFILE ) if ( fileno($LOGFILE) ); + my $this = shift; + close( $LOGFILE ) if ( fileno($LOGFILE) ); } sub logPrint { - my $this = shift; - my $level = shift; - my $string = shift; + my $this = shift; + my $level = shift; + my $string = shift; - if ( $level <= $this->{effectiveLevel} ) - { - $string =~ s/[\r\n]+$//g; + if ( $level <= $this->{effectiveLevel} ) + { + $string =~ s/[\r\n]+$//g; - my $code = $codes{$level}; + my $code = $codes{$level}; - my ($seconds, $microseconds) = gettimeofday(); - my $message = sprintf( - "%s.%06d %s[%d].%s [%s]" - , strftime( "%x %H:%M:%S" - ,localtime( $seconds ) - ) - , $microseconds - , $this->{id} - , $$ - , $code - , $string + my ($seconds, $microseconds) = gettimeofday(); + my $message = sprintf( + "%s.%06d %s[%d].%s [%s]" + , strftime( "%x %H:%M:%S" + ,localtime( $seconds ) + ) + , $microseconds + , $this->{id} + , $$ + , $code + , $string ); - if ( $this->{trace} ) - { - $message = Carp::shortmess( $message ); - } - else - { - $message = $message."\n"; - } - syslog( $priorities{$level}, $code." [%s]", $string ) - if ( $level <= $this->{syslogLevel} ); - print( $LOGFILE $message ) if ( $level <= $this->{fileLevel} ); - if ( $level <= $this->{databaseLevel} ) - { - my $sql = "insert into Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, NULL )"; - $this->{sth} = $this->{dbh}->prepare_cached( $sql ); - if ( !$this->{sth} ) - { - $this->{databaseLevel} = NOLOG; - Fatal( "Can't prepare log entry '$sql': ".$this->{dbh}->errstr() ); - } - my $res = $this->{sth}->execute( $seconds+($microseconds/1000000.0) - , $this->{id} - , $$ - , $level - , $code - , $string - , $this->{fileName} - ); - if ( !$res ) - { - $this->{databaseLevel} = NOLOG; - Fatal( "Can't execute log entry '$sql': ".$this->{sth}->errstr() ); - } - } - print( STDERR $message ) if ( $level <= $this->{termLevel} ); + if ( $this->{trace} ) + { + $message = Carp::shortmess( $message ); } + else + { + $message = $message."\n"; + } + syslog( $priorities{$level}, $code." [%s]", $string ) + if ( $level <= $this->{syslogLevel} ); + print( $LOGFILE $message ) if ( $level <= $this->{fileLevel} ); + if ( $level <= $this->{databaseLevel} ) + { + my $sql = "insert into Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, NULL )"; + $this->{sth} = $this->{dbh}->prepare_cached( $sql ); + if ( !$this->{sth} ) + { + $this->{databaseLevel} = NOLOG; + Fatal( "Can't prepare log entry '$sql': ".$this->{dbh}->errstr() ); + } + my $res = $this->{sth}->execute( $seconds+($microseconds/1000000.0) + , $this->{id} + , $$ + , $level + , $code + , $string + , $this->{fileName} + ); + if ( !$res ) + { + $this->{databaseLevel} = NOLOG; + Fatal( "Can't execute log entry '$sql': ".$this->{sth}->errstr() ); + } + } + print( STDERR $message ) if ( $level <= $this->{termLevel} ); + } } sub logInit( ;@ ) { - my %options = @_ ? @_ : (); - $logger = ZoneMinder::Logger->new() if ( !$logger ); - $logger->initialise( %options ); + my %options = @_ ? @_ : (); + $logger = ZoneMinder::Logger->new() if ( !$logger ); + $logger->initialise( %options ); } sub logReinit { - fetch()->reinitialise(); + fetch()->reinitialise(); } sub logTerm { - return unless ( $logger ); - $logger->terminate(); - $logger = undef; + return unless ( $logger ); + $logger->terminate(); + $logger = undef; } sub logHupHandler { - my $savedErrno = $!; - return unless( $logger ); - fetch()->reinitialise(); - logSetSignal(); - $! = $savedErrno; + my $savedErrno = $!; + return unless( $logger ); + fetch()->reinitialise(); + logSetSignal(); + $! = $savedErrno; } sub logSetSignal { - $SIG{HUP} = \&logHupHandler; + $SIG{HUP} = \&logHupHandler; } sub logClearSignal { - $SIG{HUP} = 'DEFAULT'; + $SIG{HUP} = 'DEFAULT'; } sub logLevel { - return( fetch()->level( @_ ) ); + return( fetch()->level( @_ ) ); } sub logDebugging { - return( fetch()->debugOn() ); + return( fetch()->debugOn() ); } sub logTermLevel { - return( fetch()->termLevel( @_ ) ); + return( fetch()->termLevel( @_ ) ); } sub logDatabaseLevel { - return( fetch()->databaseLevel( @_ ) ); + return( fetch()->databaseLevel( @_ ) ); } sub logFileLevel { - return( fetch()->fileLevel( @_ ) ); + return( fetch()->fileLevel( @_ ) ); } sub logSyslogLevel { - return( fetch()->syslogLevel( @_ ) ); + return( fetch()->syslogLevel( @_ ) ); } sub Mark { - my $level = shift; - $level = DEBUG unless( defined($level) ); - my $tag = "Mark"; - fetch()->logPrint( $level, $tag ); + my $level = shift; + $level = DEBUG unless( defined($level) ); + my $tag = "Mark"; + fetch()->logPrint( $level, $tag ); } sub Dump { - my $var = shift; - my $label = shift; - $label = "VAR" unless( defined($label) ); - fetch()->logPrint( DEBUG, Data::Dumper->Dump( [ $var ], [ $label ] ) ); + my $var = shift; + my $label = shift; + $label = "VAR" unless( defined($label) ); + fetch()->logPrint( DEBUG, Data::Dumper->Dump( [ $var ], [ $label ] ) ); } sub Debug( @ ) { - fetch()->logPrint( DEBUG, @_ ); + fetch()->logPrint( DEBUG, @_ ); } sub Info( @ ) { - fetch()->logPrint( INFO, @_ ); + fetch()->logPrint( INFO, @_ ); } sub Warning( @ ) { - fetch()->logPrint( WARNING, @_ ); + fetch()->logPrint( WARNING, @_ ); } sub Error( @ ) { - fetch()->logPrint( ERROR, @_ ); + fetch()->logPrint( ERROR, @_ ); } sub Fatal( @ ) { - fetch()->logPrint( FATAL, @_ ); - exit( -1 ); + fetch()->logPrint( FATAL, @_ ); + exit( -1 ); } sub Panic( @ ) { - fetch()->logPrint( PANIC, @_ ); - confess( $_[0] ); + fetch()->logPrint( PANIC, @_ ); + confess( $_[0] ); } 1; @@ -796,17 +796,17 @@ ZoneMinder::Logger - ZoneMinder Logger module =head1 SYNOPSIS - use ZoneMinder::Logger; - use ZoneMinder::Logger qw(:all); +use ZoneMinder::Logger; +use ZoneMinder::Logger qw(:all); - logInit( "myproc", DEBUG ); +logInit( "myproc", DEBUG ); - Debug( "This is what is happening" ); - Info( "Something interesting is happening" ); - Warning( "Something might be going wrong." ); - Error( "Something has gone wrong!!" ); - Fatal( "Something has gone badly wrong, gotta stop!!" ); - Panic( "Something fundamental has gone wrong, die with stack trace ); +Debug( "This is what is happening" ); +Info( "Something interesting is happening" ); +Warning( "Something might be going wrong." ); +Error( "Something has gone wrong!!" ); +Fatal( "Something has gone badly wrong, gotta stop!!" ); +Panic( "Something fundamental has gone wrong, die with stack trace ); =head1 DESCRIPTION @@ -837,15 +837,15 @@ compulsory arguments are $id which must be a string that will identify debug coming from this script in mixed logs. Other options may be provided as below, - Option Default Description - --------- --------- ----------- - level INFO The initial debug level which defines which statements are output and which are ignored - trace 0 Whether to use the Carp::shortmess format in debug statements to identify where the debug was emitted from - termLevel NOLOG At what level debug is written to terminal standard error, 0 is no, 1 is yes, 2 is write only if terminal - databaseLevel INFO At what level debug is written to the Log table in the database; - fileLevel NOLOG At what level debug is written to a log file of the format of .log in the standard log directory. - syslogLevel INFO At what level debug is written to syslog. - +Option Default Description +--------- --------- ----------- +level INFO The initial debug level which defines which statements are output and which are ignored +trace 0 Whether to use the Carp::shortmess format in debug statements to identify where the debug was emitted from +termLevel NOLOG At what level debug is written to terminal standard error, 0 is no, 1 is yes, 2 is write only if terminal +databaseLevel INFO At what level debug is written to the Log table in the database; +fileLevel NOLOG At what level debug is written to a log file of the format of .log in the standard log directory. +syslogLevel INFO At what level debug is written to syslog. + To disable any of these action entirely set to NOLOG =item logTerm (); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in index 8e7b9228e..17f53cdff 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in @@ -42,44 +42,44 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( 'constants' => [ qw( - STATE_IDLE - STATE_PREALARM - STATE_ALARM - STATE_ALERT - STATE_TAPE - ACTION_GET - ACTION_SET - ACTION_RELOAD - ACTION_SUSPEND - ACTION_RESUME - TRIGGER_CANCEL - TRIGGER_ON - TRIGGER_OFF - ) ], + STATE_IDLE + STATE_PREALARM + STATE_ALARM + STATE_ALERT + STATE_TAPE + ACTION_GET + ACTION_SET + ACTION_RELOAD + ACTION_SUSPEND + ACTION_RESUME + TRIGGER_CANCEL + TRIGGER_ON + TRIGGER_OFF + ) ], 'functions' => [ qw( - zmMemVerify - zmMemInvalidate - zmMemRead - zmMemWrite - zmMemTidy - zmGetMonitorState - zmGetAlarmLocation - zmIsAlarmed - zmInAlarm - zmHasAlarmed - zmGetLastEvent - zmGetLastWriteTime - zmGetLastReadTime - zmMonitorEnable - zmMonitorDisable - zmMonitorSuspend - zmMonitorResume - zmTriggerEventOn - zmTriggerEventOff - zmTriggerEventCancel - zmTriggerShowtext + zmMemVerify + zmMemInvalidate + zmMemRead + zmMemWrite + zmMemTidy + zmGetMonitorState + zmGetAlarmLocation + zmIsAlarmed + zmInAlarm + zmHasAlarmed + zmGetLastEvent + zmGetLastWriteTime + zmGetLastReadTime + zmMonitorEnable + zmMonitorDisable + zmMonitorSuspend + zmMonitorResume + zmTriggerEventOn + zmTriggerEventOff + zmTriggerEventCancel + zmTriggerShowtext ) ], -); + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); @@ -117,13 +117,13 @@ use Storable qw( freeze thaw ); if ( "@ENABLE_MMAP@" eq 'yes' ) # 'yes' if memory is mmapped { - require ZoneMinder::Memory::Mapped; - ZoneMinder::Memory::Mapped->import(); + require ZoneMinder::Memory::Mapped; + ZoneMinder::Memory::Mapped->import(); } else { - require ZoneMinder::Memory::Shared; - ZoneMinder::Memory::Shared->import(); + require ZoneMinder::Memory::Shared; + ZoneMinder::Memory::Shared->import(); } # Detaint our environment @@ -144,42 +144,42 @@ our $mem_seq = 0; our $mem_data = { - "shared_data" => { "type"=>"SharedData", "seq"=>$mem_seq++, "contents"=> { - "size" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_write_index" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_read_index" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "state" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_event" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "action" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "brightness" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "hue" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "colour" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "contrast" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "alarm_x" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "alarm_y" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "valid" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "active" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "signal" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "format" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "imagesize" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "epadding1" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "epadding2" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_write_time" => { "type"=>"time_t64", "seq"=>$mem_seq++ }, - "last_read_time" => { "type"=>"time_t64", "seq"=>$mem_seq++ }, - "control_state" => { "type"=>"uint8[256]", "seq"=>$mem_seq++ }, - } - }, - "trigger_data" => { "type"=>"TriggerData", "seq"=>$mem_seq++, "contents"=> { - "size" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_state" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_score" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "padding" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_cause" => { "type"=>"int8[32]", "seq"=>$mem_seq++ }, - "trigger_text" => { "type"=>"int8[256]", "seq"=>$mem_seq++ }, - "trigger_showtext" => { "type"=>"int8[256]", "seq"=>$mem_seq++ }, - } - }, - "end" => { "seq"=>$mem_seq++, "size"=> 0 } + "shared_data" => { "type"=>"SharedData", "seq"=>$mem_seq++, "contents"=> { + "size" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "last_write_index" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "last_read_index" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "state" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "last_event" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "action" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "brightness" => { "type"=>"int32", "seq"=>$mem_seq++ }, + "hue" => { "type"=>"int32", "seq"=>$mem_seq++ }, + "colour" => { "type"=>"int32", "seq"=>$mem_seq++ }, + "contrast" => { "type"=>"int32", "seq"=>$mem_seq++ }, + "alarm_x" => { "type"=>"int32", "seq"=>$mem_seq++ }, + "alarm_y" => { "type"=>"int32", "seq"=>$mem_seq++ }, + "valid" => { "type"=>"uint8", "seq"=>$mem_seq++ }, + "active" => { "type"=>"uint8", "seq"=>$mem_seq++ }, + "signal" => { "type"=>"uint8", "seq"=>$mem_seq++ }, + "format" => { "type"=>"uint8", "seq"=>$mem_seq++ }, + "imagesize" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "epadding1" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "epadding2" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "last_write_time" => { "type"=>"time_t64", "seq"=>$mem_seq++ }, + "last_read_time" => { "type"=>"time_t64", "seq"=>$mem_seq++ }, + "control_state" => { "type"=>"uint8[256]", "seq"=>$mem_seq++ }, + } + }, + "trigger_data" => { "type"=>"TriggerData", "seq"=>$mem_seq++, "contents"=> { + "size" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "trigger_state" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "trigger_score" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "padding" => { "type"=>"uint32", "seq"=>$mem_seq++ }, + "trigger_cause" => { "type"=>"int8[32]", "seq"=>$mem_seq++ }, + "trigger_text" => { "type"=>"int8[256]", "seq"=>$mem_seq++ }, + "trigger_showtext" => { "type"=>"int8[256]", "seq"=>$mem_seq++ }, + } + }, + "end" => { "seq"=>$mem_seq++, "size"=> 0 } }; our $mem_size = 0; @@ -187,563 +187,563 @@ our $mem_verified = {}; sub zmMemInit { - my $offset = 0; + my $offset = 0; - foreach my $section_data ( sort { $a->{seq} <=> $b->{seq} } values( %$mem_data ) ) + foreach my $section_data ( sort { $a->{seq} <=> $b->{seq} } values( %$mem_data ) ) + { + $section_data->{offset} = $offset; + $section_data->{align} = 0; + + if ( $section_data->{align} > 1 ) { - $section_data->{offset} = $offset; - $section_data->{align} = 0; - - if ( $section_data->{align} > 1 ) - { - my $rem = $offset % $section_data->{align}; - if ( $rem > 0 ) - { - $offset += ($section_data->{align} - $rem); - } - } - foreach my $member_data ( sort { $a->{seq} <=> $b->{seq} } values( %{$section_data->{contents}} ) ) - { - if ( $member_data->{type} eq "long" - || $member_data->{type} eq "ulong" - || $member_data->{type} eq "size_t" - ) - { - $member_data->{size} = $member_data->{align} = $native; - } - elsif( $member_data->{type} eq "int64" - || $member_data->{type} eq "uint64" - || $member_data->{type} eq "time_t64" - ) - { - $member_data->{size} = $member_data->{align} = 8; - } - elsif ( $member_data->{type} eq "int32" - || $member_data->{type} eq "uint32" - || $member_data->{type} eq "bool4" - ) - { - $member_data->{size} = $member_data->{align} = 4; - } - elsif ($member_data->{type} eq "int16" - || $member_data->{type} eq "uint16" - ) - { - $member_data->{size} = $member_data->{align} = 2; - } - elsif ( $member_data->{type} eq "int8" - || $member_data->{type} eq "uint8" - || $member_data->{type} eq "bool1" - ) - { - $member_data->{size} = $member_data->{align} = 1; - } - elsif ( $member_data->{type} =~ /^u?int8\[(\d+)\]$/ ) - { - $member_data->{size} = $1; - $member_data->{align} = 1; - } - else - { - Fatal( "Unexpected type '".$member_data->{type} - ."' found in shared data definition." - ); - } - - if ( $member_data->{align} > 1 && ($offset%$member_data->{align}) > 0 ) - { - $offset += ($member_data->{align} - ($offset%$member_data->{align})); - } - $member_data->{offset} = $offset; - $offset += $member_data->{size} - } - $section_data->{size} = $offset - $section_data->{offset}; + my $rem = $offset % $section_data->{align}; + if ( $rem > 0 ) + { + $offset += ($section_data->{align} - $rem); + } } + foreach my $member_data ( sort { $a->{seq} <=> $b->{seq} } values( %{$section_data->{contents}} ) ) + { + if ( $member_data->{type} eq "long" + || $member_data->{type} eq "ulong" + || $member_data->{type} eq "size_t" + ) + { + $member_data->{size} = $member_data->{align} = $native; + } + elsif( $member_data->{type} eq "int64" + || $member_data->{type} eq "uint64" + || $member_data->{type} eq "time_t64" + ) + { + $member_data->{size} = $member_data->{align} = 8; + } + elsif ( $member_data->{type} eq "int32" + || $member_data->{type} eq "uint32" + || $member_data->{type} eq "bool4" + ) + { + $member_data->{size} = $member_data->{align} = 4; + } + elsif ($member_data->{type} eq "int16" + || $member_data->{type} eq "uint16" + ) + { + $member_data->{size} = $member_data->{align} = 2; + } + elsif ( $member_data->{type} eq "int8" + || $member_data->{type} eq "uint8" + || $member_data->{type} eq "bool1" + ) + { + $member_data->{size} = $member_data->{align} = 1; + } + elsif ( $member_data->{type} =~ /^u?int8\[(\d+)\]$/ ) + { + $member_data->{size} = $1; + $member_data->{align} = 1; + } + else + { + Fatal( "Unexpected type '".$member_data->{type} + ."' found in shared data definition." + ); + } - $mem_size = $offset; + if ( $member_data->{align} > 1 && ($offset%$member_data->{align}) > 0 ) + { + $offset += ($member_data->{align} - ($offset%$member_data->{align})); + } + $member_data->{offset} = $offset; + $offset += $member_data->{size} + } + $section_data->{size} = $offset - $section_data->{offset}; + } + + $mem_size = $offset; } &zmMemInit(); sub zmMemVerify { - my $monitor = shift; - if ( !zmMemAttach( $monitor, $mem_size ) ) - { - return( undef ); - } + my $monitor = shift; + if ( !zmMemAttach( $monitor, $mem_size ) ) + { + return( undef ); + } - my $mem_key = zmMemKey( $monitor ); - if ( !defined($mem_verified->{$mem_key}) ) + my $mem_key = zmMemKey( $monitor ); + if ( !defined($mem_verified->{$mem_key}) ) + { + my $sd_size = zmMemRead( $monitor, "shared_data:size", 1 ); + if ( $sd_size != $mem_data->{shared_data}->{size} ) { - my $sd_size = zmMemRead( $monitor, "shared_data:size", 1 ); - if ( $sd_size != $mem_data->{shared_data}->{size} ) - { - if ( $sd_size ) - { - Error( "Shared data size conflict in shared_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{shared_data}->{size} - .", got " - .$sd_size - ); - } - else - { - Debug( "Shared data size conflict in shared_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{shared_data}->{size} - .", got ".$sd_size - ); - } - return( undef ); - } - my $td_size = zmMemRead( $monitor, "trigger_data:size", 1 ); - if ( $td_size != $mem_data->{trigger_data}->{size} ) - { - if ( $td_size ) - { - Error( "Shared data size conflict in trigger_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{triggger_data}->{size} - .", got " - .$td_size - ); - } - else - { - Debug( "Shared data size conflict in trigger_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{triggger_data}->{size} - .", got " - .$td_size - ); - } - return( undef ); - } - $mem_verified->{$mem_key} = !undef; + if ( $sd_size ) + { + Error( "Shared data size conflict in shared_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{shared_data}->{size} + .", got " + .$sd_size + ); + } + else + { + Debug( "Shared data size conflict in shared_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{shared_data}->{size} + .", got ".$sd_size + ); + } + return( undef ); } - return( !undef ); + my $td_size = zmMemRead( $monitor, "trigger_data:size", 1 ); + if ( $td_size != $mem_data->{trigger_data}->{size} ) + { + if ( $td_size ) + { + Error( "Shared data size conflict in trigger_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{triggger_data}->{size} + .", got " + .$td_size + ); + } + else + { + Debug( "Shared data size conflict in trigger_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{triggger_data}->{size} + .", got " + .$td_size + ); + } + return( undef ); + } + $mem_verified->{$mem_key} = !undef; + } + return( !undef ); } sub zmMemRead { - my $monitor = shift; - my $fields = shift; - my $nocheck = shift; + my $monitor = shift; + my $fields = shift; + my $nocheck = shift; - if ( !($nocheck || zmMemVerify( $monitor )) ) - { - return( undef ); - } + if ( !($nocheck || zmMemVerify( $monitor )) ) + { + return( undef ); + } - if ( !ref($fields) ) - { - $fields = [ $fields ]; - } - my @values; - foreach my $field ( @$fields ) - { - my ( $section, $element ) = split( /[\/:.]/, $field ); - Fatal( "Invalid shared data selector '$field'" ) if ( !$section || !$element ); + if ( !ref($fields) ) + { + $fields = [ $fields ]; + } + my @values; + foreach my $field ( @$fields ) + { + my ( $section, $element ) = split( /[\/:.]/, $field ); + Fatal( "Invalid shared data selector '$field'" ) if ( !$section || !$element ); - my $offset = $mem_data->{$section}->{contents}->{$element}->{offset}; - my $type = $mem_data->{$section}->{contents}->{$element}->{type}; - my $size = $mem_data->{$section}->{contents}->{$element}->{size}; + my $offset = $mem_data->{$section}->{contents}->{$element}->{offset}; + my $type = $mem_data->{$section}->{contents}->{$element}->{type}; + my $size = $mem_data->{$section}->{contents}->{$element}->{size}; - my $data = zmMemGet( $monitor, $offset, $size ); - if ( !defined($data) ) - { - Error( "Unable to read '$field' from memory for monitor ".$monitor->{Id} ); - zmMemInvalidate( $monitor ); - return( undef ); - } - my $value; - if ( $type eq "long" ) - { - ( $value ) = unpack( "l!", $data ); - } - elsif ( $type eq "ulong" || $type eq "size_t" ) - { - ( $value ) = unpack( "L!", $data ); - } - elsif ( $type eq "int64" || $type eq "time_t64" ) - { - # The "q" type is only available on 64bit platforms, so use native. - ( $value ) = unpack( "l!", $data ); - } - elsif ( $type eq "uint64" ) - { - # The "q" type is only available on 64bit platforms, so use native. - ( $value ) = unpack( "L!", $data ); - } - elsif ( $type eq "int32" ) - { - ( $value ) = unpack( "l", $data ); - } - elsif ( $type eq "uint32" || $type eq "bool4" ) - { - ( $value ) = unpack( "L", $data ); - } - elsif ( $type eq "int16" ) - { - ( $value ) = unpack( "s", $data ); - } - elsif ( $type eq "uint16" ) - { - ( $value ) = unpack( "S", $data ); - } - elsif ( $type eq "int8" ) - { - ( $value ) = unpack( "c", $data ); - } - elsif ( $type eq "uint8" || $type eq "bool1" ) - { - ( $value ) = unpack( "C", $data ); - } - elsif ( $type =~ /^int8\[\d+\]$/ ) - { - ( $value ) = unpack( "Z".$size, $data ); - } - elsif ( $type =~ /^uint8\[\d+\]$/ ) - { - ( $value ) = unpack( "C".$size, $data ); - } - else - { - Fatal( "Unexpected type '".$type."' found for '".$field."'" ); - } - push( @values, $value ); - } - if ( wantarray() ) + my $data = zmMemGet( $monitor, $offset, $size ); + if ( !defined($data) ) { - return( @values ) + Error( "Unable to read '$field' from memory for monitor ".$monitor->{Id} ); + zmMemInvalidate( $monitor ); + return( undef ); } - return( $values[0] ); + my $value; + if ( $type eq "long" ) + { + ( $value ) = unpack( "l!", $data ); + } + elsif ( $type eq "ulong" || $type eq "size_t" ) + { + ( $value ) = unpack( "L!", $data ); + } + elsif ( $type eq "int64" || $type eq "time_t64" ) + { +# The "q" type is only available on 64bit platforms, so use native. + ( $value ) = unpack( "l!", $data ); + } + elsif ( $type eq "uint64" ) + { +# The "q" type is only available on 64bit platforms, so use native. + ( $value ) = unpack( "L!", $data ); + } + elsif ( $type eq "int32" ) + { + ( $value ) = unpack( "l", $data ); + } + elsif ( $type eq "uint32" || $type eq "bool4" ) + { + ( $value ) = unpack( "L", $data ); + } + elsif ( $type eq "int16" ) + { + ( $value ) = unpack( "s", $data ); + } + elsif ( $type eq "uint16" ) + { + ( $value ) = unpack( "S", $data ); + } + elsif ( $type eq "int8" ) + { + ( $value ) = unpack( "c", $data ); + } + elsif ( $type eq "uint8" || $type eq "bool1" ) + { + ( $value ) = unpack( "C", $data ); + } + elsif ( $type =~ /^int8\[\d+\]$/ ) + { + ( $value ) = unpack( "Z".$size, $data ); + } + elsif ( $type =~ /^uint8\[\d+\]$/ ) + { + ( $value ) = unpack( "C".$size, $data ); + } + else + { + Fatal( "Unexpected type '".$type."' found for '".$field."'" ); + } + push( @values, $value ); + } + if ( wantarray() ) + { + return( @values ) + } + return( $values[0] ); } sub zmMemInvalidate { - my $monitor = shift; - my $mem_key = zmMemKey($monitor); - if ( $mem_key ) - { - delete $mem_verified->{$mem_key}; - zmMemDetach( $monitor ); - } + my $monitor = shift; + my $mem_key = zmMemKey($monitor); + if ( $mem_key ) + { + delete $mem_verified->{$mem_key}; + zmMemDetach( $monitor ); + } } sub zmMemTidy { - zmMemClean(); + zmMemClean(); } sub zmMemWrite { - my $monitor = shift; - my $field_values = shift; - my $nocheck = shift; + my $monitor = shift; + my $field_values = shift; + my $nocheck = shift; - if ( !($nocheck || zmMemVerify( $monitor )) ) + if ( !($nocheck || zmMemVerify( $monitor )) ) + { + return( undef ); + } + + while ( my ( $field, $value ) = each( %$field_values ) ) + { + my ( $section, $element ) = split( /[\/:.]/, $field ); + Fatal( "Invalid shared data selector '$field'" ) + if ( !$section || !$element ); + + my $offset = $mem_data->{$section}->{contents}->{$element}->{offset}; + my $type = $mem_data->{$section}->{contents}->{$element}->{type}; + my $size = $mem_data->{$section}->{contents}->{$element}->{size}; + + my $data; + if ( $type eq "long" ) { - return( undef ); + $data = pack( "l!", $value ); + } + elsif ( $type eq "ulong" || $type eq "size_t" ) + { + $data = pack( "L!", $value ); + } + elsif ( $type eq "int64" || $type eq "time_t64" ) + { +# The "q" type is only available on 64bit platforms, so use native. + $data = pack( "l!", $value ); + } + elsif ( $type eq "uint64" ) + { +# The "q" type is only available on 64bit platforms, so use native. + $data = pack( "L!", $value ); + } + elsif ( $type eq "int32" ) + { + $data = pack( "l", $value ); + } + elsif ( $type eq "uint32" || $type eq "bool4" ) + { + $data = pack( "L", $value ); + } + elsif ( $type eq "int16" ) + { + $data = pack( "s", $value ); + } + elsif ( $type eq "uint16" ) + { + $data = pack( "S", $value ); + } + elsif ( $type eq "int8" ) + { + $data = pack( "c", $value ); + } + elsif ( $type eq "uint8" || $type eq "bool1" ) + { + $data = pack( "C", $value ); + } + elsif ( $type =~ /^int8\[\d+\]$/ ) + { + $data = pack( "Z".$size, $value ); + } + elsif ( $type =~ /^uint8\[\d+\]$/ ) + { + $data = pack( "C".$size, $value ); + } + else + { + Fatal( "Unexpected type '".$type."' found for '".$field."'" ); } - while ( my ( $field, $value ) = each( %$field_values ) ) + if ( !zmMemPut( $monitor, $offset, $size, $data ) ) { - my ( $section, $element ) = split( /[\/:.]/, $field ); - Fatal( "Invalid shared data selector '$field'" ) - if ( !$section || !$element ); - - my $offset = $mem_data->{$section}->{contents}->{$element}->{offset}; - my $type = $mem_data->{$section}->{contents}->{$element}->{type}; - my $size = $mem_data->{$section}->{contents}->{$element}->{size}; - - my $data; - if ( $type eq "long" ) - { - $data = pack( "l!", $value ); - } - elsif ( $type eq "ulong" || $type eq "size_t" ) - { - $data = pack( "L!", $value ); - } - elsif ( $type eq "int64" || $type eq "time_t64" ) - { - # The "q" type is only available on 64bit platforms, so use native. - $data = pack( "l!", $value ); - } - elsif ( $type eq "uint64" ) - { - # The "q" type is only available on 64bit platforms, so use native. - $data = pack( "L!", $value ); - } - elsif ( $type eq "int32" ) - { - $data = pack( "l", $value ); - } - elsif ( $type eq "uint32" || $type eq "bool4" ) - { - $data = pack( "L", $value ); - } - elsif ( $type eq "int16" ) - { - $data = pack( "s", $value ); - } - elsif ( $type eq "uint16" ) - { - $data = pack( "S", $value ); - } - elsif ( $type eq "int8" ) - { - $data = pack( "c", $value ); - } - elsif ( $type eq "uint8" || $type eq "bool1" ) - { - $data = pack( "C", $value ); - } - elsif ( $type =~ /^int8\[\d+\]$/ ) - { - $data = pack( "Z".$size, $value ); - } - elsif ( $type =~ /^uint8\[\d+\]$/ ) - { - $data = pack( "C".$size, $value ); - } - else - { - Fatal( "Unexpected type '".$type."' found for '".$field."'" ); - } - - if ( !zmMemPut( $monitor, $offset, $size, $data ) ) - { - Error( "Unable to write '$value' to '$field' in memory for monitor " - .$monitor->{Id} - ); - zmMemInvalidate( $monitor ); - return( undef ); - } + Error( "Unable to write '$value' to '$field' in memory for monitor " + .$monitor->{Id} + ); + zmMemInvalidate( $monitor ); + return( undef ); } - return( !undef ); + } + return( !undef ); } sub zmGetMonitorState { - my $monitor = shift; + my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:state" ) ); + return( zmMemRead( $monitor, "shared_data:state" ) ); } sub zmGetAlarmLocation { - my $monitor = shift; + my $monitor = shift; - return( zmMemRead( $monitor, [ "shared_data:alarm_x", "shared_data:alarm_y" ] ) ); + return( zmMemRead( $monitor, [ "shared_data:alarm_x", "shared_data:alarm_y" ] ) ); } sub zmSetControlState { - my $monitor = shift; - my $control_state = shift; + my $monitor = shift; + my $control_state = shift; - zmMemWrite( $monitor, { "shared_data:control_state" => $control_state } ); + zmMemWrite( $monitor, { "shared_data:control_state" => $control_state } ); } sub zmGetControlState { - my $monitor = shift; + my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:control_state" ) ); + return( zmMemRead( $monitor, "shared_data:control_state" ) ); } sub zmSaveControlState { - my $monitor = shift; - my $control_state = shift; + my $monitor = shift; + my $control_state = shift; - zmSetControlState( $monitor, freeze( $control_state ) ); + zmSetControlState( $monitor, freeze( $control_state ) ); } sub zmRestoreControlState { - my $monitor = shift; + my $monitor = shift; - return( thaw( zmGetControlState( $monitor ) ) ); + return( thaw( zmGetControlState( $monitor ) ) ); } sub zmIsAlarmed { - my $monitor = shift; + my $monitor = shift; - my $state = zmGetMonitorState( $monitor ); + my $state = zmGetMonitorState( $monitor ); - return( $state == STATE_ALARM ); + return( $state == STATE_ALARM ); } sub zmInAlarm { - my $monitor = shift; + my $monitor = shift; - my $state = zmGetMonitorState( $monitor ); + my $state = zmGetMonitorState( $monitor ); - return( $state == STATE_ALARM || $state == STATE_ALERT ); + return( $state == STATE_ALARM || $state == STATE_ALERT ); } sub zmHasAlarmed { - my $monitor = shift; - my $last_event_id = shift; + my $monitor = shift; + my $last_event_id = shift; - my ( $state, $last_event ) = zmMemRead( $monitor, [ "shared_data:state" - ,"shared_data:last_event" - ] - ); + my ( $state, $last_event ) = zmMemRead( $monitor, [ "shared_data:state" + ,"shared_data:last_event" + ] + ); - if ( $state == STATE_ALARM || $state == STATE_ALERT ) - { - return( $last_event ); - } - elsif( $last_event != $last_event_id ) - { - return( $last_event ); - } - return( undef ); + if ( $state == STATE_ALARM || $state == STATE_ALERT ) + { + return( $last_event ); + } + elsif( $last_event != $last_event_id ) + { + return( $last_event ); + } + return( undef ); } sub zmGetLastEvent { - my $monitor = shift; + my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:last_event" ) ); + return( zmMemRead( $monitor, "shared_data:last_event" ) ); } sub zmGetLastWriteTime { - my $monitor = shift; + my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:last_write_time" ) ); + return( zmMemRead( $monitor, "shared_data:last_write_time" ) ); } sub zmGetLastReadTime { - my $monitor = shift; + my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:last_read_time" ) ); + return( zmMemRead( $monitor, "shared_data:last_read_time" ) ); } sub zmGetMonitorActions { - my $monitor = shift; + my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:action" ) ); + return( zmMemRead( $monitor, "shared_data:action" ) ); } sub zmMonitorEnable { - my $monitor = shift; + my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); - $action |= ACTION_SUSPEND; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + my $action = zmMemRead( $monitor, "shared_data:action" ); + $action |= ACTION_SUSPEND; + zmMemWrite( $monitor, { "shared_data:action" => $action } ); } sub zmMonitorDisable { - my $monitor = shift; + my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); - $action |= ACTION_RESUME; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + my $action = zmMemRead( $monitor, "shared_data:action" ); + $action |= ACTION_RESUME; + zmMemWrite( $monitor, { "shared_data:action" => $action } ); } sub zmMonitorSuspend { - my $monitor = shift; + my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); - $action |= ACTION_SUSPEND; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + my $action = zmMemRead( $monitor, "shared_data:action" ); + $action |= ACTION_SUSPEND; + zmMemWrite( $monitor, { "shared_data:action" => $action } ); } sub zmMonitorResume { - my $monitor = shift; + my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); - $action |= ACTION_RESUME; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + my $action = zmMemRead( $monitor, "shared_data:action" ); + $action |= ACTION_RESUME; + zmMemWrite( $monitor, { "shared_data:action" => $action } ); } sub zmGetTriggerState { - my $monitor = shift; + my $monitor = shift; - return( zmMemRead( $monitor, "trigger_data:trigger_state" ) ); + return( zmMemRead( $monitor, "trigger_data:trigger_state" ) ); } sub zmTriggerEventOn { - my $monitor = shift; - my $score = shift; - my $cause = shift; - my $text = shift; - my $showtext = shift; + my $monitor = shift; + my $score = shift; + my $cause = shift; + my $text = shift; + my $showtext = shift; - my $values = { - "trigger_data:trigger_score" => $score, - "trigger_data:trigger_cause" => $cause, - }; - $values->{"trigger_data:trigger_text"} = $text if ( defined($text) ); - $values->{"trigger_data:trigger_showtext"} = $showtext if ( defined($showtext) ); - $values->{"trigger_data:trigger_state"} = TRIGGER_ON; # Write state last so event not read incomplete + my $values = { + "trigger_data:trigger_score" => $score, + "trigger_data:trigger_cause" => $cause, + }; + $values->{"trigger_data:trigger_text"} = $text if ( defined($text) ); + $values->{"trigger_data:trigger_showtext"} = $showtext if ( defined($showtext) ); + $values->{"trigger_data:trigger_state"} = TRIGGER_ON; # Write state last so event not read incomplete zmMemWrite( $monitor, $values ); } sub zmTriggerEventOff { - my $monitor = shift; + my $monitor = shift; - my $values = { - "trigger_data:trigger_state" => TRIGGER_OFF, - "trigger_data:trigger_score" => 0, - "trigger_data:trigger_cause" => "", - "trigger_data:trigger_text" => "", - "trigger_data:trigger_showtext" => "", - }; + my $values = { + "trigger_data:trigger_state" => TRIGGER_OFF, + "trigger_data:trigger_score" => 0, + "trigger_data:trigger_cause" => "", + "trigger_data:trigger_text" => "", + "trigger_data:trigger_showtext" => "", + }; - zmMemWrite( $monitor, $values ); + zmMemWrite( $monitor, $values ); } sub zmTriggerEventCancel { - my $monitor = shift; + my $monitor = shift; - my $values = { - "trigger_data:trigger_state" => TRIGGER_CANCEL, - "trigger_data:trigger_score" => 0, - "trigger_data:trigger_cause" => "", - "trigger_data:trigger_text" => "", - "trigger_data:trigger_showtext" => "", - }; + my $values = { + "trigger_data:trigger_state" => TRIGGER_CANCEL, + "trigger_data:trigger_score" => 0, + "trigger_data:trigger_cause" => "", + "trigger_data:trigger_text" => "", + "trigger_data:trigger_showtext" => "", + }; - zmMemWrite( $monitor, $values ); + zmMemWrite( $monitor, $values ); } sub zmTriggerShowtext { - my $monitor = shift; - my $showtext = shift; + my $monitor = shift; + my $showtext = shift; - my $values = { - "trigger_data:trigger_showtext" => $showtext, - }; + my $values = { + "trigger_data:trigger_showtext" => $showtext, + }; - zmMemWrite( $monitor, $values ); + zmMemWrite( $monitor, $values ); } 1; @@ -755,23 +755,23 @@ ZoneMinder::MappedMem - ZoneMinder Mapped Memory access module =head1 SYNOPSIS - use ZoneMinder::MappedMem; - use ZoneMinder::MappedMem qw(:all); +use ZoneMinder::MappedMem; +use ZoneMinder::MappedMem qw(:all); - if ( zmMemVerify( $monitor ) ) +if ( zmMemVerify( $monitor ) ) +{ + $state = zmGetMonitorState( $monitor ); + if ( $state == STATE_ALARM ) { - $state = zmGetMonitorState( $monitor ); - if ( $state == STATE_ALARM ) - { - ... - } + ... } +} - ( $lri, $lwi ) = zmMemRead( $monitor, [ "shared_data:last_read_index", - "shared_data:last_write_index" - ] - ); - zmMemWrite( $monitor, { "trigger_data:trigger_showtext" => "Some Text" } ); +( $lri, $lwi ) = zmMemRead( $monitor, [ "shared_data:last_read_index", + "shared_data:last_write_index" +] +); +zmMemWrite( $monitor, { "trigger_data:trigger_showtext" => "Some Text" } ); =head1 DESCRIPTION @@ -921,32 +921,32 @@ harmless, extreme care must be taken when writing to mapped memory, especially in the shared_data section as this is normally written to only by monitor capture and analysis processes. - shared_data The general mapped memory section - size The size, in bytes, of this section - valid Flag indicating whether this section has been initialised - active Flag indicating whether this monitor is active (enabled/disabled) - signal Flag indicating whether this monitor is reciving a valid signal - state The current monitor state, see the STATE constants below - last_write_index The last index, in the image buffer, that an image has been saved to - last_read_index The last index, in the image buffer, that an image has been analysed from - last_write_time The time (in utc seconds) when the last image was captured - last_read_time The time (in utc seconds) when the last image was analysed - last_event The id of the last event generated by the monitor analysis process, 0 if none - action The monitor actions bitmask, see the ACTION constants below - brightness Read/write location for the current monitor brightness - hue Read/write location for the current monitor hue - colour Read/write location for the current monitor colour - contrast Read/write location for the current monitor contrast - alarm_x Image x co-ordinate (from left) of the centre of the last motion event, -1 if none - alarm_y Image y co-ordinate (from top) of the centre of the last motion event, -1 if none +shared_data The general mapped memory section +size The size, in bytes, of this section +valid Flag indicating whether this section has been initialised +active Flag indicating whether this monitor is active (enabled/disabled) +signal Flag indicating whether this monitor is reciving a valid signal +state The current monitor state, see the STATE constants below +last_write_index The last index, in the image buffer, that an image has been saved to +last_read_index The last index, in the image buffer, that an image has been analysed from +last_write_time The time (in utc seconds) when the last image was captured +last_read_time The time (in utc seconds) when the last image was analysed +last_event The id of the last event generated by the monitor analysis process, 0 if none +action The monitor actions bitmask, see the ACTION constants below +brightness Read/write location for the current monitor brightness +hue Read/write location for the current monitor hue +colour Read/write location for the current monitor colour +contrast Read/write location for the current monitor contrast +alarm_x Image x co-ordinate (from left) of the centre of the last motion event, -1 if none +alarm_y Image y co-ordinate (from top) of the centre of the last motion event, -1 if none - trigger_data The triggered event mapped memory section - size The size, in bytes of this section - trigger_state The current trigger state, see the TRIGGER constants below - trigger_score The current triggered event score - trigger_cause The current triggered event cause string - trigger_text The current triggered event descriptive text string - trigger_showtext The triggered text that will be displayed on captured image timestamps +trigger_data The triggered event mapped memory section +size The size, in bytes of this section +trigger_state The current trigger state, see the TRIGGER constants below +trigger_score The current triggered event score +trigger_cause The current triggered event cause string +trigger_text The current triggered event descriptive text string +trigger_showtext The triggered text that will be displayed on captured image timestamps =head1 CONSTANTS diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Server.pm b/scripts/ZoneMinder/lib/ZoneMinder/Server.pm index df8f2fb10..95f6c4265 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Server.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Server.pm @@ -42,8 +42,8 @@ our @ISA = qw(Exporter ZoneMinder::Base); # will save memory. our %EXPORT_TAGS = ( 'functions' => [ qw( - ) ] -); + ) ] + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); @@ -65,46 +65,46 @@ use ZoneMinder::Database qw(:all); use POSIX; sub new { - my ( $parent, $id, $data ) = @_; + my ( $parent, $id, $data ) = @_; - my $self = {}; - bless $self, $parent; - if ( ( $$self{Id} = $id ) or $data ) { + my $self = {}; + bless $self, $parent; + if ( ( $$self{Id} = $id ) or $data ) { #$log->debug("loading $parent $id") if $debug or DEBUG_ALL; - $self->load( $data ); - } - return $self; + $self->load( $data ); + } + return $self; } # end sub new sub load { - my ( $self, $data ) = @_; - my $type = ref $self; - if ( ! $data ) { + my ( $self, $data ) = @_; + my $type = ref $self; + if ( ! $data ) { #$log->debug("Object::load Loading from db $type"); - $data = $ZoneMinder::Database::dbh->selectrow_hashref( 'SELECT * FROM Servers WHERE Id=?', {}, $$self{Id} ); - if ( ! $data ) { - if ( $ZoneMinder::Database::dbh->errstr ) { - Error( "Failure to load Server record for $$self{id}: Reason: " . $ZoneMinder::Database::dbh->errstr ); - } # end if - } # end if - } # end if ! $data - if ( $data and %$data ) { - @$self{keys %$data} = values %$data; - } # end if + $data = $ZoneMinder::Database::dbh->selectrow_hashref( 'SELECT * FROM Servers WHERE Id=?', {}, $$self{Id} ); + if ( ! $data ) { + if ( $ZoneMinder::Database::dbh->errstr ) { + Error( "Failure to load Server record for $$self{id}: Reason: " . $ZoneMinder::Database::dbh->errstr ); + } # end if + } # end if + } # end if ! $data + if ( $data and %$data ) { + @$self{keys %$data} = values %$data; + } # end if } # end sub load sub Name { - if ( @_ > 1 ) { - $_[0]{Name} = $_[1]; - } - return $_[0]{Name}; + if ( @_ > 1 ) { + $_[0]{Name} = $_[1]; + } + return $_[0]{Name}; } # end sub Name sub Hostname { - if ( @_ > 1 ) { - $_[0]{Hostname} = $_[1]; - } - return $_[0]{Hostname}; + if ( @_ > 1 ) { + $_[0]{Hostname} = $_[1]; + } + return $_[0]{Hostname}; } # end sub Hostname 1; @@ -117,8 +117,8 @@ ZoneMinder::Database - Perl extension for blah blah blah =head1 SYNOPSIS - use ZoneMinder::Server; - blah blah blah +use ZoneMinder::Server; +blah blah blah =head1 DESCRIPTION