Decided to switch using opencv's builtin pixel diff motion detection via
absdiff and thresh. Doing this should increase efficiency instead of
using the home brewed pixel loops and threads.

Added a web interface of sorts by having html files output along with
the video clips. These files are designed to link together with the
assumption that the output directory is a web root like /var/www/html
that apache2 uses. The interface is crude at best but at least allow
playback of recorded footage.

Added max_clips config variable that can limit the amount of motion
events that can recorded to storage on a single day.
This commit is contained in:
Maurice ONeal 2022-12-04 15:13:39 -05:00
parent 01a11741c7
commit 9816ba339f
9 changed files with 303 additions and 230 deletions

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 2.8.12) cmake_minimum_required(VERSION 2.8.12)
project( MotionWatch ) project( MotionWatch )
find_package( OpenCV REQUIRED ) find_package( OpenCV REQUIRED )
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -pthread") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -pthread")
include_directories( ${OpenCV_INCLUDE_DIRS} ) include_directories( ${OpenCV_INCLUDE_DIRS} )
add_executable( mow src/main.cpp src/common.cpp src/mo_detect.cpp ) add_executable( mow src/main.cpp src/common.cpp src/mo_detect.cpp src/web.cpp )
target_link_libraries( mow ${OpenCV_LIBS} ) target_link_libraries( mow ${OpenCV_LIBS} )

View File

@ -1,7 +1,11 @@
#!/bin/sh #!/bin/sh
apt update apt update -y
apt install -y cmake g++ wget unzip git ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev apt install -y cmake g++ wget unzip git ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev
add-apt-repository -y ppa:ubuntu-toolchain-r/test
apt update -y
apt install -y gcc-10 gcc-10-base gcc-10-doc g++-10
apt install -y libstdc++-10-dev libstdc++-10-doc
cd ./src cd ./src
if [ -d "./opencv" ] if [ -d "./opencv" ]
then then

View File

