// 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" #include "web.h" void detectMo(shared_t *share) { while (share->retCode == 0) { sleep(2); detectMoInStream("stream.m3u8", share); } } void eventLoop(shared_t *share) { while (share->retCode == 0) { 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")) { system("touch /tmp/mow-lock"); genCSS(share); genHTMLul(share->webRoot, string(APP_NAME) + " " + string(APP_VER), share); remove("/tmp/mow-lock"); upkLog("webroot page updated: " + cleanDir(share->webRoot) + "/index.html", share); } else { upkLog("skipping update of the webroot page, it is busy.", share); } sleep(60); } } 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"; recLog("ffmpeg_run: " + cmd, share); auto retCode = system(cmd.c_str()); recLog("ffmpeg_retcode: " + to_string(retCode), share); if (retCode != 0) { recLog("err: ffmpeg returned non zero, indicating failure. please check stderr output.", share); } sleep(60); } } int main(int argc, char** argv) { struct shared_t sharedRes; 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 the config file." << endl; cout << "-v : display the current version." << endl << 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.postCmdRunning = false; 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; } return EINVAL; }