diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index da0d9b8c4..b46d5c0bd 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -232,6 +232,7 @@ CREATE TABLE `Filters` ( `AutoExecute` tinyint(3) unsigned NOT NULL default '0', `AutoExecuteCmd` tinytext, `AutoDelete` tinyint(3) unsigned NOT NULL default '0', + `UpdateDiskSpace` tinyint(3) unsigned NOT NULL default '0', `Background` tinyint(1) unsigned NOT NULL default '0', `Concurrent` tinyint(1) unsigned NOT NULL default '0', PRIMARY KEY (`Id`), @@ -289,6 +290,7 @@ CREATE TABLE `Logs` ( ) ENGINE=@ZM_MYSQL_ENGINE@; CREATE INDEX `Logs_TimeKey_idx` ON `Logs` (`TimeKey`); +CREATE INDEX `Logs_Level_idx` ON `Logs` (`Level`); -- -- Table structure for table `Manufacturers` -- @@ -382,6 +384,8 @@ CREATE TABLE `Monitors` ( `Deinterlacing` int(10) unsigned NOT NULL default '0', `SaveJPEGs` TINYINT NOT NULL DEFAULT '3' , `VideoWriter` TINYINT NOT NULL DEFAULT '0', + `OutputCodec` enum('h264','mjpeg'), + `OutputContainer` enum('mp4','mkv'), `EncoderParameters` TEXT, `RecordAudio` TINYINT NOT NULL DEFAULT '0', `RTSPDescribe` tinyint(1) unsigned, @@ -632,7 +636,7 @@ insert into Users VALUES (NULL,'admin',password('admin'),'',1,'View','Edit','Edi -- -- Add a sample filter to purge the oldest 100 events when the disk is 95% full -- -insert into Filters values (NULL,'PurgeWhenFull','{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}',0,0,0,0,0,0,'',1,1,0); +insert into Filters values (NULL,'PurgeWhenFull','{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}',0,0,0,0,0,0,'',1,0,1,0); -- -- Add in some sample control protocol definitions @@ -760,6 +764,21 @@ CREATE TABLE Maps ( `ParentId` int(1) unsigned, PRIMARY KEY (`Id`) ); + +CREATE TABLE MontageLayouts ( + `Id` int(10) unsigned NOT NULL auto_increment, + `Name` TEXT NOT NULL, + `Positions` LONGTEXT, + /*`Positions` JSON,*/ + PRIMARY KEY (`Id`) +); + +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('Freeform', '{ "default":{"float":"left","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('2 Wide', '{ "default":{"float":"left", "width":"49%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('3 Wide', '{ "default":{"float":"left", "width":"33%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('4 Wide', '{ "default":{"float":"left", "width":"24.5%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); +INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('5 Wide', '{ "default":{"float":"left", "width":"19%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); + -- -- Apply the initial configuration -- diff --git a/db/zm_update-1.31.11.sql b/db/zm_update-1.31.11.sql new file mode 100644 index 000000000..de17d85d9 --- /dev/null +++ b/db/zm_update-1.31.11.sql @@ -0,0 +1,69 @@ +-- +-- Add UpdateDiskSpace action to Filters +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'UpdateDiskSpace' + ) > 0, +"SELECT 'Column UpdateDiskSpace already exists in Filters'", +"ALTER TABLE Filters ADD `UpdateDiskSpace` tinyint(3) unsigned NOT NULL default '0' AFTER `AutoDelete`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; +-- +-- Update Logs table to have some Indexes +-- +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.STATISTICS + WHERE table_name = 'Logs' + AND table_schema = DATABASE() + AND index_name = 'Logs_TimeKey_idx' + ) > 0, +"SELECT 'Logs_TimeKey_idx already exists on Logs table'", +"CREATE INDEX `Logs_TimeKey_idx` ON `Logs` (`TimeKey`)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.STATISTICS + WHERE table_name = 'Logs' + AND table_schema = DATABASE() + AND index_name = 'Logs_Level_idx' + ) > 0, +"SELECT 'Logs_Level_idx already exists on Logs table'", +"CREATE INDEX `Logs_Level_idx` ON `Logs` (`Level`)" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'OutputCodec' + ) > 0, +"SELECT 'Column OutputCodec already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `OutputCodec` enum('h264','mjpeg') AFTER `VideoWriter`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'OutputContainer' + ) > 0, +"SELECT 'Column OutputContainer already exists in Monitors'", +"ALTER TABLE `Monitors` ADD `OutputContainer` enum('mp4','mkv') AFTER `OutputCodec`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.31.12.sql b/db/zm_update-1.31.12.sql new file mode 100644 index 000000000..72dee28ce --- /dev/null +++ b/db/zm_update-1.31.12.sql @@ -0,0 +1,67 @@ +-- +-- This adds Manufacturers and Models +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.TABLES + WHERE table_name = 'MontageLayouts' + AND table_schema = DATABASE() + ) > 0, + "SELECT 'MontageLayouts table exists'", + " + CREATE TABLE MontageLayouts ( + `Id` int(10) unsigned NOT NULL auto_increment, + `Name` TEXT NOT NULL, + `Positions` LONGTEXT, + PRIMARY KEY (`Id`) +); +" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +DELETE FROM MontageLayouts WHERE Name IN ('Freeform','2 Wide','3 Wide','4 Wide','5 Wide'); + +SET @s = ( SELECT IF( + (SELECT COUNT(*) FROM MontageLayouts WHERE Name='Freeform') > 0, + "SELECT 'Freeform already in layouts'", +'INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES (\'Freeform\', \'{"default":{"float":"left","position":"relative","left":"0px","right":"0px","top":"0px","bottom":"0px"}}\');' +) ); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = ( SELECT IF( + (SELECT COUNT(*) FROM MontageLayouts WHERE Name='2 Wide') > 0, +"SELECT '2 Wide already in layouts'", +'INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES (\'2 Wide\', \'{"default":{"float":"left","position":"relative","width":"49%","left":"0px","right":"0px","top":"0px","bottom":"0px"}}\');' +) ); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = ( SELECT IF( + (SELECT COUNT(*) FROM MontageLayouts WHERE Name='3 Wide') > 0, + "SELECT '3 Wide already in layouts'", +'INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES (\'3 Wide\', \'{"default":{"float":"left","position":"relative","width":"33%","left":"0px","right":"0px","top":"0px","bottom":"0px"}}\');' +) ); +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = ( SELECT IF( + (SELECT COUNT(*) FROM MontageLayouts WHERE Name='4 Wide') > 0, + "SELECT '4 Wide already in layouts'", +'INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES (\'4 Wide\', \'{"default":{"float":"left","position":"relative","width":"24.5%","left":"0px","right":"0px","top":"0px","bottom":"0px"}}\');' +) ); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = ( SELECT IF( + (SELECT COUNT(*) FROM MontageLayouts WHERE Name='5 Wide') > 0, + "SELECT '5 Wide already in layouts'", + 'INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES (\'5 Wide\', \'{"default":{"float":"left","position":"relative","width":"19%"}}\' );' +) ); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/distros/ubuntu1604/rules b/distros/ubuntu1604/rules index 6bc3dffb5..10489432b 100755 --- a/distros/ubuntu1604/rules +++ b/distros/ubuntu1604/rules @@ -27,7 +27,8 @@ override_dh_auto_configure: -DZM_CGIDIR="/usr/lib/zoneminder/cgi-bin" \ -DZM_DIR_EVENTS="/var/cache/zoneminder/events" \ -DZM_DIR_IMAGES="/var/cache/zoneminder/images" \ - -DZM_PATH_ZMS="/zm/cgi-bin/nph-zms" + -DZM_PATH_ZMS="/zm/cgi-bin/nph-zms" \ + -DZM_NO_LIBVLC=1 override_dh_clean: dh_clean $(MANPAGES1) diff --git a/distros/ubuntu1604/zoneminder.dirs b/distros/ubuntu1604/zoneminder.dirs index 56dd56e6b..08840aef4 100644 --- a/distros/ubuntu1604/zoneminder.dirs +++ b/distros/ubuntu1604/zoneminder.dirs @@ -4,5 +4,6 @@ var/cache/zoneminder/events var/cache/zoneminder/images var/cache/zoneminder/temp usr/share/zoneminder/db +usr/share/zoneminder/www/cache etc/zm/ etc/zm/conf.d diff --git a/distros/ubuntu1604/zoneminder.tmpfile b/distros/ubuntu1604/zoneminder.tmpfile index ef68288ba..f23ca55b3 100644 --- a/distros/ubuntu1604/zoneminder.tmpfile +++ b/distros/ubuntu1604/zoneminder.tmpfile @@ -1,3 +1,4 @@ d /var/run/zm 0755 www-data www-data d /tmp/zm 0755 www-data www-data d /var/tmp/zm 0755 www-data www-data +d /usr/share/zoneminder/www/cache 0755 www-data www-data diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index d2b444ef4..f69e95b18 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -115,7 +115,7 @@ BEGIN { , $Config{ZM_DB_USER} , $Config{ZM_DB_PASS} ) or croak( "Can't connect to db" ); - my $sql = 'select * from Config'; + my $sql = 'SELECT Name,Value FROM Config'; my $sth = $dbh->prepare_cached( $sql ) or croak( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or croak( "Can't execute: ".$sth->errstr() ); while( my $config = $sth->fetchrow_hashref() ) { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index 8d752600d..19a5f8662 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -205,6 +205,30 @@ sub zmDbGetMonitorAndControl { return( $monitor ); } +sub start_transaction { + #my ( $caller, undef, $line ) = caller; +#$openprint::log->debug("Called start_transaction from $caller : $line"); + my $d = shift; + $d = $dbh if ! $d; + my $ac = $d->{AutoCommit}; + $d->{AutoCommit} = 0; + return $ac; +} # end sub start_transaction + +sub end_transaction { + #my ( $caller, undef, $line ) = caller; +#$openprint::log->debug("Called end_transaction from $caller : $line"); + my ( $d, $ac ) = @_; +if ( ! defined $ac ) { + Error("Undefined ac"); +} + $d = $dbh if ! $d; + if ( $ac ) { + #$log->debug("Committing"); + $d->commit(); + } # end if + $d->{AutoCommit} = $ac; +} # end sub end_transaction 1; __END__ # Below is stub documentation for your module. You'd better edit it! diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index fbb249773..2fc590977 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -48,9 +48,38 @@ use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); require Date::Parse; -use vars qw/ $table $primary_key /; +use vars qw/ $table $primary_key %fields $serial @identified_by/; $table = 'Events'; -$primary_key = 'Id'; +@identified_by = ('Id'); +$serial = $primary_key = 'Id'; +%fields = map { $_, $_ } qw( + Id + MonitorId + StorageId + Name + Cause + StartTime + EndTime + Width + Height + Length + Frames + AlarmFrames + DefaultVideo + TotScore + AvgScore + MaxScore + Archived + Videoed + Uploaded + Emailed + Messaged + Executed + Notes + StateId + Orientation + DiskSpace +); use POSIX; @@ -359,14 +388,16 @@ sub age { return $_[0]{age}; } -sub DiskUsage { +sub DiskSpace { if ( @_ > 1 ) { - $_[0]{DiskUsage} = $_[1]; + Debug("Cleared DiskSpace, was $_[0]{DiskSpace}"); + $_[0]{DiskSpace} = $_[1]; } - if ( ! defined $_[0]{DiskUsage} ) { + if ( ! defined $_[0]{DiskSpace} ) { my $size = 0; File::Find::find( { wanted=>sub { $size += -f $_ ? -s _ : 0 }, untaint=>1 }, $_[0]->Path() ); - $_[0]{DiskUsage} = $size; + $_[0]{DiskSpace} = $size; + Debug("DiskSpace for event $_[0]{Id} at $_[0]{Path} Updated to $size bytes"); } } diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 545bfaaad..02209a55c 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -125,7 +125,7 @@ sub Execute { push @results, $event; } $sth->finish(); - Debug("Loaded " . @results . " events for filter $_[0]{Name} using query ($sql)"); + Debug('Loaded ' . @results . " events for filter $_[0]{Name} using query ($sql)"); return @results; } @@ -147,38 +147,59 @@ sub Sql { foreach my $term ( @{$filter_expr->{terms}} ) { if ( exists($term->{cnj}) ) { - $self->{Sql} .= " ".$term->{cnj}." "; + $self->{Sql} .= ' '.$term->{cnj}." "; } if ( exists($term->{obr}) ) { - $self->{Sql} .= " ".str_repeat( "(", $term->{obr} )." "; + $self->{Sql} .= ' '.str_repeat( "(", $term->{obr} )." "; } my $value = $term->{val}; my @value_list; if ( $term->{attr} ) { if ( $term->{attr} =~ /^Monitor/ ) { my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/; - $self->{Sql} .= "M.".$temp_attr_name; + $self->{Sql} .= 'M.'.$temp_attr_name; } elsif ( $term->{attr} =~ /^Server/ ) { - $self->{Sql} .= "M.".$term->{attr}; + $self->{Sql} .= 'M.'.$term->{attr}; + +# StartTime options } elsif ( $term->{attr} eq 'DateTime' ) { - $self->{Sql} .= "E.StartTime"; + $self->{Sql} .= 'E.StartTime'; + } elsif ( $term->{attr} eq 'StartDateTime' ) { + $self->{Sql} .= 'E.StartTime'; } elsif ( $term->{attr} eq 'Date' ) { - $self->{Sql} .= "to_days( E.StartTime )"; + $self->{Sql} .= 'to_days( E.StartTime )'; + } elsif ( $term->{attr} eq 'StartDate' ) { + $self->{Sql} .= 'to_days( E.StartTime )'; } elsif ( $term->{attr} eq 'Time' ) { $self->{Sql} .= "extract( hour_second from E.StartTime )"; } elsif ( $term->{attr} eq 'Weekday' ) { $self->{Sql} .= "weekday( E.StartTime )"; + +# EndTIme options + } elsif ( $term->{attr} eq 'EndDateTime' ) { + $self->{Sql} .= 'E.EndTime'; + } elsif ( $term->{attr} eq 'EndDate' ) { + $self->{Sql} .= 'to_days( E.EndTime )'; + } elsif ( $term->{attr} eq 'EndTime' ) { + $self->{Sql} .= "extract( hour_second from E.EndTime )"; + } elsif ( $term->{attr} eq 'EndWeekday' ) { + $self->{Sql} .= "weekday( E.EndTime )"; + +# + } elsif ( $term->{attr} eq 'DiskSpace' ) { + $self->{Sql} .= 'E.DiskSpace'; + $self->{HasDiskPercent} = !undef; } elsif ( $term->{attr} eq 'DiskPercent' ) { - $self->{Sql} .= "zmDiskPercent"; + $self->{Sql} .= 'zmDiskPercent'; $self->{HasDiskPercent} = !undef; } elsif ( $term->{attr} eq 'DiskBlocks' ) { - $self->{Sql} .= "zmDiskBlocks"; + $self->{Sql} .= 'zmDiskBlocks'; $self->{HasDiskBlocks} = !undef; } elsif ( $term->{attr} eq 'SystemLoad' ) { - $self->{Sql} .= "zmSystemLoad"; + $self->{Sql} .= 'zmSystemLoad'; $self->{HasSystemLoad} = !undef; } else { - $self->{Sql} .= "E.".$term->{attr}; + $self->{Sql} .= 'E.'.$term->{attr}; } ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; @@ -203,7 +224,7 @@ sub Sql { || $term->{attr} eq 'Notes' ) { $value = "'$temp_value'"; - } elsif ( $term->{attr} eq 'DateTime' ) { + } elsif ( $term->{attr} eq 'DateTime' or $term->{attr} eq 'StartDateTime' or $term->{attr} eq 'EndDateTime' ) { $value = DateTimeToSQL( $temp_value ); if ( !$value ) { Error( "Error parsing date/time '$temp_value', " @@ -211,7 +232,7 @@ sub Sql { return; } $value = "'$value'"; - } elsif ( $term->{attr} eq 'Date' ) { + } elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) { $value = DateTimeToSQL( $temp_value ); if ( !$value ) { Error( "Error parsing date/time '$temp_value', " @@ -219,7 +240,7 @@ sub Sql { return; } $value = "to_days( '$value' )"; - } elsif ( $term->{attr} eq 'Time' ) { + } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' or $term->{attr} eq 'EndTime' ) { $value = DateTimeToSQL( $temp_value ); if ( !$value ) { Error( "Error parsing date/time '$temp_value', " @@ -238,16 +259,20 @@ sub Sql { $self->{Sql} .= " regexp $value"; } elsif ( $term->{op} eq '!~' ) { $self->{Sql} .= " not regexp $value"; + } elsif ( $term->{op} eq 'IS' ) { + $self->{Sql} .= " IS $value"; + } elsif ( $term->{op} eq 'IS NOT' ) { + $self->{Sql} .= " IS NOT $value"; } elsif ( $term->{op} eq '=[]' ) { $self->{Sql} .= " in (".join( ",", @value_list ).")"; } elsif ( $term->{op} eq '!~' ) { $self->{Sql} .= " not in (".join( ",", @value_list ).")"; } else { - $self->{Sql} .= " ".$term->{op}." $value"; + $self->{Sql} .= ' '.$term->{op}." $value"; } } # end if has an operator if ( exists($term->{cbr}) ) { - $self->{Sql} .= " ".str_repeat( ")", $term->{cbr} )." "; + $self->{Sql} .= ' '.str_repeat( ")", $term->{cbr} )." "; } } # end foreach term } # end if terms @@ -256,15 +281,15 @@ sub Sql { if ( $self->{AutoMessage} ) { # Include all events, including events that are still ongoing # and have no EndTime yet - $sql .= " and ( ".$self->{Sql}." )"; + $sql .= ' AND ( '.$self->{Sql}.' )'; } else { # Only include closed events (events with valid EndTime) - $sql .= " where not isnull(E.EndTime) and ( ".$self->{Sql}." )"; + $sql .= ' WHERE (E.EndTime IS NOT NULL) AND ( '.$self->{Sql}.' )'; } } my @auto_terms; if ( $self->{AutoArchive} ) { - push @auto_terms, "E.Archived = 0"; + push @auto_terms, 'E.Archived = 0'; } # Don't do this, it prevents re-generation and concatenation. # If the file already exists, then the video won't be re-recreated @@ -284,7 +309,7 @@ sub Sql { push @auto_terms, "E.Executed = 0"; } if ( @auto_terms ) { - $sql .= " and ( ".join( " or ", @auto_terms )." )"; + $sql .= " and ( ".join( ' or ', @auto_terms )." )"; } if ( !$filter_expr->{sort_field} ) { $filter_expr->{sort_field} = 'StartTime'; @@ -292,30 +317,34 @@ sub Sql { } my $sort_column = ''; if ( $filter_expr->{sort_field} eq 'Id' ) { - $sort_column = "E.Id"; + $sort_column = 'E.Id'; } elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) { - $sort_column = "M.Name"; + $sort_column = 'M.Name'; } elsif ( $filter_expr->{sort_field} eq 'Name' ) { - $sort_column = "E.Name"; + $sort_column = 'E.Name'; } elsif ( $filter_expr->{sort_field} eq 'StartTime' ) { - $sort_column = "E.StartTime"; + $sort_column = 'E.StartTime'; + } elsif ( $filter_expr->{sort_field} eq 'EndTime' ) { + $sort_column = 'E.EndTime'; } elsif ( $filter_expr->{sort_field} eq 'Secs' ) { - $sort_column = "E.Length"; + $sort_column = 'E.Length'; } elsif ( $filter_expr->{sort_field} eq 'Frames' ) { - $sort_column = "E.Frames"; + $sort_column = 'E.Frames'; } elsif ( $filter_expr->{sort_field} eq 'AlarmFrames' ) { - $sort_column = "E.AlarmFrames"; + $sort_column = 'E.AlarmFrames'; } elsif ( $filter_expr->{sort_field} eq 'TotScore' ) { - $sort_column = "E.TotScore"; + $sort_column = 'E.TotScore'; } elsif ( $filter_expr->{sort_field} eq 'AvgScore' ) { - $sort_column = "E.AvgScore"; + $sort_column = 'E.AvgScore'; } elsif ( $filter_expr->{sort_field} eq 'MaxScore' ) { - $sort_column = "E.MaxScore"; + $sort_column = 'E.MaxScore'; + } elsif ( $filter_expr->{sort_field} eq 'DiskSpace' ) { + $sort_column = 'E.DiskSpace'; } else { - $sort_column = "E.StartTime"; + $sort_column = 'E.StartTime'; } - my $sort_order = $filter_expr->{sort_asc}?"asc":"desc"; - $sql .= " order by ".$sort_column." ".$sort_order; + 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}; } @@ -325,7 +354,7 @@ sub Sql { } # end sub Sql sub getDiskPercent { - my $command = "df " . ($_[0] ? $_[0] : '.'); + my $command = 'df ' . ($_[0] ? $_[0] : '.'); my $df = qx( $command ); my $space = -1; if ( $df =~ /\s(\d+)%/ms ) { @@ -335,7 +364,7 @@ sub getDiskPercent { } sub getDiskBlocks { - my $command = "df ."; + my $command = 'df .'; my $df = qx( $command ); my $space = -1; if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) { @@ -345,7 +374,7 @@ sub getDiskBlocks { } sub getLoad { - my $command = "uptime ."; + my $command = 'uptime .'; my $uptime = qx( $command ); my $load = -1; if ( $uptime =~ /load average:\s+([\d.]+)/ms ) { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 031a2b7b6..a81a4d87b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -678,6 +678,11 @@ sub Dump { fetch()->logPrint( DEBUG, Data::Dumper->Dump( [ $var ], [ $label ] ) ); } +sub debug { + my $log = shift; + $log->logPrint( DEBUG, @_ ); +} + sub Debug( @ ) { fetch()->logPrint( DEBUG, @_ ); } @@ -685,14 +690,27 @@ sub Debug( @ ) { sub Info( @ ) { fetch()->logPrint( INFO, @_ ); } +sub info { + my $log = shift; + $log->logPrint( INFO, @_ ); +} + sub Warning( @ ) { fetch()->logPrint( WARNING, @_ ); } +sub warn { + my $log = shift; + $log->logPrint( WARNING, @_ ); +} sub Error( @ ) { fetch()->logPrint( ERROR, @_ ); } +sub error { + my $log = shift; + $log->logPrint( ERROR, @_ ); +} sub Fatal( @ ) { fetch()->logPrint( FATAL, @_ ); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm index 63193bccf..44351de65 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -42,7 +42,13 @@ use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); -use vars qw/ $AUTOLOAD /; +use vars qw/ $AUTOLOAD $log $dbh/; + +*log = \$ZoneMinder::Logger::logger; +*dbh = \$ZoneMinder::Database::dbh; + +my $debug = 1; +use constant DEBUG_ALL=>0; sub new { my ( $parent, $id, $data ) = @_; @@ -110,7 +116,282 @@ sub AUTOLOAD { return $_[0]{$name}; } +sub save { + my ( $self, $data, $force_insert ) = @_; + my $type = ref $self; + if ( ! $type ) { + my ( $caller, undef, $line ) = caller; + $log->error("No type in Object::save. self:$self from $caller:$line"); + } + my $local_dbh = eval '$'.$type.'::dbh'; + $local_dbh = $ZoneMinder::Database::dbh if ! $local_dbh; + $self->set( $data ? $data : {} ); + if ( $debug or DEBUG_ALL ) { + if ( $data ) { + foreach my $k ( keys %$data ) { + $log->debug("Object::save after set $k => $$data{$k} $$self{$k}"); + } + } else { + $log->debug("No data after set"); + } + } +#$debug = 0; + + my $table = eval '$'.$type.'::table'; + my $fields = eval '\%'.$type.'::fields'; + my $debug = eval '$'.$type.'::debug'; + #$debug = DEBUG_ALL if ! $debug; + + my %sql; + foreach my $k ( keys %$fields ) { + $sql{$$fields{$k}} = $$self{$k} if defined $$fields{$k}; + } # end foreach + if ( ! $force_insert ) { + $sql{$$fields{updated_on}} = 'NOW()' if exists $$fields{updated_on}; + } # end if + my $serial = eval '$'.$type.'::serial'; + my @identified_by = eval '@'.$type.'::identified_by'; + + my $ac = ZoneMinder::Database::start_transaction( $local_dbh ); + if ( ! $serial ) { + my $insert = $force_insert; + my %serial = eval '%'.$type.'::serial'; + if ( ! %serial ) { +$log->debug("No serial") if $debug; + # No serial columns defined, which means that we will do saving by delete/insert instead of insert/update + if ( @identified_by ) { + my $where = join(' AND ', map { $$fields{$_}.'=?' } @identified_by ); + if ( $debug ) { + $log->debug("DELETE FROM $table WHERE $where"); + } # end if + + 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 ); + return $local_dbh->errstr; + } elsif ( $debug ) { + $log->debug("SQL succesful DELETE FROM $table WHERE $where"); + } # end if + } # end if + $insert = 1; + } else { + foreach my $id ( @identified_by ) { + if ( ! $serial{$id} ) { + my ( $caller, undef, $line ) = caller; + $log->error("$id nor in serial for $type from $caller:$line") if $debug; + next; + } + if ( ! $$self{$id} ) { + my $s = qq{SELECT `auto_increment` FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '$table'}; + + ($$self{$id}) = ($sql{$$fields{$id}}) = $local_dbh->selectrow_array( $s ); + #($$self{$id}) = ($sql{$$fields{$id}}) = $local_dbh->selectrow_array( q{SELECT nextval('} . $serial{$id} . q{')} ); + $log->debug("SQL statement execution SELECT $s returned $$self{$id}") if $debug or DEBUG_ALL; + $insert = 1; + } # end if + } # end foreach + } # end if ! %serial + + if ( $insert ) { + my @keys = keys %sql; + my $command = "INSERT INTO $table (" . join(',', @keys ) . ') VALUES (' . join(',', map { '?' } @sql{@keys} ) . ')'; + if ( ! ( ( $_ = $local_dbh->prepare($command) ) and $_->execute( @sql{@keys} ) ) ) { + 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 ); + return $error; + } # end if + if ( $debug or DEBUG_ALL ) { + $command =~ s/\?/\%s/g; + $log->debug('SQL statement execution: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys} ) ).'):' ); + } # end if + } else { + my @keys = keys %sql; + my $command = "UPDATE $table SET " . join(',', map { $_ . ' = ?' } @keys ) . ' WHERE ' . join(' AND ', map { $_ . ' = ?' } @$fields{@identified_by} ); + if ( ! ( $_ = $local_dbh->prepare($command) and $_->execute( @sql{@keys,@$fields{@identified_by}} ) ) ) { + 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 ); + return $error; + } # end if + if ( $debug or DEBUG_ALL ) { + $command =~ s/\?/\%s/g; + $log->debug('SQL DEBUG: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys,@$fields{@identified_by}} ) ).'):' ); + } # end if + } # end if + } else { # not identified_by + @identified_by = ('Id') if ! @identified_by; + + # If the size of the arrays are not equal which means one or more are missing + my @identified_by_without_values = map { $$self{$_} ? () : $_ } @identified_by; + my $need_serial = @identified_by_without_values > 0; + + if ( $force_insert or $need_serial ) { + + if ( $need_serial ) { + if ( $serial ) { + my $s = qq{SELECT `auto_increment` FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '$table'}; + @$self{@identified_by} = @sql{@$fields{@identified_by}} = $local_dbh->selectrow_array( $s ); +#@$self{@identified_by} = @sql{@$fields{@identified_by}} = $local_dbh->selectrow_array( q{SELECT nextval('} . $serial . q{')} ); + if ( $local_dbh->errstr() ) { + $log->error("Error getting next id. " . $local_dbh->errstr() ); + $log->error("SQL statement execution $s returned ".join(',',@$self{@identified_by})); + } elsif ( $debug or DEBUG_ALL ) { + $log->debug("SQL statement execution $s returned ".join(',',@$self{@identified_by})); + } # end if + } # end if + } # end if + + my @keys = keys %sql; + my $command = "INSERT INTO $table (" . join(',', @keys ) . ') VALUES (' . join(',', map { '?' } @sql{@keys} ) . ')'; + if ( ! ( $_ = $local_dbh->prepare($command) and $_->execute( @sql{@keys} ) ) ) { + $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 ); + return $error; + } # end if + if ( $debug or DEBUG_ALL ) { + $command =~ s/\?/\%s/g; + $log->debug('SQL DEBUG: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys} ) ).'):' ); + } # end if + } else { + delete $sql{created_on}; + my @keys = keys %sql; + my %identified_by = map { $_, $_ } @identified_by; + + @keys = map { $identified_by{$_} ? () : $$fields{$_} } @keys; + my $command = "UPDATE $table SET " . join(',', map { $_ . ' = ?' } @keys ) . ' WHERE ' . join(' AND ', map { $$fields{$_} .'= ?' } @identified_by ); + if ( ! ( $_ = $local_dbh->prepare($command) and $_->execute( @sql{@keys}, @sql{@$fields{@identified_by}} ) ) ) { + 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 ); + return $error; + } # end if + if ( $debug or DEBUG_ALL ) { + $command =~ s/\?/\%s/g; + $log->debug('SQL DEBUG: ('.sprintf($command, map { defined $_ ? ( ref $_ eq 'ARRAY' ? join(',',@{$_}) : $_ ) : 'undef' } ( @sql{@keys}, @$self{@identified_by} ) ).'):' ); + } # end if + } # end if + } # end if + ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $self->load(); + #if ( $$fields{id} ) { + #if ( ! $ZoneMinder::Object::cache{$type}{$$self{id}} ) { + #$ZoneMinder::Object::cache{$type}{$$self{id}} = $self; + #} # end if + #delete $ZoneMinder::Object::cache{$config{db_name}}{$type}{$$self{id}}; + #} # end if +#$log->debug("after delete"); + #eval 'if ( %'.$type.'::find_cache ) { %'.$type.'::find_cache = (); }'; +#$log->debug("after clear cache"); + return ''; +} # end sub save + +sub set { + my ( $self, $params ) = @_; + my @set_fields = (); + + my $type = ref $self; + my %fields = eval ('%'.$type.'::fields'); + if ( ! %fields ) { + $log->warn("ZoneMinder::Object::set called on an object ($type) with no fields".$@); + } # end if + my %defaults = eval('%'.$type.'::defaults'); + if ( ref $params ne 'HASH' ) { + my ( $caller, undef, $line ) = caller; + $log->error("$type -> set called with non-hash params from $caller $line"); + } + + foreach my $field ( keys %fields ) { + if ( $params ) { + $log->debug("field: $field, param: ".$$params{$field}) if $debug; + if ( exists $$params{$field} ) { + $log->debug("field: $field, $$self{$field} =? param: ".$$params{$field}) if $debug; + if ( ( ! defined $$self{$field} ) or ($$self{$field} ne $params->{$field}) ) { +# Only make changes to fields that have changed + if ( defined $fields{$field} ) { + $$self{$field} = $$params{$field} if defined $fields{$field}; + push @set_fields, $fields{$field}, $$params{$field}; #mark for sql updating + } # end if + $log->debug("Running $field with $$params{$field}") if $debug; + if ( my $func = $self->can( $field ) ) { + $func->( $self, $$params{$field} ); + } # end if + } # end if + } # end if + } # end if $params + + if ( defined $fields{$field} ) { + if ( $$self{$field} ) { + $$self{$field} = transform( $type, $field, $$self{$field} ); + } # end if $$self{field} + } + } # end foreach field + + foreach my $field ( keys %defaults ) { + + if ( ( ! exists $$self{$field} ) or (!defined $$self{$field}) or ( $$self{$field} eq '' ) ) { + $log->debug("Setting default ($field) ($$self{$field}) ($defaults{$field}) ") if $debug; + if ( defined $defaults{$field} ) { + $log->debug("Default $field is defined: $defaults{$field}") if $debug; + if ( $defaults{$field} eq 'NOW()' ) { + $$self{$field} = 'NOW()'; + } else { + $$self{$field} = eval($defaults{$field}); + $log->error( "Eval error of object default $field default ($defaults{$field}) Reason: " . $@ ) if $@; + } # end if + } else { + $$self{$field} = $defaults{$field}; + } # end if +#$$self{$field} = ( defined $defaults{$field} ) ? eval($defaults{$field}) : $defaults{$field}; + $log->debug("Setting default for ($field) using ($defaults{$field}) to ($$self{$field}) ") if $debug; + } # end if + } # end foreach default + return @set_fields; +} # end sub set + +sub transform { + my $type = ref $_[0]; + $type = $_[0] if ! $type; + my $fields = eval '\%'.$type.'::fields'; + my $value = $_[2]; + + if ( defined $$fields{$_[1]} ) { + my @transforms = eval('@{$'.$type.'::transforms{$_[1]}}'); + $log->debug("Transforms for $_[1] before $_[2]: @transforms") if $debug; + if ( @transforms ) { + foreach my $transform ( @transforms ) { + if ( $transform =~ /^s\// or $transform =~ /^tr\// ) { + eval '$value =~ ' . $transform; + } elsif ( $transform =~ /^<(\d+)/ ) { + if ( $value > $1 ) { + $value = undef; + } # end if + } else { + $log->debug("evalling $value ".$transform . " Now value is $value" ); + eval '$value '.$transform; + $log->error("Eval error $@") if $@; + } + $log->debug("After $transform: $value") if $debug; + } # end foreach + } # end if + } else { + $log->error("Object::transform ($_[1]) not in fields for $type"); + } # end if + return $value; + +} # end sub transform 1; __END__ diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 9ccfc2a6e..cbb68faff 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -246,7 +246,7 @@ MAIN: while( $loop ) { $$Event{Path} = join('/', $Storage->Path(), $day_dir,$event_path); $Event->MonitorId( $monitor_dir ); $Event->StorageId( $Storage->Id() ); - $Event->DiskUsage( undef ); + $Event->DiskSpace( undef ); } # event path exists } # end foreach event_link chdir( $Storage->Path() ); diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 0560fb0c0..efe988882 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -214,6 +214,7 @@ sub getFilters { or AutoMessage = 1 or AutoExecute = 1 or AutoDelete = 1 + or UpdateDiskSpace = 1 ) ORDER BY Name'; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Unable toprepare '$sql': ".$dbh->errstr() ); @@ -250,17 +251,23 @@ sub checkFilter { my $filter = shift; my @Events = $filter->Execute(); - Info( join( "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':''), - ' returned ' , scalar @Events , ' events', - "\n", - ) ); + Info( + join(' ', + 'Checking filter', $filter->{Name}, + join( ', ', + + ($filter->{AutoDelete}?'delete':()), + ($filter->{AutoArchive}?'archive':()), + ($filter->{AutoVideo}?'video':()), + ($filter->{AutoUpload}?'upload':()), + ($filter->{AutoEmail}?'email':()), + ($filter->{AutoMessage}?'message':()), + ($filter->{AutoExecute}?'execute':()), + ($filter->{UpdateDiskSpace}?'update disk space':()), + ), + 'returned' , scalar @Events , 'events', + "\n", + ) ); foreach my $event ( @Events ) { Debug( "Checking event $event->{Id}\n" ); @@ -308,6 +315,12 @@ sub checkFilter { Error( "Unable toto delete event $event->{Id} as previous operations failed\n" ); } } # end if AutoDelete + if ( $filter->{UpdateDiskSpace} ) { + my $Event = new ZoneMinder::Event( $$event{Id}, $event ); + $Event->DiskSpace(undef); + $Event->save(); + + } # end if UpdateDiskSpace } # end foreach event } diff --git a/src/zm_analysis_thread.cpp b/src/zm_analysis_thread.cpp index b38e19108..bb23c5d3f 100644 --- a/src/zm_analysis_thread.cpp +++ b/src/zm_analysis_thread.cpp @@ -3,10 +3,11 @@ AnalysisThread::AnalysisThread(Monitor *p_monitor) { monitor = p_monitor; terminate = false; - sigemptyset(&block_set); + //sigemptyset(&block_set); } AnalysisThread::~AnalysisThread() { + Debug(2, "THREAD: deleteing"); } int AnalysisThread::run() { @@ -17,11 +18,12 @@ int AnalysisThread::run() { monitor->UpdateAdaptiveSkip(); last_analysis_update_time = time(0); + Debug(2, "THREAD: Getting ref image"); monitor->get_ref_image(); while( !terminate ) { // Process the next image - sigprocmask(SIG_BLOCK, &block_set, 0); + //sigprocmask(SIG_BLOCK, &block_set, 0); // Some periodic updates are required for variable capturing framerate if ( analysis_update_delay ) { @@ -34,12 +36,14 @@ int AnalysisThread::run() { } if ( !monitor->Analyse() ) { +Debug(4, "Sleeping for %d", monitor->Active()?ZM_SAMPLE_RATE:ZM_SUSPENDED_RATE); usleep(monitor->Active()?ZM_SAMPLE_RATE:ZM_SUSPENDED_RATE); } else if ( analysis_rate ) { +Debug(4, "Sleeping for %d", analysis_rate); usleep(analysis_rate); } - sigprocmask(SIG_UNBLOCK, &block_set, 0); + //sigprocmask(SIG_UNBLOCK, &block_set, 0); } return 0; } // end in AnalysisThread::run() diff --git a/src/zm_camera.h b/src/zm_camera.h index 8ce981897..9d3447fe4 100644 --- a/src/zm_camera.h +++ b/src/zm_camera.h @@ -58,6 +58,7 @@ protected: int mAudioStreamId; AVCodecContext *mVideoCodecContext; AVCodecContext *mAudioCodecContext; + AVStream *video_stream; public: Camera( @@ -104,12 +105,12 @@ public: virtual int PrimeCapture() { return( 0 ); } virtual int PreCapture()=0; - virtual ZMPacket * Capture( Image &image )=0; + virtual int Capture(ZMPacket &p)=0; virtual int PostCapture()=0; - AVStream *get_VideoStream() { return NULL; }; - AVStream *get_AudioStream() { return NULL; }; - int get_VideoStreamId() { return mVideoStreamId; }; - int get_AudioStreamId() { return mAudioStreamId; }; + AVStream *get_VideoStream() { return NULL; }; + AVStream *get_AudioStream() { return NULL; }; + int get_VideoStreamId() { return mVideoStreamId; }; + int get_AudioStreamId() { return mAudioStreamId; }; }; #endif // ZM_CAMERA_H diff --git a/src/zm_curl_camera.cpp b/src/zm_curl_camera.cpp index 3db890bf9..fe241677b 100644 --- a/src/zm_curl_camera.cpp +++ b/src/zm_curl_camera.cpp @@ -116,7 +116,7 @@ int cURLCamera::PreCapture() { return( 0 ); } -int cURLCamera::Capture( Image &image ) { +int cURLCamera::Capture( ZMPacket &zm_packet ) { bool frameComplete = false; /* MODE_STREAM specific variables */ @@ -128,10 +128,10 @@ int cURLCamera::Capture( Image &image ) { /* Grab the mutex to ensure exclusive access to the shared data */ lock(); - while (!frameComplete) { + while ( !frameComplete ) { /* If the work thread did a reset, reset our local variables */ - if(bReset) { + if ( bReset ) { SubHeadersParsingComplete = false; frame_content_length = 0; frame_content_type.clear(); @@ -139,25 +139,25 @@ int cURLCamera::Capture( Image &image ) { bReset = false; } - if(mode == MODE_UNSET) { + if ( mode == MODE_UNSET ) { /* Don't have a mode yet. Sleep while waiting for data */ nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); - if(nRet != 0) { + if ( nRet != 0 ) { Error("Failed waiting for available data condition variable: %s",strerror(nRet)); - return -20; + return -1; } } - if(mode == MODE_STREAM) { + if ( mode == MODE_STREAM ) { /* Subheader parsing */ - while(!SubHeadersParsingComplete && !need_more_data) { + while( !SubHeadersParsingComplete && !need_more_data ) { size_t crlf_start, crlf_end, crlf_size; std::string subheader; /* Check if the buffer contains something */ - if(databuffer.empty()) { + if ( databuffer.empty() ) { /* Empty buffer, wait for data */ need_more_data = true; break; @@ -165,14 +165,14 @@ int cURLCamera::Capture( Image &image ) { /* Find crlf start */ crlf_start = memcspn(databuffer,"\r\n",databuffer.size()); - if(crlf_start == databuffer.size()) { + if ( crlf_start == databuffer.size() ) { /* Not found, wait for more data */ need_more_data = true; break; } /* See if we have enough data for determining crlf length */ - if(databuffer.size() < crlf_start+5) { + if ( databuffer.size() < crlf_start+5 ) { /* Need more data */ need_more_data = true; break; @@ -183,13 +183,13 @@ int cURLCamera::Capture( Image &image ) { crlf_size = (crlf_start + crlf_end) - crlf_start; /* Is this the end of a previous stream? (This is just before the boundary) */ - if(crlf_start == 0) { + if ( crlf_start == 0 ) { databuffer.consume(crlf_size); continue; } /* Check for invalid CRLF size */ - if(crlf_size > 4) { + if ( crlf_size > 4 ) { Error("Invalid CRLF length"); } @@ -209,7 +209,7 @@ int cURLCamera::Capture( Image &image ) { /* Find where the data in this header starts */ size_t subheader_data_start = subheader.rfind(' '); - if(subheader_data_start == std::string::npos) { + if ( subheader_data_start == std::string::npos ) { subheader_data_start = subheader.find(':'); } @@ -247,7 +247,7 @@ int cURLCamera::Capture( Image &image ) { need_more_data = true; } else { /* All good. decode the image */ - image.DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder); + zm_packet.image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder); frameComplete = true; } } @@ -257,7 +257,7 @@ int cURLCamera::Capture( Image &image ) { nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex); if(nRet != 0) { Error("Failed waiting for available data condition variable: %s",strerror(nRet)); - return -18; + return -1; } need_more_data = false; } @@ -267,7 +267,7 @@ int cURLCamera::Capture( Image &image ) { if (!single_offsets.empty()) { if( (single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front()) ) { /* Extract frame */ - image.DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder); + zm_packet.image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder); single_offsets.pop_front(); frameComplete = true; } else { @@ -281,7 +281,7 @@ int cURLCamera::Capture( Image &image ) { nRet = pthread_cond_wait(&request_complete_cond,&shareddata_mutex); if(nRet != 0) { Error("Failed waiting for request complete condition variable: %s",strerror(nRet)); - return -19; + return -1; } } } else { @@ -295,9 +295,9 @@ int cURLCamera::Capture( Image &image ) { unlock(); if(!frameComplete) - return -1; + return 0; - return 0; + return 1; } int cURLCamera::PostCapture() { @@ -305,12 +305,6 @@ int cURLCamera::PostCapture() { return( 0 ); } -int cURLCamera::CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ) { - Error("Capture and Record not implemented for the cURL camera type"); - // Nothing to do here - return( 0 ); -} - size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { lock(); diff --git a/src/zm_curl_camera.h b/src/zm_curl_camera.h index c9dc2e935..aff911bae 100644 --- a/src/zm_curl_camera.h +++ b/src/zm_curl_camera.h @@ -76,9 +76,8 @@ public: int PrimeCapture(); int PreCapture(); - int Capture( Image &image ); + int Capture( ZMPacket &p ); int PostCapture(); - int CaptureAndRecord( Image &image, struct timeval recording, char* event_directory ); size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata); size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata); diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 87d0e27d2..3e9b46514 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -140,6 +140,7 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string if ( symlink( time_path, id_file ) < 0 ) Error( "Can't symlink %s -> %s: %s", id_file, path, strerror(errno)); } else { + // Shallow Storage snprintf( path, sizeof(path), "%s/%d/%d", storage->Path(), monitor->Id(), id ); errno = 0; @@ -168,40 +169,11 @@ Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string snprintf( video_name, sizeof(video_name), "%d-%s", id, "video.mp4" ); snprintf( video_file, sizeof(video_file), staticConfig.video_file_format, path, video_name ); Debug(1,"Writing video file to %s", video_file ); - -#if 0 - /* X264 MP4 video writer */ - if ( monitor->GetOptVideoWriter() == Monitor::X264ENCODE ) { -#if ZM_HAVE_VIDEOWRITER_X264MP4 - videowriter = new X264MP4Writer(video_file, monitor->Width(), monitor->Height(), monitor->Colours(), monitor->SubpixelOrder(), monitor->GetOptEncoderParams()); -#else - Error("ZoneMinder was not compiled with the X264 MP4 video writer, check dependencies (x264 and mp4v2)"); -#endif - } - - if ( videowriter != NULL ) { - /* Open the video stream */ - int nRet = videowriter->Open(); - if(nRet != 0) { - Error("Failed opening video stream"); - delete videowriter; - videowriter = NULL; - } - - snprintf( timecodes_name, sizeof(timecodes_name), "%d-%s", id, "video.timecodes" ); - snprintf( timecodes_file, sizeof(timecodes_file), staticConfig.video_file_format, path, timecodes_name ); - - /* Create timecodes file */ - timecodes_fd = fopen(timecodes_file, "wb"); - if ( timecodes_fd == NULL ) { - Error("Failed creating timecodes file"); - } - } + videowriter = NULL; } else { /* No video object */ videowriter = NULL; } -#endif } // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent ) @@ -211,7 +183,6 @@ Event::~Event() { DELTA_TIMEVAL( delta_time, end_time, start_time, DT_PREC_2 ); if ( frames > last_db_frame ) { - Debug( 1, "Adding closing frame %d to DB", frames ); snprintf( sql, sizeof(sql), "insert into Frames ( EventId, FrameId, TimeStamp, Delta ) values ( %d, %d, from_unixtime( %ld ), %s%ld.%02ld )", id, frames, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec ); if ( mysql_query( &dbconn, sql ) ) { diff --git a/src/zm_eventstream.h b/src/zm_eventstream.h index 07e161214..152935d65 100644 --- a/src/zm_eventstream.h +++ b/src/zm_eventstream.h @@ -27,6 +27,7 @@ #include "zm_stream.h" #include "zm_video.h" #include "zm_ffmpeg_input.h" +#include "zm_monitor.h" #ifdef __cplusplus extern "C" { @@ -108,11 +109,17 @@ class EventStream : public StreamBase { } void setStreamStart( int init_event_id, unsigned int init_frame_id=0 ) { loadInitialEventData( init_event_id, init_frame_id ); - loadMonitor( event_data->monitor_id ); + if ( !(monitor = Monitor::Load( event_data->monitor_id, false, Monitor::QUERY )) ) { + Fatal( "Unable to load monitor id %d for streaming", event_data->monitor_id ); + return; + } } void setStreamStart( int monitor_id, time_t event_time ) { loadInitialEventData( monitor_id, event_time ); - loadMonitor( monitor_id ); + if ( !(monitor = Monitor::Load( event_data->monitor_id, false, Monitor::QUERY )) ) { + Fatal( "Unable to load monitor id %d for streaming", monitor_id ); + return; + } } void setStreamMode( StreamMode p_mode ) { mode = p_mode; diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 4ba88d900..c9fe65e91 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -133,6 +133,7 @@ int av_dict_parse_string(AVDictionary **pm, const char *str, #endif // HAVE_LIBAVCODEC || HAVE_LIBAVUTIL || HAVE_LIBSWSCALE #if HAVE_LIBAVUTIL +#if LIBAVUTIL_VERSION_CHECK(56, 0, 0, 17, 100) int64_t av_rescale_delta(AVRational in_tb, int64_t in_ts, AVRational fs_tb, int duration, int64_t *last, AVRational out_tb){ int64_t a, b, this_thing; @@ -156,6 +157,7 @@ simple_round: return av_rescale_q(this_thing, fs_tb, out_tb); } #endif +#endif int hacked_up_context2_for_older_ffmpeg(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format, const char *filename) { AVFormatContext *s = avformat_alloc_context(); diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 8bb393475..af635e840 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -177,138 +177,43 @@ int FfmpegCamera::PreCapture() { return( 0 ); } -ZMPacket * FfmpegCamera::Capture( Image &image ) { +int FfmpegCamera::Capture( ZMPacket &zm_packet ) { if ( ! mCanCapture ) { - return NULL; + return -1; } int ret; - char errbuf[AV_ERROR_MAX_STRING_SIZE]; // If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread. if ( mReopenThread != 0 ) { void *retval = 0; - ret = pthread_join(mReopenThread, &retval); if ( ret != 0 ) { Error("Could not join reopen thread."); } - Info( "Successfully reopened stream." ); mReopenThread = 0; } - ZMPacket *zm_packet = NULL; - ret = av_read_frame( mFormatContext, &packet ); if ( ret < 0 ) { - av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); if ( // Check if EOF. (ret == AVERROR_EOF || (mFormatContext->pb && mFormatContext->pb->eof_reached)) || // Check for Connection failure. (ret == -110) ) { - Info( "av_read_frame returned \"%s\". Reopening stream.", errbuf ); + Info( "av_read_frame returned \"%s\". Reopening stream.", av_make_error_string(ret).c_str() ); ReopenFfmpeg(); } - - Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret, errbuf ); - return NULL; + Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret, av_make_error_string(ret).c_str() ); + return -1; } Debug( 5, "Got packet from stream %d dts (%d) pts(%d)", packet.stream_index, packet.pts, packet.dts ); -#if 0 - // What about audio stream? Maybe someday we could do sound detection... - if ( ( packet.stream_index == mVideoStreamId ) && ( keyframe || have_video_keyframe ) ) { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - ret = avcodec_send_packet( mVideoCodecContext, &packet ); - if ( ret < 0 ) { - av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); - Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf ); - zm_av_packet_unref( &packet ); - continue; - } - -#if HAVE_AVUTIL_HWCONTEXT_H - if ( hwaccel ) { - ret = avcodec_receive_frame( mVideoCodecContext, hwFrame ); - if ( ret < 0 ) { - av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); - Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf ); - zm_av_packet_unref( &packet ); - continue; - } - ret = av_hwframe_transfer_data(mRawFrame, hwFrame, 0); - if (ret < 0) { - av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); - Error( "Unable to transfer frame at frame %d: %s, continuing", frameCount, errbuf ); - zm_av_packet_unref( &packet ); - continue; - } - } else { -#endif - ret = avcodec_receive_frame( mVideoCodecContext, mRawFrame ); - if ( ret < 0 ) { - av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); - Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf ); - zm_av_packet_unref( &packet ); - continue; - } - -#if HAVE_AVUTIL_HWCONTEXT_H - } -#endif - - frameComplete = 1; -# else - ret = zm_avcodec_decode_video( mVideoCodecContext, mRawFrame, &frameComplete, &packet ); - if ( ret < 0 ) { - av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); - Error( "Unable to decode frame at frame %d: %s, continuing", frameCount, errbuf ); - zm_av_packet_unref( &packet ); - continue; - } -#endif - - Debug( 4, "Decoded video packet at frame %d", frameCount ); - - if ( frameComplete ) { - Debug( 4, "Got frame %d", frameCount ); - - uint8_t* directbuffer; - - /* Request a writeable buffer of the target image */ - directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); - if ( directbuffer == NULL ) { - Error("Failed requesting writeable buffer for the captured image."); - return (-1); - } - -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(mFrame->data, mFrame->linesize, - directbuffer, imagePixFormat, width, height, 1); -#else - avpicture_fill( (AVPicture *)mFrame, directbuffer, - imagePixFormat, width, height); -#endif - -#if HAVE_LIBSWSCALE - if ( sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0 ) - Fatal("Unable to convert raw format %u to target format %u at frame %d", mVideoCodecContext->pix_fmt, imagePixFormat, frameCount); -#else // HAVE_LIBSWSCALE - Fatal("You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras"); -#endif // HAVE_LIBSWSCALE - - frameCount++; - } // end if frameComplete - } else { - Debug( 4, "Different stream_index %d", packet.stream_index ); - } // end if packet.stream_index == mVideoStreamId -#endif - zm_packet = new ZMPacket( &packet ); + zm_packet.set_packet( &packet ); zm_av_packet_unref( &packet ); - return zm_packet; + return 1; } // FfmpegCamera::Capture int FfmpegCamera::PostCapture() { @@ -448,7 +353,9 @@ int FfmpegCamera::OpenFfmpeg() { // STolen from ispy //this fixes issues with rtsp streams!! woot. //mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG2_CHUNKS | CODEC_FLAG_LOW_DELAY; // Enable faster H264 decode. +#ifdef CODEC_FLAG2_FAST mVideoCodecContext->flags2 |= CODEC_FLAG2_FAST | CODEC_FLAG_LOW_DELAY; +#endif #if HAVE_AVUTIL_HWCONTEXT_H if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H264 ) { @@ -485,7 +392,16 @@ int FfmpegCamera::OpenFfmpeg() { } } } - + } else { +#ifdef AV_CODEC_ID_H265 + if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H265 ) { + Debug( 1, "Input stream appears to be h265. The stored event file may not be viewable in browser." ); + } else { +#endif + Error( "Input stream is not h264. The stored event file may not be viewable in browser." ); +#ifdef AV_CODEC_ID_H265 + } +#endif } // end if h264 #endif @@ -517,6 +433,18 @@ int FfmpegCamera::OpenFfmpeg() { } } + if ( mVideoCodecContext->codec_id != AV_CODEC_ID_H264 ) { +#ifdef AV_CODEC_ID_H265 + if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H265 ) { + Debug( 1, "Input stream appears to be h265. The stored event file may not be viewable in browser." ); + } else { +#endif + Warning( "Input stream is not h264. The stored event file may not be viewable in browser." ); +#ifdef AV_CODEC_ID_H265 + } +#endif + } + if (mVideoCodecContext->hwaccel != NULL) { Debug(1, "HWACCEL in use"); } else { diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index ac63735ab..ef3b259ae 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -40,6 +40,7 @@ class FfmpegCamera : public Camera { std::string mPath; std::string mMethod; std::string mOptions; + std::string encoder_options; int frameCount; @@ -89,7 +90,20 @@ class FfmpegCamera : public Camera { int64_t startTime; public: - FfmpegCamera( int p_id, const std::string &path, const std::string &p_method, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); + FfmpegCamera( + int p_id, + const std::string &path, + const std::string &p_method, + const std::string &p_options, + int p_width, + int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio ); ~FfmpegCamera(); const std::string &Path() const { return( mPath ); } @@ -101,15 +115,14 @@ class FfmpegCamera : public Camera { int PrimeCapture(); int PreCapture(); - ZMPacket * Capture( Image &image ); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); + int Capture(ZMPacket &p); int PostCapture(); - AVStream *get_VideoStream() { + AVStream *get_VideoStream() { if ( mVideoStreamId != -1 ) return mFormatContext->streams[mVideoStreamId]; return NULL; } - AVStream *get_AudioStream() { + AVStream *get_AudioStream() { if ( mAudioStreamId != -1 ) return mFormatContext->streams[mAudioStreamId]; return NULL; diff --git a/src/zm_ffmpeg_input.cpp b/src/zm_ffmpeg_input.cpp index 6c715b6d1..54395a027 100644 --- a/src/zm_ffmpeg_input.cpp +++ b/src/zm_ffmpeg_input.cpp @@ -116,9 +116,9 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) { if ( (stream_id < 0 ) || ( packet.stream_index == stream_id ) ) { Debug(1,"Packet is for our stream (%d)", packet.stream_index ); - AVCodecContext *context = streams[packet.stream_index].context; - #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + AVCodecContext *context = streams[packet.stream_index].context; + ret = avcodec_send_packet( context, &packet ); if ( ret < 0 ) { av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); diff --git a/src/zm_ffmpeg_output.cpp b/src/zm_ffmpeg_output.cpp new file mode 100644 index 000000000..423595d19 --- /dev/null +++ b/src/zm_ffmpeg_output.cpp @@ -0,0 +1,180 @@ + +#include "zm_ffmpeg_input.h" +#include "zm_logger.h" +#include "zm_ffmpeg.h" + +FFmpeg_Output::FFmpeg_Output() { + input_format_context = NULL; + video_stream_id = -1; + audio_stream_id = -1; + av_register_all(); + avcodec_register_all(); + +} +FFmpeg_Output::~FFmpeg_Output() { +} + +int FFmpeg_Output::Open( const char *filepath ) { + + int error; + + /** Open the input file to read from it. */ + if ( (error = avformat_open_input( &input_format_context, filepath, NULL, NULL)) < 0 ) { + + Error("Could not open input file '%s' (error '%s')\n", + filepath, av_make_error_string(error).c_str() ); + input_format_context = NULL; + return error; + } + + /** Get information on the input file (number of streams etc.). */ + if ( (error = avformat_find_stream_info(input_format_context, NULL)) < 0 ) { + Error( "Could not open find stream info (error '%s')\n", + av_make_error_string(error).c_str() ); + avformat_close_input(&input_format_context); + return error; + } + + for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) { + if ( is_video_stream( input_format_context->streams[i] ) ) { + zm_dump_stream_format(input_format_context, i, 0, 0); + if ( video_stream_id == -1 ) { + video_stream_id = i; + // if we break, then we won't find the audio stream + } else { + Warning( "Have another video stream." ); + } + } else if ( is_audio_stream( input_format_context->streams[i] ) ) { + if ( audio_stream_id == -1 ) { + audio_stream_id = i; + } else { + Warning( "Have another audio stream." ); + } + } + + streams[i].frame_count = 0; +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + streams[i].context = avcodec_alloc_context3( NULL ); + avcodec_parameters_to_context( streams[i].context, input_format_context->streams[i]->codecpar ); +#else + streams[i].context = input_format_context->streams[i]->codec; +#endif + + if ( !(streams[i].codec = avcodec_find_decoder(streams[i].context->codec_id)) ) { + Error( "Could not find input codec\n"); + avformat_close_input(&input_format_context); + return AVERROR_EXIT; + } else { + Debug(1, "Using codec (%s) for stream %d", streams[i].codec->name, i ); + } + + if ((error = avcodec_open2( streams[i].context, streams[i].codec, NULL)) < 0) { + Error( "Could not open input codec (error '%s')\n", + av_make_error_string(error).c_str() ); +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + avcodec_free_context( &streams[i].context ); +#endif + avformat_close_input(&input_format_context); + return error; + } + } // end foreach stream + + if ( video_stream_id == -1 ) + Error( "Unable to locate video stream in %s", filepath ); + if ( audio_stream_id == -1 ) + Debug( 3, "Unable to locate audio stream in %s", filepath ); + + return 0; +} // end int FFmpeg_Output::Open( const char * filepath ) + +AVFrame *FFmpeg_Output::get_frame( int stream_id ) { + Debug(1, "Getting frame from stream %d", stream_id ); + + int frameComplete = false; + AVPacket packet; + av_init_packet( &packet ); + AVFrame *frame = zm_av_frame_alloc(); + char errbuf[AV_ERROR_MAX_STRING_SIZE]; + + while ( !frameComplete ) { + int ret = av_read_frame( input_format_context, &packet ); + if ( ret < 0 ) { + av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); + if ( + // Check if EOF. + (ret == AVERROR_EOF || (input_format_context->pb && input_format_context->pb->eof_reached)) || + // Check for Connection failure. + (ret == -110) + ) { + Info( "av_read_frame returned %s.", errbuf ); + return NULL; + } + Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret, errbuf ); + return NULL; + } + + if ( (stream_id < 0 ) || ( packet.stream_index == stream_id ) ) { + Debug(1,"Packet is for our stream (%d)", packet.stream_index ); + + AVCodecContext *context = streams[packet.stream_index].context; + +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + ret = avcodec_send_packet( context, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } else { + Debug(1, "Success getting a packet"); + } + +#if HAVE_AVUTIL_HWCONTEXT_H + if ( hwaccel ) { + ret = avcodec_receive_frame( context, hwFrame ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to receive frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + ret = av_hwframe_transfer_data(frame, hwFrame, 0); + if (ret < 0) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to transfer frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + } else { +#endif + Debug(1,"Getting a frame?"); + ret = avcodec_receive_frame( context, frame ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } + +#if HAVE_AVUTIL_HWCONTEXT_H + } +#endif + + frameComplete = 1; +# else + ret = zm_avcodec_decode_video( streams[packet.stream_index].context, frame, &frameComplete, &packet ); + if ( ret < 0 ) { + av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE ); + Error( "Unable to decode frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf ); + zm_av_packet_unref( &packet ); + continue; + } +#endif + } // end if it's the right stream + + zm_av_packet_unref( &packet ); + + } // end while ! frameComplete + return frame; + +} // end AVFrame *FFmpeg_Output::get_frame diff --git a/src/zm_ffmpeg_output.h b/src/zm_ffmpeg_output.h new file mode 100644 index 000000000..76afad0d7 --- /dev/null +++ b/src/zm_ffmpeg_output.h @@ -0,0 +1,46 @@ +#ifndef ZM_FFMPEG_INPUT_H +#define ZM_FFMPEG_INPUT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "libavformat/avformat.h" +#include "libavformat/avio.h" +#include "libavcodec/avcodec.h" + +#ifdef __cplusplus +} +#endif + +class FFmpeg_Output { + + public: + FFmpeg_Output(); + ~FFmpeg_Output(); + + int Open( const char *filename ); + int Close(); + AVFrame *put_frame( int stream_id=-1 ); + AVFrame *put_packet( int stream_id=-1 ); + int get_video_stream_id() { + return video_stream_id; + } + int get_audio_stream_id() { + return audio_stream_id; + } + + private: + typedef struct { + AVCodecContext *context; + AVCodec *codec; + int frame_count; + } stream; + + stream streams[2]; + int video_stream_id; + int audio_stream_id; + AVFormatContext *input_format_context; +}; + +#endif diff --git a/src/zm_file_camera.cpp b/src/zm_file_camera.cpp index 50b5fc160..fc39ebade 100644 --- a/src/zm_file_camera.cpp +++ b/src/zm_file_camera.cpp @@ -34,7 +34,31 @@ #include "zm.h" #include "zm_file_camera.h" -FileCamera::FileCamera( int p_id, const char *p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ) : Camera( p_id, FILE_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio ) +FileCamera::FileCamera( + int p_id, + const char *p_path, + int p_width, + int p_height, + int p_colours, + int p_brightness, + int p_contrast, + int p_hue, + int p_colour, + bool p_capture, + bool p_record_audio + ) : Camera( + p_id, + FILE_SRC, + p_width, + p_height, + p_colours, + ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), + p_brightness, + p_contrast, + p_hue, + p_colour, + p_capture, + p_record_audio ) { strncpy( path, p_path, sizeof(path) ); if ( capture ) { @@ -72,11 +96,8 @@ int FileCamera::PreCapture() { return( 0 ); } -ZMPacket * FileCamera::Capture( Image &image ) { - ZMPacket * packet = NULL; - if ( image.ReadJpeg( path, colours, subpixelorder ) ) - packet = new ZMPacket( &image ); - return packet; +int FileCamera::Capture( ZMPacket &zm_packet ) { + return zm_packet.image->ReadJpeg( path, colours, subpixelorder ) ; } int FileCamera::PostCapture() { diff --git a/src/zm_file_camera.h b/src/zm_file_camera.h index e17d7f828..6bf054de7 100644 --- a/src/zm_file_camera.h +++ b/src/zm_file_camera.h @@ -56,7 +56,7 @@ public: void Initialise(); void Terminate(); int PreCapture(); - ZMPacket * Capture( Image &image ); + int Capture( ZMPacket &p ); int PostCapture(); }; diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 9dfb447da..e8293b3fb 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -130,7 +130,6 @@ Image::Image( int p_width, int p_height, int p_colours, int p_subpixelorder, uin } Image::Image( const AVFrame *frame ) { - AVFrame *dest_frame = zm_av_frame_alloc(); width = frame->width; height = frame->height; @@ -141,7 +140,13 @@ Image::Image( const AVFrame *frame ) { buffer = 0; holdbuffer = 0; AllocImgBuffer(size); + this->Assign( frame ); +} +void Image::Assign( const AVFrame *frame ) { +/* Assume the dimensions etc are correct. FIXME */ + + AVFrame *dest_frame = zm_av_frame_alloc(); #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(dest_frame->data, dest_frame->linesize, buffer, AV_PIX_FMT_RGBA, width, height, 1); @@ -167,7 +172,7 @@ Image::Image( const AVFrame *frame ) { Fatal("You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras"); #endif // HAVE_LIBSWSCALE av_frame_free( &dest_frame ); -} +} // end Image::Image( const AVFrame *frame ) Image::Image( const Image &p_image ) { if ( !initialised ) @@ -613,25 +618,25 @@ void Image::Assign(const unsigned int p_width, const unsigned int p_height, cons void Image::Assign( const Image &image ) { unsigned int new_size = (image.width * image.height) * image.colours; - if(image.buffer == NULL) { + if ( image.buffer == NULL ) { Error("Attempt to assign image with an empty buffer"); return; } - if(image.colours != ZM_COLOUR_GRAY8 && image.colours != ZM_COLOUR_RGB24 && image.colours != ZM_COLOUR_RGB32) { + if ( image.colours != ZM_COLOUR_GRAY8 && image.colours != ZM_COLOUR_RGB24 && image.colours != ZM_COLOUR_RGB32 ) { Error("Attempt to assign image with unexpected colours per pixel: %d",image.colours); return; } - if ( !buffer || image.width != width || image.height != height || image.colours != colours || image.subpixelorder != subpixelorder) { + if ( !buffer || image.width != width || image.height != height || image.colours != colours || image.subpixelorder != subpixelorder ) { - if (holdbuffer && buffer) { - if (new_size > allocation) { + if ( holdbuffer && buffer ) { + if ( new_size > allocation ) { Error("Held buffer is undersized for assigned buffer"); return; } } else { - if(new_size > allocation || !buffer) { + if ( new_size > allocation || !buffer ) { // DumpImgBuffer(); This is also done in AllocImgBuffer AllocImgBuffer(new_size); } @@ -645,7 +650,7 @@ void Image::Assign( const Image &image ) { size = new_size; } - if(image.buffer != buffer) + if ( image.buffer != buffer ) (*fptr_imgbufcpy)(buffer, image.buffer, size); } diff --git a/src/zm_image.h b/src/zm_image.h index 1d586d5fc..ae630f169 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -207,6 +207,7 @@ public: void Assign( unsigned int p_width, unsigned int p_height, unsigned int p_colours, unsigned int p_subpixelorder, const uint8_t* new_buffer, const size_t buffer_size); void Assign( const Image &image ); + void Assign( const AVFrame *frame ); void AssignDirect( const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder, uint8_t *new_buffer, const size_t buffer_size, const int p_buffertype); inline void CopyBuffer( const Image &image ) { diff --git a/src/zm_libvlc_camera.cpp b/src/zm_libvlc_camera.cpp index a4135d352..0593771d6 100644 --- a/src/zm_libvlc_camera.cpp +++ b/src/zm_libvlc_camera.cpp @@ -23,8 +23,7 @@ #if HAVE_LIBVLC // Do all the buffer checking work here to avoid unnecessary locking -void* LibvlcLockBuffer(void* opaque, void** planes) -{ +void* LibvlcLockBuffer(void* opaque, void** planes) { LibvlcPrivateData* data = (LibvlcPrivateData*)opaque; data->mutex.lock(); @@ -36,15 +35,12 @@ void* LibvlcLockBuffer(void* opaque, void** planes) return NULL; } -void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) -{ +void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) { LibvlcPrivateData* data = (LibvlcPrivateData*)opaque; bool newFrame = false; - for(uint32_t i = 0; i < data->bufferSize; i++) - { - if(data->buffer[i] != data->prevBuffer[i]) - { + for( uint32_t i = 0; i < data->bufferSize; i++ ) { + if ( data->buffer[i] != data->prevBuffer[i] ) { newFrame = true; break; } @@ -54,8 +50,7 @@ void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes) time_t now; time(&now); // Return frames slightly faster than 1fps (if time() supports greater than one second resolution) - if(newFrame || difftime(now, data->prevTime) >= 0.8) - { + if ( newFrame || difftime(now, data->prevTime) >= 0.8 ) { data->prevTime = now; data->newImage.updateValueSignal(true); } @@ -90,58 +85,46 @@ LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::stri Panic("Unexpected colours: %d",colours); } - if ( capture ) - { + if ( capture ) { Initialise(); } } -LibvlcCamera::~LibvlcCamera() -{ - if ( capture ) - { +LibvlcCamera::~LibvlcCamera() { + if ( capture ) { Terminate(); } - if(mLibvlcMediaPlayer != NULL) - { + if ( mLibvlcMediaPlayer != NULL ) { libvlc_media_player_release(mLibvlcMediaPlayer); mLibvlcMediaPlayer = NULL; } - if(mLibvlcMedia != NULL) - { + if ( mLibvlcMedia != NULL ) { libvlc_media_release(mLibvlcMedia); mLibvlcMedia = NULL; } - if(mLibvlcInstance != NULL) - { + if ( mLibvlcInstance != NULL ) { libvlc_release(mLibvlcInstance); mLibvlcInstance = NULL; } - if (mOptArgV != NULL) - { + if ( mOptArgV != NULL ) { delete[] mOptArgV; } } -void LibvlcCamera::Initialise() -{ +void LibvlcCamera::Initialise() { } -void LibvlcCamera::Terminate() -{ +void LibvlcCamera::Terminate() { libvlc_media_player_stop(mLibvlcMediaPlayer); - if(mLibvlcData.buffer != NULL) - { + if(mLibvlcData.buffer != NULL) { zm_freealigned(mLibvlcData.buffer); } - if(mLibvlcData.prevBuffer != NULL) - { + if(mLibvlcData.prevBuffer != NULL) { zm_freealigned(mLibvlcData.prevBuffer); } } -int LibvlcCamera::PrimeCapture() -{ +int LibvlcCamera::PrimeCapture() { Info("Priming capture from %s", mPath.c_str()); StringVector opVect = split(Options(), ","); @@ -154,8 +137,7 @@ int LibvlcCamera::PrimeCapture() else if ( Method() == "rtpRtspHttp" ) opVect.push_back("--rtsp-http"); - if (opVect.size() > 0) - { + if ( opVect.size() > 0 ) { mOptArgV = new char*[opVect.size()]; Debug(2, "Number of Options: %d",opVect.size()); for (size_t i=0; i< opVect.size(); i++) { @@ -166,7 +148,7 @@ int LibvlcCamera::PrimeCapture() } mLibvlcInstance = libvlc_new (opVect.size(), (const char* const*)mOptArgV); - if(mLibvlcInstance == NULL) + if ( mLibvlcInstance == NULL ) Fatal("Unable to create libvlc instance due to: %s", libvlc_errmsg()); mLibvlcMedia = libvlc_media_new_location(mLibvlcInstance, mPath.c_str()); @@ -189,44 +171,27 @@ int LibvlcCamera::PrimeCapture() libvlc_media_player_play(mLibvlcMediaPlayer); - return(0); + return 0; } -int LibvlcCamera::PreCapture() -{ +int LibvlcCamera::PreCapture() { return(0); } // Should not return -1 as cancels capture. Always wait for image if available. -int LibvlcCamera::Capture( Image &image ) -{ +int LibvlcCamera::Capture( ZMPacket &zm_packet ) { while(!mLibvlcData.newImage.getValueImmediate()) mLibvlcData.newImage.getUpdatedValue(1); mLibvlcData.mutex.lock(); - image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); + zm_packet.image->Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); mLibvlcData.newImage.setValueImmediate(false); mLibvlcData.mutex.unlock(); - return (0); + return 1; } -// Should not return -1 as cancels capture. Always wait for image if available. -int LibvlcCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory) -{ - while(!mLibvlcData.newImage.getValueImmediate()) - mLibvlcData.newImage.getUpdatedValue(1); - - mLibvlcData.mutex.lock(); - image.Assign(width, height, colours, subpixelorder, mLibvlcData.buffer, width * height * mBpp); - mLibvlcData.newImage.setValueImmediate(false); - mLibvlcData.mutex.unlock(); - - return (0); -} - -int LibvlcCamera::PostCapture() -{ +int LibvlcCamera::PostCapture() { return(0); } diff --git a/src/zm_libvlc_camera.h b/src/zm_libvlc_camera.h index 4221bd0b7..aa2765dbf 100644 --- a/src/zm_libvlc_camera.h +++ b/src/zm_libvlc_camera.h @@ -31,8 +31,7 @@ #endif // Used by libvlc callbacks -struct LibvlcPrivateData -{ +struct LibvlcPrivateData { uint8_t* buffer; uint8_t* prevBuffer; time_t prevTime; @@ -41,8 +40,7 @@ struct LibvlcPrivateData ThreadData newImage; }; -class LibvlcCamera : public Camera -{ +class LibvlcCamera : public Camera { protected: std::string mPath; std::string mMethod; @@ -69,8 +67,7 @@ public: int PrimeCapture(); int PreCapture(); - int Capture( Image &image ); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ); + int Capture( ZMPacket &p ); int PostCapture(); }; diff --git a/src/zm_local_camera.cpp b/src/zm_local_camera.cpp index 6eddce5e4..edeae3c8c 100644 --- a/src/zm_local_camera.cpp +++ b/src/zm_local_camera.cpp @@ -1902,7 +1902,7 @@ int LocalCamera::PreCapture() { return( 0 ); } -ZMPacket *LocalCamera::Capture( Image &image ) { +int LocalCamera::Capture( ZMPacket &zm_packet ) { // We assume that the avpacket is allocated, and just needs to be filled Debug( 3, "Capturing" ); @@ -1938,7 +1938,7 @@ ZMPacket *LocalCamera::Capture( Image &image ) { Warning( "Capture failure, possible signal loss?: %s", strerror(errno) ) else Error( "Unable to capture frame %d: %s", vid_buf.index, strerror(errno) ) - return NULL; + return -1; } v4l2_data.bufptr = &vid_buf; @@ -1946,10 +1946,10 @@ ZMPacket *LocalCamera::Capture( Image &image ) { if ( --captures_per_frame ) { if ( vidioctl( vid_fd, VIDIOC_QBUF, &vid_buf ) < 0 ) { Error( "Unable to requeue buffer %d: %s", vid_buf.index, strerror(errno) ); - return NULL; + return -1; } } - } + } // while captures_per_frame Debug( 3, "Captured frame %d/%d from channel %d", capture_frame, v4l2_data.bufptr->sequence, channel ); @@ -1959,7 +1959,7 @@ ZMPacket *LocalCamera::Capture( Image &image ) { if ( (v4l2_data.fmt.fmt.pix.width * v4l2_data.fmt.fmt.pix.height) != (width * height) ) { Fatal("Captured image dimensions differ: V4L2: %dx%d monitor: %dx%d",v4l2_data.fmt.fmt.pix.width,v4l2_data.fmt.fmt.pix.height,width,height); } - } else + } else // end if v4l2 #endif // ZM_HAS_V4L2 #if ZM_HAS_V4L1 if ( v4l_version == 1 ) { @@ -1968,14 +1968,14 @@ ZMPacket *LocalCamera::Capture( Image &image ) { Debug( 3, "Syncing frame %d", v4l1_data.active_frame ); if ( ioctl( vid_fd, VIDIOCSYNC, &v4l1_data.active_frame ) < 0 ) { Error( "Sync failure for frame %d buffer %d: %s", v4l1_data.active_frame, captures_per_frame, strerror(errno) ); - return NULL; + return -1; } captures_per_frame--; if ( captures_per_frame ) { Debug( 3, "Capturing frame %d", v4l1_data.active_frame ); if ( ioctl( vid_fd, VIDIOCMCAPTURE, &v4l1_data.buffers[v4l1_data.active_frame] ) < 0 ) { Error( "Capture failure for buffer %d (%d): %s", v4l1_data.active_frame, captures_per_frame, strerror(errno) ); - return NULL; + return -1; } } } @@ -1987,17 +1987,15 @@ ZMPacket *LocalCamera::Capture( Image &image ) { #endif // ZM_HAS_V4L1 } /* prime capture */ - ZMPacket *packet = new ZMPacket( &image ); if ( conversion_type != 0 ) { Debug( 3, "Performing format conversion" ); /* Request a writeable buffer of the target image */ - directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); + directbuffer = zm_packet.image->WriteBuffer(width, height, colours, subpixelorder); if ( directbuffer == NULL ) { Error("Failed requesting writeable buffer for the captured image."); - delete packet; - return NULL; + return -1; } #if HAVE_LIBSWSCALE if ( conversion_type == 1 ) { @@ -2012,11 +2010,18 @@ ZMPacket *LocalCamera::Capture( Image &image ) { avpicture_fill( (AVPicture *)tmpPicture, directbuffer, imagePixFormat, width, height ); #endif - sws_scale( imgConversionContext, capturePictures[capture_frame]->data, capturePictures[capture_frame]->linesize, 0, height, tmpPicture->data, tmpPicture->linesize ); + sws_scale( + imgConversionContext, + capturePictures[capture_frame]->data, + capturePictures[capture_frame]->linesize, + 0, + height, + tmpPicture->data, + tmpPicture->linesize + ); } else #endif if ( conversion_type == 2 ) { - Debug( 9, "Calling the conversion function" ); /* Call the image conversion function and convert directly into the shared memory */ (*conversion_fptr)(buffer, directbuffer, pixels); @@ -2024,17 +2029,17 @@ ZMPacket *LocalCamera::Capture( Image &image ) { // Need to store the jpeg data too Debug( 9, "Decoding the JPEG image" ); /* JPEG decoding */ - image.DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder); + zm_packet.image->DecodeJpeg(buffer, buffer_bytesused, colours, subpixelorder); } } else { Debug( 3, "No format conversion performed. Assigning the image" ); /* No conversion was performed, the image is in the V4L buffers and needs to be copied into the shared memory */ - image.Assign(width, height, colours, subpixelorder, buffer, imagesize); + zm_packet.image->Assign(width, height, colours, subpixelorder, buffer, imagesize); } // end if doing conversion or not - return packet; + return 1; } // end Capture int LocalCamera::PostCapture() { diff --git a/src/zm_local_camera.h b/src/zm_local_camera.h index f9c0e80e3..de1d7f9c5 100644 --- a/src/zm_local_camera.h +++ b/src/zm_local_camera.h @@ -156,7 +156,7 @@ public: int PrimeCapture(); int PreCapture(); - ZMPacket *Capture( Image &image ); + int Capture(ZMPacket &p); int PostCapture(); static bool GetCurrentSettings( const char *device, char *output, int version, bool verbose ); }; diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 0ce1d0f73..fe95023a5 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -453,6 +453,7 @@ Monitor::Monitor( adaptive_skip = true; ReloadLinkedMonitors( p_linked_monitors ); + videoStore = NULL; } // Monitor::Monitor bool Monitor::connect() { @@ -1145,14 +1146,15 @@ bool Monitor::CheckSignal( const Image *image ) { bool Monitor::Analyse() { if ( shared_data->last_read_index == shared_data->last_write_index ) { // I wonder how often this happens. Maybe if this happens we should sleep or something? - return( false ); + //Debug(3, " shared_data->last_read_index == shared_data->last_write_index " ); + return false; } struct timeval now; gettimeofday( &now, NULL ); if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) { - fps = double(fps_report_interval)/(now.tv_sec-last_fps_time); + fps = double(fps_report_interval)/(now.tv_sec - last_fps_time); Info( "%s: %d - Analysing at %.2f fps", name, image_count, fps ); static char sql[ZM_SQL_SML_BUFSIZ]; snprintf( sql, sizeof(sql), "UPDATE Monitors SET AnalysisFPS = '%.2lf' WHERE Id = '%d'", fps, id ); @@ -1177,7 +1179,8 @@ bool Monitor::Analyse() { int pending_frames = shared_data->last_write_index - shared_data->last_read_index; if ( pending_frames < 0 ) pending_frames += image_buffer_count; - Debug( 4, "ReadIndex:%d, WriteIndex: %d, PendingFrames = %d, ReadMargin = %d, Step = %d", shared_data->last_read_index, shared_data->last_write_index, pending_frames, read_margin, step ); + Debug( 4, "ReadIndex:%d, WriteIndex: %d, PendingFrames = %d, ReadMargin = %d, Step = %d", + shared_data->last_read_index, shared_data->last_write_index, pending_frames, read_margin, step ); if ( step <= pending_frames ) { index = (shared_data->last_read_index+step)%image_buffer_count; } else { @@ -1359,12 +1362,12 @@ Debug(3,"before DetectMotion"); if ( event ) { //TODO: We shouldn't have to do this every time. Not sure why it clears itself if this isn't here?? //snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - Debug( 3, "Detected new event at (%d.%d)", timestamp->tv_sec,timestamp->tv_usec ); + //Debug( 3, "Detected new event at (%d.%d)", timestamp->tv_sec,timestamp->tv_usec ); if ( section_length ) { // TODO: Wouldn't this be clearer if we just did something like if now - event->start > section_length ? int section_mod = timestamp->tv_sec % section_length; - Debug( 3, "Section length (%d) Last Section Mod(%d), new section mod(%d)", section_length, last_section_mod, section_mod ); + Debug( 4, "Section length (%d) Last Section Mod(%d), new section mod(%d)", section_length, last_section_mod, section_mod ); if ( section_mod < last_section_mod ) { //if ( state == IDLE || state == TAPE || event_close_mode == CLOSE_TIME ) { //if ( state == TAPE ) { @@ -2846,107 +2849,85 @@ Monitor *Monitor::Load( unsigned int p_id, bool load_zones, Purpose purpose ) { int Monitor::Capture() { static int FirstCapture = 1; // Used in de-interlacing to indicate whether this is the even or odd image - unsigned int index = image_count%image_buffer_count; + unsigned int index = image_count % image_buffer_count; Image* capture_image = image_buffer[index].image; - - unsigned int deinterlacing_value = deinterlacing & 0xff; - - ZMPacket *packet; + ZMPacket packet; + packet.set_image(capture_image); int captureResult = 0; + unsigned int deinterlacing_value = deinterlacing & 0xff; if ( deinterlacing_value == 4 ) { if ( FirstCapture != 1 ) { /* Copy the next image into the shared memory */ capture_image->CopyBuffer(*(next_buffer.image)); } - /* Capture a new next image */ - - packet = camera->Capture(*(next_buffer.image)); + captureResult = camera->Capture(packet); if ( FirstCapture ) { FirstCapture = 0; return 0; } - } else { - packet = camera->Capture(*capture_image); - if ( ! packet ) { - packet = new ZMPacket(capture_image); + captureResult = camera->Capture(packet); + if ( captureResult < 0 ) { // Unable to capture image for temporary reason // Fake a signal loss image Rgb signalcolor; - signalcolor = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); /* HTML colour code is actually BGR in memory, we want RGB */ + /* HTML colour code is actually BGR in memory, we want RGB */ + signalcolor = rgb_convert(signal_check_colour, ZM_SUBPIX_ORDER_BGR); capture_image->Fill(signalcolor); - } else { - captureResult = 1; + shared_data->signal = false; + return -1; } + int video_stream_id = camera->get_VideoStreamId(); - // Should maybe not write to videowriter when no signal FIXME //Video recording if ( video_store_data->recording.tv_sec ) { - if ( shared_data->last_event_id != this->GetVideoWriterEventId() ) { - Debug(2, "Have change of event. last_event(%d), our current (%d)", shared_data->last_event_id, this->GetVideoWriterEventId() ); - + Debug(2, "Have change of event. last_event(%d), our current (%d)", + shared_data->last_event_id, + this->GetVideoWriterEventId() + ); if ( videoStore ) { - Info("Re-starting video storage module"); - +Debug(2, "Have videostore already?"); // I don't know if this is important or not... but I figure we might as well write this last packet out to the store before closing it. // Also don't know how much it matters for audio. - int ret = videoStore->writePacket( packet ); - if ( ret < 0 ) { //Less than zero and we skipped a frame - Warning("Error writing last packet to videostore."); - } - - delete videoStore; - videoStore = NULL; - this->SetVideoWriterEventId( 0 ); + int ret = videoStore->writePacket( &packet ); + if ( ret < 0 ) { //Less than zero and we skipped a frame + Warning("Error writing last packet to videostore."); + } + + delete videoStore; + videoStore = NULL; + this->SetVideoWriterEventId( 0 ); } // end if videoStore } // end if end of recording if ( shared_data->last_event_id and ! videoStore ) { - //Instantiate the video storage module - - videoStore = new VideoStore((const char *) video_store_data->event_file, "mp4", +Debug(2,"New videostore"); + videoStore = new VideoStore( + (const char *) video_store_data->event_file, + "mp4", camera->get_VideoStream(), ( record_audio ? camera->get_AudioStream() : NULL ), + video_store_data->recording.tv_sec, this ); if ( ! videoStore->open() ) { delete videoStore; videoStore = NULL; - } else { - this->SetVideoWriterEventId( shared_data->last_event_id ); - - // Need to write out all the frames from the last keyframe? - // No... need to write out all frames from when the event began. Due to PreEventFrames, this could be more than since the last keyframe. - unsigned int packet_count = 0; - ZMPacket *queued_packet; + this->SetVideoWriterEventId(shared_data->last_event_id); + Debug(2, "Clearing packets"); // Clear all packets that predate the moment when the recording began - packetqueue.clear_unwanted_packets( &video_store_data->recording, video_stream_id ); - - while ( ( queued_packet = packetqueue.popPacket() ) ) { - AVPacket *avp = queued_packet->av_packet(); - - packet_count += 1; - //Write the packet to our video store - Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue.size() ); - int ret = videoStore->writePacket( queued_packet ); - if ( ret < 0 ) { - //Less than zero and we skipped a frame - } - delete queued_packet; - } // end while packets in the packetqueue - Debug(2, "Wrote %d queued packets", packet_count ); + packetqueue.clear_unwanted_packets(&video_store_data->recording, video_stream_id); + videoStore->write_packets(packetqueue); } // success opening } // end if ! was recording - - } else { - // Not recording + } else { // Not recording if ( videoStore ) { Info("Deleting videoStore instance"); delete videoStore; @@ -2954,42 +2935,33 @@ int Monitor::Capture() { this->SetVideoWriterEventId( 0 ); } - // Buffer video packets, since we are not recording. + // Buffer video packets, since we are not recording. // All audio packets are keyframes, so only if it's a video keyframe - if ( packet->packet.stream_index == video_stream_id ) { - if ( packet->keyframe ) { - Debug(3, "Clearing queue"); - packetqueue.clearQueue( this->GetPreEventCount(), video_stream_id ); - } - + if ( ( packet.packet.stream_index == video_stream_id ) && ( packet.keyframe ) ) { + packetqueue.clearQueue( this->GetPreEventCount(), video_stream_id ); } // The following lines should ensure that the queue always begins with a video keyframe - if ( packet->packet.stream_index == camera->get_AudioStreamId() ) { -//Debug(2, "Have audio packet, reocrd_audio is (%d) and packetqueue.size is (%d)", record_audio, packetqueue.size() ); + if ( packet.packet.stream_index == camera->get_AudioStreamId() ) { + //Debug(2, "Have audio packet, reocrd_audio is (%d) and packetqueue.size is (%d)", record_audio, packetqueue.size() ); if ( record_audio && packetqueue.size() ) { // if it's audio, and we are doing audio, and there is already something in the queue - packetqueue.queuePacket( packet ); + packetqueue.queuePacket( &packet ); } - } else if ( packet->packet.stream_index == video_stream_id ) { - if ( packet->keyframe || packetqueue.size() ) // it's a keyframe or we already have something in the queue - packetqueue.queuePacket( packet ); - } + } else if ( packet.packet.stream_index == video_stream_id ) { + if ( packet.keyframe || packetqueue.size() ) // it's a keyframe or we already have something in the queue + packetqueue.queuePacket( &packet ); + } // end if audio or video } // end if recording or not if ( videoStore ) { //Write the packet to our video store, it will be smart enough to know what to do - int ret = videoStore->writePacket( packet ); + int ret = videoStore->writePacket( &packet ); if ( ret < 0 ) { //Less than zero and we skipped a frame Warning("problem writing packet"); } } } // end if de-interlacing or not - if ( ! captureResult ) { - shared_data->signal = false; - return -1; - } // end if captureResults == 1 which is success I think - /* Deinterlacing */ if ( deinterlacing_value == 1 ) { capture_image->Deinterlace_Discard(); @@ -3046,17 +3018,26 @@ int Monitor::Capture() { shared_data->last_write_time = image_buffer[index].timestamp->tv_sec; image_count++; + if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) { - time_t now = image_buffer[index].timestamp->tv_sec; - fps = double(fps_report_interval)/(now-last_fps_time); - //Info( "%d -> %d -> %d", fps_report_interval, now, last_fps_time ); - //Info( "%d -> %d -> %lf -> %lf", now-last_fps_time, fps_report_interval/(now-last_fps_time), double(fps_report_interval)/(now-last_fps_time), fps ); - Info( "%s: %d - Capturing at %.2lf fps", name, image_count, fps ); - last_fps_time = now; - static char sql[ZM_SQL_SML_BUFSIZ]; - snprintf( sql, sizeof(sql), "UPDATE Monitors SET CaptureFPS = '%.2lf' WHERE Id = '%d'", fps, id ); - if ( mysql_query( &dbconn, sql ) ) { - Error( "Can't run query: %s", mysql_error( &dbconn ) ); + struct timeval now; + if ( !captureResult ) { + gettimeofday( &now, NULL ); + } else { + now.tv_sec = image_buffer[index].timestamp->tv_sec; + } + // If we are too fast, we get div by zero. This seems to happen in the case of audio packets. + if ( now.tv_sec != last_fps_time ) { + fps = double(fps_report_interval)/(now.tv_sec-last_fps_time); + Info( "%d -> %d -> %d", fps_report_interval, now.tv_sec, last_fps_time ); + //Info( "%d -> %d -> %lf -> %lf", now-last_fps_time, fps_report_interval/(now-last_fps_time), double(fps_report_interval)/(now-last_fps_time), fps ); + Info( "%s: %d - Capturing at %.2lf fps", name, image_count, fps ); + last_fps_time = now.tv_sec; + static char sql[ZM_SQL_SML_BUFSIZ]; + snprintf( sql, sizeof(sql), "UPDATE Monitors SET CaptureFPS='%.2lf' WHERE Id=%d", fps, id ); + if ( mysql_query( &dbconn, sql ) ) { + Error( "Can't run query: %s", mysql_error( &dbconn ) ); + } } } @@ -3075,8 +3056,8 @@ int Monitor::Capture() { camera->Contrast( shared_data->contrast ); shared_data->action &= ~SET_SETTINGS; } - return( 0 ); -} + return captureResult; +} // end Monitor::Capture void Monitor::TimestampImage( Image *ts_image, const struct timeval *ts_time ) const { if ( label_format[0] ) { diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 59eaa62df..4bd682ddf 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -434,6 +434,7 @@ public: int GetOptSaveJPEGs() const { return( savejpegspref ); } VideoWriter GetOptVideoWriter() const { return( videowriter ); } const std::vector* GetOptEncoderParams() const { return( &encoderparamsvec ); } + const std::string &GetEncoderOptions() const { return( encoderparams ); } uint32_t GetLastEventId() const { return shared_data->last_event_id; } uint32_t GetVideoWriterEventId() const { return video_store_data->current_event; } void SetVideoWriterEventId( uint32_t p_event_id ) { video_store_data->current_event = p_event_id; } diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index f904f23cf..010877b90 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -270,7 +270,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) { Debug( 1, "Got SCALE command, to %d", scale ); break; } - case CMD_QUIT : + case CMD_QUIT : { Info ("User initiated exit - CMD_QUIT"); break; @@ -316,7 +316,7 @@ void MonitorStream::processCommand( const CmdMsg *msg ) { //status_data.enabled = monitor->shared_data->active; status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF; status_data.forced = monitor->trigger_data->trigger_state==Monitor::TRIGGER_ON; - Debug( 2, "L:%d, D:%d, P:%d, R:%d, d:%.3f, Z:%d, E:%d F:%d", + Debug( 2, "Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d", status_data.buffer_level, status_data.delayed, status_data.paused, @@ -338,11 +338,15 @@ void MonitorStream::processCommand( const CmdMsg *msg ) { //exit( -1 ); } } +Debug(2, "NUmber of bytes sent: (%d)", nbytes ); // quit after sending a status, if this was a quit request - if ((MsgCommand)msg->msg_data[0]==CMD_QUIT) - exit(0); + if ( (MsgCommand)msg->msg_data[0]==CMD_QUIT ) { + Debug(2,"Quitting"); + exit(0); + } + Debug(2,"Updating framrate"); updateFrameRate( monitor->GetFPS() ); } // end void MonitorStream::processCommand( const CmdMsg *msg ) @@ -553,20 +557,28 @@ void MonitorStream::runStream() { Debug( 2, "Assigned temporary buffer" ); } } - } + } // end if connkey & playback_buffer float max_secs_since_last_sent_frame = 10.0; //should be > keep alive amount (5 secs) while ( !zm_terminate ) { bool got_command = false; if ( feof( stdout ) || ferror( stdout ) || !monitor->ShmValid() ) { + if ( feof( stdout ) ) { + Debug(2,"feof stdout"); + } else if ( ferror( stdout ) ) { + Debug(2,"ferror stdout"); + } else if ( !monitor->ShmValid() ) { + Debug(2,"monitor not valid.... maybe we should wait until it comes back."); + } break; } gettimeofday( &now, NULL ); if ( connkey ) { -Debug(2, "checking command Queue"); +//Debug(2, "checking command Queue for connkey: %d", connkey ); while(checkCommandQueue()) { +Debug(2, "Have checking command Queue for connkey: %d", connkey ); got_command = true; } } @@ -655,8 +667,10 @@ Debug(2, "checking command Queue"); // Send the next frame Monitor::Snapshot *snap = &monitor->image_buffer[index]; - if ( !sendFrame( snap->image, snap->timestamp ) ) + if ( !sendFrame( snap->image, snap->timestamp ) ) { + Debug(2, "sendFrame failed, quiting."); zm_terminate = true; + } memcpy( &last_frame_timestamp, snap->timestamp, sizeof(last_frame_timestamp) ); //frame_sent = true; @@ -693,9 +707,12 @@ Debug(2, "checking command Queue"); } // end if buffered playback frame_count++; } + unsigned long sleep_time = (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))); + Debug(2, "Sleeping for (%d)", sleep_time); usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) ); if ( ttl ) { if ( (now.tv_sec - stream_start_time) > ttl ) { + Debug(2, "now(%d) - start(%d) > ttl(%d) break", now.tv_sec, stream_start_time, ttl); break; } } diff --git a/src/zm_mpeg.cpp b/src/zm_mpeg.cpp index e949052dc..ee27ae383 100644 --- a/src/zm_mpeg.cpp +++ b/src/zm_mpeg.cpp @@ -117,10 +117,10 @@ void VideoStream::SetupFormat( ) { void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int height, int bitrate, double frame_rate ) { /* ffmpeg format matching */ - switch(colours) { + switch ( colours ) { case ZM_COLOUR_RGB24: { - if(subpixelorder == ZM_SUBPIX_ORDER_BGR) { + if ( subpixelorder == ZM_SUBPIX_ORDER_BGR ) { /* BGR subpixel order */ pf = AV_PIX_FMT_BGR24; } else { @@ -211,39 +211,52 @@ void VideoStream::SetupCodec( int colours, int subpixelorder, int width, int hei Debug( 1, "Allocated stream" ); - AVCodecContext *c = ost->codec; +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + codec_context = avcodec_alloc_context3(NULL); + avcodec_parameters_to_context(codec_context, ost->codecpar); +#else + codec_context = ost->codec; +#endif - c->codec_id = codec->id; - c->codec_type = codec->type; + codec_context->codec_id = codec->id; + codec_context->codec_type = codec->type; - c->pix_fmt = strcmp( "mjpeg", ofc->oformat->name ) == 0 ? AV_PIX_FMT_YUVJ422P : AV_PIX_FMT_YUV420P; + codec_context->pix_fmt = strcmp( "mjpeg", ofc->oformat->name ) == 0 ? AV_PIX_FMT_YUVJ422P : AV_PIX_FMT_YUV420P; if ( bitrate <= 100 ) { // Quality based bitrate control (VBR). Scale is 1..31 where 1 is best. // This gets rid of artifacts in the beginning of the movie; and well, even quality. - c->flags |= CODEC_FLAG_QSCALE; - c->global_quality = FF_QP2LAMBDA * (31 - (31 * (bitrate / 100.0))); +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + codec_context->flags |= AV_CODEC_FLAG_QSCALE; +#else + codec_context->flags |= CODEC_FLAG_QSCALE; +#endif + codec_context->global_quality = FF_QP2LAMBDA * (31 - (31 * (bitrate / 100.0))); } else { - c->bit_rate = bitrate; + codec_context->bit_rate = bitrate; } /* resolution must be a multiple of two */ - c->width = width; - c->height = height; + codec_context->width = width; + codec_context->height = height; /* time base: this is the fundamental unit of time (in seconds) in terms of which frame timestamps are represented. for fixed-fps content, timebase should be 1/framerate and timestamp increments should be identically 1. */ - c->time_base.den = frame_rate; - c->time_base.num = 1; + codec_context->time_base.den = frame_rate; + codec_context->time_base.num = 1; - Debug( 1, "Will encode in %d fps.", c->time_base.den ); + Debug( 1, "Will encode in %d fps.", codec_context->time_base.den ); /* emit one intra frame every second */ - c->gop_size = frame_rate; + codec_context->gop_size = frame_rate; // some formats want stream headers to be separate if ( of->flags & AVFMT_GLOBALHEADER ) - c->flags |= CODEC_FLAG_GLOBAL_HEADER; +#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) + codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else + codec_context->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif } else { Fatal( "of->video_codec == AV_CODEC_ID_NONE" ); } @@ -278,13 +291,11 @@ void VideoStream::OpenStream( ) { /* now that all the parameters are set, we can open the video codecs and allocate the necessary encode buffers */ if ( ost ) { - AVCodecContext *c = ost->codec; - /* open the codec */ #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) - if ( (avRet = avcodec_open( c, codec )) < 0 ) + if ( (avRet = avcodec_open( codec_context, codec )) < 0 ) #else - if ( (avRet = avcodec_open2( c, codec, 0 )) < 0 ) + if ( (avRet = avcodec_open2( codec_context, codec, 0 )) < 0 ) #endif { Fatal( "Could not open codec. Error code %d \"%s\"", avRet, av_err2str( avRet ) ); @@ -293,19 +304,15 @@ void VideoStream::OpenStream( ) { Debug( 1, "Opened codec" ); /* allocate the encoded raw picture */ -#if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) - opicture = av_frame_alloc( ); -#else - opicture = avcodec_alloc_frame( ); -#endif + opicture = zm_av_frame_alloc( ); if ( !opicture ) { Panic( "Could not allocate opicture" ); } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - int size = av_image_get_buffer_size( c->pix_fmt, c->width, c->height, 1 ); + int size = av_image_get_buffer_size( codec_context->pix_fmt, codec_context->width, codec_context->height, 1 ); #else - int size = avpicture_get_size( c->pix_fmt, c->width, c->height ); + int size = avpicture_get_size( codec_context->pix_fmt, codec_context->width, codec_context->height ); #endif uint8_t *opicture_buf = (uint8_t *)av_malloc( size ); @@ -315,17 +322,17 @@ void VideoStream::OpenStream( ) { } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(opicture->data, opicture->linesize, - opicture_buf, c->pix_fmt, c->width, c->height, 1); + opicture_buf, codec_context->pix_fmt, codec_context->width, codec_context->height, 1); #else - avpicture_fill( (AVPicture *)opicture, opicture_buf, c->pix_fmt, - c->width, c->height ); + avpicture_fill( (AVPicture *)opicture, opicture_buf, codec_context->pix_fmt, + codec_context->width, codec_context->height ); #endif /* if the output format is not identical to the input format, then a temporary picture is needed too. It is then converted to the required output format */ tmp_opicture = NULL; - if ( c->pix_fmt != pf ) { + if ( codec_context->pix_fmt != pf ) { #if LIBAVCODEC_VERSION_CHECK(55, 28, 1, 45, 101) tmp_opicture = av_frame_alloc( ); #else @@ -335,9 +342,9 @@ void VideoStream::OpenStream( ) { Panic( "Could not allocate tmp_opicture" ); } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - int size = av_image_get_buffer_size( pf, c->width, c->height,1 ); + int size = av_image_get_buffer_size( pf, codec_context->width, codec_context->height,1 ); #else - int size = avpicture_get_size( pf, c->width, c->height ); + int size = avpicture_get_size( pf, codec_context->width, codec_context->height ); #endif uint8_t *tmp_opicture_buf = (uint8_t *)av_malloc( size ); if ( !tmp_opicture_buf ) { @@ -347,10 +354,10 @@ void VideoStream::OpenStream( ) { #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) av_image_fill_arrays(tmp_opicture->data, tmp_opicture->linesize, tmp_opicture_buf, pf, - c->width, c->height, 1); + codec_context->width, codec_context->height, 1); #else avpicture_fill( (AVPicture *)tmp_opicture, - tmp_opicture_buf, pf, c->width, c->height ); + tmp_opicture_buf, pf, codec_context->width, codec_context->height ); #endif } } @@ -375,7 +382,12 @@ void VideoStream::OpenStream( ) { } video_outbuf = NULL; +#if LIBAVFORMAT_VERSION_CHECK(57, 0, 0, 0, 0) + if (codec_context->codec_type == AVMEDIA_TYPE_VIDEO && + codec_context->codec_id == AV_CODEC_ID_RAWVIDEO) { +#else if ( !(of->flags & AVFMT_RAWPICTURE) ) { +#endif /* allocate output buffer */ /* XXX: API change will be done */ // TODO: Make buffer dynamic. @@ -446,6 +458,8 @@ VideoStream::VideoStream( const char *in_filename, const char *in_format, int bi if ( pthread_mutex_init( buffer_copy_lock, NULL ) != 0 ) { Fatal("pthread_mutex_init failed"); } + + codec_context = NULL; } VideoStream::~VideoStream( ) { @@ -481,7 +495,7 @@ VideoStream::~VideoStream( ) { /* close each codec */ if ( ost ) { - avcodec_close( ost->codec ); + avcodec_close( codec_context ); av_free( opicture->data[0] ); av_frame_free( &opicture ); if ( tmp_opicture ) { @@ -564,17 +578,15 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, static struct SwsContext *img_convert_ctx = 0; #endif // HAVE_LIBSWSCALE - AVCodecContext *c = ost->codec; - - if ( c->pix_fmt != pf ) { + if ( codec_context->pix_fmt != pf ) { memcpy( tmp_opicture->data[0], buffer, buffer_size ); #ifdef HAVE_LIBSWSCALE if ( !img_convert_ctx ) { - img_convert_ctx = sws_getCachedContext( NULL, c->width, c->height, pf, c->width, c->height, c->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL ); + img_convert_ctx = sws_getCachedContext( NULL, codec_context->width, codec_context->height, pf, codec_context->width, codec_context->height, codec_context->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL ); if ( !img_convert_ctx ) Panic( "Unable to initialise image scaling context" ); } - sws_scale( img_convert_ctx, tmp_opicture->data, tmp_opicture->linesize, 0, c->height, opicture->data, opicture->linesize ); + sws_scale( img_convert_ctx, tmp_opicture->data, tmp_opicture->linesize, 0, codec_context->height, opicture->data, opicture->linesize ); #else // HAVE_LIBSWSCALE Fatal( "swscale is required for MPEG mode" ); #endif // HAVE_LIBSWSCALE @@ -586,7 +598,13 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, AVPacket *pkt = packet_buffers[packet_index]; av_init_packet( pkt ); int got_packet = 0; +#if LIBAVFORMAT_VERSION_CHECK(57, 0, 0, 0, 0) + if (codec_context->codec_type == AVMEDIA_TYPE_VIDEO && + codec_context->codec_id == AV_CODEC_ID_RAWVIDEO) { +#else if ( of->flags & AVFMT_RAWPICTURE ) { +#endif + #if LIBAVCODEC_VERSION_CHECK(52, 30, 2, 30, 2) pkt->flags |= AV_PKT_FLAG_KEY; #else @@ -597,19 +615,34 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, pkt->size = sizeof (AVPicture); got_packet = 1; } else { - opicture_ptr->pts = c->frame_number; - opicture_ptr->quality = c->global_quality; + opicture_ptr->pts = codec_context->frame_number; + opicture_ptr->quality = codec_context->global_quality; + +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + // Put encoder into flushing mode + avcodec_send_frame(codec_context, opicture_ptr); + int ret = avcodec_receive_packet(codec_context, pkt); + if ( ret < 0 ) { + if ( AVERROR_EOF != ret ) { + Error("ERror encoding video (%d) (%s)", ret, + av_err2str(ret)); + } + } else { + got_packet = 1; + } +#else #if LIBAVFORMAT_VERSION_CHECK(54, 1, 0, 2, 100) - int ret = avcodec_encode_video2( c, pkt, opicture_ptr, &got_packet ); + int ret = avcodec_encode_video2( codec_context, pkt, opicture_ptr, &got_packet ); if ( ret != 0 ) { Fatal( "avcodec_encode_video2 failed with errorcode %d \"%s\"", ret, av_err2str( ret ) ); } #else - int out_size = avcodec_encode_video( c, video_outbuf, video_outbuf_size, opicture_ptr ); + int out_size = avcodec_encode_video( codec_context, video_outbuf, video_outbuf_size, opicture_ptr ); got_packet = out_size > 0 ? 1 : 0; pkt->data = got_packet ? video_outbuf : NULL; pkt->size = got_packet ? out_size : 0; +#endif #endif if ( got_packet ) { // if ( c->coded_frame->key_frame ) @@ -622,12 +655,12 @@ double VideoStream::ActuallyEncodeFrame( const uint8_t *buffer, int buffer_size, // } if ( pkt->pts != (int64_t)AV_NOPTS_VALUE ) { - pkt->pts = av_rescale_q( pkt->pts, c->time_base, ost->time_base ); + pkt->pts = av_rescale_q( pkt->pts, codec_context->time_base, ost->time_base ); } if ( pkt->dts != (int64_t)AV_NOPTS_VALUE ) { - pkt->dts = av_rescale_q( pkt->dts, c->time_base, ost->time_base ); + pkt->dts = av_rescale_q( pkt->dts, codec_context->time_base, ost->time_base ); } - pkt->duration = av_rescale_q( pkt->duration, c->time_base, ost->time_base ); + pkt->duration = av_rescale_q( pkt->duration, codec_context->time_base, ost->time_base ); pkt->stream_index = ost->index; } } @@ -658,8 +691,12 @@ void *VideoStream::StreamingThreadCallback(void *ctx){ VideoStream* videoStream = reinterpret_cast(ctx); const uint64_t nanosecond_multiplier = 1000000000; - - uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->ost->codec->time_base.num) / (videoStream->ost->codec->time_base.den) ); + +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->codec_context->time_base.num) / (videoStream->codec_context->time_base.den) ); +#else + uint64_t target_interval_ns = nanosecond_multiplier * ( ((double)videoStream->codec_context->time_base.num) / (videoStream->codec_context->time_base.den) ); +#endif uint64_t frame_count = 0; timespec start_time; clock_gettime(CLOCK_MONOTONIC, &start_time); diff --git a/src/zm_mpeg.h b/src/zm_mpeg.h index 931f9d687..f943b4fd7 100644 --- a/src/zm_mpeg.h +++ b/src/zm_mpeg.h @@ -46,6 +46,7 @@ protected: AVOutputFormat *of; AVFormatContext *ofc; AVStream *ost; + AVCodecContext *codec_context; AVCodec *codec; AVFrame *opicture; AVFrame *tmp_opicture; diff --git a/src/zm_packet.cpp b/src/zm_packet.cpp index 34a78bd26..5056cf1c3 100644 --- a/src/zm_packet.cpp +++ b/src/zm_packet.cpp @@ -29,6 +29,7 @@ ZMPacket::ZMPacket( ) { image = NULL; frame = NULL; av_init_packet( &packet ); + packet.size = 0; gettimeofday( ×tamp, NULL ); } @@ -42,24 +43,31 @@ ZMPacket::ZMPacket( Image *i ) { ZMPacket::ZMPacket( AVPacket *p ) { av_init_packet( &packet ); - if ( zm_av_packet_ref( &packet, p ) < 0 ) { - Error("error refing packet"); - } - gettimeofday( ×tamp, NULL ); + set_packet( p ); keyframe = p->flags & AV_PKT_FLAG_KEY; } ZMPacket::ZMPacket( AVPacket *p, struct timeval *t ) { av_init_packet( &packet ); - if ( zm_av_packet_ref( &packet, p ) < 0 ) { - Error("error refing packet"); - } + set_packet( p ); timestamp = *t; keyframe = p->flags & AV_PKT_FLAG_KEY; } +ZMPacket::ZMPacket( AVPacket *p, AVFrame *f, Image *i ) { + av_init_packet( &packet ); + set_packet( p ); + image = i; + frame = f; +} ZMPacket::~ZMPacket() { zm_av_packet_unref( &packet ); + if ( frame ) { + av_frame_free( &frame ); + } + if ( image ) { + delete image; + } } int ZMPacket::decode( AVCodecContext *ctx ) { @@ -74,8 +82,8 @@ int ZMPacket::decode( AVCodecContext *ctx ) { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) int ret = avcodec_send_packet( ctx, &packet ); if ( ret < 0 ) { - Error( "Unable to send packet: %s", av_make_error_string( ret ) ); - av_frame_free( &frame ); + Error( "Unable to send packet: %s", av_make_error_string(ret).c_str() ); + av_frame_free( &frame ); return 0; } @@ -83,13 +91,13 @@ int ZMPacket::decode( AVCodecContext *ctx ) { if ( hwaccel ) { ret = avcodec_receive_frame( ctx, hwFrame ); if ( ret < 0 ) { - Error( "Unable to receive frame: %s", av_make_error_string( ret ) ); + Error( "Unable to receive frame: %s", av_make_error_string(ret).c_str() ); av_frame_free( &frame ); return 0; } ret = av_hwframe_transfer_data(frame, hwFrame, 0); if ( ret < 0 ) { - Error( "Unable to transfer frame: %s", av_make_error_string( ret ) ); + Error( "Unable to transfer frame: %s", av_make_error_string(ret).c_str() ); av_frame_free( &frame ); return 0; } @@ -97,7 +105,7 @@ int ZMPacket::decode( AVCodecContext *ctx ) { #endif ret = avcodec_receive_frame( ctx, frame ); if ( ret < 0 ) { - Error( "Unable to receive frame: %s", av_make_error_string( ret ) ); + Error( "Unable to receive frame: %s", av_make_error_string(ret).c_str() ); av_frame_free( &frame ); return 0; } @@ -108,15 +116,15 @@ int ZMPacket::decode( AVCodecContext *ctx ) { # else int frameComplete = 0; - ret = zm_avcodec_decode_video( ctx, frame, &frameComplete, &packet ); + int ret = zm_avcodec_decode_video( ctx, frame, &frameComplete, &packet ); if ( ret < 0 ) { - Error( "Unable to decode frame at frame %s", av_make_error_string( ret ) ); - av_frame_free( &frame ); + Error( "Unable to decode frame at frame %s", av_make_error_string(ret).c_str() ); + av_frame_free( &frame ); return 0; } if ( ! frameComplete ) { Debug(1, "incomplete frame?"); - av_frame_free( &frame ); + av_frame_free( &frame ); return 0; } #endif @@ -124,12 +132,10 @@ int ZMPacket::decode( AVCodecContext *ctx ) { } // end ZMPacket::decode Image * ZMPacket::get_image( Image *i = NULL ) { - if ( ! frame ) { Error("Can't get image without frame.. maybe need to decode first"); return NULL; } - if ( ! image ) { if ( ! i ) { Error("Need a pre-allocated image buffer"); @@ -137,27 +143,19 @@ Image * ZMPacket::get_image( Image *i = NULL ) { } image = i; } - /* Request a writeable buffer of the target image */ - uint8_t* directbuffer = image->WriteBuffer(); - //uint8_t* directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); - if ( directbuffer == NULL ) { - Error("Failed requesting writeable buffer for the captured image."); - image = NULL; - return NULL; - } - - AVFrame *mFrame = zm_av_frame_alloc(); -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(mFrame->data, mFrame->linesize, directbuffer, imagePixFormat, frame->width, frame->height, 1); -#else - avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, frame->width, frame->height); -#endif - if (sws_scale(mConvertContext, frame->data, frame->linesize, - 0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) { - Fatal("Unable to convert raw format %u to target format %u", - mVideoCodecContext->pix_fmt, imagePixFormat); - } - av_frame_free( &mFrame ); + image->Assign( frame ); return image; } +Image *ZMPacket::set_image( Image *i ) { + image = i; + return image; +} + +AVPacket *ZMPacket::set_packet( AVPacket *p ) { + if ( zm_av_packet_ref( &packet, p ) < 0 ) { + Error("error refing packet"); + } + gettimeofday( ×tamp, NULL ); + keyframe = p->flags & AV_PKT_FLAG_KEY; +} diff --git a/src/zm_packet.h b/src/zm_packet.h index 75532ac7c..bf1b65d4c 100644 --- a/src/zm_packet.h +++ b/src/zm_packet.h @@ -40,15 +40,20 @@ class ZMPacket { struct timeval timestamp; public: AVPacket *av_packet() { return &packet; } + AVPacket *set_packet( AVPacket *p ) ; AVFrame *av_frame() { return frame; } Image *get_image( Image * ); + Image *set_image( Image * ); + int is_keyframe() { return keyframe; }; int decode( AVCodecContext *ctx ); ZMPacket( AVPacket *packet, struct timeval *timestamp ); ZMPacket( AVPacket *packet ); + ZMPacket( AVPacket *packet, AVFrame *frame, Image *image ); ZMPacket( Image *image ); ZMPacket(); ~ZMPacket(); + }; #endif /* ZM_PACKET_H */ diff --git a/src/zm_remote_camera.cpp b/src/zm_remote_camera.cpp index 6afc7251b..58d92b73d 100644 --- a/src/zm_remote_camera.cpp +++ b/src/zm_remote_camera.cpp @@ -103,6 +103,7 @@ int RemoteCamera::Read( int fd, char *buf, int size ) { int bytes_to_recv = size - ReceivedBytes; if ( SOCKET_BUF_SIZE < bytes_to_recv ) bytes_to_recv = SOCKET_BUF_SIZE; +//Debug(3, "Aiming to receive %d of %d bytes", bytes_to_recv, size ); bytes = recv(fd, &buf[ReceivedBytes], bytes_to_recv, 0); //socket, buffer, len, flags if ( bytes <= 0 ) { Error("RemoteCamera::Read Recv error. Closing Socket\n"); diff --git a/src/zm_remote_camera.h b/src/zm_remote_camera.h index bf04f9bd6..545fce48f 100644 --- a/src/zm_remote_camera.h +++ b/src/zm_remote_camera.h @@ -87,8 +87,9 @@ public: virtual void Terminate() = 0; virtual int Connect() = 0; virtual int Disconnect() = 0; - virtual int PreCapture() = 0; - virtual ZMPacket *Capture( Image &image ) = 0; + virtual int PreCapture() { return 0; }; + virtual int PrimeCapture() { return 0; }; + virtual int Capture( ZMPacket &p ) = 0; virtual int PostCapture() = 0; int Read( int fd, char*buf, int size ); }; diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index e78b8a295..56b3e459e 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -75,26 +75,22 @@ RemoteCameraHttp::RemoteCameraHttp( method = REGEXP; else Fatal( "Unrecognised method '%s' when creating HTTP camera %d", p_method.c_str(), monitor_id ); - if ( capture ) - { + if ( capture ) { Initialise(); } + video_stream = NULL; } -RemoteCameraHttp::~RemoteCameraHttp() -{ - if ( capture ) - { +RemoteCameraHttp::~RemoteCameraHttp() { + if ( capture ) { Terminate(); } } -void RemoteCameraHttp::Initialise() -{ +void RemoteCameraHttp::Initialise() { RemoteCamera::Initialise(); - if ( request.empty() ) - { + if ( request.empty() ) { request = stringtf( "GET %s HTTP/%s\r\n", path.c_str(), config.http_version ); request += stringtf( "User-Agent: %s/%s\r\n", config.http_ua, ZM_VERSION ); request += stringtf( "Host: %s\r\n", host.c_str()); @@ -106,8 +102,7 @@ void RemoteCameraHttp::Initialise() Debug( 2, "Request: %s", request.c_str() ); } - if ( !timeout.tv_sec ) - { + if ( !timeout.tv_sec ) { timeout.tv_sec = config.http_timeout/1000; timeout.tv_usec = (config.http_timeout%1000)*1000; } @@ -121,21 +116,17 @@ void RemoteCameraHttp::Initialise() state = HEADER; } -int RemoteCameraHttp::Connect() -{ +int RemoteCameraHttp::Connect() { struct addrinfo *p; - for(p = hp; p != NULL; p = p->ai_next) - { + for ( p = hp; p != NULL; p = p->ai_next ) { sd = socket( p->ai_family, p->ai_socktype, p->ai_protocol ); - if ( sd < 0 ) - { + if ( sd < 0 ) { Warning("Can't create socket: %s", strerror(errno) ); continue; } - if ( connect( sd, p->ai_addr, p->ai_addrlen ) < 0 ) - { + if ( connect( sd, p->ai_addr, p->ai_addrlen ) < 0 ) { close(sd); sd = -1; char buf[sizeof(struct in6_addr)]; @@ -151,7 +142,7 @@ int RemoteCameraHttp::Connect() break; } - if(p == NULL) { + if ( p == NULL ) { Error("Unable to connect to the remote camera, aborting"); return( -1 ); } @@ -160,16 +151,14 @@ int RemoteCameraHttp::Connect() return( sd ); } -int RemoteCameraHttp::Disconnect() -{ +int RemoteCameraHttp::Disconnect() { close( sd ); sd = -1; Debug( 3, "Disconnected from host" ); return( 0 ); } -int RemoteCameraHttp::SendRequest() -{ +int RemoteCameraHttp::SendRequest() { Debug( 2, "Sending request: %s", request.c_str() ); if ( write( sd, request.data(), request.length() ) < 0 ) { @@ -250,12 +239,12 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { // There can be lots of bytes available. I've seen 4MB or more. This will vastly inflate our buffer size unnecessarily. if ( total_bytes_to_read > ZM_NETWORK_BUFSIZ ) { total_bytes_to_read = ZM_NETWORK_BUFSIZ; - Debug(3, "Just getting 32K" ); + Debug(4, "Just getting 32K" ); } else { - Debug(3, "Just getting %d", total_bytes_to_read ); + Debug(4, "Just getting %d", total_bytes_to_read ); } } // end if bytes_expected or not - Debug( 3, "Expecting %d bytes", total_bytes_to_read ); + Debug( 4, "Expecting %d bytes", total_bytes_to_read ); int total_bytes_read = 0; do { @@ -1036,59 +1025,65 @@ int RemoteCameraHttp::PreCapture() { return( 0 ); } // end int RemoteCameraHttp::PreCapture() -ZMPacket * RemoteCameraHttp::Capture( Image &image ) { +int RemoteCameraHttp::Capture( ZMPacket &packet ) { int content_length = GetResponse(); if ( content_length == 0 ) { Warning( "Unable to capture image, retrying" ); - return NULL; + return 0; } if ( content_length < 0 ) { Error( "Unable to get response, disconnecting" ); Disconnect(); - return NULL; + return -1; } + Image *image = packet.image; + switch( format ) { case JPEG : - { - if ( !image.DecodeJpeg( buffer.extract( content_length ), content_length, colours, subpixelorder ) ) { - Error( "Unable to decode jpeg" ); - Disconnect(); - return NULL; - } - break; - } - case X_RGB : - { - if ( content_length != (long)image.Size() ) { - Error( "Image length mismatch, expected %d bytes, content length was %d", image.Size(), content_length ); - Disconnect(); - return NULL; - } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - break; - } - case X_RGBZ : - { - if ( !image.Unzip( buffer.extract( content_length ), content_length ) ) { - Error( "Unable to unzip RGB image" ); - Disconnect(); - return NULL; - } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - break; - } - default : - { - Error( "Unexpected image format encountered" ); + if ( !image->DecodeJpeg( buffer.extract( content_length ), content_length, colours, subpixelorder ) ) { + Error( "Unable to decode jpeg" ); Disconnect(); - return NULL; + return -1; } + break; + case X_RGB : + if ( content_length != (long)image->Size() ) { + Error( "Image length mismatch, expected %d bytes, content length was %d", image->Size(), content_length ); + Disconnect(); + return -1; + } + image->Assign( width, height, colours, subpixelorder, buffer, imagesize ); + break; + case X_RGBZ : + if ( !image->Unzip( buffer.extract( content_length ), content_length ) ) { + Error( "Unable to unzip RGB image" ); + Disconnect(); + return -1; + } + image->Assign( width, height, colours, subpixelorder, buffer, imagesize ); + break; + default : + Error( "Unexpected image format encountered" ); + Disconnect(); + return -1; } - ZMPacket *packet = new ZMPacket( &image ); - return packet; + return 1; } // end ZmPacket *RmoteCameraHttp::Capture( &image ); int RemoteCameraHttp::PostCapture() { - return( 0 ); + return 0; +} + +AVStream *RemoteCameraHttp::get_VideoStream() { + if ( video_stream ) { + AVFormatContext *oc = avformat_alloc_context(); + video_stream = avformat_new_stream( oc, NULL ); + if ( video_stream ) { + video_stream->codec->width = width; + video_stream->codec->height = height; + video_stream->codec->pix_fmt = GetFFMPEGPixelFormat(colours,subpixelorder); + } + } + return video_stream; } diff --git a/src/zm_remote_camera_http.h b/src/zm_remote_camera_http.h index 792e431b2..d2093c4d5 100644 --- a/src/zm_remote_camera_http.h +++ b/src/zm_remote_camera_http.h @@ -70,8 +70,9 @@ public: int ReadData( Buffer &buffer, unsigned int bytes_expected=0 ); int GetResponse(); int PreCapture(); - ZMPacket *Capture( Image &image ); + int Capture( ZMPacket &p ); int PostCapture(); +AVStream* get_VideoStream(); }; #endif // ZM_REMOTE_CAMERA_HTTP_H diff --git a/src/zm_remote_camera_nvsocket.cpp b/src/zm_remote_camera_nvsocket.cpp index f265b60f3..88227735b 100644 --- a/src/zm_remote_camera_nvsocket.cpp +++ b/src/zm_remote_camera_nvsocket.cpp @@ -67,6 +67,7 @@ RemoteCameraNVSocket::RemoteCameraNVSocket( timeout.tv_sec = 0; timeout.tv_usec = 0; + subpixelorder = ZM_SUBPIX_ORDER_BGR; if ( capture ) { Initialise(); @@ -97,43 +98,39 @@ void RemoteCameraNVSocket::Initialise() { } int RemoteCameraNVSocket::Connect() { + int port_num = atoi(port.c_str()); //struct addrinfo *p; -struct sockaddr_in servaddr; - bzero( &servaddr, sizeof(servaddr)); - servaddr.sin_family = AF_INET; - servaddr.sin_addr.s_addr = htons(INADDR_ANY); - servaddr.sin_port = htons(atoi(port.c_str())); + struct sockaddr_in servaddr; + bzero( &servaddr, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htons(INADDR_ANY); + servaddr.sin_port = htons(port_num); - - sd = socket(AF_INET, SOCK_STREAM, 0); + sd = socket(AF_INET, SOCK_STREAM, 0); //for(p = hp; p != NULL; p = p->ai_next) { - //sd = socket( p->ai_family, p->ai_socktype, p->ai_protocol ); - if ( sd < 0 ) { - Warning("Can't create socket: %s", strerror(errno) ); - //continue; - return -1; - } - - //if ( connect( sd, p->ai_addr, p->ai_addrlen ) < 0 ) { - if ( connect( sd, (struct sockaddr *)&servaddr , sizeof(servaddr) ) < 0 ) { - close(sd); - sd = -1; - - Warning("Can't connect to socket mid: %d : %s", monitor_id, strerror(errno) ); - return -1; - //continue; - //} - /* If we got here, we must have connected successfully */ - //break; + //sd = socket( p->ai_family, p->ai_socktype, p->ai_protocol ); + if ( sd < 0 ) { + Warning("Can't create socket: %s", strerror(errno) ); + //continue; + return -1; } - //if ( p == NULL ) { - //Error("Unable to connect to the remote camera, aborting"); - //return( -1 ); - //} + //if ( connect( sd, p->ai_addr, p->ai_addrlen ) < 0 ) { + if ( connect( sd, (struct sockaddr *)&servaddr , sizeof(servaddr) ) < 0 ) { + close(sd); + sd = -1; - Debug( 3, "Connected to host, socket = %d", sd ); - return( sd ); + Warning("Can't connect to socket mid: %d : %s", monitor_id, strerror(errno) ); + return -1; + } + +//if ( p == NULL ) { +//Error("Unable to connect to the remote camera, aborting"); +//return( -1 ); +//} + + Debug( 3, "Connected to host:%d, socket = %d", port_num, sd ); + return sd; } int RemoteCameraNVSocket::Disconnect() { @@ -144,132 +141,33 @@ int RemoteCameraNVSocket::Disconnect() { } int RemoteCameraNVSocket::SendRequest( std::string request ) { - Debug( 2, "Sending request: %s", request.c_str() ); + Debug( 4, "Sending request: %s", request.c_str() ); if ( write( sd, request.data(), request.length() ) < 0 ) { Error( "Can't write: %s", strerror(errno) ); Disconnect(); return( -1 ); } - Debug( 3, "Request sent" ); + Debug( 4, "Request sent" ); return( 0 ); } -/* Return codes are as follows: - * -1 means there was an error - * 0 means no bytes were returned but there wasn't actually an error. - * > 0 is the # of bytes read. - */ - -int RemoteCameraNVSocket::ReadData( Buffer &buffer, unsigned int bytes_expected ) { - fd_set rfds; - FD_ZERO(&rfds); - FD_SET(sd, &rfds); - - struct timeval temp_timeout = timeout; - - int n_found = select(sd+1, &rfds, NULL, NULL, &temp_timeout); - if ( n_found == 0 ) { - Debug( 4, "Select timed out timeout was %d secs %d usecs", temp_timeout.tv_sec, temp_timeout.tv_usec ); - int error = 0; - socklen_t len = sizeof(error); - int retval = getsockopt(sd, SOL_SOCKET, SO_ERROR, &error, &len); - if ( retval != 0 ) { - Debug(1, "error getting socket error code %s", strerror(retval)); - } - if ( error != 0 ) { - return -1; - } - // Why are we disconnecting? It's just a timeout, meaning that data wasn't available. - //Disconnect(); - return 0; - } else if ( n_found < 0 ) { - Error("Select error: %s", strerror(errno)); - return -1; - } - - unsigned int total_bytes_to_read = 0; - - if ( bytes_expected ) { - total_bytes_to_read = bytes_expected; - } else { - if ( ioctl( sd, FIONREAD, &total_bytes_to_read ) < 0 ) { - Error( "Can't ioctl(): %s", strerror(errno) ); - return( -1 ); - } - - if ( total_bytes_to_read == 0 ) { - if ( mode == SINGLE_IMAGE ) { - int error = 0; - socklen_t len = sizeof (error); - int retval = getsockopt( sd, SOL_SOCKET, SO_ERROR, &error, &len ); - if(retval != 0 ) { - Debug( 1, "error getting socket error code %s", strerror(retval) ); - } - if (error != 0) { - return -1; - } - // Case where we are grabbing a single jpg, but no content-length was given, so the expectation is that we read until close. - return( 0 ); - } - // If socket is closed locally, then select will fail, but if it is closed remotely - // then we have an exception on our socket.. but no data. - Debug( 3, "Socket closed remotely" ); - //Disconnect(); // Disconnect is done outside of ReadData now. - return( -1 ); - } - - // There can be lots of bytes available. I've seen 4MB or more. This will vastly inflate our buffer size unnecessarily. - if ( total_bytes_to_read > ZM_NETWORK_BUFSIZ ) { - total_bytes_to_read = ZM_NETWORK_BUFSIZ; - Debug(3, "Just getting 32K" ); - } else { - Debug(3, "Just getting %d", total_bytes_to_read ); - } - } // end if bytes_expected or not - Debug( 3, "Expecting %d bytes", total_bytes_to_read ); - - int total_bytes_read = 0; - do { - int bytes_read = buffer.read_into( sd, total_bytes_to_read ); - if ( bytes_read < 0 ) { - Error( "Read error: %s", strerror(errno) ); - return( -1 ); - } else if ( bytes_read == 0 ) { - Debug( 2, "Socket closed" ); - //Disconnect(); // Disconnect is done outside of ReadData now. - return( -1 ); - } else if ( (unsigned int)bytes_read < total_bytes_to_read ) { - Error( "Incomplete read, expected %d, got %d", total_bytes_to_read, bytes_read ); - return( -1 ); - } - Debug( 3, "Read %d bytes", bytes_read ); - total_bytes_read += bytes_read; - total_bytes_to_read -= bytes_read; - } while ( total_bytes_to_read ); - - Debug( 4, buffer ); - - return( total_bytes_read ); -} - -int RemoteCameraNVSocket::PreCapture() { +int RemoteCameraNVSocket::PrimeCapture() { if ( sd < 0 ) { Connect(); if ( sd < 0 ) { Error( "Unable to connect to camera" ); return( -1 ); } - mode = SINGLE_IMAGE; - buffer.clear(); } -struct image_def { - uint16_t width; - uint16_t height; - uint16_t type; -}; -struct image_def image_def; + buffer.clear(); + struct image_def { + uint16_t width; + uint16_t height; + uint16_t type; + }; + struct image_def image_def; - if ( SendRequest("GetImageParams") < 0 ) { + if ( SendRequest("GetImageParams\n") < 0 ) { Error( "Unable to send request" ); Disconnect(); return -1; @@ -288,22 +186,29 @@ struct image_def image_def; return 0; } -ZMPacket * RemoteCameraNVSocket::Capture( Image &image ) { - if ( SendRequest("GetNextImage") < 0 ) { +int RemoteCameraNVSocket::Capture( ZMPacket &zm_packet ) { + if ( SendRequest("GetNextImage\n") < 0 ) { Warning( "Unable to capture image, retrying" ); - return NULL; + return 0; } if ( Read( sd, buffer, imagesize ) < imagesize ) { Warning( "Unable to capture image, retrying" ); - return NULL; + return 0; + } + uint32_t end; + if ( Read(sd, (char *) &end , sizeof(end)) < 0 ) { + Warning( "Unable to capture image, retrying" ); + return 0; + } + if ( end != 0xFFFFFFFF) { + Warning("End Bytes Failed\n"); + return 0; } - image.Assign( width, height, colours, subpixelorder, buffer, imagesize ); - ZMPacket *packet = new ZMPacket( &image ); - return packet; + zm_packet.image->Assign( width, height, colours, subpixelorder, buffer, imagesize ); + return 1; } -int RemoteCameraNVSocket::PostCapture() -{ +int RemoteCameraNVSocket::PostCapture() { return( 0 ); } diff --git a/src/zm_remote_camera_nvsocket.h b/src/zm_remote_camera_nvsocket.h index 64d060007..990d34aec 100644 --- a/src/zm_remote_camera_nvsocket.h +++ b/src/zm_remote_camera_nvsocket.h @@ -65,12 +65,10 @@ bool p_record_audio ); int Connect(); int Disconnect(); int SendRequest( std::string ); - int ReadData( Buffer &buffer, unsigned int bytes_expected=0 ); int GetResponse(); - int PreCapture(); - ZMPacket * Capture( Image &image ); + int PrimeCapture(); + int Capture( ZMPacket &p ); int PostCapture(); - int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return(0);}; }; #endif // ZM_REMOTE_CAMERA_NVSOCKET_H diff --git a/src/zm_remote_camera_rtsp.cpp b/src/zm_remote_camera_rtsp.cpp index e759f03f3..419cdb9fd 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -154,13 +154,19 @@ int RemoteCameraRtsp::PrimeCapture() { // Find first video stream present mVideoStreamId = -1; mAudioStreamId = -1; + // Find the first video stream. for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) { -#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + AVCodecParameters *codec_context = mFormatContext->streams[i]->codecpar; #else - if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO ) + AVCodecContext *codec_context = mFormatContext->streams[i]->codec; +#endif +#if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) + if ( codec_context->codec_type == AVMEDIA_TYPE_VIDEO ) +#else + if ( codec_context->codec_type == CODEC_TYPE_VIDEO ) #endif { if ( mVideoStreamId == -1 ) { @@ -171,9 +177,9 @@ int RemoteCameraRtsp::PrimeCapture() { } } #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) - if ( mFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO ) + if ( codec_context->codec_type == AVMEDIA_TYPE_AUDIO ) #else - if ( mFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO ) + if ( codec_context->codec_type == CODEC_TYPE_AUDIO ) #endif { if ( mAudioStreamId == -1 ) { @@ -190,7 +196,12 @@ int RemoteCameraRtsp::PrimeCapture() { Debug( 3, "Unable to locate audio stream" ); // Get a pointer to the codec context for the video stream +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + mCodecContext = avcodec_alloc_context3(NULL); + avcodec_parameters_to_context(mCodecContext, mFormatContext->streams[mVideoStreamId]->codecpar); +#else mCodecContext = mFormatContext->streams[mVideoStreamId]->codec; +#endif // Find the decoder for the video stream mCodec = avcodec_find_decoder( mCodecContext->codec_id ); @@ -231,6 +242,14 @@ int RemoteCameraRtsp::PrimeCapture() { if ( (unsigned int)pSize != imagesize ) { Fatal("Image size mismatch. Required: %d Available: %d",pSize,imagesize); } + +#if HAVE_LIBSWSCALE + mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); + if ( mConvertContext == NULL ) + Fatal( "Unable to create conversion context"); +#else // HAVE_LIBSWSCALE + Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); +#endif // HAVE_LIBSWSCALE /* #if HAVE_LIBSWSCALE if(!sws_isSupportedInput(mCodecContext->pix_fmt)) { @@ -259,23 +278,22 @@ int RemoteCameraRtsp::PreCapture() { return( 0 ); } -ZMPacket * RemoteCameraRtsp::Capture( Image &image ) { +int RemoteCameraRtsp::Capture( ZMPacket &zm_packet ) { AVPacket packet; - ZMPacket *zmpacket = NULL; uint8_t* directbuffer; int frameComplete = false; /* Request a writeable buffer of the target image */ - directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); + directbuffer = zm_packet.image->WriteBuffer(width, height, colours, subpixelorder); if ( directbuffer == NULL ) { Error("Failed requesting writeable buffer for the captured image."); return NULL; } - while ( true ) { + while ( !frameComplete ) { buffer.clear(); if ( !rtspThread->isRunning() ) - return (-1); + return NULL; if ( rtspThread->getFrame( buffer ) ) { Debug( 3, "Read frame %d bytes", buffer.size() ); @@ -283,7 +301,7 @@ ZMPacket * RemoteCameraRtsp::Capture( Image &image ) { Hexdump( 4, buffer.head(), 16 ); if ( !buffer.size() ) - return( -1 ); + return NULL; if ( mCodecContext->codec_id == AV_CODEC_ID_H264 ) { // SPS and PPS frames should be saved and appended to IDR frames @@ -325,223 +343,29 @@ ZMPacket * RemoteCameraRtsp::Capture( Image &image ) { continue; } Debug( 2, "Frame: %d - %d/%d", frameCount, len, buffer.size() ); - //if ( buffer.size() < 400 ) - //Hexdump( 0, buffer.head(), buffer.size() ); - buffer -= len; } // At this point, we either have a frame or ran out of buffer. What happens if we run out of buffer? if ( frameComplete ) { - Debug( 3, "Got frame %d", frameCount ); avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height ); - #if HAVE_LIBSWSCALE if ( mConvertContext == NULL ) { - mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); - - if ( mConvertContext == NULL ) - Fatal( "Unable to create conversion context"); - } - - if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) - Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); - #else // HAVE_LIBSWSCALE - Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); - #endif // HAVE_LIBSWSCALE - - frameCount++; - - zm_packet = new ZMPacket( &packet, mFrame, &image ); - } /* frame complete */ - - - zm_av_packet_unref( &packet ); - } /* getFrame() */ - - if ( frameComplete ) break; - - } // end while true - - return zm_packet; -} - -//Function to handle capture and store - -int RemoteCameraRtsp::CaptureAndRecord(Image &image, timeval recording, char* event_file ) { - AVPacket packet; - uint8_t* directbuffer; - int frameComplete = false; - - while ( true ) { - -// WHY Are we clearing it? Might be something good in it. - buffer.clear(); - - if ( !rtspThread->isRunning() ) - return (-1); - - //Video recording - if ( recording.tv_sec ) { - // The directory we are recording to is no longer tied to the current event. - // Need to re-init the videostore with the correct directory and start recording again - // Not sure why we are only doing this on keyframe, al - if ( videoStore && (strcmp(oldDirectory, event_file)!=0) ) { - //don't open new videostore until we're on a key frame..would this require an offset adjustment for the event as a result?...if we store our key frame location with the event will that be enough? - Info("Re-starting video storage module"); - if ( videoStore ) { - delete videoStore; - videoStore = NULL; - } - } // end if changed to new event - - if ( ! videoStore ) { - //Instantiate the video storage module - - videoStore = new VideoStore((const char *)event_file, "mp4", - mFormatContext->streams[mVideoStreamId], - mAudioStreamId==-1?NULL:mFormatContext->streams[mAudioStreamId], - startTime, - this->getMonitor() ); - strcpy(oldDirectory, event_file); - } // end if ! videoStore - - } else { - if ( videoStore ) { - Info("Deleting videoStore instance"); - delete videoStore; - videoStore = NULL; - } - } // end if recording or not - - if ( rtspThread->getFrame( buffer ) ) { - Debug( 3, "Read frame %d bytes", buffer.size() ); - Debug( 4, "Address %p", buffer.head() ); - Hexdump( 4, buffer.head(), 16 ); - - if ( !buffer.size() ) - return( -1 ); - - if ( mCodecContext->codec_id == AV_CODEC_ID_H264 ) { - // SPS and PPS frames should be saved and appended to IDR frames - int nalType = (buffer.head()[3] & 0x1f); - - // SPS - if(nalType == 7) { - lastSps = buffer; - continue; - } else if(nalType == 8) { - // PPS - lastPps = buffer; - continue; - } else if(nalType == 5) { - // IDR - buffer += lastSps; - buffer += lastPps; - } - } // end if H264, what about other codecs? - - av_init_packet( &packet ); - - // Keep decoding until a complete frame is had. - while ( !frameComplete && buffer.size() > 0 ) { - packet.data = buffer.head(); - packet.size = buffer.size(); - - // Why are we checking for it being the video stream? Because it might be audio or something else. - // Um... we just initialized packet... we can't be testing for what it is yet.... - if ( packet.stream_index == mVideoStreamId ) { - // So this does the decode -#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) - int len = avcodec_decode_video2( mCodecContext, mRawFrame, &frameComplete, &packet ); -#else - int len = avcodec_decode_video( mCodecContext, mRawFrame, &frameComplete, packet.data, packet.size ); -#endif - if ( len < 0 ) { - Error( "Error while decoding frame %d", frameCount ); - Hexdump( Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size() ); - buffer.clear(); - continue; - } - Debug( 2, "Frame: %d - %d/%d", frameCount, len, buffer.size() ); - //if ( buffer.size() < 400 ) - //Hexdump( 0, buffer.head(), buffer.size() ); - - buffer -= len; - - if ( frameComplete ) { - - Debug( 3, "Got frame %d", frameCount ); - - /* Request a writeable buffer of the target image */ - directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); - if(directbuffer == NULL) { - Error("Failed requesting writeable buffer for the captured image."); - return (-1); - } - -#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) - av_image_fill_arrays(mFrame->data, mFrame->linesize, - directbuffer, imagePixFormat, width, height, 1); -#else - avpicture_fill( (AVPicture *)mFrame, directbuffer, - imagePixFormat, width, height); -#endif - - } // endif frameComplete - - if ( videoStore ) { - //Write the packet to our video store - int ret = videoStore->writeVideoFramePacket(&packet);//, &lastKeyframePkt); - if ( ret < 0 ) {//Less than zero and we skipped a frame -// Should not - zm_av_packet_unref( &packet ); - return 0; - } - } // end if videoStore, so we are recording - -#if HAVE_LIBSWSCALE - // Why are we re-scaling after writing out the packet? - if ( mConvertContext == NULL ) { - mConvertContext = sws_getContext( mCodecContext->width, mCodecContext->height, mCodecContext->pix_fmt, width, height, imagePixFormat, SWS_BICUBIC, NULL, NULL, NULL ); - - if ( mConvertContext == NULL ) - Fatal( "Unable to create conversion context"); - } - if ( sws_scale( mConvertContext, mRawFrame->data, mRawFrame->linesize, 0, mCodecContext->height, mFrame->data, mFrame->linesize ) < 0 ) Fatal( "Unable to convert raw format %u to target format %u at frame %d", mCodecContext->pix_fmt, imagePixFormat, frameCount ); -#else // HAVE_LIBSWSCALE - Fatal( "You must compile ffmpeg with the --enable-swscale option to use RTSP cameras" ); -#endif // HAVE_LIBSWSCALE - - frameCount++; - - } else if ( packet.stream_index == mAudioStreamId ) { - Debug( 4, "Got audio packet" ); - if ( videoStore && record_audio ) { - Debug( 4, "Storing Audio packet" ); - //Write the packet to our video store - int ret = videoStore->writeAudioFramePacket( &packet ); //FIXME no relevance of last key frame - if ( ret < 0 ) { //Less than zero and we skipped a frame - zm_av_packet_unref( &packet ); - return 0; - } } - } // end if video or audio packet - + #endif + + frameCount++; + } /* frame complete */ + zm_packet.set_packet( &packet ); zm_av_packet_unref( &packet ); - } // end while ! framecomplete and buffer.size() - if(frameComplete) - return (0); - } /* getFrame() */ + } /* getFrame() */ + } // end while true -} // end while true - -// can never get here. - return (0) ; -} // int RemoteCameraRtsp::CaptureAndRecord( Image &image, bool recording, char* event_file ) + return 1; +} // end int RemoteCameraRtsp::Capture(ZMPacket &packet) int RemoteCameraRtsp::PostCapture() { return( 0 ); diff --git a/src/zm_remote_camera_rtsp.h b/src/zm_remote_camera_rtsp.h index d61aefd9d..ddc456cfc 100644 --- a/src/zm_remote_camera_rtsp.h +++ b/src/zm_remote_camera_rtsp.h @@ -34,8 +34,7 @@ // accessed over a network connection using rtsp protocol // (Real Time Streaming Protocol) // -class RemoteCameraRtsp : public RemoteCamera -{ +class RemoteCameraRtsp : public RemoteCamera { protected: struct sockaddr_in rtsp_sa; struct sockaddr_in rtcp_sa; @@ -84,7 +83,7 @@ public: int PrimeCapture(); int PreCapture(); - ZMPacket * Capture( Image &image ); + int Capture( ZMPacket &p ); int PostCapture(); }; diff --git a/src/zm_sdp.cpp b/src/zm_sdp.cpp index ffcea791b..b9f4eb837 100644 --- a/src/zm_sdp.cpp +++ b/src/zm_sdp.cpp @@ -379,21 +379,31 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const stream->id = i; #endif + AVCodecContext *codec_context = NULL; +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + codec_context = avcodec_alloc_context3(NULL); + avcodec_parameters_to_context(codec_context, stream->codecpar); +#else + codec_context = stream->codec; +#endif + + + Debug( 1, "Looking for codec for %s payload type %d / %s", mediaDesc->getType().c_str(), mediaDesc->getPayloadType(), mediaDesc->getPayloadDesc().c_str() ); #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) if ( mediaDesc->getType() == "video" ) - stream->codec->codec_type = AVMEDIA_TYPE_VIDEO; + codec_context->codec_type = AVMEDIA_TYPE_VIDEO; else if ( mediaDesc->getType() == "audio" ) - stream->codec->codec_type = AVMEDIA_TYPE_AUDIO; + codec_context->codec_type = AVMEDIA_TYPE_AUDIO; else if ( mediaDesc->getType() == "application" ) - stream->codec->codec_type = AVMEDIA_TYPE_DATA; + codec_context->codec_type = AVMEDIA_TYPE_DATA; #else if ( mediaDesc->getType() == "video" ) - stream->codec->codec_type = CODEC_TYPE_VIDEO; + codec_context->codec_type = CODEC_TYPE_VIDEO; else if ( mediaDesc->getType() == "audio" ) - stream->codec->codec_type = CODEC_TYPE_AUDIO; + codec_context->codec_type = CODEC_TYPE_AUDIO; else if ( mediaDesc->getType() == "application" ) - stream->codec->codec_type = CODEC_TYPE_DATA; + codec_context->codec_type = CODEC_TYPE_DATA; #endif #if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) @@ -410,31 +420,27 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const #if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) codec_name = std::string( smStaticPayloads[i].payloadName ); #else - strncpy( stream->codec->codec_name, smStaticPayloads[i].payloadName, sizeof(stream->codec->codec_name) );; + strncpy( codec_context->codec_name, smStaticPayloads[i].payloadName, sizeof(codec_context->codec_name) );; #endif - stream->codec->codec_type = smStaticPayloads[i].codecType; - stream->codec->codec_id = smStaticPayloads[i].codecId; - stream->codec->sample_rate = smStaticPayloads[i].clockRate; + codec_context->codec_type = smStaticPayloads[i].codecType; + codec_context->codec_id = smStaticPayloads[i].codecId; + codec_context->sample_rate = smStaticPayloads[i].clockRate; break; } } - } - else - { + } else { // Look in dynamic table - for ( unsigned int i = 0; i < (sizeof(smDynamicPayloads)/sizeof(*smDynamicPayloads)); i++ ) - { - if ( smDynamicPayloads[i].payloadName == mediaDesc->getPayloadDesc() ) - { + for ( unsigned int i = 0; i < (sizeof(smDynamicPayloads)/sizeof(*smDynamicPayloads)); i++ ) { + if ( smDynamicPayloads[i].payloadName == mediaDesc->getPayloadDesc() ) { Debug( 1, "Got dynamic payload type %d, %s", mediaDesc->getPayloadType(), smDynamicPayloads[i].payloadName ); #if LIBAVCODEC_VERSION_CHECK(55, 50, 3, 60, 103) codec_name = std::string( smStaticPayloads[i].payloadName ); #else - strncpy( stream->codec->codec_name, smDynamicPayloads[i].payloadName, sizeof(stream->codec->codec_name) );; + strncpy( codec_context->codec_name, smDynamicPayloads[i].payloadName, sizeof(codec_context->codec_name) );; #endif - stream->codec->codec_type = smDynamicPayloads[i].codecType; - stream->codec->codec_id = smDynamicPayloads[i].codecId; - stream->codec->sample_rate = mediaDesc->getClock(); + codec_context->codec_type = smDynamicPayloads[i].codecType; + codec_context->codec_id = smDynamicPayloads[i].codecId; + codec_context->sample_rate = mediaDesc->getClock(); break; } } @@ -450,14 +456,13 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const //return( 0 ); } if ( mediaDesc->getWidth() ) - stream->codec->width = mediaDesc->getWidth(); + codec_context->width = mediaDesc->getWidth(); if ( mediaDesc->getHeight() ) - stream->codec->height = mediaDesc->getHeight(); - if ( stream->codec->codec_id == AV_CODEC_ID_H264 && mediaDesc->getSprops().size()) - { + codec_context->height = mediaDesc->getHeight(); + if ( codec_context->codec_id == AV_CODEC_ID_H264 && mediaDesc->getSprops().size()) { uint8_t start_sequence[]= { 0, 0, 1 }; - stream->codec->extradata_size= 0; - stream->codec->extradata= NULL; + codec_context->extradata_size= 0; + codec_context->extradata= NULL; char pvalue[1024], *value = pvalue; strcpy(pvalue, mediaDesc->getSprops().c_str()); @@ -482,22 +487,33 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const if (packet_size) { uint8_t *dest = (uint8_t *)av_malloc(packet_size + sizeof(start_sequence) + - stream->codec->extradata_size + - FF_INPUT_BUFFER_PADDING_SIZE); + codec_context->extradata_size + +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + AV_INPUT_BUFFER_PADDING_SIZE +#else + FF_INPUT_BUFFER_PADDING_SIZE +#endif +); if(dest) { - if(stream->codec->extradata_size) { + if(codec_context->extradata_size) { // av_realloc? - memcpy(dest, stream->codec->extradata, stream->codec->extradata_size); - av_free(stream->codec->extradata); + memcpy(dest, codec_context->extradata, codec_context->extradata_size); + av_free(codec_context->extradata); } - memcpy(dest+stream->codec->extradata_size, start_sequence, sizeof(start_sequence)); - memcpy(dest+stream->codec->extradata_size+sizeof(start_sequence), decoded_packet, packet_size); - memset(dest+stream->codec->extradata_size+sizeof(start_sequence)+ - packet_size, 0, FF_INPUT_BUFFER_PADDING_SIZE); + memcpy(dest+codec_context->extradata_size, start_sequence, sizeof(start_sequence)); + memcpy(dest+codec_context->extradata_size+sizeof(start_sequence), decoded_packet, packet_size); + memset(dest+codec_context->extradata_size+sizeof(start_sequence)+ + packet_size, 0, +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + AV_INPUT_BUFFER_PADDING_SIZE +#else + FF_INPUT_BUFFER_PADDING_SIZE +#endif +); - stream->codec->extradata= dest; - stream->codec->extradata_size+= sizeof(start_sequence)+packet_size; + codec_context->extradata= dest; + codec_context->extradata_size+= sizeof(start_sequence)+packet_size; // } else { // av_log(codec, AV_LOG_ERROR, "Unable to allocate memory for extradata!"); // return AVERROR(ENOMEM); diff --git a/src/zm_sdp.h b/src/zm_sdp.h index 48a05b706..68e574d0f 100644 --- a/src/zm_sdp.h +++ b/src/zm_sdp.h @@ -31,13 +31,11 @@ #include #include -class SessionDescriptor -{ +class SessionDescriptor { protected: enum { PAYLOAD_TYPE_DYNAMIC=96 }; - struct StaticPayloadDesc - { + struct StaticPayloadDesc { int payloadType; const char payloadName[6]; #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) @@ -50,8 +48,7 @@ protected: int autoChannels; }; - struct DynamicPayloadDesc - { + struct DynamicPayloadDesc { const char payloadName[32]; #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) AVMediaType codecType; @@ -65,8 +62,7 @@ protected: }; public: - class ConnInfo - { + class ConnInfo { protected: std::string mNetworkType; std::string mAddressType; @@ -78,8 +74,7 @@ public: ConnInfo( const std::string &connInfo ); }; - class BandInfo - { + class BandInfo { protected: std::string mType; int mValue; @@ -88,8 +83,7 @@ public: BandInfo( const std::string &bandInfo ); }; - class MediaDescriptor - { + class MediaDescriptor { protected: std::string mType; int mPort; diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 8002c4f18..282db0f40 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -311,7 +311,7 @@ void StreamBase::openComms() { strncpy( rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path) ); rem_addr.sun_family = AF_UNIX; } // end if connKey > 0 - Debug(3, "comms open" ); + Debug(2, "comms open" ); } void StreamBase::closeComms() { diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 8d4452f07..c0434db2e 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -31,30 +31,27 @@ extern "C" { #include "libavutil/time.h" } -VideoStore::VideoStore(const char *filename_in, const char *format_in, - AVStream *p_video_in_stream, - AVStream *p_audio_in_stream, - Monitor *monitor) { +VideoStore::VideoStore( + const char *filename_in, + const char *format_in, + AVStream *p_video_in_stream, + AVStream *p_audio_in_stream, + int64_t nStartTime, + Monitor *monitor + ) { video_in_stream = p_video_in_stream; audio_in_stream = p_audio_in_stream; - -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - video_in_ctx = avcodec_alloc_context3(NULL); - avcodec_parameters_to_context(video_in_ctx, - video_in_stream->codecpar); -// zm_dump_codecpar( video_in_stream->codecpar ); -#else - video_in_ctx = video_in_stream->codec; -#endif - - // store ins in variables local to class filename = filename_in; format = format_in; + av_register_all(); + packets_written = 0; + frame_count = 0; + Info("Opening video storage stream %s format: %s", filename, format); ret = avformat_alloc_output_context2(&oc, NULL, NULL, filename); - if (ret < 0) { + if ( ret < 0 ) { Warning( "Could not create video storage stream %s as no out ctx" " could be assigned based on filename: %s", @@ -64,7 +61,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, } // Couldn't deduce format from filename, trying from format name - if (!oc) { + if ( !oc ) { avformat_alloc_output_context2(&oc, NULL, format, filename); if (!oc) { Fatal( @@ -72,117 +69,197 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, " could not be assigned based on filename or format %s", filename, format); } else { - Debug(4, "Success alocateing out ctx"); + Debug(4, "Success alocating out ctx"); } } // end if ! oc + Debug(2, "Success opening output contect"); AVDictionary *pmetadata = NULL; - int dsr = - av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0); + int dsr = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0); if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__); + Debug(2, "Success setting up dictcontect"); oc->metadata = pmetadata; out_format = oc->oformat; + in_frame = NULL; + if ( video_in_stream ) { + video_in_stream_index = video_in_stream->index; #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + video_in_ctx = avcodec_alloc_context3(NULL); + avcodec_parameters_to_context(video_in_ctx, + video_in_stream->codecpar); + zm_dump_codecpar( video_in_stream->codecpar ); +#else + video_in_ctx = video_in_stream->codec; +#endif + } else { + Debug(2, "No input ctx"); + video_in_ctx = avcodec_alloc_context3(NULL); + video_in_stream_index = 0; + } - // Since we are not re-encoding, all we have to do is copy the parameters - video_out_ctx = avcodec_alloc_context3(NULL); + video_out_ctx = NULL; // Copy params from instream to ctx - ret = avcodec_parameters_to_context(video_out_ctx, - video_in_stream->codecpar); - if (ret < 0) { - Error("Could not initialize ctx parameteres"); - return; + if ( video_in_stream && ( video_in_ctx->codec_id == AV_CODEC_ID_H264 ) ) { +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + ret = avcodec_parameters_to_context(video_out_ctx, + video_in_stream->codecpar); + if ( ret < 0 ) { + Error("Could not initialize ctx parameteres"); + return; + } else { + Debug(2, "Going to dump the outctx"); + zm_dump_codec(video_out_ctx); + } +#else + video_out_ctx = avcodec_alloc_context3(NULL); + avcodec_copy_context( video_out_ctx, video_in_ctx ); +#endif + // Same codec, just copy the packets, otherwise we have to decode/encode + video_out_codec = (AVCodec *)video_in_ctx->codec; + video_out_ctx->time_base = video_in_ctx->time_base; + video_out_stream->time_base = video_in_stream->time_base; } else { - zm_dump_codec(video_out_ctx); + + /** Create a new frame to store the */ + if ( !(in_frame = zm_av_frame_alloc()) ) { + Error("Could not allocate in frame"); + return; + } + video_out_codec = avcodec_find_encoder_by_name("h264_omx"); + if ( ! video_out_codec ) { + Debug(1, "Didn't find omx"); + video_out_codec = avcodec_find_encoder(AV_CODEC_ID_H264); + } + if ( !video_out_codec ) { + Fatal("Could not find codec for H264"); + } + Debug(2, "Have video out codec"); + + video_out_ctx = avcodec_alloc_context3( video_out_codec ); + // Don't have an input stream, so need to tell it what we are sending it, or are transcoding + video_out_ctx->width = monitor->Width(); + video_out_ctx->height = monitor->Height(); + video_out_ctx->codec_id = AV_CODEC_ID_H264; + video_out_ctx->sample_aspect_ratio = (AVRational){4,3}; + video_out_ctx->codec_type = AVMEDIA_TYPE_VIDEO; +//video_in_ctx->sample_aspect_ratio; + /* take first format from list of supported formats */ + //video_out_ctx->pix_fmt = video_out_codec->pix_fmts[0]; + video_out_ctx->pix_fmt = AV_PIX_FMT_YUV420P; + /* video time_base can be set to whatever is handy and supported by encoder */ + //video_out_ctx->time_base = video_in_ctx->time_base; + video_out_ctx->time_base = (AVRational){1, 1000000}; // microseconds as base frame rate + //video_out_ctx->framerate = (AVRational){0,1}; // Unknown framerate + video_out_ctx->gop_size = 12; + video_out_ctx->bit_rate = 4000000; + video_out_ctx->qmin = 10; + video_out_ctx->qmax = 51; + video_out_ctx->qcompress = 0.6; + + if (oc->oformat->flags & AVFMT_GLOBALHEADER) { +#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) + video_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else + video_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif } - video_out_stream = avformat_new_stream(oc, NULL); - if (!video_out_stream) { - Fatal("Unable to create video out stream\n"); - } else { - Debug(2, "Success creating video out stream"); - } + AVDictionary *opts = 0; + std::string Options = monitor->GetEncoderOptions(); + ret = av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); + if ( ret < 0 ) { + Warning("Could not parse ffmpeg encoder options list '%s'\n", Options.c_str()); + } else { + AVDictionaryEntry *e = NULL; + while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) { + Debug( 3, "Encoder Option %s=%s", e->key, e->value ); + } + } + if ( (ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0 ) { + Warning("Can't open video codec (%s)! %s, trying h264", + video_out_codec->name, + av_make_error_string(ret).c_str() + ); + video_out_codec = avcodec_find_encoder_by_name("h264"); + if ( ! video_out_codec ) { + Error("Can't find h264 encoder"); + video_out_codec = avcodec_find_encoder_by_name("libx264"); + if ( ! video_out_codec ) { + Error("Can't find libx264 encoder"); + return; + } + } + if ( (ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0 ) { + Error("Can't open video codec (%s)! %s", + video_out_codec->name, + av_make_error_string(ret).c_str() ); + return; + } + } + AVDictionaryEntry *e = NULL; + while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) { + Warning( "Encoder Option %s not recognized by ffmpeg codec", e->key); + } + av_dict_free(&opts); - if (!video_out_ctx->codec_tag) { + swscale.SetDefaults( + video_in_ctx->pix_fmt, + video_out_ctx->pix_fmt, + video_out_ctx->width, + video_out_ctx->height + ); + } // end if copying or trasncoding + + if ( !video_out_ctx->codec_tag ) { video_out_ctx->codec_tag = - av_codec_get_tag(oc->oformat->codec_tag, video_in_ctx->codec_id); + av_codec_get_tag(oc->oformat->codec_tag, AV_CODEC_ID_H264 ); Debug(2, "No codec_tag, setting to %d", video_out_ctx->codec_tag); } - // Now copy them to the out stream - ret = avcodec_parameters_from_context(video_out_stream->codecpar, - video_out_ctx); - if (ret < 0) { - Error("Could not initialize stream parameteres"); - return; - } else { - Debug(2, "Success setting parameters"); - } - zm_dump_codecpar(video_out_stream->codecpar); - -#else - video_out_stream = - avformat_new_stream(oc,(const AVCodec *)(video_in_ctx->codec)); - if (!video_out_stream) { + video_out_stream = avformat_new_stream(oc, video_out_codec); + if ( !video_out_stream ) { Fatal("Unable to create video out stream\n"); } else { Debug(2, "Success creating video out stream"); } - video_out_ctx = video_out_stream->codec; - ret = avcodec_copy_context(video_out_ctx, video_in_ctx); - if (ret < 0) { - Fatal("Unable to copy in video ctx to out video ctx %s\n", - av_make_error_string(ret).c_str()); - } else { - Debug(3, "Success copying ctx"); - } - if (!video_out_ctx->codec_tag) { - Debug(2, "No codec_tag"); - if (!oc->oformat->codec_tag || - av_codec_get_id(oc->oformat->codec_tag, - video_in_ctx->codec_tag) == - video_out_ctx->codec_id || - av_codec_get_tag(oc->oformat->codec_tag, - video_in_ctx->codec_id) <= 0) { - Warning("Setting codec tag"); - video_out_ctx->codec_tag = video_in_ctx->codec_tag; - } +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + ret = avcodec_parameters_from_context(video_out_stream->codecpar, + video_out_ctx); + if ( ret < 0 ) { + Error("Could not initialize stream parameteres"); + return; } + zm_dump_codecpar(video_out_stream->codecpar); +#else +video_out_stream->time_base.num = video_out_ctx->time_base.num; +video_out_stream->time_base.den = video_out_ctx->time_base.den; +avcodec_copy_context( video_out_stream->codec, video_out_ctx ); +Debug(2, "%dx%d", video_out_stream->codec->width, video_out_stream->codec->height ); #endif - // Just copy them from the in, no reason to choose different - video_out_ctx->time_base = video_in_ctx->time_base; - video_out_stream->time_base = video_in_stream->time_base; - Debug(3, - "Time bases: VIDEO in stream (%d/%d) in codec: (%d/%d) out " - "stream: (%d/%d) out codec (%d/%d)", - video_in_stream->time_base.num, video_in_stream->time_base.den, - video_in_ctx->time_base.num, video_in_ctx->time_base.den, - video_out_stream->time_base.num, video_out_stream->time_base.den, + "Time bases: VIDEO out stream: (%d/%d) out codec (%d/%d)", + video_out_stream->time_base.num, + video_out_stream->time_base.den, video_out_ctx->time_base.num, video_out_ctx->time_base.den); - if (oc->oformat->flags & AVFMT_GLOBALHEADER) { - video_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; - } - Monitor::Orientation orientation = monitor->getOrientation(); Debug(3, "Have orientation"); - if (orientation) { - if (orientation == Monitor::ROTATE_0) { - } else if (orientation == Monitor::ROTATE_90) { + if ( orientation ) { + if ( orientation == Monitor::ROTATE_0 ) { + } else if ( orientation == Monitor::ROTATE_90 ) { dsr = av_dict_set(&video_out_stream->metadata, "rotate", "90", 0); - if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__); - } else if (orientation == Monitor::ROTATE_180) { + if ( dsr < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); + } else if ( orientation == Monitor::ROTATE_180 ) { dsr = av_dict_set(&video_out_stream->metadata, "rotate", "180", 0); - if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__); - } else if (orientation == Monitor::ROTATE_270) { + if ( dsr < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); + } else if ( orientation == Monitor::ROTATE_270 ) { dsr = av_dict_set(&video_out_stream->metadata, "rotate", "270", 0); - if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__); + if ( dsr < 0 ) Warning("%s:%d: title set failed", __FILE__, __LINE__); } else { Warning("Unsupported Orientation(%d)", orientation); } @@ -192,13 +269,13 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, audio_out_codec = NULL; audio_in_ctx = NULL; audio_out_stream = NULL; - in_frame = NULL; out_frame = NULL; #ifdef HAVE_LIBAVRESAMPLE resample_ctx = NULL; #endif - if (audio_in_stream) { + if ( audio_in_stream ) { + audio_in_stream_index = audio_in_stream->index; Debug(3, "Have audio stream"); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) @@ -209,7 +286,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, audio_in_ctx = audio_in_stream->codec; #endif - if (audio_in_ctx->codec_id != AV_CODEC_ID_AAC) { + if ( audio_in_ctx->codec_id != AV_CODEC_ID_AAC ) { static char error_buffer[256]; avcodec_string(error_buffer, sizeof(error_buffer), audio_in_ctx, 0); @@ -272,14 +349,17 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, } // end if audio_out_stream } // end if is AAC - if (audio_out_stream) { + if ( audio_out_stream ) { if (oc->oformat->flags & AVFMT_GLOBALHEADER) { - audio_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; +#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) + audio_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +#else + audio_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; +#endif } } } // end if audio_in_stream - video_last_pts = 0; video_last_dts = 0; audio_last_pts = 0; @@ -312,34 +392,105 @@ bool VideoStore::open() { // av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); // av_dict_set(&opts, "movflags", // "frag_keyframe+empty_moov+default_base_moof", 0); - if ((ret = avformat_write_header(oc, &opts)) < 0) { + if ( (ret = avformat_write_header(oc, &opts)) < 0 ) { // if ((ret = avformat_write_header(oc, &opts)) < 0) { Warning("Unable to set movflags to frag_custom+dash+delay_moov"); /* Write the stream header, if any. */ ret = avformat_write_header(oc, NULL); - } else if (av_dict_count(opts) != 0) { + } else if ( av_dict_count(opts) != 0 ) { Warning("some options not set\n"); } - if (ret < 0) { + if ( ret < 0 ) { Error("Error occurred when writing out file header to %s: %s\n", filename, av_make_error_string(ret).c_str()); return false; } - if (opts) av_dict_free(&opts); + if ( opts ) av_dict_free(&opts); return true; +} // end bool VideoStore::open() + +void VideoStore::write_audio_packet( AVPacket &pkt ) { + Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, + pkt.dts, pkt.duration); + pkt.pts = audio_next_pts; + pkt.dts = audio_next_dts; + + if (pkt.duration > 0) + pkt.duration = + av_rescale_q(pkt.duration, audio_out_ctx->time_base, + audio_out_stream->time_base); + audio_next_pts += pkt.duration; + audio_next_dts += pkt.duration; + + Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, + pkt.dts, pkt.duration); + pkt.stream_index = audio_out_stream->index; + av_interleaved_write_frame(oc, &pkt); } VideoStore::~VideoStore() { + if ( video_out_ctx->codec_id != video_in_ctx->codec_id ) { +Debug(2,"Different codecs between in and out"); + + if ( video_out_ctx->codec && ( video_out_ctx->codec->capabilities & AV_CODEC_CAP_DELAY ) ) { +Debug(2,"May have delayed packets"); + // The codec queues data. We need to send a flush command and out + // whatever we get. Failures are not fatal. + AVPacket pkt; + av_init_packet(&pkt); + +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + // Put encoder into flushing mode + avcodec_send_frame(video_out_ctx, NULL); + while (1) { + ret = avcodec_receive_packet(video_out_ctx, &pkt); + if (ret < 0) { + if (AVERROR_EOF != ret) { + Error("ERror encoding audio while flushing (%d) (%s)", ret, + av_err2str(ret)); + } + break; + } +#else + while (1) { + // WIthout these we seg fault I don't know why. + pkt.data = NULL; + pkt.size = 0; + av_init_packet(&pkt); + int got_packet = 0; + ret = avcodec_encode_video2(video_out_ctx, &pkt, NULL, &got_packet); + if ( ret < 0 ) { + Error("ERror encoding video while flushing (%d) (%s)", ret, + av_err2str(ret)); + break; + } + if (!got_packet) { + break; + } +#endif +Debug(3, "(%d, %d)", video_next_dts, video_next_pts ); + pkt.dts = video_next_dts; + pkt.pts = video_next_pts; + pkt.duration = video_last_duration; + write_video_packet(pkt); + zm_av_packet_unref(&pkt); + } // while have buffered frames + } // end if have delay capability + } // end if have buffered video + if (audio_out_codec) { // The codec queues data. We need to send a flush command and out // whatever we get. Failures are not fatal. AVPacket pkt; + // WIthout these we seg fault I don't know why. + pkt.data = NULL; + pkt.size = 0; av_init_packet(&pkt); - while (1) { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // Put encoder into flushing mode - avcodec_send_frame(audio_out_ctx, NULL); + // Put encoder into flushing mode + avcodec_send_frame(audio_out_ctx, NULL); + while (1) { ret = avcodec_receive_packet(audio_out_ctx, &pkt); if (ret < 0) { if (AVERROR_EOF != ret) { @@ -349,6 +500,7 @@ VideoStore::~VideoStore() { break; } #else + while (1) { int got_packet = 0; ret = avcodec_encode_audio2(audio_out_ctx, &pkt, NULL, &got_packet); @@ -362,22 +514,7 @@ VideoStore::~VideoStore() { break; } #endif - Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, - pkt.dts, pkt.duration); - pkt.pts = audio_next_pts; - pkt.dts = audio_next_dts; - - if (pkt.duration > 0) - pkt.duration = - av_rescale_q(pkt.duration, audio_out_ctx->time_base, - audio_out_stream->time_base); - audio_next_pts += pkt.duration; - audio_next_dts += pkt.duration; - - Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, - pkt.dts, pkt.duration); - pkt.stream_index = audio_out_stream->index; - av_interleaved_write_frame(oc, &pkt); + write_audio_packet(pkt); zm_av_packet_unref(&pkt); } // while have buffered frames } // end if audio_out_codec @@ -397,38 +534,39 @@ VideoStore::~VideoStore() { // allocation/de-allocation constantly, or whether we can just re-use it. // Just do a file open/close/writeheader/etc. // What if we were only doing audio recording? - if (video_out_stream) { + if ( video_out_stream ) { avcodec_close(video_out_ctx); video_out_ctx = NULL; Debug(4, "Success freeing video_out_ctx"); } - if (audio_out_stream) { - avcodec_close(audio_out_ctx); - audio_out_ctx = NULL; -#ifdef HAVE_LIBAVRESAMPLE - if (resample_ctx) { - avresample_close(resample_ctx); - avresample_free(&resample_ctx); - } - if (in_frame) { +// Used by both audio and video conversions + if ( in_frame ) { av_frame_free(&in_frame); in_frame = NULL; } - if (out_frame) { + if ( audio_out_stream ) { + avcodec_close(audio_out_ctx); + audio_out_ctx = NULL; +#ifdef HAVE_LIBAVRESAMPLE + if ( resample_ctx ) { + avresample_close(resample_ctx); + avresample_free(&resample_ctx); + } + if ( out_frame ) { av_frame_free(&out_frame); out_frame = NULL; } - if (converted_in_samples) { + if ( converted_in_samples ) { av_free(converted_in_samples); converted_in_samples = NULL; } #endif } - // WHen will be not using a file ? - if (!(out_format->flags & AVFMT_NOFILE)) { + // When will be not using a file ? // Might someday use this for streaming + if ( !(out_format->flags & AVFMT_NOFILE) ) { /* Close the out file. */ - if (int rc = avio_close(oc->pb)) { + if ( int rc = avio_close(oc->pb) ) { Error("Error closing avio %s", av_err2str(rc)); } } else { @@ -441,17 +579,15 @@ VideoStore::~VideoStore() { bool VideoStore::setup_resampler() { #ifdef HAVE_LIBAVRESAMPLE - static char error_buffer[256]; - -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // Newer ffmpeg wants to keep everything separate... so have to lookup our own // decoder, can't reuse the one from the camera. - AVCodec *audio_in_codec = - avcodec_find_decoder(audio_in_stream->codecpar->codec_id); + AVCodec *audio_in_codec = avcodec_find_decoder( +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + audio_in_stream->codecpar->codec_id #else - AVCodec *audio_in_codec = - avcodec_find_decoder(audio_in_ctx->codec_id); + audio_in_ctx->codec_id #endif + ); ret = avcodec_open2(audio_in_ctx, audio_in_codec, NULL); if (ret < 0) { Error("Can't open in codec!"); @@ -459,7 +595,7 @@ bool VideoStore::setup_resampler() { } audio_out_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); - if (!audio_out_codec) { + if ( !audio_out_codec ) { Error("Could not find codec for AAC"); return false; } @@ -467,34 +603,30 @@ bool VideoStore::setup_resampler() { // audio_out_ctx = audio_out_stream->codec; audio_out_ctx = avcodec_alloc_context3(audio_out_codec); - - if (!audio_out_ctx) { + if ( !audio_out_ctx ) { Error("could not allocate codec ctx for AAC\n"); audio_out_stream = NULL; return false; } - Debug(2, "Have audio_out_ctx"); - /* put sample parameters */ audio_out_ctx->bit_rate = audio_in_ctx->bit_rate; audio_out_ctx->sample_rate = audio_in_ctx->sample_rate; audio_out_ctx->channels = audio_in_ctx->channels; audio_out_ctx->channel_layout = audio_in_ctx->channel_layout; audio_out_ctx->sample_fmt = audio_in_ctx->sample_fmt; - audio_out_ctx->refcounted_frames = 1; + //audio_out_ctx->refcounted_frames = 1; - if (audio_out_codec->supported_samplerates) { + if ( audio_out_codec->supported_samplerates ) { int found = 0; - for (unsigned int i = 0; audio_out_codec->supported_samplerates[i]; - i++) { - if (audio_out_ctx->sample_rate == + for ( int i=0; audio_out_codec->supported_samplerates[i]; i++) { + if ( audio_out_ctx->sample_rate == audio_out_codec->supported_samplerates[i]) { found = 1; break; } } - if (found) { + if ( found ) { Debug(3, "Sample rate is good"); } else { audio_out_ctx->sample_rate = @@ -505,34 +637,31 @@ bool VideoStore::setup_resampler() { } /* check that the encoder supports s16 pcm in */ - if (!check_sample_fmt(audio_out_codec, audio_out_ctx->sample_fmt)) { + if ( !check_sample_fmt(audio_out_codec, audio_out_ctx->sample_fmt) ) { Debug(3, "Encoder does not support sample format %s, setting to FLTP", av_get_sample_fmt_name(audio_out_ctx->sample_fmt)); audio_out_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; } - audio_out_ctx->time_base = - (AVRational){1, audio_out_ctx->sample_rate}; + audio_out_ctx->time_base = (AVRational){1, audio_out_ctx->sample_rate}; // Now copy them to the out stream audio_out_stream = avformat_new_stream(oc, audio_out_codec); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - ret = avcodec_parameters_from_context(audio_out_stream->codecpar, - audio_out_ctx); - if (ret < 0) { + if ( (ret = avcodec_parameters_from_context(audio_out_stream->codecpar, + audio_out_ctx)) < 0 ) { Error("Could not initialize stream parameteres"); return false; } #endif AVDictionary *opts = NULL; - av_dict_set(&opts, "strict", "experimental", 0); + av_dict_set(&opts, "strict", "experimental", 0); // Needed to allow AAC ret = avcodec_open2(audio_out_ctx, audio_out_codec, &opts); av_dict_free(&opts); - if (ret < 0) { - av_strerror(ret, error_buffer, sizeof(error_buffer)); - Fatal("could not open codec (%d) (%s)\n", ret, error_buffer); + if ( ret < 0 ) { + Fatal("could not open codec (%d) (%s)\n", ret, av_make_error_string(ret).c_str()); audio_out_codec = NULL; audio_out_ctx = NULL; audio_out_stream = NULL; @@ -547,13 +676,15 @@ bool VideoStore::setup_resampler() { audio_out_ctx->channel_layout, audio_out_ctx->frame_size); /** Create a new frame to store the audio samples. */ - if (!(in_frame = zm_av_frame_alloc())) { - Error("Could not allocate in frame"); - return false; + if ( ! in_frame ) { + if (!(in_frame = zm_av_frame_alloc())) { + Error("Could not allocate in frame"); + return false; + } } /** Create a new frame to store the audio samples. */ - if (!(out_frame = zm_av_frame_alloc())) { + if ( !(out_frame = zm_av_frame_alloc()) ) { Error("Could not allocate out frame"); av_frame_free(&in_frame); return false; @@ -561,13 +692,13 @@ bool VideoStore::setup_resampler() { // Setup the audio resampler resample_ctx = avresample_alloc_context(); - if (!resample_ctx) { + if ( !resample_ctx ) { Error("Could not allocate resample ctx\n"); return false; } // Some formats (i.e. WAV) do not produce the proper channel layout - if (audio_in_ctx->channel_layout == 0) { + if ( audio_in_ctx->channel_layout == 0 ) { uint64_t layout = av_get_channel_layout("mono"); av_opt_set_int(resample_ctx, "in_channel_layout", av_get_channel_layout("mono"), 0); @@ -577,12 +708,9 @@ bool VideoStore::setup_resampler() { audio_in_ctx->channel_layout, 0); } - av_opt_set_int(resample_ctx, "in_sample_fmt", - audio_in_ctx->sample_fmt, 0); - av_opt_set_int(resample_ctx, "in_sample_rate", - audio_in_ctx->sample_rate, 0); - av_opt_set_int(resample_ctx, "in_channels", audio_in_ctx->channels, - 0); + av_opt_set_int(resample_ctx, "in_sample_fmt", audio_in_ctx->sample_fmt, 0); + av_opt_set_int(resample_ctx, "in_sample_rate", audio_in_ctx->sample_rate, 0); + av_opt_set_int(resample_ctx, "in_channels", audio_in_ctx->channels, 0); // av_opt_set_int( resample_ctx, "out_channel_layout", // audio_out_ctx->channel_layout, 0); av_opt_set_int(resample_ctx, "out_channel_layout", @@ -594,39 +722,11 @@ bool VideoStore::setup_resampler() { av_opt_set_int(resample_ctx, "out_channels", audio_out_ctx->channels, 0); - ret = avresample_open(resample_ctx); - if (ret < 0) { + if ( (ret = avresample_open(resample_ctx)) < 0 ) { Error("Could not open resample ctx\n"); return false; } -#if 0 - /** - * Allocate as many pointers as there are audio channels. - * Each pointer will later point to the audio samples of the corresponding - * channels (although it may be NULL for interleaved formats). - */ - if (!( converted_in_samples = reinterpret_castcalloc( audio_out_ctx->channels, sizeof(*converted_in_samples))) ) { - Error("Could not allocate converted in sample pointers\n"); - return; - } - /** - * Allocate memory for the samples of all channels in one consecutive - * block for convenience. - */ - if ( (ret = av_samples_alloc( &converted_in_samples, NULL, - audio_out_ctx->channels, - audio_out_ctx->frame_size, - audio_out_ctx->sample_fmt, 0)) < 0 ) { - Error("Could not allocate converted in samples (error '%s')\n", - av_make_error_string(ret).c_str()); - - av_freep(converted_in_samples); - free(converted_in_samples); - return; - } -#endif - out_frame->nb_samples = audio_out_ctx->frame_size; out_frame->format = audio_out_ctx->sample_fmt; out_frame->channel_layout = audio_out_ctx->channel_layout; @@ -638,16 +738,16 @@ bool VideoStore::setup_resampler() { audio_out_ctx->sample_fmt, 0); converted_in_samples = (uint8_t *)av_malloc(audioSampleBuffer_size); - if (!converted_in_samples) { + if ( !converted_in_samples ) { Error("Could not allocate converted in sample pointers\n"); return false; } // Setup the data pointers in the AVFrame - if (avcodec_fill_audio_frame(out_frame, audio_out_ctx->channels, + if ( avcodec_fill_audio_frame(out_frame, audio_out_ctx->channels, audio_out_ctx->sample_fmt, (const uint8_t *)converted_in_samples, - audioSampleBuffer_size, 0) < 0) { + audioSampleBuffer_size, 0) < 0 ) { Error("Could not allocate converted in sample pointers\n"); return false; } @@ -667,112 +767,188 @@ void VideoStore::dumpPacket(AVPacket *pkt) { snprintf(b, sizeof(b), " pts: %" PRId64 ", dts: %" PRId64 ", data: %p, size: %d, sindex: %d, dflags: %04x, s-pos: %" PRId64 - ", c-duration: %d\n", - pkt->pts, pkt->dts, pkt->data, pkt->size, pkt->stream_index, - pkt->flags, pkt->pos, pkt->duration); + ", c-duration: %" PRId64 "\n", + pkt->pts, + pkt->dts, + pkt->data, + pkt->size, + pkt->stream_index, + pkt->flags, + pkt->pos, + pkt->duration); Debug(1, "%s:%d:DEBUG: %s", __FILE__, __LINE__, b); } int VideoStore::writePacket( ZMPacket *ipkt ) { - if ( ipkt->stream_index == video_in_stream.index ) { - return writeVideoFramePacket( &ipkt.packet ); - } else if ( ipkt->stream_index == audio_in_stream.index ) { - return writeAudioFramePacket( &ipkt.packet ); - } else { - Error("Unknown stream type in packet (%d) out input video stream is (%d) and audio is (%d)", - ipkt->packet.stream_index, video_in_stream->index, ( audio_in_stream ? audio_in_stream->index : -1 ) - ); + if ( ipkt->packet.stream_index == video_in_stream_index ) { +Debug(2, "writing a video packet"); + return writeVideoFramePacket( ipkt ); + } else if ( ipkt->packet.stream_index == audio_in_stream_index ) { + return writeAudioFramePacket( ipkt ); } + Error("Unknown stream type in packet (%d) out input video stream is (%d) and audio is (%d)", + ipkt->packet.stream_index, video_in_stream_index, ( audio_in_stream ? audio_in_stream_index : -1 ) + ); + return 0; } -int VideoStore::writeVideoFramePacket(AVPacket *ipkt) { +int VideoStore::writeVideoFramePacket( ZMPacket * zm_packet ) { av_init_packet(&opkt); + frame_count += 1; - opkt.pts = video_next_pts; + // if we have to transcode + if ( video_out_ctx->codec_id != video_in_ctx->codec_id ) { + Debug(3, "Have encoding video frame count (%d)", frame_count); + + if ( ! zm_packet->frame ) { + if ( zm_packet->packet.size ) { + AVPacket *ipkt = &zm_packet->packet; + +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + if ( ( ret = avcodec_send_packet(video_in_ctx, ipkt) ) < 0 ) { + Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str()); + return 0; + } + if ( ( ret = avcodec_receive_frame(video_in_ctx, in_frame) ) < 0 ) { + Error("avcodec_receive_frame fail %s", av_make_error_string(ret).c_str()); + return 0; + } +#else + if ((ret = avcodec_decode_video2(video_in_ctx, in_frame, + &data_present, zm_packet->packet )) < 0) { + Error("Could not decode frame (error '%s')\n", + av_make_error_string(ret).c_str()); + av_frame_free(&in_frame); + return 0; + } else { + Debug(3, "Decoded frame data_present(%d)", data_present); + } + if ( !data_present ) { + Debug(2, "Not ready to transcode a frame yet."); + return 0; + } +#endif + zm_packet->frame = in_frame; + } else if ( zm_packet->image ) { + AVFrame *frame = zm_packet->frame = zm_av_frame_alloc(); + if ( ! frame ) { + Error("Unable to allocate a frame"); + return 0; + } +#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + int codec_imgsize = av_image_get_buffer_size( + video_out_ctx->pix_fmt, + video_out_ctx->width, + video_out_ctx->height, 1); +#else + int codec_imgsize = avpicture_get_size( + video_out_ctx->pix_fmt, + video_out_ctx->width, + video_out_ctx->height); +#endif + + uint8_t *buffer = (uint8_t *)av_malloc(codec_imgsize); + av_image_fill_arrays( + frame->data, + frame->linesize, + buffer, + video_out_ctx->pix_fmt, + video_out_ctx->width, + video_out_ctx->height, + 1); + frame->width = video_out_ctx->width; + frame->height = video_out_ctx->height; + frame->format = video_out_ctx->pix_fmt; + swscale.Convert(zm_packet->image, + buffer, + codec_imgsize, + (AVPixelFormat)zm_packet->image->AVPixFormat(), + video_out_ctx->pix_fmt, + video_out_ctx->width, + video_out_ctx->height + ); + + } // end if has packet or image + } // end if no frame + +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + if ( (ret = avcodec_send_frame(video_out_ctx, zm_packet->frame)) < 0 ) { + Error("Could not send frame (error '%s')", av_make_error_string(ret).c_str()); + zm_av_packet_unref(&opkt); // NOT SURE THIS IS NECCESSARY + return 0; + } + if ( (ret = avcodec_receive_packet(video_out_ctx, &opkt)) < 0 ) { + if (AVERROR(EAGAIN) == ret) { + // THe codec may need more samples than it has, perfectly valid + Debug(3, "Could not recieve packet (error '%s')", + av_make_error_string(ret).c_str()); + } else { + Error("Could not recieve packet (error %d = '%s')", ret, + av_make_error_string(ret).c_str()); + } + zm_av_packet_unref(&opkt); + return 0; + } +#else + if ( (ret = avcodec_encode_video2( + video_out_ctx, &opkt, zm_packet->frame, &data_present)) < 0) { + Error("Could not encode frame (error '%s')", + av_make_error_string(ret).c_str()); + zm_av_packet_unref(&opkt); + return 0; + } + if ( !data_present ) { + Debug(2, "Not ready to out a frame yet."); + zm_av_packet_unref(&opkt); + return 0; + } +#endif + + } else { + AVPacket *ipkt = &zm_packet->packet; + Debug(3, "Doing passthrough, just copy packet"); + // Just copy it because the codec is the same + opkt.data = ipkt->data; + opkt.size = ipkt->size; + opkt.flags = ipkt->flags; + } + + opkt.dts = opkt.pts = zm_packet->timestamp.tv_sec*1000000 + zm_packet->timestamp.tv_usec; +#if 0 opkt.dts = video_next_dts; - opkt.duration = 0; + opkt.pts = video_next_pts; int duration; - if (!video_last_pts) { + if ( !video_last_pts ) { duration = 0; } else { - duration = - av_rescale_q(ipkt->pts - video_last_pts, video_in_stream->time_base, - video_out_stream->time_base); + duration = av_rescale_q( + ipkt->pts - video_last_pts, + video_in_stream->time_base, + video_out_stream->time_base + ); Debug(1, "duration calc: pts(%d) - last_pts(%d) = (%d)", ipkt->pts, - video_last_pts, duration); - if (duration < 0) { + video_last_pts, duration); + if ( duration < 0 ) { duration = ipkt->duration; } } - //#if ( 0 && video_last_pts && ( ipkt->duration == AV_NOPTS_VALUE || ! - //ipkt->duration ) ) { - // Video packets don't really have a duration. Audio does. - // opkt.duration = av_rescale_q(duration, video_in_stream->time_base, - // video_out_stream->time_base); - // opkt.duration = 0; - //} else { - // duration = opkt.duration = av_rescale_q(ipkt->duration, - // video_in_stream->time_base, video_out_stream->time_base); - //} - video_last_pts = ipkt->pts; - video_last_dts = ipkt->dts; - -#if 0 - //Scale the PTS of the outgoing packet to be the correct time base - if ( ipkt->pts != AV_NOPTS_VALUE ) { - - if ( ! video_last_pts ) { - // This is the first packet. - opkt.pts = 0; - Debug(2, "Starting video video_last_pts will become (%d)", ipkt->pts); - } else { - if ( ipkt->pts < video_last_pts ) { - Debug(1, "Resetting video_last_pts from (%d) to (%d)", video_last_pts, ipkt->pts); - // wrap around, need to figure out the distance FIXME having this wrong should cause a jump, but then play ok? - opkt.pts = video_next_pts + av_rescale_q( ipkt->pts, video_in_stream->time_base, video_out_stream->time_base); - } else { - opkt.pts = video_next_pts + av_rescale_q( ipkt->pts - video_last_pts, video_in_stream->time_base, video_out_stream->time_base); - } - } - Debug(3, "opkt.pts = %d from ipkt->pts(%d) - last_pts(%d)", opkt.pts, ipkt->pts, video_last_pts); - video_last_pts = ipkt->pts; - } else { - Debug(3, "opkt.pts = undef"); - opkt.pts = AV_NOPTS_VALUE; - } - // Just because the in stream wraps, doesn't mean the out needs to. Really, if we are limiting ourselves to 10min segments I can't imagine every wrapping in the out. So need to handle in wrap, without causing out wrap. - if ( !video_last_dts ) { - // This is the first packet. - opkt.dts = 0; - Debug(1, "Starting video video_last_dts will become (%lu)", ipkt->dts); - video_last_dts = ipkt->dts; - } else { - // Scale the DTS of the outgoing packet to be the correct time base - - if ( ipkt->dts == AV_NOPTS_VALUE ) { - // why are we using cur_dts instead of packet.dts? I think cur_dts is in AV_TIME_BASE_Q, but ipkt.dts is in video_in_stream->time_base - if ( video_in_stream->cur_dts < video_last_dts ) { - Debug(1, "Resetting video_last_dts from (%d) to (%d) p.dts was (%d)", video_last_dts, video_in_stream->cur_dts, ipkt->dts); - opkt.dts = video_next_dts + av_rescale_q(video_in_stream->cur_dts, AV_TIME_BASE_Q, video_out_stream->time_base); - } else { - opkt.dts = video_next_dts + av_rescale_q(video_in_stream->cur_dts - video_last_dts, AV_TIME_BASE_Q, video_out_stream->time_base); - } - Debug(3, "opkt.dts = %d from video_in_stream->cur_dts(%d) - previus_dts(%d)", opkt.dts, video_in_stream->cur_dts, video_last_dts); - video_last_dts = video_in_stream->cur_dts; - } else { - if ( ipkt->dts < video_last_dts ) { - Debug(1, "Resetting video_last_dts from (%d) to (%d)", video_last_dts, ipkt->dts); - opkt.dts = video_next_dts + av_rescale_q( ipkt->dts, video_in_stream->time_base, video_out_stream->time_base); - } else { - opkt.dts = video_next_dts + av_rescale_q( ipkt->dts - video_last_dts, video_in_stream->time_base, video_out_stream->time_base); - } - Debug(3, "opkt.dts = %d from ipkt.dts(%d) - previus_dts(%d)", opkt.dts, ipkt->dts, video_last_dts); - video_last_dts = ipkt->dts; - } - } + // our timebase is always /1000000 now, so we can use the timestamp as the pts/dts + video_last_pts = zm_packet->timestamp.tv_sec*1000000 + zm_packet->timestamp.tv_usec; + video_last_dts = video_last_pts; + video_last_duration = duration; + opkt.duration = duration; #endif + + write_video_packet( opkt ); + zm_av_packet_unref(&opkt); + + return 1; +} // end int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) + +void VideoStore::write_video_packet( AVPacket &opkt ) { + if (opkt.dts > opkt.pts) { Debug(1, "opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen " @@ -781,36 +957,30 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt) { opkt.dts = opkt.pts; } - opkt.flags = ipkt->flags; int keyframe = opkt.flags & AV_PKT_FLAG_KEY; opkt.pos = -1; - - opkt.data = ipkt->data; - opkt.size = ipkt->size; - opkt.stream_index = video_out_stream->index; + video_next_dts += opkt.duration; + video_next_pts += opkt.duration; + AVPacket safepkt; memcpy(&safepkt, &opkt, sizeof(AVPacket)); Debug(1, - "writing video packet keyframe(%d) pts(%d) dts(%d) duration(%d) " - "ipkt.duration(%d)", - keyframe, opkt.pts, opkt.dts, duration, ipkt->duration); - if ((opkt.data == NULL) || (opkt.size < 1)) { + "writing video packet keyframe(%d) pts(%d) dts(%d) duration(%d) packet_count(%d)", + keyframe, opkt.pts, opkt.dts, opkt.duration, packets_written ); + if ( (opkt.data == NULL) || (opkt.size < 1) ) { Warning("%s:%d: Mangled AVPacket: discarding frame", __FILE__, __LINE__); - dumpPacket(ipkt); dumpPacket(&opkt); - } else if ((video_next_dts > 0) && (video_next_dts > opkt.dts)) { - Warning("%s:%d: DTS out of order: %lld \u226E %lld; discarding frame", - __FILE__, __LINE__, video_next_dts, opkt.dts); - video_next_dts = opkt.dts; - dumpPacket(&opkt); + //} else if ((video_next_dts > 0) && (video_next_dts > opkt.dts)) { + //Warning("%s:%d: DTS out of order: next:%lld \u226E opkt.dts %lld; discarding frame", + //__FILE__, __LINE__, video_next_dts, opkt.dts); + //video_next_dts = opkt.dts; + //dumpPacket(&opkt); } else { - video_next_dts = opkt.dts + duration; - video_next_pts = opkt.pts + duration; ret = av_interleaved_write_frame(oc, &opkt); if (ret < 0) { // There's nothing we can really do if the frame is rejected, just drop it @@ -821,39 +991,38 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt) { __FILE__, __LINE__, av_make_error_string(ret).c_str(), (ret)); dumpPacket(&safepkt); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - zm_dump_codecpar(video_in_stream->codecpar); + if ( video_in_stream ) + zm_dump_codecpar(video_in_stream->codecpar); zm_dump_codecpar(video_out_stream->codecpar); #endif + } else { + packets_written += 1; } } - zm_av_packet_unref(&opkt); +} // end void VideoStore::write_video_packet - return 0; -} // end int VideoStore::writeVideoFramePacket( AVPacket *ipkt ) - -int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { +int VideoStore::writeAudioFramePacket(ZMPacket *zm_packet) { Debug(4, "writeAudioFrame"); - if (!audio_out_stream) { + AVPacket *ipkt = &zm_packet->packet; + + if ( !audio_out_stream ) { Debug(1, "Called writeAudioFramePacket when no audio_out_stream"); return 0; // FIXME -ve return codes do not free packet in ffmpeg_camera at // the moment } - if (audio_out_codec) { + if ( audio_out_codec ) { Debug(3, "Have audio codec"); #ifdef HAVE_LIBAVRESAMPLE #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - ret = avcodec_send_packet(audio_in_ctx, ipkt); - if (ret < 0) { + if ( (ret = avcodec_send_packet(audio_in_ctx, ipkt)) < 0 ) { Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str()); return 0; } - - ret = avcodec_receive_frame(audio_in_ctx, in_frame); - if (ret < 0) { + if ( (ret = avcodec_receive_frame(audio_in_ctx, in_frame)) < 0 ) { Error("avcodec_receive_frame fail %s", av_make_error_string(ret).c_str()); return 0; } @@ -869,15 +1038,15 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { * If we are at the end of the file, pass an empty packet to the decoder * to flush it. */ - if ((ret = avcodec_decode_audio4(audio_in_ctx, in_frame, - &data_present, ipkt)) < 0) { + if ( (ret = avcodec_decode_audio4(audio_in_ctx, in_frame, + &data_present, ipkt)) < 0 ) { Error("Could not decode frame (error '%s')\n", av_make_error_string(ret).c_str()); dumpPacket(ipkt); av_frame_free(&in_frame); return 0; } - if (!data_present) { + if ( !data_present ) { Debug(2, "Not ready to transcode a frame yet."); return 0; } @@ -886,8 +1055,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { // Resample the in into the audioSampleBuffer until we proceed the whole // decoded data - if ((ret = - avresample_convert(resample_ctx, NULL, 0, 0, in_frame->data, + if ((ret = avresample_convert(resample_ctx, NULL, 0, 0, in_frame->data, 0, in_frame->nb_samples)) < 0) { Error("Could not resample frame (error '%s')\n", av_make_error_string(ret).c_str()); @@ -1019,7 +1187,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { } #endif // audio_last_dts = ipkt->dts; - if (opkt.dts > opkt.pts) { + if ( opkt.dts > opkt.pts ) { Debug(1, "opkt.dts(%d) must be <= opkt.pts(%d). Decompression must happen " "before presentation.", @@ -1027,14 +1195,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { opkt.dts = opkt.pts; } - // I wonder if we could just use duration instead of all the hoop jumping - // above? - // - if (out_frame) { - opkt.duration = out_frame->nb_samples; - } else { - opkt.duration = ipkt->duration; - } + opkt.duration = out_frame ? out_frame->nb_samples : ipkt->duration; // opkt.duration = av_rescale_q(ipkt->duration, audio_in_stream->time_base, // audio_out_stream->time_base); Debug(2, "opkt.pts (%d), opkt.dts(%d) opkt.duration = (%d)", opkt.pts, @@ -1057,5 +1218,27 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { Debug(2, "Success writing audio frame"); } zm_av_packet_unref(&opkt); - return 0; + return 1; } // end int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) + +int VideoStore::write_packets( zm_packetqueue &queue ) { + // Need to write out all the frames from the last keyframe? + // No... need to write out all frames from when the event began. Due to PreEventFrames, this could be more than since the last keyframe. + unsigned int packet_count = 0; + ZMPacket *queued_packet; + + while ( ( queued_packet = queue.popPacket() ) ) { + AVPacket *avp = queued_packet->av_packet(); + + packet_count += 1; + //Write the packet to our video store + Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, queue.size() ); + int ret = this->writePacket( queued_packet ); + if ( ret < 0 ) { + //Less than zero and we skipped a frame + } + delete queued_packet; + } // end while packets in the packetqueue + Debug(2, "Wrote %d queued packets", packet_count ); + return packet_count; +} // end int VideoStore::write_packets( PacketQueue &queue ) { diff --git a/src/zm_videostore.h b/src/zm_videostore.h index 553604f88..e0e4eec41 100644 --- a/src/zm_videostore.h +++ b/src/zm_videostore.h @@ -12,17 +12,24 @@ extern "C" { #if HAVE_LIBAVCODEC +class VideoStore; #include "zm_monitor.h" #include "zm_packet.h" +#include "zm_packetqueue.h" class VideoStore { private: unsigned int packets_written; + unsigned int frame_count; AVOutputFormat *out_format; AVFormatContext *oc; AVStream *video_out_stream; AVStream *audio_out_stream; +int video_in_stream_index; +int audio_in_stream_index; + + AVCodec *video_out_codec; AVCodecContext *video_out_ctx; AVStream *video_in_stream; @@ -31,6 +38,7 @@ private: // Move this into the object so that we aren't constantly allocating/deallocating it on the stack AVPacket opkt; // we are transcoding + AVFrame *video_in_frame; AVFrame *in_frame; AVFrame *out_frame; @@ -38,6 +46,8 @@ private: AVCodecContext *audio_in_ctx; int ret; + SWScale swscale; + // The following are used when encoding the audio stream to AAC AVCodec *audio_out_codec; AVCodecContext *audio_out_ctx; @@ -58,12 +68,14 @@ AVAudioResampleContext* resample_ctx; // These are for in int64_t video_last_pts; int64_t video_last_dts; + int64_t video_last_duration; int64_t audio_last_pts; int64_t audio_last_dts; // These are for out, should start at zero. We assume they do not wrap because we just aren't going to save files that big. int64_t video_next_pts; int64_t video_next_dts; +; int64_t audio_next_pts; int64_t audio_next_dts; @@ -77,14 +89,19 @@ public: const char *format_in, AVStream *video_in_stream, AVStream *audio_in_stream, - Monitor * p_monitor); - bool open(); + int64_t starttime, + Monitor * p_monitor + ); ~VideoStore(); + bool open(); - int writeVideoFramePacket( AVPacket *pkt ); - int writeAudioFramePacket( AVPacket *pkt ); + void write_video_packet( AVPacket &pkt ); + void write_audio_packet( AVPacket &pkt ); + int writeVideoFramePacket( ZMPacket *pkt ); + int writeAudioFramePacket( ZMPacket *pkt ); int writePacket( ZMPacket *pkt ); void dumpPacket( AVPacket *pkt ); + int write_packets( zm_packetqueue &queue ); }; #endif //havelibav diff --git a/src/zmc.cpp b/src/zmc.cpp index 39538505e..d235767c5 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -256,14 +256,14 @@ int main(int argc, char *argv[]) { alarm_capture_delays[i] = monitors[i]->GetAlarmCaptureDelay(); Monitor::Function function = monitors[0]->GetFunction(); - if ( function == Monitor::MODECT || function == Monitor::MOCORD ) { + if ( function == Monitor::MODECT || function == Monitor::MOCORD || function == Monitor::RECORD) { Debug(1, "Starting an analysis thread for monitor (%d)", monitors[i]->Id()); analysis_threads[i] = new AnalysisThread(monitors[i]); analysis_threads[i]->start(); } else { analysis_threads[i] = NULL; } - } + } // end foreach monitor int result = 0; struct timeval now; diff --git a/version b/version index b051fa802..623203d2d 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.31.10 +1.31.12 diff --git a/web/ajax/add_monitors.php b/web/ajax/add_monitors.php new file mode 100644 index 000000000..b95f0e8f4 --- /dev/null +++ b/web/ajax/add_monitors.php @@ -0,0 +1,209 @@ +set(array( + 'StorageId' => 1, + 'ServerId' => 'auto', + 'Function' => 'Record', + 'Type' => 'Ffmpeg', + 'Enabled' => '1', + 'Colour' => '4', // 32bit + 'PreEventCount' => 0, +) ); + +function probe( &$url_bits ) { + global $defaultMonitor; + $available_streams = array(); + if ( ! isset($url_bits['port']) ) { + // No port given, do a port scan + foreach ( range( 2000, 2007 ) as $port ) { + $socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP ); + socket_set_option( $socket, + SOL_SOCKET, // socket level + SO_SNDTIMEO, // timeout option + array( + "sec"=>0, // Timeout in seconds + "usec"=>500 // I assume timeout in microseconds + ) + ); + $new_stream = null; +Info("Testing connection to " . $url_bits['host'].':'.$port); + if ( socket_connect( $socket, $url_bits['host'], $port ) ) { + $new_stream = $url_bits; // make a copy + $new_stream['port'] = $port; + } else { + socket_close($socket); + Info("No connection to ".$url_bits['host'] . " on port $port"); + continue; + } + if ( $new_stream ) { + if ( ! isset($new_stream['scheme'] ) ) + $new_stream['scheme'] = 'http'; + $url = unparse_url($new_stream, array('path'=>'/', 'query'=>'action=snapshot')); + list($width, $height, $type, $attr) = getimagesize( $url ); + Info("Got $width x $height from $url"); + $new_stream['Width'] = $width; + $new_stream['Height'] = $height; + + //try { + //if ( $response = do_request( 'GET', $url ) ) { + //$new_stream['path'] = '/'; + //$new_stream['query'] = '?action=stream'; +//$image = imagecreatefromstring($response); + ////$size = getimagesize( $image ); + // + //} else { + //Info("No response from $url"); + //} + //} catch ( EXception $e ) { + //Info("No response from $url"); + //} + $available_streams[] = $new_stream; + } // end if new_Stream + } // end foreach port to scan + } else { + // A port was specified, so don't need to port scan. + $available_streams[] = $url_bits; + } + foreach ( $available_streams as &$stream ) { + # check for existence in db. + $stream['url'] = unparse_url( $stream, array( 'path'=>'/','query'=>'action=stream' ) ); + $monitors = Monitor::find_all( array( 'Path'=>$stream['url'] ) ); + if ( count($monitors ) ) { + $stream['Monitor'] = $monitors[0]; + if ( isset( $stream['Width'] ) and ( $stream['Monitor']->Width() != $stream['Width'] ) ) { + $stream['Warning'] .= 'Monitor width ('.$stream['Monitor']->Width().') and stream width ('.$stream['Width'].") do not match!\n"; + } + if ( isset( $stream['Height'] ) and ( $stream['Monitor']->Height() != $stream['Height'] ) ) { + $stream['Warning'] .= 'Monitor height ('.$stream['Monitor']->Height().') and stream width ('.$stream['Height'].") do not match!\n"; + } + } else { + $stream['Monitor'] = $defaultMonitor; + if ( isset($stream['Width']) ) { + $stream['Monitor']->Width( $stream['Width'] ); + $stream['Monitor']->Height( $stream['Height'] ); + } + } // Monitor found or not + } // end foreach Stream + + #$macCommandString = 'arp ' . $url_bits['host'] . " | awk 'BEGIN{ i=1; } { i++; if(i==3) print $3 }'"; + #$mac = exec($macCommandString); + #Info("Mac $mac"); + return $available_streams; +} // end function probe + +if ( canEdit( 'Monitors' ) ) { + switch ( $_REQUEST['action'] ) { + case 'probe' : + { + $available_streams = array(); + $url_bits = null; + if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $_REQUEST['url'] ) ) { + $url_bits = array( 'host'=>$_REQUEST['url'] ); + } else { + $url_bits = parse_url( $_REQUEST['url'] ); + } + +if ( 0 ) { + // Shortcut test + $monitors = Monitor::find_all( array( 'Path'=>$_REQUEST['url'] ) ); + if ( count( $monitors ) ) { + Info("Monitor found for " . $_REQUEST['url']); + $url_bits['url'] = $_REQUEST['url']; + $url_bits['Monitor'] = $monitors[0]; + $available_stream[] = $url_bits; + ajaxResponse( array ( 'Streams'=>$available_streams) ); + return; + } # end url already has a monitor +} + + if ( ! $url_bits ) { + ajaxError("The given URL was too malformed to parse."); + return; + } + + $available_streams = probe( $url_bits ); + + ajaxResponse( array('Streams'=>$available_streams) ); + return; + } // end case url_probe + case 'import': + { + + $file = $_FILES['import_file']; + + if ($file["error"] > 0) { + ajaxError($file["error"]); + return; + } else { + $filename = $file["name"]; + + $available_streams = array(); + $row = 1; + if (($handle = fopen($file['tmp_name'], 'r')) !== FALSE) { + while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) { + $name = $data[0]; + $url = $data[1]; + $group = $data[2]; + Info("Have the following line data $name $url $group"); + + $url_bits = null; + if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $url) ) { + $url_bits = array( 'host'=>$url, 'scheme'=>'http' ); + } else { + $url_bits = parse_url( $url ); + } + if ( ! $url_bits ) { + Info("Bad url, skipping line $name $url $group"); + continue; + } + + $available_streams += probe( $url_bits ); + + //$url_bits['url'] = unparse_url( $url_bits ); + //$url_bits['Monitor'] = $defaultMonitor; + //$url_bits['Monitor']->Name( $name ); + //$url_bits['Monitor']->merge( $_POST['newMonitor'] ); + //$available_streams[] = $url_bits; + + } // end while rows + fclose($handle); + ajaxResponse( array('Streams'=>$available_streams) ); + } else { + ajaxError("Uploaded file does not exist"); + return; + } + + } + } // end case import + default: + { + Warning("unknown action " . $_REQUEST['action'] ); + } // end ddcase default + } +} else { + Warning("Cannot edit monitors" ); +} + +ajaxError( 'Unrecognised action or insufficient permissions' ); + +?> diff --git a/web/ajax/status.php b/web/ajax/status.php index 2c0af909d..1d7dad8d0 100644 --- a/web/ajax/status.php +++ b/web/ajax/status.php @@ -1,170 +1,170 @@ array( - "permission" => "System", - "table" => "Monitors", - "limit" => 1, - "elements" => array( - "MonitorCount" => array( "sql" => "count(*)" ), - "ActiveMonitorCount" => array( "sql" => "count(if(Function != 'None',1,NULL))" ), - "State" => array( "func" => "daemonCheck()?".translate('Running').":".translate('Stopped') ), - "Load" => array( "func" => "getLoad()" ), - "Disk" => array( "func" => "getDiskPercent()" ), + 'system' => array( + 'permission' => 'System', + 'table' => 'Monitors', + 'limit' => 1, + 'elements' => array( + 'MonitorCount' => array( 'sql' => "count(*)" ), + 'ActiveMonitorCount' => array( 'sql' => "count(if(Function != 'None',1,NULL))" ), + 'State' => array( 'func' => "daemonCheck()?".translate('Running').":".translate('Stopped') ), + 'Load' => array( 'func' => "getLoad()" ), + 'Disk' => array( 'func' => "getDiskPercent()" ), ), ), - "monitor" => array( - "permission" => "Monitors", - "table" => "Monitors", - "limit" => 1, - "selector" => "Monitors.Id", - "elements" => array( - "Id" => array( "sql" => "Monitors.Id" ), - "Name" => array( "sql" => "Monitors.Name" ), - "Type" => true, - "Function" => true, - "Enabled" => true, - "LinkedMonitors" => true, - "Triggers" => true, - "Device" => true, - "Channel" => true, - "Format" => true, - "Host" => true, - "Port" => true, - "Path" => true, - "Width" => array( "sql" => "Monitors.Width" ), - "Height" => array( "sql" => "Monitors.Height" ), - "Palette" => true, - "Orientation" => true, - "Brightness" => true, - "Contrast" => true, - "Hue" => true, - "Colour" => true, - "EventPrefix" => true, - "LabelFormat" => true, - "LabelX" => true, - "LabelY" => true, - "LabelSize" => true, - "ImageBufferCount" => true, - "WarmupCount" => true, - "PreEventCount" => true, - "PostEventCount" => true, - "AlarmFrameCount" => true, - "SectionLength" => true, - "FrameSkip" => true, - "MotionFrameSkip" => true, - "MaxFPS" => true, - "AlarmMaxFPS" => true, - "FPSReportInterval" => true, - "RefBlendPerc" => true, - "Controllable" => true, - "ControlId" => true, - "ControlDevice" => true, - "ControlAddress" => true, - "AutoStopTimeout" => true, - "TrackMotion" => true, - "TrackDelay" => true, - "ReturnLocation" => true, - "ReturnDelay" => true, - "DefaultView" => true, - "DefaultRate" => true, - "DefaultScale" => true, - "WebColour" => true, - "Sequence" => true, - "MinEventId" => array( "sql" => "(SELECT min(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), - "MaxEventId" => array( "sql" => "(SELECT max(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), - "TotalEvents" => array( "sql" => "(SELECT count(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), - "Status" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -s" ), - "FrameRate" => array( "zmu" => "-m ".escapeshellarg($_REQUEST['id'][0])." -f" ), + 'monitor' => array( + 'permission' => 'Monitors', + 'table' => 'Monitors', + 'limit' => 1, + 'selector' => "Monitors.Id", + 'elements' => array( + 'Id' => array( 'sql' => "Monitors.Id" ), + 'Name' => array( 'sql' => "Monitors.Name" ), + 'Type' => true, + 'Function' => true, + 'Enabled' => true, + 'LinkedMonitors' => true, + 'Triggers' => true, + 'Device' => true, + 'Channel' => true, + 'Format' => true, + 'Host' => true, + 'Port' => true, + 'Path' => true, + 'Width' => array( 'sql' => "Monitors.Width" ), + 'Height' => array( 'sql' => "Monitors.Height" ), + 'Palette' => true, + 'Orientation' => true, + 'Brightness' => true, + 'Contrast' => true, + 'Hue' => true, + 'Colour' => true, + 'EventPrefix' => true, + 'LabelFormat' => true, + 'LabelX' => true, + 'LabelY' => true, + 'LabelSize' => true, + 'ImageBufferCount' => true, + 'WarmupCount' => true, + 'PreEventCount' => true, + 'PostEventCount' => true, + 'AlarmFrameCount' => true, + 'SectionLength' => true, + 'FrameSkip' => true, + 'MotionFrameSkip' => true, + 'MaxFPS' => true, + 'AlarmMaxFPS' => true, + 'FPSReportInterval' => true, + 'RefBlendPerc' => true, + 'Controllable' => true, + 'ControlId' => true, + 'ControlDevice' => true, + 'ControlAddress' => true, + 'AutoStopTimeout' => true, + 'TrackMotion' => true, + 'TrackDelay' => true, + 'ReturnLocation' => true, + 'ReturnDelay' => true, + 'DefaultView' => true, + 'DefaultRate' => true, + 'DefaultScale' => true, + 'WebColour' => true, + 'Sequence' => true, + 'MinEventId' => array( 'sql' => "(SELECT min(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), + 'MaxEventId' => array( 'sql' => "(SELECT max(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), + 'TotalEvents' => array( 'sql' => "(SELECT count(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id" ), + 'Status' => array( 'zmu' => "-m ".escapeshellarg($_REQUEST['id'][0])." -s" ), + 'FrameRate' => array( 'zmu' => "-m ".escapeshellarg($_REQUEST['id'][0])." -f" ), ), ), - "events" => array( - "permission" => "Events", - "table" => "Events", - "selector" => "Events.MonitorId", - "elements" => array( - "Id" => true, - "Name" => true, - "Cause" => true, - "Notes" => true, - "StartTime" => true, - "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), - "EndTime" => true, - "Width" => true, - "Height" => true, - "Length" => true, - "Frames" => true, - "AlarmFrames" => true, - "TotScore" => true, - "AvgScore" => true, - "MaxScore" => true, + 'events' => array( + 'permission' => 'Events', + 'table' => 'Events', + 'selector' => "Events.MonitorId", + 'elements' => array( + 'Id' => true, + 'Name' => true, + 'Cause' => true, + 'Notes' => true, + 'StartTime' => true, + 'StartTimeShort' => array( 'sql' => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), + 'EndTime' => true, + 'Width' => true, + 'Height' => true, + 'Length' => true, + 'Frames' => true, + 'AlarmFrames' => true, + 'TotScore' => true, + 'AvgScore' => true, + 'MaxScore' => true, ), ), - "event" => array( - "permission" => "Events", - "table" => "Events", - "limit" => 1, - "selector" => "Events.Id", - "elements" => array( - "Id" => array( "sql" => "Events.Id" ), - "MonitorId" => true, - "MonitorName" => array("sql" => "(SELECT Monitors.Name FROM Monitors WHERE Monitors.Id = Events.MonitorId)"), - "Name" => true, - "Cause" => true, - "StartTime" => true, - "StartTimeShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), - "EndTime" => true, - "Width" => true, - "Height" => true, - "Length" => true, - "Frames" => true, - "DefaultVideo" => true, - "AlarmFrames" => true, - "TotScore" => true, - "AvgScore" => true, - "MaxScore" => true, - "Archived" => true, - "Videoed" => true, - "Uploaded" => true, - "Emailed" => true, - "Messaged" => true, - "Executed" => true, - "Notes" => true, - "MinFrameId" => array( "sql" => "(SELECT min(Frames.FrameId) FROM Frames WHERE EventId=Events.Id)" ), - "MaxFrameId" => array( "sql" => "(SELECT max(Frames.FrameId) FROM Frames WHERE Events.Id = Frames.EventId)" ), - "MinFrameDelta" => array( "sql" => "(SELECT min(Frames.Delta) FROM Frames WHERE Events.Id = Frames.EventId)" ), - "MaxFrameDelta" => array( "sql" => "(SELECT max(Frames.Delta) FROM Frames WHERE Events.Id = Frames.EventId)" ), - //"Path" => array( "postFunc" => "getEventPath" ), + 'event' => array( + 'permission' => 'Events', + 'table' => 'Events', + 'limit' => 1, + 'selector' => "Events.Id", + 'elements' => array( + 'Id' => array( 'sql' => "Events.Id" ), + 'MonitorId' => true, + 'MonitorName' => array('sql' => "(SELECT Monitors.Name FROM Monitors WHERE Monitors.Id = Events.MonitorId)"), + 'Name' => true, + 'Cause' => true, + 'StartTime' => true, + 'StartTimeShort' => array( 'sql' => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), + 'EndTime' => true, + 'Width' => true, + 'Height' => true, + 'Length' => true, + 'Frames' => true, + 'DefaultVideo' => true, + 'AlarmFrames' => true, + 'TotScore' => true, + 'AvgScore' => true, + 'MaxScore' => true, + 'Archived' => true, + 'Videoed' => true, + 'Uploaded' => true, + 'Emailed' => true, + 'Messaged' => true, + 'Executed' => true, + 'Notes' => true, + 'MinFrameId' => array( 'sql' => "(SELECT min(Frames.FrameId) FROM Frames WHERE EventId=Events.Id)" ), + 'MaxFrameId' => array( 'sql' => "(SELECT max(Frames.FrameId) FROM Frames WHERE Events.Id = Frames.EventId)" ), + 'MinFrameDelta' => array( 'sql' => "(SELECT min(Frames.Delta) FROM Frames WHERE Events.Id = Frames.EventId)" ), + 'MaxFrameDelta' => array( 'sql' => "(SELECT max(Frames.Delta) FROM Frames WHERE Events.Id = Frames.EventId)" ), + //'Path' => array( 'postFunc' => 'getEventPath' ), ), ), - "frame" => array( - "permission" => "Events", - "table" => "Frames", - "limit" => 1, - "selector" => array( array( "table" => "Events", "join" => "Events.Id = Frames.EventId", "selector"=>"Events.Id" ), "Frames.FrameId" ), - "elements" => array( - //"Id" => array( "sql" => "Frames.FrameId" ), - "FrameId" => true, - "EventId" => true, - "Type" => true, - "TimeStamp" => true, - "TimeStampShort" => array( "sql" => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), - "Delta" => true, - "Score" => true, - //"Image" => array( "postFunc" => "getFrameImage" ), + 'frame' => array( + 'permission' => 'Events', + 'table' => 'Frames', + 'limit' => 1, + 'selector' => array( array( 'table' => 'Events', 'join' => "Events.Id = Frames.EventId", 'selector'=>"Events.Id" ), "Frames.FrameId" ), + 'elements' => array( + //'Id' => array( 'sql' => "Frames.FrameId" ), + 'FrameId' => true, + 'EventId' => true, + 'Type' => true, + 'TimeStamp' => true, + 'TimeStampShort' => array( 'sql' => "date_format( StartTime, '".MYSQL_FMT_DATETIME_SHORT."' )" ), + 'Delta' => true, + 'Score' => true, + //'Image' => array( 'postFunc' => 'getFrameImage' ), ), ), - "frameimage" => array( - "permission" => "Events", - "func" => "getFrameImage()" + 'frameimage' => array( + 'permission' => 'Events', + 'func' => "getFrameImage()" ), - "nearframe" => array( - "permission" => "Events", - "func" => "getNearFrame()" + 'nearframe' => array( + 'permission' => 'Events', + 'func' => "getNearFrame()" ), - "nearevents" => array( - "permission" => "Events", - "func" => "getNearEvents()" + 'nearevents' => array( + 'permission' => 'Events', + 'func' => "getNearEvents()" ) ); @@ -290,7 +290,7 @@ function collectData() { $data = collectData(); if ( !isset($_REQUEST['layout']) ) { - $_REQUEST['layout'] = "json"; + $_REQUEST['layout'] = 'json'; } switch( $_REQUEST['layout'] ) { @@ -331,7 +331,7 @@ function getFrameImage() { $frame = array(); $frame['EventId'] = $eventId; $frame['FrameId'] = $frameId; - $frame['Type'] = "Virtual"; + $frame['Type'] = 'Virtual'; } $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $frame['EventId'] ) ); $frame['Image'] = getImageSrc( $event, $frame, SCALE_BASE ); @@ -349,7 +349,7 @@ function getNearFrame() { return( array() ); } } - $_REQUEST['entity'] = "frame"; + $_REQUEST['entity'] = 'frame'; $_REQUEST['id'][1] = $nearFrameId; return( collectData() ); } @@ -358,7 +358,7 @@ function getNearEvents() { global $user, $sortColumn, $sortOrder; $eventId = $_REQUEST['id']; - $event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $eventId ) ); + $event = dbFetchOne( 'SELECT * FROM Events WHERE Id=?', NULL, array( $eventId ) ); if ( isset($_REQUEST['filter']) ) parseFilter( $_REQUEST['filter'] ); @@ -369,7 +369,7 @@ function getNearEvents() { else $midSql = ''; - $sql = "select E.Id as Id, E.StartTime as StartTime from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn ".($sortOrder=='asc'?'desc':'asc'); + $sql = "SELECT E.Id AS Id, E.StartTime AS StartTime FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE $sortColumn ".($sortOrder=='asc'?'<=':'>=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." ORDER BY $sortColumn ".($sortOrder=='asc'?'desc':'asc') . ' LIMIT 2'; $result = dbQuery( $sql ); while ( $id = dbFetchNext( $result, 'Id' ) ) { if ( $id == $eventId ) { @@ -378,7 +378,7 @@ function getNearEvents() { } } - $sql = "select E.Id as Id, E.StartTime as StartTime from Events as E inner join Monitors as M on E.MonitorId = M.Id where $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." order by $sortColumn $sortOrder"; + $sql = "SELECT E.Id AS Id, E.StartTime AS StartTime FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE $sortColumn ".($sortOrder=='asc'?'>=':'<=')." '".$event[$_REQUEST['sort_field']]."'".$_REQUEST['filter']['sql'].$midSql." ORDER BY $sortColumn $sortOrder LIMIT 2"; $result = dbQuery( $sql ); while ( $id = dbFetchNext( $result, 'Id' ) ) { if ( $id == $eventId ) { diff --git a/web/ajax/stream.php b/web/ajax/stream.php index 7f050bda8..03e63407d 100644 --- a/web/ajax/stream.php +++ b/web/ajax/stream.php @@ -1,5 +1,7 @@ 0 ) { - if ( count($rSockets) != 1 ) - ajaxError( "Bogus return from select, ".count($rSockets)." sockets available" ); + if ( count($rSockets) != 1 ) { + Error( "Bogus return from select, ".count($rSockets).' sockets available' ); + ajaxError( "Bogus return from select, ".count($rSockets).' sockets available' ); + } } switch( $nbytes = @socket_recvfrom( $socket, $msg, MSG_DATA_SIZE, 0, $remSockFile ) ) { @@ -79,7 +99,7 @@ switch( $nbytes = @socket_recvfrom( $socket, $msg, MSG_DATA_SIZE, 0, $remSockFil } case 0 : { - ajaxError( "No data to read from socket" ); + ajaxError( 'No data to read from socket' ); break; } default : @@ -90,7 +110,7 @@ switch( $nbytes = @socket_recvfrom( $socket, $msg, MSG_DATA_SIZE, 0, $remSockFil } } -$data = unpack( "ltype", $msg ); +$data = unpack( 'ltype', $msg ); switch ( $data['type'] ) { case MSG_DATA_WATCH : { @@ -99,7 +119,7 @@ switch ( $data['type'] ) { $data['rate'] /= RATE_BASE; $data['delay'] = round( $data['delay'], 2 ); $data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 ); - if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == "hashed" ) { + if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) { session_start(); $time = time(); // Regenerate auth hash after half the lifetime of the hash @@ -117,7 +137,7 @@ switch ( $data['type'] ) { //$data['progress'] = sprintf( "%.2f", $data['progress'] ); $data['rate'] /= RATE_BASE; $data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 ); - if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == "hashed" ) { + if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) { session_start(); $time = time(); // Regenerate auth hash after half the lifetime of the hash diff --git a/web/includes/Event.php b/web/includes/Event.php index 392973a6a..9f775f039 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -198,14 +198,6 @@ class Event { } function createListThumbnail( $overwrite=false ) { - # Load the frame with the highest score to use as a thumbnail - if ( !($frame = dbFetchOne( 'SELECT * FROM Frames WHERE EventId=? AND Score=? ORDER BY FrameId LIMIT 1', NULL, array( $this->{'Id'}, $this->{'MaxScore'} ) )) ) { - Error("Unable to find a Frame matching max score " . $this->{'MaxScore'} . ' for event ' . $this->{'Id'} ); - // FIXME: What if somehow the db frame was lost or score was changed? Should probably try another search for any frame. - return( false ); - } - - $frameId = $frame['FrameId']; if ( ZM_WEB_LIST_THUMB_WIDTH ) { $thumbWidth = ZM_WEB_LIST_THUMB_WIDTH; @@ -219,12 +211,25 @@ class Event { Fatal( "No thumbnail width or height specified, please check in Options->Web" ); } - $imageData = $this->getImageSrc( $frame, $scale, false, $overwrite ); - if ( ! $imageData ) { - return ( false ); + $eventPath = $this->Path(); + if ( file_exists( $eventPath.'/snapshot.jpg' ) ) { + $frame = 'snapshot'; + $humbData = array('Path'=>$eventPath.'/snapshot.jpg'); + } else { +# Load the frame with the highest score to use as a thumbnail + if ( !($frame = dbFetchOne( 'SELECT * FROM Frames WHERE EventId=? AND Score=? ORDER BY FrameId LIMIT 1', NULL, array( $this->{'Id'}, $this->{'MaxScore'} ) )) ) { + Error("Unable to find a Frame matching max score " . $this->{'MaxScore'} . ' for event ' . $this->{'Id'} ); + // FIXME: What if somehow the db frame was lost or score was changed? Should probably try another search for any frame. + return( false ); + } + + $imageData = $this->getImageSrc( $frame, $scale, false, $overwrite ); + if ( ! $imageData ) { + return ( false ); + } + $thumbData = $frame; + $thumbData['Path'] = $imageData['thumbPath']; } - $thumbData = $frame; - $thumbData['Path'] = $imageData['thumbPath']; $thumbData['Width'] = (int)$thumbWidth; $thumbData['Height'] = (int)$thumbHeight; diff --git a/web/includes/Filter.php b/web/includes/Filter.php index 59988430d..11d47973a 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -12,6 +12,7 @@ public $defaults = array( 'AutoArchive' => 0, 'AutoVideo' => 0, 'AutoMessage' => 0, + 'UpdateDiskSpace' => 0, 'Background' => 0, 'Concurrent' => 0, 'limit' => 100, diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 8e74c6256..e7b114dde 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -4,13 +4,16 @@ require_once( 'Server.php' ); class Monitor { -private $fields = array( -'Id', -'Name', -'StorageId', -'ServerId', -'Function', -'Enabled', +private $defaults = array( +'Id' => null, +'Name' => '', +'StorageId' => 0, +'ServerId' => 0, +'Function' => 'None', +'Enabled' => 1, +'Width' => null, +'Height' => null, +'Orientation' => null, ); private $control_fields = array( 'Name' => '', @@ -158,19 +161,20 @@ private $control_fields = array( public function Server() { return new Server( $this->{'ServerId'} ); } - public function __call( $fn, array $args){ - if ( count( $args ) ) { + public function __call($fn, array $args){ + if ( count($args) ) { $this->{$fn} = $args[0]; } - if ( array_key_exists( $fn, $this ) ) { + if ( array_key_exists($fn, $this) ) { return $this->{$fn}; #array_unshift($args, $this); #call_user_func_array( $this->{$fn}, $args); } else { - if ( array_key_exists( $fn, $this->control_fields ) ) { + if ( array_key_exists($fn, $this->control_fields) ) { return $this->control_fields{$fn}; + } else if ( array_key_exists( $fn, $this->defaults ) ) { + return $this->defaults{$fn}; } else { - $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; @@ -214,14 +218,19 @@ private $control_fields = array( return( $streamSrc ); } // end function getStreamSrc - public function Width() { + public function Width( $new = null ) { + if ( $new ) + $this->{'Width'} = $new; + if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { return $this->{'Height'}; } return $this->{'Width'}; } - public function Height() { + public function Height( $new=null ) { + if ( $new ) + $this->{'Height'} = $new; if ( $this->Orientation() == '90' or $this->Orientation() == '270' ) { return $this->{'Width'}; } @@ -287,11 +296,12 @@ private $control_fields = array( } } - $sql = 'UPDATE Monitors SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $this->fields ) ) . ' WHERE Id=?'; + $sql = 'UPDATE Monitors SET '.implode(', ', array_map( function($field) {return $field.'=?';}, array_keys( $this->defaults ) ) ) . ' WHERE Id=?'; $values = array_map( function($field){return $this->{$field};}, $this->fields ); $values[] = $this->{'Id'}; dbQuery( $sql, $values ); } // end function save + function zmcControl( $mode=false ) { if ( (!defined('ZM_SERVER_ID')) or ( ZM_SERVER_ID==$this->{'ServerId'} ) ) { if ( $this->{'Type'} == 'Local' ) { @@ -306,7 +316,8 @@ private $control_fields = array( if ( $mode == 'restart' ) { daemonControl( 'stop', 'zmc', $zmcArgs ); } - daemonControl( 'start', 'zmc', $zmcArgs ); + if ( $this->{'Function'} != 'None' ) + daemonControl( 'start', 'zmc', $zmcArgs ); } } else { $Server = $this->Server(); diff --git a/web/includes/MontageLayout.php b/web/includes/MontageLayout.php new file mode 100644 index 000000000..0196d0c9c --- /dev/null +++ b/web/includes/MontageLayout.php @@ -0,0 +1,132 @@ + null, + 'Name' => '', + 'Positions' => 0, + ); + + public function __construct( $IdOrRow = NULL ) { + if ( $IdOrRow ) { + $row = NULL; + if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { + $row = dbFetchOne( 'SELECT * FROM MontageLayouts WHERE Id=?', NULL, array( $IdOrRow ) ); + if ( ! $row ) { + Error("Unable to load MontageLayout record for Id=" . $IdOrRow ); + } + } elseif ( is_array( $IdOrRow ) ) { + $row = $IdOrRow; + } else { + Error("Unknown argument passed to MontageLayout Constructor ($IdOrRow)"); + return; + } + + if ( $row ) { + foreach ($row as $k => $v) { + $this->{$k} = $v; + } + } else { + Error('No row for MontageLayout ' . $IdOrRow ); + } + } # end if isset($IdOrRow) + } // end function __construct + + public function __call($fn, array $args){ + if ( count($args) ) { + $this->{$fn} = $args[0]; + } + if ( array_key_exists($fn, $this) ) { + return $this->{$fn}; + } else { + if ( array_key_exists( $fn, $this->defaults ) ) { + return $this->defaults{$fn}; + } else { + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + Warning( "Unknown function call MontageLayout->$fn from $file:$line" ); + } + } + } + + public function set( $data ) { + foreach ($data as $k => $v) { + if ( is_array( $v ) ) { + # perhaps should turn into a comma-separated string + $this->{$k} = implode(',',$v); + } else if ( is_string( $v ) ) { + $this->{$k} = trim( $v ); + } else if ( is_integer( $v ) ) { + $this->{$k} = $v; + } else if ( is_bool( $v ) ) { + $this->{$k} = $v; + } else { + Error( "Unknown type $k => $v of var " . gettype( $v ) ); + $this->{$k} = $v; + } + } + } + public static function find( $parameters = null, $options = null ) { + $filters = array(); + $sql = 'SELECT * FROM MontageLayouts '; + $values = array(); + + if ( $parameters ) { + $fields = array(); + $sql .= 'WHERE '; + foreach ( $parameters as $field => $value ) { + if ( $value == null ) { + $fields[] = $field.' IS NULL'; + } else if ( is_array( $value ) ) { + $func = function(){return '?';}; + $fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')'; + $values += $value; + + } else { + $fields[] = $field.'=?'; + $values[] = $value; + } + } + $sql .= implode(' AND ', $fields ); + } + if ( $options and isset($options['order']) ) { + $sql .= ' ORDER BY ' . $options['order']; + } + $result = dbQuery($sql, $values); + if ( $result ) { + $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'MontageLayout'); + foreach ( $results as $row => $obj ) { + $filters[] = $obj; + } + } + return $filters; + } + public function save( $new_values = null ) { + if ( $new_values ) { + foreach ( $new_values as $k=>$v ) { + $this->{$k} = $v; + } + } + + $fields = array_values( array_filter( array_keys($this->defaults), function($field){return $field != 'Id';} ) ); + $values = null; + if ( isset($this->{'Id'}) ) { + $sql = 'UPDATE MontageLayouts SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $fields ) ) . ' WHERE Id=?'; + $values = array_map( function($field){return $this->{$field};}, $fields ); + $values[] = $this->{'Id'}; + dbQuery( $sql, $values ); + } else { + $sql = 'INSERT INTO MontageLayouts ('.implode( ',', $fields ).') VALUES ('.implode(',',array_map( function(){return '?';}, $fields ) ).')'; + $values = array_map( function($field){return $this->{$field};}, $fields ); + dbQuery( $sql, $values ); + global $dbConn; + $this->{Id} = $dbConn->lastInsertId(); + } + } // end function save + +} // end class MontageLayout +?> diff --git a/web/includes/actions.php b/web/includes/actions.php index 046b7bbf2..24184d7db 100644 --- a/web/includes/actions.php +++ b/web/includes/actions.php @@ -18,11 +18,32 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // - // PP - POST request handler for PHP which does not need extensions // credit: http://wezfurlong.org/blog/2006/nov/http-post-from-php-without-curl/ +function do_request($method, $url, $data=array(), $optional_headers = null) { + global $php_errormsg; + + $params = array('http' => array( + 'method' => $method, + 'content' => $data + )); + if ($optional_headers !== null) { + $params['http']['header'] = $optional_headers; + } + $ctx = stream_context_create($params); + $fp = @fopen($url, 'rb', false, $ctx); + if (!$fp) { + throw new Exception("Problem with $url, $php_errormsg"); + } + $response = @stream_get_contents($fp); + if ($response === false) { + throw new Exception("Problem reading data from $url, $php_errormsg"); + } + return $response; +} + function do_post_request($url, $data, $optional_headers = null) { $params = array('http' => array( 'method' => 'POST', @@ -56,855 +77,876 @@ function getAffectedIds( $name ) { } -if ( !empty($action) ) { - if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 'remote' || isset($_REQUEST['password']) ) ) { - // if true, a popup will display after login - // PP - lets validate reCaptcha if it exists - if ( defined('ZM_OPT_USE_GOOG_RECAPTCHA') - && defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY') - && defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY') - && ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_GOOG_RECAPTCHA_SECRETKEY - && ZM_OPT_GOOG_RECAPTCHA_SITEKEY) - { - $url = 'https://www.google.com/recaptcha/api/siteverify'; - $fields = array ( - 'secret'=> ZM_OPT_GOOG_RECAPTCHA_SECRETKEY, - 'response' => $_REQUEST['g-recaptcha-response'], - 'remoteip'=> $_SERVER['REMOTE_ADDR'] - ); - $res= do_post_request($url, http_build_query($fields)); - $responseData = json_decode($res,true); - // PP - credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php - // if recaptcha resulted in error, we might have to deny login - if (isset($responseData['success']) && $responseData['success'] == false) { - // PP - before we deny auth, let's make sure the error was not 'invalid secret' - // because that means the user did not configure the secret key correctly - // in this case, we prefer to let him login in and display a message to correct - // the key. Unfortunately, there is no way to check for invalid site key in code - // as it produces the same error as when you don't answer a recaptcha - if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) { - if (!in_array('invalid-input-secret',$responseData['error-codes'])) { - Error ('reCaptcha authentication failed'); - userLogout(); - $view='login'; - $refreshParent = true; - } else { - //Let them login but show an error - echo ''; - Error ("Invalid recaptcha secret detected"); - } - } - } // end if success==false - - } // end if using reCaptcha - - $username = validStr( $_REQUEST['username'] ); - $password = isset($_REQUEST['password'])?validStr($_REQUEST['password']):''; - userLogin( $username, $password ); - $refreshParent = true; - $view = 'console'; - $redirect = true; - } else if ( $action == 'logout' ) { - userLogout(); - $refreshParent = true; - $view = 'none'; - } else if ( $action == 'bandwidth' && isset($_REQUEST['newBandwidth']) ) { - $_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']); - setcookie( 'zmBandwidth', validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10 ); - $refreshParent = true; - } - - // Event scope actions, view permissions only required - if ( canView( 'Events' ) ) { - - if ( isset( $_REQUEST['object'] ) and ( $_REQUEST['object'] == 'filter' ) ) { - if ( $action == 'addterm' ) { -Warning("Addterm"); - $_REQUEST['filter'] = addFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); - } elseif ( $action == 'delterm' ) { - $_REQUEST['filter'] = delFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); - } else if ( canEdit( 'Events' ) ) { - if ( $action == 'delete' ) { - if ( ! empty($_REQUEST['Id']) ) { - dbQuery( 'DELETE FROM Filters WHERE Id=?', array( $_REQUEST['Id'] ) ); - } - } else if ( ( $action == 'save' ) or ( $action == 'execute' ) or ( $action == 'submit' ) ) { - - $sql = ''; - $_REQUEST['filter']['Query']['sort_field'] = validStr($_REQUEST['filter']['Query']['sort_field']); - $_REQUEST['filter']['Query']['sort_asc'] = validStr($_REQUEST['filter']['Query']['sort_asc']); - $_REQUEST['filter']['Query']['limit'] = validInt($_REQUEST['filter']['Query']['limit']); - if ( $action == 'execute' or $action == 'submit' ) { - $sql .= ' Name = \'_TempFilter'.time().'\''; - } else { - $sql .= ' Name = '.dbEscape($_REQUEST['filter']['Name']); - } - $sql .= ', Query = '.dbEscape(jsonEncode($_REQUEST['filter']['Query'])); - $sql .= ', AutoArchive = '.(!empty($_REQUEST['filter']['AutoArchive']) ? 1 : 0); - $sql .= ', AutoVideo = '. ( !empty($_REQUEST['filter']['AutoVideo']) ? 1 : 0); - $sql .= ', AutoUpload = '. ( !empty($_REQUEST['filter']['AutoUpload']) ? 1 : 0); - $sql .= ', AutoEmail = '. ( !empty($_REQUEST['filter']['AutoEmail']) ? 1 : 0); - $sql .= ', AutoMessage = '. ( !empty($_REQUEST['filter']['AutoMessage']) ? 1 : 0); - $sql .= ', AutoExecute = '. ( !empty($_REQUEST['filter']['AutoExecute']) ? 1 : 0); - $sql .= ', AutoExecuteCmd = '.dbEscape($_REQUEST['filter']['AutoExecuteCmd']); - $sql .= ', AutoDelete = '. ( !empty($_REQUEST['filter']['AutoDelete']) ? 1 : 0); - $sql .= ', Background = '. ( !empty($_REQUEST['filter']['Background']) ? 1 : 0); - $sql .= ', Concurrent = '. ( !empty($_REQUEST['filter']['Concurrent']) ? 1 : 0); - - if ( $_REQUEST['Id'] ) { - dbQuery( 'UPDATE Filters SET ' . $sql. ' WHERE Id=?', array($_REQUEST['Id']) ); - } else { - dbQuery( 'INSERT INTO Filters SET' . $sql ); - $_REQUEST['Id'] = dbInsertId(); - } - - } // end if save or execute - } // end if canEdit(Events) - return; - } // end if object == filter - else { - - // Event scope actions, edit permissions required - if ( canEdit( 'Events' ) ) { - if ( $action == 'rename' && isset($_REQUEST['eventName']) && !empty($_REQUEST['eid']) ) { - dbQuery( 'UPDATE Events SET Name=? WHERE Id=?', array( $_REQUEST['eventName'], $_REQUEST['eid'] ) ); - } else if ( $action == 'eventdetail' ) { - if ( !empty($_REQUEST['eid']) ) { - dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['eid'] ) ); - } else { - foreach( getAffectedIds( 'markEid' ) as $markEid ) { - dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid ) ); - } - } - $refreshParent = true; - $closePopup = true; - } elseif ( $action == 'archive' || $action == 'unarchive' ) { - $archiveVal = ($action == 'archive')?1:0; - if ( !empty($_REQUEST['eid']) ) { - dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $_REQUEST['eid']) ); - } else { - foreach( getAffectedIds( 'markEid' ) as $markEid ) { - dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $markEid ) ); - } - $refreshParent = true; - } - } elseif ( $action == 'delete' ) { - foreach( getAffectedIds( 'markEid' ) as $markEid ) { - deleteEvent( $markEid ); - } - $refreshParent = true; - } - } // end if canEdit(Events) - } // end if filter or something else - } // end canView(Events) - - // Monitor control actions, require a monitor id and control view permissions for that monitor - if ( !empty($_REQUEST['mid']) && canView( 'Control', $_REQUEST['mid'] ) ) { - require_once( 'control_functions.php' ); - require_once( 'Monitor.php' ); - $mid = validInt($_REQUEST['mid']); - if ( $action == 'control' ) { - $monitor = new Monitor( $mid ); - - $ctrlCommand = buildControlCommand( $monitor ); - sendControlCommand( $monitor->Id(), $ctrlCommand ); - } elseif ( $action == 'settings' ) { - $args = " -m " . escapeshellarg($mid); - $args .= " -B" . escapeshellarg($_REQUEST['newBrightness']); - $args .= " -C" . escapeshellarg($_REQUEST['newContrast']); - $args .= " -H" . escapeshellarg($_REQUEST['newHue']); - $args .= " -O" . escapeshellarg($_REQUEST['newColour']); - - $zmuCommand = getZmuCommand( $args ); - - $zmuOutput = exec( $zmuCommand ); - list( $brightness, $contrast, $hue, $colour ) = explode( ' ', $zmuOutput ); - dbQuery( 'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?', array($brightness, $contrast, $hue, $colour, $mid)); - } - } - - // Control capability actions, require control edit permissions - if ( canEdit( 'Control' ) ) { - if ( $action == 'controlcap' ) { - if ( !empty($_REQUEST['cid']) ) { - $control = dbFetchOne( 'SELECT * FROM Controls WHERE Id = ?', NULL, array($_REQUEST['cid']) ); - } else { - $control = array(); - } - - // Define a field type for anything that's not simple text equivalent - $types = array( - // Empty - ); - - $columns = getTableColumns( 'Controls' ); - foreach ( $columns as $name=>$type ) { - if ( preg_match( '/^(Can|Has)/', $name ) ) { - $types[$name] = 'toggle'; - } - } - $changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); - - if ( count( $changes ) ) { - if ( !empty($_REQUEST['cid']) ) { - dbQuery( "update Controls set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['cid']) ); - } else { - dbQuery( "insert into Controls set ".implode( ", ", $changes ) ); - //$_REQUEST['cid'] = dbInsertId(); - } - $refreshParent = true; - } - $view = 'none'; - } elseif ( $action == 'delete' ) { - if ( isset($_REQUEST['markCids']) ) { - foreach( $_REQUEST['markCids'] as $markCid ) { - dbQuery( "delete from Controls where Id = ?", array($markCid) ); - dbQuery( "update Monitors set Controllable = 0, ControlId = 0 where ControlId = ?", array($markCid) ); - $refreshParent = true; - } - } - } - } - - if ( isset($_REQUEST['object']) and $_REQUEST['object'] == 'Monitor' ) { - if ( $action == 'save' ) { - foreach ( $_REQUEST['mids'] as $mid ) { - $mid = ValidInt( $mid ); - if ( ! canEdit('Monitors', $mid ) ) { - Warning("Cannot edit monitor $mid"); - continue; - } - $Monitor = new Monitor( $mid ); - $Monitor->zmaControl('stop'); - $Monitor->zmcControl('stop'); - $Monitor->save( $_REQUEST['newMonitor'] ); - if ($Monitor->Function() != 'None' ) { - $Monitor->zmcControl('start'); - if ( $Monitor->Enabled() ) { - $Monitor->zmaControl('start'); - } - } - - } // end foreach mid - $refreshParent = true; - } // end if action == save - } // end if object is Monitor - - // Monitor edit actions, require a monitor id and edit permissions for that monitor - if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) { - $mid = validInt($_REQUEST['mid']); - if ( $action == 'function' ) { - $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array($mid) ); - - $newFunction = validStr($_REQUEST['newFunction']); - # Because we use a checkbox, it won't get passed in the request. So not being in _REQUEST means 0 - $newEnabled = ( !isset( $_REQUEST['newEnabled'] ) or $_REQUEST['newEnabled'] != '1' ) ? '0' : '1'; - $oldFunction = $monitor['Function']; - $oldEnabled = $monitor['Enabled']; - if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) { - dbQuery( 'UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?', array( $newFunction, $newEnabled, $mid ) ); - - $monitor['Function'] = $newFunction; - $monitor['Enabled'] = $newEnabled; - if ( daemonCheck() ) { - $restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled); - zmaControl( $monitor, 'stop' ); - zmcControl( $monitor, $restart?'restart':'' ); - zmaControl( $monitor, 'start' ); - } - $refreshParent = true; - } - } elseif ( $action == 'zone' && isset( $_REQUEST['zid'] ) ) { - $zid = validInt($_REQUEST['zid']); - $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array($mid) ); - - if ( !empty($zid) ) { - $zone = dbFetchOne( 'SELECT * FROM Zones WHERE MonitorId=? AND Id=?', NULL, array( $mid, $zid ) ); - } else { - $zone = array(); - } - - if ( $_REQUEST['newZone']['Units'] == 'Percent' ) { - $_REQUEST['newZone']['MinAlarmPixels'] = intval(($_REQUEST['newZone']['MinAlarmPixels']*$_REQUEST['newZone']['Area'])/100); - $_REQUEST['newZone']['MaxAlarmPixels'] = intval(($_REQUEST['newZone']['MaxAlarmPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MinFilterPixels']) ) - $_REQUEST['newZone']['MinFilterPixels'] = intval(($_REQUEST['newZone']['MinFilterPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MaxFilterPixels']) ) - $_REQUEST['newZone']['MaxFilterPixels'] = intval(($_REQUEST['newZone']['MaxFilterPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MinBlobPixels']) ) - $_REQUEST['newZone']['MinBlobPixels'] = intval(($_REQUEST['newZone']['MinBlobPixels']*$_REQUEST['newZone']['Area'])/100); - if ( isset($_REQUEST['newZone']['MaxBlobPixels']) ) - $_REQUEST['newZone']['MaxBlobPixels'] = intval(($_REQUEST['newZone']['MaxBlobPixels']*$_REQUEST['newZone']['Area'])/100); - } - - unset( $_REQUEST['newZone']['Points'] ); - $types = array(); - $changes = getFormChanges( $zone, $_REQUEST['newZone'], $types ); - - if ( count( $changes ) ) { - if ( $zid > 0 ) { - dbQuery( "UPDATE Zones SET ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zid) ); - } else { - dbQuery( "INSERT INTO Zones SET MonitorId=?, ".implode( ", ", $changes ), array( $mid ) ); - } - //if ( $cookies ) session_write_close(); - if ( daemonCheck() ) { - if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) { - zmaControl( $monitor, 'stop' ); - zmcControl( $monitor, 'restart' ); - zmaControl( $monitor, 'start' ); - } else { - zmaControl( $mid, 'restart' ); - } - } - if ( $_REQUEST['newZone']['Type'] == 'Privacy' && $monitor['Controllable'] ) { - require_once( 'control_functions.php' ); - sendControlCommand( $mid, 'quit' ); - } - $refreshParent = true; - } - $view = 'none'; - } elseif ( $action == 'plugin' && isset($_REQUEST['pl'])) { - $sql='SELECT * FROM PluginsConfig WHERE MonitorId=? AND ZoneId=? AND pluginName=?'; - $pconfs=dbFetchAll( $sql, NULL, array( $mid, $_REQUEST['zid'], $_REQUEST['pl'] ) ); - $changes=0; - foreach( $pconfs as $pconf ) { - $value=$_REQUEST['pluginOpt'][$pconf['Name']]; - if(array_key_exists($pconf['Name'], $_REQUEST['pluginOpt']) && ($pconf['Value']!=$value)) { - dbQuery("UPDATE PluginsConfig SET Value=? WHERE id=?", array( $value, $pconf['Id'] ) ); - $changes++; - } - } - if($changes>0) { - if ( daemonCheck() ) { - zmaControl( $mid, 'restart' ); - } - $refreshParent = true; - } - $view = 'none'; - } elseif ( $action == 'sequence' && isset($_REQUEST['smid']) ) { - $smid = validInt($_REQUEST['smid']); - $monitor = dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($mid) ); - $smonitor = dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($smid) ); - - dbQuery( 'update Monitors set Sequence=? where Id=?', array( $smonitor['Sequence'], $monitor['Id'] ) ); - dbQuery( 'update Monitors set Sequence=? WHERE Id=?', array( $monitor['Sequence'], $smonitor['Id'] ) ); - - $refreshParent = true; - fixSequences(); - } elseif ( $action == 'delete' ) { - if ( isset($_REQUEST['markZids']) ) { - $deletedZid = 0; - foreach( $_REQUEST['markZids'] as $markZid ) { - $zone = dbFetchOne( 'select * from Zones where Id=?', NULL, array($markZid) ); - dbQuery( 'delete from Zones WHERE MonitorId=? AND Id=?', array( $mid, $markZid) ); - $deletedZid = 1; - } - if ( $deletedZid ) { - //if ( $cookies ) - //session_write_close(); - if ( daemonCheck() ) { - if ( $zone['Type'] == 'Privacy' ) { - zmaControl( $mid, 'stop' ); - zmcControl( $mid, 'restart' ); - zmaControl( $mid, 'start' ); - } else { - zmaControl( $mid, 'restart' ); - } - } // end if daemonCheck() - $refreshParent = true; - } // end if deletedzid - } // end if isset($_REQUEST['markZids']) - } // end if action - } // end if $mid and canEdit($mid) - - // Monitor edit actions, monitor id derived, require edit permissions for that monitor - if ( canEdit( 'Monitors' ) ) { - if ( $action == 'monitor' ) { - $mid = 0; - if ( !empty($_REQUEST['mid']) ) { - $mid = validInt($_REQUEST['mid']); - $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid) ); - - if ( ZM_OPT_X10 ) { - $x10Monitor = dbFetchOne( 'SELECT * FROM TriggersX10 WHERE MonitorId=?', NULL, array($mid) ); - if ( !$x10Monitor ) - $x10Monitor = array(); - } - } else { - $monitor = array(); - if ( ZM_OPT_X10 ) { - $x10Monitor = array(); - } - } - - // Define a field type for anything that's not simple text equivalent - $types = array( - 'Triggers' => 'set', - 'Controllable' => 'toggle', - 'TrackMotion' => 'toggle', - 'Enabled' => 'toggle', - 'DoNativeMotDet' => 'toggle', - 'Exif' => 'toggle', - 'RTSPDescribe' => 'toggle', - 'RecordAudio' => 'toggle', - ); - - $columns = getTableColumns( 'Monitors' ); - $changes = getFormChanges( $monitor, $_REQUEST['newMonitor'], $types, $columns ); - - if ( count( $changes ) ) { - if ( $mid ) { - - # If we change anything that changes the shared mem size, zma can complain. So let's stop first. - zmaControl( $monitor, 'stop' ); - zmcControl( $monitor, 'stop' ); - dbQuery( 'UPDATE Monitors SET '.implode( ', ', $changes ).' WHERE Id=?', array($mid) ); - if ( isset($changes['Name']) ) { - $saferOldName = basename( $monitor['Name'] ); - $saferNewName = basename( $_REQUEST['newMonitor']['Name'] ); - rename( ZM_DIR_EVENTS.'/'.$saferOldName, ZM_DIR_EVENTS.'/'.$saferNewName); - } - if ( isset($changes['Width']) || isset($changes['Height']) ) { - $newW = $_REQUEST['newMonitor']['Width']; - $newH = $_REQUEST['newMonitor']['Height']; - $newA = $newW * $newH; - $oldW = $monitor['Width']; - $oldH = $monitor['Height']; - $oldA = $oldW * $oldH; - - $zones = dbFetchAll( 'SELECT * FROM Zones WHERE MonitorId=?', NULL, array($mid) ); - foreach ( $zones as $zone ) { - $newZone = $zone; - $points = coordsToPoints( $zone['Coords'] ); - for ( $i = 0; $i < count($points); $i++ ) { - $points[$i]['x'] = intval(($points[$i]['x']*($newW-1))/($oldW-1)); - $points[$i]['y'] = intval(($points[$i]['y']*($newH-1))/($oldH-1)); - } - $newZone['Coords'] = pointsToCoords( $points ); - $newZone['Area'] = intval(round(($zone['Area']*$newA)/$oldA)); - $newZone['MinAlarmPixels'] = intval(round(($newZone['MinAlarmPixels']*$newA)/$oldA)); - $newZone['MaxAlarmPixels'] = intval(round(($newZone['MaxAlarmPixels']*$newA)/$oldA)); - $newZone['MinFilterPixels'] = intval(round(($newZone['MinFilterPixels']*$newA)/$oldA)); - $newZone['MaxFilterPixels'] = intval(round(($newZone['MaxFilterPixels']*$newA)/$oldA)); - $newZone['MinBlobPixels'] = intval(round(($newZone['MinBlobPixels']*$newA)/$oldA)); - $newZone['MaxBlobPixels'] = intval(round(($newZone['MaxBlobPixels']*$newA)/$oldA)); - - $changes = getFormChanges( $zone, $newZone, $types ); - - if ( count( $changes ) ) { - dbQuery( "update Zones set ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zone['Id'] ) ); - } - } - } - } elseif ( ! $user['MonitorIds'] ) { // Can only create new monitors if we are not restricted to specific monitors -# FIXME This is actually a race condition. Should lock the table. - $maxSeq = dbFetchOne( 'SELECT max(Sequence) AS MaxSequence FROM Monitors', 'MaxSequence' ); - $changes[] = 'Sequence = '.($maxSeq+1); - - dbQuery( 'INSERT INTO Monitors SET '.implode( ', ', $changes ) ); - $mid = dbInsertId(); - $zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height']; - dbQuery( "insert into Zones set MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) ); - //$view = 'none'; - mkdir( ZM_DIR_EVENTS.'/'.$mid, 0755 ); - $saferName = basename($_REQUEST['newMonitor']['Name']); - symlink( $mid, ZM_DIR_EVENTS.'/'.$saferName ); - if ( isset($_COOKIE['zmGroup']) ) { - dbQuery( "UPDATE Groups SET MonitorIds = concat(MonitorIds,',".$mid."') WHERE Id=?", array($_COOKIE['zmGroup']) ); - } - } else { - Error("Users with Monitors restrictions cannot create new monitors."); - } - $restart = true; - } # end if count(changes) - - if ( ZM_OPT_X10 ) { - $x10Changes = getFormChanges( $x10Monitor, $_REQUEST['newX10Monitor'] ); - - if ( count( $x10Changes ) ) { - if ( $x10Monitor && isset($_REQUEST['newX10Monitor']) ) { - dbQuery( "update TriggersX10 set ".implode( ", ", $x10Changes )." where MonitorId=?", array($mid) ); - } elseif ( !$user['MonitorIds'] ) { - if ( !$x10Monitor ) { - dbQuery( "insert into TriggersX10 set MonitorId = ?, ".implode( ", ", $x10Changes ), array( $mid ) ); - } else { - dbQuery( "delete from TriggersX10 where MonitorId = ?", array($mid) ); - } - } - $restart = true; - } - } - - if ( $restart ) { - $new_monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid) ); - //fixDevices(); - //if ( $cookies ) - //session_write_close(); - - zmcControl( $new_monitor, 'start' ); - zmaControl( $new_monitor, 'start' ); - - if ( $new_monitor['Controllable'] ) { - require_once( 'control_functions.php' ); - sendControlCommand( $mid, 'quit' ); - } - // really should thump zmwatch and maybe zmtrigger too. - //daemonControl( 'restart', 'zmwatch.pl' ); - $refreshParent = true; - } // end if restart - $view = 'none'; - } elseif ( $action == 'delete' ) { - if ( isset($_REQUEST['markMids']) && !$user['MonitorIds'] ) { - foreach( $_REQUEST['markMids'] as $markMid ) { - if ( canEdit( 'Monitors', $markMid ) ) { - if ( $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($markMid) ) ) { - if ( daemonCheck() ) { - zmaControl( $monitor, 'stop' ); - zmcControl( $monitor, 'stop' ); - } - - // If fast deletes are on, then zmaudit will clean everything else up later - // If fast deletes are off and there are lots of events then this step may - // well time out before completing, in which case zmaudit will still tidy up - if ( !ZM_OPT_FAST_DELETE ) { - $markEids = dbFetchAll( 'SELECT Id FROM Events WHERE MonitorId=?', 'Id', array($markMid) ); - foreach( $markEids as $markEid ) - deleteEvent( $markEid ); - - deletePath( ZM_DIR_EVENTS.'/'.basename($monitor['Name']) ); - deletePath( ZM_DIR_EVENTS.'/'.$monitor['Id'] ); // I'm trusting the Id. - } // end if ZM_OPT_FAST_DELETE - - // This is the important stuff - dbQuery( 'DELETE FROM Monitors WHERE Id = ?', array($markMid) ); - dbQuery( 'DELETE FROM Zones WHERE MonitorId = ?', array($markMid) ); - if ( ZM_OPT_X10 ) - dbQuery( 'DELETE FROM TriggersX10 WHERE MonitorId=?', array($markMid) ); - - fixSequences(); - - } // end if found the monitor in the db - } // end if canedit this monitor - } // end foreach monitor in MarkMid - } // markMids is set and we aren't limited to specific monitors - } // end if action == Delete - } - - // Device view actions - if ( canEdit( 'Devices' ) ) { - if ( $action == 'device' ) { - if ( !empty($_REQUEST['command']) ) { - setDeviceStatusX10( $_REQUEST['key'], $_REQUEST['command'] ); - } elseif ( isset( $_REQUEST['newDevice'] ) ) { - if ( isset($_REQUEST['did']) ) { - dbQuery( "update Devices set Name=?, KeyString=? where Id=?", array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'], $_REQUEST['did']) ); - } else { - dbQuery( "insert into Devices set Name=?, KeyString=?", array( $_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'] ) ); - } - $refreshParent = true; - $view = 'none'; - } - } elseif ( $action == 'delete' ) { - if ( isset($_REQUEST['markDids']) ) { - foreach( $_REQUEST['markDids'] as $markDid ) { - dbQuery( "delete from Devices where Id=?", array($markDid) ); - $refreshParent = true; - } - } - } // end if action - } // end if canedit devices - - // Group view actions - if ( canView( 'Groups' ) && $action == 'setgroup' ) { - if ( !empty($_REQUEST['gid']) ) { - setcookie( 'zmGroup', validInt($_REQUEST['gid']), time()+3600*24*30*12*10 ); - } else { - setcookie( 'zmGroup', '', time()-3600*24*2 ); - } - $refreshParent = true; - } - - // Group edit actions -# Should probably verify that each monitor id is a valid monitor, that we have access to. However at the moment, you have to have System permissions to do this - if ( canEdit( 'Groups' ) ) { - if ( $action == 'group' ) { - $monitors = empty( $_POST['newGroup']['MonitorIds'] ) ? '' : implode(',', $_POST['newGroup']['MonitorIds']); - if ( !empty($_POST['gid']) ) { - dbQuery( 'UPDATE Groups SET Name=?, ParentId=?, MonitorIds=? WHERE Id=?', - array($_POST['newGroup']['Name'], ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), $monitors, $_POST['gid']) ); - } else { - dbQuery( 'INSERT INTO Groups SET Name=?, ParentId=?, MonitorIds=?', - array( $_POST['newGroup']['Name'], ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), $monitors ) ); - } - $view = 'none'; - $refreshParent = true; - } else if ( $action == 'delete' ) { - if ( !empty($_REQUEST['gid']) ) { - if ( is_array( $_REQUEST['gid'] ) ) { - foreach( $_REQUEST['gid'] as $gid ) { - $Group = new Group( $gid ); - $Group->delete(); - } - } else { - $Group = new Group( $_REQUEST['gid'] ); - $Group->delete(); - } - } - $refreshParent = true; - } # end if action - } // end if can edit groups - - // System edit actions - if ( canEdit( 'System' ) ) { - if ( isset( $_REQUEST['object'] ) ) { - if ( $_REQUEST['object'] == 'server' ) { - - if ( $action == 'Save' ) { - if ( !empty($_REQUEST['id']) ) - $dbServer = dbFetchOne( 'SELECT * FROM Servers WHERE Id=?', NULL, array($_REQUEST['id']) ); - else - $dbServer = array(); - - $types = array(); - $changes = getFormChanges( $dbServer, $_REQUEST['newServer'], $types ); - - if ( count( $changes ) ) { - if ( !empty($_REQUEST['id']) ) { - dbQuery( "UPDATE Servers SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); - } else { - dbQuery( "INSERT INTO Servers set ".implode( ", ", $changes ) ); - } - $refreshParent = true; - } - $view = 'none'; - } else if ( $action == 'delete' ) { - if ( !empty($_REQUEST['markIds']) ) { - foreach( $_REQUEST['markIds'] as $Id ) - dbQuery( "DELETE FROM Servers WHERE Id=?", array($Id) ); - } - $refreshParent = true; - } else { - Error( "Unknown action $action in saving Server" ); - } - } else if ( $_REQUEST['object'] == 'storage' ) { - if ( $action == 'Save' ) { - if ( !empty($_REQUEST['id']) ) - $dbStorage = dbFetchOne( 'SELECT * FROM Storage WHERE Id=?', NULL, array($_REQUEST['id']) ); - else - $dbStorage = array(); - - $types = array(); - $changes = getFormChanges( $dbStorage, $_REQUEST['newStorage'], $types ); - - if ( count( $changes ) ) { - if ( !empty($_REQUEST['id']) ) { - dbQuery( "UPDATE Storage SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); - } else { - dbQuery( "INSERT INTO Storage set ".implode( ", ", $changes ) ); - } - $refreshParent = true; - } - $view = 'none'; - } else if ( $action == 'delete' ) { - if ( !empty($_REQUEST['markIds']) ) { - foreach( $_REQUEST['markIds'] as $Id ) - dbQuery( 'DELETE FROM Storage WHERE Id=?', array($Id) ); - } - $refreshParent = true; - } else { - Error( "Unknown action $action in saving Storage" ); - } - } # end if isset($_REQUEST['object'] ) - - } else if ( $action == 'version' && isset($_REQUEST['option']) ) { - $option = $_REQUEST['option']; - switch( $option ) { - case 'go' : - { - // Ignore this, the caller will open the page itself - break; - } - case 'ignore' : - { - dbQuery( "update Config set Value = '".ZM_DYN_LAST_VERSION."' where Name = 'ZM_DYN_CURR_VERSION'" ); - break; - } - case 'hour' : - case 'day' : - case 'week' : - { - $nextReminder = time(); - if ( $option == 'hour' ) { - $nextReminder += 60*60; - } elseif ( $option == 'day' ) { - $nextReminder += 24*60*60; - } elseif ( $option == 'week' ) { - $nextReminder += 7*24*60*60; - } - dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_NEXT_REMINDER'" ); - break; - } - case 'never' : - { - dbQuery( "update Config set Value = '0' where Name = 'ZM_CHECK_FOR_UPDATES'" ); - break; - } - } - } - if ( $action == 'donate' && isset($_REQUEST['option']) ) { - $option = $_REQUEST['option']; - switch( $option ) { - case 'go' : - { - // Ignore this, the caller will open the page itself - break; - } - case 'hour' : - case 'day' : - case 'week' : - case 'month' : - { - $nextReminder = time(); - if ( $option == 'hour' ) { - $nextReminder += 60*60; - } elseif ( $option == 'day' ) { - $nextReminder += 24*60*60; - } elseif ( $option == 'week' ) { - $nextReminder += 7*24*60*60; - } elseif ( $option == 'month' ) { - $nextReminder += 30*24*60*60; - } - dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_DONATE_REMINDER_TIME'" ); - break; - } - case 'never' : - case 'already' : - { - dbQuery( "update Config set Value = '0' where Name = 'ZM_DYN_SHOW_DONATE_REMINDER'" ); - break; - } - } // end switch option - } - if ( $action == 'options' && isset($_REQUEST['tab']) ) { - $configCat = $configCats[$_REQUEST['tab']]; - $changed = false; - foreach ( $configCat as $name=>$value ) { - unset( $newValue ); - if ( $value['Type'] == 'boolean' && empty($_REQUEST['newConfig'][$name]) ) - $newValue = 0; - elseif ( isset($_REQUEST['newConfig'][$name]) ) - $newValue = preg_replace( "/\r\n/", "\n", stripslashes( $_REQUEST['newConfig'][$name] ) ); - - if ( isset($newValue) && ($newValue != $value['Value']) ) { - dbQuery( 'UPDATE Config SET Value=? WHERE Name=?', array( $newValue, $name ) ); - $changed = true; - } - } - if ( $changed ) { - switch( $_REQUEST['tab'] ) { - case 'system' : - case 'config' : - $restartWarning = true; - break; - case 'web' : - case 'tools' : - break; - case 'logging' : - case 'network' : - case 'mail' : - case 'upload' : - $restartWarning = true; - break; - case 'highband' : - case 'medband' : - case 'lowband' : - break; - } - } - loadConfig( false ); - } elseif ( $action == 'user' ) { - if ( !empty($_REQUEST['uid']) ) - $dbUser = dbFetchOne( "SELECT * FROM Users WHERE Id=?", NULL, array($_REQUEST['uid']) ); - else - $dbUser = array(); - - $types = array(); - $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); - - if ( $_REQUEST['newUser']['Password'] ) - $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; - else - unset( $changes['Password'] ); - - if ( count( $changes ) ) { - if ( !empty($_REQUEST['uid']) ) { - dbQuery( "update Users set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['uid']) ); - # If we are updating the logged in user, then update our session user data. - if ( $user and ( $dbUser['Username'] == $user['Username'] ) ) - userLogin( $dbUser['Username'], $dbUser['Password'] ); - } else { - dbQuery( "insert into Users set ".implode( ", ", $changes ) ); - } - $refreshParent = true; - } - $view = 'none'; - } elseif ( $action == 'state' ) { - if ( !empty($_REQUEST['runState']) ) { - //if ( $cookies ) session_write_close(); - packageControl( $_REQUEST['runState'] ); - $refreshParent = true; - } - } elseif ( $action == 'save' ) { - if ( !empty($_REQUEST['runState']) || !empty($_REQUEST['newState']) ) { - $sql = 'SELECT Id,Function,Enabled FROM Monitors ORDER BY Id'; - $definitions = array(); - foreach( dbFetchAll( $sql ) as $monitor ) - { - $definitions[] = $monitor['Id'].":".$monitor['Function'].":".$monitor['Enabled']; - } - $definition = join( ',', $definitions ); - if ( $_REQUEST['newState'] ) - $_REQUEST['runState'] = $_REQUEST['newState']; - dbQuery( "replace into States set Name=?, Definition=?", array( $_REQUEST['runState'],$definition) ); - } - } elseif ( $action == 'delete' ) { - if ( isset($_REQUEST['runState']) ) - dbQuery( "delete from States where Name=?", array($_REQUEST['runState']) ); - - if ( isset($_REQUEST['markUids']) ) { - foreach( $_REQUEST['markUids'] as $markUid ) - dbQuery( "delete from Users where Id = ?", array($markUid) ); - if ( $markUid == $user['Id'] ) +if ( empty($action) ) { + return; +} +if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 'remote' || isset($_REQUEST['password']) ) ) { + // if true, a popup will display after login + // PP - lets validate reCaptcha if it exists + if ( defined('ZM_OPT_USE_GOOG_RECAPTCHA') + && defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY') + && defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY') + && ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_GOOG_RECAPTCHA_SECRETKEY + && ZM_OPT_GOOG_RECAPTCHA_SITEKEY ) + { + $url = 'https://www.google.com/recaptcha/api/siteverify'; + $fields = array ( + 'secret' => ZM_OPT_GOOG_RECAPTCHA_SECRETKEY, + 'response' => $_REQUEST['g-recaptcha-response'], + 'remoteip' => $_SERVER['REMOTE_ADDR'] + ); + $res = do_post_request($url, http_build_query($fields)); + $responseData = json_decode($res,true); + // PP - credit: https://github.com/google/recaptcha/blob/master/src/ReCaptcha/Response.php + // if recaptcha resulted in error, we might have to deny login + if (isset($responseData['success']) && $responseData['success'] == false) { + // PP - before we deny auth, let's make sure the error was not 'invalid secret' + // because that means the user did not configure the secret key correctly + // in this case, we prefer to let him login in and display a message to correct + // the key. Unfortunately, there is no way to check for invalid site key in code + // as it produces the same error as when you don't answer a recaptcha + if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) { + if (!in_array('invalid-input-secret',$responseData['error-codes'])) { + Error ('reCaptcha authentication failed'); userLogout(); + $view='login'; + $refreshParent = true; + } else { + //Let them login but show an error + echo ''; + Error ("Invalid recaptcha secret detected"); + } } - } - } else { - if ( ZM_USER_SELF_EDIT && $action == 'user' ) { - $uid = $user['Id']; + } // end if success==false - $dbUser = dbFetchOne( 'SELECT Id, Password, Language FROM Users WHERE Id = ?', NULL, array($uid) ); + } // end if using reCaptcha - $types = array(); - $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); + $username = validStr( $_REQUEST['username'] ); + $password = isset($_REQUEST['password'])?validStr($_REQUEST['password']):''; + userLogin( $username, $password ); + $refreshParent = true; + $view = 'console'; + $redirect = true; +} else if ( $action == 'logout' ) { + userLogout(); + $refreshParent = true; + $view = 'none'; +} else if ( $action == 'bandwidth' && isset($_REQUEST['newBandwidth']) ) { + $_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']); + setcookie( 'zmBandwidth', validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10 ); + $refreshParent = true; +} - if ( !empty($_REQUEST['newUser']['Password']) ) - $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; - else - unset( $changes['Password'] ); - if ( count( $changes ) ) { - dbQuery( "update Users set ".implode( ", ", $changes )." where Id=?", array($uid) ); +// Event scope actions, view permissions only required +if ( canView( 'Events' ) ) { + + if ( isset( $_REQUEST['object'] ) and ( $_REQUEST['object'] == 'filter' ) ) { + if ( $action == 'addterm' ) { +Warning("Addterm"); + $_REQUEST['filter'] = addFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); + } elseif ( $action == 'delterm' ) { + $_REQUEST['filter'] = delFilterTerm( $_REQUEST['filter'], $_REQUEST['line'] ); + } else if ( canEdit( 'Events' ) ) { + if ( $action == 'delete' ) { + if ( ! empty($_REQUEST['Id']) ) { + dbQuery( 'DELETE FROM Filters WHERE Id=?', array( $_REQUEST['Id'] ) ); + } + } else if ( ( $action == 'save' ) or ( $action == 'execute' ) or ( $action == 'submit' ) ) { + + $sql = ''; + $_REQUEST['filter']['Query']['sort_field'] = validStr($_REQUEST['filter']['Query']['sort_field']); + $_REQUEST['filter']['Query']['sort_asc'] = validStr($_REQUEST['filter']['Query']['sort_asc']); + $_REQUEST['filter']['Query']['limit'] = validInt($_REQUEST['filter']['Query']['limit']); + if ( $action == 'execute' or $action == 'submit' ) { + $sql .= ' Name = \'_TempFilter'.time().'\''; + } else { + $sql .= ' Name = '.dbEscape($_REQUEST['filter']['Name']); + } + $sql .= ', Query = '.dbEscape(jsonEncode($_REQUEST['filter']['Query'])); + $sql .= ', AutoArchive = '.(!empty($_REQUEST['filter']['AutoArchive']) ? 1 : 0); + $sql .= ', AutoVideo = '. ( !empty($_REQUEST['filter']['AutoVideo']) ? 1 : 0); + $sql .= ', AutoUpload = '. ( !empty($_REQUEST['filter']['AutoUpload']) ? 1 : 0); + $sql .= ', AutoEmail = '. ( !empty($_REQUEST['filter']['AutoEmail']) ? 1 : 0); + $sql .= ', AutoMessage = '. ( !empty($_REQUEST['filter']['AutoMessage']) ? 1 : 0); + $sql .= ', AutoExecute = '. ( !empty($_REQUEST['filter']['AutoExecute']) ? 1 : 0); + $sql .= ', AutoExecuteCmd = '.dbEscape($_REQUEST['filter']['AutoExecuteCmd']); + $sql .= ', AutoDelete = '. ( !empty($_REQUEST['filter']['AutoDelete']) ? 1 : 0); + $sql .= ', UpdateDiskSpace = '. ( !empty($_REQUEST['filter']['UpdateDiskSpace']) ? 1 : 0); + $sql .= ', Background = '. ( !empty($_REQUEST['filter']['Background']) ? 1 : 0); + $sql .= ', Concurrent = '. ( !empty($_REQUEST['filter']['Concurrent']) ? 1 : 0); + + if ( $_REQUEST['Id'] ) { + dbQuery( 'UPDATE Filters SET ' . $sql. ' WHERE Id=?', array($_REQUEST['Id']) ); + } else { + dbQuery( 'INSERT INTO Filters SET' . $sql ); + $_REQUEST['Id'] = dbInsertId(); + } + + } // end if save or execute + } // end if canEdit(Events) + return; + } // end if object == filter + else { + + // Event scope actions, edit permissions required + if ( canEdit( 'Events' ) ) { + if ( $action == 'rename' && isset($_REQUEST['eventName']) && !empty($_REQUEST['eid']) ) { + dbQuery( 'UPDATE Events SET Name=? WHERE Id=?', array( $_REQUEST['eventName'], $_REQUEST['eid'] ) ); + } else if ( $action == 'eventdetail' ) { + if ( !empty($_REQUEST['eid']) ) { + dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['eid'] ) ); + } else { + foreach( getAffectedIds( 'markEid' ) as $markEid ) { + dbQuery( 'UPDATE Events SET Cause=?, Notes=? WHERE Id=?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid ) ); + } + } + $refreshParent = true; + $closePopup = true; + } elseif ( $action == 'archive' || $action == 'unarchive' ) { + $archiveVal = ($action == 'archive')?1:0; + if ( !empty($_REQUEST['eid']) ) { + dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $_REQUEST['eid']) ); + } else { + foreach( getAffectedIds( 'markEid' ) as $markEid ) { + dbQuery( 'UPDATE Events SET Archived=? WHERE Id=?', array( $archiveVal, $markEid ) ); + } + $refreshParent = true; + } + } elseif ( $action == 'delete' ) { + foreach( getAffectedIds( 'markEid' ) as $markEid ) { + deleteEvent( $markEid ); + } $refreshParent = true; } - $view = 'none'; - } - } + } // end if canEdit(Events) + } // end if filter or something else +} // end canView(Events) - if ( $action == 'reset' ) { - $_SESSION['zmEventResetTime'] = strftime( STRF_FMT_DATETIME_DB ); - setcookie( 'zmEventResetTime', $_SESSION['zmEventResetTime'], time()+3600*24*30*12*10 ); - //if ( $cookies ) session_write_close(); +// Monitor control actions, require a monitor id and control view permissions for that monitor +if ( !empty($_REQUEST['mid']) && canView( 'Control', $_REQUEST['mid'] ) ) { + require_once( 'control_functions.php' ); + require_once( 'Monitor.php' ); + $mid = validInt($_REQUEST['mid']); + if ( $action == 'control' ) { + $monitor = new Monitor( $mid ); + + $ctrlCommand = buildControlCommand( $monitor ); + sendControlCommand( $monitor->Id(), $ctrlCommand ); + } elseif ( $action == 'settings' ) { + $args = " -m " . escapeshellarg($mid); + $args .= " -B" . escapeshellarg($_REQUEST['newBrightness']); + $args .= " -C" . escapeshellarg($_REQUEST['newContrast']); + $args .= " -H" . escapeshellarg($_REQUEST['newHue']); + $args .= " -O" . escapeshellarg($_REQUEST['newColour']); + + $zmuCommand = getZmuCommand( $args ); + + $zmuOutput = exec( $zmuCommand ); + list( $brightness, $contrast, $hue, $colour ) = explode( ' ', $zmuOutput ); + dbQuery( 'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?', array($brightness, $contrast, $hue, $colour, $mid)); } } +// Control capability actions, require control edit permissions +if ( canEdit( 'Control' ) ) { + if ( $action == 'controlcap' ) { + if ( !empty($_REQUEST['cid']) ) { + $control = dbFetchOne( 'SELECT * FROM Controls WHERE Id = ?', NULL, array($_REQUEST['cid']) ); + } else { + $control = array(); + } + + // Define a field type for anything that's not simple text equivalent + $types = array( + // Empty + ); + + $columns = getTableColumns( 'Controls' ); + foreach ( $columns as $name=>$type ) { + if ( preg_match( '/^(Can|Has)/', $name ) ) { + $types[$name] = 'toggle'; + } + } + $changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['cid']) ) { + dbQuery( "update Controls set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['cid']) ); + } else { + dbQuery( "insert into Controls set ".implode( ", ", $changes ) ); + //$_REQUEST['cid'] = dbInsertId(); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markCids']) ) { + foreach( $_REQUEST['markCids'] as $markCid ) { + dbQuery( "delete from Controls where Id = ?", array($markCid) ); + dbQuery( "update Monitors set Controllable = 0, ControlId = 0 where ControlId = ?", array($markCid) ); + $refreshParent = true; + } + } + } +} + +if ( isset($_REQUEST['object']) and $_REQUEST['object'] == 'Monitor' ) { + if ( $action == 'save' ) { + foreach ( $_REQUEST['mids'] as $mid ) { + $mid = ValidInt( $mid ); + if ( ! canEdit('Monitors', $mid ) ) { + Warning("Cannot edit monitor $mid"); + continue; + } + $Monitor = new Monitor( $mid ); + $Monitor->zmaControl('stop'); + $Monitor->zmcControl('stop'); + $Monitor->save( $_REQUEST['newMonitor'] ); + if ($Monitor->Function() != 'None' ) { + $Monitor->zmcControl('start'); + if ( $Monitor->Enabled() ) { + $Monitor->zmaControl('start'); + } + } + + } // end foreach mid + $refreshParent = true; + } // end if action == save +} // end if object is Monitor + +// Monitor edit actions, require a monitor id and edit permissions for that monitor +if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) { + $mid = validInt($_REQUEST['mid']); + if ( $action == 'function' ) { + $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array($mid) ); + + $newFunction = validStr($_REQUEST['newFunction']); + # Because we use a checkbox, it won't get passed in the request. So not being in _REQUEST means 0 + $newEnabled = ( !isset( $_REQUEST['newEnabled'] ) or $_REQUEST['newEnabled'] != '1' ) ? '0' : '1'; + $oldFunction = $monitor['Function']; + $oldEnabled = $monitor['Enabled']; + if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) { + dbQuery( 'UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?', array( $newFunction, $newEnabled, $mid ) ); + + $monitor['Function'] = $newFunction; + $monitor['Enabled'] = $newEnabled; + if ( daemonCheck() ) { + $restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled); + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, $restart?'restart':'' ); + zmaControl( $monitor, 'start' ); + } + $refreshParent = true; + } + } elseif ( $action == 'zone' && isset( $_REQUEST['zid'] ) ) { + $zid = validInt($_REQUEST['zid']); + $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array($mid) ); + + if ( !empty($zid) ) { + $zone = dbFetchOne( 'SELECT * FROM Zones WHERE MonitorId=? AND Id=?', NULL, array( $mid, $zid ) ); + } else { + $zone = array(); + } + + if ( $_REQUEST['newZone']['Units'] == 'Percent' ) { + $_REQUEST['newZone']['MinAlarmPixels'] = intval(($_REQUEST['newZone']['MinAlarmPixels']*$_REQUEST['newZone']['Area'])/100); + $_REQUEST['newZone']['MaxAlarmPixels'] = intval(($_REQUEST['newZone']['MaxAlarmPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MinFilterPixels']) ) + $_REQUEST['newZone']['MinFilterPixels'] = intval(($_REQUEST['newZone']['MinFilterPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MaxFilterPixels']) ) + $_REQUEST['newZone']['MaxFilterPixels'] = intval(($_REQUEST['newZone']['MaxFilterPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MinBlobPixels']) ) + $_REQUEST['newZone']['MinBlobPixels'] = intval(($_REQUEST['newZone']['MinBlobPixels']*$_REQUEST['newZone']['Area'])/100); + if ( isset($_REQUEST['newZone']['MaxBlobPixels']) ) + $_REQUEST['newZone']['MaxBlobPixels'] = intval(($_REQUEST['newZone']['MaxBlobPixels']*$_REQUEST['newZone']['Area'])/100); + } + + unset( $_REQUEST['newZone']['Points'] ); + $types = array(); + $changes = getFormChanges( $zone, $_REQUEST['newZone'], $types ); + + if ( count( $changes ) ) { + if ( $zid > 0 ) { + dbQuery( "UPDATE Zones SET ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zid) ); + } else { + dbQuery( "INSERT INTO Zones SET MonitorId=?, ".implode( ", ", $changes ), array( $mid ) ); + } + //if ( $cookies ) session_write_close(); + if ( daemonCheck() ) { + if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) { + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'restart' ); + zmaControl( $monitor, 'start' ); + } else { + zmaControl( $mid, 'restart' ); + } + } + if ( $_REQUEST['newZone']['Type'] == 'Privacy' && $monitor['Controllable'] ) { + require_once( 'control_functions.php' ); + sendControlCommand( $mid, 'quit' ); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'plugin' && isset($_REQUEST['pl'])) { + $sql='SELECT * FROM PluginsConfig WHERE MonitorId=? AND ZoneId=? AND pluginName=?'; + $pconfs=dbFetchAll( $sql, NULL, array( $mid, $_REQUEST['zid'], $_REQUEST['pl'] ) ); + $changes=0; + foreach( $pconfs as $pconf ) { + $value=$_REQUEST['pluginOpt'][$pconf['Name']]; + if(array_key_exists($pconf['Name'], $_REQUEST['pluginOpt']) && ($pconf['Value']!=$value)) { + dbQuery("UPDATE PluginsConfig SET Value=? WHERE id=?", array( $value, $pconf['Id'] ) ); + $changes++; + } + } + if($changes>0) { + if ( daemonCheck() ) { + zmaControl( $mid, 'restart' ); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'sequence' && isset($_REQUEST['smid']) ) { + $smid = validInt($_REQUEST['smid']); + $monitor = dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($mid) ); + $smonitor = dbFetchOne( 'select * from Monitors where Id = ?', NULL, array($smid) ); + + dbQuery( 'update Monitors set Sequence=? where Id=?', array( $smonitor['Sequence'], $monitor['Id'] ) ); + dbQuery( 'update Monitors set Sequence=? WHERE Id=?', array( $monitor['Sequence'], $smonitor['Id'] ) ); + + $refreshParent = true; + fixSequences(); + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markZids']) ) { + $deletedZid = 0; + foreach( $_REQUEST['markZids'] as $markZid ) { + $zone = dbFetchOne( 'select * from Zones where Id=?', NULL, array($markZid) ); + dbQuery( 'delete from Zones WHERE MonitorId=? AND Id=?', array( $mid, $markZid) ); + $deletedZid = 1; + } + if ( $deletedZid ) { + //if ( $cookies ) + //session_write_close(); + if ( daemonCheck() ) { + if ( $zone['Type'] == 'Privacy' ) { + zmaControl( $mid, 'stop' ); + zmcControl( $mid, 'restart' ); + zmaControl( $mid, 'start' ); + } else { + zmaControl( $mid, 'restart' ); + } + } // end if daemonCheck() + $refreshParent = true; + } // end if deletedzid + } // end if isset($_REQUEST['markZids']) + } // end if action +} // end if $mid and canEdit($mid) + +// Monitor edit actions, monitor id derived, require edit permissions for that monitor +if ( canEdit( 'Monitors' ) ) { + if ( $action == 'monitor' ) { + $mid = 0; + if ( !empty($_REQUEST['mid']) ) { + $mid = validInt($_REQUEST['mid']); + $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid) ); + + if ( ZM_OPT_X10 ) { + $x10Monitor = dbFetchOne( 'SELECT * FROM TriggersX10 WHERE MonitorId=?', NULL, array($mid) ); + if ( !$x10Monitor ) + $x10Monitor = array(); + } + } else { + $monitor = array(); + if ( ZM_OPT_X10 ) { + $x10Monitor = array(); + } + } + + // Define a field type for anything that's not simple text equivalent + $types = array( + 'Triggers' => 'set', + 'Controllable' => 'toggle', + 'TrackMotion' => 'toggle', + 'Enabled' => 'toggle', + 'DoNativeMotDet' => 'toggle', + 'Exif' => 'toggle', + 'RTSPDescribe' => 'toggle', + 'RecordAudio' => 'toggle', + ); + + $columns = getTableColumns( 'Monitors' ); + $changes = getFormChanges( $monitor, $_REQUEST['newMonitor'], $types, $columns ); + + if ( count( $changes ) ) { + if ( $mid ) { + + # If we change anything that changes the shared mem size, zma can complain. So let's stop first. + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'stop' ); + dbQuery( 'UPDATE Monitors SET '.implode( ', ', $changes ).' WHERE Id=?', array($mid) ); + if ( isset($changes['Name']) ) { + $saferOldName = basename( $monitor['Name'] ); + $saferNewName = basename( $_REQUEST['newMonitor']['Name'] ); + rename( ZM_DIR_EVENTS.'/'.$saferOldName, ZM_DIR_EVENTS.'/'.$saferNewName); + } + if ( isset($changes['Width']) || isset($changes['Height']) ) { + $newW = $_REQUEST['newMonitor']['Width']; + $newH = $_REQUEST['newMonitor']['Height']; + $newA = $newW * $newH; + $oldW = $monitor['Width']; + $oldH = $monitor['Height']; + $oldA = $oldW * $oldH; + + $zones = dbFetchAll( 'SELECT * FROM Zones WHERE MonitorId=?', NULL, array($mid) ); + foreach ( $zones as $zone ) { + $newZone = $zone; + $points = coordsToPoints( $zone['Coords'] ); + for ( $i = 0; $i < count($points); $i++ ) { + $points[$i]['x'] = intval(($points[$i]['x']*($newW-1))/($oldW-1)); + $points[$i]['y'] = intval(($points[$i]['y']*($newH-1))/($oldH-1)); + } + $newZone['Coords'] = pointsToCoords( $points ); + $newZone['Area'] = intval(round(($zone['Area']*$newA)/$oldA)); + $newZone['MinAlarmPixels'] = intval(round(($newZone['MinAlarmPixels']*$newA)/$oldA)); + $newZone['MaxAlarmPixels'] = intval(round(($newZone['MaxAlarmPixels']*$newA)/$oldA)); + $newZone['MinFilterPixels'] = intval(round(($newZone['MinFilterPixels']*$newA)/$oldA)); + $newZone['MaxFilterPixels'] = intval(round(($newZone['MaxFilterPixels']*$newA)/$oldA)); + $newZone['MinBlobPixels'] = intval(round(($newZone['MinBlobPixels']*$newA)/$oldA)); + $newZone['MaxBlobPixels'] = intval(round(($newZone['MaxBlobPixels']*$newA)/$oldA)); + + $changes = getFormChanges( $zone, $newZone, $types ); + + if ( count( $changes ) ) { + dbQuery( "update Zones set ".implode( ", ", $changes )." WHERE MonitorId=? AND Id=?", array( $mid, $zone['Id'] ) ); + } + } + } + } elseif ( ! $user['MonitorIds'] ) { // Can only create new monitors if we are not restricted to specific monitors +# FIXME This is actually a race condition. Should lock the table. + $maxSeq = dbFetchOne( 'SELECT max(Sequence) AS MaxSequence FROM Monitors', 'MaxSequence' ); + $changes[] = 'Sequence = '.($maxSeq+1); + + dbQuery( 'INSERT INTO Monitors SET '.implode( ', ', $changes ) ); + $mid = dbInsertId(); + $zoneArea = $_REQUEST['newMonitor']['Width'] * $_REQUEST['newMonitor']['Height']; + dbQuery( "insert into Zones set MonitorId = ?, Name = 'All', Type = 'Active', Units = 'Percent', NumCoords = 4, Coords = ?, Area=?, AlarmRGB = 0xff0000, CheckMethod = 'Blobs', MinPixelThreshold = 25, MinAlarmPixels=?, MaxAlarmPixels=?, FilterX = 3, FilterY = 3, MinFilterPixels=?, MaxFilterPixels=?, MinBlobPixels=?, MinBlobs = 1", array( $mid, sprintf( "%d,%d %d,%d %d,%d %d,%d", 0, 0, $_REQUEST['newMonitor']['Width']-1, 0, $_REQUEST['newMonitor']['Width']-1, $_REQUEST['newMonitor']['Height']-1, 0, $_REQUEST['newMonitor']['Height']-1 ), $zoneArea, intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*3)/100), intval(($zoneArea*75)/100), intval(($zoneArea*2)/100) ) ); + //$view = 'none'; + mkdir( ZM_DIR_EVENTS.'/'.$mid, 0755 ); + $saferName = basename($_REQUEST['newMonitor']['Name']); + symlink( $mid, ZM_DIR_EVENTS.'/'.$saferName ); + if ( isset($_COOKIE['zmGroup']) ) { + dbQuery( "UPDATE Groups SET MonitorIds = concat(MonitorIds,',".$mid."') WHERE Id=?", array($_COOKIE['zmGroup']) ); + } + } else { + Error("Users with Monitors restrictions cannot create new monitors."); + } + $restart = true; + } # end if count(changes) + + if ( ZM_OPT_X10 ) { + $x10Changes = getFormChanges( $x10Monitor, $_REQUEST['newX10Monitor'] ); + + if ( count( $x10Changes ) ) { + if ( $x10Monitor && isset($_REQUEST['newX10Monitor']) ) { + dbQuery( "update TriggersX10 set ".implode( ", ", $x10Changes )." where MonitorId=?", array($mid) ); + } elseif ( !$user['MonitorIds'] ) { + if ( !$x10Monitor ) { + dbQuery( "insert into TriggersX10 set MonitorId = ?, ".implode( ", ", $x10Changes ), array( $mid ) ); + } else { + dbQuery( "delete from TriggersX10 where MonitorId = ?", array($mid) ); + } + } + $restart = true; + } + } + + if ( $restart ) { + $new_monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid) ); + //fixDevices(); + //if ( $cookies ) + //session_write_close(); + + zmcControl( $new_monitor, 'start' ); + zmaControl( $new_monitor, 'start' ); + + if ( $new_monitor['Controllable'] ) { + require_once( 'control_functions.php' ); + sendControlCommand( $mid, 'quit' ); + } + // really should thump zmwatch and maybe zmtrigger too. + //daemonControl( 'restart', 'zmwatch.pl' ); + $refreshParent = true; + } // end if restart + $view = 'none'; + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markMids']) && !$user['MonitorIds'] ) { + foreach( $_REQUEST['markMids'] as $markMid ) { + if ( canEdit( 'Monitors', $markMid ) ) { + if ( $monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($markMid) ) ) { + if ( daemonCheck() ) { + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'stop' ); + } + + // If fast deletes are on, then zmaudit will clean everything else up later + // If fast deletes are off and there are lots of events then this step may + // well time out before completing, in which case zmaudit will still tidy up + if ( !ZM_OPT_FAST_DELETE ) { + $markEids = dbFetchAll( 'SELECT Id FROM Events WHERE MonitorId=?', 'Id', array($markMid) ); + foreach( $markEids as $markEid ) + deleteEvent( $markEid ); + + deletePath( ZM_DIR_EVENTS.'/'.basename($monitor['Name']) ); + deletePath( ZM_DIR_EVENTS.'/'.$monitor['Id'] ); // I'm trusting the Id. + } // end if ZM_OPT_FAST_DELETE + + // This is the important stuff + dbQuery( 'DELETE FROM Monitors WHERE Id = ?', array($markMid) ); + dbQuery( 'DELETE FROM Zones WHERE MonitorId = ?', array($markMid) ); + if ( ZM_OPT_X10 ) + dbQuery( 'DELETE FROM TriggersX10 WHERE MonitorId=?', array($markMid) ); + + fixSequences(); + + } // end if found the monitor in the db + } // end if canedit this monitor + } // end foreach monitor in MarkMid + } // markMids is set and we aren't limited to specific monitors + } // end if action == Delete +} + +// Device view actions +if ( canEdit( 'Devices' ) ) { + if ( $action == 'device' ) { + if ( !empty($_REQUEST['command']) ) { + setDeviceStatusX10( $_REQUEST['key'], $_REQUEST['command'] ); + } elseif ( isset( $_REQUEST['newDevice'] ) ) { + if ( isset($_REQUEST['did']) ) { + dbQuery( "update Devices set Name=?, KeyString=? where Id=?", array($_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'], $_REQUEST['did']) ); + } else { + dbQuery( "insert into Devices set Name=?, KeyString=?", array( $_REQUEST['newDevice']['Name'], $_REQUEST['newDevice']['KeyString'] ) ); + } + $refreshParent = true; + $view = 'none'; + } + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['markDids']) ) { + foreach( $_REQUEST['markDids'] as $markDid ) { + dbQuery( "delete from Devices where Id=?", array($markDid) ); + $refreshParent = true; + } + } + } // end if action +} // end if canedit devices + +// Group view actions +if ( canView( 'Groups' ) && $action == 'setgroup' ) { + if ( !empty($_REQUEST['gid']) ) { + setcookie( 'zmGroup', validInt($_REQUEST['gid']), time()+3600*24*30*12*10 ); + } else { + setcookie( 'zmGroup', '', time()-3600*24*2 ); + } + $refreshParent = true; +} + +// Group edit actions +# Should probably verify that each monitor id is a valid monitor, that we have access to. However at the moment, you have to have System permissions to do this +if ( canEdit( 'Groups' ) ) { + if ( $action == 'group' ) { + $monitors = empty( $_POST['newGroup']['MonitorIds'] ) ? '' : implode(',', $_POST['newGroup']['MonitorIds']); + if ( !empty($_POST['gid']) ) { + dbQuery( 'UPDATE Groups SET Name=?, ParentId=?, MonitorIds=? WHERE Id=?', + array($_POST['newGroup']['Name'], ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), $monitors, $_POST['gid']) ); + } else { + dbQuery( 'INSERT INTO Groups SET Name=?, ParentId=?, MonitorIds=?', + array( $_POST['newGroup']['Name'], ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), $monitors ) ); + } + $view = 'none'; + $refreshParent = true; + } else if ( $action == 'delete' ) { + if ( !empty($_REQUEST['gid']) ) { + if ( is_array( $_REQUEST['gid'] ) ) { + foreach( $_REQUEST['gid'] as $gid ) { + $Group = new Group( $gid ); + $Group->delete(); + } + } else { + $Group = new Group( $_REQUEST['gid'] ); + $Group->delete(); + } + } + $refreshParent = true; + } # end if action +} // end if can edit groups + +// System edit actions +if ( canEdit( 'System' ) ) { + if ( isset( $_REQUEST['object'] ) ) { + if ( $_REQUEST['object'] == 'MontageLayout' ) { + require_once('MontageLayout.php'); + if ( $action == 'Save' ) { + $Layout = null; + if ( $_REQUEST['Name'] != '' ) { + $Layout = new MontageLayout(); + $Layout->Name( $_REQUEST['Name'] ); + } else { + $Layout = new MontageLayout( $_REQUEST['zmMontageLayout'] ); + } + $Layout->Positions( $_REQUEST['Positions'] ); + $Layout->save(); + session_start(); + $_SESSION['zmMontageLayout'] = $Layout->Id(); + setcookie('zmMontageLayout', $Layout->Id(), 1 ); + session_write_close(); + $redirect = true; + } // end if save + + } else if ( $_REQUEST['object'] == 'server' ) { + + if ( $action == 'Save' ) { + if ( !empty($_REQUEST['id']) ) + $dbServer = dbFetchOne( 'SELECT * FROM Servers WHERE Id=?', NULL, array($_REQUEST['id']) ); + else + $dbServer = array(); + + $types = array(); + $changes = getFormChanges( $dbServer, $_REQUEST['newServer'], $types ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['id']) ) { + dbQuery( "UPDATE Servers SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); + } else { + dbQuery( "INSERT INTO Servers set ".implode( ", ", $changes ) ); + } + $refreshParent = true; + } + $view = 'none'; + } else if ( $action == 'delete' ) { + if ( !empty($_REQUEST['markIds']) ) { + foreach( $_REQUEST['markIds'] as $Id ) + dbQuery( "DELETE FROM Servers WHERE Id=?", array($Id) ); + } + $refreshParent = true; + } else { + Error( "Unknown action $action in saving Server" ); + } + } else if ( $_REQUEST['object'] == 'storage' ) { + if ( $action == 'Save' ) { + if ( !empty($_REQUEST['id']) ) + $dbStorage = dbFetchOne( 'SELECT * FROM Storage WHERE Id=?', NULL, array($_REQUEST['id']) ); + else + $dbStorage = array(); + + $types = array(); + $changes = getFormChanges( $dbStorage, $_REQUEST['newStorage'], $types ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['id']) ) { + dbQuery( "UPDATE Storage SET ".implode( ", ", $changes )." WHERE Id = ?", array($_REQUEST['id']) ); + } else { + dbQuery( "INSERT INTO Storage set ".implode( ", ", $changes ) ); + } + $refreshParent = true; + } + $view = 'none'; + } else if ( $action == 'delete' ) { + if ( !empty($_REQUEST['markIds']) ) { + foreach( $_REQUEST['markIds'] as $Id ) + dbQuery( 'DELETE FROM Storage WHERE Id=?', array($Id) ); + } + $refreshParent = true; + } else { + Error( "Unknown action $action in saving Storage" ); + } + } # end if isset($_REQUEST['object'] ) + + } else if ( $action == 'version' && isset($_REQUEST['option']) ) { + $option = $_REQUEST['option']; + switch( $option ) { + case 'go' : + { + // Ignore this, the caller will open the page itself + break; + } + case 'ignore' : + { + dbQuery( "update Config set Value = '".ZM_DYN_LAST_VERSION."' where Name = 'ZM_DYN_CURR_VERSION'" ); + break; + } + case 'hour' : + case 'day' : + case 'week' : + { + $nextReminder = time(); + if ( $option == 'hour' ) { + $nextReminder += 60*60; + } elseif ( $option == 'day' ) { + $nextReminder += 24*60*60; + } elseif ( $option == 'week' ) { + $nextReminder += 7*24*60*60; + } + dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_NEXT_REMINDER'" ); + break; + } + case 'never' : + { + dbQuery( "update Config set Value = '0' where Name = 'ZM_CHECK_FOR_UPDATES'" ); + break; + } + } + } + if ( $action == 'donate' && isset($_REQUEST['option']) ) { + $option = $_REQUEST['option']; + switch( $option ) { + case 'go' : + { + // Ignore this, the caller will open the page itself + break; + } + case 'hour' : + case 'day' : + case 'week' : + case 'month' : + { + $nextReminder = time(); + if ( $option == 'hour' ) { + $nextReminder += 60*60; + } elseif ( $option == 'day' ) { + $nextReminder += 24*60*60; + } elseif ( $option == 'week' ) { + $nextReminder += 7*24*60*60; + } elseif ( $option == 'month' ) { + $nextReminder += 30*24*60*60; + } + dbQuery( "update Config set Value = '".$nextReminder."' where Name = 'ZM_DYN_DONATE_REMINDER_TIME'" ); + break; + } + case 'never' : + case 'already' : + { + dbQuery( "update Config set Value = '0' where Name = 'ZM_DYN_SHOW_DONATE_REMINDER'" ); + break; + } + } // end switch option + } + if ( $action == 'options' && isset($_REQUEST['tab']) ) { + $configCat = $configCats[$_REQUEST['tab']]; + $changed = false; + foreach ( $configCat as $name=>$value ) { + unset( $newValue ); + if ( $value['Type'] == 'boolean' && empty($_REQUEST['newConfig'][$name]) ) + $newValue = 0; + elseif ( isset($_REQUEST['newConfig'][$name]) ) + $newValue = preg_replace( "/\r\n/", "\n", stripslashes( $_REQUEST['newConfig'][$name] ) ); + + if ( isset($newValue) && ($newValue != $value['Value']) ) { + dbQuery( 'UPDATE Config SET Value=? WHERE Name=?', array( $newValue, $name ) ); + $changed = true; + } + } + if ( $changed ) { + switch( $_REQUEST['tab'] ) { + case 'system' : + case 'config' : + $restartWarning = true; + break; + case 'web' : + case 'tools' : + break; + case 'logging' : + case 'network' : + case 'mail' : + case 'upload' : + $restartWarning = true; + break; + case 'highband' : + case 'medband' : + case 'lowband' : + break; + } + } + loadConfig( false ); + } elseif ( $action == 'user' ) { + if ( !empty($_REQUEST['uid']) ) + $dbUser = dbFetchOne( "SELECT * FROM Users WHERE Id=?", NULL, array($_REQUEST['uid']) ); + else + $dbUser = array(); + + $types = array(); + $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); + + if ( $_REQUEST['newUser']['Password'] ) + $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; + else + unset( $changes['Password'] ); + + if ( count( $changes ) ) { + if ( !empty($_REQUEST['uid']) ) { + dbQuery( "update Users set ".implode( ", ", $changes )." where Id = ?", array($_REQUEST['uid']) ); + # If we are updating the logged in user, then update our session user data. + if ( $user and ( $dbUser['Username'] == $user['Username'] ) ) + userLogin( $dbUser['Username'], $dbUser['Password'] ); + } else { + dbQuery( "insert into Users set ".implode( ", ", $changes ) ); + } + $refreshParent = true; + } + $view = 'none'; + } elseif ( $action == 'state' ) { + if ( !empty($_REQUEST['runState']) ) { + //if ( $cookies ) session_write_close(); + packageControl( $_REQUEST['runState'] ); + $refreshParent = true; + } + } elseif ( $action == 'save' ) { + if ( !empty($_REQUEST['runState']) || !empty($_REQUEST['newState']) ) { + $sql = 'SELECT Id,Function,Enabled FROM Monitors ORDER BY Id'; + $definitions = array(); + foreach( dbFetchAll( $sql ) as $monitor ) + { + $definitions[] = $monitor['Id'].":".$monitor['Function'].":".$monitor['Enabled']; + } + $definition = join( ',', $definitions ); + if ( $_REQUEST['newState'] ) + $_REQUEST['runState'] = $_REQUEST['newState']; + dbQuery( "replace into States set Name=?, Definition=?", array( $_REQUEST['runState'],$definition) ); + } + } elseif ( $action == 'delete' ) { + if ( isset($_REQUEST['runState']) ) + dbQuery( "delete from States where Name=?", array($_REQUEST['runState']) ); + + if ( isset($_REQUEST['markUids']) ) { + foreach( $_REQUEST['markUids'] as $markUid ) + dbQuery( "delete from Users where Id = ?", array($markUid) ); + if ( $markUid == $user['Id'] ) + userLogout(); + } + } +} else { + if ( ZM_USER_SELF_EDIT && $action == 'user' ) { + $uid = $user['Id']; + + $dbUser = dbFetchOne( 'SELECT Id, Password, Language FROM Users WHERE Id = ?', NULL, array($uid) ); + + $types = array(); + $changes = getFormChanges( $dbUser, $_REQUEST['newUser'], $types ); + + if ( !empty($_REQUEST['newUser']['Password']) ) + $changes['Password'] = "Password = password(".dbEscape($_REQUEST['newUser']['Password']).")"; + else + unset( $changes['Password'] ); + if ( count( $changes ) ) { + dbQuery( "update Users set ".implode( ", ", $changes )." where Id=?", array($uid) ); + $refreshParent = true; + } + $view = 'none'; + } +} + +if ( $action == 'reset' ) { + $_SESSION['zmEventResetTime'] = strftime( STRF_FMT_DATETIME_DB ); + setcookie( 'zmEventResetTime', $_SESSION['zmEventResetTime'], time()+3600*24*30*12*10 ); + //if ( $cookies ) session_write_close(); +} + ?> diff --git a/web/includes/database.php b/web/includes/database.php index d19bc86ac..34387e040 100644 --- a/web/includes/database.php +++ b/web/includes/database.php @@ -122,17 +122,26 @@ function dbQuery( $sql, $params=NULL ) { $result = NULL; try { if ( isset($params) ) { - $result = $dbConn->prepare( $sql ); - $result->execute( $params ); + if ( ! $result = $dbConn->prepare( $sql ) ) { + Error("SQL: Error preparing $sql: " . $pdo->errorInfo); + return NULL; + } + + if ( ! $result->execute( $params ) ) { + Error("SQL: Error executing $sql: " . implode(',', $result->errorInfo() ) ); + return NULL; + } } else { $result = $dbConn->query( $sql ); } - //if ( $params ) - //Warning("SQL: $sql" . implode(',',$params)); - //else - //Warning("SQL: $sql" ); +if ( 0 ) { + if ( $params ) + Warning("SQL: $sql" . implode(',',$params) . ' rows: '.$result->rowCount() ); + else + Warning("SQL: $sql: rows:" . $result->rowCount() ); +} } catch(PDOException $e) { - Error( "SQL-ERR '".$e->getMessage()."', statement was '".$sql."' params:" . implode(',',$params) ); + Error( "SQL-ERR '".$e->getMessage()."', statement was '".$sql."' params:" . $params?implode(',',$params):'' ); } return( $result ); } diff --git a/web/includes/functions.php b/web/includes/functions.php index ea9d8de02..ec6a7b5dc 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -1057,10 +1057,18 @@ function parseSort( $saveToSession=false, $querySep='&' ) { $sortColumn = 'E.Cause'; break; case 'DateTime' : + $sortColumn = 'E.StartTime'; $_REQUEST['sort_field'] = 'StartTime'; + break; + case 'DiskSpace' : + $sortColumn = 'E.DiskSpace'; + break; case 'StartTime' : $sortColumn = 'E.StartTime'; break; + case 'EndTime' : + $sortColumn = 'E.EndTime'; + break; case 'Length' : $sortColumn = 'E.Length'; break; @@ -1126,6 +1134,7 @@ function parseFilter( &$filter, $saveToSession=false, $querySep='&' ) { case 'ServerId': $filter['sql'] .= 'M.ServerId'; break; +# Unspecified start or end, so assume start, this is to support legacy filters case 'DateTime': $filter['sql'] .= 'E.StartTime'; break; @@ -1138,8 +1147,35 @@ function parseFilter( &$filter, $saveToSession=false, $querySep='&' ) { case 'Weekday': $filter['sql'] .= 'weekday( E.StartTime )'; break; +# Starting Time + case 'StartDateTime': + $filter['sql'] .= 'E.StartTime'; + break; + case 'StartDate': + $filter['sql'] .= 'to_days( E.StartTime )'; + break; + case 'StartTime': + $filter['sql'] .= 'extract( hour_second from E.StartTime )'; + break; + case 'StartWeekday': + $filter['sql'] .= 'weekday( E.StartTime )'; + break; +# Ending Time + case 'EndDateTime': + $filter['sql'] .= 'E.EndTime'; + break; + case 'EndDate': + $filter['sql'] .= 'to_days( E.EndTime )'; + break; + case 'EndTime': + $filter['sql'] .= 'extract( hour_second from E.EndTime )'; + break; + case 'EndWeekday': + $filter['sql'] .= 'weekday( E.EndTime )'; + break; case 'Id': case 'Name': + case 'DiskSpace': case 'MonitorId': case 'StorageId': case 'Length': @@ -1239,6 +1275,12 @@ function parseFilter( &$filter, $saveToSession=false, $querySep='&' ) { case '![]' : $filter['sql'] .= ' not in ('.join( ',', $valueList ).')'; break; + case 'IS' : + $filter['sql'] .= " IS $value"; + break; + case 'IS NOT' : + $filter['sql'] .= " IS NOT $value"; + break; } $filter['query'] .= $querySep.urlencode("filter[Query][terms][$i][op]").'='.urlencode($terms[$i]['op']); @@ -1853,7 +1895,8 @@ function logState() { Logger::WARNING => array( ZM_LOG_ALERT_WAR_COUNT, ZM_LOG_ALARM_WAR_COUNT ), ); - $sql = "select Level, count(Level) as LevelCount from Logs where Level < ".Logger::INFO." and TimeKey > unix_timestamp(now() - interval ".ZM_LOG_CHECK_PERIOD." second) group by Level order by Level asc"; + # This is an expensive request, as it has to hit every row of the Logs Table + $sql = 'SELECT Level, COUNT(Level) AS LevelCount FROM Logs WHERE Level < '.Logger::INFO.' AND TimeKey > unix_timestamp(now() - interval '.ZM_LOG_CHECK_PERIOD.' second) GROUP BY Level ORDER BY Level ASC'; $counts = dbFetchAll( $sql ); foreach ( $counts as $count ) { @@ -2020,6 +2063,19 @@ function detaintPath( $path ) { return( $path ); } +function cache_bust( $file ) { + # Use the last modified timestamp to create a link that gets a different filename + # To defeat caching. Should probably use md5 hash + $parts = pathinfo($file); + $cacheFile = 'cache/'.$parts['filename'].'-'.filemtime($file).'.'.$parts['extension']; + if ( file_exists( ZM_PATH_WEB.'/'.$cacheFile ) or symlink( ZM_PATH_WEB.'/'.$file, ZM_PATH_WEB.'/'.$cacheFile ) ) { + return $cacheFile; + } else { + Warning("Failed linking $file to $cacheFile"); + } + return $file; +} + function getSkinFile( $file ) { global $skinBase; $skinFile = false; @@ -2028,7 +2084,7 @@ function getSkinFile( $file ) { if ( file_exists( $tempSkinFile ) ) $skinFile = $tempSkinFile; } - return( $skinFile ); + return $skinFile; } function getSkinIncludes( $file, $includeBase=false, $asOverride=false ) { diff --git a/web/includes/logger.php b/web/includes/logger.php index b10749f10..f9bc214f6 100644 --- a/web/includes/logger.php +++ b/web/includes/logger.php @@ -2,586 +2,505 @@ require_once( 'config.php' ); -class Logger -{ - private static $instance; +class Logger { + private static $instance; - const DEBUG = 1; - const INFO = 0; - const WARNING = -1; - const ERROR = -2; - const FATAL = -3; - const PANIC = -4; - const NOLOG = -5; // Special artificial level to prevent logging + const DEBUG = 1; + const INFO = 0; + const WARNING = -1; + const ERROR = -2; + const FATAL = -3; + const PANIC = -4; + const NOLOG = -5; // Special artificial level to prevent logging - private $initialised = false; + private $initialised = false; - private $id = "web"; - private $idRoot = "web"; - private $idArgs = ""; - private $useErrorLog = true; + private $id = "web"; + private $idRoot = "web"; + private $idArgs = ""; + private $useErrorLog = true; - private $level = self::INFO; - private $termLevel = self::NOLOG; - private $databaseLevel = self::NOLOG; - private $fileLevel = self::NOLOG; - private $weblogLevel = self::NOLOG; - private $syslogLevel = self::NOLOG; - private $effectiveLevel = self::NOLOG; + private $level = self::INFO; + private $termLevel = self::NOLOG; + private $databaseLevel = self::NOLOG; + private $fileLevel = self::NOLOG; + private $weblogLevel = self::NOLOG; + private $syslogLevel = self::NOLOG; + private $effectiveLevel = self::NOLOG; - private $hasTerm = false; + private $hasTerm = false; - private $logPath = ZM_PATH_LOGS; - private $logFile = ""; - private $logFd = NULL; + private $logPath = ZM_PATH_LOGS; + private $logFile = ""; + private $logFd = NULL; - public static $codes = array( - self::DEBUG => "DBG", - self::INFO => "INF", - self::WARNING => "WAR", - self::ERROR => "ERR", - self::FATAL => "FAT", - self::PANIC => "PNC", - self::NOLOG => "OFF", - ); - private static $syslogPriorities = array( - self::DEBUG => LOG_DEBUG, - self::INFO => LOG_INFO, - self::WARNING => LOG_WARNING, - self::ERROR => LOG_ERR, - self::FATAL => LOG_ERR, - self::PANIC => LOG_ERR, - ); - private static $phpErrorLevels = array( - self::DEBUG => E_USER_NOTICE, - self::INFO => E_USER_NOTICE, - self::WARNING => E_USER_WARNING, - self::ERROR => E_USER_WARNING, - self::FATAL => E_USER_ERROR, - self::PANIC => E_USER_ERROR, - ); + public static $codes = array( + self::DEBUG => "DBG", + self::INFO => "INF", + self::WARNING => "WAR", + self::ERROR => "ERR", + self::FATAL => "FAT", + self::PANIC => "PNC", + self::NOLOG => "OFF", + ); + private static $syslogPriorities = array( + self::DEBUG => LOG_DEBUG, + self::INFO => LOG_INFO, + self::WARNING => LOG_WARNING, + self::ERROR => LOG_ERR, + self::FATAL => LOG_ERR, + self::PANIC => LOG_ERR, + ); + private static $phpErrorLevels = array( + self::DEBUG => E_USER_NOTICE, + self::INFO => E_USER_NOTICE, + self::WARNING => E_USER_WARNING, + self::ERROR => E_USER_WARNING, + self::FATAL => E_USER_ERROR, + self::PANIC => E_USER_ERROR, + ); - private function __construct() - { - $this->hasTerm = (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR'])); - $this->logFile = $this->logPath."/".$this->id.".log"; + private function __construct() { + $this->hasTerm = (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR'])); + $this->logFile = $this->logPath."/".$this->id.".log"; + } + + public function __destruct() { + $this->terminate(); + } + + public function initialise( $options=array() ) { + if ( !empty($options['id']) ) + $this->id = $options['id']; + + //if ( isset($options['useErrorLog']) ) + //$this->useErrorLog = $options['useErrorLog']; + if ( isset($options['logPath']) ) { + $this->logPath = $options['logPath']; + $tempLogFile = $this->logPath."/".$this->id.".log"; } + if ( isset($options['logFile']) ) + $tempLogFile = $options['logFile']; + else + $tempLogFile = $this->logPath."/".$this->id.".log"; + if ( !is_null($logFile = $this->getTargettedEnv('LOG_FILE')) ) + $tempLogFile = $logFile; - public function __destruct() - { - $this->terminate(); - } + $tempLevel = self::INFO; + $tempTermLevel = $this->termLevel; + $tempDatabaseLevel = $this->databaseLevel; + $tempFileLevel = $this->fileLevel; + $tempSyslogLevel = $this->syslogLevel; + $tempWeblogLevel = $this->weblogLevel; - public function initialise( $options=array() ) - { - if ( !empty($options['id']) ) - $this->id = $options['id']; + if ( isset($options['termLevel']) ) + $tempTermLevel = $options['termLevel']; + if ( isset($options['databaseLevel']) ) + $tempDatabaseLevel = $options['databaseLevel']; + else + $tempDatabaseLevel = ZM_LOG_LEVEL_DATABASE; + if ( isset($options['fileLevel']) ) + $tempFileLevel = $options['fileLevel']; + else + $tempFileLevel = ZM_LOG_LEVEL_FILE; + if ( isset($options['weblogLevel']) ) + $tempWeblogLevel = $options['weblogLevel']; + else + $tempWeblogLevel = ZM_LOG_LEVEL_WEBLOG; + if ( isset($options['syslogLevel']) ) + $tempSyslogLevel = $options['syslogLevel']; + else + $tempSyslogLevel = ZM_LOG_LEVEL_SYSLOG; - //if ( isset($options['useErrorLog']) ) - //$this->useErrorLog = $options['useErrorLog']; - if ( isset($options['logPath']) ) - { - $this->logPath = $options['logPath']; - $tempLogFile = $this->logPath."/".$this->id.".log"; + if ( $value = getenv('LOG_PRINT') ) + $tempTermLevel = $value ? self::DEBUG : self::NOLOG; + + if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL')) ) + $tempLevel = $level; + + if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_TERM')) ) + $tempTermLevel = $level; + if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_DATABASE')) ) + $tempDatabaseLevel = $level; + if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_FILE')) ) + $tempFileLevel = $level; + if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_SYSLOG')) ) + $tempSyslogLevel = $level; + if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_WEBLOG')) ) + $tempWeblogLevel = $level; + + if ( ZM_LOG_DEBUG ) { + foreach ( explode( '|', ZM_LOG_DEBUG_TARGET ) as $target ) { + if ( $target == $this->id || $target == '_'.$this->id || $target == $this->idRoot || $target == '_'.$this->idRoot || $target == '' ) { + if ( ZM_LOG_DEBUG_LEVEL > self::NOLOG ) { + $tempLevel = $this->limit( ZM_LOG_DEBUG_LEVEL ); + if ( ZM_LOG_DEBUG_FILE != '' ) { + $tempLogFile = ZM_LOG_DEBUG_FILE; + $tempFileLevel = $tempLevel; + } + } } - if ( isset($options['logFile']) ) - $tempLogFile = $options['logFile']; + } // end foreach target + } // end if DEBUG + + $this->logFile( $tempLogFile ); + $this->termLevel( $tempTermLevel ); + $this->databaseLevel( $tempDatabaseLevel ); + $this->fileLevel( $tempFileLevel ); + $this->syslogLevel( $tempSyslogLevel ); + $this->weblogLevel( $tempWeblogLevel ); + + $this->level( $tempLevel ); + + $this->initialised = true; + + Logger::Debug( "LogOpts: level=".self::$codes[$this->level]."/".self::$codes[$this->effectiveLevel].", screen=".self::$codes[$this->termLevel].", database=".self::$codes[$this->databaseLevel].", logfile=".self::$codes[$this->fileLevel]."->".$this->logFile.", weblog=".self::$codes[$this->weblogLevel].", syslog=".self::$codes[$this->syslogLevel] ); + } + + private function terminate() { + if ( $this->initialised ) { + if ( $this->fileLevel > self::NOLOG ) + $this->closeFile(); + if ( $this->syslogLevel > self::NOLOG ) + $this->closeSyslog(); + } + $this->initialised = false; + } + + private function limit( $level ) { + if ( $level > self::DEBUG ) + return( self::DEBUG ); + if ( $level < self::NOLOG ) + return( self::NOLOG ); + return( $level ); + } + + private function getTargettedEnv( $name ) { + $envName = $name."_".$this->id; + $value = getenv( $envName ); + if ( $value === false && $this->id != $this->idRoot ) + $value = getenv( $name."_".$this->idRoot ); + if ( $value === false ) + $value = getenv( $name ); + return( $value !== false ? $value : NULL ); + } + + public static function fetch( $initialise=true ) { + if ( !isset(self::$instance) ) { + $class = __CLASS__; + self::$instance = new $class; + if ( $initialise ) + self::$instance->initialise( array( 'id'=>'web_php', 'syslogLevel'=>self::INFO, 'weblogLevel'=>self::INFO ) ); + } + return self::$instance; + } + + public static function Debug( $string ) { + Logger::fetch()->logPrint( Logger::DEBUG, $string ); + } + + public function id( $id=NULL ) { + if ( isset($id) && $this->id != $id ) { + // Remove whitespace + $id = preg_replace( '/\S/', '', $id ); + // Replace non-alphanum with underscore + $id = preg_replace( '/[^a-zA-Z_]/', '_', $id ); + + if ( $this->id != $id ) { + $this->id = $this->idRoot = $id; + if ( preg_match( '/^([^_]+)_(.+)$/', $id, $matches ) ) { + $this->idRoot = $matches[1]; + $this->idArgs = $matches[2]; + } + } + } + return( $this->id ); + } + + public function level( $level ) { + if ( isset($level) ) { + $lastLevel = $this->level; + $this->level = $this->limit($level); + $this->effectiveLevel = self::NOLOG; + if ( $this->termLevel > $this->effectiveLevel ) + $this->effectiveLevel = $this->termLevel; + if ( $this->databaseLevel > $this->effectiveLevel ) + $this->effectiveLevel = $this->databaseLevel; + if ( $this->fileLevel > $this->effectiveLevel ) + $this->effectiveLevel = $this->fileLevel; + if ( $this->weblogLevel > $this->effectiveLevel ) + $this->effectiveLevel = $this->weblogLevel; + if ( $this->syslogLevel > $this->effectiveLevel ) + $this->effectiveLevel = $this->syslogLevel; + if ( $this->effectiveLevel > $this->level ) + $this->effectiveLevel = $this->level; + if ( !$this->hasTerm ) { + if ( $lastLevel < self::DEBUG && $this->level >= self::DEBUG ) { + $this->savedErrorReporting = error_reporting( E_ALL ); + $this->savedDisplayErrors = ini_set( 'display_errors', true ); + } elseif ( $lastLevel >= self::DEBUG && $this->level < self::DEBUG ) { + error_reporting( $this->savedErrorReporting ); + ini_set( 'display_errors', $this->savedDisplayErrors ); + } + } + } + return( $this->level ); + } + + public function debugOn() { + return( $this->effectiveLevel >= self::DEBUG ); + } + + public function termLevel( $termLevel ) { + if ( isset($termLevel) ) { + $termLevel = $this->limit($termLevel); + if ( $this->termLevel != $termLevel ) + $this->termLevel = $termLevel; + } + return( $this->termLevel ); + } + + public function databaseLevel( $databaseLevel=NULL ) { + if ( !is_null($databaseLevel) ) { + $databaseLevel = $this->limit($databaseLevel); + if ( $this->databaseLevel != $databaseLevel ) { + $this->databaseLevel = $databaseLevel; + if ( $this->databaseLevel > self::NOLOG ) { + if ( (include_once 'database.php') === FALSE ) { + $this->databaseLevel = self::NOLOG; + Warning( "Unable to write log entries to DB, database.php not found" ); + } + } + } + } + return( $this->databaseLevel ); + } + + public function fileLevel( $fileLevel ) { + if ( isset($fileLevel) ) { + $fileLevel = $this->limit($fileLevel); + if ( $this->fileLevel != $fileLevel ) { + if ( $this->fileLevel > self::NOLOG ) + $this->closeFile(); + $this->fileLevel = $fileLevel; + if ( $this->fileLevel > self::NOLOG ) + $this->openFile(); + } + } + return( $this->fileLevel ); + } + + public function weblogLevel( $weblogLevel ) { + if ( isset($weblogLevel) ) { + $weblogLevel = $this->limit($weblogLevel); + if ( $this->weblogLevel != $weblogLevel ) { + if ( $weblogLevel > self::NOLOG && $this->weblogLevel <= self::NOLOG ) { + $this->savedLogErrors = ini_set( 'log_errors', true ); + } elseif ( $weblogLevel <= self::NOLOG && $this->weblogLevel > self::NOLOG ) { + ini_set( 'log_errors', $this->savedLogErrors ); + } + $this->weblogLevel = $weblogLevel; + } + } + return( $this->weblogLevel ); + } + + public function syslogLevel( $syslogLevel ) { + if ( isset($syslogLevel) ) { + $syslogLevel = $this->limit($syslogLevel); + if ( $this->syslogLevel != $syslogLevel ) { + if ( $this->syslogLevel > self::NOLOG ) + $this->closeSyslog(); + $this->syslogLevel = $syslogLevel; + if ( $this->syslogLevel > self::NOLOG ) + $this->openSyslog(); + } + } + return( $this->syslogLevel ); + } + + private function openSyslog() { + openlog( $this->id, LOG_PID|LOG_NDELAY, LOG_LOCAL1 ); + } + + private function closeSyslog() { + closelog(); + } + + private function logFile( $logFile ) { + if ( preg_match( '/^(.+)\+$/', $logFile, $matches ) ) + $this->logFile = $matches[1].'.'.getmypid(); + else + $this->logFile = $logFile; + } + + private function openFile() { + if ( !$this->useErrorLog ) { + if ( $this->logFd = fopen( $this->logFile, 'a+' ) ) { + if ( strnatcmp( phpversion(), '5.2.0' ) >= 0 ) { + $error = error_get_last(); + trigger_error( "Can't open log file '$logFile': ".$error['message'].' @ '.$error['file'].'/'.$error['line'], E_USER_ERROR ); + } + $this->fileLevel = self::NOLOG; + } + } + } + + private function closeFile() { + if ( $this->logFd ) + fclose( $this->logFd ); + } + + public function logPrint( $level, $string, $file=NULL, $line=NULL ) { + if ( $level <= $this->effectiveLevel ) { + $string = preg_replace( '/[\r\n]+$/', '', $string ); + $code = self::$codes[$level]; + + $time = gettimeofday(); + $message = sprintf( '%s.%06d %s[%d].%s [%s]', strftime( '%x %H:%M:%S', $time['sec'] ), $time['usec'], $this->id, getmypid(), $code, $string ); + + if ( is_null($file) ) { + if ( $this->useErrorLog || $this->databaseLevel > self::NOLOG ) { + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; + if ( $this->hasTerm ) + $rootPath = getcwd(); + else + $rootPath = $_SERVER['DOCUMENT_ROOT']; + $file = preg_replace( '/^'.addcslashes($rootPath,'/').'\/?/', '', $file ); + } + } + + if ( $this->useErrorLog ) + $message .= " at ".$file." line ".$line; + else + $message = $message; + + if ( $level <= $this->termLevel ) + if ( $this->hasTerm ) + print( $message."\n" ); else - $tempLogFile = $this->logPath."/".$this->id.".log"; - if ( !is_null($logFile = $this->getTargettedEnv('LOG_FILE')) ) - $tempLogFile = $logFile; + print( preg_replace( "/\n/", '
', htmlspecialchars($message) ).'
' ); - $tempLevel = self::INFO; - $tempTermLevel = $this->termLevel; - $tempDatabaseLevel = $this->databaseLevel; - $tempFileLevel = $this->fileLevel; - $tempSyslogLevel = $this->syslogLevel; - $tempWeblogLevel = $this->weblogLevel; + if ( $level <= $this->fileLevel ) + if ( $this->useErrorLog ) { + if ( !error_log( $message."\n", 3, $this->logFile ) ) { + if ( strnatcmp( phpversion(), '5.2.0' ) >= 0 ) { + $error = error_get_last(); + trigger_error( "Can't write to log file '".$this->logFile."': ".$error['message'].' @ '.$error['file'].'/'.$error['line'], E_USER_ERROR ); + } + } + } elseif ( $this->logFd ) { + fprintf( $this->logFd, $message."\n" ); + } - if ( isset($options['termLevel']) ) - $tempTermLevel = $options['termLevel']; - if ( isset($options['databaseLevel']) ) - $tempDatabaseLevel = $options['databaseLevel']; + $message = $code.' ['.$string.']'; + if ( $level <= $this->syslogLevel ) + syslog( self::$syslogPriorities[$level], $message ); + if ( $level <= $this->databaseLevel ) { + try { + global $dbConn; + $sql = "INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, ? )"; + $stmt = $dbConn->prepare( $sql ); + $result = $stmt->execute( array( sprintf( "%d.%06d", $time['sec'], $time['usec'] ), $this->id, getmypid(), $level, $code, $string, $file, $line ) ); + } catch(PDOException $ex) { + $this->databaseLevel = self::NOLOG; + Fatal( "Can't write log entry '$sql': ". $ex->getMessage() ); + } + } + // This has to be last as trigger_error can be fatal + if ( $level <= $this->weblogLevel ) { + if ( $this->useErrorLog ) + error_log( $message, 0 ); else - $tempDatabaseLevel = ZM_LOG_LEVEL_DATABASE; - if ( isset($options['fileLevel']) ) - $tempFileLevel = $options['fileLevel']; - else - $tempFileLevel = ZM_LOG_LEVEL_FILE; - if ( isset($options['weblogLevel']) ) - $tempWeblogLevel = $options['weblogLevel']; - else - $tempWeblogLevel = ZM_LOG_LEVEL_WEBLOG; - if ( isset($options['syslogLevel']) ) - $tempSyslogLevel = $options['syslogLevel']; - else - $tempSyslogLevel = ZM_LOG_LEVEL_SYSLOG; - - if ( $value = getenv('LOG_PRINT') ) - $tempTermLevel = $value ? self::DEBUG : self::NOLOG; - - if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL')) ) - $tempLevel = $level; - - if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_TERM')) ) - $tempTermLevel = $level; - if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_DATABASE')) ) - $tempDatabaseLevel = $level; - if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_FILE')) ) - $tempFileLevel = $level; - if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_SYSLOG')) ) - $tempSyslogLevel = $level; - if ( !is_null($level = $this->getTargettedEnv('LOG_LEVEL_WEBLOG')) ) - $tempWeblogLevel = $level; - - if ( ZM_LOG_DEBUG ) - { - foreach ( explode( '|', ZM_LOG_DEBUG_TARGET ) as $target ) - { - if ( $target == $this->id || $target == "_".$this->id || $target == $this->idRoot || $target == "_".$this->idRoot || $target == "" ) - { - if ( ZM_LOG_DEBUG_LEVEL > self::NOLOG ) - { - $tempLevel = $this->limit( ZM_LOG_DEBUG_LEVEL ); - if ( ZM_LOG_DEBUG_FILE != "" ) - { - $tempLogFile = ZM_LOG_DEBUG_FILE; - $tempFileLevel = $tempLevel; - } - } - } - } - } - - $this->logFile( $tempLogFile ); - - $this->termLevel( $tempTermLevel ); - $this->databaseLevel( $tempDatabaseLevel ); - $this->fileLevel( $tempFileLevel ); - $this->syslogLevel( $tempSyslogLevel ); - $this->weblogLevel( $tempWeblogLevel ); - - $this->level( $tempLevel ); - - $this->initialised = true; - - Logger::Debug( "LogOpts: level=".self::$codes[$this->level]."/".self::$codes[$this->effectiveLevel].", screen=".self::$codes[$this->termLevel].", database=".self::$codes[$this->databaseLevel].", logfile=".self::$codes[$this->fileLevel]."->".$this->logFile.", weblog=".self::$codes[$this->weblogLevel].", syslog=".self::$codes[$this->syslogLevel] ); - } - - private function terminate() - { - if ( $this->initialised ) - { - if ( $this->fileLevel > self::NOLOG ) - $this->closeFile(); - if ( $this->syslogLevel > self::NOLOG ) - $this->closeSyslog(); - } - $this->initialised = false; - } - - private function limit( $level ) - { - if ( $level > self::DEBUG ) - return( self::DEBUG ); - if ( $level < self::NOLOG ) - return( self::NOLOG ); - return( $level ); - } - - private function getTargettedEnv( $name ) - { - $envName = $name."_".$this->id; - $value = getenv( $envName ); - if ( $value === false && $this->id != $this->idRoot ) - $value = getenv( $name."_".$this->idRoot ); - if ( $value === false ) - $value = getenv( $name ); - return( $value !== false ? $value : NULL ); - } - - public static function fetch( $initialise=true ) - { - if ( !isset(self::$instance) ) - { - $class = __CLASS__; - self::$instance = new $class; - if ( $initialise ) - self::$instance->initialise( array( 'id'=>'web_php', 'syslogLevel'=>self::INFO, 'weblogLevel'=>self::INFO ) ); - } - return self::$instance; - } - - public static function Debug( $string ) - { - Logger::fetch()->logPrint( Logger::DEBUG, $string ); - } - - public function id( $id=NULL ) - { - if ( isset($id) && $this->id != $id ) - { - // Remove whitespace - $id = preg_replace( '/\S/', '', $id ); - // Replace non-alphanum with underscore - $id = preg_replace( '/[^a-zA-Z_]/', '_', $id ); - - if ( $this->id != $id ) - { - $this->id = $this->idRoot = $id; - if ( preg_match( '/^([^_]+)_(.+)$/', $id, $matches ) ) - { - $this->idRoot = $matches[1]; - $this->idArgs = $matches[2]; - } - } - } - return( $this->id ); - } - - public function level( $level ) - { - if ( isset($level) ) - { - $lastLevel = $this->level; - $this->level = $this->limit($level); - $this->effectiveLevel = self::NOLOG; - if ( $this->termLevel > $this->effectiveLevel ) - $this->effectiveLevel = $this->termLevel; - if ( $this->databaseLevel > $this->effectiveLevel ) - $this->effectiveLevel = $this->databaseLevel; - if ( $this->fileLevel > $this->effectiveLevel ) - $this->effectiveLevel = $this->fileLevel; - if ( $this->weblogLevel > $this->effectiveLevel ) - $this->effectiveLevel = $this->weblogLevel; - if ( $this->syslogLevel > $this->effectiveLevel ) - $this->effectiveLevel = $this->syslogLevel; - if ( $this->effectiveLevel > $this->level ) - $this->effectiveLevel = $this->level; - if ( !$this->hasTerm ) - { - if ( $lastLevel < self::DEBUG && $this->level >= self::DEBUG ) - { - $this->savedErrorReporting = error_reporting( E_ALL ); - $this->savedDisplayErrors = ini_set( 'display_errors', true ); - } - elseif ( $lastLevel >= self::DEBUG && $this->level < self::DEBUG ) - { - error_reporting( $this->savedErrorReporting ); - ini_set( 'display_errors', $this->savedDisplayErrors ); - } - } - } - return( $this->level ); - } - - public function debugOn() - { - return( $this->effectiveLevel >= self::DEBUG ); - } - - public function termLevel( $termLevel ) - { - if ( isset($termLevel) ) - { - $termLevel = $this->limit($termLevel); - if ( $this->termLevel != $termLevel ) - $this->termLevel = $termLevel; - } - return( $this->termLevel ); - } - - public function databaseLevel( $databaseLevel=NULL ) - { - if ( !is_null($databaseLevel) ) - { - $databaseLevel = $this->limit($databaseLevel); - if ( $this->databaseLevel != $databaseLevel ) - { - $this->databaseLevel = $databaseLevel; - if ( $this->databaseLevel > self::NOLOG ) - { - if ( (include_once 'database.php') === FALSE ) - { - $this->databaseLevel = self::NOLOG; - Warning( "Unable to write log entries to DB, database.php not found" ); - } - } - } - } - return( $this->databaseLevel ); - } - - public function fileLevel( $fileLevel ) - { - if ( isset($fileLevel) ) - { - $fileLevel = $this->limit($fileLevel); - if ( $this->fileLevel != $fileLevel ) - { - if ( $this->fileLevel > self::NOLOG ) - $this->closeFile(); - $this->fileLevel = $fileLevel; - if ( $this->fileLevel > self::NOLOG ) - $this->openFile(); - } - } - return( $this->fileLevel ); - } - - public function weblogLevel( $weblogLevel ) - { - if ( isset($weblogLevel) ) - { - $weblogLevel = $this->limit($weblogLevel); - if ( $this->weblogLevel != $weblogLevel ) - { - if ( $weblogLevel > self::NOLOG && $this->weblogLevel <= self::NOLOG ) - { - $this->savedLogErrors = ini_set( 'log_errors', true ); - } - elseif ( $weblogLevel <= self::NOLOG && $this->weblogLevel > self::NOLOG ) - { - ini_set( 'log_errors', $this->savedLogErrors ); - } - $this->weblogLevel = $weblogLevel; - } - } - return( $this->weblogLevel ); - } - - public function syslogLevel( $syslogLevel ) - { - if ( isset($syslogLevel) ) - { - $syslogLevel = $this->limit($syslogLevel); - if ( $this->syslogLevel != $syslogLevel ) - { - if ( $this->syslogLevel > self::NOLOG ) - $this->closeSyslog(); - $this->syslogLevel = $syslogLevel; - if ( $this->syslogLevel > self::NOLOG ) - $this->openSyslog(); - } - } - return( $this->syslogLevel ); - } - - private function openSyslog() - { - openlog( $this->id, LOG_PID|LOG_NDELAY, LOG_LOCAL1 ); - } - - private function closeSyslog() - { - closelog(); - } - - private function logFile( $logFile ) - { - if ( preg_match( '/^(.+)\+$/', $logFile, $matches ) ) - $this->logFile = $matches[1].'.'.getmypid(); - else - $this->logFile = $logFile; - } - - private function openFile() - { - if ( !$this->useErrorLog ) - { - if ( $this->logFd = fopen( $this->logFile, "a+" ) ) - { - if ( strnatcmp( phpversion(), '5.2.0' ) >= 0 ) - { - $error = error_get_last(); - trigger_error( "Can't open log file '$logFile': ".$error['message']." @ ".$error['file']."/".$error['line'], E_USER_ERROR ); - } - $this->fileLevel = self::NOLOG; - } - } - } - - private function closeFile() - { - if ( $this->logFd ) - fclose( $this->logFd ); - } - - public function logPrint( $level, $string, $file=NULL, $line=NULL ) - { - if ( $level <= $this->effectiveLevel ) - { - $string = preg_replace( '/[\r\n]+$/', '', $string ); - $code = self::$codes[$level]; - - $time = gettimeofday(); - $message = sprintf( "%s.%06d %s[%d].%s [%s]", strftime( "%x %H:%M:%S", $time['sec'] ), $time['usec'], $this->id, getmypid(), $code, $string ); - - if ( is_null( $file) ) - { - if ( $this->useErrorLog || $this->databaseLevel > self::NOLOG ) - { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - if ( $this->hasTerm ) - $rootPath = getcwd(); - else - $rootPath = $_SERVER['DOCUMENT_ROOT']; - $file = preg_replace( '/^'.addcslashes($rootPath,'/').'\/?/', '', $file ); - } - } - - if ( $this->useErrorLog ) - $message .= " at ".$file." line ".$line; - else - $message = $message; - - if ( $level <= $this->termLevel ) - if ( $this->hasTerm ) - print( $message."\n" ); - else - print( preg_replace( "/\n/", '
', htmlspecialchars($message) )."
" ); - - if ( $level <= $this->fileLevel ) - if ( $this->useErrorLog ) - { - if ( !error_log( $message."\n", 3, $this->logFile ) ) - { - if ( strnatcmp( phpversion(), '5.2.0' ) >= 0 ) - { - $error = error_get_last(); - trigger_error( "Can't write to log file '".$this->logFile."': ".$error['message']." @ ".$error['file']."/".$error['line'], E_USER_ERROR ); - } - } - } - elseif ( $this->logFd ) - fprintf( $this->logFd, $message."\n" ); - - $message = $code." [".$string."]"; - if ( $level <= $this->syslogLevel ) - syslog( self::$syslogPriorities[$level], $message ); - if ( $level <= $this->databaseLevel ) - { - try { - global $dbConn; - $sql = "INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, ? )"; - $stmt = $dbConn->prepare( $sql ); - $result = $stmt->execute( array( sprintf( "%d.%06d", $time['sec'], $time['usec'] ), $this->id, getmypid(), $level, $code, $string, $file, $line ) ); - } catch(PDOException $ex) { - $this->databaseLevel = self::NOLOG; - Fatal( "Can't write log entry '$sql': ". $ex->getMessage() ); - } - } - // This has to be last as trigger_error can be fatal - if ( $level <= $this->weblogLevel ) - { - if ( $this->useErrorLog ) - error_log( $message, 0 ); - else - trigger_error( $message, self::$phpErrorLevels[$level] ); - } - } + trigger_error( $message, self::$phpErrorLevels[$level] ); + } } + } }; -function logInit( $options=array() ) -{ - $logger = Logger::fetch(); - $logger->initialise( $options ); - set_error_handler( 'ErrorHandler' ); +function logInit( $options=array() ) { + $logger = Logger::fetch(); + $logger->initialise( $options ); + set_error_handler( 'ErrorHandler' ); } -function logToDatabase( $level=NULL ) -{ - return( Logger::fetch()->databaseLevel( $level ) ); +function logToDatabase( $level=NULL ) { + return( Logger::fetch()->databaseLevel( $level ) ); } -function Mark( $level=Logger::DEBUG, $tag="Mark" ) -{ - Logger::fetch()->logPrint( $level, $tag ); +function Mark( $level=Logger::DEBUG, $tag='Mark' ) { + Logger::fetch()->logPrint( $level, $tag ); } -function Dump( &$var, $label="VAR" ) -{ +function Dump( &$var, $label='VAR' ) { + ob_start(); + print( $label.' => ' ); + print_r( $var ); + Logger::fetch()->logPrint( Logger::DEBUG, ob_get_clean() ); +} + +function Info( $string ) { + Logger::fetch()->logPrint( Logger::INFO, $string ); +} + +function Warning( $string ) { + Logger::fetch()->logPrint( Logger::WARNING, $string ); +} + +function Error( $string ) { + Logger::fetch()->logPrint( Logger::ERROR, $string ); +} + +function Fatal( $string ) { + Logger::fetch()->logPrint( Logger::FATAL, $string ); + die( htmlentities($string) ); +} + +function Panic( $string ) { + if ( true ) { + // Use builtin function ob_start(); - print( $label." => " ); - print_r( $var ); - Logger::fetch()->logPrint( Logger::DEBUG, ob_get_clean() ); -} - -function Info( $string ) -{ - Logger::fetch()->logPrint( Logger::INFO, $string ); -} - -function Warning( $string ) -{ - Logger::fetch()->logPrint( Logger::WARNING, $string ); -} - -function Error( $string ) -{ - Logger::fetch()->logPrint( Logger::ERROR, $string ); -} - -function Fatal( $string ) -{ - Logger::fetch()->logPrint( Logger::FATAL, $string ); - die( htmlentities($string) ); -} - -function Panic( $string ) -{ - if ( true ) - { - // Use builtin function - ob_start(); - debug_print_backtrace(); - $backtrace = "\n".ob_get_clean(); + debug_print_backtrace(); + $backtrace = "\n".ob_get_clean(); + } else { + // Roll your own + $backtrace = ''; + $frames = debug_backtrace(); + for ( $i = 0; $i < count($frames); $i++ ) { + $frame = $frames[$i]; + $backtrace .= sprintf( "\n#%d %s() at %s/%d", $i, $frame['function'], $frame['file'], $frame['line'] ); } - else - { - // Roll your own - $backtrace = ''; - $frames = debug_backtrace(); - for ( $i = 0; $i < count($frames); $i++ ) - { - $frame = $frames[$i]; - $backtrace .= sprintf( "\n#%d %s() at %s/%d", $i, $frame['function'], $frame['file'], $frame['line'] ); - } - } - Logger::fetch()->logPrint( Logger::PANIC, $string.$backtrace ); - die( $string ); + } + Logger::fetch()->logPrint( Logger::PANIC, $string.$backtrace ); + die( $string ); } -function ErrorHandler( $error, $string, $file, $line ) -{ - if ( ! (error_reporting() & $error) ) - { - // This error code is not included in error_reporting - return( false ); - } +function ErrorHandler( $error, $string, $file, $line ) { + if ( ! (error_reporting() & $error) ) { + // This error code is not included in error_reporting + return( false ); + } - switch ( $error ) - { - case E_USER_ERROR: - Logger::fetch()->logPrint( Logger::FATAL, $string, $file, $line ); - break; + switch ( $error ) { + case E_USER_ERROR: + Logger::fetch()->logPrint( Logger::FATAL, $string, $file, $line ); + break; - case E_USER_WARNING: - Logger::fetch()->logPrint( Logger::ERROR, $string, $file, $line ); - break; + case E_USER_WARNING: + Logger::fetch()->logPrint( Logger::ERROR, $string, $file, $line ); + break; - case E_USER_NOTICE: - Logger::fetch()->logPrint( Logger::WARNING, $string, $file, $line ); - break; + case E_USER_NOTICE: + Logger::fetch()->logPrint( Logger::WARNING, $string, $file, $line ); + break; - default: - Panic( "Unknown error type: [$error] $string" ); - break; - } - return( true ); + default: + Panic( "Unknown error type: [$error] $string" ); + break; + } + return( true ); } ?> diff --git a/web/index.php b/web/index.php index d6f53af1b..1829f289b 100644 --- a/web/index.php +++ b/web/index.php @@ -34,7 +34,7 @@ if ( version_compare( phpversion(), '4.1.0', '<') ) { } // Useful debugging lines for mobile devices -if ( false ) { +if ( true ) { ob_start(); phpinfo( INFO_VARIABLES ); $fp = fopen( '/tmp/env.html', 'w' ); @@ -113,7 +113,7 @@ if ( !file_exists( ZM_SKIN_PATH ) ) $skinBase[] = $skin; $currentCookieParams = session_get_cookie_params(); -Logger::Debug('Setting cookie parameters to lifetime('.$currentCookieParams['lifetime'].') path('.$currentCookieParams['path'].') domain ('.$currentCookieParams['domain'].') secure('.$currentCookieParams['secure'].') httpOnly(1)'); +//Logger::Debug('Setting cookie parameters to lifetime('.$currentCookieParams['lifetime'].') path('.$currentCookieParams['path'].') domain ('.$currentCookieParams['domain'].') secure('.$currentCookieParams['secure'].') httpOnly(1)'); session_set_cookie_params( $currentCookieParams['lifetime'], $currentCookieParams['path'], @@ -175,9 +175,7 @@ foreach ( getSkinIncludes( 'skin.php' ) as $includeFile ) if ( ZM_OPT_USE_AUTH && ZM_AUTH_HASH_LOGINS ) { if ( empty($user) && ! empty($_REQUEST['auth']) ) { -Logger::Debug("Getting user from auth hash"); if ( $authUser = getAuthUser( $_REQUEST['auth'] ) ) { -Logger::Debug("Success Getting user from auth hash"); userLogin( $authUser['Username'], $authUser['Password'], true ); } } else if ( ! empty($user) ) { diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 8783f1ed8..4d00798be 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -116,8 +116,11 @@ $SLANG = array( 'AttrArchiveStatus' => 'Archive Status', 'AttrAvgScore' => 'Avg. Score', 'AttrCause' => 'Cause', - 'AttrDate' => 'Date', - 'AttrDateTime' => 'Date/Time', + 'AttrStartDate' => 'Start Date', + 'AttrEndDate' => 'End Date', + 'AttrStartDateTime' => 'Start Date/Time', + 'AttrEndDateTime' => 'End Date/Time', + 'AttrDiskSpace' => 'Disk Space', 'AttrDiskBlocks' => 'Disk Blocks', 'AttrDiskPercent' => 'Disk Percent', 'AttrDuration' => 'Duration', @@ -132,9 +135,11 @@ $SLANG = array( 'AttrName' => 'Name', 'AttrNotes' => 'Notes', 'AttrSystemLoad' => 'System Load', - 'AttrTime' => 'Time', + 'AttrStartTime' => 'Start Time', + 'AttrEndTime' => 'End Time', 'AttrTotalScore' => 'Total Score', - 'AttrWeekday' => 'Weekday', + 'AttrStartWeekday' => 'Start Weekday', + 'AttrEndWeekday' => 'End Weekday', 'Auto' => 'Auto', 'AutoStopTimeout' => 'Auto Stop Timeout', 'Available' => 'Available', @@ -297,6 +302,7 @@ $SLANG = array( 'DuplicateMonitorName' => 'Duplicate Monitor Name', 'Duration' => 'Duration', 'Edit' => 'Edit', + 'EditLayout' => 'Edit Layout', 'Email' => 'Email', 'EnableAlarms' => 'Enable Alarms', 'Enabled' => 'Enabled', @@ -334,6 +340,7 @@ $SLANG = array( 'Ffmpeg' => 'Ffmpeg', 'File' => 'File', 'FilterArchiveEvents' => 'Archive all matches', + 'FilterUpdateDiskSpace' => 'Update used disk space', 'FilterDeleteEvents' => 'Delete all matches', 'FilterEmailEvents' => 'Email details of all matches', 'FilterExecuteEvents' => 'Execute command on all matches', @@ -413,6 +420,7 @@ $SLANG = array( 'LimitResultsPre' => 'Limit to first', // This is used at the beginning of the phrase 'Limit to first N results only' 'LinkedMonitors' => 'Linked Monitors', 'List' => 'List', + 'ListMatches' => 'List Matches', 'Load' => 'Load', 'Local' => 'Local', 'Log' => 'Log', @@ -549,6 +557,8 @@ $SLANG = array( 'OpNe' => 'not equal to', 'OpNotIn' => 'not in set', 'OpNotMatches' => 'does not match', + 'OpIs' => 'is', + 'OpIsNot' => 'is not', 'OptionalEncoderParam' => 'Optional Encoder Parameters', 'OptionHelp' => 'Option Help', 'OptionRestartWarning' => 'These changes may not come into effect fully\nwhile the system is running. When you have\nfinished making your changes please ensure that\nyou restart ZoneMinder.', diff --git a/web/skins/classic/css/flat/views/montage.css b/web/skins/classic/css/flat/views/montage.css index 7c571f7c1..479ade89e 100644 --- a/web/skins/classic/css/flat/views/montage.css +++ b/web/skins/classic/css/flat/views/montage.css @@ -6,6 +6,15 @@ width: 99%; } +#monitors:after { + content: "."; + display: block; + height: 0; + font-size: 0; + clear: both; + visibility: hidden; +} + #monitors .monitor { min-width: 180px; margin: 0; diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index 9cae836ce..450cf63b3 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -48,8 +48,10 @@ function xhtmlHeaders( $file, $title ) { <?php echo ZM_WEB_TITLE_PREFIX ?> - <?php echo validHtmlStr($title) ?> \n"; - echo "\n"; + echo " + + +"; } else { echo ' @@ -60,11 +62,11 @@ if ( file_exists( "skins/$skin/css/$css/graphics/favicon.ico" ) ) { - + - + - + - + - + @@ -194,9 +196,19 @@ ZoneMinder requires Javascript. Please enable Javascript in your browser for thi
  • -
  • Logger::NOLOG ) { ?> '.translate('Log').'' ) ?>
  • - - +
  • + Logger::NOLOG ) { + if ( ! ZM_RUN_AUDIT ) { + # zmaudit can clean the logs, but if we aren't running it, then we should clecan them regularly + dbQuery("DELETE FROM Logs WHERE TimeKey < NOW()-to_days('".ZM_LOG_DATABASE_LIMIT."')"); + } + echo makePopupLink( '?view=log', 'zmLog', 'log', ''.translate('Log').'' ); + } +} // end if canview(System) +?>
  • +
  • Devices
  • >
  • diff --git a/web/skins/classic/views/_monitor_filters.php b/web/skins/classic/views/_monitor_filters.php new file mode 100644 index 000000000..c8607f845 --- /dev/null +++ b/web/skins/classic/views/_monitor_filters.php @@ -0,0 +1,150 @@ +Id()] = $S; +} +session_start(); +foreach ( array('ServerFilter','StorageFilter','StatusFilter','MonitorId') as $var ) { + if ( isset( $_REQUEST[$var] ) ) { + if ( $_REQUEST[$var] != '' ) { + $_SESSION[$var] = $_REQUEST[$var]; + } else { + unset( $_SESSION[$var] ); + } + } else if ( isset( $_COOKIE[$var] ) ) { + if ( $_COOKIE[$var] != '' ) { + $_SESSION[$var] = $_COOKIE[$var]; + } else { + unset($_SESSION[$var]); + } + } +} +session_write_close(); + +$storage_areas = Storage::find_all(); +$StorageById = array(); +foreach ( $storage_areas as $S ) { + $StorageById[$S->Id()] = $S; +} + +?> +
    + + + + +'All'); + + if ( $monitor_id ) { + $found_selected_monitor = false; + + for ( $i = 0; $i < count($monitors); $i++ ) { + if ( !visibleMonitor( $monitors[$i]['Id'] ) ) { + continue; + } + $monitors_dropdown[$monitors[$i]['Id']] = $monitors[$i]['Name']; + if ( $monitors[$i]['Id'] == $monitor_id ) { + $found_selected_monitor = true; + } + } + if ( ! $found_selected_monitor ) { + $monitor_id = ''; + } + } + for ( $i = 0; $i < count($monitors); $i++ ) { + if ( !visibleMonitor( $monitors[$i]['Id'] ) ) { + continue; + } + $monitors_dropdown[$monitors[$i]['Id']] = $monitors[$i]['Name']; + + if ( $monitor_id and ( $monitors[$i]['Id'] != $monitor_id ) ) { + continue; + } + $displayMonitors[] = $monitors[$i]; + } + echo htmlSelect( 'MonitorId', $monitors_dropdown, $monitor_id, array('onchange'=>'changeMonitor(this);') ); +?> + + 1 ) { +?> + +'All')+$ServersById, (isset($_SESSION['ServerFilter'])?$_SESSION['ServerFilter']:''), array('onchange'=>'changeFilter(this);') ); +?> + + 1 ) { ?> + +'All')+$StorageById, (isset($_SESSION['StorageFilter'])?$_SESSION['StorageFilter']:''), array('onchange'=>'changeFilter(this);') ); +?> + + + +'All', + 'Unknown' => translate('Unknown'), + 'NotRunning' => translate('NotRunning'), + 'Running' => translate('Running'), + ); +echo htmlSelect( 'StatusFilter', $status_options, ( isset($_SESSION['StatusFilter']) ? $_SESSION['StatusFilter'] : '' ), array('onchange'=>'changeFilter(this);') ); +?> + +
    diff --git a/web/skins/classic/views/_monitor_source_nvsocket.html b/web/skins/classic/views/_monitor_source_nvsocket.html new file mode 100644 index 000000000..2a1567dfe --- /dev/null +++ b/web/skins/classic/views/_monitor_source_nvsocket.html @@ -0,0 +1,14 @@ + + + + + + + + + () + () + +Orientation() );?> + + diff --git a/web/skins/classic/views/add_monitors.php b/web/skins/classic/views/add_monitors.php new file mode 100644 index 000000000..61f26c4d5 --- /dev/null +++ b/web/skins/classic/views/add_monitors.php @@ -0,0 +1,112 @@ + + +
    + +
    + +
    +
    +
    +
    Results +
    + +
    +
    +
    +
    +
    Enter by IP or URL + + +
    +
    Import CSV Spreadsheet + Spreadsheet should have the following format:
    + + + + + + + + + + + +
    NameURLGroup
    Example Name MN1-30 INQ37.01http://10.34.152.20:2001/?action=streamMN1
    + Defaults to apply to each monitor:
    + + +Id()] = $S; + } + + if ( count($ServersById) > 0 ) { ?> + +Id()] = $S; + } + if ( count($StorageById) > 0 ) { +?> + + + +
    SettingValue
    + +
    + 'Auto')+$ServersById, '' ); ?> +
    +'All')+$StorageById, 1 ); ?> +
    + + + +
    +
    + +
    +
    +
    + diff --git a/web/skins/classic/views/console.php b/web/skins/classic/views/console.php index 4ba9e2457..43214ea1f 100644 --- a/web/skins/classic/views/console.php +++ b/web/skins/classic/views/console.php @@ -18,36 +18,6 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -$servers = Server::find_all(); -$ServersById = array(); -foreach ( $servers as $S ) { - $ServersById[$S->Id()] = $S; -} -session_start(); -foreach ( array('ServerFilter','StorageFilter') as $var ) { - if ( isset( $_REQUEST[$var] ) ) { - if ( $_REQUEST[$var] != '' ) { - $_SESSION[$var] = $_REQUEST[$var]; - } else { - unset( $_SESSION[$var] ); - } - } else if ( isset( $_COOKIE[$var] ) ) { - if ( $_COOKIE[$var] != '' ) { - $_SESSION[$var] = $_COOKIE[$var]; - } else { - unset($_SESSION[$var]); - } - } -} -session_write_close(); - -$storage_areas = Storage::find_all(); -$StorageById = array(); -foreach ( $storage_areas as $S ) { - $StorageById[$S->Id()] = $S; -} - -$show_storage_areas = count($storage_areas) > 1 and canEdit( 'System' ) ? 1 : 0; if ( $running == null ) $running = daemonCheck(); @@ -120,6 +90,46 @@ $eventCounts = array( $navbar = getNavBarHTML(); +ob_start(); +include('_monitor_filters.php'); +$filterbar = ob_get_contents(); +ob_end_clean(); + +$show_storage_areas = count($storage_areas) > 1 and canEdit( 'System' ) ? 1 : 0; +$maxWidth = 0; +$maxHeight = 0; +$zoneCount = 0; +for ( $i = 0; $i < count($displayMonitors); $i++ ) { + $monitor = &$displayMonitors[$i]; + if ( $monitor['Function'] != 'None' ) { + $scaleWidth = reScale( $monitor['Width'], $monitor['DefaultScale'], ZM_WEB_DEFAULT_SCALE ); + $scaleHeight = reScale( $monitor['Height'], $monitor['DefaultScale'], ZM_WEB_DEFAULT_SCALE ); + if ( $maxWidth < $scaleWidth ) $maxWidth = $scaleWidth; + if ( $maxHeight < $scaleHeight ) $maxHeight = $scaleHeight; + } + $monitor['zmc'] = zmcStatus( $monitor ); + $monitor['zma'] = zmaStatus( $monitor ); + $monitor['ZoneCount'] = dbFetchOne( 'select count(Id) as ZoneCount from Zones where MonitorId = ?', 'ZoneCount', array($monitor['Id']) ); + $zoneCount += $monitor['ZoneCount']; + + $counts = array(); + for ( $j = 0; $j < count($eventCounts); $j += 1 ) { + $filter = addFilterTerm( $eventCounts[$j]['filter'], count($eventCounts[$j]['filter']['Query']['terms']), array( 'cnj' => 'and', 'attr' => 'MonitorId', 'op' => '=', 'val' => $monitor['Id'] ) ); + parseFilter( $filter ); + $counts[] = 'count(if(1'.$filter['sql'].",1,NULL)) AS EventCount$j, SUM(if(1".$filter['sql'].",DiskSpace,NULL)) As DiskSpace$j"; + $monitor['eventCounts'][$j]['filter'] = $filter; + } + $sql = 'SELECT '.join($counts,', ').' FROM Events as E where MonitorId = ?'; + $counts = dbFetchOne( $sql, NULL, array($monitor['Id']) ); + if ( $counts ) + $monitor = array_merge( $monitor, $counts ); + for ( $j = 0; $j < count($eventCounts); $j += 1 ) { + $eventCounts[$j]['total'] += $monitor['EventCount'.$j]; + } + unset($monitor); +} +$cycleWidth = $maxWidth; +$cycleHeight = $maxHeight; noCacheHeaders(); @@ -137,128 +147,7 @@ xhtmlHeaders( __FILE__, translate('Console') ); -
    - - - - -'All'); - - if ( $monitor_id ) { - $found_selected_monitor = false; - - for ( $i = 0; $i < count($monitors); $i++ ) { - if ( !visibleMonitor( $monitors[$i]['Id'] ) ) { - continue; - } - $monitors_dropdown[$monitors[$i]['Id']] = $monitors[$i]['Name']; - if ( $monitors[$i]['Id'] == $monitor_id ) { - $found_selected_monitor = true; - } - } - if ( ! $found_selected_monitor ) { - $monitor_id = ''; - } - } - for ( $i = 0; $i < count($monitors); $i++ ) { - if ( !visibleMonitor( $monitors[$i]['Id'] ) ) { - continue; - } - $monitors_dropdown[$monitors[$i]['Id']] = $monitors[$i]['Name']; - - if ( $monitor_id and ( $monitors[$i]['Id'] != $monitor_id ) ) { - continue; - } - if ( $monitors[$i]['Function'] != 'None' ) { - $scaleWidth = reScale( $monitors[$i]['Width'], $monitors[$i]['DefaultScale'], ZM_WEB_DEFAULT_SCALE ); - $scaleHeight = reScale( $monitors[$i]['Height'], $monitors[$i]['DefaultScale'], ZM_WEB_DEFAULT_SCALE ); - if ( $maxWidth < $scaleWidth ) $maxWidth = $scaleWidth; - if ( $maxHeight < $scaleHeight ) $maxHeight = $scaleHeight; - } - $displayMonitors[] = $monitors[$i]; - } - - - echo htmlSelect( 'monitor_id', $monitors_dropdown, $monitor_id, array('onchange'=>'changeMonitor(this);') ); - - $cycleWidth = $maxWidth; - $cycleHeight = $maxHeight; - $zoneCount = 0; - -for( $i = 0; $i < count($displayMonitors); $i += 1 ) { - $monitor = $displayMonitors[$i]; - $monitor['zmc'] = zmcStatus( $monitor ); - $monitor['zma'] = zmaStatus( $monitor ); - $monitor['ZoneCount'] = dbFetchOne( 'select count(Id) as ZoneCount from Zones where MonitorId = ?', 'ZoneCount', array($monitor['Id']) ); - $counts = array(); - for ( $j = 0; $j < count($eventCounts); $j += 1 ) { - $filter = addFilterTerm( $eventCounts[$j]['filter'], count($eventCounts[$j]['filter']['Query']['terms']), array( 'cnj' => 'and', 'attr' => 'MonitorId', 'op' => '=', 'val' => $monitor['Id'] ) ); - parseFilter( $filter ); - $counts[] = 'count(if(1'.$filter['sql'].",1,NULL)) AS EventCount$j, SUM(if(1".$filter['sql'].",DiskSpace,NULL)) As DiskSpace$j"; - $monitor['eventCounts'][$j]['filter'] = $filter; - } - $sql = 'SELECT '.join($counts,', ').' FROM Events as E where MonitorId = ?'; - $counts = dbFetchOne( $sql, NULL, array($monitor['Id']) ); - if ( $counts ) - $displayMonitors[$i] = $monitor = array_merge( $monitor, $counts ); - for ( $j = 0; $j < count($eventCounts); $j += 1 ) { - $eventCounts[$j]['total'] += $monitor['EventCount'.$j]; - } - $zoneCount += $monitor['ZoneCount']; -} -?> - - 0 ) { ?> - -'All')+$ServersById, (isset($_SESSION['ServerFilter'])?$_SESSION['ServerFilter']:''), array('onchange'=>'changeFilter(this);') ); -?> - - 0 ) { ?> - -'All')+$StorageById, (isset($_SESSION['StorageFilter'])?$_SESSION['StorageFilter']:''), array('onchange'=>'changeFilter(this);') ); -?> - - -
    +
    diff --git a/web/skins/classic/views/filter.php b/web/skins/classic/views/filter.php index 33faecbc7..cb7e30087 100644 --- a/web/skins/classic/views/filter.php +++ b/web/skins/classic/views/filter.php @@ -69,10 +69,14 @@ $attrTypes = array( 'Name' => translate('AttrName'), 'Cause' => translate('AttrCause'), 'Notes' => translate('AttrNotes'), - 'DateTime' => translate('AttrDateTime'), - 'Date' => translate('AttrDate'), - 'Time' => translate('AttrTime'), - 'Weekday' => translate('AttrWeekday'), + 'StartDateTime' => translate('AttrStartDateTime'), + 'StartDate' => translate('AttrStartDate'), + 'StartTime' => translate('AttrStartTime'), + 'StartWeekday' => translate('AttrStartWeekday'), + 'EndDateTime' => translate('AttrEndDateTime'), + 'EndDate' => translate('AttrEndDate'), + 'EndTime' => translate('AttrEndTime'), + 'EndWeekday' => translate('AttrEndWeekday'), 'Length' => translate('AttrDuration'), 'Frames' => translate('AttrFrames'), 'AlarmFrames' => translate('AttrAlarmFrames'), @@ -80,8 +84,9 @@ $attrTypes = array( 'AvgScore' => translate('AttrAvgScore'), 'MaxScore' => translate('AttrMaxScore'), 'Archived' => translate('AttrArchiveStatus'), - 'DiskPercent' => translate('AttrDiskPercent'), 'DiskBlocks' => translate('AttrDiskBlocks'), + 'DiskPercent' => translate('AttrDiskPercent'), + 'DiskSpace' => translate('AttrDiskSpace'), 'SystemLoad' => translate('AttrSystemLoad'), 'StorageId' => translate('AttrStorageArea'), 'ServerId' => translate('AttrServer'), @@ -99,6 +104,8 @@ $opTypes = array( '!~' => translate('OpNotMatches'), '=[]' => translate('OpIn'), '![]' => translate('OpNotIn'), + 'IS' => translate('OpIs'), + 'IS NOT' => translate('OpIsNot'), ); $archiveTypes = array( @@ -297,18 +304,19 @@ if ( count($terms) == 0 ) { translate('AttrId'), - 'Name' => translate('AttrName'), - 'Cause' => translate('AttrCause'), - 'Notes' => translate('AttrNotes'), - 'MonitorName' => translate('AttrMonitorName'), - 'DateTime' => translate('AttrDateTime'), - 'Length' => translate('AttrDuration'), - 'Frames' => translate('AttrFrames'), - 'AlarmFrames' => translate('AttrAlarmFrames'), - 'TotScore' => translate('AttrTotalScore'), - 'AvgScore' => translate('AttrAvgScore'), - 'MaxScore' => translate('AttrMaxScore'), + 'Id' => translate('AttrId'), + 'Name' => translate('AttrName'), + 'Cause' => translate('AttrCause'), + 'DiskSpace' => translate('AttrDiskSpace'), + 'Notes' => translate('AttrNotes'), + 'MonitorName' => translate('AttrMonitorName'), + 'StartDateTime' => translate('AttrStartDateTime'), + 'Length' => translate('AttrDuration'), + 'Frames' => translate('AttrFrames'), + 'AlarmFrames' => translate('AttrAlarmFrames'), + 'TotScore' => translate('AttrTotalScore'), + 'AvgScore' => translate('AttrAvgScore'), + 'MaxScore' => translate('AttrMaxScore'), ); echo htmlSelect( 'filter[Query][sort_field]', $sort_fields, $filter->sort_field() ); $sort_dirns = array( @@ -332,6 +340,9 @@ echo htmlSelect( 'filter[Query][sort_asc]', $sort_dirns, $filter->sort_asc() ); AutoArchive()) ) { ?> checked="checked" onclick="updateButtons( this )"/>

    +

    + UpdateDiskSpace()) ? '' : ' checked="checked"' ?> onclick="updateButtons(this);"/> +

    @@ -387,7 +398,7 @@ if ( ZM_OPT_MESSAGE ) {
    - + Id(); +if ( $newGroup->Id() ) + $kids[] = $newGroup->Id(); +$sql = 'SELECT Id,Name from Groups'.(count($kids)?' WHERE Id NOT IN ('.implode(',',array_map(function(){return '?';}, $kids )).')' : '').' ORDER BY Name'; $options = array(''=>'None'); -foreach ( dbFetchAll( 'SELECT Id,Name from Groups WHERE Id NOT IN ('.implode(',',array_map(function(){return '?';}, $kids )).') ORDER BY Name', null, $kids ) as $option ) { +foreach ( dbFetchAll( $sql, null, $kids ) as $option ) { $options[$option['Id']] = $option['Name']; } echo htmlSelect( 'newGroup[ParentId]', $options, $newGroup->ParentId(), array('onchange'=>'configureButtons(this);' )); diff --git a/web/skins/classic/views/js/add_monitors.js b/web/skins/classic/views/js/add_monitors.js new file mode 100644 index 000000000..a94fd2200 --- /dev/null +++ b/web/skins/classic/views/js/add_monitors.js @@ -0,0 +1,89 @@ + +var probeReq = new Request.JSON( { url:thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getProbeResponse } ); + +function probe( url_e ) { + probeReq.send( "request=add_monitors&action=probe&url="+url_e.value ); +} + +var ProbeResults; + +function getProbeResponse( respObj, respText ) { + if ( checkStreamForErrors( "getProbeResponse", respObj ) ) + return; +//alert(respText); + + if ( respObj.Streams ) { + parseStreams( respObj.Streams ); + } else { + alert("No Streams"); + } +} // end function getProbeResponse + +function parseStreams( Streams ) { + ProbeResults = Array(); + + var results_div = $j('#url_results')[0]; + if ( ! results_div ) { + console.log("No results div found."); + return; + } + results_div.innerHTML = ''; + var html = ''; + + for( i in Streams ) { + var stream = Streams[i]; + if ( stream.url ) { + html += '

    '+stream.url; + if ( stream.Monitor.Id ) { + html += ' is already entered into the system by Monitor ' + stream.Monitor.Id + ' ' + stream.Monitor.Name + '
    '; + html += ''; + } else { + html += ''; + } + html += '

    '; + ProbeResults[stream.url] = stream; + } else { + //console.log(stream); + } + } // end for eah Stream + + results_div.innerHTML = html; +} + +function addMonitor(url) { + if ( ! ProbeResults[url] ) { + alert("Monitor for url " + url + " not found in probe results." ); + return; + } + var Stream = ProbeResults[url]; + var Monitor = Stream.Monitor; + + popup_url = '?view=monitor&newMonitor[Path]='+url; + keys = Object.keys( Monitor ); + for ( i in Monitor ) { + if ( ! Monitor[i] ) + continue; + if ( Monitor[i] == 'null' ) + Monitor[i]=''; + popup_url += '&newMonitor['+i+']='+Monitor[i]; + } + createPopup( popup_url, 'zmMonitor0', 'monitor' ); +} + +function import_csv( form ) { + var formData = new FormData( form ); + console.log(formData); + //formData.append('file', $('#file')[0].files[0]); + + $j.ajax({ + url : thisUrl+"?request=add_monitors&action=import", + type : 'POST', + data : formData, + processData: false, // tell jQuery not to process the data + contentType: false, // tell jQuery not to set contentType + success : function(data) { + var json = JSON.parse(data); + parseStreams( json.Streams ); + } + }); +} diff --git a/web/skins/classic/views/js/console.js b/web/skins/classic/views/js/console.js index 9675a96b5..3ba206f81 100644 --- a/web/skins/classic/views/js/console.js +++ b/web/skins/classic/views/js/console.js @@ -19,7 +19,7 @@ function setButtonStates( element ) { form.deleteBtn.disabled = (checked==0); } -function addMonitor( element) { +function addMonitor(element) { var form = element.form; var dupParam; var monitorId=-1; diff --git a/web/skins/classic/views/js/filter.js b/web/skins/classic/views/js/filter.js index 768158a4c..ff93716c5 100644 --- a/web/skins/classic/views/js/filter.js +++ b/web/skins/classic/views/js/filter.js @@ -19,6 +19,8 @@ function updateButtons( element ) { canExecute = true; else if ( form.elements['filter[AutoDelete]'].checked ) canExecute = true; + else if ( form.elements['filter[UpdateDiskSpace]'].checked ) + canExecute = true; form.elements['executeButton'].disabled = !canExecute; } } diff --git a/web/skins/classic/views/js/monitor.js.php b/web/skins/classic/views/js/monitor.js.php index 755c1671f..470bb92b8 100644 --- a/web/skins/classic/views/js/monitor.js.php +++ b/web/skins/classic/views/js/monitor.js.php @@ -55,7 +55,7 @@ function validateForm( form ) { else if ( form.elements.mid.value == 0 && monitorNames[form.elements['newMonitor[Name]'].value] ) errors[errors.length] = ""; - if ( form.elements['newMonitor[AnalysisFPS]'].value && !(parseFloat(form.elements['newMonitor[AnalysisFPS]'].value) > 0 ) ) + if ( form.elements['newMonitor[AnalysisFPSLimit]'].value && !(parseFloat(form.elements['newMonitor[AnalysisFPSLimit]'].value) > 0 ) ) errors[errors.length] = ""; if ( form.elements['newMonitor[MaxFPS]'].value && !(parseFloat(form.elements['newMonitor[MaxFPS]'].value) > 0 ) ) errors[errors.length] = ""; @@ -97,7 +97,7 @@ function validateForm( form ) { errors[errors.length] = ""; if ( !form.elements['newMonitor[WarmupCount]'].value || !(parseInt(form.elements['newMonitor[WarmupCount]'].value) >= 0 ) ) errors[errors.length] = ""; - if ( !form.elements['newMonitor[PreEventCount]'].value || !(parseInt(form.elements['newMonitor[PreEventCount]'].value) > 0 ) || (parseInt(form.elements['newMonitor[PreEventCount]'].value) > parseInt(form.elements['newMonitor[ImageBufferCount]'].value)) ) + if ( !form.elements['newMonitor[PreEventCount]'].value || !(parseInt(form.elements['newMonitor[PreEventCount]'].value) >= 0 ) || (parseInt(form.elements['newMonitor[PreEventCount]'].value) > parseInt(form.elements['newMonitor[ImageBufferCount]'].value)) ) errors[errors.length] = ""; if ( !form.elements['newMonitor[PostEventCount]'].value || !(parseInt(form.elements['newMonitor[PostEventCount]'].value) >= 0 ) ) errors[errors.length] = ""; diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index 3fe8c5b87..364e79b91 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -7,11 +7,10 @@ function Monitor( monitorData ) { this.status = null; this.alarmState = STATE_IDLE; this.lastAlarmState = STATE_IDLE; - this.streamCmdParms = "view=request&request=stream&connkey="+this.connKey; + this.streamCmdParms = 'view=request&request=stream&connkey='+this.connKey; + this.onclick = monitorData.onclick; if ( auth_hash ) this.streamCmdParms += '&auth='+auth_hash; - else - console.log("No auth_hash"); this.streamCmdTimer = null; this.start = function( delay ) { @@ -34,7 +33,8 @@ function Monitor( monitorData ) { if ( this.streamCmdTimer ) this.streamCmdTimer = clearTimeout( this.streamCmdTimer ); - var stream = $j('#liveStream'+this.id )[0]; + var stream = $j('#liveStream'+this.id)[0]; + if ( respObj.result == 'Ok' ) { this.status = respObj.status; this.alarmState = this.status.state; @@ -83,42 +83,100 @@ function Monitor( monitorData ) { // Try to reload the image stream. if ( stream ) stream.src = stream.src.replace( /auth=\w+/i, 'auth='+this.status.auth ); - console.log("Changed auth to " + this.status.auth ); + console.log("Changed auth from " + auth_hash + " to " + this.status.auth ); + auth_hash = this.status.auth; } - } // end if haev a new auth hash + } // end if have a new auth hash } else { console.error( respObj.message ); // Try to reload the image stream. - if ( stream ) + if ( stream ) { + console.log('Reloading stream: ' + stream.src ); stream.src = stream.src.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) )); - } + } else { + console.log( 'No stream to reload?' ); + } + } // end if Ok or not var streamCmdTimeout = statusRefreshTimeout; - if ( this.alarmState == STATE_ALARM || this.alarmState == STATE_ALERT ) - streamCmdTimeout = streamCmdTimeout/5; + // The idea here is if we are alarmed, do updates faster. However, there is a timeout in the php side which isn't getting modified, so this may cause a problem. Also the server may only be able to update so fast. + //if ( this.alarmState == STATE_ALARM || this.alarmState == STATE_ALERT ) { + //streamCmdTimeout = streamCmdTimeout/5; + //} this.streamCmdTimer = this.streamCmdQuery.delay( streamCmdTimeout, this ); this.lastAlarmState = this.alarmState; }; this.streamCmdQuery = function( resent ) { - //if ( resent ) - //console.log( this.connKey+": Resending" ); + if ( resent ) + console.log( this.connKey+": Resending" ); //this.streamCmdReq.cancel(); this.streamCmdReq.send( this.streamCmdParms+"&command="+CMD_QUERY ); }; + this.onError = function( text, error ) { + console.log('onerror: ' + text + ' error:'+error); + }; + this.onFailure = function( xhr ) { + console.log('onFailure: ' ); + console.log(xhr ); + }; - this.streamCmdReq = new Request.JSON( { url: this.server_url, method: 'get', timeout: AJAX_TIMEOUT, onSuccess: this.getStreamCmdResponse.bind( this ), onTimeout: this.streamCmdQuery.bind( this, true ), link: 'cancel' } ); + this.streamCmdReq = new Request.JSON( { + url: this.server_url, + method: 'get', + timeout: 1000+AJAX_TIMEOUT, + onSuccess: this.getStreamCmdResponse.bind( this ), + onTimeout: this.streamCmdQuery.bind( this, true ), + onError: this.onError.bind(this), + onFailure: this.onFailure.bind(this), + link: 'cancel' + } ); requestQueue.addRequest( "cmdReq"+this.id, this.streamCmdReq ); } function selectLayout( element ) { - layout = $(element).get('value') - var cssFile = skinPath+'/css/'+Cookie.read('zmCSS')+'/views/'+layout; - if ( $('dynamicStyles') ) - $('dynamicStyles').destroy(); - new Asset.css( cssFile, { id: 'dynamicStyles' } ); - Cookie.write( 'zmMontageLayout', layout, { duration: 10*365 } ); - if ( layout != 'montage_freeform.css' ) { + layout = $j(element).val(); + + if ( layout_id = parseInt(layout) ) { + layout = layouts[layout]; + console.log(layout); + + for ( var i = 0; i < monitors.length; i++ ) { + monitor = monitors[i]; + // Need to clear the current positioning, and apply the new + + monitor_frame = $j('#monitorFrame'+monitor.id); + if ( ! monitor_frame ) { + console.log("Error finding frame for " + monitor.id ); + continue; + } + + // Apply default layout options, like float left + if ( layout.Positions['default'] ) { + styles = layout.Positions['default']; + for ( style in styles ) { + monitor_frame.css(style, styles[style]); + } + } else { + console.log("No default styles to apply" + layout.Positions); + } // end if default styles + + if ( layout.Positions['mId'+monitor.id] ) { + styles = layout.Positions['mId'+monitor.id]; + for ( style in styles ) { + monitor_frame.css(style, styles[style]); + console.log("Applying " + style + ' : ' + styles[style] ); + } + } else { + console.log("No Monitor styles to apply"); + } // end if specific monitor style + } // end foreach monitor + } // end if a stored layout + if ( ! layout ) { + return; + } + Cookie.write( 'zmMontageLayout', layout_id, { duration: 10*365 } ); + if ( layouts[layout_id].Name != 'Freeform' ) { // 'montage_freeform.css' ) { Cookie.write( 'zmMontageScale', '', { duration: 10*365 } ); $('scale').set('value', '' ); $('width').set('value', ''); @@ -152,6 +210,17 @@ function changeSize() { for ( var x = 0; x < monitors.length; x++ ) { var monitor = monitors[x]; + + // Scale the frame + monitor_frame = $j('#monitorFrame'+monitor.id); + if ( ! monitor_frame ) { + console.log("Error finding frame for " + monitor.id ); + continue; + } + if ( width ) + monitor_frame.css('width',width+'px'); + if ( height ) + monitor_frame.css('height',height+'px'); /*Stream could be an applet so can't use moo tools*/ var streamImg = $( 'liveStream'+monitor.id ); if ( streamImg ) { @@ -181,13 +250,32 @@ function changeSize() { function changeScale() { var scale = $('scale').get('value'); - + $('width').set('value', ''); + $('height').set('value', ''); + Cookie.write( 'zmMontageScale', scale, { duration: 10*365 } ); + Cookie.write( 'zmMontageWidth', '', { duration: 10*365 } ); + Cookie.write( 'zmMontageHeight', '', { duration: 10*365 } ); + if ( ! scale ) { + selectLayout('#zmMontageLayout'); + return; + } for ( var x = 0; x < monitors.length; x++ ) { var monitor = monitors[x]; var newWidth = ( monitorData[x].width * scale ) / SCALE_BASE; var newHeight = ( monitorData[x].height * scale ) / SCALE_BASE; + + // Scale the frame + monitor_frame = $j('#monitorFrame'+monitor.id); + if ( ! monitor_frame ) { + console.log("Error finding frame for " + monitor.id ); + continue; + } + if ( width ) + monitor_frame.css('width',width+'px'); + if ( height ) + monitor_frame.css('height',height+'px'); /*Stream could be an applet so can't use moo tools*/ - var streamImg = document.getElementById( 'liveStream'+monitor.id ); + var streamImg = $j('#liveStream'+monitor.id )[0]; if ( streamImg ) { if ( streamImg.nodeName == 'IMG' ) { var src = streamImg.src; @@ -208,22 +296,72 @@ function changeScale() { zonesSVG.style.height = newHeight + "px"; } } - $('width').set('value', ''); - $('height').set('value', ''); - Cookie.write( 'zmMontageScale', scale, { duration: 10*365 } ); - Cookie.write( 'zmMontageWidth', '', { duration: 10*365 } ); - Cookie.write( 'zmMontageHeight', '', { duration: 10*365 } ); +} + +function toGrid(value) { + return Math.round(value / 80) * 80; +} + +// Makes monitorFrames draggable. +function edit_layout(button) { + + // Turn off the onclick on the image. + + for ( var i = 0; i < monitors.length; i++ ) { + var monitor = monitors[i]; + monitor_feed = $j('#imageFeed'+monitor.id)[0]; + monitor_feed.onclick=''; + }; + + $j('#monitors .monitorFrame').draggable({ + cursor: 'crosshair', + //revert: 'invalid' + }); + $j('#SaveLayout').show(); + $j('#EditLayout').hide(); +} // end function edit_layout + +function save_layout(button) { + var form=button.form; + // In fixed positioning, order doesn't matter. In floating positioning, it does. + var Positions = {}; + for ( var i = 0; i < monitors.length; i++ ) { + var monitor = monitors[i]; + monitor_frame = $j('#monitorFrame'+monitor.id); + + Positions['mId'+monitor.id] = { + width: monitor_frame.css('width'), + height: monitor_frame.css('height'), + top: monitor_frame.css('top'), + bottom: monitor_frame.css('bottom'), + left: monitor_frame.css('left'), + right: monitor_frame.css('right'), + position: monitor_frame.css('position'), + float: monitor_frame.css('float'), + }; + } // end foreach monitor + form.Positions.value = JSON.stringify( Positions ); + form.submit(); +} +function cancel_layout(button) { + $j('#SaveLayout').hide(); + $j('#EditLayout').show(); + for ( var i = 0; i < monitors.length; i++ ) { + var monitor = monitors[i]; + monitor_feed = $j('#imageFeed'+monitor.id); + monitor_feed.click( monitor.onclick ); + }; + selectLayout('#zmMontageLayout'); } var monitors = new Array(); function initPage() { for ( var i = 0; i < monitorData.length; i++ ) { - monitors[i] = new Monitor( monitorData[i] ); - var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout ); - monitors[i].start( delay ); + monitors[i] = new Monitor(monitorData[i]); + var delay = Math.round( (Math.random()+0.75)*statusRefreshTimeout ); + monitors[i].start(delay); } - selectLayout($('layout')); + selectLayout('#zmMontageLayout'); } - // Kick everything off window.addEvent( 'domready', initPage ); diff --git a/web/skins/classic/views/js/montage.js.php b/web/skins/classic/views/js/montage.js.php index cd065a355..b6470da3f 100644 --- a/web/skins/classic/views/js/montage.js.php +++ b/web/skins/classic/views/js/montage.js.php @@ -31,12 +31,22 @@ var monitorData = new Array(); foreach ( $monitors as $monitor ) { ?> monitorData[monitorData.length] = { - 'id': Id() ?>, - 'connKey': connKey() ?>, - 'width': Width() ?>, - 'height':Height() ?>, - 'server_url': 'Server()->Url().(ZM_MIN_STREAMING_PORT?':'.(ZM_MIN_STREAMING_PORT+$monitor->Id()):'').$_SERVER['PHP_SELF'] ?>' + 'id': Id() ?>, + 'connKey': connKey() ?>, + 'width': Width() ?>, + 'height':Height() ?>, + 'server_url': 'Server()->Url().(ZM_MIN_STREAMING_PORT?':'.(ZM_MIN_STREAMING_PORT+$monitor->Id()):'').$_SERVER['PHP_SELF'] ?>', + 'onclick': function(){createPopup( '?view=watch&mid=Id() ?>', 'zmWatchId() ?>', 'watch', Width(), $monitor->PopupScale() ); ?>, Height(), $monitor->PopupScale() ); ?> );} }; +layouts = new Array(); +layouts[0] = {}; // reserved, should hold which fields to clear when transitioning + +layouts[Id() ?>] = {"Name":"Name()?>","Positions":Positions() ?>}; + diff --git a/web/skins/classic/views/js/montagereview.js b/web/skins/classic/views/js/montagereview.js index 305de1574..561f7e6c2 100644 --- a/web/skins/classic/views/js/montagereview.js +++ b/web/skins/classic/views/js/montagereview.js @@ -84,7 +84,11 @@ function imagedone( obj, monId, success ) { if ( ! success ) { // if we had a failrue queue up the no-data image //loadImage2Monitor(monId,"no data"); // leave the staged URL if there is one, just ignore it here. - loadNoData( monId ); + if ( liveMode ) { + writeText( monId, "Camera Offline" ); + } else { + writeText( monId, "No Data" ); + } } else { if ( monitorLoadingStageURL[monId] == "" ) { console.log("Not showing image for " + monId ); diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 42891ac23..ef15bf1e8 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -122,7 +122,7 @@ if ( ! $monitor ) { 'FrameSkip' => 0, 'MotionFrameSkip' => 0, 'EventPrefix' => 'Event-', - 'AnalysisFPS' => '', + 'AnalysisFPSLimit' => '', 'AnalysisUpdateDelay' => 0, 'MaxFPS' => '', 'AlarmMaxFPS' => '', @@ -170,8 +170,8 @@ if ( isset( $_REQUEST['newMonitor'] ) ) { } # What if it has less zeros? This is not robust code. -if ( $monitor->AnalysisFPS() == '0.00' ) - $monitor->AnalysisFPS( '' ); +if ( $monitor->AnalysisFPSLimit() == '0.00' ) + $monitor->AnalysisFPSLimit( '' ); if ( $monitor->MaxFPS() == '0.00' ) $monitor->MaxFPS( '' ); if ( $monitor->AlarmMaxFPS() == '0.00' ) @@ -538,7 +538,7 @@ if ( $tab != 'general' ) { - + Type()!= 'Local') ) { -
    +
    - + Type() != 'Local' && $monitor->Type() != 'File' && $monitor->Type() != 'NVSocket' ) { ?> diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index e4cc59dde..224de0215 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -23,6 +23,8 @@ if ( !canView('Stream') ) { return; } +require_once('includes/MontageLayout.php'); + $showControl = false; $showZones = false; if ( isset( $_REQUEST['showZones'] ) ) { @@ -57,58 +59,52 @@ if ( isset( $_REQUEST['scale'] ) ) { if ( ! $scale ) $scale = 100; -$focusWindow = true; +$layouts = MontageLayout::find(NULL, array('order'=>"lower('Name')")); +$layoutsById = array(); +foreach ( $layouts as $l ) { + $layoutsById[$l->Id()] = $l->Name(); +} -$layouts = array( - 'montage_freeform.css' => translate('MtgDefault'), - 'montage_2wide.css' => translate('Mtg2widgrd'), - 'montage_3wide.css' => translate('Mtg3widgrd'), - 'montage_4wide.css' => translate('Mtg4widgrd'), - 'montage_3wide50enlarge.css' => translate('Mtg3widgrx'), -); + +session_start(); $layout = ''; -if ( isset($_COOKIE['zmMontageLayout']) ) - $layout = $_COOKIE['zmMontageLayout']; +if ( isset($_COOKIE['zmMontageLayout']) ) { + $layout = $_SESSION['zmMontageLayout'] = $_COOKIE['zmMontageLayout']; +Warning("Setting layout by cookie"); +} elseif ( isset($_SESSION['zmMontageLayout']) ) { + $layout = $_SESSION['zmMontageLayout']; +Warning("Setting layout by session"); +} $options = array(); -if ( isset($_COOKIE['zmMontageWidth']) and $_COOKIE['zmMontageWidth'] ) - $options['width'] = $_COOKIE['zmMontageWidth']; -else +if ( isset($_COOKIE['zmMontageWidth']) and $_COOKIE['zmMontageWidth'] ) { + $_SESSION['zmMontageWidth'] = $options['width'] = $_COOKIE['zmMontageWidth']; +} elseif ( isset($_SESSION['zmMontageWidth']) and $_SESSION['zmMontageWidth'] ) { + $options['width'] = $_SESSION['zmMontageWidth']; +} else $options['width'] = ''; + if ( isset($_COOKIE['zmMontageHeight']) and $_COOKIE['zmMontageHeight'] ) - $options['height'] = $_COOKIE['zmMontageHeight']; + $_SESSION['zmMontageHeight'] = $options['height'] = $_COOKIE['zmMontageHeight']; +else if ( isset($_SESSION['zmMontageHeight']) and $_SESSION['zmMontageHeight'] ) + $options['height'] = $_SESSION['zmMontageHeight']; else $options['height'] = ''; +session_write_close(); + if ( $scale ) $options['scale'] = $scale; ob_start(); -# This will end up with the group_id of the deepest selection -$group_id = Group::get_group_dropdowns(); -$group_dropdowns = ob_get_contents(); +include('_monitor_filters.php'); +$filterbar = ob_get_contents(); ob_end_clean(); -$groupSql = Group::get_group_sql( $group_id ); - -$monitor_id = 0; -if ( isset( $_REQUEST['monitor_id'] ) ) { - $monitor_id = $_REQUEST['monitor_id']; -} else if ( isset($_COOKIE['zmMonitorId']) ) { - $monitor_id = $_COOKIE['zmMonitorId']; -} - $monitors = array(); -$monitors_dropdown = array( '' => 'All' ); -$sql = "SELECT * FROM Monitors WHERE Function != 'None'"; -if ( $groupSql ) { $sql .= ' AND ' . $groupSql; }; -if ( $monitor_id ) { $sql .= ' AND Id='.$monitor_id; }; - -$sql .= ' ORDER BY Sequence'; -foreach( dbFetchAll( $sql ) as $row ) { - if ( !visibleMonitor( $row['Id'] ) ) { +foreach( $displayMonitors as &$row ) { + if ( $row['Function'] == 'None' ) continue; - } $row['Scale'] = $scale; $row['PopupScale'] = reScale( SCALE_BASE, $row['DefaultScale'], ZM_WEB_DEFAULT_SCALE ); @@ -116,14 +112,13 @@ foreach( dbFetchAll( $sql ) as $row ) { if ( ZM_OPT_CONTROL && $row['ControlId'] && $row['Controllable'] ) $showControl = true; $row['connKey'] = generateConnKey(); - $Monitor = $monitors[] = new Monitor( $row ); - $monitors_dropdown[$Monitor->Id()] = $Monitor->Name(); if ( ! isset( $widths[$row['Width']] ) ) { $widths[$row['Width']] = $row['Width']; } if ( ! isset( $heights[$row['Height']] ) ) { $heights[$row['Height']] = $row['Height']; } + $monitors[] = new Monitor( $row ); } # end foreach Monitor xhtmlHeaders(__FILE__, translate('Montage') ); @@ -141,26 +136,36 @@ if ( $showControl ) { } if ( $showZones ) { ?> - Hide Zones + Hide Zones - Show Zones + Show Zones -
    - - - - - 'changeMonitor(this);') ); ?> - - - - - + +
    +
    + + + + + + + + + 'selectLayout(this);', 'id'=>'zmMontageLayout') ); ?> + + + + +
    @@ -169,7 +174,7 @@ if ( $showZones ) { foreach ( $monitors as $monitor ) { $connkey = $monitor->connKey(); // Minor hack ?> -
    +
    - - '; } // end foreach zone ?> Sorry, your browser does not support inline SVG diff --git a/web/skins/classic/views/montagereview.php b/web/skins/classic/views/montagereview.php index 2aeac465a..69a5c2921 100644 --- a/web/skins/classic/views/montagereview.php +++ b/web/skins/classic/views/montagereview.php @@ -55,62 +55,10 @@ if ( !canView( 'Events' ) ) { } ob_start(); -# This will end up with the group_id of the deepest selection -$group_id = Group::get_group_dropdowns(); -$group_dropdowns = ob_get_contents(); +include('_monitor_filters.php'); +$filter_bar = ob_get_contents(); ob_end_clean(); -$groupSql = Group::get_group_sql( $group_id ); - -$servers = Server::find_all(); -$ServersById = array(); -foreach ( $servers as $S ) { - $ServersById[$S->Id()] = $S; -} -$storage_areas = Storage::find_all(); -$StorageById = array(); -foreach ( $storage_areas as $S ) { - $StorageById[$S->Id()] = $S; -} -session_start(); -foreach ( array('minTime','maxTime','ServerFilter','StorageFilter') as $var ) { - if ( isset( $_REQUEST[$var] ) ) { - if ( $_REQUEST[$var] != '' ) { - $_SESSION[$var] = $_REQUEST[$var]; - } else { - unset( $_SESSION[$var] ); - } - } else if ( isset( $_COOKIE[$var] ) ) { - if ( $_COOKIE[$var] != '' ) { - $_SESSION[$var] = $_COOKIE[$var]; - } else { - unset($_SESSION[$var]); - } - } -} -session_write_close(); - -$monitor_id = 0; -if ( isset( $_REQUEST['monitor_id'] ) ) { - $monitor_id = $_REQUEST['monitor_id']; -} else if ( isset($_COOKIE['zmMonitorId']) ) { - $monitor_id = $_COOKIE['zmMonitorId']; -} - - $conditions = array(); - $values = array(); - - if ( $groupSql ) - $conditions[] = $groupSql; - if ( isset($_SESSION['ServerFilter']) ) { - $conditions[] = 'ServerId=?'; - $values[] = $_SESSION['ServerFilter']; - } - if ( isset($_SESSION['StorageFilter']) ) { - $conditions[] = 'StorageId=?'; - $values[] = $_SESSION['StorageFilter']; - } - // Note that this finds incomplete events as well, and any frame records written, but still cannot "see" to the end frame // if the bulk record has not been written - to be able to include more current frames reduce bulk frame sizes (event size can be large) // Note we round up just a bit on the end time as otherwise you get gaps, like 59.78 to 00 in the next second, which can give blank frames when moved through slowly. @@ -141,18 +89,12 @@ $frameSql = ' // This program only calls itself with the time range involved -- it does all monitors (the user can see, in the called group) all the time if ( ! empty( $user['MonitorIds'] ) ) { - $eventsSql .= ' AND M.Id IN ('.$user['MonitorIds'].')'; - $monitorsSql .= ' AND Id IN ('.$user['MonitorIds'].')'; - $monitor_ids = explode(',',$user['MonitorIds']); - $conditions[] .= 'Id IN ('.array_map( function(){return '?';}, $monitor_ids ) . ')'; - array_push( $values, $monitor_ids ); - $frameSql .= ' AND E.MonitorId IN ('.$user['MonitorIds'].')'; + $eventsSql .= ' AND M.Id IN ('.$user['MonitorIds'].')'; + $frameSql .= ' AND E.MonitorId IN ('.$user['MonitorIds'].')'; } if ( $monitor_id ) { - $conditions[] = 'Id=?'; - $values[] = $monitor_id; $eventsSql .= ' AND M.Id='.$monitor_id; - $frameSql .= ' AND E.MonitorId='.$monitor_id; + $frameSql .= ' AND E.MonitorId='.$monitor_id; } // Parse input parameters -- note for future, validate/clean up better in case we don't get called from self. @@ -160,7 +102,6 @@ if ( $monitor_id ) { // The default (nothing at all specified) is for 1 hour so we do not read the whole database - if ( !isset($_REQUEST['minTime']) && !isset($_REQUEST['maxTime']) ) { $time = time(); $maxTime = strftime("%FT%T",$time); @@ -180,7 +121,7 @@ if ( (strtotime($maxTime) - strtotime($minTime))/(365*24*3600) > 30 ) { $maxTime = null; } -$fitMode=1; +$fitMode = 1; if (isset($_REQUEST['fit']) && $_REQUEST['fit']=='0' ) $fitMode = 0; @@ -196,7 +137,7 @@ if ( isset($_REQUEST['speed']) ) else $defaultSpeed = 1; -$speedIndex=5; // default to 1x +$speedIndex = 5; // default to 1x for ( $i = 0; $i < count($speeds); $i++ ) { if ( $speeds[$i] == $defaultSpeed ) { $speedIndex = $i; @@ -225,10 +166,10 @@ if ( isset($minTime) && isset($maxTime) ) { } $frameSql .= ' GROUP BY E.Id, E.MonitorId, F.TimeStamp, F.Delta ORDER BY E.MonitorId, F.TimeStamp ASC'; - $monitors = array(); -$monitorsSql = 'SELECT * FROM Monitors' . ( count($conditions) ? ' WHERE ' . implode(' AND ', $conditions ) : '' ).' ORDER BY Sequence ASC'; -foreach( dbFetchAll( $monitorsSql, null, $values ) as $row ) { +foreach( $displayMonitors as &$row ) { + if ( $row['Function'] == 'None' ) + continue; $Monitor = new Monitor( $row ); $monitors[] = $Monitor; } @@ -243,32 +184,7 @@ xhtmlHeaders(__FILE__, translate('MontageReview') );