#include #include #include #include #include #include #include #include #include #include #include #include #include using namespace cv; using namespace std; #define BUF_SZ 3 struct shared_t { string recordUrl; string outDir; string postCmd; string conf; string buffDir; string concatTxtTmp; string concatShTmp; string createShTmp; mutex thrMutex; bool init; int tmpId; int colorThresh; int secs; int blockThresh; int blockX; int blockY; int retCode; } sharedRes; string cleanDir(const string &path) { if (path[path.size() - 1] == '/') { return path.substr(0, path.size() - 1); } else { return path; } } bool createDir(const string &dir) { auto ret = mkdir(dir.c_str(), 0777); if (ret == -1) { return errno == EEXIST; } else { return true; } } bool createDirTree(const string &full_path) { size_t pos = 0; auto ret = true; while (ret == true && pos != string::npos) { pos = full_path.find('/', pos + 1); ret = createDir(full_path.substr(0, pos)); } return ret; } bool fileExists(const string& name) { return access(name.c_str(), F_OK) != -1; } void replaceAll(string &str, const string &from, const string &to) { if(from.empty()) return; size_t startPos = 0; while((startPos = str.find(from, startPos)) != string::npos) { str.replace(startPos, from.length(), to); startPos += to.length(); } } string genDstFile(const string &dirOut, const string &ext) { time_t rawtime; time(&rawtime); auto timeinfo = localtime(&rawtime); char dateC[20]; strftime(dateC, 20, "%Y-%m-%d", timeinfo); createDirTree(cleanDir(dirOut)); return cleanDir(dirOut) + string("/") + string(dateC) + ext; } string genTmpFile(const string &dirOut, const string &ext, shared_t *share) { createDirTree(cleanDir(dirOut)); share->tmpId += 1; return cleanDir(dirOut) + string("/") + to_string(share->tmpId) + ext; } Mat toGray(const Mat &src) { Mat ret; cvtColor(src, ret, COLOR_BGR2GRAY); return ret; } bool pixDiff(const uchar &pixA, const uchar &pixB, shared_t *share) { auto diff = 0; if (pixA > pixB) diff = pixA - pixB; if (pixB > pixA) diff = pixB - pixA; if (diff < share->colorThresh) { diff = 0; } return diff != 0; } void secDiff(Mat imgA, Mat imgB, int rows, int cols, int rowOffs, int colOffs, bool *mod, shared_t *share) { auto pnts = 0; for (auto y = rowOffs; y < rows; y++) { for (auto x = colOffs; x < cols; x++) { auto pixA = imgA.at(Point(x, y)); auto pixB = imgB.at(Point(x, y)); //cout << "pnts: " << pnts << endl; if (pixDiff(pixA, pixB, share)) { pnts += 1; if (pnts >= share->blockThresh) { lock_guard guard(share->thrMutex); *mod = true; return; } } } } } bool imgDiff(Mat prev, Mat next, shared_t *share) { auto numOfXBlocks = prev.cols / share->blockX; auto numOfYBlocks = prev.rows / share->blockY; auto moInBlock = false; vector threads; for (auto x = 0; (x < numOfXBlocks) && !moInBlock; x += share->blockX) { for (auto y = 0; (y < numOfYBlocks) && !moInBlock; y += share->blockY) { threads.push_back(thread(secDiff, prev, next, share->blockY, share->blockX, y, x, &moInBlock, share)); } } for (auto &&thr : threads) { thr.join(); } return moInBlock; } string parseForParam(const string &arg, int argc, char** argv, bool argOnly) { for (int i = 0; i < argc; ++i) { auto argInParams = string(argv[i]); 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)) { return string(argv[i + 1]); } } else { return string("true"); } } } return string(); } void rdLine(const string ¶m, const string &line, string *value) { if (line.rfind(param.c_str(), 0) == 0) { *value = line.substr(param.size()); //cout << param << *value << endl; } } void rdLine(const string ¶m, const string &line, int *value) { if (line.rfind(param.c_str(), 0) == 0) { *value = strtol(line.substr(param.size()).c_str(), NULL, 10); //cout << param << *value << endl; } } bool rdConf(shared_t *share) { ifstream varFile(share->conf.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; } else { string line; share->recordUrl.clear(); share->outDir.clear(); share->postCmd.clear(); share->buffDir.clear(); share->colorThresh = 5; share->secs = 60; share->blockX = 32; share->blockY = 32; share->blockThresh = 900; do { getline(varFile, line); if (line.rfind("#", 0) != 0) { rdLine("recording_stream = ", line, &share->recordUrl); rdLine("output_dir = ", line, &share->outDir); rdLine("post_cmd = ", line, &share->postCmd); rdLine("color_threshold = ", line, &share->colorThresh); rdLine("duration = ", line, &share->secs); rdLine("buff_dir = ", line, &share->buffDir); rdLine("block_x = ", line, &share->blockX); rdLine("block_y = ", line, &share->blockY); rdLine("block_threshold = ", line, &share->blockThresh); } } while(!line.empty()); if (share->init) { system(string("rm -r " + share->buffDir).c_str()); share->init = false; } share->retCode = 0; } varFile.close(); return share->retCode == 0; } bool capPair(Mat &prev, Mat &next, VideoCapture &capture, shared_t *share) { capture >> prev; capture >> next; return !prev.empty() && !next.empty(); } void wrOut(const string &buffFile, const string &dstPath, shared_t *share) { ofstream file; auto scriptFile = genTmpFile(share->buffDir, ".sh", share); auto scriptData = string(); if (fileExists(dstPath)) { auto concatFile = genTmpFile(share->buffDir, ".txt", share); auto existsFile = genTmpFile(share->outDir, ".ts", share); auto concatData = share->concatTxtTmp; scriptData = share->concatShTmp; replaceAll(concatData, "%existsFile%", existsFile); replaceAll(concatData, "%buffFile%", buffFile); replaceAll(scriptData, "%existsFile%", existsFile); replaceAll(scriptData, "%concatFile%", concatFile); file.open(concatFile.c_str()); file << concatData; file.close(); } else { scriptData = share->createShTmp; } replaceAll(scriptData, "%buffFile%", buffFile); replaceAll(scriptData, "%dstPath%", dstPath); replaceAll(scriptData, "%scriptFile%", scriptFile); file.open(scriptFile.c_str()); file << scriptData; file.close(); system(string("sh " + scriptFile + " &").c_str()); } bool moDetect(const string &buffFile, shared_t *share) { auto mod = false; VideoCapture capture(buffFile.c_str(), CAP_FFMPEG); if (capture.isOpened()) { Mat prev; Mat next; while (capPair(prev, next, capture, share)) { if (imgDiff(toGray(prev), toGray(next), share)) { mod = true; break; } } if (mod) { auto dstPath = genDstFile(share->outDir, ".ts"); wrOut(buffFile, dstPath, share); } } else { cerr << "err: Could not open buff file: " << buffFile << " for reading. check formatting/permissions." << endl; } if (!mod) { system(string("rm " + buffFile + " &").c_str()); } return mod; } void recLoop(shared_t *share) { while (rdConf(share)) { auto mod = false; for (auto i = 0; i < share->secs; i += BUF_SZ) { auto dstPath = genTmpFile(share->buffDir, ".ts", share); auto cmd = "ffmpeg -hide_banner -loglevel error -i " + share->recordUrl + " -y -vcodec copy -t " + to_string(BUF_SZ) + " " + dstPath; system(cmd.c_str()); mod = moDetect(dstPath, share); } if (!mod) { system(share->postCmd.c_str()); } } } void showHelp() { cout << "Motion Watch v1.1" << endl << endl; cout << "Usage: mow " << endl << endl; cout << "-h : display usage information about this application." << endl; cout << "-c : path to the config file." << endl; } int main(int argc, char** argv) { sharedRes.conf = parseForParam("-c", argc, argv, false); if (parseForParam("-h", argc, argv, true) == "true") { showHelp(); } else if (sharedRes.conf.empty()) { cerr << "err: A config file was not given in -c" << endl; } else { sharedRes.retCode = 0; sharedRes.tmpId = 0; sharedRes.init = true; sharedRes.concatTxtTmp += "file '%existsFile%'\n"; sharedRes.concatTxtTmp += "file '%buffFile%'\n"; sharedRes.concatShTmp += "#!/bin/sh\n"; sharedRes.concatShTmp += "cp '%dstPath%' '%existsFile%'\n"; sharedRes.concatShTmp += "ffmpeg -hide_banner -loglevel error -y -f concat -safe 0 -i '%concatFile%' -c copy '%dstPath%'\n"; sharedRes.concatShTmp += "rm '%concatFile%'\n"; sharedRes.concatShTmp += "rm '%existsFile%'\n"; sharedRes.concatShTmp += "rm '%buffFile%'\n"; sharedRes.concatShTmp += "rm '%scriptFile%'\n"; sharedRes.createShTmp += "#!/bin/sh\n"; sharedRes.createShTmp += "cp '%buffFile%' '%dstPath%'\n"; sharedRes.createShTmp += "rm '%buffFile%'\n"; sharedRes.createShTmp += "rm '%scriptFile%'\n"; thread th1(recLoop, &sharedRes); th1.join(); return sharedRes.retCode; } return EINVAL; }