From 21df2c6ac7eed3fb0bdf0c82bf34b22a68038241 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Oct 2019 16:25:09 -0400 Subject: [PATCH 1/2] Always write snapshot.jpg and alarm.jpg instead of only when using video storage --- src/zm_event.cpp | 219 ++++++++++++++++++++++------------------------- 1 file changed, 102 insertions(+), 117 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index e12b76327..109df5dd3 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -87,7 +87,7 @@ Event::Event( char sql[ZM_SQL_MED_BUFSIZ]; struct tm *stime = localtime(&start_time.tv_sec); - snprintf(sql, sizeof(sql), "INSERT INTO Events ( MonitorId, StorageId, Name, StartTime, Width, Height, Cause, Notes, StateId, Orientation, Videoed, DefaultVideo, SaveJPEGs, Scheme ) values ( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '', %d, '%s' )", + snprintf(sql, sizeof(sql), "INSERT INTO Events ( MonitorId, StorageId, Name, StartTime, Width, Height, Cause, Notes, StateId, Orientation, Videoed, DefaultVideo, SaveJPEGs, Scheme ) VALUES ( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '', %d, '%s' )", monitor->Id(), storage->Id(), start_time.tv_sec, @@ -274,7 +274,7 @@ Event::~Event() { } db_mutex.unlock(); -} // Event::~Event() +} // Event::~Event() void Event::createNotes(std::string ¬es) { notes.clear(); @@ -288,27 +288,40 @@ void Event::createNotes(std::string ¬es) { notes += *setIter; } } -} +} // void Event::createNotes(std::string ¬es) -bool Event::WriteFrameImage(Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame) { +bool Event::WriteFrameImage( + Image *image, + struct timeval timestamp, + const char *event_file, + bool alarm_frame) { + + int thisquality = + (alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality)) ? + config.jpeg_alarm_file_quality : 0 ; // quality to use, zero is default - int thisquality = ( alarm_frame && (config.jpeg_alarm_file_quality > config.jpeg_file_quality) ) ? config.jpeg_alarm_file_quality : 0 ; // quality to use, zero is default bool rc; if ( !config.timestamp_on_capture ) { // stash the image we plan to use in another pointer regardless if timestamped. + // exif is only timestamp at present this switches on or off for write Image *ts_image = new Image(*image); monitor->TimestampImage(ts_image, ×tamp); - rc = ts_image->WriteJpeg(event_file, thisquality, (monitor->Exif() ? timestamp : (timeval){0,0})); // exif is only timestamp at present this switches on or off for write + rc = ts_image->WriteJpeg(event_file, thisquality, + (monitor->Exif() ? timestamp : (timeval){0,0})); delete(ts_image); } else { - rc = image->WriteJpeg(event_file, thisquality, (monitor->Exif() ? timestamp : (timeval){0,0})); // exif is only timestamp at present this switches on or off for write + rc = image->WriteJpeg(event_file, thisquality, + (monitor->Exif() ? timestamp : (timeval){0,0})); } return rc; } -bool Event::WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow ) { +bool Event::WriteFrameVideo( + const Image *image, + const struct timeval timestamp, + VideoWriter* videow) { const Image* frameimg = image; Image ts_image; @@ -321,13 +334,13 @@ bool Event::WriteFrameVideo( const Image *image, const struct timeval timestamp, /* If the image does not contain a timestamp, add the timestamp */ if ( !config.timestamp_on_capture ) { ts_image = *image; - monitor->TimestampImage( &ts_image, ×tamp ); + monitor->TimestampImage(&ts_image, ×tamp); frameimg = &ts_image; } /* Calculate delta time */ struct DeltaTimeval delta_time3; - DELTA_TIMEVAL( delta_time3, timestamp, start_time, DT_PREC_3 ); + DELTA_TIMEVAL(delta_time3, timestamp, start_time, DT_PREC_3); unsigned int timeMS = (delta_time3.sec * delta_time3.prec) + delta_time3.fsec; /* Encode and write the frame */ @@ -336,9 +349,9 @@ bool Event::WriteFrameVideo( const Image *image, const struct timeval timestamp, } return true; -} +} // bool Event::WriteFrameVideo -void Event::updateNotes( const StringSetMap &newNoteSetMap ) { +void Event::updateNotes(const StringSetMap &newNoteSetMap) { bool update = false; //Info( "Checking notes, %d <> %d", noteSetMap.size(), newNoteSetMap.size() ); @@ -347,24 +360,28 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) { noteSetMap = newNoteSetMap; update = true; } else { - for ( StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); newNoteSetMapIter != newNoteSetMap.end(); ++newNoteSetMapIter ) { + for ( StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin(); + newNoteSetMapIter != newNoteSetMap.end(); + ++newNoteSetMapIter ) { const std::string &newNoteGroup = newNoteSetMapIter->first; const StringSet &newNoteSet = newNoteSetMapIter->second; //Info( "Got %d new strings", newNoteSet.size() ); if ( newNoteSet.size() > 0 ) { - StringSetMap::iterator noteSetMapIter = noteSetMap.find( newNoteGroup ); + StringSetMap::iterator noteSetMapIter = noteSetMap.find(newNoteGroup); if ( noteSetMapIter == noteSetMap.end() ) { //Info( "Can't find note group %s, copying %d strings", newNoteGroup.c_str(), newNoteSet.size() ); - noteSetMap.insert( StringSetMap::value_type( newNoteGroup, newNoteSet ) ); + noteSetMap.insert(StringSetMap::value_type(newNoteGroup, newNoteSet)); update = true; } else { StringSet ¬eSet = noteSetMapIter->second; //Info( "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size() ); - for ( StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); newNoteSetIter != newNoteSet.end(); ++newNoteSetIter ) { + for ( StringSet::const_iterator newNoteSetIter = newNoteSet.begin(); + newNoteSetIter != newNoteSet.end(); + ++newNoteSetIter ) { const std::string &newNote = *newNoteSetIter; - StringSet::iterator noteSetIter = noteSet.find( newNote ); + StringSet::iterator noteSetIter = noteSet.find(newNote); if ( noteSetIter == noteSet.end() ) { - noteSet.insert( newNote ); + noteSet.insert(newNote); update = true; } } // end for @@ -376,9 +393,9 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) { if ( update ) { std::string notes; - createNotes( notes ); + createNotes(notes); - Debug( 2, "Updating notes for event %d, '%s'", id, notes.c_str() ); + Debug(2, "Updating notes for event %d, '%s'", id, notes.c_str()); static char sql[ZM_SQL_LGE_BUFSIZ]; #if USE_PREPARED_SQL static MYSQL_STMT *stmt = 0; @@ -387,16 +404,16 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) { unsigned long notesLen = 0; if ( !stmt ) { - const char *sql = "update Events set Notes = ? where Id = ?"; + const char *sql = "UPDATE `Events` SET `Notes` = ? WHERE `Id` = ?"; - stmt = mysql_stmt_init( &dbconn ); - if ( mysql_stmt_prepare( stmt, sql, strlen(sql) ) ) { - Fatal( "Unable to prepare sql '%s': %s", sql, mysql_stmt_error(stmt) ); + stmt = mysql_stmt_init(&dbconn); + if ( mysql_stmt_prepare(stmt, sql, strlen(sql)) ) { + Fatal("Unable to prepare sql '%s': %s", sql, mysql_stmt_error(stmt)); } /* Get the parameter count from the statement */ - if ( mysql_stmt_param_count( stmt ) != 2 ) { - Fatal( "Unexpected parameter count %ld in sql '%s'", mysql_stmt_param_count( stmt ), sql ); + if ( mysql_stmt_param_count(stmt) != 2 ) { + Error("Unexpected parameter count %ld in sql '%s'", mysql_stmt_param_count(stmt), sql); } MYSQL_BIND bind[2]; @@ -415,40 +432,40 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) { bind[1].length= 0; /* Bind the buffers */ - if ( mysql_stmt_bind_param( stmt, bind ) ) { - Fatal( "Unable to bind sql '%s': %s", sql, mysql_stmt_error(stmt) ); + if ( mysql_stmt_bind_param(stmt, bind) ) { + Error("Unable to bind sql '%s': %s", sql, mysql_stmt_error(stmt)); } } - strncpy( notesStr, notes.c_str(), sizeof(notesStr) ); + strncpy(notesStr, notes.c_str(), sizeof(notesStr)); - if ( mysql_stmt_execute( stmt ) ) { - Fatal( "Unable to execute sql '%s': %s", sql, mysql_stmt_error(stmt) ); + if ( mysql_stmt_execute(stmt) ) { + Error("Unable to execute sql '%s': %s", sql, mysql_stmt_error(stmt)); } #else static char escapedNotes[ZM_SQL_MED_BUFSIZ]; mysql_real_escape_string(&dbconn, escapedNotes, notes.c_str(), notes.length()); - snprintf(sql, sizeof(sql), "UPDATE Events SET Notes = '%s' WHERE Id = %" PRIu64, escapedNotes, id); + snprintf(sql, sizeof(sql), "UPDATE `Events` SET `Notes` = '%s' WHERE `Id` = %" PRIu64, escapedNotes, id); db_mutex.lock(); if ( mysql_query(&dbconn, sql) ) { Error("Can't insert event: %s", mysql_error(&dbconn)); } db_mutex.unlock(); #endif - } // end if update -} + } // end if update +} // void Event::updateNotes(const StringSetMap &newNoteSetMap) -void Event::AddFrames( int n_frames, Image **images, struct timeval **timestamps ) { +void Event::AddFrames(int n_frames, Image **images, struct timeval **timestamps) { for (int i = 0; i < n_frames; i += ZM_SQL_BATCH_SIZE) { AddFramesInternal(n_frames, i, images, timestamps); } } -void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ) { +void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, struct timeval **timestamps) { static char sql[ZM_SQL_LGE_BUFSIZ]; - strncpy(sql, "INSERT INTO Frames ( EventId, FrameId, TimeStamp, Delta ) VALUES ", sizeof(sql)); + strncpy(sql, "INSERT INTO `Frames` (`EventId`, `FrameId`, `TimeStamp`, `Delta`) VALUES ", sizeof(sql)); int frameCount = 0; for ( int i = start_frame; i < n_frames && i - start_frame < ZM_SQL_BATCH_SIZE; i++ ) { if ( timestamps[i]->tv_sec <= 0 ) { @@ -463,25 +480,28 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st 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 { - //If this is the first frame, we should add a thumbnail to the event directory - // ICON: We are working through the pre-event frames so this snapshot won't - // 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 ) { - WriteFrameImage(images[i], *(timestamps[i]), snapshot_file); - } } + //If this is the first frame, we should add a thumbnail to the event directory + // ICON: We are working through the pre-event frames so this snapshot won't + // 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 ) { + WriteFrameImage(images[i], *(timestamps[i]), snapshot_file); + } + if ( videowriter != NULL ) { WriteFrameVideo(images[i], *(timestamps[i]), videowriter); } struct DeltaTimeval delta_time; - DELTA_TIMEVAL( delta_time, *(timestamps[i]), start_time, DT_PREC_2 ); + DELTA_TIMEVAL(delta_time, *(timestamps[i]), start_time, DT_PREC_2); // Delta is Decimal(8,2) so 6 integer digits and 2 decimal digits if ( delta_time.sec > 999999 ) { Warning("Invalid delta_time from_unixtime(%ld), %s%ld.%02ld", - timestamps[i]->tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec ); + timestamps[i]->tv_sec, + (delta_time.positive?"":"-"), + delta_time.sec, + delta_time.fsec); delta_time.sec = 0; } @@ -504,7 +524,7 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st } else { Debug(1, "No valid pre-capture frames to add"); } -} +} // void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, struct timeval **timestamps) void Event::WriteDbFrames() { static char sql[ZM_SQL_LGE_BUFSIZ]; @@ -542,29 +562,52 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a frames++; bool write_to_db = false; + FrameType frame_type = score>0?ALARM:(score<0?BULK:NORMAL); + // < 0 means no motion detection is being done. + if ( score < 0 ) + score = 0; if ( monitor->GetOptSaveJPEGs() & 1 ) { - if ( frames == 1 ) - write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it. 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) ) { - write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it. - WriteFrameImage(image, timestamp, snapshot_file); - } + } + + // If this is the first frame, we should add a thumbnail to the event directory + if ( (frames == 1) || (score > (int)max_score) ) { + write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it. + WriteFrameImage(image, timestamp, snapshot_file); + } + + // We are writing an Alarm frame + if ( frame_type == ALARM ) { // The first frame with a score will be the frame that alarmed the event - if ( (!alarm_frame_written) && (score > 0) ) { + if ( !alarm_frame_written ) { write_to_db = true; // OD processing will need it, so the db needs to know about it alarm_frame_written = true; WriteFrameImage(image, timestamp, alarm_file); } - } + alarm_frames++; + + tot_score += score; + if ( score > (int)max_score ) + max_score = score; + + 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) ) { + Error("Failed to write analysis frame image"); + } + } + } + } // end if frame_type == ALARM + if ( videowriter != NULL ) { WriteFrameVideo(image, timestamp, videowriter); } @@ -572,11 +615,6 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a struct DeltaTimeval delta_time; DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2); - FrameType frame_type = score>0?ALARM:(score<0?BULK:NORMAL); - // < 0 means no motion detection is being done. - if ( score < 0 ) - score = 0; - bool db_frame = ( frame_type != BULK ) || (frames==1) || ((frames%config.bulk_frame_interval)==0) ; if ( db_frame ) { static char sql[ZM_SQL_MED_BUFSIZ]; @@ -613,57 +651,4 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a } // end if db_frame end_time = timestamp; - - // We are writing an Alarm frame - if ( frame_type == ALARM ) { - alarm_frames++; - - tot_score += score; - if ( score > (int)max_score ) - max_score = score; - - 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) ) { - Error("Failed to write analysis frame image"); - } - } - } - } // end if frame_type == ALARM - - /* This makes viewing the diagnostic images impossible because it keeps deleting them - if ( config.record_diag_images ) { - char diag_glob[PATH_MAX] = ""; - - snprintf( diag_glob, sizeof(diag_glob), "%s/%d/diag-*.jpg", staticConfig.DIR_EVENTS.c_str(), monitor->Id() ); - glob_t pglob; - int glob_status = glob( diag_glob, 0, 0, &pglob ); - if ( glob_status != 0 ) { - if ( glob_status < 0 ) { - Error( "Can't glob '%s': %s", diag_glob, strerror(errno) ); - } else { - Debug( 1, "Can't glob '%s': %d", diag_glob, glob_status ); - } - } else { - char new_diag_path[PATH_MAX] = ""; - for ( int i = 0; i < pglob.gl_pathc; i++ ) { - char *diag_path = pglob.gl_pathv[i]; - - char *diag_file = strstr( diag_path, "diag-" ); - - if ( diag_file ) { - snprintf( new_diag_path, sizeof(new_diag_path), general_file_format, path, frames, diag_file ); - - if ( rename( diag_path, new_diag_path ) < 0 ) { - Error( "Can't rename '%s' to '%s': %s", diag_path, new_diag_path, strerror(errno) ); - } - } - } - } - globfree( &pglob ); - } - */ -} +} // end void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image) From e0d085d1872285197e65b094bfd9aca0518da094 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 30 Oct 2019 16:57:45 -0400 Subject: [PATCH 2/2] CHeck for existence of snapshot.jpg or alarm.jpg instead of hitting the database. --- web/views/image.php | 89 +++++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 35 deletions(-) diff --git a/web/views/image.php b/web/views/image.php index 9486a282d..295b4b6c2 100644 --- a/web/views/image.php +++ b/web/views/image.php @@ -85,47 +85,66 @@ if ( empty($_REQUEST['path']) ) { $Frame = new ZM\Frame(); $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')); - if ( !$Frame ) { # no alarms, get first one I find - $Frame = ZM\Frame::find_one(array('EventId'=>$_REQUEST['eid'])); - if ( !$Frame ) { - ZM\Warning('No frame found for event '.$_REQUEST['eid']); - $Frame = new ZM\Frame(); - $Frame->Delta(1); - $Frame->FrameId(1); + $path = $Event->Path().'/alarm.jpg'; + if ( !file_exists($path) ) { + # legacy support + # look for first alarmed frame + $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 ) { + ZM\Warning('No frame found for event '.$_REQUEST['eid']); + $Frame = new ZM\Frame(); + $Frame->Delta(1); + $Frame->FrameId(1); + } + } + $Monitor = $Event->Monitor(); + if ( $Event->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'; + } else { + header('HTTP/1.0 404 Not Found'); + ZM\Fatal('No alarm jpg found for event '.$_REQUEST['eid']); + return; } - } - $Monitor = $Event->Monitor(); - if ( $Event->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'; } else { - $path = $Event->Path().'/alarm.jpg'; - } - } else if ( $_REQUEST['fid'] == 'snapshot' ) { - $Frame = ZM\Frame::find_one(array('EventId'=>$_REQUEST['eid'], 'Score'=>$Event->MaxScore())); - if ( !$Frame ) - $Frame = ZM\Frame::find_one(array('EventId'=>$_REQUEST['eid'])); - if ( !$Frame ) { - ZM\Warning('No frame found for event ' . $_REQUEST['eid']); $Frame = new ZM\Frame(); $Frame->Delta(1); - if ( $Event->SaveJPEGs() & 1 ) { - $Frame->FrameId(0); - } else { - $Frame->FrameId('snapshot'); + $Frame->FrameId('alarm'); + } # alarm.jpg found + } else if ( $_REQUEST['fid'] == 'snapshot' ) { + $path = $Event->Path().'/snapshot.jpg'; + if ( !file_exists($path) ) { + $Frame = ZM\Frame::find_one(array('EventId'=>$_REQUEST['eid'], 'Score'=>$Event->MaxScore())); + if ( !$Frame ) + $Frame = ZM\Frame::find_one(array('EventId'=>$_REQUEST['eid'])); + if ( !$Frame ) { + ZM\Warning('No frame found for event ' . $_REQUEST['eid']); + $Frame = new ZM\Frame(); + $Frame->Delta(1); + if ( $Event->SaveJPEGs() & 1 ) { + $Frame->FrameId(0); + } else { + $Frame->FrameId('snapshot'); + } } - } - $Monitor = $Event->Monitor(); - if ( $Event->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'; + $Monitor = $Event->Monitor(); + if ( $Event->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'; + } else { + header('HTTP/1.0 404 Not Found'); + ZM\Fatal('No alarm jpg found for event '.$_REQUEST['eid']); + return; + } # end if stored jpgs } else { - $path = $Event->Path().'/snapshot.jpg'; - } + $Frame = new ZM\Frame(); + $Frame->Delta(1); + $Frame->FrameId('snapshot'); + } # end if found snapshot.jpg } else { $Frame = ZM\Frame::find_one(array('EventId'=>$_REQUEST['eid'], 'FrameId'=>$_REQUEST['fid']));