// 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 "common.h" 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(); } } vector lsFilesInDir(const string &path, const string &ext) { DIR *dir; struct dirent *ent; vector names; if ((dir = opendir(path.c_str())) != NULL) { while ((ent = readdir(dir)) != NULL) { auto name = string(ent->d_name); if ((name.size() >= 4) && (ent->d_type & DT_REG)) { if (name.substr(name.size() - 4) == ext) { names.push_back(name); } } } closedir(dir); } sort(names.begin(), names.end()); return names; } void enforceMaxDays(shared_t *share) { auto names = lsFilesInDir(share->outDir, ".m3u"); while (names.size() > share->maxDays) { auto name = names[0]; auto plsFile = cleanDir(share->outDir) + "/" + name; auto vidFold = cleanDir(share->outDir) + "/." + name.substr(0, name.size() - 4); remove(plsFile.c_str()); remove_all(vidFold.c_str()); names.erase(names.begin()); } } string genTimeStr(const char *fmt) { time_t rawtime; time(&rawtime); auto timeinfo = localtime(&rawtime); char ret[50]; strftime(ret, 50, fmt, timeinfo); return string(ret); } string genDstFile(const string &dirOut, const char *fmt, const string &ext) { createDirTree(cleanDir(dirOut)); return cleanDir(dirOut) + string("/") + genTimeStr(fmt) + ext; } string genTmpFile(const string &dirOut, const string &ext, shared_t *share) { createDirTree(cleanDir(dirOut)); if (share->tmpId == 9999999) { share->tmpId = 0; } return cleanDir(dirOut) + string("/") + to_string(share->tmpId++) + ext; } Mat toGray(const Mat &src) { Mat ret; cvtColor(src, ret, COLOR_BGR2GRAY); return ret; } 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; } } vector loadClassList() { vector ret; ifstream ifs("/etc/mow/classes.txt"); string line; while (getline(ifs, line)) { ret.push_back(line); } return ret; } 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; share->maxDays = 5; share->vidExt = "mp4"; share->recLoopWait = false; share->skipCmd = false; share->network = dnn::readNet("/etc/mow/yolov5s.onnx"); share->classNames = loadClassList(); share->network.setPreferableBackend(dnn::DNN_BACKEND_OPENCV); share->network.setPreferableTarget(dnn::DNN_TARGET_CPU); 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); rdLine("max_days = ", line, &share->maxDays); rdLine("vid_container = ", line, &share->vidExt); } } while(!line.empty()); // it's imperative that blockX/Y are not zero or it will cause // an infinte loop. if bad data is read from the conf, default // values will be used. if (share->blockX == 0) share->blockX = 32; if (share->blockY == 0) share->blockY = 32; if (share->init) { remove_all(share->buffDir.c_str()); share->init = false; new thread(statFifo, share); } new thread(enforceMaxDays, share); 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, shared_t *share) { auto clnDir = cleanDir(share->outDir); auto vidOut = genDstFile(clnDir + "/." + genTimeStr("%Y-%m-%d"), "%H%M%S", "." + share->vidExt); auto m3uOut = vidOut.substr(clnDir.size() + 1); auto lisOut = genDstFile(share->outDir, "%Y-%m-%d", ".m3u"); copy_file(buffFile.c_str(), vidOut.c_str()); remove(buffFile.c_str()); ofstream file; if (fileExists(lisOut)) { file.open(lisOut.c_str(), ios_base::app); } else { file.open(lisOut.c_str()); } file << m3uOut << endl; file.close(); } 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 statFifo(shared_t *share) { createDirTree(cleanDir(share->buffDir)); auto path = string(cleanDir(share->buffDir) + "/stat"); auto newLine = string("\n"); if (!fileExists(path)) { mkfifo(path.c_str(), 0666); } while (true) { auto fd = open(path.c_str(), O_WRONLY); write(fd, newLine.c_str(), newLine.size() + 1); write(fd, share->stat.c_str(), share->stat.size() + 1); close(fd); } }