The fork() architecture from the previous commit is also deemed a
failure. Reverted back to v1.5.t19 code. I'll start from scratch, using
this commit as the new base.
This commit is contained in:
Maurice ONeal 2023-02-18 21:20:24 -05:00
parent 13eaf75c8a
commit 81da33ba81
7 changed files with 144 additions and 250 deletions

View File

@ -13,8 +13,13 @@ of this app can be used to operate multiple cameras.
Usage: mow <argument> Usage: mow <argument>
-h : display usage information about this application. -h : display usage information about this application.
-c : path to the config file. -c : path to the config file(s).
-v : display the current version. -v : display the current version.
note: multiple -c config files can be passed, reading from left
to right. any conflicting values between the files will
have the latest value from the latest file overwrite the
the earliest.
``` ```
### Config File ### ### Config File ###
@ -33,14 +38,6 @@ recording_stream = rtsp://1.2.3.4:554/h264
# this is the url to the main stream of the IP camera that will be used # this is the url to the main stream of the IP camera that will be used
# to record footage. # to record footage.
# #
detect_stream = rtsp://1.2.3.4:554/h264cif
# this is an optional detection stream that will be used to detect motion.
# most cameras have the option to broadcast multiple streams of the same
# footage. it's recommend to use this parameter with a smaller, lower bit
# rate stream to reduce CPU usage. recording_stream will still be used to
# record higher quality footage. if not defined, recording_stream will be
# used for both motion detection and recording.
#
web_root = /var/www/html web_root = /var/www/html
# this is the output directory that will be used to store recorded footage # this is the output directory that will be used to store recorded footage
# from the cameras as well as the web interface for the application. # from the cameras as well as the web interface for the application.

View File

@ -6,7 +6,7 @@ cp ./.build-mow/mow /opt/mow/bin
printf "#!/bin/sh\n" > /opt/mow/run printf "#!/bin/sh\n" > /opt/mow/run
printf "export OPENCV_LOG_LEVEL=DEBUG\n" >> /opt/mow/run printf "export OPENCV_LOG_LEVEL=DEBUG\n" >> /opt/mow/run
printf "export OPENCV_VIDEOIO_DEBUG=1\n" >> /opt/mow/run printf "export OPENCV_VIDEOIO_DEBUG=1\n" >> /opt/mow/run
printf "/opt/mow/bin \$1 \$2 \$3 \$4 \$5 \$6 \$7 \$8\n" >> /opt/mow/run printf "/opt/mow/bin \$1 \$2 \$3\n" >> /opt/mow/run
chmod +x /opt/mow/run chmod +x /opt/mow/run
chmod +x /opt/mow/bin chmod +x /opt/mow/bin
rm /usr/bin/mow rm /usr/bin/mow

View File

