956 lines
29 KiB
Perl
Executable File
956 lines
29 KiB
Perl
Executable File
#!/usr/bin/perl -wT
|
|
#
|
|
# ==========================================================================
|
|
#
|
|
# ZoneMinder Event Filter Script, $Date$, $Revision$
|
|
# Copyright (C) 2001-2008 Philip Coombes
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
#
|
|
# ==========================================================================
|
|
|
|
=head1 NAME
|
|
|
|
zmfilter.pl - ZoneMinder tool to filter events
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
zmfilter.pl [-f <filter name>,--filter=<filter name>] [--filter_id=<filter id>] | -v, --version
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This script continuously monitors the recorded events for the given
|
|
monitor and applies any filters which would delete and/or upload
|
|
matching events.
|
|
|
|
=head1 OPTIONS
|
|
|
|
|
|
-f{filter name}, --filter={filter name} - The name of a specific filter to run
|
|
--filter_id={filter id} - The id of a specific filter to run
|
|
-v, --version - Print ZoneMinder version
|
|
|
|
=cut
|
|
use strict;
|
|
use bytes;
|
|
|
|
# ==========================================================================
|
|
#
|
|
# These are the elements you can edit to suit your installation
|
|
#
|
|
# ==========================================================================
|
|
|
|
use constant START_DELAY => 5; # How long to wait before starting
|
|
|
|
# ==========================================================================
|
|
#
|
|
# You shouldn't need to change anything from here downwards
|
|
#
|
|
# ==========================================================================
|
|
|
|
@EXTRA_PERL_LIB@
|
|
use ZoneMinder;
|
|
use DBI;
|
|
use POSIX;
|
|
use Time::HiRes qw/gettimeofday/;
|
|
#use Date::Manip;
|
|
use Getopt::Long;
|
|
use autouse 'Pod::Usage'=>qw(pod2usage);
|
|
use autouse 'Data::Dumper'=>qw(Dumper);
|
|
|
|
my $filter_name = "";
|
|
my $filter_id;
|
|
my $version = 0;
|
|
|
|
GetOptions(
|
|
'filter=s' =>\$filter_name,
|
|
'filter_id=s' =>\$filter_id,
|
|
'version' =>\$version
|
|
) or pod2usage(-exitstatus => -1);
|
|
|
|
if ( $version ) {
|
|
print ZoneMinder::Base::ZM_VERSION . "\n";
|
|
exit(0);
|
|
}
|
|
|
|
require ZoneMinder::Event;
|
|
require ZoneMinder::Filter;
|
|
|
|
use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|)
|
|
? $Config{ZM_DIR_EVENTS}
|
|
: ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS})
|
|
;
|
|
|
|
logInit();
|
|
logSetSignal();
|
|
|
|
if ( $Config{ZM_OPT_UPLOAD} )
|
|
{
|
|
# Comment these out if you don't have them and don't want to upload
|
|
# or don't want to use that format
|
|
if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" )
|
|
{
|
|
require Archive::Zip;
|
|
import Archive::Zip qw( :ERROR_CODES :CONSTANTS );
|
|
}
|
|
else
|
|
{
|
|
require Archive::Tar;
|
|
}
|
|
if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" )
|
|
{
|
|
require Net::FTP;
|
|
}
|
|
else
|
|
{
|
|
require Net::SFTP::Foreign;
|
|
}
|
|
}
|
|
|
|
if ( $Config{ZM_OPT_EMAIL} )
|
|
{
|
|
if ( $Config{ZM_NEW_MAIL_MODULES} )
|
|
{
|
|
require MIME::Lite;
|
|
require Net::SMTP;
|
|
}
|
|
else
|
|
{
|
|
require MIME::Entity;
|
|
}
|
|
}
|
|
|
|
if ( $Config{ZM_OPT_MESSAGE} )
|
|
{
|
|
if ( $Config{ZM_NEW_MAIL_MODULES} )
|
|
{
|
|
require MIME::Lite;
|
|
require Net::SMTP;
|
|
}
|
|
else
|
|
{
|
|
require MIME::Entity;
|
|
}
|
|
}
|
|
|
|
$| = 1;
|
|
|
|
$ENV{PATH} = '/bin:/usr/bin';
|
|
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
|
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
|
|
|
my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL};
|
|
my $event_id = 0;
|
|
|
|
if ( ! EVENT_PATH ) {
|
|
Error( "No event path defined. Config was $Config{ZM_DIR_EVENTS}\n" );
|
|
die;
|
|
}
|
|
|
|
chdir( EVENT_PATH );
|
|
|
|
my $dbh = zmDbConnect();
|
|
|
|
if ( $filter_name ) {
|
|
Info( "Scanning for events using filter '$filter_name'\n" );
|
|
} elsif ( $filter_id ) {
|
|
Info( "Scanning for events using filter id '$filter_id'\n" );
|
|
} else {
|
|
Info( "Scanning for events\n" );
|
|
}
|
|
|
|
if ( ! ( $filter_name or $filter_id ) ) {
|
|
sleep( START_DELAY );
|
|
}
|
|
|
|
my $filters;
|
|
my $last_action = 0;
|
|
|
|
while( 1 ) {
|
|
my $now = time;
|
|
if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) {
|
|
Debug( "Reloading filters\n" );
|
|
$last_action = $now;
|
|
$filters = getFilters( { Name=>$filter_name, Id=>$filter_id } );
|
|
}
|
|
|
|
foreach my $filter ( @$filters ) {
|
|
if ( $$filter{Concurrent} and ! $filter_id ) {
|
|
my ( $proc ) = $0 =~ /(\S+)/;
|
|
my ( $id ) = $$filter{Id} =~ /(\d+)/;
|
|
|
|
Debug("Running concurrent filter process ($proc) for $id $$filter{Name}");
|
|
`$proc -filter_id $id`;
|
|
} else {
|
|
checkFilter( $filter );
|
|
}
|
|
}
|
|
|
|
last if ( $filter_name or $filter_id );
|
|
|
|
Debug( "Sleeping for $delay seconds\n" );
|
|
sleep( $delay );
|
|
}
|
|
|
|
sub getFilters
|
|
{
|
|
my $sql_filters = @_ ? shift : {};
|
|
my @sql_values;
|
|
|
|
my @filters;
|
|
my $sql = "SELECT * FROM Filters WHERE";
|
|
if ( $$sql_filters{Name} ) {
|
|
$sql .= " Name = ? and";
|
|
push @sql_values, $$sql_filters{Name};
|
|
} elsif ( $$sql_filters{Id} ) {
|
|
$sql .= " Id = ? and";
|
|
push @sql_values, $$sql_filters{Id};
|
|
} else {
|
|
$sql .= " Background = 1 and";
|
|
}
|
|
$sql .= "( AutoArchive = 1
|
|
or AutoVideo = 1
|
|
or AutoUpload = 1
|
|
or AutoEmail = 1
|
|
or AutoMessage = 1
|
|
or AutoExecute = 1
|
|
or AutoDelete = 1
|
|
) ORDER BY Name";
|
|
my $sth = $dbh->prepare_cached( $sql )
|
|
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
my $res = $sth->execute( @sql_values )
|
|
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
|
FILTER: while( my $db_filter = $sth->fetchrow_hashref() )
|
|
{
|
|
my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter );
|
|
Debug( "Found filter '$db_filter->{Name}'\n" );
|
|
my $sql = $filter->Sql();
|
|
|
|
if ( ! $sql ) {
|
|
Error( "Error parsing Sql. skipping filter '$db_filter->{Name}'\n" );
|
|
next FILTER;
|
|
}
|
|
push( @filters, $filter );
|
|
}
|
|
$sth->finish();
|
|
if ( @sql_values ) {
|
|
Error("No filter found for $sql with values(@sql_values)");
|
|
} else {
|
|
Debug( "Got " . @filters . " filters" );
|
|
}
|
|
|
|
return( \@filters );
|
|
}
|
|
|
|
sub checkFilter
|
|
{
|
|
my $filter = shift;
|
|
|
|
Debug( "Checking filter '$filter->{Name}'".
|
|
($filter->{AutoDelete}?", delete":"").
|
|
($filter->{AutoArchive}?", archive":"").
|
|
($filter->{AutoVideo}?", video":"").
|
|
($filter->{AutoUpload}?", upload":"").
|
|
($filter->{AutoEmail}?", email":"").
|
|
($filter->{AutoMessage}?", message":"").
|
|
($filter->{AutoExecute}?", execute":"").
|
|
"\n"
|
|
);
|
|
|
|
foreach my $event ( $filter->Execute() ) {
|
|
Debug( "Checking event $event->{Id}\n" );
|
|
my $delete_ok = !undef;
|
|
$dbh->ping();
|
|
if ( $filter->{AutoArchive} )
|
|
{
|
|
Info( "Archiving event $event->{Id}\n" );
|
|
# Do it individually to avoid locking up the table for new events
|
|
my $sql = "update Events set Archived = 1 where Id = ?";
|
|
my $sth = $dbh->prepare_cached( $sql )
|
|
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
my $res = $sth->execute( $event->{Id} )
|
|
or Error( "Can't execute '$sql': ".$sth->errstr() );
|
|
}
|
|
if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} )
|
|
{
|
|
if ( !$event->{Videoed} )
|
|
{
|
|
$delete_ok = undef if ( !generateVideo( $filter, $event ) );
|
|
}
|
|
}
|
|
if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} )
|
|
{
|
|
if ( !$event->{Emailed} )
|
|
{
|
|
$delete_ok = undef if ( !sendEmail( $filter, $event ) );
|
|
}
|
|
}
|
|
if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} )
|
|
{
|
|
if ( !$event->{Messaged} )
|
|
{
|
|
$delete_ok = undef if ( !sendMessage( $filter, $event ) );
|
|
}
|
|
}
|
|
if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} )
|
|
{
|
|
if ( !$event->{Uploaded} )
|
|
{
|
|
$delete_ok = undef if ( !uploadArchFile( $filter, $event ) );
|
|
}
|
|
}
|
|
if ( $filter->{AutoExecute} )
|
|
{
|
|
if ( !$event->{Execute} )
|
|
{
|
|
$delete_ok = undef if ( !executeCommand( $filter, $event ) );
|
|
}
|
|
}
|
|
if ( $filter->{AutoDelete} )
|
|
{
|
|
if ( $delete_ok )
|
|
{
|
|
my $Event = new ZoneMinder::Event( $$event{Id}, $event );
|
|
$Event->delete();
|
|
}
|
|
else
|
|
{
|
|
Error( "Unable to delete event $event->{Id} as previous operations failed\n" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub generateVideo
|
|
{
|
|
my $filter = shift;
|
|
my $event = shift;
|
|
my $phone = shift;
|
|
|
|
my $rate = $event->{DefaultRate}/100;
|
|
my $scale = $event->{DefaultScale}/100;
|
|
my $format;
|
|
|
|
my @ffmpeg_formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} );
|
|
my $default_video_format;
|
|
my $default_phone_format;
|
|
foreach my $ffmpeg_format( @ffmpeg_formats )
|
|
{
|
|
if ( $ffmpeg_format =~ /^(.+)\*\*$/ )
|
|
{
|
|
$default_phone_format = $1;
|
|
}
|
|
elsif ( $ffmpeg_format =~ /^(.+)\*$/ )
|
|
{
|
|
$default_video_format = $1;
|
|
}
|
|
}
|
|
|
|
if ( $phone && $default_phone_format )
|
|
{
|
|
$format = $default_phone_format;
|
|
}
|
|
elsif ( $default_video_format )
|
|
{
|
|
$format = $default_video_format;
|
|
}
|
|
else
|
|
{
|
|
$format = $ffmpeg_formats[0];
|
|
}
|
|
|
|
my $command = $Config{ZM_PATH_BIN}."/zmvideo.pl -e "
|
|
.$event->{Id}." -r ".$rate." -s ".$scale." -f ".$format;
|
|
my $output = qx($command);
|
|
chomp( $output );
|
|
my $status = $? >> 8;
|
|
if ( $status || logDebugging() )
|
|
{
|
|
Debug( "Output: $output\n" );
|
|
}
|
|
if ( $status )
|
|
{
|
|
Error( "Video generation '$command' failed with status: $status\n" );
|
|
if ( wantarray() )
|
|
{
|
|
return( undef, undef );
|
|
}
|
|
return( 0 );
|
|
}
|
|
else
|
|
{
|
|
my $sql = "update Events set Videoed = 1 where Id = ?";
|
|
my $sth = $dbh->prepare_cached( $sql )
|
|
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
my $res = $sth->execute( $event->{Id} )
|
|
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
|
if ( wantarray() )
|
|
{
|
|
return( $format, sprintf( "%s/%s", getEventPath( $event ), $output ) );
|
|
}
|
|
}
|
|
return( 1 );
|
|
}
|
|
|
|
sub uploadArchFile
|
|
{
|
|
my $filter = shift;
|
|
my $event = shift;
|
|
|
|
if ( ! $Config{ZM_UPLOAD_HOST} )
|
|
{
|
|
Error( "Cannot upload archive as no upload host defined" );
|
|
return( 0 );
|
|
}
|
|
|
|
my $archFile = $event->{MonitorName}.'-'.$event->{Id};
|
|
my $archImagePath = getEventPath( $event )
|
|
."/"
|
|
.(
|
|
( $Config{ZM_UPLOAD_ARCH_ANALYSE} )
|
|
? '{*analyse,*capture}'
|
|
: '*capture'
|
|
)
|
|
.".jpg"
|
|
;
|
|
my @archImageFiles = glob($archImagePath);
|
|
my $archLocPath;
|
|
|
|
my $archError = 0;
|
|
if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" )
|
|
{
|
|
$archFile .= '.zip';
|
|
$archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile;
|
|
my $zip = Archive::Zip->new();
|
|
Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" );
|
|
|
|
my $status = &AZ_OK;
|
|
foreach my $imageFile ( @archImageFiles )
|
|
{
|
|
Debug( "Adding $imageFile\n" );
|
|
my $member = $zip->addFile( $imageFile );
|
|
if ( !$member )
|
|
{
|
|
Error( "Unable to add image file $imageFile to zip archive $archLocPath" );
|
|
$archError = 1;
|
|
last;
|
|
}
|
|
$member->desiredCompressionMethod( $Config{ZM_UPLOAD_ARCH_COMPRESS}
|
|
? &COMPRESSION_DEFLATED
|
|
: &COMPRESSION_STORED
|
|
);
|
|
}
|
|
if ( !$archError )
|
|
{
|
|
$status = $zip->writeToFileNamed( $archLocPath );
|
|
|
|
if ( $archError = ($status != &AZ_OK) )
|
|
{
|
|
Error( "Zip error: $status\n " );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Error( "Error adding images to zip archive $archLocPath, not writing" );
|
|
}
|
|
}
|
|
elsif ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "tar" )
|
|
{
|
|
if ( $Config{ZM_UPLOAD_ARCH_COMPRESS} )
|
|
{
|
|
$archFile .= '.tar.gz';
|
|
}
|
|
else
|
|
{
|
|
$archFile .= '.tar';
|
|
}
|
|
$archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile;
|
|
Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" );
|
|
|
|
if ( $archError = !Archive::Tar->create_archive(
|
|
$archLocPath,
|
|
$Config{ZM_UPLOAD_ARCH_COMPRESS},
|
|
@archImageFiles
|
|
)
|
|
)
|
|
{
|
|
Error( "Tar error: ".Archive::Tar->error()."\n " );
|
|
}
|
|
}
|
|
|
|
if ( $archError )
|
|
{
|
|
return( 0 );
|
|
}
|
|
else
|
|
{
|
|
if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" )
|
|
{
|
|
Info( "Uploading to ".$Config{ZM_UPLOAD_HOST}." using FTP\n" );
|
|
my $ftp = Net::FTP->new(
|
|
$Config{ZM_UPLOAD_HOST},
|
|
Timeout=>$Config{ZM_UPLOAD_TIMEOUT},
|
|
Passive=>$Config{ZM_UPLOAD_FTP_PASSIVE},
|
|
Debug=>$Config{ZM_UPLOAD_DEBUG}
|
|
);
|
|
if ( !$ftp )
|
|
{
|
|
Error( "Can't create FTP connection: $@" );
|
|
return( 0 );
|
|
}
|
|
$ftp->login( $Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS} )
|
|
or Error( "FTP - Can't login" );
|
|
$ftp->binary()
|
|
or Error( "FTP - Can't go binary" );
|
|
$ftp->cwd( $Config{ZM_UPLOAD_REM_DIR} )
|
|
or Error( "FTP - Can't cwd" )
|
|
if ( $Config{ZM_UPLOAD_REM_DIR} );
|
|
$ftp->put( $archLocPath )
|
|
or Error( "FTP - Can't upload '$archLocPath'" );
|
|
$ftp->quit()
|
|
or Error( "FTP - Can't quit" );
|
|
}
|
|
else
|
|
{
|
|
my $host = $Config{ZM_UPLOAD_HOST};
|
|
$host .= ":".$Config{ZM_UPLOAD_PORT}
|
|
if $Config{ZM_UPLOAD_PORT};
|
|
Info( "Uploading to ".$host." using SFTP\n" );
|
|
my %sftpOptions = ( host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} );
|
|
$sftpOptions{password} = $Config{ZM_UPLOAD_PASS}
|
|
if $Config{ZM_UPLOAD_PASS};
|
|
$sftpOptions{port} = $Config{ZM_UPLOAD_PORT}
|
|
if $Config{ZM_UPLOAD_PORT};
|
|
$sftpOptions{timeout} = $Config{ZM_UPLOAD_TIMEOUT}
|
|
if $Config{ZM_UPLOAD_TIMEOUT};
|
|
$sftpOptions{more} = [ '-o'=>'StrictHostKeyChecking=no' ]
|
|
if ! $Config{ZM_UPLOAD_STRICT};
|
|
$Net::SFTP::Foreign::debug = -1
|
|
if $Config{ZM_UPLOAD_DEBUG};
|
|
my $sftp = Net::SFTP::Foreign->new( $Config{ZM_UPLOAD_HOST}, %sftpOptions );
|
|
if ( $sftp->error )
|
|
{
|
|
Error( "Can't create SFTP connection: ".$sftp->error );
|
|
return( 0 );
|
|
}
|
|
$sftp->setcwd( $Config{ZM_UPLOAD_REM_DIR} )
|
|
or Error( "SFTP - Can't setcwd: ".$sftp->error )
|
|
if $Config{ZM_UPLOAD_REM_DIR};
|
|
$sftp->put( $archLocPath, $archFile )
|
|
or Error( "SFTP - Can't upload '$archLocPath': ".$sftp->error );
|
|
}
|
|
unlink( $archLocPath );
|
|
my $sql = "update Events set Uploaded = 1 where Id = ?";
|
|
my $sth = $dbh->prepare_cached( $sql )
|
|
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
my $res = $sth->execute( $event->{Id} )
|
|
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
|
}
|
|
return( 1 );
|
|
}
|
|
|
|
sub substituteTags
|
|
{
|
|
my $text = shift;
|
|
my $filter = shift;
|
|
my $event = shift;
|
|
my $attachments_ref = shift;
|
|
|
|
# First we'd better check what we need to get
|
|
# We have a filter and an event, do we need any more
|
|
# monitor information?
|
|
my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/;
|
|
|
|
my $monitor = {};
|
|
if ( $need_monitor )
|
|
{
|
|
my $db_now = strftime( "%Y-%m-%d %H:%M:%S", localtime() );
|
|
my $sql = "SELECT
|
|
M.Id,
|
|
count(E.Id) as EventCount,
|
|
count(if(E.Archived,1,NULL))
|
|
as ArchEventCount,
|
|
count(if(E.StartTime>'$db_now' - INTERVAL 1 HOUR && E.Archived = 0,1,NULL))
|
|
as HourEventCount,
|
|
count(if(E.StartTime>'$db_now' - INTERVAL 1 DAY && E.Archived = 0,1,NULL))
|
|
as DayEventCount,
|
|
count(if(E.StartTime>'$db_now' - INTERVAL 7 DAY && E.Archived = 0,1,NULL))
|
|
as WeekEventCount,
|
|
count(if(E.StartTime>'$db_now' - INTERVAL 1 MONTH && E.Archived = 0,1,NULL))
|
|
as MonthEventCount
|
|
FROM Monitors as M LEFT JOIN Events as E on E.MonitorId = M.Id
|
|
WHERE MonitorId = ?
|
|
GROUP BY E.MonitorId
|
|
ORDER BY Id"
|
|
;
|
|
my $sth = $dbh->prepare_cached( $sql )
|
|
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
my $res = $sth->execute( $event->{MonitorId} )
|
|
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
|
$monitor = $sth->fetchrow_hashref();
|
|
$sth->finish();
|
|
return() if ( !$monitor );
|
|
}
|
|
|
|
# Do we need the image information too?
|
|
my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM)%/;
|
|
my $first_alarm_frame;
|
|
my $max_alarm_frame;
|
|
my $max_alarm_score = 0;
|
|
if ( $need_images )
|
|
{
|
|
my $sql = "SELECT * FROM Frames
|
|
WHERE EventId = ? AND Type = 'Alarm'
|
|
ORDER BY FrameId"
|
|
;
|
|
my $sth = $dbh->prepare_cached( $sql )
|
|
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
my $res = $sth->execute( $event->{Id} )
|
|
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
|
while( my $frame = $sth->fetchrow_hashref() )
|
|
{
|
|
if ( !$first_alarm_frame )
|
|
{
|
|
$first_alarm_frame = $frame;
|
|
}
|
|
if ( $frame->{Score} > $max_alarm_score )
|
|
{
|
|
$max_alarm_frame = $frame;
|
|
$max_alarm_score = $frame->{Score};
|
|
}
|
|
}
|
|
$sth->finish();
|
|
}
|
|
|
|
my $url = $Config{ZM_URL};
|
|
$text =~ s/%ZP%/$url/g;
|
|
$text =~ s/%MN%/$event->{MonitorName}/g;
|
|
$text =~ s/%MET%/$monitor->{EventCount}/g;
|
|
$text =~ s/%MEH%/$monitor->{HourEventCount}/g;
|
|
$text =~ s/%MED%/$monitor->{DayEventCount}/g;
|
|
$text =~ s/%MEW%/$monitor->{WeekEventCount}/g;
|
|
$text =~ s/%MEM%/$monitor->{MonthEventCount}/g;
|
|
$text =~ s/%MEA%/$monitor->{ArchEventCount}/g;
|
|
$text =~ s/%MP%/$url?view=watch&mid=$event->{MonitorId}/g;
|
|
$text =~ s/%MPS%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=stream/g;
|
|
$text =~ s/%MPI%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=still/g;
|
|
$text =~ s/%EP%/$url?view=event&mid=$event->{MonitorId}&eid=$event->{Id}/g;
|
|
$text =~ s/%EPS%/$url?view=event&mode=stream&mid=$event->{MonitorId}&eid=$event->{Id}/g;
|
|
$text =~ s/%EPI%/$url?view=event&mode=still&mid=$event->{MonitorId}&eid=$event->{Id}/g;
|
|
$text =~ s/%EI%/$event->{Id}/g;
|
|
$text =~ s/%EN%/$event->{Name}/g;
|
|
$text =~ s/%EC%/$event->{Cause}/g;
|
|
$text =~ s/%ED%/$event->{Notes}/g;
|
|
$text =~ s/%ET%/$event->{StartTime}/g;
|
|
$text =~ s/%EL%/$event->{Length}/g;
|
|
$text =~ s/%EF%/$event->{Frames}/g;
|
|
$text =~ s/%EFA%/$event->{AlarmFrames}/g;
|
|
$text =~ s/%EST%/$event->{TotScore}/g;
|
|
$text =~ s/%ESA%/$event->{AvgScore}/g;
|
|
$text =~ s/%ESM%/$event->{MaxScore}/g;
|
|
if ( $first_alarm_frame )
|
|
{
|
|
$text =~ s/%EPI1%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$first_alarm_frame->{FrameId}/g;
|
|
$text =~ s/%EPIM%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$max_alarm_frame->{FrameId}/g;
|
|
if ( $attachments_ref && $text =~ s/%EI1%//g )
|
|
{
|
|
push( @$attachments_ref,
|
|
{
|
|
type=>"image/jpeg",
|
|
path=>sprintf(
|
|
"%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg",
|
|
getEventPath( $event ),
|
|
$first_alarm_frame->{FrameId}
|
|
)
|
|
}
|
|
);
|
|
}
|
|
if ( $attachments_ref && $text =~ s/%EIM%//g )
|
|
{
|
|
# Don't attach the same image twice
|
|
if ( !@$attachments_ref
|
|
|| ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} )
|
|
)
|
|
{
|
|
push( @$attachments_ref,
|
|
{
|
|
type=>"image/jpeg",
|
|
path=>sprintf(
|
|
"%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg",
|
|
getEventPath( $event ),
|
|
$max_alarm_frame->{FrameId}
|
|
)
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} )
|
|
{
|
|
if ( $text =~ s/%EV%//g )
|
|
{
|
|
my ( $format, $path ) = generateVideo( $filter, $event );
|
|
if ( !$format )
|
|
{
|
|
return( undef );
|
|
}
|
|
push( @$attachments_ref, { type=>"video/$format", path=>$path } );
|
|
}
|
|
if ( $text =~ s/%EVM%//g )
|
|
{
|
|
my ( $format, $path ) = generateVideo( $filter, $event, 1 );
|
|
if ( !$format )
|
|
{
|
|
return( undef );
|
|
}
|
|
push( @$attachments_ref, { type=>"video/$format", path=>$path } );
|
|
}
|
|
}
|
|
$text =~ s/%FN%/$filter->{Name}/g;
|
|
( my $filter_name = $filter->{Name} ) =~ s/ /+/g;
|
|
$text =~ s/%FP%/$url?view=filter&mid=$event->{MonitorId}&filter_name=$filter_name/g;
|
|
|
|
return( $text );
|
|
}
|
|
|
|
sub sendEmail
|
|
{
|
|
my $filter = shift;
|
|
my $event = shift;
|
|
|
|
if ( ! $Config{ZM_FROM_EMAIL} )
|
|
{
|
|
Error( "No 'from' email address defined, not sending email" );
|
|
return( 0 );
|
|
}
|
|
if ( ! $Config{ZM_EMAIL_ADDRESS} )
|
|
{
|
|
Error( "No email address defined, not sending email" );
|
|
return( 0 );
|
|
}
|
|
|
|
Info( "Creating notification email\n" );
|
|
|
|
my $subject = substituteTags( $Config{ZM_EMAIL_SUBJECT}, $filter, $event );
|
|
return( 0 ) if ( !$subject );
|
|
my @attachments;
|
|
my $body = substituteTags( $Config{ZM_EMAIL_BODY}, $filter, $event, \@attachments );
|
|
return( 0 ) if ( !$body );
|
|
|
|
Info( "Sending notification email '$subject'\n" );
|
|
|
|
eval
|
|
{
|
|
if ( $Config{ZM_NEW_MAIL_MODULES} )
|
|
{
|
|
### Create the multipart container
|
|
my $mail = MIME::Lite->new (
|
|
From => $Config{ZM_FROM_EMAIL},
|
|
To => $Config{ZM_EMAIL_ADDRESS},
|
|
Subject => $subject,
|
|
Type => "multipart/mixed"
|
|
);
|
|
### Add the text message part
|
|
$mail->attach (
|
|
Type => "TEXT",
|
|
Data => $body
|
|
);
|
|
### Add the attachments
|
|
foreach my $attachment ( @attachments )
|
|
{
|
|
Info( "Attaching '$attachment->{path}\n" );
|
|
$mail->attach(
|
|
Path => $attachment->{path},
|
|
Type => $attachment->{type},
|
|
Disposition => "attachment"
|
|
);
|
|
}
|
|
### Send the Message
|
|
MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 );
|
|
$mail->send();
|
|
}
|
|
else
|
|
{
|
|
my $mail = MIME::Entity->build(
|
|
From => $Config{ZM_FROM_EMAIL},
|
|
To => $Config{ZM_EMAIL_ADDRESS},
|
|
Subject => $subject,
|
|
Type => (($body=~/<html>/)?'text/html':'text/plain'),
|
|
Data => $body
|
|
);
|
|
|
|
foreach my $attachment ( @attachments )
|
|
{
|
|
Info( "Attaching '$attachment->{path}\n" );
|
|
$mail->attach(
|
|
Path => $attachment->{path},
|
|
Type => $attachment->{type},
|
|
Encoding => "base64"
|
|
);
|
|
}
|
|
$mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} );
|
|
}
|
|
};
|
|
if ( $@ )
|
|
{
|
|
Error( "Can't send email: $@" );
|
|
return( 0 );
|
|
}
|
|
else
|
|
{
|
|
Info( "Notification email sent\n" );
|
|
}
|
|
my $sql = "update Events set Emailed = 1 where Id = ?";
|
|
my $sth = $dbh->prepare_cached( $sql )
|
|
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
my $res = $sth->execute( $event->{Id} )
|
|
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
|
|
|
return( 1 );
|
|
}
|
|
|
|
sub sendMessage
|
|
{
|
|
my $filter = shift;
|
|
my $event = shift;
|
|
|
|
if ( ! $Config{ZM_FROM_EMAIL} )
|
|
{
|
|
Error( "No 'from' email address defined, not sending message" );
|
|
return( 0 );
|
|
}
|
|
if ( ! $Config{ZM_MESSAGE_ADDRESS} )
|
|
{
|
|
Error( "No message address defined, not sending message" );
|
|
return( 0 );
|
|
}
|
|
|
|
Info( "Creating notification message\n" );
|
|
|
|
my $subject = substituteTags( $Config{ZM_MESSAGE_SUBJECT}, $filter, $event );
|
|
return( 0 ) if ( !$subject );
|
|
my @attachments;
|
|
my $body = substituteTags( $Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments );
|
|
return( 0 ) if ( !$body );
|
|
|
|
Info( "Sending notification message '$subject'\n" );
|
|
|
|
eval
|
|
{
|
|
if ( $Config{ZM_NEW_MAIL_MODULES} )
|
|
{
|
|
### Create the multipart container
|
|
my $mail = MIME::Lite->new (
|
|
From => $Config{ZM_FROM_EMAIL},
|
|
To => $Config{ZM_MESSAGE_ADDRESS},
|
|
Subject => $subject,
|
|
Type => "multipart/mixed"
|
|
);
|
|
### Add the text message part
|
|
$mail->attach (
|
|
Type => "TEXT",
|
|
Data => $body
|
|
);
|
|
### Add the attachments
|
|
foreach my $attachment ( @attachments )
|
|
{
|
|
Info( "Attaching '$attachment->{path}\n" );
|
|
$mail->attach(
|
|
Path => $attachment->{path},
|
|
Type => $attachment->{type},
|
|
Disposition => "attachment"
|
|
);
|
|
}
|
|
### Send the Message
|
|
MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 );
|
|
$mail->send();
|
|
}
|
|
else
|
|
{
|
|
my $mail = MIME::Entity->build(
|
|
From => $Config{ZM_FROM_EMAIL},
|
|
To => $Config{ZM_MESSAGE_ADDRESS},
|
|
Subject => $subject,
|
|
Type => (($body=~/<html>/)?'text/html':'text/plain'),
|
|
Data => $body
|
|
);
|
|
|
|
foreach my $attachment ( @attachments )
|
|
{
|
|
Info( "Attaching '$attachment->{path}\n" );
|
|
$mail->attach(
|
|
Path => $attachment->{path},
|
|
Type => $attachment->{type},
|
|
Encoding => "base64"
|
|
);
|
|
}
|
|
$mail->smtpsend( Host => $Config{ZM_EMAIL_HOST},
|
|
MailFrom => $Config{ZM_FROM_EMAIL}
|
|
);
|
|
}
|
|
};
|
|
if ( $@ )
|
|
{
|
|
Error( "Can't send email: $@" );
|
|
return( 0 );
|
|
}
|
|
else
|
|
{
|
|
Info( "Notification message sent\n" );
|
|
}
|
|
my $sql = "update Events set Messaged = 1 where Id = ?";
|
|
my $sth = $dbh->prepare_cached( $sql )
|
|
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
my $res = $sth->execute( $event->{Id} )
|
|
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
|
|
|
return( 1 );
|
|
}
|
|
|
|
sub executeCommand
|
|
{
|
|
my $filter = shift;
|
|
my $event = shift;
|
|
|
|
my $event_path = getEventPath( $event );
|
|
|
|
my $command = $filter->{AutoExecuteCmd};
|
|
$command .= " $event_path";
|
|
|
|
Info( "Executing '$command'\n" );
|
|
my $output = qx($command);
|
|
my $status = $? >> 8;
|
|
if ( $status || logDebugging() )
|
|
{
|
|
chomp( $output );
|
|
Debug( "Output: $output\n" );
|
|
}
|
|
if ( $status )
|
|
{
|
|
Error( "Command '$command' exited with status: $status\n" );
|
|
return( 0 );
|
|
}
|
|
else
|
|
{
|
|
my $sql = "update Events set Executed = 1 where Id = ?";
|
|
my $sth = $dbh->prepare_cached( $sql )
|
|
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
my $res = $sth->execute( $event->{Id} )
|
|
or Fatal( "Can't execute '$sql': ".$sth->errstr() );
|
|
}
|
|
return( 1 );
|
|
}
|
|
|