diff --git a/setup.sh b/setup.sh
index 7b1f528..34579af 100644
--- a/setup.sh
+++ b/setup.sh
@@ -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
diff --git a/src/common.cpp b/src/common.cpp
index 9f51039..5057722 100644
--- a/src/common.cpp
+++ b/src/common.cpp
@@ -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 lsFilesInDir(const string &path, const string &ext)
{
vector names;
@@ -97,30 +119,32 @@ vector 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
- {
- cerr << "err: none of the expected config files could be read." << endl;
+ current_path(share->outDir, ec);
+
+ share->retCode = ec.value();
+
+ if (share->retCode != 0)
+ {
+ 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 parseForList(const string &arg, int argc, char** argv)
+string genEventPath(const string &tsPath)
{
- auto offs = 0;
- auto ret = vector();
- string param;
-
- do
+ if (tsPath.size() > 14)
{
- param = parseForParam(arg, argc, argv, false, offs);
+ // removes 'VIDEO_TS/live/' from the front of the string.
+ auto ret = tsPath.substr(14);
- if (!param.empty())
+ 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)
{
- ret.push_back(param);
+ // remove all '/'
+ ret.erase(ind, 1);
+
+ ind = ret.find('/');
}
+
+ return ret;
}
- while (!param.empty());
-
- return ret;
-}
-
-void waitForDetThreads(shared_t *share)
-{
- for (auto &&thr : share->detThreads)
+ else
{
- thr.join();
+ return string();
+ }
+}
+
+uint64_t genEpoch()
+{
+ return duration_cast(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();
}
-
- share->detThreads.clear();
}
diff --git a/src/common.h b/src/common.h
index f882d79..bea0239 100644
--- a/src/common.h
+++ b/src/common.h
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -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 detThreads;
- vector conf;
- ofstream recLogFile;
- ofstream detLogFile;
- string recLogPath;
- string detLogPath;
- 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;
- int frameGap;
- int pixThresh;
- int imgThresh;
- int numOfClips;
- int maxDays;
- int maxClips;
- int maxLogSize;
- int retCode;
+ vector recList;
+ ofstream recLogFile;
+ ofstream detLogFile;
+ ofstream upkLogFile;
+ string conf;
+ string recLogPath;
+ string detLogPath;
+ string upkLogPath;
+ string recordUrl;
+ string outDir;
+ string postCmd;
+ string vidCodec;
+ string camName;
+ string webBg;
+ string webTxt;
+ string webFont;
+ string webRoot;
+ bool skipCmd;
+ bool postCmdRunning;
+ int schSec;
+ int frameGap;
+ int pixThresh;
+ int imgThresh;
+ int maxDays;
+ 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 ¶m, const string &line, string *value);
void rdLine(const string ¶m, 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 parseForList(const string &arg, int argc, char** argv);
vector lsFilesInDir(const string &path, const string &ext = string());
vector lsDirsInDir(const string &path);
+uint64_t genEpoch();
#endif // COMMON_H
diff --git a/src/logger.cpp b/src/logger.cpp
index 7ba30e3..1bbbbd8 100644
--- a/src/logger.cpp
+++ b/src/logger.cpp
@@ -22,6 +22,11 @@ void detLog(const string &line, shared_t *share)
share->detLogFile << genTimeStr("[%Y-%m-%d-%H-%M-%S] ") << line << "
" << endl;
}
+void upkLog(const string &line, shared_t *share)
+{
+ share->upkLogFile << genTimeStr("[%Y-%m-%d-%H-%M-%S] ") << line << "
" << endl;
+}
+
void enforceMaxLogSize(const string &filePath, shared_t *share)
{
if (exists(filePath))
@@ -48,61 +53,66 @@ void initLogFile(const string &filePath, ofstream &fileObj)
void initLogFrontPage(const string &filePath, const string &logLinesFile)
{
- string htmlText = "\n";
+ if (!exists(filePath))
+ {
+ string htmlText = "\n";
- htmlText += "\n";
- htmlText += "\n";
- htmlText += "\n";
- htmlText += "\n";
- htmlText += "\n";
- htmlText += "\n";
- htmlText += "\n";
- htmlText += "\n";
- htmlText += "\n";
- htmlText += "\n";
- htmlText += "
\n";
- htmlText += "\n";
- htmlText += "
\n";
- htmlText += "\n";
- htmlText += "\n";
+ htmlText += "\n";
+ htmlText += "\n";
+ htmlText += "\n";
+ htmlText += "\n";
+ htmlText += "\n";
+ htmlText += "\n";
+ htmlText += "\n";
+ htmlText += "\n";
+ htmlText += "\n";
+ htmlText += "\n";
+ htmlText += "
\n";
+ htmlText += "\n";
+ htmlText += "\n";
+ htmlText += "\n";
+ htmlText += "\n";
- ofstream outFile(filePath);
+ ofstream outFile(filePath);
- outFile << htmlText;
+ outFile << htmlText;
- outFile.close();
+ 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());
}
diff --git a/src/logger.h b/src/logger.h
index 29f9074..3c81557 100644
--- a/src/logger.h
+++ b/src/logger.h
@@ -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);
diff --git a/src/main.cpp b/src/main.cpp
index 8aec3d4..4885df7 100755
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -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 recLoop(shared_t *share)
+void eventLoop(shared_t *share)
{
- while (rdConf(share))
+ while (share->retCode == 0)
{
- recLog("rec_loop() -- start", share);
+ 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);
+ }
+
+ 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,76 +117,44 @@ 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);
+void recLoop(shared_t *share)
+{
+ 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";
- for (auto i = 0; i < share->numOfClips; ++i)
+ recLog("ffmpeg_run: " + cmd, share);
+
+ auto retCode = system(cmd.c_str());
+
+ recLog("ffmpeg_retcode: " + to_string(retCode), share);
+
+ if (retCode != 0)
{
- 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;
-
- recLog("ffmpeg_run: " + cmd, share);
-
- auto retCode = system(cmd.c_str());
-
- recLog("ffmpeg_retcode: " + to_string(retCode), share);
-
- 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);
- }
-
- sleep(share->clipLen);
- }
+ recLog("err: ffmpeg returned non zero, indicating failure. please check stderr output.", share);
}
- 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 " << 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")
{
@@ -156,11 +182,28 @@ int main(int argc, char** argv)
}
else
{
- sharedRes.retCode = 0;
- sharedRes.skipCmd = false;
- sharedRes.init = true;
+ sharedRes.retCode = 0;
+ sharedRes.skipCmd = false;
+ 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;
}
diff --git a/src/mo_detect.cpp b/src/mo_detect.cpp
index 48a8ab1..4a6dbe1 100644
--- a/src/mo_detect.cpp
+++ b/src/mo_detect.cpp
@@ -12,131 +12,107 @@
#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);
+ Mat diff;
- if (!prev.empty() && !next.empty())
- {
- Mat diff;
+ absdiff(prev, next, diff);
+ threshold(diff, diff, share->pixThresh, 255, THRESH_BINARY);
- absdiff(prev, next, diff);
- threshold(diff, diff, share->pixThresh, 255, THRESH_BINARY);
+ auto diffScore = countNonZero(diff);
- auto diffScore = countNonZero(diff);
+ detLog("diff_score: " + to_string(diffScore), 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 (imgDiff(prev, next, share))
+ if (prev.empty())
{
- resize(next, vidThumb, Size(720, 480), INTER_LINEAR);
-
- mod = true; break;
+ capture.retrieve(prev);
}
+ else
+ {
+ capture.retrieve(next);
+
+ if (!share->postCmdRunning)
+ {
+ if (imgDiff(prev, next, share))
+ {
+ resize(next, vidThumb, Size(720, 480), INTER_LINEAR);
+
+ 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;
}
diff --git a/src/mo_detect.h b/src/mo_detect.h
index 894573c..fc0c823 100644
--- a/src/mo_detect.h
+++ b/src/mo_detect.h
@@ -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
diff --git a/src/web.cpp b/src/web.cpp
index ad64476..6154f12 100644
--- a/src/web.cpp
+++ b/src/web.cpp
@@ -31,7 +31,7 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share)
htmlText += "\n";
htmlText += "" + title + "
\n";
- if (!dirNames.empty())
+ if (!dirNames.empty() && !exists(outputDir + "/VIDEO_TS"))
{
htmlText += "\n";
@@ -43,6 +43,11 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share)
htmlText += "
\n";
}
+ if (exists(outputDir + "/VIDEO_TS"))
+ {
+ htmlText += "Motion Events
\n";
+ }
+
for (auto &®Name : 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 = "\n";
htmlText += "\n";
@@ -102,12 +103,12 @@ void genHTMLvid(const string &outputVid, shared_t *share)
htmlText += "\n";
htmlText += "\n";
htmlText += "\n";
htmlText += "\n";
htmlText += "";
- ofstream file(string(filePath + "/" + fileName + ".html").c_str());
+ ofstream file(string(name + ".html").c_str());
file << htmlText << endl;
diff --git a/src/web.h b/src/web.h
index 7e9575b..b716fc0 100644
--- a/src/web.h
+++ b/src/web.h
@@ -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