From 39db8136b3556d818a61e3c0f487eaf862ea9e13 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 26 May 2018 13:01:30 -0400 Subject: [PATCH 001/310] move pre_event_images init to constructor, getting rid of static_undef tests. Initialize timestamps to 0. Clean up some logic in Analyze --- src/zm_monitor.cpp | 67 +++++++++++++++++++++++----------------------- src/zm_monitor.h | 2 ++ 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 20f9c9421..06b916dc2 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -617,8 +617,13 @@ bool Monitor::connect() { pre_event_buffer = new Snapshot[pre_event_buffer_count]; for ( int i = 0; i < pre_event_buffer_count; i++ ) { pre_event_buffer[i].timestamp = new struct timeval; + *pre_event_buffer[i].timestamp = {0,0}; pre_event_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder()); } + + timestamps = new struct timeval *[pre_event_count]; + images = new Image *[pre_event_count]; + last_signal = shared_data->signal; } Debug(3, "Success connecting"); return true; @@ -1234,11 +1239,11 @@ 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 ); + return false; } struct timeval now; - gettimeofday( &now, NULL ); + gettimeofday(&now, NULL); if ( image_count && fps_report_interval && !(image_count%fps_report_interval) ) { if ( now.tv_sec != last_fps_time ) { @@ -1261,6 +1266,7 @@ bool Monitor::Analyse() { int index; if ( adaptive_skip ) { + // I think the idea behind adaptive skip is if we are falling behind, then skip a bunch, but not all int read_margin = shared_data->last_read_index - shared_data->last_write_index; if ( read_margin < 0 ) read_margin += image_buffer_count; @@ -1274,7 +1280,10 @@ 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 { @@ -1294,17 +1303,17 @@ bool Monitor::Analyse() { if ( shared_data->action ) { // Can there be more than 1 bit set in the action? Shouldn't these be elseifs? if ( shared_data->action & RELOAD ) { - Info( "Received reload indication at count %d", image_count ); + Info("Received reload indication at count %d", image_count); shared_data->action &= ~RELOAD; Reload(); } if ( shared_data->action & SUSPEND ) { if ( Active() ) { - Info( "Received suspend indication at count %d", image_count ); + Info("Received suspend indication at count %d", image_count); shared_data->active = false; //closeEvent(); } else { - Info( "Received suspend indication at count %d, but wasn't active", image_count ); + Info("Received suspend indication at count %d, but wasn't active", image_count); } if ( config.max_suspend_time ) { auto_resume_time = now.tv_sec + config.max_suspend_time; @@ -1313,7 +1322,7 @@ bool Monitor::Analyse() { } if ( shared_data->action & RESUME ) { if ( Enabled() && !Active() ) { - Info( "Received resume indication at count %d", image_count ); + Info("Received resume indication at count %d", image_count); shared_data->active = true; ref_image = *snap_image; ready_count = image_count+(warmup_count/2); @@ -1324,24 +1333,14 @@ bool Monitor::Analyse() { } // end if shared_data->action if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) { - Info( "Auto resuming at count %d", image_count ); + Info("Auto resuming at count %d", image_count); shared_data->active = true; ref_image = *snap_image; ready_count = image_count+(warmup_count/2); auto_resume_time = 0; } - static bool static_undef = true; static int last_section_mod = 0; - static bool last_signal; - - if ( static_undef ) { -// Sure would be nice to be able to assume that these were already initialized. It's just 1 compare/branch, but really not neccessary. - static_undef = false; - timestamps = new struct timeval *[pre_event_count]; - images = new Image *[pre_event_count]; - last_signal = shared_data->signal; - } if ( Enabled() ) { bool signal = shared_data->signal; @@ -1394,27 +1393,24 @@ bool Monitor::Analyse() { } else if ( signal && Active() && (function == MODECT || function == MOCORD) ) { Event::StringSet zoneSet; - int motion_score = last_motion_score; if ( !(image_count % (motion_frame_skip+1) ) ) { // Get new score. - motion_score = DetectMotion(*snap_image, zoneSet); + int new_motion_score = DetectMotion(*snap_image, zoneSet); Debug(3, "After motion detection, last_motion_score(%d), new motion score(%d)", - last_motion_score, motion_score + last_motion_score, new_motion_score ); - // Why are we updating the last_motion_score too? - last_motion_score = motion_score; + last_motion_score = new_motion_score; } - //int motion_score = DetectBlack( *snap_image, zoneSet ); - if ( motion_score ) { + if ( last_motion_score ) { if ( !event ) { - score += motion_score; + score += last_motion_score; if ( cause.length() ) cause += ", "; cause += MOTION_CAUSE; } else { - score += motion_score; + score += last_motion_score; } noteSetMap[MOTION_CAUSE] = zoneSet; } // end if motion_score @@ -1436,7 +1432,7 @@ bool Monitor::Analyse() { first_link = false; } } - noteSet.insert( linked_monitors[i]->Name() ); + noteSet.insert(linked_monitors[i]->Name()); score += 50; } } else { @@ -1450,14 +1446,15 @@ bool Monitor::Analyse() { //TODO: What happens is the event closes and sets recording to false then recording to true again so quickly that our capture daemon never picks it up. Maybe need a refresh flag? if ( (!signal_change && signal) && (function == RECORD || function == MOCORD) ) { 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 && ( timestamp->tv_sec >= 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(3, + "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 ) { @@ -1480,7 +1477,7 @@ bool Monitor::Analyse() { if ( ! event ) { // Create event - event = new Event( this, *timestamp, "Continuous", noteSetMap, videoRecording ); + event = new Event(this, *timestamp, "Continuous", noteSetMap, videoRecording); shared_data->last_event = event->Id(); //set up video store data snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); @@ -1554,6 +1551,10 @@ bool Monitor::Analyse() { int pre_index; int pre_event_images = pre_event_count; +if ( event ) { +// SHouldn't be able to happen because +Error("Creating new event when one exists"); +} if ( analysis_fps && pre_event_count ) { // If analysis fps is set, // compute the index for pre event images in the dedicated buffer diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 6eb40a7a2..2d7678466 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -288,6 +288,8 @@ protected: Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected bool embed_exif; // Whether to embed Exif data into each image frame or not + bool last_signal; + double fps; unsigned int last_camera_bytes; From 84db1be31d989e31ba18133097324d9c15e5f3ff Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 26 May 2018 13:01:51 -0400 Subject: [PATCH 002/310] add version test to get rid of deprecation warning for refcounted_frames --- src/zm_videostore.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 7d5d2de59..a98aecf34 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -524,7 +524,10 @@ bool VideoStore::setup_resampler() { 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; +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) +#else audio_out_ctx->refcounted_frames = 1; +#endif if (audio_out_codec->supported_samplerates) { int found = 0; From 64d5f180c84aa7fa5a0e8617101d882ba4f22416 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 26 May 2018 13:03:13 -0400 Subject: [PATCH 003/310] add Disconnect to Close --- src/zm_remote_camera_http.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_remote_camera_http.h b/src/zm_remote_camera_http.h index a1ed4a267..c3be50885 100644 --- a/src/zm_remote_camera_http.h +++ b/src/zm_remote_camera_http.h @@ -58,7 +58,7 @@ public: int Capture( Image &image ); int PostCapture(); int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) {return 0;}; - int Close() { return 0; }; + int Close() { Disconnect(); return 0; }; }; #endif // ZM_REMOTE_CAMERA_HTTP_H From 20bef5f381983b485b1d2982e13cdf5f593a65a2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 7 Jun 2018 09:57:54 -0400 Subject: [PATCH 004/310] Remove unused Server loading and debug statements --- scripts/zmdc.pl.in | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index b65388892..8eaf7de4c 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -246,6 +246,8 @@ our $zm_terminate = 0; sub run { my $fd = 0; + + # THis also closes dbh and CLIENT and SERVER while( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) { POSIX::close($fd++); } @@ -287,29 +289,23 @@ sub run { my $win = $rin; my $ein = $win; my $timeout = 1; - my $Server = undef; my $secs_count = 0; - if ( $Config{ZM_SERVER_ID} ) { - require ZoneMinder::Server; - $Server = new ZoneMinder::Server($Config{ZM_SERVER_ID}); - dPrint(ZoneMinder::Logger::INFO, 'Loading Server record have ' . $$Server{Name}); - } - while( !$zm_terminate ) { if ( $Config{ZM_SERVER_ID} ) { if ( ! ( $secs_count % 60 ) ) { - Debug("Connecting"); while ( (!$zm_terminate) and !($dbh and $dbh->ping()) ) { - Warning("Not connected to db ($dbh)".($dbh?" ping(".$dbh->ping().")":''). ($DBI::errstr?" errstr($DBI::errstr)":'').' Reconnecting'); + Warning("Not connected to db ($dbh)".($dbh?' ping('.$dbh->ping().')':''). ($DBI::errstr?" errstr($DBI::errstr)":'').' Reconnecting'); $dbh = zmDbConnect(); } + last if $zm_terminate; + my @cpuload = CpuLoad(); Debug("Updating Server record @cpuload"); if ( ! defined $dbh->do(q{UPDATE Servers SET Status=?,CpuLoad=?,TotalMem=?,FreeMem=?,TotalSwap=?,FreeSwap=? WHERE Id=?}, undef, 'Running', $cpuload[0], &totalmem, &freemem, &totalswap, &freeswap, $Config{ZM_SERVER_ID} ) ) { - Error("Failed Updating status of Server record for Id=$Config{ZM_SERVER_ID}".$dbh->errstr()); + Error("Failed Updating status of Server record for Id=$Config{ZM_SERVER_ID} :".$dbh->errstr()); } } $secs_count += 1; @@ -377,7 +373,7 @@ Debug("check_for_processes_to_kill"); } # end while dPrint(ZoneMinder::Logger::INFO, 'Server exiting at ' - .strftime( '%y/%m/%d %H:%M:%S', localtime() ) + .strftime('%y/%m/%d %H:%M:%S', localtime()) ."\n" ); if ( $Config{ZM_SERVER_ID} ) { @@ -436,14 +432,10 @@ sub start { my $sigset = POSIX::SigSet->new; my $blockset = POSIX::SigSet->new(SIGCHLD); - Debug("Blocking SIGCHLD"); sigprocmask(SIG_BLOCK, $blockset, $sigset) or Fatal("Can't block SIGCHLD: $!"); - Debug("forking"); if ( my $cpid = fork() ) { - Debug("before logReinit"); # This logReinit is required. Not sure why. logReinit(); - Debug("aftere logReinit"); $process->{pid} = $cpid; $process->{started} = time(); @@ -456,7 +448,6 @@ sub start { $cmd_hash{$process->{command}} = $pid_hash{$cpid} = $process; sigprocmask(SIG_SETMASK, $sigset) or Fatal("Can't restore SIGCHLD: $!"); - Debug("unblocking child"); } elsif ( defined($cpid) ) { # Force reconnection to the db. $dbh = zmDbConnect(1); @@ -519,7 +510,7 @@ sub send_stop { .strftime('%y/%m/%d %H:%M:%S', localtime()) ."\n" ); - sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; + sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; return(); } @@ -704,14 +695,13 @@ sub reaper { $process->{delay} = $Config{ZM_MAX_RESTART_DELAY}; } } - Debug("Delay for $$process{command} is now $$process{delay}"); + #Debug("Delay for $$process{command} is now $$process{delay}"); } else { delete $cmd_hash{$$process{command}}; } } # end while waitpid $SIG{CHLD} = \&reaper; $! = $saved_status; - Debug("Leaving reaper"); } sub restartPending { @@ -723,7 +713,6 @@ sub restartPending { start($process->{daemon}, @{$process->{args}}); } } - Debug("done restartPending"); } sub shutdownAll { From 26b2887d6bb9134fa76313e61782205f317888d9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 8 Jul 2018 15:21:07 -0400 Subject: [PATCH 005/310] Add Export Matches --- web/lang/en_gb.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 9042fe839..82b270a79 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -324,6 +324,7 @@ $SLANG = array( 'Exclude' => 'Exclude', 'Execute' => 'Execute', 'ExportDetails' => 'Export Event Details', + 'ExportMatches' => 'Export Matches', 'Exif' => 'Embed EXIF data into image', 'Export' => 'Export', 'DownloadVideo' => 'Download Video', From 639f9161569b73163bbadd955b7148fb6ddbe60b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 09:59:03 -0400 Subject: [PATCH 006/310] spacing --- web/ajax/event.php | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/web/ajax/event.php b/web/ajax/event.php index 04ebca9ad..585f327a1 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -1,10 +1,10 @@ $exportFile ) ); + if ( $exportFile = exportEvents( + $exportIds, + (isset($_REQUEST['connkey'])?$_REQUEST['connkey']:''), + $exportDetail, + $exportFrames, + $exportImages, + $exportVideo, + $exportMisc, + $exportFormat + ) ) + ajaxResponse(array('exportFile'=>$exportFile)); else - ajaxError( 'Export Failed' ); + ajaxError('Export Failed'); break; } case 'download' : From e85c16aba6459b83c78bb560243074d9ecd89fa7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 11:58:30 -0400 Subject: [PATCH 007/310] Cleanups, add video viewing --- .../classic/includes/export_functions.php | 641 +++++++++--------- 1 file changed, 335 insertions(+), 306 deletions(-) diff --git a/web/skins/classic/includes/export_functions.php b/web/skins/classic/includes/export_functions.php index 7750f165b..a5cc8f9dd 100644 --- a/web/skins/classic/includes/export_functions.php +++ b/web/skins/classic/includes/export_functions.php @@ -18,21 +18,19 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -function exportHeader( $title ) -{ +function exportHeader($title) { ?> - - - - <?php echo $title ?> - - - + + - + }); + // ]]> + + Id() ); +function exportEventDetail($event, $exportFrames, $exportImages) { + ob_start(); + exportHeader(translate('Event').' '.$event->Id()); $otherlinks = ''; if( $exportFrames ) $otherlinks .= ''.translate('Frames').','; if( $exportImages ) $otherlinks .= ''.translate('Images').','; $otherlinks = substr($otherlinks,0,-1); - - ?>
@@ -139,22 +133,20 @@ function exportEventDetail( $event, $exportFrames, $exportImages ) Id() ) ); +function exportEventFrames($event, $exportDetail, $exportImages) { + $sql = 'SELECT *, unix_timestamp( TimeStamp ) AS UnixTimeStamp FROM Frames WHERE EventID = ? ORDER BY FrameId'; + $frames = dbFetchAll($sql, NULL, array($event->Id())); - ob_start(); - exportHeader( translate('Frames')." ".$event->Id() ); + ob_start(); + exportHeader(translate('Frames').' '.$event->Id()); $otherlinks = ''; if( $exportDetail ) $otherlinks .= ''.translate('Event').','; if( $exportImages ) $otherlinks .= ''.translate('Images').','; $otherlinks = substr($otherlinks,0,-1); - ?>
@@ -168,56 +160,49 @@ function exportEventFrames( $event, $exportDetail, $exportImages ) Path(); - foreach ( $frames as $frame ) - { - $imageFile = sprintf( "%0".ZM_EVENT_IMAGE_DIGITS."d-capture.jpg", $frame['FrameId'] ); - $imagePath = $eventPath."/".$imageFile; - $analImage = preg_replace( "/capture/", "analyse", $imagePath ); - if ( file_exists( $analImage ) ) - { - $imageFile = preg_replace( "/capture/", "analyse", $imageFile ); - } + if ( count($frames) ) { + $eventPath = $event->Path(); + foreach ( $frames as $frame ) { + $imageFile = sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d-capture.jpg', $frame['FrameId']); + $imagePath = $eventPath.'/'.$imageFile; + $analImage = preg_replace('/capture/', 'analyse', $imagePath); + if ( file_exists($analImage) ) { + $imageFile = preg_replace('/capture/', 'analyse', $imageFile); + } - $class = strtolower($frame['Type']); + $class = strtolower($frame['Type']); ?> - - + + - Frame <?php echo $frame['FrameId'] ?> + Frame <?php echo $frame['FrameId'] ?>
@@ -225,13 +210,12 @@ function exportEventFrames( $event, $exportDetail, $exportImages ) Id() ); +function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist) { + ob_start(); + exportHeader(translate('Images').' '.$event->Id()); $otherlinks = ''; if( $exportDetail ) $otherlinks .= ''.translate('Event').','; @@ -257,28 +241,53 @@ function exportEventImages( $event, $exportDetail, $exportFrames, $myfilelist )

: Name()) ?> ()

+DefaultVideo() ) { + // videojs zoomrotate only when direct recording + $Zoom = 1; + $Rotation = 0; + if ( $Monitor->VideoWriter() == '2' ) { + # Passthrough + $Rotation = $Event->Orientation(); + if ( in_array($Event->Orientation(),array('90','270')) ) + $Zoom = $Event->Height()/$Event->Width(); + } +?> +
+ +
+ - +

- + + + + + +
 
 
-
-
+ onmousedown="slide(event,'horizontal', Width()-20)?>, 1, , ,0, 'imageslider_display_id');"> 
+ + +
- - - +

Master

- - + +
-
-

All

- $eids
"; - } - ?> - -
- + + + -
+
+

All

+SaveJPEGs() ) { +?> +
+
- "; - echo "

Monitor: " . $monitorNames[$monitor] . "

"; - foreach ($eids as $eid) { - if ($eventMonitorId[$eid] == $monitor) { - ?> -
- "; + echo '

Monitor: ' . $monitorNames[$monitor] . '

'; + foreach ( $eids as $eid ) { + if ( $eventMonitorId[$eid] == $monitor ) { +?> +
+'; } - ?> - -
- - - -
+ echo ''; + } # end foreach monitor +?> +
+ +
- - + + Path(); + $eventPath = $event->Path(); + $eventRelativePath = $event->Relative_Path(); $files = array(); if ( $dir = opendir($eventPath) ) { while ( ($file = readdir($dir)) !== false ) { @@ -754,29 +756,29 @@ function exportFileList( $eid, $exportDetail, $exportFrames, $exportImages, $exp if ( $exportDetail ) { $file = 'zmEventDetail.html'; - if ( !($fp = fopen( $eventPath.'/'.$file, 'w' )) ) { - Fatal( "Can't open event detail export file '$file'" ); + if ( !($fp = fopen($eventPath.'/'.$file, 'w')) ) { + Fatal("Can't open event detail export file '$file'"); } - fwrite( $fp, exportEventDetail( $event, $exportFrames, $exportImages ) ); - fclose( $fp ); - $exportFileList[$file] = $eventPath."/".$file; + fwrite($fp, exportEventDetail($event, $exportFrames, $exportImages)); + fclose($fp); + $exportFileList[$file] = $event->Id().'/'.$file; } if ( $exportFrames ) { $file = 'zmEventFrames.html'; - if ( !($fp = fopen( $eventPath.'/'.$file, 'w' )) ) { - Fatal( "Can't open event frames export file '$file'" ); + if ( !($fp = fopen($eventPath.'/'.$file, 'w')) ) { + Fatal("Can't open event frames export file '$file'"); } - fwrite( $fp, exportEventFrames( $event, $exportDetail, $exportImages ) ); - fclose( $fp ); - $exportFileList[$file] = $eventPath."/".$file; + fwrite($fp, exportEventFrames($event, $exportDetail, $exportImages)); + fclose($fp); + $exportFileList[$file] = $event->Id().'/'.$file; } + if ( $exportImages ) { $filesLeft = array(); $myfilelist = array(); foreach ( $files as $file ) { - if ( preg_match( '/-(?:capture|analyse).jpg$/', $file ) ) { - $exportFileList[$file] = $eventPath."/".$file; - $myfilelist[$file] = $eventPath."/".$file; + if ( preg_match('/-(?:capture|analyse).jpg$/', $file ) ) { + $myfilelist[$file] = $exportFileList[$file] = $event->Id().'/'.$file; } else { $filesLeft[$file] = $file; } @@ -786,113 +788,140 @@ function exportFileList( $eid, $exportDetail, $exportFrames, $exportImages, $exp // create an image slider if ( !empty($myfilelist) ) { $file = 'zmEventImages.html'; - if ( !($fp = fopen($eventPath.'/'.$file, 'w')) ) Fatal( "Can't open event images export file '$file'" ); - fwrite( $fp, exportEventImages( $event, $exportDetail, $exportFrames, $myfilelist ) ); - fclose( $fp ); - $exportFileList[$file] = $eventPath."/".$file; + if ( !($fp = fopen($event->Id().'/'.$file, 'w')) ) + Fatal("Can't open event images export file '$file'"); + fwrite($fp, exportEventImages($event, $exportDetail, $exportFrames, $myfilelist)); + fclose($fp); + $exportFileList[$file] = $event->Id().'/'.$file; } } # end if exportImages if ( $exportVideo ) { $filesLeft = array(); foreach ( $files as $file ) { - if ( preg_match( '/\.(?:mpg|mpeg|mov|swf|mp4|mkv|avi|asf|3gp)$/', $file ) ) { - $exportFileList[$file] = $eventPath.'/'.$file; + if ( preg_match('/\.(?:mpg|mpeg|mov|swf|mp4|mkv|avi|asf|3gp)$/', $file) ) { + $exportFileList[$file] = $event->Id().'/'.$file; } else { $filesLeft[$file] = $file; } } $files = $filesLeft; - } # end if exportVideo + } # end if exportVideo if ( $exportMisc ) { foreach ( $files as $file ) { - $exportFileList[$file] = $eventPath.'/'.$file; + $exportFileList[$file] = $event->Id().'/'.$file; } $files = array(); } return array_values($exportFileList); -} +} # end exportFileList() -function exportEvents( $eids, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc, $exportFormat, $exportStructure = false ) { +function exportEvents( + $eids, + $connkey, + $exportDetail, + $exportFrames, + $exportImages, + $exportVideo, + $exportMisc, + $exportFormat, + $exportStructure = false +) { - if ( (!canView('Events')) || empty($eids) ) { + if ( !canView('Events') ) { + Error("You do not have permission to view events."); + return false; + } else if ( empty($eids) ) { + Error("Attempt to export an empty list of events."); return false; } - $export_root = 'zmExport'; - $export_listFile = 'zmFileList.txt'; - $exportFileList = array(); - $html_eventMaster = ''; - - if ( is_array($eids) ) { - foreach ( $eids as $eid ) { - $exportFileList = array_merge( $exportFileList, exportFileList( $eid , $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc ) ); - } - } else { - $eid = $eids; - $exportFileList = exportFileList( $eid, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc ); - } - - // create an master image slider - if ( $exportImages ) { - if ( !is_array($eids) ) { - $eids = array($eids); - } - $monitorPath = ZM_DIR_EVENTS.'/'; - $html_eventMaster = 'zmEventImagesMaster_'.date('Ymd_His'). '.html'; - if ( !($fp = fopen( $monitorPath.'/'.$html_eventMaster, 'w' )) ) Fatal( "Can't open event images export file '$html_eventMaster'" ); - fwrite($fp, exportEventImagesMaster($eids)); - fclose($fp); - $exportFileList[] = $monitorPath.'/'.$html_eventMaster; - } + # Ensure that we are going to be able to do this. if ( ! file_exists(ZM_DIR_EXPORTS) ) { if ( ! mkdir(ZM_DIR_EXPORTS) ) { Fatal("Can't create exports dir at '".ZM_DIR_EXPORTS."'"); } } - $listFile = ZM_DIR_EXPORTS.'/'.$export_listFile; + $export_dir = ZM_DIR_EXPORTS.'/zmExport'.$connkey; + + # Ensure that we are going to be able to do this. + if ( ! file_exists($export_dir) ) { + if ( ! mkdir($export_dir) ) { + Fatal("Can't create exports dir at '$export_dir'"); + } else { + Logger::Debug("Successfully created '$export_dir'"); + } + } + + $export_root = 'zmExport'; + $export_listFile = 'zmFileList.txt'; + $exportFileList = array(); + $html_eventMaster = ''; + + if ( !is_array($eids) ) { + $eids = array($eids); + } + foreach ( $eids as $eid ) { + $event = new Event($eid); + $exportFileList = array_merge($exportFileList, exportFileList($event, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc)); + exec("cp -as ".$event->Path()." $export_dir/"); + } + if ( !symlink(ZM_PATH_WEB.'/'.ZM_SKIN_PATH.'/js/jquery.js', $export_dir.'/jquery.js') ) + Error("Failed linking jquery.js"); + if ( !symlink(ZM_PATH_WEB.'/'.ZM_SKIN_PATH.'/js/video.js', $export_dir.'/video.js') ) + Error("Failed linking video.js"); + + // create an master image + if ( $exportImages ) { + $html_eventMaster_file = 'zmEventImagesMaster_'.date('Ymd_His'). '.html'; + $html_eventMaster_path = $export_dir.'/'.$html_eventMaster_file; + + if ( !($fp = fopen($html_eventMaster_path, 'w')) ) + Fatal("Can't open event images export file '$html_eventMaster_path'"); + fwrite($fp, exportEventImagesMaster($eids)); + fclose($fp); + $exportFileList[] = $html_eventMaster_file; + } + + $listFile = $export_dir.'/'.$export_listFile; if ( !($fp = fopen($listFile, 'w')) ) { - Fatal( "Can't open event export list file '$listFile'" ); + Fatal("Can't open event export list file '$listFile'"); } foreach ( $exportFileList as $exportFile ) { - fwrite( $fp, "$exportFile\n" ); + $exportFile = 'zmExport'.$connkey.'/'.$exportFile; + fwrite($fp, "$exportFile\n"); } - fclose( $fp ); + fwrite($fp, "$listFile\n"); + fclose($fp); + $archive = ''; if ( $exportFormat == 'tar' ) { - $archive = ZM_DIR_EXPORTS.'/'.$export_root.'.tar.gz'; - @unlink( $archive ); - if ( $exportStructure == 'flat' ) { //strip file paths if we choose - $command = "nice -10 tar --create --gzip --file=".escapeshellarg($archive)." --files-from=".escapeshellarg($listFile)." --xform='s#^.+/##x'"; - } else { - $command = "nice -10 tar --create --gzip --file=".escapeshellarg($archive)." --files-from=".escapeshellarg($listFile); - } - exec( $command, $output, $status ); - if ( $status ) { - Error( "Command '$command' returned with status $status" ); - if ( $output[0] ) - Error( "First line of output is '".$output[0]."'" ); - return( false ); + $archive = ZM_DIR_EXPORTS.'/'.$export_root.'_'.$connkey.'.tar.gz'; + @unlink($archive); + chdir(ZM_DIR_EXPORTS); + $command = 'nice -10 tar --create --gzip --dereference --file='.escapeshellarg($archive).' zmExport' . $connkey; + #$command = 'nice -10 tar --create --gzip --file='.escapeshellarg($archive).' --files-from='.escapeshellarg($listFile); + if ( $exportStructure == 'flat' ) { + //strip file paths if we + $command .= " --xform='s#^.+/##x'"; } } elseif ( $exportFormat == 'zip' ) { - $archive = ZM_DIR_EXPORTS.'/'.$export_root.'.zip'; - @unlink( $archive ); - if ($exportStructure == 'flat') { - $command = "cat ".escapeshellarg($listFile)." | nice -10 zip -q -j ".escapeshellarg($archive)." -@"; + $archive = $export_dir.'/'.$export_root.'.zip'; + @unlink($archive); + if ( $exportStructure == 'flat' ) { + $command = 'cat '.escapeshellarg($listFile).' | nice -10 zip -q -j '.escapeshellarg($archive).' -@'; } else { - $command = "cat ".escapeshellarg($listFile)." | nice -10 zip -q ".escapeshellarg($archive)." -@"; + $command = 'cat '.escapeshellarg($listFile).' | nice -10 zip -q '.escapeshellarg($archive).' -@'; } - //cat zmFileList.txt | zip -q zm_export.zip -@ - //-bash: zip: command not found + } // if $exportFormat - exec( $command, $output, $status ); - if ( $status ) { - Error("Command '$command' returned with status $status"); - if ( $output[0] ) - Error("First line of output is '".$output[0]."'"); - return false; - } + exec($command, $output, $status); + if ( $status ) { + Error("Command '$command' returned with status $status"); + if ( $output[0] ) + Error("First line of output is '".$output[0]."'"); + return false; } //clean up temporary files @@ -900,5 +929,5 @@ function exportEvents( $eids, $exportDetail, $exportFrames, $exportImages, $expo unlink($monitorPath.'/'.$html_eventMaster); } - return '?view=archive%26type='.$exportFormat; + return '?view=archive%26type='.$exportFormat.'%26connkey='.$connkey; } From 24a5b78f4c17e468dfe5139636b0c1518ee91c8e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 11:59:08 -0400 Subject: [PATCH 008/310] Convert export from a popup to a full page. Add loading events by filter. --- web/skins/classic/views/export.php | 104 ++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 11 deletions(-) diff --git a/web/skins/classic/views/export.php b/web/skins/classic/views/export.php index 54fad211c..20dfe2c11 100644 --- a/web/skins/classic/views/export.php +++ b/web/skins/classic/views/export.php @@ -39,33 +39,115 @@ if ( isset($_SESSION['export']) ) { } $focusWindow = true; +$connkey = isset($_REQUEST['connkey']) ? $_REQUEST['connkey'] : generateConnKey(); xhtmlHeaders(__FILE__, translate('Export') ); ?>
+
+ - -rowCount() . ' events:
'; +$disk_space_total = 0; ?> - + + + + + + + + + + + + + + + + + + + +DefaultScale(), ZM_WEB_DEFAULT_SCALE ), SCALE_BASE ); + echo '\n"; +?> + Archived() ? ' class="archived"' : '' ?>> + + + + + + + + + + + +DiskSpace(); +?> + + + +
'.$event->Id().($event->Archived()?'*':'') ?> '.validHtmlStr($event->Name()).($event->Archived()?'*':'') ?>MonitorId(), 'zmMonitor'.$event->Monitorid(), 'monitor', $event->MonitorName(), canEdit( 'Monitors' ) ) ?>Id(), 'zmEventDetail', 'eventdetail', validHtmlStr($event->Cause()), canEdit( 'Events' ), 'title="'.htmlspecialchars($event->Notes()).'"' ) ?>StartTime())) . +( $event->EndTime() ? ' until ' . strftime(STRF_FMT_DATETIME_SHORTER, strtotime($event->EndTime()) ) : '' ) ?> + Length() ) ?>Id(), 'zmFrames', 'frames', $event->Frames() ) ?>Id(), 'zmFrames', 'frames', $event->AlarmFrames() ) ?>TotScore() ?>AvgScore() ?>MaxScore(); + #makePopupLink('?view=frame&eid='.$event->Id().'&fid=0', 'zmImage', array('image', reScale($event->Width(), $scale), reScale($event->Height(), $scale)), $event->MaxScore()) ?>DiskSpace()) ?>
+ @@ -112,7 +194,7 @@ if ( !empty($_REQUEST['eid']) ) { } if ( !empty($_REQUEST['generated']) ) { ?> - + From 8ca35fa5abbed7ee515a266169bbe4aced923f7f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 11:59:44 -0400 Subject: [PATCH 009/310] add Export button to Filter --- web/skins/classic/views/filter.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/skins/classic/views/filter.php b/web/skins/classic/views/filter.php index 675a178bf..beb5b34a2 100644 --- a/web/skins/classic/views/filter.php +++ b/web/skins/classic/views/filter.php @@ -411,6 +411,7 @@ if ( ZM_OPT_MESSAGE ) {
+ Date: Mon, 9 Jul 2018 12:00:10 -0400 Subject: [PATCH 010/310] Convert export from a popup to a full page. --- web/skins/classic/views/js/events.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/skins/classic/views/js/events.js b/web/skins/classic/views/js/events.js index 1b784eaea..2498950fa 100644 --- a/web/skins/classic/views/js/events.js +++ b/web/skins/classic/views/js/events.js @@ -99,6 +99,7 @@ function downloadVideo( element, name ) { function exportEvents( element, name ) { var form = element.form; + if ( 0 ) { var eids = new Array(); for (var i = 0; i < form.elements.length; i++) { if (form.elements[i].name.indexOf(name) == 0) { @@ -108,6 +109,9 @@ function exportEvents( element, name ) { } } createPopup( '?view=export&'+eids.join( '&' ), 'zmExport', 'export' ); + } + form.action='?view=export'; + form.submit(); } function viewEvents( element, name ) { From 4de7ae8edc425eb28c21df67fc76e71f985e8b42 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 12:01:09 -0400 Subject: [PATCH 011/310] Pass event_ids when reloading after export generation. --- web/skins/classic/views/js/export.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/web/skins/classic/views/js/export.js b/web/skins/classic/views/js/export.js index ce1b00a16..80324a16f 100644 --- a/web/skins/classic/views/js/export.js +++ b/web/skins/classic/views/js/export.js @@ -12,8 +12,8 @@ function configureExportButton( element ) { form.elements['exportButton'].disabled = (checkCount == 0 || radioCount == 0); } -function startDownload( exportFile ) { - window.location.replace( exportFile ); +function startDownload(exportFile) { + window.location.replace(exportFile); } var exportTimer = null; @@ -27,7 +27,17 @@ function exportProgress() { } function exportResponse( respObj, respText ) { - window.location.replace( thisUrl+'?view='+currentView+'&'+eidParm+'&exportFile='+respObj.exportFile+'&generated='+((respObj.result=='Ok')?1:0) ); + console.log(respObj); + console.log(respText); + + var form = $j('#contentForm')[0]; + var eids = new Array(); + for (var i = 0, len=form.elements.length; i < len; i++) { + if ( form.elements[i].name == 'eids[]' ) { + eids[eids.length] = 'eids[]='+form.elements[i].value; + } + } + window.location.replace( thisUrl+'?view='+currentView+'&'+eids.join('&')+'&exportFile='+respObj.exportFile+'&generated='+((respObj.result=='Ok')?1:0) ); } function exportEvent( form ) { From 4c916e880b411ace15985f88255bc661ade4ebfe Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 12:01:25 -0400 Subject: [PATCH 012/310] Pass event_ids when reloading after export generation. --- web/skins/classic/views/js/export.js.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/web/skins/classic/views/js/export.js.php b/web/skins/classic/views/js/export.js.php index fd9d64dc5..963b29bf0 100644 --- a/web/skins/classic/views/js/export.js.php +++ b/web/skins/classic/views/js/export.js.php @@ -1,19 +1,3 @@ - -var eidParm = ''; - -var eidParm = 'eid='; - - var exportReady = ; var exportFile = ''; - var exportProgressString = ''; From e29ed9bc54692b2bffabb3b44bfb1a542e796d7a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 12:01:40 -0400 Subject: [PATCH 013/310] add Export button to Filter --- web/skins/classic/views/js/filter.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/skins/classic/views/js/filter.js b/web/skins/classic/views/js/filter.js index f752dbff7..167eab63b 100644 --- a/web/skins/classic/views/js/filter.js +++ b/web/skins/classic/views/js/filter.js @@ -79,6 +79,12 @@ function submitToEvents( element ) { history.replaceState(null, null, '?view=filter&' + $j(form).serialize()); } +function submitToExport(element) { + var form = element.form; + createPopup('?view=export&filter='+$j(form).serialize(), 'zmExport', 'export' ); + //createPopup('?view=export&filter_id='+form.elements['Id'].value, 'zmExport', 'export' ); +} + function executeFilter( element ) { var form = element.form; form.action = thisUrl + '?view=events'; From 62946578c65d6f0e1e166869deada11727b3ad46 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 9 Jul 2018 12:02:05 -0400 Subject: [PATCH 014/310] Use to determine export filename --- web/views/archive.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/views/archive.php b/web/views/archive.php index 017c1f0a7..047c421a6 100644 --- a/web/views/archive.php +++ b/web/views/archive.php @@ -24,6 +24,7 @@ if ( !canView('Events') ) { } $archivetype = $_REQUEST['type']; +$connkey = isset($_REQUEST['connkey'])?$_REQUEST['connkey']:''; if ( $archivetype ) { switch ($archivetype) { @@ -41,7 +42,7 @@ if ( $archivetype ) { } if ( $mimetype ) { - $filename = "zmExport.$file_ext"; + $filename = "zmExport_$connkey.$file_ext"; $filename_path = ZM_DIR_EXPORTS.'/'.$filename; Logger::Debug("downloading archive from $filename_path"); if ( is_readable($filename_path) ) { @@ -49,7 +50,7 @@ if ( $archivetype ) { header("Content-Disposition: inline; filename=$filename"); header('Content-Length: ' . filesize($filename_path) ); set_time_limit(0); - if ( ! @readfile( $filename_path ) ) { + if ( ! @readfile($filename_path) ) { Error("Error sending $filename_path"); } } else { From e05604e4b340b8b50e1a70b69b78f86b8e970812 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 12 Jul 2018 14:05:23 -0400 Subject: [PATCH 015/310] Alter code style, spacing --- web/ajax/event.php | 64 ++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/web/ajax/event.php b/web/ajax/event.php index 585f327a1..cf8c59aa3 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -9,20 +9,20 @@ if ( canView('Events') ) { case 'video' : { if ( empty($_REQUEST['videoFormat']) ) { - ajaxError( 'Video Generation Failure, no format given' ); + ajaxError('Video Generation Failure, no format given'); } elseif ( empty($_REQUEST['rate']) ) { - ajaxError( 'Video Generation Failure, no rate given' ); + ajaxError('Video Generation Failure, no rate given'); } elseif ( empty($_REQUEST['scale']) ) { - ajaxError( 'Video Generation Failure, no scale given' ); + ajaxError('Video Generation Failure, no scale given'); } else { $sql = 'SELECT E.*,M.Name AS MonitorName,M.DefaultRate,M.DefaultScale FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE E.Id = ?'.monitorLimitSql(); - if ( !($event = dbFetchOne( $sql, NULL, array( $_REQUEST['id'] ) )) ) - ajaxError( 'Video Generation Failure, Unable to load event' ); - else { - if ( $videoFile = createVideo( $event, $_REQUEST['videoFormat'], $_REQUEST['rate'], $_REQUEST['scale'], !empty($_REQUEST['overwrite']) ) ) - ajaxResponse( array( 'response'=>$videoFile ) ); + if ( !($event = dbFetchOne($sql, NULL, array( $_REQUEST['id']))) ) { + ajaxError('Video Generation Failure, Unable to load event'); + } else { + if ( $videoFile = createVideo($event, $_REQUEST['videoFormat'], $_REQUEST['rate'], $_REQUEST['scale'], !empty($_REQUEST['overwrite'])) ) + ajaxResponse(array('response'=>$videoFile)); else - ajaxError( 'Video Generation Failed' ); + ajaxError('Video Generation Failed'); } } $ok = true; @@ -92,59 +92,67 @@ if ( canView('Events') ) { } case 'download' : { - require_once( ZM_SKIN_PATH.'/includes/export_functions.php' ); + require_once(ZM_SKIN_PATH.'/includes/export_functions.php'); $exportVideo = 1; $exportFormat = $_REQUEST['exportFormat']; $exportStructure = 'flat'; $exportIds = !empty($_REQUEST['eids'])?$_REQUEST['eids']:$_REQUEST['id']; - if ( $exportFile = exportEvents( $exportIds, false, false, false, $exportVideo, false, $exportFormat, $exportStructure ) ) - ajaxResponse( array( 'exportFile'=>$exportFile ) ); + if ( $exportFile = exportEvents( + $exportIds, + (isset($_REQUEST['connkey'])?$_REQUEST['connkey']:''), + false,false, false, $exportVideo, false, $exportFormat, $exportStructure ) ) + ajaxResponse(array('exportFile'=>$exportFile)); else - ajaxError( 'Export Failed' ); + ajaxError('Export Failed'); break; } } -} +} // end if canView('Events') -if ( canEdit( 'Events' ) ) { +if ( canEdit('Events') ) { switch ( $_REQUEST['action'] ) { case 'rename' : { if ( !empty($_REQUEST['eventName']) ) - dbQuery( 'UPDATE Events SET Name = ? WHERE Id = ?', array( $_REQUEST['eventName'], $_REQUEST['id'] ) ); + dbQuery('UPDATE Events SET Name = ? WHERE Id = ?', array($_REQUEST['eventName'], $_REQUEST['id'])); else - ajaxError( 'No new event name supplied' ); - ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>true ) ); + ajaxError('No new event name supplied'); + ajaxResponse(array('refreshEvent'=>true, 'refreshParent'=>true)); break; } case 'eventdetail' : { - dbQuery( 'UPDATE Events SET Cause = ?, Notes = ? WHERE Id = ?', array( $_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['id'] ) ); - ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>true ) ); + dbQuery( + 'UPDATE Events SET Cause = ?, Notes = ? WHERE Id = ?', + array($_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $_REQUEST['id']) + ); + ajaxResponse(array('refreshEvent'=>true, 'refreshParent'=>true)); break; } case 'archive' : case 'unarchive' : { $archiveVal = ($_REQUEST['action'] == 'archive')?1:0; - dbQuery( 'UPDATE Events SET Archived = ? WHERE Id = ?', array( $archiveVal, $_REQUEST['id']) ); - ajaxResponse( array( 'refreshEvent'=>true, 'refreshParent'=>false ) ); + dbQuery( + 'UPDATE Events SET Archived = ? WHERE Id = ?', + array($archiveVal, $_REQUEST['id']) + ); + ajaxResponse(array('refreshEvent'=>true, 'refreshParent'=>false)); break; } case 'delete' : { - $Event = new Event( $_REQUEST['id'] ); + $Event = new Event($_REQUEST['id']); if ( ! $Event->Id() ) { - ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true, 'message'=> 'Event not found.' ) ); + ajaxResponse(array('refreshEvent'=>false, 'refreshParent'=>true, 'message'=> 'Event not found.')); } else { $Event->delete(); - ajaxResponse( array( 'refreshEvent'=>false, 'refreshParent'=>true ) ); + ajaxResponse(array('refreshEvent'=>false, 'refreshParent'=>true)); } break; } } -} - -ajaxError( 'Unrecognised action or insufficient permissions' ); +} // end if canEdit('Events') +ajaxError('Unrecognised action or insufficient permissions'); ?> From 95e4341d2836415740c2b43203d5b13b8eeffbb7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 12 Jul 2018 14:05:43 -0400 Subject: [PATCH 016/310] test for existence of DiskSpace in array --- web/includes/Event.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/Event.php b/web/includes/Event.php index d7ce25dc4..18adff226 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -246,7 +246,7 @@ class Event { if ( is_null($new) or ( $new != '' ) ) { $this->{'DiskSpace'} = $new; } - if ( null === $this->{'DiskSpace'} ) { + if ( (!array_key_exists('DiskSpace',$this)) or (null === $this->{'DiskSpace'}) ) { $this->{'DiskSpace'} = folder_size($this->Path()); dbQuery('UPDATE Events SET DiskSpace=? WHERE Id=?', array($this->{'DiskSpace'}, $this->{'Id'})); } From 43cee7c383b1023789f97a2d21c01d9ce68dc0ee Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 12 Jul 2018 14:07:03 -0400 Subject: [PATCH 017/310] make export work and fix links to jquery.js --- .../classic/includes/export_functions.php | 73 +++++++++++-------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/web/skins/classic/includes/export_functions.php b/web/skins/classic/includes/export_functions.php index a5cc8f9dd..545a7eb96 100644 --- a/web/skins/classic/includes/export_functions.php +++ b/web/skins/classic/includes/export_functions.php @@ -26,7 +26,7 @@ function exportHeader($title) { <?php echo $title ?> - - + + @@ -94,7 +92,6 @@ html ul.tabs li.active, html ul.tabs li.active a:hover { }); }); - // ]]> Id()); $otherlinks = ''; - if( $exportDetail ) $otherlinks .= ''.translate('Event').','; - if( $exportImages ) $otherlinks .= ''.translate('Images').','; + if ( $exportDetail ) $otherlinks .= ''.translate('Event').','; + if ( $exportImages ) $otherlinks .= ''.translate('Images').','; $otherlinks = substr($otherlinks,0,-1); ?> @@ -265,7 +262,7 @@ function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist) {
@@ -587,6 +584,33 @@ else if (document.layers) window.onload=start_slider; return ob_get_clean(); } +function eventlist_html($Event) { +?> +
+SaveJPEGs() ) { +?> + + +<?php echo $Event->Id()?> +Id(); } ?> + +DefaultVideo() ) { + if ( ZM_WEB_LIST_THUMBS ) { +?> + + <?php echo $Event->Id()?> + + +
+SaveJPEGs() ) { -?> -
- "; - echo '

Monitor: ' . $monitorNames[$monitor] . '

'; + foreach ($monitors as $monitor_id) { + echo "
"; + echo '

Monitor: ' . $monitorNames[$monitor_id] . '

'; foreach ( $eids as $eid ) { - if ( $eventMonitorId[$eid] == $monitor ) { -?> -
-MonitorId() == $monitor_id ) { + eventlist_html($Event); + } # end if its the right monitor + } # end foreach event echo '
'; } # end foreach monitor ?> diff --git a/web/skins/classic/views/export.php b/web/skins/classic/views/export.php index 1d205337d..92b26b14a 100644 --- a/web/skins/classic/views/export.php +++ b/web/skins/classic/views/export.php @@ -125,14 +125,15 @@ $disk_space_total = 0;
DefaultScale(), ZM_WEB_DEFAULT_SCALE ), SCALE_BASE ); echo '\n"; ?> Archived() ? ' class="archived"' : '' ?>> - - + + + + + +
'.$event->Id().($event->Archived()?'*':'') ?> '.validHtmlStr($event->Name()).($event->Archived()?'*':'') ?>Id().($event->Archived()?'*':'') ?>Name()).($event->Archived()?'*':'') ?> MonitorId(), 'zmMonitor'.$event->Monitorid(), 'monitor', $event->MonitorName(), canEdit( 'Monitors' ) ) ?> Id(), 'zmEventDetail', 'eventdetail', validHtmlStr($event->Cause()), canEdit( 'Events' ), 'title="'.htmlspecialchars($event->Notes()).'"' ) ?> StartTime())) . @@ -149,6 +150,7 @@ while ( $event_row = dbFetchNext($results) ) { DiskSpace(); + $event_count += 1; ?> DiskSpace()) ?>
events
From 700b3c33a09298f866ce4f66c5de10e0e84596da Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 25 Sep 2018 16:09:50 -0400 Subject: [PATCH 082/310] fix command line when using .zip --- web/skins/classic/includes/export_functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/includes/export_functions.php b/web/skins/classic/includes/export_functions.php index a21f19534..02789249b 100644 --- a/web/skins/classic/includes/export_functions.php +++ b/web/skins/classic/includes/export_functions.php @@ -947,7 +947,7 @@ function exportEvents( } elseif ( $exportFormat == 'zip' ) { $archive = ZM_DIR_EXPORTS.'/'.$export_root.($connkey?'_'.$connkey:'').'.zip'; $command = 'zip '; - $command = ($exportStructure == 'flat' ? ' -j ' : ' -r ' ).escapeshellarg($archive); + $command .= ($exportStructure == 'flat' ? ' -j ' : ' -r ' ).escapeshellarg($archive); $command .= $exportCompressed ? ' -9' : ' -0'; } else { Error("No exportFormat specified."); From 2399615a0a9cba2e671ea1cc1cb49d3f37cb3a33 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 26 Sep 2018 14:10:21 -0400 Subject: [PATCH 083/310] When events have the same starttime, sort secondly by Id --- web/skins/classic/views/events.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/events.php b/web/skins/classic/views/events.php index 4c68134ee..b4b46849f 100644 --- a/web/skins/classic/views/events.php +++ b/web/skins/classic/views/events.php @@ -44,7 +44,7 @@ if ( $_REQUEST['filter']['sql'] ) { $countSql .= $_REQUEST['filter']['sql']; $eventsSql .= $_REQUEST['filter']['sql']; } -$eventsSql .= " ORDER BY $sortColumn $sortOrder"; +$eventsSql .= " ORDER BY $sortColumn $sortOrder, Id $sortOrder"; $page = isset($_REQUEST['page']) ? validInt($_REQUEST['page']) : 0; $limit = isset($_REQUEST['limit']) ? validInt($_REQUEST['limit']) : 0; From 7768b4eeef4bacc6dae9ca78c08e6973d5ac3960 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 28 Sep 2018 11:37:16 -0400 Subject: [PATCH 084/310] Don't need to delete videoStore in destructor, as it gets done in CLose() --- src/zm_ffmpeg_camera.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 83d837812..27737f66e 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -134,10 +134,6 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri FfmpegCamera::~FfmpegCamera() { - if ( videoStore ) { - delete videoStore; - videoStore = NULL; - } Close(); if ( capture ) { From d45406bce7779d1eb4be84c35a5903694c102f0c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 28 Sep 2018 11:50:34 -0400 Subject: [PATCH 085/310] ode style and some more debug --- .../ZoneMinder/lib/ZoneMinder/Memory.pm.in | 24 +++-- .../lib/ZoneMinder/Memory/Mapped.pm | 101 ++++++++---------- scripts/zmtrigger.pl.in | 73 +++++++------ 3 files changed, 97 insertions(+), 101 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in index 2529c8786..fc8731cb6 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in @@ -247,11 +247,12 @@ sub zmMemInit { sub zmMemVerify { my $monitor = shift; - if ( !zmMemAttach( $monitor, $mem_size ) ) { - return( undef ); + + if ( !zmMemAttach($monitor, $mem_size) ) { + return undef; } - my $sd_size = zmMemRead( $monitor, 'shared_data:size', 1 ); + my $sd_size = zmMemRead($monitor, 'shared_data:size', 1); if ( $sd_size != $mem_data->{shared_data}->{size} ) { if ( $sd_size ) { Error( "Shared data size conflict in shared_data for monitor " @@ -269,9 +270,9 @@ sub zmMemVerify { .", got ".$sd_size ); } - return( undef ); + return undef; } - my $td_size = zmMemRead( $monitor, 'trigger_data:size', 1 ); + my $td_size = zmMemRead($monitor, 'trigger_data:size', 1); if ( $td_size != $mem_data->{trigger_data}->{size} ) { if ( $td_size ) { Error( "Shared data size conflict in trigger_data for monitor " @@ -290,14 +291,17 @@ sub zmMemVerify { .$td_size ); } - return( undef ); + return undef; } - if ( !zmMemRead($monitor, 'shared_data:valid',1) ) { - Error( "Shared data not valid for monitor $$monitor{Id}" ); - return( undef ); + my $valid = zmMemRead($monitor, 'shared_data:valid',1); + if ( !$valid ) { + Error("Shared data not valid for monitor $$monitor{Id}"); + return undef; + } else { + Debug("Shared data appears vaild for monitor $$monitor{Id}: $valid"); } - return( !undef ); + return !undef; } sub zmMemRead { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm index d782c6c9a..b9a8b6a1c 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm @@ -75,55 +75,50 @@ sub zmMemKey { sub zmMemAttach { my ( $monitor, $size ) = @_; - if ( ! $size ) { - Error( "No size passed to zmMemAttach for monitor $$monitor{Id}\n" ); + + if ( !$size ) { + Error("No size passed to zmMemAttach for monitor $$monitor{Id}"); return undef; } - if ( !defined($monitor->{MMapAddr}) ) { - - my $mmap_file = $Config{ZM_PATH_MAP}.'/zm.mmap.'.$monitor->{Id}; - if ( ! -e $mmap_file ) { - Error( sprintf( "Memory map file '%s' does not exist. zmc might not be running." - , $mmap_file - ) - ); - return undef; - } - my $mmap_file_size = -s $mmap_file; - - if ( $mmap_file_size < $size ) { - Error( sprintf( "Memory map file '%s' should have been %d but was instead %d" - , $mmap_file - , $size - , $mmap_file_size - ) - ); - return undef; - } - my $MMAP; - if ( !open( $MMAP, '+<', $mmap_file ) ) { - Error( sprintf( "Can't open memory map file '%s': $!", $mmap_file ) ); - return undef; - } - my $mmap = undef; - my $mmap_addr = mmap( $mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, $MMAP ); - if ( !$mmap_addr || !$mmap ) { - Error( sprintf( "Can't mmap to file '%s': $!\n", $mmap_file ) ); - close( $MMAP ); - return undef; - } - $monitor->{MMapHandle} = $MMAP; - $monitor->{MMapAddr} = $mmap_addr; - $monitor->{MMap} = \$mmap; + if ( defined($monitor->{MMapAddr}) ) { + Debug("zmMemAttach already attached at $monitor->{MMapAddr}"); + return !undef; } + + my $mmap_file = $Config{ZM_PATH_MAP}.'/zm.mmap.'.$monitor->{Id}; + if ( ! -e $mmap_file ) { + Error("Memory map file '$mmap_file' does not exist. zmc might not be running."); + return undef; + } + my $mmap_file_size = -s $mmap_file; + + if ( $mmap_file_size < $size ) { + Error("Memory map file '$mmap_file' should have been $size but was instead $mmap_file_size"); + return undef; + } + my $MMAP; + if ( !open($MMAP, '+<', $mmap_file) ) { + Error("Can't open memory map file '$mmap_file': $!"); + return undef; + } + my $mmap = undef; + my $mmap_addr = mmap($mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, $MMAP); + if ( !$mmap_addr || !$mmap ) { + Error("Can't mmap to file '$mmap_file': $!"); + close($MMAP); + return undef; + } + $monitor->{MMapHandle} = $MMAP; + $monitor->{MMapAddr} = $mmap_addr; + $monitor->{MMap} = \$mmap; return !undef; -} +} # end sub zmMemAttach sub zmMemDetach { my $monitor = shift; if ( $monitor->{MMap} ) { - if ( ! munmap( ${$monitor->{MMap}} ) ) { + if ( ! munmap(${$monitor->{MMap}}) ) { Warn( "Unable to munmap for monitor $$monitor{Id}\n"); } delete $monitor->{MMap}; @@ -132,7 +127,7 @@ sub zmMemDetach { delete $monitor->{MMapAddr}; } if ( $monitor->{MMapHandle} ) { - close( $monitor->{MMapHandle} ); + close($monitor->{MMapHandle}); delete $monitor->{MMapHandle}; } } @@ -144,13 +139,10 @@ sub zmMemGet { my $mmap = $monitor->{MMap}; if ( !$mmap || !$$mmap ) { - Error( sprintf( "Can't read from mapped memory for monitor '%d', gone away?" - , $monitor->{Id} - ) - ); + Error("Can't read from mapped memory for monitor '$$monitor{Id}', gone away?"); return undef; } - my $data = substr( $$mmap, $offset, $size ); + my $data = substr($$mmap, $offset, $size); return $data; } @@ -162,23 +154,20 @@ sub zmMemPut { my $mmap = $monitor->{MMap}; if ( !$mmap || !$$mmap ) { - Error( sprintf( "Can't write mapped memory for monitor '%d', gone away?" - , $monitor->{Id} - ) - ); - return( undef ); + Error("Can't write mapped memory for monitor '$$monitor{Id}', gone away?"); + return undef; } - substr( $$mmap, $offset, $size ) = $data; - return( !undef ); + substr($$mmap, $offset, $size) = $data; + return !undef; } sub zmMemClean { - Debug( "Removing memory map files\n" ); + Debug("Removing memory map files"); my $mapPath = $Config{ZM_PATH_MAP}.'/zm.mmap.*'; foreach my $mapFile( glob( $mapPath ) ) { ( $mapFile ) = $mapFile =~ /^(.+)$/; - Debug( "Removing memory map file '$mapFile'\n" ); - unlink( $mapFile ); + Debug("Removing memory map file '$mapFile'"); + unlink($mapFile); } } diff --git a/scripts/zmtrigger.pl.in b/scripts/zmtrigger.pl.in index 53492e6a8..7919cc77c 100644 --- a/scripts/zmtrigger.pl.in +++ b/scripts/zmtrigger.pl.in @@ -305,11 +305,11 @@ while( 1 ) { # zmDbConnect will ping and reconnect if neccessary $dbh = zmDbConnect(); } # end while ( 1 ) -Info( "Trigger daemon exiting\n" ); +Info("Trigger daemon exiting"); exit; sub loadMonitors { - Debug( "Loading monitors\n" ); + Debug("Loading monitors"); $monitor_reload_time = time(); my %new_monitors = (); @@ -324,13 +324,15 @@ sub loadMonitors { or Fatal( "Can't execute: ".$sth->errstr() ); while( my $monitor = $sth->fetchrow_hashref() ) { # Check shared memory ok + if ( 0 ) { if ( !zmMemVerify( $monitor ) ) { zmMemInvalidate( $monitor ); next; } - $monitor->{LastState} = zmGetMonitorState( $monitor ); - $monitor->{LastEvent} = zmGetLastEvent( $monitor ); + $monitor->{LastState} = zmGetMonitorState($monitor); + $monitor->{LastEvent} = zmGetLastEvent($monitor); + } $new_monitors{$monitor->{Id}} = $monitor; } # end while fetchrow %monitors = %new_monitors; @@ -348,25 +350,25 @@ sub handleMessage { my $monitor = $monitors{$id}; if ( !$monitor ) { - Warning( "Can't find monitor '$id' for message '$message'\n" ); + Warning("Can't find monitor '$id' for message '$message'"); return; } - Debug( "Found monitor for id '$id'\n" ); + Debug("Found monitor for id '$id'"); - next if ( !zmMemVerify( $monitor ) ); + next if ( !zmMemVerify($monitor) ); - Debug( "Handling action '$action'\n" ); + Debug("Handling action '$action'"); if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ ) { my $state = $1; my $delay = $2; if ( $state eq 'enable' ) { - zmMonitorEnable( $monitor ); + zmMonitorEnable($monitor); } else { - zmMonitorDisable( $monitor ); + zmMonitorDisable($monitor); } # Force a reload $monitor_reload_time = 0; - Info( "Set monitor to $state\n" ); + Info("Set monitor to $state"); if ( $delay ) { my $action_text = $id.'|'.( ($state eq 'enable') ? 'disable' @@ -381,9 +383,9 @@ sub handleMessage { my $delay = $2; my $trigger_data; if ( $trigger eq 'on' ) { - zmTriggerEventOn( $monitor, $score, $cause, $text ); - zmTriggerShowtext( $monitor, $showtext ) if defined($showtext); - Info( "Trigger '$trigger' '$cause'\n" ); + zmTriggerEventOn($monitor, $score, $cause, $text); + zmTriggerShowtext($monitor, $showtext) if defined($showtext); + Info("Trigger '$trigger' '$cause'"); if ( $delay ) { my $action_text = $id.'|cancel'; handleDelay($delay, $connection, $action_text); @@ -393,29 +395,29 @@ sub handleMessage { my $action_text = $id.'|off|0|'.$cause.'|'.$text; handleDelay($delay, $connection, $action_text); } else { - my $last_event = zmGetLastEvent( $monitor ); - zmTriggerEventOff( $monitor ); - zmTriggerShowtext( $monitor, $showtext ) if defined($showtext); - Info( "Trigger '$trigger'\n" ); + my $last_event = zmGetLastEvent($monitor); + zmTriggerEventOff($monitor); + zmTriggerShowtext($monitor, $showtext) if defined($showtext); + Info("Trigger '$trigger'"); # Wait til it's finished - while( zmInAlarm( $monitor ) - && ($last_event == zmGetLastEvent( $monitor )) + while( zmInAlarm($monitor) + && ($last_event == zmGetLastEvent($monitor)) ) { # Tenth of a second - usleep( 100000 ); + usleep(100000); } - zmTriggerEventCancel( $monitor ); + zmTriggerEventCancel($monitor); } } # end if trigger is on or off } elsif( $action eq 'cancel' ) { - zmTriggerEventCancel( $monitor ); - zmTriggerShowtext( $monitor, $showtext ) if defined($showtext); - Info( "Cancelled event\n" ); + zmTriggerEventCancel($monitor); + zmTriggerShowtext($monitor, $showtext) if defined($showtext); + Info("Cancelled event"); } elsif( $action eq 'show' ) { zmTriggerShowtext( $monitor, $showtext ); - Info( "Updated show text to '$showtext'\n" ); + Info("Updated show text to '$showtext'"); } else { - Error( "Unrecognised action '$action' in message '$message'\n" ); + Error("Unrecognised action '$action' in message '$message'"); } } # end sub handleMessage @@ -430,8 +432,9 @@ sub handleDelay { $action_array = $actions{$action_time} = []; } push( @$action_array, { connection=>$connection, message=>$action_text } ); - Debug( "Added timed event '$action_text', expires at $action_time (+$delay secs)\n" ); + Debug("Added timed event '$action_text', expires at $action_time (+$delay secs)"); } + 1; __END__ @@ -506,13 +509,13 @@ B|B|B|B|B|B =back -Note that multiple messages can be sent at once and should be LF or CRLF -delimited. This script is not necessarily intended to be a solution in -itself, but is intended to be used as 'glue' to help ZoneMinder interface -with other systems. It will almost certainly require some customisation -before you can make any use of it. If all you want to do is generate alarms -from external sources then using the ZoneMinder::SharedMem perl module is -likely to be easier. + Note that multiple messages can be sent at once and should be LF or CRLF + delimited. This script is not necessarily intended to be a solution in + itself, but is intended to be used as 'glue' to help ZoneMinder interface + with other systems. It will almost certainly require some customisation + before you can make any use of it. If all you want to do is generate alarms + from external sources then using the ZoneMinder::SharedMem perl module is + likely to be easier. =head1 EXAMPLES From e8316a0aea3c010b848030c15614c8d4d14a9fdc Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 28 Sep 2018 14:19:36 -0400 Subject: [PATCH 086/310] Add detection of packets with massively negative pts. Just skip them --- src/zm_ffmpeg_camera.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 27737f66e..babde5e0e 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -710,9 +710,15 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event return -1; } + if ( packet.pts < -100000 ) { + // Ignore packets that have crazy negative pts. They aren't supposed to happen. + Warning("Ignore packet because pts is massively negative"); + dumpPacket(&packet,"Ignored packet"); + continue; + } int keyframe = packet.flags & AV_PKT_FLAG_KEY; bytes += packet.size; - dumpPacket(&packet); + dumpPacket(&packet,"Captured Packet"); //Video recording if ( recording.tv_sec ) { From 45a1a1b1e854d1c1eb47e1171c848798d0307fab Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 15 Oct 2018 10:51:56 -0400 Subject: [PATCH 087/310] Make packetqueue keep track of audio vs video packet counts. --- src/zm_ffmpeg_camera.cpp | 61 ++++++++++++++++++++++++++++--------- src/zm_ffmpeg_camera.h | 2 +- src/zm_packetqueue.cpp | 65 ++++++++++++++++++++++++---------------- src/zm_packetqueue.h | 20 +++++++------ 4 files changed, 99 insertions(+), 49 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 83d837812..146bd3856 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -82,8 +82,35 @@ static AVPixelFormat get_format(AVCodecContext *avctx, const enum AVPixelFormat } #endif -FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_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 ) : - Camera( p_id, FFMPEG_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 ), +FfmpegCamera::FfmpegCamera( + int p_id, + const std::string &p_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 + ) : + Camera( + p_id, + FFMPEG_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 + ), mPath( p_path ), mMethod( p_method ), mOptions( p_options ) @@ -113,6 +140,7 @@ FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::stri videoStore = NULL; video_last_pts = 0; have_video_keyframe = false; + packetqueue = NULL; #if HAVE_LIBSWSCALE mConvertContext = NULL; @@ -155,12 +183,12 @@ void FfmpegCamera::Terminate() { int FfmpegCamera::PrimeCapture() { if ( mCanCapture ) { - Info( "Priming capture from %s, CLosing", mPath.c_str() ); + Info("Priming capture from %s, Closing", mPath.c_str()); Close(); } mVideoStreamId = -1; mAudioStreamId = -1; - Info( "Priming capture from %s", mPath.c_str() ); + Info("Priming capture from %s", mPath.c_str()); return OpenFfmpeg(); } @@ -170,7 +198,7 @@ int FfmpegCamera::PreCapture() { if ( ! mCanCapture ) return OpenFfmpeg(); // Nothing to do here - return( 0 ); + return 0; } int FfmpegCamera::Capture( Image &image ) { @@ -437,6 +465,7 @@ int FfmpegCamera::OpenFfmpeg() { Debug(3, "Found video stream at index %d", mVideoStreamId); Debug(3, "Found audio stream at index %d", mAudioStreamId); + packetqueue = new zm_packetqueue( mVideoStreamId > mAudioStreamId ? mVideoStreamId : mAudioStreamId ); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) mVideoCodecContext = avcodec_alloc_context3(NULL); @@ -682,6 +711,10 @@ int FfmpegCamera::Close() { delete videoStore; videoStore = NULL; } + if ( packetqueue ) { + delete packetqueue; + packetqueue = NULL; + } return 0; } // end FfmpegCamera::Close @@ -793,14 +826,14 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event ZMPacket *queued_packet; // Clear all packets that predate the moment when the recording began - packetqueue.clear_unwanted_packets( &recording, mVideoStreamId ); + packetqueue->clear_unwanted_packets( &recording, mVideoStreamId ); - while ( ( queued_packet = packetqueue.popPacket() ) ) { + 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() ); + Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue->size() ); if ( avp->stream_index == mVideoStreamId ) { ret = videoStore->writeVideoFramePacket( avp ); have_video_keyframe = true; @@ -834,18 +867,18 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event if ( packet.stream_index == mVideoStreamId ) { if ( keyframe ) { Debug(3, "Clearing queue"); - packetqueue.clearQueue(monitor->GetPreEventCount(), mVideoStreamId); - packetqueue.queuePacket(&packet); - } else if ( packetqueue.size() ) { + packetqueue->clearQueue(monitor->GetPreEventCount(), mVideoStreamId); + packetqueue->queuePacket(&packet); + } else if ( packetqueue->size() ) { // it's a keyframe or we already have something in the queue - packetqueue.queuePacket(&packet); + packetqueue->queuePacket(&packet); } } else if ( packet.stream_index == mAudioStreamId ) { // The following lines should ensure that the queue always begins with a video keyframe //Debug(2, "Have audio packet, reocrd_audio is (%d) and packetqueue.size is (%d)", record_audio, packetqueue.size() ); - if ( 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); } } } // end if recording or not diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index 21eeb328f..067361ae5 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -79,7 +79,7 @@ class FfmpegCamera : public Camera { #endif // HAVE_LIBAVFORMAT VideoStore *videoStore; - zm_packetqueue packetqueue; + zm_packetqueue *packetqueue; bool have_video_keyframe; #if HAVE_LIBSWSCALE diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index 92062eab0..3fc4d72e9 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -21,27 +21,31 @@ #include "zm_ffmpeg.h" #include -#define VIDEO_QUEUESIZE 200 -#define AUDIO_QUEUESIZE 50 - -zm_packetqueue::zm_packetqueue(){ - +zm_packetqueue::zm_packetqueue( int p_max_stream_id ) { + max_stream_id = p_max_stream_id; + packet_counts = new int[max_stream_id+1]; + for ( int i=0; i <= max_stream_id; ++i ) + packet_counts = 0; } zm_packetqueue::~zm_packetqueue() { clearQueue(); + delete[] packet_counts; + packet_counts = NULL; } -bool zm_packetqueue::queuePacket( ZMPacket* zm_packet ) { - pktQueue.push_back( zm_packet ); - +bool zm_packetqueue::queuePacket(ZMPacket* zm_packet) { + pktQueue.push_back(zm_packet); + packet_counts[zm_packet->packet.stream_index] += 1; return true; } -bool zm_packetqueue::queuePacket( AVPacket* av_packet ) { + +bool zm_packetqueue::queuePacket(AVPacket* av_packet) { - ZMPacket *zm_packet = new ZMPacket( av_packet ); + ZMPacket *zm_packet = new ZMPacket(av_packet); - pktQueue.push_back( zm_packet ); + pktQueue.push_back(zm_packet); + packet_counts[zm_packet->packet.stream_index] += 1; return true; } @@ -53,13 +57,14 @@ ZMPacket* zm_packetqueue::popPacket( ) { ZMPacket *packet = pktQueue.front(); pktQueue.pop_front(); + packet_counts[packet->packet.stream_index] -= 1; return packet; } -unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) { +unsigned int zm_packetqueue::clearQueue(unsigned int frames_to_keep, int stream_id) { - Debug(3, "Clearing all but %d frames, queue has %d", frames_to_keep, pktQueue.size() ); + Debug(3, "Clearing all but %d frames, queue has %d", frames_to_keep, pktQueue.size()); frames_to_keep += 1; if ( pktQueue.empty() ) { @@ -74,7 +79,8 @@ unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream ZMPacket *zm_packet = *it; AVPacket *av_packet = &(zm_packet->packet); - Debug(4, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); + Debug(4, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", + av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); // Want frames_to_keep video keyframes. Otherwise, we may not have enough if ( ( av_packet->stream_index == stream_id) ) { @@ -83,21 +89,23 @@ unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream } } - // Make sure we start on a keyframe + // Make sure we start on a keyframe for ( ; it != pktQueue.rend(); ++it ) { ZMPacket *zm_packet = *it; AVPacket *av_packet = &(zm_packet->packet); - Debug(5, "Looking for keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); + Debug(5, "Looking for keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", + av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); // Want frames_to_keep video keyframes. Otherwise, we may not have enough if ( ( av_packet->stream_index == stream_id) && ( av_packet->flags & AV_PKT_FLAG_KEY ) ) { - Debug(4, "Found keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); + Debug(4, "Found keyframe at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", + av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); break; } } if ( frames_to_keep ) { - Debug(3, "Hit end of queue, still need (%d) video frames", frames_to_keep ); + Debug(3, "Hit end of queue, still need (%d) video frames", frames_to_keep); } if ( it != pktQueue.rend() ) { // We want to keep this packet, so advance to the next @@ -105,22 +113,25 @@ unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream } unsigned int delete_count = 0; while ( it != pktQueue.rend() ) { - Debug(4, "Deleting a packet from the front, count is (%d)", delete_count ); + Debug(4, "Deleting a packet from the front, count is (%d)", delete_count); packet = pktQueue.front(); pktQueue.pop_front(); + packet_counts[packet->packet.stream_index] -= 1; delete packet; delete_count += 1; } - Debug(3, "Deleted (%d) packets", delete_count ); + packet = NULL; // tidy up for valgrind + Debug(3, "Deleted %d packets, %d remaining", delete_count, pktQueue.size()); return delete_count; } // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) void zm_packetqueue::clearQueue() { ZMPacket *packet = NULL; - while(!pktQueue.empty()) { + while (!pktQueue.empty()) { packet = pktQueue.front(); + packet_counts[packet->packet.stream_index] -= 1; pktQueue.pop_front(); delete packet; } @@ -130,18 +141,20 @@ unsigned int zm_packetqueue::size() { return pktQueue.size(); } +int zm_packetqueue::packet_count( int stream_id ) { + return packet_counts[stream_id]; +} // end int zm_packetqueue::packet_count( int stream_id ) void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId ) { // Need to find the keyframe <= recording_started. Can get rid of audio packets. - if ( pktQueue.empty() ) { + if ( pktQueue.empty() ) return; - } // Step 1 - find keyframe < recording_started. // Step 2 - pop packets until we get to the packet in step 2 std::list::reverse_iterator it; - Debug(3, "Looking for keyframe after start recording stream id (%d)", mVideoStreamId ); + Debug(3, "Looking for keyframe after start recording stream id (%d)", mVideoStreamId); for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { ZMPacket *zm_packet = *it; AVPacket *av_packet = &(zm_packet->packet); @@ -175,9 +188,11 @@ void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVi //while ( pktQueue.rend() != it ) { packet = pktQueue.front(); pktQueue.pop_front(); + packet_counts[packet->packet.stream_index] -= 1; delete packet; deleted_frames += 1; } + packet = NULL; // tidy up for valgrind zm_packet = pktQueue.front(); av_packet = &(zm_packet->packet); @@ -186,4 +201,4 @@ void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVi } else { Debug(1, "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); } -} +} // end void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVideoStreamId ) diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h index 39160ddfd..fa422a529 100644 --- a/src/zm_packetqueue.h +++ b/src/zm_packetqueue.h @@ -29,23 +29,25 @@ extern "C" { #include } - class zm_packetqueue { public: - zm_packetqueue(); + zm_packetqueue(int max_stream_id); virtual ~zm_packetqueue(); - bool queuePacket( AVPacket* packet, struct timeval *timestamp ); - bool queuePacket( ZMPacket* packet ); - bool queuePacket( AVPacket* packet ); - ZMPacket * popPacket( ); + bool queuePacket(AVPacket* packet, struct timeval *timestamp); + bool queuePacket(ZMPacket* packet); + bool queuePacket(AVPacket* packet); + ZMPacket * popPacket(); bool popVideoPacket(ZMPacket* packet); bool popAudioPacket(ZMPacket* packet); - unsigned int clearQueue( unsigned int video_frames_to_keep, int stream_id ); - void clearQueue( ); + unsigned int clearQueue(unsigned int video_frames_to_keep, int stream_id); + void clearQueue(); unsigned int size(); - void clear_unwanted_packets( timeval *recording, int mVideoStreamId ); + void clear_unwanted_packets(timeval *recording, int mVideoStreamId); + int packet_count(int stream_id); private: std::list pktQueue; + int max_stream_id; + int *packet_counts; /* packet count for each stream_id, to keep track of how many video vs audio packets are in the queue */ }; From 33c903a6440acea934b0114b3a476faad97d5c6f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 15 Oct 2018 11:13:10 -0400 Subject: [PATCH 088/310] properly initialize packet_counts --- src/zm_packetqueue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index 3fc4d72e9..8a14c3a5e 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -25,7 +25,7 @@ zm_packetqueue::zm_packetqueue( int p_max_stream_id ) { max_stream_id = p_max_stream_id; packet_counts = new int[max_stream_id+1]; for ( int i=0; i <= max_stream_id; ++i ) - packet_counts = 0; + packet_counts[i] = 0; } zm_packetqueue::~zm_packetqueue() { From dae31d226b74065df8334e74d139161a0ad3e85b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 15 Oct 2018 11:33:16 -0400 Subject: [PATCH 089/310] add GetImageBufferCount to monitor --- src/zm_monitor.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zm_monitor.h b/src/zm_monitor.h index b98f5953f..2f3b43637 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -456,6 +456,7 @@ public: void SetVideoWriterEventId( unsigned long long p_event_id ) { video_store_data->current_event = p_event_id; } unsigned int GetPreEventCount() const { return pre_event_count; }; + int GetImageBufferCount() const { return image_buffer_count; }; State GetState() const; int GetImage( int index=-1, int scale=100 ); Snapshot *getSnapshot() const; From 990c4b1b45618f211ae55e47eb53a5aef9adefce Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 15 Oct 2018 11:33:58 -0400 Subject: [PATCH 090/310] Add a warning when there are more videoframes in the packetqueue than image_buffers --- src/zm_ffmpeg_camera.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 146bd3856..66391862e 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -867,6 +867,10 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event if ( packet.stream_index == mVideoStreamId ) { if ( keyframe ) { Debug(3, "Clearing queue"); + if ( packetqueue->packet_count(mVideoStreamId) >= monitor->GetImageBufferCount() ) { + Warning("ImageBufferCount is too small. Either increase it or decrease time between keyframes"); + } + packetqueue->clearQueue(monitor->GetPreEventCount(), mVideoStreamId); packetqueue->queuePacket(&packet); } else if ( packetqueue->size() ) { From b0078fb1d9014d68311536b1548bcebabcf71d7c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 16 Oct 2018 17:20:10 -0400 Subject: [PATCH 091/310] whitespace, get rid of redundant ajax startup --- web/skins/classic/views/js/montage.js | 225 +++++++++++++------------- 1 file changed, 112 insertions(+), 113 deletions(-) diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index 55c1ec900..c79e27f5b 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -1,6 +1,9 @@ -var requestQueue = new Request.Queue( { concurrent: monitorData.length, stopOnFailure: false } ); +var requestQueue = new Request.Queue({ + concurrent: monitorData.length, + stopOnFailure: false +}); -function Monitor( monitorData ) { +function Monitor(monitorData) { this.id = monitorData.id; this.connKey = monitorData.connKey; this.url = monitorData.url; @@ -14,36 +17,35 @@ function Monitor( monitorData ) { this.streamCmdTimer = null; this.type = monitorData.type; this.refresh = monitorData.refresh; - this.start = function( delay ) { + this.start = function(delay) { if ( this.streamCmdQuery ) - this.streamCmdTimer = this.streamCmdQuery.delay( delay, this ); + this.streamCmdTimer = this.streamCmdQuery.delay(delay, this); else console.log("No streamCmdQuery"); }; - - this.setStateClass = function( element, stateClass ) { - if ( !element.hasClass( stateClass ) ) { + this.setStateClass = function(element, stateClass) { + if ( !element.hasClass(stateClass) ) { if ( stateClass != 'alarm' ) - element.removeClass( 'alarm' ); + element.removeClass('alarm'); if ( stateClass != 'alert' ) - element.removeClass( 'alert' ); + element.removeClass('alert'); if ( stateClass != 'idle' ) - element.removeClass( 'idle' ); - element.addClass( stateClass ); + element.removeClass('idle'); + element.addClass(stateClass); } }; - this.onError = function( text, error ) { + this.onError = function(text, error) { console.log('onerror: ' + text + ' error:'+error); // Requeue, but want to wait a while. - var streamCmdTimeout = 1000*statusRefreshTimeout; + var streamCmdTimeout = 10*statusRefreshTimeout; this.streamCmdTimer = this.streamCmdQuery.delay(streamCmdTimeout, this); }; - this.onFailure = function( xhr ) { + this.onFailure = function(xhr) { console.log('onFailure: ' + this.connKey); console.log(xhr); - if ( ! requestQueue.hasNext("cmdReq"+this.id) ) { + if ( ! requestQueue.hasNext("cmdReq"+this.id) ) { console.log("Not requeuing because there is one already"); requestQueue.addRequest("cmdReq"+this.id, this.streamCmdReq); } @@ -58,9 +60,9 @@ function Monitor( monitorData ) { console.log("done failure"); }; - this.getStreamCmdResponse = function( respObj, respText ) { + this.getStreamCmdResponse = function(respObj, respText) { if ( this.streamCmdTimer ) - this.streamCmdTimer = clearTimeout( this.streamCmdTimer ); + this.streamCmdTimer = clearTimeout(this.streamCmdTimer); var stream = $j('#liveStream'+this.id)[0]; @@ -78,11 +80,11 @@ function Monitor( monitorData ) { stateClass = "idle"; if ( (!COMPACT_MONTAGE) && (this.type != 'WebSite') ) { - $('fpsValue'+this.id).set( 'text', this.status.fps ); - $('stateValue'+this.id).set( 'text', stateStrings[this.alarmState] ); - this.setStateClass( $('monitorState'+this.id), stateClass ); + $('fpsValue'+this.id).set('text', this.status.fps); + $('stateValue'+this.id).set('text', stateStrings[this.alarmState]); + this.setStateClass($('monitorState'+this.id), stateClass); } - this.setStateClass( $('monitor'+this.id), stateClass ); + this.setStateClass($('monitor'+this.id), stateClass); /*Stream could be an applet so can't use moo tools*/ stream.className = stateClass; @@ -96,7 +98,7 @@ function Monitor( monitorData ) { if ( newAlarm ) { if ( false && SOUND_ON_ALARM ) { // Enable the alarm sound - $('alarmSound').removeClass( 'hidden' ); + $('alarmSound').removeClass('hidden'); } if ( POPUP_ON_ALARM ) { windowToFront(); @@ -105,49 +107,51 @@ function Monitor( monitorData ) { if ( false && SOUND_ON_ALARM ) { if ( oldAlarm ) { // Disable alarm sound - $('alarmSound').addClass( 'hidden' ); + $('alarmSound').addClass('hidden'); } } if ( this.status.auth ) { if ( this.status.auth != auth_hash ) { // Try to reload the image stream. if ( stream ) - stream.src = stream.src.replace( /auth=\w+/i, 'auth='+this.status.auth ); - console.log("Changed auth from " + auth_hash + " to " + this.status.auth ); + stream.src = stream.src.replace(/auth=\w+/i, 'auth='+this.status.auth); + console.log("Changed auth from " + auth_hash + " to " + this.status.auth); auth_hash = this.status.auth; } } // end if have a new auth hash } // end if has state } else { - console.error( respObj.message ); + console.error(respObj.message); // Try to reload the image stream. if ( stream ) { if ( stream.src ) { - console.log('Reloading stream: ' + stream.src ); + console.log('Reloading stream: ' + stream.src); stream.src = stream.src.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) )); } else { } } else { - console.log( 'No stream to reload?' ); + console.log('No stream to reload?'); } } // end if Ok or not var streamCmdTimeout = statusRefreshTimeout; - // 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. + // 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.streamCmdTimer = this.streamCmdQuery.delay(streamCmdTimeout, this); this.lastAlarmState = this.alarmState; }; - this.streamCmdQuery = function( resent ) { + this.streamCmdQuery = function(resent) { if ( resent ) { - console.log( this.connKey+": Resending" ); + console.log(this.connKey+": timeout: Resending"); this.streamCmdReq.cancel(); } //console.log("Starting CmdQuery for " + this.connKey ); if ( this.type != 'WebSite' ) { - this.streamCmdReq.send( this.streamCmdParms+"&command="+CMD_QUERY ); + this.streamCmdReq.send(this.streamCmdParms+"&command="+CMD_QUERY); } }; @@ -155,51 +159,50 @@ function Monitor( monitorData ) { this.streamCmdReq = new Request.JSON( { url: this.url, method: 'get', - timeout: 1000+AJAX_TIMEOUT, - onSuccess: this.getStreamCmdResponse.bind( this ), - onTimeout: this.streamCmdQuery.bind( this, true ), + timeout: 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' } ); - console.log("queueing for " + this.id + " " + this.connKey ); - requestQueue.addRequest( "cmdReq"+this.id, this.streamCmdReq ); + console.log("queueing for " + this.id + " " + this.connKey + " timeout is: " + AJAX_TIMEOUT); + requestQueue.addRequest("cmdReq"+this.id, this.streamCmdReq); } +} // end function Monitor -} - -function selectLayout( element ) { +function selectLayout(element) { layout = $j(element).val(); if ( layout_id = parseInt(layout) ) { layout = layouts[layout]; console.log(layout); - for ( var i = 0; i < monitors.length; i++ ) { + for ( var i = 0, length = monitors.length; i < 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 ); + console.log("Error finding frame for " + monitor.id); continue; } // Apply default layout options, like float left if ( layout.Positions['default'] ) { - styles = layout.Positions['default']; + styles = layout.Positions['default']; for ( style in styles ) { - monitor_frame.css(style, styles[style]); + 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]; + styles = layout.Positions['mId'+monitor.id]; for ( style in styles ) { - monitor_frame.css(style, styles[style]); - console.log("Applying " + style + ' : ' + styles[style] ); + monitor_frame.css(style, styles[style]); + console.log("Applying " + style + ' : ' + styles[style]); } } else { console.log("No Monitor styles to apply"); @@ -209,21 +212,20 @@ function selectLayout( element ) { if ( ! layout ) { return; } - Cookie.write( 'zmMontageLayout', layout_id, { duration: 10*365 } ); + 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', '' ); + $('scale').set('value', ''); $('width').set('value', ''); -if ( 1 ) { - for ( var x = 0; x < monitors.length; x++ ) { - var monitor = monitors[x]; - var streamImg = $( 'liveStream'+monitor.id ); + for ( var i = 0, length = monitors.length; i < length; i++ ) { + var monitor = monitors[i]; + var streamImg = $('liveStream'+monitor.id); if ( streamImg ) { if ( streamImg.nodeName == 'IMG' ) { var src = streamImg.src; src = src.replace(/width=[\.\d]+/i,'width=0' ); if ( src != streamImg.src ) { - streamImg.src=''; + streamImg.src = ''; streamImg.src = src; } } else if ( streamImg.nodeName == 'APPLET' || streamImg.nodeName == 'OBJECT' ) { @@ -236,37 +238,39 @@ if ( 1 ) { zonesSVG.style.width = ''; } } // end foreach monitor -} } -} +} // end function selectLayout(element) function changeSize() { var width = $('width').get('value'); var height = $('height').get('value'); - for ( var x = 0; x < monitors.length; x++ ) { - var monitor = monitors[x]; - + for ( var i = 0, length = monitors.length; i < length; i++ ) { + var monitor = monitors[i]; + // Scale the frame monitor_frame = $j('#monitorFrame'+monitor.id); - if ( ! monitor_frame ) { - console.log("Error finding frame for " + monitor.id ); + if ( !monitor_frame ) { + console.log("Error finding frame for " + monitor.id); continue; } - monitor_frame.css('width',width?width+'px':''); - monitor_frame.css('height',height?height+'px':''); - /*Stream could be an applet so can't use moo tools*/ - var streamImg = $( 'liveStream'+monitor.id ); + 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 ) { if ( streamImg.nodeName == 'IMG' ) { var src = streamImg.src; - streamImg.src=''; - src = src.replace(/width=[\.\d]+/i,'width='+width ); - src = src.replace(/height=[\.\d]+/i,'height='+height ); + streamImg.src = ''; + src = src.replace(/width=[\.\d]+/i,'width='+width); + src = src.replace(/height=[\.\d]+/i,'height='+height); src = src.replace(/rand=\d+/i,'rand='+Math.floor((Math.random() * 1000000) )); streamImg.src = src; } - streamImg.style.width = width? width + "px" : null; + streamImg.style.width = width ? width + "px" : null; streamImg.style.height = height ? height + "px" : null; //streamImg.style.height = ''; } @@ -276,10 +280,10 @@ function changeSize() { zonesSVG.style.height = height + "px"; } } - $('scale').set('value', '' ); - Cookie.write( 'zmMontageScale', '', { duration: 10*365 } ); - Cookie.write( 'zmMontageWidth', width, { duration: 10*365 } ); - Cookie.write( 'zmMontageHeight', height, { duration: 10*365 } ); + $('scale').set('value', ''); + Cookie.write('zmMontageScale', '', { duration: 10*365 }); + Cookie.write('zmMontageWidth', width, { duration: 10*365 }); + Cookie.write('zmMontageHeight', height, { duration: 10*365 }); selectLayout('#zmMontageLayout'); } // end function changeSize() @@ -287,22 +291,22 @@ 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 ) { + 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; + for ( var i = 0, length = monitors.length; i < length; i++ ) { + var monitor = monitors[i]; + var newWidth = ( monitorData[i].width * scale ) / SCALE_BASE; + var newHeight = ( monitorData[i].height * scale ) / SCALE_BASE; // Scale the frame monitor_frame = $j('#monitorFrame'+monitor.id); - if ( ! monitor_frame ) { - console.log("Error finding frame for " + monitor.id ); + if ( !monitor_frame ) { + console.log("Error finding frame for " + monitor.id); continue; } if ( width ) @@ -310,16 +314,16 @@ function changeScale() { if ( height ) monitor_frame.css('height',height+'px'); /*Stream could be an applet so can't use moo tools*/ - var streamImg = $j('#liveStream'+monitor.id )[0]; + var streamImg = $j('#liveStream'+monitor.id)[0]; if ( streamImg ) { if ( streamImg.nodeName == 'IMG' ) { var src = streamImg.src; - streamImg.src=''; + streamImg.src = ''; //src = src.replace(/rand=\d+/i,'rand='+Math.floor((Math.random() * 1000000) )); - src = src.replace(/scale=[\.\d]+/i,'scale='+ scale ); - src = src.replace(/width=[\.\d]+/i,'width='+newWidth ); - src = src.replace(/height=[\.\d]+/i,'height='+newHeight ); + src = src.replace(/scale=[\.\d]+/i, 'scale='+scale); + src = src.replace(/width=[\.\d]+/i, 'width='+newWidth); + src = src.replace(/height=[\.\d]+/i, 'height='+newHeight); streamImg.src = src; } streamImg.style.width = newWidth + "px"; @@ -341,11 +345,11 @@ function toGrid(value) { function edit_layout(button) { // Turn off the onclick on the image. - - for ( var i = 0; i < monitors.length; i++ ) { + + for ( var i = 0, length = monitors.length; i < length; i++ ) { var monitor = monitors[i]; monitor_feed = $j('#imageFeed'+monitor.id)[0]; - monitor_feed.click(''); + monitor_feed.onclick = ''; }; $j('#monitors .monitorFrame').draggable({ @@ -360,11 +364,11 @@ 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++ ) { + for ( var i = 0, length = monitors.length; i < lenth; i++ ) { var monitor = monitors[i]; monitor_frame = $j('#monitorFrame'+monitor.id); - Positions['mId'+monitor.id] = { + Positions['mId'+monitor.id] = { width: monitor_frame.css('width'), height: monitor_frame.css('height'), top: monitor_frame.css('top'), @@ -375,22 +379,23 @@ function save_layout(button) { float: monitor_frame.css('float'), }; } // end foreach monitor - form.Positions.value = JSON.stringify( Positions ); + form.Positions.value = JSON.stringify(Positions); form.submit(); -} +} // end function save_layout + function cancel_layout(button) { $j('#SaveLayout').hide(); $j('#EditLayout').show(); - for ( var i = 0; i < monitors.length; i++ ) { + for ( var i = 0, length = monitors.length; i < length; i++ ) { var monitor = monitors[i]; monitor_feed = $j('#imageFeed'+monitor.id); - monitor_feed.click( monitor.onclick ); + monitor_feed.click(monitor.onclick); }; selectLayout('#zmMontageLayout'); } function reloadWebSite(ndx) { - document.getElementById('imageFeed'+ndx).innerHTML = document.getElementById('imageFeed'+ndx).innerHTML; + document.getElementById('imageFeed'+ndx).innerHTML = document.getElementById('imageFeed'+ndx).innerHTML; } var monitors = new Array(); @@ -409,25 +414,19 @@ function initPage() { jQuery("#hdrbutton").toggleClass('glyphicon-menu-down').toggleClass('glyphicon-menu-up'); } - for ( var i = 0; i < monitorData.length; i++ ) { + for ( var i = 0, length = monitorData.length; i < length; i++ ) { monitors[i] = new Monitor(monitorData[i]); + + // Start the fps and status updates. give a random delay so that we don't assault the server var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout ); + monitors[i].start(delay); + var interval = monitors[i].refresh; - monitors[i].start( delay ); if ( monitors[i].type == 'WebSite' && interval > 0 ) { - setInterval(reloadWebSite, interval*1000, i); - } + setInterval(reloadWebSite, interval*1000, i); + } } selectLayout('#zmMontageLayout'); - - for ( var i = 0; i < monitorData.length; i++ ) { - if ( monitors[i].type == 'WebSite' ) - continue; - var delay = Math.round( (Math.random()+0.75)*statusRefreshTimeout ); - console.log("Delay for monitor " + monitorData[i].id + " is " + delay ); - monitors[i].streamCmdQuery.delay( delay, monitors[i] ); - //monitors[i].zm_startup(delay); - } } // Kick everything off -window.addEvent( 'domready', initPage ); +window.addEvent('domready', initPage); From 17726bbe386b14d986effb5ade8a958fffb7245d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Oct 2018 17:19:27 -0400 Subject: [PATCH 092/310] Actually say how big the ImageBufferCOunt should be --- src/zm_ffmpeg_camera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index f294367cc..b4f267155 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -870,7 +870,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event if ( keyframe ) { Debug(3, "Clearing queue"); if ( packetqueue->packet_count(mVideoStreamId) >= monitor->GetImageBufferCount() ) { - Warning("ImageBufferCount is too small. Either increase it or decrease time between keyframes"); + Warning("ImageBufferCount is too small. Needs to be at least %d. Either increase it or decrease time between keyframes", packetqueue->packet_count(mVideoStreamId) ); } packetqueue->clearQueue(monitor->GetPreEventCount(), mVideoStreamId); From 372c6b7f693be5ae8fa9bd91a178ebc9df378738 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Oct 2018 20:57:24 -0400 Subject: [PATCH 093/310] explicitly call ZoneMinder::Database::zmDbDisconnect --- scripts/ZoneMinder/lib/ZoneMinder/Logger.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 3caa6af4a..6d7ecc660 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -705,7 +705,7 @@ sub Fatal( @ ) { $SIG{TERM}(); } # I think if we don't disconnect we will leave sockets around in TIME_WAIT - zmDbDisconnect(); + ZoneMinder::Database::zmDbDisconnect(); exit(-1); } From 88b9a1b2591b95ee241d67aa91cdf567a22885f5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 18 Oct 2018 08:49:13 -0400 Subject: [PATCH 094/310] Don't auto enable show privacy --- scripts/zmupdate.pl.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index 45d45851a..b66397740 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -923,10 +923,10 @@ if ( $version ) { die( "Can't find upgrade from version '$version'" ); } # Re-enable the privacy popup after each upgrade - my $sql = "update Config set Value = 1 where Name = 'ZM_SHOW_PRIVACY'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); + #my $sql = "update Config set Value = 1 where Name = 'ZM_SHOW_PRIVACY'"; + #my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + #my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() ); + #$sth->finish(); print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" ); } zmDbDisconnect(); From 9cf31eafa06f5a2c1c12e57110994b1bec9258d0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Oct 2018 12:43:43 -0400 Subject: [PATCH 095/310] Use a button element instead of an anchor tag for plus and minus point buttons. Cleanup dead code --- web/skins/classic/views/js/zone.js | 30 ++++++---- web/skins/classic/views/zone.php | 94 +++++++++++++----------------- 2 files changed, 59 insertions(+), 65 deletions(-) diff --git a/web/skins/classic/views/js/zone.js b/web/skins/classic/views/js/zone.js index 6389e3ce9..094ea54c8 100644 --- a/web/skins/classic/views/js/zone.js +++ b/web/skins/classic/views/js/zone.js @@ -49,9 +49,9 @@ function validateForm( form ) { } if ( errors.length ) { alert( errors.join( "\n" ) ); - return( false ); + return false; } - return( true ); + return true; } function submitForm( form ) { @@ -324,7 +324,7 @@ function limitPointValue( point, loVal, hiVal ) { } function updateArea( ) { - area = Polygon_calcArea( zone['Points'] ); + area = Polygon_calcArea(zone['Points']); zone.Area = area; var form = $('zoneForm'); form.elements['newZone[Area]'].value = area; @@ -333,7 +333,7 @@ function updateArea( ) { } else if ( form.elements['newZone[Units]'].value == 'Pixels' ) { form.elements['newZone[TempArea]'].value = area; } else { - alert("Unknown units: " + form.elements['newZone[Units]'].value ); + alert("Unknown units: " + form.elements['newZone[Units]'].value); } } @@ -368,15 +368,23 @@ function saveChanges( element ) { if ( form.elements['newZone[Type]'].value == 'Privacy' ) { alert( 'Capture process for this monitor will be restarted for the Privacy zone changes to take effect.' ); } - return( true ); + return true; } - return( false ); + return false; } function drawZonePoints() { $('imageFrame').getElements( 'div.zonePoint' ).each( function( element ) { element.destroy(); } ); for ( var i = 0; i < zone['Points'].length; i++ ) { - var div = new Element( 'div', { 'id': 'point'+i, 'class': 'zonePoint', 'title': 'Point '+(i+1), 'styles': { 'left': zone['Points'][i].x, 'top': zone['Points'][i].y } } ); + var div = new Element( 'div', { + 'id': 'point'+i, + 'class': 'zonePoint', + 'title': 'Point '+(i+1), + 'styles': { + 'left': zone['Points'][i].x, + 'top': zone['Points'][i].y + } + } ); div.addEvent( 'mouseover', highlightOn.pass( i ) ); div.addEvent( 'mouseout', highlightOff.pass( i ) ); div.inject( $('imageFrame') ); @@ -410,9 +418,11 @@ function drawZonePoints() { cell.inject( row ); cell = new Element( 'td' ); - new Element( 'a', { 'href': '#', 'events': { 'click': addPoint.pass( i ) } } ).set( 'text', '+' ).inject( cell ); - if ( zone['Points'].length > 3 ) - new Element( 'a', { 'id': 'delete'+i, 'href': '#', 'events': { 'click': delPoint.pass( i ) } } ).set( 'text', '-' ).inject( cell ); + new Element( 'button', { 'type': 'button', 'events': { 'click': addPoint.pass( i ) } } ).set( 'text', '+' ).inject( cell ); + if ( zone['Points'].length > 3 ) { + cell.appendText(' '); + new Element( 'button', { 'id': 'delete'+i, 'type': 'button', 'events': { 'click': delPoint.pass( i ) } } ).set( 'text', '-' ).inject( cell ); + } cell.inject( row ); row.inject( tables[i%tables.length].getElement( 'tbody' ) ); diff --git a/web/skins/classic/views/zone.php b/web/skins/classic/views/zone.php index f6a763c19..0ab5d2770 100644 --- a/web/skins/classic/views/zone.php +++ b/web/skins/classic/views/zone.php @@ -18,9 +18,9 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -if ( !canView( 'Monitors' ) ) { - $view = 'error'; - return; +if ( !canView('Monitors') ) { + $view = 'error'; + return; } $mid = validInt($_REQUEST['mid']); @@ -62,39 +62,39 @@ $minY = 0; $maxY = $monitor->Height()-1; if ( !isset($newZone) ) { - if ( $zid > 0 ) { - $zone = dbFetchOne( 'SELECT * FROM Zones WHERE MonitorId = ? AND Id=?', NULL, array( $monitor->Id(), $zid ) ); - } else { - $zone = array( - 'Id' => 0, - 'Name' => translate('New'), - 'Type' => 'Active', - 'MonitorId' => $monitor->Id(), - 'NumCoords' => 4, - 'Coords' => sprintf( "%d,%d %d,%d, %d,%d %d,%d", $minX, $minY, $maxX, $minY, $maxX, $maxY, $minX, $maxY ), - 'Area' => $monitor->Width() * $monitor->Height(), - 'AlarmRGB' => 0xff0000, - 'CheckMethod' => 'Blobs', - 'MinPixelThreshold' => '', - 'MaxPixelThreshold' => '', - 'MinAlarmPixels' => '', - 'MaxAlarmPixels' => '', - 'FilterX' => '', - 'FilterY' => '', - 'MinFilterPixels' => '', - 'MaxFilterPixels' => '', - 'MinBlobPixels' => '', - 'MaxBlobPixels' => '', - 'MinBlobs' => '', - 'MaxBlobs' => '', - 'OverloadFrames' => '', - 'ExtendAlarmFrames' => '', - ); - } - $zone['Points'] = coordsToPoints( $zone['Coords'] ); - $zone['AreaCoords'] = preg_replace( '/\s+/', ',', $zone['Coords'] ); + if ( $zid > 0 ) { + $zone = dbFetchOne( 'SELECT * FROM Zones WHERE MonitorId = ? AND Id=?', NULL, array( $monitor->Id(), $zid ) ); + } else { + $zone = array( + 'Id' => 0, + 'Name' => translate('New'), + 'Type' => 'Active', + 'MonitorId' => $monitor->Id(), + 'NumCoords' => 4, + 'Coords' => sprintf("%d,%d %d,%d, %d,%d %d,%d", $minX, $minY, $maxX, $minY, $maxX, $maxY, $minX, $maxY), + 'Area' => $monitor->Width() * $monitor->Height(), + 'AlarmRGB' => 0xff0000, + 'CheckMethod' => 'Blobs', + 'MinPixelThreshold' => '', + 'MaxPixelThreshold' => '', + 'MinAlarmPixels' => '', + 'MaxAlarmPixels' => '', + 'FilterX' => '', + 'FilterY' => '', + 'MinFilterPixels' => '', + 'MaxFilterPixels' => '', + 'MinBlobPixels' => '', + 'MaxBlobPixels' => '', + 'MinBlobs' => '', + 'MaxBlobs' => '', + 'OverloadFrames' => '', + 'ExtendAlarmFrames' => '', + ); + } + $zone['Points'] = coordsToPoints( $zone['Coords'] ); + $zone['AreaCoords'] = preg_replace( '/\s+/', ',', $zone['Coords'] ); - $newZone = $zone; + $newZone = $zone; } # end if new Zone # Ensure Zone fits within the limits of the Monitor @@ -235,16 +235,15 @@ if ( count( $other_zones ) ) {
 -  fps
-
+
Date: Wed, 24 Oct 2018 12:44:14 -0400 Subject: [PATCH 096/310] Turn ffmpeg logging back on --- src/zm_ffmpeg.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 79a1d8b26..a2a1d4a96 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -28,9 +28,9 @@ void FFMPEGInit() { static bool bInit = false; if ( !bInit ) { - //if ( logDebugging() ) - //av_log_set_level( AV_LOG_DEBUG ); - //else + if ( logDebugging() ) + av_log_set_level( AV_LOG_DEBUG ); + else av_log_set_level( AV_LOG_QUIET ); av_register_all(); avformat_network_init(); From 1fa13e57574a4dd3a06a3e7978e371a499879ca2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Oct 2018 13:10:54 -0400 Subject: [PATCH 097/310] Add SWRESAMPLE libs --- CMakeLists.txt | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d070c6d3..e49686ef1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -602,7 +602,7 @@ if(NOT ZM_NO_FFMPEG) set(optlibsnotfound "${optlibsnotfound} SWScale") endif(SWSCALE_LIBRARIES) - # rescale (using find_library and find_path) + # AVresample (using find_library and find_path) find_library(AVRESAMPLE_LIBRARIES avresample) if(AVRESAMPLE_LIBRARIES) set(HAVE_LIBAVRESAMPLE 1) @@ -619,6 +619,23 @@ if(NOT ZM_NO_FFMPEG) set(optlibsnotfound "${optlibsnotfound} AVResample") endif(AVRESAMPLE_LIBRARIES) + # SWresample (using find_library and find_path) + find_library(SWRESAMPLE_LIBRARIES swresample) + if(SWRESAMPLE_LIBRARIES) + set(HAVE_LIBSWRESAMPLE 1) + list(APPEND ZM_BIN_LIBS "${SWRESAMPLE_LIBRARIES}") + find_path(SWRESAMPLE_INCLUDE_DIR "libswresample/swresample.h" /usr/include/ffmpeg) + if(SWRESAMPLE_INCLUDE_DIR) + include_directories("${SWRESAMPLE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${SWRESAMPLE_INCLUDE_DIR}") + endif(SWRESAMPLE_INCLUDE_DIR) + mark_as_advanced(FORCE SWRESAMPLE_LIBRARIES SWRESAMPLE_INCLUDE_DIR) + check_include_file("libswresample/swresample.h" HAVE_LIBSWRESAMPLE_SWRESAMPLE_H) + set(optlibsfound "${optlibsfound} SWResample") + else(SWRESAMPLE_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} SWResample") + endif(SWRESAMPLE_LIBRARIES) + # Find the path to the ffmpeg executable find_program(FFMPEG_EXECUTABLE NAMES ffmpeg avconv From c48ce090686b9e9146d4b941ad5477c3acd7e416 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Oct 2018 13:11:09 -0400 Subject: [PATCH 098/310] Turn back on ffmpeg debug logging --- src/zm_ffmpeg.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index a2a1d4a96..0690b5607 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -32,7 +32,10 @@ void FFMPEGInit() { av_log_set_level( AV_LOG_DEBUG ); else av_log_set_level( AV_LOG_QUIET ); +#if LIBAVCODEC_VERSION_CHECK(58, 18, 0, 64, 0) +#else av_register_all(); +#endif avformat_network_init(); bInit = true; } From d025adab6d75eff46b18555bc3eb9971c5c0c348 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 25 Oct 2018 09:13:07 -0400 Subject: [PATCH 099/310] Need to load Config Categories in options save --- web/includes/actions.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/web/includes/actions.php b/web/includes/actions.php index a8db2c936..675e35d67 100644 --- a/web/includes/actions.php +++ b/web/includes/actions.php @@ -831,6 +831,22 @@ if ( canEdit('System') ) { return; } if ( $action == 'options' && isset($_REQUEST['tab']) ) { + $config = array(); + $configCat = array(); + $configCats = array(); + + $result = $dbConn->query('SELECT * FROM Config ORDER BY Id ASC'); + if ( !$result ) + echo mysql_error(); + while( $row = dbFetchNext($result) ) { + $config[$row['Name']] = $row; + if ( !($configCat = &$configCats[$row['Category']]) ) { + $configCats[$row['Category']] = array(); + $configCat = &$configCats[$row['Category']]; + } + $configCat[$row['Name']] = $row; + } + $configCat = $configCats[$_REQUEST['tab']]; $changed = false; foreach ( $configCat as $name=>$value ) { From ad51a464b0d9fb3064efb85a4b402019f6b6f242 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Oct 2018 13:03:04 -0400 Subject: [PATCH 100/310] Add cosmic to default distros to build for --- utils/do_debian_package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 71f1ec460..d9a813c5c 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -76,7 +76,7 @@ fi; if [ "$DISTROS" == "" ]; then if [ "$RELEASE" != "" ]; then - DISTROS="xenial,bionic,trusty" + DISTROS="xenial,bionic,cosmic,trusty" else DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; fi; From 89c3de432fd1a5e40021a11f510e8d307b3705ff Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 1 Nov 2018 17:50:51 -0400 Subject: [PATCH 101/310] fix crud version --- web/api/app/Plugin/Crud | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/api/app/Plugin/Crud b/web/api/app/Plugin/Crud index c3976f147..0bd63fb46 160000 --- a/web/api/app/Plugin/Crud +++ b/web/api/app/Plugin/Crud @@ -1 +1 @@ -Subproject commit c3976f1478c681b0bbc132ec3a3e82c3984eeed5 +Subproject commit 0bd63fb464957080ead342db58ca9e01532cf1ef From 17c091f9f9c519ba60bab0c37b2d6a4aca706460 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 2 Nov 2018 10:26:39 -0400 Subject: [PATCH 102/310] Google code style and add a debug showing the # of addresses in hp --- src/zm_remote_camera.cpp | 12 ++++++--- src/zm_remote_camera_http.cpp | 51 ++++++++++++++--------------------- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/zm_remote_camera.cpp b/src/zm_remote_camera.cpp index 25a963a17..d80532d54 100644 --- a/src/zm_remote_camera.cpp +++ b/src/zm_remote_camera.cpp @@ -52,7 +52,7 @@ RemoteCamera::RemoteCamera( RemoteCamera::~RemoteCamera() { if ( hp != NULL ) { - freeaddrinfo(hp); + freeaddrinfo(hp); hp = NULL; } if ( mAuthenticator ) { @@ -68,8 +68,8 @@ void RemoteCamera::Initialise() { if( host.empty() ) Fatal( "No host specified for remote camera" ); - if( port.empty() ) - Fatal( "No port specified for remote camera" ); + if ( port.empty() ) + Fatal("No port specified for remote camera"); //if( path.empty() ) //Fatal( "No path specified for remote camera" ); @@ -99,6 +99,12 @@ void RemoteCamera::Initialise() { if ( ret != 0 ) { Fatal( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) ); } + struct addrinfo *p = NULL; + int addr_count = 0; + for ( p = hp; p != NULL; p = p->ai_next ) { + addr_count++; + } + Debug(1, "%d addresses returned", addr_count); } int RemoteCamera::Read( int fd, char *buf, int size ) { diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index 9c91d235b..0aeaffe6d 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -76,26 +76,21 @@ 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(); } } -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()); @@ -107,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; } @@ -123,7 +117,7 @@ void RemoteCameraHttp::Initialise() } int RemoteCameraHttp::Connect() { - struct addrinfo *p; + struct addrinfo *p = NULL; for ( p = hp; p != NULL; p = p->ai_next ) { sd = socket( p->ai_family, p->ai_socktype, p->ai_protocol ); @@ -157,27 +151,24 @@ int RemoteCameraHttp::Connect() { return sd; } // end int RemoteCameraHttp::Connect() -int RemoteCameraHttp::Disconnect() -{ - close( sd ); +int RemoteCameraHttp::Disconnect() { + close(sd); sd = -1; - Debug( 3, "Disconnected from host" ); - return( 0 ); + Debug(3, "Disconnected from host"); + return 0; } -int RemoteCameraHttp::SendRequest() -{ - Debug( 2, "Sending request: %s", request.c_str() ); - if ( write( sd, request.data(), request.length() ) < 0 ) - { - Error( "Can't write: %s", strerror(errno) ); +int RemoteCameraHttp::SendRequest() { + Debug(2, "Sending request: %s", request.c_str()); + if ( write(sd, request.data(), request.length()) < 0 ) { + Error("Can't write: %s", strerror(errno)); Disconnect(); - return( -1 ); + return -1; } format = UNDEF; state = HEADER; - Debug( 3, "Request sent" ); - return( 0 ); + Debug(3, "Request sent"); + return 0; } /* Return codes are as follows: @@ -278,12 +269,10 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { return( total_bytes_read ); } -int RemoteCameraHttp::GetResponse() -{ +int RemoteCameraHttp::GetResponse() { int buffer_len; #if HAVE_LIBPCRE - if ( method == REGEXP ) - { + if ( method == REGEXP ) { const char *header = 0; int header_len = 0; const char *http_version = 0; From 09fab280eab974e70fd1d61d39782fcc68c61a67 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 2 Nov 2018 10:57:47 -0400 Subject: [PATCH 103/310] SOCKS dir creation logs should be debug --- src/zm_stream.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 75fe4e856..6dc0e099a 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -291,11 +291,11 @@ void StreamBase::openComms() { if ( errno != EEXIST ) { Error("Can't mkdir %s: %s", staticConfig.PATH_SOCKS.c_str(), strerror(errno)); return; - } else { -Error("EEXISTsuccess making dir %s", staticConfig.PATH_SOCKS.c_str() ); + } else { + Debug(3, "SOCKS dir %s already exists", staticConfig.PATH_SOCKS.c_str() ); } } else { -Error("success making dir %s", staticConfig.PATH_SOCKS.c_str() ); + Debug(3, "Success making SOCKS dir %s", staticConfig.PATH_SOCKS.c_str() ); } lock_fd = open(sock_path_lock, O_CREAT|O_WRONLY, S_IRUSR | S_IWUSR); From 451c42ddf59e2809fe530859142e39f3cf6807fa Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 5 Nov 2018 16:52:34 -0500 Subject: [PATCH 104/310] Implement rmrecover script. Flush out Object code to support find, find_one, find_sql, improve to_string, etc. --- scripts/CMakeLists.txt | 3 +- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 144 +++--- scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm | 11 - scripts/ZoneMinder/lib/ZoneMinder/Object.pm | 440 ++++++++++++++++- scripts/zmrecover.pl.in | 475 +++++++++++++++++++ 5 files changed, 985 insertions(+), 88 deletions(-) create mode 100644 scripts/zmrecover.pl.in diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index f1bfa2ed1..e278c3eaf 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -10,6 +10,7 @@ configure_file(zmdc.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" @ONLY) configure_file(zmfilter.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" @ONLY) configure_file(zmonvif-probe.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" @ONLY) configure_file(zmpkg.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" @ONLY) +configure_file(zmrecover.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmrecover.pl" @ONLY) configure_file(zmtrack.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" @ONLY) configure_file(zmtrigger.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" @ONLY) configure_file(zmupdate.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" @ONLY) @@ -35,7 +36,7 @@ FOREACH(PERLSCRIPT ${perlscripts}) ENDFOREACH(PERLSCRIPT ${perlscripts}) # Install the perl scripts -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmstats.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmrecover.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmstats.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) if(NOT ZM_NO_X10) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) endif(NOT ZM_NO_X10) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index d52c9829e..a91b8c948 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -52,7 +52,7 @@ use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); require Date::Parse; -use vars qw/ $table $primary_key %fields $serial @identified_by/; +use vars qw/ $table $primary_key %fields $serial @identified_by %defaults/; $table = 'Events'; @identified_by = ('Id'); $serial = $primary_key = 'Id'; @@ -84,6 +84,16 @@ $serial = $primary_key = 'Id'; Orientation DiskSpace ); +%defaults = ( + Cause => q`'Unknown'`, + TotScore => '0', + Archived => '0', + Videoed => '0', + Uploaded => '0', + Emailed => '0', + Messaged => '0', + Executed => '0', +); use POSIX; @@ -99,56 +109,10 @@ sub Time { return $_[0]{Time}; } -sub Name { - if ( @_ > 1 ) { - $_[0]{Name} = $_[1]; - } - return $_[0]{Name}; -} # end sub Name - -sub find { - shift if $_[0] eq 'ZoneMinder::Event'; - my %sql_filters = @_; - - my $sql = 'SELECT * FROM Events'; - my @sql_filters; - my @sql_values; - - if ( exists $sql_filters{Name} ) { - push @sql_filters , ' Name = ? '; - push @sql_values, $sql_filters{Name}; - } - if ( exists $sql_filters{Id} ) { - push @sql_filters , ' Id = ? '; - push @sql_values, $sql_filters{Id}; - } - - $sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; - $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; - - my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); - my $res = $sth->execute( @sql_values ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - my @results; - - while( my $db_filter = $sth->fetchrow_hashref() ) { - my $filter = new ZoneMinder::Event( $$db_filter{Id}, $db_filter ); - push @results, $filter; - } # end while - $sth->finish(); - return @results; -} - -sub find_one { - my @results = find(@_); - return $results[0] if @results; -} - sub getPath { return Path( @_ ); } + sub Path { my $event = shift; @@ -168,6 +132,9 @@ sub Path { sub Scheme { my $self = shift; + + $$self{Scheme} = shift if @_; + if ( ! $$self{Scheme} ) { if ( $$self{RelativePath} ) { if ( $$self{RelativePath} =~ /^\d+\/\d{4}\-\d{2}\-\d{2}\/\d+$/ ) { @@ -182,9 +149,8 @@ sub Scheme { sub RelativePath { my $event = shift; - if ( @_ ) { - $$event{RelativePath} = $_[0]; - } + + $$event{RelativePath} = shift if @_; if ( ! $$event{RelativePath} ) { if ( $$event{Scheme} eq 'Deep' ) { @@ -203,7 +169,7 @@ sub RelativePath { if ( $event->Time() ) { $$event{RelativePath} = join('/', $event->{MonitorId}, - strftime( '%Y-%m-%d', localtime($event->Time())), + strftime('%Y-%m-%d', localtime($event->Time())), $event->{Id}, ); } else { @@ -223,9 +189,8 @@ sub RelativePath { sub LinkPath { my $event = shift; - if ( @_ ) { - $$event{LinkPath} = $_[0]; - } + + $$event{LinkPath} = shift if @_; if ( ! $$event{LinkPath} ) { if ( $$event{Scheme} eq 'Deep' ) { @@ -351,19 +316,19 @@ sub GenerateVideo { .$Config{ZM_FFMPEG_OUTPUT_OPTIONS} ." '$video_file' > ffmpeg.log 2>&1" ; - Debug( $command."\n" ); + Debug($command); my $output = qx($command); my $status = $? >> 8; if ( $status ) { - Error( "Unable to generate video, check $event_path/ffmpeg.log for details"); + Error("Unable to generate video, check $event_path/ffmpeg.log for details"); return; } - Info( "Finished $video_file\n" ); + Info("Finished $video_file"); return $event_path.'/'.$video_file; } else { - Info( "Video file $video_file already exists for event $self->{Id}\n" ); + Info("Video file $video_file already exists for event $self->{Id}"); return $event_path.'/'.$video_file; } return; @@ -373,14 +338,14 @@ sub delete { my $event = $_[0]; if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) { my ( $caller, undef, $line ) = caller; - Warning("Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from $caller:$line\n"); + Warning("Can't delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from $caller:$line"); return; } if ( ! -e $event->Storage()->Path() ) { Warning("Not deleting event because storage path doesn't exist"); return; } - Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}\n"); + Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}"); $ZoneMinder::Database::dbh->ping(); $ZoneMinder::Database::dbh->begin_work(); @@ -697,6 +662,63 @@ Debug("Done deleting files, returning"); return $error; } # end sub MoveTo +# Assumes $path is absolute +# +sub recover_timestamps { + my ( $Event, $path ) = @_; + $path = $Event->Path() if ! $path; + + if ( ! opendir(DIR, $path) ) { + Error("Can't open directory '$path': $!"); + next; + } + my @contents = readdir(DIR); + Debug('Have ' . @contents . " files in $path"); + closedir(DIR); + + my @mp4_files = grep( /^\d+\-video\.mp4$/, @contents); + my @capture_jpgs = grep( /^\d+\-capture\.jpg$/, @contents); + + if ( @capture_jpgs ) { + # can get start and end times from stat'ing first and last jpg + @capture_jpgs = sort { $a cmp $b } @capture_jpgs; + my $first_file = "$path/$capture_jpgs[0]"; + ( $first_file ) = $first_file =~ /^(.*)$/; + my $first_timestamp = (stat($first_file))[9]; + + my $last_file = "$path/$capture_jpgs[@capture_jpgs-1]"; + ( $last_file ) = $last_file =~ /^(.*)$/; + my $last_timestamp = (stat($last_file))[9]; + + my $duration = $last_timestamp - $first_timestamp; + $Event->Length($duration); + $Event->StartTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) ); + $Event->EndTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $last_timestamp) ); + Debug("From capture Jpegs have duration $duration = $last_timestamp - $first_timestamp : $$Event{StartTime} to $$Event{EndTime}"); + } elsif ( @mp4_files ) { + my $file = "$path/$mp4_files[0]"; + ( $file ) = $file =~ /^(.*)$/; + + my $first_timestamp = (stat($file))[9]; + my $output = `ffprobe $file 2>&1`; + my ($duration) = $output =~ /Duration: [:\.0-9]+/gm; + Debug("From mp4 have duration $duration, start: $first_timestamp"); + + my ( $h, $m, $s, $u ); + if ( $duration =~ m/(\d+):(\d+):(\d+)\.(\d+)/ ) { + ( $h, $m, $s, $u ) = ($1, $2, $3, $4 ); + Debug("( $h, $m, $s, $u ) from /^(\\d{2}):(\\d{2}):(\\d{2})\.(\\d+)/"); + } + my $seconds = ($h*60*60)+($m*60)+$s; + $Event->Length($seconds.'.'.$u); + $Event->StartTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) ); + $Event->EndTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp+$seconds) ); + } + if ( @mp4_files ) { + $Event->DefaultVideo($mp4_files[0]); + } +} + 1; __END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index 2d8400a31..47ec7c557 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -36,17 +36,6 @@ require ZoneMinder::Server; #our @ISA = qw(Exporter ZoneMinder::Base); use parent qw(ZoneMinder::Object); -# ========================================================================== -# -# General Utility Functions -# -# ========================================================================== - -use ZoneMinder::Config qw(:all); -use ZoneMinder::Logger qw(:all); -use ZoneMinder::Database qw(:all); - -use POSIX; use vars qw/ $table $primary_key /; $table = 'Monitors'; $primary_key = 'Id'; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm index 8c28028a0..ee51d0aab 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -27,6 +27,8 @@ package ZoneMinder::Object; use 5.006; use strict; use warnings; +use Time::HiRes qw{ gettimeofday tv_interval }; +use Carp qw( cluck ); require ZoneMinder::Base; @@ -49,7 +51,7 @@ use vars qw/ $AUTOLOAD $log $dbh %cache $no_cache/; my $debug = 0; $no_cache = 0; -use constant DEBUG_ALL=>0; +use constant DEBUG_ALL=>1; sub init_cache { $no_cache = 0; @@ -167,17 +169,6 @@ sub lock_and_load { } # end sub lock_and_load -sub AUTOLOAD { - my ( $self, $newvalue ) = @_; - my $type = ref($_[0]); - my $name = $AUTOLOAD; - $name =~ s/.*://; - if ( @_ > 1 ) { - return $_[0]{$name} = $_[1]; - } - return $_[0]{$name}; -} - sub save { my ( $self, $data, $force_insert ) = @_; @@ -187,7 +178,12 @@ sub save { $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; + if ( ! $local_dbh ) { + $local_dbh = $ZoneMinder::Database::dbh; + if ( $debug or DEBUG_ALL ) { + $log->debug("Using global dbh"); + } + } $self->set( $data ? $data : {} ); if ( $debug or DEBUG_ALL ) { if ( $data ) { @@ -196,7 +192,6 @@ sub save { } } } -#$debug = 0; my $table = eval '$'.$type.'::table'; my $fields = eval '\%'.$type.'::fields'; @@ -297,6 +292,7 @@ $log->debug("No serial") if $debug; if ( $need_serial ) { if ( $serial ) { + $log->debug("Getting auto_increments"); 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{')} ); @@ -368,6 +364,7 @@ sub set { $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"); @@ -456,7 +453,420 @@ sub transform { sub to_string { my $type = ref($_[0]); my $fields = eval '\%'.$type.'::fields'; - return $type . ': '. join(' ' , map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } keys %$fields ); + if ( $fields and %{$fields} ) { + return $type . ': '. join(' ', map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } sort { $a cmp $b } keys %$fields ); + } + return $type . ': '. join(' ', map { $_ .' => '.(defined $_[0]{$_} ? $_[0]{$_} : 'undef') } sort { $a cmp $b } keys %{$_[0]} ); +} + +# We make this a separate function so that we can use it to generate the sql statements for each value in an OR +sub find_operators { + my ( $field, $type, $operator, $value ) = @_; +$log->debug("find_operators: field($field) type($type) op($operator) value($value)") if DEBUG_ALL; + +my $add_placeholder = ( ! ( $field =~ /\?/ ) ) ? 1 : 0; + + if ( sets::isin( $operator, [ '=', '!=', '<', '>', '<=', '>=', '<<=' ] ) ) { + return ( $field.$type.' ' . $operator . ( $add_placeholder ? ' ?' : '' ), $value ); + } elsif ( $operator eq 'not' ) { + return ( '( NOT ' . $field.$type.')', $value ); + } elsif ( sets::isin( $operator, [ '&&', '<@', '@>' ] ) ) { + if ( ref $value eq 'ARRAY' ) { + if ( $field =~ /^\(/ ) { + return ( 'ARRAY('.$field.$type.') ' . $operator . ' ?', $value ); + } else { + return ( $field.$type.' ' . $operator . ' ?', $value ); + } # emd of + } else { + return ( $field.$type.' ' . $operator . ' ?', [ $value ] ); + } # end if + } elsif ( $operator eq 'exists' ) { + return ( $value ? '' : 'NOT ' ) . 'EXISTS ' . $field.$type; + } elsif ( sets::isin( $operator, [ 'in', 'not in' ] ) ) { + if ( ref $value eq 'ARRAY' ) { + return ( $field.$type.' ' . $operator . ' ('. join(',', map { '?' } @{$value} ) . ')', @{$value} ); + } else { + return ( $field.$type.' ' . $operator . ' (?)', $value ); + } # end if + } elsif ( $operator eq 'contains' ) { + return ( '? IN '.$field.$type, $value ); + } elsif ( $operator eq 'does not contain' ) { + return ( '? NOT IN '.$field.$type, $value ); + } elsif ( sets::isin( $operator, [ 'like','ilike' ] ) ) { + return $field.'::text ' . $operator . ' ?', $value; + } elsif ( $operator eq 'null_or_<=' ) { + return '('.$field.$type.' IS NULL OR '.$field.$type.' <= ?)', $value; + } elsif ( $operator eq 'is null or <=' ) { + return '('.$field.$type.' IS NULL OR '.$field.$type.' <= ?)', $value; + } elsif ( $operator eq 'null_or_>=' ) { + return '('.$field.$type.' IS NULL OR '.$field.$type.' >= ?)', $value; + } elsif ( $operator eq 'is null or >=' ) { + return '('.$field.$type.' IS NULL OR '.$field.$type.' >= ?)', $value; + } elsif ( $operator eq 'null_or_>' or $operator eq 'is null or >' ) { + return '('.$field.$type.' IS NULL OR '.$field.$type.' > ?)', $value; + } elsif ( $operator eq 'null_or_<' or $operator eq 'is null or <' ) { + return '('.$field.$type.' IS NULL OR '.$field.$type.' < ?)', $value; + } elsif ( $operator eq 'null_or_=' or $operator eq 'is null or =' ) { + return '('.$field.$type.' IS NULL OR '.$field.$type.' = ?)', $value; + } elsif ( $operator eq 'null or in' or $operator eq 'is null or in' ) { + return '('.$field.$type.' IS NULL OR '.$field.$type.' IN ('.join(',', map { '?' } @{$value} ) . '))', @{$value}; + } elsif ( $operator eq 'null or not in' ) { + return '('.$field.$type.' IS NULL OR '.$field.$type.' NOT IN ('.join(',', map { '?' } @{$value} ) . '))', @{$value}; + } elsif ( $operator eq 'exists' ) { + return ( $value ? ' EXISTS ' : 'NOT EXISTS ' ).$field; + } elsif ( $operator eq 'lc' ) { + return 'lower('.$field.$type.') = ?', $value; + } elsif ( $operator eq 'uc' ) { + return 'upper('.$field.$type.') = ?', $value; + } elsif ( $operator eq 'trunc' ) { + return 'trunc('.$field.$type.') = ?', $value; + } elsif ( $operator eq 'any' ) { + if ( ref $value eq 'ARRAY' ) { + return '(' . join(',', map { '?' } @{$value} ).") = ANY($field)", @{$value}; + } else { + return "? = ANY($field)", $value; + } # end if + } elsif ( $operator eq 'not any' ) { + if ( ref $value eq 'ARRAY' ) { + return '(' . join(',', map { '?' } @{$value} ).") != ANY($field)", @{$value}; + } else { + return "? != ANY($field)", $value; + } # end if + } elsif ( $operator eq 'is null' ) { + if ( $value ) { + return $field.$type. ' is null'; + } else { + return $field.$type. ' is not null'; + } # end if + } elsif ( $operator eq 'is not null' ) { + if ( $value ) { + return $field.$type. ' is not null'; + } else { + return $field.$type. ' is null'; + } # end if + } else { +$log->warn("find_operators: op not found field($field) type($type) op($operator) value($value)"); + } # end if + return; +} # end sub find_operators + +sub get_fields_values { + my ( $object_type, $search, $param_keys ) = @_; + + my @used_fields; + my @where; + my @values; + no strict 'refs'; + + foreach my $k ( @$param_keys ) { + if ( $k eq 'or' ) { + my $or_ref = ref $$search{or}; + + if ( $or_ref eq 'HASH' ) { + my @keys = keys %{$$search{or}}; + if ( @keys ) { + my ( $where, $values, $used_fields ) = get_fields_values( $object_type, $$search{or}, \@keys ); + + push @where, '('.join(' OR ', @{$where} ).')'; + push @values, @{$values}; + } else { + $log->error("No keys in or"); + } + + } elsif ( $or_ref eq 'ARRAY' ) { + my %s = @{$$search{or}}; + my ( $where, $values, $used_fields ) = get_fields_values( $object_type, \%s, [ keys %s ] ); + push @where, '('.join(' OR ', @{$where} ).')'; + push @values, @{$values}; + + } else { + $log->error("Deprecated use of or $or_ref for $$search{or}"); + } # end if + push @used_fields, $k; + next; + } elsif ( $k eq 'and' ) { + my $and_ref = ref $$search{and}; + if ( $and_ref eq 'HASH' ) { + my @keys = keys %{$$search{and}}; + if ( @keys ) { + my ( $where, $values, $used_fields ) = get_fields_values( $object_type, $$search{and}, \@keys ); + + push @where, '('.join(' AND ', @{$where} ).')'; + push @values, @{$values}; + } else { + $log->error("No keys in and"); + } + } elsif ( $and_ref eq 'ARRAY' and @{$$search{and}} ) { + my @sub_where; + + for( my $p_index = 0; $p_index < @{$$search{and}}; $p_index += 2 ) { + my %p = ( $$search{and}[$p_index], $$search{and}[$p_index+1] ); + + my ( $where, $values, $used_fields ) = get_fields_values( $object_type, \%p, [ keys %p ] ); + push @sub_where, @{$where}; + push @values, @{$values}; + } + push @where, '('.join(' AND ', @sub_where ).')'; + } else { + $log->error("incorrect ref of and $and_ref"); + } + push @used_fields, $k; + next; + } + my ( $field, $type, $function ) = $k =~ /^([_\+\w\-]+)(::\w+\[?\]?)?[\s_]*(.*)?$/; + $type = '' if ! defined $type; +$log->debug("$object_type param $field($type) func($function) " . ( ref $$search{$k} eq 'ARRAY' ? join(',',@{$$search{$k}}) : $$search{$k} ) ) if DEBUG_ALL; + + foreach ( 'find_fields', 'fields' ) { + my $fields = \%{$object_type.'::'.$_}; + if ( ! $fields ) { + $log->debug("No $fields in $object_type") if DEBUG_ALL; + next; + } # end if + + if ( ! $$fields{$field} ) { + #$log->debug("No $field in $_ for $object_type") if DEBUG_ALL; + next; + } # end if + +# This allows mainly for find_fields to reference multiple values, opinion in Project, value + foreach my $db_field ( ref $$fields{$field} eq 'ARRAY' ? @{$$fields{$field}} : $$fields{$field} ) { + if ( ! $function ) { + $db_field .= $type; + + if ( ref $$search{$k} eq 'ARRAY' ) { +$log->debug("Have array for $k $$search{$k}") if DEBUG_ALL; + + if ( ! ( $db_field =~ /\?/ ) ) { + if ( @{$$search{$k}} != 1 ) { + push @where, $db_field .' IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')'; + } else { + push @where, $db_field.'=?'; + } # end if + } else { +$log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; + + $db_field =~ s/=/IN/g; + my $question_replacement = '('.join(',', map {'?'} @{$$search{$k}} ) . ')'; + $db_field =~ s/\?/$question_replacement/; + push @where, $db_field; + } + push @values, @{$$search{$k}}; + } elsif ( ref $$search{$k} eq 'HASH' ) { + foreach my $p_k ( keys %{$$search{$k}} ) { + my $v = $$search{$k}{$p_k}; + if ( ref $v eq 'ARRAY' ) { + push @where, $db_field.' IN ('.join(',', map {'?'} @{$v} ) . ')'; + push @values, $p_k, @{$v}; + } else { + push @where, $db_field.'=?'; + push @values, $p_k, $v; + } # end if + } # end foreach p_k + } elsif ( ! defined $$search{$k} ) { + push @where, $db_field.' IS NULL'; + } else { + if ( ! ( $db_field =~ /\?/ ) ) { + push @where, $db_field .'=?'; + } else { + push @where, $db_field; + } + push @values, $$search{$k}; + } # end if + push @used_fields, $k; + } else { + #my @w = +#ref $search{$k} eq 'ARRAY' ? + #map { find_operators( $field, $type, $function, $_ ); } @{$search{$k}} : + my ( $w, @v ) = find_operators( $db_field, $type, $function, $$search{$k} ); + if ( $w ) { + #push @where, '(' . join(' OR ', @w ) . ')'; + push @where, $w; + push @values, @v if @v; + push @used_fields, $k; + } # end if @w + } # end if has function or not + } # end foreach db_field + } # end foreach find_field + } # end foreach k + return ( \@where, \@values, \@used_fields ); +} + +sub find { + no strict 'refs'; + my $object_type = shift; + my $debug = ${$object_type.'::debug'}; + $debug = DEBUG_ALL if ! $debug; + + my $starttime = [gettimeofday] if $debug; + my $params; + if ( @_ == 1 ) { + $params = $_[0]; + if ( ref $params ne 'HASH' ) { + $log->error("params $params was not a has"); + } # end if + } else { + $params = { @_ }; + } # end if + + my $local_dbh = ${$object_type.'::dbh'}; + if ( $$params{dbh} ) { + $local_dbh = $$params{dbh}; + delete $$params{dbh}; + } elsif ( ! $local_dbh ) { + $local_dbh = $dbh if ! $local_dbh; + } # end if + + my $sql = find_sql( $object_type, $params); + + my $do_cache = $$sql{columns} ne '*' ? 0 : 1; + +#$log->debug( 'find prepare: ' . sprintf('%.4f', tv_interval($starttime)*1000) ." useconds") if $debug; + my $data = $local_dbh->selectall_arrayref($$sql{sql}, { Slice => {} }, @{$$sql{values}}); + if ( ! $data ) { + $log->error('Error ' . $local_dbh->errstr() . " loading $object_type ($$sql{sql}) (". join(',', map { ref $_ eq 'ARRAY' ? 'ARRAY('.join(',',@$_).')' : $_ } @{$$sql{values}} ) . ')' ); + return (); + #} elsif ( ( ! @$data ) and $debug ) { + #$log->debug("No $type ($sql) (@values) " ); + } elsif ( $debug ) { + $log->debug("Loading Debug:$debug $object_type ($$sql{sql}) (".join(',', map { ref $_ eq 'ARRAY' ? join(',', @{$_}) : $_ } @{$$sql{values}}).') # of results:' . @$data . ' in ' . sprintf('%.4f', tv_interval($starttime)*1000) .' useconds' ); + } # end if + + my $fields = \%{$object_type.'::fields'}; + my $primary_key = ${$object_type.'::primary_key'}; + if ( ! $primary_key ) { + Error( 'NO primary_key for type ' . $object_type ); + return; + } # end if + if ( ! ($fields and keys %{$fields}) ) { + return map { new($object_type, $$_{$primary_key}, $_ ) } @$data; + } elsif ( $$fields{$primary_key} ) { + return map { new($object_type, $_->{$$fields{$primary_key}}, $_) } @$data; + } else { + my @identified_by = eval '@'.$object_type.'::identified_by'; + if ( ! @identified_by ) { + $log->debug("Multi key object $object_type but no identified by $fields") if $debug; + } # end if + return map { new($object_type, \@identified_by, $_, !$do_cache) } @$data; + } # end if +} # end sub find + +sub find_one { + my $object_type = shift; + my $params; + if ( @_ == 1 ) { + $params = $_[0]; + } else { + %{$params} = @_; + } # end if + $$params{limit}=1; + my @Results = $object_type->find(%$params); + my ( $caller, undef, $line ) = caller; +$log->debug("returning to $caller:$line from find_one") if DEBUG_ALL; + return $Results[0] if @Results; +} # end sub find_one + +sub find_sql { + no strict 'refs'; + my $object_type = shift; + + my $debug = ${$object_type.'::debug'}; + $debug = DEBUG_ALL if ! $debug; + + my $params; + if ( @_ == 1 ) { + $params = $_[0]; + if ( ref $params ne 'HASH' ) { + $log->error("params $params was not a has"); + } # end if + } else { + $params = { @_ }; + } # end if + + my %sql = ( + ( distinct => ( exists $$params{distinct} ? 1:0 ) ), + ( columns => ( exists $$params{columns} ? $$params{columns} : '*' ) ), + ( table => ( exists $$params{table} ? $$params{table} : ${$object_type.'::table'} )), + 'group by'=> $$params{'group by'}, + limit => $$params{limit}, + offset => $$params{offset}, + ); + if ( exists $$params{order} ) { + $sql{order} = $$params{order}; + } else { + my $order = eval '$'.$object_type.'::default_sort'; +#$log->debug("default sort: $object_type :: default_sort = $order") if DEBUG_ALL; + $sql{order} = $order if $order; + } # end if + delete @$params{'distinct','columns','table','group by','limit','offset','order'}; + + my @where; + my @values; + if ( exists $$params{custom} ) { + push @where, '(' . (shift @{$$params{custom}}) . ')'; + push @values, @{$$params{custom}}; + delete $$params{custom}; + } # end if + + my @param_keys = keys %$params; + + # no operators, just which fields are being searched on. Mostly just useful for detetion of the deleted field. + my %used_fields; + + # We use this search hash so that we can mash it up and leave the params hash alone + my %search; + @search{@param_keys} = @$params{@param_keys}; + + my ( $where, $values, $used_fields ) = get_fields_values( $object_type, \%search, \@param_keys ); + delete @search{@{$used_fields}}; + @used_fields{ @{$used_fields} } = @{$used_fields}; + push @where, @{$where}; + push @values, @{$values}; + + my $fields = \%{$object_type.'::fields'}; + +#optimise this + if ( $$fields{deleted} and ! $used_fields{deleted} ) { + push @where, 'deleted=?'; + push @values, 0; + } # end if + $sql{where} = \@where; + $sql{values} = \@values; + $sql{used_fields} = \%used_fields; + + foreach my $k ( keys %search ) { + $log->error("Extra parameters in $object_type ::find $k => $search{$k}"); + Carp::cluck("Extra parameters in $object_type ::find $k => $search{$k}"); + } # end foreach + + $sql{sql} = join( ' ', + ( 'SELECT', ( $sql{distinct} ? ('DISTINCT') : () ) ), + ( $sql{columns}, 'FROM', $sql{table} ), + ( @{$sql{where}} ? ('WHERE', join(' AND ', @{$sql{where}})) : () ), + ( $sql{order} ? ( 'ORDER BY', $sql{order} ) : () ), + ( $sql{'group by'} ? ( 'GROUP BY', $sql{'group by'} ) : () ), + ( $sql{limit} ? ( 'LIMIT', $sql{limit}) : () ), + ( $sql{offset} ? ( 'OFFSET', $sql{offset} ) : () ), + ); + #$log->debug("Loading Debug:$debug $object_type ($sql) (".join(',', map { ref $_ eq 'ARRAY' ? join(',', @{$_}) : $_ } @values).')' ) if $debug; + return \%sql; +} # end sub find_sql + +sub AUTOLOAD { + my $type = ref($_[0]); + Carp::cluck("No type in autoload") if ! $type; + if ( DEBUG_ALL ) { + Carp::cluck("Using AUTOLOAD $AUTOLOAD"); + } + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( @_ > 1 ) { + return $_[0]{$name} = $_[1]; + } + return $_[0]{$name}; +} + +sub DESTROY { } 1; diff --git a/scripts/zmrecover.pl.in b/scripts/zmrecover.pl.in new file mode 100644 index 000000000..9b0f589ff --- /dev/null +++ b/scripts/zmrecover.pl.in @@ -0,0 +1,475 @@ +#!/usr/bin/perl -wT + +use strict; +use bytes; + +# ========================================================================== +# +# These are the elements you can edit to suit your installation +# +# ========================================================================== + +use constant RECOVER_TAG => '(r)'; # Tag to append to event name when recovered +use constant RECOVER_TEXT => 'Recovered.'; # Text to append to event notes when recovered + +# ========================================================================== +# +# You shouldn't need to change anything from here downwards +# +# ========================================================================== + +@EXTRA_PERL_LIB@ +use ZoneMinder; +use DBI; +use POSIX; +use File::Find; +use Time::HiRes qw/gettimeofday/; +use Getopt::Long; +use Date::Format; +use autouse 'Pod::Usage'=>qw(pod2usage); + +use constant ZM_RECOVER_PID => '@ZM_RUNDIR@/zmrecover.pid'; + + +$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; +$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; +delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; + +my $report = 0; +my $interactive = 1; +my $monitor_id = 0; +my $version; +my $force = 0; +my $server_id = undef; +my $storage_id = undef; + +logInit(); + +GetOptions( + force =>\$force, + interactive =>\$interactive, + 'monitor_id=i' =>\$monitor_id, + report =>\$report, + 'server_id=i' =>\$server_id, + 'storage_id=i' =>\$storage_id, + version =>\$version + ) or pod2usage(-exitstatus => -1); + +if ( $version ) { + print( ZoneMinder::Base::ZM_VERSION . "\n"); + exit(0); +} +if ( ($report + $interactive) > 1 ) { + print( STDERR "Error, only one option may be specified\n" ); + pod2usage(-exitstatus => -1); +} + +if ( -e ZM_RECOVER_PID ) { + local $/ = undef; + open FILE, ZM_RECOVER_PID or die "Couldn't open file: $!"; + binmode FILE; + my $pid = ; + close FILE; + if ( $force ) { + Error("zmrecover.pl appears to already be running at pid $pid. Continuing." ); + } else { + Fatal("zmrecover.pl appears to already be running at pid $pid. If not, please delete " . + ZM_RECOVER_PID . " or use the --force command line option." ); + } +} # end if ZM_RECOVER_PID exists + +if ( open(my $PID, '>', ZM_RECOVER_PID) ) { + print($PID $$); + close($PID); +} else { + Error( "Can't open pid file at " . ZM_PID ); +} + +sub HupHandler { + Info('Received HUP, reloading'); + &ZoneMinder::Logger::logHupHandler(); +} +sub TermHandler { + Info('Received TERM, exiting'); + Term(); +} +sub Term { + unlink ZM_RECOVER_PID; + exit(0); +} +$SIG{HUP} = \&HupHandler; +$SIG{TERM} = \&TermHandler; +$SIG{INT} = \&TermHandler; + +my $dbh = zmDbConnect(); +if ( ! $dbh ) { + Error('Unable to connect to database'); + Term(); +} # end if + +$| = 1; + +require ZoneMinder::Monitor; +require ZoneMinder::Storage; +require ZoneMinder::Event; + + +my @Storage_Areas; +if ( defined $storage_id ) { + @Storage_Areas = ZoneMinder::Storage->find( Id=>$storage_id ); + if ( !@Storage_Areas ) { + Error("No Storage Area found with Id $storage_id"); + Term(); + } + Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}"); +} elsif ( $server_id ) { + @Storage_Areas = ZoneMinder::Storage->find( ServerId => $server_id ); + if ( ! @Storage_Areas ) { + Error("No Storage Area found with ServerId =" . $server_id); + Term(); + } + foreach my $Storage ( @Storage_Areas ) { + Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name() ); + } +} else { + @Storage_Areas = ZoneMinder::Storage->find(); + Info("Auditing All Storage Areas"); +} + +my @Monitors = ZoneMinder::Monitor->find(); +Debug("@Monitors"); + foreach my $Monitor ( @Monitors ) { + Debug("Monitor " . $Monitor->to_string() ) + } + my %Monitors = map { $$_{Id} => $_ } @Monitors; + #ZoneMinder::Monitor->find( + + # ($monitor_id ? ( Id=>$monitor_id ) : () ), + + #); + Debug("Found " . (keys %Monitors) . " monitors"); + foreach my $id ( keys %Monitors ) { + Debug("Monitor $id $Monitors{$id}{Name}"); + } + + foreach my $Storage ( @Storage_Areas ) { + Debug('Checking events in ' . $Storage->Path() ); + if ( ! chdir($Storage->Path()) ) { + Error('Unable to change dir to ' . $Storage->Path()); + next; + } # end if + + # Please note that this glob will take all files beginning with a digit. + foreach my $monitor ( glob('[0-9]*') ) { + if ( $monitor =~ /\D/ ) { + Debug("Weird non digit characters in $monitor"); + next; + } + # De-taint + ( my $monitor_dir ) = ( $monitor =~ /^(\d+)$/ ); + if ( $monitor_id and ( $monitor_id != $monitor_dir ) ) { + Debug("Skipping monitor $monitor_dir because we are only interested in monitor $monitor_id"); + next; + } + if ( ! $Monitors{$monitor_dir} ) { + Warning("There is no monitor in the database for $$Storage{Path}/$monitor_dir. Skipping it."); + next; + } + my $Monitor = $Monitors{$monitor_dir}; + + Debug("Found filesystem monitor '$monitor_dir'"); + + { + my @day_dirs = glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]"); + Debug(qq`Checking for Deep Events under $$Storage{Path} using glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]") returned `. scalar @day_dirs . ' days with events'); + foreach my $day_dir ( @day_dirs ) { + Debug("Checking day dir $day_dir"); + ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint + if ( !chdir($day_dir) ) { + Error("Can't chdir to '$$Storage{Path}/$day_dir': $!"); + next; + } + if ( ! opendir(DIR, '.') ) { + Error("Can't open directory '$$Storage{Path}/$day_dir': $!"); + next; + } + my %event_ids_by_path; + + my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); + Debug('Have ' . (scalar @event_links) . ' event links'); + closedir(DIR); + + my $count = 0; + foreach my $event_link ( @event_links ) { + # Event links start with a period and consist of the digits of the event id. + # Anything else is not an event link + my ($event_id) = $event_link =~ /^\.(\d+)$/; + if ( !$event_id ) { + Warning("Non-event link found $event_link in $day_dir, skipping"); + next; + } + Debug("Checking link $event_link"); + #Event path is hour/minute/sec + my $event_path = readlink($event_link); + + if ( !($event_path and -e $event_path) ) { + Warning("Event link $day_dir/$event_link does not point to valid target at $event_path"); + next; + } + if ( ! ZoneMinder::Event->find_one(Id=>$event_id) ) { + Info("Event not found in db for event data found at $$Storage{Path}/$day_dir/$event_path with Id=$event_id"); + if ( confirm() ) { + my $Event = new ZoneMinder::Event(); + $$Event{Id} = $event_id; + $$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_path); + $$Event{RelativePath} = join('/', $day_dir, $event_path); + $$Event{Scheme} = 'Deep'; + $$Event{Name} = "Event $event_id recovered"; + $Event->MonitorId( $monitor_dir ); + $Event->StorageId( $Storage->Id() ); + $Event->DiskSpace( undef ); + $Event->Width( $Monitor->Width() ); + $Event->Height( $Monitor->Height() ); + $Event->Orientation( $Monitor->Orientation() ); + + $Event->recover_timestamps(); + + $Event->save({}, 1); + Debug("Event resurrected as " . $Event->to_string() ); + next; + } # end if resurrection + } # event path exists + } # end foreach event_link + + # Now check for events that have lost their link. We can determine event Id from .mp4 + + my @time_dirs = glob('[0-9][0-9]/[0-9][0-9]/[0-9][0-9]'); + foreach my $event_dir ( @time_dirs ) { + Debug("Checking time dir $event_dir"); + ( $event_dir ) = ( $event_dir =~ /^(.*)$/ ); # De-taint + + my $event_id = undef; + + if ( ! opendir(DIR, $event_dir) ) { + Error("Can't open directory '$$Storage{Path}/$day_dir': $!"); + next; + } + my @contents = readdir( DIR ); + Debug('Have ' . @contents . " files in $day_dir/$event_dir"); + closedir(DIR); + + my @mp4_files = grep( /^\d+\-video.mp4$/, @contents); + foreach my $mp4_file ( @mp4_files ) { + my ( $id ) = $mp4_file =~ /^([0-9]+)\-video\.mp4$/; + if ( $id ) { + $event_id = $id; + Debug("Got event id from mp4 file $mp4_file => $event_id"); + last; + } + } # end foreach mp4 + + if ( ! $event_id ) { + # Look for .id file + my @hidden_files = grep( /^\.\d+$/, @contents); + Debug('Have ' . @hidden_files . ' hidden files'); + if ( @hidden_files ) { + ( $event_id ) = $hidden_files[0] =~ /^.(\d+)$/; + } + } + + if ( $event_id and ! ZoneMinder::Event->find_one(Id=>$event_id) ) { + Info("Event not found in db for event data found at $$Storage{Path}/$monitor_dir/$day_dir/$event_dir"); + if ( confirm() ) { + my $Event = new ZoneMinder::Event(); + $$Event{Id} = $event_id; + $$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_dir); + $$Event{RelativePath} = join('/', $day_dir, $event_dir); + $$Event{Scheme} = 'Deep'; + $$Event{Name} = "Event $event_id recovered"; + $Event->MonitorId( $monitor_dir ); + $Event->Width( $Monitor->Width() ); + $Event->Height( $Monitor->Height() ); + $Event->Orientation( $Monitor->Orientation() ); + $Event->StorageId( $Storage->Id() ); + $Event->DiskSpace( undef ); + $Event->recover_timestamps(); + $Event->save({}, 1); + Debug("Event resurrected as " . $Event->to_string() ); + next; + } + } # end if event found + + # Search in db for given timestamp? + + my ( undef, $year, $month, $day ) = split('/', $day_dir); + $year += 2000; + my ( $hour, $minute, $second ) = split('/', $event_dir); + my $StartTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second); + my $Event = ZoneMinder::Event->find_one( + MonitorId=>$monitor_dir, + StartTime=>$StartTime, + ); + if ( $Event ) { + Debug("Found event matching starttime on monitor $monitor_dir at $StartTime: " . $Event->to_string()); + next; + } + + } # end foreach event_dir without link + chdir( $Storage->Path() ); + } # end foreach day dir + } + + Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir"); + { + my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"); + Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . " entries." ); + foreach my $event_dir ( @event_dirs ) { + if ( ! -d $event_dir ) { + Debug("$event_dir is not a dir. Skipping"); + next; + } + my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/; + if ( !$event_id ) { + Debug("Unable to parse date/event_id from $event_dir"); + next; + } + + my $Event = ZoneMinder::Event->find_one(Id=>$event_id); + if ( $Event ) { + Debug('Found event in the db, moving on.'); + next; + } + $Event = new ZoneMinder::Event(); + $$Event{Id} = $event_id; + $$Event{Path} = join('/', $Storage->Path(), $event_dir ); + Debug("Have event $$Event{Id} at $$Event{Path}"); + $$Event{Scheme} = 'Medium'; + $$Event{RelativePath} = $event_dir; + $$Event{Name} = "Event $event_id recovered"; + $Event->MonitorId( $monitor_dir ); + $Event->Width( $Monitor->Width() ); + $Event->Height( $Monitor->Height() ); + $Event->Orientation( $Monitor->Orientation() ); + $Event->StorageId( $Storage->Id() ); + $Event->recover_timestamps(); + if ( confirm() ) { + $Event->save({}, 1); + Debug("Event resurrected as " . $Event->to_string() ); + } + } # end foreach event + } # end search for Medium + + # Shallow + Debug("Checking for ShallowScheme Events under $$Storage{Path}/$monitor_dir"); + if ( ! chdir($monitor_dir) ) { + Error("Can't chdir directory '$$Storage{Path}/$monitor_dir': $!"); + next; + } + if ( ! opendir(DIR, '.') ) { + Error("Can't open directory '$$Storage{Path}/$monitor_dir': $!"); + next; + } + my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); + closedir(DIR); + foreach my $event ( @temp_events ) { + my $Event = ZoneMinder::Event->find_one(Id=>$event); + if ( $Event ) { + Debug("Found an event in db for $event"); + next; + } + $$Event{Id} = $event; + $$Event{Path} = join('/', $Storage->Path(), $event ); + Debug("Have event $$Event{Id} at $$Event{Path}"); + $$Event{Scheme} = 'Shallow'; + $$Event{Name} = "Event $event recovered"; + #$$Event{Path} = $event_path; + $Event->MonitorId( $monitor_dir ); + $Event->Width( $Monitor->Width() ); + $Event->Height( $Monitor->Height() ); + $Event->Orientation( $Monitor->Orientation() ); + $Event->StorageId( $Storage->Id() ); + $Event->recover_timestamps(); + $Event->save({}, 1); + Debug("Event resurrected as " . $Event->to_string() ); + } # end foreach event + chdir( $Storage->Path() ); + } # end foreach monitor + + } # end foreach Storage Area + +Term(); + +sub confirm { + my $prompt = shift || 'resurrect'; + my $action = shift || 'resurrecting'; + + my $yesno = 0; + if ( $report ) { + print( "\n" ); + } elsif ( $interactive ) { + print(", $prompt Y/n/q: "); + my $char = <>; + chomp( $char ); + if ( $char eq 'q' ) { + Term(); + } + if ( !$char ) { + $char = 'y'; + } + $yesno = ( $char =~ /[yY]/ ); + } else { + Info($action); + $yesno = 1; + } + return $yesno; +} + +1; +__END__ + +=head1 NAME + +zmrecover.pl - ZoneMinder event file system and database recovery checker + +=head1 SYNOPSIS + + zmrecover.pl [-r,-report|-i,-interactive] + +=head1 DESCRIPTION + +This script checks for consistency between the event filesystem and +the database. If events are found in one and not the other they are +deleted (optionally). Additionally any monitor event directories that +do not correspond to a database monitor are similarly disposed of. +However monitors in the database that don't have a directory are left +alone as this is valid if they are newly created and have no events +yet. + +=head1 OPTIONS + + -i, --interactive - Ask before applying any changes + -m, --monitor_id - Only consider the given monitor + -r, --report - Just report don't actually do anything + -s, --storage_id - Specify a storage area to recover instead of all + -v, --version - Print the installed version of ZoneMinder + +=head1 COPYRIGHT + +ZoneMinder Recover Script +Copyright (C) 2018 ZoneMinder LLC + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +=cut From 18b327b4acdd8f224203e18242544766f43f086d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 6 Nov 2018 09:55:53 -0500 Subject: [PATCH 105/310] Turn off massive debugging --- scripts/ZoneMinder/lib/ZoneMinder/Object.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm index ee51d0aab..faac440ac 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -51,7 +51,7 @@ use vars qw/ $AUTOLOAD $log $dbh %cache $no_cache/; my $debug = 0; $no_cache = 0; -use constant DEBUG_ALL=>1; +use constant DEBUG_ALL=>0; sub init_cache { $no_cache = 0; From e0bc0484d49e598c8c512e6bc68bd7e7aff0f8ec Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 6 Nov 2018 09:56:29 -0500 Subject: [PATCH 106/310] Add empty string default for DefaultVideo --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index a91b8c948..6c4750845 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -86,6 +86,7 @@ $serial = $primary_key = 'Id'; ); %defaults = ( Cause => q`'Unknown'`, + DefaultVideo => q`''`, TotScore => '0', Archived => '0', Videoed => '0', From d973d55d2f3fd3b64a78589f52d91b97b3a68dde Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 6 Nov 2018 11:45:34 -0500 Subject: [PATCH 107/310] Use SaveJPEGs from the Event record instead of the Monitor Object --- src/zm_eventstream.cpp | 16 ++++++++++++---- src/zm_eventstream.h | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 9f7eae4ec..23edf3463 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -109,7 +109,10 @@ bool EventStream::loadInitialEventData( uint64_t init_event_id, unsigned int ini bool EventStream::loadEventData(uint64_t event_id) { static char sql[ZM_SQL_MED_BUFSIZ]; - snprintf(sql, sizeof(sql), "SELECT MonitorId, StorageId, Frames, unix_timestamp( StartTime ) AS StartTimestamp, (SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id) AS Duration, DefaultVideo, Scheme FROM Events WHERE Id = %" PRIu64, event_id); + snprintf(sql, sizeof(sql), + "SELECT MonitorId, StorageId, Frames, unix_timestamp( StartTime ) AS StartTimestamp, " + "(SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id) AS Duration, " + "DefaultVideo, Scheme, SaveJPEGs FROM Events WHERE Id = %" PRIu64, event_id); if ( mysql_query(&dbconn, sql) ) { Error("Can't run query: %s", mysql_error(&dbconn)); @@ -151,6 +154,7 @@ bool EventStream::loadEventData(uint64_t event_id) { } else { event_data->scheme = Storage::SHALLOW; } + event_data->SaveJPEGs = dbrow[7] == NULL ? 0 : atoi(dbrow[7]); mysql_free_result( result ); Storage * storage = new Storage(event_data->storage_id); @@ -241,6 +245,7 @@ bool EventStream::loadEventData(uint64_t event_id) { if ( event_data->video_file[0] ) { char filepath[PATH_MAX]; snprintf(filepath, sizeof(filepath), "%s/%s", event_data->path, event_data->video_file); + Debug(1, "Loading video file from %s", filepath); ffmpeg_input = new FFmpeg_Input(); if ( 0 > ffmpeg_input->Open( filepath ) ) { Warning("Unable to open ffmpeg_input %s/%s", event_data->path, event_data->video_file); @@ -570,14 +575,17 @@ bool EventStream::sendFrame( int delta_us ) { // This needs to be abstracted. If we are saving jpgs, then load the capture file. If we are only saving analysis frames, then send that. // // This is also wrong, need to have this info stored in the event! FIXME - if ( monitor->GetOptSaveJPEGs() & 1 ) { + if ( event_data->SaveJPEGs & 1 ) { snprintf( filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id ); - } else if ( monitor->GetOptSaveJPEGs() & 2 ) { + } else if ( event_data->SaveJPEGs & 2 ) { snprintf( filepath, sizeof(filepath), staticConfig.analyse_file_format, event_data->path, curr_frame_id ); if ( stat( filepath, &filestat ) < 0 ) { Debug(1, "analyze file %s not found will try to stream from other", filepath); snprintf( filepath, sizeof(filepath), staticConfig.capture_file_format, event_data->path, curr_frame_id ); - filepath[0] = 0; + if ( stat( filepath, &filestat ) < 0 ) { + Debug(1, "capture file %s not found either", filepath); + filepath[0] = 0; + } } } else if ( ! ffmpeg_input ) { diff --git a/src/zm_eventstream.h b/src/zm_eventstream.h index 90200df65..a6ca99aee 100644 --- a/src/zm_eventstream.h +++ b/src/zm_eventstream.h @@ -65,6 +65,7 @@ class EventStream : public StreamBase { FrameData *frames; char video_file[PATH_MAX]; Storage::Schemes scheme; + int SaveJPEGs; }; protected: From 3cd1887d54688e501bb53060f712e9f154221f18 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 6 Nov 2018 12:14:42 -0500 Subject: [PATCH 108/310] Cleanup POSIX use. Fix conflict between POSIX and Date::Format. --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 43 ++++++++++++++++--- scripts/ZoneMinder/lib/ZoneMinder/Filter.pm | 5 +-- scripts/ZoneMinder/lib/ZoneMinder/Logger.pm | 4 +- scripts/ZoneMinder/lib/ZoneMinder/Object.pm | 2 +- scripts/zmrecover.pl.in | 46 ++++++++++----------- 5 files changed, 65 insertions(+), 35 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 6c4750845..bb0923c99 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -31,12 +31,16 @@ use warnings; require ZoneMinder::Base; require ZoneMinder::Object; require ZoneMinder::Storage; +require ZoneMinder::Frame; require Date::Manip; require File::Find; require File::Path; require File::Copy; require File::Basename; require Number::Bytes::Human; +require Date::Parse; +require POSIX; +use Date::Format qw(time2str); #our @ISA = qw(ZoneMinder::Object); use parent qw(ZoneMinder::Object); @@ -50,7 +54,6 @@ use parent qw(ZoneMinder::Object); use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); -require Date::Parse; use vars qw/ $table $primary_key %fields $serial @identified_by %defaults/; $table = 'Events'; @@ -96,7 +99,6 @@ $serial = $primary_key = 'Id'; Executed => '0', ); -use POSIX; sub Time { if ( @_ > 1 ) { @@ -158,7 +160,7 @@ sub RelativePath { if ( $event->Time() ) { $$event{RelativePath} = join('/', $event->{MonitorId}, - strftime( '%y/%m/%d/%H/%M/%S', + POSIX::strftime( '%y/%m/%d/%H/%M/%S', localtime($event->Time()) ), ); @@ -170,7 +172,7 @@ sub RelativePath { if ( $event->Time() ) { $$event{RelativePath} = join('/', $event->{MonitorId}, - strftime('%Y-%m-%d', localtime($event->Time())), + POSIX::strftime('%Y-%m-%d', localtime($event->Time())), $event->{Id}, ); } else { @@ -198,7 +200,7 @@ sub LinkPath { if ( $event->Time() ) { $$event{LinkPath} = join('/', $event->{MonitorId}, - strftime( '%y/%m/%d', + POSIX::strftime( '%y/%m/%d', localtime($event->Time()) ), '.'.$$event{Id} @@ -678,9 +680,19 @@ sub recover_timestamps { closedir(DIR); my @mp4_files = grep( /^\d+\-video\.mp4$/, @contents); - my @capture_jpgs = grep( /^\d+\-capture\.jpg$/, @contents); + if ( @mp4_files ) { + $$Event{DefaultVideo} = $mp4_files[0]; + } + my @analyse_jpgs = grep( /^\d+\-analyse\.jpg$/, @contents); + if ( @analyse_jpgs ) { + $$Event{Save_JPEGs} |= 2; + } + + my @capture_jpgs = grep( /^\d+\-capture\.jpg$/, @contents); if ( @capture_jpgs ) { + $$Event{Frames} = scalar @capture_jpgs; + $$Event{Save_JPEGs} |= 1; # can get start and end times from stat'ing first and last jpg @capture_jpgs = sort { $a cmp $b } @capture_jpgs; my $first_file = "$path/$capture_jpgs[0]"; @@ -696,6 +708,25 @@ sub recover_timestamps { $Event->StartTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) ); $Event->EndTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $last_timestamp) ); Debug("From capture Jpegs have duration $duration = $last_timestamp - $first_timestamp : $$Event{StartTime} to $$Event{EndTime}"); + $ZoneMinder::Database::dbh->begin_work(); + foreach my $jpg ( @capture_jpgs ) { + my ( $id ) = $jpg =~ /^(\d+)\-capture\.jpg$/; + + if ( ! ZoneMinder::Frame->find_one( EventId=>$$Event{Id}, FrameId=>$id ) ) { + my $file = "$path/$jpg"; + ( $file ) = $file =~ /^(.*)$/; + my $timestamp = (stat($file))[9]; + my $Frame = new ZoneMinder::Frame(); + $Frame->save({ + EventId=>$$Event{Id}, FrameId=>$id, + TimeStamp=>Date::Format::time2str('%Y-%m-%d %H:%M:%S',$timestamp), + Delta => $timestamp - $first_timestamp, + Type=>'Normal', + Score=>0, + }); + } + } + $ZoneMinder::Database::dbh->commit(); } elsif ( @mp4_files ) { my $file = "$path/$mp4_files[0]"; ( $file ) = $file =~ /^(.*)$/; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index f1a72c8cf..73ebc2d10 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -30,6 +30,7 @@ use warnings; require ZoneMinder::Base; require Date::Manip; +require POSIX; use parent qw(ZoneMinder::Object); @@ -48,8 +49,6 @@ use ZoneMinder::Database qw(:all); require ZoneMinder::Storage; require ZoneMinder::Server; -use POSIX; - sub Name { if ( @_ > 1 ) { $_[0]{Name} = $_[1]; @@ -435,7 +434,7 @@ sub DateTimeToSQL { Error( "Unable to parse date string '$dt_str'\n" ); return( undef ); } - return( strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) ); + return( POSIX::strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) ); } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 6d7ecc660..d6c90d6aa 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -91,7 +91,7 @@ use ZoneMinder::Config qw(:all); use DBI; use Carp; -use POSIX; +require POSIX; use IO::Handle; use Data::Dumper; use Time::HiRes qw/gettimeofday/; @@ -539,7 +539,7 @@ sub logPrint { if ( $level <= $this->{fileLevel} or $level <= $this->{termLevel} ) { my $message = sprintf( '%s.%06d %s[%d].%s [%s:%d] [%s]' - , strftime('%x %H:%M:%S', localtime($seconds)) + , POSIX::strftime('%x %H:%M:%S', localtime($seconds)) , $microseconds , $this->{id} , $$ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm index faac440ac..a3f08a402 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -341,7 +341,7 @@ $log->debug("No serial") if $debug; } # end if } # end if ZoneMinder::Database::end_transaction( $local_dbh, $ac ); - $self->load(); + #$self->load(); #if ( $$fields{id} ) { #if ( ! $ZoneMinder::Object::cache{$type}{$$self{id}} ) { #$ZoneMinder::Object::cache{$type}{$$self{id}} = $self; diff --git a/scripts/zmrecover.pl.in b/scripts/zmrecover.pl.in index 9b0f589ff..5d1942ce2 100644 --- a/scripts/zmrecover.pl.in +++ b/scripts/zmrecover.pl.in @@ -25,7 +25,6 @@ use POSIX; use File::Find; use Time::HiRes qw/gettimeofday/; use Getopt::Long; -use Date::Format; use autouse 'Pod::Usage'=>qw(pod2usage); use constant ZM_RECOVER_PID => '@ZM_RUNDIR@/zmrecover.pid'; @@ -231,7 +230,6 @@ Debug("@Monitors"); $Event->Width( $Monitor->Width() ); $Event->Height( $Monitor->Height() ); $Event->Orientation( $Monitor->Orientation() ); - $Event->recover_timestamps(); $Event->save({}, 1); @@ -343,18 +341,18 @@ Debug("@Monitors"); $$Event{Id} = $event_id; $$Event{Path} = join('/', $Storage->Path(), $event_dir ); Debug("Have event $$Event{Id} at $$Event{Path}"); - $$Event{Scheme} = 'Medium'; - $$Event{RelativePath} = $event_dir; - $$Event{Name} = "Event $event_id recovered"; - $Event->MonitorId( $monitor_dir ); - $Event->Width( $Monitor->Width() ); - $Event->Height( $Monitor->Height() ); - $Event->Orientation( $Monitor->Orientation() ); - $Event->StorageId( $Storage->Id() ); - $Event->recover_timestamps(); if ( confirm() ) { + $$Event{Scheme} = 'Medium'; + $$Event{RelativePath} = $event_dir; + $$Event{Name} = "Event $event_id recovered"; + $Event->MonitorId( $monitor_dir ); + $Event->Width( $Monitor->Width() ); + $Event->Height( $Monitor->Height() ); + $Event->Orientation( $Monitor->Orientation() ); + $Event->StorageId( $Storage->Id() ); + $Event->recover_timestamps(); $Event->save({}, 1); - Debug("Event resurrected as " . $Event->to_string() ); + Debug("Event resurrected as " . $Event->to_string() ); } } # end foreach event } # end search for Medium @@ -380,17 +378,19 @@ Debug("@Monitors"); $$Event{Id} = $event; $$Event{Path} = join('/', $Storage->Path(), $event ); Debug("Have event $$Event{Id} at $$Event{Path}"); - $$Event{Scheme} = 'Shallow'; - $$Event{Name} = "Event $event recovered"; - #$$Event{Path} = $event_path; - $Event->MonitorId( $monitor_dir ); - $Event->Width( $Monitor->Width() ); - $Event->Height( $Monitor->Height() ); - $Event->Orientation( $Monitor->Orientation() ); - $Event->StorageId( $Storage->Id() ); - $Event->recover_timestamps(); - $Event->save({}, 1); - Debug("Event resurrected as " . $Event->to_string() ); + if ( confirm() ) { + $$Event{Scheme} = 'Shallow'; + $$Event{Name} = "Event $event recovered"; + #$$Event{Path} = $event_path; + $Event->MonitorId( $monitor_dir ); + $Event->Width( $Monitor->Width() ); + $Event->Height( $Monitor->Height() ); + $Event->Orientation( $Monitor->Orientation() ); + $Event->StorageId( $Storage->Id() ); + $Event->recover_timestamps(); + $Event->save({}, 1); + Debug("Event resurrected as " . $Event->to_string() ); + } } # end foreach event chdir( $Storage->Path() ); } # end foreach monitor From 5a933a93c20b6de902d6b7cc79d96ffcb1748109 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 6 Nov 2018 15:27:10 -0500 Subject: [PATCH 109/310] clean up static vars and google code style --- src/zm_remote_camera_http.cpp | 144 ++++++++++++++++------------------ 1 file changed, 67 insertions(+), 77 deletions(-) diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index 9c91d235b..e15ab69bb 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -35,6 +35,14 @@ #include #endif +#if HAVE_LIBPCRE +static RegExpr *header_expr = 0; +static RegExpr *status_expr = 0; +static RegExpr *connection_expr = 0; +static RegExpr *content_length_expr = 0; +static RegExpr *content_type_expr = 0; +#endif + RemoteCameraHttp::RemoteCameraHttp( unsigned int p_monitor_id, const std::string &p_method, @@ -72,30 +80,25 @@ RemoteCameraHttp::RemoteCameraHttp( if ( p_method == "simple" ) method = SIMPLE; - else if ( p_method == "regexp" ) + else if ( p_method == "regexp" ) { method = REGEXP; - else + } else Fatal( "Unrecognised method '%s' when creating HTTP camera %d", p_method.c_str(), monitor_id ); - if ( capture ) - { + if ( capture ) { Initialise(); } } -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()); @@ -107,8 +110,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; } @@ -120,7 +122,22 @@ void RemoteCameraHttp::Initialise() mode = SINGLE_IMAGE; format = UNDEF; state = HEADER; -} + +#if HAVE_LIBPCRE + if ( method == REGEXP ) { + if ( !header_expr ) + header_expr = new RegExpr("^(.+?\r?\n\r?\n)", PCRE_DOTALL); + if ( !status_expr ) + status_expr = new RegExpr("^HTTP/(1\\.[01]) +([0-9]+) +(.+?)\r?\n", PCRE_CASELESS); + if ( !connection_expr ) + connection_expr = new RegExpr("Connection: ?(.+?)\r?\n", PCRE_CASELESS); + if ( !content_length_expr ) + content_length_expr = new RegExpr("Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS); + if ( !content_type_expr ) + content_type_expr = new RegExpr("Content-type: ?(.+?)(?:; ?boundary=\x22?(.+?)\x22?)?\r?\n", PCRE_CASELESS); + } +#endif +} // end void RemoteCameraHttp::Initialise() int RemoteCameraHttp::Connect() { struct addrinfo *p; @@ -157,27 +174,24 @@ int RemoteCameraHttp::Connect() { return sd; } // end int RemoteCameraHttp::Connect() -int RemoteCameraHttp::Disconnect() -{ - close( sd ); +int RemoteCameraHttp::Disconnect() { + close(sd); sd = -1; - Debug( 3, "Disconnected from host" ); - return( 0 ); + Debug(3, "Disconnected from host"); + return 0; } -int RemoteCameraHttp::SendRequest() -{ - Debug( 2, "Sending request: %s", request.c_str() ); - if ( write( sd, request.data(), request.length() ) < 0 ) - { - Error( "Can't write: %s", strerror(errno) ); +int RemoteCameraHttp::SendRequest() { + Debug(2, "Sending request: %s", request.c_str()); + if ( write(sd, request.data(), request.length()) < 0 ) { + Error("Can't write: %s", strerror(errno)); Disconnect(); - return( -1 ); + return -1; } format = UNDEF; state = HEADER; - Debug( 3, "Request sent" ); - return( 0 ); + Debug(3, "Request sent"); + return 0; } /* Return codes are as follows: @@ -273,17 +287,15 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { total_bytes_to_read -= bytes_read; } while ( total_bytes_to_read ); - Debug( 4, buffer ); + Debug(4, "%s", buffer); - return( total_bytes_read ); + return total_bytes_read; } -int RemoteCameraHttp::GetResponse() -{ +int RemoteCameraHttp::GetResponse() { int buffer_len; #if HAVE_LIBPCRE - if ( method == REGEXP ) - { + if ( method == REGEXP ) { const char *header = 0; int header_len = 0; const char *http_version = 0; @@ -298,36 +310,23 @@ int RemoteCameraHttp::GetResponse() //int subcontent_length = 0; //const char *subcontent_type = ""; - while ( true ) - { - switch( state ) - { + while ( !zm_terminate ) { + switch( state ) { case HEADER : { - static RegExpr *header_expr = 0; - static RegExpr *status_expr = 0; - static RegExpr *connection_expr = 0; - static RegExpr *content_length_expr = 0; - static RegExpr *content_type_expr = 0; - while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) { Debug(4, "Timeout waiting for REGEXP HEADER"); } if ( buffer_len < 0 ) { - Error( "Unable to read header data" ); - return( -1 ); + Error("Unable to read header data"); + return -1; } bytes += buffer_len; - if ( !header_expr ) - header_expr = new RegExpr( "^(.+?\r?\n\r?\n)", PCRE_DOTALL ); - if ( header_expr->Match( (char*)buffer, buffer.size() ) == 2 ) - { + if ( header_expr->Match( (char*)buffer, buffer.size() ) == 2 ) { header = header_expr->MatchString( 1 ); header_len = header_expr->MatchLength( 1 ); - Debug( 4, "Captured header (%d bytes):\n'%s'", header_len, header ); + Debug(4, "Captured header (%d bytes):\n'%s'", header_len, header); - if ( !status_expr ) - status_expr = new RegExpr( "^HTTP/(1\\.[01]) +([0-9]+) +(.+?)\r?\n", PCRE_CASELESS ); if ( status_expr->Match( header, header_len ) < 4 ) { Error( "Unable to extract HTTP status from header" ); @@ -366,24 +365,18 @@ int RemoteCameraHttp::GetResponse() } Debug( 3, "Got status '%d' (%s), http version %s", status_code, status_mesg, http_version ); - if ( !connection_expr ) - connection_expr = new RegExpr( "Connection: ?(.+?)\r?\n", PCRE_CASELESS ); if ( connection_expr->Match( header, header_len ) == 2 ) { connection_type = connection_expr->MatchString( 1 ); Debug( 3, "Got connection '%s'", connection_type ); } - if ( !content_length_expr ) - content_length_expr = new RegExpr( "Content-length: ?([0-9]+)\r?\n", PCRE_CASELESS ); if ( content_length_expr->Match( header, header_len ) == 2 ) { content_length = atoi( content_length_expr->MatchString( 1 ) ); Debug( 3, "Got content length '%d'", content_length ); } - if ( !content_type_expr ) - content_type_expr = new RegExpr( "Content-type: ?(.+?)(?:; ?boundary=\x22?(.+?)\x22?)?\r?\n", PCRE_CASELESS ); if ( content_type_expr->Match( header, header_len ) >= 2 ) { content_type = content_type_expr->MatchString( 1 ); @@ -567,17 +560,14 @@ int RemoteCameraHttp::GetResponse() } } } - if ( mode == SINGLE_IMAGE ) - { + if ( mode == SINGLE_IMAGE ) { state = HEADER; Disconnect(); - } - else - { + } else { state = SUBHEADER; } Debug( 3, "Returning %d (%d) bytes of captured content", content_length, buffer.size() ); - return( content_length ); + return content_length; } case HEADERCONT : case SUBHEADERCONT : @@ -587,8 +577,7 @@ int RemoteCameraHttp::GetResponse() } } } - } - else + } else #endif // HAVE_LIBPCRE { static const char *http_match = "HTTP/"; @@ -641,7 +630,7 @@ int RemoteCameraHttp::GetResponse() static char content_boundary[64]; static int content_boundary_len; - while ( true ) { + while ( !zm_terminate ) { switch( state ) { case HEADER : { @@ -664,11 +653,11 @@ int RemoteCameraHttp::GetResponse() case HEADERCONT : { while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) { - Debug(4, "Timeout waiting for HEADERCONT"); + Debug(1, "Timeout waiting for HEADERCONT"); } if ( buffer_len < 0 ) { - Error( "Unable to read header" ); - return( -1 ); + Error("Unable to read header"); + return -1; } bytes += buffer_len; @@ -678,9 +667,10 @@ int RemoteCameraHttp::GetResponse() bool all_headers = false; while( true ) { - int crlf_len = memspn( header_ptr, "\r\n", header_len ); + int crlf_len = memspn(header_ptr, "\r\n", header_len); if ( n_headers ) { if ( (crlf_len == 2 && !strncmp( header_ptr, "\n\n", crlf_len )) || (crlf_len == 4 && !strncmp( header_ptr, "\r\n\r\n", crlf_len )) ) { + Debug(3, "Have double linefeed, done headers"); *header_ptr = '\0'; header_ptr += crlf_len; header_len -= buffer.consume( header_ptr-(char *)buffer ); @@ -740,7 +730,7 @@ int RemoteCameraHttp::GetResponse() start_ptr = http_header; end_ptr = start_ptr+strspn( start_ptr, "10." ); - // FIXME WHy are we memsetting every time? Can we not do it once? + // FIXME Why are we memsetting every time? Can we not do it once? memset( http_version, 0, sizeof(http_version) ); strncpy( http_version, start_ptr, end_ptr-start_ptr ); @@ -824,7 +814,7 @@ int RemoteCameraHttp::GetResponse() strcpy( content_type, start_ptr ); Debug( 3, "Got content type '%s'", content_type ); } - } + } // end if content_type_header if ( !strcasecmp( content_type, "image/jpeg" ) || !strcasecmp( content_type, "image/jpg" ) ) { // Single image @@ -859,10 +849,10 @@ int RemoteCameraHttp::GetResponse() return( -1 ); } } else { - Debug( 3, "Unable to extract entire header from stream, continuing" ); + Debug(3, "Unable to extract entire header from stream, continuing"); state = HEADERCONT; //return( -1 ); - } + } // end if all_headers break; } case SUBHEADER : From 181a8c29e105f6865236b8a656fbf6492160350f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 6 Nov 2018 15:28:37 -0500 Subject: [PATCH 110/310] Add SaveJPEGs to Event --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index bb0923c99..d1394dd29 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -86,6 +86,7 @@ $serial = $primary_key = 'Id'; StateId Orientation DiskSpace + SaveJPEGs ); %defaults = ( Cause => q`'Unknown'`, From f86ed5c66cc1e6c0b52f802d4a8cfefd31fbcdc9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 6 Nov 2018 15:28:56 -0500 Subject: [PATCH 111/310] allow interactive=0 and use Inf for log lines about resurecting events --- scripts/zmrecover.pl.in | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/zmrecover.pl.in b/scripts/zmrecover.pl.in index 5d1942ce2..5a6b5b0f9 100644 --- a/scripts/zmrecover.pl.in +++ b/scripts/zmrecover.pl.in @@ -46,7 +46,7 @@ logInit(); GetOptions( force =>\$force, - interactive =>\$interactive, + 'interactive=i' =>\$interactive, 'monitor_id=i' =>\$monitor_id, report =>\$report, 'server_id=i' =>\$server_id, @@ -233,7 +233,7 @@ Debug("@Monitors"); $Event->recover_timestamps(); $Event->save({}, 1); - Debug("Event resurrected as " . $Event->to_string() ); + Info("Event resurrected as " . $Event->to_string() ); next; } # end if resurrection } # event path exists @@ -340,7 +340,7 @@ Debug("@Monitors"); $Event = new ZoneMinder::Event(); $$Event{Id} = $event_id; $$Event{Path} = join('/', $Storage->Path(), $event_dir ); - Debug("Have event $$Event{Id} at $$Event{Path}"); + Info("Have event $$Event{Id} at $$Event{Path}"); if ( confirm() ) { $$Event{Scheme} = 'Medium'; $$Event{RelativePath} = $event_dir; @@ -352,7 +352,7 @@ Debug("@Monitors"); $Event->StorageId( $Storage->Id() ); $Event->recover_timestamps(); $Event->save({}, 1); - Debug("Event resurrected as " . $Event->to_string() ); + Info("Event resurrected as " . $Event->to_string() ); } } # end foreach event } # end search for Medium @@ -377,7 +377,7 @@ Debug("@Monitors"); } $$Event{Id} = $event; $$Event{Path} = join('/', $Storage->Path(), $event ); - Debug("Have event $$Event{Id} at $$Event{Path}"); + Info("Have event $$Event{Id} at $$Event{Path}"); if ( confirm() ) { $$Event{Scheme} = 'Shallow'; $$Event{Name} = "Event $event recovered"; From c60bd3af1e2662c18a96e85627a0eaa6e890483c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 6 Nov 2018 15:43:22 -0500 Subject: [PATCH 112/310] Need to new an Event --- scripts/zmrecover.pl.in | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/zmrecover.pl.in b/scripts/zmrecover.pl.in index 5a6b5b0f9..4a3858a70 100644 --- a/scripts/zmrecover.pl.in +++ b/scripts/zmrecover.pl.in @@ -375,6 +375,7 @@ Debug("@Monitors"); Debug("Found an event in db for $event"); next; } + $Event = new openprint::Event(); $$Event{Id} = $event; $$Event{Path} = join('/', $Storage->Path(), $event ); Info("Have event $$Event{Id} at $$Event{Path}"); From 5495ab80364ea543c2500e4c93f77fec619f6e9d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 6 Nov 2018 16:15:27 -0500 Subject: [PATCH 113/310] Fix package for Event --- scripts/zmrecover.pl.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/zmrecover.pl.in b/scripts/zmrecover.pl.in index 4a3858a70..6b8f3831f 100644 --- a/scripts/zmrecover.pl.in +++ b/scripts/zmrecover.pl.in @@ -375,7 +375,7 @@ Debug("@Monitors"); Debug("Found an event in db for $event"); next; } - $Event = new openprint::Event(); + $Event = new ZoneMinder::Event(); $$Event{Id} = $event; $$Event{Path} = join('/', $Storage->Path(), $event ); Info("Have event $$Event{Id} at $$Event{Path}"); From 9a96785fa424f491a7f25b0c932f2403ea807bad Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 6 Nov 2018 20:37:00 -0500 Subject: [PATCH 114/310] Add in Frame.php --- scripts/ZoneMinder/lib/ZoneMinder/Frame.pm | 78 ++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 scripts/ZoneMinder/lib/ZoneMinder/Frame.pm diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Frame.pm b/scripts/ZoneMinder/lib/ZoneMinder/Frame.pm new file mode 100644 index 000000000..6ce8f9ca0 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Frame.pm @@ -0,0 +1,78 @@ +# ========================================================================== +# +# ZoneMinder Monitor Module, $Date$, $Revision$ +# Copyright (C) 2001-2008 Philip Coombes +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Frame; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; + +use parent qw(ZoneMinder::Object); + +use vars qw/ $table $primary_key %fields /; +$table = 'Frames'; +$primary_key = 'Id'; + +%fields = ( + Id => 'Id', + EventId => 'EventId', + FrameId => 'FrameId', + Type => 'Type', + TimeStamp => 'TimeStamp', + Delta => 'Delta', + Score => 'Score', +); + +sub Event { + return new ZoneMinder::Event( $_[0]{EventId} ); +} # end sub Event + +1; +__END__ + +=head1 NAME + +ZoneMinder::Frame - Perl Class for Frames + +=head1 SYNOPSIS + +use ZoneMinder::Frame; + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2017 ZoneMinder LLC + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut From d2dc23b4e98090803ed516b3c5e77c03c2b1fc80 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 13 Nov 2018 16:35:09 -0500 Subject: [PATCH 115/310] fixes, better logging --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 18 ++---------- scripts/zmrecover.pl.in | 33 ++++++++++++++++------ 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 07b005aa1..8c8bf4944 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -342,19 +342,6 @@ sub GenerateVideo { sub delete { my $event = $_[0]; -<<<<<<< HEAD - if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) { - my ( $caller, undef, $line ) = caller; - Warning("Can't delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from $caller:$line"); - return; - } - if ( ! -e $event->Storage()->Path() ) { - Warning("Not deleting event because storage path doesn't exist"); - return; - } - Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}"); - $ZoneMinder::Database::dbh->ping(); -======= my $in_zmaudit = ( $0 =~ 'zmaudit.pl$'); @@ -371,7 +358,6 @@ sub delete { return; } } ->>>>>>> master if ( $$event{Id} ) { # Need to have an event Id if we are to delete from the db. @@ -680,9 +666,9 @@ sub recover_timestamps { my ( $Event, $path ) = @_; $path = $Event->Path() if ! $path; - if ( ! opendir(DIR, $path) ) { + if ( !opendir(DIR, $path) ) { Error("Can't open directory '$path': $!"); - next; + return; } my @contents = readdir(DIR); Debug('Have ' . @contents . " files in $path"); diff --git a/scripts/zmrecover.pl.in b/scripts/zmrecover.pl.in index 6b8f3831f..56ee724fb 100644 --- a/scripts/zmrecover.pl.in +++ b/scripts/zmrecover.pl.in @@ -231,9 +231,12 @@ Debug("@Monitors"); $Event->Height( $Monitor->Height() ); $Event->Orientation( $Monitor->Orientation() ); $Event->recover_timestamps(); - - $Event->save({}, 1); - Info("Event resurrected as " . $Event->to_string() ); + if ( $$Event{StartTime} ) { + $Event->save({}, 1); + Info("Event resurrected as " . $Event->to_string() ); + } else { + Warning("Unable to determine starttime. Not resurrecting this event."); + } next; } # end if resurrection } # event path exists @@ -291,8 +294,12 @@ Debug("@Monitors"); $Event->StorageId( $Storage->Id() ); $Event->DiskSpace( undef ); $Event->recover_timestamps(); - $Event->save({}, 1); - Debug("Event resurrected as " . $Event->to_string() ); + if ( $$Event{StartTime} ) { + $Event->save({}, 1); + Info("Event resurrected as " . $Event->to_string() ); + } else { + Warning("Unable to determine starttime. Not resurrecting this event."); + } next; } } # end if event found @@ -351,8 +358,12 @@ Debug("@Monitors"); $Event->Orientation( $Monitor->Orientation() ); $Event->StorageId( $Storage->Id() ); $Event->recover_timestamps(); - $Event->save({}, 1); - Info("Event resurrected as " . $Event->to_string() ); + if ( $$Event{StartTime} ) { + $Event->save({}, 1); + Info("Event resurrected as " . $Event->to_string() ); + } else { + Warning("Unable to determine starttime. Not resurrecting this event."); + } } } # end foreach event } # end search for Medium @@ -389,8 +400,12 @@ Debug("@Monitors"); $Event->Orientation( $Monitor->Orientation() ); $Event->StorageId( $Storage->Id() ); $Event->recover_timestamps(); - $Event->save({}, 1); - Debug("Event resurrected as " . $Event->to_string() ); + if ( $$Event{StartTime} ) { + $Event->save({}, 1); + Info("Event resurrected as " . $Event->to_string() ); + } else { + Warning("Unable to determine starttime. Not resurrecting this event."); + } } } # end foreach event chdir( $Storage->Path() ); From 381071eb0f880a9d90425d266723d6ae54241f01 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 13 Nov 2018 16:36:39 -0500 Subject: [PATCH 116/310] Fix log message to say recovering instead of auditing --- scripts/zmrecover.pl.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/zmrecover.pl.in b/scripts/zmrecover.pl.in index 56ee724fb..02083d757 100644 --- a/scripts/zmrecover.pl.in +++ b/scripts/zmrecover.pl.in @@ -120,7 +120,7 @@ if ( defined $storage_id ) { Error("No Storage Area found with Id $storage_id"); Term(); } - Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}"); + Info("Recovering from Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}"); } elsif ( $server_id ) { @Storage_Areas = ZoneMinder::Storage->find( ServerId => $server_id ); if ( ! @Storage_Areas ) { @@ -128,11 +128,11 @@ if ( defined $storage_id ) { Term(); } foreach my $Storage ( @Storage_Areas ) { - Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name() ); + Info('Recovering from ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name() ); } } else { @Storage_Areas = ZoneMinder::Storage->find(); - Info("Auditing All Storage Areas"); + Info("Recovering from All Storage Areas"); } my @Monitors = ZoneMinder::Monitor->find(); From d671761a35262f1e326665e881f48fbe746ade66 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 14 Nov 2018 12:54:10 -0500 Subject: [PATCH 117/310] simplify params to daemonControl since they really aren't being used anyways. Return the status text --- web/api/app/Controller/MonitorsController.php | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 185a06c84..9d5631043 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -182,6 +182,9 @@ class MonitorsController extends AppController { ($Monitor['ServerId']==ZM_SERVER_ID) ) ) { + if ( !defined('ZM_SERVER_ID')) { + Logger::Debug("Not defined ZM_SERVER_ID"); + } $this->daemonControl($this->Monitor->id, 'start'); } } else { @@ -349,17 +352,15 @@ class MonitorsController extends AppController { )); } - public function daemonControl($id, $command, $monitor=null, $daemon=null) { + public function daemonControl($id, $command) { $daemons = array(); - if ( !$monitor ) { - // Need to see if it is local or remote - $monitor = $this->Monitor->find('first', array( - 'fields' => array('Type', 'Function', 'Device'), - 'conditions' => array('Id' => $id) - )); - $monitor = $monitor['Monitor']; - } + // Need to see if it is local or remote + $monitor = $this->Monitor->find('first', array( + 'fields' => array('Type', 'Function', 'Device'), + 'conditions' => array('Id' => $id) + )); + $monitor = $monitor['Monitor']; if ( $monitor['Function'] == 'Monitor' ) { array_push($daemons, 'zmc'); @@ -369,6 +370,7 @@ class MonitorsController extends AppController { $zm_path_bin = Configure::read('ZM_PATH_BIN'); + $status_text = ''; foreach ( $daemons as $daemon ) { $args = ''; if ( $daemon == 'zmc' and $monitor['Type'] == 'Local' ) { @@ -378,7 +380,14 @@ class MonitorsController extends AppController { } $shellcmd = escapeshellcmd("$zm_path_bin/zmdc.pl $command $daemon $args"); - $status = exec( $shellcmd ); + Logger::Debug("Command $shellcmd"); + $status = exec($shellcmd); + $status_text .= $status."\n"; } - } + $this->set(array( + 'status' => 'ok', + 'statustext' => $status_text, + '_serialize' => array('status','statustext'), + )); + } // end function daemonControl } // end class MonitorsController From 9d8f0fef0c420c90055e13e20e0044c46259dd35 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 14 Nov 2018 12:54:40 -0500 Subject: [PATCH 118/310] add templates for daemonControl to api --- web/api/app/View/Monitors/json/daemon_control.ctp | 1 + web/api/app/View/Monitors/xml/daemon_control.ctp | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 web/api/app/View/Monitors/json/daemon_control.ctp create mode 100644 web/api/app/View/Monitors/xml/daemon_control.ctp diff --git a/web/api/app/View/Monitors/json/daemon_control.ctp b/web/api/app/View/Monitors/json/daemon_control.ctp new file mode 100644 index 000000000..4dbb0a055 --- /dev/null +++ b/web/api/app/View/Monitors/json/daemon_control.ctp @@ -0,0 +1 @@ +echo json_encode($status_text); diff --git a/web/api/app/View/Monitors/xml/daemon_control.ctp b/web/api/app/View/Monitors/xml/daemon_control.ctp new file mode 100644 index 000000000..8e7d3a290 --- /dev/null +++ b/web/api/app/View/Monitors/xml/daemon_control.ctp @@ -0,0 +1,2 @@ +$xml = Xml::fromArray(array('response' => $status_text)); +echo $xml->asXML(); From 51d8c0ea73932e7ee3bf037555d9e09b68d14a5b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 14 Nov 2018 12:59:44 -0500 Subject: [PATCH 119/310] add back daemon parameter, but make it actually work --- web/api/app/Controller/MonitorsController.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 9d5631043..69799a820 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -352,8 +352,7 @@ class MonitorsController extends AppController { )); } - public function daemonControl($id, $command) { - $daemons = array(); + public function daemonControl($id, $command, $daemon=null) { // Need to see if it is local or remote $monitor = $this->Monitor->find('first', array( @@ -362,10 +361,14 @@ class MonitorsController extends AppController { )); $monitor = $monitor['Monitor']; - if ( $monitor['Function'] == 'Monitor' ) { - array_push($daemons, 'zmc'); + if ( ! $daemon ) { + if ( $monitor['Function'] == 'Monitor' ) { + array_push($daemons, 'zmc'); + } else { + array_push($daemons, 'zmc', 'zma'); + } } else { - array_push($daemons, 'zmc', 'zma'); + array_push($daemons, $daemon); } $zm_path_bin = Configure::read('ZM_PATH_BIN'); From 786ca5b22a404f6377675349ea3893bf91618b8e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 14 Nov 2018 13:00:19 -0500 Subject: [PATCH 120/310] implement remove service restart for zma. Use daemonControl instead of saving the monitor when restarting zmc --- web/includes/Monitor.php | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index e5b44682c..977c14993 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -460,7 +460,7 @@ private $control_fields = array( } else if ( $this->ServerId() ) { $Server = $this->Server(); - $url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/'.$this->{'Id'}.'.json'; + $url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zmc.json'; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); @@ -472,17 +472,8 @@ private $control_fields = array( } } Logger::Debug("sending command to $url"); - $data = array('Monitor[Function]' => $this->{'Function'} ); - // use key 'http' even if you send the request to https://... - $options = array( - 'http' => array( - 'header' => "Content-type: application/x-www-form-urlencoded\r\n", - 'method' => 'POST', - 'content' => http_build_query($data) - ) - ); - $context = stream_context_create($options); + $context = stream_context_create(); try { $result = file_get_contents($url, false, $context); if ($result === FALSE) { /* Handle error */ @@ -518,6 +509,33 @@ private $control_fields = array( daemonControl( 'reload', 'zma', '-m '.$this->{'Id'} ); } } + } else if ( $this->ServerId() ) { + $Server = $this->Server(); + + $url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zma.json'; + if ( ZM_OPT_USE_AUTH ) { + if ( ZM_AUTH_RELAY == 'hashed' ) { + $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); + } elseif ( ZM_AUTH_RELAY == 'plain' ) { + $url = '?user='.$_SESSION['username']; + $url = '?pass='.$_SESSION['password']; + } elseif ( ZM_AUTH_RELAY == 'none' ) { + $url = '?user='.$_SESSION['username']; + } + } + Logger::Debug("sending command to $url"); + + $context = stream_context_create(); + try { + $result = file_get_contents($url, false, $context); + if ($result === FALSE) { /* Handle error */ + Error("Error restarting zma using $url"); + } + } catch ( Exception $e ) { + Error("Except $e thrown trying to restart zma"); + } + } else { + Error("Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor."); } // end if we are on the recording server } // end public function zmaControl From b291c060356d93c3be89c8aac624ddf31a0a6526 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 19 Nov 2018 16:45:56 -0500 Subject: [PATCH 121/310] Working zms h265 playing. --- src/zm_eventstream.cpp | 14 +++++-- src/zm_ffmpeg.cpp | 7 +--- src/zm_ffmpeg_input.cpp | 89 +++++++++++++++++++++++++++++++---------- src/zm_ffmpeg_input.h | 1 + src/zm_monitor.cpp | 2 +- src/zm_videostore.cpp | 86 +++++++++++++++++++++++++++------------ 6 files changed, 141 insertions(+), 58 deletions(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 22d3749d1..79d91df8a 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -71,8 +71,9 @@ bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) { if ( event_time ) { curr_stream_time = event_time; - curr_frame_id = 1; + curr_frame_id = 1; // curr_frame_id is 1-based if ( event_time >= event_data->start_time ) { + Debug(2, "event time is after event start"); for (unsigned int i = 0; i < event_data->frame_count; i++ ) { //Info( "eft %d > et %d", event_data->frames[i].timestamp, event_time ); if ( event_data->frames[i].timestamp >= event_time ) { @@ -231,7 +232,7 @@ bool EventStream::loadEventData(uint64_t event_id) { event_data->frames[i-1].timestamp = last_timestamp + ((i-last_id)*frame_delta); event_data->frames[i-1].offset = event_data->frames[i-1].timestamp - event_data->start_time; event_data->frames[i-1].in_db = false; - Debug(4,"Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)", + Debug(3,"Frame %d timestamp:(%f), offset(%f) delta(%f), in_db(%d)", i, event_data->frames[i-1].timestamp, event_data->frames[i-1].offset, @@ -664,11 +665,16 @@ Debug(1, "Loading image"); } else if ( ffmpeg_input ) { // Get the frame from the mp4 input Debug(1,"Getting frame from ffmpeg"); + AVFrame *frame; + if ( curr_frame_id == 1 ) { + // Special case, first frame, we want to send the initial keyframe. + frame = ffmpeg_input->get_frame( ffmpeg_input->get_video_stream_id(), 0 ); + } FrameData *frame_data = &event_data->frames[curr_frame_id-1]; - AVFrame *frame = ffmpeg_input->get_frame( ffmpeg_input->get_video_stream_id(), frame_data->offset ); + frame = ffmpeg_input->get_frame( ffmpeg_input->get_video_stream_id(), frame_data->offset ); if ( frame ) { image = new Image(frame); - av_frame_free(&frame); + //av_frame_free(&frame); } else { Error("Failed getting a frame."); return false; diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 6580b5ff7..642f14813 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -276,8 +276,8 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) if (flags & AVFMT_SHOW_IDS) Debug(1, "[0x%x]", st->id); if (lang) - Debug(1, "(%s)", lang->value); - Debug(1, ", frames:%d, timebase: %d/%d", st->nb_frames, st->time_base.num, st->time_base.den); + Debug(1, "language (%s)", lang->value); + Debug(1, "frames:%d, timebase: %d/%d", st->nb_frames, st->time_base.num, st->time_base.den); avcodec_string(buf, sizeof(buf), st->codec, is_output); Debug(1, ": %s", buf); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) @@ -303,9 +303,6 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) int tbn = st->time_base.den && st->time_base.num; int tbc = st->codec->time_base.den && st->codec->time_base.num; - if (fps || tbn || tbc) - Debug(3, "\n" ); - if (fps) zm_log_fps(av_q2d(st->avg_frame_rate), tbn || tbc ? "fps, " : "fps"); if (tbn) diff --git a/src/zm_ffmpeg_input.cpp b/src/zm_ffmpeg_input.cpp index ed2b7e1a2..9f0336bbf 100644 --- a/src/zm_ffmpeg_input.cpp +++ b/src/zm_ffmpeg_input.cpp @@ -10,6 +10,7 @@ FFmpeg_Input::FFmpeg_Input() { av_register_all(); avcodec_register_all(); streams = NULL; + frame = NULL; } FFmpeg_Input::~FFmpeg_Input() { @@ -34,13 +35,16 @@ int FFmpeg_Input::Open( const char *filepath ) { /** 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() ); + Error( + "Could not open find stream info (error '%s')", + av_make_error_string(error).c_str() + ); avformat_close_input(&input_format_context); return error; } streams = new stream[input_format_context->nb_streams]; + Debug(2,"Have %d streams", input_format_context->nb_streams); for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) { if ( is_video_stream( input_format_context->streams[i] ) ) { @@ -53,10 +57,13 @@ int FFmpeg_Input::Open( const char *filepath ) { } } else if ( is_audio_stream( input_format_context->streams[i] ) ) { if ( audio_stream_id == -1 ) { + Debug(2,"Audio stream is %d", i); audio_stream_id = i; } else { Warning( "Have another audio stream." ); } + } else { + Warning("Unknown stream type"); } streams[i].frame_count = 0; @@ -95,12 +102,11 @@ int FFmpeg_Input::Open( const char *filepath ) { } // end int FFmpeg_Input::Open( const char * filepath ) AVFrame *FFmpeg_Input::get_frame( int stream_id ) { - Debug(1, "Getting frame from stream %d", 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(); + av_init_packet(&packet); char errbuf[AV_ERROR_MAX_STRING_SIZE]; while ( !frameComplete ) { @@ -127,15 +133,13 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) { 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"); - } + 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; + } #if HAVE_AVUTIL_HWCONTEXT_H if ( hwaccel ) { @@ -150,17 +154,24 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) { 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 ); + zm_av_packet_unref(&packet); continue; } } else { #endif - Debug(1,"Getting frame %d", streams[packet.stream_index].frame_count); - ret = avcodec_receive_frame( context, frame ); + if ( frame ) { + av_frame_free(&frame); + frame = zm_av_frame_alloc(); + } else { + frame = zm_av_frame_alloc(); + } + //Debug(1,"Getting frame %d", streams[packet.stream_index].frame_count); + 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 ); + av_frame_free(&frame); continue; } @@ -170,11 +181,18 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) { frameComplete = 1; # else + if ( frame ) { + av_frame_free(&frame); + frame = zm_av_frame_alloc(); + } else { + frame = zm_av_frame_alloc(); + } ret = zm_avcodec_decode_video(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 ); + av_frame_free(&frame); continue; } #endif @@ -190,15 +208,42 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) { AVFrame *FFmpeg_Input::get_frame( int stream_id, double at ) { Debug(1, "Getting frame from stream %d at %f", stream_id, at); - int64_t seek_target = (int64_t)at * AV_TIME_BASE; - Debug(1, "Getting frame from stream %d at %" PRId64, stream_id, seek_target); + int64_t seek_target = (int64_t)(at * AV_TIME_BASE); + Debug(1, "Getting frame from stream %d at seektarget: %" PRId64, stream_id, seek_target); seek_target = av_rescale_q(seek_target, AV_TIME_BASE_Q, input_format_context->streams[stream_id]->time_base); Debug(1, "Getting frame from stream %d at %" PRId64, stream_id, seek_target); + if ( frame ) { + if ( (frame->pts + frame->pkt_duration) > seek_target ) { + // The current frame is still the valid picture. + Debug(2,"Returning previous frame which is still good"); + return frame; + } + if ( frame->pts < seek_target ) { + Debug(2, "Frame pts %" PRId64 " pkt_pts %" PRId64 " duration %" PRId64, frame->pts, frame->pkt_pts, frame->pkt_duration); + while ( frame && (frame->pts < seek_target) ) { + if ( ! get_frame(stream_id) ) + return frame; + } + return frame; + } + } + int ret; - if ( ( ret = av_seek_frame(input_format_context, stream_id, seek_target, 0/*FORWARDS*/) < 0 ) ) { - Error("Unable to seek in stream"); - return NULL; + if ( frame ) { + if ( ( ret = av_seek_frame(input_format_context, stream_id, seek_target, AVSEEK_FLAG_ANY) < 0 ) ) { + Error("Unable to seek in stream"); + return NULL; + } + + } else { + // Must go for a keyframe + if ( ( ret = av_seek_frame(input_format_context, stream_id, seek_target, + AVSEEK_FLAG_FRAME + ) < 0 ) ) { + Error("Unable to seek in stream"); + return NULL; + } } return get_frame(stream_id); diff --git a/src/zm_ffmpeg_input.h b/src/zm_ffmpeg_input.h index ba03fad52..2f524ac45 100644 --- a/src/zm_ffmpeg_input.h +++ b/src/zm_ffmpeg_input.h @@ -41,6 +41,7 @@ class FFmpeg_Input { int video_stream_id; int audio_stream_id; AVFormatContext *input_format_context; + AVFrame *frame; }; #endif diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index d5d2a87a7..5091ff540 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1442,7 +1442,7 @@ bool Monitor::Analyse() { //TODO: What happens is the event closes and sets recording to false then recording to true again so quickly that our capture daemon never picks it up. Maybe need a refresh flag? if ( (!signal_change && signal) && (function == RECORD || function == MOCORD) ) { if ( event ) { - Debug(3, "Detected new event at (%d.%d)", timestamp->tv_sec, timestamp->tv_usec); + Debug(3, "Have signal and recording with open event at (%d.%d)", timestamp->tv_sec, timestamp->tv_usec); if ( section_length && ( timestamp->tv_sec >= section_length ) ) { // TODO: Wouldn't this be clearer if we just did something like if now - event->start > section_length ? diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 67201ff89..b6243ab34 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -87,27 +87,27 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + video_out_stream = avformat_new_stream(oc, NULL); + if ( !video_out_stream ) { + Error("Unable to create video out stream"); + return; + } else { + Debug(2, "Success creating video out stream"); + } // 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 = video_out_stream->codec; + //video_out_ctx = avcodec_alloc_context3(NULL); // Copy params from instream to ctx ret = avcodec_parameters_to_context(video_out_ctx, video_in_stream->codecpar); - if (ret < 0) { + if ( ret < 0 ) { Error("Could not initialize ctx parameteres"); return; } else { zm_dump_codec(video_out_ctx); } - video_out_stream = avformat_new_stream(oc, NULL); - if (!video_out_stream) { - Error("Unable to create video out stream\n"); - return; - } else { - Debug(2, "Success creating video out stream"); - } - if ( !video_out_ctx->codec_tag ) { video_out_ctx->codec_tag = av_codec_get_tag(oc->oformat->codec_tag, video_in_ctx->codec_id); @@ -117,21 +117,46 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, // 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"); + if ( ret < 0 ) { + Error("Could not initialize stream parameters"); return; } else { Debug(2, "Success setting parameters"); } + zm_dump_codecpar(video_in_stream->codecpar); zm_dump_codecpar(video_out_stream->codecpar); + AVCodec *video_out_codec = avcodec_find_encoder( video_out_ctx->codec_id ); + if ( !video_out_codec ) { +#if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) + Fatal( "Could not find encoder for '%s'", avcodec_get_name( video_out_ctx->codec_id ) ); +#else + Fatal( "Could not find encoder for '%d'", video_out_ctx->codec_id ); +#endif + } + + AVDictionary *opts = 0; + if ( (ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0 ) { + Warning("Can't open video codec (%s) %s", + video_out_codec->name, + av_make_error_string(ret).c_str() + ); + video_out_codec = NULL; + } + + 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); + } + + #else video_out_stream = avformat_new_stream(oc, NULL); //(AVCodec *)(video_in_ctx->codec)); //avformat_new_stream(oc,(const AVCodec *)(video_in_ctx->codec)); if ( !video_out_stream ) { - Fatal("Unable to create video out stream\n"); + Fatal("Unable to create video out stream"); } else { Debug(2, "Success creating video out stream"); } @@ -160,6 +185,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, // Just copy them from the in, no reason to choose different video_out_ctx->time_base = video_in_ctx->time_base; if ( ! (video_out_ctx->time_base.num && video_out_ctx->time_base.den) ) { + Debug(2,"No timebase found in video in context, defaulting to Q"); video_out_ctx->time_base = AV_TIME_BASE_Q; } video_out_stream->time_base = video_in_stream->time_base; @@ -182,17 +208,17 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, } Monitor::Orientation orientation = monitor->getOrientation(); - 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__); + 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__); + 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); } @@ -209,7 +235,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, resample_ctx = NULL; #endif - if (audio_in_stream) { + if ( audio_in_stream ) { Debug(3, "Have audio stream"); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) @@ -352,6 +378,14 @@ bool VideoStore::open() { //avformat_free_context(oc); return false; } + 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, + video_out_ctx->time_base.num, + video_out_ctx->time_base.den); return true; } // end VideoStore::open() @@ -359,7 +393,7 @@ VideoStore::~VideoStore() { if ( oc->pb ) { - if (audio_out_codec) { + 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; @@ -394,8 +428,8 @@ VideoStore::~VideoStore() { break; } #endif - Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", pkt.pts, - pkt.dts, pkt.duration); + Debug(2, "writing flushed packet pts(%d) dts(%d) duration(%d)", + pkt.pts, pkt.dts, pkt.duration); #if 0 if ( pkt.duration > 0 ) @@ -450,7 +484,7 @@ VideoStore::~VideoStore() { avcodec_close(video_out_ctx); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - avcodec_free_context(&video_out_ctx); + //avcodec_free_context(&video_out_ctx); #endif video_out_ctx = NULL; Debug(4, "Success freeing video_out_ctx"); @@ -780,7 +814,7 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt) { Debug(2, "Starting video first_pts will become %" PRId64, ipkt->pts); video_first_pts = ipkt->pts; } else { - if ( ipkt->pts < video_first_pts ) { + if ( 0 && ipkt->pts < video_first_pts ) { Debug(1, "Resetting first_pts from %" PRId64 " to %" PRId64, video_last_pts, ipkt->pts); video_first_pts -= video_last_pts; // wrap around, need to figure out the distance FIXME having this wrong should cause a jump, but then play ok? @@ -805,7 +839,7 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt) { Debug(1, "Starting video first_dts will become (%" PRId64 ")", ipkt->dts); video_first_dts = ipkt->dts; } else { - if ( ipkt->dts < video_first_dts ) { + if ( 0 && ipkt->dts < video_first_dts ) { Debug(1, "Resetting first_dts from (%" PRId64 ") to (%" PRId64")", video_first_dts, ipkt->dts); video_first_dts -= video_last_dts; From 73d27f50954142cf26c019220f94d5435f227ad0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 23 Nov 2018 10:10:50 -0500 Subject: [PATCH 122/310] Add code to see if a db event is stored at the wrong storage area --- scripts/zmaudit.pl.in | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 7a5017fbd..c467d26e3 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -531,6 +531,19 @@ MAIN: while( $loop ) { next; } Debug("Event $db_event is not in fs. Should have been at ".$Event->Path()); + # Check for existence in other Storage Areas + foreach my $Storage ( ZoneMinder::Storage->find( ( $$Event{StorageId} ? ( 'Id !='=>$$Event{StorageId} ) : () ) ) ) { + my $path = $Storage->Path().'/'.$Event->RelativePath(); + if ( -e $path ) { + Info("Event $$Event{Id} found at $path instead of $$Event{Path}"); + if ( confirm() ) { + $Event->save({StorageId=>$$Storage{Id}}); + last; + } + } else { + Debug("$$Event{Id} Not found at $path"); + } + } if ( $Event->Archived() ) { Warning("Event $$Event{Id} is Archived. Taking no further action on it."); next; From 6d192800697c0e0d86b79d39b00067d368199ca4 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 23 Nov 2018 12:16:11 -0500 Subject: [PATCH 123/310] Set Scheme when Shallow. Improve debug message. Fix spacing --- scripts/zmaudit.pl.in | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index c467d26e3..afd4881a0 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -409,21 +409,22 @@ MAIN: while( $loop ) { if ( ! $$Storage{Scheme} ) { Error("Storage Scheme not set on $$Storage{Name}"); - if ( ! chdir( $monitor_dir ) ) { - Error( "Can't chdir directory '$$Storage{Path}/$monitor_dir': $!" ); + if ( ! chdir($monitor_dir) ) { + Error("Can't chdir directory '$$Storage{Path}/$monitor_dir': $!"); next; } - if ( ! opendir( DIR, "." ) ) { - Error( "Can't open directory '$$Storage{Path}/$monitor_dir': $!" ); + if ( ! opendir(DIR, '.') ) { + Error("Can't open directory '$$Storage{Path}/$monitor_dir': $!"); next; } - my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); - closedir( DIR ); + my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir(DIR); + closedir(DIR); my $count = 0; foreach my $event ( @temp_events ) { my $Event = $fs_events->{$event} = new ZoneMinder::Event(); $$Event{Id} = $event; #$$Event{Path} = $event_path; + $$Event{Scheme} = 'Shallow'; $Event->MonitorId( $monitor_dir ); $Event->StorageId( $Storage->Id() ); } # end foreach event @@ -570,7 +571,7 @@ MAIN: while( $loop ) { Debug("Database event $$Event{Id} apparently exists at " . $Event->Path() ); } else { if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { - aud_print( "Database event '$db_monitor/$db_event' does not exist at " . $Event->Path().' in filesystem, deleting' ); + aud_print("Database event '$db_monitor/$db_event' does not exist at " . $Event->Path().' in filesystem, deleting'); if ( confirm() ) { $Event->delete(); $cleaned = 1; @@ -580,7 +581,7 @@ MAIN: while( $loop ) { } } # end if exists in filesystem } else { - Debug("Found fs event for $db_event, $age at " . $$fs_events{$db_event}->Path()); + Debug("Found fs event for id $db_event, $age seconds old at " . $$fs_events{$db_event}->Path()); my $Event = new ZoneMinder::Event( $db_event ); if ( ! $Event->check_for_in_filesystem() ) { Warning("Not found at " . $Event->Path() . ' was found at ' . $$fs_events{$db_event}->Path() ); From 817a760559bb1c3e2e0932495c481715b55df34f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 23 Nov 2018 13:04:20 -0500 Subject: [PATCH 124/310] send logPrint a pre-formatted string --- src/zm_ffmpeg.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 793869e4a..f9c2029c9 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -56,7 +56,10 @@ void log_libav_callback( void *ptr, int level, const char *fmt, va_list vargs ) } if ( log ) { - log->logPrint(false, __FILE__, __LINE__, log_level, fmt, vargs); + char logString[8192]; + vsnprintf(logString, sizeof(logString)-1, fmt, vargs); + + log->logPrint(false, __FILE__, __LINE__, log_level, logString); } } From 19f3cce41f60445773281a4b73618e65c10e9276 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 23 Nov 2018 13:54:14 -0500 Subject: [PATCH 125/310] Dont auto-guess pathPrefix --- web/includes/Server.php | 3 ++- web/skins/classic/views/js/watch.js.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/web/includes/Server.php b/web/includes/Server.php index 7d0b5f436..e39c92a3e 100644 --- a/web/includes/Server.php +++ b/web/includes/Server.php @@ -95,7 +95,8 @@ class Server { if ( isset($this->{'PathPrefix'}) and $this->{'PathPrefix'} ) { return $this->{'PathPrefix'}; } - return $_SERVER['PHP_SELF']; + return ''; + //return $_SERVER['PHP_SELF']; } public function __call($fn, array $args){ diff --git a/web/skins/classic/views/js/watch.js.php b/web/skins/classic/views/js/watch.js.php index 51c1801e8..d9826c66f 100644 --- a/web/skins/classic/views/js/watch.js.php +++ b/web/skins/classic/views/js/watch.js.php @@ -48,7 +48,7 @@ var maxDisplayEvents = ; var monitorId = Id() ?>; var monitorWidth = Width() ?>; var monitorHeight = Height() ?>; -var monitorUrl = 'Url(); ?>'; +var monitorUrl = 'Url(); ?>/index.php'; var monitorType = 'Type() ) ?>'; var monitorRefresh = 'Refresh() ) ?>'; From 32337c5dbb1de73a2fc890bdb360b46fd0b308ba Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 23 Nov 2018 15:13:47 -0500 Subject: [PATCH 126/310] fix prompt when updating StorageArea --- scripts/zmaudit.pl.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index afd4881a0..cbfb0e923 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -537,7 +537,7 @@ MAIN: while( $loop ) { my $path = $Storage->Path().'/'.$Event->RelativePath(); if ( -e $path ) { Info("Event $$Event{Id} found at $path instead of $$Event{Path}"); - if ( confirm() ) { + if ( confirm('update', 'updating') ) { $Event->save({StorageId=>$$Storage{Id}}); last; } From 1b74aa959b7a1d88d7d6b6c2f53e5c58a9f30a37 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 23 Nov 2018 15:15:51 -0500 Subject: [PATCH 127/310] Have to refresh the Path in the Event when updating Storage Area --- scripts/zmaudit.pl.in | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index cbfb0e923..678c07d3c 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -539,6 +539,7 @@ MAIN: while( $loop ) { Info("Event $$Event{Id} found at $path instead of $$Event{Path}"); if ( confirm('update', 'updating') ) { $Event->save({StorageId=>$$Storage{Id}}); + $Event->Path(undef);# Refresh Path last; } } else { From 7fb0e1eaf7a59f7628f1ecc3da55ecc389e9b8dc Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 26 Nov 2018 15:02:34 -0500 Subject: [PATCH 128/310] Cache All StorageAreas and just to next event if we find the event somewhere other than where it should be. --- scripts/zmaudit.pl.in | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 678c07d3c..a455af74a 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -176,8 +176,10 @@ MAIN: while( $loop ) { } # end while can't connect to the db my @Storage_Areas; + my @all_Storage_Areas = ZoneMinder::Storage->find(); + if ( defined $storage_id ) { - @Storage_Areas = ZoneMinder::Storage->find( Id=>$storage_id ); + @Storage_Areas = map { $$_{Id} == $storage_id ? $_ : () } @all_Storage_Areas; if ( !@Storage_Areas ) { Error("No Storage Area found with Id $storage_id"); Term(); @@ -403,7 +405,7 @@ MAIN: while( $loop ) { $$Event{RelativePath} = $event_dir; $Event->MonitorId( $monitor_dir ); $Event->StorageId( $Storage->Id() ); - $Event->StartTime( POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($$Event{Path})) ) ); + $Event->StartTime( POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($Event->Path())) ) ); } # end foreach event } @@ -523,7 +525,7 @@ MAIN: while( $loop ) { # If we found the monitor in the file system my $fs_events = $fs_monitors->{$db_monitor}; - while ( my ( $db_event, $age ) = each( %$db_events ) ) { +EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { if ( ! ($fs_events and defined( $fs_events->{$db_event} ) ) ) { Debug("Don't have an fs event for $db_event"); my $Event = ZoneMinder::Event->find_one( Id=>$db_event ); @@ -533,14 +535,15 @@ MAIN: while( $loop ) { } Debug("Event $db_event is not in fs. Should have been at ".$Event->Path()); # Check for existence in other Storage Areas - foreach my $Storage ( ZoneMinder::Storage->find( ( $$Event{StorageId} ? ( 'Id !='=>$$Event{StorageId} ) : () ) ) ) { + foreach my $Storage ( @all_Storage_Areas ) { + next if $$Storage{Id} == $$Event{StorageId}; + my $path = $Storage->Path().'/'.$Event->RelativePath(); if ( -e $path ) { Info("Event $$Event{Id} found at $path instead of $$Event{Path}"); if ( confirm('update', 'updating') ) { $Event->save({StorageId=>$$Storage{Id}}); - $Event->Path(undef);# Refresh Path - last; + next EVENT; } } else { Debug("$$Event{Id} Not found at $path"); From d6fb4e19105115c827c39697460e79b002dd007e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 26 Nov 2018 15:09:04 -0500 Subject: [PATCH 129/310] Add StorageId function to clear Storage when you set a new StorageId. Clear Path when assigning a new StorageArea --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 5916f8cdb..50eef1d6f 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -483,9 +483,23 @@ sub delete_files { } } # end sub delete_files +sub StorageId { + my $event = shift; + if ( @_ ) { + $$event{StorageId} = shift; + delete $$event{Storage}; + delete $$event{Path}; + } + return $$event{StorageId}; +} + sub Storage { if ( @_ > 1 ) { $_[0]{Storage} = $_[1]; + if ( $_[0]{Storage} ) { + $_[0]{StorageId} = $_[0]{Storage}->Id(); + delete $_[0]{Path}; + } } if ( ! $_[0]{Storage} ) { $_[0]{Storage} = new ZoneMinder::Storage($_[0]{StorageId}); From e15bbba0908ec8543a4ba52b210ad75a91d0a307 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 26 Nov 2018 15:45:05 -0500 Subject: [PATCH 130/310] Make debug line more useful by printing out the containing directory --- scripts/zmaudit.pl.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index a455af74a..e36fcde4b 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -1000,7 +1000,7 @@ sub delete_empty_directories { #Debug("delete_empty_directories $_[0] has " . @contents .' entries:' . ( @contents <= 2 ? join(',',@contents) : '' )); my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents; if ( @dirs ) { - Debug("Have " . @dirs . " dirs"); + Debug("Have " . @dirs . " dirs in $_[0]"); foreach ( @dirs ) { delete_empty_directories( $_[0].'/'.$_ ); } From 6de704922c442690598cbea387e1ea2bd71d6f0b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 27 Nov 2018 17:01:49 -0500 Subject: [PATCH 131/310] Set scheme to '' if undefined to quiet warnings --- scripts/zmaudit.pl.in | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index e36fcde4b..ce3fb1397 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -591,6 +591,7 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { Warning("Not found at " . $Event->Path() . ' was found at ' . $$fs_events{$db_event}->Path() ); Warning($Event->to_string()); Warning($$fs_events{$db_event}->to_string()); + $$Event{Scheme} = '' if ! defined $$Event{Scheme}; if ( $$fs_events{$db_event}->Scheme() ne $Event->Scheme() ) { Info("Updating scheme on event $$Event{Id} from $$Event{Scheme} to $$fs_events{$db_event}{Scheme}"); $Event->Scheme($$fs_events{$db_event}->Scheme()); From f5328265ef97701888ba8f00eab1a85a94838de1 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 28 Nov 2018 09:12:22 -0500 Subject: [PATCH 132/310] fix missing daemons definition --- web/api/app/Controller/MonitorsController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 69799a820..8908955b5 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -361,6 +361,7 @@ class MonitorsController extends AppController { )); $monitor = $monitor['Monitor']; + $daemons = array(); if ( ! $daemon ) { if ( $monitor['Function'] == 'Monitor' ) { array_push($daemons, 'zmc'); From 1a85e282e2ff51bfcf567eea65de2349f39c3bc1 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 29 Nov 2018 10:09:49 -0500 Subject: [PATCH 133/310] use find_one instead of new. We seem to be comparing against unfound event objects --- scripts/zmaudit.pl.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 6a1d8554f..c373c2a04 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -592,8 +592,8 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { } # end if exists in filesystem } else { Debug("Found fs event for id $db_event, $age seconds old at " . $$fs_events{$db_event}->Path()); - my $Event = new ZoneMinder::Event( $db_event ); - if ( ! $Event->check_for_in_filesystem() ) { + my $Event = ZoneMinder::Event->find_one( Id=>$db_event ); + if ( $Event and ! $Event->check_for_in_filesystem() ) { Warning("Not found at " . $Event->Path() . ' was found at ' . $$fs_events{$db_event}->Path() ); Warning($Event->to_string()); Warning($$fs_events{$db_event}->to_string()); From a3047dbac6683ae4ceccc9fca6a919315ffca8c9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 29 Nov 2018 10:32:22 -0500 Subject: [PATCH 134/310] fix submodules --- web/api/app/Plugin/CakePHP-Enum-Behavior | 2 +- web/api/app/Plugin/Crud | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/api/app/Plugin/CakePHP-Enum-Behavior b/web/api/app/Plugin/CakePHP-Enum-Behavior index 7108489f2..ea90c0cd7 160000 --- a/web/api/app/Plugin/CakePHP-Enum-Behavior +++ b/web/api/app/Plugin/CakePHP-Enum-Behavior @@ -1 +1 @@ -Subproject commit 7108489f218c54d36d235d3af91d6da2f8311237 +Subproject commit ea90c0cd7f6e24333a90885e563b5d30b793db29 diff --git a/web/api/app/Plugin/Crud b/web/api/app/Plugin/Crud index c3976f147..0bd63fb46 160000 --- a/web/api/app/Plugin/Crud +++ b/web/api/app/Plugin/Crud @@ -1 +1 @@ -Subproject commit c3976f1478c681b0bbc132ec3a3e82c3984eeed5 +Subproject commit 0bd63fb464957080ead342db58ca9e01532cf1ef From af501128f2bca9e7198cf469c8a8c3928063da5c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 29 Nov 2018 10:54:01 -0500 Subject: [PATCH 135/310] fix invalid use of %s. Pass the buffer as the format string instead of a ... param --- src/zm_remote_camera_http.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index 508ed8807..53ca71be2 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -287,7 +287,7 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { total_bytes_to_read -= bytes_read; } while ( total_bytes_to_read ); - Debug(4, "%s", buffer); + Debug(4, buffer); return total_bytes_read; } From 9cbc3352b9c7f4ea67d967246b5a607faf00f2fd Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 29 Nov 2018 13:09:29 -0500 Subject: [PATCH 136/310] Implement an error count. Return -1 when it gets over 100 so that the camera will be reopened. --- src/zm_ffmpeg_camera.cpp | 12 +++++++++++- src/zm_ffmpeg_camera.h | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index b4f267155..9cb56a0ec 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -141,6 +141,7 @@ FfmpegCamera::FfmpegCamera( video_last_pts = 0; have_video_keyframe = false; packetqueue = NULL; + error_count = 0; #if HAVE_LIBSWSCALE mConvertContext = NULL; @@ -334,6 +335,7 @@ int FfmpegCamera::OpenFfmpeg() { int ret; have_video_keyframe = false; + error_count = 0; // Open the input, not necessarily a file #if !LIBAVFORMAT_VERSION_CHECK(53, 2, 0, 4, 0) @@ -745,10 +747,18 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event if ( packet.pts < -100000 ) { // Ignore packets that have crazy negative pts. They aren't supposed to happen. - Warning("Ignore packet because pts is massively negative"); + Warning("Ignore packet because pts %" PRId64 " is massively negative", packet.pts); dumpPacket(&packet,"Ignored packet"); + if ( error_count > 100 ) { + Error("Bad packet count over 100, going to close and re-open stream"); + return -1; + } + error_count += 1; continue; } + // If we get a goot frame, decrease the error count.. We could zero it... + if ( error_count ) error_count -= 1; + int keyframe = packet.flags & AV_PKT_FLAG_KEY; bytes += packet.size; dumpPacket(&packet,"Captured Packet"); diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index 067361ae5..9cd21f472 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -87,6 +87,7 @@ class FfmpegCamera : public Camera { #endif int64_t startTime; + int error_count; 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 ); From e8622b68dcb18ce2373798d7d94c15002c7c9ec5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 29 Nov 2018 13:17:41 -0500 Subject: [PATCH 137/310] whitespace, delete dead code --- src/zm_videostore.cpp | 66 +++++++++++++------------------------------ 1 file changed, 19 insertions(+), 47 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index a3b66646b..0c214bef7 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -554,28 +554,28 @@ bool VideoStore::setup_resampler() { audio_out_ctx->channel_layout = av_get_default_channel_layout(audio_out_ctx->channels); } - 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]; + for ( unsigned int i = 0; audio_out_codec->supported_samplerates[i]; i++) { - if (audio_out_ctx->sample_rate == - audio_out_codec->supported_samplerates[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 = audio_out_codec->supported_samplerates[0]; - Debug(1, "Sampel rate is no good, setting to (%d)", + Debug(1, "Sample rate is no good, setting to (%d)", audio_out_codec->supported_samplerates[0]); } } /* 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; @@ -584,11 +584,10 @@ bool VideoStore::setup_resampler() { audio_out_ctx->time_base = (AVRational){1, audio_out_ctx->sample_rate}; - #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 < 0 ) { Error("Could not initialize stream parameteres"); return false; } @@ -616,13 +615,13 @@ 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())) { + 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; @@ -630,13 +629,13 @@ bool VideoStore::setup_resampler() { // Setup the audio resampler resample_ctx = avresample_alloc_context(); - if (!resample_ctx) { - Error("Could not allocate resample ctx\n"); + if ( !resample_ctx ) { + Error("Could not allocate resample ctx"); 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); @@ -664,38 +663,11 @@ bool VideoStore::setup_resampler() { audio_out_ctx->channels, 0); ret = avresample_open(resample_ctx); - if (ret < 0) { - Error("Could not open resample ctx\n"); + if ( ret < 0 ) { + Error("Could not open resample ctx"); 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; @@ -707,17 +679,17 @@ bool VideoStore::setup_resampler() { audio_out_ctx->sample_fmt, 0); converted_in_samples = (uint8_t *)av_malloc(audioSampleBuffer_size); - if (!converted_in_samples) { - Error("Could not allocate converted in sample pointers\n"); + if ( !converted_in_samples ) { + Error("Could not allocate converted in sample pointers"); 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) { - Error("Could not allocate converted in sample pointers\n"); + Error("Could not allocate converted in sample pointers"); return false; } From fe45e83bb4f3a58ac95567b8b12d2feae73d81f0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 29 Nov 2018 15:54:25 -0500 Subject: [PATCH 138/310] Fix PathToIndex --- web/includes/Server.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/includes/Server.php b/web/includes/Server.php index e91a84142..06219a1f5 100644 --- a/web/includes/Server.php +++ b/web/includes/Server.php @@ -110,8 +110,7 @@ class Server { if ( isset($this->{'PathToIndex'}) and $this->{'PathToIndex'} ) { return $this->{'PathToIndex'}; } - return ''; - //return $_SERVER['PHP_SELF']; + return $_SERVER['PHP_SELF']; } public function UrlToIndex( ) { From 0cf56f186c1b1dba4c827b2a30bc1060a1fccc4b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 3 Dec 2018 10:32:01 -0500 Subject: [PATCH 139/310] add frame_size to stream dump --- src/zm_ffmpeg.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index f9c2029c9..4e880e1a5 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -311,6 +311,11 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) int flags = (is_output ? ic->oformat->flags : ic->iformat->flags); AVStream *st = ic->streams[i]; AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0); +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + AVCodecParameters *codec = st->codecpar; +#else + AVCodecContext *codec = st->codec; +#endif Debug(1, " Stream #%d:%d", index, i); @@ -320,19 +325,20 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) Debug(1, "[0x%x]", st->id); if (lang) Debug(1, "(%s)", lang->value); - Debug(1, ", frames:%d, timebase: %d/%d", st->codec_info_nb_frames, st->time_base.num, st->time_base.den); + Debug(1, ", frames:%d, frame_size:%d timebase: %d/%d", + st->codec_info_nb_frames, + codec->frame_size, + st->time_base.num, + st->time_base.den); avcodec_string(buf, sizeof(buf), st->codec, is_output); Debug(1, ": %s", buf); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - AVCodecParameters *codec = st->codecpar; -#else - AVCodecContext *codec = st->codec; -#endif - if (st->sample_aspect_ratio.num && // default - av_cmp_q(st->sample_aspect_ratio, codec->sample_aspect_ratio)) { + if ( st->sample_aspect_ratio.num && // default + av_cmp_q(st->sample_aspect_ratio, codec->sample_aspect_ratio) + ) { AVRational display_aspect_ratio; - av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, + av_reduce(&display_aspect_ratio.num, + &display_aspect_ratio.den, codec->width * (int64_t)st->sample_aspect_ratio.num, codec->height * (int64_t)st->sample_aspect_ratio.den, 1024 * 1024); @@ -341,7 +347,7 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) display_aspect_ratio.num, display_aspect_ratio.den); } - if (st->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + if ( st->codec->codec_type == AVMEDIA_TYPE_VIDEO ) { int fps = st->avg_frame_rate.den && st->avg_frame_rate.num; int tbn = st->time_base.den && st->time_base.num; int tbc = st->codec->time_base.den && st->codec->time_base.num; @@ -377,7 +383,6 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) Debug(1, " (visual impaired)"); if (st->disposition & AV_DISPOSITION_CLEAN_EFFECTS) Debug(1, " (clean effects)"); - Debug(1, "\n"); //dump_metadata(NULL, st->metadata, " "); From ddd2e24dcb573091df1c04d52faa991781c3466e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 3 Dec 2018 10:32:37 -0500 Subject: [PATCH 140/310] fix ffmpeg warnings. We have to copy parameters from the context to the stream AFTER we open the codec --- src/zm_videostore.cpp | 62 +++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 0c214bef7..746036efe 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -238,7 +238,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, avformat_new_stream(oc, (AVCodec *)audio_in_ctx->codec); #endif if ( !audio_out_stream ) { - Error("Unable to create audio out stream\n"); + Error("Unable to create audio out stream"); audio_out_stream = NULL; } else { Debug(2, "setting parameters"); @@ -265,7 +265,6 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, Debug(2, "Setting audio codec tag to %d", audio_out_ctx->codec_tag); } - #else audio_out_ctx = audio_out_stream->codec; ret = avcodec_copy_context(audio_out_ctx, audio_in_ctx); @@ -287,7 +286,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, } // end if is AAC if ( audio_out_stream ) { - if (oc->oformat->flags & AVFMT_GLOBALHEADER) { + if ( oc->oformat->flags & AVFMT_GLOBALHEADER ) { #if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) audio_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; #else @@ -297,7 +296,6 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, } } // end if audio_in_stream - video_last_pts = 0; video_last_dts = 0; audio_last_pts = 0; @@ -310,12 +308,11 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, bool VideoStore::open() { /* open the out file, if needed */ - if (!(out_format->flags & AVFMT_NOFILE)) { + if ( !(out_format->flags & AVFMT_NOFILE) ) { ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, NULL, NULL); - if (ret < 0) { + if ( ret < 0 ) { Error("Could not open out file '%s': %s\n", filename, av_make_error_string(ret).c_str()); - return false; } } @@ -339,9 +336,9 @@ bool VideoStore::open() { } else if (av_dict_count(opts) != 0) { Warning("some options not set\n"); } - if (opts) av_dict_free(&opts); - if (ret < 0) { - Error("Error occurred when writing out file header to %s: %s\n", + if ( opts ) av_dict_free(&opts); + if ( ret < 0 ) { + Error("Error occurred when writing out file header to %s: %s", filename, av_make_error_string(ret).c_str()); /* free the stream */ avio_closep(&oc->pb); @@ -506,13 +503,13 @@ bool VideoStore::setup_resampler() { avcodec_find_decoder(audio_in_ctx->codec_id); #endif ret = avcodec_open2(audio_in_ctx, audio_in_codec, NULL); - if (ret < 0) { + if ( ret < 0 ) { Error("Can't open in codec!"); return false; } 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; } @@ -521,7 +518,6 @@ bool VideoStore::setup_resampler() { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // audio_out_ctx = audio_out_stream->codec; audio_out_ctx = avcodec_alloc_context3(audio_out_codec); - if ( !audio_out_ctx ) { Error("could not allocate codec ctx for AAC"); audio_out_stream = NULL; @@ -530,7 +526,7 @@ bool VideoStore::setup_resampler() { Debug(2, "Have audio_out_ctx"); // Now copy them to the out stream - audio_out_stream = avformat_new_stream(oc, NULL); + audio_out_stream = avformat_new_stream(oc, audio_out_codec); #else audio_out_stream = avformat_new_stream(oc, NULL); audio_out_ctx = audio_out_stream->codec; @@ -556,8 +552,7 @@ bool VideoStore::setup_resampler() { if ( audio_out_codec->supported_samplerates ) { int found = 0; - for ( unsigned int i = 0; audio_out_codec->supported_samplerates[i]; - i++) { + for ( unsigned int i = 0; audio_out_codec->supported_samplerates[i]; i++) { if ( audio_out_ctx->sample_rate == audio_out_codec->supported_samplerates[i] ) { found = 1; @@ -584,15 +579,6 @@ bool VideoStore::setup_resampler() { audio_out_ctx->time_base = (AVRational){1, audio_out_ctx->sample_rate}; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - ret = avcodec_parameters_from_context(audio_out_stream->codecpar, - audio_out_ctx); - if ( ret < 0 ) { - Error("Could not initialize stream parameteres"); - return false; - } -#endif - AVDictionary *opts = NULL; if ( (ret = av_dict_set(&opts, "strict", "experimental", 0)) < 0 ) { Error("Couldn't set experimental"); @@ -607,6 +593,15 @@ bool VideoStore::setup_resampler() { return false; } +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + ret = avcodec_parameters_from_context( + audio_out_stream->codecpar, audio_out_ctx); + if ( ret < 0 ) { + Error("Could not initialize stream parameteres"); + return false; + } +#endif + Debug(1, "Audio out bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) " "layout(%d) frame_size(%d)", @@ -635,14 +630,14 @@ bool VideoStore::setup_resampler() { } // Some formats (i.e. WAV) do not produce the proper channel layout + uint64_t layout = av_get_channel_layout("mono"); 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); - Debug(1, "Bad channel layout. Need to set it to mono (%d).", layout); + av_opt_set_int(resample_ctx, "in_channel_layout", layout, 0); + Debug(1, "Bad in channel layout. Need to set it to mono (%d).", layout); } else { av_opt_set_int(resample_ctx, "in_channel_layout", audio_in_ctx->channel_layout, 0); + layout = audio_in_ctx->channel_layout; } av_opt_set_int(resample_ctx, "in_sample_fmt", @@ -654,7 +649,7 @@ bool VideoStore::setup_resampler() { // av_opt_set_int( resample_ctx, "out_channel_layout", // audio_out_ctx->channel_layout, 0); av_opt_set_int(resample_ctx, "out_channel_layout", - av_get_channel_layout("mono"), 0); + layout, 0); av_opt_set_int(resample_ctx, "out_sample_fmt", audio_out_ctx->sample_fmt, 0); av_opt_set_int(resample_ctx, "out_sample_rate", @@ -666,6 +661,8 @@ bool VideoStore::setup_resampler() { if ( ret < 0 ) { Error("Could not open resample ctx"); return false; + } else { + Debug(2, "Success opening resampler"); } out_frame->nb_samples = audio_out_ctx->frame_size; @@ -675,13 +672,16 @@ bool VideoStore::setup_resampler() { // The codec gives us the frame size, in samples, we calculate the size of the // samples buffer in bytes unsigned int audioSampleBuffer_size = av_samples_get_buffer_size( - NULL, audio_out_ctx->channels, audio_out_ctx->frame_size, + NULL, audio_out_ctx->channels, + audio_out_ctx->frame_size, audio_out_ctx->sample_fmt, 0); converted_in_samples = (uint8_t *)av_malloc(audioSampleBuffer_size); if ( !converted_in_samples ) { Error("Could not allocate converted in sample pointers"); return false; + } else { + Debug(2, "Frame Size %d, sample buffer size %d", audio_out_ctx->frame_size, audioSampleBuffer_size); } // Setup the data pointers in the AVFrame From 23124d634c18181641025ffe897d7fb7753aa791 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 3 Dec 2018 11:02:45 -0500 Subject: [PATCH 141/310] improve ImageBufferCount too small message --- src/zm_ffmpeg_camera.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 9cb56a0ec..a3a160f75 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -880,7 +880,9 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event if ( keyframe ) { Debug(3, "Clearing queue"); if ( packetqueue->packet_count(mVideoStreamId) >= monitor->GetImageBufferCount() ) { - Warning("ImageBufferCount is too small. Needs to be at least %d. Either increase it or decrease time between keyframes", packetqueue->packet_count(mVideoStreamId) ); + Warning("ImageBufferCount %d is too small. Needs to be at least %d. Either increase it or decrease time between keyframes", + monitor->GetImageBufferCount(), + packetqueue->packet_count(mVideoStreamId)+1 ); } packetqueue->clearQueue(monitor->GetPreEventCount(), mVideoStreamId); From 14272a42b49c3c847e24692e4b4e05d49c63c61f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 3 Dec 2018 11:08:24 -0500 Subject: [PATCH 142/310] fix use of getcwd in delete_empty_subdirs. We use absolute paths --- scripts/zmaudit.pl.in | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index c373c2a04..9b0163999 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -981,10 +981,11 @@ sub deleteSwapImage { # Deletes empty sub directories of the given path. # Does not delete the path if empty. Is not meant to be recursive. +# Assumes absolute path sub delete_empty_subdirs { my $DIR; if ( !opendir($DIR, $_[0]) ) { - Error("delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" ); + Error("delete_empty_subdirs: Can't open directory '/$_[0]': $!" ); return; } my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR ); @@ -997,10 +998,11 @@ sub delete_empty_subdirs { closedir($DIR); } +# Assumes absolute path sub delete_empty_directories { my $DIR; if ( !opendir($DIR, $_[0]) ) { - Error("delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" ); + Error("delete_empty_directories: Can't open directory '/$_[0]': $!" ); return; } my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR ); From ab9081e1bf71ea224b8ad6683a0dcb797a718afd Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 5 Dec 2018 13:18:21 -0500 Subject: [PATCH 143/310] create a simple Frame class. Add a queue of Frames to the Event. When we get 10 of them write them out to the db. --- src/CMakeLists.txt | 2 +- src/zm_event.cpp | 62 +++++++++++++++++++++++++++++++++------------- src/zm_event.h | 7 +++++- src/zm_frame.cpp | 18 ++++++++++++++ src/zm_frame.h | 54 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 src/zm_frame.cpp create mode 100644 src/zm_frame.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a87abe955..61dfb024a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ configure_file(zm_config.h.in "${CMAKE_CURRENT_BINARY_DIR}/zm_config.h" @ONLY) # Group together all the source files that are used by all the binaries (zmc, zma, zmu, zms etc) -set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_eventstream.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_input.cpp zm_ffmpeg_camera.cpp zm_group.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_monitorstream.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packet.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_nvsocket.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_swscale.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp zm_storage.cpp) +set(ZM_BIN_SRC_FILES zm_box.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp zm_config.cpp zm_coord.cpp zm_curl_camera.cpp zm.cpp zm_db.cpp zm_logger.cpp zm_event.cpp zm_frame.cpp zm_eventstream.cpp zm_exception.cpp zm_file_camera.cpp zm_ffmpeg_input.cpp zm_ffmpeg_camera.cpp zm_group.cpp zm_image.cpp zm_jpeg.cpp zm_libvlc_camera.cpp zm_local_camera.cpp zm_monitor.cpp zm_monitorstream.cpp zm_ffmpeg.cpp zm_mpeg.cpp zm_packet.cpp zm_packetqueue.cpp zm_poly.cpp zm_regexp.cpp zm_remote_camera.cpp zm_remote_camera_http.cpp zm_remote_camera_nvsocket.cpp zm_remote_camera_rtsp.cpp zm_rtp.cpp zm_rtp_ctrl.cpp zm_rtp_data.cpp zm_rtp_source.cpp zm_rtsp.cpp zm_rtsp_auth.cpp zm_sdp.cpp zm_signal.cpp zm_stream.cpp zm_swscale.cpp zm_thread.cpp zm_time.cpp zm_timer.cpp zm_user.cpp zm_utils.cpp zm_video.cpp zm_videostore.cpp zm_zone.cpp zm_storage.cpp) # A fix for cmake recompiling the source files for every target. add_library(zm STATIC ${ZM_BIN_SRC_FILES}) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index fbf5cf848..6da57c29e 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -238,6 +238,9 @@ Event::~Event() { videowriter = NULL; } + if ( frame_data.size() ) + WriteDbFrames(); + // Should not be static because we are multi-threaded char sql[ZM_SQL_MED_BUFSIZ]; struct DeltaTimeval delta_time; @@ -260,7 +263,11 @@ Event::~Event() { snprintf(sql, sizeof(sql), "UPDATE Events SET Name='%s %" PRIu64 "', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo = '%s' WHERE Id = %" PRIu64, - monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, video_name, id ); + monitor->EventPrefix(), id, end_time.tv_sec, + delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, + frames, alarm_frames, + tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, + video_name, id ); db_mutex.lock(); while ( mysql_query(&dbconn, sql) && !zm_terminate ) { Error("Can't update event: %s reason: %s", sql, mysql_error(&dbconn)); @@ -376,7 +383,7 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) { createNotes( notes ); Debug( 2, "Updating notes for event %d, '%s'", id, notes.c_str() ); - static char sql[ZM_SQL_MED_BUFSIZ]; + static char sql[ZM_SQL_LGE_BUFSIZ]; #if USE_PREPARED_SQL static MYSQL_STMT *stmt = 0; @@ -504,6 +511,35 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st } } +void Event::WriteDbFrames() { + static char sql[ZM_SQL_LGE_BUFSIZ]; + char * sql_ptr = (char *)&sql; + sql_ptr += snprintf(sql, sizeof(sql), + "INSERT INTO Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score ) VALUES " + ); + while ( frame_data.size() ) { + Frame *frame = frame_data.front(); + frame_data.pop(); + sql_ptr += snprintf(sql_ptr, sizeof(sql)-(sql_ptr-(char *)&sql), "( %" PRIu64 ", %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d ), ", + id, frame->frame_id, frame_type_names[frame->type], + frame->timestamp.tv_sec, + frame->delta.positive?"":"-", + frame->delta.sec, + frame->delta.fsec, + frame->score); + delete frame; + } + *(sql_ptr-2) = '\0'; + db_mutex.lock(); + if ( mysql_query(&dbconn, sql) ) { + Error("Can't insert frames: %s", mysql_error(&dbconn)); + Error("SQL was %s", sql); + db_mutex.unlock(); + return; + } + db_mutex.unlock(); +} + void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image) { if ( !timestamp.tv_sec ) { Debug(1, "Not adding new frame, zero timestamp"); @@ -543,22 +579,14 @@ Debug(3, "Writing video"); bool db_frame = ( frame_type != BULK ) || (!frames) || ((frames%config.bulk_frame_interval)==0) ; if ( db_frame ) { - - Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, Event::frame_type_names[frame_type] ); static char sql[ZM_SQL_MED_BUFSIZ]; - snprintf(sql, sizeof(sql), - "INSERT INTO Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score )" - " VALUES ( %" PRIu64 ", %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d )", - id, frames, frame_type_names[frame_type], timestamp.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, score); - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - Error("Can't insert frame: %s", mysql_error(&dbconn)); - Error("SQL was %s", sql); - db_mutex.unlock(); - return; + + frame_data.push( new Frame(id, frames, frame_type, timestamp, delta_time, score ) ); + if ( frame_data.size() > 10 ) { + WriteDbFrames(); + Debug(1, "Adding 10 frames to DB"); + last_db_frame = frames; } - db_mutex.unlock(); - last_db_frame = frames; // We are writing a Bulk frame if ( frame_type == BULK ) { @@ -581,7 +609,7 @@ Debug(3, "Writing video"); db_mutex.lock(); } db_mutex.unlock(); - } + } // end if frame_type == BULK } // end if db_frame end_time = timestamp; diff --git a/src/zm_event.h b/src/zm_event.h index 1e55ae593..39319e6c7 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -33,6 +33,7 @@ #include #include +#include #include "zm.h" #include "zm_image.h" @@ -45,7 +46,10 @@ class Monitor; class EventStream; #define MAX_PRE_ALARM_FRAMES 16 // Maximum number of prealarm frames that can be stored +typedef uint64_t event_id_t; + typedef enum { NORMAL=0, BULK, ALARM } FrameType; +#include "zm_frame.h" // // Class describing events, i.e. captured periods of activity. // @@ -60,7 +64,6 @@ class Event { typedef std::map StringSetMap; protected: - typedef enum { NORMAL=0, BULK, ALARM } FrameType; static const char * frame_type_names[3]; struct PreAlarmData { @@ -69,6 +72,7 @@ class Event { unsigned int score; Image *alarm_frame; }; + std::queue frame_data; static int pre_alarm_count; static PreAlarmData pre_alarm_data[MAX_PRE_ALARM_FRAMES]; @@ -124,6 +128,7 @@ class Event { private: void AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ); + void WriteDbFrames(); public: static const char *getSubPath( struct tm *time ) { diff --git a/src/zm_frame.cpp b/src/zm_frame.cpp new file mode 100644 index 000000000..bf47bdbf5 --- /dev/null +++ b/src/zm_frame.cpp @@ -0,0 +1,18 @@ +#include "zm_frame.h" + +Frame::Frame( + event_id_t p_event_id, + int p_frame_id, + FrameType p_type, + struct timeval p_timestamp, + struct DeltaTimeval p_delta, + int p_score + ) : + event_id(p_event_id), + frame_id(p_frame_id), + type(p_type), + timestamp(p_timestamp), + delta(p_delta), + score(p_score) +{ +} diff --git a/src/zm_frame.h b/src/zm_frame.h new file mode 100644 index 000000000..84190e779 --- /dev/null +++ b/src/zm_frame.h @@ -0,0 +1,54 @@ +// +// ZoneMinder Frame Class Interfaces, $Date$, $Revision$ +// Copyright (C) 2001-2008 Philip Coombes +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// + +#ifndef ZM_FRAME_H +#define ZM_FRAME_H + +#include +#include +class Frame; + +#include "zm_event.h" +#include "zm_time.h" + +// +// This describes a frame record +// +class Frame { + +public: + Frame( + event_id_t p_event_id, + int p_frame_id, + FrameType p_type, + struct timeval p_timestamp, + struct DeltaTimeval p_delta, + int p_score + ); + + event_id_t event_id; + int frame_id; + FrameType type; + struct timeval timestamp; + struct DeltaTimeval delta; + int score; + +}; + +#endif // ZM_FRAME_H From e9ae2e6968d360eda0cea3948841fb5532dc1cef Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 14 Dec 2018 08:39:55 -0500 Subject: [PATCH 144/310] COmment out logging in signal handlers. Logging is too complex. --- src/zm_signal.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/zm_signal.cpp b/src/zm_signal.cpp index 034ff7d66..e13bfdc9c 100644 --- a/src/zm_signal.cpp +++ b/src/zm_signal.cpp @@ -31,13 +31,15 @@ bool zm_terminate = false; RETSIGTYPE zm_hup_handler(int signal) { - Info("Got signal %d (%s), reloading", signal, strsignal(signal)); + // Shouldn't do complex things in signal handlers, logging is complex and can block due to mutexes. + //Info("Got signal %d (%s), reloading", signal, strsignal(signal)); zm_reload = true; } RETSIGTYPE zm_term_handler(int signal) { - Info("Got signal %d (%s), exiting", signal, strsignal(signal)); + // Shouldn't do complex things in signal handlers, logging is complex and can block due to mutexes. + //Info("Got signal %d (%s), exiting", signal, strsignal(signal)); zm_terminate = true; } From 62ce3c1f65de815639ac53035e10298340641dba Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Dec 2018 13:38:11 -0500 Subject: [PATCH 145/310] Implement libswresample in place of libavresample which is deprecated --- src/zm_videostore.cpp | 125 ++++++++++++++++++++++++++++-------------- src/zm_videostore.h | 12 +++- 2 files changed, 93 insertions(+), 44 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 746036efe..31916bdab 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -205,7 +205,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, audio_out_stream = NULL; in_frame = NULL; out_frame = NULL; -#ifdef HAVE_LIBAVRESAMPLE +#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) resample_ctx = NULL; #endif @@ -220,7 +220,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); Debug(2, "Got something other than AAC (%s)", error_buffer); @@ -241,8 +241,6 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, Error("Unable to create audio out stream"); audio_out_stream = NULL; } else { - Debug(2, "setting parameters"); - #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) audio_out_ctx = avcodec_alloc_context3(audio_out_codec); // Copy params from instream to ctx @@ -466,10 +464,16 @@ VideoStore::~VideoStore() { avcodec_free_context(&audio_out_ctx); #endif audio_out_ctx = NULL; -#ifdef HAVE_LIBAVRESAMPLE +#if defined(HAVE_LIBAVRESAMPLE) || defined(HAVE_LIBSWRESAMPLE) if ( resample_ctx ) { +#if defined(HAVE_LIBSWRESAMPLE) + swr_free(&resample_ctx); +#else +#if defined(HAVE_LIBAVRESAMPLE) avresample_close(resample_ctx); avresample_free(&resample_ctx); +#endif +#endif } if ( in_frame ) { av_frame_free(&in_frame); @@ -491,7 +495,12 @@ VideoStore::~VideoStore() { } // VideoStore::~VideoStore() bool VideoStore::setup_resampler() { -#ifdef HAVE_LIBAVRESAMPLE +#if !defined(HAVE_LIBSWRESAMPLE) && !defined(HAVE_LIBAVRESAMPLE) + Error( + "Not built with resample library. " + "Cannot do audio conversion to AAC"); + return false; +#else #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // Newer ffmpeg wants to keep everything separate... so have to lookup our own @@ -499,8 +508,7 @@ bool VideoStore::setup_resampler() { audio_in_codec = avcodec_find_decoder(audio_in_stream->codecpar->codec_id); #else - audio_in_codec = - avcodec_find_decoder(audio_in_ctx->codec_id); + audio_in_codec = avcodec_find_decoder(audio_in_ctx->codec_id); #endif ret = avcodec_open2(audio_in_ctx, audio_in_codec, NULL); if ( ret < 0 ) { @@ -531,6 +539,9 @@ bool VideoStore::setup_resampler() { audio_out_stream = avformat_new_stream(oc, NULL); audio_out_ctx = audio_out_stream->codec; #endif + // Some formats (i.e. WAV) do not produce the proper channel layout + if ( audio_in_ctx->channel_layout == 0 ) + audio_in_ctx->channel_layout = av_get_channel_layout("mono"); /* put sample parameters */ audio_out_ctx->bit_rate = audio_in_ctx->bit_rate; @@ -622,40 +633,55 @@ bool VideoStore::setup_resampler() { return false; } +#if defined(HAVE_LIBSWRESAMPLE) + resample_ctx = swr_alloc_set_opts(NULL, + av_get_default_channel_layout(audio_out_ctx->channels), + audio_out_ctx->sample_fmt, + audio_out_ctx->sample_rate, + av_get_default_channel_layout(audio_in_ctx->channels), + audio_in_ctx->sample_fmt, + audio_in_ctx->sample_rate, + 0, NULL); + if ( !resample_ctx ) { + Error("Could not allocate resample context"); + av_frame_free(&in_frame); + av_frame_free(&out_frame); + return false; + } + if ( (ret = swr_init(resample_ctx)) < 0 ) { + Error("Could not open resampler"); + av_frame_free(&in_frame); + av_frame_free(&out_frame); + swr_free(&resample_ctx); + return false; + } +#else +#if defined(HAVE_LIBAVRESAMPLE) // Setup the audio resampler resample_ctx = avresample_alloc_context(); if ( !resample_ctx ) { Error("Could not allocate resample ctx"); + av_frame_free(&in_frame); + av_frame_free(&out_frame); return false; } - // Some formats (i.e. WAV) do not produce the proper channel layout - uint64_t layout = av_get_channel_layout("mono"); - if ( audio_in_ctx->channel_layout == 0 ) { - av_opt_set_int(resample_ctx, "in_channel_layout", layout, 0); - Debug(1, "Bad in channel layout. Need to set it to mono (%d).", layout); - } else { - av_opt_set_int(resample_ctx, "in_channel_layout", - audio_in_ctx->channel_layout, 0); - layout = audio_in_ctx->channel_layout; - } - + av_opt_set_int(resample_ctx, "in_channel_layout", + audio_in_ctx->channel_layout, 0); av_opt_set_int(resample_ctx, "in_sample_fmt", - audio_in_ctx->sample_fmt, 0); + 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); + 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", - layout, 0); + audio_in_ctx->channel_layout, 0); av_opt_set_int(resample_ctx, "out_sample_fmt", - audio_out_ctx->sample_fmt, 0); + audio_out_ctx->sample_fmt, 0); av_opt_set_int(resample_ctx, "out_sample_rate", - audio_out_ctx->sample_rate, 0); + audio_out_ctx->sample_rate, 0); av_opt_set_int(resample_ctx, "out_channels", - audio_out_ctx->channels, 0); + audio_out_ctx->channels, 0); ret = avresample_open(resample_ctx); if ( ret < 0 ) { @@ -664,6 +690,8 @@ bool VideoStore::setup_resampler() { } else { Debug(2, "Success opening resampler"); } +#endif +#endif out_frame->nb_samples = audio_out_ctx->frame_size; out_frame->format = audio_out_ctx->sample_fmt; @@ -694,11 +722,6 @@ bool VideoStore::setup_resampler() { } return true; -#else - Error( - "Not built with libavresample library. Cannot do audio conversion to " - "AAC"); - return false; #endif } // end bool VideoStore::setup_resampler() @@ -858,9 +881,9 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { if ( audio_out_codec ) { Debug(3, "Have audio codec"); -#ifdef HAVE_LIBAVRESAMPLE +#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) ret = avcodec_send_packet(audio_in_ctx, ipkt); if ( ret < 0 ) { Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str()); @@ -877,7 +900,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { "layout(%d)", in_frame->nb_samples, in_frame->format, in_frame->sample_rate, in_frame->channel_layout); -#else + #else /** * Decode the audio frame stored in the packet. * The in audio stream decoder is used to do this. @@ -897,11 +920,24 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { Debug(2, "Not ready to transcode a frame yet."); return 0; } -#endif + #endif int frame_size = out_frame->nb_samples; // Resample the in into the audioSampleBuffer until we proceed the whole // decoded data + #if defined(HAVE_LIBSWRESAMPLE) + Debug(2, "Converting %d to %d samples", in_frame->nb_samples, out_frame->nb_samples); + if ((ret = swr_convert(resample_ctx, + out_frame->data, frame_size, + (const uint8_t**)in_frame->data, + in_frame->nb_samples)) < 0) { + Error("Could not resample frame (error '%s')\n", + av_make_error_string(ret).c_str()); + av_frame_unref(in_frame); + return 0; + } + #else + #if defined(HAVE_LIBAVRESAMPLE) if ((ret = avresample_convert(resample_ctx, NULL, 0, 0, in_frame->data, 0, in_frame->nb_samples)) < 0) { @@ -910,8 +946,11 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { av_frame_unref(in_frame); return 0; } + #endif + #endif av_frame_unref(in_frame); + #if defined(HAVE_LIBAVRESAMPLE) int samples_available = avresample_available(resample_ctx); if (samples_available < frame_size) { @@ -926,6 +965,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { Warning("Error reading resampled audio: "); return 0; } + #endif Debug(2, "Frame: samples(%d), format(%d), sample_rate(%d), channel layout(%d)", out_frame->nb_samples, out_frame->format, @@ -934,7 +974,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { av_init_packet(&opkt); Debug(5, "after init packet"); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) if ((ret = avcodec_send_frame(audio_out_ctx, out_frame)) < 0) { Error("Could not send frame (error '%s')", av_make_error_string(ret).c_str()); @@ -958,7 +998,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { // av_frame_unref( out_frame ); return 0; } -#else + #else if ((ret = avcodec_encode_audio2(audio_out_ctx, &opkt, out_frame, &data_present)) < 0) { Error("Could not encode frame (error '%s')", @@ -971,8 +1011,9 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { zm_av_packet_unref(&opkt); return 0; } -#endif - + #endif +#else + Error("Have audio codec but no resampler?!"); #endif } else { av_init_packet(&opkt); diff --git a/src/zm_videostore.h b/src/zm_videostore.h index 282d96da7..bc7cd8417 100644 --- a/src/zm_videostore.h +++ b/src/zm_videostore.h @@ -3,8 +3,12 @@ #include "zm_ffmpeg.h" extern "C" { -#ifdef HAVE_LIBAVRESAMPLE -#include "libavresample/avresample.h" +#ifdef HAVE_LIBSWRESAMPLE + #include "libswresample/swresample.h" +#else + #ifdef HAVE_LIBAVRESAMPLE + #include "libavresample/avresample.h" + #endif #endif } @@ -38,8 +42,12 @@ private: // The following are used when encoding the audio stream to AAC AVCodec *audio_out_codec; AVCodecContext *audio_out_ctx; +#ifdef HAVE_LIBSWRESAMPLE + SwrContext *resample_ctx; +#else #ifdef HAVE_LIBAVRESAMPLE AVAudioResampleContext* resample_ctx; +#endif #endif uint8_t *converted_in_samples; From 53c66e44dbe2ec0a50f289ed2920e35505495f8f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Dec 2018 13:38:24 -0500 Subject: [PATCH 146/310] Implement libswresample in place of libavresample which is deprecated --- CMakeLists.txt | 35 ++++++++++++++++++----------------- zoneminder-config.cmake | 2 ++ 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ef8afc91e..a0d037099 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -607,23 +607,6 @@ if(NOT ZM_NO_FFMPEG) set(optlibsnotfound "${optlibsnotfound} SWScale") endif(SWSCALE_LIBRARIES) - # AVresample (using find_library and find_path) - find_library(AVRESAMPLE_LIBRARIES avresample) - if(AVRESAMPLE_LIBRARIES) - set(HAVE_LIBAVRESAMPLE 1) - list(APPEND ZM_BIN_LIBS "${AVRESAMPLE_LIBRARIES}") - find_path(AVRESAMPLE_INCLUDE_DIR "libavresample/avresample.h" /usr/include/ffmpeg) - if(AVRESAMPLE_INCLUDE_DIR) - include_directories("${AVRESAMPLE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${AVRESAMPLE_INCLUDE_DIR}") - endif(AVRESAMPLE_INCLUDE_DIR) - mark_as_advanced(FORCE AVRESAMPLE_LIBRARIES AVRESAMPLE_INCLUDE_DIR) - check_include_file("libavresample/avresample.h" HAVE_LIBAVRESAMPLE_AVRESAMPLE_H) - set(optlibsfound "${optlibsfound} AVResample") - else(AVRESAMPLE_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} AVResample") - endif(AVRESAMPLE_LIBRARIES) - # SWresample (using find_library and find_path) find_library(SWRESAMPLE_LIBRARIES swresample) if(SWRESAMPLE_LIBRARIES) @@ -639,6 +622,24 @@ if(NOT ZM_NO_FFMPEG) set(optlibsfound "${optlibsfound} SWResample") else(SWRESAMPLE_LIBRARIES) set(optlibsnotfound "${optlibsnotfound} SWResample") + + # AVresample (using find_library and find_path) + find_library(AVRESAMPLE_LIBRARIES avresample) + if(AVRESAMPLE_LIBRARIES) + set(HAVE_LIBAVRESAMPLE 1) + list(APPEND ZM_BIN_LIBS "${AVRESAMPLE_LIBRARIES}") + find_path(AVRESAMPLE_INCLUDE_DIR "libavresample/avresample.h" /usr/include/ffmpeg) + if(AVRESAMPLE_INCLUDE_DIR) + include_directories("${AVRESAMPLE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${AVRESAMPLE_INCLUDE_DIR}") + endif(AVRESAMPLE_INCLUDE_DIR) + mark_as_advanced(FORCE AVRESAMPLE_LIBRARIES AVRESAMPLE_INCLUDE_DIR) + check_include_file("libavresample/avresample.h" HAVE_LIBAVRESAMPLE_AVRESAMPLE_H) + set(optlibsfound "${optlibsfound} AVResample") + else(AVRESAMPLE_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} AVResample") + endif(AVRESAMPLE_LIBRARIES) + endif(SWRESAMPLE_LIBRARIES) # Find the path to the ffmpeg executable diff --git a/zoneminder-config.cmake b/zoneminder-config.cmake index 8641bb4a8..e088e68dc 100644 --- a/zoneminder-config.cmake +++ b/zoneminder-config.cmake @@ -54,6 +54,8 @@ #cmakedefine HAVE_LIBAVUTIL_HWCONTEXT_H 0 #cmakedefine HAVE_LIBSWSCALE 1 #cmakedefine HAVE_LIBSWSCALE_SWSCALE_H 1 +#cmakedefine HAVE_LIBSWRESAMPLE 1 +#cmakedefine HAVE_LIBSWRESAMPLE_SWRESAMPLE_H 1 #cmakedefine HAVE_LIBAVRESAMPLE 1 #cmakedefine HAVE_LIBAVRESAMPLE_AVRESAMPLE_H 1 #cmakedefine HAVE_LIBVLC 1 From 3f1e49587270ba19d8fb4accb93f0ad2cae3abec Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Dec 2018 13:41:09 -0500 Subject: [PATCH 147/310] Add libswresample to dependencies --- distros/ubuntu1604/control | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control index 991ed9e54..d91fd4f53 100644 --- a/distros/ubuntu1604/control +++ b/distros/ubuntu1604/control @@ -10,6 +10,7 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa ,libavcodec-dev (>= 6:10~) ,libavformat-dev (>= 6:10~) ,libavutil-dev (>= 6:10~) + ,libswresample-dev ,libswscale-dev (>= 6:10~) ,ffmpeg | libav-tools ,net-tools @@ -41,7 +42,9 @@ Package: zoneminder Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,javascript-common - ,libmp4v2-2, libx264-142|libx264-148|libx264-152, libswscale-ffmpeg3|libswscale4|libswscale3|libswscale5 + ,libmp4v2-2, libx264-142|libx264-148|libx264-152 + ,libswscale-ffmpeg3|libswscale4|libswscale3|libswscale5 + ,libswresample2|libswresample3|libswresample24 ,ffmpeg | libav-tools ,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl ,libdbd-mysql-perl From 0c096160596530cbae4ba6c89562e9da328ddcc5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Dec 2018 13:58:09 -0500 Subject: [PATCH 148/310] put back privacy notice update --- scripts/zmupdate.pl.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index bb9dddac4..8517e0277 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -923,10 +923,10 @@ if ( $version ) { die( "Can't find upgrade from version '$version'" ); } # Re-enable the privacy popup after each upgrade - #my $sql = "update Config set Value = 1 where Name = 'ZM_SHOW_PRIVACY'"; - #my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - #my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() ); - #$sth->finish(); + my $sql = "update Config set Value = 1 where Name = 'ZM_SHOW_PRIVACY'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() ); + $sth->finish(); print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" ); } zmDbDisconnect(); From 3d9c8814f9073c27894951e9b5894a1da2b3d271 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 27 Dec 2018 13:52:41 -0500 Subject: [PATCH 149/310] increase error_count logging --- src/zm_ffmpeg_camera.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index a3a160f75..22dfa35ba 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -747,7 +747,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event if ( packet.pts < -100000 ) { // Ignore packets that have crazy negative pts. They aren't supposed to happen. - Warning("Ignore packet because pts %" PRId64 " is massively negative", packet.pts); + Warning("Ignore packet because pts %" PRId64 " is massively negative. Error count is %d", packet.pts, error_count); dumpPacket(&packet,"Ignored packet"); if ( error_count > 100 ) { Error("Bad packet count over 100, going to close and re-open stream"); @@ -756,8 +756,8 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event error_count += 1; continue; } - // If we get a goot frame, decrease the error count.. We could zero it... - if ( error_count ) error_count -= 1; + // If we get a good frame, decrease the error count.. We could zero it... + if ( error_count > 0 ) error_count -= 1; int keyframe = packet.flags & AV_PKT_FLAG_KEY; bytes += packet.size; From ab198bfd75ab188878f9440d1b8c328b801118ec Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 4 Jan 2019 15:29:21 -0500 Subject: [PATCH 150/310] remove master version of actions.php --- web/includes/actions.php | 975 --------------------------------------- 1 file changed, 975 deletions(-) delete mode 100644 web/includes/actions.php diff --git a/web/includes/actions.php b/web/includes/actions.php deleted file mode 100644 index 47c74546f..000000000 --- a/web/includes/actions.php +++ /dev/null @@ -1,975 +0,0 @@ -beginTransaction(); - foreach( getAffectedIds('markEid') as $markEid ) { - dbQuery('UPDATE Events SET Cause=?, Notes=? WHERE Id=?', - array($_REQUEST['newEvent']['Cause'], $_REQUEST['newEvent']['Notes'], $markEid) ); - } - $dbConn->commit(); - } - $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 { - $dbConn->beginTransaction(); - foreach( getAffectedIds('markEid') as $markEid ) { - dbQuery('UPDATE Events SET Archived=? WHERE Id=?', array($archiveVal, $markEid)); - } - $dbConn->commit(); - $refreshParent = true; - } - } elseif ( $action == 'delete' ) { - $dbConn->beginTransaction(); - foreach( getAffectedIds('eids') as $markEid ) { - deleteEvent($markEid); - } - $dbConn->commit(); - $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); - } else if ( $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' ) { - require_once('Control.php'); - $Control = new Control( !empty($_REQUEST['cid']) ? $_REQUEST['cid'] : null ); - - //$changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); - $Control->save($_REQUEST['newControl']); - $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; - } - } - } // end if action -} // end if canEdit Controls - -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); - if ( $Monitor->Type() != 'WebSite' ) { - $Monitor->zmaControl('stop'); - $Monitor->zmcControl('stop'); - } - $Monitor->save($_REQUEST['newMonitor']); - if ( $Monitor->Function() != 'None' && $Monitor->Type() != 'WebSite' ) { - $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() && ($monitor['Type'] != 'WebSite') ) { - $restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled); - zmaControl($monitor, 'stop'); - zmcControl($monitor, $restart?'restart':''); - zmaControl($monitor, 'start'); - } - $refreshParent = true; - } - } else if ( $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'] ); - - # convert these fields to integer e.g. NULL -> 0 - $types = array( - 'OverloadFrames' => 'integer', - 'ExtendAlarmFrames' => 'integer', - ); - - $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 ( daemonCheck() && ($monitor['Type'] != 'WebSite') ) { - if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) { - zmaControl($monitor, 'stop'); - zmcControl($monitor, 'restart'); - zmaControl($monitor, 'start'); - } else { - zmaControl($monitor, '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() && ($monitor['Type'] != 'WebSite') ) { - 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 ( daemonCheck() && $monitor['Type'] != 'WebSite' ) { - 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(); - } - } - $Monitor = new Monitor($monitor); - - // 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', - 'Method' => 'raw', - ); - - if ( $_REQUEST['newMonitor']['ServerId'] == 'auto' ) { - $_REQUEST['newMonitor']['ServerId'] = dbFetchOne( - 'SELECT Id FROM Servers WHERE Status=\'Running\' ORDER BY FreeMem DESC, CpuLoad ASC LIMIT 1', 'Id'); - Logger::Debug('Auto selecting server: Got ' . $_REQUEST['newMonitor']['ServerId'] ); - if ( ( ! $_REQUEST['newMonitor'] ) and defined('ZM_SERVER_ID') ) { - $_REQUEST['newMonitor']['ServerId'] = ZM_SERVER_ID; - Logger::Debug('Auto selecting server to ' . ZM_SERVER_ID); - } - } - - $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. - if ( $monitor['Type'] != 'WebSite' ) { - zmaControl($monitor, 'stop'); - zmcControl($monitor, 'stop'); - } - dbQuery('UPDATE Monitors SET '.implode(', ', $changes).' WHERE Id=?', array($mid)); - // Groups will be added below - if ( isset($changes['Name']) or isset($changes['StorageId']) ) { - $OldStorage = new Storage($monitor['StorageId']); - $saferOldName = basename($monitor['Name']); - if ( file_exists($OldStorage->Path().'/'.$saferOldName) ) - unlink($OldStorage->Path().'/'.$saferOldName); - - $NewStorage = new Storage($_REQUEST['newMonitor']['StorageId']); - if ( ! file_exists($NewStorage->Path().'/'.$mid) ) - mkdir($NewStorage->Path().'/'.$mid, 0755); - $saferNewName = basename($_REQUEST['newMonitor']['Name']); - symlink($mid, $NewStorage->Path().'/'.$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'])); - } - } // end foreach zone - } // end if width and height - $restart = true; - } else if ( ! $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); - - $sql = 'INSERT INTO Monitors SET '.implode(', ', $changes); - if ( dbQuery($sql) ) { - $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'; - $Storage = new Storage($_REQUEST['newMonitor']['StorageId']); - mkdir($Storage->Path().'/'.$mid, 0755); - $saferName = basename($_REQUEST['newMonitor']['Name']); - symlink($mid, $Storage->Path().'/'.$saferName); - - } else { - Error('Error saving new Monitor.'); - $error_message = dbError($sql); - return; - } - } else { - Error('Users with Monitors restrictions cannot create new monitors.'); - return; - } - - $restart = true; - } else { - Logger::Debug('No action due to no changes to Monitor'); - } # end if count(changes) - - if ( - ( !isset($_POST['newMonitor']['GroupIds']) ) - or - ( count($_POST['newMonitor']['GroupIds']) != count($Monitor->GroupIds()) ) - or - array_diff($_POST['newMonitor']['GroupIds'], $Monitor->GroupIds()) - ) { - if ( $Monitor->Id() ) - dbQuery('DELETE FROM Groups_Monitors WHERE MonitorId=?', array($mid)); - - if ( isset($_POST['newMonitor']['GroupIds']) ) { - foreach ( $_POST['newMonitor']['GroupIds'] as $group_id ) { - dbQuery('INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($group_id, $mid)); - } - } - } // end if there has been a change of groups - - 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; - } # end if has x10Changes - } # end if ZM_OPT_X10 - - if ( $restart ) { - - $new_monitor = new Monitor($mid); - //fixDevices(); - - if ( $new_monitor->Type() != 'WebSite' ) { - $new_monitor->zmcControl('start'); - $new_monitor->zmaControl('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'] ) { - require_once('Monitor.php'); - foreach ( $_REQUEST['markMids'] as $markMid ) { - if ( canEdit('Monitors', $markMid) ) { - // This could be faster as a select all - if ( $monitor = dbFetchOne('SELECT * FROM Monitors WHERE Id = ?', NULL, array($markMid)) ) { - $Monitor = new Monitor($monitor); - $Monitor->delete(); - } // end if monitor found in 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']); - } else if ( 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']); - $group_id = null; - if ( !empty($_POST['gid']) ) { - $group_id = $_POST['gid']; - dbQuery( - 'UPDATE Groups SET Name=?, ParentId=? WHERE Id=?', - array( - $_POST['newGroup']['Name'], - ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), - $group_id, - ) - ); - dbQuery('DELETE FROM Groups_Monitors WHERE GroupId=?', array($group_id)); - } else { - dbQuery( - 'INSERT INTO Groups (Name,ParentId) VALUES (?,?)', - array( - $_POST['newGroup']['Name'], - ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), - ) - ); - $group_id = dbInsertId(); - } - if ( $group_id ) { - foreach ( $_POST['newGroup']['MonitorIds'] as $mid ) { - dbQuery('INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($group_id, $mid)); - } - } - $view = 'none'; - $refreshParent = true; - } else if ( $action == 'delete' ) { - if ( !empty($_REQUEST['gid']) ) { - foreach ( Group::find(array('Id'=>$_REQUEST['gid'])) as $Group ) { - $Group->delete(); - } - } - $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=groups'; - $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 = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=montage'; - } // 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 == 'privacy') && isset($_REQUEST['option']) ) { - switch( $_REQUEST['option'] ) { - case 'decline' : - { - dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_SHOW_PRIVACY'"); - dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_TELEMETRY_DATA'"); - $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console'; - break; - } - case 'accept' : - { - dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_SHOW_PRIVACY'"); - dbQuery("UPDATE Config SET Value = '1' WHERE Name = 'ZM_TELEMETRY_DATA'"); - $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console'; - break; - } - default: # Enable the privacy statement if we somehow submit something other than accept or decline - dbQuery("UPDATE Config SET Value = '1' WHERE Name = 'ZM_SHOW_PRIVACY'"); - } // end switch option - return; - } - if ( $action == 'options' && isset($_REQUEST['tab']) ) { - $config = array(); - $configCat = array(); - $configCats = array(); - - $result = $dbConn->query('SELECT * FROM Config ORDER BY Id ASC'); - if ( !$result ) - echo mysql_error(); - while( $row = dbFetchNext($result) ) { - $config[$row['Name']] = $row; - if ( !($configCat = &$configCats[$row['Category']]) ) { - $configCats[$row['Category']] = array(); - $configCat = &$configCats[$row['Category']]; - } - $configCat[$row['Name']] = $row; - } - - $configCat = $configCats[$_REQUEST['tab']]; - $changed = false; - foreach ( $configCat as $name=>$value ) { - unset($newValue); - if ( $value['Type'] == 'boolean' && empty($_REQUEST['newConfig'][$name]) ) { - $newValue = 0; - } else if ( 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; - } - $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=options&tab='.$_REQUEST['tab']; - } - loadConfig(false); - return; - } 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_start(); - $_SESSION['zmEventResetTime'] = strftime(STRF_FMT_DATETIME_DB); - setcookie('zmEventResetTime', $_SESSION['zmEventResetTime'], time()+3600*24*30*12*10); - session_write_close(); -} - -?> From c4c6d5b6429e0c06dde25f1c9d42da88d8753901 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 5 Jan 2019 10:15:22 -0500 Subject: [PATCH 151/310] switch to swr_convert_frame as a test --- src/zm_ffmpeg_camera.cpp | 9 ++++---- src/zm_videostore.cpp | 48 ++++++++++++++++++++++------------------ 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 22dfa35ba..f1fe2528e 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -845,22 +845,23 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event 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() ); + Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", + avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue->size()); if ( avp->stream_index == mVideoStreamId ) { ret = videoStore->writeVideoFramePacket( avp ); have_video_keyframe = true; } else if ( avp->stream_index == mAudioStreamId ) { ret = videoStore->writeAudioFramePacket( avp ); } else { - Warning("Unknown stream id in queued packet (%d)", avp->stream_index ); + Warning("Unknown stream id in queued packet (%d)", avp->stream_index); ret = -1; } if ( ret < 0 ) { - //Less than zero and we skipped a frame + // 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 ); + Debug(2, "Wrote %d queued packets", packet_count); } } // end if ! was recording diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index cb7bac722..fe812d80e 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -350,7 +350,7 @@ VideoStore::~VideoStore() { if ( oc->pb ) { - if (audio_out_codec) { + 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; @@ -366,7 +366,7 @@ VideoStore::~VideoStore() { ret = avcodec_receive_packet(audio_out_ctx, &pkt); if ( ret < 0 ) { if ( AVERROR_EOF != ret ) { - Error("ERror encoding audio while flushing (%d) (%s)", ret, + Error("Error encoding audio while flushing (%d) (%s)", ret, av_err2str(ret)); } break; @@ -411,7 +411,7 @@ VideoStore::~VideoStore() { Debug(1,"Writing trailer"); /* Write the trailer before close */ - if (int rc = av_write_trailer(oc)) { + if ( int rc = av_write_trailer(oc) ) { Error("Error writing trailer %s", av_err2str(rc)); } else { Debug(3, "Success Writing trailer"); @@ -421,7 +421,7 @@ VideoStore::~VideoStore() { if ( !(out_format->flags & AVFMT_NOFILE) ) { /* Close the out file. */ Debug(2, "Closing"); - if (int rc = avio_close(oc->pb)) { + if ( int rc = avio_close(oc->pb) ) { oc->pb = NULL; Error("Error closing avio %s", av_err2str(rc)); } @@ -510,8 +510,7 @@ bool VideoStore::setup_resampler() { #else audio_in_codec = avcodec_find_decoder(audio_in_ctx->codec_id); #endif - ret = avcodec_open2(audio_in_ctx, audio_in_codec, NULL); - if ( ret < 0 ) { + if ( (ret = avcodec_open2(audio_in_ctx, audio_in_codec, NULL)) < 0 ) { Error("Can't open in codec!"); return false; } @@ -540,11 +539,13 @@ bool VideoStore::setup_resampler() { audio_out_ctx = audio_out_stream->codec; #endif // 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 ) { + Debug(2, "Setting input channel layout to mono"); audio_in_ctx->channel_layout = av_get_channel_layout("mono"); + } /* put sample parameters */ - audio_out_ctx->bit_rate = audio_in_ctx->bit_rate <= 96000 ? audio_in_ctx->bit_rate : 96000; + audio_out_ctx->bit_rate = audio_in_ctx->bit_rate <= 32768 ? audio_in_ctx->bit_rate : 32768; 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; @@ -582,13 +583,12 @@ bool VideoStore::setup_resampler() { /* check that the encoder supports s16 pcm in */ if ( !check_sample_fmt(audio_out_codec, audio_out_ctx->sample_fmt) ) { - Debug(3, "Encoder does not support sample format %s, setting to FLTP", + Debug(2, "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}; AVDictionary *opts = NULL; if ( (ret = av_dict_set(&opts, "strict", "experimental", 0)) < 0 ) { @@ -655,6 +655,7 @@ bool VideoStore::setup_resampler() { swr_free(&resample_ctx); return false; } + Debug(1,"Success setting up SWRESAMPLE"); #else #if defined(HAVE_LIBAVRESAMPLE) // Setup the audio resampler @@ -884,14 +885,12 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { #if defined(HAVE_LIBSWRESAMPLE) || defined(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; } @@ -928,17 +927,22 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { Debug(2, "Converting %d to %d samples", in_frame->nb_samples, out_frame->nb_samples); if ( #if defined(HAVE_LIBSWRESAMPLE) +#if 0 (ret = swr_convert(resample_ctx, out_frame->data, frame_size, - (const uint8_t**)in_frame->data, - in_frame->nb_samples)) + (const uint8_t**)in_frame->data, in_frame->nb_samples + )) +#else + (ret = swr_convert_frame(resample_ctx, out_frame, in_frame)) + +#endif #else #if defined(HAVE_LIBAVRESAMPLE) (ret = avresample_convert(resample_ctx, NULL, 0, 0, in_frame->data, 0, in_frame->nb_samples)) #endif #endif - < 0) { + < 0 ) { Error("Could not resample frame (error '%s')", av_make_error_string(ret).c_str()); av_frame_unref(in_frame); @@ -971,7 +975,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { Debug(5, "after init packet"); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - if ((ret = avcodec_send_frame(audio_out_ctx, out_frame)) < 0) { + if ( (ret = avcodec_send_frame(audio_out_ctx, out_frame)) < 0 ) { Error("Could not send frame (error '%s')", av_make_error_string(ret).c_str()); zm_av_packet_unref(&opkt); @@ -995,14 +999,14 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { return 0; } #else - if ((ret = avcodec_encode_audio2(audio_out_ctx, &opkt, out_frame, - &data_present)) < 0) { + if ( (ret = avcodec_encode_audio2(audio_out_ctx, &opkt, out_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) { + if ( !data_present ) { Debug(2, "Not ready to out a frame yet."); zm_av_packet_unref(&opkt); return 0; From c97eb16942f67bfa5d1e2c5d053da888df71b97f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 14 Jan 2019 10:47:22 -0500 Subject: [PATCH 152/310] create zm_dump_frame to print out useful frame info. --- src/zm_ffmpeg.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 2b8b4d910..36b499ee9 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -278,6 +278,18 @@ static void zm_log_fps(double d, const char *postfix) { } } +void zm_dump_frame(const AVFrame *frame,const char *text) { + Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d layout %d", + text, + frame->format, + av_get_sample_fmt_name((AVSampleFormat)frame->format), + frame->sample_rate, + frame->nb_samples, + frame->channels, + frame->channel_layout + ); +} + #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) void zm_dump_codecpar ( const AVCodecParameters *par ) { Debug(1, "Dumping codecpar codec_type(%d) codec_id(%d) codec_tag(%d) width(%d) height(%d) bit_rate(%d) format(%d = %s)", From d0cca01c4c5869f3c592c9e5b715e934dd03bed8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 14 Jan 2019 10:47:53 -0500 Subject: [PATCH 153/310] setup url instead of filename in the AVFormatContext to satisfy filename being deprecated --- src/zm_sdp.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/zm_sdp.cpp b/src/zm_sdp.cpp index 1361c5143..af293d64a 100644 --- a/src/zm_sdp.cpp +++ b/src/zm_sdp.cpp @@ -338,7 +338,11 @@ AVFormatContext *SessionDescriptor::generateFormatContext() const { AVFormatContext *formatContext = avformat_alloc_context(); - strncpy( formatContext->filename, mUrl.c_str(), sizeof(formatContext->filename) ); +#if (LIBAVFORMAT_VERSION_CHECK(58, 12, 0, 0, 100)) + formatContext->url = av_strdup(mUrl.c_str()); +#else + strncpy(formatContext->filename, mUrl.c_str(), sizeof(formatContext->filename)); +#endif /* if ( mName.length() ) strncpy( formatContext->title, mName.c_str(), sizeof(formatContext->title) ); From a46b443d4a978ce4b6e17598b4bafa831e9bef6d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 14 Jan 2019 10:48:06 -0500 Subject: [PATCH 154/310] create zm_dump_frame to print out useful frame info. --- src/zm_ffmpeg.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/zm_ffmpeg.h b/src/zm_ffmpeg.h index a3a88519c..880045670 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -294,10 +294,11 @@ static av_always_inline av_const int64_t av_clip64_c(int64_t a, int64_t amin, in #endif void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output); -void zm_dump_codec ( const AVCodecContext *codec ); +void zm_dump_codec(const AVCodecContext *codec); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) -void zm_dump_codecpar ( const AVCodecParameters *par ); +void zm_dump_codecpar(const AVCodecParameters *par); #endif +void zm_dump_frame(const AVFrame *frame, const char *text="Frame"); #if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) #define zm_av_packet_unref( packet ) av_packet_unref( packet ) From 9f8be169f94b21a702ce1877b0ddd920b3fce856 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 14 Jan 2019 10:48:29 -0500 Subject: [PATCH 155/310] set channels and sample rate in output frame. Use new zm_dump_frame() --- src/zm_videostore.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index fe812d80e..31a92045c 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -554,7 +554,7 @@ bool VideoStore::setup_resampler() { #else audio_out_ctx->refcounted_frames = 1; #endif - if ( ! audio_out_ctx->channel_layout ) { + if ( !audio_out_ctx->channel_layout ) { Debug(3, "Correcting channel layout from (%d) to (%d)", audio_out_ctx->channel_layout, av_get_default_channel_layout(audio_out_ctx->channels) @@ -696,7 +696,9 @@ bool VideoStore::setup_resampler() { out_frame->nb_samples = audio_out_ctx->frame_size; out_frame->format = audio_out_ctx->sample_fmt; + out_frame->channels = audio_out_ctx->channels; out_frame->channel_layout = audio_out_ctx->channel_layout; + out_frame->sample_rate = audio_out_ctx->sample_rate; // The codec gives us the frame size, in samples, we calculate the size of the // samples buffer in bytes @@ -717,7 +719,7 @@ bool VideoStore::setup_resampler() { 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"); return false; } @@ -920,11 +922,10 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { return 0; } #endif - int frame_size = out_frame->nb_samples; - // Resample the in into the audioSampleBuffer until we proceed the whole // decoded data - Debug(2, "Converting %d to %d samples", in_frame->nb_samples, out_frame->nb_samples); + zm_dump_frame(in_frame, "In frame"); + zm_dump_frame(out_frame, "Out frame before resample"); if ( #if defined(HAVE_LIBSWRESAMPLE) #if 0 @@ -958,7 +959,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { return 0; } - Debug(3, "Output_frame samples (%d)", out_frame->nb_samples); // Read a frame audio data from the resample fifo if ( avresample_read(resample_ctx, out_frame->data, frame_size) != frame_size) { @@ -966,10 +966,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { return 0; } #endif - Debug(2, - "Frame: samples(%d), format(%d), sample_rate(%d), channel layout(%d)", - out_frame->nb_samples, out_frame->format, - out_frame->sample_rate, out_frame->channel_layout); + zm_dump_frame(out_frame,"Out frame after resample"); av_init_packet(&opkt); Debug(5, "after init packet"); From 84c812725e5322c87a0428fec83704580098e52d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 14 Jan 2019 14:00:45 -0500 Subject: [PATCH 156/310] spacing, improve debug. WHen looking for starting keyframe in packetqueue, use <= instead of <. --- src/zm_ffmpeg_camera.cpp | 2 +- src/zm_packetqueue.cpp | 17 +++++++++++------ src/zm_stream.cpp | 6 +++--- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index ef61afc09..390f347de 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -721,7 +721,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event //Video recording if ( keyframe && recording.tv_sec ) { - uint32_t last_event_id = monitor->GetLastEventId() ; + uint32_t last_event_id = monitor->GetLastEventId(); uint32_t video_writer_event_id = monitor->GetVideoWriterEventId(); if ( last_event_id != video_writer_event_id ) { diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index 850e482fb..44bc35c0b 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -144,7 +144,7 @@ void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVi // Step 2 - pop packets until we get to the packet in step 2 std::list::reverse_iterator it; - Debug(3, "Looking for keyframe after start recording stream id (%d)", mVideoStreamId ); + Debug(3, "Looking for keyframe after start recording stream id (%d)", mVideoStreamId); for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { ZMPacket *zm_packet = *it; AVPacket *av_packet = &(zm_packet->packet); @@ -153,9 +153,12 @@ void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVi && ( av_packet->stream_index == mVideoStreamId ) && - timercmp( &(zm_packet->timestamp), recording_started, < ) + timercmp( &(zm_packet->timestamp), recording_started, <= ) ) { - Debug(3, "Found keyframe before start with stream index (%d) with keyframe (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ) ); + Debug(3, "Found keyframe before start with stream index %d at %d.%d", + av_packet->stream_index, + zm_packet->timestamp.tv_sec, + zm_packet->timestamp.tv_usec ); break; } } @@ -174,7 +177,7 @@ void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVi unsigned int deleted_frames = 0; ZMPacket *packet = NULL; - while ( distance( it, pktQueue.rend() ) > 1 ) { + while ( distance(it, pktQueue.rend()) > 1 ) { //while ( pktQueue.rend() != it ) { packet = pktQueue.front(); pktQueue.pop_front(); @@ -185,8 +188,10 @@ void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVi zm_packet = pktQueue.front(); av_packet = &(zm_packet->packet); if ( ( ! ( av_packet->flags & AV_PKT_FLAG_KEY ) ) || ( av_packet->stream_index != mVideoStreamId ) ) { - Error( "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); + Error( "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", + deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); } else { - Debug(1, "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); + Debug(1, "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", + deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); } } diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 8602fa88a..357d23ed7 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -54,7 +54,7 @@ bool StreamBase::loadMonitor(int monitor_id) { bool StreamBase::checkInitialised() { if ( !monitor ) { - Fatal( "Cannot stream, not initialised" ); + Fatal("Cannot stream, not initialised"); return false; } return true; @@ -67,10 +67,10 @@ void StreamBase::updateFrameRate(double fps) { Debug(3, "FPS:%.2f, MaxFPS:%.2f, BaseFPS:%.2f, EffectiveFPS:%.2f, FrameMod:%d, replay_rate(%d)", fps, maxfps, base_fps, effective_fps, frame_mod, replay_rate); // Min frame repeat? - while( effective_fps > maxfps ) { + while ( effective_fps > maxfps ) { effective_fps /= 2.0; frame_mod *= 2; - Debug(3, "EffectiveFPS:%.2f, FrameMod:%d", effective_fps, frame_mod); + Debug(3, "Changing fps to be < max %.2f EffectiveFPS:%.2f, FrameMod:%d", maxfps, effective_fps, frame_mod); } } From 6608cb4e35159e552415b86d7b562ea2ddf92438 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Jan 2019 11:25:22 -0500 Subject: [PATCH 157/310] use frame delta to determine how much to sleep --- src/zm_eventstream.cpp | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 91d3dfeb4..f390a8c03 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -145,7 +145,7 @@ bool EventStream::loadEventData(uint64_t event_id) { event_data->storage_id = dbrow[1] ? atoi( dbrow[1] ) : 0; event_data->frame_count = dbrow[2] == NULL ? 0 : atoi(dbrow[2]); event_data->start_time = atoi(dbrow[3]); - event_data->duration = atof(dbrow[4]); + event_data->duration = dbrow[4] ? atof(dbrow[4]) : 0.0; strncpy( event_data->video_file, dbrow[5], sizeof(event_data->video_file)-1 ); std::string scheme_str = std::string(dbrow[6]); if ( scheme_str == "Deep" ) { @@ -762,7 +762,7 @@ Debug(1, "Loading image"); } last_frame_sent = TV_2_FLOAT(now); return true; -} +} // sendFrame(int delta_us) void EventStream::runStream() { openComms(); @@ -780,11 +780,12 @@ void EventStream::runStream() { Debug(3, "frame rate is: (%f)", (double)event_data->frame_count/event_data->duration); updateFrameRate((double)event_data->frame_count/event_data->duration); + gettimeofday(&start, NULL); while( !zm_terminate ) { gettimeofday(&now, NULL); - unsigned int delta_us = 0; + int delta_us = 0; send_frame = false; // commands may set send_frame to true @@ -850,6 +851,7 @@ Debug(3,"cur_frame_id (%d-1) mod frame_mod(%d)",curr_frame_id, frame_mod); Debug(3,"delta %u = base_fps(%f)/effective fps(%f)", delta_us, base_fps, effective_fps); // but must not exceed maxfps delta_us = max(delta_us, 1000000 / maxfps); + Debug(3,"delta %u = base_fps(%f)/effective fps(%f)", delta_us, base_fps, effective_fps); send_frame = true; } } else if ( step != 0 ) { @@ -870,18 +872,34 @@ Debug(3,"cur_frame_id (%d-1) mod frame_mod(%d)",curr_frame_id, frame_mod); if ( !sendFrame(delta_us) ) zm_terminate = true; + curr_stream_time = frame_data->timestamp; if ( !paused ) { curr_frame_id += (replay_rate>0) ? 1 : -1; + + + if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) { Debug(2, "Have mode==MODE_SINGLE and at end of event, looping back to start"); curr_frame_id = 1; } + frame_data = &event_data->frames[curr_frame_id-1]; + + uint64_t now_usec = (now.tv_sec * 1000000 + now.tv_usec); + uint64_t start_usec = (start.tv_sec * 1000000 + start.tv_usec); + uint64_t frame_delta = frame_data->delta*1000000; + + delta_us = frame_delta - (now_usec - start_usec); + Debug(2, "New delta_us now %" PRIu64 " - start %" PRIu64 " = %d - frame %" PRIu64 " = %d", + now_usec, start_usec, now_usec-start_usec, frame_delta, + delta_us); + if ( send_frame && type != STREAM_MPEG ) { - Debug( 3, "dUs: %d", delta_us ); - if ( delta_us ) + if ( delta_us > 0 ) { + Debug( 3, "dUs: %d", delta_us ); usleep( delta_us ); + } } } else { usleep( (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))) ); From 8c53f778b696914e854607cb1da8567d620aa527 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Jan 2019 11:32:44 -0500 Subject: [PATCH 158/310] Start and stop events not just on keyframe. This means we must always queue packets --- src/zm_ffmpeg_camera.cpp | 54 ++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 390f347de..940fa3fd2 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -718,15 +718,15 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event bytes += packet.size; dumpPacket(mFormatContext->streams[packet.stream_index], &packet, "Captured"); - //Video recording - if ( keyframe && recording.tv_sec ) { + // Video recording + if ( recording.tv_sec ) { uint32_t last_event_id = monitor->GetLastEventId(); uint32_t video_writer_event_id = monitor->GetVideoWriterEventId(); if ( last_event_id != video_writer_event_id ) { Debug(2, "Have change of event. last_event(%d), our current (%d)", - last_event_id, video_writer_event_id); + last_event_id, video_writer_event_id); if ( videoStore ) { Info("Re-starting video storage module"); @@ -800,7 +800,8 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event 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() ); + Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", + avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue.size() ); if ( avp->stream_index == mVideoStreamId ) { ret = videoStore->writeVideoFramePacket( avp ); have_video_keyframe = true; @@ -822,36 +823,35 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event } else { // Not recording - if ( videoStore && keyframe ) { - Info("Deleting videoStore instance"); + if ( videoStore ) { + Debug(1,"Deleting videoStore instance"); delete videoStore; videoStore = NULL; have_video_keyframe = false; monitor->SetVideoWriterEventId(0); } - if ( ! videoStore ) { - // Buffer video packets, since we are not recording. - // All audio packets are keyframes, so only if it's a video keyframe - if ( packet.stream_index == mVideoStreamId ) { - if ( keyframe ) { - packetqueue.clearQueue(monitor->GetPreEventCount(), mVideoStreamId); - packetqueue.queuePacket(&packet); - } else if ( packetqueue.size() ) { - Debug(3, "queue has %d", packetqueue.size()); - // it's a keyframe or we already have something in the queue - packetqueue.queuePacket(&packet); - } - } else if ( packet.stream_index == mAudioStreamId ) { - // The following lines should ensure that the queue always begins with a video keyframe - //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); - } - } - } } // end if recording or not + // Buffer video packets, we need to always have from the last keyframe buffered + // All audio packets are keyframes, so only if it's a video keyframe + if ( packet.stream_index == mVideoStreamId ) { + if ( keyframe ) { + packetqueue.clearQueue(monitor->GetPreEventCount(), mVideoStreamId); + packetqueue.queuePacket(&packet); + } else if ( packetqueue.size() ) { + Debug(3, "queue has %d", packetqueue.size()); + // it's a keyframe or we already have something in the queue + packetqueue.queuePacket(&packet); + } + } else if ( packet.stream_index == mAudioStreamId ) { + // The following lines should ensure that the queue always begins with a video keyframe + //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); + } + } + if ( packet.stream_index == mVideoStreamId ) { // only do decode if we have had a keyframe, should save a few cycles. if ( have_video_keyframe || keyframe ) { From d068d019fba6112532fd5d154dbde819b53a1cce Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Jan 2019 11:34:17 -0500 Subject: [PATCH 159/310] turn section_length into seconds instead of frames --- src/zm_monitor.cpp | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index ef6a35367..284ec3d11 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1347,8 +1347,6 @@ bool Monitor::Analyse() { auto_resume_time = 0; } - static int last_section_mod = 0; - if ( Enabled() ) { bool signal = shared_data->signal; bool signal_change = (signal != last_signal); @@ -1384,7 +1382,6 @@ bool Monitor::Analyse() { if ( event && !signal ) { Info( "%s: %03d - Closing event %" PRIu64 ", signal loss", name, image_count, event->Id() ); closeEvent(); - last_section_mod = 0; } if ( !event ) { if ( cause.length() ) @@ -1455,29 +1452,14 @@ bool Monitor::Analyse() { if ( event ) { Debug(3, "Have signal and recording with open event at (%d.%d)", timestamp->tv_sec, timestamp->tv_usec); - if ( section_length && ( timestamp->tv_sec >= 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 + if ( section_length && ( ( timestamp->tv_sec - video_store_data->recording.tv_sec ) >= section_length ) ) { + Info( "%s: %03d - Closing event %" PRIu64 ", section end forced %d - %d = %d >= %d", + name, image_count, event->Id(), + timestamp->tv_sec, video_store_data->recording.tv_sec, + timestamp->tv_sec - video_store_data->recording.tv_sec, + section_length ); - if ( section_mod < last_section_mod ) { - //if ( state == IDLE || state == TAPE || event_close_mode == CLOSE_TIME ) { - //if ( state == TAPE ) { - //shared_data->state = state = IDLE; - //Info( "%s: %03d - Closing event %d, section end", name, image_count, event->Id() ) - //} else { - Info( "%s: %03d - Closing event %" PRIu64 ", section end forced ", name, image_count, event->Id() ); - //} - closeEvent(); - last_section_mod = 0; - //} else { - //Debug( 2, "Time to close event, but state (%d) is not IDLE or TAPE and event_close_mode is not CLOSE_TIME (%d)", state, event_close_mode ); - //} - } else { - last_section_mod = section_mod; - } + closeEvent(); } // end if section_length } // end if event @@ -1737,11 +1719,10 @@ Error("Creating new event when one exists"); } } else { if ( event ) { - Info( "%s: %03d - Closing event %" PRIu64 ", trigger off", name, image_count, event->Id() ); + Info("%s: %03d - Closing event %" PRIu64 ", trigger off", name, image_count, event->Id()); closeEvent(); } shared_data->state = state = IDLE; - last_section_mod = 0; trigger_data->trigger_state = TRIGGER_CANCEL; } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) From 5f6bcec4ca42baf69f0402d312f99a858e8b843d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Jan 2019 11:35:37 -0500 Subject: [PATCH 160/310] Use start_pts instead of start_dts when calculating output pkt.dts. Because start_dts is often lower than start_pts, we can get into a situation where we calculate a dts that is > pts. --- src/zm_videostore.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 6d728cf3c..e28ce6b59 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -815,34 +815,38 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt) { } // 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 ( ipkt->dts != AV_NOPTS_VALUE ) { +#if 0 if ( (!video_first_dts) && ( ipkt->dts >= 0 ) ) { // This is the first packet. opkt.dts = 0; Debug(1, "Starting video first_dts will become (%" PRId64 ")", ipkt->dts); video_first_dts = ipkt->dts; } else { +#endif opkt.dts = av_rescale_q( - ipkt->dts - video_first_dts, + ipkt->dts - video_first_pts, video_in_stream->time_base, video_out_stream->time_base ); - Debug(3, "opkt.dts = %" PRId64 " from ipkt.dts(%" PRId64 ") - first_dts(%" PRId64 ")", - opkt.dts, ipkt->dts, video_first_dts); + Debug(3, "opkt.dts = %" PRId64 " from ipkt.dts(%" PRId64 ") - first_pts(%" PRId64 ")", + opkt.dts, ipkt->dts, video_first_pts); video_last_dts = ipkt->dts; +#if 0 } - } else { - Debug(3, "opkt.dts = undef"); - opkt.dts = AV_NOPTS_VALUE; - } - - if ( opkt.dts > opkt.pts ) { - Debug(1, +#endif + if ( opkt.dts > opkt.pts ) { + Debug(1, "opkt.dts(%" PRId64 ") must be <= opkt.pts(%" PRId64 "). Decompression must happen " "before presentation.", opkt.dts, opkt.pts); - opkt.dts = opkt.pts; + opkt.dts = opkt.pts; + } + } else { + Debug(3, "opkt.dts = undef"); + opkt.dts = 0; } + opkt.flags = ipkt->flags; opkt.pos = -1; opkt.data = ipkt->data; From 92066aaf4f4ab6b62448e766233842d65e74fc74 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Jan 2019 16:33:27 -0500 Subject: [PATCH 161/310] add stream start timeval --- src/zm_stream.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zm_stream.h b/src/zm_stream.h index f9364bbef..92fb591b8 100644 --- a/src/zm_stream.h +++ b/src/zm_stream.h @@ -85,6 +85,7 @@ protected: int step; struct timeval now; + struct timeval start; // clock time when started the event struct timeval last_comm_update; double base_fps; From 345e2fef0fed3ea0c981adb96e743f8b7b119c19 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 15 Jan 2019 16:44:58 -0500 Subject: [PATCH 162/310] start only used in eventstream --- src/zm_eventstream.h | 2 +- src/zm_stream.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/zm_eventstream.h b/src/zm_eventstream.h index 0c9ee0db3..792473c4a 100644 --- a/src/zm_eventstream.h +++ b/src/zm_eventstream.h @@ -78,6 +78,7 @@ class EventStream : public StreamBase { int curr_frame_id; double curr_stream_time; bool send_frame; + struct timeval start; // clock time when started the event EventData *event_data; FFmpeg_Input *ffmpeg_input; @@ -108,7 +109,6 @@ class EventStream : public StreamBase { input_codec = 0; ffmpeg_input = NULL; - } void setStreamStart( uint64_t init_event_id, unsigned int init_frame_id ); void setStreamStart( int monitor_id, time_t event_time ); diff --git a/src/zm_stream.h b/src/zm_stream.h index 92fb591b8..f9364bbef 100644 --- a/src/zm_stream.h +++ b/src/zm_stream.h @@ -85,7 +85,6 @@ protected: int step; struct timeval now; - struct timeval start; // clock time when started the event struct timeval last_comm_update; double base_fps; From a39b92154cc62ea66af7c6160bc4ceb39efaeb89 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 17 Jan 2019 08:49:48 -0500 Subject: [PATCH 163/310] wip --- src/zm_ffmpeg_camera.cpp | 3 ++- src/zm_videostore.cpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 13cbde10d..0cfa53b82 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -362,6 +362,7 @@ int FfmpegCamera::OpenFfmpeg() { } else { Warning("Unknown method (%s)", method.c_str() ); } +//#av_dict_set(&opts, "timeout", "10000000", 0); // in microseconds. if ( ret < 0 ) { Warning("Could not set rtsp_transport method '%s'\n", method.c_str()); @@ -1039,7 +1040,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event int FfmpegCamera::FfmpegInterruptCallback(void *ctx) { //FfmpegCamera* camera = reinterpret_cast(ctx); - + Debug(4, "FfmpegInterruptCallback"); return zm_terminate; } diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 8af95e616..79b084912 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -100,6 +100,7 @@ VideoStore::VideoStore( } // Since we are not re-encoding, all we have to do is copy the parameters video_out_ctx = video_out_stream->codec; + video_out_ctx->time_base = video_in_ctx->time_base; #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // Copy params from instream to ctx From ad69915e4ae849c42b9d4a632652dcd765ece63d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 22 Jan 2019 13:30:40 -0500 Subject: [PATCH 164/310] fix build on libav 9, ubuntu trusty --- src/zm_ffmpeg.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 36b499ee9..ad21f98f2 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -285,7 +285,11 @@ void zm_dump_frame(const AVFrame *frame,const char *text) { av_get_sample_fmt_name((AVSampleFormat)frame->format), frame->sample_rate, frame->nb_samples, +#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) frame->channels, +#else +0, +#endif frame->channel_layout ); } From 7026ebaface44bd9d496a6aefbd6527be427b2b6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 22 Jan 2019 16:45:38 -0500 Subject: [PATCH 165/310] Make ajax/stream wait longer for zms. On pi can take up to 3 seconds. Also for php < 5.6, we need to fake 64bit unpack support --- web/ajax/stream.php | 47 +++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/web/ajax/stream.php b/web/ajax/stream.php index 356227529..e8e7aca7f 100644 --- a/web/ajax/stream.php +++ b/web/ajax/stream.php @@ -26,7 +26,7 @@ if ( sem_acquire($semaphore,1) !== false ) { Warning("sock file $localSocketFile already exists?! Is someone else talking to zms?"); // They could be. We can maybe have concurrent requests from a browser. } - if ( ! socket_bind( $socket, $localSocketFile ) ) { + if ( !socket_bind( $socket, $localSocketFile ) ) { ajaxError("socket_bind( $localSocketFile ) failed: ".socket_strerror(socket_last_error()) ); } @@ -52,12 +52,14 @@ if ( sem_acquire($semaphore,1) !== false ) { $msg = pack( 'lcN', MSG_CMD, $_REQUEST['command'], $_REQUEST['offset'] ); break; default : + Logger::Debug("Sending command " . $_REQUEST['command']); $msg = pack( 'lc', MSG_CMD, $_REQUEST['command'] ); break; } $remSockFile = ZM_PATH_SOCKS.'/zms-'.sprintf('%06d',$_REQUEST['connkey']).'s.sock'; - $max_socket_tries = 10; + // Pi can take up to 3 seconds for zms to start up. + $max_socket_tries = 1000; // FIXME This should not exceed web_ajax_timeout while ( !file_exists($remSockFile) && $max_socket_tries-- ) { //sometimes we are too fast for our own good, if it hasn't been setup yet give it a second. // WHY? We will just send another one... @@ -92,39 +94,31 @@ if ( sem_acquire($semaphore,1) !== false ) { } else if ( $numSockets == 0 ) { Error( "Timed out waiting for msg $remSockFile" ); socket_Set_nonblock($socket); - #ajaxError( "Timed out waiting for msg $remSockFile" ); + #ajaxError("Timed out waiting for msg $remSockFile"); } else if ( $numSockets > 0 ) { if ( count($rSockets) != 1 ) { - Error( 'Bogus return from select, '.count($rSockets).' sockets available' ); - ajaxError( 'Bogus return from select, '.count($rSockets).' sockets available' ); + 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 ) ) { case -1 : - { - ajaxError( "socket_recvfrom( $remSockFile ) failed: ".socket_strerror(socket_last_error()) ); + ajaxError("socket_recvfrom( $remSockFile ) failed: ".socket_strerror(socket_last_error())); break; - } case 0 : - { - ajaxError( 'No data to read from socket' ); + ajaxError('No data to read from socket'); break; - } default : - { if ( $nbytes != MSG_DATA_SIZE ) - ajaxError( "Got unexpected message size, got $nbytes, expected ".MSG_DATA_SIZE ); + ajaxError("Got unexpected message size, got $nbytes, expected ".MSG_DATA_SIZE); break; } - } - - $data = unpack( 'ltype', $msg ); + $data = unpack('ltype', $msg); switch ( $data['type'] ) { case MSG_DATA_WATCH : - { - $data = unpack( "ltype/imonitor/istate/dfps/ilevel/irate/ddelay/izoom/Cdelayed/Cpaused/Cenabled/Cforced", $msg ); + $data = unpack('ltype/imonitor/istate/dfps/ilevel/irate/ddelay/izoom/Cdelayed/Cpaused/Cenabled/Cforced', $msg); Logger::Debug("FPS: " . $data['fps'] ); $data['fps'] = round( $data['fps'], 2 ); Logger::Debug("FPS: " . $data['fps'] ); @@ -140,11 +134,13 @@ if ( sem_acquire($semaphore,1) !== false ) { } ajaxResponse( array( 'status'=>$data ) ); break; - } case MSG_DATA_EVENT : - { - $data = unpack( "ltype/Pevent/iprogress/irate/izoom/Cpaused", $msg ); - //$data['progress'] = sprintf( "%.2f", $data['progress'] ); + if ( version_compare( phpversion(), '5.6.0', '>') ) { + $data = unpack('ltype/ieventlow/ieventhigh/iprogress/irate/izoom/Cpaused', $msg); + $data['event'] = $data['eventhigh'] << 32 | $data['eventlow']; + } else { + $data = unpack('ltype/Qevent/iprogress/irate/izoom/Cpaused', $msg); + } $data['rate'] /= RATE_BASE; $data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 ); if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) { @@ -154,13 +150,10 @@ if ( sem_acquire($semaphore,1) !== false ) { $data['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS); } } - ajaxResponse( array( 'status'=>$data ) ); + ajaxResponse(array('status'=>$data)); break; - } default : - { - ajaxError( "Unexpected received message type '$type'" ); - } + ajaxError("Unexpected received message type '$type'"); } sem_release($semaphore); } else { From 1e56e750cf689e3b158deb71652fb8aa02653ab5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Jan 2019 11:04:38 -0500 Subject: [PATCH 166/310] Introduce ZM_COOKIE_LIFETIME which sets the life of the SESSION cookie, instead of using what is in php.ini --- scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index fa7b86079..5378a25d0 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -3941,6 +3941,13 @@ our @options = ( type => $types{string}, category => 'mail', }, + { + name => 'ZM_COOKIE_LIFETIME', + default => '3600', + description => q`The maximum life of a COOKIE used when setting up PHP's session handler. This will affect how long a session will be valid for since the last request. Keeping this short helps prevent session hijacking. Keeping it long allows you to stay logged in longer without refreshing the view.`, + type => $types{integer}, + category => 'system', + } ); our %options_hash = map { ( $_->{name}, $_ ) } @options; From 85bb70df6831a9f00027da05b65e73c498b012c4 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Jan 2019 11:05:19 -0500 Subject: [PATCH 167/310] Use zm specific session functions, which are now located in includes/session.php. Be more agressive about clearing session on logout. --- web/includes/auth.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/web/includes/auth.php b/web/includes/auth.php index c74c13b80..edc9ecdc7 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -93,7 +93,7 @@ function userLogin($username='', $password='', $passwordHashed=false) { if ( ZM_AUTH_TYPE == 'builtin' ) { $_SESSION['passwordHash'] = $user['Password']; } - session_regenerate_id(); + zm_session_regenerate_id(); } else { Warning("Login denied for user \"$username\""); $_SESSION['loginFailed'] = true; @@ -107,9 +107,15 @@ function userLogin($username='', $password='', $passwordHashed=false) { function userLogout() { global $user; Info('User "'.$user['Username'].'" logged out'); - session_start(); - unset($_SESSION['user']); unset($user); + session_start(); + $_SESSION = array(); + if ( ini_get('session.use_cookies') ) { + $p = session_get_cookie_params(); + # Update the cookie to expire in the past. + setcookie(session_name(), '', time() - 31536000, $p['path'], $p['domain'], $p['secure'], $p['httponly']); + } + session_unset(); session_destroy(); } From 4e9ce3c5b70b26c9e056e9e7aa0276d85211c9c5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Jan 2019 11:05:36 -0500 Subject: [PATCH 168/310] Move session code to includes/session.php --- web/index.php | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/web/index.php b/web/index.php index 5190fad65..736ee4af5 100644 --- a/web/index.php +++ b/web/index.php @@ -44,6 +44,7 @@ if ( false ) { } require_once('includes/config.php'); +require_once('includes/session.php'); require_once('includes/logger.php'); require_once('includes/Server.php'); require_once('includes/Storage.php'); @@ -114,19 +115,7 @@ if ( !file_exists(ZM_SKIN_PATH) ) Fatal("Invalid skin '$skin'"); $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)'); -session_set_cookie_params( - $currentCookieParams['lifetime'], - $currentCookieParams['path'], - $currentCookieParams['domain'], - $currentCookieParams['secure'], - true -); - -ini_set('session.name', 'ZMSESSID'); - -session_start(); +zm_session_start(); if ( !isset($_SESSION['skin']) || isset($_REQUEST['skin']) || !isset($_COOKIE['zmSkin']) || $_COOKIE['zmSkin'] != $skin ) { $_SESSION['skin'] = $skin; From 0eba430932512abe485fcd65be4bdbd5333ed21f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Jan 2019 11:05:43 -0500 Subject: [PATCH 169/310] remove duplicate line --- web/includes/session.php | 52 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 web/includes/session.php diff --git a/web/includes/session.php b/web/includes/session.php new file mode 100644 index 000000000..e5aaa50ac --- /dev/null +++ b/web/includes/session.php @@ -0,0 +1,52 @@ + From cc0b5e0f1f0bc9c14a4a0f911e4c24c0073c0fdb Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Jan 2019 12:52:01 -0500 Subject: [PATCH 170/310] Move is_session_open to session.php. Move code to clear a session into session.php --- web/includes/auth.php | 22 +--------------------- web/includes/session.php | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/web/includes/auth.php b/web/includes/auth.php index edc9ecdc7..e365ecef3 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -108,15 +108,7 @@ function userLogout() { global $user; Info('User "'.$user['Username'].'" logged out'); unset($user); - session_start(); - $_SESSION = array(); - if ( ini_get('session.use_cookies') ) { - $p = session_get_cookie_params(); - # Update the cookie to expire in the past. - setcookie(session_name(), '', time() - 31536000, $p['path'], $p['domain'], $p['secure'], $p['httponly']); - } - session_unset(); - session_destroy(); + zm_session_clear(); } function getAuthUser($auth) { @@ -211,18 +203,6 @@ function canEdit($area, $mid=false) { return ( $user[$area] == 'Edit' && ( !$mid || visibleMonitor($mid) )); } -function is_session_started() { - if ( php_sapi_name() !== 'cli' ) { - if ( version_compare(phpversion(), '5.4.0', '>=') ) { - return session_status() === PHP_SESSION_ACTIVE ? TRUE : FALSE; - } else { - return session_id() === '' ? FALSE : TRUE; - } - } else { - Warning("php_sapi_name === 'cli'"); - } - return FALSE; -} if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_HASH_LOGINS && empty($user) && ! empty($_REQUEST['auth']) ) { diff --git a/web/includes/session.php b/web/includes/session.php index e5aaa50ac..ae102424c 100644 --- a/web/includes/session.php +++ b/web/includes/session.php @@ -49,4 +49,28 @@ function zm_session_regenerate_id() { session_start(); } +function is_session_started() { + if ( php_sapi_name() !== 'cli' ) { + if ( version_compare(phpversion(), '5.4.0', '>=') ) { + return session_status() === PHP_SESSION_ACTIVE ? TRUE : FALSE; + } else { + return session_id() === '' ? FALSE : TRUE; + } + } else { + Warning("php_sapi_name === 'cli'"); + } + return FALSE; +} + +function zm_session_clear() { + session_start(); + $_SESSION = array(); + if ( ini_get('session.use_cookies') ) { + $p = session_get_cookie_params(); + # Update the cookie to expire in the past. + setcookie(session_name(), '', time() - 31536000, $p['path'], $p['domain'], $p['secure'], $p['httponly']); + } + session_unset(); + session_destroy(); +} ?> From 2e2404643f9f3c54c381ec5fa6d14576d53e8a71 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Jan 2019 13:20:24 -0500 Subject: [PATCH 171/310] Fix bandwidth due to new actions code. Update buttons on bandwidth popup --- web/includes/actions/bandwidth.php | 2 ++ web/skins/classic/views/bandwidth.php | 30 +++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/web/includes/actions/bandwidth.php b/web/includes/actions/bandwidth.php index 8a9056cd2..c5fafa1c9 100644 --- a/web/includes/actions/bandwidth.php +++ b/web/includes/actions/bandwidth.php @@ -23,5 +23,7 @@ if ( $action == 'bandwidth' && isset($_REQUEST['newBandwidth']) ) { $_COOKIE['zmBandwidth'] = validStr($_REQUEST['newBandwidth']); setcookie('zmBandwidth', validStr($_REQUEST['newBandwidth']), time()+3600*24*30*12*10); $refreshParent = true; + $view = 'none'; + $closePopup = true; } ?> diff --git a/web/skins/classic/views/bandwidth.php b/web/skins/classic/views/bandwidth.php index ca8d1c6ce..2d4fbc5bf 100644 --- a/web/skins/classic/views/bandwidth.php +++ b/web/skins/classic/views/bandwidth.php @@ -20,22 +20,19 @@ $newBandwidth = $_COOKIE['zmBandwidth']; -if ( $user && !empty($user['MaxBandwidth']) ) -{ - if ( $user['MaxBandwidth'] == "low" ) - { - unset( $bandwidth_options['high'] ); - unset( $bandwidth_options['medium'] ); - } - elseif ( $user['MaxBandwidth'] == "medium" ) - { - unset( $bandwidth_options['high'] ); - } +# Limit available options to what are available in user +if ( $user && !empty($user['MaxBandwidth']) ) { + if ( $user['MaxBandwidth'] == 'low' ) { + unset($bandwidth_options['high']); + unset($bandwidth_options['medium']); + } else if ( $user['MaxBandwidth'] == 'medium' ) { + unset($bandwidth_options['high']); + } } $focusWindow = true; -xhtmlHeaders(__FILE__, translate('Bandwidth') ); +xhtmlHeaders(__FILE__, translate('Bandwidth')); ?>
@@ -43,13 +40,14 @@ xhtmlHeaders(__FILE__, translate('Bandwidth') );

- - + +

-

+

- + +
From 71f961d01248dcbfe01a96f3bfaa0475124f2de0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Jan 2019 16:05:51 -0500 Subject: [PATCH 172/310] remove redirect to console on login, as it is done in javascript after Logging in message is displayed --- web/includes/actions/login.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/includes/actions/login.php b/web/includes/actions/login.php index b497b29f5..787bb34ca 100644 --- a/web/includes/actions/login.php +++ b/web/includes/actions/login.php @@ -19,7 +19,7 @@ // -if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 'remote' || isset($_REQUEST['password']) ) ) { +if ( ('login' == $action) && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 'remote' || isset($_REQUEST['password']) ) ) { $refreshParent = true; // User login is automatically performed in includes/auth.php So we don't need to perform a login here, @@ -29,7 +29,6 @@ if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 're $view = 'login'; } else { $view = 'postlogin'; - $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console'; } } ?> From b09a71d0e2a135ee28735e1eff673d910c685872 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Jan 2019 16:06:16 -0500 Subject: [PATCH 173/310] code style --- web/includes/auth.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/includes/auth.php b/web/includes/auth.php index e365ecef3..45e6ad267 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -203,14 +203,12 @@ function canEdit($area, $mid=false) { return ( $user[$area] == 'Edit' && ( !$mid || visibleMonitor($mid) )); } - if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_HASH_LOGINS && empty($user) && ! empty($_REQUEST['auth']) ) { if ( $authUser = getAuthUser($_REQUEST['auth']) ) { userLogin($authUser['Username'], $authUser['Password'], true); } - } - else if ( isset($_REQUEST['username']) and isset($_REQUEST['password']) ) { + } else if ( isset($_REQUEST['username']) and isset($_REQUEST['password']) ) { userLogin($_REQUEST['username'], $_REQUEST['password'], false); } if ( !empty($user) ) { From 97e3a8178a31e9ccc53cd3c1238d9cc6b1dc0622 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Jan 2019 16:08:09 -0500 Subject: [PATCH 174/310] use session_regenerate_id instead of other strange code --- web/includes/session.php | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/web/includes/session.php b/web/includes/session.php index ae102424c..b65c65df6 100644 --- a/web/includes/session.php +++ b/web/includes/session.php @@ -21,7 +21,8 @@ function zm_session_start() { session_start(); // Do not allow to use too old session ID - if (!empty($_SESSION['last_time']) && $_SESSION['last_time'] < time() - 180) { + if ( !empty($_SESSION['last_time']) && ( $_SESSION['last_time'] < (time() - 180) ) ) { + Info('Destroying session due to timeout. '); session_destroy(); session_start(); } @@ -34,19 +35,14 @@ function zm_session_regenerate_id() { if ( session_status() != PHP_SESSION_ACTIVE ) { session_start(); } - // WARNING: Never use confidential strings for prefix! - $newid = session_create_id(); + // Set deleted timestamp. Session data must not be deleted immediately for reasons. $_SESSION['last_time'] = time(); // Finish session - session_commit(); - // Make sure to accept user defined session ID - // NOTE: You must enable use_strict_mode for normal operations. - ini_set('session.use_strict_mode', 0); - // Set new custome session ID - session_id($newid); - // Start with custome session ID + session_write_close(); + session_start(); + session_regenerate_id(); } function is_session_started() { From 4bacd26c982fee4c4aad54c95fd8dc6693527571 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Jan 2019 16:08:24 -0500 Subject: [PATCH 175/310] log redirections --- web/index.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/index.php b/web/index.php index 736ee4af5..4b3f88ce1 100644 --- a/web/index.php +++ b/web/index.php @@ -222,6 +222,7 @@ if ( ZM_OPT_USE_AUTH and !isset($user) ) { CSPHeaders($view, $cspNonce); if ( $redirect ) { + Logger::Debug("Redirecting to $redirect"); header('Location: '.$redirect); return; } From 86b7fe5d290438b4a764758f735287f963ffc1e6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Jan 2019 16:08:41 -0500 Subject: [PATCH 176/310] fix spacing --- web/skins/classic/views/js/postlogin.js.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/js/postlogin.js.php b/web/skins/classic/views/js/postlogin.js.php index 914d3e77b..976a87bbd 100644 --- a/web/skins/classic/views/js/postlogin.js.php +++ b/web/skins/classic/views/js/postlogin.js.php @@ -13,9 +13,9 @@ // Append '?(GET query)' to URL if the GET query is not empty. var querySuffix = ""; var newUrl = thisUrl + querySuffix; From a176c9bbd2db37cca25ad63e105a635c0c774186 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 5 Feb 2019 11:39:21 -0500 Subject: [PATCH 177/310] improve debug line when there is a problem updating config entry --- scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index 92085b07b..3fdeb722c 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -257,7 +257,7 @@ sub saveConfigToDB { $option->{category}, $option->{readonly} ? 1 : 0, $option->{db_requires} - ) or croak( "Can't execute: ".$sth->errstr() ); + ) or croak("Can't execute when updating config entry $$option{name}: ".$sth->errstr() ); } # end foreach option $sth->finish(); From 5b288d3b670c895b4a37e764bf84c48b2fd7039e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 5 Feb 2019 11:39:50 -0500 Subject: [PATCH 178/310] split description into description and help text for COOKIE_LIFETIME --- scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 5378a25d0..d1614007e 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -3944,7 +3944,8 @@ our @options = ( { name => 'ZM_COOKIE_LIFETIME', default => '3600', - description => q`The maximum life of a COOKIE used when setting up PHP's session handler. This will affect how long a session will be valid for since the last request. Keeping this short helps prevent session hijacking. Keeping it long allows you to stay logged in longer without refreshing the view.`, + description => q`The maximum life of a COOKIE used when setting up PHP's session handler.`, + help => q`This will affect how long a session will be valid for since the last request. Keeping this short helps prevent session hijacking. Keeping it long allows you to stay logged in longer without refreshing the view.`, type => $types{integer}, category => 'system', } From 5a9083fe86a1451f6c5f4ee9a4aa6c8a7af9ce72 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 5 Feb 2019 11:40:58 -0500 Subject: [PATCH 179/310] Remove redirect on line. We do it in javascript on postlogin view so that we can say logging in before switching to console --- web/includes/actions/login.php | 1 - 1 file changed, 1 deletion(-) diff --git a/web/includes/actions/login.php b/web/includes/actions/login.php index b497b29f5..48257fa28 100644 --- a/web/includes/actions/login.php +++ b/web/includes/actions/login.php @@ -29,7 +29,6 @@ if ( $action == 'login' && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == 're $view = 'login'; } else { $view = 'postlogin'; - $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=console'; } } ?> From 2466d765bf55032cc229b144d07d618face82b0f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 5 Feb 2019 11:44:45 -0500 Subject: [PATCH 180/310] If there is a username in the session, then we are logged in, but we need to load the user object from the db. We can't just trust it from the session. The user may have been deleted and having that data in the session can be a security risk. So load the user object on every request. --- web/includes/auth.php | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/web/includes/auth.php b/web/includes/auth.php index e365ecef3..fcc4e94e2 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -88,7 +88,7 @@ function userLogin($username='', $password='', $passwordHashed=false) { $_SESSION['remoteAddr'] = $_SERVER['REMOTE_ADDR']; // To help prevent session hijacking if ( $dbUser = dbFetchOne($sql, NULL, $sql_values) ) { Info("Login successful for user \"$username\""); - $_SESSION['user'] = $user = $dbUser; + $user = $dbUser; unset($_SESSION['loginFailed']); if ( ZM_AUTH_TYPE == 'builtin' ) { $_SESSION['passwordHash'] = $user['Password']; @@ -203,19 +203,39 @@ function canEdit($area, $mid=false) { return ( $user[$area] == 'Edit' && ( !$mid || visibleMonitor($mid) )); } - if ( ZM_OPT_USE_AUTH ) { - if ( ZM_AUTH_HASH_LOGINS && empty($user) && ! empty($_REQUEST['auth']) ) { + if ( isset($_SESSION['username']) ) { + # Need to refresh permissions and validate that the user still exists + $sql = 'SELECT * FROM Users WHERE Enabled=1 AND Username=?'; + $user = dbFetchOne($sql, NULL, array($_SESSION['username'])); + } + + $close_session = 0; + if ( !is_session_started() ) { + session_start(); + $close_session = 1; + } + + if ( ZM_AUTH_RELAY == 'plain' ) { + // Need to save this in session + $_SESSION['password'] = $password; + } + $_SESSION['remoteAddr'] = $_SERVER['REMOTE_ADDR']; // To help prevent session hijacking + + if ( ZM_AUTH_HASH_LOGINS && empty($user) && !empty($_REQUEST['auth']) ) { if ( $authUser = getAuthUser($_REQUEST['auth']) ) { userLogin($authUser['Username'], $authUser['Password'], true); } - } - else if ( isset($_REQUEST['username']) and isset($_REQUEST['password']) ) { + } else if ( isset($_REQUEST['username']) and isset($_REQUEST['password']) ) { userLogin($_REQUEST['username'], $_REQUEST['password'], false); } if ( !empty($user) ) { // generate it once here, while session is open. Value will be cached in session and return when called later on generateAuthHash(ZM_AUTH_HASH_IPS); } + if ( $close_session ) + session_write_close(); +} else { + $user = $defaultUser; } ?> From cb0d9325e6f32c300827b292c1fdaf11fb4cb796 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 5 Feb 2019 11:45:09 -0500 Subject: [PATCH 181/310] Use session_regenerate_id instead of our broken code to do the same --- web/includes/session.php | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/web/includes/session.php b/web/includes/session.php index ae102424c..8f9c90e66 100644 --- a/web/includes/session.php +++ b/web/includes/session.php @@ -20,34 +20,23 @@ function zm_session_start() { ini_set('session.name', 'ZMSESSID'); session_start(); - // Do not allow to use too old session ID + // Do not allow to use expired session ID if (!empty($_SESSION['last_time']) && $_SESSION['last_time'] < time() - 180) { session_destroy(); session_start(); } -} +} // function zm_session_start() // My session regenerate id function function zm_session_regenerate_id() { - // Call session_create_id() while session is active to - // make sure collision free. if ( session_status() != PHP_SESSION_ACTIVE ) { session_start(); } - // WARNING: Never use confidential strings for prefix! - $newid = session_create_id(); // Set deleted timestamp. Session data must not be deleted immediately for reasons. $_SESSION['last_time'] = time(); - // Finish session - session_commit(); - // Make sure to accept user defined session ID - // NOTE: You must enable use_strict_mode for normal operations. - ini_set('session.use_strict_mode', 0); - // Set new custome session ID - session_id($newid); - // Start with custome session ID - session_start(); -} + session_regenerate_id(); + unset($_SESSION['last_time']); +} // function zm_session_regenerate_id() function is_session_started() { if ( php_sapi_name() !== 'cli' ) { @@ -60,7 +49,7 @@ function is_session_started() { Warning("php_sapi_name === 'cli'"); } return FALSE; -} +} // function is_session_started() function zm_session_clear() { session_start(); @@ -72,5 +61,5 @@ function zm_session_clear() { } session_unset(); session_destroy(); -} +} // function zm_session_clear() ?> From b6b4a21dbe3174a54121f063a8670ffcd964bf9f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 5 Feb 2019 11:45:58 -0500 Subject: [PATCH 182/310] Move auth code to includes/auth.php --- web/index.php | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/web/index.php b/web/index.php index 736ee4af5..7c3fc6141 100644 --- a/web/index.php +++ b/web/index.php @@ -117,25 +117,26 @@ $skinBase[] = $skin; zm_session_start(); -if ( !isset($_SESSION['skin']) || isset($_REQUEST['skin']) || !isset($_COOKIE['zmSkin']) || $_COOKIE['zmSkin'] != $skin ) { +if ( + !isset($_SESSION['skin']) || + isset($_REQUEST['skin']) || + !isset($_COOKIE['zmSkin']) || + $_COOKIE['zmSkin'] != $skin +) { $_SESSION['skin'] = $skin; setcookie('zmSkin', $skin, time()+3600*24*30*12*10); } -if ( !isset($_SESSION['css']) || isset($_REQUEST['css']) || !isset($_COOKIE['zmCSS']) || $_COOKIE['zmCSS'] != $css ) { +if ( + !isset($_SESSION['css']) || + isset($_REQUEST['css']) || + !isset($_COOKIE['zmCSS']) || + $_COOKIE['zmCSS'] != $css +) { $_SESSION['css'] = $css; setcookie('zmCSS', $css, time()+3600*24*30*12*10); } -if ( ZM_OPT_USE_AUTH ) { - if ( isset($_SESSION['user']) ) { - $user = $_SESSION['user']; - } else { - unset($user); - } -} else { - $user = $defaultUser; -} # Only one request can open the session file at a time, so let's close the session here to improve concurrency. # Any file/page that sets session variables must re-open it. session_write_close(); @@ -222,6 +223,7 @@ if ( ZM_OPT_USE_AUTH and !isset($user) ) { CSPHeaders($view, $cspNonce); if ( $redirect ) { + Logger::Debug("Redirecting to $redirect"); header('Location: '.$redirect); return; } From 78bc2c1dc200afb7f9cf46880d6bef657b9ade10 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 5 Feb 2019 11:53:57 -0500 Subject: [PATCH 183/310] add autocomplete tags to username and password inputs --- web/skins/classic/views/login.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/login.php b/web/skins/classic/views/login.php index c40f50615..d41634003 100644 --- a/web/skins/classic/views/login.php +++ b/web/skins/classic/views/login.php @@ -19,10 +19,10 @@ xhtmlHeaders(__FILE__, translate('Login') );

account_circle

- + - + Date: Tue, 5 Feb 2019 12:32:24 -0500 Subject: [PATCH 184/310] Don't redirect to login if we are already viewing login. Put auth before including skin includes --- web/index.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/web/index.php b/web/index.php index 7c3fc6141..4f9cb2dd2 100644 --- a/web/index.php +++ b/web/index.php @@ -169,12 +169,14 @@ $request = null; if ( isset($_REQUEST['request']) ) $request = detaintPath($_REQUEST['request']); -foreach ( getSkinIncludes('skin.php') as $includeFile ) - require_once $includeFile; - # User Login will be performed in auth.php require_once('includes/auth.php'); +foreach ( getSkinIncludes('skin.php') as $includeFile ) { + #Logger::Debug("including $includeFile"); + require_once $includeFile; +} + if ( isset($_REQUEST['action']) ) $action = detaintPath($_REQUEST['action']); @@ -210,13 +212,14 @@ if ( $action ) { } # If I put this here, it protects all views and popups, but it has to go after actions.php because actions.php does the actual logging in. -if ( ZM_OPT_USE_AUTH and !isset($user) ) { +if ( ZM_OPT_USE_AUTH and !isset($user) and ($view != 'login') ) { Logger::Debug('Redirecting to login'); - $view = 'login'; + $view = 'none'; + $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=login'; $request = null; } else if ( ZM_SHOW_PRIVACY && ($action != 'privacy') && ($view != 'options') && (!$request) && canEdit('System') ) { - Logger::Debug('Redirecting to privacy'); - $view = 'privacy'; + $view = 'none'; + $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=privacy'; $request = null; } From 060751602b9ceeafce998d9828efa18a887ab4d5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 13 Feb 2019 10:34:09 -0500 Subject: [PATCH 185/310] fix missing ; --- src/zm_videostore.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 2818bc845..4b3ac4bc8 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -944,13 +944,13 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { in_frame->nb_samples )) #else - (ret = swr_convert_frame(resample_ctx, out_frame, in_frame)) + ret = swr_convert_frame(resample_ctx, out_frame, in_frame); #endif #else #if defined(HAVE_LIBAVRESAMPLE) - (ret = avresample_convert(resample_ctx, NULL, 0, 0, in_frame->data, - 0, in_frame->nb_samples)) + ret = avresample_convert(resample_ctx, NULL, 0, 0, in_frame->data, + 0, in_frame->nb_samples); #endif #endif av_frame_unref(in_frame); From 91a280e56e03bdf0ab865db5386396bdcb26cf03 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 13 Feb 2019 11:17:15 -0500 Subject: [PATCH 186/310] need to include session.php in auth.php --- web/includes/auth.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/includes/auth.php b/web/includes/auth.php index fcc4e94e2..bf8ed99ca 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -17,6 +17,8 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // +// +require_once('session.php'); function userLogin($username='', $password='', $passwordHashed=false) { global $user; From 1aad1f765d795a3eba03d19bef3b2f404f8556e0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 13 Feb 2019 11:28:18 -0500 Subject: [PATCH 187/310] slep for 1 second if there is an error in capturing --- src/zmc.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zmc.cpp b/src/zmc.cpp index 9270c75cd..8bea06f88 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -344,6 +344,7 @@ int main(int argc, char *argv[]) { } if ( result < 0 ) { // Failure, try reconnecting + sleep(1); break; } } // end while ! zm_terminate From 57316aac6ed912458a35783ab947ffd0871c0ced Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 13 Feb 2019 11:29:07 -0500 Subject: [PATCH 188/310] fix missing negation when executing sql... should be scripts/ZoneMinder/lib/ZoneMinder/Database.pmres --- scripts/ZoneMinder/lib/ZoneMinder/Database.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index dcc797bd4..173c0033c 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -208,7 +208,7 @@ sub zmDbGetMonitor { return undef; } my $res = $sth->execute($id); - if ( $res ) { + if ( !$res ) { Error("Can't execute '$sql': ".$sth->errstr()); return undef; } From 2563951c1fbc0e67c34ba8499951cd6c2e97f896 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 13 Feb 2019 11:32:34 -0500 Subject: [PATCH 189/310] comment out the signal blocking. I still can't figure out why we would want it --- src/zmc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zmc.cpp b/src/zmc.cpp index 9270c75cd..88a9807b2 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -276,7 +276,7 @@ int main(int argc, char *argv[]) { struct timeval now; struct DeltaTimeval delta_time; while ( !zm_terminate ) { - sigprocmask(SIG_BLOCK, &block_set, 0); + //sigprocmask(SIG_BLOCK, &block_set, 0); for ( int i = 0; i < n_monitors; i++ ) { long min_delay = MAXINT; @@ -333,7 +333,7 @@ int main(int argc, char *argv[]) { } // end if next_delay <= min_delay || next_delays[i] <= 0 ) } // end foreach n_monitors - sigprocmask(SIG_UNBLOCK, &block_set, 0); + //sigprocmask(SIG_UNBLOCK, &block_set, 0); if ( zm_reload ) { for ( int i = 0; i < n_monitors; i++ ) { monitors[i]->Reload(); From 31a11a69922e883f204340c0beb4a78fc23a7c84 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 13 Feb 2019 11:34:47 -0500 Subject: [PATCH 190/310] init log earlier in zms --- src/zms.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/zms.cpp b/src/zms.cpp index a5fef0134..cf3265fd0 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -85,6 +85,9 @@ int main( int argc, const char *argv[] ) { } zmLoadConfig(); + char log_id_string[32] = "zms"; + logInit( log_id_string ); + Debug(1,"rate %d", rate); const char *query = getenv("QUERY_STRING"); if ( query ) { @@ -95,7 +98,7 @@ int main( int argc, const char *argv[] ) { char *q_ptr = temp_query; char *parms[16]; // Shouldn't be more than this int parm_no = 0; - while( (parm_no < 16) && (parms[parm_no] = strtok(q_ptr, "&")) ) { + while ( (parm_no < 16) && (parms[parm_no] = strtok(q_ptr, "&")) ) { parm_no++; q_ptr = NULL; } @@ -130,6 +133,7 @@ int main( int argc, const char *argv[] ) { scale = atoi( value ); } else if ( !strcmp( name, "rate" ) ) { rate = atoi( value ); + Debug(2,"Setting rate to %d from %s", rate, value); } else if ( !strcmp( name, "maxfps" ) ) { maxfps = atof( value ); } else if ( !strcmp( name, "bitrate" ) ) { @@ -177,9 +181,10 @@ int main( int argc, const char *argv[] ) { } } } // end foreach parm + } else { + Fatal("No query string."); } // end if query - char log_id_string[32] = "zms"; if ( monitor_id ) { snprintf(log_id_string, sizeof(log_id_string), "zms_m%d", monitor_id); } else { From 9e07d5cd17bbe0071568b244fc541308f7e660a6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 14 Feb 2019 15:54:00 -0500 Subject: [PATCH 191/310] try setting out_frame pts from in_frame --- src/zm_videostore.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 4b3ac4bc8..b2281b101 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -953,6 +953,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { 0, in_frame->nb_samples); #endif #endif + out_frame->pts = in_frame->pts; av_frame_unref(in_frame); if ( ret < 0 ) { Error("Could not resample frame (error '%s')", From baba438ce13538e681bede7b096aba44a8ae313a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 16 Feb 2019 11:49:28 -0500 Subject: [PATCH 192/310] use image_count when reporting earlier than fps_report_interval --- src/zm_monitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 72d9c8c2a..b95f569f7 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2481,7 +2481,7 @@ int Monitor::Capture() { // If we are too fast, we get div by zero. This seems to happen in the case of audio packets. if ( now != last_fps_time ) { // # of images per interval / the amount of time it took - double new_fps = double(fps_report_interval)/(now-last_fps_time); + double new_fps = double(image_count%fps_report_interval?image_count:fps_report_interval)/(now-last_fps_time); unsigned int new_camera_bytes = camera->Bytes(); unsigned int new_capture_bandwidth = (new_camera_bytes - last_camera_bytes)/(now-last_fps_time); last_camera_bytes = new_camera_bytes; From c626b0fb8ea4816453fe280277fb5487f8dfccd5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 19 Feb 2019 12:00:17 -0500 Subject: [PATCH 193/310] if adding a closing frame, queue it with the rest of the frames so they get written all at once. Queue 20 frames instead of 10 --- src/zm_event.cpp | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 0e55adc32..876a031d4 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -239,30 +239,19 @@ Event::~Event() { videowriter = NULL; } - if ( frame_data.size() ) - WriteDbFrames(); - - // Should not be static because we are multi-threaded - char sql[ZM_SQL_MED_BUFSIZ]; struct DeltaTimeval delta_time; DELTA_TIMEVAL(delta_time, end_time, start_time, DT_PREC_2); Debug(2, "start_time:%d.%d end_time%d.%d", start_time.tv_sec, start_time.tv_usec, end_time.tv_sec, end_time.tv_usec); 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 ( %" PRIu64 ", %d, from_unixtime( %ld ), %s%ld.%02ld )", - id, frames, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec); - db_mutex.lock(); - if ( mysql_query(&dbconn, sql) ) { - db_mutex.unlock(); - Error("Can't insert frame: %s", mysql_error(&dbconn)); - } else { - db_mutex.unlock(); - Debug(1,"Success writing last frame"); - } + frame_data.push(new Frame(id, frames, NORMAL, end_time, delta_time, 0)); } + if ( frame_data.size() ) + WriteDbFrames(); + // Should not be static because we might be multi-threaded + char sql[ZM_SQL_MED_BUFSIZ]; snprintf(sql, sizeof(sql), "UPDATE Events SET Name='%s %" PRIu64 "', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo = '%s' WHERE Id = %" PRIu64, monitor->EventPrefix(), id, end_time.tv_sec, @@ -534,13 +523,12 @@ void Event::WriteDbFrames() { *(sql_ptr-2) = '\0'; db_mutex.lock(); if ( mysql_query(&dbconn, sql) ) { - Error("Can't insert frames: %s", mysql_error(&dbconn)); - Error("SQL was %s", sql); db_mutex.unlock(); + Error("Can't insert frames: %s, sql was %s", mysql_error(&dbconn), sql); return; } db_mutex.unlock(); -} +} // end void Event::WriteDbFrames() void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image) { if ( !timestamp.tv_sec ) { @@ -590,10 +578,10 @@ Debug(3, "Writing video"); if ( db_frame ) { static char sql[ZM_SQL_MED_BUFSIZ]; - frame_data.push( new Frame(id, frames, frame_type, timestamp, delta_time, score ) ); - if ( frame_data.size() > 10 ) { + frame_data.push(new Frame(id, frames, frame_type, timestamp, delta_time, score)); + if ( frame_data.size() > 20 ) { WriteDbFrames(); - Debug(1, "Adding 10 frames to DB"); + Debug(1, "Adding 20 frames to DB"); last_db_frame = frames; } From 97a888c0db3ad3e73fbd6d74aed1628023d790c2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 19 Feb 2019 12:54:12 -0500 Subject: [PATCH 194/310] get rid of js that just does the form submit. Upgrade the button from an input to a button. Use 0 and 1 instead of accept and decline, which allows us to pre-select the current value of ZM_TELEMETRY_DATA. So that if you had previously declined, you won't accidentally accept. This fixes the reported error that choosing decline would cause the setting to not be saved and the privacy popup to happen again. --- web/includes/actions/privacy.php | 10 +++++----- web/skins/classic/views/js/privacy.js | 10 ---------- web/skins/classic/views/privacy.php | 27 +++++++++++++-------------- 3 files changed, 18 insertions(+), 29 deletions(-) delete mode 100644 web/skins/classic/views/js/privacy.js diff --git a/web/includes/actions/privacy.php b/web/includes/actions/privacy.php index 99bbd7150..f3a805773 100644 --- a/web/includes/actions/privacy.php +++ b/web/includes/actions/privacy.php @@ -19,18 +19,18 @@ // if ( !canEdit('System') ) { - Warning("Need System permissions to update privacy"); + Warning('Need System permissions to update privacy'); return; } -if ( ($action == 'privacy') && isset($_REQUEST['option']) ) { - switch( $_REQUEST['option'] ) { - case 'decline' : +if ( ($action == 'privacy') && isset($_POST['option']) ) { + switch( $_POST['option'] ) { + case '0' : dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_SHOW_PRIVACY'"); dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_TELEMETRY_DATA'"); $redirect = '?view=console'; break; - case 'accept' : + case '1' : dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_SHOW_PRIVACY'"); dbQuery("UPDATE Config SET Value = '1' WHERE Name = 'ZM_TELEMETRY_DATA'"); $redirect = '?view=console'; diff --git a/web/skins/classic/views/js/privacy.js b/web/skins/classic/views/js/privacy.js deleted file mode 100644 index bbf17f864..000000000 --- a/web/skins/classic/views/js/privacy.js +++ /dev/null @@ -1,10 +0,0 @@ -function submitForm( element ) { - var form = element.form; - if ( form.option.selectedIndex == 0 ) { - form.view.value = currentView; - } else { - form.view.value = 'none'; - } - form.submit(); -} - diff --git a/web/skins/classic/views/privacy.php b/web/skins/classic/views/privacy.php index 21c0bdb59..d82e4327f 100644 --- a/web/skins/classic/views/privacy.php +++ b/web/skins/classic/views/privacy.php @@ -18,20 +18,19 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -if ( !canEdit( 'System' ) ) -{ - $view = "error"; - return; +if ( !canEdit('System') ) { + $view = 'error'; + return; } $options = array( - "accept" => translate('Accept'), - "decline" => translate('Decline'), + '1' => translate('Accept'), + '0' => translate('Decline'), ); $focusWindow = true; -xhtmlHeaders(__FILE__, translate('Privacy') ); +xhtmlHeaders(__FILE__, translate('Privacy')); ?>
@@ -41,31 +40,31 @@ xhtmlHeaders(__FILE__, translate('Privacy') );
- +

-
+

-
+

-
+

-
+

-

+

- +
From 9477ebad5d5f2a7839af1e35b2dc0f34abaedc49 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 22 Feb 2019 09:35:42 -0500 Subject: [PATCH 195/310] update to php namespace --- web/includes/session.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/session.php b/web/includes/session.php index 8f9c90e66..7da3ee4fb 100644 --- a/web/includes/session.php +++ b/web/includes/session.php @@ -8,7 +8,7 @@ function zm_session_start() { $currentCookieParams = session_get_cookie_params(); $currentCookieParams['lifetime'] = ZM_COOKIE_LIFETIME; - Logger::Debug('Setting cookie parameters to lifetime('.$currentCookieParams['lifetime'].') path('.$currentCookieParams['path'].') domain ('.$currentCookieParams['domain'].') secure('.$currentCookieParams['secure'].') httpOnly(1)'); + ZM\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'], From 3b045f3958094fa47d1070b5563e54c686b0f85a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 22 Feb 2019 10:48:42 -0500 Subject: [PATCH 196/310] when release is a 1.30, then use the old stable ppa --- utils/do_debian_package.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index a87c03379..b8688d57c 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -124,7 +124,11 @@ PPA=""; if [ "$RELEASE" != "" ]; then # We need to use our official tarball for the original source, so grab it and overwrite our generated one. IFS='.' read -r -a VERSION <<< "$RELEASE" - PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}" + if [ "${VERSION[0]}.${VERSION[1]}" == "1.30" ]; then + PPA="ppa:iconnor/zoneminder-stable" + else + PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}" + fi; else if [ "$BRANCH" == "" ]; then PPA="ppa:iconnor/zoneminder-master"; From a38fcf5297e627cb7a28637d250088c96e25b803 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 26 Feb 2019 11:41:02 -0500 Subject: [PATCH 197/310] Add debug and use alternate way of figuring out how long to sleep to maintain fps --- src/zm_eventstream.cpp | 36 +++++++++++++++++++++++++++++++++--- src/zm_eventstream.h | 1 + 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 4cf8ae8e1..5007204d7 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -336,6 +336,7 @@ void EventStream::processCommand(const CmdMsg *msg) { replay_rate = 50 * ZM_RATE_BASE; break; default : + Debug(1,"Defaulting replay_rate to 2*ZM_RATE_BASE because it is %d", replay_rate); replay_rate = 2 * ZM_RATE_BASE; break; } @@ -490,6 +491,7 @@ void EventStream::processCommand(const CmdMsg *msg) { DataMsg status_msg; status_msg.msg_type = MSG_DATA_EVENT; memcpy(&status_msg.msg_data, &status_data, sizeof(status_data)); + Debug(1,"Size of msg %d", sizeof(status_data)); if ( sendto(sd, &status_msg, sizeof(status_msg), MSG_DONTWAIT, (sockaddr *)&rem_addr, sizeof(rem_addr)) < 0 ) { //if ( errno != EAGAIN ) { @@ -502,7 +504,7 @@ void EventStream::processCommand(const CmdMsg *msg) { exit(0); updateFrameRate((double)event_data->frame_count/event_data->duration); -} +} // void EventStream::processCommand(const CmdMsg *msg) void EventStream::checkEventLoaded() { static char sql[ZM_SQL_SML_BUFSIZ]; @@ -513,6 +515,7 @@ void EventStream::checkEventLoaded() { snprintf(sql, sizeof(sql), "SELECT Id FROM Events WHERE MonitorId = %ld AND Id > %" PRIu64 " ORDER BY Id ASC LIMIT 1", event_data->monitor_id, event_data->event_id); } else { // No event change required + //Debug(3, "No event change required"); return; } @@ -769,13 +772,16 @@ void EventStream::runStream() { unsigned int delta_us = 0; send_frame = false; + //Debug(3,"Before checking command queue"); // commands may set send_frame to true while ( checkCommandQueue() && !zm_terminate ) { // The idea is to loop here processing all commands before proceeding. } + ////Debug(3,"After checking command queue"); // Update modified time of the socket .lock file so that we can tell which ones are stale. if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) { + //Debug(3,"Touching sock path"); touch(sock_path_lock); last_comm_update = now; } @@ -793,6 +799,7 @@ void EventStream::runStream() { //Info( "cfid:%d", curr_frame_id ); //Info( "fdt:%d", frame_data->timestamp ); if ( !paused ) { + Debug(3,"Not paused"); bool in_event = true; double time_to_event = 0; if ( replay_rate > 0 ) { @@ -815,6 +822,7 @@ void EventStream::runStream() { } //else //{ + Debug(2,"Sleeping because paused"); usleep(STREAM_PAUSE_WAIT); //curr_stream_time += (replay_rate>0?1:-1) * ((1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000)); curr_stream_time += (1.0L * replay_rate * STREAM_PAUSE_WAIT)/(ZM_RATE_BASE * 1000000); @@ -834,6 +842,7 @@ void EventStream::runStream() { send_frame = true; } } else if ( step != 0 ) { + Debug(2,"Paused with step"); // We are paused and are just stepping forward or backward one frame step = 0; send_frame = true; @@ -844,12 +853,18 @@ void EventStream::runStream() { // Send keepalive Debug(2, "Sending keepalive frame"); send_frame = true; + //} else { + //Debug(2, "Not Sending keepalive frame"); } } // end if streaming stepping or doing nothing - if ( send_frame ) + if ( send_frame ) { + //Debug(3,"sending frame"); if ( !sendFrame(delta_us) ) zm_terminate = true; + //} else { + //Debug(3,"Not sending frame"); + } curr_stream_time = frame_data->timestamp; @@ -861,11 +876,26 @@ void EventStream::runStream() { } if ( send_frame && type != STREAM_MPEG ) { Debug(3, "dUs: %d", delta_us); - if ( delta_us ) + if ( delta_us ) { usleep(delta_us); + Debug(3, "Done sleeping: %d usec", delta_us); + } } } else { + delta_us = ((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*(replay_rate?abs(replay_rate*2):2))); + + Debug(2,"Sleeping %d because 1000000 * ZM_RATE_BASE(%d) / ( base_fps (%f), replay_rate(%d)", + (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))), + ZM_RATE_BASE, + (base_fps?base_fps:1), + (replay_rate?abs(replay_rate*2):200) + ); + if ( delta_us > 0 and delta_us < 100000 ) { usleep((unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2)))); + } else { + //Error("Not sleeping!"); + usleep(100000); + } } } // end while ! zm_terminate #if HAVE_LIBAVCODEC diff --git a/src/zm_eventstream.h b/src/zm_eventstream.h index a6ca99aee..dc1b60dea 100644 --- a/src/zm_eventstream.h +++ b/src/zm_eventstream.h @@ -95,6 +95,7 @@ class EventStream : public StreamBase { public: EventStream() { mode = DEFAULT_MODE; + replay_rate = DEFAULT_RATE; forceEventChange = false; From 1828ea2d6ffc25d3a398b7fefd4db5e0a27fe8bf Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 1 Mar 2019 10:26:55 -0500 Subject: [PATCH 198/310] small cleanup of zmstats --- scripts/zmstats.pl.in | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/scripts/zmstats.pl.in b/scripts/zmstats.pl.in index 410459158..ba6d8302a 100644 --- a/scripts/zmstats.pl.in +++ b/scripts/zmstats.pl.in @@ -18,9 +18,7 @@ use constant START_DELAY => 30; # To give everything else time to start @EXTRA_PERL_LIB@ use ZoneMinder; -use POSIX; use DBI; -use autouse 'Data::Dumper'=>qw(Dumper); $| = 1; @@ -31,15 +29,15 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); logSetSignal(); -Info("Stats Daemon starting in ".START_DELAY." seconds"); +Info('Stats Daemon starting in '.START_DELAY.' seconds'); sleep(START_DELAY); my $dbh = zmDbConnect(); while( 1 ) { while ( ! ( $dbh and $dbh->ping() ) ) { - Info("Reconnecting to db"); - if ( ! ( $dbh = zmDbConnect() ) ) { + Info('Reconnecting to db'); + if ( !($dbh = zmDbConnect()) ) { #What we do here is not that important, so just skip this interval sleep($Config{ZM_STATS_UPDATE_INTERVAL}); } @@ -53,7 +51,7 @@ while( 1 ) { sleep($Config{ZM_STATS_UPDATE_INTERVAL}); } # end while (1) -Info("Stats Daemon exiting"); +Info('Stats Daemon exiting'); exit(); 1; __END__ From 016fda22ce865df9fc36d56dc1b0256daa5e68b7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 1 Mar 2019 12:31:41 -0500 Subject: [PATCH 199/310] reduce duplicated db code from Config. Use fully namespace'd variables so we can use require instead of use --- .../ZoneMinder/lib/ZoneMinder/Config.pm.in | 93 ++++++++----------- .../lib/ZoneMinder/ConfigData.pm.in | 2 +- scripts/ZoneMinder/lib/ZoneMinder/Database.pm | 23 ++--- scripts/ZoneMinder/lib/ZoneMinder/Logger.pm | 30 +++--- 4 files changed, 65 insertions(+), 83 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index 3fdeb722c..10e4b4733 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -30,6 +30,7 @@ use warnings; require Exporter; require ZoneMinder::Base; + use ZoneMinder::ConfigData qw(:all); our @ISA = qw(Exporter ZoneMinder::Base); @@ -57,20 +58,22 @@ our %EXPORT_TAGS = ( push( @{$EXPORT_TAGS{config}}, @EXPORT_CONFIG ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); +our @EXPORT_OK = @{ $EXPORT_TAGS{all} }; our @EXPORT = qw(); our $VERSION = $ZoneMinder::Base::VERSION; -use constant ZM_PID => "@ZM_PID@"; # Path to the ZoneMinder run pid file -use constant ZM_CONFIG => "@ZM_CONFIG@"; # Path to the ZoneMinder config file -use constant ZM_CONFIG_SUBDIR => "@ZM_CONFIG_SUBDIR@"; # Path to the ZoneMinder config subfolder +use constant ZM_PID => '@ZM_PID@'; # Path to the ZoneMinder run pid file +use constant ZM_CONFIG => '@ZM_CONFIG@'; # Path to the ZoneMinder config file +use constant ZM_CONFIG_SUBDIR => '@ZM_CONFIG_SUBDIR@'; # Path to the ZoneMinder config subfolder use Carp; +require ZoneMinder::Database; # Load the config from the database into the symbol table BEGIN { +require ZoneMinder::Database; # Process name, value pairs from the main config file first my $config_file = ZM_CONFIG; @@ -78,52 +81,25 @@ BEGIN { # Search for user created config files. If one or more are found then # update the Config hash with those values - if ( -d ZM_CONFIG_SUBDIR ) { + if ( ZM_CONFIG_SUBDIR and -d ZM_CONFIG_SUBDIR ) { if ( -R ZM_CONFIG_SUBDIR ) { - foreach my $filename ( glob ZM_CONFIG_SUBDIR."/*.conf" ) { + foreach my $filename ( glob ZM_CONFIG_SUBDIR.'/*.conf' ) { process_configfile($filename); } } else { - print( STDERR "WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on ".ZM_CONFIG_SUBDIR.".\n" ); + print( STDERR 'WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on '.ZM_CONFIG_SUBDIR.".\n" ); } } - use DBI; - my $socket; - my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); - - if ( defined($portOrSocket) ) { - if ( $portOrSocket =~ /^\// ) { - $socket = ';mysql_socket='.$portOrSocket; - } else { - $socket = ';host='.$host.';port='.$portOrSocket; - } - } else { - $socket = ';host='.$Config{ZM_DB_HOST}; - } - my $sslOptions = ''; - if ( $Config{ZM_DB_SSL_CA_CERT} ) { - $sslOptions = ';'.join(';', - "mysql_ssl=1", - "mysql_ssl_ca_file=".$Config{ZM_DB_SSL_CA_CERT}, - "mysql_ssl_client_key=".$Config{ZM_DB_SSL_CLIENT_KEY}, - "mysql_ssl_client_cert=".$Config{ZM_DB_SSL_CLIENT_CERT} - ); - } - my $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} - .$socket.$sslOptions - , $Config{ZM_DB_USER} - , $Config{ZM_DB_PASS} - ) or croak( "Can't connect to db" ); + my $dbh = ZoneMinder::Database::zmDbConnect(); 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() ); + 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() ) { $Config{$config->{Name}} = $config->{Value}; } $sth->finish(); -#$dbh->disconnect(); - # + if ( ! $Config{ZM_SERVER_ID} ) { $Config{ZM_SERVER_ID} = undef; $sth = $dbh->prepare_cached( 'SELECT * FROM Servers WHERE Name=?' ); @@ -143,25 +119,30 @@ BEGIN { sub process_configfile { my $config_file = shift; - if ( -R $config_file ) { - open( my $CONFIG, '<', $config_file ) - or croak( "Can't open config file '$config_file': $!" ); - foreach my $str ( <$CONFIG> ) { - next if ( $str =~ /^\s*$/ ); - next if ( $str =~ /^\s*#/ ); - my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/; - if ( ! $name ) { - print( STDERR "Warning, bad line in $config_file: $str\n" ); - next; - } # end if - $name =~ tr/a-z/A-Z/; - $Config{$name} = $value; - } - close( $CONFIG ); - } else { - print( STDERR "WARNING: ZoneMinder configuration file found but is not readable. Check file permissions on $config_file\n" ); + if ( ! -R $config_file ) { + print(STDERR "WARNING: ZoneMinder configuration file found but is not readable. Check file permissions on $config_file\n"); + return; } - } + open( my $CONFIG, '<', $config_file ) + or croak("Can't open config file '$config_file': $!"); + foreach my $str ( <$CONFIG> ) { + next if ( $str =~ /^\s*$/ ); + next if ( $str =~ /^\s*#/ ); + my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/; + if ( !$name ) { + print(STDERR "Warning, bad line in $config_file: $str\n"); + next; + } # end if + $name =~ tr/a-z/A-Z/; + #if ( !$ZoneMinder::ConfigData::options_hash{$name} ) { + #print(STDERR "Warning, unknown config option name $name in $config_file\n"); +#} else { + #print(STDERR "Warning, known config option name $name in $config_file\n"); + #} + $Config{$name} = $value; + } # end foreach config line + close($CONFIG); + } # end sub process_configfile } # end BEGIN diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index cd53d6754..c6ec697f1 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -3952,7 +3952,7 @@ our %options_hash = map { ( $_->{name}, $_ ) } @options; # This function should never need to be called explicitly, except if # this module is 'require'd rather than 'use'd. See zmconfgen.pl. sub initialiseConfig { - return if ( $configInitialised ); + return if $configInitialised; # Do some initial data munging to finish the data structures # Create option ids diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index 173c0033c..5c6617510 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -65,7 +65,8 @@ our $VERSION = $ZoneMinder::Base::VERSION; # ========================================================================== use ZoneMinder::Logger qw(:all); -use ZoneMinder::Config qw(:all); + +require ZoneMinder::Config; our $dbh = undef; @@ -87,25 +88,25 @@ sub zmDbConnect { $socket = ';host='.$host.';port='.$portOrSocket; } } else { - $socket = ';host='.$Config{ZM_DB_HOST}; + $socket = ';host='.$ZoneMinder::Config::Config{ZM_DB_HOST}; } my $sslOptions = ''; - if ( $Config{ZM_DB_SSL_CA_CERT} ) { - $sslOptions = join(';','', + if ( $ZoneMinder::Config::Config{ZM_DB_SSL_CA_CERT} ) { + $sslOptions = join(';', '', 'mysql_ssl=1', - 'mysql_ssl_ca_file='.$Config{ZM_DB_SSL_CA_CERT}, - 'mysql_ssl_client_key='.$Config{ZM_DB_SSL_CLIENT_KEY}, - 'mysql_ssl_client_cert='.$Config{ZM_DB_SSL_CLIENT_CERT} + 'mysql_ssl_ca_file='.$ZoneMinder::Config::Config{ZM_DB_SSL_CA_CERT}, + 'mysql_ssl_client_key='.$ZoneMinder::Config::Config{ZM_DB_SSL_CLIENT_KEY}, + 'mysql_ssl_client_cert='.$ZoneMinder::Config::Config{ZM_DB_SSL_CLIENT_CERT} ); } eval { $dbh = DBI->connect( - 'DBI:mysql:database='.$Config{ZM_DB_NAME} + 'DBI:mysql:database='.$ZoneMinder::Config::Config{ZM_DB_NAME} .$socket . $sslOptions . ($options?join(';', '', map { $_.'='.$$options{$_} } keys %{$options} ) : '') - , $Config{ZM_DB_USER} - , $Config{ZM_DB_PASS} + , $ZoneMinder::Config::Config{ZM_DB_USER} + , $ZoneMinder::Config::Config{ZM_DB_PASS} ); }; if ( !$dbh or $@ ) { @@ -124,7 +125,7 @@ sub zmDbConnect { } # end sub zmDbConnect sub zmDbDisconnect { - if ( defined( $dbh ) ) { + if ( defined($dbh) ) { $dbh->disconnect() or Error('Error disconnecting db? ' . $dbh->errstr()); $dbh = undef; } diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 8d18ade09..117f2a1ab 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -87,7 +87,7 @@ our $VERSION = $ZoneMinder::Base::VERSION; # # ========================================================================== -use ZoneMinder::Config qw(:all); +require ZoneMinder::Config; use DBI; use Carp; @@ -156,7 +156,7 @@ sub new { $this->{autoFlush} = 1; ( $this->{fileName} = $0 ) =~ s|^.*/||; - $this->{logPath} = $Config{ZM_PATH_LOGS}; + $this->{logPath} = $ZoneMinder::Config::Config{ZM_PATH_LOGS}; $this->{logFile} = $this->{logPath}.'/'.$this->{id}.'.log'; ($this->{logFile}) = $this->{logFile} =~ /^([\w\.\/]+)$/; @@ -169,7 +169,7 @@ sub new { sub BEGIN { # Fake the config variables that are used in case they are not defined yet # Only really necessary to support upgrade from previous version - if ( !eval('defined($Config{ZM_LOG_DEBUG})') ) { + if ( !eval('defined($ZoneMinder::Config::Config{ZM_LOG_DEBUG})') ) { no strict 'subs'; no strict 'refs'; my %dbgConfig = ( @@ -221,17 +221,17 @@ sub initialise( @ ) { if ( defined($options{databaseLevel}) ) { $tempDatabaseLevel = $options{databaseLevel}; } else { - $tempDatabaseLevel = $Config{ZM_LOG_LEVEL_DATABASE}; + $tempDatabaseLevel = $ZoneMinder::Config::Config{ZM_LOG_LEVEL_DATABASE}; } if ( defined($options{fileLevel}) ) { $tempFileLevel = $options{fileLevel}; } else { - $tempFileLevel = $Config{ZM_LOG_LEVEL_FILE}; + $tempFileLevel = $ZoneMinder::Config::Config{ZM_LOG_LEVEL_FILE}; } if ( defined($options{syslogLevel}) ) { $tempSyslogLevel = $options{syslogLevel}; } else { - $tempSyslogLevel = $Config{ZM_LOG_LEVEL_SYSLOG}; + $tempSyslogLevel = $ZoneMinder::Config::Config{ZM_LOG_LEVEL_SYSLOG}; } if ( defined($ENV{LOG_PRINT}) ) { @@ -245,19 +245,19 @@ sub initialise( @ ) { $tempFileLevel = $level if defined($level = $this->getTargettedEnv('LOG_LEVEL_FILE')); $tempSyslogLevel = $level if defined($level = $this->getTargettedEnv('LOG_LEVEL_SYSLOG')); - if ( $Config{ZM_LOG_DEBUG} ) { + if ( $ZoneMinder::Config::Config{ZM_LOG_DEBUG} ) { # Splitting on an empty string doesn't return an empty string, it returns an empty array - foreach my $target ( $Config{ZM_LOG_DEBUG_TARGET} ? split(/\|/, $Config{ZM_LOG_DEBUG_TARGET}) : '' ) { + foreach my $target ( $ZoneMinder::Config::Config{ZM_LOG_DEBUG_TARGET} ? split(/\|/, $ZoneMinder::Config::Config{ZM_LOG_DEBUG_TARGET}) : '' ) { if ( $target eq $this->{id} || $target eq '_'.$this->{id} || $target eq $this->{idRoot} || $target eq '_'.$this->{idRoot} || $target eq '' ) { - if ( $Config{ZM_LOG_DEBUG_LEVEL} > NOLOG ) { - $tempLevel = $this->limit( $Config{ZM_LOG_DEBUG_LEVEL} ); - if ( $Config{ZM_LOG_DEBUG_FILE} ne '' ) { - $tempLogFile = $Config{ZM_LOG_DEBUG_FILE}; + if ( $ZoneMinder::Config::Config{ZM_LOG_DEBUG_LEVEL} > NOLOG ) { + $tempLevel = $this->limit( $ZoneMinder::Config::Config{ZM_LOG_DEBUG_LEVEL} ); + if ( $ZoneMinder::Config::Config{ZM_LOG_DEBUG_FILE} ne '' ) { + $tempLogFile = $ZoneMinder::Config::Config{ZM_LOG_DEBUG_FILE}; $tempFileLevel = $tempLevel; } } @@ -501,8 +501,8 @@ sub openFile { if ( open($LOGFILE, '>>', $this->{logFile}) ) { $LOGFILE->autoflush() if $this->{autoFlush}; - my $webUid = (getpwnam($Config{ZM_WEB_USER}))[2]; - my $webGid = (getgrnam($Config{ZM_WEB_GROUP}))[2]; + my $webUid = (getpwnam($ZoneMinder::Config::Config{ZM_WEB_USER}))[2]; + my $webGid = (getgrnam($ZoneMinder::Config::Config{ZM_WEB_GROUP}))[2]; if ( $> == 0 ) { chown( $webUid, $webGid, $this->{logFile} ) or Fatal("Can't change permissions on log file $$this{logFile}: $!"); @@ -577,7 +577,7 @@ sub logPrint { my $res = $this->{sth}->execute( $seconds+($microseconds/1000000.0), $this->{id}, - ($Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : undef), + ($ZoneMinder::Config::Config{ZM_SERVER_ID} ? $ZoneMinder::Config::Config{ZM_SERVER_ID} : undef), $$, $level, $codes{$level}, From abfb5642c8c39decb2090445ba7c19a039d72fa3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 6 Mar 2019 09:46:28 -0500 Subject: [PATCH 200/310] cleanup and document updateFrameRate --- src/zm_stream.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index bff550a77..335091946 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -70,14 +70,16 @@ void StreamBase::updateFrameRate(double fps) { } base_fps = fps; effective_fps = (base_fps*abs(replay_rate))/ZM_RATE_BASE; - Debug(3, "FPS:%.2f, MXFPS:%.2f, BFPS:%.2f, EFPS:%.2f, FM:%d", fps, maxfps, base_fps, effective_fps, frame_mod); + Debug(3, "FPS:%.2f, MXFPS:%.2f, BFPS:%.2f, EFPS:%.2f, FM:%d", + fps, maxfps, base_fps, effective_fps, frame_mod); // Min frame repeat? - while( effective_fps > maxfps ) { + // We want to keep the frame skip easy... problem is ... if effective = 31 and max = 30 then we end up with 15.5 fps. + while ( effective_fps > maxfps ) { effective_fps /= 2.0; frame_mod *= 2; Debug(3, "EffectiveFPS:%.2f, FrameMod:%d", effective_fps, frame_mod); } -} +} // void StreamBase::updateFrameRate(double fps) bool StreamBase::checkCommandQueue() { if ( sd >= 0 ) { From 9b713a489d2696a7578272823d7b53a2ed1ed3a2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 6 Mar 2019 14:50:36 -0500 Subject: [PATCH 201/310] fix sleep time by using a comparison between the frame display time and the distance between now and when we started playing. handle infinite fps --- src/zm_eventstream.cpp | 48 +++++++++++++++++++++++++---------------- src/zm_ffmpeg_input.cpp | 40 ++++++++++++++++++---------------- src/zm_stream.cpp | 2 +- src/zm_videostore.cpp | 9 -------- 4 files changed, 51 insertions(+), 48 deletions(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 03996e49c..d46c9b385 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -228,7 +228,7 @@ bool EventStream::loadEventData(uint64_t event_id) { //timestamp = atof(dbrow[1]); double delta = atof(dbrow[2]); int id_diff = id - last_id; - double frame_delta = id_diff ? (delta-last_delta)/id_diff : (delta - last_delta); + double frame_delta = id_diff ? (delta-last_delta)/id_diff : (delta-last_delta); // Fill in data between bulk frames if ( id_diff > 1 ) { for ( int i = last_id+1; i < id; i++ ) { @@ -262,8 +262,8 @@ bool EventStream::loadEventData(uint64_t event_id) { ); } if ( mysql_errno( &dbconn ) ) { - Error( "Can't fetch row: %s", mysql_error( &dbconn ) ); - exit( mysql_errno( &dbconn ) ); + Error("Can't fetch row: %s", mysql_error(&dbconn)); + exit(mysql_errno(&dbconn)); } mysql_free_result(result); @@ -676,10 +676,6 @@ Debug(1, "Loading image"); // Get the frame from the mp4 input Debug(1,"Getting frame from ffmpeg"); AVFrame *frame; - if ( curr_frame_id == 1 ) { - // Special case, first frame, we want to send the initial keyframe. - frame = ffmpeg_input->get_frame( ffmpeg_input->get_video_stream_id(), 0 ); - } FrameData *frame_data = &event_data->frames[curr_frame_id-1]; frame = ffmpeg_input->get_frame( ffmpeg_input->get_video_stream_id(), frame_data->offset ); if ( frame ) { @@ -790,7 +786,9 @@ void EventStream::runStream() { Debug(3, "frame rate is: (%f)", (double)event_data->frame_count/event_data->duration); updateFrameRate((double)event_data->frame_count/event_data->duration); - gettimeofday(&start, NULL); + gettimeofday(&start, NULL); + uint64_t start_usec = start.tv_sec * 1000000 + start.tv_usec; + uint64_t last_frame_offset = 0; while ( !zm_terminate ) { gettimeofday(&now, NULL); @@ -868,7 +866,7 @@ Debug(3,"cur_frame_id (%d-1) mod frame_mod(%d)",curr_frame_id, frame_mod); Debug(3,"delta %u = base_fps(%f)/effective fps(%f)", delta_us, base_fps, effective_fps); // but must not exceed maxfps delta_us = max(delta_us, 1000000 / maxfps); - Debug(3,"delta %u = base_fps(%f)/effective fps(%f)", delta_us, base_fps, effective_fps); + Debug(3,"delta %u = base_fps(%f)/effective fps(%f) from 30fps", delta_us, base_fps, effective_fps); send_frame = true; } } else if ( step != 0 ) { @@ -896,13 +894,11 @@ Debug(3,"cur_frame_id (%d-1) mod frame_mod(%d)",curr_frame_id, frame_mod); //Debug(3,"Not sending frame"); } - curr_stream_time = frame_data->timestamp; if ( !paused ) { - curr_frame_id += (replay_rate>0) ? 1 : -1; - - + // +/- 1? What if we are skipping frames? + curr_frame_id += (replay_rate>0) ? frame_mod : -1*frame_mod; if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) { Debug(2, "Have mode==MODE_SINGLE and at end of event, looping back to start"); @@ -910,14 +906,28 @@ Debug(3,"cur_frame_id (%d-1) mod frame_mod(%d)",curr_frame_id, frame_mod); } frame_data = &event_data->frames[curr_frame_id-1]; + // sending the frame may have taken some time, so reload now + gettimeofday(&now, NULL); uint64_t now_usec = (now.tv_sec * 1000000 + now.tv_usec); - uint64_t start_usec = (start.tv_sec * 1000000 + start.tv_usec); - uint64_t frame_delta = frame_data->delta*1000000; + // frame_data->delta is the time since last frame as a float in seconds + // but what if we are skipping frames? We need the distance from the last frame sent + // Also, what about reverse? needs to be absolute value - delta_us = frame_delta - (now_usec - start_usec); - Debug(2, "New delta_us now %" PRIu64 " - start %" PRIu64 " = %d - frame %" PRIu64 " = %d", - now_usec, start_usec, now_usec-start_usec, frame_delta, - delta_us); + // There are two ways to go about this, not sure which is correct. + // you can calculate the relationship between now and the start + // or calc the relationship from the last frame. I think from the start is better as it self-corrects + + if ( last_frame_offset ) { + // We assume that we are going forward and the next frame is in the future. + delta_us = frame_data->offset * 1000000 - (now_usec-start_usec); + // - (now_usec - start_usec); + Debug(2, "New delta_us now %" PRIu64 " - start %" PRIu64 " = %d offset %" PRId64 " - elapsed = %dusec", + now_usec, start_usec, now_usec-start_usec, frame_data->offset * 1000000, delta_us); + } else { + Debug(2, "No last frame_offset, no sleep"); + delta_us = 0; + } + last_frame_offset = frame_data->offset * 1000000; if ( send_frame && type != STREAM_MPEG ) { if ( delta_us > 0 ) { diff --git a/src/zm_ffmpeg_input.cpp b/src/zm_ffmpeg_input.cpp index 9f0336bbf..d9c7c2fd3 100644 --- a/src/zm_ffmpeg_input.cpp +++ b/src/zm_ffmpeg_input.cpp @@ -110,7 +110,7 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) { char errbuf[AV_ERROR_MAX_STRING_SIZE]; while ( !frameComplete ) { - int ret = av_read_frame( input_format_context, &packet ); + int ret = av_read_frame(input_format_context, &packet); if ( ret < 0 ) { av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); if ( @@ -135,9 +135,10 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) { #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 ); + 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; } @@ -159,12 +160,12 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) { } } else { #endif - if ( frame ) { - av_frame_free(&frame); - frame = zm_av_frame_alloc(); - } else { - frame = zm_av_frame_alloc(); - } + if ( frame ) { + av_frame_free(&frame); + frame = zm_av_frame_alloc(); + } else { + frame = zm_av_frame_alloc(); + } //Debug(1,"Getting frame %d", streams[packet.stream_index].frame_count); ret = avcodec_receive_frame(context, frame); if ( ret < 0 ) { @@ -181,12 +182,12 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) { frameComplete = 1; # else - if ( frame ) { - av_frame_free(&frame); - frame = zm_av_frame_alloc(); - } else { - frame = zm_av_frame_alloc(); - } + if ( frame ) { + av_frame_free(&frame); + frame = zm_av_frame_alloc(); + } else { + frame = zm_av_frame_alloc(); + } ret = zm_avcodec_decode_video(context, frame, &frameComplete, &packet); if ( ret < 0 ) { av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE); @@ -198,7 +199,7 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id ) { #endif } // end if it's the right stream - zm_av_packet_unref( &packet ); + zm_av_packet_unref(&packet); } // end while ! frameComplete return frame; @@ -220,7 +221,7 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id, double at ) { return frame; } if ( frame->pts < seek_target ) { - Debug(2, "Frame pts %" PRId64 " pkt_pts %" PRId64 " duration %" PRId64, frame->pts, frame->pkt_pts, frame->pkt_duration); + Debug(2, "Frame pts %" PRId64 " duration %" PRId64, frame->pts, frame->pkt_duration); while ( frame && (frame->pts < seek_target) ) { if ( ! get_frame(stream_id) ) return frame; @@ -237,9 +238,10 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id, double at ) { } } else { + // No previous frame... are we asking for first frame? // Must go for a keyframe if ( ( ret = av_seek_frame(input_format_context, stream_id, seek_target, - AVSEEK_FLAG_FRAME + AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME ) < 0 ) ) { Error("Unable to seek in stream"); return NULL; diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index b34773ea5..20d4f1632 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -62,7 +62,7 @@ bool StreamBase::checkInitialised() { void StreamBase::updateFrameRate(double fps) { frame_mod = 1; - if ( (fps < 0) || !fps ) { + if ( (fps < 0) || !fps || isinf(fps) ) { Debug(1, "Zero or negative fps %f in updateFrameRate. Setting frame_mod=1 and effective_fps=0.0", fps); effective_fps = 0.0; base_fps = 0.0; diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 30a482f75..849c9575f 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -979,13 +979,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { // decoded data Debug(2, "Converting %d to %d samples", in_frame->nb_samples, out_frame->nb_samples); #if defined(HAVE_LIBSWRESAMPLE) -#if 0 - ret = swr_convert(resample_ctx, - out_frame->data, frame_size, - (const uint8_t**)in_frame->data, - in_frame->nb_samples - ); -#else ret = swr_convert_frame(resample_ctx, out_frame, in_frame); av_frame_unref(in_frame); if ( ret < 0 ) { @@ -993,7 +986,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { av_make_error_string(ret).c_str()); return 0; } -#endif if ((ret = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + out_frame->nb_samples)) < 0) { Error("Could not reallocate FIFO"); return 0; @@ -1031,7 +1023,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { return 0; } - #if defined(HAVE_LIBAVRESAMPLE) int samples_available = avresample_available(resample_ctx); if ( samples_available < frame_size ) { Debug(1, "Not enough samples yet (%d)", samples_available); From d737e81e216671e03d2cea18d7d30f2b265c605f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 6 Mar 2019 14:57:05 -0500 Subject: [PATCH 202/310] bump version for DefaultCodec in monitors --- db/zm_update-1.33.4.sql | 12 ++++++++++++ version | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 db/zm_update-1.33.4.sql diff --git a/db/zm_update-1.33.4.sql b/db/zm_update-1.33.4.sql new file mode 100644 index 000000000..cffa35aa5 --- /dev/null +++ b/db/zm_update-1.33.4.sql @@ -0,0 +1,12 @@ + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'DefaultCodec' + ) > 0, + "SELECT 'Column DefaultCodec already exists in Monitors'", + "ALTER TABLE Monitors ADD `DefaultCodec` enum('auto','H264','H265','MJPEG') NOT NULL default 'auto', AFTER `DefaultScale`" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/version b/version index c7f962f33..9f406e0c8 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.33.3 +1.33.4 From e6220e9d07cac046b39fd00c17125f1eb1075cde Mon Sep 17 00:00:00 2001 From: Matthew Noorenberghe Date: Sun, 10 Mar 2019 20:56:08 -0700 Subject: [PATCH 203/310] Fix eslint issues in cycle.js --- web/skins/classic/views/js/cycle.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/web/skins/classic/views/js/cycle.js b/web/skins/classic/views/js/cycle.js index 9422890c8..4e6de5320 100644 --- a/web/skins/classic/views/js/cycle.js +++ b/web/skins/classic/views/js/cycle.js @@ -26,10 +26,11 @@ function cycleNext() { window.location.replace('?view=cycle&mid='+monitorData[monIdx].id+'&mode='+mode, cycleRefreshTimeout); } function cyclePrev() { - if ( monIdx ) + if (monIdx) { monIdx -= 1; - else - monIdx = monitorData.length-1; + } else { + monIdx = monitorData.length - 1; + } window.location.replace('?view=cycle&mid='+monitorData[monIdx].id+'&mode='+mode, cycleRefreshTimeout); } @@ -82,9 +83,9 @@ function changeScale() { var scale = $('scale').get('value'); $('width').set('value', 'auto'); $('height').set('value', 'auto'); - Cookie.write('zmCycleScale', scale, { duration: 10*365 }); - Cookie.write('zmCycleWidth', 'auto', { duration: 10*365 }); - Cookie.write('zmCycleHeight', 'auto', { duration: 10*365 }); + Cookie.write('zmCycleScale', scale, {duration: 10*365}); + Cookie.write('zmCycleWidth', 'auto', {duration: 10*365}); + Cookie.write('zmCycleHeight', 'auto', {duration: 10*365}); var newWidth = ( monitorData[monIdx].width * scale ) / SCALE_BASE; var newHeight = ( monitorData[monIdx].height * scale ) / SCALE_BASE; From ac547e0d5d1da2d9935af59ce23681567a367c3b Mon Sep 17 00:00:00 2001 From: Matthew Noorenberghe Date: Sun, 10 Mar 2019 20:52:28 -0700 Subject: [PATCH 204/310] Don't scroll to the top of the page when force/cancel alarm is clicked --- web/skins/classic/views/js/watch.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/skins/classic/views/js/watch.js b/web/skins/classic/views/js/watch.js index a0594825b..a30a05fee 100644 --- a/web/skins/classic/views/js/watch.js +++ b/web/skins/classic/views/js/watch.js @@ -448,10 +448,16 @@ function cmdEnableAlarms() { function cmdForceAlarm() { alarmCmdReq.send(alarmCmdParms+"&command=forceAlarm"); + if (window.event) { + window.event.preventDefault(); + } } function cmdCancelForcedAlarm() { alarmCmdReq.send(alarmCmdParms+"&command=cancelForcedAlarm"); + if (window.event) { + window.event.preventDefault(); + } return false; } From 056b96f7fcb1a4747e015a9b6193636c0c8f5c5f Mon Sep 17 00:00:00 2001 From: Matthew Noorenberghe Date: Sun, 10 Feb 2019 21:27:57 -0800 Subject: [PATCH 205/310] API: Monitor and Event 'index' SQLi. Fixes #2099 --- .../Controller/Component/FilterComponent.php | 84 +++++++++++++++++-- web/api/app/Controller/EventsController.php | 5 +- web/api/app/Controller/MonitorsController.php | 7 +- 3 files changed, 86 insertions(+), 10 deletions(-) diff --git a/web/api/app/Controller/Component/FilterComponent.php b/web/api/app/Controller/Component/FilterComponent.php index 326c1ce45..798452793 100644 --- a/web/api/app/Controller/Component/FilterComponent.php +++ b/web/api/app/Controller/Component/FilterComponent.php @@ -1,14 +1,88 @@ ', + '>', + '>=', + 'IS', + 'IS NOT', + //'IS NOT NULL', + //'IS NULL', + //'->', + //'->>', + '<<', + '<', + '<=', + 'LIKE', + '-', + '%', + 'MOD', + //'NOT', + //'!', + //'NOT BETWEEN ... AND ...', + '!=', + '<>', + 'NOT LIKE', + 'NOT REGEXP', + // `or` operators aren't safe as they can + // be used to skip an existing condition + // enforcing access to only certain + // monitors/events. + //'||', + //'OR', + '+', + 'REGEXP', + '>>', + 'RLIKE', + 'SOUNDS LIKE', + //'*', + '-', + //'XOR', + ); + // Build a CakePHP find() condition based on the named parameters // that are passed in public function buildFilter($namedParams) { - if ($namedParams) { - $conditions = array(); - + $conditions = array(); + if ($namedParams) { foreach ($namedParams as $attribute => $value) { + // We need to sanitize $attribute to avoid SQL injection. + $lhs = trim($attribute); + $matches = NULL; + if (preg_match('/^(?P[a-z0-9]+)(?P.+)?$/i', $lhs, $matches) !== 1) { + throw new Exception('Invalid argument before `:`: ' . $lhs); + } + $operator = trim($matches['operator']); + + // Only allow operators on our allow list. No operator + // specified defaults to `=` by cakePHP. + if ($operator != '' && !in_array($operator, $this->twoOperandSQLOperands)) { + throw new Exception('Invalid operator: ' . $operator); + } + + $lhs = '`' . $matches['field'] . '` ' . $operator; // If the named param contains an array, we want to turn it into an IN condition // Otherwise, we add it right into the $conditions array if (is_array($value)) { @@ -18,10 +92,10 @@ class FilterComponent extends Component { array_push($array, $term); } - $query = array($attribute => $array); + $query = array($lhs => $array); array_push($conditions, $query); } else { - $conditions[$attribute] = $value; + $conditions[$lhs] = $value; } } diff --git a/web/api/app/Controller/EventsController.php b/web/api/app/Controller/EventsController.php index cf8ca8f37..4f706bec8 100644 --- a/web/api/app/Controller/EventsController.php +++ b/web/api/app/Controller/EventsController.php @@ -44,9 +44,8 @@ class EventsController extends AppController { } if ( $this->request->params['named'] ) { - //$this->FilterComponent = $this->Components->load('Filter'); - //$conditions = $this->FilterComponent->buildFilter($this->request->params['named']); - $conditions = $this->request->params['named']; + $this->FilterComponent = $this->Components->load('Filter'); + $conditions = $this->FilterComponent->buildFilter($this->request->params['named']); } else { $conditions = array(); } diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 185a06c84..fd0d1e94a 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -40,8 +40,7 @@ class MonitorsController extends AppController { if ( $this->request->params['named'] ) { $this->FilterComponent = $this->Components->load('Filter'); - //$conditions = $this->FilterComponent->buildFilter($this->request->params['named']); - $conditions = $this->request->params['named']; + $conditions = $this->FilterComponent->buildFilter($this->request->params['named']); } else { $conditions = array(); } @@ -315,6 +314,10 @@ class MonitorsController extends AppController { throw new NotFoundException(__('Invalid monitor')); } + if (preg_match('/^[a-z]+$/i', $daemon) !== 1) { + throw new BadRequestException(__('Invalid command')); + } + $monitor = $this->Monitor->find('first', array( 'fields' => array('Id', 'Type', 'Device'), 'conditions' => array('Id' => $id) From abb6ef1688b145670d351b14370e6f52aec8483e Mon Sep 17 00:00:00 2001 From: Matthew Noorenberghe Date: Sun, 10 Feb 2019 21:34:49 -0800 Subject: [PATCH 206/310] API: Escape 'named' params for SQLi in two more Event endpoints. Fixes #2099 --- web/api/app/Controller/EventsController.php | 54 ++++++++++++++++----- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/web/api/app/Controller/EventsController.php b/web/api/app/Controller/EventsController.php index 4f706bec8..959ccc596 100644 --- a/web/api/app/Controller/EventsController.php +++ b/web/api/app/Controller/EventsController.php @@ -233,18 +233,34 @@ class EventsController extends AppController { public function search() { $this->Event->recursive = -1; + // Unmodified conditions to pass to find() + $find_conditions = array(); + // Conditions to be filtered by buildFilter $conditions = array(); foreach ($this->params['named'] as $param_name => $value) { - // Transform params into mysql - if ( preg_match('/interval/i', $value, $matches) ) { - $condition = array("$param_name >= (date_sub(now(), $value))"); + // Transform params into conditions + if ( preg_match('/^\s?interval\s?/i', $value) ) { + if (preg_match('/^[a-z0-9]+$/i', $param_name) !== 1) { + throw new Exception('Invalid field name: ' . $param_name); + } + $matches = NULL; + $value = preg_replace('/^\s?interval\s?/i', '', $value); + if (preg_match('/^(?P[ -.:0-9\']+)\s+(?P[_a-z]+)$/i', trim($value), $matches) !== 1) { + throw new Exception('Invalid interval: ' . $value); + } + $expr = trim($matches['expr']); + $unit = trim($matches['unit']); + array_push($find_conditions, "$param_name >= DATE_SUB(NOW(), INTERVAL $expr $unit)"); } else { - $condition = array($param_name => $value); + $conditions[$param_name] = $value; } - array_push($conditions, $condition); } + $this->FilterComponent = $this->Components->load('Filter'); + $conditions = $this->FilterComponent->buildFilter($conditions); + array_push($conditions, $find_conditions); + $results = $this->Event->find('all', array( 'conditions' => $conditions )); @@ -260,18 +276,32 @@ class EventsController extends AppController { // consoleEvents/1 hour/AlarmFrames >=: 1/AlarmFrames <=: 20.json public function consoleEvents($interval = null) { + $matches = NULL; + // https://dev.mysql.com/doc/refman/5.5/en/expressions.html#temporal-intervals + // Examples: `'1-1' YEAR_MONTH`, `'-1 10' DAY_HOUR`, `'1.999999' SECOND_MICROSECOND` + if (preg_match('/^(?P[ -.:0-9\']+)\s+(?P[_a-z]+)$/i', trim($interval), $matches) !== 1) { + throw new Exception('Invalid interval: ' . $interval); + } + $expr = trim($matches['expr']); + $unit = trim($matches['unit']); + $this->Event->recursive = -1; $results = array(); + $this->FilterComponent = $this->Components->load('Filter'); + $conditions = $this->FilterComponent->buildFilter($conditions); + array_push($conditions, array("StartTime >= DATE_SUB(NOW(), INTERVAL $expr $unit)")); - $moreconditions = ''; - foreach ($this->request->params['named'] as $name => $param) { - $moreconditions = $moreconditions . ' AND '.$name.$param; - } - - $query = $this->Event->query("SELECT MonitorId, COUNT(*) AS Count FROM Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;"); + $query = $this->Event->find('all', array( + 'fields' => array( + 'MonitorId', + 'COUNT(*) AS Count', + ), + 'conditions' => $conditions, + 'group' => 'MonitorId', + )); foreach ($query as $result) { - $results[$result['Events']['MonitorId']] = $result[0]['Count']; + $results[$result['Event']['MonitorId']] = $result[0]['Count']; } $this->set(array( From 3c31dd63cedf2365639600979b3fb5fe010c6a68 Mon Sep 17 00:00:00 2001 From: Matthew Noorenberghe Date: Sun, 10 Mar 2019 20:40:32 -0700 Subject: [PATCH 207/310] Use zm_session_start() for API auth. Fixes #2547 --- web/includes/auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/auth.php b/web/includes/auth.php index 5ce960388..6b061f7fc 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -211,7 +211,7 @@ global $user; if ( ZM_OPT_USE_AUTH ) { $close_session = 0; if ( !is_session_started() ) { - session_start(); + zm_session_start(); $close_session = 1; } From 6ee689f4bfa57e04002c64ff145af13b0bf11fe4 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 12 Mar 2019 11:09:57 -0400 Subject: [PATCH 208/310] Dahua control improvements (#2552) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adding a presetHome method The Dahua protocol does not appear to support a preset Home feature. We could allow the user to assign a preset slot as the "home" slot. Dahua does appear to support naming presets which may lend itself to this sort of thing. At this point, we'll just send the camera back to center and zoom wide. (0°,0°,0) * Adjusting naming of private methods and adding POD * Adding relative focus methods This patch also adds the return value of the get request used to send the command to the camera. Furthermore, it fixes a small bug in the authentication of requests sent to the camera after the connection is opened. I really have no idea why the problem occurred and it appeared to have no practical effect on the execution of the command. It showed up when I enabled debug, so this attempts to fix it or at least quiet it. * Adding POD for the new relative focus methods Also doing a bit of cleanup on POD in general. * Adding some documentation to demystify control motion types This was not clear to me at the outset, and I could not locate existing documentation which explained the prioritization and exclusion aspects. Maybe it will help someone else. * Renaming private methods and adding more POD This patch renames private methods by prefixing an underscore to them. This at least helps visually distinguish them as private when reading through the code. It also adds more documentation on public methods. * Grouping all relative motion methods together This makes for easier reading. * Adding in a reset method from Issue #2414 This method was supplied by kobold81 who got it from bobylapointe69300 who posted it in the following forum post: https://forums.zoneminder.com/viewtopic.php?f=9&t=27000&p=104601&hilit=dahua#p104601 This patch also includes some POD cleanup. * Adding continuous movement methods and fixing reset This patch adds the continuous movement methods provided in the patch for Issue #2414. Note that these are not truly continuous as they result in movement for less than a ms. Adapting the code to support truly continuous movement would invovle some considerable work. This patch also correct the reset method and adds a reboot method. The two are different creatures with different effects. POD added as well. * Removing redundant get request code Two slightly different versions of code are currently used to post the get request to the camera in order to execute commands. This patch modifies the open method in order to allow its re-use and removes redundant code. Note: This is the first installment on changes towards improving the way the HTTP transactions are handled. * Making authenication failures fatal Authentication failures result in camera commands not being executed. They may as well be fatal and return the general reason to the user directly. * Work on persistent sessions I think this will have to wait since it appears that the camera expects some sort of keepalive/heartbeat signal to keep the session open. * Restoring accidentally deleted code --- .../lib/ZoneMinder/Control/Dahua.pm | 396 +++++++++++++++--- 1 file changed, 329 insertions(+), 67 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm index 6cb55d3a6..10a37e102 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm @@ -39,6 +39,8 @@ sub AUTOLOAD my $class = ref($self) || croak( "$self not object" ); my $name = $AUTOLOAD; $name =~ s/.*://; + ## This seems odd... if the method existed would we even be here? + ## https://perldoc.perl.org/perlsub.html#Autoloading if ( exists($self->{$name}) ) { return( $self->{$name} ); @@ -46,9 +48,17 @@ sub AUTOLOAD Fatal( "Can't access $name member of object of class $class" ); } +# FIXME: Do we really have to open a new connection every time? + +#Digest usernbme="bdmin", reblm="Login to 4K05DB3PAJE98BE", nonae="1720242756", +#uri="/agi-bin/ptz.agi?bation=getStbtus&ahbnnel=1", response="10dd925b26ebd559353734635b859b8b", +#opbque="1a99677524b4ae63bbe3a132b2e9b38e3b163ebd", qop=buth, na=00000001, anonae="ab1bb5d43aa5d542" + sub open { + #Debug("&open invoked by: " . (caller(1))[3]); my $self = shift; + my $cgi = shift || '/cgi-bin/configManager.cgi?action=getConfig&name=Ptz'; $self->loadMonitor(); # The Dahua camera firmware API supports the concept of having multiple @@ -73,60 +83,55 @@ sub open } use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; + $self->{ua} = LWP::UserAgent->new(keep_alive => 1); $self->{ua}->agent("ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION); $self->{state} = 'closed'; # credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string) - Debug("sendCmd credentials control address:'".$ADDRESS - ."' realm:'" . $REALM - . "' username:'" . $USERNAME - . "' password:'".$PASSWORD - ."'" - ); $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); # Detect REALM - my $get_config_url = $PROTOCOL . $ADDRESS . "/cgi-bin/configManager.cgi?action=getConfig&name=Ptz"; - my $req = HTTP::Request->new(GET=>$get_config_url); + my $url = $PROTOCOL . $ADDRESS . $cgi; + my $req = HTTP::Request->new(GET=>$url); my $res = $self->{ua}->request($req); if ($res->is_success) { $self->{state} = 'open'; - return; + return 1; } if ( $res->status_line() eq '401 Unauthorized' ) { my $headers = $res->headers(); - foreach my $k (keys %$headers) { - Debug("Initial Header $k => $$headers{$k}"); - } - if ($$headers{'www-authenticate'}) { my ($auth, $tokens) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; + Debug("Tokens: " . $tokens); + ## FIXME: This is necessary because the Dahua spec does not match reality if ($tokens =~ /\w+="([^"]+)"/i) { if ($REALM ne $1) { $REALM = $1; Debug("Changing REALM to '" . $REALM . "'"); $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); - my $req = HTTP::Request->new(GET=>$get_config_url); + my $req = HTTP::Request->new(GET=>$url); $res = $self->{ua}->request($req); + if ($res->is_success()) { $self->{state} = 'open'; - return; + Debug('Authentication succeeded...'); + return 1; } Debug('Authentication still failed after updating REALM' . $res->status_line); $headers = $res->headers(); foreach my $k ( keys %$headers ) { Debug("Initial Header $k => $$headers{$k}"); } # end foreach - } else { - Error('Authentication failed, not a REALM problem'); + } else { ## NOTE: Each of these else conditions is fatal as the command will not be + ## executed. No use going further. + Fatal('Authentication failed: Check username and password.'); } } else { - Error('Failed to match realm in tokens'); + Fatal('Authentication failed: Incorrect realm.'); } # end if } else { - Error('No WWW-Authenticate Header'); + Fatal('Authentication failed: No www-authenticate header returned.'); } # end if headers } # end if $res->status_line() eq '401 Unauthorized' } @@ -146,45 +151,40 @@ sub printMsg Debug( $msg."[".$msg_len."]" ); } -sub sendGetRequest { +sub _sendGetRequest { my $self = shift; my $url_path = shift; - my $result = undef; + # Attempt to reuse the connection + + # FIXME: I think we need some sort of keepalive/heartbeat sent to the camera + # in order to keep the session alive. As it is, it appears that the + # ua's authentication times out or some such. + # + # This might be of some use: + # {"method":"global.keepAlive","params":{"timeout":300,"active":false},"id":1518,"session":"dae233a51c0693519395209b271411b6"}[!http] + # The web browser interface POSTs commands as JSON using js my $url = $PROTOCOL . $ADDRESS . $url_path; - my $req = HTTP::Request->new(GET=>$url); - + my $req = HTTP::Request->new(GET => $url); my $res = $self->{ua}->request($req); if ($res->is_success) { - $result = !undef; + return 1; } else { - if ($res->status_line() eq '401 Unauthorized') { - Debug("Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD); - Debug("Content was " . $res->content() ); - my $res = $self->{ua}->request($req); - if ($res->is_success) { - $result = !undef; - } else { - Error("Content was " . $res->content() ); - } - } - if ( ! $result ) { - Error("Error check failed: '".$res->status_line()); - } + return($self->open($url_path)); # if we have to, open a new connection } - return($result); } -sub sendPtzCommand +sub _sendPtzCommand { my $self = shift; my $action = shift; my $command_code = shift; - my $arg1 = shift; - my $arg2 = shift; - my $arg3 = shift; + my $arg1 = shift || 0; + my $arg2 = shift || 0; + my $arg3 = shift || 0; + my $arg4 = shift || 0; my $channel = $self->{dahua_channel_number}; @@ -194,10 +194,12 @@ sub sendPtzCommand $url_path .= "code=" . $command_code . "&"; $url_path .= "arg1=" . $arg1 . "&"; $url_path .= "arg2=" . $arg2 . "&"; - $url_path .= "arg3=" . $arg3; - $self->sendGetRequest($url_path); + $url_path .= "arg3=" . $arg3 . "&"; + $url_path .= "arg4=" . $arg4; + return $self->_sendGetRequest($url_path); } -sub sendMomentaryPtzCommand + +sub _sendMomentaryPtzCommand { my $self = shift; my $command_code = shift; @@ -206,92 +208,195 @@ sub sendMomentaryPtzCommand my $arg3 = shift; my $duration_ms = shift; - $self->sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3); + $self->_sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3); my $duration_ns = $duration_ms * 1000; usleep($duration_ns); - $self->sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3); + $self->_sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3); +} + +sub _sendAbsolutePositionCommand +{ + my $self = shift; + my $arg1 = shift; + my $arg2 = shift; + my $arg3 = shift; + my $arg4 = shift; + + $self->_sendPtzCommand("start", "PositionABS", $arg1, $arg2, $arg3, $arg4); +} + +sub moveConLeft +{ + my $self = shift; + Debug("Move Up Left"); + $self->_sendMomentaryPtzCommand("Left", 0, 1, 0, 0); +} + +sub moveConRight +{ + my $self = shift; + Debug( "Move Right" ); + $self->_sendMomentaryPtzCommand("Right", 0, 1, 0, 0); +} + +sub moveConUp +{ + my $self = shift; + Debug( "Move Up" ); + $self->_sendMomentaryPtzCommand("Up", 0, 1, 0, 0); +} + +sub moveConDown +{ + my $self = shift; + Debug( "Move Down" ); + $self->_sendMomentaryPtzCommand("Down", 0, 1, 0, 0); +} + +sub moveConUpRight +{ + my $self = shift; + Debug( "Move Diagonally Up Right" ); + $self->_sendMomentaryPtzCommand("RightUp", 1, 1, 0, 0); +} + +sub moveConDownRight +{ + my $self = shift; + Debug( "Move Diagonally Down Right" ); + $self->_sendMomentaryPtzCommand("RightDown", 1, 1, 0, 0); +} + +sub moveConUpLeft +{ + my $self = shift; + Debug( "Move Diagonally Up Left" ); + $self->_sendMomentaryPtzCommand("LeftUp", 1, 1, 0, 0); +} + +sub moveConDownLeft +{ + my $self = shift; + Debug( "Move Diagonally Up Right" ); + $self->_sendMomentaryPtzCommand("LeftDown", 1, 1, 0, 0); +} + +sub zoomConTele +{ + my $self = shift; + Debug( "Zoom Tele" ); + $self->_sendMomentaryPtzCommand("ZoomTele", 0, 1, 0, 0); +} + +sub zoomConWide +{ + my $self = shift; + Debug( "Zoom Wide" ); + $self->_sendMomentaryPtzCommand("ZoomWide", 0, 1, 0, 0); } sub moveRelUpLeft { my $self = shift; Debug("Move Up Left"); - $self->sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500); + $self->_sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500); } sub moveRelUp { my $self = shift; Debug("Move Up"); - $self->sendMomentaryPtzCommand("Up", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("Up", 0, 4, 0, 500); } sub moveRelUpRight { my $self = shift; Debug("Move Up Right"); - $self->sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500); } sub moveRelLeft { my $self = shift; Debug("Move Left"); - $self->sendMomentaryPtzCommand("Left", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("Left", 0, 4, 0, 500); } sub moveRelRight { my $self = shift; Debug("Move Right"); - $self->sendMomentaryPtzCommand("Right", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("Right", 0, 4, 0, 500); } sub moveRelDownLeft { my $self = shift; Debug("Move Down Left"); - $self->sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500); + $self->_sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500); } sub moveRelDown { my $self = shift; Debug("Move Down"); - $self->sendMomentaryPtzCommand("Down", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("Down", 0, 4, 0, 500); } sub moveRelDownRight { my $self = shift; Debug("Move Down Right"); - $self->sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500); + $self->_sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500); } sub zoomRelTele { my $self = shift; Debug("Zoom Relative Tele"); - $self->sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500); + $self->_sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500); } sub zoomRelWide { my $self = shift; Debug("Zoom Relative Wide"); - $self->sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500); + $self->_sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500); } +sub focusRelNear +{ + my $self = shift; + + my $response = $self->_sendPtzCommand("start", "FocusNear", 0, 1, 0, 0); + Debug("focusRelNear response: " . $response); +} + +sub focusRelFar +{ + my $self = shift; + + my $response = $self->_sendPtzCommand("start", "FocusFar", 0, 1, 0, 0); + Debug("focusRelFar response: " . $response); +} + +sub moveStop +{ + my $self = shift; + Debug( "Move Stop" ); + # The command does not matter here, just the stop... + $self->_sendPtzCommand("stop", "Up", 0, 0, 1, 0); +} sub presetClear { my $self = shift; my $params = shift; my $preset_id = $self->getParam($params, 'preset'); - $self->sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0); + $self->_sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0); } - sub presetSet { my $self = shift; @@ -308,8 +413,8 @@ sub presetSet my $control_preset_row = $sth->fetchrow_hashref(); my $new_label_name = $control_preset_row->{'Label'}; - $self->sendPtzCommand("start", "SetPreset", 0, $preset_id, 0); - $self->sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0); + $self->_sendPtzCommand("start", "SetPreset", 0, $preset_id, 0); + $self->_sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0); } sub presetGoto @@ -318,12 +423,39 @@ sub presetGoto my $params = shift; my $preset_id = $self->getParam($params, 'preset'); - $self->sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0); + $self->_sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0); +} + +sub presetHome +{ + my $self = shift; + + $self->_sendAbsolutePositionCommand( 0, 0, 0, 1 ); +} + +sub reset +{ + my $self = shift; + Debug( "Camera Reset" ); + $self->_sendPtzCommand("Reset", 0, 0, 0, 0); +} + +sub reboot +{ + my $self = shift; + Debug( "Camera Reboot" ); + my $cmd = "cgi-bin/magicBox.cgi?action=reboot"; + $self->_sendGetRequest($cmd); } 1; + __END__ +=pod + +=encoding utf8 + =head1 NAME ZoneMinder::Control::Dahua - Perl module for Dahua cameras @@ -337,10 +469,6 @@ place this in /usr/share/perl5/ZoneMinder/Control This module is an implementation of the Dahua IP camera HTTP control API. -=head2 EXPORT - -None by default. - =head1 COPYRIGHT AND LICENSE Copyright (C) 2018 ZoneMinder LLC @@ -359,4 +487,138 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +=head1 Private Methods + + Methods intended for use internally but documented here for future developers. + +=head2 _sendAbsolutePositionCommand( $arg1, $arg2, $arg3, $arg4 ) + + Where: + + $arg1 = Horizontal angle 0° to 360° + $arg2 = Vertical angle 0° to -90° + $arg3 = Zoom multiplier + $arg4 = Speed 1 to 8 + + This is an private method used to send an absolute position command to the + camera. + +=head1 Public Methods + + Methods made available to control.pl via ZoneMinder::Control + +=head2 Notes: + +=over 1 + + Which methods are invoked depends on which types of movement are selected in + the camera control type. For example: if the 'Can Move Continuous' option is + checked, then methods including 'Con' in their names are invoked. Likewise if + the 'Can Move Relative" option is checked, then methods including 'Rel' in + their names are invoked. + + + At present, these types of movement are prioritized and exclusive. This applies + to all types of movement, not just PTZ, but focus, iris, etc. as well. The options + are tested in the following order: + + 1. Continuous + + 2. Relative + + 3. Absolute + + These types are exclusive meaning that the first one that matches is the one + ZoneMinder will use to control with. It would be nice to allow the user to + select the type used given that some cameras support all three types of + movement. + +=back + +=head2 new + + This method instantiates a new control object based upon this control module + and sets the 'id' attribute to the value passed in. + +=head2 open + + This method opens an HTTP connection to the camera. It handles authentication, + etc. Upon success it sets the 'state' attribute to 'open.' + +=head2 close + + This method effectively closes the HTTP connection to the camera. It sets the + 'state' attribute to 'close.' + +=head2 printMsg + + This method appears to be used for debugging. + +=head2 moveCon + + This set of methods invoke continuous movement in the direction indicated by + the portion of their name. They accept no arguments and move the + camera at a speed of 1 for 0ms. The speed index of 1 is the lowest of the + accepted range of 1-8. + + NOTE: + + This is not true continuous movmement as currently implemented. + +=head2 focusCon + + This set of methods invoke continuous focus in the range direction indicated + by the portion of their name. They accept no arguments. + + NOTE: + + This is not true continuous movmement as currently implemented. + +=head2 moveRel + + This set of methods invoke relatvie movement in the direction indicated by + the portion of their name. They accept no arguments and move the + camera at a speed of 4 for 500ms. The speed index of 4 is half-way between + the accepted range of 1-8. + +=head2 focusRel + + This set of methods invoke realtive focus in the range direction indicated by + the portion of their name. They accept no arguments. + + NOTE: + + This only just does work. The Dahua API specifies "multiples" as the input. + We pass in a 1 for that as it does not seem to matter what number (0-8) is + provided, the camera focus behaves the same. + +=head2 moveStop + + This method attempts to stop the camera. The problem is that if continuous + motion is occurring in multiple directions, this will only stop the motion + in the 'Up' direction. Dahua does not support an "all-stop" command. + +=head2 presetHome + + This method "homes" the camera to a preset position. It accepts no arguments. + When either continuous or relative movement is enabled, pressing the center + button on the movement controls invokes this method. + + NOTE: + + The Dahua protocol does not appear to support a preset Home feature. We could + allow the user to assign a preset slot as the "home" slot. Dahua does appear + to support naming presets which may lend itself to this sort of thing. At + this point, we'll just send the camera back to center and zoom wide. (0°,0°,0) + +=head2 reset + + This method will reset the PTZ controls to their "default." It is not clear + what that is. + +=head2 reboot + + This method performs a reboot of the camera. This will take the camera offline + for the time it takes to reboot. + =cut From 84172306908f5fa3275a5fe4b29bd7dabf26a42f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 18 Mar 2019 10:55:08 -0400 Subject: [PATCH 209/310] debug where the comms sock is --- src/zm_stream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index 20d4f1632..797946046 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -357,7 +357,7 @@ void StreamBase::openComms() { gettimeofday(&last_comm_update, NULL); } // end if connKey > 0 - Debug(3, "comms open"); + Debug(3, "comms open at %s", loc_sock_path); } // end void StreamBase::openComms() void StreamBase::closeComms() { From dde911d16a9b3c4cf5c62263881592a1f1f6953a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 18 Mar 2019 10:55:21 -0400 Subject: [PATCH 210/310] add more debug about command queue --- src/zm_eventstream.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index d46c9b385..e69185858 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -800,13 +800,17 @@ void EventStream::runStream() { // commands may set send_frame to true while ( checkCommandQueue() && !zm_terminate ) { // The idea is to loop here processing all commands before proceeding. + Debug(1, "Have command queue"); } + Debug(1, "Done command queue"); // Update modified time of the socket .lock file so that we can tell which ones are stale. if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) { touch(sock_path_lock); last_comm_update = now; } + } else { + Debug(1, "Not checking command queue"); } if ( step != 0 ) From 9482207f5c7b4f9fd65e8bf1f8296bb6d2a27f2b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 18 Mar 2019 11:24:28 -0400 Subject: [PATCH 211/310] revert namespace stuff in index.php --- web/index.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/web/index.php b/web/index.php index 7a5abb79b..99e77d285 100644 --- a/web/index.php +++ b/web/index.php @@ -17,7 +17,6 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -namespace ZM; error_reporting(E_ALL); @@ -194,7 +193,7 @@ isset($view) || $view = NULL; isset($request) || $request = NULL; isset($action) || $action = NULL; -Logger::Debug("View: $view Request: $request Action: $action User: " . ( isset($user) ? $user['Username'] : 'none' )); +ZM\Logger::Debug("View: $view Request: $request Action: $action User: " . ( isset($user) ? $user['Username'] : 'none' )); if ( ZM_ENABLE_CSRF_MAGIC && ( $action != 'login' ) && @@ -205,17 +204,17 @@ if ( ( $view != 'archive' ) ) { require_once( 'includes/csrf/csrf-magic.php' ); - #Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\""); + #ZM\Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\""); csrf_check(); } # Need to include actions because it does auth if ( $action ) { if ( file_exists('includes/actions/'.$view.'.php') ) { - Logger::Debug("Including includes/actions/$view.php"); + ZM\Logger::Debug("Including includes/actions/$view.php"); require_once('includes/actions/'.$view.'.php'); } else { - Warning("No includes/actions/$view.php for action $action"); + ZM\Warning("No includes/actions/$view.php for action $action"); } } @@ -227,7 +226,7 @@ if ( ZM_OPT_USE_AUTH and !isset($user) and ($view != 'login') ) { header('HTTP/1.1 401 Unauthorized'); exit; } - Logger::Debug('Redirecting to login'); + ZM\Logger::Debug('Redirecting to login'); $view = 'none'; $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=login'; $request = null; @@ -239,7 +238,7 @@ if ( ZM_OPT_USE_AUTH and !isset($user) and ($view != 'login') ) { CSPHeaders($view, $cspNonce); if ( $redirect ) { - Logger::Debug("Redirecting to $redirect"); + ZM\Logger::Debug("Redirecting to $redirect"); header('Location: '.$redirect); return; } @@ -247,7 +246,7 @@ if ( $redirect ) { if ( $request ) { foreach ( getSkinIncludes('ajax/'.$request.'.php', true, true) as $includeFile ) { if ( !file_exists($includeFile) ) - Fatal("Request '$request' does not exist"); + ZM\Fatal("Request '$request' does not exist"); require_once $includeFile; } return; @@ -256,7 +255,7 @@ if ( $request ) { if ( $includeFiles = getSkinIncludes('views/'.$view.'.php', true, true) ) { foreach ( $includeFiles as $includeFile ) { if ( !file_exists($includeFile) ) - Fatal("View '$view' does not exist"); + ZM\Fatal("View '$view' does not exist"); require_once $includeFile; } // If the view overrides $view to 'error', and the user is not logged in, then the From b794c2ca203bd11bf804ea0b5ac49aaa6000cf03 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 18 Mar 2019 12:01:51 -0400 Subject: [PATCH 212/310] fix crash by checking username without checking if it is NULL --- src/zm_user.cpp | 12 ++++++++---- src/zmu.cpp | 46 ++++++++++++++++++++-------------------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/zm_user.cpp b/src/zm_user.cpp index da0c66416..46ee2cdf1 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -248,15 +248,19 @@ User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) { //Function to check Username length bool checkUser ( const char *username) { - if ( strlen(username) > 32) { + if ( ! username ) return false; - } + if ( strlen(username) > 32 ) + return false; + return true; } //Function to check password length bool checkPass (const char *password) { - if ( strlen(password) > 64) { + if ( !password ) return false; - } + if ( strlen(password) > 64 ) + return false; + return true; } diff --git a/src/zmu.cpp b/src/zmu.cpp index a8ee61273..2b5d95c40 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -394,7 +394,7 @@ int main(int argc, char *argv[]) { //fprintf( stderr, "?? getopt returned character code 0%o ??\n", c ); break; } - } + } // end getopt loop if ( optind < argc ) { fprintf(stderr, "Extraneous options, "); @@ -425,44 +425,38 @@ int main(int argc, char *argv[]) { if ( config.opt_use_auth ) { if ( strcmp(config.auth_relay, "none") == 0 ) { - if ( !checkUser(username)) { - fprintf(stderr, "Error, username greater than allowed 32 characters\n"); - exit_zmu(-1); - } if ( !username ) { fprintf(stderr, "Error, username must be supplied\n"); exit_zmu(-1); } - if ( username ) { - user = zmLoadUser(username); - } - } else { - if ( !(username && password) && !auth ) { - fprintf(stderr, "Error, username and password or auth string must be supplied\n"); - exit_zmu(-1); - } if ( !checkUser(username)) { fprintf(stderr, "Error, username greater than allowed 32 characters\n"); exit_zmu(-1); } - if ( !checkPass(password)) { - fprintf(stderr, "Error, password greater than allowed 64 characters\n"); + + user = zmLoadUser(username); + } else { + + if ( !(username && password) && !auth ) { + fprintf(stderr, "Error, username and password or auth string must be supplied\n"); exit_zmu(-1); } - //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) - { - if ( auth ) { - user = zmLoadAuthUser(auth, false); - } + if ( auth ) { + user = zmLoadAuthUser(auth, false); } - //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) - { - if ( username && password ) { - user = zmLoadUser(username, password); + if ( username && password ) { + if ( !checkUser(username)) { + fprintf(stderr, "Error, username greater than allowed 32 characters\n"); + exit_zmu(-1); } - } - } + if ( !checkPass(password)) { + fprintf(stderr, "Error, password greater than allowed 64 characters\n"); + exit_zmu(-1); + } + user = zmLoadUser(username, password); + } // end if username && password + } // end if relay or not if ( !user ) { fprintf(stderr, "Error, unable to authenticate user\n"); return exit_zmu(-1); From d86b1ea49c895e4c4778988c7487a37369efefc6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 18 Mar 2019 13:48:55 -0400 Subject: [PATCH 213/310] Implement a date filter to zmaudit so that it only looks in directories by date. --- scripts/zmaudit.pl.in | 73 +++++++++++++++++++++++-------------------- scripts/zmdc.pl.in | 5 +-- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 10917a729..67d1d23ce 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -62,6 +62,7 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $report = 0; my $interactive = 0; my $continuous = 0; +my $date = undef; my $level = 1; my $monitor_id = 0; my $version; @@ -73,6 +74,7 @@ logInit(); GetOptions( continuous =>\$continuous, + 'date=s' =>\$date, force =>\$force, interactive =>\$interactive, level =>\$level, @@ -110,24 +112,24 @@ if ( -e ZM_AUDIT_PID ) { } } # end if ZM_AUDIT_PID exists -if ( open( my $PID, '>', ZM_AUDIT_PID ) ) { - print( $PID $$ ); - close( $PID ); +if ( open(my $PID, '>', ZM_AUDIT_PID) ) { + print($PID $$); + close($PID); } else { - Error( "Can't open pid file at " . ZM_PID ); + Error("Can't open pid file at " . ZM_PID); } sub HupHandler { - Info("Received HUP, reloading"); + Info('Received HUP, reloading'); &ZoneMinder::Logger::logHupHandler(); } sub TermHandler { - Info("Received TERM, exiting"); + Info('Received TERM, exiting'); Term(); } sub Term { unlink ZM_AUDIT_PID; - exit( 0 ); + exit(0); } $SIG{HUP} = \&HupHandler; $SIG{TERM} = \&TermHandler; @@ -154,7 +156,7 @@ MAIN: while( $loop ) { # if we are running continuously, then just skip to the next # interval, otherwise we are a one off run, so wait a second and # retry until someone kills us. - sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); + sleep($Config{ZM_AUDIT_CHECK_INTERVAL}); } else { sleep 1; } # end if @@ -182,53 +184,53 @@ MAIN: while( $loop ) { Error("No Storage Area found with Id $storage_id"); Term(); } - Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}"); + Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}".( $date ? " limited to $date" : '')); } elsif ( $server_id ) { @Storage_Areas = ZoneMinder::Storage->find( ServerId => $server_id ); if ( ! @Storage_Areas ) { - Error("No Storage Area found with ServerId =" . $server_id); + Error('No Storage Area found with ServerId =' . $server_id); Term(); } foreach my $Storage ( @Storage_Areas ) { - Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name() ); + Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name().( $date ? " limited to $date" : '')); } } else { @Storage_Areas = ZoneMinder::Storage->find(); - Info("Auditing All Storage Areas"); + Info('Auditing All Storage Areas'.( $date ? " limited to $date" : '')); } my %Monitors; my $db_monitors; my $monitorSelectSql = $monitor_id ? 'SELECT * FROM Monitors WHERE Id=?' : 'SELECT * FROM Monitors ORDER BY Id'; - my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) - or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); + my $monitorSelectSth = $dbh->prepare_cached($monitorSelectSql) + or Fatal("Can't prepare '$monitorSelectSql': ".$dbh->errstr()); my $eventSelectSql = 'SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) AS Age FROM Events WHERE MonitorId = ?'.(@Storage_Areas ? ' AND StorageId IN ('.join(',',map { '?'} @Storage_Areas).')' : '' ). ' ORDER BY Id'; - my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) - or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); + my $eventSelectSth = $dbh->prepare_cached($eventSelectSql) + or Fatal("Can't prepare '$eventSelectSql': ".$dbh->errstr()); $cleaned = 0; my $res = $monitorSelectSth->execute( $monitor_id ? $monitor_id : () ) - or Fatal( "Can't execute: $monitorSelectSql ".$monitorSelectSth->errstr() ); + or Fatal("Can't execute: $monitorSelectSql ".$monitorSelectSth->errstr()); while( my $monitor = $monitorSelectSth->fetchrow_hashref() ) { $Monitors{$$monitor{Id}} = $monitor; my $db_events = $db_monitors->{$monitor->{Id}} = {}; my $res = $eventSelectSth->execute( $monitor->{Id}, map { $$_{Id} } @Storage_Areas ) - or Fatal( "Can't execute: ".$eventSelectSth->errstr() ); + or Fatal("Can't execute: ".$eventSelectSth->errstr()); while ( my $event = $eventSelectSth->fetchrow_hashref() ) { $db_events->{$event->{Id}} = $event->{Age}; } - Debug( 'Got '.int(keys(%$db_events))." events for monitor $monitor->{Id}" ); + Debug('Got '.int(keys(%$db_events))." events for monitor $monitor->{Id}"); } # end while monitors my $fs_monitors; foreach my $Storage ( @Storage_Areas ) { - Debug('Checking events in ' . $Storage->Path() ); + Debug('Checking events in ' . $Storage->Path()); if ( ! chdir( $Storage->Path() ) ) { - Error( 'Unable to change dir to ' . $Storage->Path() ); + Error('Unable to change dir to ' . $Storage->Path()); next; } # end if @@ -243,7 +245,7 @@ MAIN: while( $loop ) { next; } - Debug( "Found filesystem monitor '$monitor'" ); + Debug("Found filesystem monitor '$monitor'"); $fs_monitors->{$monitor} = {} if ! $fs_monitors->{$monitor}; my $fs_events = $fs_monitors->{$monitor}; @@ -251,10 +253,11 @@ MAIN: while( $loop ) { ( my $monitor_dir ) = ( $monitor =~ /^(.*)$/ ); { - my @day_dirs = glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]"); + my ( $y, $m, $d ) = $date =~ /^(\d\d\d\d)\-(\d\d)\-(\d\d)$/; + my @day_dirs = glob($date ? "$monitor_dir/$y/$m/$d" : "$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]"); Debug(qq`Checking for Deep Events under $$Storage{Path} using glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]") returned `. scalar @day_dirs . ' events'); foreach my $day_dir ( @day_dirs ) { - Debug( "Checking day dir $day_dir" ); + Debug("Checking day dir $day_dir"); ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint if ( !chdir($day_dir) ) { Error("Can't chdir to '$$Storage{Path}/$day_dir': $!"); @@ -267,7 +270,7 @@ MAIN: while( $loop ) { my %event_ids_by_path; my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); - Debug("Have " . @event_links . ' event links'); + Debug('Have ' . @event_links . ' event links'); closedir(DIR); my $count = 0; @@ -383,11 +386,12 @@ MAIN: while( $loop ) { Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir"); { - my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"); - Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . ' entries.' ); + my $glob = $date ? "$monitor_dir/$date/*":"$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"; + my @event_dirs = glob($glob); + Debug(qq`glob($glob) returned ` . scalar @event_dirs . ' entries.' ); foreach my $event_dir ( @event_dirs ) { if ( ! -d $event_dir ) { - Debug( "$event_dir is not a dir. Skipping" ); + Debug("$event_dir is not a dir. Skipping"); next; } my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/; @@ -410,11 +414,11 @@ MAIN: while( $loop ) { if ( ! $$Storage{Scheme} ) { Error("Storage Scheme not set on $$Storage{Name}"); if ( ! chdir( $monitor_dir ) ) { - Error( "Can't chdir directory '$$Storage{Path}/$monitor_dir': $!" ); + Error("Can't chdir directory '$$Storage{Path}/$monitor_dir': $!"); next; } if ( ! opendir( DIR, "." ) ) { - Error( "Can't open directory '$$Storage{Path}/$monitor_dir': $!" ); + Error("Can't open directory '$$Storage{Path}/$monitor_dir': $!"); next; } my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); @@ -429,7 +433,7 @@ MAIN: while( $loop ) { } # end foreach event chdir( $Storage->Path() ); } # if USE_DEEP_STORAGE - Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir" ); + Debug('Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir"); delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir); } # end foreach monitor @@ -463,7 +467,7 @@ MAIN: while( $loop ) { my $age = $Event->age(); if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { - aud_print( "Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old" ); + aud_print("Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old"); if ( confirm() ) { $Event->delete_files(); $cleaned = 1; @@ -473,7 +477,7 @@ MAIN: while( $loop ) { } # end if ! in db events } # end foreach fs event } else { - aud_print( "Filesystem monitor '$monitor_id' in $$Storage{Path} does not exist in database" ); + aud_print("Filesystem monitor '$monitor_id' in $$Storage{Path} does not exist in database"); if ( confirm() ) { my $command = "rm -rf $monitor_id"; @@ -488,7 +492,7 @@ MAIN: while( $loop ) { next if ( !-l $link ); next if ( -e $link ); - aud_print( "Filesystem monitor link '$link' does not point to valid monitor directory" ); + aud_print("Filesystem monitor link '$link' does not point to valid monitor directory"); if ( confirm() ) { ( $link ) = ( $link =~ /^(.*)$/ ); # De-taint my $command = qq`rm "$link"`; @@ -1049,6 +1053,7 @@ yet. =head1 OPTIONS -c, --continuous - Run continuously + -d, --date - Limit search for events on given date. Use YYYY-MM-DD format -f, --force - Run even if pid file exists -i, --interactive - Ask before applying any changes -m, --monitor_id - Only consider the given monitor diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 67bbc294b..61b9b1b82 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -93,8 +93,9 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my @daemons = ( 'zmc', 'zma', - 'zmfilter.pl', 'zmaudit.pl', + 'zmcontrol.pl', + 'zmfilter.pl', 'zmtrigger.pl', 'zmx10.pl', 'zmwatch.pl', @@ -105,7 +106,7 @@ my @daemons = ( ); if ( $Config{ZM_OPT_USE_EVENTNOTIFICATION} ) { - push @daemons,'zmeventnotification.pl'; + push @daemons, 'zmeventnotification.pl'; } my $command = shift @ARGV; From e486b035f09bd3bce46aacd64546162f1204d44a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 18 Mar 2019 13:50:00 -0400 Subject: [PATCH 214/310] Revert "Implement a date filter to zmaudit so that it only looks in directories by date." This reverts commit d86b1ea49c895e4c4778988c7487a37369efefc6. --- scripts/zmaudit.pl.in | 73 ++++++++++++++++++++----------------------- scripts/zmdc.pl.in | 5 ++- 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 67d1d23ce..10917a729 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -62,7 +62,6 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $report = 0; my $interactive = 0; my $continuous = 0; -my $date = undef; my $level = 1; my $monitor_id = 0; my $version; @@ -74,7 +73,6 @@ logInit(); GetOptions( continuous =>\$continuous, - 'date=s' =>\$date, force =>\$force, interactive =>\$interactive, level =>\$level, @@ -112,24 +110,24 @@ if ( -e ZM_AUDIT_PID ) { } } # end if ZM_AUDIT_PID exists -if ( open(my $PID, '>', ZM_AUDIT_PID) ) { - print($PID $$); - close($PID); +if ( open( my $PID, '>', ZM_AUDIT_PID ) ) { + print( $PID $$ ); + close( $PID ); } else { - Error("Can't open pid file at " . ZM_PID); + Error( "Can't open pid file at " . ZM_PID ); } sub HupHandler { - Info('Received HUP, reloading'); + Info("Received HUP, reloading"); &ZoneMinder::Logger::logHupHandler(); } sub TermHandler { - Info('Received TERM, exiting'); + Info("Received TERM, exiting"); Term(); } sub Term { unlink ZM_AUDIT_PID; - exit(0); + exit( 0 ); } $SIG{HUP} = \&HupHandler; $SIG{TERM} = \&TermHandler; @@ -156,7 +154,7 @@ MAIN: while( $loop ) { # if we are running continuously, then just skip to the next # interval, otherwise we are a one off run, so wait a second and # retry until someone kills us. - sleep($Config{ZM_AUDIT_CHECK_INTERVAL}); + sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); } else { sleep 1; } # end if @@ -184,53 +182,53 @@ MAIN: while( $loop ) { Error("No Storage Area found with Id $storage_id"); Term(); } - Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}".( $date ? " limited to $date" : '')); + Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}"); } elsif ( $server_id ) { @Storage_Areas = ZoneMinder::Storage->find( ServerId => $server_id ); if ( ! @Storage_Areas ) { - Error('No Storage Area found with ServerId =' . $server_id); + Error("No Storage Area found with ServerId =" . $server_id); Term(); } foreach my $Storage ( @Storage_Areas ) { - Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name().( $date ? " limited to $date" : '')); + Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name() ); } } else { @Storage_Areas = ZoneMinder::Storage->find(); - Info('Auditing All Storage Areas'.( $date ? " limited to $date" : '')); + Info("Auditing All Storage Areas"); } my %Monitors; my $db_monitors; my $monitorSelectSql = $monitor_id ? 'SELECT * FROM Monitors WHERE Id=?' : 'SELECT * FROM Monitors ORDER BY Id'; - my $monitorSelectSth = $dbh->prepare_cached($monitorSelectSql) - or Fatal("Can't prepare '$monitorSelectSql': ".$dbh->errstr()); + my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) + or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); my $eventSelectSql = 'SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) AS Age FROM Events WHERE MonitorId = ?'.(@Storage_Areas ? ' AND StorageId IN ('.join(',',map { '?'} @Storage_Areas).')' : '' ). ' ORDER BY Id'; - my $eventSelectSth = $dbh->prepare_cached($eventSelectSql) - or Fatal("Can't prepare '$eventSelectSql': ".$dbh->errstr()); + my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) + or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); $cleaned = 0; my $res = $monitorSelectSth->execute( $monitor_id ? $monitor_id : () ) - or Fatal("Can't execute: $monitorSelectSql ".$monitorSelectSth->errstr()); + or Fatal( "Can't execute: $monitorSelectSql ".$monitorSelectSth->errstr() ); while( my $monitor = $monitorSelectSth->fetchrow_hashref() ) { $Monitors{$$monitor{Id}} = $monitor; my $db_events = $db_monitors->{$monitor->{Id}} = {}; my $res = $eventSelectSth->execute( $monitor->{Id}, map { $$_{Id} } @Storage_Areas ) - or Fatal("Can't execute: ".$eventSelectSth->errstr()); + or Fatal( "Can't execute: ".$eventSelectSth->errstr() ); while ( my $event = $eventSelectSth->fetchrow_hashref() ) { $db_events->{$event->{Id}} = $event->{Age}; } - Debug('Got '.int(keys(%$db_events))." events for monitor $monitor->{Id}"); + Debug( 'Got '.int(keys(%$db_events))." events for monitor $monitor->{Id}" ); } # end while monitors my $fs_monitors; foreach my $Storage ( @Storage_Areas ) { - Debug('Checking events in ' . $Storage->Path()); + Debug('Checking events in ' . $Storage->Path() ); if ( ! chdir( $Storage->Path() ) ) { - Error('Unable to change dir to ' . $Storage->Path()); + Error( 'Unable to change dir to ' . $Storage->Path() ); next; } # end if @@ -245,7 +243,7 @@ MAIN: while( $loop ) { next; } - Debug("Found filesystem monitor '$monitor'"); + Debug( "Found filesystem monitor '$monitor'" ); $fs_monitors->{$monitor} = {} if ! $fs_monitors->{$monitor}; my $fs_events = $fs_monitors->{$monitor}; @@ -253,11 +251,10 @@ MAIN: while( $loop ) { ( my $monitor_dir ) = ( $monitor =~ /^(.*)$/ ); { - my ( $y, $m, $d ) = $date =~ /^(\d\d\d\d)\-(\d\d)\-(\d\d)$/; - my @day_dirs = glob($date ? "$monitor_dir/$y/$m/$d" : "$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]"); + my @day_dirs = glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]"); Debug(qq`Checking for Deep Events under $$Storage{Path} using glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]") returned `. scalar @day_dirs . ' events'); foreach my $day_dir ( @day_dirs ) { - Debug("Checking day dir $day_dir"); + Debug( "Checking day dir $day_dir" ); ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint if ( !chdir($day_dir) ) { Error("Can't chdir to '$$Storage{Path}/$day_dir': $!"); @@ -270,7 +267,7 @@ MAIN: while( $loop ) { my %event_ids_by_path; my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); - Debug('Have ' . @event_links . ' event links'); + Debug("Have " . @event_links . ' event links'); closedir(DIR); my $count = 0; @@ -386,12 +383,11 @@ MAIN: while( $loop ) { Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir"); { - my $glob = $date ? "$monitor_dir/$date/*":"$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"; - my @event_dirs = glob($glob); - Debug(qq`glob($glob) returned ` . scalar @event_dirs . ' entries.' ); + my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"); + Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . ' entries.' ); foreach my $event_dir ( @event_dirs ) { if ( ! -d $event_dir ) { - Debug("$event_dir is not a dir. Skipping"); + Debug( "$event_dir is not a dir. Skipping" ); next; } my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/; @@ -414,11 +410,11 @@ MAIN: while( $loop ) { if ( ! $$Storage{Scheme} ) { Error("Storage Scheme not set on $$Storage{Name}"); if ( ! chdir( $monitor_dir ) ) { - Error("Can't chdir directory '$$Storage{Path}/$monitor_dir': $!"); + Error( "Can't chdir directory '$$Storage{Path}/$monitor_dir': $!" ); next; } if ( ! opendir( DIR, "." ) ) { - Error("Can't open directory '$$Storage{Path}/$monitor_dir': $!"); + Error( "Can't open directory '$$Storage{Path}/$monitor_dir': $!" ); next; } my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR ); @@ -433,7 +429,7 @@ MAIN: while( $loop ) { } # end foreach event chdir( $Storage->Path() ); } # if USE_DEEP_STORAGE - Debug('Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir"); + Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir" ); delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir); } # end foreach monitor @@ -467,7 +463,7 @@ MAIN: while( $loop ) { my $age = $Event->age(); if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { - aud_print("Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old"); + aud_print( "Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old" ); if ( confirm() ) { $Event->delete_files(); $cleaned = 1; @@ -477,7 +473,7 @@ MAIN: while( $loop ) { } # end if ! in db events } # end foreach fs event } else { - aud_print("Filesystem monitor '$monitor_id' in $$Storage{Path} does not exist in database"); + aud_print( "Filesystem monitor '$monitor_id' in $$Storage{Path} does not exist in database" ); if ( confirm() ) { my $command = "rm -rf $monitor_id"; @@ -492,7 +488,7 @@ MAIN: while( $loop ) { next if ( !-l $link ); next if ( -e $link ); - aud_print("Filesystem monitor link '$link' does not point to valid monitor directory"); + aud_print( "Filesystem monitor link '$link' does not point to valid monitor directory" ); if ( confirm() ) { ( $link ) = ( $link =~ /^(.*)$/ ); # De-taint my $command = qq`rm "$link"`; @@ -1053,7 +1049,6 @@ yet. =head1 OPTIONS -c, --continuous - Run continuously - -d, --date - Limit search for events on given date. Use YYYY-MM-DD format -f, --force - Run even if pid file exists -i, --interactive - Ask before applying any changes -m, --monitor_id - Only consider the given monitor diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 61b9b1b82..67bbc294b 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -93,9 +93,8 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my @daemons = ( 'zmc', 'zma', - 'zmaudit.pl', - 'zmcontrol.pl', 'zmfilter.pl', + 'zmaudit.pl', 'zmtrigger.pl', 'zmx10.pl', 'zmwatch.pl', @@ -106,7 +105,7 @@ my @daemons = ( ); if ( $Config{ZM_OPT_USE_EVENTNOTIFICATION} ) { - push @daemons, 'zmeventnotification.pl'; + push @daemons,'zmeventnotification.pl'; } my $command = shift @ARGV; From 63982a62824ac97d5b2a6f520e75446a5f3f36ea Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 18 Mar 2019 14:42:05 -0400 Subject: [PATCH 215/310] Disable PRIVACY enable --- scripts/zmupdate.pl.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index 8517e0277..bb9dddac4 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -923,10 +923,10 @@ if ( $version ) { die( "Can't find upgrade from version '$version'" ); } # Re-enable the privacy popup after each upgrade - my $sql = "update Config set Value = 1 where Name = 'ZM_SHOW_PRIVACY'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); + #my $sql = "update Config set Value = 1 where Name = 'ZM_SHOW_PRIVACY'"; + #my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + #my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() ); + #$sth->finish(); print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" ); } zmDbDisconnect(); From ad5f6a87294d2fb2b1bc7e5cdbe322dd98951b38 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 18 Mar 2019 14:49:05 -0400 Subject: [PATCH 216/310] Camera reboot function (#2554) * Adding a button for camera reboot function This series of commits will add a camera reboot function to the control interface if supported by the camera configuration. * Adding reboot function option to contorl configuration This patch adds a reboot option to the camera control configuration view. * Adding Reboot field to Controls table This patch adds a Reboot field to the Controls table to support a camera reboot control option. * Correcting button value to match reset * Updating language files I'm not sure of the proper procedure to trigger updating of non-english language files so I'm updating them all with English hoping that that will draw attention to the changes and others will translate accordingly. * Add missing forward slash --- db/zm_create.sql.in | 1 + scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm | 2 +- web/lang/ba_ba.php | 1 + web/lang/big5_big5.php | 1 + web/lang/cn_zh.php | 1 + web/lang/cs_cz.php | 1 + web/lang/de_de.php | 1 + web/lang/dk_dk.php | 1 + web/lang/en_gb.php | 1 + web/lang/es_ar.php | 1 + web/lang/es_es.php | 1 + web/lang/et_ee.php | 1 + web/lang/fr_fr.php | 1 + web/lang/he_il.php | 1 + web/lang/hu_hu.php | 1 + web/lang/it_it.php | 1 + web/lang/ja_jp.php | 1 + web/lang/nl_nl.php | 1 + web/lang/pl_pl.php | 1 + web/lang/pt_br.php | 1 + web/lang/ro_ro.php | 1 + web/lang/ru_ru.php | 1 + web/lang/se_se.php | 1 + web/skins/classic/includes/control_functions.php | 6 ++++++ web/skins/classic/views/controlcap.php | 2 ++ 25 files changed, 31 insertions(+), 1 deletion(-) diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index ea0b42288..4063348fc 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -68,6 +68,7 @@ CREATE TABLE `Controls` ( `CanWake` tinyint(3) unsigned NOT NULL default '0', `CanSleep` tinyint(3) unsigned NOT NULL default '0', `CanReset` tinyint(3) unsigned NOT NULL default '0', + `CanReboot` tinyint(3) unsigned NOT NULL default '0', `CanZoom` tinyint(3) unsigned NOT NULL default '0', `CanAutoZoom` tinyint(3) unsigned NOT NULL default '0', `CanZoomAbs` tinyint(3) unsigned NOT NULL default '0', diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm index 10a37e102..e274aaaf7 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm @@ -444,7 +444,7 @@ sub reboot { my $self = shift; Debug( "Camera Reboot" ); - my $cmd = "cgi-bin/magicBox.cgi?action=reboot"; + my $cmd = "/cgi-bin/magicBox.cgi?action=reboot"; $self->_sendGetRequest($cmd); } diff --git a/web/lang/ba_ba.php b/web/lang/ba_ba.php index e60e69066..02ad8e5f3 100644 --- a/web/lang/ba_ba.php +++ b/web/lang/ba_ba.php @@ -216,6 +216,7 @@ $SLANG = array( 'CanMoveRel' => 'Podržava Relativno kretanje', 'CanPan' => 'Podržava Pomak' , 'CanReset' => 'PodržavaReset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Podržava presetove', 'CanSleep' => 'Podržava Sleep', 'CanTilt' => 'Podržava nagib', diff --git a/web/lang/big5_big5.php b/web/lang/big5_big5.php index 63ffb2c1d..518cbe57c 100644 --- a/web/lang/big5_big5.php +++ b/web/lang/big5_big5.php @@ -209,6 +209,7 @@ $SLANG = array( 'CanMoveRel' => 'Can Move Relative', 'CanPan' => 'Can Pan' , 'CanReset' => 'Can Reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Can Set Presets', 'CanSleep' => 'Can Sleep', 'CanTilt' => 'Can Tilt', diff --git a/web/lang/cn_zh.php b/web/lang/cn_zh.php index 177f44377..8e2bfa8f9 100644 --- a/web/lang/cn_zh.php +++ b/web/lang/cn_zh.php @@ -205,6 +205,7 @@ $SLANG = array( 'CanMoveRel' => '可以相对移动', 'CanPan' => '可以平移' , 'CanReset' => '可以复位', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => '可以进行预设', 'CanSleep' => '可以休眠', 'CanTilt' => '可以倾斜', diff --git a/web/lang/cs_cz.php b/web/lang/cs_cz.php index a7939dbff..8e76b20ba 100644 --- a/web/lang/cs_cz.php +++ b/web/lang/cs_cz.php @@ -205,6 +205,7 @@ $SLANG = array( 'CanMoveRel' => 'Umí relativní pohyb', 'CanPan' => 'Umí otáčení', 'CanReset' => 'Umí reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Umí navolit předvolby', 'CanSleep' => 'Může spát', 'CanTilt' => 'Umí náklon', diff --git a/web/lang/de_de.php b/web/lang/de_de.php index fb7af310d..37ea4d81c 100644 --- a/web/lang/de_de.php +++ b/web/lang/de_de.php @@ -207,6 +207,7 @@ $SLANG = array( 'CanMoveRel' => 'Kann relative Bewegung', 'CanPan' => 'Kann Pan' , 'CanReset' => 'Kann Reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Kann Voreinstellungen setzen', 'CanSleep' => 'Kann Sleep', 'CanTilt' => 'Kann Neigung', diff --git a/web/lang/dk_dk.php b/web/lang/dk_dk.php index 88ee943dc..26533a7fb 100644 --- a/web/lang/dk_dk.php +++ b/web/lang/dk_dk.php @@ -206,6 +206,7 @@ $SLANG = array( 'CanMoveRel' => 'Can Move Relative', 'CanPan' => 'Can Pan' , 'CanReset' => 'Can Reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Can Set Presets', 'CanSleep' => 'Can Sleep', 'CanTilt' => 'Can Tilt', diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 05bca82d0..560a8ad82 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -216,6 +216,7 @@ $SLANG = array( 'CanMoveRel' => 'Can Move Relative', 'CanPan' => 'Can Pan' , 'CanReset' => 'Can Reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Can Set Presets', 'CanSleep' => 'Can Sleep', 'CanTilt' => 'Can Tilt', diff --git a/web/lang/es_ar.php b/web/lang/es_ar.php index e9c84af93..114002620 100644 --- a/web/lang/es_ar.php +++ b/web/lang/es_ar.php @@ -156,6 +156,7 @@ $SLANG = array( 'CanMoveRel' => 'Can Move Relative', 'CanPan' => 'Can Pan' , 'CanReset' => 'Can Reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Can Set Presets', 'CanSleep' => 'Can Sleep', 'CanTilt' => 'Can Tilt', diff --git a/web/lang/es_es.php b/web/lang/es_es.php index 7b64fdd57..767b58d97 100644 --- a/web/lang/es_es.php +++ b/web/lang/es_es.php @@ -205,6 +205,7 @@ $SLANG = array( 'CanMoveRel' => 'Puede moverse de forma relativa', 'CanPan' => 'Puede desplazarse' , 'CanReset' => 'Puede restablecerse', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Puede fefinir programaciones', 'CanSleep' => 'Puede dormirse', 'CanTilt' => 'Puede inclinarse', diff --git a/web/lang/et_ee.php b/web/lang/et_ee.php index 3a2f68ad9..19d6e777b 100644 --- a/web/lang/et_ee.php +++ b/web/lang/et_ee.php @@ -212,6 +212,7 @@ $SLANG = array( 'CanMoveRel' => 'Can Move Relative', 'CanPan' => 'Can Pan' , 'CanReset' => 'Can Reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Can Set Presets', 'CanSleep' => 'Can Sleep', 'CanTilt' => 'Can Tilt', diff --git a/web/lang/fr_fr.php b/web/lang/fr_fr.php index 46590f4aa..03ffc70ba 100644 --- a/web/lang/fr_fr.php +++ b/web/lang/fr_fr.php @@ -211,6 +211,7 @@ $SLANG = array( 'CanMoveRel' => 'Relatif', 'CanPan' => 'Panoramique' , 'CanReset' => 'RàZ', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Stockage prépos.', 'CanSleep' => 'Veille', 'CanTilt' => 'Inclinaison', diff --git a/web/lang/he_il.php b/web/lang/he_il.php index 67b7b87bd..26e8bcea6 100644 --- a/web/lang/he_il.php +++ b/web/lang/he_il.php @@ -205,6 +205,7 @@ $SLANG = array( 'CanMoveRel' => 'àôùø úæåæä éçñéú', 'CanPan' => 'Can Pan' , 'CanReset' => 'àôùø àúçåì', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Can Set Presets', 'CanSleep' => 'àôùø îöá ùéðä', 'CanTilt' => 'àôùø æòæåò', diff --git a/web/lang/hu_hu.php b/web/lang/hu_hu.php index 7c1336a87..15cf72aab 100644 --- a/web/lang/hu_hu.php +++ b/web/lang/hu_hu.php @@ -248,6 +248,7 @@ $SLANG = array( 'CanMoveRel' => 'Relatíven tud mozogni', 'CanPan' => 'Tud jobb-bal mozgást' , 'CanReset' => 'Tud alaphelyzetbe jönni', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Tud menteni profilokat', 'CanSleep' => 'Tud phihenő üzemmódot', 'CanTilt' => 'Tud fel-le mozgást', diff --git a/web/lang/it_it.php b/web/lang/it_it.php index 8890650e8..9049b3c2f 100644 --- a/web/lang/it_it.php +++ b/web/lang/it_it.php @@ -210,6 +210,7 @@ $SLANG = array( 'CanMoveRel' => 'Puo\' Mov. Relativo', 'CanPan' => 'Puo\' Pan' , 'CanReset' => 'Puo\' Reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Puo\' impostare preset', 'CanSleep' => 'Puo\' andare in sleep', 'CanTilt' => 'Puo\' Tilt', diff --git a/web/lang/ja_jp.php b/web/lang/ja_jp.php index 4d1854550..b34c9f3f0 100644 --- a/web/lang/ja_jp.php +++ b/web/lang/ja_jp.php @@ -206,6 +206,7 @@ $SLANG = array( 'CanMoveRel' => 'Can Move Relative', 'CanPan' => 'Can Pan' , 'CanReset' => 'Can Reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Can Set Presets', 'CanSleep' => 'Can Sleep', 'CanTilt' => 'Can Tilt', diff --git a/web/lang/nl_nl.php b/web/lang/nl_nl.php index a50fdd082..a2003b1c3 100644 --- a/web/lang/nl_nl.php +++ b/web/lang/nl_nl.php @@ -206,6 +206,7 @@ $SLANG = array( 'CanMoveRel' => 'Can Move Relatief', 'CanPan' => 'Can Pan' , 'CanReset' => 'Can Reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Can Set Presets', 'CanSleep' => 'Can Sleep', 'CanTilt' => 'Can Tilt', diff --git a/web/lang/pl_pl.php b/web/lang/pl_pl.php index 48c34b1a3..d41242317 100644 --- a/web/lang/pl_pl.php +++ b/web/lang/pl_pl.php @@ -220,6 +220,7 @@ $SLANG = array( 'CanMoveRel' => 'Can Move Relative', 'CanPan' => 'Can Pan' , 'CanReset' => 'Can Reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Can Set Presets', 'CanSleep' => 'Can Sleep', 'CanTilt' => 'Can Tilt', diff --git a/web/lang/pt_br.php b/web/lang/pt_br.php index ec4807765..3d5a2cf83 100644 --- a/web/lang/pt_br.php +++ b/web/lang/pt_br.php @@ -145,6 +145,7 @@ $SLANG = array( 'CanMoveRel' => 'Can Move Relative', 'CanPan' => 'Can Pan' , 'CanReset' => 'Can Reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Can Set Presets', 'CanSleep' => 'Can Sleep', 'CanTilt' => 'Can Tilt', diff --git a/web/lang/ro_ro.php b/web/lang/ro_ro.php index 5526be753..97e07b836 100644 --- a/web/lang/ro_ro.php +++ b/web/lang/ro_ro.php @@ -176,6 +176,7 @@ $SLANG = array( 'CanMoveRel' => 'Mişcare relativă', 'CanPan' => 'Rotativ' , 'CanReset' => 'Can Reset', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Can Set Presets', 'CanSleep' => 'Can Sleep', 'CanTilt' => 'Se poate înclina', diff --git a/web/lang/ru_ru.php b/web/lang/ru_ru.php index 0416f3358..11110745e 100644 --- a/web/lang/ru_ru.php +++ b/web/lang/ru_ru.php @@ -206,6 +206,7 @@ $SLANG = array( 'CanMoveRel' => 'Относительное перемещение', 'CanPan' => 'Панорама' , 'CanReset' => 'Сброс', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Создание предустановок', 'CanSleep' => 'Сон', 'CanTilt' => 'Наклон', diff --git a/web/lang/se_se.php b/web/lang/se_se.php index 995e88156..4245ae974 100644 --- a/web/lang/se_se.php +++ b/web/lang/se_se.php @@ -206,6 +206,7 @@ $SLANG = array( 'CanMoveRel' => 'Har relativ förflyttning', 'CanPan' => 'Har panorering', 'CanReset' => 'Har återställning', + 'CanReboot' => 'Can Reboot', 'CanSetPresets' => 'Har förinställningar', 'CanSleep' => 'Kan vila', 'CanTilt' => 'Kan tilta', diff --git a/web/skins/classic/includes/control_functions.php b/web/skins/classic/includes/control_functions.php index b2a42a1d4..2c3fbd73a 100644 --- a/web/skins/classic/includes/control_functions.php +++ b/web/skins/classic/includes/control_functions.php @@ -24,6 +24,7 @@ function getControlCommands( $monitor ) { $cmds['Wake'] = 'wake'; $cmds['Sleep'] = 'sleep'; $cmds['Reset'] = 'reset'; + $cmds['Reboot'] = 'reboot'; $cmds['PresetSet'] = 'presetSet'; $cmds['PresetGoto'] = 'presetGoto'; @@ -319,6 +320,11 @@ function controlPower( $monitor, $cmds ) { if ( $monitor->CanReset() ) { ?> +CanReboot() ) { +?> + diff --git a/web/skins/classic/views/controlcap.php b/web/skins/classic/views/controlcap.php index 546c27a93..bc48d05e4 100644 --- a/web/skins/classic/views/controlcap.php +++ b/web/skins/classic/views/controlcap.php @@ -59,6 +59,7 @@ else 'CanWake' => "", 'CanSleep' => "", 'CanReset' => "", + 'CanReboot' => "", 'CanMove' => "", 'CanMoveDiag' => "", 'CanMoveMap' => "", @@ -352,6 +353,7 @@ switch ( $tab )
+ Date: Mon, 18 Mar 2019 17:13:19 -0400 Subject: [PATCH 217/310] spacing --- scripts/zmaudit.pl.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 45035ca23..851921db2 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -1005,17 +1005,17 @@ sub delete_empty_directories { Error("delete_empty_directories: Can't open directory '/$_[0]': $!" ); return; } - my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR ); + my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir($DIR); #Debug("delete_empty_directories $_[0] has " . @contents .' entries:' . ( @contents <= 2 ? join(',',@contents) : '' )); my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents; if ( @dirs ) { - Debug("Have " . @dirs . " dirs in $_[0]"); + Debug('Have ' . @dirs . " dirs in $_[0]"); foreach ( @dirs ) { - delete_empty_directories( $_[0].'/'.$_ ); + delete_empty_directories($_[0].'/'.$_); } #Reload, since we may now be empty rewinddir $DIR; - @contents = map { ($_ eq '.' or $_ eq '..') ? () : $_ } readdir( $DIR ); + @contents = map { ($_ eq '.' or $_ eq '..') ? () : $_ } readdir($DIR); } closedir($DIR); if ( ! @contents ) { From 1d3af44d0277dfcdfe27f42587262f5df35f1313 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 19 Mar 2019 09:13:56 -0400 Subject: [PATCH 218/310] Fix namespace Warning --- web/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/index.php b/web/index.php index 956ce8122..3ee1836ca 100644 --- a/web/index.php +++ b/web/index.php @@ -158,7 +158,7 @@ CORSHeaders(); // Check for valid content dirs if ( !is_writable(ZM_DIR_EVENTS) ) { - Warning("Cannot write to event folder ".ZM_DIR_EVENTS.". Check that it exists and is owned by the web account user."); + ZM\Warning("Cannot write to event folder ".ZM_DIR_EVENTS.". Check that it exists and is owned by the web account user."); } # Globals From 2c1c9fe6cd347ceecabcc1e41a533fab5e618675 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 19 Mar 2019 09:23:35 -0400 Subject: [PATCH 219/310] fix missing ZM namespaces --- web/index.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/index.php b/web/index.php index 99e77d285..0f91180e3 100644 --- a/web/index.php +++ b/web/index.php @@ -71,7 +71,7 @@ define('ZM_BASE_URL', ''); require_once('includes/functions.php'); if ( $_SERVER['REQUEST_METHOD'] == 'OPTIONS' ) { - Logger::Debug("OPTIONS Method, only doing CORS"); + ZM\Logger::Debug("OPTIONS Method, only doing CORS"); # Add Cross domain access headers CORSHeaders(); return; @@ -158,7 +158,7 @@ CORSHeaders(); // Check for valid content dirs if ( !is_writable(ZM_DIR_EVENTS) ) { - Warning("Cannot write to event folder ".ZM_DIR_EVENTS.". Check that it exists and is owned by the web account user."); + ZM\Warning("Cannot write to event folder ".ZM_DIR_EVENTS.". Check that it exists and is owned by the web account user."); } # Globals @@ -180,7 +180,7 @@ if ( isset($_REQUEST['request']) ) require_once('includes/auth.php'); foreach ( getSkinIncludes('skin.php') as $includeFile ) { - #Logger::Debug("including $includeFile"); + #ZM\Logger::Debug("including $includeFile"); require_once $includeFile; } From 72b87a7c004494be7d6858583eec446fef6e0a9b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 19 Mar 2019 09:36:58 -0400 Subject: [PATCH 220/310] Add code to be a bit more careful about not deleting all events when an incomplete event object is used. --- web/includes/Event.php | 46 ++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/web/includes/Event.php b/web/includes/Event.php index 227dc6842..944e55248 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -122,7 +122,12 @@ class Event { public function Path() { $Storage = $this->Storage(); - return $Storage->Path().'/'.$this->Relative_Path(); + if ( $Storage->Path() and $this->Relative_Path() ) { + return $Storage->Path().'/'.$this->Relative_Path(); + } else { + Error("Event Path not complete. Storage: " . $Storage->Path() . " relative: " . $this->Relative_Path()); + return ''; + } } public function Relative_Path() { @@ -148,17 +153,19 @@ class Event { } public function delete() { - # This wouldn't work with foreign keys - dbQuery( 'DELETE FROM Events WHERE Id = ?', array($this->{'Id'}) ); + if ( ! $this->{'Id'} ) { + Error('Event delete on event with empty Id'); + return; + } if ( !ZM_OPT_FAST_DELETE ) { - dbQuery( 'DELETE FROM Stats WHERE EventId = ?', array($this->{'Id'}) ); - dbQuery( 'DELETE FROM Frames WHERE EventId = ?', array($this->{'Id'}) ); + dbQuery('DELETE FROM Stats WHERE EventId = ?', array($this->{'Id'})); + dbQuery('DELETE FROM Frames WHERE EventId = ?', array($this->{'Id'})); if ( $this->{'Scheme'} == 'Deep' ) { # Assumption: All events have a start time - $start_date = date_parse( $this->{'StartTime'} ); + $start_date = date_parse($this->{'StartTime'}); if ( ! $start_date ) { - Error('Unable to parse start time for event ' . $this->{'Id'} . ' not deleting files.' ); + Error('Unable to parse start time for event ' . $this->{'Id'} . ' not deleting files.'); return; } $start_date['year'] = $start_date['year'] % 100; @@ -166,37 +173,42 @@ class Event { # So this is because ZM creates a link under the day pointing to the time that the event happened. $link_path = $this->Link_Path(); if ( ! $link_path ) { - Error('Unable to determine link path for event ' . $this->{'Id'} . ' not deleting files.' ); + Error('Unable to determine link path for event ' . $this->{'Id'} . ' not deleting files.'); return; } $Storage = $this->Storage(); $eventlink_path = $Storage->Path().'/'.$link_path; - if ( $id_files = glob( $eventlink_path ) ) { + if ( $id_files = glob($eventlink_path) ) { if ( ! $eventPath = readlink($id_files[0]) ) { Error("Unable to read link at $id_files[0]"); return; } # I know we are using arrays here, but really there can only ever be 1 in the array $eventPath = preg_replace( '/\.'.$this->{'Id'}.'$/', $eventPath, $id_files[0] ); - deletePath( $eventPath ); - deletePath( $id_files[0] ); - $pathParts = explode( '/', $eventPath ); + deletePath($eventPath); + deletePath($id_files[0]); + $pathParts = explode('/', $eventPath); for ( $i = count($pathParts)-1; $i >= 2; $i-- ) { - $deletePath = join( '/', array_slice( $pathParts, 0, $i ) ); - if ( !glob( $deletePath."/*" ) ) { - deletePath( $deletePath ); + $deletePath = join('/', array_slice($pathParts, 0, $i)); + if ( !glob($deletePath.'/*') ) { + deletePath($deletePath); } } } else { - Warning( "Found no event files under $eventlink_path" ); + Warning("Found no event files under $eventlink_path"); } # end if found files } else { $eventPath = $this->Path(); - deletePath( $eventPath ); + if ( ! $eventPath ) { + Error("No event Path in Event delete. Not deleting"); + return; + } + deletePath($eventPath); } # USE_DEEP_STORAGE OR NOT } # ! ZM_OPT_FAST_DELETE + dbQuery('DELETE FROM Events WHERE Id = ?', array($this->{'Id'})); } # end Event->delete public function getStreamSrc( $args=array(), $querySep='&' ) { From 428f7e8e8fa9a94789fa849325d58a2719422ad3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 19 Mar 2019 10:24:30 -0400 Subject: [PATCH 221/310] create setup_onclick and disable_onclick in the monitor object. Use it to setup and disable the click event when editing/cancelling layout editing --- web/skins/classic/views/js/montage.js | 33 ++++++++++++++++++++++----- web/skins/classic/views/montage.php | 5 +--- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index 781adbd31..0ad9a5355 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -8,7 +8,6 @@ function Monitor( monitorData ) { this.alarmState = STATE_IDLE; this.lastAlarmState = STATE_IDLE; this.streamCmdParms = 'view=request&request=stream&connkey='+this.connKey; - this.onclick = monitorData.onclick; if ( auth_hash ) { this.streamCmdParms += '&auth='+auth_hash; } @@ -23,6 +22,27 @@ function Monitor( monitorData ) { } }; + this.onclick = function() { + var el = this; + var url = '?view=watch&mid='+this.id; + var name = 'zmWatch'+this.id; + var tag = 'watch'; + var width = el.getAttribute("data-window-width"); + var height = el.getAttribute("data-window-height"); + evt.preventDefault(); + createPopup(url, name, tag, width, height); + }; + + this.setup_onclick = function() { + document.querySelectorAll('#imageFeed'+this.id).forEach(function(el) { + el.addEventListener('click', this.onclick); + }); + } + this.disable_onclick = function() { + document.querySelectorAll('#imageFeed'+this.id).forEach(function(el) { + el.removeEventListener('click',this.onclick); + }); + } this.setStateClass = function( element, stateClass ) { if ( !element.hasClass( stateClass ) ) { @@ -359,8 +379,7 @@ function edit_layout(button) { for ( var i = 0; i < monitors.length; i++ ) { var monitor = monitors[i]; - monitor_feed = $j('#imageFeed'+monitor.id)[0]; - monitor_feed.onclick=''; + monitor.disable_onclick(); }; $j('#monitors .monitorFrame').draggable({ @@ -372,7 +391,7 @@ function edit_layout(button) { } // end function edit_layout function save_layout(button) { - var form=button.form; + 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++ ) { @@ -390,7 +409,7 @@ function save_layout(button) { float: monitor_frame.css('float'), }; } // end foreach monitor - form.Positions.value = JSON.stringify( Positions ); + form.Positions.value = JSON.stringify(Positions); form.submit(); } function cancel_layout(button) { @@ -399,7 +418,7 @@ function cancel_layout(button) { for ( var i = 0; i < monitors.length; i++ ) { var monitor = monitors[i]; monitor_feed = $j('#imageFeed'+monitor.id); - monitor_feed.click( monitor.onclick ); + monitor_feed.click(monitor.onclick); }; selectLayout('#zmMontageLayout'); } @@ -431,6 +450,7 @@ function initPage() { if ( monitors[i].type == 'WebSite' && interval > 0 ) { setInterval(reloadWebSite, interval*1000, i); } + monitors[i].setup_onclick(); } selectLayout('#zmMontageLayout'); @@ -444,6 +464,7 @@ function initPage() { var delay = Math.round( (Math.random()+0.75)*statusRefreshTimeout ); console.log("Delay for monitor " + monitorData[i].id + " is " + delay ); monitors[i].streamCmdQuery.delay( delay, monitors[i] ); + //monitors[i].zm_startup(delay); } } diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index 7df2a63b3..e589d2206 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -199,10 +199,7 @@ foreach ( $monitors as $monitor ) {
DefaultVideo() ) { +if ( ($codec == 'MP4' || $codec == 'auto' ) && $Event->DefaultVideo() ) { ?>
- - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From e6547953b060f9ba6e6543dea4a9dfe79c88f754 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 28 Mar 2019 09:24:32 -0400 Subject: [PATCH 266/310] fix segfault using old ffmpeg --- src/zm_videostore.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 4abf5998a..5ae3404b7 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -114,8 +114,7 @@ VideoStore::VideoStore( #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) //video_out_stream->codec = avcodec_alloc_context3(video_out_codec); // Since we are not re-encoding, all we have to do is copy the parameters - video_out_ctx = video_out_stream->codec; - //video_out_ctx = avcodec_alloc_context3(video_out_codec); + video_out_ctx = avcodec_alloc_context3(video_out_codec); // Copy params from instream to ctx ret = avcodec_parameters_to_context(video_out_ctx, video_in_stream->codecpar); if ( ret < 0 ) { @@ -125,6 +124,7 @@ VideoStore::VideoStore( zm_dump_codec(video_out_ctx); } #else + 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", From 9a70dff1439dde2897c37c9b5ce7cb63c926e25b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 28 Mar 2019 09:43:17 -0400 Subject: [PATCH 267/310] to single quotes, include monitor Id when logging the closing of an event --- scripts/zmaudit.pl.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 851921db2..41c8b0e75 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -766,7 +766,7 @@ FROM Frames WHERE EventId=?'; $res = $selectUnclosedEventsSth->execute() or Fatal( "Can't execute: ".$selectUnclosedEventsSth->errstr() ); while( my $event = $selectUnclosedEventsSth->fetchrow_hashref() ) { - aud_print( "Found open event '$event->{Id}' at $$event{StartTime}" ); + aud_print( "Found open event '$event->{Id}' on Monitor $event->{MonitorId} at $$event{StartTime}" ); if ( confirm( 'close', 'closing' ) ) { if ( ! ( $res = $selectFrameDataSth->execute($event->{Id}) ) ) { Error( "Can't execute: $selectFrameDataSql:".$selectFrameDataSth->errstr() ); @@ -792,13 +792,13 @@ FROM Frames WHERE EventId=?'; $frame->{MaxScore}, RECOVER_TEXT, $event->{Id} - ) or Error( "Can't execute: ".$updateUnclosedEventsSth->errstr() ); + ) or Error( 'Can\'t execute: '.$updateUnclosedEventsSth->errstr() ); } else { Error('SHOULD DELETE'); } # end if has frame data } } # end while unclosed event - Debug("Done closing open events."); + Debug('Done closing open events.'); # Now delete any old image files if ( my @old_files = grep { -M > $max_image_age } <$image_path/*.{jpg,gif,wbmp}> ) { From 49e3f0a68eda1a005d30a3a9ee2e756c05011412 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 28 Mar 2019 09:43:31 -0400 Subject: [PATCH 268/310] eslint fixes --- web/includes/csrf/csrf-magic.js | 310 +++++++++++++------------- web/skins/classic/views/js/export.js | 10 +- web/skins/classic/views/js/montage.js | 2 +- 3 files changed, 162 insertions(+), 160 deletions(-) diff --git a/web/includes/csrf/csrf-magic.js b/web/includes/csrf/csrf-magic.js index 0989c1065..06ed62076 100644 --- a/web/includes/csrf/csrf-magic.js +++ b/web/includes/csrf/csrf-magic.js @@ -9,183 +9,185 @@ // The wrapper must be set BEFORE onreadystatechange is written to, since // a bug in ActiveXObject prevents us from properly testing for it. CsrfMagic = function(real) { - // try to make it ourselves, if you didn't pass it - if (!real) try { real = new XMLHttpRequest; } catch (e) {;} - if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) {;} - if (!real) try { real = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) {;} - if (!real) try { real = new ActiveXObject('Msxml2.XMLHTTP.4.0'); } catch (e) {;} - this.csrf = real; - // properties - var csrfMagic = this; - real.onreadystatechange = function() { - csrfMagic._updateProps(); - return csrfMagic.onreadystatechange ? csrfMagic.onreadystatechange() : null; - }; + // try to make it ourselves, if you didn't pass it + if (!real) try {real = new XMLHttpRequest;} catch (e) {;} + if (!real) try {real = new ActiveXObject('Msxml2.XMLHTTP');} catch (e) {;} + if (!real) try {real = new ActiveXObject('Microsoft.XMLHTTP');} catch (e) {;} + if (!real) try {real = new ActiveXObject('Msxml2.XMLHTTP.4.0');} catch (e) {;} + this.csrf = real; + // properties + var csrfMagic = this; + real.onreadystatechange = function() { csrfMagic._updateProps(); -} + return csrfMagic.onreadystatechange ? csrfMagic.onreadystatechange() : null; + }; + csrfMagic._updateProps(); +}; CsrfMagic.prototype = { - open: function(method, url, async, username, password) { - if (method == 'POST') this.csrf_isPost = true; - // deal with Opera bug, thanks jQuery - if (username) return this.csrf_open(method, url, async, username, password); - else return this.csrf_open(method, url, async); - }, - csrf_open: function(method, url, async, username, password) { - if (username) return this.csrf.open(method, url, async, username, password); - else return this.csrf.open(method, url, async); - }, + open: function(method, url, async, username, password) { + if (method == 'POST') this.csrf_isPost = true; + // deal with Opera bug, thanks jQuery + if (username) return this.csrf_open(method, url, async, username, password); + else return this.csrf_open(method, url, async); + }, + csrf_open: function(method, url, async, username, password) { + if (username) return this.csrf.open(method, url, async, username, password); + else return this.csrf.open(method, url, async); + }, - send: function(data) { - if (!this.csrf_isPost) return this.csrf_send(data); - prepend = csrfMagicName + '=' + csrfMagicToken + '&'; + send: function(data) { + if (!this.csrf_isPost) return this.csrf_send(data); + prepend = csrfMagicName + '=' + csrfMagicToken + '&'; // XXX: Removed to eliminate 'Refused to set unsafe header "Content-length" ' errors in modern browsers // if (this.csrf_purportedLength === undefined) { // this.csrf_setRequestHeader("Content-length", this.csrf_purportedLength + prepend.length); // delete this.csrf_purportedLength; // } - delete this.csrf_isPost; - return this.csrf_send(prepend + data); - }, - csrf_send: function(data) { - return this.csrf.send(data); - }, + delete this.csrf_isPost; + return this.csrf_send(prepend + data); + }, + csrf_send: function(data) { + return this.csrf.send(data); + }, - setRequestHeader: function(header, value) { - // We have to auto-set this at the end, since we don't know how long the - // nonce is when added to the data. - if (this.csrf_isPost && header == "Content-length") { - this.csrf_purportedLength = value; - return; - } - return this.csrf_setRequestHeader(header, value); - }, - csrf_setRequestHeader: function(header, value) { - return this.csrf.setRequestHeader(header, value); - }, + setRequestHeader: function(header, value) { + // We have to auto-set this at the end, since we don't know how long the + // nonce is when added to the data. + if (this.csrf_isPost && header == "Content-length") { + this.csrf_purportedLength = value; + return; + } + return this.csrf_setRequestHeader(header, value); + }, + csrf_setRequestHeader: function(header, value) { + return this.csrf.setRequestHeader(header, value); + }, - abort: function() { - return this.csrf.abort(); - }, - getAllResponseHeaders: function() { - return this.csrf.getAllResponseHeaders(); - }, - getResponseHeader: function(header) { - return this.csrf.getResponseHeader(header); - } // , -} + abort: function() { + return this.csrf.abort(); + }, + getAllResponseHeaders: function() { + return this.csrf.getAllResponseHeaders(); + }, + getResponseHeader: function(header) { + return this.csrf.getResponseHeader(header); + } // , +}; // proprietary CsrfMagic.prototype._updateProps = function() { - this.readyState = this.csrf.readyState; - if (this.readyState == 4) { - this.responseText = this.csrf.responseText; - this.responseXML = this.csrf.responseXML; - this.status = this.csrf.status; - this.statusText = this.csrf.statusText; - } -} + this.readyState = this.csrf.readyState; + if (this.readyState == 4) { + this.responseText = this.csrf.responseText; + this.responseXML = this.csrf.responseXML; + this.status = this.csrf.status; + this.statusText = this.csrf.statusText; + } +}; + CsrfMagic.process = function(base) { - if(typeof base == 'object') { - base[csrfMagicName] = csrfMagicToken; - return base; - } - var prepend = csrfMagicName + '=' + csrfMagicToken; - if (base) return prepend + '&' + base; - return prepend; -} + if ( typeof base == 'object' ) { + base[csrfMagicName] = csrfMagicToken; + return base; + } + var prepend = csrfMagicName + '=' + csrfMagicToken; + if ( base ) return prepend + '&' + base; + return prepend; +}; + // callback function for when everything on the page has loaded CsrfMagic.end = function() { - // This rewrites forms AGAIN, so in case buffering didn't work this - // certainly will. - forms = document.getElementsByTagName('form'); - for (var i = 0; i < forms.length; i++) { - form = forms[i]; - if (form.method.toUpperCase() !== 'POST') continue; - if (form.elements[csrfMagicName]) continue; - var input = document.createElement('input'); - input.setAttribute('name', csrfMagicName); - input.setAttribute('value', csrfMagicToken); - input.setAttribute('type', 'hidden'); - form.appendChild(input); - } -} + // This rewrites forms AGAIN, so in case buffering didn't work this + // certainly will. + forms = document.getElementsByTagName('form'); + for (var i = 0; i < forms.length; i++) { + form = forms[i]; + if (form.method.toUpperCase() !== 'POST') continue; + if (form.elements[csrfMagicName]) continue; + var input = document.createElement('input'); + input.setAttribute('name', csrfMagicName); + input.setAttribute('value', csrfMagicToken); + input.setAttribute('type', 'hidden'); + form.appendChild(input); + } +}; // Sets things up for Mozilla/Opera/nice browsers // We very specifically match against Internet Explorer, since they haven't // implemented prototypes correctly yet. -if (window.XMLHttpRequest && window.XMLHttpRequest.prototype && '\v' != 'v') { - var x = XMLHttpRequest.prototype; - var c = CsrfMagic.prototype; +if ( window.XMLHttpRequest && window.XMLHttpRequest.prototype && '\v' != 'v' ) { + var x = XMLHttpRequest.prototype; + var c = CsrfMagic.prototype; - // Save the original functions - x.csrf_open = x.open; - x.csrf_send = x.send; - x.csrf_setRequestHeader = x.setRequestHeader; + // Save the original functions + x.csrf_open = x.open; + x.csrf_send = x.send; + x.csrf_setRequestHeader = x.setRequestHeader; - // Notice that CsrfMagic is itself an instantiatable object, but only - // open, send and setRequestHeader are necessary as decorators. - x.open = c.open; - x.send = c.send; - x.setRequestHeader = c.setRequestHeader; + // Notice that CsrfMagic is itself an instantiatable object, but only + // open, send and setRequestHeader are necessary as decorators. + x.open = c.open; + x.send = c.send; + x.setRequestHeader = c.setRequestHeader; } else { - // The only way we can do this is by modifying a library you have been - // using. We support YUI, script.aculo.us, prototype, MooTools, - // jQuery, Ext and Dojo. - if (window.jQuery) { - // jQuery didn't implement a new XMLHttpRequest function, so we have - // to do this the hard way. - jQuery.csrf_ajax = jQuery.ajax; - jQuery.ajax = function( s ) { - if (s.type && s.type.toUpperCase() == 'POST') { - s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s)); - if ( s.data && s.processData && typeof s.data != "string" ) { - s.data = jQuery.param(s.data); - } - s.data = CsrfMagic.process(s.data); - } - return jQuery.csrf_ajax( s ); + // The only way we can do this is by modifying a library you have been + // using. We support YUI, script.aculo.us, prototype, MooTools, + // jQuery, Ext and Dojo. + if ( window.jQuery ) { + // jQuery didn't implement a new XMLHttpRequest function, so we have + // to do this the hard way. + jQuery.csrf_ajax = jQuery.ajax; + jQuery.ajax = function( s ) { + if (s.type && s.type.toUpperCase() == 'POST') { + s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s)); + if ( s.data && s.processData && typeof s.data != "string" ) { + s.data = jQuery.param(s.data); } - } - if (window.Prototype) { - // This works for script.aculo.us too - Ajax.csrf_getTransport = Ajax.getTransport; - Ajax.getTransport = function() { - return new CsrfMagic(Ajax.csrf_getTransport()); - } - } - if (window.MooTools) { - Browser.csrf_Request = Browser.Request; - Browser.Request = function () { - return new CsrfMagic(Browser.csrf_Request()); - } - } - if (window.YAHOO) { - // old YUI API - YAHOO.util.Connect.csrf_createXhrObject = YAHOO.util.Connect.createXhrObject; - YAHOO.util.Connect.createXhrObject = function (transaction) { - obj = YAHOO.util.Connect.csrf_createXhrObject(transaction); - obj.conn = new CsrfMagic(obj.conn); - return obj; - } - } - if (window.Ext) { - // Ext can use other js libraries as loaders, so it has to come last - // Ext's implementation is pretty identical to Yahoo's, but we duplicate - // it for comprehensiveness's sake. - Ext.lib.Ajax.csrf_createXhrObject = Ext.lib.Ajax.createXhrObject; - Ext.lib.Ajax.createXhrObject = function (transaction) { - obj = Ext.lib.Ajax.csrf_createXhrObject(transaction); - obj.conn = new CsrfMagic(obj.conn); - return obj; - } - } - if (window.dojo) { - // NOTE: this doesn't work with latest dojo - dojo.csrf__xhrObj = dojo._xhrObj; - dojo._xhrObj = function () { - return new CsrfMagic(dojo.csrf__xhrObj()); - } - } -} + s.data = CsrfMagic.process(s.data); + } + return jQuery.csrf_ajax(s); + }; + } + if ( window.Prototype ) { + // This works for script.aculo.us too + Ajax.csrf_getTransport = Ajax.getTransport; + Ajax.getTransport = function() { + return new CsrfMagic(Ajax.csrf_getTransport()); + }; + } + if ( window.MooTools ) { + Browser.csrf_Request = Browser.Request; + Browser.Request = function() { + return new CsrfMagic(Browser.csrf_Request()); + }; + } + if ( window.YAHOO ) { + // old YUI API + YAHOO.util.Connect.csrf_createXhrObject = YAHOO.util.Connect.createXhrObject; + YAHOO.util.Connect.createXhrObject = function(transaction) { + obj = YAHOO.util.Connect.csrf_createXhrObject(transaction); + obj.conn = new CsrfMagic(obj.conn); + return obj; + }; + } + if ( window.Ext ) { + // Ext can use other js libraries as loaders, so it has to come last + // Ext's implementation is pretty identical to Yahoo's, but we duplicate + // it for comprehensiveness's sake. + Ext.lib.Ajax.csrf_createXhrObject = Ext.lib.Ajax.createXhrObject; + Ext.lib.Ajax.createXhrObject = function(transaction) { + obj = Ext.lib.Ajax.csrf_createXhrObject(transaction); + obj.conn = new CsrfMagic(obj.conn); + return obj; + }; + } + if ( window.dojo ) { + // NOTE: this doesn't work with latest dojo + dojo.csrf__xhrObj = dojo._xhrObj; + dojo._xhrObj = function() { + return new CsrfMagic(dojo.csrf__xhrObj()); + }; + } +}; diff --git a/web/skins/classic/views/js/export.js b/web/skins/classic/views/js/export.js index 9dcd7bfeb..74e7d029b 100644 --- a/web/skins/classic/views/js/export.js +++ b/web/skins/classic/views/js/export.js @@ -47,13 +47,13 @@ function exportResponse(respObj, respText) { return; if ( 0 ) { - var eids = new Array(); - for (var i = 0, len=form.elements.length; i < len; i++) { - if ( form.elements[i].name == 'eids[]' ) { - eids[eids.length] = 'eids[]='+form.elements[i].value; + var eids = new Array(); + for (var i = 0, len=form.elements.length; i < len; i++) { + if ( form.elements[i].name == 'eids[]' ) { + eids[eids.length] = 'eids[]='+form.elements[i].value; + } } } - } form.submit(); //window.location.replace( thisUrl+'?view='+currentView+'&'+eids.join('&')+'&exportFile='+respObj.exportFile+'&generated='+((respObj.result=='Ok')?1:0) ); diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index 8b5bf12e2..23d2f32ec 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -27,7 +27,7 @@ function Monitor(monitorData) { this.eventHandler = function( event ) { console.log(event); - } + }; this.onclick = function(evt) { var el = evt.currentTarget; From d579faa2913510e4a250d55cdea44606bed26fc0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 28 Mar 2019 15:13:53 -0400 Subject: [PATCH 269/310] attr in modern ZM should be StartWeekday. Continue to support legacy filters --- scripts/ZoneMinder/lib/ZoneMinder/Filter.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index ccace879a..a057f55d9 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -177,7 +177,7 @@ sub Sql { $self->{Sql} .= 'to_days( E.StartTime )'; } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' ) { $self->{Sql} .= 'extract( hour_second from E.StartTime )'; - } elsif ( $term->{attr} eq 'Weekday' ) { + } elsif ( $term->{attr} eq 'Weekday' or $term->{attr} eq 'StartWeekday' ) { $self->{Sql} .= 'weekday( E.StartTime )'; # EndTIme options From 8556e13504fd808cdd6974b9bbc27b205b1345bf Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Sat, 30 Mar 2019 08:16:46 -0500 Subject: [PATCH 270/310] Update zoneminder.spec --- distros/redhat/zoneminder.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 8f30d6fee..7112fd4c0 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -23,7 +23,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.33.0 +Version: 1.33.4 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons @@ -409,6 +409,9 @@ EOF %dir %attr(755,nginx,nginx) %{_localstatedir}/spool/zoneminder-upload %changelog +* Sat Mar 30 2018 Andrew Bauer - 1.33.4-1 +- Bump tp 1.33.4 Development + * Tue Dec 11 2018 Andrew Bauer - 1.33.0-1 - Bump tp 1.33.0 Development From e3caf38a72359911e46c448e0d7a45920c8b8337 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Sat, 30 Mar 2019 08:20:18 -0500 Subject: [PATCH 271/310] Update zoneminder.spec --- distros/redhat/zoneminder.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 7112fd4c0..3678c2da0 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -409,7 +409,7 @@ EOF %dir %attr(755,nginx,nginx) %{_localstatedir}/spool/zoneminder-upload %changelog -* Sat Mar 30 2018 Andrew Bauer - 1.33.4-1 +* Sat Mar 30 2019 Andrew Bauer - 1.33.4-1 - Bump tp 1.33.4 Development * Tue Dec 11 2018 Andrew Bauer - 1.33.0-1 From 4e5ed682322d2b5bb24a3c7e9bd5c7f25946d912 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Sat, 30 Mar 2019 08:36:45 -0500 Subject: [PATCH 272/310] update redhat install guide --- docs/installationguide/redhat.rst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/installationguide/redhat.rst b/docs/installationguide/redhat.rst index 71b4f5278..5b5067058 100644 --- a/docs/installationguide/redhat.rst +++ b/docs/installationguide/redhat.rst @@ -158,7 +158,7 @@ The list of available Mock config files are available here: ls /etc/mock/*rpmfusion_free.cfg -You choose the config file based on the desired distro (e.g. el6, el7, f20, f21) and basearch (e.g. x86, x86_64, arhmhfp). Notice that, when specifying the Mock config as a commandline parameter, you should leave off the ".cfg" filename extension. +You choose the config file based on the desired distro (e.g. el7, f29, f30) and basearch (e.g. x86, x86_64, arhmhfp). Notice that, when specifying the Mock config as a commandline parameter, you should leave off the ".cfg" filename extension. Installation ************ @@ -188,8 +188,8 @@ Now clone the ZoneMinder git repository from your home folder: :: cd - git clone https://github.com/ZoneMinder/ZoneMinder - cd ZoneMinder + git clone https://github.com/ZoneMinder/zoneminder + cd zoneminder This will create a sub-folder called ZoneMinder, which will contain the latest development source code. @@ -197,27 +197,27 @@ If you have previsouly cloned the ZoneMinder git repo and wish to update it to t :: - cd ~/ZoneMinder + cd ~/zoneminder git pull origin master Get the crud submodule tarball: :: - spectool -f -g -R -s 1 ~/ZoneMinder/distros/redhat/zoneminder.spec + spectool -f -g -R -s 1 ~/zoneminder/distros/redhat/zoneminder.spec At this point, you can make changes to the source code. Depending on what you want to do with those changes, you generally want to create a new branch first: :: - cd ~/ZoneMinder + cd ~/zoneminder git checkout -b mynewbranch Again, depending on what you want to do with those changes, you may want to commit your changes: :: - cd ~/ZoneMinder + cd ~/zoneminder git add . git commit @@ -225,28 +225,28 @@ Once you have made your changes, it is time to turn your work into a new tarball :: - less ~/ZoneMinder/distros/redhat/zoneminder.spec + less ~/zoneminder/distros/redhat/zoneminder.spec Scroll down until you see the Version field. Note the value, which will be in the format x.xx.x. Now create the tarball with the following command: :: - cd ~/ZoneMinder - git archive --prefix=ZoneMinder-1.31.1/ -o ~/rpmbuild/SOURCES/zoneminder-1.31.1.tar.gz HEAD + cd ~/zoneminder + git archive --prefix=zoneminder-1.33.4/ -o ~/rpmbuild/SOURCES/zoneminder-1.33.4.tar.gz HEAD -Replace "1.31.1" with the Version shown in the rpm specfile. +Replace "1.33.4" with the Version shown in the rpm specfile. From the root of the local ZoneMinder git repo, execute the following: :: - cd ~/ZoneMinder + cd ~/zoneminder rpmbuild -bs --nodeps distros/redhat/zoneminder.spec This step will create a source rpm and it will tell you where it was saved. For example: :: - Wrote: /home/abauer/rpmbuild/SRPMS/zoneminder-1.31.1-1.fc26.src.rpm + Wrote: /home/abauer/rpmbuild/SRPMS/zoneminder-1.33.4-1.fc26.src.rpm Now follow the previous instructions `Build from SRPM`_ which describe how to build that source rpm into an rpm. From 1d3617c1cf93dd2f3b33933963d9dbc76a8eb0ae Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Sat, 30 Mar 2019 08:43:46 -0500 Subject: [PATCH 273/310] Rem reference to AUTHORS since it no longer exists --- distros/redhat/zoneminder.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 3678c2da0..980508cec 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -317,7 +317,7 @@ EOF %files common %license COPYING -%doc AUTHORS README.md distros/redhat/readme/README distros/redhat/readme/README.httpd distros/redhat/readme/README.nginx distros/redhat/readme/README.https +%doc README.md distros/redhat/readme/README distros/redhat/readme/README.httpd distros/redhat/readme/README.nginx distros/redhat/readme/README.https # We want these two folders to have "normal" read permission # compared to the folder contents From 593f1571e32f39c568ac5ff482bbe860333db3db Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Sat, 30 Mar 2019 08:57:04 -0500 Subject: [PATCH 274/310] add zmrecover.pl to rpm specfile --- distros/redhat/zoneminder.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 980508cec..1f0352fc4 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -352,6 +352,7 @@ EOF %{_bindir}/zmx10.pl %{_bindir}/zmonvif-probe.pl %{_bindir}/zmstats.pl +%{_bindir}/zmrecover.pl %{perl_vendorlib}/ZoneMinder* %{perl_vendorlib}/ONVIF* From e97751558fef6bf176de3bc72f40107095d80252 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Sat, 30 Mar 2019 10:41:04 -0500 Subject: [PATCH 275/310] eslint fix --- web/skins/classic/views/js/monitor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/monitor.js b/web/skins/classic/views/js/monitor.js index 2ac4515a7..e0ca4f1d4 100644 --- a/web/skins/classic/views/js/monitor.js +++ b/web/skins/classic/views/js/monitor.js @@ -56,7 +56,7 @@ function initPage() { $j('#WebSwatch').css('background-color', event.target.value); }; }); - $j('#contentForm').submit(function(event){ + $j('#contentForm').submit(function(event) { if ( validateForm(this) ) { $j('#contentButtons').hide(); return true; From fa9803d819ff1aca9ae916af8564d06b06b6c050 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 1 Apr 2019 10:11:56 -0400 Subject: [PATCH 276/310] Can't use this->data to avoid another db hit. Must load by id --- web/api/app/Controller/EventsController.php | 2 +- web/api/app/Model/Event.php | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/web/api/app/Controller/EventsController.php b/web/api/app/Controller/EventsController.php index 959ccc596..91a2de75c 100644 --- a/web/api/app/Controller/EventsController.php +++ b/web/api/app/Controller/EventsController.php @@ -142,7 +142,7 @@ class EventsController extends AppController { 'event' => $event, '_serialize' => array('event') )); - } + } // end function view /** diff --git a/web/api/app/Model/Event.php b/web/api/app/Model/Event.php index 51e24052e..de83473f1 100644 --- a/web/api/app/Model/Event.php +++ b/web/api/app/Model/Event.php @@ -103,17 +103,17 @@ class Event extends AppModel { ); public function Relative_Path() { - $Event = new ZM\Event($this->data); + $Event = new ZM\Event($this->id); return $Event->Relative_Path(); } // end function Relative_Path() - public function Path() { - $Event = new ZM\Event($this->data); + public function Path() { + $Event = new ZM\Event($this->id); return $Event->Path(); } public function Link_Path() { - $Event = new ZM\Event($this->data); + $Event = new ZM\Event($this->id); return $Event->Link_Path(); } @@ -123,20 +123,19 @@ class Event extends AppModel { $storage = $this->Storage->findById($event['StorageId']); if ( $event['DefaultVideo'] ) { - if ( file_exists($storage['Storage']['Path'].'/'.$this->Relative_Path().'/'.$event['DefaultVideo']) ) { + if ( file_exists($this->Path().'/'.$event['DefaultVideo']) ) { return 1; } else { - ZM\Logger::Debug("FIle does not exist at " . $storage['Storage']['Path'].'/'.$this->Relative_Path().'/'.$event['DefaultVideo'] ); + ZM\Logger::Debug('File does not exist at ' . $this->Path().'/'.$event['DefaultVideo'] ); } } else { - ZM\Logger::Debug("No DefaultVideo in Event" . $this->Event); + ZM\Logger::Debug('No DefaultVideo in Event' . $this->Event); return 0; } } // end function fileExists($event) public function fileSize($event) { - $storage = $this->Storage->findById($event['StorageId']); - return filesize($storage['Storage']['Path'].'/'.$this->Relative_Path($event).'/'.$event['DefaultVideo']); + return filesize($this->Path().'/'.$event['DefaultVideo']); } public function beforeDelete($cascade=true) { From ed5a448a1df292aedb3bba3d94caaebcc074cefb Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 1 Apr 2019 11:13:14 -0400 Subject: [PATCH 277/310] Correct pts on audio frame before sending it to the encoder so that when we flush the encoder the resulting packets have the right pts. --- src/zm_videostore.cpp | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 5ae3404b7..d54e5c51f 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -435,11 +435,13 @@ VideoStore::~VideoStore() { pkt.data = NULL; pkt.size = 0; av_init_packet(&pkt); +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + // Put encoder into flushing mode + avcodec_send_frame(audio_out_ctx, NULL); +#endif while (1) { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // Put encoder into flushing mode - avcodec_send_frame(audio_out_ctx, NULL); ret = avcodec_receive_packet(audio_out_ctx, &pkt); if ( ret < 0 ) { if ( AVERROR_EOF != ret ) { @@ -450,8 +452,7 @@ VideoStore::~VideoStore() { } #else int got_packet = 0; - ret = - avcodec_encode_audio2(audio_out_ctx, &pkt, NULL, &got_packet); + ret = avcodec_encode_audio2(audio_out_ctx, &pkt, NULL, &got_packet); if ( ret < 0 ) { Error("Error encoding audio while flushing (%d) (%s)", ret, av_err2str(ret)); @@ -1056,8 +1057,27 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { #endif zm_dump_frame(out_frame, "Out frame after resample"); - av_init_packet(&opkt); + // out_frame pts is in the input pkt pts... needs to be adjusted before sending to the encoder + if ( ipkt->pts != AV_NOPTS_VALUE ) { + if ( !audio_first_pts ) { + out_frame->pts = 0; + audio_first_pts = ipkt->pts; + Debug(1, "No audio_first_pts"); + } else { + out_frame->pts = av_rescale_q( + out_frame->pts - audio_first_pts, + audio_in_stream->time_base, + audio_out_stream->time_base); + Debug(2, "audio out_frame.pts = %d from ipkt->pts(%d) - first_pts(%d)", + out_frame->pts, ipkt->pts, audio_first_pts); + } + } else { + Debug(2, "out_frame.pts = undef"); + out_frame->pts = AV_NOPTS_VALUE; + } + zm_dump_frame(out_frame, "Out frame after resample and pts adjustment"); + av_init_packet(&opkt); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) if ( (ret = avcodec_send_frame(audio_out_ctx, out_frame)) < 0 ) { Error("Could not send frame (error '%s')", @@ -1098,7 +1118,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { #endif } else { av_init_packet(&opkt); - Debug(5, "after init packet"); opkt.data = ipkt->data; opkt.size = ipkt->size; } @@ -1123,7 +1142,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { // out // Scale the PTS of the outgoing packet to be the correct time base -#if 1 if ( ipkt->pts != AV_NOPTS_VALUE ) { if ( !audio_first_pts ) { opkt.pts = 0; @@ -1142,12 +1160,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { Debug(2, "opkt.pts = undef"); opkt.pts = AV_NOPTS_VALUE; } -#else - opkt.pts = audio_next_pts; - opkt.dts = audio_next_dts; -#endif -#if 1 if ( ipkt->dts != AV_NOPTS_VALUE ) { // So if the in has no dts assigned... still need an out dts... so we use cur_dts? @@ -1174,8 +1187,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { } else { opkt.dts = AV_NOPTS_VALUE; } -#endif - // audio_last_dts = ipkt->dts; if ( opkt.dts > opkt.pts ) { Debug(1, "opkt.dts(%" PRId64 ") must be <= opkt.pts(%" PRId64 ")." @@ -1185,9 +1196,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { } dumpPacket(audio_out_stream, &opkt, "finished opkt"); - // pkt.pos: byte position in stream, -1 if unknown - //audio_next_dts = opkt.dts + opkt.duration; - //audio_next_pts = opkt.pts + opkt.duration; ret = av_interleaved_write_frame(oc, &opkt); if ( ret != 0 ) { From 15fb546e15f833d9dbfc795ff29b593ec4568fa6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 1 Apr 2019 11:13:35 -0400 Subject: [PATCH 278/310] spacing --- web/includes/Event.php | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/web/includes/Event.php b/web/includes/Event.php index 627133659..ba4f04e8c 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -43,9 +43,9 @@ class Event { $row = NULL; if ( $IdOrRow ) { if ( is_integer($IdOrRow) or is_numeric($IdOrRow) ) { - $row = dbFetchOne('SELECT *,unix_timestamp(StartTime) as Time FROM Events WHERE Id=?', NULL, array($IdOrRow)); + $row = dbFetchOne('SELECT *,unix_timestamp(StartTime) AS Time FROM Events WHERE Id=?', NULL, array($IdOrRow)); if ( ! $row ) { - Error('Unable to load Event record for Id=' . $IdOrRow ); + Error('Unable to load Event record for Id=' . $IdOrRow); } } elseif ( is_array($IdOrRow) ) { $row = $IdOrRow; @@ -53,8 +53,7 @@ class Event { $backTrace = debug_backtrace(); $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; - Error("Unknown argument passed to Event Constructor from $file:$line)"); - Error("Unknown argument passed to Event Constructor ($IdOrRow)"); + Error("Unknown argument passed to Event Constructor from $file:$line) Id was $IdOrRow"); return; } @@ -95,11 +94,11 @@ class Event { return new Monitor(); } - 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}; $backTrace = debug_backtrace(); @@ -109,7 +108,7 @@ class Event { $file = $backTrace[1]['file']; $line = $backTrace[1]['line']; Warning("Unknown function call Event->$fn from $file:$line"); - Warning(print_r( $this, true )); + Warning(print_r($this, true)); } } @@ -125,7 +124,7 @@ class Event { if ( $Storage->Path() and $this->Relative_Path() ) { return $Storage->Path().'/'.$this->Relative_Path(); } else { - Error("Event Path not complete. Storage: " . $Storage->Path() . " relative: " . $this->Relative_Path()); + Error('Event Path not complete. Storage: '.$Storage->Path().' relative: '.$this->Relative_Path()); return ''; } } @@ -134,11 +133,11 @@ class Event { $event_path = ''; if ( $this->{'Scheme'} == 'Deep' ) { - $event_path = $this->{'MonitorId'} .'/'.strftime( '%y/%m/%d/%H/%M/%S', $this->Time()) ; + $event_path = $this->{'MonitorId'}.'/'.strftime('%y/%m/%d/%H/%M/%S', $this->Time()); } else if ( $this->{'Scheme'} == 'Medium' ) { - $event_path = $this->{'MonitorId'} .'/'.strftime( '%Y-%m-%d', $this->Time() ) . '/'.$this->{'Id'}; + $event_path = $this->{'MonitorId'}.'/'.strftime('%Y-%m-%d', $this->Time()).'/'.$this->{'Id'}; } else { - $event_path = $this->{'MonitorId'} .'/'.$this->{'Id'}; + $event_path = $this->{'MonitorId'}.'/'.$this->{'Id'}; } return $event_path; @@ -146,7 +145,7 @@ class Event { public function Link_Path() { if ( $this->{'Scheme'} == 'Deep' ) { - return $this->{'MonitorId'} .'/'.strftime( '%y/%m/%d/.', $this->Time()).$this->{'Id'}; + return $this->{'MonitorId'}.'/'.strftime('%y/%m/%d/.', $this->Time()).$this->{'Id'}; } Error('Calling Link_Path when not using deep storage'); return ''; @@ -173,7 +172,7 @@ class Event { # So this is because ZM creates a link under the day pointing to the time that the event happened. $link_path = $this->Link_Path(); if ( ! $link_path ) { - Error('Unable to determine link path for event ' . $this->{'Id'} . ' not deleting files.'); + Error('Unable to determine link path for event '.$this->{'Id'}.' not deleting files.'); return; } @@ -186,7 +185,7 @@ class Event { return; } # I know we are using arrays here, but really there can only ever be 1 in the array - $eventPath = preg_replace( '/\.'.$this->{'Id'}.'$/', $eventPath, $id_files[0] ); + $eventPath = preg_replace('/\.'.$this->{'Id'}.'$/', $eventPath, $id_files[0]); deletePath($eventPath); deletePath($id_files[0]); $pathParts = explode('/', $eventPath); @@ -202,7 +201,7 @@ class Event { } else { $eventPath = $this->Path(); if ( ! $eventPath ) { - Error("No event Path in Event delete. Not deleting"); + Error('No event Path in Event delete. Not deleting'); return; } deletePath($eventPath); @@ -581,8 +580,8 @@ class Event { if ( file_exists( $this->Path().'/'.$this->DefaultVideo() ) ) { return true; } - $Storage= $this->Storage(); - $Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server(); + $Storage= $this->Storage(); + $Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server(); if ( $Server->Id() != ZM_SERVER_ID ) { $url = $Server->UrlToApi() . '/events/'.$this->{'Id'}.'.json'; @@ -649,14 +648,14 @@ class Event { 'content' => '' ) ); - $context = stream_context_create($options); + $context = stream_context_create($options); try { $result = file_get_contents($url, false, $context); - if ($result === FALSE) { /* Handle error */ + if ( $result === FALSE ) { /* Handle error */ Error("Error restarting zmc using $url"); } $event_data = json_decode($result,true); - Logger::Debug(print_r($event_data['event']['Event'],1)); + Logger::Debug(print_r($event_data['event']['Event'], 1)); return $event_data['event']['Event']['fileSize']; } catch ( Exception $e ) { Error("Except $e thrown trying to get event data"); From c9985107ee45fac584a4be0e1414396a50e4c75e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 1 Apr 2019 13:38:13 -0400 Subject: [PATCH 279/310] Add a deinit function to handle avformat_network_deinit --- src/zm_ffmpeg.cpp | 8 +++++++- src/zm_ffmpeg.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index b22ca6df5..5efbdb07a 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -65,8 +65,9 @@ void log_libav_callback( void *ptr, int level, const char *fmt, va_list vargs ) } } +static bool bInit = false; + void FFMPEGInit() { - static bool bInit = false; if ( !bInit ) { if ( logDebugging() ) @@ -86,6 +87,11 @@ void FFMPEGInit() { } } +void FFMPEGDeInit() { + avformat_network_deinit(); + bInit = false; +} + #if HAVE_LIBAVUTIL enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder) { enum _AVPIXELFORMAT pf; diff --git a/src/zm_ffmpeg.h b/src/zm_ffmpeg.h index 383de6af3..a4d984dec 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -199,6 +199,7 @@ extern "C" { /* A single function to initialize ffmpeg, to avoid multiple initializations */ void FFMPEGInit(); +void FFMPEGDeInit(); #if HAVE_LIBAVUTIL enum _AVPIXELFORMAT GetFFMPEGPixelFormat(unsigned int p_colours, unsigned p_subpixelorder); From a6998cbc9ebe4fff07484c0b60033446a708d8b4 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 1 Apr 2019 13:39:11 -0400 Subject: [PATCH 280/310] Use FFMPEGDeInit instead of avformat_network_deinit --- src/zm_ffmpeg_camera.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 5da4c0dc4..18ef7da12 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -168,7 +168,7 @@ FfmpegCamera::~FfmpegCamera() { if ( capture ) { Terminate(); } - avformat_network_deinit(); + FFMPEGDeInit(); } void FfmpegCamera::Initialise() { @@ -1034,7 +1034,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event int FfmpegCamera::FfmpegInterruptCallback(void *ctx) { //FfmpegCamera* camera = reinterpret_cast(ctx); - Debug(4, "FfmpegInterruptCallback"); + //Debug(4, "FfmpegInterruptCallback"); return zm_terminate; } From cb33318c4ac53ee472a07d2847731da640cab64d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 1 Apr 2019 14:02:39 -0400 Subject: [PATCH 281/310] fix invalid memory access due to incorrect opening/closing/copying of contexts --- src/zm_videostore.cpp | 59 +++++++++++++++++++++++++++---------------- src/zm_videostore.h | 17 ++++++++----- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 5ae3404b7..4e0ee2eee 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -81,7 +81,7 @@ VideoStore::VideoStore( filename, format); return; } else { - Debug(4, "Success alocateing out ctx"); + Debug(4, "Success allocating out ctx"); } } // end if ! oc @@ -94,7 +94,7 @@ VideoStore::VideoStore( out_format = oc->oformat; - AVCodec *video_out_codec = avcodec_find_encoder(video_in_ctx->codec_id); + video_out_codec = avcodec_find_encoder(video_in_ctx->codec_id); if ( !video_out_codec ) { #if (LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 11, 0) && (LIBAVFORMAT_VERSION_MICRO >= 100)) Fatal("Could not find encoder for '%s'", avcodec_get_name(video_out_ctx->codec_id)); @@ -112,9 +112,8 @@ VideoStore::VideoStore( } #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - //video_out_stream->codec = avcodec_alloc_context3(video_out_codec); - // Since we are not re-encoding, all we have to do is copy the parameters video_out_ctx = avcodec_alloc_context3(video_out_codec); + // Since we are not re-encoding, all we have to do is copy the parameters // Copy params from instream to ctx ret = avcodec_parameters_to_context(video_out_ctx, video_in_stream->codecpar); if ( ret < 0 ) { @@ -230,7 +229,6 @@ VideoStore::VideoStore( #endif } - AVDictionary *opts = 0; if ( (ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0 ) { Warning("Can't open video codec (%s) %s", @@ -278,16 +276,14 @@ VideoStore::VideoStore( if ( audio_in_stream ) { Debug(3, "Have audio stream"); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - audio_in_ctx = avcodec_alloc_context3(NULL); - ret = avcodec_parameters_to_context(audio_in_ctx, - audio_in_stream->codecpar); - audio_in_ctx->time_base = audio_in_stream->time_base; -#else - audio_in_ctx = audio_in_stream->codec; -#endif - if ( audio_in_ctx->codec_id != AV_CODEC_ID_AAC ) { + if ( +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + audio_in_stream->codecpar->codec_id +#else + audio_in_stream->codec->codec_id +#endif + != AV_CODEC_ID_AAC ) { static char error_buffer[256]; avcodec_string(error_buffer, sizeof(error_buffer), audio_in_ctx, 0); Debug(2, "Got something other than AAC (%s)", error_buffer); @@ -298,6 +294,15 @@ VideoStore::VideoStore( } else { Debug(2, "Got AAC"); +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + audio_in_ctx = avcodec_alloc_context3(NULL); + ret = avcodec_parameters_to_context(audio_in_ctx, + audio_in_stream->codecpar); + audio_in_ctx->time_base = audio_in_stream->time_base; +#else + audio_in_ctx = audio_in_stream->codec; +#endif + audio_out_stream = #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avformat_new_stream(oc, (const AVCodec *)(audio_in_ctx->codec)); @@ -506,30 +511,39 @@ VideoStore::~VideoStore() { #endif video_in_ctx = NULL; + if ( video_out_codec ) { + avcodec_close(video_out_ctx); + Debug(4, "Success closing video_out_ctx"); + video_out_codec = NULL; + } // end if video_out_codec avcodec_close(video_out_ctx); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - //avcodec_free_context(&video_out_ctx); + avcodec_free_context(&video_out_ctx); #endif video_out_ctx = NULL; - Debug(4, "Success freeing video_out_ctx"); } // end if video_out_stream if ( audio_out_stream ) { if ( audio_in_codec ) { avcodec_close(audio_in_ctx); + Debug(4, "Success closing audio_in_ctx"); audio_in_codec = NULL; } // end if audio_in_codec #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // We allocate and copy in newer ffmpeg, so need to free it avcodec_free_context(&audio_in_ctx); + Debug(4, "Success freeing audio_in_ctx"); #endif audio_in_ctx = NULL; - avcodec_close(audio_out_ctx); + if ( audio_out_ctx ) { + avcodec_close(audio_out_ctx); + Debug(4, "Success closing audio_out_ctx"); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - avcodec_free_context(&audio_out_ctx); + avcodec_free_context(&audio_out_ctx); #endif + } audio_out_ctx = NULL; #if defined(HAVE_LIBAVRESAMPLE) || defined(HAVE_LIBSWRESAMPLE) if ( resample_ctx ) { @@ -579,8 +593,11 @@ bool VideoStore::setup_resampler() { audio_in_codec = avcodec_find_decoder(audio_in_stream->codecpar->codec_id); #else - audio_in_codec = avcodec_find_decoder(audio_in_ctx->codec_id); +// codec is already open in ffmpeg_camera + audio_in_codec = avcodec_find_decoder(audio_in_stream->codec->codec_id); #endif + audio_in_ctx = avcodec_alloc_context3(audio_in_codec); + if ( (ret = avcodec_open2(audio_in_ctx, audio_in_codec, NULL)) < 0 ) { Error("Can't open in codec!"); return false; @@ -592,6 +609,7 @@ bool VideoStore::setup_resampler() { return false; } + audio_out_stream = avformat_new_stream(oc, audio_out_codec); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // audio_out_ctx = audio_out_stream->codec; audio_out_ctx = avcodec_alloc_context3(audio_out_codec); @@ -600,10 +618,7 @@ bool VideoStore::setup_resampler() { audio_out_stream = NULL; return false; } - - audio_out_stream = avformat_new_stream(oc, audio_out_codec); #else - audio_out_stream = avformat_new_stream(oc, NULL); audio_out_ctx = audio_out_stream->codec; #endif // Some formats (i.e. WAV) do not produce the proper channel layout diff --git a/src/zm_videostore.h b/src/zm_videostore.h index 08cf01a42..1fe3fe81b 100644 --- a/src/zm_videostore.h +++ b/src/zm_videostore.h @@ -20,14 +20,16 @@ extern "C" { class VideoStore { private: - AVOutputFormat *out_format; - AVFormatContext *oc; - AVStream *video_out_stream; - AVStream *audio_out_stream; - AVCodecContext *video_out_ctx; + AVOutputFormat *out_format; + AVFormatContext *oc; - AVStream *video_in_stream; - AVStream *audio_in_stream; + AVCodec *video_out_codec; + AVCodecContext *video_out_ctx; + AVStream *video_out_stream; + + AVStream *video_in_stream; + + AVStream *audio_in_stream; // Move this into the object so that we aren't constantly allocating/deallocating it on the stack AVPacket opkt; @@ -41,6 +43,7 @@ private: int ret; // The following are used when encoding the audio stream to AAC + AVStream *audio_out_stream; AVCodec *audio_out_codec; AVCodecContext *audio_out_ctx; #ifdef HAVE_LIBSWRESAMPLE From 110e5075f442e13160700126658095af2e2f4f49 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 1 Apr 2019 17:21:01 -0400 Subject: [PATCH 282/310] fix namespace fixes #3566 --- web/api/app/Controller/MonitorsController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 21ccf59ef..f83aecf97 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -182,7 +182,7 @@ class MonitorsController extends AppController { ) ) { if ( !defined('ZM_SERVER_ID')) { - Logger::Debug("Not defined ZM_SERVER_ID"); + ZM\Logger::Debug("Not defined ZM_SERVER_ID"); } $this->daemonControl($this->Monitor->id, 'start'); } @@ -387,7 +387,7 @@ class MonitorsController extends AppController { } $shellcmd = escapeshellcmd("$zm_path_bin/zmdc.pl $command $daemon $args"); - Logger::Debug("Command $shellcmd"); + ZM\Logger::Debug("Command $shellcmd"); $status = exec($shellcmd); $status_text .= $status."\n"; } From 2bb15c4f9033452b81dd4a74b7a6f45ccf4ecf26 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Mon, 1 Apr 2019 17:26:24 -0400 Subject: [PATCH 283/310] daemonize zmcontrol (#2563) --- scripts/zmcontrol.pl.in | 12 +++++++----- scripts/zmdc.pl.in | 1 + scripts/zmpkg.pl.in | 19 +++++++++++-------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/scripts/zmcontrol.pl.in b/scripts/zmcontrol.pl.in index 7d7053335..b0388bcf3 100644 --- a/scripts/zmcontrol.pl.in +++ b/scripts/zmcontrol.pl.in @@ -63,8 +63,8 @@ GetOptions( 'autostop' =>\$options{autostop}, ) or pod2usage(-exitstatus => -1); -if ( !$id || !$options{command} ) { - print( STDERR "Please give a valid monitor id and command\n" ); +if ( !$id ) { + print( STDERR "Please give a valid monitor id\n" ); pod2usage(-exitstatus => -1); } @@ -201,8 +201,10 @@ if ( !$server_up ) { #print( "Writing commands\n" ); CLIENT->autoflush(); -my $message = jsonEncode(\%options); -print(CLIENT $message); +if ( $options{command} ) { + my $message = jsonEncode(\%options); + print(CLIENT $message); +} shutdown(CLIENT, 1); exit(0); @@ -216,7 +218,7 @@ zmcontrol.pl - ZoneMinder control script =head1 SYNOPSIS - zmcontrol.pl --id {monitor_id} --command={command} [various options] + zmcontrol.pl --id {monitor_id} [--command={command}] [various options] =head1 DESCRIPTION diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 67bbc294b..744164f3d 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -101,6 +101,7 @@ my @daemons = ( 'zmupdate.pl', 'zmstats.pl', 'zmtrack.pl', + 'zmcontrol.pl', 'zmtelemetry.pl' ); diff --git a/scripts/zmpkg.pl.in b/scripts/zmpkg.pl.in index 5463c6153..3f6e18fa1 100644 --- a/scripts/zmpkg.pl.in +++ b/scripts/zmpkg.pl.in @@ -220,14 +220,17 @@ if ( $command =~ /^(?:start|restart)$/ ) { runCommand("zmdc.pl start zma -m $monitor->{Id}"); } if ( $Config{ZM_OPT_CONTROL} ) { - if ( $monitor->{Controllable} && $monitor->{TrackMotion} ) { - if ( $monitor->{Function} eq 'Modect' || $monitor->{Function} eq 'Mocord' ) { - runCommand("zmdc.pl start zmtrack.pl -m $monitor->{Id}"); - } else { - Warning('Monitor is set to track motion, but does not have motion detection enabled.'); - } # end if Has motion enabled - } # end if track motion - } # end if ZM_OPT_CONTROL + if ( $monitor->{Controllable} ) { + runCommand("zmdc.pl start zmcontrol.pl --id $monitor->{Id}"); + if ( $monitor->{TrackMotion} ) { + if ( $monitor->{Function} eq 'Modect' || $monitor->{Function} eq 'Mocord' ) { + runCommand("zmdc.pl start zmtrack.pl -m $monitor->{Id}"); + } else { + Warning('Monitor is set to track motion, but does not have motion detection enabled.'); + } # end if Has motion enabled + } # end if track motion + } # end if controllable + } # end if ZM_OPT_CONTROL } # end if function is not none or Website } # end foreach monitor $sth->finish(); From 3ab1c9983a7c91db0890e93fa496b3c7127c413f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 2 Apr 2019 09:05:52 -0400 Subject: [PATCH 284/310] Update Controls INSERTS to match new column count. Add a db update script to update the Controls table with the new CanReboot column --- db/zm_create.sql.in | 80 ++++++++++++++++++++--------------------- db/zm_update-1.33.5.sql | 12 +++++++ version | 2 +- 3 files changed, 53 insertions(+), 41 deletions(-) create mode 100644 db/zm_update-1.33.5.sql diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 53a6a13af..1eaca832b 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -752,46 +752,46 @@ insert into Filters values (NULL,'Update DiskSpace','{"terms":[{"attr":"DiskSpac -- -- Add in some sample control protocol definitions -- -INSERT INTO Controls VALUES (NULL,'Pelco-D','Local','PelcoD',1,1,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); -INSERT INTO Controls VALUES (NULL,'Pelco-P','Local','PelcoP',1,1,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); -INSERT INTO Controls VALUES (NULL,'Sony VISCA','Local','Visca',1,1,0,1,0,0,0,1,0,16384,10,4000,1,1,6,1,1,1,0,1,0,1536,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,3,1,1,1,1,0,1,1,0,1,-15578,15578,100,10000,1,1,50,1,254,1,-7789,7789,100,5000,1,1,50,1,254,0,0); -INSERT INTO Controls VALUES (NULL,'Axis API v2','Remote','AxisV2',0,0,0,1,0,0,1,0,0,9999,10,2500,0,NULL,NULL,1,1,0,1,0,0,9999,10,2500,0,NULL,NULL,1,1,0,1,0,0,9999,10,2500,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,12,1,1,1,1,1,0,1,0,1,-360,360,1,90,0,NULL,NULL,0,NULL,1,-360,360,1,90,0,NULL,NULL,0,NULL,0,0); -INSERT INTO Controls VALUES (NULL,'Panasonic IP','Remote','PanasonicIP',0,0,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,8,1,1,1,0,1,0,0,1,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0); -INSERT INTO Controls VALUES (NULL,'Neu-Fusion NCS370','Remote','Ncs370',0,0,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,24,1,0,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0); -INSERT INTO Controls VALUES (NULL,'AirLink SkyIPCam 7xx','Remote','SkyIPCam7xx',0,0,1,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,8,1,1,1,0,1,0,1,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0); -INSERT INTO Controls VALUES (NULL,'Pelco-D','Ffmpeg','PelcoD',1,1,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); -INSERT INTO Controls VALUES (NULL,'Pelco-P','Ffmpeg','PelcoP',1,1,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); -INSERT INTO Controls VALUES (NULL,'Foscam FI8620','Ffmpeg','FI8620_Y2k',0,0,0,1,0,0,0,1,1,10,1,10,1,1,63,1,1,0,0,1,1,63,1,63,1,1,63,1,1,0,0,1,0,0,0,0,1,0,255,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,255,1,8,0,1,1,1,0,0,0,1,1,1,360,1,360,1,1,63,0,0,1,1,90,1,90,1,1,63,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'Foscam FI8608W','Ffmpeg','FI8608W_Y2k',1,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,255,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,255,1,8,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,128,0,0,1,0,0,0,0,1,1,128,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'Foscam FI8908W','Remote','FI8908W',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'Foscam FI9821W','Ffmpeg','FI9821W_Y2k',1,0,1,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,100,1,1,0,0,1,0,100,0,100,1,0,100,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,100,0,100,1,0,100,1,16,0,1,1,1,0,0,0,1,1,0,360,0,360,1,0,4,0,0,1,0,90,0,90,1,0,4,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'Loftek Sentinel','Remote','LoftekSentinel',0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,255,16,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,6,1,1,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'Toshiba IK-WB11A','Remote','Toshiba_IK_WB11A',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'WanscamPT','Remote','Wanscam',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,16,0,0,0,0,0,1,16,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO Controls VALUES (NULL,'3S Domo N5071', 'Remote', '3S', 0, 0, 1, 1, 0, 1, 1, 0, 0, 9999, 0, 9999, 0, 0, 0, 1, 1, 1, 1, 0, 0, 9999, 20, 9999, 0, 0, 0, 1, 1, 1, 1, 0, 0, 9999, 1, 9999, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 1, 0, 1, 1, 0, 0, 0, 0, 1, -180, 180, 40, 100, 1, 40, 100, 0, 0, 1, -180, 180, 40, 100, 1, 40, 100, 0, 0, 0, 0); -INSERT INTO Controls VALUES (NULL,'ONVIF Camera','Ffmpeg','onvif',0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,255,16,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,6,1,1,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Foscam 9831W','Ffmpeg','FI9831W',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,16,1,1,1,1,0,0,0,1,1,0,360,0,360,1,0,4,0,0,1,0,90,0,90,0,0,0,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Foscam FI8918W','Ffmpeg','FI8918W',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,8,0,1,1,1,0,0,0,1,1,0,360,0,360,1,0,4,0,0,1,0,90,0,90,1,0,4,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'SunEyes SP-P1802SWPTZ','Libvlc','SPP1802SWPTZ',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,8,0,1,1,0,0,0,0,1,1,0,0,0,0,1,0,64,0,0,1,0,0,0,0,1,0,64,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Wanscam HW0025','Libvlc','WanscamHW0025', 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 16, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 350, 0, 0, 1, 0, 10, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10, 0, 0, 0, 0); -INSERT INTO `Controls` VALUES (NULL,'IPCC 7210W','Remote','IPCC7210W', 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 16, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); -INSERT INTO `Controls` VALUES (NULL,'Vivotek ePTZ','Remote','Vivotek_ePTZ',0,0,1,1,0,0,0,1,0,0,0,0,1,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,1,0,5,0,0,1,0,0,0,0,1,0,5,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Netcat ONVIF','Ffmpeg','Netcat',0,0,1,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,100,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,100,5,5,0,0,0,1,255,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Keekoon','Remote','Keekoon', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); -INSERT INTO `Controls` VALUES (NULL,'HikVision','Local','',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,1,1,1,1,0,0,0,1,1,0,0,0,0,1,1,100,0,0,1,0,0,0,0,1,1,100,1,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Maginon Supra IPC','cURL','MaginonIPC',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,0,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Floureon 1080P','Ffmpeg','Floureon',0,0,0,1,0,0,0,1,1,18,1,1,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,8,0,0,1,0,0,0,0,1,1,8,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-423','Ffmpeg','Reolink',0,0,1,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,64,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-411','Ffmpeg','Reolink',0,0,1,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-420','Ffmpeg','Reolink',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Dericam P2','Ffmpeg','DericamP2',0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'PSIA','Remote','PSIA',0,0,0,1,0,0,1,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,1,0,1,0,0,0,0,1,-100,100,0,0,1,0,0,0,0,1,-100,100,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'Dahua','Remote','Dahua',0,0,0,1,0,0,1,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,1,0,1,0,0,0,0,1,1,8,0,0,1,0,0,0,0,1,1,8,0,0,0,0); -INSERT INTO `Controls` VALUES (NULL,'FOSCAMR2C','Libvlc','FOSCAMR2C',1,1,1,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,12,0,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,4,0,NULL,1,NULL,NULL,NULL,NULL,1,0,4,0,NULL,0,0); -INSERT INTO `Controls` VALUES (NULL,'Amcrest HTTP API','Ffmpeg','Amcrest_HTTP',0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,5,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,5); +INSERT INTO Controls VALUES (NULL,'Pelco-D','Local','PelcoD',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); +INSERT INTO Controls VALUES (NULL,'Pelco-P','Local','PelcoP',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); +INSERT INTO Controls VALUES (NULL,'Sony VISCA','Local','Visca',1,1,0,0,1,0,0,0,1,0,16384,10,4000,1,1,6,1,1,1,0,1,0,1536,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,3,1,1,1,1,0,1,1,0,1,-15578,15578,100,10000,1,1,50,1,254,1,-7789,7789,100,5000,1,1,50,1,254,0,0); +INSERT INTO Controls VALUES (NULL,'Axis API v2','Remote','AxisV2',0,0,0,0,1,0,0,1,0,0,9999,10,2500,0,NULL,NULL,1,1,0,1,0,0,9999,10,2500,0,NULL,NULL,1,1,0,1,0,0,9999,10,2500,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,12,1,1,1,1,1,0,1,0,1,-360,360,1,90,0,NULL,NULL,0,NULL,1,-360,360,1,90,0,NULL,NULL,0,NULL,0,0); +INSERT INTO Controls VALUES (NULL,'Panasonic IP','Remote','PanasonicIP',0,0,0,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,8,1,1,1,0,1,0,0,1,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0); +INSERT INTO Controls VALUES (NULL,'Neu-Fusion NCS370','Remote','Ncs370',0,0,0,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,24,1,0,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0); +INSERT INTO Controls VALUES (NULL,'AirLink SkyIPCam 7xx','Remote','SkyIPCam7xx',0,0,1,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,8,1,1,1,0,1,0,1,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,1,NULL,NULL,NULL,NULL,0,NULL,NULL,0,NULL,0,0); +INSERT INTO Controls VALUES (NULL,'Pelco-D','Ffmpeg','PelcoD',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); +INSERT INTO Controls VALUES (NULL,'Pelco-P','Ffmpeg','PelcoP',1,1,0,0,1,1,0,0,1,NULL,NULL,NULL,NULL,1,0,3,1,1,0,0,1,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,1,0,1,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,20,1,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,63,1,254,1,NULL,NULL,NULL,NULL,1,0,63,1,254,0,0); +INSERT INTO Controls VALUES (NULL,'Foscam FI8620','Ffmpeg','FI8620_Y2k',0,0,0,0,1,0,0,0,1,1,10,1,10,1,1,63,1,1,0,0,1,1,63,1,63,1,1,63,1,1,0,0,1,0,0,0,0,1,0,255,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,255,1,8,0,1,1,1,0,0,0,1,1,1,360,1,360,1,1,63,0,0,1,1,90,1,90,1,1,63,0,0,0,0); +INSERT INTO Controls VALUES (NULL,'Foscam FI8608W','Ffmpeg','FI8608W_Y2k',1,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,255,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,255,1,8,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,128,0,0,1,0,0,0,0,1,1,128,0,0,0,0); +INSERT INTO Controls VALUES (NULL,'Foscam FI8908W','Remote','FI8908W',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO Controls VALUES (NULL,'Foscam FI9821W','Ffmpeg','FI9821W_Y2k',1,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,1,0,100,1,1,0,0,1,0,100,0,100,1,0,100,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,100,0,100,1,0,100,1,16,0,1,1,1,0,0,0,1,1,0,360,0,360,1,0,4,0,0,1,0,90,0,90,1,0,4,0,0,0,0); +INSERT INTO Controls VALUES (NULL,'Loftek Sentinel','Remote','LoftekSentinel',0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,255,16,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,6,1,1,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO Controls VALUES (NULL,'Toshiba IK-WB11A','Remote','Toshiba_IK_WB11A',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO Controls VALUES (NULL,'WanscamPT','Remote','Wanscam',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,16,0,0,0,0,0,1,16,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO Controls VALUES (NULL,'3S Domo N5071', 'Remote', '3S', 0, 0, 1, 0,1, 0, 1, 1, 0, 0, 9999, 0, 9999, 0, 0, 0, 1, 1, 1, 1, 0, 0, 9999, 20, 9999, 0, 0, 0, 1, 1, 1, 1, 0, 0, 9999, 1, 9999, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 1, 0, 1, 1, 0, 0, 0, 0, 1, -180, 180, 40, 100, 1, 40, 100, 0, 0, 1, -180, 180, 40, 100, 1, 40, 100, 0, 0, 0, 0); +INSERT INTO Controls VALUES (NULL,'ONVIF Camera','Ffmpeg','onvif',0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,255,16,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,6,1,1,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Foscam 9831W','Ffmpeg','FI9831W',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,16,1,1,1,1,0,0,0,1,1,0,360,0,360,1,0,4,0,0,1,0,90,0,90,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Foscam FI8918W','Ffmpeg','FI8918W',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,8,0,1,1,1,0,0,0,1,1,0,360,0,360,1,0,4,0,0,1,0,90,0,90,1,0,4,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'SunEyes SP-P1802SWPTZ','Libvlc','SPP1802SWPTZ',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,8,0,1,1,0,0,0,0,1,1,0,0,0,0,1,0,64,0,0,1,0,0,0,0,1,0,64,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Wanscam HW0025','Libvlc','WanscamHW0025', 1, 1, 1, 0,1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 16, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 350, 0, 0, 1, 0, 10, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10, 0, 0, 0, 0); +INSERT INTO `Controls` VALUES (NULL,'IPCC 7210W','Remote','IPCC7210W', 1, 1, 1, 0,1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 16, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); +INSERT INTO `Controls` VALUES (NULL,'Vivotek ePTZ','Remote','Vivotek_ePTZ',0,0,1,0,1,0,0,0,1,0,0,0,0,1,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,1,0,5,0,0,1,0,0,0,0,1,0,5,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Netcat ONVIF','Ffmpeg','Netcat',0,0,1,0,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,100,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,100,5,5,0,0,0,1,255,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Keekoon','Remote','Keekoon', 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); +INSERT INTO `Controls` VALUES (NULL,'HikVision','Local','',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,1,1,1,1,0,0,0,1,1,0,0,0,0,1,1,100,0,0,1,0,0,0,0,1,1,100,1,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Maginon Supra IPC','cURL','MaginonIPC',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,0,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Floureon 1080P','Ffmpeg','Floureon',0,0,0,0,1,0,0,0,1,1,18,1,1,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,8,0,0,1,0,0,0,0,1,1,8,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-423','Ffmpeg','Reolink',0,0,1,0,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,64,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-411','Ffmpeg','Reolink',0,0,1,0,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-420','Ffmpeg','Reolink',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Dericam P2','Ffmpeg','DericamP2',0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'PSIA','Remote','PSIA',0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,1,0,1,0,0,0,0,1,-100,100,0,0,1,0,0,0,0,1,-100,100,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'Dahua','Remote','Dahua',0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,1,0,1,0,0,0,0,1,1,8,0,0,1,0,0,0,0,1,1,8,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'FOSCAMR2C','Libvlc','FOSCAMR2C',1,1,1,0,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,0,0,0,0,0,NULL,NULL,NULL,NULL,0,NULL,NULL,1,12,0,1,1,1,0,0,0,1,1,NULL,NULL,NULL,NULL,1,0,4,0,NULL,1,NULL,NULL,NULL,NULL,1,0,4,0,NULL,0,0); +INSERT INTO `Controls` VALUES (NULL,'Amcrest HTTP API','Ffmpeg','Amcrest_HTTP',0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,5,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,5); -- -- Add some monitor preset values diff --git a/db/zm_update-1.33.5.sql b/db/zm_update-1.33.5.sql new file mode 100644 index 000000000..39400b5d8 --- /dev/null +++ b/db/zm_update-1.33.5.sql @@ -0,0 +1,12 @@ + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Controls' + AND column_name = 'CanReboot' + ) > 0, + "SELECT 'Column CanReboot already exists in Controls'", + "ALTER TABLE Controls ADD `CanReboot` tinyint(3) unsigned NOT NULL default '0' AFTER `CanReset`" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/version b/version index 9f406e0c8..ad5831d4d 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.33.4 +1.33.5 From 8a90176a2c89809f0379a889860666f404d05e3f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 2 Apr 2019 09:25:50 -0400 Subject: [PATCH 285/310] Add CanReboot to Controls --- web/includes/Control.php | 1 + web/includes/Monitor.php | 1 + 2 files changed, 2 insertions(+) diff --git a/web/includes/Control.php b/web/includes/Control.php index 4a8952b27..72e6c49a9 100644 --- a/web/includes/Control.php +++ b/web/includes/Control.php @@ -14,6 +14,7 @@ private $defaults = array( 'CanMoveCon' => 0, 'CanPan' => 0, 'CanReset' => 0, + 'CanReboot' => 0, 'CanSleep' => 0, 'CanWake' => 0, 'MinPanRange' => NULL, diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index ff0e32ff0..83a2f8559 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -113,6 +113,7 @@ private $control_fields = array( 'CanWake' => '0', 'CanSleep' => '0', 'CanReset' => '0', + 'CanReboot' => '0', 'CanZoom' => '0', 'CanAutoZoom' => '0', 'CanZoomAbs' => '0', From cd28559c03b766ac4abbb116fec24d2312c8e778 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 2 Apr 2019 14:08:46 -0400 Subject: [PATCH 286/310] when encoding audio, pts and dts have to come from the encoder. --- src/zm_videostore.cpp | 189 ++++++++++++++++++++---------------------- 1 file changed, 90 insertions(+), 99 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index d54e5c51f..8413354fb 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -684,6 +684,14 @@ bool VideoStore::setup_resampler() { } #endif + Debug(3, + "Time bases: AUDIO in stream (%d/%d) in codec: (%d/%d) out " + "stream: (%d/%d) out codec (%d/%d)", + audio_in_stream->time_base.num, audio_in_stream->time_base.den, + audio_in_ctx->time_base.num, audio_in_ctx->time_base.den, + audio_out_stream->time_base.num, audio_out_stream->time_base.den, + audio_out_ctx->time_base.num, audio_out_ctx->time_base.den); + Debug(1, "Audio in bit_rate (%d) sample_rate(%d) channels(%d) fmt(%d) " "layout(%d) frame_size(%d)", @@ -867,7 +875,8 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt) { video_out_stream->time_base ); } - Debug(3, "opkt.pts = %" PRId64 " from ipkt->pts(%" PRId64 ") - first_pts(%" PRId64 ")", opkt.pts, ipkt->pts, video_first_pts); + Debug(3, "opkt.pts = %" PRId64 " from ipkt->pts(%" PRId64 ") - first_pts(%" PRId64 ")", + opkt.pts, ipkt->pts, video_first_pts); video_last_pts = ipkt->pts; } else { Debug(3, "opkt.pts = undef"); @@ -888,7 +897,7 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt) { video_in_stream->time_base, video_out_stream->time_base ); - Debug(3, "opkt.dts = %" PRId64 " from ipkt.dts(%" PRId64 ") - first_pts(%" PRId64 ")", + Debug(3, "opkt.dts = %" PRId64 " from ipkt->dts(%" PRId64 ") - first_pts(%" PRId64 ")", opkt.dts, ipkt->dts, video_first_pts); video_last_dts = ipkt->dts; #if 0 @@ -947,7 +956,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { } if ( audio_out_codec ) { - Debug(3, "Have audio codec"); #if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) @@ -960,22 +968,8 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { Error("avcodec_receive_frame fail %s", av_make_error_string(ret).c_str()); return 0; } - Debug(2, - "In Frame: samples(%d), format(%d), sample_rate(%d), " - "channels(%d) channel layout(%d) pts(%" PRId64 ")", - in_frame->nb_samples, - in_frame->format, - in_frame->sample_rate, - in_frame->channels, - in_frame->channel_layout, - in_frame->pts); + #else - /** - * Decode the audio frame stored in the packet. - * The in audio stream decoder is used to do this. - * If we are at the end of the file, pass an empty packet to the decoder - * to flush it. - */ int data_present; if ( (ret = avcodec_decode_audio4( audio_in_ctx, in_frame, &data_present, ipkt)) < 0 ) { @@ -991,14 +985,27 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { return 0; } #endif + zm_dump_frame(in_frame, "In frame from decode"); int frame_size = in_frame->nb_samples; // Resample the in into the audioSampleBuffer until we proceed the whole - // decoded data + // decoded data. Note: pts does not survive resampling or converting #if defined(HAVE_LIBSWRESAMPLE) Debug(2, "Converting %d to %d samples using swresample", in_frame->nb_samples, out_frame->nb_samples); ret = swr_convert_frame(resample_ctx, out_frame, in_frame); + zm_dump_frame(out_frame, "Out frame after convert"); + out_frame->pts = in_frame->pts; + // out_frame pts is in the input pkt pts... needs to be adjusted before sending to the encoder + if ( out_frame->pts != AV_NOPTS_VALUE ) { + if ( !audio_first_pts ) { + audio_first_pts = out_frame->pts; + Debug(1, "No audio_first_pts setting to %" PRId64, audio_first_pts); + out_frame->pts = 0; + } else { + out_frame->pts = out_frame->pts - audio_first_pts; + } + } av_frame_unref(in_frame); if ( ret < 0 ) { Error("Could not resample frame (error '%s')", @@ -1057,26 +1064,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { #endif zm_dump_frame(out_frame, "Out frame after resample"); - // out_frame pts is in the input pkt pts... needs to be adjusted before sending to the encoder - if ( ipkt->pts != AV_NOPTS_VALUE ) { - if ( !audio_first_pts ) { - out_frame->pts = 0; - audio_first_pts = ipkt->pts; - Debug(1, "No audio_first_pts"); - } else { - out_frame->pts = av_rescale_q( - out_frame->pts - audio_first_pts, - audio_in_stream->time_base, - audio_out_stream->time_base); - Debug(2, "audio out_frame.pts = %d from ipkt->pts(%d) - first_pts(%d)", - out_frame->pts, ipkt->pts, audio_first_pts); - } - } else { - Debug(2, "out_frame.pts = undef"); - out_frame->pts = AV_NOPTS_VALUE; - } - zm_dump_frame(out_frame, "Out frame after resample and pts adjustment"); - av_init_packet(&opkt); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) if ( (ret = avcodec_send_frame(audio_out_ctx, out_frame)) < 0 ) { @@ -1116,77 +1103,81 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { #else Error("Have audio codec but no resampler?!"); #endif + //if ( out_frame ) { + //opkt.duration = out_frame->nb_samples; + opkt.duration = av_rescale_q(opkt.duration, + audio_in_stream->time_base, + audio_out_stream->time_base); + opkt.pts = av_rescale_q(opkt.pts, + audio_in_stream->time_base, + audio_out_stream->time_base); + opkt.dts = av_rescale_q(opkt.dts, + audio_in_stream->time_base, + audio_out_stream->time_base); } else { av_init_packet(&opkt); opkt.data = ipkt->data; opkt.size = ipkt->size; + + if ( ipkt->duration != AV_NOPTS_VALUE ) { + opkt.duration = av_rescale_q( + ipkt->duration, + audio_in_stream->time_base, + audio_out_stream->time_base); + } + // Scale the PTS of the outgoing packet to be the correct time base + if ( ipkt->pts != AV_NOPTS_VALUE ) { + if ( !audio_first_pts ) { + opkt.pts = 0; + audio_first_pts = ipkt->pts; + Debug(1, "No audio_first_pts"); + } else { + opkt.pts = av_rescale_q( + ipkt->pts - audio_first_pts, + audio_in_stream->time_base, + audio_out_stream->time_base); + Debug(2, "audio opkt.pts = %" PRId64 " from ipkt->pts(%" PRId64 ") - first_pts(%" PRId64 ")", + opkt.pts, ipkt->pts, audio_first_pts); + } + audio_last_pts = ipkt->pts; + } else { + Debug(2, "opkt.pts = undef"); + opkt.pts = AV_NOPTS_VALUE; + } + + if ( ipkt->dts != AV_NOPTS_VALUE ) { + // So if the in has no dts assigned... still need an out dts... so we use cur_dts? + +#if 0 + if ( audio_last_dts >= audio_in_stream->cur_dts ) { + Debug(1, "Resetting audio_last_dts from (%d) to cur_dts (%d)", audio_last_dts, audio_in_stream->cur_dts); + opkt.dts = audio_next_dts + av_rescale_q( audio_in_stream->cur_dts, AV_TIME_BASE_Q, audio_out_stream->time_base); + } else { + opkt.dts = audio_next_dts + av_rescale_q( audio_in_stream->cur_dts - audio_last_dts, AV_TIME_BASE_Q, audio_out_stream->time_base); + } +#endif + if ( !audio_first_dts ) { + opkt.dts = 0; + audio_first_dts = ipkt->dts; + } else { + opkt.dts = av_rescale_q( + ipkt->dts - audio_first_dts, + audio_in_stream->time_base, + audio_out_stream->time_base); + Debug(2, "opkt.dts = %" PRId64 " from ipkt.dts(%" PRId64 ") - first_dts(%" PRId64 ")", + opkt.dts, ipkt->dts, audio_first_dts); + } + audio_last_dts = ipkt->dts; + } else { + opkt.dts = AV_NOPTS_VALUE; + } } opkt.pos = -1; opkt.stream_index = audio_out_stream->index; - if ( out_frame ) { - opkt.duration = out_frame->nb_samples; - } else if ( ipkt->duration != AV_NOPTS_VALUE ) { - opkt.duration = av_rescale_q( - ipkt->duration, - audio_in_stream->time_base, - audio_out_stream->time_base); - } else { - // calculate it? - } dumpPacket(audio_out_stream, &opkt, "raw opkt"); -// PTS is difficult, because of the buffering of the audio packets in the -// resampler. So we have to do it once we actually have a packet... -// audio_last_pts is the pts of ipkt, audio_next_pts is the last pts of the -// out -// Scale the PTS of the outgoing packet to be the correct time base - if ( ipkt->pts != AV_NOPTS_VALUE ) { - if ( !audio_first_pts ) { - opkt.pts = 0; - audio_first_pts = ipkt->pts; - Debug(1, "No audio_first_pts"); - } else { - opkt.pts = av_rescale_q( - ipkt->pts - audio_first_pts, - audio_in_stream->time_base, - audio_out_stream->time_base); - Debug(2, "audio opkt.pts = %d from ipkt->pts(%d) - first_pts(%d)", - opkt.pts, ipkt->pts, audio_first_pts); - } - audio_last_pts = ipkt->pts; - } else { - Debug(2, "opkt.pts = undef"); - opkt.pts = AV_NOPTS_VALUE; - } - - if ( ipkt->dts != AV_NOPTS_VALUE ) { - // So if the in has no dts assigned... still need an out dts... so we use cur_dts? - -#if 0 - if ( audio_last_dts >= audio_in_stream->cur_dts ) { - Debug(1, "Resetting audio_last_dts from (%d) to cur_dts (%d)", audio_last_dts, audio_in_stream->cur_dts); - opkt.dts = audio_next_dts + av_rescale_q( audio_in_stream->cur_dts, AV_TIME_BASE_Q, audio_out_stream->time_base); - } else { - opkt.dts = audio_next_dts + av_rescale_q( audio_in_stream->cur_dts - audio_last_dts, AV_TIME_BASE_Q, audio_out_stream->time_base); - } -#endif - if ( !audio_first_dts ) { - opkt.dts = 0; - audio_first_dts = ipkt->dts; - } else { - opkt.dts = av_rescale_q( - ipkt->dts - audio_first_dts, - audio_in_stream->time_base, - audio_out_stream->time_base); - Debug(2, "opkt.dts = %" PRId64 " from ipkt.dts(%" PRId64 ") - first_dts(%" PRId64 ")", - opkt.dts, ipkt->dts, audio_first_dts); - } - audio_last_dts = ipkt->dts; - } else { - opkt.dts = AV_NOPTS_VALUE; - } if ( opkt.dts > opkt.pts ) { Debug(1, "opkt.dts(%" PRId64 ") must be <= opkt.pts(%" PRId64 ")." From 036560e3e926b1a1bfc18de796c47ddf8402cdc0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 2 Apr 2019 14:23:49 -0400 Subject: [PATCH 287/310] Allow packets with pts=AV_NOPTS_VALUE --- src/zm_ffmpeg_camera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 5da4c0dc4..8cf714717 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -740,7 +740,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event return -1; } - if ( packet.pts < -100000 ) { + if ( (packet.pts != AV_NOPTS_VALUE) && (packet.pts < -100000) ) { // Ignore packets that have crazy negative pts. They aren't supposed to happen. Warning("Ignore packet because pts %" PRId64 " is massively negative. Error count is %d", packet.pts, error_count); dumpPacket(mFormatContext->streams[packet.stream_index], &packet,"Ignored packet"); From 9e96c29620fa5a5711971e092d212ae42d30c254 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 2 Apr 2019 15:24:47 -0400 Subject: [PATCH 288/310] Log a failed csrf check --- web/includes/csrf/csrf-magic.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/includes/csrf/csrf-magic.php b/web/includes/csrf/csrf-magic.php index 584432ef7..ac2ab073b 100644 --- a/web/includes/csrf/csrf-magic.php +++ b/web/includes/csrf/csrf-magic.php @@ -209,6 +209,7 @@ break; } $ok = true; } while (false); + if ($fatal && !$ok) { $callback = $GLOBALS['csrf']['callback']; if (trim($tokens, 'A..Za..z0..9:;,') !== '') $tokens = 'hidden'; @@ -293,6 +294,7 @@ function csrf_callback($tokens) { // Don't make it too easy for users to inflict a CSRF attack on themselves. echo "

Only try again if you weren't sent to this page by someone as this is potentially a sign of an attack.

"; echo "$data"; + ZM\Logger::Debug("Failed csrf check"); } echo "

Debug: $tokens

"; From 8f3d1f865347c9db6f2b61e23ae304935ff58e81 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 2 Apr 2019 15:25:14 -0400 Subject: [PATCH 289/310] fix a missing = and use csrf_get_tokens instead of csrf_get_secret which is the wrong function --- web/includes/Filter.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/web/includes/Filter.php b/web/includes/Filter.php index fb5c74d01..8da602539 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -214,11 +214,13 @@ public $defaults = array( $url = '?user='.$_SESSION['username']; } } - $url .= '&view=filter&action=control&command='.$command.'&Id='.$this->Id().'&ServerId'.$Server->Id(); + $url .= '&view=filter&action=control&command='.$command.'&Id='.$this->Id().'&ServerId='.$Server->Id(); Logger::Debug("sending command to $url"); $data = array(); - if ( defined('ZM_ENABLE_CSRF_MAGIC') ) - $data['__csrf_magic'] = csrf_get_secret(); + if ( defined('ZM_ENABLE_CSRF_MAGIC') ) { + require_once( 'includes/csrf/csrf-magic.php' ); + $data['__csrf_magic'] = csrf_get_tokens(); + } // use key 'http' even if you send the request to https://... $options = array( From f78e95f5d4dde01fa0fb1c83400fcde8e08b07ba Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 3 Apr 2019 13:21:52 -0400 Subject: [PATCH 290/310] add is_video_context and is_audio_context and use them in zm_revceive_frame to add audio decoding support --- src/zm_ffmpeg.cpp | 36 ++++++++++++++++++++++++++++++------ src/zm_ffmpeg.h | 7 +++++-- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 5efbdb07a..011def0ee 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -285,7 +285,8 @@ static void zm_log_fps(double d, const char *postfix) { } void zm_dump_frame(const AVFrame *frame,const char *text) { - Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d layout %d pts %" PRId64, + Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d layout %d pts %" PRId64 + " duration %" PRId64, text, frame->format, av_get_sample_fmt_name((AVSampleFormat)frame->format), @@ -297,7 +298,8 @@ void zm_dump_frame(const AVFrame *frame,const char *text) { 0, #endif frame->channel_layout, - frame->pts + frame->pts, + frame->pkt_duration ); } @@ -450,6 +452,14 @@ bool is_video_stream( AVStream * stream ) { return false; } +bool is_video_context( AVCodecContext *codec_context ) { + return + #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) + ( codec_context->codec_type == AVMEDIA_TYPE_VIDEO ); + #else + ( codec_context->codec_type == CODEC_TYPE_VIDEO ); + #endif +} bool is_audio_stream( AVStream * stream ) { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) @@ -466,6 +476,15 @@ bool is_audio_stream( AVStream * stream ) { return false; } +bool is_audio_context( AVCodecContext *codec_context ) { + return + #if (LIBAVCODEC_VERSION_CHECK(52, 64, 0, 64, 0) || LIBAVUTIL_VERSION_CHECK(50, 14, 0, 14, 0)) + ( codec_context->codec_type == AVMEDIA_TYPE_AUDIO ); + #else + ( codec_context->codec_type == CODEC_TYPE_AUDIO ); + #endif +} + int zm_receive_frame( AVCodecContext *context, AVFrame *frame, AVPacket &packet ) { int ret; #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) @@ -500,15 +519,20 @@ int zm_receive_frame( AVCodecContext *context, AVFrame *frame, AVPacket &packet # else int frameComplete = 0; while ( !frameComplete ) { - if ( (ret = zm_avcodec_decode_video( context, frame, &frameComplete, &packet )) < 0 ) { - Error( "Unable to decode frame at frame: %s, continuing", - av_make_error_string(ret).c_str() ); + if ( is_video_context(context) ) { + ret = zm_avcodec_decode_video(context, frame, &frameComplete, &packet); + } else { + ret = avcodec_decode_audio4(context, frame, &frameComplete, &packet); + } + if ( ret < 0 ) { + Error("Unable to decode frame: %s", av_make_error_string(ret).c_str()); return 0; } - } + } // end while !frameComplete #endif return 1; } // end int zm_receive_frame( AVCodecContext *context, AVFrame *frame, AVPacket &packet ) + void dumpPacket(AVStream *stream, AVPacket *pkt, const char *text) { char b[10240]; diff --git a/src/zm_ffmpeg.h b/src/zm_ffmpeg.h index a4d984dec..37a239509 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -326,8 +326,11 @@ void zm_dump_frame(const AVFrame *frame, const char *text="Frame"); int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt); -bool is_video_stream( AVStream * stream ); -bool is_audio_stream( AVStream * stream ); +bool is_video_stream(AVStream *); +bool is_audio_stream(AVStream *); +bool is_video_context(AVCodec *); +bool is_audio_context(AVCodec *); + int zm_receive_frame( AVCodecContext *context, AVFrame *frame, AVPacket &packet ); void dumpPacket(AVStream *, AVPacket *,const char *text=""); #endif // ZM_FFMPEG_H From aece64049f7f91038f4a647fd849c630a82c2138 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 3 Apr 2019 13:23:00 -0400 Subject: [PATCH 291/310] We cannot avcodec_open2 a context that has been duped. This code currently works without crashes and leaks on ffmpeg 2.8 --- src/zm_videostore.cpp | 457 ++++++++++++++++++++++-------------------- src/zm_videostore.h | 1 + 2 files changed, 237 insertions(+), 221 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 7d3c4b256..0d0c88765 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -103,6 +103,7 @@ VideoStore::VideoStore( #endif } +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) video_out_stream = avformat_new_stream(oc, video_out_codec); if ( !video_out_stream ) { Error("Unable to create video out stream"); @@ -111,7 +112,7 @@ VideoStore::VideoStore( Debug(2, "Success creating video out stream"); } -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + // by allocating our own copy, we don't run into the problems when we free the streams video_out_ctx = avcodec_alloc_context3(video_out_codec); // Since we are not re-encoding, all we have to do is copy the parameters // Copy params from instream to ctx @@ -123,8 +124,21 @@ VideoStore::VideoStore( zm_dump_codec(video_out_ctx); } #else + video_out_stream = avformat_new_stream(oc, NULL); + if ( !video_out_stream ) { + Error("Unable to create video out stream"); + return; + } else { + Debug(2, "Success creating video out stream"); + } video_out_ctx = video_out_stream->codec; + // This will wipe out the codec defaults ret = avcodec_copy_context(video_out_ctx, video_in_ctx); + //video_out_ctx->width = video_in_ctx->width; + //video_out_ctx->height = video_in_ctx->height; + //video_out_ctx->pix_fmt = video_in_ctx->pix_fmt; + //video_out_ctx->max_b_frames = video_in_ctx->max_b_frames; + //video_out_ctx->has_b_frames = video_in_ctx->has_b_frames; if ( ret < 0 ) { Fatal("Unable to copy in video ctx to out video ctx %s", av_make_error_string(ret).c_str()); @@ -142,8 +156,7 @@ VideoStore::VideoStore( zm_dump_codec(video_out_ctx); - //video_out_ctx->bit_rate = 400*1024; - //video_out_ctx->thread_count = 0; +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) //// Fix deprecated formats switch ( video_out_ctx->pix_fmt ) { case AV_PIX_FMT_YUVJ422P : @@ -162,15 +175,6 @@ VideoStore::VideoStore( break; } - if ( video_out_ctx->codec_id == AV_CODEC_ID_H264 ) { - video_out_ctx->max_b_frames = 1; - if ( video_out_ctx->priv_data ) { - av_opt_set(video_out_ctx->priv_data, "crf", "1", AV_OPT_SEARCH_CHILDREN); - //av_opt_set(video_out_ctx->priv_data, "preset", "ultrafast", 0); - } else { - Debug(2, "Not setting priv_data"); - } - } if ( !video_out_ctx->codec_tag ) { Debug(2, "No codec_tag"); @@ -185,6 +189,7 @@ VideoStore::VideoStore( video_out_ctx->codec_tag = video_in_ctx->codec_tag; } } +#endif video_out_stream->time_base = video_in_stream->time_base; if ( video_in_stream->avg_frame_rate.num ) { @@ -195,13 +200,26 @@ VideoStore::VideoStore( video_out_stream->avg_frame_rate = video_in_stream->avg_frame_rate; } if ( video_in_stream->r_frame_rate.num ) { - Debug(3,"Copying r_frame_rate (%d/%d)", + Debug(3,"Copying r_frame_rate (%d/%d) to out (%d/%d)", video_in_stream->r_frame_rate.num, - video_in_stream->r_frame_rate.den + video_in_stream->r_frame_rate.den , + video_out_stream->r_frame_rate.num, + video_out_stream->r_frame_rate.den ); video_out_stream->r_frame_rate = video_in_stream->r_frame_rate; } #if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) + if ( video_out_ctx->codec_id == AV_CODEC_ID_H264 ) { + //video_out_ctx->level = 32;I// + video_out_ctx->bit_rate = 400*1024; + video_out_ctx->max_b_frames = 1; + if ( video_out_ctx->priv_data ) { + av_opt_set(video_out_ctx->priv_data, "crf", "1", AV_OPT_SEARCH_CHILDREN); + av_opt_set(video_out_ctx->priv_data, "preset", "ultrafast", 0); + } else { + Debug(2, "Not setting priv_data"); + } + } ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx); if ( ret < 0 ) { Error("Could not initialize video_out_ctx parameters"); @@ -229,6 +247,7 @@ VideoStore::VideoStore( #endif } +#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) AVDictionary *opts = 0; if ( (ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0 ) { Warning("Can't open video codec (%s) %s", @@ -242,6 +261,7 @@ VideoStore::VideoStore( while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) { Warning("Encoder Option %s not recognized by ffmpeg codec", e->key); } +#endif Monitor::Orientation orientation = monitor->getOrientation(); if ( orientation ) { @@ -273,6 +293,15 @@ VideoStore::VideoStore( fifo = NULL; #endif #endif + video_first_pts = 0; + video_first_dts = 0; + video_last_pts = 0; + video_last_dts = 0; + + audio_first_pts = 0; + audio_first_dts = 0; + audio_next_pts = 0; + audio_next_dts = 0; if ( audio_in_stream ) { Debug(3, "Have audio stream"); @@ -284,9 +313,27 @@ VideoStore::VideoStore( audio_in_stream->codec->codec_id #endif != AV_CODEC_ID_AAC ) { - static char error_buffer[256]; - avcodec_string(error_buffer, sizeof(error_buffer), audio_in_ctx, 0); - Debug(2, "Got something other than AAC (%s)", error_buffer); + + audio_out_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); + if ( !audio_out_codec ) { + Error("Could not find codec for AAC"); + return; + } + +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + audio_out_stream = avformat_new_stream(oc, NULL); + audio_out_stream->time_base = audio_in_stream->time_base; + audio_out_ctx = avcodec_alloc_context3(audio_out_codec); + if ( !audio_out_ctx ) { + Error("could not allocate codec ctx for AAC"); + audio_out_stream = NULL; + return; + } +#else + audio_out_stream = avformat_new_stream(oc, audio_out_codec); + audio_out_ctx = audio_out_stream->codec; +#endif + audio_out_stream->time_base = audio_in_stream->time_base; if ( !setup_resampler() ) { return; @@ -294,90 +341,66 @@ VideoStore::VideoStore( } else { Debug(2, "Got AAC"); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - audio_in_ctx = avcodec_alloc_context3(NULL); - ret = avcodec_parameters_to_context(audio_in_ctx, - audio_in_stream->codecpar); - audio_in_ctx->time_base = audio_in_stream->time_base; -#else - audio_in_ctx = audio_in_stream->codec; -#endif - - audio_out_stream = -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - avformat_new_stream(oc, (const AVCodec *)(audio_in_ctx->codec)); -#else - avformat_new_stream(oc, (AVCodec *)audio_in_ctx->codec); -#endif + audio_out_stream = avformat_new_stream(oc, NULL); if ( !audio_out_stream ) { - Error("Unable to create audio out stream"); - audio_out_stream = NULL; - } else { -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - audio_out_ctx = avcodec_alloc_context3(audio_out_codec); - // Copy params from instream to ctx - ret = avcodec_parameters_to_context( - audio_out_ctx, audio_in_stream->codecpar); - if ( ret < 0 ) { - Error("Unable to copy audio params to ctx %s", - av_make_error_string(ret).c_str()); - } - ret = avcodec_parameters_from_context( - audio_out_stream->codecpar, audio_out_ctx); - if ( ret < 0 ) { - Error("Unable to copy audio params to stream %s", - av_make_error_string(ret).c_str()); - } + Error("Could not allocate new stream"); + return; + } + audio_out_stream->time_base = audio_in_stream->time_base; - if ( !audio_out_ctx->codec_tag ) { - audio_out_ctx->codec_tag = av_codec_get_tag( - oc->oformat->codec_tag, audio_in_ctx->codec_id); - Debug(2, "Setting audio codec tag to %d", - audio_out_ctx->codec_tag); - } +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + // Just use the ctx to copy the parameters over + audio_out_ctx = avcodec_alloc_context3(NULL); + if ( !audio_out_ctx ) { + Error("Could not allocate new output_context"); + return; + } + + // We don't actually care what the time_base is.. + audio_out_ctx->time_base = audio_in_stream->time_base; + + // Copy params from instream to ctx + ret = avcodec_parameters_to_context( + audio_out_ctx, audio_in_stream->codecpar); + if ( ret < 0 ) { + Error("Unable to copy audio params to ctx %s", + av_make_error_string(ret).c_str()); + } + ret = avcodec_parameters_from_context( + audio_out_stream->codecpar, audio_out_ctx); + if ( ret < 0 ) { + Error("Unable to copy audio params to stream %s", + av_make_error_string(ret).c_str()); + } + avcodec_free_context(&audio_out_ctx); #else - audio_out_ctx = audio_out_stream->codec; - ret = avcodec_copy_context(audio_out_ctx, audio_in_ctx); - audio_out_ctx->codec_tag = 0; + audio_out_ctx = audio_out_stream->codec; + ret = avcodec_copy_context(audio_out_ctx, audio_in_stream->codec); + if ( ret < 0 ) { + Error("Unable to copy audio ctx %s", + av_make_error_string(ret).c_str()); + audio_out_stream = NULL; + return; + } // end if + audio_out_ctx->codec_tag = 0; #endif - if ( ret < 0 ) { - Error("Unable to copy audio ctx %s", - av_make_error_string(ret).c_str()); - audio_out_stream = NULL; - } else { - if ( audio_out_ctx->channels > 1 ) { - Warning("Audio isn't mono, changing it."); - audio_out_ctx->channels = 1; - } else { - Debug(3, "Audio is mono"); - } - } - } // end if audio_out_stream + + if ( audio_out_ctx->channels > 1 ) { + Warning("Audio isn't mono, changing it."); + audio_out_ctx->channels = 1; + } else { + Debug(3, "Audio is mono"); + } } // end if is AAC - if ( audio_out_stream ) { - if ( oc->oformat->flags & AVFMT_GLOBALHEADER ) { + if ( oc->oformat->flags & AVFMT_GLOBALHEADER ) { #if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) - audio_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + audio_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; #else - audio_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; + audio_out_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER; #endif - } } } // end if audio_in_stream - - video_first_pts = 0; - video_first_dts = 0; - video_last_pts = 0; - video_last_dts = 0; - - audio_first_pts = 0; - audio_first_dts = 0; - audio_last_pts = 0; - audio_last_dts = 0; - audio_next_pts = 0; - audio_next_dts = 0; - } // VideoStore::VideoStore bool VideoStore::open() { @@ -500,6 +523,7 @@ VideoStore::~VideoStore() { } } // end if ( oc->pb ) +#if 0 // I wonder if we should be closing the file first. // I also wonder if we really need to be doing all the ctx // allocation/de-allocation constantly, or whether we can just re-use it. @@ -508,7 +532,7 @@ VideoStore::~VideoStore() { if ( video_out_stream ) { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // We allocate and copy in newer ffmpeg, so need to free it - //avcodec_free_context(&video_in_ctx); + avcodec_free_context(&video_in_ctx); #endif video_in_ctx = NULL; @@ -517,14 +541,15 @@ VideoStore::~VideoStore() { Debug(4, "Success closing video_out_ctx"); video_out_codec = NULL; } // end if video_out_codec - avcodec_close(video_out_ctx); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - avcodec_free_context(&video_out_ctx); #endif + avcodec_free_context(&video_out_ctx); video_out_ctx = NULL; } // end if video_out_stream +#endif if ( audio_out_stream ) { +#if 0 if ( audio_in_codec ) { avcodec_close(audio_in_ctx); Debug(4, "Success closing audio_in_ctx"); @@ -533,33 +558,35 @@ VideoStore::~VideoStore() { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // We allocate and copy in newer ffmpeg, so need to free it +#endif avcodec_free_context(&audio_in_ctx); Debug(4, "Success freeing audio_in_ctx"); -#endif audio_in_ctx = NULL; if ( audio_out_ctx ) { avcodec_close(audio_out_ctx); Debug(4, "Success closing audio_out_ctx"); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) avcodec_free_context(&audio_out_ctx); +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) #endif } audio_out_ctx = NULL; +#endif + #if defined(HAVE_LIBAVRESAMPLE) || defined(HAVE_LIBSWRESAMPLE) if ( resample_ctx ) { -#if defined(HAVE_LIBSWRESAMPLE) + #if defined(HAVE_LIBSWRESAMPLE) if ( fifo ) { av_audio_fifo_free(fifo); fifo = NULL; } swr_free(&resample_ctx); -#else -#if defined(HAVE_LIBAVRESAMPLE) + #else + #if defined(HAVE_LIBAVRESAMPLE) avresample_close(resample_ctx); avresample_free(&resample_ctx); -#endif -#endif + #endif + #endif } if ( in_frame ) { av_frame_free(&in_frame); @@ -576,7 +603,7 @@ VideoStore::~VideoStore() { #endif } // end if audio_out_stream - /* free the stream */ + /* free the streams */ avformat_free_context(oc); } // VideoStore::~VideoStore() @@ -593,35 +620,35 @@ bool VideoStore::setup_resampler() { // decoder, can't reuse the one from the camera. audio_in_codec = avcodec_find_decoder(audio_in_stream->codecpar->codec_id); + audio_in_ctx = avcodec_alloc_context3(audio_in_codec); #else // codec is already open in ffmpeg_camera - audio_in_codec = avcodec_find_decoder(audio_in_stream->codec->codec_id); + audio_in_ctx = audio_in_stream->codec; + audio_in_codec = (AVCodec *)audio_in_ctx->codec; + //audio_in_codec = avcodec_find_decoder(audio_in_stream->codec->codec_id); #endif - audio_in_ctx = avcodec_alloc_context3(audio_in_codec); +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) +#else +#if 0 + ret = avcodec_copy_context(audio_in_ctx, audio_in_stream->codec); + if ( ret < 0 ) { + Fatal("Unable to copy in video ctx to out video ctx %s", + av_make_error_string(ret).c_str()); + } else { + Debug(3, "Success copying ctx"); + } +#endif +#endif + + // if the codec is already open, nothing is done. if ( (ret = avcodec_open2(audio_in_ctx, audio_in_codec, NULL)) < 0 ) { Error("Can't open in codec!"); return false; } - audio_out_codec = avcodec_find_encoder(AV_CODEC_ID_AAC); - if ( !audio_out_codec ) { - Error("Could not find codec for AAC"); - return false; - } + Debug(2, "Got something other than AAC (%s)", audio_in_codec->name); - audio_out_stream = avformat_new_stream(oc, audio_out_codec); -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // audio_out_ctx = audio_out_stream->codec; - audio_out_ctx = avcodec_alloc_context3(audio_out_codec); - if ( !audio_out_ctx ) { - Error("could not allocate codec ctx for AAC"); - audio_out_stream = NULL; - return false; - } -#else - audio_out_ctx = audio_out_stream->codec; -#endif // Some formats (i.e. WAV) do not produce the proper channel layout if ( audio_in_ctx->channel_layout == 0 ) { Debug(2, "Setting input channel layout to mono"); @@ -930,7 +957,6 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt) { opkt.dts = 0; } - opkt.flags = ipkt->flags; opkt.pos = -1; opkt.data = ipkt->data; @@ -969,46 +995,21 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { return 0; // FIXME -ve return codes do not free packet in ffmpeg_camera at // the moment } + dumpPacket(audio_in_stream, ipkt, "input packet"); if ( audio_out_codec ) { -#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) - - #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - if ( (ret = avcodec_send_packet(audio_in_ctx, ipkt)) < 0 ) { - Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str()); + Debug(2, "Have output codec"); + if ( ! zm_receive_frame(audio_in_ctx, in_frame, *ipkt) ) { return 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; - } - - #else - int data_present; - if ( (ret = avcodec_decode_audio4( - audio_in_ctx, in_frame, &data_present, ipkt)) < 0 ) { - Error("Could not decode frame (error '%s')", - av_make_error_string(ret).c_str()); - dumpPacket(video_in_stream, ipkt); - // I'm not sure if we should be freeing the frame. - av_frame_free(&in_frame); - return 0; - } - if ( !data_present ) { - Debug(2, "Not ready to transcode a frame yet."); - return 0; - } - #endif zm_dump_frame(in_frame, "In frame from decode"); - int frame_size = in_frame->nb_samples; - // Resample the in into the audioSampleBuffer until we proceed the whole - // decoded data. Note: pts does not survive resampling or converting - #if defined(HAVE_LIBSWRESAMPLE) - Debug(2, "Converting %d to %d samples using swresample", in_frame->nb_samples, out_frame->nb_samples); - ret = swr_convert_frame(resample_ctx, out_frame, in_frame); - zm_dump_frame(out_frame, "Out frame after convert"); + if ( ! resample_audio() ) { + av_frame_unref(in_frame); + return 0; + } + zm_dump_frame(out_frame, "Out frame after resample"); out_frame->pts = in_frame->pts; // out_frame pts is in the input pkt pts... needs to be adjusted before sending to the encoder @@ -1020,64 +1021,12 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { } else { out_frame->pts = out_frame->pts - audio_first_pts; } + // + } else { + // sending AV_NOPTS_VALUE doesn't really work but we seem to get it in ffmpeg 2.8 + out_frame->pts = audio_next_pts; } - av_frame_unref(in_frame); - if ( ret < 0 ) { - Error("Could not resample frame (error '%s')", - av_make_error_string(ret).c_str()); - return 0; - } - if ((ret = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + out_frame->nb_samples)) < 0) { - Error("Could not reallocate FIFO"); - return 0; - } - /** Store the new samples in the FIFO buffer. */ - ret = av_audio_fifo_write(fifo, (void **)out_frame->data, out_frame->nb_samples); - if ( ret < frame_size ) { - Error("Could not write data to FIFO on %d written", ret); - return 0; - } - - // Reset frame_size to output_frame_size - frame_size = audio_out_ctx->frame_size; - - // AAC requires 1024 samples per encode. Our input tends to be 160, so need to buffer them. - if ( frame_size > av_audio_fifo_size(fifo) ) { - return 0; - } - - if ( av_audio_fifo_read(fifo, (void **)out_frame->data, frame_size) < frame_size ) { - Error("Could not read data from FIFO"); - return 0; - } - out_frame->nb_samples = frame_size; - /// FIXME this is not the correct pts - #else - #if defined(HAVE_LIBAVRESAMPLE) - (ret = avresample_convert(resample_ctx, NULL, 0, 0, in_frame->data, - 0, in_frame->nb_samples)) - av_frame_unref(in_frame); - if ( ret < 0 ) { - Error("Could not resample frame (error '%s')", - av_make_error_string(ret).c_str()); - return 0; - } - - int samples_available = avresample_available(resample_ctx); - if ( samples_available < frame_size ) { - Debug(1, "Not enough samples yet (%d)", samples_available); - return 0; - } - - // Read a frame audio data from the resample fifo - if ( avresample_read(resample_ctx, out_frame->data, frame_size) != - frame_size) { - Warning("Error reading resampled audio."); - return 0; - } - #endif - #endif - zm_dump_frame(out_frame, "Out frame after resample"); + audio_next_pts = out_frame->pts + out_frame->nb_samples; av_init_packet(&opkt); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) @@ -1102,6 +1051,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { return 0; } #else + int data_present; if ( (ret = avcodec_encode_audio2( audio_out_ctx, &opkt, out_frame, &data_present)) < 0 ) { Error("Could not encode frame (error '%s')", @@ -1115,11 +1065,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { return 0; } #endif -#else - Error("Have audio codec but no resampler?!"); -#endif - //if ( out_frame ) { - //opkt.duration = out_frame->nb_samples; opkt.duration = av_rescale_q(opkt.duration, audio_in_stream->time_base, audio_out_stream->time_base); @@ -1129,12 +1074,15 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { opkt.dts = av_rescale_q(opkt.dts, audio_in_stream->time_base, audio_out_stream->time_base); + dumpPacket(audio_out_stream, &opkt, "raw opkt"); + } else { + Debug(2,"copying"); av_init_packet(&opkt); opkt.data = ipkt->data; opkt.size = ipkt->size; - if ( ipkt->duration != AV_NOPTS_VALUE ) { + if ( ipkt->duration && (ipkt->duration != AV_NOPTS_VALUE) ) { opkt.duration = av_rescale_q( ipkt->duration, audio_in_stream->time_base, @@ -1154,7 +1102,6 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { Debug(2, "audio opkt.pts = %" PRId64 " from ipkt->pts(%" PRId64 ") - first_pts(%" PRId64 ")", opkt.pts, ipkt->pts, audio_first_pts); } - audio_last_pts = ipkt->pts; } else { Debug(2, "opkt.pts = undef"); opkt.pts = AV_NOPTS_VALUE; @@ -1186,13 +1133,11 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { } else { opkt.dts = AV_NOPTS_VALUE; } - } + } // end if encoding or copying opkt.pos = -1; opkt.stream_index = audio_out_stream->index; - dumpPacket(audio_out_stream, &opkt, "raw opkt"); - if ( opkt.dts > opkt.pts ) { Debug(1, "opkt.dts(%" PRId64 ") must be <= opkt.pts(%" PRId64 ")." @@ -1213,3 +1158,73 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { zm_av_packet_unref(&opkt); return 0; } // end int VideoStore::writeAudioFramePacket(AVPacket *ipkt) + +int VideoStore::resample_audio() { + // Resample the in into the audioSampleBuffer until we process the whole + // decoded data. Note: pts does not survive resampling or converting +#if defined(HAVE_LIBSWRESAMPLE) || defined(HAVE_LIBAVRESAMPLE) +#if defined(HAVE_LIBSWRESAMPLE) + Debug(2, "Converting %d to %d samples using swresample", + in_frame->nb_samples, out_frame->nb_samples); + ret = swr_convert_frame(resample_ctx, out_frame, in_frame); + zm_dump_frame(out_frame, "Out frame after convert"); + + if ( ret < 0 ) { + Error("Could not resample frame (error '%s')", + av_make_error_string(ret).c_str()); + return 0; + } + if ((ret = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + out_frame->nb_samples)) < 0) { + Error("Could not reallocate FIFO"); + return 0; + } + /** Store the new samples in the FIFO buffer. */ + ret = av_audio_fifo_write(fifo, (void **)out_frame->data, out_frame->nb_samples); + if ( ret < in_frame->nb_samples ) { + Error("Could not write data to FIFO on %d written", ret); + return 0; + } + + // Reset frame_size to output_frame_size + int frame_size = audio_out_ctx->frame_size; + + // AAC requires 1024 samples per encode. Our input tends to be 160, so need to buffer them. + if ( frame_size > av_audio_fifo_size(fifo) ) { + return 0; + } + + if ( av_audio_fifo_read(fifo, (void **)out_frame->data, frame_size) < frame_size ) { + Error("Could not read data from FIFO"); + return 0; + } + out_frame->nb_samples = frame_size; +#else +#if defined(HAVE_LIBAVRESAMPLE) + (ret = avresample_convert(resample_ctx, NULL, 0, 0, in_frame->data, + 0, in_frame->nb_samples)) + if ( ret < 0 ) { + Error("Could not resample frame (error '%s')", + av_make_error_string(ret).c_str()); + return 0; + } + + int samples_available = avresample_available(resample_ctx); + if ( samples_available < frame_size ) { + Debug(1, "Not enough samples yet (%d)", samples_available); + return 0; + } + + // Read a frame audio data from the resample fifo + if ( avresample_read(resample_ctx, out_frame->data, frame_size) != + frame_size) { + Warning("Error reading resampled audio."); + return 0; + } +#endif +#endif +#else + Error("Have audio codec but no resampler?!"); + return 0; +#endif + return 1; +} // end int VideoStore::resample_audio diff --git a/src/zm_videostore.h b/src/zm_videostore.h index 1fe3fe81b..8e7308e69 100644 --- a/src/zm_videostore.h +++ b/src/zm_videostore.h @@ -77,6 +77,7 @@ private: int64_t audio_next_dts; bool setup_resampler(); + int resample_audio(); public: VideoStore( From b6f35db4defaa0c2a934bc15787567d6d8c254db Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 3 Apr 2019 14:25:18 -0400 Subject: [PATCH 292/310] put back codec closing in destructure, testing with bionic --- src/zm_videostore.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 0d0c88765..d2804de90 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -104,7 +104,7 @@ VideoStore::VideoStore( } #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - video_out_stream = avformat_new_stream(oc, video_out_codec); + video_out_stream = avformat_new_stream(oc, NULL); if ( !video_out_stream ) { Error("Unable to create video out stream"); return; @@ -523,7 +523,6 @@ VideoStore::~VideoStore() { } } // end if ( oc->pb ) -#if 0 // I wonder if we should be closing the file first. // I also wonder if we really need to be doing all the ctx // allocation/de-allocation constantly, or whether we can just re-use it. @@ -532,7 +531,7 @@ VideoStore::~VideoStore() { if ( video_out_stream ) { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // We allocate and copy in newer ffmpeg, so need to free it - avcodec_free_context(&video_in_ctx); + //avcodec_free_context(&video_in_ctx); #endif video_in_ctx = NULL; @@ -542,14 +541,12 @@ VideoStore::~VideoStore() { video_out_codec = NULL; } // end if video_out_codec #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) -#endif avcodec_free_context(&video_out_ctx); +#endif video_out_ctx = NULL; } // end if video_out_stream -#endif if ( audio_out_stream ) { -#if 0 if ( audio_in_codec ) { avcodec_close(audio_in_ctx); Debug(4, "Success closing audio_in_ctx"); @@ -558,20 +555,19 @@ VideoStore::~VideoStore() { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // We allocate and copy in newer ffmpeg, so need to free it -#endif avcodec_free_context(&audio_in_ctx); +#endif Debug(4, "Success freeing audio_in_ctx"); audio_in_ctx = NULL; if ( audio_out_ctx ) { avcodec_close(audio_out_ctx); Debug(4, "Success closing audio_out_ctx"); - avcodec_free_context(&audio_out_ctx); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + avcodec_free_context(&audio_out_ctx); #endif } audio_out_ctx = NULL; -#endif #if defined(HAVE_LIBAVRESAMPLE) || defined(HAVE_LIBSWRESAMPLE) if ( resample_ctx ) { @@ -621,6 +617,14 @@ bool VideoStore::setup_resampler() { audio_in_codec = avcodec_find_decoder(audio_in_stream->codecpar->codec_id); audio_in_ctx = avcodec_alloc_context3(audio_in_codec); + // Copy params from instream to ctx + ret = avcodec_parameters_to_context( + audio_in_ctx, audio_in_stream->codecpar); + if ( ret < 0 ) { + Error("Unable to copy audio params to ctx %s", + av_make_error_string(ret).c_str()); + } + #else // codec is already open in ffmpeg_camera audio_in_ctx = audio_in_stream->codec; @@ -643,7 +647,7 @@ bool VideoStore::setup_resampler() { // if the codec is already open, nothing is done. if ( (ret = avcodec_open2(audio_in_ctx, audio_in_codec, NULL)) < 0 ) { - Error("Can't open in codec!"); + Error("Can't open audio in codec!"); return false; } From b53e4aa803f45c1f33af5d9045d09c4770e711de Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 3 Apr 2019 14:51:10 -0400 Subject: [PATCH 293/310] fix segfault --- src/zm_videostore.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index d2804de90..993136e27 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -350,7 +350,7 @@ VideoStore::VideoStore( #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) // Just use the ctx to copy the parameters over - audio_out_ctx = avcodec_alloc_context3(NULL); + audio_out_ctx = avcodec_alloc_context3(audio_out_codec); if ( !audio_out_ctx ) { Error("Could not allocate new output_context"); return; @@ -372,7 +372,6 @@ VideoStore::VideoStore( Error("Unable to copy audio params to stream %s", av_make_error_string(ret).c_str()); } - avcodec_free_context(&audio_out_ctx); #else audio_out_ctx = audio_out_stream->codec; ret = avcodec_copy_context(audio_out_ctx, audio_in_stream->codec); From 2e7fd7c9a24907a0b7f0ef8fb63969176f63ca75 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 3 Apr 2019 14:55:29 -0400 Subject: [PATCH 294/310] handle really old ffmpeg not having pkt_duration --- src/zm_ffmpeg.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 011def0ee..22d680260 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -285,8 +285,9 @@ static void zm_log_fps(double d, const char *postfix) { } void zm_dump_frame(const AVFrame *frame,const char *text) { - Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d layout %d pts %" PRId64 - " duration %" PRId64, + Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d" + " duration %" PRId64 + " layout %d pts %" PRId64, text, frame->format, av_get_sample_fmt_name((AVSampleFormat)frame->format), @@ -294,12 +295,12 @@ void zm_dump_frame(const AVFrame *frame,const char *text) { frame->nb_samples, #if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) frame->channels, + frame->pkt_duration, #else -0, +0, 0, #endif frame->channel_layout, - frame->pts, - frame->pkt_duration + frame->pts ); } From bd9b5afe46db7e9f9c590891bfb0417a21f446b1 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 3 Apr 2019 14:56:14 -0400 Subject: [PATCH 295/310] old ffmpeg doesn't have av_frame_unref and it doesn't seem to be needed --- src/zm_videostore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 0d0c88765..0586532fa 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -1006,7 +1006,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { zm_dump_frame(in_frame, "In frame from decode"); if ( ! resample_audio() ) { - av_frame_unref(in_frame); + //av_frame_unref(in_frame); return 0; } zm_dump_frame(out_frame, "Out frame after resample"); From 94479fc850afe605e0c5e8b7a9f040914932f875 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 3 Apr 2019 16:03:21 -0400 Subject: [PATCH 296/310] copy pkt flags as well. When pts is NOPTS_VALUE set it to zero --- src/zm_videostore.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index f6e38ee2c..0f51358f1 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -871,7 +871,7 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt) { dumpPacket(video_in_stream, ipkt, "input packet"); int64_t duration; - if ( ipkt->duration ) { + if ( ipkt->duration && ( ipkt->duration != AV_NOPTS_VALUE ) ) { duration = av_rescale_q( ipkt->duration, video_in_stream->time_base, @@ -925,7 +925,8 @@ int VideoStore::writeVideoFramePacket(AVPacket *ipkt) { video_last_pts = ipkt->pts; } else { Debug(3, "opkt.pts = undef"); - opkt.pts = AV_NOPTS_VALUE; + opkt.pts = 0; + //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 ( ipkt->dts != AV_NOPTS_VALUE ) { @@ -1140,6 +1141,7 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { opkt.pos = -1; opkt.stream_index = audio_out_stream->index; + opkt.flags = ipkt->flags; if ( opkt.dts > opkt.pts ) { Debug(1, From 12631abd46d664e9cf4389ec001cb34bf2de06a9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 3 Apr 2019 16:51:27 -0400 Subject: [PATCH 297/310] when copying a packet (for old ffmpeg) copy pts, dts and duration too --- src/zm_ffmpeg.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 22d680260..578e12e6b 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -434,6 +434,9 @@ unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ) { av_new_packet(dst,src->size); memcpy(dst->data, src->data, src->size); dst->flags = src->flags; + dst->pts = src->pts; + dst->dts = src->dts; + dst->duration = src->duration; return 0; } #endif From 8d3fa807e2ad2ee2abdd9dabf5795eeb912d1278 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 3 Apr 2019 16:51:41 -0400 Subject: [PATCH 298/310] compilation fixes for old ffmpeg --- src/zm_videostore.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 0f51358f1..8b307ed7e 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -665,10 +665,7 @@ bool VideoStore::setup_resampler() { 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; -#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) -#else - audio_out_ctx->refcounted_frames = 1; -#endif +#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) if ( !audio_out_ctx->channel_layout ) { Debug(3, "Correcting channel layout from (%d) to (%d)", audio_out_ctx->channel_layout, @@ -676,7 +673,7 @@ bool VideoStore::setup_resampler() { ); audio_out_ctx->channel_layout = av_get_default_channel_layout(audio_out_ctx->channels); } - +#endif if ( audio_out_codec->supported_samplerates ) { int found = 0; for ( unsigned int i = 0; audio_out_codec->supported_samplerates[i]; i++ ) { @@ -832,7 +829,9 @@ bool VideoStore::setup_resampler() { out_frame->nb_samples = audio_out_ctx->frame_size; out_frame->format = audio_out_ctx->sample_fmt; +#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) out_frame->channels = audio_out_ctx->channels; +#endif out_frame->channel_layout = audio_out_ctx->channel_layout; out_frame->sample_rate = audio_out_ctx->sample_rate; @@ -1205,14 +1204,16 @@ int VideoStore::resample_audio() { out_frame->nb_samples = frame_size; #else #if defined(HAVE_LIBAVRESAMPLE) - (ret = avresample_convert(resample_ctx, NULL, 0, 0, in_frame->data, - 0, in_frame->nb_samples)) + ret = avresample_convert(resample_ctx, NULL, 0, 0, in_frame->data, + 0, in_frame->nb_samples); if ( ret < 0 ) { Error("Could not resample frame (error '%s')", av_make_error_string(ret).c_str()); return 0; } + int frame_size = audio_out_ctx->frame_size; + int samples_available = avresample_available(resample_ctx); if ( samples_available < frame_size ) { Debug(1, "Not enough samples yet (%d)", samples_available); From 8de7d576a590556f9272e3477f537c30543d315b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 4 Apr 2019 09:35:37 -0400 Subject: [PATCH 299/310] spacing and use Error instead of fprintf(stderr) so that things get logged. --- src/zmu.cpp | 202 +++++++++++++++++++++++++--------------------------- 1 file changed, 99 insertions(+), 103 deletions(-) diff --git a/src/zmu.cpp b/src/zmu.cpp index a8ee61273..479bbd973 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -426,11 +426,11 @@ int main(int argc, char *argv[]) { if ( config.opt_use_auth ) { if ( strcmp(config.auth_relay, "none") == 0 ) { if ( !checkUser(username)) { - fprintf(stderr, "Error, username greater than allowed 32 characters\n"); + Error("Username greater than allowed 32 characters"); exit_zmu(-1); } if ( !username ) { - fprintf(stderr, "Error, username must be supplied\n"); + Error("Username must be supplied"); exit_zmu(-1); } @@ -439,36 +439,32 @@ int main(int argc, char *argv[]) { } } else { if ( !(username && password) && !auth ) { - fprintf(stderr, "Error, username and password or auth string must be supplied\n"); + Error("Username and password or auth string must be supplied"); exit_zmu(-1); } if ( !checkUser(username)) { - fprintf(stderr, "Error, username greater than allowed 32 characters\n"); + Error("Username greater than allowed 32 characters"); exit_zmu(-1); } if ( !checkPass(password)) { - fprintf(stderr, "Error, password greater than allowed 64 characters\n"); + Error("Password greater than allowed 64 characters"); exit_zmu(-1); } - //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) - { - if ( auth ) { - user = zmLoadAuthUser(auth, false); - } + + if ( auth ) { + user = zmLoadAuthUser(auth, false); } - //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) - { - if ( username && password ) { - user = zmLoadUser(username, password); - } + if ( username && password ) { + user = zmLoadUser(username, password); } - } + } // auth relay == none or not + if ( !user ) { - fprintf(stderr, "Error, unable to authenticate user\n"); - return exit_zmu(-1); + Error("Unable to authenticate user"); + exit_zmu(-1); } if ( !ValidateAccess(user, mon_id, function) ) { - fprintf(stderr, "Error, insufficient privileges for requested action\n"); + Error("Insufficient privileges for requested action"); exit_zmu(-1); } } // end if auth @@ -503,201 +499,201 @@ int main(int argc, char *argv[]) { if ( verbose ) { char timestamp_str[64] = "None"; if ( timestamp.tv_sec ) - strftime( timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime( ×tamp.tv_sec ) ); + strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime(×tamp.tv_sec)); if ( image_idx == -1 ) - printf( "Time of last image capture: %s.%02ld\n", timestamp_str, timestamp.tv_usec/10000 ); + printf("Time of last image capture: %s.%02ld\n", timestamp_str, timestamp.tv_usec/10000); else - printf( "Time of image %d capture: %s.%02ld\n", image_idx, timestamp_str, timestamp.tv_usec/10000 ); + printf("Time of image %d capture: %s.%02ld\n", image_idx, timestamp_str, timestamp.tv_usec/10000); } else { - if ( have_output ) printf( "%c", separator ); - printf( "%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000 ); + if ( have_output ) printf("%c", separator); + printf("%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000); have_output = true; } } if ( function & ZMU_READ_IDX ) { if ( verbose ) - printf( "Last read index: %d\n", monitor->GetLastReadIndex() ); + printf("Last read index: %d\n", monitor->GetLastReadIndex()); else { - if ( have_output ) printf( "%c", separator ); - printf( "%d", monitor->GetLastReadIndex() ); + if ( have_output ) printf("%c", separator); + printf("%d", monitor->GetLastReadIndex()); have_output = true; } } if ( function & ZMU_WRITE_IDX ) { - if ( verbose ) - printf( "Last write index: %d\n", monitor->GetLastWriteIndex() ); - else { - if ( have_output ) printf( "%c", separator ); - printf( "%d", monitor->GetLastWriteIndex() ); + if ( verbose ) { + printf("Last write index: %d\n", monitor->GetLastWriteIndex()); + } else { + if ( have_output ) printf("%c", separator); + printf("%d", monitor->GetLastWriteIndex()); have_output = true; } } if ( function & ZMU_EVENT ) { - if ( verbose ) - printf( "Last event id: %" PRIu64 "\n", monitor->GetLastEventId() ); - else { - if ( have_output ) printf( "%c", separator ); - printf( "%" PRIu64, monitor->GetLastEventId() ); + if ( verbose ) { + printf("Last event id: %" PRIu64 "\n", monitor->GetLastEventId()); + } else { + if ( have_output ) printf("%c", separator); + printf("%" PRIu64, monitor->GetLastEventId()); have_output = true; } } if ( function & ZMU_FPS ) { if ( verbose ) - printf( "Current capture rate: %.2f frames per second\n", monitor->GetFPS() ); + printf("Current capture rate: %.2f frames per second\n", monitor->GetFPS()); else { - if ( have_output ) printf( "%c", separator ); - printf( "%.2f", monitor->GetFPS() ); + if ( have_output ) printf("%c", separator); + printf("%.2f", monitor->GetFPS()); have_output = true; } } if ( function & ZMU_IMAGE ) { if ( verbose ) { if ( image_idx == -1 ) - printf( "Dumping last image captured to Monitor%d.jpg", monitor->Id() ); + printf("Dumping last image captured to Monitor%d.jpg", monitor->Id()); else - printf( "Dumping buffer image %d to Monitor%d.jpg", image_idx, monitor->Id() ); + printf("Dumping buffer image %d to Monitor%d.jpg", image_idx, monitor->Id()); if ( scale != -1 ) - printf( ", scaling by %d%%", scale ); - printf( "\n" ); + printf(", scaling by %d%%", scale); + printf("\n"); } - monitor->GetImage( image_idx, scale>0?scale:100 ); + monitor->GetImage(image_idx, scale>0?scale:100); } if ( function & ZMU_ZONES ) { if ( verbose ) - printf( "Dumping zone image to Zones%d.jpg\n", monitor->Id() ); - monitor->DumpZoneImage( zoneString ); + printf("Dumping zone image to Zones%d.jpg\n", monitor->Id()); + monitor->DumpZoneImage(zoneString); } if ( function & ZMU_ALARM ) { if ( verbose ) - printf( "Forcing alarm on\n" ); - monitor->ForceAlarmOn( config.forced_alarm_score, "Forced Web" ); + printf("Forcing alarm on\n"); + monitor->ForceAlarmOn(config.forced_alarm_score, "Forced Web"); while ( monitor->GetState() != Monitor::ALARM ) { // Wait for monitor to notice. usleep(1000); } - printf( "Alarmed event id: %" PRIu64 "\n", monitor->GetLastEventId() ); + printf("Alarmed event id: %" PRIu64 "\n", monitor->GetLastEventId()); } if ( function & ZMU_NOALARM ) { if ( verbose ) - printf( "Forcing alarm off\n" ); + printf("Forcing alarm off\n"); monitor->ForceAlarmOff(); } if ( function & ZMU_CANCEL ) { if ( verbose ) - printf( "Cancelling forced alarm on/off\n" ); + printf("Cancelling forced alarm on/off\n"); monitor->CancelForced(); } if ( function & ZMU_RELOAD ) { if ( verbose ) - printf( "Reloading monitor settings\n" ); + printf("Reloading monitor settings\n"); monitor->actionReload(); } if ( function & ZMU_ENABLE ) { if ( verbose ) - printf( "Enabling event generation\n" ); + printf("Enabling event generation\n"); monitor->actionEnable(); } if ( function & ZMU_DISABLE ) { if ( verbose ) - printf( "Disabling event generation\n" ); + printf("Disabling event generation\n"); monitor->actionDisable(); } if ( function & ZMU_SUSPEND ) { if ( verbose ) - printf( "Suspending event generation\n" ); + printf("Suspending event generation\n"); monitor->actionSuspend(); } if ( function & ZMU_RESUME ) { if ( verbose ) - printf( "Resuming event generation\n" ); + printf("Resuming event generation\n"); monitor->actionResume(); } if ( function & ZMU_QUERY ) { char monString[16382] = ""; - monitor->DumpSettings( monString, verbose ); - printf( "%s\n", monString ); + monitor->DumpSettings(monString, verbose); + printf("%s\n", monString); } if ( function & ZMU_BRIGHTNESS ) { if ( verbose ) { if ( brightness >= 0 ) - printf( "New brightness: %d\n", monitor->actionBrightness( brightness ) ); + printf("New brightness: %d\n", monitor->actionBrightness(brightness)); else - printf( "Current brightness: %d\n", monitor->actionBrightness() ); + printf("Current brightness: %d\n", monitor->actionBrightness()); } else { - if ( have_output ) printf( "%c", separator ); + if ( have_output ) printf("%c", separator); if ( brightness >= 0 ) - printf( "%d", monitor->actionBrightness( brightness ) ); + printf("%d", monitor->actionBrightness(brightness)); else - printf( "%d", monitor->actionBrightness() ); + printf("%d", monitor->actionBrightness()); have_output = true; } } if ( function & ZMU_CONTRAST ) { if ( verbose ) { if ( contrast >= 0 ) - printf( "New brightness: %d\n", monitor->actionContrast( contrast ) ); + printf("New brightness: %d\n", monitor->actionContrast(contrast)); else - printf( "Current contrast: %d\n", monitor->actionContrast() ); + printf("Current contrast: %d\n", monitor->actionContrast()); } else { - if ( have_output ) printf( "%c", separator ); + if ( have_output ) printf("%c", separator); if ( contrast >= 0 ) - printf( "%d", monitor->actionContrast( contrast ) ); + printf("%d", monitor->actionContrast(contrast)); else - printf( "%d", monitor->actionContrast() ); + printf("%d", monitor->actionContrast()); have_output = true; } } if ( function & ZMU_HUE ) { if ( verbose ) { if ( hue >= 0 ) - printf( "New hue: %d\n", monitor->actionHue( hue ) ); + printf("New hue: %d\n", monitor->actionHue(hue)); else - printf( "Current hue: %d\n", monitor->actionHue() ); + printf("Current hue: %d\n", monitor->actionHue()); } else { - if ( have_output ) printf( "%c", separator ); + if ( have_output ) printf("%c", separator); if ( hue >= 0 ) - printf( "%d", monitor->actionHue( hue ) ); + printf("%d", monitor->actionHue(hue)); else - printf( "%d", monitor->actionHue() ); + printf("%d", monitor->actionHue()); have_output = true; } } if ( function & ZMU_COLOUR ) { if ( verbose ) { if ( colour >= 0 ) - printf( "New colour: %d\n", monitor->actionColour( colour ) ); + printf("New colour: %d\n", monitor->actionColour(colour)); else - printf( "Current colour: %d\n", monitor->actionColour() ); + printf("Current colour: %d\n", monitor->actionColour()); } else { - if ( have_output ) printf( "%c", separator ); + if ( have_output ) printf("%c", separator); if ( colour >= 0 ) - printf( "%d", monitor->actionColour( colour ) ); + printf("%d", monitor->actionColour(colour)); else - printf( "%d", monitor->actionColour() ); + printf("%d", monitor->actionColour()); have_output = true; } } if ( have_output ) { - printf( "\n" ); + printf("\n"); } if ( !function ) { Usage(); } delete monitor; } else { - fprintf(stderr, "Error, invalid monitor id %d\n", mon_id); + Error("Invalid monitor id %d", mon_id); exit_zmu(-1); } } else { if ( function & ZMU_QUERY ) { #if ZM_HAS_V4L char vidString[0x10000] = ""; - bool ok = LocalCamera::GetCurrentSettings( device, vidString, v4lVersion, verbose ); - printf( "%s", vidString ); - exit_zmu( ok?0:-1 ); + bool ok = LocalCamera::GetCurrentSettings(device, vidString, v4lVersion, verbose); + printf("%s", vidString); + exit_zmu(ok ? 0 : -1); #else // ZM_HAS_V4L - fprintf( stderr, "Error, video4linux is required for device querying\n" ); - exit_zmu( -1 ); + Error("Video4linux is required for device querying"); + exit_zmu(-1); #endif // ZM_HAS_V4L } @@ -708,25 +704,25 @@ int main(int argc, char *argv[]) { } sql += " order by Id asc"; - if ( mysql_query( &dbconn, sql.c_str() ) ) { - Error( "Can't run query: %s", mysql_error( &dbconn ) ); - exit_zmu( mysql_errno( &dbconn ) ); + if ( mysql_query(&dbconn, sql.c_str()) ) { + Error("Can't run query: %s", mysql_error(&dbconn)); + exit_zmu(mysql_errno(&dbconn)); } - MYSQL_RES *result = mysql_store_result( &dbconn ); + MYSQL_RES *result = mysql_store_result(&dbconn); if ( !result ) { - Error( "Can't use query result: %s", mysql_error( &dbconn ) ); - exit_zmu( mysql_errno( &dbconn ) ); + Error("Can't use query result: %s", mysql_error(&dbconn)); + exit_zmu(mysql_errno(&dbconn)); } - Debug( 1, "Got %d monitors", mysql_num_rows( result ) ); + Debug(1, "Got %d monitors", mysql_num_rows(result)); - printf( "%4s%5s%6s%9s%14s%6s%6s%8s%8s\n", "Id", "Func", "State", "TrgState", "LastImgTim", "RdIdx", "WrIdx", "LastEvt", "FrmRate" ); - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { + printf("%4s%5s%6s%9s%14s%6s%6s%8s%8s\n", "Id", "Func", "State", "TrgState", "LastImgTim", "RdIdx", "WrIdx", "LastEvt", "FrmRate"); + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { int mon_id = atoi(dbrow[0]); int function = atoi(dbrow[1]); - if ( !user || user->canAccess( mon_id ) ) { + if ( !user || user->canAccess(mon_id) ) { if ( function > 1 ) { - Monitor *monitor = Monitor::Load( mon_id, false, Monitor::QUERY ); + Monitor *monitor = Monitor::Load(mon_id, false, Monitor::QUERY); if ( monitor && monitor->connect() ) { struct timeval tv = monitor->GetTimestamp(); printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8" PRIu64 "%8.2f\n", @@ -744,7 +740,7 @@ int main(int argc, char *argv[]) { } } else { struct timeval tv = { 0, 0 }; - printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", + printf("%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", mon_id, function, 0, @@ -755,11 +751,11 @@ int main(int argc, char *argv[]) { 0, 0.0 ); - } - } - } - mysql_free_result( result ); - } + } // end if function filter + } // endif !user || canAccess(mon_id) + } // end foreach row + mysql_free_result(result); + } // end if function && ZMU_LIST } delete user; From 79de2b65cde72927932f5c4152ec7fae0acb3bb5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 4 Apr 2019 12:18:46 -0400 Subject: [PATCH 300/310] If we are saving jpegs we don't save a snapshot image, and since we may delay writing frame info to the db, we have to default to frame 0 instead of snapshot --- web/views/image.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/web/views/image.php b/web/views/image.php index 672baf3f5..5959982d8 100644 --- a/web/views/image.php +++ b/web/views/image.php @@ -87,8 +87,9 @@ if ( empty($_REQUEST['path']) ) { $Frame->Id('objdetect'); } else if ( $_REQUEST['fid'] == 'alarm' ) { # look for first alarmed frame - $Frame = ZM\Frame::find_one(array('EventId'=>$_REQUEST['eid'], 'Type'=>'Alarm'), - array('order'=>'FrameId ASC')); + $Frame = ZM\Frame::find_one( + array('EventId'=>$_REQUEST['eid'], 'Type'=>'Alarm'), + array('order'=>'FrameId ASC')); if ( !$Frame ) { # no alarms, get first one I find $Frame = ZM\Frame::find_one(array('EventId'=>$_REQUEST['eid'])); if ( !$Frame ) { @@ -101,7 +102,7 @@ if ( empty($_REQUEST['path']) ) { $Monitor = $Event->Monitor(); if ( $Monitor->SaveJPEGs() & 1 ) { # If we store Frames as jpgs, then we don't store an alarmed snapshot - $path = $Event->Path().'/'.sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d',$Frame->FrameId()).'-'.$show.'.jpg'; + $path = $Event->Path().'/'.sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d', $Frame->FrameId()).'-'.$show.'.jpg'; } else { $path = $Event->Path().'/alarm.jpg'; } @@ -113,12 +114,16 @@ if ( empty($_REQUEST['path']) ) { ZM\Warning('No frame found for event ' . $_REQUEST['eid']); $Frame = new ZM\Frame(); $Frame->Delta(1); - $Frame->FrameId('snapshot'); + if ( $Monitor->SaveJPEGs() & 1 ) { + $Frame->FrameId(0); + } else { + $Frame->FrameId('snapshot'); + } } $Monitor = $Event->Monitor(); if ( $Monitor->SaveJPEGs() & 1 ) { # If we store Frames as jpgs, then we don't store a snapshot - $path = $Event->Path().'/'.sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d',$Frame->FrameId()).'-'.$show.'.jpg'; + $path = $Event->Path().'/'.sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d', $Frame->FrameId()).'-'.$show.'.jpg'; } else { $path = $Event->Path().'/snapshot.jpg'; } From 271937f005e3dc84662a5668d7b946972d285cd3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 4 Apr 2019 12:28:08 -0400 Subject: [PATCH 301/310] store the snapshot_file and alarm_file strings in the boject during construction so that we don't have to do it everytime we write out an image --- src/zm_event.cpp | 9 +++------ src/zm_event.h | 2 ++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 876a031d4..c44f8ee93 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -193,6 +193,9 @@ Event::Event( video_name[0] = 0; + snprintf(snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path); + snprintf(alarm_file, sizeof(alarm_file), "%s/alarm.jpg", path); + /* Save as video */ if ( monitor->GetOptVideoWriter() != 0 ) { @@ -464,8 +467,6 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st // neccessarily be of the motion. But some events are less than 10 frames, // so I am changing this to 1, but we should overwrite it later with a better snapshot. if ( frames == 1 ) { - char snapshot_file[PATH_MAX]; - snprintf(snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path); WriteFrameImage(images[i], *(timestamps[i]), snapshot_file); } } @@ -549,15 +550,11 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a } else { //If this is the first frame, we should add a thumbnail to the event directory if ( frames == 1 || score > (int)max_score ) { - char snapshot_file[PATH_MAX]; - snprintf(snapshot_file, sizeof(snapshot_file), "%s/snapshot.jpg", path); WriteFrameImage(image, timestamp, snapshot_file); } // The first frame with a score will be the frame that alarmed the event if (!alarm_frame_written && score > 0) { alarm_frame_written = true; - char alarm_file[PATH_MAX]; - snprintf(alarm_file, sizeof(alarm_file), "%s/alarm.jpg", path); WriteFrameImage(image, timestamp, alarm_file); } } diff --git a/src/zm_event.h b/src/zm_event.h index 1d31addcc..2035b6af6 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -90,6 +90,8 @@ class Event { unsigned int tot_score; unsigned int max_score; char path[PATH_MAX]; + char snapshot_file[PATH_MAX]; + char alarm_file[PATH_MAX]; VideoWriter* videowriter; FILE* timecodes_fd; char video_name[PATH_MAX]; From f6d5038586f524bb3331f5e2ebb4b2ca69084692 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 4 Apr 2019 12:37:25 -0400 Subject: [PATCH 302/310] we only use event_file if we are writing out a jpg, so only generate the event_file string if we are writing out jpgs --- src/zm_event.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index c44f8ee93..3fda77e11 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -456,9 +456,9 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st frames++; - static char event_file[PATH_MAX]; - snprintf(event_file, sizeof(event_file), staticConfig.capture_file_format, path, frames); if ( monitor->GetOptSaveJPEGs() & 1 ) { + static char event_file[PATH_MAX]; + snprintf(event_file, sizeof(event_file), staticConfig.capture_file_format, path, frames); Debug(1, "Writing pre-capture frame %d", frames); WriteFrameImage(images[i], *(timestamps[i]), event_file); } else { @@ -539,27 +539,25 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a frames++; - static char event_file[PATH_MAX]; - snprintf(event_file, sizeof(event_file), staticConfig.capture_file_format, path, frames); - if ( monitor->GetOptSaveJPEGs() & 1 ) { + static char event_file[PATH_MAX]; + snprintf(event_file, sizeof(event_file), staticConfig.capture_file_format, path, frames); Debug(1, "Writing capture frame %d to %s", frames, event_file); if ( ! WriteFrameImage(image, timestamp, event_file) ) { Error("Failed to write frame image"); } } else { //If this is the first frame, we should add a thumbnail to the event directory - if ( frames == 1 || score > (int)max_score ) { + if ( (frames == 1) || (score > (int)max_score) ) { WriteFrameImage(image, timestamp, snapshot_file); } // The first frame with a score will be the frame that alarmed the event - if (!alarm_frame_written && score > 0) { + if ( (!alarm_frame_written) && (score > 0) ) { alarm_frame_written = true; WriteFrameImage(image, timestamp, alarm_file); } } if ( videowriter != NULL ) { -Debug(3, "Writing video"); WriteFrameVideo(image, timestamp, videowriter); } @@ -625,7 +623,7 @@ Debug(3, "Writing video"); } } } - } + } // end if frame_type == ALARM /* This makes viewing the diagnostic images impossible because it keeps deleting them if ( config.record_diag_images ) { From bc6249309699fb865f191e16acd9beb68be748a5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 4 Apr 2019 12:55:35 -0400 Subject: [PATCH 303/310] Increase sql buffer space because we are using path strings that are PATH_MAX size. --- src/zm_event.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 3fda77e11..7b8b446d4 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -254,7 +254,7 @@ Event::~Event() { WriteDbFrames(); // Should not be static because we might be multi-threaded - char sql[ZM_SQL_MED_BUFSIZ]; + char sql[ZM_SQL_LGE_BUFSIZ]; snprintf(sql, sizeof(sql), "UPDATE Events SET Name='%s %" PRIu64 "', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo = '%s' WHERE Id = %" PRIu64, monitor->EventPrefix(), id, end_time.tv_sec, @@ -616,6 +616,7 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a if ( alarm_image ) { if ( monitor->GetOptSaveJPEGs() & 2 ) { + static char event_file[PATH_MAX]; snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path, frames); Debug(1, "Writing analysis frame %d", frames); if ( ! WriteFrameImage(alarm_image, timestamp, event_file, true) ) { From 7955a1c9b2d85c39e8ca831f009d9a3978e89fc1 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 4 Apr 2019 12:56:00 -0400 Subject: [PATCH 304/310] fix version check to get rid of deprecated av_register_all --- src/zm_ffmpeg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 578e12e6b..c5bc1b471 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -78,7 +78,7 @@ void FFMPEGInit() { av_log_set_callback(log_libav_callback); else Info("Not enabling ffmpeg logs, as LOG_FFMPEG is disabled in options"); -#if LIBAVCODEC_VERSION_CHECK(58, 18, 0, 64, 0) +#if LIBAVFORMAT_VERSION_CHECK(58, 9, 0, 64, 0) #else av_register_all(); #endif From d97dd6cdd97438de87c97cbfd69599ef99ad3dde Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 4 Apr 2019 14:11:56 -0400 Subject: [PATCH 305/310] Should close videostore before closing input codecs/streams. --- src/zm_ffmpeg_camera.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 04b4588d9..c07c9312e 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -675,6 +675,11 @@ int FfmpegCamera::Close() { } #endif + if ( videoStore ) { + delete videoStore; + videoStore = NULL; + } + if ( mVideoCodecContext ) { avcodec_close(mVideoCodecContext); Debug(1,"After codec close"); @@ -700,10 +705,6 @@ int FfmpegCamera::Close() { mFormatContext = NULL; } - if ( videoStore ) { - delete videoStore; - videoStore = NULL; - } if ( packetqueue ) { delete packetqueue; packetqueue = NULL; From 9ae6acf532f4bc471adc61a4ed417a15ddd34fde Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 4 Apr 2019 17:20:21 -0400 Subject: [PATCH 306/310] Fix for opening audio_in_codec --- src/zm_ffmpeg.cpp | 1 + src/zm_ffmpeg_camera.cpp | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index c5bc1b471..63c0c9aa3 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -437,6 +437,7 @@ unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ) { dst->pts = src->pts; dst->dts = src->dts; dst->duration = src->duration; + dst->stream_index = src->stream_index; return 0; } #endif diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 04b4588d9..ae04e0bc9 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -565,15 +565,23 @@ int FfmpegCamera::OpenFfmpeg() { Debug(1, "HWACCEL not in use"); } if ( mAudioStreamId >= 0 ) { + if ( (mAudioCodec = avcodec_find_decoder( #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - mAudioCodecContext = avcodec_alloc_context3( NULL ); - avcodec_parameters_to_context( mAudioCodecContext, mFormatContext->streams[mAudioStreamId]->codecpar ); + mFormatContext->streams[mAudioStreamId]->codecpar->codec_id #else - mAudioCodecContext = mFormatContext->streams[mAudioStreamId]->codec; + mFormatContext->streams[mAudioStreamId]->codec->codec_id #endif - if ( (mAudioCodec = avcodec_find_decoder(mAudioCodecContext->codec_id)) == NULL ) { + )) == NULL ) { Debug(1, "Can't find codec for audio stream from %s", mPath.c_str()); } else { +#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) + mAudioCodecContext = avcodec_alloc_context3(mAudioCodec); + avcodec_parameters_to_context( mAudioCodecContext, mFormatContext->streams[mAudioStreamId]->codecpar ); +#else + mAudioCodecContext = mFormatContext->streams[mAudioStreamId]->codec; + // = avcodec_alloc_context3(mAudioCodec); +#endif + Debug(1, "Audio Found decoder"); zm_dump_stream_format(mFormatContext, mAudioStreamId, 0, 0); // Open the codec @@ -584,7 +592,7 @@ int FfmpegCamera::OpenFfmpeg() { Debug ( 1, "Calling avcodec_open2" ); if ( avcodec_open2(mAudioCodecContext, mAudioCodec, 0) < 0 ) { #endif - Error( "Unable to open codec for video stream from %s", mPath.c_str() ); + Error( "Unable to open codec for audio stream from %s", mPath.c_str() ); return -1; } Debug(2, "Opened audio codec"); From aaef151815a1909dfba7153aaf7fa7cbf0a72193 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 5 Apr 2019 13:18:06 -0400 Subject: [PATCH 307/310] ZM_VERSION is a constant not a scalar variable --- scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm | 2 +- scripts/ZoneMinder/lib/ZoneMinder/Control/PSIA.pm | 2 +- scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm index e274aaaf7..e55a6da1c 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm @@ -84,7 +84,7 @@ sub open use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new(keep_alive => 1); - $self->{ua}->agent("ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION); + $self->{ua}->agent("ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION); $self->{state} = 'closed'; # credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string) $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/PSIA.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/PSIA.pm index fe067fe1f..438c5cff5 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/PSIA.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/PSIA.pm @@ -40,7 +40,7 @@ sub open use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION ); + $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); $self->{state} = 'closed'; Debug( "sendCmd credentials control address:'".$ADDRESS ."' realm:'" . $REALM diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm index 4ee710297..c57fc5c62 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm @@ -45,7 +45,7 @@ sub open { use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent('ZoneMinder Control Agent/'.$ZoneMinder::Base::ZM_VERSION); + $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); $self->{state} = 'closed'; # credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string) Debug ( "sendCmd credentials control address:'".$ADDRESS From e1873b1693b4b15b6df184f729538c69515f6975 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 5 Apr 2019 15:17:13 -0400 Subject: [PATCH 308/310] debug response content --- scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm index c57fc5c62..4afba372c 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm @@ -120,6 +120,7 @@ sub sendCmd { Debug('sendCmd command: ' . $url); if ( $res->is_success ) { + Debug($res->content); return !undef; } Error("Error check failed: '".$res->status_line()."' cmd:'".$cmd."'"); @@ -155,6 +156,7 @@ sub sendCmdPost { Debug("sendCmdPost credentials control to: $PROTOCOL$ADDRESS$url realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD."'"); if ( $res->is_success ) { + Debug($res->content); return !undef; } Error("sendCmdPost Error check failed: '".$res->status_line()."' cmd:"); From aa83239069431ad387c43fa659a4204709832dd8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 5 Apr 2019 15:18:03 -0400 Subject: [PATCH 309/310] rework zmcontrol.pl. If command is given and server is not up, use zmdc.pl to start it. Give up after 10 seconds. --- scripts/zmcontrol.pl.in | 185 ++++++++++++++++++++-------------------- 1 file changed, 91 insertions(+), 94 deletions(-) diff --git a/scripts/zmcontrol.pl.in b/scripts/zmcontrol.pl.in index b0388bcf3..f83bc82d4 100644 --- a/scripts/zmcontrol.pl.in +++ b/scripts/zmcontrol.pl.in @@ -30,7 +30,7 @@ use autouse 'Pod::Usage'=>qw(pod2usage); use POSIX qw/strftime EPIPE/; use Socket; #use Data::Dumper; -use Module::Load::Conditional qw{can_load};; +use Module::Load::Conditional qw{can_load}; use constant MAX_CONNECT_DELAY => 15; use constant MAX_COMMAND_WAIT => 1800; @@ -43,7 +43,7 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; logInit(); -my $arg_string = join( " ", @ARGV ); +my $arg_string = join(' ', @ARGV); my $id; my %options; @@ -64,22 +64,46 @@ GetOptions( ) or pod2usage(-exitstatus => -1); if ( !$id ) { - print( STDERR "Please give a valid monitor id\n" ); + print(STDERR "Please give a valid monitor id\n"); pod2usage(-exitstatus => -1); } ( $id ) = $id =~ /^(\w+)$/; -Debug("zmcontrol: arg string: $arg_string"); my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock'; +Debug("zmcontrol: arg string: $arg_string sock file $sock_file"); -socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) - or Fatal("Can't open socket: $!"); +socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!"); my $saddr = sockaddr_un($sock_file); -my $server_up = connect(CLIENT, $saddr); -if ( !$server_up ) { + +if ( $options{command} ) { + # Have a command, so we are the client, connect to the server and send it. + + my $tries = 10; + my $server_up; + while ( $tries and ! ( $server_up = connect(CLIENT, $saddr) ) ) { + Debug("Failed to connect to $server_up at $sock_file"); + runCommand("zmdc.pl start zmcontrol.pl --id=$id"); + sleep 1; + $tries -= 1; + } + if ( $server_up ) { + # The server is there, connect to it + #print( "Writing commands\n" ); + CLIENT->autoflush(); + + if ( $options{command} ) { + my $message = jsonEncode(\%options); + print(CLIENT $message); + } + shutdown(CLIENT, 1); + } else { + Error("Unable to connect to zmcontrol server at $sock_file"); + } +} else { + # The server isn't there my $monitor = zmDbGetMonitorAndControl($id); if ( !$monitor ) { @@ -113,99 +137,72 @@ if ( !$server_up ) { Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR"); } - if ( my $cpid = fork() ) { - logReinit(); + Info("Control server $id/$protocol starting at " + .strftime('%y/%m/%d %H:%M:%S', localtime()) + ); - # Parent process just sleep and fall through - socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) - or die("Can't open socket: $!"); - my $attempts = 0; - while ( !connect(CLIENT, $saddr) ) { - $attempts++; - Fatal("Can't connect: $! after $attempts attempts to $sock_file") if $attempts > MAX_CONNECT_DELAY; - sleep(1); - } - } elsif ( defined($cpid) ) { - close(STDOUT); - close(STDERR); + $0 = $0." --id=$id"; - setpgrp(); + my $control = "ZoneMinder::Control::$protocol"->new($id); + my $control_key = $control->getKey(); + $control->loadMonitor(); - logReinit(); + $control->open(); - Info("Control server $id/$protocol starting at " - .strftime('%y/%m/%d %H:%M:%S', localtime()) - ); - - $0 = $0." --id $id"; - - my $control = "ZoneMinder::Control::$protocol"->new($id); - my $control_key = $control->getKey(); - $control->loadMonitor(); - - $control->open(); - - socket(SERVER, PF_UNIX, SOCK_STREAM, 0) - or Fatal("Can't open socket: $!"); - unlink($sock_file); - bind(SERVER, $saddr) or Fatal("Can't bind: $!"); - listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!"); - - my $rin = ''; - vec( $rin, fileno(SERVER), 1 ) = 1; - my $win = $rin; - my $ein = $win; - my $timeout = MAX_COMMAND_WAIT; - while( 1 ) { - my $nfound = select(my $rout = $rin, undef, undef, $timeout); - if ( $nfound > 0 ) { - if ( vec( $rout, fileno(SERVER), 1 ) ) { - my $paddr = accept(CLIENT, SERVER); - my $message = ; - - next if !$message; - - my $params = jsonDecode($message); - #Debug( Dumper( $params ) ); - - my $command = $params->{command}; - close( CLIENT ); - if ( $command eq 'quit' ) { - last; - } - $control->$command($params); - } else { - Fatal('Bogus descriptor'); - } - } elsif ( $nfound < 0 ) { - if ( $! == EPIPE ) { - Error("Can't select: $!"); - } else { - Fatal("Can't select: $!"); - } - } else { - #print( "Select timed out\n" ); - last; - } - } # end while forever - Info("Control server $id/$protocol exiting"); - unlink($sock_file); - $control->close(); - exit(0); - } else { - Fatal("Can't fork: $!"); + # If we have a command when starting up, then do it. + if ( $options{command} ) { + my $command = $options{command}; + $control->$command(\%options); } + + socket(SERVER, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!"); + unlink($sock_file); + bind(SERVER, $saddr) or Fatal("Can't bind: $!"); + listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!"); + + my $rin = ''; + vec( $rin, fileno(SERVER), 1 ) = 1; + my $win = $rin; + my $ein = $win; + my $timeout = MAX_COMMAND_WAIT; + while( 1 ) { + my $nfound = select(my $rout = $rin, undef, undef, $timeout); + if ( $nfound > 0 ) { + if ( vec( $rout, fileno(SERVER), 1 ) ) { + my $paddr = accept(CLIENT, SERVER); + my $message = ; + + next if !$message; + + my $params = jsonDecode($message); + #Debug( Dumper( $params ) ); + + my $command = $params->{command}; + close( CLIENT ); + if ( $command eq 'quit' ) { + last; + } + $control->$command($params); + } else { + Fatal('Bogus descriptor'); + } + } elsif ( $nfound < 0 ) { + if ( $! == EPIPE ) { + Error("Can't select: $!"); + } else { + Fatal("Can't select: $!"); + } + } else { + #print( "Select timed out\n" ); + last; + } + } # end while forever + Info("Control server $id/$protocol exiting"); + unlink($sock_file); + $control->close(); + exit(0); } # end if !server up -# The server is there, connect to it -#print( "Writing commands\n" ); -CLIENT->autoflush(); - -if ( $options{command} ) { - my $message = jsonEncode(\%options); - print(CLIENT $message); -} -shutdown(CLIENT, 1); exit(0); From 381f526d66874703b877a315e108a6d4c82cb4a8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 5 Apr 2019 15:18:20 -0400 Subject: [PATCH 310/310] spacing --- web/includes/control_functions.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/web/includes/control_functions.php b/web/includes/control_functions.php index bea235668..77240df49 100644 --- a/web/includes/control_functions.php +++ b/web/includes/control_functions.php @@ -735,29 +735,29 @@ function buildControlCommand( $monitor ) { return( $ctrlCommand ); } -function sendControlCommand($mid,$command) { +function sendControlCommand($mid, $command) { // Either connects to running zmcontrol.pl or runs zmcontrol.pl to send the command. - $socket = socket_create( AF_UNIX, SOCK_STREAM, 0 ); + $socket = socket_create(AF_UNIX, SOCK_STREAM, 0); if ( $socket < 0 ) { - Fatal( 'socket_create() failed: '.socket_strerror($socket) ); + Fatal('socket_create() failed: '.socket_strerror($socket)); } $sockFile = ZM_PATH_SOCKS.'/zmcontrol-'.$mid.'.sock'; - if ( @socket_connect( $socket, $sockFile ) ) { + if ( @socket_connect($socket, $sockFile) ) { $options = array(); - foreach ( explode( ' ', $command ) as $option ) { - if ( preg_match( '/--([^=]+)(?:=(.+))?/', $option, $matches ) ) { + foreach ( explode(' ', $command) as $option ) { + if ( preg_match('/--([^=]+)(?:=(.+))?/', $option, $matches) ) { $options[$matches[1]] = $matches[2]?$matches[2]:1; } } - $optionString = jsonEncode( $options ); - if ( !socket_write( $socket, $optionString ) ) { - Fatal( "Can't write to control socket: ".socket_strerror(socket_last_error($socket)) ); + $optionString = jsonEncode($options); + if ( !socket_write($socket, $optionString) ) { + Fatal("Can't write to control socket: ".socket_strerror(socket_last_error($socket))); } - socket_close( $socket ); + socket_close($socket); } else if ( $command != 'quit' ) { $command .= ' --id='.$mid; // Can't connect so use script - $ctrlOutput = exec( escapeshellcmd( $command ) ); + $ctrlOutput = exec(escapeshellcmd($command)); } -} // end function sendControlCommand( $mid, $command ) +} // end function sendControlCommand($mid, $command)
- +
@@ -254,26 +253,11 @@ for ( $i = 0; $i < $pointCols; $i++ ) - - - - - - - -
disabled="disabled"/> disabled="disabled"/>+ 3 ) { ?>  X
 
checked="checked"/>
checked="checked"/>
checked="checked"/>
checked="checked"/>
DefaultRate() ); ?>
DefaultScale() ); ?>
DefaultCodec() ); ?>
+ + +
+ + +
+ + +
+ + +
+ + +
DefaultRate()); ?>
DefaultScale()); ?>
DefaultCodec()); ?>
@@ -1024,14 +1065,14 @@ if ( $monitor->Type() == 'Local' ) {
- +     
- +