23e0ae935e
Added string trimming to the vid_container parameter to filter out bad user input. Added detection_stream url to the config file and made it so the application can now use a smaller/lower bit rate stream for motion detection separate from the recording stream. This can significantly lower CPU usage. Moved away from using system() and the explicit timeout command. Instead opted to using popen() and cancelable pthreads. Doing this pulls back more control over ffmpeg than before and the app will now properly respond term signals and even the CTRL-C keyboard interrupt.
207 lines
6.4 KiB
C++
Executable File
207 lines
6.4 KiB
C++
Executable File
// 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 "mo_detect.h"
|
|
#include "logger.h"
|
|
|
|
void detectMoInFile(const string &detPath, const string &recPath, shared_t *share)
|
|
{
|
|
detLog("detect_mo_in_file() -- start", share);
|
|
|
|
Mat thumbNail;
|
|
|
|
if (moDetect(detPath, thumbNail, share))
|
|
{
|
|
share->skipCmd = true;
|
|
|
|
wrOut(recPath, thumbNail, share);
|
|
}
|
|
|
|
if (exists(detPath)) remove(detPath);
|
|
if (exists(recPath)) remove(recPath);
|
|
|
|
detLog("detect_mo_in_file() -- finished", share);
|
|
}
|
|
|
|
static void *runDetCmd(void *arg)
|
|
{
|
|
auto share = reinterpret_cast<shared_t*>(arg);
|
|
|
|
recLog("ffmpeg_det_run: " + share->detCmd, share);
|
|
pclose(popen(share->detCmd.c_str(), "r"));
|
|
|
|
share->cmdFinished++; return NULL;
|
|
}
|
|
|
|
static void *runRecCmd(void *arg)
|
|
{
|
|
auto share = reinterpret_cast<shared_t*>(arg);
|
|
|
|
if (share->recordUrl != share->detectUrl)
|
|
{
|
|
// recording command is not allowed to run if both
|
|
// streams are the same.
|
|
recLog("ffmpeg_rec_run: " + share->recCmd, share);
|
|
pclose(popen(share->recCmd.c_str(), "r"));
|
|
}
|
|
|
|
share->cmdFinished++; return NULL;
|
|
}
|
|
|
|
void recLoop(shared_t *share)
|
|
{
|
|
while (rdConf(share))
|
|
{
|
|
recLog("rec_loop() -- start", share);
|
|
|
|
enforceMaxLogSize(share->recLogPath, share);
|
|
enforceMaxLogSize(share->detLogPath, share);
|
|
|
|
initLogFile(share->recLogPath, share->recLogFile);
|
|
initLogFile(share->detLogPath, share->detLogFile);
|
|
|
|
initLogFrontPages(share);
|
|
|
|
if (!exists("/tmp/mow-lock"))
|
|
{
|
|
system("touch /tmp/mow-lock");
|
|
|
|
genCSS(share);
|
|
genHTMLul(share->webRoot, string(APP_NAME) + " " + string(APP_VER), share);
|
|
|
|
remove("/tmp/mow-lock");
|
|
recLog("webroot page updated: " + cleanDir(share->webRoot) + "/index.html", share);
|
|
}
|
|
else
|
|
{
|
|
recLog("skipping update of the webroot page, it is busy.", share);
|
|
}
|
|
|
|
genHTMLul(share->outDir, share->camName, share);
|
|
|
|
recLog("camera specific webroot page updated: " + share->outDir + "/index.html", share);
|
|
|
|
for (auto i = 0; i < share->numOfClips; ++i)
|
|
{
|
|
auto detPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->detSuffix;
|
|
auto recPath = cleanDir(share->buffDir) + "/" + to_string(i) + share->recSuffix;
|
|
|
|
if (share->recordUrl == share->detectUrl)
|
|
{
|
|
recPath = detPath;
|
|
}
|
|
|
|
share->recCmd = "ffmpeg -hide_banner -i " + share->recordUrl + " -y -vcodec " + share->vidCodec + " -t " + to_string(share->clipLen) + " " + recPath;
|
|
share->detCmd = "ffmpeg -hide_banner -i " + share->detectUrl + " -y -vcodec " + share->vidCodec + " -t " + to_string(share->clipLen) + " " + detPath;
|
|
|
|
share->cmdFinished = 0;
|
|
|
|
pthread_t detThr;
|
|
pthread_t recThr;
|
|
|
|
auto detOk = pthread_create(&detThr, NULL, &runDetCmd, share);
|
|
auto recOk = pthread_create(&recThr, NULL, &runRecCmd, share);
|
|
|
|
if (detOk == 0 && recOk == 0)
|
|
{
|
|
sleep(share->clipLen);
|
|
|
|
if (share->cmdFinished < 2)
|
|
{
|
|
// 2 second grace period.
|
|
sleep(2);
|
|
}
|
|
|
|
if (share->cmdFinished < 2)
|
|
{
|
|
// force close ffmpeg cmds after failing to finish
|
|
// after the grace period.
|
|
pthread_cancel(detThr);
|
|
pthread_cancel(recThr);
|
|
}
|
|
|
|
new thread(detectMoInFile, detPath, recPath, share);
|
|
}
|
|
else
|
|
{
|
|
recLog("could not start one or both ffmpeg cmds in seperate threads. detOk = " + to_string(detOk) + " recOk = " + to_string(recOk), share);
|
|
}
|
|
}
|
|
|
|
if (!share->skipCmd)
|
|
{
|
|
recLog("no motion detected", share);
|
|
|
|
if (share->postCmd.empty())
|
|
{
|
|
recLog("post command not defined, skipping.", share);
|
|
}
|
|
else
|
|
{
|
|
recLog("running post command: " + share->postCmd, share);
|
|
system(share->postCmd.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
recLog("motion detected, skipping the post command.", share);
|
|
}
|
|
|
|
recLog("rec_loop() -- finished", share);
|
|
|
|
if (share->retCode != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
struct shared_t sharedRes;
|
|
|
|
sharedRes.conf = parseForList("-c", argc, argv);
|
|
|
|
if (parseForParam("-h", argc, argv, true) == "true")
|
|
{
|
|
cout << "Motion Watch " << APP_VER << endl << endl;
|
|
cout << "Usage: mow <argument>" << endl << endl;
|
|
cout << "-h : display usage information about this application." << endl;
|
|
cout << "-c : path to a config file." << 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")
|
|
{
|
|
cout << APP_VER << endl;
|
|
}
|
|
else if (sharedRes.conf.empty())
|
|
{
|
|
cerr << "err: no config file(s) were given in -c" << endl;
|
|
}
|
|
else
|
|
{
|
|
sharedRes.retCode = 0;
|
|
sharedRes.skipCmd = false;
|
|
sharedRes.init = true;
|
|
|
|
recLoop(&sharedRes);
|
|
|
|
return sharedRes.retCode;
|
|
}
|
|
|
|
return EINVAL;
|
|
}
|