Merge branch 'master' of github.com:ZoneMinder/ZoneMinder

This commit is contained in:
Isaac Connor 2017-06-28 11:58:23 -04:00
commit 323cd3540d
21 changed files with 1048 additions and 996 deletions

View File

@ -331,13 +331,13 @@ CREATE TABLE `Monitors` (
`Format` int(10) unsigned NOT NULL default '0',
`V4LMultiBuffer` tinyint(1) unsigned,
`V4LCapturesPerFrame` tinyint(3) unsigned,
`Protocol` varchar(16) NOT NULL default '',
`Protocol` varchar(16) default '',
`Method` varchar(16) NOT NULL default '',
`Host` varchar(64),
`Port` varchar(8) NOT NULL default '',
`SubPath` varchar(64) NOT NULL default '',
`Path` varchar(255),
`Options` varchar(255) not null default '',
`Options` varchar(255) default '',
`User` varchar(64),
`Pass` varchar(64),
`Width` smallint(5) unsigned NOT NULL default '0',

View File

@ -6,3 +6,4 @@ usr/share/perl5/ZoneMinder.pm
usr/share/zoneminder/db
usr/share/zoneminder/www
etc/zm
etc/zm/conf.d/*

View File

@ -18,7 +18,7 @@ Alias /zm "@ZM_WEBDIR@"
Allow from all
</Directory>
ScriptAlias /cgi-bin/zm "@ZM_CGIDIR@"
ScriptAlias /cgi-bin-zm "@ZM_CGIDIR@"
<Directory "@ZM_CGIDIR@">
SSLRequireSSL
AllowOverride All

View File

@ -33,7 +33,7 @@
%global _hardened_build 1
Name: zoneminder
Version: 1.31.0
Version: 1.31.1
Release: 1%{?dist}
Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons

View File

@ -1,4 +1,5 @@
etc/zm/zm.conf
etc/zm/conf.d/*
usr/bin
usr/lib/zoneminder
usr/share/polkit-1

View File

@ -1,4 +1,5 @@
etc/zm
etc/zm/conf.d/*
usr/bin
usr/share/polkit-1/actions
usr/share/polkit-1/rules.d

View File

@ -1,4 +1,5 @@
etc/zm/zm.conf
etc/zm/conf.d/*
usr/bin
usr/lib/zoneminder
usr/share/polkit-1

View File

@ -4,60 +4,62 @@ set -e
if [ "$1" = "configure" ]; then
. /etc/zm/zm.conf
. /etc/zm/zm.conf
# The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group
chown www-data:root /var/log/zm
chown www-data:www-data /var/lib/zm
if [ -z "$2" ]; then
chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/*
fi
if [ ! -e "/etc/apache2/mods-enabled/cgi.load" ]; then
echo "The cgi module is not enabled in apache2. I am enabling it using a2enmod cgi."
a2enmod cgi
fi
# The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group
chown www-data:root /var/log/zm
chown www-data:www-data /var/lib/zm
if [ -z "$2" ]; then
chown www-data:www-data /var/cache/zoneminder /var/cache/zoneminder/*
fi
if [ ! -e "/etc/apache2/mods-enabled/cgi.load" ]; then
echo "The cgi module is not enabled in apache2. I am enabling it using a2enmod cgi."
a2enmod cgi
fi
# Do this every time the package is installed or upgraded
# Ensure zoneminder is stopped
deb-systemd-invoke stop zoneminder.service || exit $?
# Ensure zoneminder is stopped
deb-systemd-invoke stop zoneminder.service || exit $?
if [ "$ZM_DB_HOST" = "localhost" ]; then
if [ -e "/etc/init.d/mysql" ]; then
if [ -e "/etc/init.d/mysql" ]; then
#
# Get mysql started if it isn't
#
if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then
deb-systemd-invoke start mysql.service || exit $?
fi
#
# Get mysql started if it isn't
#
if ! $(/etc/init.d/mysql status >/dev/null 2>&1); then
deb-systemd-invoke start mysql.service || exit $?
fi
if $(/etc/init.d/mysql status >/dev/null 2>&1); then
mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload
# test if database if already present...
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf
# This creates the user.
echo "grant lock tables,alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
else
echo "grant lock tables,alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
fi
zmupdate.pl --nointeractive
zmupdate.pl --nointeractive -f
else
echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.'
fi
if $(/etc/init.d/mysql status >/dev/null 2>&1); then
mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload
# test if database if already present...
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
cat /usr/share/zoneminder/db/zm_create.sql | mysql --defaults-file=/etc/mysql/debian.cnf
# This creates the user.
echo "grant lock tables,alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost identified by \"${ZM_DB_PASS}\";" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
else
echo 'mysql not found, assuming remote server.'
echo "grant lock tables,alter,select,insert,update,delete,create,index on ${ZM_DB_NAME}.* to '${ZM_DB_USER}'@localhost;" | mysql --defaults-file=/etc/mysql/debian.cnf mysql
fi
zmupdate.pl --nointeractive
zmupdate.pl --nointeractive -f
else
echo 'NOTE: mysql not running, please start mysql and run dpkg-reconfigure zoneminder when it is running.'
fi
else
echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)"
echo 'mysql not found, assuming remote server.'
fi
echo "Done Updating, starting ZoneMinder"
deb-systemd-invoke start zoneminder.service || exit $?
else
echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)"
fi
echo "Done Updating, starting ZoneMinder"
deb-systemd-invoke restart zoneminder.service || exit $?
fi
#DEBHELPER#

View File

@ -11,6 +11,8 @@
@ZM_LOGDIR@/*.log {
missingok
notifempty
delaycompress
compress
sharedscripts
postrotate
/bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true

View File

@ -33,8 +33,11 @@ FOREACH(PERLSCRIPT ${perlscripts})
ENDFOREACH(PERLSCRIPT ${perlscripts})
# Install the perl scripts
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmsystemctl.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
if(NOT ZM_NO_X10)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
endif(NOT ZM_NO_X10)
if(WITH_SYSTEMD)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmsystemctl.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
endif(WITH_SYSTEMD)

View File

@ -41,7 +41,7 @@ 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 = (
'constants' => [ qw(
constants => [ qw(
DEBUG
INFO
WARNING
@ -50,7 +50,7 @@ our %EXPORT_TAGS = (
PANIC
NOLOG
) ],
'functions' => [ qw(
functions => [ qw(
logInit
logReinit
logTerm
@ -72,13 +72,14 @@ our %EXPORT_TAGS = (
Panic
) ]
);
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
our @EXPORT = qw();
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our $VERSION = $ZoneMinder::Base::VERSION;
our @EXPORT = qw();
our $VERSION = $ZoneMinder::Base::VERSION;
# ==========================================================================
#
@ -86,43 +87,43 @@ our %EXPORT_TAGS = (
#
# ==========================================================================
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",
&INFO => "INF",
&WARNING => "WAR",
&ERROR => "ERR",
&FATAL => "FAT",
&PANIC => "PNC",
&NOLOG => "OFF"
&DEBUG => 'DBG',
&INFO => 'INF',
&WARNING => 'WAR',
&ERROR => 'ERR',
&FATAL => 'FAT',
&PANIC => 'PNC',
&NOLOG => 'OFF'
);
our %priorities = (
&DEBUG => "debug",
&INFO => "info",
&WARNING => "warning",
&ERROR => "err",
&FATAL => "err",
&PANIC => "err"
&DEBUG => 'debug',
&INFO => 'info',
&WARNING => 'warning',
&ERROR => 'err',
&FATAL => 'err',
&PANIC => 'err'
);
our $logger;
@ -134,10 +135,10 @@ sub new {
$this->{initialised} = undef;
#$this->{id} = "zmundef";
#$this->{id} = 'zmundef';
( $this->{id} ) = $0 =~ m|^(?:.*/)?([^/]+?)(?:\.[^/.]+)?$|;
$this->{idRoot} = $this->{id};
$this->{idArgs} = "";
$this->{idArgs} = '';
$this->{level} = INFO;
$this->{termLevel} = NOLOG;
@ -151,7 +152,7 @@ sub new {
( $this->{fileName} = $0 ) =~ s|^.*/||;
$this->{logPath} = $Config{ZM_PATH_LOGS};
$this->{logFile} = $this->{logPath}."/".$this->{id}.".log";
$this->{logFile} = $this->{logPath}.'/'.$this->{id}.".log";
$this->{trace} = 0;
@ -196,7 +197,7 @@ sub initialise( @ ) {
$this->{logPath} = $options{logPath} if ( defined($options{logPath}) );
my $tempLogFile;
$tempLogFile = $this->{logPath}."/".$this->{id}.".log";
$tempLogFile = $this->{logPath}.'/'.$this->{id}.".log";
$tempLogFile = $options{logFile} if ( defined($options{logFile}) );
if ( my $logFile = $this->getTargettedEnv('LOG_FILE') ) {
$tempLogFile = $logFile;
@ -231,7 +232,6 @@ sub initialise( @ ) {
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')) );
@ -240,9 +240,9 @@ sub initialise( @ ) {
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->{id}
|| $target eq $this->{idRoot}
|| $target eq "_".$this->{idRoot}
|| $target eq '_'.$this->{idRoot}
|| $target eq ""
) {
if ( $Config{ZM_LOG_DEBUG_LEVEL} > NOLOG ) {
@ -271,13 +271,13 @@ sub initialise( @ ) {
$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}}
);
}
@ -322,11 +322,11 @@ sub limit {
sub getTargettedEnv {
my $this = shift;
my $name = shift;
my $envName = $name."_".$this->{id};
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};
$envName = $name.'_'.$this->{idRoot};
$value = $ENV{$envName} if ( defined($ENV{$envName}) );
}
if ( !defined($value) ) {
@ -375,8 +375,8 @@ sub level {
$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} );
$this->{effectiveLevel} = $this->{syslogLevel} if ( $this->{syslogLevel} > $this->{effectiveLevel} );
$this->{effectiveLevel} = $this->{level} if ( $this->{effectiveLevel} > $this->{effectiveLevel} );
}
return( $this->{level} );
}
@ -490,7 +490,7 @@ sub syslogLevel {
sub openSyslog {
my $this = shift;
openlog( $this->{id}, "pid", "local1" );
openlog( $this->{id}, 'pid', "local1" );
}
sub closeSyslog {
@ -523,6 +523,7 @@ sub openFile {
}
} else {
$this->fileLevel( NOLOG );
$this->termLevel( INFO );
Error( "Can't open log file '".$this->{logFile}."': $!" );
}
}
@ -544,10 +545,8 @@ sub logPrint {
my ($seconds, $microseconds) = gettimeofday();
my $message = sprintf(
"%s.%06d %s[%d].%s [%s]"
, strftime( "%x %H:%M:%S"
,localtime( $seconds )
)
'%s.%06d %s[%d].%s [%s]'
, strftime( '%x %H:%M:%S' ,localtime( $seconds ) )
, $microseconds
, $this->{id}
, $$
@ -564,7 +563,7 @@ sub logPrint {
}
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 )";
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;
@ -589,7 +588,7 @@ sub logPrint {
sub logInit( ;@ ) {
my %options = @_ ? @_ : ();
$logger = ZoneMinder::Logger->new() if ( !$logger );
$logger = ZoneMinder::Logger->new() if !$logger;
$logger->initialise( %options );
}
@ -646,14 +645,14 @@ sub logSyslogLevel {
sub Mark {
my $level = shift;
$level = DEBUG unless( defined($level) );
my $tag = "Mark";
my $tag = 'Mark';
fetch()->logPrint( $level, $tag );
}
sub Dump {
my $var = shift;
my $label = shift;
$label = "VAR" unless( defined($label) );
$label = 'VAR' unless( defined($label) );
fetch()->logPrint( DEBUG, Data::Dumper->Dump( [ $var ], [ $label ] ) );
}
@ -695,10 +694,10 @@ ZoneMinder::Logger - ZoneMinder Logger module
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" );
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!!" );

View File

@ -21,6 +21,304 @@
#
# ==========================================================================
use strict;
use bytes;
@EXTRA_PERL_LIB@
use ZoneMinder;
use DBI;
use Getopt::Long;
use autouse 'Pod::Usage'=>qw(pod2usage);
use LWP::UserAgent;
use Sys::MemInfo qw(totalmem);
use Sys::CPU qw(cpu_count);
use POSIX qw(strftime uname);
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
use constant CHECK_INTERVAL => (14*24*60*60); # Interval between version checks
# Setting these as contants for now.
# Alternatively, we can put these in the dB and then retrieve using the Config hash.
use constant ZM_TELEMETRY_SERVER_ENDPOINT => 'https://zmanon:2b2d0b4skps@telemetry.zoneminder.com/zmtelemetry/testing5';
if ( $Config{ZM_TELEMETRY_DATA} ) {
print( 'Update agent starting at '.strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" );
my $lastCheck = $Config{ZM_TELEMETRY_LAST_UPLOAD};
while( 1 ) {
my $now = time();
if ( ($now-$lastCheck) > CHECK_INTERVAL ) {
Info( 'Collecting data to send to ZoneMinder Telemetry server.' );
my $dbh = zmDbConnect();
# Build the telemetry hash
# We should keep *BSD systems in mind when calling system commands
my %telemetry;
$telemetry{uuid} = getUUID($dbh);
$telemetry{ip} = getIP();
$telemetry{timestamp} = strftime( '%Y-%m-%dT%H:%M:%S%z', localtime() );
$telemetry{monitor_count} = countQuery($dbh,'Monitors');
$telemetry{event_count} = countQuery($dbh,'Events');
$telemetry{architecture} = runSysCmd('uname -p');
($telemetry{kernel}, $telemetry{distro}, $telemetry{version}) = getDistro();
$telemetry{zm_version} = ZoneMinder::Base::ZM_VERSION;
$telemetry{system_memory} = totalmem();
$telemetry{processor_count} = cpu_count();
$telemetry{monitors} = getMonitorRef($dbh);
Info( 'Sending data to ZoneMinder Telemetry server.' );
my $result = jsonEncode( \%telemetry );
if ( sendData($result) ) {
$lastCheck = $now;
my $sql = q`update Config set Value = ? where Name = 'ZM_TELEMETRY_LAST_UPLOAD'`;
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute( "$lastCheck" ) or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
}
}
sleep( 3600 );
}
print( 'Update agent exiting at '.strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" );
}
###############
# SUBROUTINES #
###############
# Find, verify, then run the supplied system command
sub runSysCmd {
my $msg = shift;
my @arguments = split(/ /,$msg);
chomp($arguments[0]);
my $path = qx( which $arguments[0] );
my $status = $? >> 8;
my $result = '';
if ( !$path || $status ) {
Warning( "Cannot find the $arguments[0] executable." );
} else {
chomp($path);
$arguments[0] = $path;
my $cmd = join(' ',@arguments);
$result = qx( $cmd );
chomp($result);
}
return $result;
}
# Upload message data to ZoneMinder telemetry server
sub sendData {
my $msg = shift;
my $ua = LWP::UserAgent->new;
my $server_endpoint = ZM_TELEMETRY_SERVER_ENDPOINT;
if ( $Config{ZM_UPDATE_CHECK_PROXY} ) {
$ua->proxy( 'https', $Config{ZM_UPDATE_CHECK_PROXY} );
}
Debug("Posting telemetry data to: $server_endpoint");
# set custom HTTP request header fields
my $req = HTTP::Request->new(POST => $server_endpoint);
$req->header('content-type' => 'application/x-www-form-urlencoded');
$req->header('content-length' => length($msg));
$req->header('connection' => 'Close');
$req->content($msg);
my $resp = $ua->request($req);
my $resp_msg = $resp->decoded_content;
my $resp_code = $resp->code;
if ($resp->is_success) {
Info('Telemetry data uploaded successfully.');
Debug("Telemetry server upload success response message: $resp_msg");
} else {
Warning("Telemetry server returned HTTP POST error code: $resp_code");
Debug("Telemetry server upload failure response message: $resp_msg");
}
return $resp->is_success;
}
# Retrieves the UUID from the database. Creates a new UUID if one does not exist.
sub getUUID {
my $dbh = shift;
my $uuid= "";
# Verify the current UUID is valid and not nil
if (( $Config{ZM_TELEMETRY_UUID} =~ /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i ) && ( $Config{ZM_TELEMETRY_UUID} ne '00000000-0000-0000-0000-000000000000' )) {
$uuid = $Config{ZM_TELEMETRY_UUID};
} else {
my $sql = 'SELECT uuid()';
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() );
$uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array();
$sth->finish();
$sql = q`UPDATE Config set Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'`;
$sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
$res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
}
Debug("Using UUID of: $uuid");
return $uuid;
}
# Retrieves the local server's external IP address
sub getIP {
my $ipaddr = '0.0.0.0';
my $ua = LWP::UserAgent->new;
my $server_endpoint = 'https://wiki.zoneminder.com/ip.php';
my $req = HTTP::Request->new(GET => $server_endpoint);
my $resp = $ua->request($req);
if ($resp->is_success) {
$ipaddr = $resp->decoded_content;
}
Debug("Found external ip address of: $ipaddr");
return $ipaddr;
}
# As the name implies, just your average mysql count query
sub countQuery {
my $dbh = shift;
my $table = shift;
my $sql = "SELECT count(*) FROM $table";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() );
my $count = $sth->fetchrow_array();
$sth->finish();
return $count
}
# Returns a reference to an array of hashes containing data from all monitors
sub getMonitorRef {
my $dbh = shift;
my $sql = 'SELECT Id,Name,Type,Function,Width,Height,Colours,MaxFPS,AlarmMaxFPS FROM Monitors';
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() );
my $arrayref = $sth->fetchall_arrayref({});
return $arrayref;
}
sub getDistro {
my $kernel = '';
my $distro = '';
my $version = '';
my @uname = uname();
if ( $uname[0] =~ /Linux/ ) {
Debug('Linux distro detected.');
($kernel, $distro, $version) = linuxDistro();
} elsif ( $uname[0] =~ /.*BSD/ ) {
Debug('BSD distro detected.');
$kernel = $uname[3];
$distro = $uname[0];
$version = $uname[2];
} elsif ( $uname[0] =~ /Darwin/ ) {
Debug('Mac OS distro detected.');
$kernel = $uname[3];
$distro = runSysCmd('sw_vers -productName');
$version = runSysCmd('sw_vers -productVersion');
} elsif ( $uname[0] =~ /SunOS|Solaris/ ) {
Debug('Sun Solaris detected.');
$kernel = $uname[3];
$distro = $uname[1];
$version = $uname[2];
} else {
Warning('ZoneMinder was unable to determine the host system. Please report.');
$kernel = 'Unknown';
$distro = 'Unknown';
$version = 'Unknown';
}
return ($kernel, $distro, $version);
}
sub linuxDistro {
my @uname = uname();
my $kernel = $uname[2];
my $distro = 'Unknown Linux Distro';
my $version = 'Unknown Linux Version';
my $found = 0;
# os-release is the standard for many new distros based on systemd
if ( -f '/etc/os-release' ) {
open(my $RELFILE,'<','/etc/os-release') or die( "Can't Open file: $!\n" );
while (<$RELFILE>) {
if ( /^NAME=(")?(.*)(?(1)\1|).*$/ ) {
$distro = $2;
$found = 1;
}
if ( /^VERSION_ID=(")?(.*)(?(1)\1|).*$/ ) {
$version = $2;
$found = 1;
}
}
close $RELFILE;
# exists on many distros but does not always contain useful information, such as redhat
} elsif ( -f '/etc/lsb-release' ) {
open(my $RELFILE,'<','/etc/lsb-release') or die( "Can't Open file: $!\n" );
while (<$RELFILE>) {
if ( /^DISTRIB_DESCRIPTION=(")?(.*)(?(1)\1|).*$/ ) {
$distro = $2;
$found = 1;
}
if ( /^DISTRIB_RELEASE=(")?(.*)(?(1)\1|).*$/ ) {
$version = $2;
$found = 1;
}
}
close $RELFILE;
}
# If all else fails, search through a list of known release files until we find one
if ( !$found ) {
my @releasefile = ('/etc/SuSE-release', '/etc/redhat-release', '/etc/redhat_version',
'/etc/fedora-release', '/etc/slackware-release', '/etc/slackware-version',
'/etc/debian_release', '/etc/debian_version', '/etc/mandrake-release',
'/etc/yellowdog-release', '/etc/gentoo-release');
foreach (@releasefile) {
if ( -f $_ ) {
open(my $RELFILE,'<',$_) or die( "Can't Open file: $!\n" );
while (<$RELFILE>) {
if ( /(.*).* (\d+\.?\d*) .*/ ) {
$distro = $1;
$version = $2;
$found = 1;
}
}
close $RELFILE;
last;
}
}
}
if ( !$found ) {
Warning('ZoneMinder was unable to determine Linux distro. Please report.');
}
return ($kernel, $distro, $version);
}
1;
__END__
=head1 NAME
zmtelemetry.pl - Send usage information to the ZoneMinder development team
@ -44,300 +342,3 @@ console under Options.
none currently
=cut
use strict;
use bytes;
@EXTRA_PERL_LIB@
use ZoneMinder;
use DBI;
use Getopt::Long;
use autouse 'Pod::Usage'=>qw(pod2usage);
use LWP::UserAgent;
use Sys::MemInfo qw(totalmem);
use Sys::CPU qw(cpu_count);
use POSIX qw(strftime uname);
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
use constant CHECK_INTERVAL => (14*24*60*60); # Interval between version checks
# Setting these as contants for now.
# Alternatively, we can put these in the dB and then retrieve using the Config hash.
use constant ZM_TELEMETRY_SERVER_ENDPOINT => 'https://zmanon:2b2d0b4skps@telemetry.zoneminder.com/zmtelemetry/testing5';
if ( $Config{ZM_TELEMETRY_DATA} )
{
print( "Update agent starting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" );
my $lastCheck = $Config{ZM_TELEMETRY_LAST_UPLOAD};
while( 1 ) {
my $now = time();
if ( ($now-$lastCheck) > CHECK_INTERVAL ) {
Info( "Collecting data to send to ZoneMinder Telemetry server." );
my $dbh = zmDbConnect();
# Build the telemetry hash
# We should keep *BSD systems in mind when calling system commands
my %telemetry;
$telemetry{uuid} = getUUID($dbh);
$telemetry{ip} = getIP();
$telemetry{timestamp} = strftime( "%Y-%m-%dT%H:%M:%S%z", localtime() );
$telemetry{monitor_count} = countQuery($dbh,"Monitors");
$telemetry{event_count} = countQuery($dbh,"Events");
$telemetry{architecture} = runSysCmd("uname -p");
($telemetry{kernel}, $telemetry{distro}, $telemetry{version}) = getDistro();
$telemetry{zm_version} = ZoneMinder::Base::ZM_VERSION;
$telemetry{system_memory} = totalmem();
$telemetry{processor_count} = cpu_count();
$telemetry{monitors} = getMonitorRef($dbh);
Info( "Sending data to ZoneMinder Telemetry server." );
my $result = jsonEncode( \%telemetry );
if ( sendData($result) ) {
$lastCheck = $now;
my $sql = "update Config set Value = ? where Name = 'ZM_TELEMETRY_LAST_UPLOAD'";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute( "$lastCheck" ) or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
}
}
sleep( 3600 );
}
print( "Update agent exiting at ".strftime( '%y/%m/%d %H:%M:%S', localtime() )."\n" );
}
###############
# SUBROUTINES #
###############
# Find, verify, then run the supplied system command
sub runSysCmd {
my $msg = shift;
my @arguments = split(/ /,$msg);
chomp($arguments[0]);
my $path = qx( which $arguments[0] );
my $status = $? >> 8;
my $result = "";
if ( !$path || $status ) {
Warning( "Cannot find the $arguments[0] executable." );
} else {
chomp($path);
$arguments[0] = $path;
my $cmd = join(" ",@arguments);
$result = qx( $cmd );
chomp($result);
}
return $result;
}
# Upload message data to ZoneMinder telemetry server
sub sendData {
my $msg = shift;
my $ua = LWP::UserAgent->new;
my $server_endpoint = ZM_TELEMETRY_SERVER_ENDPOINT;
if ( $Config{ZM_UPDATE_CHECK_PROXY} ) {
$ua->proxy( "https", $Config{ZM_UPDATE_CHECK_PROXY} );
}
Debug("Posting telemetry data to: $server_endpoint");
# set custom HTTP request header fields
my $req = HTTP::Request->new(POST => $server_endpoint);
$req->header('content-type' => 'application/x-www-form-urlencoded');
$req->header('content-length' => length($msg));
$req->header('connection' => 'Close');
$req->content($msg);
my $resp = $ua->request($req);
my $resp_msg = $resp->decoded_content;
my $resp_code = $resp->code;
if ($resp->is_success) {
Info("Telemetry data uploaded successfully.");
Debug("Telemetry server upload success response message: $resp_msg");
} else {
Warning("Telemetry server returned HTTP POST error code: $resp_code");
Debug("Telemetry server upload failure response message: $resp_msg");
}
return $resp->is_success;
}
# Retrieves the UUID from the database. Creates a new UUID if one does not exist.
sub getUUID {
my $dbh = shift;
my $uuid= "";
# Verify the current UUID is valid and not nil
if (( $Config{ZM_TELEMETRY_UUID} =~ /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i ) && ( $Config{ZM_TELEMETRY_UUID} ne "00000000-0000-0000-0000-000000000000" )) {
$uuid = $Config{ZM_TELEMETRY_UUID};
} else {
my $sql = "SELECT uuid()";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() );
$uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array();
$sth->finish();
$sql = "UPDATE Config set Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'";
$sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
$res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
}
Debug("Using UUID of: $uuid");
return $uuid;
}
# Retrieves the local server's external IP address
sub getIP {
my $ipaddr = "0.0.0.0";
my $ua = LWP::UserAgent->new;
my $server_endpoint = "https://wiki.zoneminder.com/ip.php";
my $req = HTTP::Request->new(GET => $server_endpoint);
my $resp = $ua->request($req);
if ($resp->is_success) {
$ipaddr = $resp->decoded_content;
}
Debug("Found external ip address of: $ipaddr");
return $ipaddr;
}
# As the name implies, just your average mysql count query
sub countQuery {
my $dbh = shift;
my $table = shift;
my $sql = "SELECT count(*) FROM $table";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() );
my $count = $sth->fetchrow_array();
$sth->finish();
return $count
}
# Returns a reference to an array of hashes containing data from all monitors
sub getMonitorRef {
my $dbh = shift;
my $sql = "SELECT Id,Name,Type,Function,Width,Height,Colours,MaxFPS,AlarmMaxFPS FROM Monitors";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() );
my $arrayref = $sth->fetchall_arrayref({});
return $arrayref;
}
sub getDistro {
my $kernel = "";
my $distro = "";
my $version = "";
my @uname = uname();
if ( $uname[0] =~ /Linux/ ) {
Debug("Linux distro detected.");
($kernel, $distro, $version) = linuxDistro();
} elsif ( $uname[0] =~ /.*BSD/ ) {
Debug("BSD distro detected.");
$kernel = $uname[3];
$distro = $uname[0];
$version = $uname[2];
} elsif ( $uname[0] =~ /Darwin/ ) {
Debug("Mac OS distro detected.");
$kernel = $uname[3];
$distro = runSysCmd("sw_vers -productName");
$version = runSysCmd("sw_vers -productVersion");
} elsif ( $uname[0] =~ /SunOS|Solaris/ ) {
Debug("Sun Solaris detected.");
$kernel = $uname[3];
$distro = $uname[1];
$version = $uname[2];
} else {
Warning("ZoneMinder was unable to determine the host system. Please report.");
$kernel = "Unknown";
$distro = "Unknown";
$version = "Unknown";
}
return ($kernel, $distro, $version);
}
sub linuxDistro {
my @uname = uname();
my $kernel = $uname[2];
my $distro = "Unknown Linux Distro";
my $version = "Unknown Linux Version";
my $found = 0;
# os-release is the standard for many new distros based on systemd
if ( -f "/etc/os-release" ) {
open(my $RELFILE,"<","/etc/os-release") or die( "Can't Open file: $!\n" );
while (<$RELFILE>) {
if ( /^NAME=(")?(.*)(?(1)\1|).*$/ ) {
$distro = $2;
$found = 1;
}
if ( /^VERSION_ID=(")?(.*)(?(1)\1|).*$/ ) {
$version = $2;
$found = 1;
}
}
close $RELFILE;
# exists on many distros but does not always contain useful information, such as redhat
} elsif ( -f "/etc/lsb-release" ) {
open(my $RELFILE,"<","/etc/lsb-release") or die( "Can't Open file: $!\n" );
while (<$RELFILE>) {
if ( /^DISTRIB_DESCRIPTION=(")?(.*)(?(1)\1|).*$/ ) {
$distro = $2;
$found = 1;
}
if ( /^DISTRIB_RELEASE=(")?(.*)(?(1)\1|).*$/ ) {
$version = $2;
$found = 1;
}
}
close $RELFILE;
}
# If all else fails, search through a list of known release files until we find one
if ( !$found ) {
my @releasefile = ("/etc/SuSE-release", "/etc/redhat-release", "/etc/redhat_version",
"/etc/fedora-release", "/etc/slackware-release", "/etc/slackware-version",
"/etc/debian_release", "/etc/debian_version", "/etc/mandrake-release",
"/etc/yellowdog-release", "/etc/gentoo-release");
foreach (@releasefile) {
if ( -f $_ ) {
open(my $RELFILE,"<",$_) or die( "Can't Open file: $!\n" );
while (<$RELFILE>) {
if ( /(.*).* (\d+\.?\d*) .*/ ) {
$distro = $1;
$version = $2;
$found = 1;
}
}
close $RELFILE;
last;
}
}
}
if ( !$found ) {
Warning("ZoneMinder was unable to determine Linux distro. Please report.");
}
return ($kernel, $distro, $version);
}

View File

@ -94,20 +94,11 @@ my $size = '';
my $overwrite = 0;
my $version = 0;
my @formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} );
for ( my $i = 0; $i < @formats; $i++ )
{
if ( $i =~ /^(.+)\*$/ )
{
$format = $formats[$i] = $1;
}
}
GetOptions(
'concat|c:s' =>\$concat_name,
'concat|c:s' =>\$concat_name,
'event|e=i' =>\$event_id,
'filter_name=s' =>\$filter_name,
'filter_id=i' =>\$filter_id,
'filter_id=i' =>\$filter_id,
'format|f=s' =>\$format,
'rate|r=f' =>\$rate,
'scale|s=f' =>\$scale,
@ -115,51 +106,51 @@ GetOptions(
'size|S=s' =>\$size,
'overwrite' =>\$overwrite,
'version' =>\$version
) or pod2usage(-exitstatus => -1);
) or pod2usage(-exitstatus => -1);
if ( $version ) {
print ZoneMinder::Base::ZM_VERSION . "\n";
exit(0);
print ZoneMinder::Base::ZM_VERSION . "\n";
exit(0);
}
if ( !( $filter_id or $filter_name or $event_id ) || $event_id < 0 )
{
print( STDERR "Please give a valid event id or filter name\n" );
pod2usage(-exitstatus => -1);
if ( !( $filter_id or $filter_name or $event_id ) || ($event_id and ( $event_id < 0 ) ) ) {
print( STDERR "Please give a valid event id or filter name\n" );
pod2usage(-exitstatus => -1);
}
if ( ! $Config{ZM_OPT_FFMPEG} )
{
print( STDERR "Mpeg encoding is not currently enabled\n" );
exit(-1);
if ( ! $Config{ZM_OPT_FFMPEG} ) {
print( STDERR "Mpeg encoding is not currently enabled\n" );
exit(-1);
}
if ( !$rate && !$fps )
{
$rate = 1;
my @formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} );
for ( my $i = 0; $i < @formats; $i++ ) {
if ( $i =~ /^(.+)\*$/ ) {
$format = $formats[$i] = $1;
}
}
if ( !$scale && !$size )
{
$scale = 1;
if ( !$rate && !$fps ) {
$rate = 1;
}
if ( $rate && ($rate < 0.25 || $rate > 100) )
{
print( STDERR "Rate is out of range, 0.25 >= rate <= 100\n" );
pod2usage(-exitstatus => -1);
if ( !$scale && !$size ) {
$scale = 1;
}
if ( $scale && ($scale < 0.25 || $scale > 4) )
{
print( STDERR "Scale is out of range, 0.25 >= scale <= 4\n" );
pod2usage(-exitstatus => -1);
if ( $rate && ($rate < 0.25 || $rate > 100) ) {
print( STDERR "Rate is out of range, 0.25 >= rate <= 100\n" );
pod2usage(-exitstatus => -1);
}
if ( $fps && ($fps > 30) )
{
print( STDERR "FPS is out of range, <= 30\n" );
pod2usage(-exitstatus => -1);
if ( $scale && ($scale < 0.25 || $scale > 4) ) {
print( STDERR "Scale is out of range, 0.25 >= scale <= 4\n" );
pod2usage(-exitstatus => -1);
}
if ( $fps && ($fps > 30) ) {
print( STDERR "FPS is out of range, <= 30\n" );
pod2usage(-exitstatus => -1);
}
my ( $detaint_format ) = $format =~ /^(\w+)$/;
@ -181,19 +172,25 @@ my $cwd = getcwd;
my $video_name;
my @event_ids;
if ( $event_id ) {
@event_ids = ( $event_id );
@event_ids = ( $event_id );
} elsif ( $filter_name or $filter_id ) {
my $Filter = ZoneMinder::Filter->find_one(
($filter_name ? ( Name => $filter_name ) : () ),
($filter_id ? ( Id => $filter_name ) : () ),
);
if ( ! $Filter ) {
Fatal("Filter $filter_name $filter_id not found.");
}
@event_ids = map { $_->{Id} }$Filter->Execute();
Fatal( "No events found for $filter_name") if ! @event_ids;
$concat_name = $filter_name if $concat_name eq '';
my $Filter = ZoneMinder::Filter->find_one(
($filter_name ? ( Name => $filter_name ) : () ),
($filter_id ? ( Id => $filter_name ) : () ),
);
if ( ! $Filter ) {
Fatal("Filter $filter_name $filter_id not found.");
}
@event_ids = map { $_->{Id} } $Filter->Execute();
if ( ! @event_ids ) {
Fatal( "No events found for $filter_name")
} else {
Debug(@event_ids . " events found for $filter_name");
}
$concat_name = $filter_name if $concat_name eq '';
} else {
Warning("Nothing to do");
}
my $sql = " SELECT max(F.Delta)-min(F.Delta) as FullLength,
@ -207,53 +204,54 @@ my $sql = " SELECT max(F.Delta)-min(F.Delta) as FullLength,
INNER JOIN Events as E on F.EventId = E.Id
INNER JOIN Monitors as M on E.MonitorId = M.Id
WHERE EventId = ?
GROUP BY F.EventId"
;
GROUP BY F.EventId";
my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
my @video_files;
foreach my $event_id ( @event_ids ) {
my $res = $sth->execute( $event_id )
or Fatal( "Can't execute: ".$sth->errstr() );
my $event = $sth->fetchrow_hashref();
my $res = $sth->execute( $event_id )
or Fatal( "Can't execute: ".$sth->errstr() );
my $event = $sth->fetchrow_hashref();
my $Event = new ZoneMinder::Event( $$event{Id}, $event );
my $video_file = $Event->GenerateVideo( $rate, $fps, $scale, $size, $overwrite, $format );
if ( $video_file ) {
push @video_files, $video_file;
print( STDOUT $video_file."\n" );
}
my $Event = new ZoneMinder::Event( $$event{Id}, $event );
my $video_file = $Event->GenerateVideo( $rate, $fps, $scale, $size, $overwrite, $format );
if ( $video_file ) {
push @video_files, $video_file;
print( STDOUT $video_file."\n" );
} else {
Warning("No video file generated for event $event_id");
}
} # end foreach event_id
if ( $concat_name ) {
($cwd) = $cwd =~ /(.*)/; # detaint
chdir( $cwd );
($concat_name ) = $concat_name =~ /([\-A-Za-z0-9_\.]*)/;
my $concat_list_file = "/tmp/$concat_name.concat.lst";
($cwd) = $cwd =~ /(.*)/; # detaint
chdir( $cwd );
($concat_name ) = $concat_name =~ /([\-A-Za-z0-9_\.]*)/;
my $concat_list_file = "/tmp/$concat_name.concat.lst";
my $video_file = $concat_name . '.'. $detaint_format;
my $video_file = $concat_name . '.'. $detaint_format;
open( my $fd, '>', $concat_list_file ) or die "Can't open $concat_list_file: $!";
foreach ( @video_files ) {
print $fd "file '$_'\n";
}
close $fd;
my $command = $Config{ZM_PATH_FFMPEG}
. " -f concat -i $concat_list_file -c copy "
." '$video_file' > ffmpeg.log 2>&1"
;
Debug( $command."\n" );
my $output = qx($command);
open( my $fd, '>', $concat_list_file ) or die "Can't open $concat_list_file: $!";
foreach ( @video_files ) {
print $fd "file '$_'\n";
}
close $fd;
my $command = $Config{ZM_PATH_FFMPEG}
. " -f concat -i $concat_list_file -c copy "
." '$video_file' > ffmpeg.log 2>&1"
;
Debug( $command."\n" );
my $output = qx($command);
my $status = $? >> 8;
my $status = $? >> 8;
unlink $concat_list_file;
if ( $status )
{
Error( "Unable to generate video, check /ffmpeg.log for details");
exit(-1);
}
print( STDOUT $video_file."\n" );
unlink $concat_list_file;
if ( $status ) {
Error( "Unable to generate video, check /ffmpeg.log for details");
exit(-1);
}
print( STDOUT $video_file."\n" );
}
exit( 0 );
__END__

View File

@ -32,8 +32,8 @@
#include <stdlib.h>
#include <limits.h>
/* Workaround for GNU/kFreeBSD */
#if defined(__FreeBSD_kernel__)
/* Workaround for GNU/kFreeBSD and FreeBSD */
#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
#ifndef ENODATA
#define ENODATA ENOATTR
#endif

View File

@ -24,6 +24,10 @@ extern "C" {
#include <libavformat/avformat.h>
}
#ifdef __FreeBSD__
#include <sys/time.h>
#endif // __FreeBSD__
class ZMPacket {
public:

View File

@ -1,30 +1,30 @@
#ifdef HAVE_SENDFILE4_SUPPORT
#include <sys/sendfile.h>
int zm_sendfile(int out_fd, int in_fd, off_t *offset, size_t size) {
int err;
int err;
err = sendfile(out_fd, in_fd, offset, size);
if (err < 0)
return -errno;
err = sendfile(out_fd, in_fd, offset, size);
if (err < 0)
return -errno;
return err;
return err;
}
#elif HAVE_SENDFILE7_SUPPORT
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
int zm_sendfile(int out_fd, int in_fd, off_t *offset, off_t size) {
int err;
err = sendfile(in_fd, out_fd, *offset, size, NULL, &size, 0);
if (err && errno != EAGAIN)
return -errno;
int err;
err = sendfile(in_fd, out_fd, *offset, size, NULL, &size, 0);
if (err && errno != EAGAIN)
return -errno;
if (size) {
*offset += size;
return size;
}
if (size) {
*offset += size;
return size;
}
return -EAGAIN;
return -EAGAIN;
}
#else
#error "Your platform does not support sendfile. Sorry."

View File

@ -63,13 +63,13 @@ RETSIGTYPE zm_die_handler(int signal)
ucontext_t *uc = (ucontext_t *) context;
cr2 = info->si_addr;
#if defined(__x86_64__)
#ifdef __FreeBSD_kernel__
#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
ip = (void *)(uc->uc_mcontext.mc_rip);
#else
ip = (void *)(uc->uc_mcontext.gregs[REG_RIP]);
#endif
#else
#ifdef __FreeBSD_kernel__
#if defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
ip = (void *)(uc->uc_mcontext.mc_eip);
#else
ip = (void *)(uc->uc_mcontext.gregs[REG_EIP]);

View File

@ -1,522 +1,577 @@
// Copyright (C) 2001-2017 ZoneMinder LLC
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//
#include "zm.h"
#include "zm_video.h"
#include "zm_image.h"
#include "zm_utils.h"
#include "zm_rgb.h"
#include <sstream>
#include <string>
#include <vector>
VideoWriter::VideoWriter(const char* p_container, const char* p_codec, const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder) :
container(p_container), codec(p_codec), path(p_path), width(p_width), height(p_height), colours(p_colours), subpixelorder(p_subpixelorder), frame_count(0) {
Debug(7,"Video object created");
/* Parameter checking */
if(path.empty()) {
Error("Invalid file path");
}
if(!width || !height) {
Error("Invalid width or height");
}
VideoWriter::VideoWriter(
const char* p_container,
const char* p_codec,
const char* p_path,
const unsigned int p_width,
const unsigned int p_height,
const unsigned int p_colours,
const unsigned int p_subpixelorder) :
container(p_container),
codec(p_codec),
path(p_path),
width(p_width),
height(p_height),
colours(p_colours),
subpixelorder(p_subpixelorder),
frame_count(0) {
Debug(7, "Video object created");
/* Parameter checking */
if ( path.empty() ) {
Error("Invalid file path");
}
if ( !width || !height ) {
Error("Invalid width or height");
}
}
VideoWriter::~VideoWriter() {
Debug(7,"Video object destroyed");
Debug(7, "Video object destroyed");
}
int VideoWriter::Reset(const char* new_path) {
/* Common variables reset */
/* Common variables reset */
/* If there is a new path, use it */
if(new_path != NULL) {
path = new_path;
}
/* If there is a new path, use it */
if ( new_path != NULL ) {
path = new_path;
}
/* Reset frame counter */
frame_count = 0;
/* Reset frame counter */
frame_count = 0;
return 0;
return 0;
}
#if ZM_HAVE_VIDEOWRITER_X264MP4
X264MP4Writer::X264MP4Writer(const char* p_path, const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, const std::vector<EncoderParameter_t>* p_user_params) : VideoWriter("mp4", "h264", p_path, p_width, p_height, p_colours, p_subpixelorder), bOpen(false), bGotH264AVCInfo(false), bFirstFrame(true) {
/* Initialize ffmpeg if it hasn't been initialized yet */
FFMPEGInit();
X264MP4Writer::X264MP4Writer(
const char* p_path,
const unsigned int p_width,
const unsigned int p_height,
const unsigned int p_colours,
const unsigned int p_subpixelorder,
const std::vector<EncoderParameter_t>* p_user_params) :
VideoWriter(
"mp4",
"h264",
p_path,
p_width,
p_height,
p_colours,
p_subpixelorder),
bOpen(false),
bGotH264AVCInfo(false),
bFirstFrame(true) {
/* Initialize ffmpeg if it hasn't been initialized yet */
FFMPEGInit();
/* Initialize swscale */
zm_pf = GetFFMPEGPixelFormat(colours,subpixelorder);
if(zm_pf == 0) {
Error("Unable to match ffmpeg pixelformat");
}
codec_pf = AV_PIX_FMT_YUV420P;
/* Initialize swscale */
zm_pf = GetFFMPEGPixelFormat(colours, subpixelorder);
if ( zm_pf == 0 ) {
Error("Unable to match ffmpeg pixelformat");
}
codec_pf = AV_PIX_FMT_YUV420P;
swscaleobj.SetDefaults(zm_pf, codec_pf, width, height);
swscaleobj.SetDefaults(zm_pf, codec_pf, width, height);
/* Calculate the image sizes. We will need this for parameter checking */
zm_imgsize = colours * width * height;
/* Calculate the image sizes. We will need this for parameter checking */
zm_imgsize = colours * width * height;
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
codec_imgsize = av_image_get_buffer_size( codec_pf, width, height, 1 );
codec_imgsize = av_image_get_buffer_size(codec_pf, width, height, 1);
#else
codec_imgsize = avpicture_get_size( codec_pf, width, height);
codec_imgsize = avpicture_get_size(codec_pf, width, height);
#endif
if(!codec_imgsize) {
Error("Failed calculating codec pixel format image size");
}
if ( !codec_imgsize ) {
Error("Failed calculating codec pixel format image size");
}
/* If supplied with user parameters to the encoder, copy them */
if(p_user_params != NULL) {
user_params = *p_user_params;
}
/* Setup x264 parameters */
if(x264config() < 0) {
Error("Failed setting x264 parameters");
}
/* Allocate x264 input picture */
x264_picture_alloc(&x264picin, X264_CSP_I420, x264params.i_width, x264params.i_height);
/* If supplied with user parameters to the encoder, copy them */
if ( p_user_params != NULL ) {
user_params = *p_user_params;
}
/* Setup x264 parameters */
if ( x264config() < 0 ) {
Error("Failed setting x264 parameters");
}
/* Allocate x264 input picture */
x264_picture_alloc(
&x264picin,
X264_CSP_I420,
x264params.i_width,
x264params.i_height);
}
X264MP4Writer::~X264MP4Writer() {
/* Free x264 input picture */
x264_picture_clean(&x264picin);
/* Free x264 input picture */
x264_picture_clean(&x264picin);
if(bOpen)
Close();
if ( bOpen )
Close();
}
int X264MP4Writer::Open() {
/* Open the encoder */
x264enc = x264_encoder_open(&x264params);
if ( x264enc == NULL ) {
Error("Failed opening x264 encoder");
return -1;
}
/* Open the encoder */
x264enc = x264_encoder_open(&x264params);
if(x264enc == NULL) {
Error("Failed opening x264 encoder");
return -1;
}
// Debug(4,"x264 maximum delayed frames: %d",
// x264_encoder_maximum_delayed_frames(x264enc));
// Debug(4,"x264 maximum delayed frames: %d",x264_encoder_maximum_delayed_frames(x264enc));
x264_nal_t* nals;
int i_nals;
if(!x264_encoder_headers(x264enc,&nals,&i_nals)) {
Error("Failed getting encoder headers");
return -2;
}
/* Search SPS NAL for AVC information */
for(int i=0;i<i_nals;i++) {
if(nals[i].i_type == NAL_SPS) {
x264_profleindication = nals[i].p_payload[5];
x264_profilecompat = nals[i].p_payload[6];
x264_levelindication = nals[i].p_payload[7];
bGotH264AVCInfo = true;
break;
}
}
if(!bGotH264AVCInfo) {
Warning("Missing AVC information");
}
/* Create the file */
mp4h = MP4Create((path + ".incomplete").c_str());
if(mp4h == MP4_INVALID_FILE_HANDLE) {
Error("Failed creating mp4 file: %s",path.c_str());
return -10;
}
/* Set the global timescale */
if(!MP4SetTimeScale(mp4h, 1000)) {
Error("Failed setting timescale");
return -11;
}
/* Set the global video profile */
/* I am a bit confused about this one.
I couldn't find what the value should be
Some use 0x15 while others use 0x7f. */
MP4SetVideoProfileLevel(mp4h, 0x7f);
/* Add H264 video track */
mp4vtid = MP4AddH264VideoTrack(mp4h,1000,MP4_INVALID_DURATION,width,height,x264_profleindication,x264_profilecompat,x264_levelindication,3);
if(mp4vtid == MP4_INVALID_TRACK_ID) {
Error("Failed adding H264 video track");
return -12;
}
x264_nal_t* nals;
int i_nals;
if ( !x264_encoder_headers(x264enc, &nals, &i_nals) ) {
Error("Failed getting encoder headers");
return -2;
}
bOpen = true;
return 0;
/* Search SPS NAL for AVC information */
for ( unsigned int i = 0; i < i_nals; i++ ) {
if ( nals[i].i_type == NAL_SPS ) {
x264_profleindication = nals[i].p_payload[5];
x264_profilecompat = nals[i].p_payload[6];
x264_levelindication = nals[i].p_payload[7];
bGotH264AVCInfo = true;
break;
}
}
if ( !bGotH264AVCInfo ) {
Warning("Missing AVC information");
}
/* Create the file */
mp4h = MP4Create((path + ".incomplete").c_str());
if ( mp4h == MP4_INVALID_FILE_HANDLE ) {
Error("Failed creating mp4 file: %s", path.c_str());
return -10;
}
/* Set the global timescale */
if ( !MP4SetTimeScale(mp4h, 1000) ) {
Error("Failed setting timescale");
return -11;
}
/* Set the global video profile */
/* I am a bit confused about this one.
I couldn't find what the value should be
Some use 0x15 while others use 0x7f. */
MP4SetVideoProfileLevel(mp4h, 0x7f);
/* Add H264 video track */
mp4vtid = MP4AddH264VideoTrack(
mp4h,
1000,
MP4_INVALID_DURATION,
width,
height,
x264_profleindication,
x264_profilecompat,
x264_levelindication,
3);
if ( mp4vtid == MP4_INVALID_TRACK_ID ) {
Error("Failed adding H264 video track");
return -12;
}
bOpen = true;
return 0;
}
int X264MP4Writer::Close() {
/* Flush all pending frames */
for ( int i = (x264_encoder_delayed_frames(x264enc) + 1); i > 0; i-- ) {
x264encodeloop(true);
}
/* Flush all pending frames */
for(int i = (x264_encoder_delayed_frames(x264enc) + 1); i > 0; i-- ) {
x264encodeloop(true);
}
/* Close the encoder */
x264_encoder_close(x264enc);
/* Close the encoder */
x264_encoder_close(x264enc);
/* Close MP4 handle */
MP4Close(mp4h);
/* Close MP4 handle */
MP4Close(mp4h);
/* Required for proper HTTP streaming */
MP4Optimize((path + ".incomplete").c_str(), path.c_str());
/* Required for proper HTTP streaming */
MP4Optimize((path + ".incomplete").c_str(), path.c_str());
/* Delete the temporary file */
unlink((path + ".incomplete").c_str());
bOpen = false;
/* Delete the temporary file */
unlink((path + ".incomplete").c_str());
Debug(7, "Video closed. Total frames: %d", frame_count);
return 0;
bOpen = false;
Debug(7, "Video closed. Total frames: %d", frame_count);
return 0;
}
int X264MP4Writer::Reset(const char* new_path) {
/* Close the encoder and file */
if(bOpen)
Close();
/* Close the encoder and file */
if ( bOpen )
Close();
/* Reset common variables */
VideoWriter::Reset(new_path);
/* Reset common variables */
VideoWriter::Reset(new_path);
/* Reset local variables */
bFirstFrame = true;
bGotH264AVCInfo = false;
prevnals.clear();
prevpayload.clear();
/* Reset x264 parameters */
x264config();
/* Reset local variables */
bFirstFrame = true;
bGotH264AVCInfo = false;
prevnals.clear();
prevpayload.clear();
/* Open the encoder */
Open();
return 0;
/* Reset x264 parameters */
x264config();
/* Open the encoder */
Open();
return 0;
}
int X264MP4Writer::Encode(const uint8_t* data, const size_t data_size, const unsigned int frame_time) {
int X264MP4Writer::Encode(
const uint8_t* data,
const size_t data_size,
const unsigned int frame_time) {
/* Parameter checking */
if ( data == NULL ) {
Error("NULL buffer");
return -1;
}
/* Parameter checking */
if(data == NULL) {
Error("NULL buffer");
return -1;
}
if ( data_size != zm_imgsize ) {
Error("The data buffer size (%d) != expected (%d)", data_size, zm_imgsize);
return -2;
}
if(data_size != zm_imgsize) {
Error("The data buffer size does not match the expected size. Expected: %d Current: %d", zm_imgsize, data_size);
return -2;
}
if(!bOpen) {
Warning("The encoder was not initialized, initializing now");
Open();
}
/* Convert the image into the x264 input picture */
if(swscaleobj.ConvertDefaults(data, data_size, x264picin.img.plane[0], codec_imgsize) < 0) {
Error("Image conversion failed");
return -3;
}
if ( !bOpen ) {
Warning("The encoder was not initialized, initializing now");
Open();
}
/* Set PTS */
x264picin.i_pts = frame_time;
/* Convert the image into the x264 input picture */
if ( swscaleobj.ConvertDefaults(data, data_size, x264picin.img.plane[0], codec_imgsize) < 0 ) {
Error("Image conversion failed");
return -3;
}
/* Do the encoding */
x264encodeloop();
/* Set PTS */
x264picin.i_pts = frame_time;
/* Increment frame counter */
frame_count++;
/* Do the encoding */
x264encodeloop();
return 0;
/* Increment frame counter */
frame_count++;
return 0;
}
int X264MP4Writer::Encode(const Image* img, const unsigned int frame_time) {
if(img->Width() != width) {
Error("Source image width differs. Source: %d Output: %d",img->Width(), width);
return -12;
}
if ( img->Width() != width ) {
Error("Source image width differs. Source: %d Output: %d", img->Width(), width);
return -12;
}
if(img->Height() != height) {
Error("Source image height differs. Source: %d Output: %d",img->Height(), height);
return -13;
}
return Encode(img->Buffer(),img->Size(),frame_time);
if ( img->Height() != height ) {
Error("Source image height differs. Source: %d Output: %d", img->Height(), height);
return -13;
}
return Encode(img->Buffer(), img->Size(), frame_time);
}
int X264MP4Writer::x264config() {
/* Sets up the encoder configuration */
/* Sets up the encoder configuration */
int x264ret;
int x264ret;
/* Defaults */
const char* preset = "veryfast";
const char* tune = "stillimage";
const char* profile = "main";
/* Defaults */
const char* preset = "veryfast";
const char* tune = "stillimage";
const char* profile = "main";
/* Search the user parameters for preset, tune and profile */
for(unsigned int i=0; i < user_params.size(); i++) {
if(strcmp(user_params[i].pname, "preset") == 0) {
/* Got preset */
preset = user_params[i].pvalue;
} else if(strcmp(user_params[i].pname, "tune") == 0) {
/* Got tune */
tune = user_params[i].pvalue;
} else if(strcmp(user_params[i].pname, "profile") == 0) {
/* Got profile */
profile = user_params[i].pvalue;
}
}
/* Search the user parameters for preset, tune and profile */
for ( unsigned int i = 0; i < user_params.size(); i++ ) {
if ( strcmp(user_params[i].pname, "preset") == 0 ) {
/* Got preset */
preset = user_params[i].pvalue;
} else if ( strcmp(user_params[i].pname, "tune") == 0 ) {
/* Got tune */
tune = user_params[i].pvalue;
} else if ( strcmp(user_params[i].pname, "profile") == 0 ) {
/* Got profile */
profile = user_params[i].pvalue;
}
}
/* Set the defaults and preset and tune */
x264ret = x264_param_default_preset(&x264params, preset, tune);
if(x264ret != 0) {
Error("Failed setting x264 preset %s and tune %s : %d",preset,tune,x264ret);
}
/* Set the profile */
x264ret = x264_param_apply_profile(&x264params, profile);
if(x264ret != 0) {
Error("Failed setting x264 profile %s : %d",profile,x264ret);
}
/* Input format */
x264params.i_width = width;
x264params.i_height = height;
x264params.i_csp = X264_CSP_I420;
/* Quality control */
x264params.rc.i_rc_method = X264_RC_CRF;
x264params.rc.f_rf_constant = 23.0;
/* Set the defaults and preset and tune */
x264ret = x264_param_default_preset(&x264params, preset, tune);
if ( x264ret != 0 ) {
Error("Failed setting x264 preset %s and tune %s : %d", preset, tune, x264ret);
}
/* Enable b-frames */
x264params.i_bframe = 16;
x264params.i_bframe_adaptive = 1;
/* Timebase */
x264params.i_timebase_num = 1;
x264params.i_timebase_den = 1000;
/* Set the profile */
x264ret = x264_param_apply_profile(&x264params, profile);
if ( x264ret != 0 ) {
Error("Failed setting x264 profile %s : %d", profile, x264ret);
}
/* Enable variable frame rate */
x264params.b_vfr_input = 1;
/* Input format */
x264params.i_width = width;
x264params.i_height = height;
x264params.i_csp = X264_CSP_I420;
/* Disable annex-b (start codes) */
x264params.b_annexb = 0;
/* TODO: Setup error handler */
//x264params.i_log_level = X264_LOG_DEBUG;
/* Quality control */
x264params.rc.i_rc_method = X264_RC_CRF;
x264params.rc.f_rf_constant = 23.0;
/* Process user parameters (excluding preset, tune and profile) */
for(unsigned int i=0; i < user_params.size(); i++) {
/* Skip preset, tune and profile */
if( (strcmp(user_params[i].pname, "preset") == 0) || (strcmp(user_params[i].pname, "tune") == 0) || (strcmp(user_params[i].pname, "profile") == 0) ) {
continue;
}
/* Enable b-frames */
x264params.i_bframe = 16;
x264params.i_bframe_adaptive = 1;
/* Pass the name and value to x264 */
x264ret = x264_param_parse(&x264params, user_params[i].pname, user_params[i].pvalue);
/* Timebase */
x264params.i_timebase_num = 1;
x264params.i_timebase_den = 1000;
/* Error checking */
if(x264ret != 0) {
if(x264ret == X264_PARAM_BAD_NAME) {
Error("Failed processing x264 user parameter %s=%s : Bad name", user_params[i].pname, user_params[i].pvalue);
} else if(x264ret == X264_PARAM_BAD_VALUE) {
Error("Failed processing x264 user parameter %s=%s : Bad value", user_params[i].pname, user_params[i].pvalue);
} else {
Error("Failed processing x264 user parameter %s=%s : Unknown error (%d)", user_params[i].pname, user_params[i].pvalue, x264ret);
}
}
}
return 0;
/* Enable variable frame rate */
x264params.b_vfr_input = 1;
/* Disable annex-b (start codes) */
x264params.b_annexb = 0;
/* TODO: Setup error handler */
// x264params.i_log_level = X264_LOG_DEBUG;
/* Process user parameters (excluding preset, tune and profile) */
for ( unsigned int i = 0; i < user_params.size(); i++ ) {
/* Skip preset, tune and profile */
if (
(strcmp(user_params[i].pname, "preset") == 0) ||
(strcmp(user_params[i].pname, "tune") == 0) ||
(strcmp(user_params[i].pname, "profile") == 0) ) {
continue;
}
/* Pass the name and value to x264 */
x264ret = x264_param_parse(&x264params, user_params[i].pname, user_params[i].pvalue);
/* Error checking */
if ( x264ret != 0 ) {
if ( x264ret == X264_PARAM_BAD_NAME ) {
Error("Failed processing x264 user parameter %s=%s : Bad name",
user_params[i].pname, user_params[i].pvalue);
} else if ( x264ret == X264_PARAM_BAD_VALUE ) {
Error("Failed processing x264 user parameter %s=%s : Bad value",
user_params[i].pname, user_params[i].pvalue);
} else {
Error("Failed processing x264 user parameter %s=%s : Unknown error (%d)",
user_params[i].pname, user_params[i].pvalue, x264ret);
}
}
}
return 0;
}
void X264MP4Writer::x264encodeloop(bool bFlush) {
x264_nal_t* nals;
int i_nals;
int frame_size;
x264_nal_t* nals;
int i_nals;
int frame_size;
if ( bFlush ) {
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, NULL, &x264picout);
} else {
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, &x264picin, &x264picout);
}
if(bFlush) {
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, NULL, &x264picout);
} else {
frame_size = x264_encoder_encode(x264enc, &nals, &i_nals, &x264picin, &x264picout);
}
if ( frame_size > 0 || bFlush ) {
Debug(8, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",
frame_count, x264picout.i_pts, x264picout.i_dts, frame_size);
if (frame_size > 0 || bFlush) {
Debug(8, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",frame_count, x264picout.i_pts, x264picout.i_dts, frame_size);
/* Handle the previous frame */
if ( !bFirstFrame ) {
buffer.clear();
/* Handle the previous frame */
if(!bFirstFrame) {
/* Process the NALs for the previous frame */
for ( unsigned int i = 0; i < prevnals.size(); i++ ) {
Debug(9, "Processing NAL: Type %d Size %d",
prevnals[i].i_type,
prevnals[i].i_payload);
buffer.clear();
switch ( prevnals[i].i_type ) {
case NAL_PPS:
/* PPS NAL */
MP4AddH264PictureParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4);
break;
case NAL_SPS:
/* SPS NAL */
MP4AddH264SequenceParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4);
break;
default:
/* Anything else, hopefully frames, so copy it into the sample */
buffer.append(prevnals[i].p_payload, prevnals[i].i_payload);
}
}
/* Process the NALs for the previous frame */
for(unsigned int i=0; i < prevnals.size(); i++) {
Debug(9,"Processing NAL: Type %d Size %d",prevnals[i].i_type,prevnals[i].i_payload);
/* Calculate frame duration and offset */
int duration = x264picout.i_dts - prevDTS;
int offset = prevPTS - prevDTS;
switch(prevnals[i].i_type) {
case NAL_PPS:
/* PPS NAL */
MP4AddH264PictureParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4);
break;
case NAL_SPS:
/* SPS NAL */
MP4AddH264SequenceParameterSet(mp4h, mp4vtid, prevnals[i].p_payload+4, prevnals[i].i_payload-4);
break;
default:
/* Anything else, hopefully frames, so copy it into the sample */
buffer.append(prevnals[i].p_payload, prevnals[i].i_payload);
}
}
/* Write the sample */
if ( !buffer.empty() ) {
if ( !MP4WriteSample(
mp4h,
mp4vtid,
buffer.extract(buffer.size()),
buffer.size(),
duration,
offset,
prevKeyframe) ) {
Error("Failed writing sample");
}
}
/* Calculate frame duration and offset */
int duration = x264picout.i_dts - prevDTS;
int offset = prevPTS - prevDTS;
/* Cleanup */
prevnals.clear();
prevpayload.clear();
}
/* Write the sample */
if(!buffer.empty()) {
if(!MP4WriteSample(mp4h, mp4vtid, buffer.extract(buffer.size()), buffer.size(), duration, offset, prevKeyframe)) {
Error("Failed writing sample");
}
}
/* Got a frame. Copy this new frame into the previous frame */
if ( frame_size > 0 ) {
/* Copy the NALs and the payloads */
for ( unsigned int i = 0; i < i_nals; i++ ) {
prevnals.push_back(nals[i]);
prevpayload.append(nals[i].p_payload, nals[i].i_payload);
}
/* Cleanup */
prevnals.clear();
prevpayload.clear();
/* Update the payload pointers */
/* This is done in a separate loop because the previous loop might reallocate memory when appending,
making the pointers invalid */
unsigned int payload_offset = 0;
for ( unsigned int i = 0; i < prevnals.size(); i++ ) {
prevnals[i].p_payload = prevpayload.head() + payload_offset;
payload_offset += nals[i].i_payload;
}
}
/* We need this for the next frame */
prevPTS = x264picout.i_pts;
prevDTS = x264picout.i_dts;
prevKeyframe = x264picout.b_keyframe;
/* Got a frame. Copy this new frame into the previous frame */
if(frame_size > 0) {
/* Copy the NALs and the payloads */
for(int i=0;i<i_nals;i++) {
prevnals.push_back(nals[i]);
prevpayload.append(nals[i].p_payload, nals[i].i_payload);
}
bFirstFrame = false;
}
/* Update the payload pointers */
/* This is done in a separate loop because the previous loop might reallocate memory when appending,
making the pointers invalid */
unsigned int payload_offset = 0;
for(unsigned int i=0;i<prevnals.size();i++) {
prevnals[i].p_payload = prevpayload.head() + payload_offset;
payload_offset += nals[i].i_payload;
}
/* We need this for the next frame */
prevPTS = x264picout.i_pts;
prevDTS = x264picout.i_dts;
prevKeyframe = x264picout.b_keyframe;
bFirstFrame = false;
}
} else if(frame_size == 0) {
Debug(7,"x264 encode returned zero. Delayed frames: %d",x264_encoder_delayed_frames(x264enc));
} else {
Error("x264 encode failed: %d",frame_size);
}
} else if ( frame_size == 0 ) {
Debug(7, "x264 encode returned zero. Delayed frames: %d",
x264_encoder_delayed_frames(x264enc));
} else {
Error("x264 encode failed: %d", frame_size);
}
}
#endif // ZM_VIDEOWRITER_X264MP4
#endif // ZM_VIDEOWRITER_X264MP4
int ParseEncoderParameters(const char* str, std::vector<EncoderParameter_t>* vec) {
if(vec == NULL) {
Error("NULL Encoder parameters vector pointer");
return -1;
}
int ParseEncoderParameters(
const char* str,
std::vector<EncoderParameter_t>* vec
) {
if ( vec == NULL ) {
Error("NULL Encoder parameters vector pointer");
return -1;
}
if(str == NULL) {
Error("NULL Encoder parameters string");
return -2;
}
if ( str == NULL ) {
Error("NULL Encoder parameters string");
return -2;
}
vec->clear();
vec->clear();
if(str[0] == 0) {
/* Empty */
return 0;
}
if ( str[0] == 0 ) {
/* Empty */
return 0;
}
std::string line;
std::stringstream ss(str);
size_t valueoffset;
size_t valuelen;
unsigned int lineno = 0;
EncoderParameter_t param;
std::string line;
std::stringstream ss(str);
size_t valueoffset;
size_t valuelen;
unsigned int lineno = 0;
EncoderParameter_t param;
while(std::getline(ss, line) ) {
lineno++;
while ( std::getline(ss, line) ) {
lineno++;
/* Remove CR if exists */
if(line.length() >= 1 && line[line.length()-1] == '\r') {
line.erase(line.length()-1);
}
/* Remove CR if exists */
if ( line.length() >= 1 && line[line.length()-1] == '\r' ) {
line.erase(line.length() - 1);
}
/* Skip comments and empty lines */
if(line.empty() || line[0] == '#') {
continue;
}
/* Skip comments and empty lines */
if ( line.empty() || line[0] == '#' ) {
continue;
}
valueoffset = line.find('=');
if(valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0) {
Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno);
continue;
}
valueoffset = line.find('=');
if ( valueoffset == std::string::npos || valueoffset+1 >= line.length() || valueoffset == 0 ) {
Warning("Failed parsing encoder parameters line %d: Invalid pair", lineno);
continue;
}
if ( valueoffset > (sizeof(param.pname) - 1 ) ) {
Warning("Failed parsing encoder parameters line %d: Name too long", lineno);
continue;
}
if(valueoffset > (sizeof(param.pname)-1) ) {
Warning("Failed parsing encoder parameters line %d: Name too long", lineno);
continue;
}
valuelen = line.length() - (valueoffset+1);
valuelen = line.length() - (valueoffset+1);
if ( valuelen > (sizeof(param.pvalue) - 1 ) ) {
Warning("Failed parsing encoder parameters line %d: Value too long", lineno);
continue;
}
if( valuelen > (sizeof(param.pvalue)-1) ) {
Warning("Failed parsing encoder parameters line %d: Value too long", lineno);
continue;
}
/* Copy and NULL terminate */
line.copy(param.pname, valueoffset, 0);
line.copy(param.pvalue, valuelen, valueoffset+1);
param.pname[valueoffset] = 0;
param.pvalue[valuelen] = 0;
/* Copy and NULL terminate */
line.copy(param.pname, valueoffset, 0);
line.copy(param.pvalue, valuelen, valueoffset+1);
param.pname[valueoffset] = 0;
param.pvalue[valuelen] = 0;
/* Push to the vector */
vec->push_back(param);
/* Push to the vector */
vec->push_back(param);
Debug(7, "Parsed encoder parameter: %s = %s", param.pname, param.pvalue);
}
Debug(7, "Parsed encoder parameter: %s = %s", param.pname, param.pvalue);
}
Debug(7, "Parsed %d lines", lineno);
Debug(7, "Parsed %d lines", lineno);
return 0;
return 0;
}

View File

@ -37,7 +37,7 @@ if [ "${TRAVIS_EVENT_TYPE}" == "cron" ]; then
# Don't keep packages older than 5 days
find ./zmrepo/$targetfolder/ -maxdepth 1 -type f -mtime +5 -delete
rsync -vzh --ignore-errors build/* zmrepo/$targetfolder/
rsync -vzlh --ignore-errors build/* zmrepo/$targetfolder/
fusermount -zu zmrepo
else
echo

View File

@ -176,8 +176,8 @@ if ( !empty($action) ) {
} else {
Error("No new Id despite new name");
}
$refreshParent = '/index.php?view=filter&Id='.$_REQUEST['Id'];
}
$refreshParent = '/index.php?view=filter&Id='.$_REQUEST['Id'];
}
} // end if canedit events
} // end if action == filter

View File

@ -21,29 +21,26 @@
error_reporting( E_ALL );
$debug = false;
if ( $debug )
{
// Use these for debugging, though not both at once!
phpinfo( INFO_VARIABLES );
//error_reporting( E_ALL );
if ( $debug ) {
// Use these for debugging, though not both at once!
phpinfo( INFO_VARIABLES );
//error_reporting( E_ALL );
}
// Use new style autoglobals where possible
if ( version_compare( phpversion(), "4.1.0", "<") )
{
$_SESSION = &$HTTP_SESSION_VARS;
$_SERVER = &$HTTP_SERVER_VARS;
if ( version_compare( phpversion(), '4.1.0', '<') ) {
$_SESSION = &$HTTP_SESSION_VARS;
$_SERVER = &$HTTP_SERVER_VARS;
}
// Useful debugging lines for mobile devices
if ( false )
{
ob_start();
phpinfo( INFO_VARIABLES );
$fp = fopen( "/tmp/env.html", "w" );
fwrite( $fp, ob_get_contents() );
fclose( $fp );
ob_end_clean();
if ( false ) {
ob_start();
phpinfo( INFO_VARIABLES );
$fp = fopen( '/tmp/env.html', 'w' );
fwrite( $fp, ob_get_contents() );
fclose( $fp );
ob_end_clean();
}
require_once( 'includes/config.php' );
@ -53,26 +50,23 @@ require_once( 'includes/Storage.php' );
require_once( 'includes/Event.php' );
require_once( 'includes/Monitor.php' );
if ( isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == 'on' )
{
$protocol = 'https';
if ( isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ) {
$protocol = 'https';
} else {
$protocol = 'http';
}
else
{
$protocol = 'http';
}
define( "ZM_BASE_PROTOCOL", $protocol );
define( 'ZM_BASE_PROTOCOL', $protocol );
// Absolute URL's are unnecessary and break compatibility with reverse proxies
// define( "ZM_BASE_URL", $protocol.'://'.$_SERVER['HTTP_HOST'] );
// Use relative URL's instead
define( "ZM_BASE_URL", "" );
define( 'ZM_BASE_URL', '' );
// Check time zone is set
if (!ini_get('date.timezone') || !date_default_timezone_set(ini_get('date.timezone'))) {
date_default_timezone_set('UTC');
Fatal( "ZoneMinder is not installed properly: php's date.timezone is not set to a valid timezone" );
date_default_timezone_set('UTC');
Fatal( "ZoneMinder is not installed properly: php's date.timezone is not set to a valid timezone" );
}
if ( isset($_GET['skin']) )
@ -82,7 +76,7 @@ elseif ( isset($_COOKIE['zmSkin']) )
elseif ( defined('ZM_SKIN_DEFAULT') )
$skin = ZM_SKIN_DEFAULT;
else
$skin = "classic";
$skin = 'classic';
$skins = array_map( 'basename', glob('skins/*',GLOB_ONLYDIR) );
if ( ! in_array( $skin, $skins ) ) {
@ -97,7 +91,7 @@ elseif ( isset($_COOKIE['zmCSS']) )
elseif (defined('ZM_CSS_DEFAULT'))
$css = ZM_CSS_DEFAULT;
else
$css = "classic";
$css = 'classic';
$css_skins = array_map( 'basename', glob('skins/'.$skin.'/css/*',GLOB_ONLYDIR) );
if ( ! in_array( $css, $css_skins ) ) {
@ -105,9 +99,9 @@ if ( ! in_array( $css, $css_skins ) ) {
$css = $css_skins[0];
}
define( "ZM_BASE_PATH", dirname( $_SERVER['REQUEST_URI'] ) );
define( "ZM_SKIN_NAME", $skin );
define( "ZM_SKIN_PATH", "skins/$skin" );
define( 'ZM_BASE_PATH', dirname( $_SERVER['REQUEST_URI'] ) );
define( 'ZM_SKIN_NAME', $skin );
define( 'ZM_SKIN_PATH', "skins/$skin" );
$skinBase = array(); // To allow for inheritance of skins
if ( !file_exists( ZM_SKIN_PATH ) )
@ -117,26 +111,25 @@ $skinBase[] = $skin;
$currentCookieParams = session_get_cookie_params();
Logger::Debug('Setting cookie parameters to lifetime('.$currentCookieParams['lifetime'].') path('.$currentCookieParams['path'].') domain ('.$currentCookieParams['domain'].') secure('.$currentCookieParams['secure'].') httpOnly(1)');
session_set_cookie_params(
$currentCookieParams["lifetime"],
$currentCookieParams["path"],
$currentCookieParams["domain"],
$currentCookieParams["secure"],
$currentCookieParams['lifetime'],
$currentCookieParams['path'],
$currentCookieParams['domain'],
$currentCookieParams['secure'],
true
);
ini_set( "session.name", "ZMSESSID" );
ini_set( 'session.name', 'ZMSESSID' );
session_start();
if ( !isset($_SESSION['skin']) || isset($_REQUEST['skin']) || !isset($_COOKIE['zmSkin']) || $_COOKIE['zmSkin'] != $skin )
{
if ( !isset($_SESSION['skin']) || isset($_REQUEST['skin']) || !isset($_COOKIE['zmSkin']) || $_COOKIE['zmSkin'] != $skin ) {
$_SESSION['skin'] = $skin;
setcookie( "zmSkin", $skin, time()+3600*24*30*12*10 );
setcookie( 'zmSkin', $skin, time()+3600*24*30*12*10 );
}
if ( !isset($_SESSION['css']) || isset($_REQUEST['css']) || !isset($_COOKIE['zmCSS']) || $_COOKIE['zmCSS'] != $css ) {
$_SESSION['css'] = $css;
setcookie( "zmCSS", $css, time()+3600*24*30*12*10 );
setcookie( 'zmCSS', $css, time()+3600*24*30*12*10 );
}
if ( ZM_OPT_USE_AUTH )
@ -149,14 +142,12 @@ else
require_once( 'includes/lang.php' );
require_once( 'includes/functions.php' );
require_once( 'includes/csrf/csrf-magic.php' );
# Add Cross domain access headers
CORSHeaders();
// Check for valid content dirs
if ( !is_writable(ZM_DIR_EVENTS) || !is_writable(ZM_DIR_IMAGES) )
{
if ( !is_writable(ZM_DIR_EVENTS) || !is_writable(ZM_DIR_IMAGES) ) {
Error( "Cannot write to content dirs('".ZM_DIR_EVENTS."','".ZM_DIR_IMAGES."'). Check that these exist and are owned by the web account user");
}
@ -177,58 +168,51 @@ isset($view) || $view = NULL;
isset($request) || $request = NULL;
isset($action) || $action = NULL;
if ( ZM_ENABLE_CSRF_MAGIC && $action != 'login' ) {
Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\"");
csrf_check();
if ( ZM_ENABLE_CSRF_MAGIC && $action != 'login' && $view != 'view_video' ) {
require_once( 'includes/csrf/csrf-magic.php' );
Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\"");
csrf_check();
}
require_once( 'includes/actions.php' );
# If I put this here, it protects all views and popups, but it has to go after actions.php because actions.php does the actual logging in.
if ( ZM_OPT_USE_AUTH && ! isset($user) && $view != 'login' ) {
$view = 'login';
$view = 'login';
}
# Only one request can open the session file at a time, so let's close the session here to improve concurrency.
# Any file/page that uses the session must re-open it.
session_write_close();
if ( isset( $_REQUEST['request'] ) )
{
foreach ( getSkinIncludes( 'ajax/'.$request.'.php', true, true ) as $includeFile )
{
if ( !file_exists( $includeFile ) )
Fatal( "Request '$request' does not exist" );
if ( isset( $_REQUEST['request'] ) ) {
foreach ( getSkinIncludes( 'ajax/'.$request.'.php', true, true ) as $includeFile ) {
if ( !file_exists( $includeFile ) )
Fatal( "Request '$request' does not exist" );
require_once $includeFile;
}
return;
} else {
if ( $includeFiles = getSkinIncludes( 'views/'.$view.'.php', true, true ) ) {
foreach ( $includeFiles as $includeFile ) {
if ( !file_exists( $includeFile ) )
Fatal( "View '$view' does not exist" );
require_once $includeFile;
}
// If the view overrides $view to 'error', and the user is not logged in, then the
// issue is probably resolvable by logging in, so provide the opportunity to do so.
// The login view should handle redirecting to the correct location afterward.
if ( $view == 'error' && !isset($user) ) {
$view = 'login';
foreach ( getSkinIncludes( 'views/login.php', true, true ) as $includeFile )
require_once $includeFile;
}
return;
}
else
{
if ( $includeFiles = getSkinIncludes( 'views/'.$view.'.php', true, true ) )
{
foreach ( $includeFiles as $includeFile )
{
if ( !file_exists( $includeFile ) )
Fatal( "View '$view' does not exist" );
require_once $includeFile;
}
// If the view overrides $view to 'error', and the user is not logged in, then the
// issue is probably resolvable by logging in, so provide the opportunity to do so.
// The login view should handle redirecting to the correct location afterward.
if ( $view == 'error' && !isset($user) )
{
$view = 'login';
foreach ( getSkinIncludes( 'views/login.php', true, true ) as $includeFile )
require_once $includeFile;
}
}
// If the view is missing or the view still returned error with the user logged in,
// then it is not recoverable.
if ( !$includeFiles || $view == 'error' )
{
foreach ( getSkinIncludes( 'views/error.php', true, true ) as $includeFile )
require_once $includeFile;
}
}
// If the view is missing or the view still returned error with the user logged in,
// then it is not recoverable.
if ( !$includeFiles || $view == 'error' ) {
foreach ( getSkinIncludes( 'views/error.php', true, true ) as $includeFile )
require_once $includeFile;
}
}
?>