Completely reformed the internal workings of the application code. I
brought back multi-threaded functions so there is now 5 separate threads
for different tasks.

recLoop() - this function calls ffmpeg to begin recording footage from
the defined camera and stores the footage in hls format. It is designed
to keep running for as long as the application is running and if it does
stop for whatever reason, it will attempt to auto re-start.

upkeep() - this function does regular cleanup and enforcement of maxDays
maxLogSize and maxEvents without the need to stop recording or detecting
motion.

detectMo() - this function reads directly from recLoop's hls output and
list all footage that has motion in it. motion detection no longer has
to wait for the clip to finish recording thanks to the use of .ts
containers for the video clips. this makes the motion detection for less
cpu intensive now that it will now operate at the camera's fps (slower).

eventLoop() - this function reads the motion list from detectMo and
copies the footage pointed out by the list to an events folder, also in
hls format.

schLoop() - this function runs an optional user defined external command
every amount of seconds defined in sch_sec. this command temporary stops
motion detection without actually terminating the thread. It will also
not run the command at the scheduled time if motion was detected.

Benefits to this reform:

- far less cpu intensive operation
- multi-threaded architecture for better asynchronous operation
- it has support for live streaming now that hls is being used
- a buff_dir is no longer necessary
This commit is contained in:
Maurice ONeal 2023-03-05 16:07:07 -05:00
parent 81da33ba81
commit a065b7a1d3
10 changed files with 446 additions and 346 deletions

View File

@ -16,6 +16,7 @@ apt install -y libswscale-dev
apt install -y libgstreamer1.0-dev
apt install -y x264
apt install -y libx264-dev
apt install -y libilmbase-dev
apt install -y libopencv-dev
apt install -y apache2
add-apt-repository -y ppa:ubuntu-toolchain-r/test

View File

