Merge branch 'master' of github.com:ZoneMinder/zoneminder

This commit is contained in:
Isaac Connor 2021-11-11 13:58:56 -05:00
commit 6d30f5431b
41 changed files with 398 additions and 282 deletions

View File

@ -4,7 +4,7 @@
web/api/lib
web/includes/csrf/
web/js/videojs.zoomrotate.js
web/skins/classic/js/bootstrap.js
web/skins/classic/js/bootstrap-4.5.0.js
web/skins/classic/js/chosen
web/skins/classic/js/dateTimePicker
web/skins/classic/js/jquery-*.js

View File

@ -255,7 +255,7 @@ void zmDbQueue::process() {
Logger *log = Logger::fetch();
Logger::Level db_level = log->databaseLevel();
log->databaseLevel(Logger::NOLOG);
Warning("db queue size has grown larger than 10 entries");
Warning("db queue size has grown larger %zu than 10 entries", mQueue.size());
log->databaseLevel(db_level);
}
std::string sql = mQueue.front();

View File

@ -60,8 +60,8 @@ Event::Event(
//snapshit_file(),
//alarm_file(""),
videoStore(nullptr),
//video_name(""),
//video_file(""),
//video_path(""),
last_db_frame(0),
have_video_keyframe(false),
//scheme
@ -103,7 +103,14 @@ Event::Event(
// Copy it in case opening the mp4 doesn't work we can set it to another value
save_jpegs = monitor->GetOptSaveJPEGs();
Storage * storage = monitor->getStorage();
Storage *storage = monitor->getStorage();
if (monitor->GetOptVideoWriter() != 0) {
container = monitor->OutputContainer();
if ( container == "auto" || container == "" ) {
container = "mp4";
}
video_incomplete_file = "incomplete."+container;
}
std::string sql = stringtf(
"INSERT INTO `Events` "
@ -120,28 +127,28 @@ Event::Event(
state_id,
monitor->getOrientation(),
0,
"",
video_incomplete_file.c_str(),
save_jpegs,
storage->SchemeString().c_str()
);
id = zmDbDoInsert(sql);
if ( !SetPath(storage) ) {
if (!SetPath(storage)) {
// Try another
Warning("Failed creating event dir at %s", storage->Path());
sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id());
if ( monitor->ServerId() )
if (monitor->ServerId())
sql += stringtf(" AND ServerId=%u", monitor->ServerId());
Debug(1, "%s", sql.c_str());
delete storage;
storage = nullptr;
MYSQL_RES *result = zmDbFetch(sql);
if ( result ) {
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
if (result) {
for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) {
storage = new Storage(atoi(dbrow[0]));
if ( SetPath(storage) )
if (SetPath(storage))
break;
delete storage;
storage = nullptr;
@ -149,18 +156,18 @@ Event::Event(
mysql_free_result(result);
result = nullptr;
}
if ( !storage ) {
if (!storage) {
Info("No valid local storage area found. Trying all other areas.");
// Try remote
sql = "SELECT `Id` FROM `Storage` WHERE ServerId IS NULL";
if ( monitor->ServerId() )
if (monitor->ServerId())
sql += stringtf(" OR ServerId != %u", monitor->ServerId());
result = zmDbFetch(sql);
if ( result ) {
if (result) {
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
storage = new Storage(atoi(dbrow[0]));
if ( SetPath(storage) )
if (SetPath(storage))
break;
delete storage;
storage = nullptr;
@ -169,7 +176,7 @@ Event::Event(
result = nullptr;
}
}
if ( !storage ) {
if (!storage) {
storage = new Storage();
Warning("Failed to find a storage area to save events.");
}
@ -178,24 +185,16 @@ Event::Event(
} // end if ! setPath(Storage)
Debug(1, "Using storage area at %s", path.c_str());
video_name = "";
snapshot_file = path + "/snapshot.jpg";
alarm_file = path + "/alarm.jpg";
/* Save as video */
video_incomplete_path = path + "/" + video_incomplete_file;
if ( monitor->GetOptVideoWriter() != 0 ) {
std::string container = monitor->OutputContainer();
if ( container == "auto" || container == "" ) {
container = "mp4";
}
if (monitor->GetOptVideoWriter() != 0) {
/* Save as video */
video_name = stringtf("%" PRIu64 "-%s.%s", id, "video", container.c_str());
video_file = path + "/" + video_name;
Debug(1, "Writing video file to %s", video_file.c_str());
videoStore = new VideoStore(
video_file.c_str(),
video_incomplete_path.c_str(),
container.c_str(),
monitor->GetVideoStream(),
monitor->GetVideoCodecContext(),
@ -213,20 +212,31 @@ Event::Event(
zmDbDo(sql);
}
} else {
sql = stringtf("UPDATE Events SET Videoed=1, DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id);
zmDbDo(sql);
std::string codec = videoStore->get_codec();
video_file = stringtf("%" PRIu64 "-%s.%s.%s", id, "video", codec.c_str(), container.c_str());
video_path = path + "/" + video_file;
Debug(1, "Video file is %s", video_file.c_str());
}
} // end if GetOptVideoWriter
delete storage;
}
Event::~Event() {
// We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet.
/* Close the video file */
if ( videoStore != nullptr ) {
if (videoStore != nullptr) {
Debug(4, "Deleting video store");
delete videoStore;
videoStore = nullptr;
int result = rename(video_incomplete_path.c_str(), video_path.c_str());
if (result == 0) {
Debug(1, "File successfully renamed");
} else {
Error("Failed renaming %s to %s", video_incomplete_path.c_str(), video_path.c_str());
// So that we don't update the event record
video_file = video_incomplete_file;
}
}
// endtime is set in AddFrame, so SHOULD be set to the value of the last frame timestamp.
@ -245,21 +255,23 @@ Event::~Event() {
}
std::string sql = stringtf(
"UPDATE Events SET Name='%s%" PRIu64 "', EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64 " AND Name='New Event'",
"UPDATE Events SET Name='%s%" PRIu64 "', EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo='%s' WHERE Id = %" PRIu64 " AND Name='New Event'",
monitor->EventPrefix(), id, std::chrono::system_clock::to_time_t(end_time),
delta_time.count(),
frames, alarm_frames,
tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score,
video_file.c_str(), // defaults to ""
id);
if (!zmDbDoUpdate(sql)) {
// Name might have been changed during recording, so just do the update without changing the name.
sql = stringtf(
"UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d WHERE Id = %" PRIu64,
"UPDATE Events SET EndDateTime = from_unixtime(%ld), Length = %.2f, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo='%s' WHERE Id = %" PRIu64,
std::chrono::system_clock::to_time_t(end_time),
delta_time.count(),
frames, alarm_frames,
tot_score, static_cast<uint32>(alarm_frames ? (tot_score / alarm_frames) : 0), max_score,
video_file.c_str(), // defaults to ""
id);
zmDbDoUpdate(sql);
} // end if no changed rows due to Name change during recording
@ -312,32 +324,32 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) {
bool update = false;
//Info( "Checking notes, %d <> %d", noteSetMap.size(), newNoteSetMap.size() );
if ( newNoteSetMap.size() > 0 ) {
if ( noteSetMap.size() == 0 ) {
if (newNoteSetMap.size() > 0) {
if (noteSetMap.size() == 0) {
noteSetMap = newNoteSetMap;
update = true;
} else {
for ( StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin();
for (StringSetMap::const_iterator newNoteSetMapIter = newNoteSetMap.begin();
newNoteSetMapIter != newNoteSetMap.end();
++newNoteSetMapIter ) {
++newNoteSetMapIter) {
const std::string &newNoteGroup = newNoteSetMapIter->first;
const StringSet &newNoteSet = newNoteSetMapIter->second;
//Info( "Got %d new strings", newNoteSet.size() );
if ( newNoteSet.size() > 0 ) {
if (newNoteSet.size() > 0) {
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() );
if (noteSetMapIter == noteSetMap.end()) {
//Debug(3, "Can't find note group %s, copying %d strings", newNoteGroup.c_str(), newNoteSet.size());
noteSetMap.insert(StringSetMap::value_type(newNoteGroup, newNoteSet));
update = true;
} else {
StringSet &noteSet = noteSetMapIter->second;
//Info( "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size() );
for ( StringSet::const_iterator newNoteSetIter = newNoteSet.begin();
//Debug(3, "Found note group %s, got %d strings", newNoteGroup.c_str(), newNoteSet.size());
for (StringSet::const_iterator newNoteSetIter = newNoteSet.begin();
newNoteSetIter != newNoteSet.end();
++newNoteSetIter ) {
++newNoteSetIter) {
const std::string &newNote = *newNoteSetIter;
StringSet::iterator noteSetIter = noteSet.find(newNote);
if ( noteSetIter == noteSet.end() ) {
if (noteSetIter == noteSet.end()) {
noteSet.insert(newNote);
update = true;
}
@ -479,7 +491,7 @@ void Event::AddFrame(Image *image,
Debug(1, "Writing snapshot");
WriteFrameImage(image, timestamp, snapshot_file.c_str());
} else {
Debug(1, "Not Writing snapshot");
Debug(1, "Not Writing snapshot because score %d > max %d", score, max_score);
}
// We are writing an Alarm frame
@ -491,7 +503,7 @@ void Event::AddFrame(Image *image,
Debug(1, "Writing alarm image");
WriteFrameImage(image, timestamp, alarm_file.c_str());
} else {
Debug(1, "Not Writing alarm image");
Debug(3, "Not Writing alarm image because alarm frame already written");
}
if (alarm_image and (save_jpegs & 2)) {

View File

@ -84,8 +84,13 @@ class Event {
std::string alarm_file;
VideoStore *videoStore;
std::string video_name;
std::string container;
std::string codec;
std::string video_file;
std::string video_path;
std::string video_incomplete_file;
std::string video_incomplete_path;
int last_db_frame;
bool have_video_keyframe; // a flag to tell us if we have had a video keyframe when writing an mp4. The first frame SHOULD be a video keyframe.
Storage::Schemes scheme;

View File

@ -141,7 +141,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
event_data->storage_id = dbrow[1] ? atoi(dbrow[1]) : 0;
event_data->frame_count = dbrow[2] == nullptr ? 0 : atoi(dbrow[2]);
event_data->start_time = SystemTimePoint(Seconds(atoi(dbrow[3])));
event_data->end_time = dbrow[4] ? SystemTimePoint(Seconds(atoi(dbrow[4]))) : SystemTimePoint();
event_data->end_time = dbrow[4] ? SystemTimePoint(Seconds(atoi(dbrow[4]))) : std::chrono::system_clock::now();
event_data->duration = std::chrono::duration_cast<Microseconds>(event_data->end_time - event_data->start_time);
event_data->frames_duration =
std::chrono::duration_cast<Microseconds>(dbrow[5] ? FPSeconds(atof(dbrow[5])) : FPSeconds(0.0));

View File

@ -143,8 +143,8 @@ bool Fifo::writePacket(std::string filename, const ZMPacket &packet) {
bool Fifo::write(uint8_t *data, size_t bytes, int64_t pts) {
if (!(outfile or open())) return false;
// Going to write a brief header
Debug(1, "Writing header ZM %lu %" PRId64, bytes, pts);
if ( fprintf(outfile, "ZM %lu %" PRId64 "\n", bytes, pts) < 0 ) {
Debug(1, "Writing header ZM %zu %" PRId64, bytes, pts);
if (fprintf(outfile, "ZM %zu %" PRId64 "\n", bytes, pts) < 0) {
if (errno != EAGAIN) {
Error("Problem during writing: %s", strerror(errno));
} else {

View File

@ -270,7 +270,6 @@ int Image::PopulateFrame(AVFrame *frame) {
frame->width = width;
frame->height = height;
frame->format = imagePixFormat;
Debug(1, "PopulateFrame: width %d height %d linesize %d colours %d imagesize %d", width, height, linesize, colours, size);
zm_dump_video_frame(frame, "Image.Populate(frame)");
return 1;
} // int Image::PopulateFrame(AVFrame *frame)

View File

@ -155,7 +155,7 @@ bool Monitor::MonitorLink::connect() {
mem_size = sizeof(SharedData) + sizeof(TriggerData);
Debug(1, "link.mem.size=%jd", mem_size);
Debug(1, "link.mem.size=%jd", static_cast<intmax_t>(mem_size));
#if ZM_MEM_MAPPED
map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600);
if (map_fd < 0) {
@ -182,14 +182,14 @@ bool Monitor::MonitorLink::connect() {
disconnect();
return false;
} else if (map_stat.st_size < mem_size) {
Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, mem_size);
Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast<intmax_t>(mem_size));
disconnect();
return false;
}
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0);
if (mem_ptr == MAP_FAILED) {
Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), mem_size, strerror(errno));
Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), static_cast<intmax_t>(mem_size), strerror(errno));
disconnect();
return false;
}
@ -947,7 +947,7 @@ bool Monitor::connect() {
map_fd = -1;
return false;
} else {
Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, mem_size);
Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast<intmax_t>(mem_size));
close(map_fd);
map_fd = -1;
return false;
@ -959,18 +959,18 @@ bool Monitor::connect() {
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, map_fd, 0);
if (mem_ptr == MAP_FAILED) {
if (errno == EAGAIN) {
Debug(1, "Unable to map file %s (%jd bytes) to locked memory, trying unlocked", mem_file.c_str(), mem_size);
Debug(1, "Unable to map file %s (%jd bytes) to locked memory, trying unlocked", mem_file.c_str(), static_cast<intmax_t>(mem_size));
#endif
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0);
Debug(1, "Mapped file %s (%jd bytes) to unlocked memory", mem_file.c_str(), mem_size);
Debug(1, "Mapped file %s (%jd bytes) to unlocked memory", mem_file.c_str(), static_cast<intmax_t>(mem_size));
#ifdef MAP_LOCKED
} else {
Error("Unable to map file %s (%jd bytes) to locked memory (%s)", mem_file.c_str(), mem_size, strerror(errno));
Error("Unable to map file %s (%jd bytes) to locked memory (%s)", mem_file.c_str(), static_cast<intmax_t>(mem_size), strerror(errno));
}
}
#endif
if ((mem_ptr == MAP_FAILED) or (mem_ptr == nullptr)) {
Error("Can't map file %s (%jd bytes) to memory: %s(%d)", mem_file.c_str(), mem_size, strerror(errno), errno);
Error("Can't map file %s (%jd bytes) to memory: %s(%d)", mem_file.c_str(), static_cast<intmax_t>(mem_size), strerror(errno), errno);
close(map_fd);
map_fd = -1;
mem_ptr = nullptr;
@ -1656,7 +1656,7 @@ void Monitor::CheckAction() {
}
}
void Monitor::UpdateCaptureFPS() {
void Monitor::UpdateFPS() {
if ( fps_report_interval and
(
!(image_count%fps_report_interval)
@ -1675,82 +1675,35 @@ void Monitor::UpdateCaptureFPS() {
uint32 new_camera_bytes = camera->Bytes();
uint32 new_capture_bandwidth =
static_cast<uint32>((new_camera_bytes - last_camera_bytes) / elapsed.count());
last_camera_bytes = new_camera_bytes;
double new_analysis_fps = (motion_frame_count - last_motion_frame_count) / elapsed.count();
Debug(4, "%s: %d - last %d = %d now:%lf, last %lf, elapsed %lf = %lffps",
"Capturing",
Debug(4, "FPS: capture count %d - last capture count %d = %d now:%lf, last %lf, elapsed %lf = capture: %lf fps analysis: %lf fps",
image_count,
last_capture_image_count,
image_count - last_capture_image_count,
FPSeconds(now.time_since_epoch()).count(),
FPSeconds(last_analysis_fps_time.time_since_epoch()).count(),
FPSeconds(last_fps_time.time_since_epoch()).count(),
elapsed.count(),
new_capture_fps);
new_capture_fps,
new_analysis_fps);
Info("%s: %d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec",
name.c_str(), image_count, new_capture_fps, new_capture_bandwidth);
Info("%s: %d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec Analysing at %.2lf fps",
name.c_str(), image_count, new_capture_fps, new_capture_bandwidth, new_analysis_fps);
shared_data->capture_fps = new_capture_fps;
last_fps_time = now;
last_capture_image_count = image_count;
shared_data->analysis_fps = new_analysis_fps;
last_motion_frame_count = motion_frame_count;
last_camera_bytes = new_camera_bytes;
std::string sql = stringtf(
"UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u WHERE MonitorId=%u",
new_capture_fps, new_capture_bandwidth, id);
"UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = %.2lf, CaptureBandwidth=%u, AnalysisFPS = %.2lf WHERE MonitorId=%u",
new_capture_fps, new_capture_bandwidth, new_analysis_fps, id);
dbQueue.push(std::move(sql));
} // now != last_fps_time
} // end if report fps
} // void Monitor::UpdateCaptureFPS()
void Monitor::UpdateAnalysisFPS() {
Debug(1, "analysis_image_count(%d) motion_count(%d) fps_report_interval(%d) mod%d",
analysis_image_count, motion_frame_count, fps_report_interval,
((analysis_image_count && fps_report_interval) ? !(analysis_image_count%fps_report_interval) : -1 ) );
if (
( analysis_image_count and fps_report_interval and !(analysis_image_count%fps_report_interval) )
or
// In startup do faster updates
( (analysis_image_count < fps_report_interval) and !(analysis_image_count%10) )
) {
SystemTimePoint now = std::chrono::system_clock::now();
FPSeconds elapsed = now - last_analysis_fps_time;
Debug(4, "%s: %d - now: %.2f, last %lf, diff %lf",
name.c_str(),
analysis_image_count,
FPSeconds(now.time_since_epoch()).count(),
FPSeconds(last_analysis_fps_time.time_since_epoch()).count(),
elapsed.count());
if (elapsed > Seconds(1)) {
double new_analysis_fps = (motion_frame_count - last_motion_frame_count) / elapsed.count();
Info("%s: %d - Analysing at %.2lf fps from %d - %d=%d / %lf - %lf = %lf",
name.c_str(),
analysis_image_count,
new_analysis_fps,
motion_frame_count,
last_motion_frame_count,
(motion_frame_count - last_motion_frame_count),
FPSeconds(now.time_since_epoch()).count(),
FPSeconds(last_analysis_fps_time.time_since_epoch()).count(),
elapsed.count());
if (new_analysis_fps != shared_data->analysis_fps) {
shared_data->analysis_fps = new_analysis_fps;
std::string sql = stringtf("UPDATE LOW_PRIORITY Monitor_Status SET AnalysisFPS = %.2lf WHERE MonitorId=%u",
new_analysis_fps, id);
dbQueue.push(std::move(sql));
last_analysis_fps_time = now;
last_motion_frame_count = motion_frame_count;
} else {
Debug(4, "No change in fps");
} // end if change in fps
} // end if at least 1 second has passed since last update
} // end if time to do an update
} // end void Monitor::UpdateAnalysisFPS
} // void Monitor::UpdateFPS()
// Would be nice if this JUST did analysis
// This idea is that we should be analysing as close to the capture frame as possible.
@ -1909,8 +1862,6 @@ bool Monitor::Analyse() {
Debug(3, "signal and active and modect");
Event::StringSet zoneSet;
int motion_score = last_motion_score;
if (analysis_fps_limit) {
double capture_fps = get_capture_fps();
motion_frame_skip = capture_fps / analysis_fps_limit;
@ -1927,7 +1878,7 @@ bool Monitor::Analyse() {
} else {
Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image);
// Get new score.
motion_score = DetectMotion(*(snap->image), zoneSet);
int motion_score = DetectMotion(*(snap->image), zoneSet);
snap->zone_stats.reserve(zones.size());
for (const Zone &zone : zones) {
@ -1939,21 +1890,20 @@ bool Monitor::Analyse() {
Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)",
score, last_motion_score, motion_score);
motion_frame_count += 1;
// Why are we updating the last_motion_score too?
last_motion_score = motion_score;
if (motion_score) {
if (cause.length()) cause += ", ";
cause += MOTION_CAUSE;
noteSetMap[MOTION_CAUSE] = zoneSet;
} // end if motion_score
}
} else {
Debug(1, "no image so skipping motion detection");
} // end if has image
} else {
Debug(1, "Skipped motion detection last motion score was %d", motion_score);
Debug(1, "Skipped motion detection last motion score was %d", last_motion_score);
}
if (motion_score) {
score += motion_score;
if (cause.length()) cause += ", ";
cause += MOTION_CAUSE;
noteSetMap[MOTION_CAUSE] = zoneSet;
} // end if motion_score
score += last_motion_score;
} else {
Debug(1, "Not Active(%d) enabled %d active %d doing motion detection: %d",
Active(), enabled, shared_data->active,
@ -1966,7 +1916,7 @@ bool Monitor::Analyse() {
if (event) {
Debug(2, "Have event %" PRIu64 " in record", event->Id());
if (section_length != Seconds(0) && (timestamp - GetVideoWriterStartTime() >= section_length)
if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length)
&& ((function == MOCORD && event_close_mode != CLOSE_TIME)
|| (function == RECORD && event_close_mode == CLOSE_TIME)
|| std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()) % section_length == Seconds(0))) {
@ -1975,8 +1925,8 @@ bool Monitor::Analyse() {
image_count,
event->Id(),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(GetVideoWriterStartTime().time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp - GetVideoWriterStartTime()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(event->StartTime().time_since_epoch()).count()),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp - event->StartTime()).count()),
static_cast<int64>(Seconds(section_length).count()));
closeEvent();
} // end if section_length
@ -2054,26 +2004,27 @@ bool Monitor::Analyse() {
} // end if ! event
} // end if RECORDING
if (score and (function == MODECT or function == NODECT)) {
if (score and (function != MONITOR)) {
if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) {
// If we should end then previous continuous event and start a new non-continuous event
if (event && event->Frames()
&& !event->AlarmFrames()
&& event_close_mode == CLOSE_ALARM
&& timestamp - GetVideoWriterStartTime() >= min_section_length
&& (!pre_event_count || Event::PreAlarmCount() >= alarm_frame_count - 1)) {
&& (event_close_mode == CLOSE_ALARM)
&& ((timestamp - event->StartTime()) >= min_section_length)
&& ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) {
Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins",
name.c_str(), image_count, event->Id());
closeEvent();
} else if (event) {
// This is so if we need more than 1 alarm frame before going into alarm, so it is basically if we have enough alarm frames
Debug(3,
"pre_alarm_count in event %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %" PRIi64 " min",
Event::PreAlarmCount(),
"pre_alarm_count in event %d of %d, event frames %d, alarm frames %d event length %" PRIi64 " >=? %" PRIi64 " min close mode is ALARM? %d",
Event::PreAlarmCount(), pre_event_count,
event->Frames(),
event->AlarmFrames(),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp - GetVideoWriterStartTime()).count()),
static_cast<int64>(Seconds(min_section_length).count()));
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp - event->StartTime()).count()),
static_cast<int64>(Seconds(min_section_length).count()),
(event_close_mode == CLOSE_ALARM));
}
if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) {
// lets construct alarm cause. It will contain cause + names of zones alarmed
@ -2170,8 +2121,10 @@ bool Monitor::Analyse() {
Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count);
shared_data->state = state = ALERT;
} else if (state == ALERT) {
if (analysis_image_count - last_alarm_count > post_event_count
&& timestamp - GetVideoWriterStartTime() >= min_section_length) {
if (
((analysis_image_count - last_alarm_count) > post_event_count)
&&
((timestamp - event->StartTime()) >= min_section_length)) {
Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images",
name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames());
//if ( function != MOCORD || event_close_mode == CLOSE_ALARM || event->Cause() == SIGNAL_CAUSE )
@ -2189,7 +2142,8 @@ bool Monitor::Analyse() {
shared_data->state = state = ((function != MOCORD) ? IDLE : TAPE);
} else {
Debug(1,
"State %s because image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%" PRIi64 ")",
"State %d %s because analysis_image_count(%d)-last_alarm_count(%d) > post_event_count(%d) and timestamp.tv_sec(%" PRIi64 ") - recording.tv_src(%" PRIi64 ") >= min_section_length(%" PRIi64 ")",
state,
State_Strings[state].c_str(),
analysis_image_count,
last_alarm_count,
@ -2208,18 +2162,15 @@ bool Monitor::Analyse() {
// Generate analysis images if necessary
if ((savejpegs > 1) and snap->image) {
for (const Zone &zone : zones) {
if (zone.Alarmed()) {
if (zone.AlarmImage()) {
if (zone.Alarmed() and zone.AlarmImage()) {
if (!snap->analysis_image)
snap->analysis_image = new Image(*(snap->image));
snap->analysis_image->Overlay(*(zone.AlarmImage()));
}
} // end if zone is alarmed
} // end foreach zone
} // end if savejpegs
// incremement pre alarm image count
//have_pre_alarmed_frames ++;
Event::AddPreAlarmFrame(snap->image, timestamp, score, nullptr);
} else if (state == ALARM) {
for (const Zone &zone : zones) {
@ -2234,7 +2185,7 @@ bool Monitor::Analyse() {
if (event) {
if (noteSetMap.size() > 0)
event->updateNotes(noteSetMap);
if (section_length != Seconds(0) && (timestamp - GetVideoWriterStartTime() >= section_length)) {
if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length)) {
Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64,
name.c_str(), analysis_image_count, event->Id(),
static_cast<int64>(std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch()).count()),
@ -2299,8 +2250,6 @@ bool Monitor::Analyse() {
// Only do these if it's a video packet.
shared_data->last_read_index = snap->image_index;
analysis_image_count++;
if (function == MODECT or function == MOCORD)
UpdateAnalysisFPS();
}
packetqueue.increment_it(analysis_it);
packetqueue.unlock(packet_lock);
@ -2363,7 +2312,7 @@ void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) {
while ( 1 ) {
dest_ptr = link_id_str;
while ( *src_ptr >= '0' && *src_ptr <= '9' ) {
if ( (dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) {
if ( (unsigned int)(dest_ptr-link_id_str) < (unsigned int)(sizeof(link_id_str)-1) ) {
*dest_ptr++ = *src_ptr++;
} else {
break;
@ -2585,7 +2534,6 @@ int Monitor::Capture() {
// Will only be queued if there are iterators allocated in the queue.
packetqueue.queuePacket(packet);
UpdateCaptureFPS();
} else { // result == 0
// Question is, do we update last_write_index etc?
return 0;
@ -2625,7 +2573,7 @@ bool Monitor::Decode() {
//
//capture_image = packet->image = new Image(width, height, camera->Colours(), camera->SubpixelOrder());
int ret = packet->decode(camera->getVideoCodecContext());
if (ret > 0) {
if (ret > 0 and !zm_terminate) {
if (packet->in_frame and !packet->image) {
packet->image = new Image(camera_width, camera_height, camera->Colours(), camera->SubpixelOrder());
AVFrame *input_frame = packet->in_frame;
@ -2795,7 +2743,7 @@ void Monitor::TimestampImage(Image *ts_image, SystemTimePoint ts_time) const {
const char *s_ptr = label_time_text;
char *d_ptr = label_text;
while (*s_ptr && ((d_ptr - label_text) < (unsigned int) sizeof(label_text))) {
while (*s_ptr && ((unsigned int)(d_ptr - label_text) < (unsigned int) sizeof(label_text))) {
if ( *s_ptr == config.timestamp_code_char[0] ) {
bool found_macro = false;
switch ( *(s_ptr+1) ) {
@ -2811,7 +2759,7 @@ void Monitor::TimestampImage(Image *ts_image, SystemTimePoint ts_time) const {
typedef std::chrono::duration<int64, std::centi> Centiseconds;
Centiseconds centi_sec = std::chrono::duration_cast<Centiseconds>(
ts_time.time_since_epoch() - std::chrono::duration_cast<Seconds>(ts_time.time_since_epoch()));
d_ptr += snprintf(d_ptr, sizeof(label_text) - (d_ptr - label_text), "%02ld", centi_sec.count());
d_ptr += snprintf(d_ptr, sizeof(label_text) - (d_ptr - label_text), "%02lld", static_cast<long long int>(centi_sec.count()));
found_macro = true;
break;
}

View File

@ -100,7 +100,7 @@ public:
} Deinterlace;
typedef enum {
UNKNOWN=-1,
UNKNOWN,
IDLE,
PREALARM,
ALARM,
@ -546,8 +546,7 @@ public:
unsigned int GetLastWriteIndex() const;
uint64_t GetLastEventId() const;
double GetFPS() const;
void UpdateAnalysisFPS();
void UpdateCaptureFPS();
void UpdateFPS();
void ForceAlarmOn( int force_score, const char *force_case, const char *force_text="" );
void ForceAlarmOff();
void CancelForced();

View File

@ -229,6 +229,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
break;
case CMD_QUIT :
Info("User initiated exit - CMD_QUIT");
zm_terminate = true;
break;
case CMD_QUERY :
Debug(1, "Got QUERY command, sending STATUS");
@ -315,16 +316,6 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
}
}
Debug(2, "Number of bytes sent to (%s): (%d)", rem_addr.sun_path, nbytes);
// quit after sending a status, if this was a quit request
if ( (MsgCommand)msg->msg_data[0] == CMD_QUIT ) {
zm_terminate = true;
Debug(2, "Quitting");
return;
}
//Debug(2,"Updating framerate");
//updateFrameRate(monitor->GetFPS());
} // end void MonitorStream::processCommand(const CmdMsg *msg)
bool MonitorStream::sendFrame(const std::string &filepath, SystemTimePoint timestamp) {

View File

@ -209,7 +209,7 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
--it;
}
}
Debug(1, "Tail count is %d, queue size is %lu", tail_count, pktQueue.size());
Debug(1, "Tail count is %d, queue size is %zu", tail_count, pktQueue.size());
if (!keep_keyframes) {
// If not doing passthrough, we don't care about starting with a keyframe so logic is simpler

View File

@ -46,6 +46,7 @@ RETSIGTYPE zm_die_handler(int signal, siginfo_t * info, void *context)
RETSIGTYPE zm_die_handler(int signal)
#endif
{
zm_terminate = true;
Error("Got signal %d (%s), crashing", signal, strsignal(signal));
#if (defined(__i386__) || defined(__x86_64__))
// Get more information if available

View File

@ -619,7 +619,8 @@ VideoStore::~VideoStore() {
Debug(1, "Writing trailer");
/* Write the trailer before close */
if (int rc = av_write_trailer(oc)) {
int rc;
if ((rc = av_write_trailer(oc)) < 0) {
Error("Error writing trailer %s", av_err2str(rc));
} else {
Debug(3, "Success Writing trailer");
@ -629,7 +630,7 @@ VideoStore::~VideoStore() {
if (!(out_format->flags & AVFMT_NOFILE)) {
/* Close the out file. */
Debug(4, "Closing");
if (int rc = avio_close(oc->pb)) {
if ((rc = avio_close(oc->pb)) < 0) {
Error("Error closing avio %s", av_err2str(rc));
}
} else {

View File

@ -111,6 +111,13 @@ class VideoStore {
int writePacket(const std::shared_ptr<ZMPacket> &pkt);
int write_packets(PacketQueue &queue);
void flush_codecs();
const char *get_codec() {
if (chosen_codec_data)
return chosen_codec_data->codec_codec;
if (video_out_stream)
return avcodec_get_name(video_out_stream->codecpar->codec_id);
return "";
}
};
#endif // ZM_VIDEOSTORE_H

View File

@ -268,7 +268,7 @@ int main(int argc, char *argv[]) {
std::this_thread::sleep_for(sleep_time);
}
if (zm_terminate){
if (zm_terminate) {
break;
}
@ -308,6 +308,7 @@ int main(int argc, char *argv[]) {
result = -1;
break;
}
monitors[i]->UpdateFPS();
// capture_delay is the amount of time we should sleep in useconds to achieve the desired framerate.
Microseconds delay = (monitors[i]->GetState() == Monitor::ALARM) ? monitors[i]->GetAlarmCaptureDelay()

View File

@ -230,12 +230,11 @@ rm .gitignore
cd ../
if [ !-e "$DIRECTORY.orig.tar.gz" ]; then
read -p "$DIRECTORY.orig.tar.gz does not exist, create it? [Y/n]"
if [[ $REPLY == [yY] ]]; then
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
fi;
if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then
read -p "$DIRECTORY.orig.tar.gz does not exist, create it? [Y/n]"
if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
fi;
fi;
IFS=',' ;for DISTRO in `echo "$DISTROS"`; do

View File

@ -93,7 +93,7 @@ if ( canView('Events') or canView('Snapshots') ) {
$exportFormat,
$exportCompress,
$exportStructure,
(!empty($_REQUEST['exportFile'])?$_REQUEST['exportFile']:'zmExport'),
(!empty($_REQUEST['exportFile'])?$_REQUEST['exportFile']:'zmExport')
)) {
ajaxResponse(array('exportFile'=>$exportFile));
} else {

View File

@ -67,20 +67,19 @@ if (isset($_REQUEST['sort'])) {
// Offset specifies the starting row to return, used for pagination
$offset = 0;
if ( isset($_REQUEST['offset']) ) {
if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) {
if (isset($_REQUEST['offset'])) {
if ((!is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']))) {
ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']);
} else {
$offset = $_REQUEST['offset'];
}
}
// Limit specifies the number of rows to return
// Set the default to 0 for events view, to prevent an issue with ALL pagination
$limit = 0;
if ( isset($_REQUEST['limit']) ) {
if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) {
if (isset($_REQUEST['limit'])) {
if ((!is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']))) {
ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']);
} else {
$limit = $_REQUEST['limit'];
@ -91,25 +90,24 @@ if ( isset($_REQUEST['limit']) ) {
// MAIN LOOP
//
switch ( $task ) {
switch ($task) {
case 'archive' :
foreach ( $eids as $eid ) archiveRequest($task, $eid);
foreach ($eids as $eid) archiveRequest($task, $eid);
break;
case 'unarchive' :
# The idea is that anyone can archive, but only people with Event Edit permission can unarchive..
if ( !canEdit('Events') ) {
if (!canEdit('Events')) {
ajaxError('Insufficient permissions for user '.$user['Username']);
return;
}
foreach ( $eids as $eid ) archiveRequest($task, $eid);
foreach ($eids as $eid) archiveRequest($task, $eid);
break;
case 'delete' :
if ( !canEdit('Events') ) {
if (!canEdit('Events')) {
ajaxError('Insufficient permissions for user '.$user['Username']);
return;
}
foreach ( $eids as $eid ) $data[] = deleteRequest($eid);
foreach ($eids as $eid) $data[] = deleteRequest($eid);
break;
case 'query' :
$data = queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit);
@ -139,6 +137,8 @@ function deleteRequest($eid) {
$message[] = array($eid=>'Event not found.');
} else if ( $event->Archived() ) {
$message[] = array($eid=>'Event is archived, cannot delete it.');
} else if (!$event->canEdit()) {
$message[] = array($eid=>'You do not have permission to delete event '.$event->Id());
} else {
$event->delete();
}
@ -147,7 +147,6 @@ function deleteRequest($eid) {
}
function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit) {
$data = array(
'total' => 0,
'totalNotFiltered' => 0,
@ -156,7 +155,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
);
$failed = !$filter->test_pre_sql_conditions();
if ( $failed ) {
if ($failed) {
ZM\Debug('Pre conditions failed, not doing sql');
return $data;
}
@ -171,7 +170,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
// The names of columns shown in the event view that are NOT dB columns in the database
$col_alt = array('Monitor', 'Storage');
if ( !in_array($sort, array_merge($columns, $col_alt)) ) {
if (!in_array($sort, array_merge($columns, $col_alt))) {
ZM\Error('Invalid sort field: ' . $sort);
$sort = 'Id';
}
@ -186,7 +185,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
$storage_areas = ZM\Storage::find();
$StorageById = array();
foreach ( $storage_areas as $S ) {
foreach ($storage_areas as $S) {
$StorageById[$S->Id()] = $S;
}
@ -195,41 +194,43 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
ZM\Debug('Calling the following sql query: ' .$sql);
$query = dbQuery($sql, $values);
if ( $query ) {
while ( $row = dbFetchNext($query) ) {
$event = new ZM\Event($row);
$event->remove_from_cache();
if ( !$filter->test_post_sql_conditions($event) ) {
continue;
}
$event_ids[] = $event->Id();
$unfiltered_rows[] = $row;
} # end foreach row
if (!$query) {
ajaxError(dbError($sql));
return;
}
while ($row = dbFetchNext($query)) {
$event = new ZM\Event($row);
$event->remove_from_cache();
if (!$filter->test_post_sql_conditions($event)) {
continue;
}
$event_ids[] = $event->Id();
$unfiltered_rows[] = $row;
} # end foreach row
ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.');
$filtered_rows = null;
if ( count($advsearch) or $search != '' ) {
if (count($advsearch) or $search != '') {
$search_filter = new ZM\Filter();
$search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'Id', 'op'=>'IN', 'val'=>$event_ids));
// There are two search bars in the log view, normal and advanced
// Making an exuctive decision to ignore the normal search, when advanced search is in use
// Alternatively we could try to do both
if ( count($advsearch) ) {
if (count($advsearch)) {
$terms = array();
foreach ( $advsearch as $col=>$text ) {
foreach ($advsearch as $col=>$text) {
$terms[] = array('cnj'=>'and', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$text);
} # end foreach col in advsearch
$terms[0]['obr'] = 1;
$terms[count($terms)-1]['cbr'] = 1;
$search_filter->addTerms($terms);
} else if ( $search != '' ) {
} else if ($search != '') {
$search = '%' .$search. '%';
$terms = array();
foreach ( $columns as $col ) {
foreach ($columns as $col) {
$terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$search);
}
$terms[0]['obr'] = 1;
@ -239,15 +240,17 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
} # end if search
$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order;
ZM\Debug('Calling the following sql query: ' .$sql);
$filtered_rows = dbFetchAll($sql);
ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter.');
ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter: '.$sql);
} else {
$filtered_rows = $unfiltered_rows;
} # end if search_filter->terms() > 1
if ($limit)
$filtered_rows = array_slice($filtered_rows, $offset, $limit);
$returned_rows = array();
foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) {
foreach ($filtered_rows as $row) {
$event = new ZM\Event($row);
$scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width());

View File

@ -38,7 +38,7 @@ if ($zmuOutput) {
$ctls = shell_exec('v4l2-ctl -d '.$monitor->Device().' --list-ctrls');
if (!$ctls) {
ZM\Warning("Guessing v4l ctrls. We need v4l2-ctl please install it");
ZM\Warning('Guessing v4l ctrls. We need v4l2-ctl please install it');
$ctls = '
brightness 0x00980900 (int) : min=-10 max=10 step=1 default=0 value=8
contrast 0x00980901 (int) : min=0 max=20 step=1 default=10 value=12
@ -83,10 +83,15 @@ foreach ($ctls as $line) {
}
}
$label = translate($setting_uc);
if ($label == $setting_uc) {
$label = ucwords(str_replace('_', ' ', $label));
}
if ($setting == 'brightness' or $setting == 'colour' or $setting == 'contrast' or $setting == 'hue') {
echo '
<tr>
<th scope="row">'.translate($setting_uc).'</th>
<th scope="row">'.$label.'</th>
<td>'.$min.'</td><td><input type="range" title="'.$value.'" min="'.$min.'" max="'.$max.'" step="'.$step.'" default="'.$default.'" value="'.$value.'" id="new'.$setting_uc.'" name="new'.$setting_uc.'" '.(canEdit('Control') ? '' : 'disabled="disabled"') .'/></td><td>'.$max.'</td>
</tr>
';
@ -94,7 +99,7 @@ foreach ($ctls as $line) {
if ($type == '(bool)') {
echo '
<tr>
<th scope="row">'.translate($setting_uc).'</th>
<th scope="row">'.$label.'</th>
<td></td><td>'.html_radio('new'.$setting_uc, array('0'=>translate('True'), '1', translate('False')), $value, array('disabled'=>'disabled')).'
</td><td></td>
</tr>
@ -102,14 +107,14 @@ foreach ($ctls as $line) {
} else if ($type == '(int)') {
echo '
<tr>
<th scope="row">'.translate($setting_uc).'</th>
<th scope="row">'.$label.'</th>
<td></td><td><input type="range" '.$ctl[1].' disabled="disabled"/></td><td></td>
</tr>
';
} else {
echo '
<tr>
<th scope="row">'.translate($setting_uc).'</th>
<th scope="row">'.$label.'</th>
<td></td><td>'.$value.'</td><td></td>
</tr>
';

View File

@ -495,6 +495,10 @@ class Monitor extends ZM_Object {
return $this->Server()->UrlToIndex($port);
}
public function UrlToZMS($port=null) {
return $this->Server()->UrlToZMS($port).'?mid='.$this->Id();
}
public function sendControlCommand($command) {
// command is generally a command option list like --command=blah but might be just the word quit

View File

@ -112,7 +112,7 @@ function dbLog($sql, $update=false) {
function dbError($sql) {
global $dbConn;
$error = $dbConn->errorInfo();
if ( !$error[0] )
if (!$error[0])
return '';
$message = "SQL-ERR '".implode("\n", $dbConn->errorInfo())."', statement was '".$sql."'";
@ -130,17 +130,17 @@ function dbEscape( $string ) {
function dbQuery($sql, $params=NULL, $debug = false) {
global $dbConn;
if ( dbLog($sql, true) )
if (dbLog($sql, true))
return;
$result = NULL;
try {
if ( isset($params) ) {
if ( ! $result = $dbConn->prepare($sql) ) {
if (isset($params)) {
if (!$result = $dbConn->prepare($sql)) {
ZM\Error("SQL: Error preparing $sql: " . $pdo->errorInfo);
return NULL;
}
if ( ! $result->execute($params) ) {
if (!$result->execute($params)) {
ZM\Error("SQL: Error executing $sql: " . print_r($result->errorInfo(), true));
return NULL;
}

View File

@ -3,8 +3,10 @@ function MonitorStream(monitorData) {
this.id = monitorData.id;
this.connKey = monitorData.connKey;
this.url = monitorData.url;
this.url_to_zms = monitorData.url_to_zms;
this.width = monitorData.width;
this.height = monitorData.height;
this.scale = 100;
this.status = null;
this.alarmState = STATE_IDLE;
this.lastAlarmState = STATE_IDLE;
@ -15,19 +17,68 @@ function MonitorStream(monitorData) {
};
this.type = monitorData.type;
this.refresh = monitorData.refresh;
this.element = null;
this.getElement = function() {
if (this.element) return this.element;
this.element = document.getElementById('liveStream'+this.id);
if (!this.element) {
console.error("No img for #liveStream"+this.id);
}
return this.element;
};
/* if the img element didn't have a src, this would fill it in, causing it to show. */
this.show = function() {
const stream = this.getElement();
if (!stream.src) {
stream.src = this.url_to_zms+"&mode=single&scale=100&connkey="+this.connKey;
}
};
this.setScale = function(newscale) {
const img = this.getElement();
if (!img) return;
this.scale = newscale;
const oldSrc = img.getAttribute('src');
let newSrc = '';
img.setAttribute('src', '');
console.log("Scaling to: " + newscale);
if (newscale == '0' || newscale == 'auto') {
let bottomElement = document.getElementById('replayStatus');
if (!bottomElement) {
bottomElement = document.getElementById('monitorState');
}
var newSize = scaleToFit(this.width, this.height, $j(img), $j(bottomElement));
console.log(newSize);
newWidth = newSize.width;
newHeight = newSize.height;
autoScale = parseInt(newSize.autoScale);
// This is so that we don't waste bandwidth and let the browser do all the scaling.
if (autoScale > 100) autoScale = 100;
if (autoScale) {
newSrc = oldSrc.replace(/scale=\d+/i, 'scale='+autoScale);
}
} else {
newWidth = this.width * newscale / SCALE_BASE;
newHeight = this.height * newscale / SCALE_BASE;
img.width(newWidth);
img.height(newHeight);
if (newscale > 100) newscale = 100;
newSrc = oldSrc.replace(/scale=\d+/i, 'scale='+newscale);
}
img.setAttribute('src', newSrc);
};
this.start = function(delay) {
// Step 1 make sure we are streaming instead of a static image
var stream = $j('#liveStream'+this.id);
if (!stream.length) {
console.log('No live stream');
return;
}
stream = stream[0];
if ( !stream ) {
console.log('No live stream');
return;
}
if ( !stream.src ) {
const stream = this.getElement();
if (!stream) return;
if (!stream.src) {
// Website Monitors won't have an img tag
console.log('No src for #liveStream'+this.id);
console.log(stream);
@ -38,7 +89,7 @@ function MonitorStream(monitorData) {
src += '&connkey='+this.connKey;
}
if ( stream.src != src ) {
console.log("Setting to streaming");
console.log("Setting to streaming: " + src);
stream.src = '';
stream.src = src;
}

View File

@ -916,7 +916,7 @@ function xhtmlFooter() {
?>
<script src="<?php echo cache_bust('skins/'.$skin.'/js/jquery.min.js'); ?>"></script>
<script src="skins/<?php echo $skin; ?>/js/jquery-ui-1.12.1/jquery-ui.min.js"></script>
<script src="<?php echo cache_bust('skins/'.$skin.'/js/bootstrap.min.js'); ?>"></script>
<script src="skins/<?php echo $skin; ?>/js/bootstrap-4.5.0.min.js"></script>
<?php echo output_script_if_exists(array(
'js/tableExport.min.js',
'js/bootstrap-table.min.js',

View File

@ -584,10 +584,21 @@ function scaleToFit(baseWidth, baseHeight, scaleEl, bottomEl) {
$j(window).on('resize', endOfResize); //set delayed scaling when Scale to Fit is selected
var ratio = baseWidth / baseHeight;
var container = $j('#content');
if (!container) {
console.error("No container found");
return;
}
if (!bottomEl || !bottomEl.length) {
bottomEl = $j(container[0].lastElementChild);
}
//console.log(bottomEl);
var viewPort = $j(window);
// jquery does not provide a bottom offet, and offset dows not include margins. outerHeight true minus false gives total vertical margins.
// jquery does not provide a bottom offset, and offset does not include margins. outerHeight true minus false gives total vertical margins.
var bottomLoc = bottomEl.offset().top + (bottomEl.outerHeight(true) - bottomEl.outerHeight()) + bottomEl.outerHeight(true);
//console.log("bottomLoc: " + bottomEl.offset().top + " + (" + bottomEl.outerHeight(true) + ' - ' + bottomEl.outerHeight() +') + '+bottomEl.outerHeight(true));
var newHeight = viewPort.height() - (bottomLoc - scaleEl.outerHeight(true));
//console.log("newHeight = " + viewPort.height() +" - " + bottomLoc + ' - ' + scaleEl.outerHeight(true));
var newWidth = ratio * newHeight;
if (newWidth > container.innerWidth()) {
newWidth = container.innerWidth();
@ -598,13 +609,15 @@ function scaleToFit(baseWidth, baseHeight, scaleEl, bottomEl) {
return parseInt($j(this).val());
}).get();
scales.shift();
var closest;
var closest = null;
$j(scales).each(function() { //Set zms scale to nearest regular scale. Zoom does not like arbitrary scale values.
if (closest == null || Math.abs(this - autoScale) < Math.abs(closest - autoScale)) {
closest = this.valueOf();
}
});
autoScale = closest;
if (closest) {
autoScale = closest;
}
return {width: Math.floor(newWidth), height: Math.floor(newHeight), autoScale: autoScale};
}
@ -947,3 +960,29 @@ function initThumbAnimation() {
});
}
}
/* View in fullscreen */
function openFullscreen(elem) {
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) {
/* Safari */
elem.webkitRequestFullscreen();
} else if (elem.msRequestFullscreen) {
/* IE11 */
elem.msRequestFullscreen();
}
}
/* Close fullscreen */
function closeFullscreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
/* Safari */
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
/* IE11 */
document.msExitFullscreen();
}
}

View File

@ -54,6 +54,7 @@ foreach ( $perms as $perm ) {
?>
var ANIMATE_THUMBS = <?php echo ZM_WEB_ANIMATE_THUMBS?'true':'false' ?>;
var SCALE_BASE = <?php echo SCALE_BASE ?>;
var refreshParent = <?php
if ( ! empty($refreshParent) ) {

View File

@ -300,7 +300,7 @@ function getCmdResponse(respObj, respText) {
if (streamStatus.auth) {
// Try to reload the image stream.
var streamImg = $j('#evtStream');
var streamImg = document.getElementById('evtStream');
if (streamImg) {
streamImg.src = streamImg.src.replace(/auth=\w+/i, 'auth='+streamStatus.auth);
}

View File

@ -35,12 +35,16 @@ var params =
// Called by bootstrap-table to retrieve zm event data
function ajaxRequest(params) {
if ( params.data && params.data.filter ) {
if (params.data && params.data.filter) {
params.data.advsearch = params.data.filter;
delete params.data.filter;
}
$j.getJSON(thisUrl + '?view=request&request=events&task=query'+filterQuery, params.data)
.done(function(data) {
if (data.result == 'Error') {
alert(data.message);
return;
}
var rows = processRows(data.rows);
// rearrange the result into what bootstrap-table expects
params.success({total: data.total, totalNotFiltered: data.totalNotFiltered, rows: rows});

View File

@ -317,5 +317,10 @@ function initPage() {
}
selectLayout('#zmMontageLayout');
}
function watchFullscreen() {
const content = document.getElementById('content');
openFullscreen(content);
}
// Kick everything off
$j(document).ready(initPage);

View File

@ -16,6 +16,9 @@ function generateVideoResponse( data, responseText ) {
}
function generateVideo() {
$j.ajaxSetup({
timeout: 0
});
var form = $j('#videoForm').serialize();
$j.getJSON(thisUrl + '?view=request&request=event&action=video', form)
.done(generateVideoResponse)

View File

@ -970,5 +970,20 @@ function initPage() {
});
} // initPage
function watchFullscreen() {
const btn = document.getElementById('fullscreenBtn');
console.log(btn);
if (btn.firstElementChild.innerHTML=='fullscreen') {
const content = document.getElementById('content');
openFullscreen(content);
btn.firstElementChild.innerHTML='fullscreen_exit';
btn.setAttribute('title', translate["Exit Fullscreen"]);
} else {
closeFullscreen();
btn.firstElementChild.innerHTML='fullscreen';
btn.setAttribute('title', translate["Fullscreen"]);
}
}
// Kick everything off
$j(document).ready(initPage);

View File

@ -97,9 +97,13 @@ foreach( dbFetchAll( 'SELECT * FROM ControlPresets WHERE MonitorId = ?', NULL, a
$labels[$row['Preset']] = $row['Label'];
}
foreach ( $labels as $index=>$label ) {
foreach ($labels as $index=>$label) {
?>
labels[<?php echo validInt($index) ?>] = '<?php echo validJsStr($label) ?>';
<?php
}
?>
var translate = {
"Fullscreen": "<?php echo translate('Fullscreen') ?>",
"Exit Fullscreen": "<?php echo translate('Exit Fullscreen') ?>",
};

View File

@ -661,6 +661,7 @@ function initPage() {
// 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].setScale('auto');
monitors[i].start(delay);
}

View File

@ -66,6 +66,7 @@ monitorData[monitorData.length] = {
'width': <?php echo $monitor->ViewWidth() ?>,
'height':<?php echo $monitor->ViewHeight() ?>,
'url': '<?php echo $monitor->UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
'url_to_zms': '<?php echo $monitor->UrlToZMS( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
'type': '<?php echo $monitor->Type() ?>',
'refresh': '<?php echo $monitor->Refresh() ?>'
};

View File

@ -12,6 +12,7 @@ function initPage() {
// 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].setScale('auto');
monitors[i].start(delay);
}
@ -31,5 +32,12 @@ function initPage() {
});
}
function streamCmdQuit() {
for ( var i = 0, length = monitorData.length; i < length; i++ ) {
monitors[i] = new MonitorStream(monitorData[i]);
monitors[i].stop();
}
}
window.addEventListener('DOMContentLoaded', initPage);

View File

@ -9,6 +9,7 @@ monitorData[monitorData.length] = {
'width': <?php echo $monitor->ViewWidth() ?>,
'height':<?php echo $monitor->ViewHeight() ?>,
'url': '<?php echo $monitor->UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
'url_to_zms': '<?php echo $monitor->UrlToZMS( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
'type': '<?php echo $monitor->Type() ?>',
'refresh': '<?php echo $monitor->Refresh() ?>'
};
@ -24,7 +25,7 @@ var STATE_TAPE = <?php echo STATE_TAPE ?>;
var stateStrings = new Array();
stateStrings[STATE_IDLE] = "<?php echo translate('Idle') ?>";
stateStrings[STATE_PREALARM] = "<?php echo translate('Idle') ?>";
stateStrings[STATE_PREALARM] = "<?php echo translate('PreAlarm') ?>";
stateStrings[STATE_ALARM] = "<?php echo translate('Alarm') ?>";
stateStrings[STATE_ALERT] = "<?php echo translate('Alert') ?>";
stateStrings[STATE_TAPE] = "<?php echo translate('Record') ?>";

View File

@ -461,9 +461,12 @@ switch ( $name ) {
<tr class="Id">
<td class="text-right pr-3"><?php echo translate('Id') ?></td>
<td><input type="number" step="1" min="1" name="newMonitor[Id]" placeholder="leave blank for auto"/><br/>
10 Available Ids:
<?php echo implode(', ', array_slice($available_monitor_ids, 0, 10)); ?>
</td>
<?php
if (count($available_monitor_ids)) {
echo 'Some available ids: '.implode(', ', array_slice($available_monitor_ids, 0, 10));
}
?>
</td>
</tr>
<?php

View File

@ -206,6 +206,9 @@ if ( canView('System') ) {
&nbsp;<?php echo translate('Snapshot') ?>
</button>
<?php } ?>
<button type="button" id="fullscreenBtn" title="<?php echo translate('Fullscreen') ?>" class="avail" data-on-click="watchFullscreen">
<i class="material-icons md-18">fullscreen</i>
</button>
</form>
</div>
</div>

View File

@ -142,6 +142,9 @@ if ( $streamMode == 'jpeg' ) {
<button type="button" id="zoomOutBtn" title="<?php echo translate('ZoomOut') ?>" class="avail" data-on-click="streamCmdZoomOut">
<i class="material-icons md-18">zoom_out</i>
</button>
<button type="button" id="fullscreenBtn" title="<?php echo translate('Fullscreen') ?>" class="avail" data-on-click="watchFullscreen">
<i class="material-icons md-18">fullscreen</i>
</button>
<?php
} // end if streamMode==jpeg
?>

View File

@ -80,15 +80,14 @@ xhtmlHeaders(__FILE__, translate('Zones'));
<?php echo getStreamHTML($monitor, $options); ?>
<svg class="zones" viewBox="0 0 <?php echo $monitor->ViewWidth().' '.$monitor->ViewHeight() ?>">
<?php
foreach( array_reverse($zones) as $zone ) {
foreach (array_reverse($zones) as $zone) {
?>
<polygon points="<?php echo $zone['AreaCoords'] ?>"
class="zmlink <?php echo $zone['Type']?>"
data-on-click-true="streamCmdQuit"
data-url="?view=zone&amp;mid=<?php echo $mid ?>&amp;zid=<?php echo $zone['Id'] ?>"
/>
<?php
} // end foreach zone
} // end foreach zone
?>
Sorry, your browser does not support inline SVG
</svg>
@ -96,37 +95,37 @@ xhtmlHeaders(__FILE__, translate('Zones'));
<?php echo translate('State') ?>:&nbsp;<span id="stateValue<?php echo $monitor->Id() ?>"></span>&nbsp;-&nbsp;<span id="fpsValue<?php echo $monitor->Id() ?>"></span>&nbsp;fps
</div>
</div>
<div class="zones">
<table id="zonesTable" class="major">
<thead>
<tr>
<th class="colName"><?php echo translate('Name') ?></th>
<th class="colType"><?php echo translate('Type') ?></th>
<th class="colUnits"><?php echo translate('AreaUnits') ?></th>
<th class="colMark"><?php echo translate('Mark') ?></th>
</tr>
</thead>
<tbody>
<?php
foreach( $zones as $zone ) {
?>
<tr>
<td class="colName"><?php echo makeLink('?view=zone&mid='.$mid.'&zid='.$zone['Id'], validHtmlStr($zone['Name']), true, 'data-on-click-true="streamCmdQuit"'); ?></td>
<td class="colType"><?php echo validHtmlStr($zone['Type']) ?></td>
<td class="colUnits"><?php echo $zone['Area'] ?>&nbsp;/&nbsp;<?php echo sprintf('%.2f', ($zone['Area']*100)/($monitor->ViewWidth()*$monitor->ViewHeight()) ) ?></td>
<td class="colMark"><input type="checkbox" name="markZids[]" value="<?php echo $zone['Id'] ?>" data-on-click-this="configureDeleteButton"<?php if ( !canEdit('Monitors') ) { ?> disabled="disabled"<?php } ?>/></td>
</tr>
<?php
}
?>
</tbody>
</table>
<div id="contentButtons">
<?php echo makeButton('?view=zone&mid='.$mid.'&zid=0', 'AddNewZone', canEdit('Monitors')); ?>
<button type="submit" name="deleteBtn" value="Delete" disabled="disabled"><?php echo translate('Delete') ?></button>
</div>
</div><!--zones-->
<br class="clear"/>
<div class="zones">
<table id="zonesTable" class="major">
<thead>
<tr>
<th class="colName"><?php echo translate('Name') ?></th>
<th class="colType"><?php echo translate('Type') ?></th>
<th class="colUnits"><?php echo translate('AreaUnits') ?></th>
<th class="colMark"><?php echo translate('Mark') ?></th>
</tr>
</thead>
<tbody>
<?php
foreach( $zones as $zone ) {
?>
<tr>
<td class="colName"><?php echo makeLink('?view=zone&mid='.$mid.'&zid='.$zone['Id'], validHtmlStr($zone['Name']), true, 'data-on-click-true="streamCmdQuit"'); ?></td>
<td class="colType"><?php echo validHtmlStr($zone['Type']) ?></td>
<td class="colUnits"><?php echo $zone['Area'] ?>&nbsp;/&nbsp;<?php echo sprintf('%.2f', ($zone['Area']*100)/($monitor->ViewWidth()*$monitor->ViewHeight()) ) ?></td>
<td class="colMark"><input type="checkbox" name="markZids[]" value="<?php echo $zone['Id'] ?>" data-on-click-this="configureDeleteButton"<?php if ( !canEdit('Monitors') ) { ?> disabled="disabled"<?php } ?>/></td>
</tr>
<?php
}
?>
</tbody>
</table>
<div id="contentButtons">
<?php echo makeButton('?view=zone&mid='.$mid.'&zid=0', 'AddNewZone', canEdit('Monitors')); ?>
<button type="submit" name="deleteBtn" value="Delete" disabled="disabled"><?php echo translate('Delete') ?></button>
</div>
</div><!--zones-->
<br class="clear"/>
</div><!--Monitor-->
<?php
} # end foreach monitor