diff --git a/README.md b/README.md index 99f6678..31b61c4 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,20 @@ 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 ### The config file is a simple text file that contain parameters that dictate the behavior of the application. Below is an example of a config file with all -parameters supported and descriptions of each parameter. +parameters supported and descriptions of each. ``` # Motion Watch config file # @@ -35,9 +40,9 @@ recording_stream = rtsp://1.2.3.4:554/h264 # 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. it is -# recommended to leave it on the default value if using apache2 as the -# http(s) server. +# from the cameras as well as the web interface for the application. +# warning: this will overwrite any existing index.html files so be sure +# to choose a directory that doesn't have an existing website. # buff_dir = /tmp # this application records small clips of the footage from the camera and @@ -57,27 +62,32 @@ pix_thresh = 150 # before the pixels are actually considered different. think of this as # pixel diff sensitivity, the higher the value the lesser the sensitivity. # -frame_gap = 10 +frame_gap = 20 # this value is used to tell the application how far in between frames to # check the pixel diffs for motion. the lower the value, the more frames # will be checked, however with that comes higher cpu usage. # -img_thresh = 10000 +img_thresh = 80000 # this indicates how many pixels need to be different in between frame_gap # before it is considered motion. any video clips found with frames # exceeding this value will be moved from buff_dir to web_root. # -post_cmd = move_the_ptz_camera.py -# this an optional command to run after the internal timer duration has -# elapsed. one great use for this is to move a ptz camera to the next -# position of it's patrol pattern. note: the call to this command will be -# delayed if motion was detected. +clip_len = 20 +# this parameter indicate the amount of seconds to record in each video +# clip from the camera that will be stored and then processed in buff_dir. # -duration = 60 -# this sets the internal timer used to reset to the detection loop and -# then call post_cmd if it is defined. this will also reload the config -# file so changes to any settings will be applied without restarting the -# application. +num_of_clips = 3 +# this will tell the application how many video clips should be recorded +# to buff_dir from the camera before the recording loop pauses to do some +# house keeping. by house keeping, it will wait until all motion detection +# threads are finished, reload the config file and then call the post_cmd +# if no motion was detected in any of the video clips. +# +post_cmd = move_the_ptz_camera.py +# this an optional command to run after num_of_clips is met. one great use +# for this is to move a ptz camera to the next position of it's patrol +# pattern. note: the call to this command will be delayed if motion was +# detected. # max_days = 15 # this defines the maximum amount of days worth of video clips that is diff --git a/src/common.cpp b/src/common.cpp index 0ddcbd5..9f51039 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -167,46 +167,20 @@ void rdLine(const string ¶m, const string &line, int *value) } } -bool rdConf(shared_t *share) +bool rdConf(const string &filePath, shared_t *share) { - ifstream varFile(share->conf.c_str()); + ifstream varFile(filePath.c_str()); if (!varFile.is_open()) { share->retCode = ENOENT; - cerr << "err: Failed to open the config file: " << share->conf << " for reading. please check file permissions or if it exists." << endl; + cout << "wrn: config file: " << filePath << " does not exists or lack read permissions." << endl; } else { string line; - share->recordUrl.clear(); - share->postCmd.clear(); - share->buffDir.clear(); - share->recLogPath.clear(); - share->detLogPath.clear(); - share->recLogFile.close(); - share->detLogFile.close(); - - share->retCode = 0; - share->frameGap = 10; - share->pixThresh = 150; - share->imgThresh = 10000; - share->secs = 60; - share->maxDays = 15; - share->maxClips = 90; - share->maxLogSize = 50000; - share->camName = path(share->conf.c_str()).filename(); - share->webRoot = "/var/www/html"; - share->buffDir = "/tmp"; - share->vidExt = "mp4"; - share->vidCodec = "copy"; - share->skipCmd = false; - share->webBg = "#485564"; - share->webTxt = "#dee5ee"; - share->webFont = "courier"; - do { getline(varFile, line); @@ -220,7 +194,8 @@ bool rdConf(shared_t *share) rdLine("web_bg = ", line, &share->webBg); rdLine("web_font = ", line, &share->webFont); rdLine("post_cmd = ", line, &share->postCmd); - rdLine("duration = ", line, &share->secs); + 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); @@ -233,6 +208,53 @@ bool rdConf(shared_t *share) } } while(!line.empty()); + } + + return share->retCode == 0; +} + +bool rdConf(shared_t *share) +{ + share->recordUrl.clear(); + share->postCmd.clear(); + share->buffDir.clear(); + share->camName.clear(); + share->recLogPath.clear(); + share->detLogPath.clear(); + share->recLogFile.close(); + share->detLogFile.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->maxLogSize = 50000; + share->webRoot = "/var/www/html"; + share->buffDir = "/tmp"; + share->vidExt = "mp4"; + share->vidCodec = "copy"; + share->skipCmd = false; + 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 (share->camName.empty()) + { + share->camName = path(share->conf.back()).filename(); + } share->outDir = cleanDir(share->webRoot) + "/" + share->camName; share->buffDir = cleanDir(share->buffDir) + "/" + share->camName; @@ -252,36 +274,68 @@ 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; + } - varFile.close(); - - return share->retCode == 0; + return ret; } -string parseForParam(const string &arg, int argc, char** argv, bool argOnly) +string parseForParam(const string &arg, int argc, char** argv, bool argOnly, int &offs) { - for (int i = 0; i < argc; ++i) + auto ret = string(); + + for (; offs < argc; ++offs) { - auto argInParams = string(argv[i]); + auto argInParams = string(argv[offs]); if (arg.compare(argInParams) == 0) { if (!argOnly) { - // check ahead, make sure i + 1 won't cause out-of-range exception - if ((i + 1) <= (argc - 1)) + offs++; + // check ahead, make sure offs + 1 won't cause out-of-range exception + if (offs <= (argc - 1)) { - return string(argv[i + 1]); + ret = string(argv[offs]); } } else { - return string("true"); + ret = string("true"); } } } - return string(); + return ret; +} + +string parseForParam(const string &arg, int argc, char** argv, bool argOnly) +{ + auto notUsed = 0; + + 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) diff --git a/src/common.h b/src/common.h index b240a42..478162c 100644 --- a/src/common.h +++ b/src/common.h @@ -35,12 +35,13 @@ using namespace cv; using namespace std; using namespace std::filesystem; -#define APP_VER "1.5.t18" +#define APP_VER "1.5.t19" #define APP_NAME "Motion Watch" struct shared_t { vector detThreads; + vector conf; ofstream recLogFile; ofstream detLogFile; string recLogPath; @@ -48,7 +49,6 @@ struct shared_t string recordUrl; string outDir; string postCmd; - string conf; string buffDir; string vidExt; string vidCodec; @@ -59,10 +59,11 @@ struct shared_t string webRoot; bool init; bool skipCmd; + int clipLen; int frameGap; int pixThresh; int imgThresh; - int secs; + int numOfClips; int maxDays; int maxClips; int maxLogSize; @@ -72,6 +73,7 @@ struct shared_t string genDstFile(const string &dirOut, const char *fmt, const string &ext); string genTimeStr(const char *fmt); string cleanDir(const string &path); +string parseForParam(const string &arg, int argc, char** argv, bool argOnly, int &offs); string parseForParam(const string &arg, int argc, char** argv, bool argOnly); bool createDir(const string &dir); bool createDirTree(const string &full_path); @@ -82,6 +84,7 @@ 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 3d19556..8aec3d4 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,13 +66,12 @@ void recLoop(shared_t *share) recLog("camera specific webroot page updated: " + share->outDir + "/index.html", share); - for (auto i = 0; i < share->secs; i += 10) + for (auto i = 0; i < share->numOfClips; ++i) { auto bufPath = cleanDir(share->buffDir) + "/" + to_string(i) + "." + share->vidExt; - auto limSecs = to_string(share->secs + 2); - auto cmd = "timeout -k 1 " + limSecs + " "; + auto cmd = "timeout -k 1 " + to_string(share->clipLen + 2) + " "; - cmd += "ffmpeg -hide_banner -i " + share->recordUrl + " -y -vcodec " + share->vidCodec + " -movflags faststart -t 10 " + bufPath; + cmd += "ffmpeg -hide_banner -i " + share->recordUrl + " -y -vcodec " + share->vidCodec + " -movflags faststart -t " + to_string(share->clipLen) + " " + bufPath; recLog("ffmpeg_run: " + cmd, share); @@ -95,7 +94,7 @@ void recLoop(shared_t *share) remove(bufPath); } - sleep(10); + sleep(share->clipLen); } } @@ -133,15 +132,19 @@ int main(int argc, char** argv) { struct shared_t sharedRes; - 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 << "-v : display the current version." << 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") { @@ -149,7 +152,7 @@ int main(int argc, char** argv) } else if (sharedRes.conf.empty()) { - cerr << "err: A config file was not given in -c" << endl; + cerr << "err: no config file(s) were given in -c" << endl; } else { diff --git a/src/mo_detect.cpp b/src/mo_detect.cpp index 44201ee..48a8ab1 100644 --- a/src/mo_detect.cpp +++ b/src/mo_detect.cpp @@ -18,7 +18,7 @@ bool imgDiff(const Mat &prev, const Mat &next, shared_t *share) detLog("img_diff() -- start()", share); - if (prev.empty()) detLog("prev_frame is empty -- this should never happen (opencv to blame).", share); + 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); if (!prev.empty() && !next.empty()) diff --git a/src/web.cpp b/src/web.cpp index 9adae51..ad64476 100644 --- a/src/web.cpp +++ b/src/web.cpp @@ -25,6 +25,7 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share) htmlText += "\n"; htmlText += "\n"; htmlText += "\n"; + htmlText += "\n"; htmlText += "\n"; htmlText += "\n"; htmlText += "\n"; @@ -56,7 +57,7 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share) // regName.substr(0, regName.size() - 5) removes .html auto name = regName.substr(0, regName.size() - 5); - htmlText += "\n"; + htmlText += "\n"; } } @@ -96,6 +97,7 @@ void genHTMLvid(const string &outputVid, shared_t *share) htmlText += "\n"; htmlText += "\n"; htmlText += "\n"; + htmlText += "\n"; htmlText += "\n"; htmlText += "\n"; htmlText += "\n";