@ -52,6 +52,28 @@ bool createDirTree(const string &full_path)
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<string> lsFilesInDir(const string &path, const string &ext)
{
vector<string> names;
@ -97,30 +119,32 @@ vector<string> lsDirsInDir(const string &path)
return names;
}
void enforceMaxDays(const string &dirPath, shared_t *share)
void cleanupStream(const string &plsPath)
{
auto names = lsDirsInDir(dirPath);
ifstream fileIn(plsPath);
while (names.size() > (share->maxDays - 1))
for (string line; getline(fileIn, line); )
{
remove_all(string(cleanDir(dirPath) + "/" + names[0]).c_str());
names.erase(names.begin());
if (line.starts_with("VIDEO_TS/"))
{
remove(line);
}
}
}
void enforceMaxClips(const string &dirPath, shared_t *share)
void enforceMaxEvents(shared_t *share)
{
auto names = lsFilesInDir(dirPath, "." + share->vidExt);
auto names = lsFilesInDir(".", ".m3u8");
while (names.size() > share->maxClips)
while (names.size() > share->maxEvents)
{
// removes the video file extension.
auto nameOnly = names[0].substr(0, names[0].size() - (share->vidExt.size() + 1));
auto imgFile = cleanDir(dirPath) + "/" + nameOnly + ".jpg";
auto webFile = cleanDir(dirPath) + "/" + nameOnly + ".html";
// removes the playlist file extension.
auto nameOnly = names[0].substr(0, names[0].size() - 5);
auto imgFile = nameOnly + ".jpg";
auto webFile = nameOnly + ".html";
remove(cleanDir(dirPath) + "/" + names[0]);
cleanupStream(names[0]);
remove(names[0]);
if (exists(imgFile)) remove(imgFile);
if (exists(webFile)) remove(webFile);
@ -129,6 +153,7 @@ void enforceMaxClips(const string &dirPath, shared_t *share)
}
}
string genTimeStr(const char *fmt)
{
time_t rawtime;
@ -175,7 +200,7 @@ bool rdConf(const string &filePath, shared_t *share)
{
share->retCode = ENOENT;
cout << "wrn: config file: " << filePath << " does not exists or lack read permissions." << endl;
cerr << "err: config file: " << filePath << " does not exists or lack read permissions." << endl;
}
else
{
@ -193,17 +218,14 @@ bool rdConf(const string &filePath, shared_t *share)
rdLine("web_text = ", line, &share->webTxt);
rdLine("web_bg = ", line, &share->webBg);
rdLine("web_font = ", line, &share->webFont);
rdLine("sch_sec = ", line, &share->schSec);
rdLine("post_cmd = ", line, &share->postCmd);
rdLine("clip_len = ", line, &share->clipLen);
rdLine("num_of_clips = ", line, &share->numOfClips);
rdLine("buff_dir = ", line, &share->buffDir);
rdLine("frame_gap = ", line, &share->frameGap);
rdLine("pix_thresh = ", line, &share->pixThresh);
rdLine("img_thresh = ", line, &share->imgThresh);
rdLine("max_days = ", line, &share->maxDays);
rdLine("max_clips = ", line, &share->maxClips);
rdLine("max_events = ", line, &share->maxEvents);
rdLine("max_log_size = ", line, &share->maxLogSize);
rdLine("vid_container = ", line, &share->vidExt);
rdLine("vid_codec = ", line, &share->vidCodec);
}
@ -217,69 +239,55 @@ bool rdConf(shared_t *share)
{
share->recordUrl.clear();
share->postCmd.clear();
share->buffDir.clear();
share->camName.clear();
share->recLogPath.clear();
share->detLogPath.clear();
share->upkLogPath.clear();
share->recLogFile.close();
share->detLogFile.close();
share->upkLogFile.close();
share->retCode = 0;
share->frameGap = 20;
share->pixThresh = 150;
share->imgThresh = 80000;
share->clipLen = 20;
share->numOfClips = 3;
share->maxDays = 15;
share->maxClips = 90;
share->maxEvents = 20;
share->maxLogSize = 50000;
share->webRoot = "/var/www/html";
share->buffDir = "/tmp";
share->vidExt = "mp4";
share->vidCodec = "copy";
share->skipCmd = false;
share->schSec = 60;
share->webRoot = "/var/www/html";
share->vidCodec = "copy";
share->webBg = "#485564";
share->webTxt = "#dee5ee";
share->webFont = "courier";
auto ret = false;
for (auto &&confPath: share->conf)
{
if (rdConf(confPath, share)) ret = true;
}
if (ret)
if (rdConf(share->conf, share))
{
if (share->camName.empty())
{
share->camName = path(share->conf.back()).filename();
share->camName = path(share->conf).filename();
}
share->outDir = cleanDir(share->webRoot) + "/" + share->camName;
share->buffDir = cleanDir(share->buffDir) + "/" + share->camName;
share->recLogPath = share->outDir + "/rec_log_lines.html";
share->detLogPath = share->outDir + "/det_log_lines.html";
share->upkLogPath = share->outDir + "/upk_log_lines.html";
if (share->init)
{
if (exists(share->buffDir))
{
remove_all(share->buffDir);
}
error_code ec;
share->init = false;
}
createDirTree(cleanDir(share->buffDir));
createDirTree(share->outDir);
}
else
current_path(share->outDir, ec);
share->retCode = ec.value();
if (share->retCode != 0)
{
cerr << "err: none of the expected config files could be read." << endl;
cerr << "err: " << ec.message() << endl;
}
}
return ret;
return share->retCode == 0;
}
string parseForParam(const string &arg, int argc, char** argv, bool argOnly, int &offs)
@ -318,32 +326,81 @@ string parseForParam(const string &arg, int argc, char** argv, bool argOnly)
return parseForParam(arg, argc, argv, argOnly, notUsed);
}
vector<string> parseForList(const string &arg, int argc, char** argv)
string genEventPath(const string &tsPath)
{
auto offs = 0;
auto ret = vector<string>();
string param;
if (tsPath.size() > 14)
{
// removes 'VIDEO_TS/live/' from the front of the string.
auto ret = tsPath.substr(14);
do
return "VIDEO_TS/events/" + ret;
}
else
{
param = parseForParam(arg, argc, argv, false, offs);
if (!param.empty())
{
ret.push_back(param);
return string();
}
}
while (!param.empty());
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;
}
void waitForDetThreads(shared_t *share)
else
{
for (auto &&thr : share->detThreads)
{
thr.join();
return string();
}
}
share->detThreads.clear();
uint64_t genEpoch()
{
return duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
}
void wrOutm3u8(const pls_t &pls)
{
createDirTree(path(pls.dstPath).parent_path().string());
copy_file(pls.clipPath, pls.dstPath);
auto plsFile = pls.fileName + ".m3u8";
if (exists(plsFile))
{
ofstream file(plsFile.c_str(), ofstream::app);
file << endl;
file << pls.extINF << endl;
file << pls.dstPath;
file.close();
}
else
{
ofstream file(plsFile.c_str());
file << "#EXTM3U" << endl;
file << "#EXT-X-VERSION:3" << endl;
file << "#EXT-X-TARGETDURATION:10" << endl;
file << "#EXT-X-MEDIA-SEQUENCE:0" << endl;
file << "#EXT-X-DISCONTINUITY" << endl;
file << pls.extINF << endl;
file << pls.dstPath;
file.close();
}
}

