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

This commit is contained in:
Isaac Connor 2021-10-28 10:47:02 -04:00
commit 76b75cc69b
27 changed files with 572 additions and 270 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -41,120 +41,133 @@ 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
{
my $self = shift;
our $REALM = '';
our $PROTOCOL = 'http://';
our $USERNAME = 'admin';
our $PASSWORD = '';
our $ADDRESS = '';
our $BASE_URL = '';
$self->loadMonitor();
Debug( "Camera open" );
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION );
sub open {
my $self = shift;
$self->loadMonitor();
$self->{state} = '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->{state} = 'open';
}
sub close
{
my $self = shift;
$self->{state} = 'closed';
sub close {
my $self = shift;
$self->{state} = 'closed';
}
sub printMsg
{
my $msg = shift;
my $msg_len = length($msg);
sub sendCmd {
my ($self, $cmd, $speedcmd) = @_;
Debug( $msg."[".$msg_len."]" );
$self->printMsg( $speedcmd, 'Tx' );
$self->printMsg( $cmd, 'Tx' );
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) {
Error('Request failed: '.$res->status_line().' (URI: '.$req->as_string().')');
}
return $res->is_success;
}
sub sendCmd
{
my ($self, $cmd, $speedcmd) = @_;
my $result = undef;
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 $res = $self->{ua}->request($req);
if ( $res->is_success )
{
$result = !undef;
}
else
{
Error( "Request failed: '" . $res->status_line() . "' (URI: '" . $req->as_string() . "')" );
}
return( $result );
sub moveConUp {
my ($self, $params) = @_;
my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6);
$self->sendCmd( 'move=up', $speed );
}
sub moveConUp
{
my ($self, $params) = @_;
my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6);
Debug( "Move Up" );
$self->sendCmd( 'move=up', $speed );
sub moveConDown {
my ($self, $params) = @_;
my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6);
$self->sendCmd( 'move=down', $speed );
}
sub moveConDown
{
my ($self, $params) = @_;
my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6);
Debug( "Move Down" );
$self->sendCmd( 'move=down', $speed );
sub moveConLeft {
my ($self, $params) = @_;
my $speed = 'speedpan=-' . $params->{panspeed};
$self->sendCmd( 'move=left', $speed );
}
sub moveConLeft
{
my ($self, $params) = @_;
my $speed = 'speedpan=-' . $params->{panspeed};
Debug( "Move Left" );
$self->sendCmd( 'move=left', $speed );
sub moveConRight {
my ($self, $params) = @_;
my $speed = 'speedpan=' . ($params->{panspeed} - 6);
$self->sendCmd( 'move=right', $speed );
}
sub moveConRight
{
my ($self, $params) = @_;
my $speed = 'speedpan=' . ($params->{panspeed} - 6);
Debug( "Move Right" );
$self->sendCmd( 'move=right', $speed );
sub moveStop {
my $self = shift;
Debug( "Move Stop: not implemented" );
# not implemented
}
sub moveStop
{
my $self = shift;
Debug( "Move Stop" );
# not implemented
sub zoomConTele {
my ($self, $params) = @_;
my $speed = 'speedzoom=' . ($params->{speed} - 6);
$self->sendCmd( 'zoom=tele', $speed );
}
sub zoomConTele
{
my ($self, $params) = @_;
my $speed = 'speedzoom=' . ($params->{speed} - 6);
Debug( "Zoom In" );
$self->sendCmd( 'zoom=tele', $speed );
sub zoomConWide {
my ($self, $params) = @_;
my $speed = 'speedzoom=' . ($params->{speed} - 6);
$self->sendCmd( 'zoom=wide', $speed );
}
sub zoomConWide
{
my ($self, $params) = @_;
my $speed = 'speedzoom=' . ($params->{speed} - 6);
Debug( "Zoom Out" );
$self->sendCmd( 'zoom=wide', $speed );
sub reset {
my $self = shift;
$self->sendCmd( 'move=home' );
}
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;

View File

@ -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,39 +658,21 @@ 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) ) {
die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr;
}
my $filename = $event_path.'/'.File::Basename::basename($file);
if (!$bucket->add_key_filename($filename, $file)) {
die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr;
}
my $duration = tv_interval($starttime);
@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}

View File

@ -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;
}
?>

View File

@ -50,6 +50,7 @@ select.chosen {
}
tr td:first-child {
min-width: 300px;
vertical-align: top;
}
.OutputContainer {
display: none;

View File

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

View File

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

View File

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

View File

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

View File

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