@ -12,44 +12,6 @@
#include "common.h" #include "common.h"
string leftTrim(const string &str, const string &toRemove)
{
auto start = str.find_first_not_of(toRemove);
if (start != string::npos)
{
return str.substr(start);
}
else
{
return str;
}
}
string rightTrim(const string &str, const string &toRemove)
{
auto end = str.find_last_not_of(toRemove);
if (end != string::npos)
{
return str.substr(0, end + 1);
}
else
{
return str;
}
}
string trim(const string &str, const string &toRemove)
{
return rightTrim(leftTrim(str, toRemove), toRemove);
}
string trim(const string &str)
{
return trim(str, TRIM_REMOVE);
}
string cleanDir(const string &path) string cleanDir(const string &path)
{ {
if (path[path.size() - 1] == '/') if (path[path.size() - 1] == '/')
@ -193,7 +155,7 @@ void rdLine(const string &param, const string &line, string *value)
{ {
if (line.rfind(param.c_str(), 0) == 0) if (line.rfind(param.c_str(), 0) == 0)
{ {
*value = trim(line).substr(param.size()); *value = line.substr(param.size());
} }
} }
@ -201,7 +163,7 @@ void rdLine(const string &param, const string &line, int *value)
{ {
if (line.rfind(param.c_str(), 0) == 0) if (line.rfind(param.c_str(), 0) == 0)
{ {
*value = strtol(trim(line).substr(param.size()).c_str(), NULL, 10); *value = strtol(line.substr(param.size()).c_str(), NULL, 10);
} }
} }
@ -213,7 +175,7 @@ bool rdConf(const string &filePath, shared_t *share)
{ {
share->retCode = ENOENT; share->retCode = ENOENT;
cerr << "err: config file: " << filePath << " does not exists or lack read permissions." << endl; cout << "wrn: config file: " << filePath << " does not exists or lack read permissions." << endl;
} }
else else
{ {
@ -227,7 +189,6 @@ bool rdConf(const string &filePath, shared_t *share)
{ {
rdLine("cam_name = ", line, &share->camName); rdLine("cam_name = ", line, &share->camName);
rdLine("recording_stream = ", line, &share->recordUrl); rdLine("recording_stream = ", line, &share->recordUrl);
rdLine("detect_stream = ", line, &share->detectUrl);
rdLine("web_root = ", line, &share->webRoot); rdLine("web_root = ", line, &share->webRoot);
rdLine("web_text = ", line, &share->webTxt); rdLine("web_text = ", line, &share->webTxt);
rdLine("web_bg = ", line, &share->webBg); rdLine("web_bg = ", line, &share->webBg);
@ -255,7 +216,6 @@ bool rdConf(const string &filePath, shared_t *share)
bool rdConf(shared_t *share) bool rdConf(shared_t *share)
{ {
share->recordUrl.clear(); share->recordUrl.clear();
share->detectUrl.clear();
share->postCmd.clear(); share->postCmd.clear();
share->buffDir.clear(); share->buffDir.clear();
share->camName.clear(); share->camName.clear();
@ -281,27 +241,25 @@ bool rdConf(shared_t *share)
share->webBg = "#485564"; share->webBg = "#485564";
share->webTxt = "#dee5ee"; share->webTxt = "#dee5ee";
share->webFont = "courier"; share->webFont = "courier";
share->detSuffix = ".det.";
share->recSuffix = ".rec.";
if (rdConf(share->conf, share)) auto ret = false;
for (auto &&confPath: share->conf)
{
if (rdConf(confPath, share)) ret = true;
}
if (ret)
{ {
if (share->camName.empty()) if (share->camName.empty())
{ {
share->camName = path(share->conf).filename(); share->camName = path(share->conf.back()).filename();
}
if (share->detectUrl.empty())
{
share->detectUrl = share->recordUrl;
} }
share->outDir = cleanDir(share->webRoot) + "/" + share->camName; share->outDir = cleanDir(share->webRoot) + "/" + share->camName;
share->buffDir = cleanDir(share->buffDir) + "/" + share->camName; share->buffDir = cleanDir(share->buffDir) + "/" + share->camName;
share->recLogPath = share->outDir + "/rec_log_lines.html"; share->recLogPath = share->outDir + "/rec_log_lines.html";
share->detLogPath = share->outDir + "/det_log_lines.html"; share->detLogPath = share->outDir + "/det_log_lines.html";
share->detSuffix += share->vidExt;
share->recSuffix += share->vidExt;
if (share->init) if (share->init)
{ {
@ -316,8 +274,12 @@ bool rdConf(shared_t *share)
createDirTree(cleanDir(share->buffDir)); createDirTree(cleanDir(share->buffDir));
createDirTree(share->outDir); createDirTree(share->outDir);
} }
else
{
cerr << "err: none of the expected config files could be read." << endl;
}
return share->retCode == 0; return ret;
} }
string parseForParam(const string &arg, int argc, char** argv, bool argOnly, int &offs) string parseForParam(const string &arg, int argc, char** argv, bool argOnly, int &offs)
@ -355,3 +317,33 @@ string parseForParam(const string &arg, int argc, char** argv, bool argOnly)
return parseForParam(arg, argc, argv, argOnly, notUsed); return parseForParam(arg, argc, argv, argOnly, notUsed);
} }
vector<string> parseForList(const string &arg, int argc, char** argv)
{
auto offs = 0;
auto ret = vector<string>();
string param;
do
{
param = parseForParam(arg, argc, argv, false, offs);
if (!param.empty())
{
ret.push_back(param);
}
}
while (!param.empty());
return ret;
}
void waitForDetThreads(shared_t *share)
{
for (auto &&thr : share->detThreads)
{
thr.join();
}
share->detThreads.clear();
}

View File

@ -21,12 +21,12 @@
#include <stdlib.h> #include <stdlib.h>
#include <errno.h> #include <errno.h>
#include <vector> #include <vector>
#include <thread>
#include <filesystem> #include <filesystem>
#include <mutex> #include <mutex>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
#include <signal.h>
#include <opencv4/opencv2/opencv.hpp> #include <opencv4/opencv2/opencv.hpp>
#include <opencv4/opencv2/videoio.hpp> #include <opencv4/opencv2/videoio.hpp>
@ -35,53 +35,41 @@ using namespace cv;
using namespace std; using namespace std;
using namespace std::filesystem; using namespace std::filesystem;
#define APP_VER "1.6.t8" #define APP_VER "1.6.t9"
#define APP_NAME "Motion Watch" #define APP_NAME "Motion Watch"
#define TRIM_REMOVE " \n\r\t\f\v."
#define PATH_ADDR 9
#define MAX_CAP_RETRY 3
struct shared_t struct shared_t
{ {
ofstream recLogFile; vector<thread> detThreads;
ofstream detLogFile; vector<string> conf;
string conf; ofstream recLogFile;
string recLogPath; ofstream detLogFile;
string detLogPath; string recLogPath;
string recordUrl; string detLogPath;
string detectUrl; string recordUrl;
string outDir; string outDir;
string postCmd; string postCmd;
string buffDir; string buffDir;
string recSuffix; string vidExt;
string detSuffix; string vidCodec;
string vidExt; string camName;
string vidCodec; string webBg;
string camName; string webTxt;
string webBg; string webFont;
string webTxt; string webRoot;
string webFont; bool init;
string webRoot; bool skipCmd;
bool init; int clipLen;
bool skipCmd; int frameGap;
bool updateRoot; int pixThresh;
int detProcs; int imgThresh;
int clipLen; int numOfClips;
int frameGap; int maxDays;
int pixThresh; int maxClips;
int imgThresh; int maxLogSize;
int numOfClips; int retCode;
int maxDays;
int maxClips;
int maxLogSize;
int index;
int retCode;
}; };
string leftTrim(const string &str, const string &toRemove);
string rightTrim(const string &str, const string &toRemove);
string trim(const string &str, const string &toRemove);
string trim(const string &str);
string genDstFile(const string &dirOut, const char *fmt, const string &ext); string genDstFile(const string &dirOut, const char *fmt, const string &ext);
string genTimeStr(const char *fmt); string genTimeStr(const char *fmt);
string cleanDir(const string &path); string cleanDir(const string &path);
@ -93,7 +81,10 @@ void enforceMaxDays(const string &dirPath, shared_t *share);
void enforceMaxClips(const string &dirPath, shared_t *share); void enforceMaxClips(const string &dirPath, shared_t *share);
void rdLine(const string &param, const string &line, string *value); void rdLine(const string &param, const string &line, string *value);
void rdLine(const string &param, const string &line, int *value); void rdLine(const string &param, const string &line, int *value);
void statOut(shared_t *share);
void waitForDetThreads(shared_t *share);
bool rdConf(shared_t *share); bool rdConf(shared_t *share);
vector<string> parseForList(const string &arg, int argc, char** argv);
vector<string> lsFilesInDir(const string &path, const string &ext = string()); vector<string> lsFilesInDir(const string &path, const string &ext = string());
vector<string> lsDirsInDir(const string &path); vector<string> lsDirsInDir(const string &path);

View File

@ -13,48 +13,32 @@
#include "mo_detect.h" #include "mo_detect.h"
#include "logger.h" #include "logger.h"
void detectMoInFile(const string &detPath, const string &recPath, shared_t *share) void detectMoInFile(const string &bufPath, shared_t *share)
{ {
if (fork() == 0) detLog("detect_mo_in_file() -- start", share);
Mat thumbNail;
if (moDetect(bufPath, thumbNail, share))
{ {
detLog("detect_mo_in_file() -- start", share); share->skipCmd = true;
share->detProcs++; wrOut(bufPath, thumbNail, share);
if (exists(detPath))
{
Mat thumbNail;
if (moDetect(detPath, thumbNail, share))
{
share->skipCmd = true;
wrOut(recPath, thumbNail, share);
}
}
if (exists(detPath)) remove(detPath);
if (exists(recPath)) remove(recPath);
share->detProcs--;
detLog("detect_mo_in_file() -- finished", share);
} }
} else if (exists(bufPath))
void exeCmd(char *const argv[])
{
if (fork() == 0)
{ {
execvp("ffmpeg", argv); remove(bufPath);
_Exit(EXIT_FAILURE);
} }
detLog("detect_mo_in_file() -- finished", share);
} }
void recLoop(shared_t *share) void recLoop(shared_t *share)
{ {
while (rdConf(share)) while (rdConf(share))
{ {
recLog("rec_loop() -- start", share);
enforceMaxLogSize(share->recLogPath, share); enforceMaxLogSize(share->recLogPath, share);
enforceMaxLogSize(share->detLogPath, share); enforceMaxLogSize(share->detLogPath, share);
@ -63,9 +47,7 @@ void recLoop(shared_t *share)
initLogFrontPages(share); initLogFrontPages(share);
recLog("rec_loop() -- start", share); if (!exists("/tmp/mow-lock"))
if (!exists("/tmp/mow-lock") && share->updateRoot)
{ {
system("touch /tmp/mow-lock"); system("touch /tmp/mow-lock");
@ -74,71 +56,49 @@ void recLoop(shared_t *share)
remove("/tmp/mow-lock"); remove("/tmp/mow-lock");
recLog("webroot page updated: " + cleanDir(share->webRoot) + "/index.html", share); recLog("webroot page updated: " + cleanDir(share->webRoot) + "/index.html", share);
}
share->updateRoot = false; else
{
recLog("skipping update of the webroot page, it is busy.", share);
} }
genHTMLul(share->outDir, share->camName, share); genHTMLul(share->outDir, share->camName, share);
recLog("camera specific webroot page updated: " + share->outDir + "/index.html", share); recLog("camera specific webroot page updated: " + share->outDir + "/index.html", share);
auto strClipLen = to_string(share->clipLen); for (auto i = 0; i < share->numOfClips; ++i)
char* argvRec[] = {(char*) "ffmpeg",
(char*) "-hide_banner",
(char*) "-i",
(char*) share->recordUrl.c_str(),
(char*) "-y",
(char*) "-vcodec",
(char*) share->vidCodec.c_str(),
(char*) "-t",
(char*) strClipLen.c_str(),
(char*) "--replace_me--",
NULL};
char* argvDet[] = {(char*) "ffmpeg",
(char*) "-hide_banner",
(char*) "-i",
(char*) share->detectUrl.c_str(),
(char*) "-y",
(char*) "-vcodec",
(char*) share->vidCodec.c_str(),
(char*) "-t",
(char*) strClipLen.c_str(),
(char*) "--replace_me--",
NULL};
for (auto i = 0; i < share->numOfClips; ++i, ++share->index)
{ {
auto detPath = cleanDir(share->buffDir) + "/" + to_string(share->index) + share->detSuffix; auto bufPath = cleanDir(share->buffDir) + "/" + to_string(i) + "." + share->vidExt;
auto recPath = cleanDir(share->buffDir) + "/" + to_string(share->index) + share->recSuffix; auto cmd = "timeout -k 1 " + to_string(share->clipLen + 2) + " ";
if (share->recordUrl == share->detectUrl) cmd += "ffmpeg -hide_banner -i " + share->recordUrl + " -y -vcodec " + share->vidCodec + " -movflags faststart -t " + to_string(share->clipLen) + " " + bufPath;
recLog("ffmpeg_run: " + cmd, share);
auto retCode = system(cmd.c_str());
recLog("ffmpeg_retcode: " + to_string(retCode), share);
if (retCode == 0)
{ {
recPath = detPath; recLog("detect_mo_in_file() -- started in a seperate thread.", share);
}
argvRec[PATH_ADDR] = (char*) recPath.c_str(); share->detThreads.push_back(thread(detectMoInFile, bufPath, share));
argvDet[PATH_ADDR] = (char*) detPath.c_str();
recLog("fetching camera footage -- ", share);
if (share->recordUrl == share->detectUrl)
{
exeCmd(argvDet);
} }
else else
{ {
exeCmd(argvDet); recLog("ffmpeg returned non zero, indicating failure. please check stderr output.", share);
exeCmd(argvRec);
if (exists(bufPath))
{
remove(bufPath);
}
sleep(share->clipLen);
} }
sleep(share->clipLen * 2);
detectMoInFile(detPath, recPath, share);
} }
while (share->detProcs > 0) sleep(1); waitForDetThreads(share);
if (!share->skipCmd) if (!share->skipCmd)
{ {
@ -151,8 +111,7 @@ void recLoop(shared_t *share)
else else
{ {
recLog("running post command: " + share->postCmd, share); recLog("running post command: " + share->postCmd, share);
system(share->postCmd.c_str());
system(string("timeout -k 1 14 " + share->postCmd).c_str());
} }
} }
else else
@ -166,52 +125,26 @@ void recLoop(shared_t *share)
{ {
break; break;
} }
destroy_at(argvDet);
destroy_at(argvRec);
} }
} }
void sigHandler(int signal, siginfo_t *info, void *context)
{
cerr << "received signal details: " << endl;
cerr << "-- si_pid: " << info->si_pid << endl;
cerr << "-- si_signo: " << info->si_signo << endl;
cerr << "-- si_code: " << info->si_code << endl;
cerr << "-- si_errno: " << info->si_errno << endl;
cerr << "-- si_uid: " << info->si_uid << endl;
cerr << "-- si_addr: " << info->si_addr << endl;
cerr << "-- si_status: " << info->si_status << endl;
cerr << "-- si_band: " << info->si_band << endl;
exit(EXIT_FAILURE);
}
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
struct shared_t sharedRes; struct shared_t sharedRes;
struct sigaction act = { 0 }; sharedRes.conf = parseForList("-c", argc, argv);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = &sigHandler;
sigaction(SIGTERM, &act, NULL);
sigaction(SIGABRT, &act, NULL);
sigaction(SIGSEGV, &act, NULL);
sigaction(SIGILL, &act, NULL);
sigaction(SIGFPE, &act, NULL);
sigaction(SIGBUS, &act, NULL);
sharedRes.conf = parseForParam("-c", argc, argv, false);
if (parseForParam("-h", argc, argv, true) == "true") if (parseForParam("-h", argc, argv, true) == "true")
{ {
cout << "Motion Watch " << APP_VER << endl << endl; cout << "Motion Watch " << APP_VER << endl << endl;
cout << "Usage: mow <argument>" << endl << endl; cout << "Usage: mow <argument>" << endl << endl;
cout << "-h : display usage information about this application." << endl; cout << "-h : display usage information about this application." << endl;
cout << "-c : path to the config file." << endl; cout << "-c : path to a config file." << endl;
cout << "-v : display the current version." << endl << endl; cout << "-v : display the current version." << endl << endl;
cout << "note: multiple -c config files can be passed, reading from left" << endl;
cout << " to right. any conflicting values between the files will" << endl;
cout << " have the latest value from the latest file overwrite the" << endl;
cout << " the earliest." << endl;
} }
else if (parseForParam("-v", argc, argv, true) == "true") else if (parseForParam("-v", argc, argv, true) == "true")
{ {
@ -219,16 +152,13 @@ int main(int argc, char** argv)
} }
else if (sharedRes.conf.empty()) else if (sharedRes.conf.empty())
{ {
cerr << "err: config file not given in -c" << endl; cerr << "err: no config file(s) were given in -c" << endl;
} }
else else
{ {
sharedRes.retCode = 0; sharedRes.retCode = 0;
sharedRes.detProcs = 0; sharedRes.skipCmd = false;
sharedRes.index = 0; sharedRes.init = true;
sharedRes.updateRoot = true;
sharedRes.skipCmd = false;
sharedRes.init = true;
recLoop(&sharedRes); recLoop(&sharedRes);

View File

@ -95,31 +95,16 @@ void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share)
detLog("wr_out() -- finished()", share); detLog("wr_out() -- finished()", share);
} }
bool openVid(const string &buffFile, VideoCapture &cap)
{
auto ret = cap.open(buffFile.c_str(), CAP_FFMPEG);
for (auto i = 0; (i < 3) && !ret; ++i)
{
sleep(1);
ret = cap.open(buffFile.c_str(), CAP_FFMPEG);
}
return ret;
}
bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share) bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share)
{ {
detLog("mo_detect() -- start()", share); detLog("mo_detect() -- start()", share);
detLog("buff_file: " + buffFile, share); detLog("buff_file: " + buffFile, share);
auto mod = false; auto mod = false;
auto trys = 0;
VideoCapture capture; VideoCapture capture(buffFile.c_str(), CAP_FFMPEG);
if (openVid(buffFile, capture)) if (capture.isOpened())
{ {
Mat prev; Mat prev;
Mat next; Mat next;

View File

@ -17,7 +17,6 @@
#include "web.h" #include "web.h"
#include "logger.h" #include "logger.h"
bool openVid(const string &buffFile, VideoCapture &cap);
bool imgDiff(const Mat &prev, const Mat &next, shared_t *share); bool imgDiff(const Mat &prev, const Mat &next, shared_t *share);
bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share); bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share);
void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share); void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share);