mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-07-13 06:32:26 -04:00
Generate snapshot.jpg/alarm.jpg from video at end of event when no decoded frames were available
When a monitor runs in PASSTHROUGH mode or decodes only keyframes, packet->image is null for most (or all) packets, so snapshot.jpg and alarm.jpg are never written during recording. Fix: in Event::~Event(), after the video file is finalised, if snapshot_file_written is still false and a completed video file exists on disk, open it with FFmpeg_Input, seek to the offset of the highest-scoring frame (tracked via the new max_score_time member), decode one frame, convert it to a ZM Image, and write snapshot.jpg. If alarm frames were recorded but alarm.jpg was also never written, copy snapshot.jpg to alarm.jpg as a fallback. Fixes #issue - snapshot.jpg generation for recorded events.
This commit is contained in:
committed by
GitHub
parent
d4199475f0
commit
ed84517dc0
@@ -22,6 +22,7 @@
|
||||
#include "zm_camera.h"
|
||||
#include "zm_db.h"
|
||||
#include "zm_event_tag.h"
|
||||
#include "zm_ffmpeg_input.h"
|
||||
#include "zm_frame.h"
|
||||
#include "zm_logger.h"
|
||||
#include "zm_monitor.h"
|
||||
@@ -64,6 +65,7 @@ Event::Event(
|
||||
tot_score(0),
|
||||
max_score(-1),
|
||||
max_score_frame_id(0),
|
||||
max_score_time(p_start_time),
|
||||
//path(""),
|
||||
//snapshit_file(),
|
||||
snapshot_file_written(false),
|
||||
@@ -244,6 +246,72 @@ Event::~Event() {
|
||||
}
|
||||
}
|
||||
|
||||
// If snapshot.jpg was not written during recording (e.g. PASSTHROUGH mode
|
||||
// with no decoded images), try to generate it now from the completed video.
|
||||
if (!snapshot_file_written && !video_file.empty()) {
|
||||
std::string actual_video_path = path + "/" + video_file;
|
||||
struct stat fs;
|
||||
if (stat(actual_video_path.c_str(), &fs) == 0) {
|
||||
double offset_secs = std::max(0.0,
|
||||
std::chrono::duration_cast<FPSeconds>(max_score_time - start_time).count());
|
||||
FFmpeg_Input ffmpeg_input;
|
||||
if (ffmpeg_input.Open(actual_video_path.c_str()) > 0) {
|
||||
int vid_stream = ffmpeg_input.get_video_stream_id();
|
||||
if (vid_stream >= 0) {
|
||||
// av_frame is owned by ffmpeg_input (av_frame_ptr) and freed on scope exit
|
||||
AVFrame *av_frame = ffmpeg_input.get_frame(vid_stream, offset_secs);
|
||||
if (av_frame) {
|
||||
Image snap(av_frame, av_frame->width, av_frame->height);
|
||||
if (snap.WriteJpeg(snapshot_file)) {
|
||||
snapshot_file_written = true;
|
||||
Debug(1, "Generated snapshot.jpg from video for event %" PRIu64, id);
|
||||
} else {
|
||||
Warning("Failed to write snapshot.jpg from video for event %" PRIu64, id);
|
||||
}
|
||||
} else {
|
||||
Warning("Failed to get video frame for snapshot, event %" PRIu64, id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Warning("Failed to open video %s to generate snapshot for event %" PRIu64,
|
||||
actual_video_path.c_str(), id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If alarm.jpg was not written but we have alarm frames and a snapshot exists,
|
||||
// copy snapshot.jpg to alarm.jpg as a fallback.
|
||||
if (!alarm_frame_written && alarm_frames > 0 && snapshot_file_written) {
|
||||
struct stat alarm_stat;
|
||||
if (stat(alarm_file.c_str(), &alarm_stat) != 0) {
|
||||
// alarm.jpg doesn't exist, copy snapshot.jpg to alarm.jpg
|
||||
FILE *src = fopen(snapshot_file.c_str(), "rb");
|
||||
if (src) {
|
||||
FILE *dst = fopen(alarm_file.c_str(), "wb");
|
||||
if (dst) {
|
||||
char buf[65536];
|
||||
size_t n;
|
||||
bool write_ok = true;
|
||||
while ((n = fread(buf, 1, sizeof(buf), src)) > 0) {
|
||||
if (fwrite(buf, 1, n, dst) != n) {
|
||||
write_ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(dst);
|
||||
if (write_ok) {
|
||||
Debug(1, "Copied snapshot.jpg to alarm.jpg for event %" PRIu64, id);
|
||||
} else {
|
||||
Warning("Failed to copy snapshot.jpg to alarm.jpg for event %" PRIu64, id);
|
||||
}
|
||||
} else {
|
||||
Warning("Failed to open alarm.jpg for writing for event %" PRIu64, id);
|
||||
}
|
||||
fclose(src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endtime is set in AddFrame, so SHOULD be set to the value of the last frame timestamp.
|
||||
if (end_time.time_since_epoch() == Seconds(0)) {
|
||||
Warning("Empty endtime for event. Should not happen. Setting to now.");
|
||||
@@ -604,6 +672,7 @@ void Event::AddFrame(const std::shared_ptr<ZMPacket>&packet) {
|
||||
if (score > max_score) {
|
||||
max_score = score;
|
||||
max_score_frame_id = frames;
|
||||
max_score_time = packet->timestamp;
|
||||
}
|
||||
|
||||
if (db_frame) {
|
||||
|
||||
@@ -92,6 +92,7 @@ class Event {
|
||||
int tot_score;
|
||||
int max_score;
|
||||
int max_score_frame_id;
|
||||
SystemTimePoint max_score_time;
|
||||
std::string path;
|
||||
std::string snapshot_file;
|
||||
bool snapshot_file_written;
|
||||
|
||||
Reference in New Issue
Block a user