-removed the web interface. this project will instead continue to
 focus on backend operations. external applications can interface
 with the buf/rec directories to provide frontend operations.

-removed the magick binary file from the project. magick will
 instead be built from source on the target machine for maximum
 support for the target architecture.

-stream codec and format are now user configurable.

-recording thumbnail and video formats are now user configurable.
This commit is contained in:
Zii 2023-10-08 10:09:15 -04:00
parent b09ff1a19a
commit 41ccf1d1e7
15 changed files with 140 additions and 397 deletions

3
.gitignore vendored
View File

@ -57,5 +57,4 @@ compile_commands.json
# Build folders # Build folders
/.build-mow /.build-mow
/.build-opencv /.build-imagemagick
/src/opencv

View File

@ -20,8 +20,6 @@ add_executable(mow
src/main.cpp src/main.cpp
src/common.h src/common.h
src/common.cpp src/common.cpp
src/web.h
src/web.cpp
src/logger.h src/logger.h
src/logger.cpp src/logger.cpp
src/camera.h src/camera.h

File diff suppressed because one or more lines are too long

Binary file not shown.

13
imgmagick_build.sh Normal file
View File

@ -0,0 +1,13 @@
#!/bin/sh
export DEBIAN_FRONTEND=noninteractive
if ! command -v magick &> /dev/null
then
apt install -y git
git clone https://github.com/ImageMagick/ImageMagick.git .build-imagemagick
cd .build-imagemagick
./configure
make
make install
ldconfig /usr/local/lib
fi

View File

@ -19,26 +19,11 @@ if [ ! -d "/var/opt/mow/buf" ]; then
mkdir /var/opt/mow/buf mkdir /var/opt/mow/buf
fi fi
if [ ! -d "/var/opt/mow/web" ]; then if [ ! -d "/var/opt/mow/rec" ]; then
mkdir /var/opt/mow/web mkdir /var/opt/mow/rec
fi fi
if [ -e "/var/opt/mow/web/index.html" ]; then
rm -v /var/opt/mow/web/index.html
fi
if [ -e "/var/opt/mow/web/theme.css" ]; then
rm -v /var/opt/mow/web/theme.css
fi
touch /var/opt/mow/buf/index.html
touch /var/opt/mow/buf/theme.css
ln -sv /var/opt/mow/buf/index.html /var/opt/mow/web/index.html
ln -sv /var/opt/mow/buf/theme.css /var/opt/mow/web/theme.css
cp -v ./.build-mow/mow /opt/mow/bin cp -v ./.build-mow/mow /opt/mow/bin
cp -v ./bin/hls.js /var/opt/mow/web/hls.js
echo "writing /opt/mow/run" echo "writing /opt/mow/run"
printf "#!/bin/sh\n" > /opt/mow/run printf "#!/bin/sh\n" > /opt/mow/run
@ -63,3 +48,5 @@ chmod -v +x /opt/mow/bin
chmod -v +x /opt/mow/uninst chmod -v +x /opt/mow/uninst
ln -sv /opt/mow/run /usr/bin/mow ln -sv /opt/mow/run /usr/bin/mow
sh imgmagick_build.sh

View File

@ -3,5 +3,4 @@ export DEBIAN_FRONTEND=noninteractive
apt update -y apt update -y
apt install -y pkg-config cmake make g++ apt install -y pkg-config cmake make g++
apt install -y ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev x264 libx264-dev libilmbase-dev qt6-base-dev qtchooser qmake6 qt6-base-dev-tools libxkbcommon-dev libfuse-dev fuse3 apt install -y ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev x264 libx264-dev libilmbase-dev qt6-base-dev qtchooser qmake6 qt6-base-dev-tools libxkbcommon-dev libfuse-dev fuse3
cp ./bin/magick /usr/bin/magick sh imgmagick_build.sh
chmod +x /usr/bin/magick

View File

@ -16,37 +16,28 @@ Camera::Camera(QObject *parent) : QObject(parent) {}
void Camera::cleanup() void Camera::cleanup()
{ {
QProcess::execute("rm", {shared.outDir + "/live"}); if (QFileInfo::exists(shared.tmpDir + "/rec"))
QProcess::execute("rm", {shared.outDir + "/logs"}); {
QProcess::execute("rm", {shared.outDir + "/img"}); QProcess::execute("rm", {shared.tmpDir + "/rec"});
QProcess::execute("rm", {shared.outDir + "/index.html"}); }
QProcess::execute("rm", {shared.outDir + "/stream.m3u8"});
QProcess::execute("rm", {shared.tmpDir + "/events"});
} }
int Camera::start(const QStringList &args) int Camera::start(const QStringList &args)
{ {
if (rdConf(getParam("-c", args), &shared)) if (rdConf(getParam("-c", args), &shared))
{ {
QDir().mkpath(shared.outDir); QProcess::execute("mkdir", {"-p", shared.outDir});
QDir().mkpath(shared.tmpDir); QProcess::execute("mkdir", {"-p", shared.tmpDir});
QDir().mkpath(shared.outDir + "/events"); QProcess::execute("mkdir", {"-p", shared.tmpDir + "/live"});
QDir().mkpath(shared.tmpDir + "/live"); QProcess::execute("mkdir", {"-p", shared.tmpDir + "/logs"});
QDir().mkpath(shared.tmpDir + "/logs"); QProcess::execute("mkdir", {"-p", shared.tmpDir + "/img"});
QDir().mkpath(shared.tmpDir + "/img");
cleanup(); cleanup();
touch(shared.tmpDir + "/index.html");
touch(shared.tmpDir + "/stream.m3u8"); touch(shared.tmpDir + "/stream.m3u8");
QProcess::execute("ln", {"-s", shared.tmpDir + "/live", shared.outDir + "/live"}); QProcess::execute("ln", {"-s", shared.outDir, shared.tmpDir + "/rec"});
QProcess::execute("ln", {"-s", shared.tmpDir + "/logs", shared.outDir + "/logs"});
QProcess::execute("ln", {"-s", shared.tmpDir + "/img", shared.outDir + "/img"});
QProcess::execute("ln", {"-s", shared.tmpDir + "/index.html", shared.outDir + "/index.html"});
QProcess::execute("ln", {"-s", shared.tmpDir + "/stream.m3u8", shared.outDir + "/stream.m3u8"});
QProcess::execute("ln", {"-s", shared.outDir + "/events", shared.tmpDir + "/events"});
if (!QDir::setCurrent(shared.tmpDir)) if (!QDir::setCurrent(shared.tmpDir))
{ {
@ -141,12 +132,12 @@ void RecLoop::updateCmd()
QStringList imgArgs; QStringList imgArgs;
recArgs << "-hide_banner"; recArgs << "-hide_banner";
recArgs << "-i" << shared->recordUrl; recArgs << "-i" << shared->recordUri;
recArgs << "-strftime" << "1"; recArgs << "-strftime" << "1";
recArgs << "-strftime_mkdir" << "1"; recArgs << "-strftime_mkdir" << "1";
recArgs << "-hls_segment_filename" << "live/" + QString(STRFTIME_FMT) + ".ts"; recArgs << "-hls_segment_filename" << "live/" + QString(STRFTIME_FMT) + shared->streamExt;
recArgs << "-y"; recArgs << "-y";
recArgs << "-vcodec" << "copy"; recArgs << "-vcodec" << shared->streamCodec;
recArgs << "-f" << "hls"; recArgs << "-f" << "hls";
recArgs << "-hls_time" << "2"; recArgs << "-hls_time" << "2";
recArgs << "-hls_list_size" << "1000"; recArgs << "-hls_list_size" << "1000";
@ -156,7 +147,7 @@ void RecLoop::updateCmd()
recArgs << "stream.m3u8"; recArgs << "stream.m3u8";
imgArgs << "-hide_banner"; imgArgs << "-hide_banner";
imgArgs << "-i" << shared->recordUrl; imgArgs << "-i" << shared->recordUri;
imgArgs << "-strftime" << "1"; imgArgs << "-strftime" << "1";
imgArgs << "-strftime_mkdir" << "1"; imgArgs << "-strftime_mkdir" << "1";
imgArgs << "-vf" << "fps=1,scale=320:240"; imgArgs << "-vf" << "fps=1,scale=320:240";
@ -236,14 +227,9 @@ bool Upkeep::exec()
shared->detLog.clear(); shared->detLog.clear();
shared->logMutex.unlock(); shared->logMutex.unlock();
initLogFrontPages();
enforceMaxEvents(shared); enforceMaxEvents(shared);
enforceMaxImages(); enforceMaxImages();
enforceMaxVids(); enforceMaxVids(shared);
genFrontPage(shared);
genCSS(shared);
genCamPage(shared);
return Loop::exec(); return Loop::exec();
} }
@ -267,14 +253,12 @@ bool EventLoop::exec()
if (wrOutVod()) if (wrOutVod())
{ {
genHTMLvod(name);
QProcess proc; QProcess proc;
QStringList args; QStringList args;
args << "convert"; args << "convert";
args << imgPath; args << imgPath;
args << "events/" + name + ".jpg"; args << "rec/" + name + shared->thumbExt;
proc.start("magick", args); proc.start("magick", args);
proc.waitForFinished(); proc.waitForFinished();
@ -307,8 +291,8 @@ bool EventLoop::exec()
auto maxSecs = shared->evMaxSecs / 2; auto maxSecs = shared->evMaxSecs / 2;
// half the maxsecs value to get front-back half secs // half the maxsecs value to get front-back half secs
auto backFiles = backwardFacingFiles("live", ".ts", event->timeStamp, maxSecs); auto backFiles = backwardFacingFiles("live", shared->streamExt, event->timeStamp, maxSecs);
auto frontFiles = forwardFacingFiles("live", ".ts", event->timeStamp, maxSecs); auto frontFiles = forwardFacingFiles("live", shared->streamExt, event->timeStamp, maxSecs);
vidList.append(backFiles + frontFiles); vidList.append(backFiles + frontFiles);
rmIndx.append(i); rmIndx.append(i);
@ -367,7 +351,7 @@ bool EventLoop::wrOutVod()
args << "-safe" << "0"; args << "-safe" << "0";
args << "-i" << concat; args << "-i" << concat;
args << "-c" << "copy"; args << "-c" << "copy";
args << "events/" + name + ".mp4"; args << "rec/" + name + shared->recExt;
proc.setProgram("ffmpeg"); proc.setProgram("ffmpeg");
proc.setArguments(args); proc.setArguments(args);

View File

@ -15,7 +15,6 @@
#include "common.h" #include "common.h"
#include "logger.h" #include "logger.h"
#include "web.h"
class Camera : public QObject class Camera : public QObject
{ {

View File

@ -117,11 +117,9 @@ void enforceMaxEvents(shared_t *share)
auto mp4File = nameOnly + ".mp4"; auto mp4File = nameOnly + ".mp4";
auto imgFile = nameOnly + ".jpg"; auto imgFile = nameOnly + ".jpg";
auto webFile = nameOnly + ".html";
QFile::remove(mp4File); QFile::remove(mp4File);
QFile::remove(imgFile); QFile::remove(imgFile);
QFile::remove(webFile);
names.removeFirst(); names.removeFirst();
} }
@ -139,9 +137,9 @@ void enforceMaxImages()
} }
} }
void enforceMaxVids() void enforceMaxVids(shared_t *share)
{ {
auto names = lsFilesInDir("live", ".ts"); auto names = lsFilesInDir("live", share->streamExt);
while (names.size() > MAX_VIDEOS) while (names.size() > MAX_VIDEOS)
{ {
@ -167,6 +165,14 @@ void rdLine(const QString &param, const QString &line, int *value)
} }
} }
void rdLine(const QString &param, const QString &line, bool *value)
{
if (line.startsWith(param))
{
*value = (line == "y" || line == "Y");
}
}
void touch(const QString &path) void touch(const QString &path)
{ {
if (!QFile::exists(path)) if (!QFile::exists(path))
@ -182,6 +188,14 @@ void touch(const QString &path)
} }
} }
void extCorrection(QString &ext)
{
if (!ext.startsWith("."))
{
ext = "." + ext;
}
}
bool rdConf(const QString &filePath, shared_t *share) bool rdConf(const QString &filePath, shared_t *share)
{ {
QFile varFile(filePath); QFile varFile(filePath);
@ -194,25 +208,27 @@ bool rdConf(const QString &filePath, shared_t *share)
} }
else else
{ {
share->recordUrl.clear(); share->recordUri.clear();
share->postCmd.clear(); share->postCmd.clear();
share->camName.clear(); share->camName.clear();
share->retCode = 0; share->retCode = 0;
share->imgThresh = 8000; share->imgThresh = 8000;
share->maxEvents = 100; share->maxEvents = 100;
share->maxLogSize = 100000; share->maxLogSize = 100000;
share->skipCmd = false; share->skipCmd = false;
share->postSecs = 60; share->postSecs = 60;
share->evMaxSecs = 30; share->evMaxSecs = 30;
share->conf = filePath; share->conf = filePath;
share->buffPath = "/var/opt/" + QString(APP_BIN) + "/buf"; share->buffPath = "/var/opt/" + QString(APP_BIN) + "/buf";
share->webRoot = "/var/opt/" + QString(APP_BIN) + "/web"; share->recRoot = "/var/opt/" + QString(APP_BIN) + "/rec";
share->webBg = "#485564"; share->outputType = "stderr";
share->webTxt = "#dee5ee"; share->compCmd = "magick compare -metric FUZZ " + QString(PREV_IMG) + " " + QString(NEXT_IMG) + " /dev/null";
share->webFont = "courier"; share->streamCodec = "copy";
share->outputType = "stderr"; share->streamExt = ".ts";
share->compCmd = "magick compare -metric FUZZ " + QString(PREV_IMG) + " " + QString(NEXT_IMG) + " /dev/null"; share->recExt = ".mp4";
share->thumbExt = ".jpg";
share->singleTenant = false;
QString line; QString line;
@ -222,21 +238,23 @@ bool rdConf(const QString &filePath, shared_t *share)
if (!line.startsWith("#")) if (!line.startsWith("#"))
{ {
rdLine("cam_name = ", line, &share->camName); rdLine("cam_name = ", line, &share->camName);
rdLine("recording_stream = ", line, &share->recordUrl); rdLine("recording_uri = ", line, &share->recordUri);
rdLine("buffer_path = ", line, &share->buffPath); rdLine("buffer_path = ", line, &share->buffPath);
rdLine("web_root = ", line, &share->webRoot); rdLine("rec_root = ", line, &share->recRoot);
rdLine("web_text = ", line, &share->webTxt); rdLine("max_event_secs = ", line, &share->evMaxSecs);
rdLine("web_bg = ", line, &share->webBg); rdLine("post_secs = ", line, &share->postSecs);
rdLine("web_font = ", line, &share->webFont); rdLine("post_cmd = ", line, &share->postCmd);
rdLine("max_event_secs = ", line, &share->evMaxSecs); rdLine("img_thresh = ", line, &share->imgThresh);
rdLine("post_secs = ", line, &share->postSecs); rdLine("max_events = ", line, &share->maxEvents);
rdLine("post_cmd = ", line, &share->postCmd); rdLine("max_log_size = ", line, &share->maxLogSize);
rdLine("img_thresh = ", line, &share->imgThresh); rdLine("img_comp_out = ", line, &share->outputType);
rdLine("max_events = ", line, &share->maxEvents); rdLine("img_comp_cmd = ", line, &share->compCmd);
rdLine("max_log_size = ", line, &share->maxLogSize); rdLine("stream_codec = ", line, &share->streamCodec);
rdLine("img_comp_out = ", line, &share->outputType); rdLine("stream_ext = ", line, &share->streamExt);
rdLine("img_comp_cmd = ", line, &share->compCmd); rdLine("rec_ext = ", line, &share->recExt);
rdLine("thumbnail_ext = ", line, &share->thumbExt);
rdLine("single_tenant = ", line, &share->singleTenant);
} }
} while(!line.isEmpty()); } while(!line.isEmpty());
@ -245,9 +263,22 @@ bool rdConf(const QString &filePath, shared_t *share)
{ {
share->camName = QFileInfo(share->conf).fileName(); share->camName = QFileInfo(share->conf).fileName();
} }
share->outDir = QDir().cleanPath(share->webRoot) + "/" + share->camName; extCorrection(share->streamExt);
share->tmpDir = share->buffPath + "/" + share->camName; extCorrection(share->recExt);
extCorrection(share->thumbExt);
if (share->singleTenant)
{
share->outDir = QDir().cleanPath(share->recRoot);
share->tmpDir = QDir().cleanPath(share->buffPath);
}
else
{
share->outDir = QDir().cleanPath(share->recRoot) + "/" + share->camName;
share->tmpDir = QDir().cleanPath(share->buffPath) + "/" + share->camName;
}
share->servPath = QString("/var/opt/") + APP_BIN + "/" + APP_BIN + "." + share->camName + ".service"; share->servPath = QString("/var/opt/") + APP_BIN + "/" + APP_BIN + "." + share->camName + ".service";
} }
@ -355,6 +386,8 @@ int loadServices(const QStringList &args)
else else
{ {
QTextStream(stdout) << "Successfully loaded camera service: " << servName << Qt::endl; QTextStream(stdout) << "Successfully loaded camera service: " << servName << Qt::endl;
if (shared.singleTenant) break;
} }
} }
} }

