diff --git a/setup.sh b/setup.sh index 7b1f528..34579af 100644 --- a/setup.sh +++ b/setup.sh @@ -16,6 +16,7 @@ apt install -y libswscale-dev apt install -y libgstreamer1.0-dev apt install -y x264 apt install -y libx264-dev +apt install -y libilmbase-dev apt install -y libopencv-dev apt install -y apache2 add-apt-repository -y ppa:ubuntu-toolchain-r/test diff --git a/src/common.cpp b/src/common.cpp index 9f51039..5057722 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -52,6 +52,28 @@ bool createDirTree(const string &full_path) return ret; } +void cleanupEmptyDirs(const string &path) +{ + if (exists(path)) + { + for (auto &entry : directory_iterator(path)) + { + if (entry.is_directory()) + { + try + { + remove(entry.path()); + } + catch (filesystem_error const &ex) + { + // non-empty dir assumed when filesystem_error is raised. + cleanupEmptyDirs(path + "/" + entry.path().filename().string()); + } + } + } + } +} + vector lsFilesInDir(const string &path, const string &ext) { vector names; @@ -97,30 +119,32 @@ vector lsDirsInDir(const string &path) return names; } -void enforceMaxDays(const string &dirPath, shared_t *share) +void cleanupStream(const string &plsPath) { - auto names = lsDirsInDir(dirPath); + ifstream fileIn(plsPath); - while (names.size() > (share->maxDays - 1)) + for (string line; getline(fileIn, line); ) { - remove_all(string(cleanDir(dirPath) + "/" + names[0]).c_str()); - - names.erase(names.begin()); + if (line.starts_with("VIDEO_TS/")) + { + remove(line); + } } } -void enforceMaxClips(const string &dirPath, shared_t *share) +void enforceMaxEvents(shared_t *share) { - auto names = lsFilesInDir(dirPath, "." + share->vidExt); + auto names = lsFilesInDir(".", ".m3u8"); - while (names.size() > share->maxClips) + while (names.size() > share->maxEvents) { - // removes the video file extension. - auto nameOnly = names[0].substr(0, names[0].size() - (share->vidExt.size() + 1)); - auto imgFile = cleanDir(dirPath) + "/" + nameOnly + ".jpg"; - auto webFile = cleanDir(dirPath) + "/" + nameOnly + ".html"; + // removes the playlist file extension. + auto nameOnly = names[0].substr(0, names[0].size() - 5); + auto imgFile = nameOnly + ".jpg"; + auto webFile = nameOnly + ".html"; - remove(cleanDir(dirPath) + "/" + names[0]); + cleanupStream(names[0]); + remove(names[0]); if (exists(imgFile)) remove(imgFile); if (exists(webFile)) remove(webFile); @@ -129,6 +153,7 @@ void enforceMaxClips(const string &dirPath, shared_t *share) } } + string genTimeStr(const char *fmt) { time_t rawtime; @@ -175,7 +200,7 @@ bool rdConf(const string &filePath, shared_t *share) { share->retCode = ENOENT; - cout << "wrn: config file: " << filePath << " does not exists or lack read permissions." << endl; + cerr << "err: config file: " << filePath << " does not exists or lack read permissions." << endl; } else { @@ -193,17 +218,14 @@ bool rdConf(const string &filePath, shared_t *share) rdLine("web_text = ", line, &share->webTxt); rdLine("web_bg = ", line, &share->webBg); rdLine("web_font = ", line, &share->webFont); + rdLine("sch_sec = ", line, &share->schSec); rdLine("post_cmd = ", line, &share->postCmd); - rdLine("clip_len = ", line, &share->clipLen); - rdLine("num_of_clips = ", line, &share->numOfClips); - rdLine("buff_dir = ", line, &share->buffDir); rdLine("frame_gap = ", line, &share->frameGap); rdLine("pix_thresh = ", line, &share->pixThresh); rdLine("img_thresh = ", line, &share->imgThresh); rdLine("max_days = ", line, &share->maxDays); - rdLine("max_clips = ", line, &share->maxClips); + rdLine("max_events = ", line, &share->maxEvents); rdLine("max_log_size = ", line, &share->maxLogSize); - rdLine("vid_container = ", line, &share->vidExt); rdLine("vid_codec = ", line, &share->vidCodec); } @@ -217,69 +239,55 @@ bool rdConf(shared_t *share) { share->recordUrl.clear(); share->postCmd.clear(); - share->buffDir.clear(); share->camName.clear(); share->recLogPath.clear(); share->detLogPath.clear(); + share->upkLogPath.clear(); share->recLogFile.close(); share->detLogFile.close(); + share->upkLogFile.close(); share->retCode = 0; share->frameGap = 20; share->pixThresh = 150; share->imgThresh = 80000; - share->clipLen = 20; - share->numOfClips = 3; share->maxDays = 15; - share->maxClips = 90; + share->maxEvents = 20; share->maxLogSize = 50000; - share->webRoot = "/var/www/html"; - share->buffDir = "/tmp"; - share->vidExt = "mp4"; - share->vidCodec = "copy"; share->skipCmd = false; + share->schSec = 60; + share->webRoot = "/var/www/html"; + share->vidCodec = "copy"; share->webBg = "#485564"; share->webTxt = "#dee5ee"; share->webFont = "courier"; - auto ret = false; - - for (auto &&confPath: share->conf) - { - if (rdConf(confPath, share)) ret = true; - } - - if (ret) + if (rdConf(share->conf, share)) { if (share->camName.empty()) { - share->camName = path(share->conf.back()).filename(); + share->camName = path(share->conf).filename(); } share->outDir = cleanDir(share->webRoot) + "/" + share->camName; - share->buffDir = cleanDir(share->buffDir) + "/" + share->camName; share->recLogPath = share->outDir + "/rec_log_lines.html"; share->detLogPath = share->outDir + "/det_log_lines.html"; + share->upkLogPath = share->outDir + "/upk_log_lines.html"; - if (share->init) - { - if (exists(share->buffDir)) - { - remove_all(share->buffDir); - } + error_code ec; - share->init = false; - } - - createDirTree(cleanDir(share->buffDir)); createDirTree(share->outDir); - } - else - { - cerr << "err: none of the expected config files could be read." << endl; + current_path(share->outDir, ec); + + share->retCode = ec.value(); + + if (share->retCode != 0) + { + cerr << "err: " << ec.message() << endl; + } } - return ret; + return share->retCode == 0; } string parseForParam(const string &arg, int argc, char** argv, bool argOnly, int &offs) @@ -318,32 +326,81 @@ string parseForParam(const string &arg, int argc, char** argv, bool argOnly) return parseForParam(arg, argc, argv, argOnly, notUsed); } -vector parseForList(const string &arg, int argc, char** argv) +string genEventPath(const string &tsPath) { - auto offs = 0; - auto ret = vector(); - string param; - - do + if (tsPath.size() > 14) { - param = parseForParam(arg, argc, argv, false, offs); + // removes 'VIDEO_TS/live/' from the front of the string. + auto ret = tsPath.substr(14); - if (!param.empty()) + return "VIDEO_TS/events/" + ret; + } + else + { + return string(); + } +} + +string genVidNameFromLive(const string &tsPath) +{ + if (tsPath.size() > 17) + { + // removes 'VIDEO_TS/live/' from the front of the string. + auto ret = tsPath.substr(14); + auto ind = tsPath.find('/'); + // removes '.ts' from the end of the string. + ret = ret.substr(0, ret.size() - 3); + + while (ind != string::npos) { - ret.push_back(param); + // remove all '/' + ret.erase(ind, 1); + + ind = ret.find('/'); } + + return ret; } - while (!param.empty()); - - return ret; -} - -void waitForDetThreads(shared_t *share) -{ - for (auto &&thr : share->detThreads) + else { - thr.join(); + return string(); + } +} + +uint64_t genEpoch() +{ + return duration_cast(system_clock::now().time_since_epoch()).count(); +} + +void wrOutm3u8(const pls_t &pls) +{ + createDirTree(path(pls.dstPath).parent_path().string()); + copy_file(pls.clipPath, pls.dstPath); + + auto plsFile = pls.fileName + ".m3u8"; + + if (exists(plsFile)) + { + ofstream file(plsFile.c_str(), ofstream::app); + + file << endl; + file << pls.extINF << endl; + file << pls.dstPath; + + file.close(); + } + else + { + ofstream file(plsFile.c_str()); + + file << "#EXTM3U" << endl; + file << "#EXT-X-VERSION:3" << endl; + file << "#EXT-X-TARGETDURATION:10" << endl; + file << "#EXT-X-MEDIA-SEQUENCE:0" << endl; + file << "#EXT-X-DISCONTINUITY" << endl; + file << pls.extINF << endl; + file << pls.dstPath; + + file.close(); } - - share->detThreads.clear(); } diff --git a/src/common.h b/src/common.h index f882d79..bea0239 100644 --- a/src/common.h +++ b/src/common.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -34,42 +35,54 @@ using namespace cv; using namespace std; using namespace std::filesystem; +using namespace std::chrono; -#define APP_VER "1.6.t9" +#define APP_VER "2.0.t1" #define APP_NAME "Motion Watch" +struct pls_t +{ + string fileName; + string clipPath; + string dstPath; + string extINF; + uint64_t createTime; + Mat thumbnail; +}; + struct shared_t { - vector detThreads; - vector conf; - ofstream recLogFile; - ofstream detLogFile; - string recLogPath; - string detLogPath; - string recordUrl; - string outDir; - string postCmd; - string buffDir; - string vidExt; - string vidCodec; - string camName; - string webBg; - string webTxt; - string webFont; - string webRoot; - bool init; - bool skipCmd; - int clipLen; - int frameGap; - int pixThresh; - int imgThresh; - int numOfClips; - int maxDays; - int maxClips; - int maxLogSize; - int retCode; + vector recList; + ofstream recLogFile; + ofstream detLogFile; + ofstream upkLogFile; + string conf; + string recLogPath; + string detLogPath; + string upkLogPath; + string recordUrl; + string outDir; + string postCmd; + string vidCodec; + string camName; + string webBg; + string webTxt; + string webFont; + string webRoot; + bool skipCmd; + bool postCmdRunning; + int schSec; + int frameGap; + int pixThresh; + int imgThresh; + int maxDays; + int maxEvents; + int maxLogSize; + int retCode; }; +string genVidNameFromLive(const string &tsPath); +string genEventPath(const string &tsPath); string genDstFile(const string &dirOut, const char *fmt, const string &ext); string genTimeStr(const char *fmt); string cleanDir(const string &path); @@ -77,15 +90,15 @@ string parseForParam(const string &arg, int argc, char** argv, bool argO string parseForParam(const string &arg, int argc, char** argv, bool argOnly); bool createDir(const string &dir); bool createDirTree(const string &full_path); -void enforceMaxDays(const string &dirPath, shared_t *share); -void enforceMaxClips(const string &dirPath, shared_t *share); void rdLine(const string ¶m, const string &line, string *value); void rdLine(const string ¶m, const string &line, int *value); -void statOut(shared_t *share); -void waitForDetThreads(shared_t *share); +void cleanupEmptyDirs(const string &path); +void cleanupStream(const string &plsPath); +void wrOutm3u8(const pls_t &pls); +void enforceMaxEvents(shared_t *share); bool rdConf(shared_t *share); -vector parseForList(const string &arg, int argc, char** argv); vector lsFilesInDir(const string &path, const string &ext = string()); vector lsDirsInDir(const string &path); +uint64_t genEpoch(); #endif // COMMON_H diff --git a/src/logger.cpp b/src/logger.cpp index 7ba30e3..1bbbbd8 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -22,6 +22,11 @@ void detLog(const string &line, shared_t *share) share->detLogFile << genTimeStr("[%Y-%m-%d-%H-%M-%S] ") << line << "
" << endl; } +void upkLog(const string &line, shared_t *share) +{ + share->upkLogFile << genTimeStr("[%Y-%m-%d-%H-%M-%S] ") << line << "
" << endl; +} + void enforceMaxLogSize(const string &filePath, shared_t *share) { if (exists(filePath)) @@ -48,61 +53,66 @@ void initLogFile(const string &filePath, ofstream &fileObj) void initLogFrontPage(const string &filePath, const string &logLinesFile) { - string htmlText = "\n"; + if (!exists(filePath)) + { + string htmlText = "\n"; - htmlText += "\n"; - htmlText += "\n"; - htmlText += "\n"; - htmlText += "\n"; - htmlText += "\n"; - htmlText += "\n"; - htmlText += "\n"; - htmlText += "\n"; - htmlText += "\n"; - htmlText += "

\n"; - htmlText += "

\n"; - htmlText += "\n"; - htmlText += "

\n"; - htmlText += "\n"; - htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; + htmlText += "\n"; + htmlText += "

\n"; + htmlText += "

\n"; + htmlText += "\n"; + htmlText += "

\n"; + htmlText += "\n"; + htmlText += "\n"; - ofstream outFile(filePath); + ofstream outFile(filePath); - outFile << htmlText; + outFile << htmlText; - outFile.close(); + outFile.close(); + } } void initLogFrontPages(shared_t *share) { auto recLogFilePath = share->outDir + "/recording_log.html"; auto detLogFilePath = share->outDir + "/detection_log.html"; + auto upkLogFilePath = share->outDir + "/upkeep_log.html"; initLogFrontPage(recLogFilePath, path(share->recLogPath).filename().string()); initLogFrontPage(detLogFilePath, path(share->detLogPath).filename().string()); + initLogFrontPage(upkLogFilePath, path(share->upkLogPath).filename().string()); } diff --git a/src/logger.h b/src/logger.h index 29f9074..3c81557 100644 --- a/src/logger.h +++ b/src/logger.h @@ -17,6 +17,7 @@ void recLog(const string &line, shared_t *share); void detLog(const string &line, shared_t *share); +void upkLog(const string &line, shared_t *share); void enforceMaxLogSize(const string &filePath, shared_t *share); void initLogFile(const string &filePath, ofstream &fileObj); void initLogFrontPages(shared_t *share); diff --git a/src/main.cpp b/src/main.cpp index 8aec3d4..4885df7 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,40 +12,102 @@ #include "mo_detect.h" #include "logger.h" +#include "web.h" -void detectMoInFile(const string &bufPath, shared_t *share) +void detectMo(shared_t *share) { - detLog("detect_mo_in_file() -- start", share); - - Mat thumbNail; - - if (moDetect(bufPath, thumbNail, share)) + while (share->retCode == 0) { - share->skipCmd = true; - - wrOut(bufPath, thumbNail, share); + sleep(2); + detectMoInStream("stream.m3u8", share); } - else if (exists(bufPath)) - { - remove(bufPath); - } - - detLog("detect_mo_in_file() -- finished", share); } -void recLoop(shared_t *share) +void eventLoop(shared_t *share) { - while (rdConf(share)) + while (share->retCode == 0) { - recLog("rec_loop() -- start", share); + while (!share->recList.empty()) + { + auto pls = share->recList[0]; + auto timeDiff = pls.createTime - genEpoch(); + // future time protection. this should never occur but + // this is in place in case the system time is incorrect. + if (timeDiff < 0) timeDiff = 0; + + // wait at least 6 seconds before processing the event in + // queue. + if (timeDiff < 6) sleep(6 - timeDiff); + + try + { + wrOutm3u8(pls); + genHTMLvid(pls.fileName); + + if (!exists(pls.fileName + ".jpg")) + { + imwrite(string(pls.fileName + "jpg").c_str(), pls.thumbnail); + } + } + catch (filesystem_error &ex) + { + recLog("err: could not copy live file: " + pls.clipPath + " reason - " + ex.what(), share); + } + + share->recList.erase(share->recList.begin()); + } + + sleep(5); + } +} + +void schLoop(shared_t *share) +{ + if (!share->postCmd.empty()) + { + while (share->retCode == 0) + { + sleep(share->schSec); + + if (!share->skipCmd) + { + share->postCmdRunning = true; + + detLog("no motion detected, running post command: " + share->postCmd, share); + system(share->postCmd.c_str()); + + share->postCmdRunning = false; + } + else + { + share->skipCmd = false; + + detLog("motion detected, skipping the post command.", share); + } + } + } +} + +void upkeep(shared_t *share) +{ + while (share->retCode == 0) + { enforceMaxLogSize(share->recLogPath, share); enforceMaxLogSize(share->detLogPath, share); + enforceMaxLogSize(share->upkLogPath, share); initLogFile(share->recLogPath, share->recLogFile); initLogFile(share->detLogPath, share->detLogFile); + initLogFile(share->upkLogPath, share->upkLogFile); initLogFrontPages(share); + enforceMaxEvents(share); + cleanupEmptyDirs("VIDEO_TS"); + + genHTMLul(".", share->camName, share); + + upkLog("camera specific webroot page updated: " + share->outDir + "/index.html", share); if (!exists("/tmp/mow-lock")) { @@ -55,76 +117,44 @@ void recLoop(shared_t *share) genHTMLul(share->webRoot, string(APP_NAME) + " " + string(APP_VER), share); remove("/tmp/mow-lock"); - recLog("webroot page updated: " + cleanDir(share->webRoot) + "/index.html", share); + upkLog("webroot page updated: " + cleanDir(share->webRoot) + "/index.html", share); } else { - recLog("skipping update of the webroot page, it is busy.", share); + upkLog("skipping update of the webroot page, it is busy.", share); } - genHTMLul(share->outDir, share->camName, share); + sleep(60); + } +} - recLog("camera specific webroot page updated: " + share->outDir + "/index.html", share); +void recLoop(shared_t *share) +{ + while (share->retCode == 0) + { + auto cmd = "ffmpeg -hide_banner -i " + + share->recordUrl + + " -strftime 1" + + " -strftime_mkdir 1" + + " -hls_segment_filename 'VIDEO_TS/live/%Y/%j/%H/%M%S.ts'" + + " -y -vcodec " + + share->vidCodec + + " -f hls -hls_time 6 -hls_list_size " + + to_string((share->maxDays * 86400) / 6) + // 86400 seconds in a day. + " stream.m3u8"; - for (auto i = 0; i < share->numOfClips; ++i) + recLog("ffmpeg_run: " + cmd, share); + + auto retCode = system(cmd.c_str()); + + recLog("ffmpeg_retcode: " + to_string(retCode), share); + + if (retCode != 0) { - auto bufPath = cleanDir(share->buffDir) + "/" + to_string(i) + "." + share->vidExt; - auto cmd = "timeout -k 1 " + to_string(share->clipLen + 2) + " "; - - cmd += "ffmpeg -hide_banner -i " + share->recordUrl + " -y -vcodec " + share->vidCodec + " -movflags faststart -t " + to_string(share->clipLen) + " " + bufPath; - - recLog("ffmpeg_run: " + cmd, share); - - auto retCode = system(cmd.c_str()); - - recLog("ffmpeg_retcode: " + to_string(retCode), share); - - if (retCode == 0) - { - recLog("detect_mo_in_file() -- started in a seperate thread.", share); - - share->detThreads.push_back(thread(detectMoInFile, bufPath, share)); - } - else - { - recLog("ffmpeg returned non zero, indicating failure. please check stderr output.", share); - - if (exists(bufPath)) - { - remove(bufPath); - } - - sleep(share->clipLen); - } + recLog("err: ffmpeg returned non zero, indicating failure. please check stderr output.", share); } - waitForDetThreads(share); - - if (!share->skipCmd) - { - recLog("no motion detected", share); - - if (share->postCmd.empty()) - { - recLog("post command not defined, skipping.", share); - } - else - { - recLog("running post command: " + share->postCmd, share); - system(share->postCmd.c_str()); - } - } - else - { - recLog("motion detected, skipping the post command.", share); - } - - recLog("rec_loop() -- finished", share); - - if (share->retCode != 0) - { - break; - } + sleep(60); } } @@ -132,19 +162,15 @@ int main(int argc, char** argv) { struct shared_t sharedRes; - sharedRes.conf = parseForList("-c", argc, argv); + sharedRes.conf = parseForParam("-c", argc, argv, false); if (parseForParam("-h", argc, argv, true) == "true") { cout << "Motion Watch " << APP_VER << endl << endl; cout << "Usage: mow " << endl << endl; cout << "-h : display usage information about this application." << endl; - cout << "-c : path to a config file." << endl; + cout << "-c : path to the config file." << endl; cout << "-v : display the current version." << endl << endl; - cout << "note: multiple -c config files can be passed, reading from left" << endl; - cout << " to right. any conflicting values between the files will" << endl; - cout << " have the latest value from the latest file overwrite the" << endl; - cout << " the earliest." << endl; } else if (parseForParam("-v", argc, argv, true) == "true") { @@ -156,11 +182,28 @@ int main(int argc, char** argv) } else { - sharedRes.retCode = 0; - sharedRes.skipCmd = false; - sharedRes.init = true; + sharedRes.retCode = 0; + sharedRes.skipCmd = false; + sharedRes.postCmdRunning = false; - recLoop(&sharedRes); + rdConf(&sharedRes); + + if (exists("VIDEO_TS/live")) + { + remove_all("VIDEO_TS/live"); + } + + auto thr1 = thread(recLoop, &sharedRes); + auto thr2 = thread(upkeep, &sharedRes); + auto thr3 = thread(detectMo, &sharedRes); + auto thr4 = thread(eventLoop, &sharedRes); + auto thr5 = thread(schLoop, &sharedRes); + + thr1.join(); + thr2.join(); + thr3.join(); + thr4.join(); + thr5.join(); return sharedRes.retCode; } diff --git a/src/mo_detect.cpp b/src/mo_detect.cpp index 48a8ab1..4a6dbe1 100644 --- a/src/mo_detect.cpp +++ b/src/mo_detect.cpp @@ -12,131 +12,107 @@ #include "mo_detect.h" -bool imgDiff(const Mat &prev, const Mat &next, shared_t *share) +void detectMoInStream(const string &bufPath, shared_t *share) +{ + ifstream fileIn(bufPath); + pls_t clip; + + auto clipPathFilter = genTimeStr("VIDEO_TS/live/%Y/%j/%H/"); + + for (string line; getline(fileIn, line); ) + { + if (line.starts_with(clipPathFilter)) + { + clip.clipPath = line; + } + else if (line.starts_with("#EXTINF")) + { + clip.extINF = line; + } + } + + if (!clip.clipPath.empty() && !clip.extINF.empty()) + { + if (moDetect(clip.clipPath, clip.thumbnail, share)) + { + clip.fileName = genTimeStr("%Y-%j-%H-%M"); + clip.dstPath = genEventPath(bufPath); + clip.createTime = genEpoch(); + + share->skipCmd = true; + + share->recList.push_back(clip); + } + } +} + +bool imgDiff(Mat &prev, Mat &next, shared_t *share) { auto ret = false; - detLog("img_diff() -- start()", share); + cvtColor(prev, prev, COLOR_BGR2GRAY); + cvtColor(next, next, COLOR_BGR2GRAY); - if (prev.empty()) detLog("prev_frame is empty -- Borken frame from the camera assumed.", share); - if (next.empty()) detLog("next_frame is empty -- EOF assumed.", share); + Mat diff; - if (!prev.empty() && !next.empty()) - { - Mat diff; + absdiff(prev, next, diff); + threshold(diff, diff, share->pixThresh, 255, THRESH_BINARY); - absdiff(prev, next, diff); - threshold(diff, diff, share->pixThresh, 255, THRESH_BINARY); + auto diffScore = countNonZero(diff); - auto diffScore = countNonZero(diff); + detLog("diff_score: " + to_string(diffScore), share); - detLog("diff_score: " + to_string(diffScore), share); - - ret = diffScore >= share->imgThresh; - } - - detLog("img_diff() -- finished()", share); - - return ret; -} - -Mat frameFF(VideoCapture *cap, int gap) -{ - Mat ret; - - if (gap == 0) gap = 1; - - for (int i = 0; i < gap; ++i) - { - cap->grab(); - } - - cap->retrieve(ret); - - if (!ret.empty()) - { - cvtColor(ret, ret, COLOR_BGR2GRAY); - } - - return ret; -} - -void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share) -{ - detLog("wr_out() -- start()", share); - detLog("buff_file: " + buffFile, share); - - auto dayStr = genTimeStr("%Y-%m-%d"); - auto timStr = genTimeStr("%H%M%S"); - auto outDir = cleanDir(share->outDir) + "/" + dayStr; - - if (!exists(outDir)) - { - enforceMaxDays(share->outDir, share); - } - - auto vidOut = genDstFile(outDir, timStr.c_str(), "." + share->vidExt); - auto imgOut = genDstFile(outDir, timStr.c_str(), ".jpg"); - - detLog("write_out_vid: " + vidOut, share); - detLog("write_out_img: " + imgOut, share); - - enforceMaxClips(outDir, share); - - copy_file(buffFile.c_str(), vidOut.c_str()); - remove(buffFile.c_str()); - - imwrite(imgOut.c_str(), vidThumb); - - genHTMLvid(vidOut, share); - genHTMLul(outDir, share->camName + ": " + dayStr, share); - genHTMLul(share->outDir, share->camName, share); - - detLog("wr_out() -- finished()", share); + return diffScore >= share->imgThresh; } bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share) { - detLog("mo_detect() -- start()", share); - detLog("buff_file: " + buffFile, share); - auto mod = false; + detLog("stream_clip: " + buffFile, share); + VideoCapture capture(buffFile.c_str(), CAP_FFMPEG); if (capture.isOpened()) { Mat prev; Mat next; - auto frameCount = capture.get(CAP_PROP_FRAME_COUNT); - auto frameGaps = frameCount / share->frameGap; - detLog("frame_count: " + to_string(frameCount), share); - detLog("frame_gaps: " + to_string(frameGaps), share); + detLog("capture open successful.", share); - for (auto i = 0; i < frameGaps; i++) + while (capture.grab()) { - if (prev.empty()) prev = frameFF(&capture, 1); - else prev = next.clone(); - - next = frameFF(&capture, share->frameGap); - - if (imgDiff(prev, next, share)) + if (prev.empty()) { - resize(next, vidThumb, Size(720, 480), INTER_LINEAR); - - mod = true; break; + capture.retrieve(prev); } + else + { + capture.retrieve(next); + + if (!share->postCmdRunning) + { + if (imgDiff(prev, next, share)) + { + resize(next, vidThumb, Size(720, 480), INTER_LINEAR); + + mod = true; break; + } + } + + prev.release(); + next.release(); + } + + sleep(1); } } else { - detLog("failed to open the buff file for reading. check permissions and/or opencv's video-io support (gstreamer/ffmpeg).", share); + detLog("capture open failure, check debug output.", share); } capture.release(); - detLog("mo_detect() -- finished()", share); - return mod; } diff --git a/src/mo_detect.h b/src/mo_detect.h index 894573c..fc0c823 100644 --- a/src/mo_detect.h +++ b/src/mo_detect.h @@ -14,12 +14,10 @@ // GNU General Public License for more details. #include "common.h" -#include "web.h" #include "logger.h" -bool imgDiff(const Mat &prev, const Mat &next, shared_t *share); +bool imgDiff(Mat &prev, Mat &next, shared_t *share); bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share); -void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share); -Mat frameFF(VideoCapture *cap, int gap); +void detectMoInStream(const string &bufPath, shared_t *share); #endif // MO_DETECT_H diff --git a/src/web.cpp b/src/web.cpp index ad64476..6154f12 100644 --- a/src/web.cpp +++ b/src/web.cpp @@ -31,7 +31,7 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share) htmlText += "\n"; htmlText += "

" + title + "

\n"; - if (!dirNames.empty()) + if (!dirNames.empty() && !exists(outputDir + "/VIDEO_TS")) { htmlText += "
    \n"; @@ -43,6 +43,11 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share) htmlText += "
\n"; } + if (exists(outputDir + "/VIDEO_TS")) + { + htmlText += "

Motion Events

\n"; + } + for (auto &®Name : regNames) { if (regName.ends_with("_log.html")) @@ -51,8 +56,7 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share) } else if (regName.ends_with(".html") && !regName.ends_with("index.html") && - !regName.ends_with("rec_log_lines.html") && - !regName.ends_with("det_log_lines.html")) + !regName.ends_with("_log_lines.html")) { // regName.substr(0, regName.size() - 5) removes .html auto name = regName.substr(0, regName.size() - 5); @@ -85,11 +89,8 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share) file.close(); } -void genHTMLvid(const string &outputVid, shared_t *share) +void genHTMLvid(const string &name) { - auto vidName = path(outputVid).filename().string(); - auto filePath = path(outputVid).parent_path().string(); - auto fileName = vidName.substr(0, vidName.size() - (share->vidExt.size() + 1)); string htmlText = "\n"; htmlText += "\n"; @@ -102,12 +103,12 @@ void genHTMLvid(const string &outputVid, shared_t *share) htmlText += "\n"; htmlText += "\n"; htmlText += "\n"; htmlText += "\n"; htmlText += ""; - ofstream file(string(filePath + "/" + fileName + ".html").c_str()); + ofstream file(string(name + ".html").c_str()); file << htmlText << endl; diff --git a/src/web.h b/src/web.h index 7e9575b..b716fc0 100644 --- a/src/web.h +++ b/src/web.h @@ -16,7 +16,7 @@ #include "common.h" void genHTMLul(const string &outputDir, const string &title, shared_t *share); -void genHTMLvid(const string &outputVid, shared_t *share); +void genHTMLvid(const string &name); void genCSS(shared_t *share); #endif // WEB_H