v3.3.t1
-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:
parent
b09ff1a19a
commit
41ccf1d1e7
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -57,5 +57,4 @@ compile_commands.json
|
||||||
|
|
||||||
# Build folders
|
# Build folders
|
||||||
/.build-mow
|
/.build-mow
|
||||||
/.build-opencv
|
/.build-imagemagick
|
||||||
/src/opencv
|
|
||||||
|
|
|
@ -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
BIN
bin/magick
BIN
bin/magick
Binary file not shown.
13
imgmagick_build.sh
Normal file
13
imgmagick_build.sh
Normal 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
|
21
install.sh
21
install.sh
|
@ -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
|
||||||
|
|
3
setup.sh
3
setup.sh
|
@ -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
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 ¶m, const QString &line, int *value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void rdLine(const QString ¶m, 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,7 +208,7 @@ 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();
|
||||||
|
|
||||||
|
@ -207,12 +221,14 @@ bool rdConf(const QString &filePath, shared_t *share)
|
||||||
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->webTxt = "#dee5ee";
|
|
||||||
share->webFont = "courier";
|
|
||||||
share->outputType = "stderr";
|
share->outputType = "stderr";
|
||||||
share->compCmd = "magick compare -metric FUZZ " + QString(PREV_IMG) + " " + QString(NEXT_IMG) + " /dev/null";
|
share->compCmd = "magick compare -metric FUZZ " + QString(PREV_IMG) + " " + QString(NEXT_IMG) + " /dev/null";
|
||||||
|
share->streamCodec = "copy";
|
||||||
|
share->streamExt = ".ts";
|
||||||
|
share->recExt = ".mp4";
|
||||||
|
share->thumbExt = ".jpg";
|
||||||
|
share->singleTenant = false;
|
||||||
|
|
||||||
QString line;
|
QString line;
|
||||||
|
|
||||||
|
@ -223,12 +239,9 @@ 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("web_bg = ", line, &share->webBg);
|
|
||||||
rdLine("web_font = ", line, &share->webFont);
|
|
||||||
rdLine("max_event_secs = ", line, &share->evMaxSecs);
|
rdLine("max_event_secs = ", line, &share->evMaxSecs);
|
||||||
rdLine("post_secs = ", line, &share->postSecs);
|
rdLine("post_secs = ", line, &share->postSecs);
|
||||||
rdLine("post_cmd = ", line, &share->postCmd);
|
rdLine("post_cmd = ", line, &share->postCmd);
|
||||||
|
@ -237,6 +250,11 @@ bool rdConf(const QString &filePath, shared_t *share)
|
||||||
rdLine("max_log_size = ", line, &share->maxLogSize);
|
rdLine("max_log_size = ", line, &share->maxLogSize);
|
||||||
rdLine("img_comp_out = ", line, &share->outputType);
|
rdLine("img_comp_out = ", line, &share->outputType);
|
||||||
rdLine("img_comp_cmd = ", line, &share->compCmd);
|
rdLine("img_comp_cmd = ", line, &share->compCmd);
|
||||||
|
rdLine("stream_codec = ", line, &share->streamCodec);
|
||||||
|
rdLine("stream_ext = ", line, &share->streamExt);
|
||||||
|
rdLine("rec_ext = ", line, &share->recExt);
|
||||||
|
rdLine("thumbnail_ext = ", line, &share->thumbExt);
|
||||||
|
rdLine("single_tenant = ", line, &share->singleTenant);
|
||||||
}
|
}
|
||||||
|
|
||||||
} while(!line.isEmpty());
|
} while(!line.isEmpty());
|
||||||
|
@ -246,8 +264,21 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
27
src/common.h
27
src/common.h
|
@ -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 ¶m, const QString &line, QString *value);
|
void rdLine(const QString ¶m, const QString &line, QString *value);
|
||||||
void rdLine(const QString ¶m, const QString &line, int *value);
|
void rdLine(const QString ¶m, const QString &line, int *value);
|
||||||
|
void rdLine(const QString ¶m, 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
|
||||||
|
|
|
@ -12,22 +12,26 @@
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
void recLog(const QString &line, shared_t *share)
|
void wrLogLine(const QString &line, shared_t *share, QString &body)
|
||||||
|
{
|
||||||
|
if (!line.isEmpty())
|
||||||
{
|
{
|
||||||
share->logMutex.lock();
|
share->logMutex.lock();
|
||||||
|
|
||||||
share->recLog += QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh-mm-ss] ") + line + "<br>\n";
|
body += QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh-mm-ss] ") + line + "\n";
|
||||||
|
|
||||||
share->logMutex.unlock();
|
share->logMutex.unlock();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void recLog(const QString &line, shared_t *share)
|
||||||
|
{
|
||||||
|
wrLogLine(line, share, share->recLog);
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
189
src/web.cpp
189
src/web.cpp
|
@ -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();
|
|
||||||
}
|
|
24
src/web.h
24
src/web.h
|
@ -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
|
|
Loading…
Reference in New Issue
Block a user