// This file is part of Motion Watch. // Motion Watch is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Motion Watch is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. #include "mo_detect.h" #include "logger.h" void detectMoInFile(const string &detPath, const string &recPath, shared_t *share) { detLog("detect_mo_in_file() -- start", share); Mat thumbNail; if (moDetect(detPath, thumbNail, share)) { share->skipCmd = true; wrOut(recPath, thumbNail, share); } if (exists(detPath)) remove(detPath); if (exists(recPath)) remove(recPath); detLog("detect_mo_in_file() -- finished", share); } static void *runDetCmd(void *arg) { auto share = reinterpret_cast(arg); recLog("ffmpeg_det_run: " + share->detCmd, share); pclose(popen(share->detCmd.c_str(), "r")); share->cmdFinished++; return NULL; } static void *runRecCmd(void *arg) { auto share = reinterpret_cast(arg); if (share->recordUrl != share->detectUrl) { // recording command is not allowed to run if both // streams are the same. recLog("ffmpeg_rec_run: " + share->recCmd, share); pclose(popen(share->recCmd.c_str(), "r")); } share->cmdFinished++; return NULL; } void recLoop(shared_t *share) { while (rdConf(share)) { recLog("rec_loop() -- start", share); enforceMaxLogSize(share->recLogPath, share); enforceMaxLogSize(share->detLogPath, share); initLogFile(share->recLogPath, share->recLogFile); initLogFile(share->detLogPath, share->detLogFile); initLogFrontPages(share); if (!exists("/tmp/mow-lock")) { system("touch /tmp/mow-lock"); genCSS(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); } 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); for (auto i = 0; i < share->numOfClips; ++i) { auto detPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->detSuffix; auto recPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->recSuffix; if (share->recordUrl == share->detectUrl) { recPath = detPath; } share->recCmd = "ffmpeg -hide_banner -i " + share->recordUrl + " -y -vcodec " + share->vidCodec + " -t " + to_string(share->clipLen) + " " + recPath; share->detCmd = "ffmpeg -hide_banner -i " + share->detectUrl + " -y -vcodec " + share->vidCodec + " -t " + to_string(share->clipLen) + " " + detPath; share->cmdFinished = 0; pthread_t detThr; pthread_t recThr; auto detOk = pthread_create(&detThr, NULL, &runDetCmd, share); auto recOk = pthread_create(&recThr, NULL, &runRecCmd, share); if (detOk == 0 && recOk == 0) { sleep(share->clipLen); if (share->cmdFinished < 2) { // 2 second grace period. sleep(2); } if (share->cmdFinished < 2) { // force close ffmpeg cmds after failing to finish // after the grace period. pthread_cancel(detThr); pthread_cancel(recThr); } new thread(detectMoInFile, detPath, recPath, share); } else { recLog("could not start one or both ffmpeg cmds in seperate threads. detOk = " + to_string(detOk) + " recOk = " + to_string(recOk), 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; } } } int main(int argc, char** argv) { struct shared_t sharedRes; 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 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") { cout << APP_VER << endl; } else if (sharedRes.conf.empty()) { cerr << "err: no config file(s) were given in -c" << endl; } else { sharedRes.retCode = 0; sharedRes.skipCmd = false; sharedRes.init = true; recLoop(&sharedRes); return sharedRes.retCode; } return EINVAL; }