JustMotion/src/common.cpp
Maurice ONeal b4ca30b0e1 v2.1.t2
the delay on the motion detection loop slowed it down too much to
the point that it falls too far behind live. I removed the delay
and re-introduced the frame gap so all frames in the video files
need to be decoded.

post command and event timers are now seperate but still tied to
a single thread so they can still be synced.

fixed an issued that cuased several thmubnails to not generate.

added more log lines the aid with debugging.
2023-04-20 14:52:59 -04:00

362 lines
8.4 KiB
C++

// 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"
string cleanDir(const string &path)
{
if (path[path.size() - 1] == '/')
{
return path.substr(0, path.size() - 1);
}
else
{
return path;
}
}
bool createDir(const string &dir)
{
auto ret = mkdir(dir.c_str(), 0777);
if (ret == -1)
{
return errno == EEXIST;
}
else
{
return true;
}
}
bool createDirTree(const string &full_path)
{
size_t pos = 0;
auto ret = true;
while (ret == true && pos != string::npos)
{
pos = full_path.find('/', pos + 1);
ret = createDir(full_path.substr(0, pos));
}
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;
if (exists(path))
{
for (auto &entry : directory_iterator(path))
{
if (entry.is_regular_file())
{
auto name = entry.path().filename().string();
if (ext.empty() || name.ends_with(ext))
{
names.push_back(name);
}
}
}
}
sort(names.begin(), names.end());
return names;
}
vector<string> lsDirsInDir(const string &path)
{
vector<string> names;
if (exists(path))
{
for (auto &entry : directory_iterator(path))
{
if (entry.is_directory())
{
names.push_back(entry.path().filename().string());
}
}
}
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);
}
}
}
void enforceMaxEvents(shared_t *share)
{
auto names = lsFilesInDir("events", ".mp4");
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");
if (exists(mp4File)) remove(mp4File);
if (exists(imgFile)) remove(imgFile);
if (exists(webFile)) remove(webFile);
names.erase(names.begin());
}
}
string genTimeStr(const char *fmt)
{
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()
{
return genTimeStr("%Y-%j-%H-%M");
}
void rdLine(const string &param, const string &line, string *value)
{
if (line.rfind(param.c_str(), 0) == 0)
{
*value = line.substr(param.size());
}
}
void rdLine(const string &param, const string &line, int *value)
{
if (line.rfind(param.c_str(), 0) == 0)
{
*value = strtol(line.substr(param.size()).c_str(), NULL, 10);
}
}
bool rdConf(const string &filePath, shared_t *share)
{
ifstream varFile(filePath.c_str());
if (!varFile.is_open())
{
share->retCode = ENOENT;
cerr << "err: config file: " << filePath << " does not exists or lack read permissions." << endl;
}
else
{
string line;
do
{
getline(varFile, line);
if (line.rfind("#", 0) != 0)
{
rdLine("cam_name = ", line, &share->camName);
rdLine("recording_stream = ", line, &share->recordUrl);
rdLine("web_root = ", line, &share->webRoot);
rdLine("web_text = ", line, &share->webTxt);
rdLine("web_bg = ", line, &share->webBg);
rdLine("web_font = ", line, &share->webFont);
rdLine("max_event_secs = ", line, &share->evMaxSecs);
rdLine("post_secs = ", line, &share->postSecs);
rdLine("post_cmd = ", line, &share->postCmd);
rdLine("pix_thresh = ", line, &share->pixThresh);
rdLine("img_thresh = ", line, &share->imgThresh);
rdLine("frame_gap = ", line, &share->frameGap);
rdLine("max_events = ", line, &share->maxEvents);
rdLine("max_log_size = ", line, &share->maxLogSize);
}
} while(!line.empty());
}
return share->retCode == 0;
}
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 = 5;
share->webRoot = "/var/www/html";
share->webBg = "#485564";
share->webTxt = "#dee5ee";
share->webFont = "courier";
if (rdConf(share->conf, share))
{
if (share->camName.empty())
{
share->camName = path(share->conf).filename();
}
share->outDir = cleanDir(share->webRoot) + "/" + share->camName;
error_code ec;
createDirTree(share->outDir);
current_path(share->outDir, ec);
share->retCode = ec.value();
if (share->retCode != 0)
{
cerr << "err: " << ec.message() << endl;
}
}
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();
}
}