View File

@ -17,25 +17,24 @@
#include <QProcess> #include <QProcess>
#include <QTextStream> #include <QTextStream>
#include <QObject> #include <QObject>
#include <QRegularExpression>
#include <QDir> #include <QDir>
#include <QCryptographicHash>
#include <QFile> #include <QFile>
#include <QDateTime> #include <QDateTime>
#include <QThread> #include <QThread>
#include <QTimer> #include <QTimer>
#include <QStringList> #include <QStringList>
#include <QMutex> #include <QMutex>
#include <QRegularExpression>
#include <iostream> #include <iostream>
using namespace std; using namespace std;
#define APP_VER "3.2" #define APP_VER "3.3.t1"
#define APP_NAME "Motion Watch" #define APP_NAME "Motion Watch"
#define APP_BIN "mow" #define APP_BIN "mow"
#define REC_LOG_NAME "rec_log_lines.html" #define REC_LOG_NAME "rec.log"
#define DET_LOG_NAME "det_log_lines.html" #define DET_LOG_NAME "det.log"
#define UPK_LOG_NAME "upk_log_lines.html" #define UPK_LOG_NAME "upk.log"
#define DATETIME_FMT "yyyyMMddhhmmss" #define DATETIME_FMT "yyyyMMddhhmmss"
#define STRFTIME_FMT "%Y%m%d%H%M%S" #define STRFTIME_FMT "%Y%m%d%H%M%S"
#define MAX_IMAGES 1000 #define MAX_IMAGES 1000
@ -59,19 +58,21 @@ struct shared_t
QString conf; QString conf;
QString recLog; QString recLog;
QString detLog; QString detLog;
QString recordUrl; QString recordUri;
QString outDir; QString outDir;
QString tmpDir; QString tmpDir;
QString buffPath; QString buffPath;
QString postCmd; QString postCmd;
QString camName; QString camName;
QString webBg; QString recRoot;
QString webTxt;
QString webFont;
QString webRoot;
QString servPath; QString servPath;
QString outputType; QString outputType;
QString compCmd; QString compCmd;
QString streamCodec;
QString streamExt;
QString recExt;
QString thumbExt;
bool singleTenant;
bool skipCmd; bool skipCmd;
int evMaxSecs; int evMaxSecs;
int postSecs; int postSecs;
@ -95,8 +96,10 @@ void rmServices();
void touch(const QString &path); void touch(const QString &path);
void rdLine(const QString &param, const QString &line, QString *value); void rdLine(const QString &param, const QString &line, QString *value);
void rdLine(const QString &param, const QString &line, int *value); void rdLine(const QString &param, const QString &line, int *value);
void rdLine(const QString &param, const QString &line, bool *value);
void enforceMaxEvents(shared_t *share); void enforceMaxEvents(shared_t *share);
void enforceMaxImages(); void enforceMaxImages();
void enforceMaxVids(); void enforceMaxVids(shared_t *share);
void extCorrection(QString &ext);
#endif // COMMON_H #endif // COMMON_H