View File

@ -18,6 +18,7 @@
#include <string>
#include <unistd.h>
#include <time.h>
#include <chrono>
#include <stdlib.h>
#include <errno.h>
#include <vector>
@ -34,42 +35,54 @@
using namespace cv;
using namespace std;
using namespace std::filesystem;
using namespace std::chrono;
#define APP_VER "1.6.t9"
#define APP_VER "2.0.t1"
#define APP_NAME "Motion Watch"
struct pls_t
{
string fileName;
string clipPath;
string dstPath;
string extINF;
uint64_t createTime;
Mat thumbnail;
};
struct shared_t
{
vector<thread> detThreads;
vector<string> conf;
vector<pls_t> recList;
ofstream recLogFile;
ofstream detLogFile;
ofstream upkLogFile;
string conf;
string recLogPath;
string detLogPath;
string upkLogPath;
string recordUrl;
string outDir;
string postCmd;
string buffDir;
string vidExt;
string vidCodec;
string camName;
string webBg;
string webTxt;
string webFont;
string webRoot;
bool init;
bool skipCmd;
int clipLen;
bool postCmdRunning;
int schSec;
int frameGap;
int pixThresh;
int imgThresh;
int numOfClips;
int maxDays;
int maxClips;
int maxEvents;
int maxLogSize;
int retCode;
};
string genVidNameFromLive(const string &tsPath);
string genEventPath(const string &tsPath);
string genDstFile(const string &dirOut, const char *fmt, const string &ext);
string genTimeStr(const char *fmt);
string cleanDir(const string &path);
@ -77,15 +90,15 @@ string parseForParam(const string &arg, int argc, char** argv, bool argO
string parseForParam(const string &arg, int argc, char** argv, bool argOnly);
bool createDir(const string &dir);
bool createDirTree(const string &full_path);
void enforceMaxDays(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, int *value);
void statOut(shared_t *share);
void waitForDetThreads(shared_t *share);
void cleanupEmptyDirs(const string &path);
void cleanupStream(const string &plsPath);
void wrOutm3u8(const pls_t &pls);
void enforceMaxEvents(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> lsDirsInDir(const string &path);
uint64_t genEpoch();
#endif // COMMON_H

View File

@ -22,6 +22,11 @@ void detLog(const string &line, shared_t *share)
share->detLogFile << genTimeStr("[%Y-%m-%d-%H-%M-%S] ") << line << "<br>" << endl;
}
void upkLog(const string &line, shared_t *share)
{
share->upkLogFile << genTimeStr("[%Y-%m-%d-%H-%M-%S] ") << line << "<br>" << endl;
}
void enforceMaxLogSize(const string &filePath, shared_t *share)
{
if (exists(filePath))
@ -47,6 +52,8 @@ void initLogFile(const string &filePath, ofstream &fileObj)
}
void initLogFrontPage(const string &filePath, const string &logLinesFile)
{
if (!exists(filePath))
{
string htmlText = "<!DOCTYPE html>\n";
@ -97,12 +104,15 @@ void initLogFrontPage(const string &filePath, const string &logLinesFile)
outFile.close();
}
}
void initLogFrontPages(shared_t *share)
{
auto recLogFilePath = share->outDir + "/recording_log.html";
auto detLogFilePath = share->outDir + "/detection_log.html";
auto upkLogFilePath = share->outDir + "/upkeep_log.html";
initLogFrontPage(recLogFilePath, path(share->recLogPath).filename().string());
initLogFrontPage(detLogFilePath, path(share->detLogPath).filename().string());
initLogFrontPage(upkLogFilePath, path(share->upkLogPath).filename().string());
}

View File

@ -17,6 +17,7 @@
void recLog(const string &line, shared_t *share);
void detLog(const string &line, shared_t *share);
void upkLog(const string &line, shared_t *share);
void enforceMaxLogSize(const string &filePath, shared_t *share);
void initLogFile(const string &filePath, ofstream &fileObj);
void initLogFrontPages(shared_t *share);

View File

@ -12,40 +12,102 @@
#include "mo_detect.h"
#include "logger.h"
#include "web.h"
void detectMoInFile(const string &bufPath, shared_t *share)
void detectMo(shared_t *share)
{
detLog("detect_mo_in_file() -- start", share);
Mat thumbNail;
if (moDetect(bufPath, thumbNail, share))
while (share->retCode == 0)
{
share->skipCmd = true;
wrOut(bufPath, thumbNail, share);
sleep(2);
detectMoInStream("stream.m3u8", share);
}
else if (exists(bufPath))
{
remove(bufPath);
}
detLog("detect_mo_in_file() -- finished", share);
void eventLoop(shared_t *share)
{
while (share->retCode == 0)
{
while (!share->recList.empty())
{
auto pls = share->recList[0];
auto timeDiff = pls.createTime - genEpoch();
// future time protection. this should never occur but
// this is in place in case the system time is incorrect.
if (timeDiff < 0) timeDiff = 0;
// wait at least 6 seconds before processing the event in
// queue.
if (timeDiff < 6) sleep(6 - timeDiff);
try
{
wrOutm3u8(pls);
genHTMLvid(pls.fileName);
if (!exists(pls.fileName + ".jpg"))
{
imwrite(string(pls.fileName + "jpg").c_str(), pls.thumbnail);
}
}
catch (filesystem_error &ex)
{
recLog("err: could not copy live file: " + pls.clipPath + " reason - " + ex.what(), share);
}
void recLoop(shared_t *share)
{
while (rdConf(share))
{
recLog("rec_loop() -- start", share);
share->recList.erase(share->recList.begin());
}
sleep(5);
}
}
void schLoop(shared_t *share)
{
if (!share->postCmd.empty())
{
while (share->retCode == 0)
{
sleep(share->schSec);
if (!share->skipCmd)
{
share->postCmdRunning = true;
detLog("no motion detected, running post command: " + share->postCmd, share);
system(share->postCmd.c_str());
share->postCmdRunning = false;
}
else
{
share->skipCmd = false;
detLog("motion detected, skipping the post command.", share);
}
}
}
}
void upkeep(shared_t *share)
{
while (share->retCode == 0)
{
enforceMaxLogSize(share->recLogPath, share);
enforceMaxLogSize(share->detLogPath, share);
enforceMaxLogSize(share->upkLogPath, share);
initLogFile(share->recLogPath, share->recLogFile);
initLogFile(share->detLogPath, share->detLogFile);
initLogFile(share->upkLogPath, share->upkLogFile);
initLogFrontPages(share);
enforceMaxEvents(share);
cleanupEmptyDirs("VIDEO_TS");
genHTMLul(".", share->camName, share);
upkLog("camera specific webroot page updated: " + share->outDir + "/index.html", share);
if (!exists("/tmp/mow-lock"))
{
@ -55,23 +117,31 @@ void recLoop(shared_t *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);
upkLog("webroot page updated: " + cleanDir(share->webRoot) + "/index.html", share);
}
else
{
recLog("skipping update of the webroot page, it is busy.", share);
upkLog("skipping update of the webroot page, it is busy.", share);
}
genHTMLul(share->outDir, share->camName, share);
sleep(60);
}
}
recLog("camera specific webroot page updated: " + share->outDir + "/index.html", share);
for (auto i = 0; i < share->numOfClips; ++i)
void recLoop(shared_t *share)
{
auto bufPath = cleanDir(share->buffDir) + "/" + to_string(i) + "." + share->vidExt;
auto cmd = "timeout -k 1 " + to_string(share->clipLen + 2) + " ";
cmd += "ffmpeg -hide_banner -i " + share->recordUrl + " -y -vcodec " + share->vidCodec + " -movflags faststart -t " + to_string(share->clipLen) + " " + bufPath;
while (share->retCode == 0)
{
auto cmd = "ffmpeg -hide_banner -i " +
share->recordUrl +
" -strftime 1" +
" -strftime_mkdir 1" +
" -hls_segment_filename 'VIDEO_TS/live/%Y/%j/%H/%M%S.ts'" +
" -y -vcodec " +
share->vidCodec +
" -f hls -hls_time 6 -hls_list_size " +
to_string((share->maxDays * 86400) / 6) + // 86400 seconds in a day.
" stream.m3u8";
recLog("ffmpeg_run: " + cmd, share);
@ -79,52 +149,12 @@ void recLoop(shared_t *share)
recLog("ffmpeg_retcode: " + to_string(retCode), share);
if (retCode == 0)
if (retCode != 0)
{
recLog("detect_mo_in_file() -- started in a seperate thread.", share);
share->detThreads.push_back(thread(detectMoInFile, bufPath, share));
}
else
{
recLog("ffmpeg returned non zero, indicating failure. please check stderr output.", share);
if (exists(bufPath))
{
remove(bufPath);
recLog("err: ffmpeg returned non zero, indicating failure. please check stderr output.", share);
}
sleep(share->clipLen);
}
}
waitForDetThreads(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;
}
sleep(60);
}
}
@ -132,19 +162,15 @@ int main(int argc, char** argv)
{
struct shared_t sharedRes;
sharedRes.conf = parseForList("-c", argc, argv);
sharedRes.conf = parseForParam("-c", argc, argv, false);
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 << "-c : path to the 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")
{
@ -158,9 +184,26 @@ int main(int argc, char** argv)
{
sharedRes.retCode = 0;
sharedRes.skipCmd = false;
sharedRes.init = true;
sharedRes.postCmdRunning = false;
recLoop(&sharedRes);
rdConf(&sharedRes);
if (exists("VIDEO_TS/live"))
{
remove_all("VIDEO_TS/live");
}
auto thr1 = thread(recLoop, &sharedRes);
auto thr2 = thread(upkeep, &sharedRes);
auto thr3 = thread(detectMo, &sharedRes);
auto thr4 = thread(eventLoop, &sharedRes);
auto thr5 = thread(schLoop, &sharedRes);
thr1.join();
thr2.join();
thr3.join();
thr4.join();
thr5.join();
return sharedRes.retCode;
}

View File

@ -12,17 +12,47 @@
#include "mo_detect.h"
bool imgDiff(const Mat &prev, const Mat &next, shared_t *share)
void detectMoInStream(const string &bufPath, shared_t *share)
{
ifstream fileIn(bufPath);
pls_t clip;
auto clipPathFilter = genTimeStr("VIDEO_TS/live/%Y/%j/%H/");
for (string line; getline(fileIn, line); )
{
if (line.starts_with(clipPathFilter))
{
clip.clipPath = line;
}
else if (line.starts_with("#EXTINF"))
{
clip.extINF = line;
}
}
if (!clip.clipPath.empty() && !clip.extINF.empty())
{
if (moDetect(clip.clipPath, clip.thumbnail, share))
{
clip.fileName = genTimeStr("%Y-%j-%H-%M");
clip.dstPath = genEventPath(bufPath);
clip.createTime = genEpoch();
share->skipCmd = true;
share->recList.push_back(clip);
}
}
}
bool imgDiff(Mat &prev, Mat &next, shared_t *share)
{
auto ret = false;
detLog("img_diff() -- start()", share);
cvtColor(prev, prev, COLOR_BGR2GRAY);
cvtColor(next, next, COLOR_BGR2GRAY);
if (prev.empty()) detLog("prev_frame is empty -- Borken frame from the camera assumed.", share);
if (next.empty()) detLog("next_frame is empty -- EOF assumed.", share);
if (!prev.empty() && !next.empty())
{
Mat diff;
absdiff(prev, next, diff);
@ -32,95 +62,36 @@ bool imgDiff(const Mat &prev, const Mat &next, shared_t *share)
detLog("diff_score: " + to_string(diffScore), share);
ret = diffScore >= share->imgThresh;
}
detLog("img_diff() -- finished()", share);
return ret;
}
Mat frameFF(VideoCapture *cap, int gap)
{
Mat ret;
if (gap == 0) gap = 1;
for (int i = 0; i < gap; ++i)
{
cap->grab();
}
cap->retrieve(ret);
if (!ret.empty())
{
cvtColor(ret, ret, COLOR_BGR2GRAY);
}
return ret;
}
void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share)
{
detLog("wr_out() -- start()", share);
detLog("buff_file: " + buffFile, share);
auto dayStr = genTimeStr("%Y-%m-%d");
auto timStr = genTimeStr("%H%M%S");
auto outDir = cleanDir(share->outDir) + "/" + dayStr;
if (!exists(outDir))
{
enforceMaxDays(share->outDir, share);
}
auto vidOut = genDstFile(outDir, timStr.c_str(), "." + share->vidExt);
auto imgOut = genDstFile(outDir, timStr.c_str(), ".jpg");
detLog("write_out_vid: " + vidOut, share);
detLog("write_out_img: " + imgOut, share);
enforceMaxClips(outDir, share);
copy_file(buffFile.c_str(), vidOut.c_str());
remove(buffFile.c_str());
imwrite(imgOut.c_str(), vidThumb);
genHTMLvid(vidOut, share);
genHTMLul(outDir, share->camName + ": " + dayStr, share);
genHTMLul(share->outDir, share->camName, share);
detLog("wr_out() -- finished()", share);
return diffScore >= share->imgThresh;
}
bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share)
{
detLog("mo_detect() -- start()", share);
detLog("buff_file: " + buffFile, share);
auto mod = false;
detLog("stream_clip: " + buffFile, share);
VideoCapture capture(buffFile.c_str(), CAP_FFMPEG);
if (capture.isOpened())
{
Mat prev;
Mat next;
auto frameCount = capture.get(CAP_PROP_FRAME_COUNT);
auto frameGaps = frameCount / share->frameGap;
detLog("frame_count: " + to_string(frameCount), share);
detLog("frame_gaps: " + to_string(frameGaps), share);
detLog("capture open successful.", share);
for (auto i = 0; i < frameGaps; i++)
while (capture.grab())
{
if (prev.empty()) prev = frameFF(&capture, 1);
else prev = next.clone();
next = frameFF(&capture, share->frameGap);
if (prev.empty())
{
capture.retrieve(prev);
}
else
{
capture.retrieve(next);
if (!share->postCmdRunning)
{
if (imgDiff(prev, next, share))
{
resize(next, vidThumb, Size(720, 480), INTER_LINEAR);
@ -128,15 +99,20 @@ bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share)
mod = true; break;
}
}
prev.release();
next.release();
}
sleep(1);
}
}
else
{
detLog("failed to open the buff file for reading. check permissions and/or opencv's video-io support (gstreamer/ffmpeg).", share);
detLog("capture open failure, check debug output.", share);
}
capture.release();
detLog("mo_detect() -- finished()", share);
return mod;
}

View File

@ -14,12 +14,10 @@
// GNU General Public License for more details.
#include "common.h"
#include "web.h"
#include "logger.h"
bool imgDiff(const Mat &prev, const Mat &next, shared_t *share);
bool imgDiff(Mat &prev, Mat &next, shared_t *share);
bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share);
void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share);
Mat frameFF(VideoCapture *cap, int gap);
void detectMoInStream(const string &bufPath, shared_t *share);
#endif // MO_DETECT_H

