diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm new file mode 100644 index 000000000..67c117460 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -0,0 +1,330 @@ +# ========================================================================== +# +# ZoneMinder Event Module, $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. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Event; + +use 5.006; +use strict; +use warnings; + +require Exporter; +require ZoneMinder::Base; +require Date::Manip; + +our @ISA = qw(Exporter ZoneMinder::Base); + +# Items to export into callers namespace by default. Note: do not export +# names by default without a very good reason. Use EXPORT_OK instead. +# Do not simply export all your public functions/methods/constants. + +# This allows declaration use ZoneMinder ':all'; +# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK +# will save memory. +our %EXPORT_TAGS = ( + 'functions' => [ qw( + ) ] +); +push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; + +our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); + +our @EXPORT = qw(); + +our $VERSION = $ZoneMinder::Base::VERSION; + +# ========================================================================== +# +# General Utility Functions +# +# ========================================================================== + +use ZoneMinder::Config qw(:all); +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Database qw(:all); + +use POSIX; + +sub new { + my ( $parent, $id, $data ) = @_; + + my $self = {}; + bless $self, $parent; + $$self{dbh} = $ZoneMinder::Database::dbh; +#zmDbConnect(); + if ( ( $$self{Id} = $id ) or $data ) { +#$log->debug("loading $parent $id") if $debug or DEBUG_ALL; + $self->load( $data ); + } + return $self; +} # end sub new + +sub load { + my ( $self, $data ) = @_; + my $type = ref $self; + if ( ! $data ) { +#$log->debug("Object::load Loading from db $type"); + $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Events WHERE Id=?', {}, $$self{Id} ); + if ( ! $data ) { + Error( "Failure to load Event record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); + } else { + Debug( 3, "Loaded Event $$self{Id}" ); + } # end if + } # end if ! $data + if ( $data and %$data ) { + @$self{keys %$data} = values %$data; + } # end if +} # end sub load + +sub Name { + if ( @_ > 1 ) { + $_[0]{Name} = $_[1]; + } + return $_[0]{Name}; +} # end sub Path + +sub find { + shift if $_[0] eq 'ZoneMinder::Event'; + my %sql_filters = @_; + + my $sql = 'SELECT * FROM Events'; + my @sql_filters; + my @sql_values; + + if ( exists $sql_filters{Name} ) { + push @sql_filters , ' Name = ? '; + push @sql_values, $sql_filters{Name}; + } + + $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; + $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; + + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( @sql_values ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + my @results; + + while( my $db_filter = $sth->fetchrow_hashref() ) { + my $filter = new ZoneMinder::Event( $$db_filter{Id}, $db_filter ); + push @results, $filter; + } # end while + return @results; +} + +sub find_one { + my @results = find(@_); + return $results[0] if @results; +} + +sub getEventPath +{ + my $event = shift; + + my $event_path = ""; + if ( $Config{ZM_USE_DEEP_STORAGE} ) + { + $event_path = $Config{ZM_DIR_EVENTS} + .'/'.$event->{MonitorId} + .'/'.strftime( "%y/%m/%d/%H/%M/%S", + localtime($event->{Time}) + ) + ; + } + else + { + $event_path = $Config{ZM_DIR_EVENTS} + .'/'.$event->{MonitorId} + .'/'.$event->{Id} + ; + } + + if ( index($Config{ZM_DIR_EVENTS},'/') != 0 ){ + $event_path = $Config{ZM_PATH_WEB} + .'/'.$event_path + ; + } + return( $event_path ); +} + +sub GenerateVideo { + my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_; + + my $event_path = getEventPath( $self ); + chdir( $event_path ); + ( my $video_name = $self->{Name} ) =~ s/\s/_/g; + + my @file_parts; + if ( $rate ) + { + my $file_rate = $rate; + $file_rate =~ s/\./_/; + $file_rate =~ s/_00//; + $file_rate =~ s/(_\d+)0+$/$1/; + $file_rate = 'r'.$file_rate; + push( @file_parts, $file_rate ); + } + elsif ( $fps ) + { + my $file_fps = $fps; + $file_fps =~ s/\./_/; + $file_fps =~ s/_00//; + $file_fps =~ s/(_\d+)0+$/$1/; + $file_fps = 'R'.$file_fps; + push( @file_parts, $file_fps ); + } + + if ( $scale ) + { + my $file_scale = $scale; + $file_scale =~ s/\./_/; + $file_scale =~ s/_00//; + $file_scale =~ s/(_\d+)0+$/$1/; + $file_scale = 's'.$file_scale; + push( @file_parts, $file_scale ); + } + elsif ( $size ) + { + my $file_size = 'S'.$size; + push( @file_parts, $file_size ); + } + my $video_file = "$video_name-".$file_parts[0]."-".$file_parts[1].".$format"; + if ( $overwrite || !-s $video_file ) + { + Info( "Creating video file $video_file for event $self->{Id}\n" ); + + my $frame_rate = sprintf( "%.2f", $self->{Frames}/$self->{FullLength} ); + if ( $rate ) + { + if ( $rate != 1.0 ) + { + $frame_rate *= $rate; + } + } + elsif ( $fps ) + { + $frame_rate = $fps; + } + + my $width = $self->{MonitorWidth}; + my $height = $self->{MonitorHeight}; + my $video_size = " ${width}x${height}"; + + if ( $scale ) + { + if ( $scale != 1.0 ) + { + $width = int($width*$scale); + $height = int($height*$scale); + $video_size = " ${width}x${height}"; + } + } + elsif ( $size ) + { + $video_size = $size; + } + my $command = $Config{ZM_PATH_FFMPEG} + ." -y -r $frame_rate " + .$Config{ZM_FFMPEG_INPUT_OPTIONS} + ." -i %0" + .$Config{ZM_EVENT_IMAGE_DIGITS} + ."d-capture.jpg -s $video_size " +#. " -f concat -i /tmp/event_files.txt" + ." -s $video_size " + .$Config{ZM_FFMPEG_OUTPUT_OPTIONS} + ." '$video_file' > ffmpeg.log 2>&1" + ; + Debug( $command."\n" ); + my $output = qx($command); + + my $status = $? >> 8; + if ( $status ) + { + Error( "Unable to generate video, check " + .$event_path."/ffmpeg.log for details" + ); + return; + } + + Info( "Finished $video_file\n" ); + return $event_path.'/'.$video_file; + } else { + Info( "Video file $video_file already exists for event $self->{Id}\n" ); + return $event_path.'/'.$video_file; + } + return; +} # end sub GenerateVideo + +1; +__END__ +# Below is stub documentation for your module. You'd better edit it! + +=head1 NAME + +ZoneMinder::Database - Perl extension for blah blah blah + +=head1 SYNOPSIS + + use ZoneMinder::Event; + blah blah blah + +=head1 DESCRIPTION + +Stub documentation for ZoneMinder, created by h2xs. It looks like the +author of the extension was negligent enough to leave the stub +unedited. + +Blah blah blah. + +=head2 EXPORT + +None by default. + + + +=head1 SEE ALSO + +Mention other useful documentation such as the documentation of +related modules or operating system documentation (such as man pages +in UNIX), or any relevant external documentation such as RFCs or +standards. + +If you have a mailing list set up for your module, mention it here. + +If you have a web site set up for your module, mention it here. + +=head1 AUTHOR + +Philip Coombes, Ephilip.coombes@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2008 Philip Coombes + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm new file mode 100644 index 000000000..b7806ec4a --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -0,0 +1,526 @@ +# ========================================================================== +# +# ZoneMinder Filter Module, $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. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Filter; + +use 5.006; +use strict; +use warnings; + +require Exporter; +require ZoneMinder::Base; +require Date::Manip; + +our @ISA = qw(Exporter ZoneMinder::Base); + +# Items to export into callers namespace by default. Note: do not export +# names by default without a very good reason. Use EXPORT_OK instead. +# Do not simply export all your public functions/methods/constants. + +# This allows declaration use ZoneMinder ':all'; +# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK +# will save memory. +our %EXPORT_TAGS = ( + 'functions' => [ qw( + ) ] +); +push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; + +our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); + +our @EXPORT = qw(); + +our $VERSION = $ZoneMinder::Base::VERSION; + +# ========================================================================== +# +# General Utility Functions +# +# ========================================================================== + +use ZoneMinder::Config qw(:all); +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Database qw(:all); + +use POSIX; + +sub new { + my ( $parent, $id, $data ) = @_; + + my $self = {}; + bless $self, $parent; + $$self{dbh} = $ZoneMinder::Database::dbh; +#zmDbConnect(); + if ( ( $$self{Id} = $id ) or $data ) { +#$log->debug("loading $parent $id") if $debug or DEBUG_ALL; + $self->load( $data ); + } + return $self; +} # end sub new + +sub load { + my ( $self, $data ) = @_; + my $type = ref $self; + if ( ! $data ) { +#$log->debug("Object::load Loading from db $type"); + $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Filter WHERE Id=?', {}, $$self{Id} ); + if ( ! $data ) { + Error( "Failure to load Filter record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); + } else { + Debug( 3, "Loaded Filter $$self{Id}" ); + } # end if + } # end if ! $data + if ( $data and %$data ) { + @$self{keys %$data} = values %$data; + } # end if +} # end sub load + +sub Name { + if ( @_ > 1 ) { + $_[0]{Name} = $_[1]; + } + return $_[0]{Name}; +} # end sub Path + +sub find { + shift if $_[0] eq 'ZoneMinder::Filter'; + my %sql_filters = @_; + + my $sql = 'SELECT * FROM Filters'; + my @sql_filters; + my @sql_values; + + if ( exists $sql_filters{Name} ) { + push @sql_filters , ' Name = ? '; + push @sql_values, $sql_filters{Name}; + } + + $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; + $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; + + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( @sql_values ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + my @results; + + while( my $db_filter = $sth->fetchrow_hashref() ) { + my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); + push @results, $filter; + } # end while + return @results; +} + +sub find_one { + my @results = find(@_); + return $results[0] if @results; +} + +sub Execute { + my $self = $_[0]; + + my $sql = $self->Sql(); + + if ( $self->{HasDiskPercent} ) + { + my $disk_percent = getDiskPercent(); + $sql =~ s/zmDiskPercent/$disk_percent/g; + } + if ( $self->{HasDiskBlocks} ) + { + my $disk_blocks = getDiskBlocks(); + $sql =~ s/zmDiskBlocks/$disk_blocks/g; + } + if ( $self->{HasSystemLoad} ) + { + my $load = getLoad(); + $sql =~ s/zmSystemLoad/$load/g; + } + + my $sth = $$self{dbh}->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$$self{dbh}->errstr() ); + my $res = $sth->execute(); + if ( !$res ) + { + Error( "Can't execute filter '$sql', ignoring: ".$sth->errstr() ); + return; + } + my @results; + + while( my $event = $sth->fetchrow_hashref() ) { + push @results, $event; + } + $sth->finish(); + return @results; +} + +sub Sql { + my $self = $_[0]; + if ( ! $$self{Sql} ) { + my $filter_expr = ZoneMinder::General::jsonDecode( $self->{Query} ); + my $sql = "SELECT E.Id, + E.MonitorId, + M.Name as MonitorName, + M.DefaultRate, + M.DefaultScale, + E.Name, + E.Cause, + E.Notes, + E.StartTime, + unix_timestamp(E.StartTime) as Time, + E.Length, + E.Frames, + E.AlarmFrames, + E.TotScore, + E.AvgScore, + E.MaxScore, + E.Archived, + E.Videoed, + E.Uploaded, + E.Emailed, + E.Messaged, + E.Executed + FROM Events as E + INNER JOIN Monitors as M on M.Id = E.MonitorId + "; + $self->{Sql} = ''; + + if ( $filter_expr->{terms} ) { + for ( my $i = 0; $i < @{$filter_expr->{terms}}; $i++ ) { + if ( exists($filter_expr->{terms}[$i]->{cnj}) ) { + $self->{Sql} .= " ".$filter_expr->{terms}[$i]->{cnj}." "; + } + if ( exists($filter_expr->{terms}[$i]->{obr}) ) { + $self->{Sql} .= " ".str_repeat( "(", $filter_expr->{terms}[$i]->{obr} )." "; + } + my $value = $filter_expr->{terms}[$i]->{val}; + my @value_list; + if ( $filter_expr->{terms}[$i]->{attr} ) { + if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) { + my ( $temp_attr_name ) = $filter_expr->{terms}[$i]->{attr} =~ /^Monitor(.+)$/; + $self->{Sql} .= "M.".$temp_attr_name; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) { + $self->{Sql} .= "E.StartTime"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) { + $self->{Sql} .= "to_days( E.StartTime )"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) { + $self->{Sql} .= "extract( hour_second from E.StartTime )"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Weekday' ) { + $self->{Sql} .= "weekday( E.StartTime )"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskPercent' ) { + $self->{Sql} .= "zmDiskPercent"; + $self->{HasDiskPercent} = !undef; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskBlocks' ) { + $self->{Sql} .= "zmDiskBlocks"; + $self->{HasDiskBlocks} = !undef; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'SystemLoad' ) { + $self->{Sql} .= "zmSystemLoad"; + $self->{HasSystemLoad} = !undef; + } else { + $self->{Sql} .= "E.".$filter_expr->{terms}[$i]->{attr}; + } + + ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; + foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { + if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) { + $value = "'$temp_value'"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Name' + || $filter_expr->{terms}[$i]->{attr} eq 'Cause' + || $filter_expr->{terms}[$i]->{attr} eq 'Notes' + ) { + $value = "'$temp_value'"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) { + $value = DateTimeToSQL( $temp_value ); + if ( !$value ) { + Error( "Error parsing date/time '$temp_value', " + ."skipping filter '$self->{Name}'\n" ); + return; + } + $value = "'$value'"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) { + $value = DateTimeToSQL( $temp_value ); + if ( !$value ) { + Error( "Error parsing date/time '$temp_value', " + ."skipping filter '$self->{Name}'\n" ); + return; + } + $value = "to_days( '$value' )"; + } elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) { + $value = DateTimeToSQL( $temp_value ); + if ( !$value ) { + Error( "Error parsing date/time '$temp_value', " + ."skipping filter '$self->{Name}'\n" ); + return; + } + $value = "extract( hour_second from '$value' )"; + } else { + $value = $temp_value; + } + push( @value_list, $value ); + } # end foreach temp_value + } # end if has an attr + if ( $filter_expr->{terms}[$i]->{op} ) { + if ( $filter_expr->{terms}[$i]->{op} eq '=~' ) { + $self->{Sql} .= " regexp $value"; + } elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) { + $self->{Sql} .= " not regexp $value"; + } elsif ( $filter_expr->{terms}[$i]->{op} eq '=[]' ) { + $self->{Sql} .= " in (".join( ",", @value_list ).")"; + } elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) { + $self->{Sql} .= " not in (".join( ",", @value_list ).")"; + } else { + $self->{Sql} .= " ".$filter_expr->{terms}[$i]->{op}." $value"; + } + } # end if has an operator + if ( exists($filter_expr->{terms}[$i]->{cbr}) ) { + $self->{Sql} .= " ".str_repeat( ")", $filter_expr->{terms}[$i]->{cbr} )." "; + } + } # end foreach term + } # end if terms + + if ( $self->{Sql} ) + { + if ( $self->{AutoMessage} ) + { + # Include all events, including events that are still ongoing + # and have no EndTime yet + $sql .= " and ( ".$self->{Sql}." )"; + } + else + { + # Only include closed events (events with valid EndTime) + $sql .= " where not isnull(E.EndTime) and ( ".$self->{Sql}." )"; + } + } + my @auto_terms; + if ( $self->{AutoArchive} ) + { + push( @auto_terms, "E.Archived = 0" ) + } + if ( $self->{AutoVideo} ) + { + push( @auto_terms, "E.Videoed = 0" ) + } + if ( $self->{AutoUpload} ) + { + push( @auto_terms, "E.Uploaded = 0" ) + } + if ( $self->{AutoEmail} ) + { + push( @auto_terms, "E.Emailed = 0" ) + } + if ( $self->{AutoMessage} ) + { + push( @auto_terms, "E.Messaged = 0" ) + } + if ( $self->{AutoExecute} ) + { + push( @auto_terms, "E.Executed = 0" ) + } + if ( @auto_terms ) + { + $sql .= " and ( ".join( " or ", @auto_terms )." )"; + } + if ( !$filter_expr->{sort_field} ) + { + $filter_expr->{sort_field} = 'StartTime'; + $filter_expr->{sort_asc} = 0; + } + my $sort_column = ''; + if ( $filter_expr->{sort_field} eq 'Id' ) + { + $sort_column = "E.Id"; + } + elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) + { + $sort_column = "M.Name"; + } + elsif ( $filter_expr->{sort_field} eq 'Name' ) + { + $sort_column = "E.Name"; + } + elsif ( $filter_expr->{sort_field} eq 'StartTime' ) + { + $sort_column = "E.StartTime"; + } + elsif ( $filter_expr->{sort_field} eq 'Secs' ) + { + $sort_column = "E.Length"; + } + elsif ( $filter_expr->{sort_field} eq 'Frames' ) + { + $sort_column = "E.Frames"; + } + elsif ( $filter_expr->{sort_field} eq 'AlarmFrames' ) + { + $sort_column = "E.AlarmFrames"; + } + elsif ( $filter_expr->{sort_field} eq 'TotScore' ) + { + $sort_column = "E.TotScore"; + } + elsif ( $filter_expr->{sort_field} eq 'AvgScore' ) + { + $sort_column = "E.AvgScore"; + } + elsif ( $filter_expr->{sort_field} eq 'MaxScore' ) + { + $sort_column = "E.MaxScore"; + } + else + { + $sort_column = "E.StartTime"; + } + my $sort_order = $filter_expr->{sort_asc}?"asc":"desc"; + $sql .= " order by ".$sort_column." ".$sort_order; + if ( $filter_expr->{limit} ) + { + $sql .= " limit 0,".$filter_expr->{limit}; + } + Debug( "SQL:$sql\n" ); + $self->{Sql} = $sql; + } # end if has Sql + return $self->{Sql}; +} # end sub Sql + +sub getDiskPercent +{ + my $command = "df ."; + my $df = qx( $command ); + my $space = -1; + if ( $df =~ /\s(\d+)%/ms ) + { + $space = $1; + } + return( $space ); +} +sub getDiskBlocks +{ + my $command = "df ."; + my $df = qx( $command ); + my $space = -1; + if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) + { + $space = $1; + } + return( $space ); +} +sub getLoad +{ + my $command = "uptime ."; + my $uptime = qx( $command ); + my $load = -1; + if ( $uptime =~ /load average:\s+([\d.]+)/ms ) + { + $load = $1; + Info( "Load: $load" ); + } + return( $load ); +} + +# +# More or less replicates the equivalent PHP function +# +sub strtotime +{ + my $dt_str = shift; + return( Date::Manip::UnixDate( $dt_str, '%s' ) ); +} + +# +# More or less replicates the equivalent PHP function +# +sub str_repeat +{ + my $string = shift; + my $count = shift; + return( ${string}x${count} ); +} + +# Formats a date into MySQL format +sub DateTimeToSQL +{ + my $dt_str = shift; + my $dt_val = strtotime( $dt_str ); + if ( !$dt_val ) + { + Error( "Unable to parse date string '$dt_str'\n" ); + return( undef ); + } + return( strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) ); +} + +1; +__END__ +# Below is stub documentation for your module. You'd better edit it! + +=head1 NAME + +ZoneMinder::Database - Perl extension for blah blah blah + +=head1 SYNOPSIS + + use ZoneMinder::Filter; + blah blah blah + +=head1 DESCRIPTION + +Stub documentation for ZoneMinder, created by h2xs. It looks like the +author of the extension was negligent enough to leave the stub +unedited. + +Blah blah blah. + +=head2 EXPORT + +None by default. + + + +=head1 SEE ALSO + +Mention other useful documentation such as the documentation of +related modules or operating system documentation (such as man pages +in UNIX), or any relevant external documentation such as RFCs or +standards. + +If you have a mailing list set up for your module, mention it here. + +If you have a web site set up for your module, mention it here. + +=head1 AUTHOR + +Philip Coombes, Ephilip.coombes@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2008 Philip Coombes + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 1a446123a..a101edf3a 100755 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -60,10 +60,11 @@ use constant START_DELAY => 5; # How long to wait before starting @EXTRA_PERL_LIB@ use ZoneMinder; +require ZoneMinder::Filter; use DBI; use POSIX; use Time::HiRes qw/gettimeofday/; -use Date::Manip; +#use Date::Manip; use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use autouse 'Data::Dumper'=>qw(Dumper); @@ -137,36 +138,6 @@ my $filter_parm = ""; my $version = 0; # -# More or less replicates the equivalent PHP function -# -sub strtotime -{ - my $dt_str = shift; - return( UnixDate( $dt_str, '%s' ) ); -} - -# -# More or less replicates the equivalent PHP function -# -sub str_repeat -{ - my $string = shift; - my $count = shift; - return( ${string}x${count} ); -} - -# Formats a date into MySQL format -sub DateTimeToSQL -{ - my $dt_str = shift; - my $dt_val = strtotime( $dt_str ); - if ( !$dt_val ) - { - Error( "Unable to parse date string '$dt_str'\n" ); - return( undef ); - } - return( strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) ); -} GetOptions( 'filter=s' =>\$filter_parm, @@ -225,43 +196,6 @@ while( 1 ) sleep( $delay ); } -sub getDiskPercent -{ - my $command = "df ."; - my $df = qx( $command ); - my $space = -1; - if ( $df =~ /\s(\d+)%/ms ) - { - $space = $1; - } - return( $space ); -} - -sub getDiskBlocks -{ - my $command = "df ."; - my $df = qx( $command ); - my $space = -1; - if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) - { - $space = $1; - } - return( $space ); -} - -sub getLoad -{ - my $command = "uptime ."; - my $uptime = qx( $command ); - my $load = -1; - if ( $uptime =~ /load average:\s+([\d.]+)/ms ) - { - $load = $1; - Info( "Load: $load" ); - } - return( $load ); -} - sub getFilters { my $filter_name = shift; @@ -299,309 +233,18 @@ sub getFilters } 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 $filter_expr = jsonDecode( $db_filter->{Query} ); - my $sql = "SELECT E.Id, - E.MonitorId, - M.Name as MonitorName, - M.DefaultRate, - M.DefaultScale, - E.Name, - E.Cause, - E.Notes, - E.StartTime, - unix_timestamp(E.StartTime) as Time, - E.Length, - E.Frames, - E.AlarmFrames, - E.TotScore, - E.AvgScore, - E.MaxScore, - E.Archived, - E.Videoed, - E.Uploaded, - E.Emailed, - E.Messaged, - E.Executed - FROM Events as E - INNER JOIN Monitors as M on M.Id = E.MonitorId - "; - $db_filter->{Sql} = ''; + my $sql = $filter->Sql(); - if ( $filter_expr->{terms} ) - { - for ( my $i = 0; $i < @{$filter_expr->{terms}}; $i++ ) - { - if ( exists($filter_expr->{terms}[$i]->{cnj}) ) - { - $db_filter->{Sql} .= " ".$filter_expr->{terms}[$i]->{cnj}." "; - } - if ( exists($filter_expr->{terms}[$i]->{obr}) ) - { - $db_filter->{Sql} .= " ".str_repeat( "(", $filter_expr->{terms}[$i]->{obr} )." "; - } - my $value = $filter_expr->{terms}[$i]->{val}; - my @value_list; - if ( $filter_expr->{terms}[$i]->{attr} ) - { - if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) - { - my ( $temp_attr_name ) = $filter_expr->{terms}[$i]->{attr} =~ /^Monitor(.+)$/; - $db_filter->{Sql} .= "M.".$temp_attr_name; - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'ServerId' ) - { - $db_filter->{Sql} .= "M.ServerId"; - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) - { - $db_filter->{Sql} .= "E.StartTime"; - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) - { - $db_filter->{Sql} .= "to_days( E.StartTime )"; - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) - { - $db_filter->{Sql} .= "extract( hour_second from E.StartTime )"; - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Weekday' ) - { - $db_filter->{Sql} .= "weekday( E.StartTime )"; - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskPercent' ) - { - $db_filter->{Sql} .= "zmDiskPercent"; - $db_filter->{HasDiskPercent} = !undef; - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskBlocks' ) - { - $db_filter->{Sql} .= "zmDiskBlocks"; - $db_filter->{HasDiskBlocks} = !undef; - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'SystemLoad' ) - { - $db_filter->{Sql} .= "zmSystemLoad"; - $db_filter->{HasSystemLoad} = !undef; - } - else - { - $db_filter->{Sql} .= "E.".$filter_expr->{terms}[$i]->{attr}; - } - - ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; - foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) - { - if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ ) - { - $value = "'$temp_value'"; - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'ServerId' ) { - if ( $temp_value eq 'ZM_SERVER_ID' ) { - $value = "'$Config{ZM_SERVER_ID}'"; - } else { - $value = "'$temp_value'"; - } - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Name' - || $filter_expr->{terms}[$i]->{attr} eq 'Cause' - || $filter_expr->{terms}[$i]->{attr} eq 'Notes' - ) - { - $value = "'$temp_value'"; - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' ) - { - $value = DateTimeToSQL( $temp_value ); - if ( !$value ) - { - Error( "Error parsing date/time '$temp_value', " - ."skipping filter '$db_filter->{Name}'\n" ); - next FILTER; - } - $value = "'$value'"; - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' ) - { - $value = DateTimeToSQL( $temp_value ); - if ( !$value ) - { - Error( "Error parsing date/time '$temp_value', " - ."skipping filter '$db_filter->{Name}'\n" ); - next FILTER; - } - $value = "to_days( '$value' )"; - } - elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' ) - { - $value = DateTimeToSQL( $temp_value ); - if ( !$value ) - { - Error( "Error parsing date/time '$temp_value', " - ."skipping filter '$db_filter->{Name}'\n" ); - next FILTER; - } - $value = "extract( hour_second from '$value' )"; - } - else - { - $value = $temp_value; - } - push( @value_list, $value ); - } - } - if ( $filter_expr->{terms}[$i]->{op} ) - { - if ( $filter_expr->{terms}[$i]->{op} eq '=~' ) - { - $db_filter->{Sql} .= " regexp $value"; - } - elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) - { - $db_filter->{Sql} .= " not regexp $value"; - } - elsif ( $filter_expr->{terms}[$i]->{op} eq '=[]' ) - { - $db_filter->{Sql} .= " in (".join( ",", @value_list ).")"; - } - elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' ) - { - $db_filter->{Sql} .= " not in (".join( ",", @value_list ).")"; - } - else - { - $db_filter->{Sql} .= " ".$filter_expr->{terms}[$i]->{op}." $value"; - } - } - if ( exists($filter_expr->{terms}[$i]->{cbr}) ) - { - $db_filter->{Sql} .= " ".str_repeat( ")", $filter_expr->{terms}[$i]->{cbr} )." "; - } - } - } - if ( $db_filter->{Sql} ) - { - if ( $db_filter->{AutoMessage} ) - { - # Include all events, including events that are still ongoing - # and have no EndTime yet - $sql .= " and ( ".$db_filter->{Sql}." )"; - } - else - { - # Only include closed events (events with valid EndTime) - $sql .= " where not isnull(E.EndTime) and ( ".$db_filter->{Sql}." )"; - } - } - my @auto_terms; - if ( $db_filter->{AutoArchive} ) - { - push( @auto_terms, "E.Archived = 0" ) - } - if ( $db_filter->{AutoVideo} ) - { - push( @auto_terms, "E.Videoed = 0" ) - } - if ( $db_filter->{AutoUpload} ) - { - push( @auto_terms, "E.Uploaded = 0" ) - } - if ( $db_filter->{AutoEmail} ) - { - push( @auto_terms, "E.Emailed = 0" ) - } - if ( $db_filter->{AutoMessage} ) - { - push( @auto_terms, "E.Messaged = 0" ) - } - if ( $db_filter->{AutoExecute} ) - { - push( @auto_terms, "E.Executed = 0" ) - } - if ( @auto_terms ) - { - $sql .= " and ( ".join( " or ", @auto_terms )." )"; - } - if ( !$filter_expr->{sort_field} ) - { - $filter_expr->{sort_field} = 'StartTime'; - $filter_expr->{sort_asc} = 0; - } - my $sort_column = ''; - if ( $filter_expr->{sort_field} eq 'Id' ) - { - $sort_column = "E.Id"; - } - elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) - { - $sort_column = "M.Name"; - } - elsif ( $filter_expr->{sort_field} eq 'Name' ) - { - $sort_column = "E.Name"; - } - elsif ( $filter_expr->{sort_field} eq 'StartTime' ) - { - $sort_column = "E.StartTime"; - } - elsif ( $filter_expr->{sort_field} eq 'Secs' ) - { - $sort_column = "E.Length"; - } - elsif ( $filter_expr->{sort_field} eq 'Frames' ) - { - $sort_column = "E.Frames"; - } - elsif ( $filter_expr->{sort_field} eq 'AlarmFrames' ) - { - $sort_column = "E.AlarmFrames"; - } - elsif ( $filter_expr->{sort_field} eq 'TotScore' ) - { - $sort_column = "E.TotScore"; - } - elsif ( $filter_expr->{sort_field} eq 'AvgScore' ) - { - $sort_column = "E.AvgScore"; - } - elsif ( $filter_expr->{sort_field} eq 'MaxScore' ) - { - $sort_column = "E.MaxScore"; - } - else - { - $sort_column = "E.StartTime"; - } - my $sort_order = $filter_expr->{sort_asc}?"asc":"desc"; - $sql .= " order by ".$sort_column." ".$sort_order; - if ( $filter_expr->{limit} ) - { - $sql .= " limit 0,".$filter_expr->{limit}; - } - Debug( "SQL:$sql\n" ); - $db_filter->{Sql} = $sql; - if ( $db_filter->{AutoExecute} ) - { - my $script = $db_filter->{AutoExecuteCmd}; - $script =~ s/\s.*$//; - if ( !-e $script ) - { - Error( "Auto execute script '$script' not found, " - ."skipping filter '$db_filter->{Name}'\n" ); - next FILTER; - - } - elsif ( !-x $script ) - { - Error( "Auto execute script '$script' not executable, " - ."skipping filter '$db_filter->{Name}'\n" ); - next FILTER; - } - } - push( @filters, $db_filter ); + if ( ! $sql ) { + Error( "Error parsing Sql. skipping filter '$db_filter->{Name}'\n" ); + next FILTER; + } + push( @filters, $filter ); } $sth->finish(); - + Debug( "Got " . @filters . " filters" ); return( \@filters ); } @@ -619,37 +262,11 @@ sub checkFilter ($filter->{AutoExecute}?", execute":""). "\n" ); - my $sql = $filter->{Sql}; - if ( $filter->{HasDiskPercent} ) - { - my $disk_percent = getDiskPercent(); - $sql =~ s/zmDiskPercent/$disk_percent/g; - } - if ( $filter->{HasDiskBlocks} ) - { - my $disk_blocks = getDiskBlocks(); - $sql =~ s/zmDiskBlocks/$disk_blocks/g; - } - if ( $filter->{HasSystemLoad} ) - { - my $load = getLoad(); - $sql =~ s/zmSystemLoad/$load/g; - } - - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute(); - if ( !$res ) - { - Error( "Can't execute filter '$sql', ignoring: ".$sth->errstr() ); - return; - } - - while( my $event = $sth->fetchrow_hashref() ) - { + 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" ); @@ -658,7 +275,7 @@ sub checkFilter 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() ); + or Error( "Can't execute '$sql': ".$sth->errstr() ); } if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { @@ -730,7 +347,6 @@ sub checkFilter } } } - $sth->finish(); } sub generateVideo diff --git a/scripts/zmvideo.pl.in b/scripts/zmvideo.pl.in index fd2005b16..754213af6 100644 --- a/scripts/zmvideo.pl.in +++ b/scripts/zmvideo.pl.in @@ -27,7 +27,7 @@ zmvideo.pl - ZoneMinder Video Creation Script =head1 SYNOPSIS - zmvideo.pl -e ,--event= [--format ] + zmvideo.pl [ -e ,--event= | --filter= ] [--format ] [--rate=] [--scale=] [--fps=] @@ -40,15 +40,18 @@ This script is used to create MPEG videos of events for the web pages or as email attachments. =head1 OPTIONS - - -e, --event= - What event to create the video for - -f, --format= - What format to create the video in, default is mpg. For ffmpeg only. - -r, --rate= - Relative rate, 1 = realtime, 2 = double speed, 0.5 = half speed etc. - -s, --scale= - Scale, 1 = normal, 2 = double size, 0.5 = half size etc. - -F, --fps= - Absolute frame rate, in frames per second - -S, --size= - Absolute video size, WxH or other specification supported by ffmpeg - -o, --overwrite - Whether to overwrite an existing file, off by default. - -v, --version - Outputs the currently installed version of ZoneMinder + -c[=filename], --concat[=filename] - When creating videos for multiple events, create a concatenated video as well. + - If not specified, filename is taken from filter name. + -e, --event= - What event to create the video for + --filter_name= - The name of a saved filter to generate a video for all events returned by it. + --filter_id= - The id of a saved filter to generate a video for all events returned by it. + -f, --format= - What format to create the video in, default is mpg. For ffmpeg only. + -r, --rate= - Relative rate, 1 = realtime, 2 = double speed, 0.5 = half speed etc. + -s, --scale= - Scale, 1 = normal, 2 = double size, 0.5 = half size etc. + -F, --fps= - Absolute frame rate, in frames per second + -S, --size= - Absolute video size, WxH or other specification supported by ffmpeg + -o, --overwrite - Whether to overwrite an existing file, off by default. + -v, --version - Outputs the currently installed version of ZoneMinder =cut use strict; @@ -62,10 +65,13 @@ use bytes; @EXTRA_PERL_LIB@ use ZoneMinder; +require ZoneMinder::Filter; +require ZoneMinder::Event; use DBI; use autouse 'Data::Dumper'=>qw(Dumper); use POSIX qw(strftime); use Getopt::Long qw(:config no_ignore_case ); +use Cwd; use autouse 'Pod::Usage'=>qw(pod2usage); $| = 1; @@ -77,6 +83,9 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); my $event_id; +my $concat_name; +my $filter_name; +my $filter_id; my $format = 'mpg'; my $rate = ''; my $scale = ''; @@ -95,7 +104,10 @@ for ( my $i = 0; $i < @formats; $i++ ) } GetOptions( - 'event=i' =>\$event_id, + 'concat|c:s' =>\$concat_name, + 'event|e=i' =>\$event_id, + 'filter_name=s' =>\$filter_name, + 'filter_id=i' =>\$filter_id, 'format|f=s' =>\$format, 'rate|r=f' =>\$rate, 'scale|s=f' =>\$scale, @@ -110,9 +122,9 @@ if ( $version ) { exit(0); } -if ( !$event_id || $event_id < 0 ) +if ( !( $filter_id or $filter_name or $event_id ) || $event_id < 0 ) { - print( STDERR "Please give a valid event id\n" ); + print( STDERR "Please give a valid event id or filter name\n" ); pod2usage(-exitstatus => -1); } @@ -164,128 +176,86 @@ $size = $detaint_size; my $dbh = zmDbConnect(); -my @filters; +my $cwd = getcwd; + +my $video_name; +my @event_ids; +if ( $event_id ) { + @event_ids = ( $event_id ); + +} elsif ( $filter_name or $filter_id ) { + my $Filter = ZoneMinder::Filter->find_one( + ($filter_name ? ( Name => $filter_name ) : () ), + ($filter_id ? ( Id => $filter_name ) : () ), + ); + if ( ! $Filter ) { + Fatal("Filter $filter_name $filter_id not found."); + } + @event_ids = map { $_->{Id} }$Filter->Execute(); + Fatal( "No events found for $filter_name") if ! @event_ids; + $concat_name = $filter_name if $concat_name eq ''; +} + my $sql = " SELECT max(F.Delta)-min(F.Delta) as FullLength, - E.*, - unix_timestamp(E.StartTime) as Time, - M.Name as MonitorName, - M.Width as MonitorWidth, - M.Height as MonitorHeight, - M.Palette - FROM Frames as F - INNER JOIN Events as E on F.EventId = E.Id - INNER JOIN Monitors as M on E.MonitorId = M.Id - WHERE EventId = '$event_id' - GROUP BY F.EventId" -; -my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); -my $res = $sth->execute() - or Fatal( "Can't execute: ".$sth->errstr() ); -my $event = $sth->fetchrow_hashref(); -$sth->finish(); -my $event_path = getEventPath( $event ); -chdir( $event_path ); -( my $video_name = $event->{Name} ) =~ s/\s/_/g; + E.*, + unix_timestamp(E.StartTime) as Time, + M.Name as MonitorName, + M.Width as MonitorWidth, + M.Height as MonitorHeight, + M.Palette + FROM Frames as F + INNER JOIN Events as E on F.EventId = E.Id + INNER JOIN Monitors as M on E.MonitorId = M.Id + WHERE EventId = ? + GROUP BY F.EventId" + ; +my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); -my @file_parts; -if ( $rate ) -{ - my $file_rate = $rate; - $file_rate =~ s/\./_/; - $file_rate =~ s/_00//; - $file_rate =~ s/(_\d+)0+$/$1/; - $file_rate = 'r'.$file_rate; - push( @file_parts, $file_rate ); -} -elsif ( $fps ) -{ - my $file_fps = $fps; - $file_fps =~ s/\./_/; - $file_fps =~ s/_00//; - $file_fps =~ s/(_\d+)0+$/$1/; - $file_fps = 'R'.$file_fps; - push( @file_parts, $file_fps ); -} - -if ( $scale ) -{ - my $file_scale = $scale; - $file_scale =~ s/\./_/; - $file_scale =~ s/_00//; - $file_scale =~ s/(_\d+)0+$/$1/; - $file_scale = 's'.$file_scale; - push( @file_parts, $file_scale ); -} -elsif ( $size ) -{ - my $file_size = 'S'.$size; - push( @file_parts, $file_size ); -} -my $video_file = "$video_name-".$file_parts[0]."-".$file_parts[1].".$format"; - -if ( $overwrite || !-s $video_file ) -{ - Info( "Creating video file $video_file for event $event->{Id}\n" ); - - my $frame_rate = sprintf( "%.2f", $event->{Frames}/$event->{FullLength} ); - if ( $rate ) - { - if ( $rate != 1.0 ) - { - $frame_rate *= $rate; - } - } - elsif ( $fps ) - { - $frame_rate = $fps; - } - - my $width = $event->{MonitorWidth}; - my $height = $event->{MonitorHeight}; - my $video_size = " ${width}x${height}"; - - if ( $scale ) - { - if ( $scale != 1.0 ) - { - $width = int($width*$scale); - $height = int($height*$scale); - $video_size = " ${width}x${height}"; - } - } - elsif ( $size ) - { - $video_size = $size; - } - - my $command = $Config{ZM_PATH_FFMPEG} - ." -y -r $frame_rate " - .$Config{ZM_FFMPEG_INPUT_OPTIONS} - ." -i %0" - .$Config{ZM_EVENT_IMAGE_DIGITS} - ."d-capture.jpg -s $video_size " - .$Config{ZM_FFMPEG_OUTPUT_OPTIONS} - ." '$video_file' > ffmpeg.log 2>&1" - ; - Debug( $command."\n" ); - my $output = qx($command); - - my $status = $? >> 8; - if ( $status ) - { - Error( "Unable to generate video, check " - .$event_path."/ffmpeg.log for details" - ); - exit( -1 ); - } - - Info( "Finished $video_file\n" ); -} -else -{ - Info( "Video file $video_file already exists for event $event->{Id}\n" ); +my @video_files; +foreach my $event_id ( @event_ids ) { + + my $res = $sth->execute( $event_id ) + or Fatal( "Can't execute: ".$sth->errstr() ); + my $event = $sth->fetchrow_hashref(); + + my $Event = new ZoneMinder::Event( $$event{Id}, $event ); + my $video_file = $Event->GenerateVideo( $rate, $fps, $scale, $size, $overwrite, $format ); + if ( $video_file ) { + push @video_files, $video_file; + } +} # end foreach event_id + +if ( $concat_name ) { + ($cwd) = $cwd =~ /(.*)/; # detaint + chdir( $cwd ); + ($concat_name ) = $concat_name =~ /([\-A-Za-z0-9_\.]*)/; + my $concat_list_file = "/tmp/$concat_name.concat.lst"; + + my $video_file = $concat_name . '.'. $detaint_format; + + open( my $fd, '>', $concat_list_file ) or die "Can't open $concat_list_file: $!"; + foreach ( @video_files ) { + print $fd "file '$_'\n"; + } + close $fd; + my $command = $Config{ZM_PATH_FFMPEG} + . " -f concat -i $concat_list_file -c copy " + ." '$video_file' > ffmpeg.log 2>&1" + ; + Debug( $command."\n" ); + my $output = qx($command); + + my $status = $? >> 8; + + unlink $concat_list_file; + if ( $status ) + { + Error( "Unable to generate video, check /ffmpeg.log for details"); + exit(-1); + } + } +#unlink $input_file_list; #print( STDOUT $event->{MonitorId}.'/'.$event->{Id}.'/'.$video_file."\n" ); -print( STDOUT $video_file."\n" ); +#print( STDOUT $video_file."\n" ); exit( 0 );