diff --git a/README.md b/README.md index 2b215d7..31b61c4 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,13 @@ of this app can be used to operate multiple cameras. Usage: mow -h : display usage information about this application. - -c : path to the config file. + -c : path to the config file(s). -v : display the current version. + +note: multiple -c config files can be passed, reading from left + to right. any conflicting values between the files will + have the latest value from the latest file overwrite the + the earliest. ``` ### Config File ### @@ -33,14 +38,6 @@ recording_stream = rtsp://1.2.3.4:554/h264 # this is the url to the main stream of the IP camera that will be used # to record footage. # -detect_stream = rtsp://1.2.3.4:554/h264cif -# this is an optional detection stream that will be used to detect motion. -# most cameras have the option to broadcast multiple streams of the same -# footage. it's recommend to use this parameter with a smaller, lower bit -# rate stream to reduce CPU usage. recording_stream will still be used to -# record higher quality footage. if not defined, recording_stream will be -# used for both motion detection and recording. -# web_root = /var/www/html # this is the output directory that will be used to store recorded footage # from the cameras as well as the web interface for the application. diff --git a/install.sh b/install.sh index 23498fe..ac8de0e 100644 --- a/install.sh +++ b/install.sh @@ -6,7 +6,7 @@ cp ./.build-mow/mow /opt/mow/bin printf "#!/bin/sh\n" > /opt/mow/run printf "export OPENCV_LOG_LEVEL=DEBUG\n" >> /opt/mow/run printf "export OPENCV_VIDEOIO_DEBUG=1\n" >> /opt/mow/run -printf "/opt/mow/bin \$1 \$2 \$3 \$4 \$5 \$6 \$7 \$8\n" >> /opt/mow/run +printf "/opt/mow/bin \$1 \$2 \$3\n" >> /opt/mow/run chmod +x /opt/mow/run chmod +x /opt/mow/bin rm /usr/bin/mow diff --git a/src/common.cpp b/src/common.cpp index 5637ba7..9f51039 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -12,44 +12,6 @@ #include "common.h" -string leftTrim(const string &str, const string &toRemove) -{ - auto start = str.find_first_not_of(toRemove); - - if (start != string::npos) - { - return str.substr(start); - } - else - { - return str; - } -} - -string rightTrim(const string &str, const string &toRemove) -{ - auto end = str.find_last_not_of(toRemove); - - if (end != string::npos) - { - return str.substr(0, end + 1); - } - else - { - return str; - } -} - -string trim(const string &str, const string &toRemove) -{ - return rightTrim(leftTrim(str, toRemove), toRemove); -} - -string trim(const string &str) -{ - return trim(str, TRIM_REMOVE); -} - string cleanDir(const string &path) { if (path[path.size() - 1] == '/') @@ -193,7 +155,7 @@ void rdLine(const string ¶m, const string &line, string *value) { if (line.rfind(param.c_str(), 0) == 0) { - *value = trim(line).substr(param.size()); + *value = line.substr(param.size()); } } @@ -201,7 +163,7 @@ void rdLine(const string ¶m, const string &line, int *value) { if (line.rfind(param.c_str(), 0) == 0) { - *value = strtol(trim(line).substr(param.size()).c_str(), NULL, 10); + *value = strtol(line.substr(param.size()).c_str(), NULL, 10); } } @@ -213,7 +175,7 @@ bool rdConf(const string &filePath, shared_t *share) { share->retCode = ENOENT; - cerr << "err: config file: " << filePath << " does not exists or lack read permissions." << endl; + cout << "wrn: config file: " << filePath << " does not exists or lack read permissions." << endl; } else { @@ -227,7 +189,6 @@ bool rdConf(const string &filePath, shared_t *share) { rdLine("cam_name = ", line, &share->camName); rdLine("recording_stream = ", line, &share->recordUrl); - rdLine("detect_stream = ", line, &share->detectUrl); rdLine("web_root = ", line, &share->webRoot); rdLine("web_text = ", line, &share->webTxt); rdLine("web_bg = ", line, &share->webBg); @@ -255,7 +216,6 @@ bool rdConf(const string &filePath, shared_t *share) bool rdConf(shared_t *share) { share->recordUrl.clear(); - share->detectUrl.clear(); share->postCmd.clear(); share->buffDir.clear(); share->camName.clear(); @@ -281,27 +241,25 @@ bool rdConf(shared_t *share) share->webBg = "#485564"; share->webTxt = "#dee5ee"; share->webFont = "courier"; - share->detSuffix = ".det."; - share->recSuffix = ".rec."; - if (rdConf(share->conf, share)) + auto ret = false; + + for (auto &&confPath: share->conf) + { + if (rdConf(confPath, share)) ret = true; + } + + if (ret) { if (share->camName.empty()) { - share->camName = path(share->conf).filename(); - } - - if (share->detectUrl.empty()) - { - share->detectUrl = share->recordUrl; + share->camName = path(share->conf.back()).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->detSuffix += share->vidExt; - share->recSuffix += share->vidExt; if (share->init) { @@ -316,8 +274,12 @@ bool rdConf(shared_t *share) createDirTree(cleanDir(share->buffDir)); createDirTree(share->outDir); } + else + { + cerr << "err: none of the expected config files could be read." << endl; + } - return share->retCode == 0; + return ret; } string parseForParam(const string &arg, int argc, char** argv, bool argOnly, int &offs) @@ -355,3 +317,33 @@ 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) +{ + auto offs = 0; + auto ret = vector(); + string param; + + do + { + param = parseForParam(arg, argc, argv, false, offs); + + if (!param.empty()) + { + ret.push_back(param); + } + } + while (!param.empty()); + + return ret; +} + +void waitForDetThreads(shared_t *share) +{ + for (auto &&thr : share->detThreads) + { + thr.join(); + } + + share->detThreads.clear(); +} diff --git a/src/common.h b/src/common.h index 2c9310c..f882d79 100644 --- a/src/common.h +++ b/src/common.h @@ -21,12 +21,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include @@ -35,53 +35,41 @@ using namespace cv; using namespace std; using namespace std::filesystem; -#define APP_VER "1.6.t8" -#define APP_NAME "Motion Watch" -#define TRIM_REMOVE " \n\r\t\f\v." -#define PATH_ADDR 9 -#define MAX_CAP_RETRY 3 +#define APP_VER "1.6.t9" +#define APP_NAME "Motion Watch" struct shared_t { - ofstream recLogFile; - ofstream detLogFile; - string conf; - string recLogPath; - string detLogPath; - string recordUrl; - string detectUrl; - string outDir; - string postCmd; - string buffDir; - string recSuffix; - string detSuffix; - string vidExt; - string vidCodec; - string camName; - string webBg; - string webTxt; - string webFont; - string webRoot; - bool init; - bool skipCmd; - bool updateRoot; - int detProcs; - int clipLen; - int frameGap; - int pixThresh; - int imgThresh; - int numOfClips; - int maxDays; - int maxClips; - int maxLogSize; - int index; - int retCode; + 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; }; -string leftTrim(const string &str, const string &toRemove); -string rightTrim(const string &str, const string &toRemove); -string trim(const string &str, const string &toRemove); -string trim(const string &str); string genDstFile(const string &dirOut, const char *fmt, const string &ext); string genTimeStr(const char *fmt); string cleanDir(const string &path); @@ -93,7 +81,10 @@ 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); 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); diff --git a/src/main.cpp b/src/main.cpp index 80f85ff..8aec3d4 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,48 +13,32 @@ #include "mo_detect.h" #include "logger.h" -void detectMoInFile(const string &detPath, const string &recPath, shared_t *share) +void detectMoInFile(const string &bufPath, shared_t *share) { - if (fork() == 0) + detLog("detect_mo_in_file() -- start", share); + + Mat thumbNail; + + if (moDetect(bufPath, thumbNail, share)) { - detLog("detect_mo_in_file() -- start", share); + share->skipCmd = true; - share->detProcs++; - - if (exists(detPath)) - { - Mat thumbNail; - - if (moDetect(detPath, thumbNail, share)) - { - share->skipCmd = true; - - wrOut(recPath, thumbNail, share); - } - } - - if (exists(detPath)) remove(detPath); - if (exists(recPath)) remove(recPath); - - share->detProcs--; - - detLog("detect_mo_in_file() -- finished", share); + wrOut(bufPath, thumbNail, share); } -} - -void exeCmd(char *const argv[]) -{ - if (fork() == 0) + else if (exists(bufPath)) { - execvp("ffmpeg", argv); - _Exit(EXIT_FAILURE); + remove(bufPath); } + + detLog("detect_mo_in_file() -- finished", share); } void recLoop(shared_t *share) { while (rdConf(share)) { + recLog("rec_loop() -- start", share); + enforceMaxLogSize(share->recLogPath, share); enforceMaxLogSize(share->detLogPath, share); @@ -63,9 +47,7 @@ void recLoop(shared_t *share) initLogFrontPages(share); - recLog("rec_loop() -- start", share); - - if (!exists("/tmp/mow-lock") && share->updateRoot) + if (!exists("/tmp/mow-lock")) { system("touch /tmp/mow-lock"); @@ -74,71 +56,49 @@ void recLoop(shared_t *share) remove("/tmp/mow-lock"); recLog("webroot page updated: " + cleanDir(share->webRoot) + "/index.html", share); - - share->updateRoot = false; + } + else + { + recLog("skipping update of the webroot page, it is busy.", share); } genHTMLul(share->outDir, share->camName, share); recLog("camera specific webroot page updated: " + share->outDir + "/index.html", share); - auto strClipLen = to_string(share->clipLen); - - char* argvRec[] = {(char*) "ffmpeg", - (char*) "-hide_banner", - (char*) "-i", - (char*) share->recordUrl.c_str(), - (char*) "-y", - (char*) "-vcodec", - (char*) share->vidCodec.c_str(), - (char*) "-t", - (char*) strClipLen.c_str(), - (char*) "--replace_me--", - NULL}; - - char* argvDet[] = {(char*) "ffmpeg", - (char*) "-hide_banner", - (char*) "-i", - (char*) share->detectUrl.c_str(), - (char*) "-y", - (char*) "-vcodec", - (char*) share->vidCodec.c_str(), - (char*) "-t", - (char*) strClipLen.c_str(), - (char*) "--replace_me--", - NULL}; - - for (auto i = 0; i < share->numOfClips; ++i, ++share->index) + for (auto i = 0; i < share->numOfClips; ++i) { - auto detPath = cleanDir(share->buffDir) + "/" + to_string(share->index) + share->detSuffix; - auto recPath = cleanDir(share->buffDir) + "/" + to_string(share->index) + share->recSuffix; + auto bufPath = cleanDir(share->buffDir) + "/" + to_string(i) + "." + share->vidExt; + auto cmd = "timeout -k 1 " + to_string(share->clipLen + 2) + " "; - if (share->recordUrl == share->detectUrl) + 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) { - recPath = detPath; - } + recLog("detect_mo_in_file() -- started in a seperate thread.", share); - argvRec[PATH_ADDR] = (char*) recPath.c_str(); - argvDet[PATH_ADDR] = (char*) detPath.c_str(); - - recLog("fetching camera footage -- ", share); - - if (share->recordUrl == share->detectUrl) - { - exeCmd(argvDet); + share->detThreads.push_back(thread(detectMoInFile, bufPath, share)); } else { - exeCmd(argvDet); - exeCmd(argvRec); + recLog("ffmpeg returned non zero, indicating failure. please check stderr output.", share); + + if (exists(bufPath)) + { + remove(bufPath); + } + + sleep(share->clipLen); } - - sleep(share->clipLen * 2); - - detectMoInFile(detPath, recPath, share); } - while (share->detProcs > 0) sleep(1); + waitForDetThreads(share); if (!share->skipCmd) { @@ -151,8 +111,7 @@ void recLoop(shared_t *share) else { recLog("running post command: " + share->postCmd, share); - - system(string("timeout -k 1 14 " + share->postCmd).c_str()); + system(share->postCmd.c_str()); } } else @@ -166,52 +125,26 @@ void recLoop(shared_t *share) { break; } - - destroy_at(argvDet); - destroy_at(argvRec); } } -void sigHandler(int signal, siginfo_t *info, void *context) -{ - cerr << "received signal details: " << endl; - cerr << "-- si_pid: " << info->si_pid << endl; - cerr << "-- si_signo: " << info->si_signo << endl; - cerr << "-- si_code: " << info->si_code << endl; - cerr << "-- si_errno: " << info->si_errno << endl; - cerr << "-- si_uid: " << info->si_uid << endl; - cerr << "-- si_addr: " << info->si_addr << endl; - cerr << "-- si_status: " << info->si_status << endl; - cerr << "-- si_band: " << info->si_band << endl; - - exit(EXIT_FAILURE); -} - int main(int argc, char** argv) { struct shared_t sharedRes; - struct sigaction act = { 0 }; - - act.sa_flags = SA_SIGINFO; - act.sa_sigaction = &sigHandler; - - sigaction(SIGTERM, &act, NULL); - sigaction(SIGABRT, &act, NULL); - sigaction(SIGSEGV, &act, NULL); - sigaction(SIGILL, &act, NULL); - sigaction(SIGFPE, &act, NULL); - sigaction(SIGBUS, &act, NULL); - - sharedRes.conf = parseForParam("-c", argc, argv, false); + sharedRes.conf = parseForList("-c", argc, argv); 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 the config file." << endl; + cout << "-c : path to a 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") { @@ -219,16 +152,13 @@ int main(int argc, char** argv) } else if (sharedRes.conf.empty()) { - cerr << "err: config file not given in -c" << endl; + cerr << "err: no config file(s) were given in -c" << endl; } else { - sharedRes.retCode = 0; - sharedRes.detProcs = 0; - sharedRes.index = 0; - sharedRes.updateRoot = true; - sharedRes.skipCmd = false; - sharedRes.init = true; + sharedRes.retCode = 0; + sharedRes.skipCmd = false; + sharedRes.init = true; recLoop(&sharedRes); diff --git a/src/mo_detect.cpp b/src/mo_detect.cpp index fc9e145..48a8ab1 100644 --- a/src/mo_detect.cpp +++ b/src/mo_detect.cpp @@ -95,31 +95,16 @@ void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share) detLog("wr_out() -- finished()", share); } -bool openVid(const string &buffFile, VideoCapture &cap) -{ - auto ret = cap.open(buffFile.c_str(), CAP_FFMPEG); - - for (auto i = 0; (i < 3) && !ret; ++i) - { - sleep(1); - - ret = cap.open(buffFile.c_str(), CAP_FFMPEG); - } - - return ret; -} - bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share) { detLog("mo_detect() -- start()", share); detLog("buff_file: " + buffFile, share); - auto mod = false; - auto trys = 0; + auto mod = false; - VideoCapture capture; + VideoCapture capture(buffFile.c_str(), CAP_FFMPEG); - if (openVid(buffFile, capture)) + if (capture.isOpened()) { Mat prev; Mat next; diff --git a/src/mo_detect.h b/src/mo_detect.h index 71c4f08..894573c 100644 --- a/src/mo_detect.h +++ b/src/mo_detect.h @@ -17,7 +17,6 @@ #include "web.h" #include "logger.h" -bool openVid(const string &buffFile, VideoCapture &cap); bool imgDiff(const Mat &prev, const 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);