@ -57,10 +57,9 @@ bool fileExists(const string& name)
return access(name.c_str(), F_OK) != -1; return access(name.c_str(), F_OK) != -1;
} }
void replaceAll(string &str, const string &from, const string &to) string replaceAll(string str, const string &from, const string &to)
{ {
if(from.empty()) if (from.empty()) return str;
return;
size_t startPos = 0; size_t startPos = 0;
@ -70,6 +69,8 @@ void replaceAll(string &str, const string &from, const string &to)
startPos += to.length(); startPos += to.length();
} }
return str;
} }
vector<string> lsFilesInDir(const string &path, const string &ext) vector<string> lsFilesInDir(const string &path, const string &ext)
@ -81,12 +82,12 @@ vector<string> lsFilesInDir(const string &path, const string &ext)
if ((dir = opendir(path.c_str())) != NULL) if ((dir = opendir(path.c_str())) != NULL)
{ {
while ((ent = readdir(dir)) != NULL) while ((ent = readdir(dir)) != NULL)
{
if (ent->d_type & DT_REG)
{ {
auto name = string(ent->d_name); auto name = string(ent->d_name);
if ((name.size() >= 4) && (ent->d_type & DT_REG)) if (name.ends_with(ext.c_str()) || ext.empty())
{
if (name.substr(name.size() - 4) == ext)
{ {
names.push_back(name); names.push_back(name);
} }
@ -101,19 +102,54 @@ vector<string> lsFilesInDir(const string &path, const string &ext)
return names; return names;
} }
void enforceMaxDays(shared_t *share) vector<string> lsDirsInDir(const string &path)
{ {
auto names = lsFilesInDir(share->outDir, ".m3u"); DIR *dir;
struct dirent *ent;
vector<string> names;
while (names.size() > share->maxDays) if ((dir = opendir(path.c_str())) != NULL)
{ {
auto name = names[0]; while ((ent = readdir(dir)) != NULL)
auto plsFile = cleanDir(share->outDir) + "/" + name; {
auto vidFold = cleanDir(share->outDir) + "/." + name.substr(0, name.size() - 4); if (ent->d_type & DT_DIR)
{
names.push_back(string(ent->d_name));
}
}
remove(plsFile.c_str()); closedir(dir);
remove_all(vidFold.c_str()); }
sort(names.begin(), names.end());
return names;
}
void enforceMaxDays(const string &dirPath, shared_t *share)
{
auto names = lsDirsInDir(dirPath);
while (names.size() > (share->maxDays - 1))
{
remove_all(string(cleanDir(dirPath) + "/" + names[0]).c_str());
names.erase(names.begin());
}
}
void enforceMaxClips(const string &dirPath, shared_t *share)
{
auto names = lsDirsInDir(dirPath);
while ((names.size() * 3) > ((share->maxClips - 1) * 3))
{
remove(string(cleanDir(dirPath) + "/" + names[0]).c_str());
remove(string(cleanDir(dirPath) + "/" + names[1]).c_str());
remove(string(cleanDir(dirPath) + "/" + names[2]).c_str());
names.erase(names.begin());
names.erase(names.begin());
names.erase(names.begin()); names.erase(names.begin());
} }
} }
@ -140,22 +176,11 @@ string genDstFile(const string &dirOut, const char *fmt, const string &ext)
return cleanDir(dirOut) + string("/") + genTimeStr(fmt) + ext; return cleanDir(dirOut) + string("/") + genTimeStr(fmt) + ext;
} }
Mat toGray(const Mat &src)
{
Mat ret;
cvtColor(src, ret, COLOR_BGR2GRAY);
return ret;
}
void rdLine(const string &param, const string &line, string *value) 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 = line.substr(param.size()); *value = line.substr(param.size());
//cout << param << *value << endl;
} }
} }
@ -164,8 +189,6 @@ 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(line.substr(param.size()).c_str(), NULL, 10); *value = strtol(line.substr(param.size()).c_str(), NULL, 10);
//cout << param << *value << endl;
} }
} }
@ -184,16 +207,17 @@ bool rdConf(shared_t *share)
string line; string line;
share->recordUrl.clear(); share->recordUrl.clear();
share->outDir.clear();
share->postCmd.clear(); share->postCmd.clear();
share->buffDir.clear(); share->buffDir.clear();
share->consecThresh = 512; share->frameGap = 10;
share->pixThresh = 30;
share->imgThresh = 512;
share->secs = 60; share->secs = 60;
share->blockX = 32; share->maxDays = 15;
share->blockY = 32; share->maxClips = 30;
share->blockThresh = 1024; share->camName = path(share->conf.c_str()).filename();
share->maxDays = 5; share->outDir = "/var/www/hmtl";
share->vidExt = "mp4"; share->vidExt = "mp4";
share->recLoopWait = false; share->recLoopWait = false;
share->skipCmd = false; share->skipCmd = false;
@ -204,26 +228,23 @@ bool rdConf(shared_t *share)
if (line.rfind("#", 0) != 0) if (line.rfind("#", 0) != 0)
{ {
rdLine("cam_name = ", line, &share->camName);
rdLine("recording_stream = ", line, &share->recordUrl); rdLine("recording_stream = ", line, &share->recordUrl);
rdLine("output_dir = ", line, &share->outDir); rdLine("output_dir = ", line, &share->outDir);
rdLine("post_cmd = ", line, &share->postCmd); rdLine("post_cmd = ", line, &share->postCmd);
rdLine("consec_threshold = ", line, &share->consecThresh);
rdLine("duration = ", line, &share->secs); rdLine("duration = ", line, &share->secs);
rdLine("buff_dir = ", line, &share->buffDir); rdLine("buff_dir = ", line, &share->buffDir);
rdLine("block_x = ", line, &share->blockX); rdLine("frame_gap = ", line, &share->frameGap);
rdLine("block_y = ", line, &share->blockY); rdLine("pix_thresh = ", line, &share->pixThresh);
rdLine("block_threshold = ", line, &share->blockThresh); rdLine("img_thresh = ", line, &share->imgThresh);
rdLine("max_days = ", line, &share->maxDays); rdLine("max_days = ", line, &share->maxDays);
rdLine("max_clips = ", line, &share->maxClips);
rdLine("vid_container = ", line, &share->vidExt); rdLine("vid_container = ", line, &share->vidExt);
} }
} while(!line.empty()); } while(!line.empty());
// it's imperative that blockX/Y are not zero or it will cause share->outDir = cleanDir(share->outDir) + "/" + share->camName;
// an infinte loop. if bad data is read from the conf, default
// values will be used.
if (share->blockX == 0) share->blockX = 32;
if (share->blockY == 0) share->blockY = 32;
if (share->init) if (share->init)
{ {
@ -232,10 +253,7 @@ bool rdConf(shared_t *share)
share->init = false; share->init = false;
} }
new thread(enforceMaxDays, share);
createDirTree(cleanDir(share->buffDir)); createDirTree(cleanDir(share->buffDir));
system(string("touch " + cleanDir(share->buffDir) + "/stat").c_str());
share->retCode = 0; share->retCode = 0;
} }
@ -245,40 +263,6 @@ bool rdConf(shared_t *share)
return share->retCode == 0; return share->retCode == 0;
} }
bool capPair(Mat &prev, Mat &next, VideoCapture &capture, shared_t *share)
{
capture >> prev;
capture >> next;
return !prev.empty() && !next.empty();
}
void wrOut(const string &buffFile, shared_t *share)
{
auto clnDir = cleanDir(share->outDir);
auto vidOut = genDstFile(clnDir + "/." + genTimeStr("%Y-%m-%d"), "%H%M%S", "." + share->vidExt);
auto m3uOut = vidOut.substr(clnDir.size() + 1);
auto lisOut = genDstFile(share->outDir, "%Y-%m-%d", ".m3u");
copy_file(buffFile.c_str(), vidOut.c_str());
remove(buffFile.c_str());
ofstream file;
if (fileExists(lisOut))
{
file.open(lisOut.c_str(), ios_base::app);
}
else
{
file.open(lisOut.c_str());
}
file << m3uOut << endl;
file.close();
}
string parseForParam(const string &arg, int argc, char** argv, bool argOnly) string parseForParam(const string &arg, int argc, char** argv, bool argOnly)
{ {
for (int i = 0; i < argc; ++i) for (int i = 0; i < argc; ++i)
@ -304,14 +288,3 @@ string parseForParam(const string &arg, int argc, char** argv, bool argOnly)
return string(); return string();
} }
void statOut(shared_t *share)
{
system(string("touch " + cleanDir(share->buffDir) + "/stat").c_str());
auto path = string(cleanDir(share->buffDir) + "/stat");
auto fd = open(path.c_str(), fstream::out | fstream::trunc);
write(fd, share->stat.c_str(), share->stat.size() + 1);
close(fd);
}

