Merge branch 'master' of github.com:ZoneMinder/zoneminder
This commit is contained in:
commit
76b75cc69b
|
@ -28,8 +28,8 @@ SET @s = (SELECT IF(
|
|||
AND table_name = 'Monitors'
|
||||
AND column_name = 'TotalEvents'
|
||||
) > 0,
|
||||
"SELECT 'Column TotalEvents is already removed from Monitors'",
|
||||
"ALTER TABLE `Monitors` DROP `TotalEvents`"
|
||||
"ALTER TABLE `Monitors` DROP `TotalEvents`",
|
||||
"SELECT 'Column TotalEvents is already removed from Monitors'"
|
||||
));
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
@ -50,8 +50,8 @@ SET @s = (SELECT IF(
|
|||
AND table_name = 'Monitors'
|
||||
AND column_name = 'TotalEventDiskSpace'
|
||||
) > 0,
|
||||
"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'",
|
||||
"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`"
|
||||
"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`",
|
||||
"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'"
|
||||
));
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
|
|
@ -3,6 +3,50 @@ Debian
|
|||
|
||||
.. contents::
|
||||
|
||||
Easy Way: Debian 11 (Bullseye)
|
||||
------------------------
|
||||
|
||||
This procedure will guide you through the installation of ZoneMinder on Debian 11 (Bullseye).
|
||||
|
||||
**Step 1:** Setup Sudo (optional but recommended)
|
||||
|
||||
By default Debian does not come with sudo, so you have to install it and configure it manually.
|
||||
This step is optional but recommended and the following instructions assume that you have setup sudo.
|
||||
If you prefer to setup ZoneMinder as root, do it at your own risk and adapt the following instructions accordingly.
|
||||
|
||||
::
|
||||
|
||||
apt install sudo
|
||||
usermod -a -G sudo <username>
|
||||
exit
|
||||
|
||||
Now your terminal session is back under your normal user. You can check that
|
||||
you are now part of the sudo group with the command ``groups``, "sudo" should
|
||||
appear in the list. If not, run ``newgrp sudo`` and check again with ``groups``.
|
||||
|
||||
**Step 2:** Update system and install zoneminder
|
||||
|
||||
Run the following commands.
|
||||
|
||||
::
|
||||
|
||||
sudo apt update
|
||||
sudo apt upgrade
|
||||
sudo apt install mariadb-server
|
||||
sudo apt install zoneminder
|
||||
|
||||
When mariadb is installed for the first time, it doesn't add a password to the root user. Therefore, for security, it is recommended to run ``mysql secure installation``.
|
||||
|
||||
**Step 3:** Setup permissions for zm.conf
|
||||
|
||||
To make sure zoneminder can read the configuration file, run the following command.
|
||||
|
||||
::
|
||||
|
||||
sudo chgrp -c www-data /etc/zm/zm.conf
|
||||
|
||||
Congratulations! You should now be able to access zoneminder at ``http://yourhostname/zm``
|
||||
|
||||
Easy Way: Debian Buster
|
||||
------------------------
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ An Easy To Use Docker Image
|
|||
===========================
|
||||
If you are interested in trying out ZoneMinder quickly, user Dan Landon maintains an easy to use docker image for ZoneMinder. With a few simple configuration changes, it also provides complete Event Notification Server and Machine Learning hook support. Please follow instructions in his repostory. He maintains two repositories:
|
||||
|
||||
* If you want to run the latest stable release, please use his `zoneminder repository <https://github.com/dlandon/zoneminder>`__.
|
||||
* If you want to run the latest stable release, please use his `zoneminder machine learning repository <https://github.com/dlandon/zoneminder.machine.learning>`__.
|
||||
* If you want to run the latest zoneminder master, please use his `zoneminder master repository <https://github.com/dlandon/zoneminder.master-docker>`__.
|
||||
|
||||
In both cases, instructions are provided in the repo README files.
|
||||
|
|
|
@ -1064,7 +1064,7 @@ our @options = (
|
|||
},
|
||||
{
|
||||
name => 'ZM_FFMPEG_FORMATS',
|
||||
default => 'mpg mpeg wmv asf avi* mov swf 3gp**',
|
||||
default => 'mp4* mpg mpeg wmv asf avi mov swf 3gp**',
|
||||
description => 'Formats to allow for ffmpeg video generation',
|
||||
help => q`
|
||||
Ffmpeg can generate video in many different formats. This
|
||||
|
|
|
@ -29,6 +29,7 @@ use strict;
|
|||
use warnings;
|
||||
|
||||
require ZoneMinder::Base;
|
||||
require ZoneMinder::Object;
|
||||
require ZoneMinder::Monitor;
|
||||
|
||||
our $VERSION = $ZoneMinder::Base::VERSION;
|
||||
|
@ -42,24 +43,116 @@ our $VERSION = $ZoneMinder::Base::VERSION;
|
|||
use ZoneMinder::Logger qw(:all);
|
||||
use ZoneMinder::Database qw(:all);
|
||||
|
||||
use parent qw(ZoneMinder::Object);
|
||||
|
||||
use vars qw/ $table $primary_key %fields $serial %defaults $debug/;
|
||||
$table = 'Controls';
|
||||
$serial = $primary_key = 'Id';
|
||||
%fields = map { $_ => $_ } qw(
|
||||
Id
|
||||
Name
|
||||
Type
|
||||
Protocol
|
||||
CanWake
|
||||
CanSleep
|
||||
CanReset
|
||||
CanReboot
|
||||
CanZoom
|
||||
CanAutoZoom
|
||||
CanZoomAbs
|
||||
CanZoomRel
|
||||
CanZoomCon
|
||||
MinZoomRange
|
||||
MaxZoomRange
|
||||
MinZoomStep
|
||||
MaxZoomStep
|
||||
HasZoomSpeed
|
||||
MinZoomSpeed
|
||||
MaxZoomSpeed
|
||||
CanFocus
|
||||
CanAutoFocus
|
||||
CanFocusAbs
|
||||
CanFocusRel
|
||||
CanFocusCon
|
||||
MinFocusRange
|
||||
MaxFocusRange
|
||||
MinFocusStep
|
||||
MaxFocusStep
|
||||
HasFocusSpeed
|
||||
MinFocusSpeed
|
||||
MaxFocusSpeed
|
||||
CanIris
|
||||
CanAutoIris
|
||||
CanIrisAbs
|
||||
CanIrisRel
|
||||
CanIrisCon
|
||||
MinIrisRange
|
||||
MaxIrisRange
|
||||
MinIrisStep
|
||||
MaxIrisStep
|
||||
HasIrisSpeed
|
||||
MinIrisSpeed
|
||||
MaxIrisSpeed
|
||||
CanGain
|
||||
CanAutoGain
|
||||
CanGainAbs
|
||||
CanGainRel
|
||||
CanGainCon
|
||||
MinGainRange
|
||||
MaxGainRange
|
||||
MinGainStep
|
||||
MaxGainStep
|
||||
HasGainSpeed
|
||||
MinGainSpeed
|
||||
MaxGainSpeed
|
||||
CanWhite
|
||||
CanAutoWhite
|
||||
CanWhiteAbs
|
||||
CanWhiteRel
|
||||
CanWhiteCon
|
||||
MinWhiteRange
|
||||
MaxWhiteRange
|
||||
MinWhiteStep
|
||||
MaxWhiteStep
|
||||
HasWhiteSpeed
|
||||
MinWhiteSpeed
|
||||
MaxWhiteSpeed
|
||||
HasPresets
|
||||
NumPresets
|
||||
HasHomePreset
|
||||
CanSetPresets
|
||||
CanMove
|
||||
CanMoveDiag
|
||||
CanMoveMap
|
||||
CanMoveAbs
|
||||
CanMoveRel
|
||||
CanMoveCon
|
||||
CanPan
|
||||
MinPanRange
|
||||
MaxPanRange
|
||||
MinPanStep
|
||||
MaxPanStep
|
||||
HasPanSpeed
|
||||
MinPanSpeed
|
||||
MaxPanSpeed
|
||||
HasTurboPan
|
||||
TurboPanSpeed
|
||||
CanTilt
|
||||
MinTiltRange
|
||||
MaxTiltRange
|
||||
MinTiltStep
|
||||
MaxTiltStep
|
||||
HasTiltSpeed
|
||||
MinTiltSpeed
|
||||
MaxTiltSpeed
|
||||
HasTurboTilt
|
||||
TurboTiltSpeed
|
||||
CanAutoScan
|
||||
NumScanPaths
|
||||
);
|
||||
|
||||
our $AUTOLOAD;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $id = shift;
|
||||
if ( !defined($id) ) {
|
||||
Fatal('No monitor defined when invoking protocol '.$class);
|
||||
}
|
||||
my $self = {};
|
||||
$self->{name} = $class;
|
||||
$self->{id} = $id;
|
||||
bless($self, $class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub DESTROY {
|
||||
}
|
||||
|
||||
sub AUTOLOAD {
|
||||
my $self = shift;
|
||||
my $class = ref($self);
|
||||
|
@ -79,24 +172,24 @@ sub AUTOLOAD {
|
|||
|
||||
sub getKey {
|
||||
my $self = shift;
|
||||
return $self->{id};
|
||||
return $self->{Id};
|
||||
}
|
||||
|
||||
sub open {
|
||||
my $self = shift;
|
||||
Fatal('No open method defined for protocol '.$self->{name});
|
||||
Fatal('No open method defined for protocol '.$self->{Protocol});
|
||||
}
|
||||
|
||||
sub close {
|
||||
my $self = shift;
|
||||
$self->{state} = 'closed';
|
||||
Debug('No close method defined for protocol '.$self->{name});
|
||||
Debug('No close method defined for protocol '.$self->{Protocol});
|
||||
}
|
||||
|
||||
sub loadMonitor {
|
||||
my $self = shift;
|
||||
if ( !$self->{Monitor} ) {
|
||||
if ( !($self->{Monitor} = ZoneMinder::Monitor->find_one(Id=>$self->{id})) ) {
|
||||
if ( !($self->{Monitor} = ZoneMinder::Monitor->find_one(Id=>$self->{MonitorId})) ) {
|
||||
Fatal('Monitor id '.$self->{id}.' not found');
|
||||
}
|
||||
if ( defined($self->{Monitor}->{AutoStopTimeout}) ) {
|
||||
|
|
|
@ -41,122 +41,135 @@ our @ISA = qw(ZoneMinder::Control);
|
|||
|
||||
use ZoneMinder::Logger qw(:all);
|
||||
use ZoneMinder::Config qw(:all);
|
||||
use ZoneMinder::General qw(:all);
|
||||
|
||||
use Time::HiRes qw( usleep );
|
||||
use URI::Encode qw(uri_encode);
|
||||
|
||||
sub open
|
||||
{
|
||||
our $REALM = '';
|
||||
our $PROTOCOL = 'http://';
|
||||
our $USERNAME = 'admin';
|
||||
our $PASSWORD = '';
|
||||
our $ADDRESS = '';
|
||||
our $BASE_URL = '';
|
||||
|
||||
sub open {
|
||||
my $self = shift;
|
||||
|
||||
$self->loadMonitor();
|
||||
Debug( "Camera open" );
|
||||
|
||||
if (($self->{Monitor}->{ControlAddress} =~ /^(?<PROTOCOL>https?:\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>.*)$/)) {
|
||||
$PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL};
|
||||
$USERNAME = $+{USERNAME} if $+{USERNAME};
|
||||
$PASSWORD = $+{PASSWORD} if $+{PASSWORD};
|
||||
$ADDRESS = $+{ADDRESS} if $+{ADDRESS};
|
||||
} else {
|
||||
Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress});
|
||||
$ADDRESS = $self->{Monitor}->{ControlAddress};
|
||||
}
|
||||
if ( !($ADDRESS =~ /:/) ) {
|
||||
Error('You generally need to also specify the port. I will append :80');
|
||||
$ADDRESS .= ':80';
|
||||
}
|
||||
$BASE_URL = $PROTOCOL.($USERNAME?$USERNAME.':'.$PASSWORD.'@':'').$ADDRESS;
|
||||
|
||||
use LWP::UserAgent;
|
||||
$self->{ua} = LWP::UserAgent->new;
|
||||
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION );
|
||||
|
||||
$self->{ua}->agent( 'ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION );
|
||||
$self->{state} = 'open';
|
||||
}
|
||||
|
||||
sub close
|
||||
{
|
||||
sub close {
|
||||
my $self = shift;
|
||||
$self->{state} = 'closed';
|
||||
}
|
||||
|
||||
sub printMsg
|
||||
{
|
||||
my $msg = shift;
|
||||
my $msg_len = length($msg);
|
||||
|
||||
Debug( $msg."[".$msg_len."]" );
|
||||
}
|
||||
|
||||
sub sendCmd
|
||||
{
|
||||
sub sendCmd {
|
||||
my ($self, $cmd, $speedcmd) = @_;
|
||||
|
||||
my $result = undef;
|
||||
$self->printMsg( $speedcmd, 'Tx' );
|
||||
$self->printMsg( $cmd, 'Tx' );
|
||||
|
||||
printMsg( $speedcmd, "Tx" );
|
||||
printMsg( $cmd, "Tx" );
|
||||
|
||||
my $req = HTTP::Request->new( GET => "http://" . $self->{Monitor}->{ControlAddress} . "/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd" );
|
||||
my $req = HTTP::Request->new( GET => $BASE_URL."/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd");
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ( $res->is_success )
|
||||
{
|
||||
$result = !undef;
|
||||
if (!$res->is_success) {
|
||||
Error('Request failed: '.$res->status_line().' (URI: '.$req->as_string().')');
|
||||
}
|
||||
else
|
||||
{
|
||||
Error( "Request failed: '" . $res->status_line() . "' (URI: '" . $req->as_string() . "')" );
|
||||
}
|
||||
|
||||
return( $result );
|
||||
return $res->is_success;
|
||||
}
|
||||
|
||||
sub moveConUp
|
||||
{
|
||||
sub moveConUp {
|
||||
my ($self, $params) = @_;
|
||||
my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6);
|
||||
Debug( "Move Up" );
|
||||
$self->sendCmd( 'move=up', $speed );
|
||||
}
|
||||
|
||||
sub moveConDown
|
||||
{
|
||||
sub moveConDown {
|
||||
my ($self, $params) = @_;
|
||||
my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6);
|
||||
Debug( "Move Down" );
|
||||
$self->sendCmd( 'move=down', $speed );
|
||||
}
|
||||
|
||||
sub moveConLeft
|
||||
{
|
||||
sub moveConLeft {
|
||||
my ($self, $params) = @_;
|
||||
my $speed = 'speedpan=-' . $params->{panspeed};
|
||||
Debug( "Move Left" );
|
||||
$self->sendCmd( 'move=left', $speed );
|
||||
}
|
||||
|
||||
sub moveConRight
|
||||
{
|
||||
sub moveConRight {
|
||||
my ($self, $params) = @_;
|
||||
my $speed = 'speedpan=' . ($params->{panspeed} - 6);
|
||||
Debug( "Move Right" );
|
||||
$self->sendCmd( 'move=right', $speed );
|
||||
}
|
||||
|
||||
sub moveStop
|
||||
{
|
||||
sub moveStop {
|
||||
my $self = shift;
|
||||
Debug( "Move Stop" );
|
||||
Debug( "Move Stop: not implemented" );
|
||||
# not implemented
|
||||
}
|
||||
|
||||
sub zoomConTele
|
||||
{
|
||||
sub zoomConTele {
|
||||
my ($self, $params) = @_;
|
||||
my $speed = 'speedzoom=' . ($params->{speed} - 6);
|
||||
Debug( "Zoom In" );
|
||||
$self->sendCmd( 'zoom=tele', $speed );
|
||||
}
|
||||
|
||||
sub zoomConWide
|
||||
{
|
||||
sub zoomConWide {
|
||||
my ($self, $params) = @_;
|
||||
my $speed = 'speedzoom=' . ($params->{speed} - 6);
|
||||
Debug( "Zoom Out" );
|
||||
$self->sendCmd( 'zoom=wide', $speed );
|
||||
}
|
||||
|
||||
sub reset
|
||||
{
|
||||
sub reset {
|
||||
my $self = shift;
|
||||
Debug( "Camera Reset" );
|
||||
$self->sendCmd( 'move=home' );
|
||||
}
|
||||
|
||||
sub get_config {
|
||||
my $self = shift;
|
||||
|
||||
my $url = $BASE_URL.'/cgi-bin/admin/lsctrl.cgi?cmd=queryStatus&retType=javascript';
|
||||
my $req = new HTTP::Request(GET => $url);
|
||||
my $response = $self->{ua}->request($req);
|
||||
if ( $response->is_success() ) {
|
||||
my $resp = $response->decoded_content;
|
||||
return ZoneMinder::General::parseNameEqualsValueToHash($resp);
|
||||
}
|
||||
Warn("Failed to get config from $url: " . $response->status_line());
|
||||
return;
|
||||
} # end sub get_config
|
||||
|
||||
sub set_config {
|
||||
my $self = shift;
|
||||
my $diff = shift;
|
||||
|
||||
my $url = $BASE_URL.'/cgi-bin/'.$USERNAME.'/setparam.cgi?'.
|
||||
join('&', map { $_.'='.uri_encode($$diff{$_}) } keys %$diff);
|
||||
my $response = $self->{ua}->get($url);
|
||||
Debug($response->content);
|
||||
return $response->is_success();
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
|
|
|
@ -584,6 +584,7 @@ sub DiskSpace {
|
|||
return $_[0]{DiskSpace};
|
||||
}
|
||||
|
||||
# Icon: I removed the locking from this. So we now have an assumption that the Event object is up to date.
|
||||
sub CopyTo {
|
||||
my ( $self, $NewStorage ) = @_;
|
||||
|
||||
|
@ -614,16 +615,12 @@ sub CopyTo {
|
|||
Debug("$NewPath is good");
|
||||
}
|
||||
|
||||
$ZoneMinder::Database::dbh->begin_work();
|
||||
$self->lock_and_load();
|
||||
# data is reloaded, so need to check that the move hasn't already happened.
|
||||
if ( $$self{StorageId} == $$NewStorage{Id} ) {
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
return 'Event has already been moved by someone else.';
|
||||
}
|
||||
|
||||
if ( $$OldStorage{Id} != $$self{StorageId} ) {
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
return 'Old Storage path changed, Event has moved somewhere else.';
|
||||
}
|
||||
|
||||
|
@ -661,40 +658,22 @@ sub CopyTo {
|
|||
}
|
||||
|
||||
my $event_path = $subpath.$self->RelativePath();
|
||||
if ( 0 ) { # Not neccessary
|
||||
Debug("Making directory $event_path/");
|
||||
if ( !$bucket->add_key($event_path.'/', '') ) {
|
||||
Warning("Unable to add key for $event_path/ :". $s3->err . ': '. $s3->errstr());
|
||||
}
|
||||
}
|
||||
|
||||
my @files = glob("$OldPath/*");
|
||||
Debug("Files to move @files");
|
||||
foreach my $file ( @files ) {
|
||||
foreach my $file (@files) {
|
||||
next if $file =~ /^\./;
|
||||
( $file ) = ( $file =~ /^(.*)$/ ); # De-taint
|
||||
($file) = ($file =~ /^(.*)$/); # De-taint
|
||||
my $starttime = [gettimeofday];
|
||||
Debug("Moving file $file to $NewPath");
|
||||
my $size = -s $file;
|
||||
if ( ! $size ) {
|
||||
if (!$size) {
|
||||
Info('Not moving file with 0 size');
|
||||
}
|
||||
if ( 0 ) {
|
||||
my $file_contents = File::Slurp::read_file($file);
|
||||
if ( ! $file_contents ) {
|
||||
die 'Loaded empty file, but it had a size. Giving up';
|
||||
}
|
||||
|
||||
my $filename = $event_path.'/'.File::Basename::basename($file);
|
||||
if ( ! $bucket->add_key($filename, $file_contents) ) {
|
||||
die "Unable to add key for $filename : ".$s3->err . ': ' . $s3->errstr;
|
||||
}
|
||||
} else {
|
||||
my $filename = $event_path.'/'.File::Basename::basename($file);
|
||||
if ( ! $bucket->add_key_filename($filename, $file) ) {
|
||||
if (!$bucket->add_key_filename($filename, $file)) {
|
||||
die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr;
|
||||
}
|
||||
}
|
||||
|
||||
my $duration = tv_interval($starttime);
|
||||
Debug('PUT to S3 ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec');
|
||||
|
@ -704,16 +683,15 @@ sub CopyTo {
|
|||
};
|
||||
Error($@) if $@;
|
||||
} else {
|
||||
Error("Unable to parse S3 Url into it's component parts.");
|
||||
Error('Unable to parse S3 Url into it\'s component parts.');
|
||||
}
|
||||
#die $@ if $@;
|
||||
} # end if Url
|
||||
} # end if s3
|
||||
|
||||
my $error = '';
|
||||
if ( !$moved ) {
|
||||
if (!$moved) {
|
||||
File::Path::make_path($NewPath, {error => \my $err});
|
||||
if ( @$err ) {
|
||||
if (@$err) {
|
||||
for my $diag (@$err) {
|
||||
my ($file, $message) = %$diag;
|
||||
next if $message eq 'File exists';
|
||||
|
@ -724,23 +702,16 @@ sub CopyTo {
|
|||
}
|
||||
}
|
||||
}
|
||||
if ( $error ) {
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
return $error;
|
||||
}
|
||||
return $error if $error;
|
||||
my @files = glob("$OldPath/*");
|
||||
if ( ! @files ) {
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
return 'No files to move.';
|
||||
}
|
||||
return 'No files to move.' if !@files;
|
||||
|
||||
for my $file (@files) {
|
||||
next if $file =~ /^\./;
|
||||
( $file ) = ( $file =~ /^(.*)$/ ); # De-taint
|
||||
($file) = ($file =~ /^(.*)$/); # De-taint
|
||||
my $starttime = [gettimeofday];
|
||||
Debug("Moving file $file to $NewPath");
|
||||
my $size = -s $file;
|
||||
if ( ! File::Copy::copy( $file, $NewPath ) ) {
|
||||
if (!File::Copy::copy($file, $NewPath)) {
|
||||
$error .= "Copy failed: for $file to $NewPath: $!";
|
||||
last;
|
||||
}
|
||||
|
@ -749,20 +720,21 @@ sub CopyTo {
|
|||
} # end foreach file.
|
||||
} # end if ! moved
|
||||
|
||||
if ( $error ) {
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
return $error;
|
||||
}
|
||||
return $error if $error;
|
||||
} # end sub CopyTo
|
||||
|
||||
sub MoveTo {
|
||||
my ( $self, $NewStorage ) = @_;
|
||||
my ($self, $NewStorage) = @_;
|
||||
|
||||
if ( !$self->canEdit() ) {
|
||||
if (!$self->canEdit()) {
|
||||
Warning('No permission to move event.');
|
||||
return 'No permission to move event.';
|
||||
}
|
||||
|
||||
my $was_in_transaction = !$ZoneMinder::Database::dbh->{AutoCommit};
|
||||
$ZoneMinder::Database::dbh->begin_work() if !$was_in_transaction;
|
||||
$self->lock_and_load(); # The fact that we are in a transaction might not imply locking
|
||||
|
||||
my $OldStorage = $self->Storage(undef);
|
||||
|
||||
my $error = $self->CopyTo($NewStorage);
|
||||
|
@ -772,11 +744,11 @@ sub MoveTo {
|
|||
$$self{StorageId} = $$NewStorage{Id};
|
||||
$self->Storage($NewStorage);
|
||||
$error .= $self->save();
|
||||
if ( $error ) {
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
if ($error) {
|
||||
$ZoneMinder::Database::dbh->commit() if !$was_in_transaction;
|
||||
return $error;
|
||||
}
|
||||
$ZoneMinder::Database::dbh->commit();
|
||||
$ZoneMinder::Database::dbh->commit() if !$was_in_transaction;
|
||||
$self->delete_files($OldStorage);
|
||||
return $error;
|
||||
} # end sub MoveTo
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
# ==========================================================================
|
||||
#
|
||||
# ZoneMinder Event_Summary Module
|
||||
# Copyright (C) 2020 ZoneMinder
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
# ==========================================================================
|
||||
#
|
||||
# This module contains the common definitions and functions used by the rest
|
||||
# of the ZoneMinder scripts
|
||||
#
|
||||
package ZoneMinder::Event_Summary;
|
||||
|
||||
use 5.006;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
require ZoneMinder::Base;
|
||||
require ZoneMinder::Object;
|
||||
|
||||
#our @ISA = qw(Exporter ZoneMinder::Base);
|
||||
use parent qw(ZoneMinder::Object);
|
||||
|
||||
use vars qw/ $table $primary_key %fields $serial %defaults $debug/;
|
||||
$table = 'Event_Summaries';
|
||||
$serial = $primary_key = 'MonitorId';
|
||||
%fields = map { $_ => $_ } qw(
|
||||
MonitorId
|
||||
TotalEvents
|
||||
TotalEventDiskSpace
|
||||
HourEvents
|
||||
HourEventDiskSpace
|
||||
DayEvents
|
||||
DayEventDiskSpace
|
||||
WeekEvents
|
||||
WeekEventDiskSpace
|
||||
MonthEvents
|
||||
MonthEventDiskSpace
|
||||
ArchivedEvents
|
||||
ArchivedEventDiskSpace
|
||||
);
|
||||
|
||||
%defaults = (
|
||||
TotalEvents => undef,
|
||||
TotalEventDiskSpace => undef,
|
||||
HourEvents => undef,
|
||||
HourEventDiskSpace => undef,
|
||||
DayEvents => undef,
|
||||
DayEventDiskSpace => undef,
|
||||
WeekEvents => undef,
|
||||
WeekEventDiskSpace => undef,
|
||||
MonthEvents => undef,
|
||||
MonthEventDiskSpace => undef,
|
||||
ArchivedEvents => undef,
|
||||
ArchivedEventDiskSpace => undef,
|
||||
);
|
||||
|
||||
sub Monitor {
|
||||
return new ZoneMinder::Monitor( $_[0]{MonitorId} );
|
||||
} # end sub Monitor
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
ZoneMinder::Event_Summary - Perl Class for Event Summaries
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use ZoneMinder::Event_Summary;
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Isaac Connor, E<lt>isaac@zoneminder.comE<gt>
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2001-2017 ZoneMinder LLC
|
||||
|
||||
This library is free software; you can redistribute it and/or modify
|
||||
it under the same terms as Perl itself, either Perl version 5.8.3 or,
|
||||
at your option, any later version of Perl 5 you may have available.
|
||||
|
||||
|
||||
=cut
|
|
@ -31,6 +31,8 @@ our %EXPORT_TAGS = (
|
|||
systemStatus
|
||||
packageControl
|
||||
daemonControl
|
||||
parseNameEqualsValueToHash
|
||||
hash_diff
|
||||
) ]
|
||||
);
|
||||
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
|
||||
|
@ -534,6 +536,42 @@ sub jsonDecode {
|
|||
return $result;
|
||||
}
|
||||
|
||||
sub parseNameEqualsValueToHash {
|
||||
my %settings;
|
||||
foreach my $line ( split ( /\r?\n/, $_[0] ) ) {
|
||||
next if ! $line;
|
||||
next if ! ( $line =~ /=/ );
|
||||
my ($name, $value ) = split('=', $line);
|
||||
$value =~ s/^'//;
|
||||
$value =~ s/'$//;
|
||||
$settings{$name} = defined $value ? $value : '';
|
||||
}
|
||||
return %settings;
|
||||
}
|
||||
|
||||
sub hash_diff {
|
||||
# assumes keys of second hash are all in the first hash
|
||||
my ( $settings, $defaults ) = @_;
|
||||
my %updates;
|
||||
|
||||
foreach my $setting ( keys %{$settings} ) {
|
||||
next if ! exists $$defaults{$setting};
|
||||
if (
|
||||
($$settings{$setting} and ! $$defaults{$setting})
|
||||
or
|
||||
(!$$settings{$setting} and $$defaults{$setting})
|
||||
or
|
||||
(
|
||||
($$settings{$setting} and $$defaults{$setting} and (
|
||||
$$settings{$setting} ne $$defaults{$setting}))
|
||||
)
|
||||
) {
|
||||
$updates{$setting} = $$defaults{$setting};
|
||||
}
|
||||
} # end foreach setting
|
||||
return %updates;
|
||||
}
|
||||
|
||||
sub packageControl {
|
||||
my $command = shift;
|
||||
my $string = $Config{ZM_PATH_BIN}.'/zmpkg.pl '.$command;
|
||||
|
@ -598,6 +636,8 @@ of the ZoneMinder scripts
|
|||
packageControl
|
||||
daemonControl
|
||||
systemStatus
|
||||
parseNameEqualsValueToHash
|
||||
hash_diff
|
||||
) ]
|
||||
|
||||
|
||||
|
|
|
@ -35,7 +35,9 @@ require ZoneMinder::Storage;
|
|||
require ZoneMinder::Server;
|
||||
require ZoneMinder::Memory;
|
||||
require ZoneMinder::Monitor_Status;
|
||||
require ZoneMinder::Event_Summary;
|
||||
require ZoneMinder::Zone;
|
||||
use ZoneMinder::Logger qw(:all);
|
||||
|
||||
#our @ISA = qw(Exporter ZoneMinder::Base);
|
||||
use parent qw(ZoneMinder::Object);
|
||||
|
@ -266,6 +268,15 @@ sub Status {
|
|||
return $$self{Status};
|
||||
}
|
||||
|
||||
sub Event_Summary {
|
||||
my $self = shift;
|
||||
$$self{Event_Summary} = shift if @_;
|
||||
if ( ! $$self{Event_Summary} ) {
|
||||
$$self{Event_Summary} = ZoneMinder::Event_Summary->find_one(MonitorId=>$$self{Id});
|
||||
}
|
||||
return $$self{Event_Summary};
|
||||
}
|
||||
|
||||
sub connect {
|
||||
my $self = shift;
|
||||
return ZoneMinder::Memory::zmMemVerify($self);
|
||||
|
@ -313,6 +324,25 @@ sub resumeMotionDetection {
|
|||
return 1;
|
||||
}
|
||||
|
||||
sub Control {
|
||||
my $self = shift;
|
||||
if ( ! exists $$self{Control}) {
|
||||
require ZoneMinder::Control;
|
||||
my $Control = ZoneMinder::Control->find_one(Id=>$$self{ControlId});
|
||||
if ($Control) {
|
||||
require Module::Load::Conditional;
|
||||
if (!Module::Load::Conditional::can_load(modules => {'ZoneMinder::Control::'.$$Control{Protocol} => undef})) {
|
||||
Error("Can't load ZoneMinder::Control::$$Control{Protocol}\n$Module::Load::Conditional::ERROR");
|
||||
return undef;
|
||||
}
|
||||
bless $Control, 'ZoneMinder::Control::'.$$Control{Protocol};
|
||||
$$Control{MonitorId} = $$self{Id};
|
||||
$$self{Control} = $Control;
|
||||
}
|
||||
}
|
||||
return $$self{Control};
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
|
|
|
@ -43,18 +43,6 @@ $serial = $primary_key = 'MonitorId';
|
|||
CaptureFPS
|
||||
AnalysisFPS
|
||||
CaptureBandwidth
|
||||
TotalEvents
|
||||
TotalEventDiskSpace
|
||||
HourEvents
|
||||
HourEventDiskSpace
|
||||
DayEvents
|
||||
DayEventDiskSpace
|
||||
WeekEvents
|
||||
WeekEventDiskSpace
|
||||
MonthEvents
|
||||
MonthEventDiskSpace
|
||||
ArchivedEvents
|
||||
ArchivedEventDiskSpace
|
||||
);
|
||||
|
||||
%defaults = (
|
||||
|
@ -62,18 +50,6 @@ $serial = $primary_key = 'MonitorId';
|
|||
CaptureFPS => undef,
|
||||
AnalysisFPS => undef,
|
||||
CaptureBandwidth => undef,
|
||||
TotalEvents => undef,
|
||||
TotalEventDiskSpace => undef,
|
||||
HourEvents => undef,
|
||||
HourEventDiskSpace => undef,
|
||||
DayEvents => undef,
|
||||
DayEventDiskSpace => undef,
|
||||
WeekEvents => undef,
|
||||
WeekEventDiskSpace => undef,
|
||||
MonthEvents => undef,
|
||||
MonthEventDiskSpace => undef,
|
||||
ArchivedEvents => undef,
|
||||
ArchivedEventDiskSpace => undef,
|
||||
);
|
||||
|
||||
sub Monitor {
|
||||
|
|
|
@ -218,7 +218,7 @@ sub save {
|
|||
my $serial = eval '$'.$type.'::serial';
|
||||
my @identified_by = eval '@'.$type.'::identified_by';
|
||||
|
||||
my $ac = ZoneMinder::Database::start_transaction( $local_dbh );
|
||||
my $ac = ZoneMinder::Database::start_transaction( $local_dbh ) if $local_dbh->{AutoCommit};
|
||||
if ( ! $serial ) {
|
||||
my $insert = $force_insert;
|
||||
my %serial = eval '%'.$type.'::serial';
|
||||
|
@ -234,8 +234,8 @@ $log->debug("No serial") if $debug;
|
|||
if ( ! ( ( $_ = $local_dbh->prepare("DELETE FROM `$table` WHERE $where") ) and $_->execute( @$self{@identified_by} ) ) ) {
|
||||
$where =~ s/\?/\%s/g;
|
||||
$log->error("Error deleting: DELETE FROM $table WHERE " . sprintf($where, map { defined $_ ? $_ : 'undef' } ( @$self{@identified_by}) ).'):' . $local_dbh->errstr);
|
||||
$local_dbh->rollback();
|
||||
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
|
||||
$local_dbh->rollback() if $ac;
|
||||
ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
|
||||
return $local_dbh->errstr;
|
||||
} elsif ( $debug ) {
|
||||
$log->debug("SQL succesful DELETE FROM $table WHERE $where");
|
||||
|
@ -267,8 +267,8 @@ $log->debug("No serial") if $debug;
|
|||
my $error = $local_dbh->errstr;
|
||||
$command =~ s/\?/\%s/g;
|
||||
$log->error('SQL statement execution failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $local_dbh->errstr);
|
||||
$local_dbh->rollback();
|
||||
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
|
||||
$local_dbh->rollback() if $ac;
|
||||
ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
|
||||
return $error;
|
||||
} # end if
|
||||
if ( $debug or DEBUG_ALL ) {
|
||||
|
@ -282,8 +282,8 @@ $log->debug("No serial") if $debug;
|
|||
my $error = $local_dbh->errstr;
|
||||
$command =~ s/\?/\%s/g;
|
||||
$log->error('SQL failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys, @$fields{@identified_by}}) ).'):' . $local_dbh->errstr);
|
||||
$local_dbh->rollback();
|
||||
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
|
||||
$local_dbh->rollback() if $ac;
|
||||
ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
|
||||
return $error;
|
||||
} # end if
|
||||
if ( $debug or DEBUG_ALL ) {
|
||||
|
@ -321,8 +321,8 @@ $log->debug("No serial") if $debug;
|
|||
$command =~ s/\?/\%s/g;
|
||||
my $error = $local_dbh->errstr;
|
||||
$log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $error);
|
||||
$local_dbh->rollback();
|
||||
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
|
||||
$local_dbh->rollback() if $ac;
|
||||
ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
|
||||
return $error;
|
||||
} # end if
|
||||
if ( $debug or DEBUG_ALL ) {
|
||||
|
@ -340,8 +340,8 @@ $log->debug("No serial") if $debug;
|
|||
my $error = $local_dbh->errstr;
|
||||
$command =~ s/\?/\%s/g;
|
||||
$log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}, @sql{@$fields{@identified_by}} ) ).'):' . $error) if $log;
|
||||
$local_dbh->rollback();
|
||||
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
|
||||
$local_dbh->rollback() if $ac;
|
||||
ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
|
||||
return $error;
|
||||
} # end if
|
||||
if ( $debug or DEBUG_ALL ) {
|
||||
|
@ -350,7 +350,7 @@ $log->debug("No serial") if $debug;
|
|||
} # end if
|
||||
} # end if
|
||||
} # end if
|
||||
ZoneMinder::Database::end_transaction( $local_dbh, $ac );
|
||||
ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
|
||||
#$self->load();
|
||||
#if ( $$fields{id} ) {
|
||||
#if ( ! $ZoneMinder::Object::cache{$type}{$$self{id}} ) {
|
||||
|
|
|
@ -30,7 +30,6 @@ use autouse 'Pod::Usage'=>qw(pod2usage);
|
|||
use POSIX qw/strftime EPIPE EINTR/;
|
||||
use Socket;
|
||||
use Data::Dumper;
|
||||
use Module::Load::Conditional qw{can_load};
|
||||
|
||||
use constant MAX_CONNECT_DELAY => 15;
|
||||
use constant MAX_COMMAND_WAIT => 1800;
|
||||
|
@ -102,40 +101,21 @@ if ($options{command}) {
|
|||
}
|
||||
} else {
|
||||
# The server isn't there
|
||||
my $monitor = zmDbGetMonitorAndControl($id);
|
||||
require ZoneMinder::Monitor;
|
||||
|
||||
my $monitor = ZoneMinder::Monitor->find_one(Id=>$id);
|
||||
Fatal("Unable to load control data for monitor $id") if !$monitor;
|
||||
|
||||
my $protocol = $monitor->{Protocol};
|
||||
my $control = $monitor->Control();
|
||||
|
||||
my $protocol = $control->{Protocol};
|
||||
if (!$protocol) {
|
||||
Fatal('No protocol is set in monitor. Please edit the monitor, edit control type, select the control capability and fill in the Protocol field');
|
||||
}
|
||||
|
||||
if (-x $protocol) {
|
||||
# Protocol is actually a script!
|
||||
# Holdover from previous versions
|
||||
my $command .= $protocol.' '.$arg_string;
|
||||
Debug($command);
|
||||
|
||||
my $output = qx($command);
|
||||
my $status = $? >> 8;
|
||||
if ($status || logDebugging()) {
|
||||
chomp($output);
|
||||
Debug("Output: $output");
|
||||
}
|
||||
if ($status) {
|
||||
Error("Command '$command' exited with status: $status");
|
||||
exit($status);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
Info("Starting control server $id/$protocol");
|
||||
close(CLIENT);
|
||||
|
||||
if (!can_load(modules => {'ZoneMinder::Control::'.$protocol => undef})) {
|
||||
Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR");
|
||||
}
|
||||
|
||||
my $zm_terminate = 0;
|
||||
sub TermHandler {
|
||||
Info('Received TERM, exiting');
|
||||
|
@ -150,7 +130,6 @@ if ($options{command}) {
|
|||
|
||||
$0 = $0.' --id '.$id;
|
||||
|
||||
my $control = ('ZoneMinder::Control::'.$protocol)->new($id);
|
||||
my $control_key = $control->getKey();
|
||||
$control->loadMonitor();
|
||||
|
||||
|
|
|
@ -665,10 +665,10 @@ sub substituteTags {
|
|||
# We have a filter and an event, do we need any more
|
||||
# monitor information?
|
||||
my $need_monitor = $text =~ /%(?:MN|MET|MEH|MED|MEW|MEN|MEA)%/;
|
||||
my $need_status = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/;
|
||||
my $need_summary = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/;
|
||||
|
||||
my $Monitor = $Event->Monitor() if $need_monitor;
|
||||
my $Status = $Monitor->Status() if $need_status;
|
||||
my $Summary = $Monitor->Event_Summary() if $need_summary;
|
||||
|
||||
# Do we need the image information too?
|
||||
my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA|EIMOD|EIMODG)%/;
|
||||
|
@ -692,19 +692,19 @@ sub substituteTags {
|
|||
}
|
||||
$rows ++;
|
||||
}
|
||||
Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alaarm_frame: $max_alarm_frame, score: $max_alarm_score");
|
||||
Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alarm_frame: $max_alarm_frame, score: $max_alarm_score");
|
||||
$sth->finish();
|
||||
}
|
||||
|
||||
my $url = $Config{ZM_URL};
|
||||
$text =~ s/%ZP%/$url/g;
|
||||
$text =~ s/%MN%/$Monitor->{Name}/g;
|
||||
$text =~ s/%MET%/$Status->{TotalEvents}/g;
|
||||
$text =~ s/%MEH%/$Status->{HourEvents}/g;
|
||||
$text =~ s/%MED%/$Status->{DayEvents}/g;
|
||||
$text =~ s/%MEW%/$Status->{WeekEvents}/g;
|
||||
$text =~ s/%MEM%/$Status->{MonthEvents}/g;
|
||||
$text =~ s/%MEA%/$Status->{ArchivedEvents}/g;
|
||||
$text =~ s/%MET%/$Summary->{TotalEvents}/g;
|
||||
$text =~ s/%MEH%/$Summary->{HourEvents}/g;
|
||||
$text =~ s/%MED%/$Summary->{DayEvents}/g;
|
||||
$text =~ s/%MEW%/$Summary->{WeekEvents}/g;
|
||||
$text =~ s/%MEM%/$Summary->{MonthEvents}/g;
|
||||
$text =~ s/%MEA%/$Summary->{ArchivedEvents}/g;
|
||||
$text =~ s/%MP%/$url?view=watch&mid=$Event->{MonitorId}/g;
|
||||
$text =~ s/%MPS%/$url?view=watch&mid=$Event->{MonitorId}&mode=stream/g;
|
||||
$text =~ s/%MPI%/$url?view=watch&mid=$Event->{MonitorId}&mode=still/g;
|
||||
|
|
|
@ -251,6 +251,13 @@ void zmDbQueue::process() {
|
|||
mCondition.wait(lock);
|
||||
}
|
||||
while (!mQueue.empty()) {
|
||||
if (mQueue.size() > 10) {
|
||||
Logger *log = Logger::fetch();
|
||||
Logger::Level db_level = log->databaseLevel();
|
||||
log->databaseLevel(Logger::NOLOG);
|
||||
Warning("db queue size has grown larger than 10 entries");
|
||||
log->databaseLevel(db_level);
|
||||
}
|
||||
std::string sql = mQueue.front();
|
||||
mQueue.pop();
|
||||
// My idea for leaving the locking around each sql statement is to allow
|
||||
|
|
|
@ -86,6 +86,7 @@ std::string load_monitor_sql =
|
|||
"`SignalCheckPoints`, `SignalCheckColour`, `Importance`-1 FROM `Monitors`";
|
||||
|
||||
std::string CameraType_Strings[] = {
|
||||
"Unknown",
|
||||
"Local",
|
||||
"Remote",
|
||||
"File",
|
||||
|
@ -93,10 +94,21 @@ std::string CameraType_Strings[] = {
|
|||
"LibVLC",
|
||||
"NVSOCKET",
|
||||
"CURL",
|
||||
"VNC",
|
||||
"VNC"
|
||||
};
|
||||
|
||||
std::string Function_Strings[] = {
|
||||
"Unknown",
|
||||
"None",
|
||||
"Monitor",
|
||||
"Modect",
|
||||
"Record",
|
||||
"Mocord",
|
||||
"Nodect"
|
||||
};
|
||||
|
||||
std::string State_Strings[] = {
|
||||
"Unknown",
|
||||
"IDLE",
|
||||
"PREALARM",
|
||||
"ALARM",
|
||||
|
@ -474,16 +486,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
|
|||
function = (Function)atoi(dbrow[col]); col++;
|
||||
enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++;
|
||||
decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++;
|
||||
decoding_enabled = !(
|
||||
( function == RECORD or function == NODECT )
|
||||
and
|
||||
( savejpegs == 0 )
|
||||
and
|
||||
( videowriter == PASSTHROUGH )
|
||||
and
|
||||
!decoding_enabled
|
||||
);
|
||||
Debug(1, "Decoding enabled: %d", decoding_enabled);
|
||||
// See below after save_jpegs for a recalculation of decoding_enabled
|
||||
|
||||
ReloadLinkedMonitors(dbrow[col]); col++;
|
||||
|
||||
|
@ -552,6 +555,17 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
|
|||
videowriter = (VideoWriter)atoi(dbrow[col]); col++;
|
||||
encoderparams = dbrow[col] ? dbrow[col] : ""; col++;
|
||||
|
||||
decoding_enabled = !(
|
||||
( function == RECORD or function == NODECT )
|
||||
and
|
||||
( savejpegs == 0 )
|
||||
and
|
||||
( videowriter == PASSTHROUGH )
|
||||
and
|
||||
!decoding_enabled
|
||||
);
|
||||
Debug(3, "Decoding enabled: %d function %d %s savejpegs %d videowriter %d", decoding_enabled, function, Function_Strings[function].c_str(), savejpegs, videowriter);
|
||||
|
||||
/*"`OutputCodec`, `Encoder`, `OutputContainer`, " */
|
||||
output_codec = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
|
||||
encoder = dbrow[col] ? dbrow[col] : ""; col++;
|
||||
|
@ -2040,8 +2054,7 @@ bool Monitor::Analyse() {
|
|||
} // end if ! event
|
||||
} // end if RECORDING
|
||||
|
||||
if (score) {
|
||||
|
||||
if (score and (function == MODECT or function == NODECT)) {
|
||||
if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) {
|
||||
// If we should end then previous continuous event and start a new non-continuous event
|
||||
if (event && event->Frames()
|
||||
|
|
|
@ -151,6 +151,7 @@ bool VideoStore::open() {
|
|||
Debug(3, "Encoder Option %s=%s", e->key, e->value);
|
||||
}
|
||||
}
|
||||
av_dict_free(&opts);
|
||||
|
||||
if (video_in_stream) {
|
||||
zm_dump_codecpar(video_in_stream->codecpar);
|
||||
|
@ -184,6 +185,7 @@ bool VideoStore::open() {
|
|||
}
|
||||
} // end if orientation
|
||||
|
||||
av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0);
|
||||
if (av_dict_get(opts, "new_extradata", nullptr, AV_DICT_MATCH_CASE)) {
|
||||
av_dict_set(&opts, "new_extradata", nullptr, 0);
|
||||
// Special flag to tell us to open a codec to get new extraflags to fix weird h265
|
||||
|
@ -224,14 +226,13 @@ bool VideoStore::open() {
|
|||
);
|
||||
video_out_codec = nullptr;
|
||||
}
|
||||
av_dict_free(&opts);
|
||||
av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0);
|
||||
} // end if video_out_codec
|
||||
|
||||
ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx);
|
||||
if (ret < 0) {
|
||||
Error("Could not initialize stream parameteres");
|
||||
}
|
||||
av_dict_free(&opts);
|
||||
} // end if extradata_entry
|
||||
} else if (monitor->GetOptVideoWriter() == Monitor::ENCODE) {
|
||||
int wanted_codec = monitor->OutputCodec();
|
||||
|
@ -486,6 +487,7 @@ bool VideoStore::open() {
|
|||
zm_dump_stream_format(oc, 0, 0, 1);
|
||||
if (audio_out_stream) zm_dump_stream_format(oc, 1, 0, 1);
|
||||
|
||||
av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0);
|
||||
const AVDictionaryEntry *movflags_entry = av_dict_get(opts, "movflags", nullptr, AV_DICT_MATCH_CASE);
|
||||
if (!movflags_entry) {
|
||||
Debug(1, "setting movflags to frag_keyframe+empty_moov");
|
||||
|
|
|
@ -6,28 +6,30 @@ $data = array();
|
|||
// INITIALIZE AND CHECK SANITY
|
||||
//
|
||||
|
||||
if ( !canView('Events') ) $message = 'Insufficient permissions for user '.$user['Username'];
|
||||
if (!canView('Events'))
|
||||
$message = 'Insufficient permissions for user '.$user['Username'].'<br/>';
|
||||
|
||||
if ( empty($_REQUEST['task']) ) {
|
||||
$message = 'Must specify a task';
|
||||
if (empty($_REQUEST['task'])) {
|
||||
$message = 'Must specify a task<br/>';
|
||||
} else {
|
||||
$task = $_REQUEST['task'];
|
||||
}
|
||||
|
||||
if ( empty($_REQUEST['eids']) ) {
|
||||
if ( isset($_REQUEST['task']) && $_REQUEST['task'] != 'query' ) $message = 'No event id(s) supplied';
|
||||
if (empty($_REQUEST['eids'])) {
|
||||
if (isset($_REQUEST['task']) && $_REQUEST['task'] != 'query')
|
||||
$message = 'No event id(s) supplied<br/>';
|
||||
} else {
|
||||
$eids = $_REQUEST['eids'];
|
||||
}
|
||||
|
||||
if ( $message ) {
|
||||
if ($message) {
|
||||
ajaxError($message);
|
||||
return;
|
||||
}
|
||||
|
||||
require_once('includes/Filter.php');
|
||||
$filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter();
|
||||
if ( $user['MonitorIds'] ) {
|
||||
if ($user['MonitorIds']) {
|
||||
$filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds']));
|
||||
}
|
||||
|
||||
|
@ -39,10 +41,19 @@ $search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
|
|||
$advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array();
|
||||
|
||||
// Order specifies the sort direction, either asc or desc
|
||||
$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC';
|
||||
$order = $filter->sort_asc() ? 'ASC' : 'DESC';
|
||||
if (isset($_REQUEST['order'])) {
|
||||
if (strtolower($_REQUEST['order']) == 'asc') {
|
||||
$order = 'ASC';
|
||||
} else if (strtolower($_REQUEST['order']) == 'desc') {
|
||||
$order = 'DESC';
|
||||
} else {
|
||||
Warning("Invalid value for order " . $_REQUEST['order']);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort specifies the name of the column to sort on
|
||||
$sort = 'StartDateTime';
|
||||
$sort = $filter->sort_field();
|
||||
if (isset($_REQUEST['sort'])) {
|
||||
$sort = $_REQUEST['sort'];
|
||||
if ($sort == 'EndDateTime') {
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
|
||||
ZoneMinder uses certain 3rd party media assets/libraries for UI display purposes. Their licenses are listed in this file
|
||||
|
||||
### Font Awesome icons
|
||||
|
||||
Origin: http://fontawesome.io
|
||||
|
||||
License: Font: SIL OFL 1.1, CSS: MIT License (http://fontawesome.io/license)
|
||||
|
||||
### Material Design icons
|
||||
|
||||
Origin: https://github.com/google/material-design-icons
|
||||
|
|
|
@ -60,6 +60,9 @@ class Filter extends ZM_Object {
|
|||
foreach ( $this->FilterTerms() as $term ) {
|
||||
$this->_querystring .= $term->querystring($objectname, $separator);
|
||||
} # end foreach term
|
||||
$this->_querystring .= $separator.urlencode($objectname.'[Query][sort_asc]').'='.$this->sort_asc();
|
||||
$this->_querystring .= $separator.urlencode($objectname.'[Query][sort_field]').'='.$this->sort_field();
|
||||
$this->_querystring .= $separator.urlencode($objectname.'[Query][limit]').'='.$this->limit();
|
||||
if ( $this->Id() ) {
|
||||
$this->_querystring .= $separator.$objectname.urlencode('[Id]').'='.$this->Id();
|
||||
}
|
||||
|
|
|
@ -20,18 +20,18 @@
|
|||
|
||||
|
||||
// Monitor control actions, require a monitor id and control view permissions for that monitor
|
||||
if ( empty($_REQUEST['mid']) ) {
|
||||
if (empty($_REQUEST['mid'])) {
|
||||
ZM\Warning('Settings requires a monitor id');
|
||||
return;
|
||||
}
|
||||
if ( ! canView('Control', $_REQUEST['mid']) ) {
|
||||
if (!canView('Control', $_REQUEST['mid'])) {
|
||||
ZM\Warning('Settings requires the Control permission');
|
||||
return;
|
||||
}
|
||||
|
||||
require_once('includes/Monitor.php');
|
||||
$mid = validInt($_REQUEST['mid']);
|
||||
if ( $action == 'settings' ) {
|
||||
if ($action == 'settings') {
|
||||
$args = ' -m ' . escapeshellarg($mid);
|
||||
$args .= ' -B' . escapeshellarg($_REQUEST['newBrightness']);
|
||||
$args .= ' -C' . escapeshellarg($_REQUEST['newContrast']);
|
||||
|
@ -45,5 +45,7 @@ if ( $action == 'settings' ) {
|
|||
dbQuery(
|
||||
'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?',
|
||||
array($brightness, $contrast, $hue, $colour, $mid));
|
||||
global $redirect;
|
||||
$redirect = '?view=watch&mid='.$mid;
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -50,6 +50,7 @@ select.chosen {
|
|||
}
|
||||
tr td:first-child {
|
||||
min-width: 300px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.OutputContainer {
|
||||
display: none;
|
||||
|
|
|
@ -153,6 +153,7 @@ if ( $Event->Id() and !file_exists($Event->Path()) )
|
|||
download
|
||||
<?php echo $Event->DefaultVideo() ? '' : 'style="display:none;"' ?>
|
||||
><i class="fa fa-download"></i></a>
|
||||
<button id="videoBtn" class="btn btn-normal" data-toggle="tooltip" data-toggle="tooltip" data-placement="top" title="<?php echo translate('GenerateVideo') ?>"><i class="fa fa-file-video-o"></i></button>
|
||||
<button id="statsBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Stats') ?>" ><i class="fa fa-info"></i></button>
|
||||
<button id="framesBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Frames') ?>" ><i class="fa fa-picture-o"></i></button>
|
||||
<button id="deleteBtn" class="btn btn-danger" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Delete') ?>"><i class="fa fa-trash"></i></button>
|
||||
|
|
|
@ -79,11 +79,14 @@ getBodyTopHTML();
|
|||
data-cookie-id-table="zmEventsTable"
|
||||
data-cookie-expire="2y"
|
||||
data-click-to-select="true"
|
||||
data-remember-order="true"
|
||||
data-remember-order="false"
|
||||
data-show-columns="true"
|
||||
data-show-export="true"
|
||||
data-uncheckAll="true"
|
||||
data-toolbar="#toolbar"
|
||||
data-sort-name="<?php echo $filter->sort_field() ?>"
|
||||
data-sort-order="<?php echo $filter->sort_asc() ? 'asc' : 'desc' ?>"
|
||||
data-server-sort="true"
|
||||
data-show-fullscreen="true"
|
||||
data-click-to-select="true"
|
||||
data-maintain-meta-data="true"
|
||||
|
|
|
@ -708,7 +708,7 @@ function renameEvent() {
|
|||
}
|
||||
|
||||
function exportEvent() {
|
||||
window.location.assign('?view=export&eid='+eventData.Id);
|
||||
window.location.assign('?view=export&eids[]='+eventData.Id);
|
||||
}
|
||||
|
||||
function showEventFrames() {
|
||||
|
@ -767,14 +767,15 @@ function handleClick(event) {
|
|||
// Manage the DELETE CONFIRMATION modal button
|
||||
function manageDelConfirmModalBtns() {
|
||||
document.getElementById("delConfirmBtn").addEventListener("click", function onDelConfirmClick(evt) {
|
||||
if ( !canEdit.Events ) {
|
||||
if (!canEdit.Events) {
|
||||
enoperm();
|
||||
return;
|
||||
}
|
||||
|
||||
evt.preventDefault();
|
||||
$j.getJSON(thisUrl + '?request=events&task=delete&eids[]='+eventData.Id)
|
||||
$j.getJSON(thisUrl + '?request=event&task=delete&id='+eventData.Id)
|
||||
.done(function(data) {
|
||||
$j('#deleteConfirm').modal('hide');
|
||||
streamNext(true);
|
||||
})
|
||||
.fail(logAjaxFail);
|
||||
|
@ -1015,7 +1016,13 @@ function initPage() {
|
|||
// Manage the EXPORT button
|
||||
bindButton('#exportBtn', 'click', null, function onExportClick(evt) {
|
||||
evt.preventDefault();
|
||||
window.location.assign('?view=export&eids[]='+eventData.Id);
|
||||
exportEvent();
|
||||
});
|
||||
|
||||
// Manage the generateVideo button
|
||||
bindButton('#videoBtn', 'click', null, function onExportClick(evt) {
|
||||
evt.preventDefault();
|
||||
videoEvent();
|
||||
});
|
||||
|
||||
// Manage the Event STATISTICS Button
|
||||
|
|
|
@ -945,7 +945,7 @@ function initPage() {
|
|||
});
|
||||
|
||||
// Only enable the settings button for local cameras
|
||||
settingsBtn.prop('disabled', !(canView.Control && monitorType == 'Local'));
|
||||
settingsBtn.prop('disabled', !(canView.Control && (monitorType == 'Local')));
|
||||
|
||||
// Init the bootstrap-table
|
||||
if (monitorType != 'WebSite') table.bootstrapTable({icons: icons});
|
||||
|
|
|
@ -102,7 +102,7 @@ $focusWindow = true;
|
|||
xhtmlHeaders(__FILE__, translate('Video'));
|
||||
?>
|
||||
<body>
|
||||
<?php if ( !$popup ) echo getNavBarHTML() ?>
|
||||
<?php echo getNavBarHTML() ?>
|
||||
<div id="page">
|
||||
<div class="w-100 py-1">
|
||||
<div class="float-left pl-3">
|
||||
|
|
Loading…
Reference in New Issue