View File

@ -12,22 +12,26 @@
#include "logger.h" #include "logger.h"
void wrLogLine(const QString &line, shared_t *share, QString &body)
{
if (!line.isEmpty())
{
share->logMutex.lock();
body += QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh-mm-ss] ") + line + "\n";
share->logMutex.unlock();
}
}
void recLog(const QString &line, shared_t *share) void recLog(const QString &line, shared_t *share)
{ {
share->logMutex.lock(); wrLogLine(line, share, share->recLog);
share->recLog += QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh-mm-ss] ") + line + "<br>\n";
share->logMutex.unlock();
} }
void detLog(const QString &line, shared_t *share) void detLog(const QString &line, shared_t *share)
{ {
share->logMutex.lock(); wrLogLine(line, share, share->detLog);
share->detLog += QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh-mm-ss] ") + line + "<br>\n";
share->logMutex.unlock();
} }
void enforceMaxLogSize(const QString &filePath, shared_t *share) void enforceMaxLogSize(const QString &filePath, shared_t *share)
@ -62,64 +66,3 @@ void dumpLogs(const QString &fileName, const QString &lines)
outFile.close(); outFile.close();
} }
} }
void initLogFrontPage(const QString &filePath, const QString &logLinesFile)
{
if (!QFile::exists(filePath))
{
QString htmlText = "<!DOCTYPE html>\n";
htmlText += "<html>\n";
htmlText += "<script>\n";
htmlText += "function includeHTML() {\n";
htmlText += " var z, i, elmnt, file, xhttp;\n";
htmlText += " z = document.getElementsByTagName(\"*\");\n";
htmlText += " for (i = 0; i < z.length; i++) {\n";
htmlText += " elmnt = z[i];\n";
htmlText += " file = elmnt.getAttribute(\"include-html\");\n";
htmlText += " if (file) {\n";
htmlText += " xhttp = new XMLHttpRequest();\n";
htmlText += " xhttp.onreadystatechange = function() {\n";
htmlText += " if (this.readyState == 4) {\n";
htmlText += " if (this.status == 200) {elmnt.innerHTML = this.responseText;}\n";
htmlText += " if (this.status == 404) {elmnt.innerHTML = \"Page not found.\";}\n";
htmlText += " elmnt.removeAttribute(\"include-html\");\n";
htmlText += " includeHTML();\n";
htmlText += " }\n";
htmlText += " }\n";
htmlText += " xhttp.open(\"GET\", file, true);\n";
htmlText += " xhttp.send();\n";
htmlText += " return;\n";
htmlText += " }\n";
htmlText += " }\n";
htmlText += "};\n";
htmlText += "</script>\n";
htmlText += "<head>\n";
htmlText += "<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />\n";
htmlText += "<meta http-equiv=\"Pragma\" content=\"no-cache\" />\n";
htmlText += "<meta http-equiv=\"Expires\" content=\"0\" />\n";
htmlText += "<link rel='stylesheet' href='/theme.css'>\n";
htmlText += "</head>\n";
htmlText += "<body>\n";
htmlText += "<p>\n";
htmlText += "<div include-html='" + logLinesFile + "'></div>\n";
htmlText += "<script>\n";
htmlText += "includeHTML();\n";
htmlText += "</script>\n";
htmlText += "</p>\n";
htmlText += "</body>\n";
htmlText += "</html>\n";
QFile outFile(filePath);
outFile.open(QFile::WriteOnly);
outFile.write(htmlText.toUtf8());
outFile.close();
}
}
void initLogFrontPages()
{
initLogFrontPage("logs/recording_log.html", REC_LOG_NAME);
initLogFrontPage("logs/detection_log.html", DET_LOG_NAME);
}

