// 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; } void cleanupEmptyDirs(const string &path) { if (exists(path)) { for (auto &entry : directory_iterator(path)) { if (entry.is_directory()) { try { remove(entry.path()); } catch (filesystem_error const &ex) { // non-empty dir assumed when filesystem_error is raised. cleanupEmptyDirs(path + "/" + entry.path().filename().string()); } } } } } vector lsFilesInDir(const string &path, const string &ext) { vector names; if (exists(path)) { for (auto &entry : directory_iterator(path)) { if (entry.is_regular_file()) { auto name = entry.path().filename().string(); if (ext.empty() || name.ends_with(ext)) { names.push_back(name); } } } } sort(names.begin(), names.end()); return names; } vector lsDirsInDir(const string &path) { vector names; if (exists(path)) { for (auto &entry : directory_iterator(path)) { if (entry.is_directory()) { names.push_back(entry.path().filename().string()); } } } sort(names.begin(), names.end()); return names; } void cleanupStream(const string &plsPath) { ifstream fileIn(plsPath); for (string line; getline(fileIn, line); ) { if (line.starts_with("VIDEO_TS/")) { remove(line); } } } void enforceMaxEvents(shared_t *share) { auto names = lsFilesInDir("events", ".mp4"); while (names.size() > share->maxEvents) { // removes the video file extension (.mp4). auto nameOnly = "events/" + names[0].substr(0, names[0].size() - 4); auto mp4File = nameOnly + string(".mp4"); auto imgFile = nameOnly + string(".jpg"); auto webFile = nameOnly + string(".html"); if (exists(mp4File)) remove(mp4File); if (exists(imgFile)) remove(imgFile); if (exists(webFile)) remove(webFile); 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 genEventName(int score) { return genTimeStr(string("%Y-%j-%H-%M-%S--" + to_string(score)).c_str()); } void rdLine(const string ¶m, const string &line, string *value) { if (line.rfind(param.c_str(), 0) == 0) { *value = line.substr(param.size()); } } 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); } } bool rdConf(const string &filePath, shared_t *share) { ifstream varFile(filePath.c_str()); if (!varFile.is_open()) { share->retCode = ENOENT; cerr << "err: config file: " << filePath << " does not exists or lack read permissions." << endl; } else { string line; do { getline(varFile, line); if (line.rfind("#", 0) != 0) { rdLine("cam_name = ", line, &share->camName); rdLine("recording_stream = ", line, &share->recordUrl); rdLine("web_root = ", line, &share->webRoot); rdLine("web_text = ", line, &share->webTxt); rdLine("web_bg = ", line, &share->webBg); rdLine("web_font = ", line, &share->webFont); rdLine("max_event_secs = ", line, &share->evMaxSecs); rdLine("post_secs = ", line, &share->postSecs); rdLine("post_cmd = ", line, &share->postCmd); rdLine("pix_thresh = ", line, &share->pixThresh); rdLine("img_thresh = ", line, &share->imgThresh); rdLine("frame_gap = ", line, &share->frameGap); rdLine("max_events = ", line, &share->maxEvents); rdLine("max_log_size = ", line, &share->maxLogSize); } } while(!line.empty()); } return share->retCode == 0; } bool rdConf(shared_t *share) { share->recordUrl.clear(); share->postCmd.clear(); share->camName.clear(); share->retCode = 0; share->pixThresh = 50; share->imgThresh = 800; share->maxEvents = 40; share->maxLogSize = 100000; share->skipCmd = false; share->postSecs = 60; share->evMaxSecs = 10; share->frameGap = 10; share->webRoot = "/var/www/html"; share->webBg = "#485564"; share->webTxt = "#dee5ee"; share->webFont = "courier"; if (rdConf(share->conf, share)) { if (share->camName.empty()) { share->camName = path(share->conf).filename(); } share->outDir = cleanDir(share->webRoot) + "/" + share->camName; error_code ec; createDirTree(share->outDir); current_path(share->outDir, ec); share->retCode = ec.value(); if (share->retCode != 0) { cerr << "err: " << ec.message() << endl; } } return share->retCode == 0; } string parseForParam(const string &arg, int argc, char** argv, bool argOnly, int &offs) { auto ret = string(); for (; offs < argc; ++offs) { auto argInParams = string(argv[offs]); if (arg.compare(argInParams) == 0) { if (!argOnly) { offs++; // check ahead, make sure offs + 1 won't cause out-of-range exception if (offs <= (argc - 1)) { ret = string(argv[offs]); } } else { ret = string("true"); } } } return ret; } string parseForParam(const string &arg, int argc, char** argv, bool argOnly) { auto notUsed = 0; return parseForParam(arg, argc, argv, argOnly, notUsed); } string genEventPath(const string &tsPath) { if (tsPath.size() > 14) { // removes 'VIDEO_TS/live/' from the front of the string. auto ret = tsPath.substr(14); return "VIDEO_TS/events/" + ret; } else { return string(); } } string genVidNameFromLive(const string &tsPath) { if (tsPath.size() > 17) { // removes 'VIDEO_TS/live/' from the front of the string. auto ret = tsPath.substr(14); auto ind = tsPath.find('/'); // removes '.ts' from the end of the string. ret = ret.substr(0, ret.size() - 3); while (ind != string::npos) { // remove all '/' ret.erase(ind, 1); ind = ret.find('/'); } return ret; } else { return string(); } }