View File

@ -36,27 +36,27 @@ using namespace cv;
using namespace std; using namespace std;
using namespace std::filesystem; using namespace std::filesystem;
#define BUF_SZ 10 #define APP_VER "1.5.t1"
#define APP_VER "1.5" #define APP_NAME "Motion Watch"
struct shared_t struct shared_t
{ {
string stat;
string recordUrl; string recordUrl;
string outDir; string outDir;
string postCmd; string postCmd;
string conf; string conf;
string buffDir; string buffDir;
string vidExt; string vidExt;
string camName;
bool init; bool init;
bool recLoopWait; bool recLoopWait;
bool skipCmd; bool skipCmd;
int consecThresh; int frameGap;
int pixThresh;
int imgThresh;
int secs; int secs;
int blockThresh;
int blockX;
int blockY;
int maxDays; int maxDays;
int maxClips;
int retCode; int retCode;
}; };
@ -64,18 +64,17 @@ string genDstFile(const string &dirOut, const char *fmt, const string &e
string genTimeStr(const char *fmt); string genTimeStr(const char *fmt);
string cleanDir(const string &path); string cleanDir(const string &path);
string parseForParam(const string &arg, int argc, char** argv, bool argOnly); string parseForParam(const string &arg, int argc, char** argv, bool argOnly);
string replaceAll(string str, const string &from, const string &to);
bool createDir(const string &dir); bool createDir(const string &dir);
bool createDirTree(const string &full_path); bool createDirTree(const string &full_path);
bool fileExists(const string& name); bool fileExists(const string& name);
void wrOut(const string &buffFile, shared_t *share); void enforceMaxDays(const string &dirPath, shared_t *share);
void replaceAll(string &str, const string &from, const string &to); void enforceMaxClips(const string &dirPath, shared_t *share);
void enforceMaxDays(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 statOut(shared_t *share);
bool rdConf(shared_t *share); bool rdConf(shared_t *share);
bool capPair(Mat &prev, Mat &next, VideoCapture &capture, shared_t *share); vector<string> lsFilesInDir(const string &path, const string &ext = string());
Mat toGray(const Mat &src); vector<string> lsDirsInDir(const string &path);
vector<string> lsFilesInDir(const string &path, const string &ext);
#endif // COMMON_H #endif // COMMON_H

View File

@ -16,40 +16,54 @@ void detectLoop(shared_t *share)
{ {
vector<string> bufFiles; vector<string> bufFiles;
while (!bufFiles.empty() || !share->recLoopWait) do
{ {
bufFiles = lsFilesInDir(share->buffDir, "." + share->vidExt); bufFiles = lsFilesInDir(share->buffDir, "." + share->vidExt);
// this loop will not process the last buffile while recLoop is still actively
// pulling footage from ffmpeg. it is assumed the last file is not finished.
// share->recLoopWait is used to detect if the recloop is still pulling. if not
// then the last file is finally processed.
if ((bufFiles.size() >= 2) || (share->recLoopWait && !bufFiles.empty())) if ((bufFiles.size() >= 2) || (share->recLoopWait && !bufFiles.empty()))
{ {
auto fullPath = cleanDir(share->buffDir) + "/" + bufFiles[0]; auto fullPath = cleanDir(share->buffDir) + "/" + bufFiles[0];
share->stat.clear(); Mat thumbNail;
if (moDetect(fullPath, share)) if (moDetect(fullPath, thumbNail, share))
{ {
share->skipCmd = true; share->skipCmd = true;
wrOut(fullPath, share); wrOut(fullPath, thumbNail, share);
} }
else else
{ {
remove(fullPath.c_str()); remove(fullPath.c_str());
} }
statOut(share);
} }
else else
{ {
sleep(1); sleep(1);
} }
} }
while (!bufFiles.empty());
} }
void recLoop(shared_t *share) void recLoop(shared_t *share)
{ {
while (rdConf(share)) while (rdConf(share))
{ {
if (!fileExists("/tmp/mow-lock"))
{
system("touch /tmp/mow-lock");
auto webRoot = path(share->outDir).parent_path().string();
genHTMLul(webRoot, string(APP_NAME) + " " + string(APP_VER));
system("rm /tmp/mow-lock");
}
createDirTree(share->buffDir); createDirTree(share->buffDir);
auto bufPath = cleanDir(share->buffDir) + "/%03d." + share->vidExt; auto bufPath = cleanDir(share->buffDir) + "/%03d." + share->vidExt;
@ -59,11 +73,19 @@ void recLoop(shared_t *share)
thread th2(detectLoop, share); thread th2(detectLoop, share);
system(cmd.c_str()); if (system(cmd.c_str()) != 0)
{
share->recLoopWait = true; share->recLoopWait = true;
th2.join(); th2.join();
}
else
{
// simulate that ffmpeg is running even after it has failed.
sleep(share->secs);
share->recLoopWait = true;
}
if (!share->skipCmd) if (!share->skipCmd)
{ {
@ -86,7 +108,7 @@ int main(int argc, char** argv)
cout << "-c : path to the config file." << endl; cout << "-c : path to the config file." << endl;
cout << "-v : display the current version." << endl; cout << "-v : display the current version." << endl;
} }
if (parseForParam("-v", argc, argv, true) == "true") else if (parseForParam("-v", argc, argv, true) == "true")
{ {
cout << APP_VER << endl; cout << APP_VER << endl;
} }
@ -101,9 +123,7 @@ int main(int argc, char** argv)
sharedRes.skipCmd = false; sharedRes.skipCmd = false;
sharedRes.init = true; sharedRes.init = true;
thread th1(recLoop, &sharedRes); recLoop(&sharedRes);
th1.join();
return sharedRes.retCode; return sharedRes.retCode;
} }

View File

@ -12,101 +12,64 @@
#include "mo_detect.h" #include "mo_detect.h"
bool pixDiff(const uchar &pixA, const uchar &pixB, shared_t *share)
{
if (pixA > pixB) return true;
if (pixB > pixA) return true;
return false;
}
void secDiff(const Mat &imgA, const Mat &imgB, int id, int rows, int cols, int rowOffs, int colOffs, vector<sec_t> *results, mutex *secMutex, shared_t *share)
{
auto diff = 0;
auto pnts = 0;
for (auto y = rowOffs; y < (rowOffs + rows); y++)
{
for (auto x = colOffs; x < (colOffs + cols); x++)
{
auto pixA = imgA.at<uchar>(Point(x, y));
auto pixB = imgB.at<uchar>(Point(x, y));
if (pixDiff(pixA, pixB, share)) pnts += 1;
else pnts = 0;
if (pnts >= share->consecThresh)
{
diff += 1;
}
}
}
struct sec_t res;
res.x = colOffs;
res.y = rowOffs;
res.xSize = cols;
res.ySize = rows;
res.pixDiff = diff;
res.id = id;
lock_guard<mutex> guard(*secMutex);
results->push_back(res);
}
bool imgDiff(const Mat &prev, const Mat &next, shared_t *share) bool imgDiff(const Mat &prev, const Mat &next, shared_t *share)
{ {
vector<thread> threads; Mat diff;
vector<sec_t> results;
mutex secMutex;
auto id = 0; absdiff(prev, next, diff);
threshold(diff, diff, share->pixThresh, 255, THRESH_BINARY);
for (auto x = 0; x < prev.cols; x += share->blockX) return countNonZero(diff) >= share->imgThresh;
}
Mat frameFF(VideoCapture *cap, int gap)
{ {
// spawn all of the block motion detection threads. Mat ret;
for (auto y = 0; y < prev.rows; y += share->blockY, id += 1)
if (gap == 0) gap = 1;
for (int i = 0; i < gap; ++i)
{ {
threads.push_back(thread(secDiff, prev, next, id, share->blockY, share->blockX, y, x, &results, &secMutex, share)); cap->grab();
}
} }
for (auto &&thr : threads) cap->retrieve(ret);
if (!ret.empty())
{ {
// wait for all of the threads to finish. cvtColor(ret, ret, COLOR_BGR2GRAY);
thr.join();
} }
auto maxPixDiff = 0; return ret;
auto blockPick = 0; }
for (auto i = 0; i < results.size(); ++i) void wrOut(const string &buffFile, const Mat &vidThumb, shared_t *share)
{ {
// out of all of the results returned form the threads, pick auto dayStr = genTimeStr("%Y-%m-%d");
// the block with the highest amount of pixDiff. auto timStr = genTimeStr("%H%M%S");
auto x = results[i].x; auto outDir = cleanDir(share->outDir) + "/" + dayStr;
auto y = results[i].y;
auto diff = results[i].pixDiff;
auto id = results[i].id;
if (diff > 0) if (!exists(outDir))
{ {
share->stat += string("block_thread:") + " id=" + to_string(id) + " diff=" + to_string(diff) + "\n"; enforceMaxDays(share->outDir, share);
} }
if ((results[i].pixDiff >= share->blockThresh) && (results[i].pixDiff > maxPixDiff)) auto vidOut = genDstFile(outDir, timStr.c_str(), "." + share->vidExt);
{ auto imgOut = genDstFile(outDir, timStr.c_str(), ".jpg");
maxPixDiff = results[i].pixDiff;
blockPick = i; 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);
genHTMLul(share->outDir, share->camName);
} }
return maxPixDiff >= share->blockThresh; bool moDetect(const string &buffFile, Mat &vidThumb, shared_t *share)
}
bool moDetect(const string &buffFile, shared_t *share)
{ {
auto mod = false; auto mod = false;
@ -117,13 +80,21 @@ bool moDetect(const string &buffFile, shared_t *share)
Mat prev; Mat prev;
Mat next; Mat next;
while (capPair(prev, next, capture, share)) do
{ {
if (imgDiff(toGray(prev), toGray(next), share)) if (prev.empty()) prev = frameFF(&capture, 1);
else prev = next.clone();
next = frameFF(&capture, share->frameGap);
if (imgDiff(prev, next, share))
{ {
resize(next, vidThumb, Size(360, 202), INTER_LINEAR);
mod = true; break; mod = true; break;
} }
} }
while (!prev.empty() && !next.empty());
} }
else else
{ {

View File

@ -14,20 +14,11 @@
// GNU General Public License for more details. // GNU General Public License for more details.
#include "common.h" #include "common.h"
#include "web.h"
struct sec_t
{
int id;
int x;
int y;
int xSize;
int ySize;
int pixDiff;
};
void secDiff(const Mat &imgA, const Mat &imgB, int id, int rows, int cols, int rowOffs, int colOffs, vector<sec_t> *results, mutex *secMutex, shared_t *share);
bool pixDiff(const uchar &pixA, const uchar &pixB, shared_t *share);
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, 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);
#endif // MO_DETECT_H #endif // MO_DETECT_H

94
src/web.cpp Normal file
View File

@ -0,0 +1,94 @@
// 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 "web.h"
void genHTMLul(const string &outputDir, const string &title)
{
DIR *dir;
struct dirent *ent;
vector<string> regNames;
vector<string> dirNames;
string htmlText = "<!DOCTYPE html>\n";
htmlText += "<html>\n";
htmlText += "<body>\n";
htmlText += "<h2>" + title + "</h2>\n";
htmlText += "<ul>\n";
if ((dir = opendir(outputDir.c_str())) != NULL)
{
while ((ent = readdir(dir)) != NULL)
{
if (ent->d_type & DT_REG)
{
regNames.push_back(string(ent->d_name));
}
else if (ent->d_type & DT_DIR)
{
dirNames.push_back(string(ent->d_name));
}
}
closedir(dir);
}
sort(regNames.begin(), regNames.end());
sort(dirNames.begin(), dirNames.end());
for (auto dirName : dirNames)
{
htmlText += " <li><a href='" + dirName + "'>" + dirName + "</a></li>\n";
}
for (auto regName : regNames)
{
if (regName.ends_with(".html") && !regName.ends_with("index.html"))
{
htmlText += " <li><a href='" + regName + "'> <img src='" + replaceAll(regName, ".html", ".jpg") + "' </a></li>\n";
}
}
htmlText += "</ul>\n";
htmlText += "</body>\n";
htmlText += "</html>";
ofstream file(string(cleanDir(outputDir) + "/index.html").c_str());
file << htmlText << endl;
file.close();
}
void genHTMLvid(const string &outputVid, shared_t *share)
{
auto filename = path(outputVid).filename().string();
auto filePath = path(outputVid).parent_path().string();
string htmlText = "<!DOCTYPE html>\n";
filename = replaceAll(filename, share->vidExt, "html");
htmlText += "<html>\n";
htmlText += "<body>\n";
htmlText += "<video width='420' height='320' controls autoplay>\n";
htmlText += " <source src='" + filename + "' type='video/" + share->vidExt + "'>\n";
htmlText += "</video>\n";
htmlText += "</body>\n";
htmlText += "</html>";
ofstream file(string(cleanDir(filePath) + "/" + filename + ".html").c_str());
file << htmlText << endl;
file.close();
}

21
src/web.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef WEB_H
#define WEB_H
// 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"
void genHTMLul(const string &outputDir, const string &title);
void genHTMLvid(const string &outputVid, shared_t *share);
#endif // WEB_H