View File

@ -31,7 +31,7 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share)
htmlText += "<body>\n";
htmlText += "<h3>" + title + "</h3>\n";
if (!dirNames.empty())
if (!dirNames.empty() && !exists(outputDir + "/VIDEO_TS"))
{
htmlText += "<ul>\n";
@ -43,6 +43,11 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share)
htmlText += "</ul>\n";
}
if (exists(outputDir + "/VIDEO_TS"))
{
htmlText += "<h4>Motion Events</h4>\n";
}
for (auto &&regName : regNames)
{
if (regName.ends_with("_log.html"))
@ -51,8 +56,7 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share)
}
else if (regName.ends_with(".html") &&
!regName.ends_with("index.html") &&
!regName.ends_with("rec_log_lines.html") &&
!regName.ends_with("det_log_lines.html"))
!regName.ends_with("_log_lines.html"))
{
// regName.substr(0, regName.size() - 5) removes .html
auto name = regName.substr(0, regName.size() - 5);
@ -85,11 +89,8 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share)
file.close();
}
void genHTMLvid(const string &outputVid, shared_t *share)
void genHTMLvid(const string &name)
{
auto vidName = path(outputVid).filename().string();
auto filePath = path(outputVid).parent_path().string();
auto fileName = vidName.substr(0, vidName.size() - (share->vidExt.size() + 1));
string htmlText = "<!DOCTYPE html>\n";
htmlText += "<html>\n";
@ -102,12 +103,12 @@ void genHTMLvid(const string &outputVid, shared_t *share)
htmlText += "</head>\n";
htmlText += "<body>\n";
htmlText += "<video width=100% height=100% controls autoplay>\n";
htmlText += " <source src='" + vidName + "' type='video/" + share->vidExt + "'>\n";
htmlText += " <source src='" + name + ".m3u8' type='video/MP2T'>\n";
htmlText += "</video>\n";
htmlText += "</body>\n";
htmlText += "</html>";
ofstream file(string(filePath + "/" + fileName + ".html").c_str());
ofstream file(string(name + ".html").c_str());
file << htmlText << endl;

View File

@ -16,7 +16,7 @@
#include "common.h"
void genHTMLul(const string &outputDir, const string &title, shared_t *share);
void genHTMLvid(const string &outputVid, shared_t *share);
void genHTMLvid(const string &name);
void genCSS(shared_t *share);
#endif // WEB_H