View File

@ -15,10 +15,10 @@
#include "common.h" #include "common.h"
void wrLogLine(const QString &line, shared_t *share, QString &body);
void recLog(const QString &line, shared_t *share); void recLog(const QString &line, shared_t *share);
void detLog(const QString &line, shared_t *share); void detLog(const QString &line, shared_t *share);
void dumpLogs(const QString &fileName, const QString &lines); void dumpLogs(const QString &fileName, const QString &lines);
void enforceMaxLogSize(const QString &filePath, shared_t *share); void enforceMaxLogSize(const QString &filePath, shared_t *share);
void initLogFrontPages();
#endif // lOGGER_H #endif // lOGGER_H

View File

@ -1,189 +0,0 @@
// 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 genFrontPage(shared_t *share)
{
QString htmlText = "<!DOCTYPE html>\n";
htmlText += "<html>\n";
htmlText += "<head>\n";
htmlText += "<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />\n";
htmlText += "<meta http-equiv=\"Pragma\" content=\"no-cache\" />\n";
htmlText += "<meta http-equiv=\"Expires\" content=\"0\" />\n";
htmlText += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n";
htmlText += "<link rel='stylesheet' href='/theme.css'>\n";
htmlText += "</head>\n";
htmlText += "<body>\n";
htmlText += "<h3>" + QString(APP_NAME) + " " + QString(APP_VER) + "</h3>\n";
auto dirNames = lsDirsInDir(share->buffPath);
htmlText += "<ul>\n";
for (auto &&dirName : dirNames)
{
htmlText += " <li><a href='" + dirName + "/index.html'>" + dirName + "</a></li>\n";
}
htmlText += "</ul>\n";
htmlText += "</body>\n";
htmlText += "</html>";
QFile outFile(QDir().cleanPath(share->buffPath) + "/index.html");
outFile.open(QFile::WriteOnly);
outFile.write(htmlText.toUtf8());
outFile.close();
}
void genCamPage(shared_t *share)
{
auto outputDir = QDir().cleanPath(share->tmpDir);
QString htmlText = "<!DOCTYPE html>\n";
htmlText += "<html>\n";
htmlText += "<head>\n";
htmlText += "<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />\n";
htmlText += "<meta http-equiv=\"Pragma\" content=\"no-cache\" />\n";
htmlText += "<meta http-equiv=\"Expires\" content=\"0\" />\n";
htmlText += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n";
htmlText += "<link rel='stylesheet' href='/theme.css'>\n";
htmlText += "</head>\n";
htmlText += "<body>\n";
htmlText += "<h3>" + share->camName + "</h3>\n";
if (QDir().exists(outputDir + "/logs"))
{
auto logNames = lsFilesInDir(outputDir + "/logs", "_log.html");
htmlText += "<h4>Logs</h4>\n";
htmlText += "<ul>\n";
for (auto &&logName : logNames)
{
auto name = logName;
name.remove("_log.html");
htmlText += " <li><a href='logs/" + logName + "'>" + name + "</a></li>\n";
}
htmlText += "</ul>\n";
}
if (QDir().exists(outputDir + "/live"))
{
htmlText += "<h4>Live</h4>\n";
genHTMLstream(htmlText);
}
if (QDir().exists(outputDir + "/events"))
{
auto eveNames = lsFilesInDir(outputDir + "/events", ".html");
htmlText += "<h4>Motion Events</h4>\n";
for (auto &&eveName : eveNames)
{
auto name = eveName;
name.remove(".html");
htmlText += "<a href='events/" + eveName + "'><img src='events/" + name + ".jpg" + "' style='width:25%;height:25%;'</a>\n";
}
}
htmlText += "</body>\n";
htmlText += "</html>";
QFile outFile(QDir().cleanPath(outputDir) + "/index.html");
outFile.open(QFile::WriteOnly);
outFile.write(htmlText.toUtf8());
outFile.close();
}
void genHTMLstream(QString &text)
{
text += "<script src=\"/hls.js\">\n";
text += "</script>\n";
text += "<video width=50% height=50% id=\"video\" controls>\n";
text += "</video>\n";
text += "<script>\n";
text += " var video = document.getElementById('video');\n";
text += " if (Hls.isSupported()) {\n";
text += " var hls = new Hls({\n";
text += " debug: true,\n";
text += " });\n";
text += " hls.loadSource('stream.m3u8');\n";
text += " hls.attachMedia(video);\n";
text += " hls.on(Hls.Events.MEDIA_ATTACHED, function () {\n";
text += " video.muted = true;\n";
text += " video.play();\n";
text += " });\n";
text += " }\n";
text += " else if (video.canPlayType('application/vnd.apple.mpegurl')) {\n";
text += " video.src = 'stream.m3u8';\n";
text += " video.addEventListener('canplay', function () {\n";
text += " video.play();\n";
text += " });\n";
text += " }\n";
text += "</script>\n";
}
void genHTMLvod(const QString &name)
{
QString htmlText = "<!DOCTYPE html>\n";
htmlText += "<html>\n";
htmlText += "<head>\n";
htmlText += "<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />\n";
htmlText += "<meta http-equiv=\"Pragma\" content=\"no-cache\" />\n";
htmlText += "<meta http-equiv=\"Expires\" content=\"0\" />\n";
htmlText += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n";
htmlText += "<link rel='stylesheet' href='/theme.css'>\n";
htmlText += "</head>\n";
htmlText += "<body>\n";
htmlText += "<video width=100% height=100% controls autoplay>\n";
htmlText += " <source src='" + name + ".mp4' type='video/mp4'>\n";
htmlText += "</video>\n";
htmlText += "</body>\n";
htmlText += "</html>";
QFile outFile("events/" + name + ".html");
outFile.open(QFile::WriteOnly);
outFile.write(htmlText.toUtf8());
outFile.close();
}
void genCSS(shared_t *share)
{
QString cssText = "body {\n";
cssText += " background-color: " + share->webBg + ";\n";
cssText += " color: " + share->webTxt + ";\n";
cssText += " font-family: " + share->webFont + ";\n";
cssText += "}\n";
cssText += "a {\n";
cssText += " color: " + share->webTxt + ";\n";
cssText += "}\n";
QFile outFile(QDir().cleanPath(share->buffPath) + "/theme.css");
outFile.open(QFile::WriteOnly);
outFile.write(cssText.toUtf8());
outFile.close();
}

View File

@ -1,24 +0,0 @@
#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 genFrontPage(shared_t *share);
void genCamPage(shared_t *share);
void genHTMLstream(QString &text);
void genHTMLvod(const QString &name);
void genCSS(shared_t *share);
#endif // WEB_H