diff --git a/CMakeLists.txt b/CMakeLists.txt index 04cc324..169898f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,34 @@ -cmake_minimum_required(VERSION 2.8.12) -project( MotionWatch ) -find_package( OpenCV REQUIRED ) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -pthread") -include_directories( ${OpenCV_INCLUDE_DIRS} ) -add_executable( mow src/main.cpp src/common.cpp src/mo_detect.cpp src/web.cpp src/logger.cpp ) -target_link_libraries( mow ${OpenCV_LIBS} ) +cmake_minimum_required(VERSION 3.14) + +project(MotionWatch LANGUAGES CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include_directories(${OpenCV_INCLUDE_DIRS}) + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED) +find_package(OpenCV REQUIRED) + +add_executable(mow + src/main.cpp + src/common.h + src/common.cpp + src/mo_detect.h + src/mo_detect.cpp + src/web.h + src/web.cpp + src/logger.h + src/logger.cpp + src/camera.h + src/camera.cpp +) + +target_link_libraries(mow Qt${QT_VERSION_MAJOR}::Core ${OpenCV_LIBS}) diff --git a/setup.sh b/setup.sh index 34579af..6d27e97 100644 --- a/setup.sh +++ b/setup.sh @@ -18,12 +18,5 @@ apt install -y x264 apt install -y libx264-dev apt install -y libilmbase-dev apt install -y libopencv-dev +apt install -y qtbase5-dev apt install -y apache2 -add-apt-repository -y ppa:ubuntu-toolchain-r/test -apt update -y -apt install -y gcc-10 -apt install -y gcc-10-base -apt install -y gcc-10-doc -apt install -y g++-10 -apt install -y libstdc++-10-dev -apt install -y libstdc++-10-doc diff --git a/src/camera.cpp b/src/camera.cpp new file mode 100644 index 0000000..416318f --- /dev/null +++ b/src/camera.cpp @@ -0,0 +1,405 @@ +// 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 "camera.h" + +Camera::Camera(QObject *parent) : QObject(parent) +{ + shared.recordUrl.clear(); + shared.postCmd.clear(); + shared.camName.clear(); + + shared.retCode = 0; + shared.pixThresh = 50; + shared.imgThresh = 800; + shared.maxEvents = 40; + shared.maxLogSize = 100000; + shared.skipCmd = false; + shared.postSecs = 60; + shared.evMaxSecs = 10; + shared.frameGap = 10; + shared.webRoot = "/var/www/html"; + shared.webBg = "#485564"; + shared.webTxt = "#dee5ee"; + shared.webFont = "courier"; +} + +bool Camera::start(const QStringList &args) +{ + auto ret = false; + + shared.conf = getParam("-c", args); + + if (rdConf(&shared)) + { + QDir("live").removeRecursively(); + + } + + return ret; +} + +Loop::Loop(shared_t *sharedRes, QObject *parent) : QObject(parent) +{ + shared = sharedRes; + heartBeat = 10; +} + +void Loop::loop() +{ + while (exec()) + { + if (heartBeat != 0) + { + thread()->sleep(heartBeat); + } + } +} + +bool Loop::exec() +{ + return shared->retCode == 0; +} + +RecLoop::RecLoop(shared_t *sharedRes, QObject *parent) : Loop(sharedRes, parent) +{ + once = true; +} + +void RecLoop::updateCmd() +{ + QStringList args; + + args << "-hide_banner"; + args << "-i" << shared->recordUrl; + args << "-strftime" << "1"; + args << "-strftime_mkdir" << "1"; + args << "-hls_segment_filename" << "live/%Y-%j-%H-%M-%S.ts"; + args << "-hls_flags" << "delete_segments"; + args << "-y"; + args << "-vcodec" << "copy"; + args << "-f" << "hls"; + args << "-hls_time" << "2"; + args << "-hls_list_size" << "1000"; + args << "stream.m3u8"; + + proc.setProgram("ffmpeg"); + proc.setArguments(args); + + recLog("rec_args_updated: " + args.join(" "), shared); +} + +void RecLoop::reset() +{ + recLog("--rec_cmd_resetting--", shared); + + proc.kill(); + proc.waitForFinished(); + + updateCmd(); +} + +bool RecLoop::exec() +{ + auto args = proc.arguments(); + auto md5 = genMD5(QString("stream.m3u8")); + + if (once) + { + updateCmd(); once = false; streamMD5 = genMD5(QByteArray("FIRST")); + } + else if ((args[3] != shared->recordUrl) || (streamMD5 == md5)) + { + reset(); + } + + auto hashLogLine = "stream_hash prev:" + QString(streamMD5.toHex()) + " new:" + QString(md5.toHex()); + + recLog(hashLogLine, shared); + + if (proc.state() == QProcess::NotRunning) + { + proc.start(); + + if (proc.waitForStarted()) + { + recLog("rec_cmd_start: ok", shared); + } + else + { + recLog("rec_cmd_start: fail", shared); + recLog("rec_cmd_stderr: " + QString(proc.readAllStandardError()), shared); + } + } + + return Loop::exec(); +} + +Upkeep::Upkeep(shared_t *sharedRes, QObject *parent) : Loop(sharedRes, parent) {} + +bool Upkeep::exec() +{ + QDir().mkdir("live"); + QDir().mkdir("events"); + QDir().mkdir("logs"); + + enforceMaxLogSize(QString("logs/") + REC_LOG_NAME, shared); + enforceMaxLogSize(QString("logs/") + DET_LOG_NAME, shared); + enforceMaxLogSize(QString("logs/") + UPK_LOG_NAME, shared); + + dumpLogs(QString("logs/") + REC_LOG_NAME, shared->recLog); + dumpLogs(QString("logs/") + DET_LOG_NAME, shared->detLog); + dumpLogs(QString("logs/") + UPK_LOG_NAME, shared->upkLog); + + shared->recLog.clear(); + shared->detLog.clear(); + shared->upkLog.clear(); + + initLogFrontPages(shared); + enforceMaxEvents(shared); + + genHTMLul(".", shared->camName, shared); + upkLog("camera specific webroot page updated: " + shared->outDir + "/index.html", shared); + + QFile tmp("/tmp/mow-lock"); + + if (!tmp.exists()) + { + tmp.open(QFile::WriteOnly); + tmp.write(QByteArray()); + + genCSS(shared); + genHTMLul(shared->webRoot, QString(APP_NAME) + " " + QString(APP_VER), shared); + + tmp.close(); + tmp.remove(); + + upkLog("webroot page updated: " + QDir::cleanPath(shared->webRoot) + "/index.html", shared); + } + else + { + upkLog("skipping update of the webroot page, it is busy.", shared); + } + + return Loop::exec(); +} + +EventLoop::EventLoop(shared_t *sharedRes, QObject *parent) : Loop(sharedRes, parent) +{ + heartBeat = 2; +} + +bool EventLoop::exec() +{ + if (!shared->recList.isEmpty()) + { + auto event = shared->recList[0]; + + recLog("attempting write out of event: " + event.evName, shared); + + if (wrOutVod(event)) + { + genHTMLvod(event.evName); + imwrite(QString("events/" + event.evName + ".jpg").toUtf8().data(), event.thumbnail); + } + + shared->recList.removeFirst(); + } + + return Loop::exec(); +} + +bool EventLoop::wrOutVod(const evt_t &event) +{ + auto cnt = 0; + auto concat = event.evName + ".tmp"; + + QFile file(concat); + + file.open(QFile::WriteOnly); + + for (auto i = 0; i < event.srcPaths.size(); ++i) + { + recLog("event_src: " + event.srcPaths[i], shared); + + if (QFile::exists(event.srcPaths[i])) + { + file.write(QString("file '" + event.srcPaths[i] + "'\n").toUtf8()); cnt++; + } + } + + file.close(); + + if (cnt == 0) + { + recLog("err: none of the event hls clips exists, canceling write out.", shared); + + QFile::remove(concat); return false; + } + else + { + QProcess proc; + QStringList args; + + args << "-f"; + args << "concat"; + args << "-safe" << "0"; + args << "-i" << concat; + args << "-c" << "copy"; + args << "events/" + event.evName + ".mp4"; + + proc.setProgram("ffmpeg"); + proc.setArguments(args); + proc.start(); + + auto ret = false; + + if (proc.waitForStarted()) + { + recLog("concat_cmd_start: ok", shared); + + proc.waitForFinished(); ret = true; + } + else + { + recLog("concat_cmd_start: fail", shared); + recLog("concat_cmd_stderr: " + QString(proc.readAllStandardError()), shared); + } + + QFile::remove(concat); + + return ret; + } +} + +DetectLoop::DetectLoop(shared_t *sharedRes, QObject *parent) : Loop(sharedRes, parent) +{ + heartBeat = 0; + evId = 0; + pcId = 0; + + resetTimers(); +} + +void DetectLoop::resetTimers() +{ + if (evId != 0) killTimer(evId); + if (pcId != 0) killTimer(pcId); + + evId = startTimer(shared->evMaxSecs); + pcId = startTimer(shared->postSecs); +} + +void DetectLoop::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == evId) + { + detLog("---EVENT_BREAK---", shared); + + if (!shared->curEvent.srcPaths.isEmpty()) + { + shared->curEvent.evName = QDateTime::currentDateTime().toString("yyyyMMddmmss--") + QString::number(shared->maxScore); + + shared->recList.append(shared->curEvent); + + detLog("motion detected in " + QString::number(shared->curEvent.srcPaths.size()) + " file(s) in " + QString::number(shared->evMaxSecs) + " secs", shared); + detLog("all video clips queued for event generation under event name: " + shared->curEvent.evName, shared); + } + else + { + detLog("no motion detected in all files. none queued for event generation.", shared); + } + + shared->curEvent.srcPaths.clear(); + shared->curEvent.evName.clear(); + shared->curEvent.thumbnail.release(); + + shared->maxScore = 0; + } + + if (event->timerId() == pcId) + { + detLog("---POST_BREAK---", shared); + + if (!shared->skipCmd) + { + detLog("no motion detected, running post command: " + shared->postCmd, shared); + system(shared->postCmd.toUtf8().data()); + } + else + { + shared->skipCmd = false; + + detLog("motion detected, skipping the post command.", shared); + } + } +} + +bool DetectLoop::exec() +{ + QFile fileIn("stream.m3u8"); + QString tsPath; + + if (!fileIn.open(QFile::ReadOnly)) + { + detLog("err: failed to open the stream hls file for reading. reason: " + fileIn.errorString(), shared); + } + else if (fileIn.size() < 50) + { + detLog("the stream hls list is not big enough yet. waiting for more clips.", shared); + } + else if (!fileIn.seek(fileIn.size() - 50)) + { + detLog("err: failed to seek to 'near end' of stream file. reason: " + fileIn.errorString(), shared); + } + else + { + QString line; + + do + { + line = QString::fromUtf8(fileIn.readLine()); + + if (line.startsWith("live/")) + { + tsPath = line; + } + + } while(!line.isEmpty()); + } + + if (tsPath.isEmpty()) + { + detLog("wrn: didn't find the latest hls clip. previous failure? waiting 5secs.", shared); + + thread()->sleep(5); + } + else if (prevTs == tsPath) + { + detLog("wrn: the lastest hls clip is the same as the previous clip. is the recording loop running? waiting 5secs.", shared); + + thread()->sleep(5); + } + else + { + prevTs = tsPath; + + if (moDetect(tsPath, thread(), shared)) + { + shared->curEvent.srcPaths.append(tsPath); + + shared->skipCmd = true; + } + } + + return Loop::exec(); +} diff --git a/src/camera.h b/src/camera.h new file mode 100644 index 0000000..a3df5ac --- /dev/null +++ b/src/camera.h @@ -0,0 +1,122 @@ +#ifndef CAMERA_H +#define CAMERA_H + +// 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" +#include "logger.h" +#include "web.h" +#include "mo_detect.h" + +class Loop : public QObject +{ + Q_OBJECT + +protected: + + shared_t *shared; + int heartBeat; + +private: + + void loop(); + +public: + + explicit Loop(shared_t *shared, QObject *parent = nullptr); + + virtual bool exec(); +}; + +class Camera : public QObject +{ + Q_OBJECT + +private: + + shared_t shared; + +public: + + explicit Camera(QObject *parent = nullptr); + + bool start(const QStringList &args); +}; + +class RecLoop : public Loop +{ + Q_OBJECT + +private: + + QProcess proc; + QByteArray streamMD5; + bool once; + + void updateCmd(); + void reset(); + +public: + + explicit RecLoop(shared_t *shared, QObject *parent = nullptr); + + bool exec(); +}; + +class Upkeep : public Loop +{ + Q_OBJECT + +public: + + explicit Upkeep(shared_t *shared, QObject *parent = nullptr); + + bool exec(); +}; + +class EventLoop : public Loop +{ + Q_OBJECT + +private: + + bool wrOutVod(const evt_t &event); + +public: + + explicit EventLoop(shared_t *shared, QObject *parent = nullptr); + + bool exec(); +}; + +class DetectLoop : public Loop +{ + Q_OBJECT + +private: + + int pcId; + int evId; + QString prevTs; + + void resetTimers(); + void timerEvent(QTimerEvent *event); + +public: + + explicit DetectLoop(shared_t *shared, QObject *parent = nullptr); + + bool exec(); +}; + +#endif // CAMERA_H diff --git a/src/common.cpp b/src/common.cpp index e5cfece..0ce5987 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -12,124 +12,87 @@ #include "common.h" -string cleanDir(const string &path) +QString getParam(const QString &key, const QStringList &args) { - if (path[path.size() - 1] == '/') - { - return path.substr(0, path.size() - 1); - } - else - { - return path; - } -} + // this can be used by command objects to pick out parameters + // from a command line that are pointed by a name identifier + // example: -i /etc/some_file, this function should pick out + // "/etc/some_file" from args if "-i" is passed into key. -bool createDir(const string &dir) -{ - auto ret = mkdir(dir.c_str(), 0777); + QString ret; - if (ret == -1) - { - return errno == EEXIST; - } - else - { - return true; - } -} + int pos = args.indexOf(QRegularExpression(key, QRegularExpression::CaseInsensitiveOption)); -bool createDirTree(const string &full_path) -{ - size_t pos = 0; - auto ret = true; - - while (ret == true && pos != string::npos) + if (pos != -1) { - pos = full_path.find('/', pos + 1); - ret = createDir(full_path.substr(0, pos)); + // key found. + + if ((pos + 1) <= (args.size() - 1)) + { + // check ahead to make sure pos + 1 will not go out + // of range. + + if (!args[pos + 1].startsWith("-")) + { + // the "-" used throughout this application + // indicates an argument so the above 'if' + // statement will check to make sure it does + // not return another argument as a parameter + // in case a back-to-back "-arg -arg" is + // present. + + ret = args[pos + 1]; + } + } } return ret; } -void cleanupEmptyDirs(const string &path) +QByteArray genMD5(const QByteArray &bytes) { - if (exists(path)) + QCryptographicHash hasher(QCryptographicHash::Md5); + + hasher.addData(bytes); return hasher.result(); +} + +QByteArray genMD5(const QString &path) +{ + auto file = QFile(path); + + if (file.open(QFile::ReadOnly)) { - 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()); - } - } - } + return genMD5(file.readAll()); + } + else + { + return genMD5(QByteArray("EMPTY")); } } -vector lsFilesInDir(const string &path, const string &ext) +QStringList lsFilesInDir(const QString &path, const QString &ext) { - vector names; + QStringList filters; - if (exists(path)) - { - for (auto &entry : directory_iterator(path)) - { - if (entry.is_regular_file()) - { - auto name = entry.path().filename().string(); + filters << "*" + ext; - if (ext.empty() || name.ends_with(ext)) - { - names.push_back(name); - } - } - } - } + QDir dirObj(path); - sort(names.begin(), names.end()); + dirObj.setFilter(QDir::Files); + dirObj.setNameFilters(filters); + dirObj.setSorting(QDir::Name); - return names; + return dirObj.entryList(); } -vector lsDirsInDir(const string &path) +QStringList lsDirsInDir(const QString &path) { - vector names; + QDir dirObj(path); - if (exists(path)) - { - for (auto &entry : directory_iterator(path)) - { - if (entry.is_directory()) - { - names.push_back(entry.path().filename().string()); - } - } - } + dirObj.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); + dirObj.setSorting(QDir::Name); - 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); - } - } + return dirObj.entryList(); } void enforceMaxEvents(shared_t *share) @@ -138,83 +101,57 @@ void enforceMaxEvents(shared_t *share) 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"); + auto nameOnly = "events/" + names[0]; - if (exists(mp4File)) remove(mp4File); - if (exists(imgFile)) remove(imgFile); - if (exists(webFile)) remove(webFile); + nameOnly.remove(".mp4"); - names.erase(names.begin()); + auto mp4File = nameOnly + ".mp4"; + auto imgFile = nameOnly + ".jpg"; + auto webFile = nameOnly + ".html"; + + QFile::remove(mp4File); + QFile::remove(imgFile); + QFile::remove(webFile); + + names.removeFirst(); } } - -string genTimeStr(const char *fmt) +void rdLine(const QString ¶m, const QString &line, QString *value) { - 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) + if (line.startsWith(param)) { - *value = line.substr(param.size()); + *value = line.mid(param.size()); } } -void rdLine(const string ¶m, const string &line, int *value) +void rdLine(const QString ¶m, const QString &line, int *value) { - if (line.rfind(param.c_str(), 0) == 0) + if (line.startsWith(param)) { - *value = strtol(line.substr(param.size()).c_str(), NULL, 10); + *value = line.mid(param.size()).toInt(); } } -bool rdConf(const string &filePath, shared_t *share) +bool rdConf(const QString &filePath, shared_t *share) { - ifstream varFile(filePath.c_str()); + QFile varFile(filePath); - if (!varFile.is_open()) + if (!varFile.open(QFile::ReadOnly)) { share->retCode = ENOENT; - cerr << "err: config file: " << filePath << " does not exists or lack read permissions." << endl; + QTextStream(stderr) << "err: config file: " << filePath << " does not exists or lack read permissions." << Qt::endl; } else { - string line; + QString line; do { - getline(varFile, line); + line = QString::fromUtf8(varFile.readLine()); - if (line.rfind("#", 0) != 0) + if (!line.startsWith("#")) { rdLine("cam_name = ", line, &share->camName); rdLine("recording_stream = ", line, &share->recordUrl); @@ -232,7 +169,7 @@ bool rdConf(const string &filePath, shared_t *share) rdLine("max_log_size = ", line, &share->maxLogSize); } - } while(!line.empty()); + } while(!line.isEmpty()); } return share->retCode == 0; @@ -240,122 +177,24 @@ bool rdConf(const string &filePath, shared_t *share) 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()) + if (share->camName.isEmpty()) { - share->camName = path(share->conf).filename(); + share->camName = QFileInfo(share->conf).fileName(); } - share->outDir = cleanDir(share->webRoot) + "/" + share->camName; + share->outDir = QDir().cleanPath(share->webRoot) + "/" + share->camName; - error_code ec; + QDir().mkpath(share->outDir); - createDirTree(share->outDir); - current_path(share->outDir, ec); - - share->retCode = ec.value(); - - if (share->retCode != 0) + if (!QDir::setCurrent(share->outDir)) { - cerr << "err: " << ec.message() << endl; + QTextStream(stderr) << "err: failed to change/create the current working directory to camera folder: '" << share->outDir << ".' does it exists?" << Qt::endl; + + share->retCode = ENOENT; } } 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(); - } -} diff --git a/src/common.h b/src/common.h index b7a8918..27d36fd 100644 --- a/src/common.h +++ b/src/common.h @@ -13,28 +13,23 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include using namespace cv; using namespace std; -using namespace std::filesystem; -using namespace std::chrono; -#define APP_VER "2.2" +#define APP_VER "3.0.t1" #define APP_NAME "Motion Watch" #define REC_LOG_NAME "rec_log_lines.html" #define DET_LOG_NAME "det_log_lines.html" @@ -42,60 +37,52 @@ using namespace std::chrono; struct evt_t { - string evName; - vector srcPaths; - Mat thumbnail; + QString evName; + QStringList srcPaths; + Mat thumbnail; }; struct shared_t { - vector recList; - string conf; - string recLog; - string detLog; - string upkLog; - string recordUrl; - string outDir; - string postCmd; - string camName; - string webBg; - string webTxt; - string webFont; - string webRoot; - evt_t curEvent; - bool skipCmd; - int frameGap; - int evMaxSecs; - int postSecs; - int maxScore; - int procCnt; - int hlsCnt; - int pixThresh; - int imgThresh; - int maxEvents; - int maxLogSize; - int retCode; - int postInd; - int evInd; + QList recList; + evt_t curEvent; + QString conf; + QString recLog; + QString detLog; + QString upkLog; + QString recordUrl; + QString outDir; + QString postCmd; + QString camName; + QString webBg; + QString webTxt; + QString webFont; + QString webRoot; + bool skipCmd; + int frameGap; + int evMaxSecs; + int postSecs; + int maxScore; + int procCnt; + int hlsCnt; + int pixThresh; + int imgThresh; + int maxEvents; + int maxLogSize; + int retCode; + int postInd; + int evInd; }; -string genVidNameFromLive(const string &tsPath); -string genEventPath(const string &tsPath); -string genEventName(int score); -string genDstFile(const string &dirOut, const char *fmt, const string &ext); -string genTimeStr(const char *fmt); -string cleanDir(const string &path); -string parseForParam(const string &arg, int argc, char** argv, bool argOnly, int &offs); -string parseForParam(const string &arg, int argc, char** argv, bool argOnly); -bool createDir(const string &dir); -bool createDirTree(const string &full_path); -void rdLine(const string ¶m, const string &line, string *value); -void rdLine(const string ¶m, const string &line, int *value); -void cleanupEmptyDirs(const string &path); -void cleanupStream(const string &plsPath); -void enforceMaxEvents(shared_t *share); -bool rdConf(shared_t *share); -vector lsFilesInDir(const string &path, const string &ext = string()); -vector lsDirsInDir(const string &path); +QByteArray genMD5(const QString &path); +QByteArray genMD5(const QByteArray &bytes); +QString getParam(const QString &key, const QStringList &args); +QStringList lsFilesInDir(const QString &path, const QString &ext = QString()); +QStringList lsDirsInDir(const QString &path); +bool rdConf(const QString &filePath, shared_t *share); +bool rdConf(shared_t *share); +void rdLine(const QString ¶m, const QString &line, QString *value); +void rdLine(const QString ¶m, const QString &line, int *value); +void enforceMaxEvents(shared_t *share); #endif // COMMON_H diff --git a/src/logger.cpp b/src/logger.cpp index 996690d..bfc77e0 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -12,58 +12,59 @@ #include "logger.h" -void recLog(const string &line, shared_t *share) +void recLog(const QString &line, shared_t *share) { - share->recLog += genTimeStr("[%Y-%m-%d-%H-%M-%S] ") + line + "
\n"; + share->recLog += QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh-mm-ss] ") + line + "
\n"; } -void detLog(const string &line, shared_t *share) +void detLog(const QString &line, shared_t *share) { - share->detLog += genTimeStr("[%Y-%m-%d-%H-%M-%S] ") + line + "
\n"; + share->detLog += QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh-mm-ss] ") + line + "
\n"; } -void upkLog(const string &line, shared_t *share) +void upkLog(const QString &line, shared_t *share) { - share->upkLog += genTimeStr("[%Y-%m-%d-%H-%M-%S] ") + line + "
\n"; + share->upkLog += QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh-mm-ss] ") + line + "
\n"; } -void enforceMaxLogSize(const string &filePath, shared_t *share) +void enforceMaxLogSize(const QString &filePath, shared_t *share) { - if (exists(filePath)) + QFile file(filePath); + + if (file.exists()) { - if (file_size(filePath) >= share->maxLogSize) + if (file.size() >= share->maxLogSize) { - remove(filePath); + file.remove(); } } } -void dumpLogs(const string &fileName, const string &lines) +void dumpLogs(const QString &fileName, const QString &lines) { - if (!lines.empty()) + if (!lines.isEmpty()) { - ofstream outFile; + QFile outFile(fileName); - if (exists(fileName)) + if (outFile.exists()) { - outFile.open(fileName.c_str(), ofstream::app); + outFile.open(QFile::Append); } else { - outFile.open(fileName.c_str()); + outFile.open(QFile::WriteOnly); } - outFile << lines; - + outFile.write(lines.toUtf8()); outFile.close(); } } -void initLogFrontPage(const string &filePath, const string &logLinesFile) +void initLogFrontPage(const QString &filePath, const QString &logLinesFile) { - if (!exists(filePath)) + if (!QFile::exists(filePath)) { - string htmlText = "\n"; + QString htmlText = "\n"; htmlText += "\n"; htmlText += "