v3.0.t1
Completely re-written the project to use the QT API. By using Qt, I've open up use of useful tools like QCryptographicHash, QString, QByteArray, QFile, etc.. In the future I could even make use of slots/signals. The code is also in general much more readable and thread management is by far much easier. General operation of the app should be the same, this commit just serves as a base for the migration over to QT.
This commit is contained in:
parent
f4ea944f97
commit
fa834aba6c
|
@ -1,7 +1,34 @@
|
||||||
cmake_minimum_required(VERSION 2.8.12)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
project( MotionWatch )
|
|
||||||
find_package( OpenCV REQUIRED )
|
project(MotionWatch LANGUAGES CXX)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -pthread")
|
|
||||||
include_directories( ${OpenCV_INCLUDE_DIRS} )
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
add_executable( mow src/main.cpp src/common.cpp src/mo_detect.cpp src/web.cpp src/logger.cpp )
|
|
||||||
target_link_libraries( mow ${OpenCV_LIBS} )
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
include_directories(${OpenCV_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED)
|
||||||
|
find_package(OpenCV REQUIRED)
|
||||||
|
|
||||||
|
add_executable(mow
|
||||||
|
src/main.cpp
|
||||||
|
src/common.h
|
||||||
|
src/common.cpp
|
||||||
|
src/mo_detect.h
|
||||||
|
src/mo_detect.cpp
|
||||||
|
src/web.h
|
||||||
|
src/web.cpp
|
||||||
|
src/logger.h
|
||||||
|
src/logger.cpp
|
||||||
|
src/camera.h
|
||||||
|
src/camera.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(mow Qt${QT_VERSION_MAJOR}::Core ${OpenCV_LIBS})
|
||||||
|
|
9
setup.sh
9
setup.sh
|
@ -18,12 +18,5 @@ apt install -y x264
|
||||||
apt install -y libx264-dev
|
apt install -y libx264-dev
|
||||||
apt install -y libilmbase-dev
|
apt install -y libilmbase-dev
|
||||||
apt install -y libopencv-dev
|
apt install -y libopencv-dev
|
||||||
|
apt install -y qtbase5-dev
|
||||||
apt install -y apache2
|
apt install -y apache2
|
||||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
|
||||||
apt update -y
|
|
||||||
apt install -y gcc-10
|
|
||||||
apt install -y gcc-10-base
|
|
||||||
apt install -y gcc-10-doc
|
|
||||||
apt install -y g++-10
|
|
||||||
apt install -y libstdc++-10-dev
|
|
||||||
apt install -y libstdc++-10-doc
|
|
||||||
|
|
405
src/camera.cpp
Normal file
405
src/camera.cpp
Normal file
|
@ -0,0 +1,405 @@
|
||||||
|
// 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 "camera.h"
|
||||||
|
|
||||||
|
Camera::Camera(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
shared.recordUrl.clear();
|
||||||
|
shared.postCmd.clear();
|
||||||
|
shared.camName.clear();
|
||||||
|
|
||||||
|
shared.retCode = 0;
|
||||||
|
shared.pixThresh = 50;
|
||||||
|
shared.imgThresh = 800;
|
||||||
|
shared.maxEvents = 40;
|
||||||
|
shared.maxLogSize = 100000;
|
||||||
|
shared.skipCmd = false;
|
||||||
|
shared.postSecs = 60;
|
||||||
|
shared.evMaxSecs = 10;
|
||||||
|
shared.frameGap = 10;
|
||||||
|
shared.webRoot = "/var/www/html";
|
||||||
|
shared.webBg = "#485564";
|
||||||
|
shared.webTxt = "#dee5ee";
|
||||||
|
shared.webFont = "courier";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Camera::start(const QStringList &args)
|
||||||
|
{
|
||||||
|
auto ret = false;
|
||||||
|
|
||||||
|
shared.conf = getParam("-c", args);
|
||||||
|
|
||||||
|
if (rdConf(&shared))
|
||||||
|
{
|
||||||
|
QDir("live").removeRecursively();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Loop::Loop(shared_t *sharedRes, QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
shared = sharedRes;
|
||||||
|
heartBeat = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loop::loop()
|
||||||
|
{
|
||||||
|
while (exec())
|
||||||
|
{
|
||||||
|
if (heartBeat != 0)
|
||||||
|
{
|
||||||
|
thread()->sleep(heartBeat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Loop::exec()
|
||||||
|
{
|
||||||
|
return shared->retCode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
RecLoop::RecLoop(shared_t *sharedRes, QObject *parent) : Loop(sharedRes, parent)
|
||||||
|
{
|
||||||
|
once = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecLoop::updateCmd()
|
||||||
|
{
|
||||||
|
QStringList args;
|
||||||
|
|
||||||
|
args << "-hide_banner";
|
||||||
|
args << "-i" << shared->recordUrl;
|
||||||
|
args << "-strftime" << "1";
|
||||||
|
args << "-strftime_mkdir" << "1";
|
||||||
|
args << "-hls_segment_filename" << "live/%Y-%j-%H-%M-%S.ts";
|
||||||
|
args << "-hls_flags" << "delete_segments";
|
||||||
|
args << "-y";
|
||||||
|
args << "-vcodec" << "copy";
|
||||||
|
args << "-f" << "hls";
|
||||||
|
args << "-hls_time" << "2";
|
||||||
|
args << "-hls_list_size" << "1000";
|
||||||
|
args << "stream.m3u8";
|
||||||
|
|
||||||
|
proc.setProgram("ffmpeg");
|
||||||
|
proc.setArguments(args);
|
||||||
|
|
||||||
|
recLog("rec_args_updated: " + args.join(" "), shared);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecLoop::reset()
|
||||||
|
{
|
||||||
|
recLog("--rec_cmd_resetting--", shared);
|
||||||
|
|
||||||
|
proc.kill();
|
||||||
|
proc.waitForFinished();
|
||||||
|
|
||||||
|
updateCmd();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RecLoop::exec()
|
||||||
|
{
|
||||||
|
auto args = proc.arguments();
|
||||||
|
auto md5 = genMD5(QString("stream.m3u8"));
|
||||||
|
|
||||||
|
if (once)
|
||||||
|
{
|
||||||
|
updateCmd(); once = false; streamMD5 = genMD5(QByteArray("FIRST"));
|
||||||
|
}
|
||||||
|
else if ((args[3] != shared->recordUrl) || (streamMD5 == md5))
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto hashLogLine = "stream_hash prev:" + QString(streamMD5.toHex()) + " new:" + QString(md5.toHex());
|
||||||
|
|
||||||
|
recLog(hashLogLine, shared);
|
||||||
|
|
||||||
|
if (proc.state() == QProcess::NotRunning)
|
||||||
|
{
|
||||||
|
proc.start();
|
||||||
|
|
||||||
|
if (proc.waitForStarted())
|
||||||
|
{
|
||||||
|
recLog("rec_cmd_start: ok", shared);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recLog("rec_cmd_start: fail", shared);
|
||||||
|
recLog("rec_cmd_stderr: " + QString(proc.readAllStandardError()), shared);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Loop::exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
Upkeep::Upkeep(shared_t *sharedRes, QObject *parent) : Loop(sharedRes, parent) {}
|
||||||
|
|
||||||
|
bool Upkeep::exec()
|
||||||
|
{
|
||||||
|
QDir().mkdir("live");
|
||||||
|
QDir().mkdir("events");
|
||||||
|
QDir().mkdir("logs");
|
||||||
|
|
||||||
|
enforceMaxLogSize(QString("logs/") + REC_LOG_NAME, shared);
|
||||||
|
enforceMaxLogSize(QString("logs/") + DET_LOG_NAME, shared);
|
||||||
|
enforceMaxLogSize(QString("logs/") + UPK_LOG_NAME, shared);
|
||||||
|
|
||||||
|
dumpLogs(QString("logs/") + REC_LOG_NAME, shared->recLog);
|
||||||
|
dumpLogs(QString("logs/") + DET_LOG_NAME, shared->detLog);
|
||||||
|
dumpLogs(QString("logs/") + UPK_LOG_NAME, shared->upkLog);
|
||||||
|
|
||||||
|
shared->recLog.clear();
|
||||||
|
shared->detLog.clear();
|
||||||
|
shared->upkLog.clear();
|
||||||
|
|
||||||
|
initLogFrontPages(shared);
|
||||||
|
enforceMaxEvents(shared);
|
||||||
|
|
||||||
|
genHTMLul(".", shared->camName, shared);
|
||||||
|
upkLog("camera specific webroot page updated: " + shared->outDir + "/index.html", shared);
|
||||||
|
|
||||||
|
QFile tmp("/tmp/mow-lock");
|
||||||
|
|
||||||
|
if (!tmp.exists())
|
||||||
|
{
|
||||||
|
tmp.open(QFile::WriteOnly);
|
||||||
|
tmp.write(QByteArray());
|
||||||
|
|
||||||
|
genCSS(shared);
|
||||||
|
genHTMLul(shared->webRoot, QString(APP_NAME) + " " + QString(APP_VER), shared);
|
||||||
|
|
||||||
|
tmp.close();
|
||||||
|
tmp.remove();
|
||||||
|
|
||||||
|
upkLog("webroot page updated: " + QDir::cleanPath(shared->webRoot) + "/index.html", shared);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
upkLog("skipping update of the webroot page, it is busy.", shared);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Loop::exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
EventLoop::EventLoop(shared_t *sharedRes, QObject *parent) : Loop(sharedRes, parent)
|
||||||
|
{
|
||||||
|
heartBeat = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EventLoop::exec()
|
||||||
|
{
|
||||||
|
if (!shared->recList.isEmpty())
|
||||||
|
{
|
||||||
|
auto event = shared->recList[0];
|
||||||
|
|
||||||
|
recLog("attempting write out of event: " + event.evName, shared);
|
||||||
|
|
||||||
|
if (wrOutVod(event))
|
||||||
|
{
|
||||||
|
genHTMLvod(event.evName);
|
||||||
|
imwrite(QString("events/" + event.evName + ".jpg").toUtf8().data(), event.thumbnail);
|
||||||
|
}
|
||||||
|
|
||||||
|
shared->recList.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Loop::exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EventLoop::wrOutVod(const evt_t &event)
|
||||||
|
{
|
||||||
|
auto cnt = 0;
|
||||||
|
auto concat = event.evName + ".tmp";
|
||||||
|
|
||||||
|
QFile file(concat);
|
||||||
|
|
||||||
|
file.open(QFile::WriteOnly);
|
||||||
|
|
||||||
|
for (auto i = 0; i < event.srcPaths.size(); ++i)
|
||||||
|
{
|
||||||
|
recLog("event_src: " + event.srcPaths[i], shared);
|
||||||
|
|
||||||
|
if (QFile::exists(event.srcPaths[i]))
|
||||||
|
{
|
||||||
|
file.write(QString("file '" + event.srcPaths[i] + "'\n").toUtf8()); cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (cnt == 0)
|
||||||
|
{
|
||||||
|
recLog("err: none of the event hls clips exists, canceling write out.", shared);
|
||||||
|
|
||||||
|
QFile::remove(concat); return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QProcess proc;
|
||||||
|
QStringList args;
|
||||||
|
|
||||||
|
args << "-f";
|
||||||
|
args << "concat";
|
||||||
|
args << "-safe" << "0";
|
||||||
|
args << "-i" << concat;
|
||||||
|
args << "-c" << "copy";
|
||||||
|
args << "events/" + event.evName + ".mp4";
|
||||||
|
|
||||||
|
proc.setProgram("ffmpeg");
|
||||||
|
proc.setArguments(args);
|
||||||
|
proc.start();
|
||||||
|
|
||||||
|
auto ret = false;
|
||||||
|
|
||||||
|
if (proc.waitForStarted())
|
||||||
|
{
|
||||||
|
recLog("concat_cmd_start: ok", shared);
|
||||||
|
|
||||||
|
proc.waitForFinished(); ret = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
recLog("concat_cmd_start: fail", shared);
|
||||||
|
recLog("concat_cmd_stderr: " + QString(proc.readAllStandardError()), shared);
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile::remove(concat);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DetectLoop::DetectLoop(shared_t *sharedRes, QObject *parent) : Loop(sharedRes, parent)
|
||||||
|
{
|
||||||
|
heartBeat = 0;
|
||||||
|
evId = 0;
|
||||||
|
pcId = 0;
|
||||||
|
|
||||||
|
resetTimers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DetectLoop::resetTimers()
|
||||||
|
{
|
||||||
|
if (evId != 0) killTimer(evId);
|
||||||
|
if (pcId != 0) killTimer(pcId);
|
||||||
|
|
||||||
|
evId = startTimer(shared->evMaxSecs);
|
||||||
|
pcId = startTimer(shared->postSecs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DetectLoop::timerEvent(QTimerEvent *event)
|
||||||
|
{
|
||||||
|
if (event->timerId() == evId)
|
||||||
|
{
|
||||||
|
detLog("---EVENT_BREAK---", shared);
|
||||||
|
|
||||||
|
if (!shared->curEvent.srcPaths.isEmpty())
|
||||||
|
{
|
||||||
|
shared->curEvent.evName = QDateTime::currentDateTime().toString("yyyyMMddmmss--") + QString::number(shared->maxScore);
|
||||||
|
|
||||||
|
shared->recList.append(shared->curEvent);
|
||||||
|
|
||||||
|
detLog("motion detected in " + QString::number(shared->curEvent.srcPaths.size()) + " file(s) in " + QString::number(shared->evMaxSecs) + " secs", shared);
|
||||||
|
detLog("all video clips queued for event generation under event name: " + shared->curEvent.evName, shared);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
detLog("no motion detected in all files. none queued for event generation.", shared);
|
||||||
|
}
|
||||||
|
|
||||||
|
shared->curEvent.srcPaths.clear();
|
||||||
|
shared->curEvent.evName.clear();
|
||||||
|
shared->curEvent.thumbnail.release();
|
||||||
|
|
||||||
|
shared->maxScore = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->timerId() == pcId)
|
||||||
|
{
|
||||||
|
detLog("---POST_BREAK---", shared);
|
||||||
|
|
||||||
|
if (!shared->skipCmd)
|
||||||
|
{
|
||||||
|
detLog("no motion detected, running post command: " + shared->postCmd, shared);
|
||||||
|
system(shared->postCmd.toUtf8().data());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shared->skipCmd = false;
|
||||||
|
|
||||||
|
detLog("motion detected, skipping the post command.", shared);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DetectLoop::exec()
|
||||||
|
{
|
||||||
|
QFile fileIn("stream.m3u8");
|
||||||
|
QString tsPath;
|
||||||
|
|
||||||
|
if (!fileIn.open(QFile::ReadOnly))
|
||||||
|
{
|
||||||
|
detLog("err: failed to open the stream hls file for reading. reason: " + fileIn.errorString(), shared);
|
||||||
|
}
|
||||||
|
else if (fileIn.size() < 50)
|
||||||
|
{
|
||||||
|
detLog("the stream hls list is not big enough yet. waiting for more clips.", shared);
|
||||||
|
}
|
||||||
|
else if (!fileIn.seek(fileIn.size() - 50))
|
||||||
|
{
|
||||||
|
detLog("err: failed to seek to 'near end' of stream file. reason: " + fileIn.errorString(), shared);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QString line;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
line = QString::fromUtf8(fileIn.readLine());
|
||||||
|
|
||||||
|
if (line.startsWith("live/"))
|
||||||
|
{
|
||||||
|
tsPath = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while(!line.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tsPath.isEmpty())
|
||||||
|
{
|
||||||
|
detLog("wrn: didn't find the latest hls clip. previous failure? waiting 5secs.", shared);
|
||||||
|
|
||||||
|
thread()->sleep(5);
|
||||||
|
}
|
||||||
|
else if (prevTs == tsPath)
|
||||||
|
{
|
||||||
|
detLog("wrn: the lastest hls clip is the same as the previous clip. is the recording loop running? waiting 5secs.", shared);
|
||||||
|
|
||||||
|
thread()->sleep(5);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prevTs = tsPath;
|
||||||
|
|
||||||
|
if (moDetect(tsPath, thread(), shared))
|
||||||
|
{
|
||||||
|
shared->curEvent.srcPaths.append(tsPath);
|
||||||
|
|
||||||
|
shared->skipCmd = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Loop::exec();
|
||||||
|
}
|
122
src/camera.h
Normal file
122
src/camera.h
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
#ifndef CAMERA_H
|
||||||
|
#define CAMERA_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"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "web.h"
|
||||||
|
#include "mo_detect.h"
|
||||||
|
|
||||||
|
class Loop : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
shared_t *shared;
|
||||||
|
int heartBeat;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit Loop(shared_t *shared, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
virtual bool exec();
|
||||||
|
};
|
||||||
|
|
||||||
|
class Camera : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
shared_t shared;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit Camera(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool start(const QStringList &args);
|
||||||
|
};
|
||||||
|
|
||||||
|
class RecLoop : public Loop
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QProcess proc;
|
||||||
|
QByteArray streamMD5;
|
||||||
|
bool once;
|
||||||
|
|
||||||
|
void updateCmd();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit RecLoop(shared_t *shared, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool exec();
|
||||||
|
};
|
||||||
|
|
||||||
|
class Upkeep : public Loop
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit Upkeep(shared_t *shared, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool exec();
|
||||||
|
};
|
||||||
|
|
||||||
|
class EventLoop : public Loop
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool wrOutVod(const evt_t &event);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit EventLoop(shared_t *shared, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool exec();
|
||||||
|
};
|
||||||
|
|
||||||
|
class DetectLoop : public Loop
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
int pcId;
|
||||||
|
int evId;
|
||||||
|
QString prevTs;
|
||||||
|
|
||||||
|
void resetTimers();
|
||||||
|
void timerEvent(QTimerEvent *event);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit DetectLoop(shared_t *shared, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
bool exec();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CAMERA_H
|
339
src/common.cpp
339
src/common.cpp
|
@ -12,124 +12,87 @@
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
string cleanDir(const string &path)
|
QString getParam(const QString &key, const QStringList &args)
|
||||||
{
|
{
|
||||||
if (path[path.size() - 1] == '/')
|
// this can be used by command objects to pick out parameters
|
||||||
{
|
// from a command line that are pointed by a name identifier
|
||||||
return path.substr(0, path.size() - 1);
|
// example: -i /etc/some_file, this function should pick out
|
||||||
}
|
// "/etc/some_file" from args if "-i" is passed into key.
|
||||||
else
|
|
||||||
{
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool createDir(const string &dir)
|
QString ret;
|
||||||
{
|
|
||||||
auto ret = mkdir(dir.c_str(), 0777);
|
|
||||||
|
|
||||||
if (ret == -1)
|
int pos = args.indexOf(QRegularExpression(key, QRegularExpression::CaseInsensitiveOption));
|
||||||
{
|
|
||||||
return errno == EEXIST;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool createDirTree(const string &full_path)
|
if (pos != -1)
|
||||||
{
|
|
||||||
size_t pos = 0;
|
|
||||||
auto ret = true;
|
|
||||||
|
|
||||||
while (ret == true && pos != string::npos)
|
|
||||||
{
|
{
|
||||||
pos = full_path.find('/', pos + 1);
|
// key found.
|
||||||
ret = createDir(full_path.substr(0, pos));
|
|
||||||
|
if ((pos + 1) <= (args.size() - 1))
|
||||||
|
{
|
||||||
|
// check ahead to make sure pos + 1 will not go out
|
||||||
|
// of range.
|
||||||
|
|
||||||
|
if (!args[pos + 1].startsWith("-"))
|
||||||
|
{
|
||||||
|
// the "-" used throughout this application
|
||||||
|
// indicates an argument so the above 'if'
|
||||||
|
// statement will check to make sure it does
|
||||||
|
// not return another argument as a parameter
|
||||||
|
// in case a back-to-back "-arg -arg" is
|
||||||
|
// present.
|
||||||
|
|
||||||
|
ret = args[pos + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanupEmptyDirs(const string &path)
|
QByteArray genMD5(const QByteArray &bytes)
|
||||||
{
|
{
|
||||||
if (exists(path))
|
QCryptographicHash hasher(QCryptographicHash::Md5);
|
||||||
|
|
||||||
|
hasher.addData(bytes); return hasher.result();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray genMD5(const QString &path)
|
||||||
|
{
|
||||||
|
auto file = QFile(path);
|
||||||
|
|
||||||
|
if (file.open(QFile::ReadOnly))
|
||||||
{
|
{
|
||||||
for (auto &entry : directory_iterator(path))
|
return genMD5(file.readAll());
|
||||||
{
|
}
|
||||||
if (entry.is_directory())
|
else
|
||||||
{
|
{
|
||||||
try
|
return genMD5(QByteArray("EMPTY"));
|
||||||
{
|
|
||||||
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)
|
QStringList lsFilesInDir(const QString &path, const QString &ext)
|
||||||
{
|
{
|
||||||
vector<string> names;
|
QStringList filters;
|
||||||
|
|
||||||
if (exists(path))
|
filters << "*" + ext;
|
||||||
{
|
|
||||||
for (auto &entry : directory_iterator(path))
|
|
||||||
{
|
|
||||||
if (entry.is_regular_file())
|
|
||||||
{
|
|
||||||
auto name = entry.path().filename().string();
|
|
||||||
|
|
||||||
if (ext.empty() || name.ends_with(ext))
|
QDir dirObj(path);
|
||||||
{
|
|
||||||
names.push_back(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort(names.begin(), names.end());
|
dirObj.setFilter(QDir::Files);
|
||||||
|
dirObj.setNameFilters(filters);
|
||||||
|
dirObj.setSorting(QDir::Name);
|
||||||
|
|
||||||
return names;
|
return dirObj.entryList();
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> lsDirsInDir(const string &path)
|
QStringList lsDirsInDir(const QString &path)
|
||||||
{
|
{
|
||||||
vector<string> names;
|
QDir dirObj(path);
|
||||||
|
|
||||||
if (exists(path))
|
dirObj.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
{
|
dirObj.setSorting(QDir::Name);
|
||||||
for (auto &entry : directory_iterator(path))
|
|
||||||
{
|
|
||||||
if (entry.is_directory())
|
|
||||||
{
|
|
||||||
names.push_back(entry.path().filename().string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort(names.begin(), names.end());
|
return dirObj.entryList();
|
||||||
|
|
||||||
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)
|
void enforceMaxEvents(shared_t *share)
|
||||||
|
@ -138,83 +101,57 @@ void enforceMaxEvents(shared_t *share)
|
||||||
|
|
||||||
while (names.size() > share->maxEvents)
|
while (names.size() > share->maxEvents)
|
||||||
{
|
{
|
||||||
// removes the video file extension (.mp4).
|
auto nameOnly = "events/" + names[0];
|
||||||
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);
|
nameOnly.remove(".mp4");
|
||||||
if (exists(imgFile)) remove(imgFile);
|
|
||||||
if (exists(webFile)) remove(webFile);
|
|
||||||
|
|
||||||
names.erase(names.begin());
|
auto mp4File = nameOnly + ".mp4";
|
||||||
|
auto imgFile = nameOnly + ".jpg";
|
||||||
|
auto webFile = nameOnly + ".html";
|
||||||
|
|
||||||
|
QFile::remove(mp4File);
|
||||||
|
QFile::remove(imgFile);
|
||||||
|
QFile::remove(webFile);
|
||||||
|
|
||||||
|
names.removeFirst();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void rdLine(const QString ¶m, const QString &line, QString *value)
|
||||||
string genTimeStr(const char *fmt)
|
|
||||||
{
|
{
|
||||||
time_t rawtime;
|
if (line.startsWith(param))
|
||||||
|
|
||||||
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(int score)
|
|
||||||
{
|
|
||||||
return genTimeStr(string("%Y-%j-%H-%M-%S--" + to_string(score)).c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void rdLine(const string ¶m, const string &line, string *value)
|
|
||||||
{
|
|
||||||
if (line.rfind(param.c_str(), 0) == 0)
|
|
||||||
{
|
{
|
||||||
*value = line.substr(param.size());
|
*value = line.mid(param.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void rdLine(const string ¶m, const string &line, int *value)
|
void rdLine(const QString ¶m, const QString &line, int *value)
|
||||||
{
|
{
|
||||||
if (line.rfind(param.c_str(), 0) == 0)
|
if (line.startsWith(param))
|
||||||
{
|
{
|
||||||
*value = strtol(line.substr(param.size()).c_str(), NULL, 10);
|
*value = line.mid(param.size()).toInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rdConf(const string &filePath, shared_t *share)
|
bool rdConf(const QString &filePath, shared_t *share)
|
||||||
{
|
{
|
||||||
ifstream varFile(filePath.c_str());
|
QFile varFile(filePath);
|
||||||
|
|
||||||
if (!varFile.is_open())
|
if (!varFile.open(QFile::ReadOnly))
|
||||||
{
|
{
|
||||||
share->retCode = ENOENT;
|
share->retCode = ENOENT;
|
||||||
|
|
||||||
cerr << "err: config file: " << filePath << " does not exists or lack read permissions." << endl;
|
QTextStream(stderr) << "err: config file: " << filePath << " does not exists or lack read permissions." << Qt::endl;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string line;
|
QString line;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
getline(varFile, line);
|
line = QString::fromUtf8(varFile.readLine());
|
||||||
|
|
||||||
if (line.rfind("#", 0) != 0)
|
if (!line.startsWith("#"))
|
||||||
{
|
{
|
||||||
rdLine("cam_name = ", line, &share->camName);
|
rdLine("cam_name = ", line, &share->camName);
|
||||||
rdLine("recording_stream = ", line, &share->recordUrl);
|
rdLine("recording_stream = ", line, &share->recordUrl);
|
||||||
|
@ -232,7 +169,7 @@ bool rdConf(const string &filePath, shared_t *share)
|
||||||
rdLine("max_log_size = ", line, &share->maxLogSize);
|
rdLine("max_log_size = ", line, &share->maxLogSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
} while(!line.empty());
|
} while(!line.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
return share->retCode == 0;
|
return share->retCode == 0;
|
||||||
|
@ -240,122 +177,24 @@ bool rdConf(const string &filePath, shared_t *share)
|
||||||
|
|
||||||
bool rdConf(shared_t *share)
|
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 = 10;
|
|
||||||
share->webRoot = "/var/www/html";
|
|
||||||
share->webBg = "#485564";
|
|
||||||
share->webTxt = "#dee5ee";
|
|
||||||
share->webFont = "courier";
|
|
||||||
|
|
||||||
if (rdConf(share->conf, share))
|
if (rdConf(share->conf, share))
|
||||||
{
|
{
|
||||||
if (share->camName.empty())
|
if (share->camName.isEmpty())
|
||||||
{
|
{
|
||||||
share->camName = path(share->conf).filename();
|
share->camName = QFileInfo(share->conf).fileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
share->outDir = cleanDir(share->webRoot) + "/" + share->camName;
|
share->outDir = QDir().cleanPath(share->webRoot) + "/" + share->camName;
|
||||||
|
|
||||||
error_code ec;
|
QDir().mkpath(share->outDir);
|
||||||
|
|
||||||
createDirTree(share->outDir);
|
if (!QDir::setCurrent(share->outDir))
|
||||||
current_path(share->outDir, ec);
|
|
||||||
|
|
||||||
share->retCode = ec.value();
|
|
||||||
|
|
||||||
if (share->retCode != 0)
|
|
||||||
{
|
{
|
||||||
cerr << "err: " << ec.message() << endl;
|
QTextStream(stderr) << "err: failed to change/create the current working directory to camera folder: '" << share->outDir << ".' does it exists?" << Qt::endl;
|
||||||
|
|
||||||
|
share->retCode = ENOENT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return share->retCode == 0;
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
115
src/common.h
115
src/common.h
|
@ -13,28 +13,23 @@
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
#include <iostream>
|
#include <QCoreApplication>
|
||||||
#include <fstream>
|
#include <QProcess>
|
||||||
#include <string>
|
#include <QTextStream>
|
||||||
#include <time.h>
|
#include <QObject>
|
||||||
#include <chrono>
|
#include <QRegularExpression>
|
||||||
#include <stdlib.h>
|
#include <QDir>
|
||||||
#include <errno.h>
|
#include <QCryptographicHash>
|
||||||
#include <vector>
|
#include <QFile>
|
||||||
#include <thread>
|
#include <QDateTime>
|
||||||
#include <filesystem>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
#include <opencv4/opencv2/opencv.hpp>
|
#include <opencv4/opencv2/opencv.hpp>
|
||||||
#include <opencv4/opencv2/videoio.hpp>
|
#include <opencv4/opencv2/videoio.hpp>
|
||||||
|
|
||||||
using namespace cv;
|
using namespace cv;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace std::filesystem;
|
|
||||||
using namespace std::chrono;
|
|
||||||
|
|
||||||
#define APP_VER "2.2"
|
#define APP_VER "3.0.t1"
|
||||||
#define APP_NAME "Motion Watch"
|
#define APP_NAME "Motion Watch"
|
||||||
#define REC_LOG_NAME "rec_log_lines.html"
|
#define REC_LOG_NAME "rec_log_lines.html"
|
||||||
#define DET_LOG_NAME "det_log_lines.html"
|
#define DET_LOG_NAME "det_log_lines.html"
|
||||||
|
@ -42,60 +37,52 @@ using namespace std::chrono;
|
||||||
|
|
||||||
struct evt_t
|
struct evt_t
|
||||||
{
|
{
|
||||||
string evName;
|
QString evName;
|
||||||
vector<string> srcPaths;
|
QStringList srcPaths;
|
||||||
Mat thumbnail;
|
Mat thumbnail;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct shared_t
|
struct shared_t
|
||||||
{
|
{
|
||||||
vector<evt_t> recList;
|
QList<evt_t> recList;
|
||||||
string conf;
|
evt_t curEvent;
|
||||||
string recLog;
|
QString conf;
|
||||||
string detLog;
|
QString recLog;
|
||||||
string upkLog;
|
QString detLog;
|
||||||
string recordUrl;
|
QString upkLog;
|
||||||
string outDir;
|
QString recordUrl;
|
||||||
string postCmd;
|
QString outDir;
|
||||||
string camName;
|
QString postCmd;
|
||||||
string webBg;
|
QString camName;
|
||||||
string webTxt;
|
QString webBg;
|
||||||
string webFont;
|
QString webTxt;
|
||||||
string webRoot;
|
QString webFont;
|
||||||
evt_t curEvent;
|
QString webRoot;
|
||||||
bool skipCmd;
|
bool skipCmd;
|
||||||
int frameGap;
|
int frameGap;
|
||||||
int evMaxSecs;
|
int evMaxSecs;
|
||||||
int postSecs;
|
int postSecs;
|
||||||
int maxScore;
|
int maxScore;
|
||||||
int procCnt;
|
int procCnt;
|
||||||
int hlsCnt;
|
int hlsCnt;
|
||||||
int pixThresh;
|
int pixThresh;
|
||||||
int imgThresh;
|
int imgThresh;
|
||||||
int maxEvents;
|
int maxEvents;
|
||||||
int maxLogSize;
|
int maxLogSize;
|
||||||
int retCode;
|
int retCode;
|
||||||
int postInd;
|
int postInd;
|
||||||
int evInd;
|
int evInd;
|
||||||
};
|
};
|
||||||
|
|
||||||
string genVidNameFromLive(const string &tsPath);
|
QByteArray genMD5(const QString &path);
|
||||||
string genEventPath(const string &tsPath);
|
QByteArray genMD5(const QByteArray &bytes);
|
||||||
string genEventName(int score);
|
QString getParam(const QString &key, const QStringList &args);
|
||||||
string genDstFile(const string &dirOut, const char *fmt, const string &ext);
|
QStringList lsFilesInDir(const QString &path, const QString &ext = QString());
|
||||||
string genTimeStr(const char *fmt);
|
QStringList lsDirsInDir(const QString &path);
|
||||||
string cleanDir(const string &path);
|
bool rdConf(const QString &filePath, shared_t *share);
|
||||||
string parseForParam(const string &arg, int argc, char** argv, bool argOnly, int &offs);
|
bool rdConf(shared_t *share);
|
||||||
string parseForParam(const string &arg, int argc, char** argv, bool argOnly);
|
void rdLine(const QString ¶m, const QString &line, QString *value);
|
||||||
bool createDir(const string &dir);
|
void rdLine(const QString ¶m, const QString &line, int *value);
|
||||||
bool createDirTree(const string &full_path);
|
void enforceMaxEvents(shared_t *share);
|
||||||
void rdLine(const string ¶m, const string &line, string *value);
|
|
||||||
void rdLine(const string ¶m, const string &line, int *value);
|
|
||||||
void cleanupEmptyDirs(const string &path);
|
|
||||||
void cleanupStream(const string &plsPath);
|
|
||||||
void enforceMaxEvents(shared_t *share);
|
|
||||||
bool rdConf(shared_t *share);
|
|
||||||
vector<string> lsFilesInDir(const string &path, const string &ext = string());
|
|
||||||
vector<string> lsDirsInDir(const string &path);
|
|
||||||
|
|
||||||
#endif // COMMON_H
|
#endif // COMMON_H
|
||||||
|
|
|
@ -12,58 +12,59 @@
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
void recLog(const string &line, shared_t *share)
|
void recLog(const QString &line, shared_t *share)
|
||||||
{
|
{
|
||||||
share->recLog += genTimeStr("[%Y-%m-%d-%H-%M-%S] ") + line + "<br>\n";
|
share->recLog += QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh-mm-ss] ") + line + "<br>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void detLog(const string &line, shared_t *share)
|
void detLog(const QString &line, shared_t *share)
|
||||||
{
|
{
|
||||||
share->detLog += genTimeStr("[%Y-%m-%d-%H-%M-%S] ") + line + "<br>\n";
|
share->detLog += QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh-mm-ss] ") + line + "<br>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void upkLog(const string &line, shared_t *share)
|
void upkLog(const QString &line, shared_t *share)
|
||||||
{
|
{
|
||||||
share->upkLog += genTimeStr("[%Y-%m-%d-%H-%M-%S] ") + line + "<br>\n";
|
share->upkLog += QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh-mm-ss] ") + line + "<br>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void enforceMaxLogSize(const string &filePath, shared_t *share)
|
void enforceMaxLogSize(const QString &filePath, shared_t *share)
|
||||||
{
|
{
|
||||||
if (exists(filePath))
|
QFile file(filePath);
|
||||||
|
|
||||||
|
if (file.exists())
|
||||||
{
|
{
|
||||||
if (file_size(filePath) >= share->maxLogSize)
|
if (file.size() >= share->maxLogSize)
|
||||||
{
|
{
|
||||||
remove(filePath);
|
file.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void dumpLogs(const string &fileName, const string &lines)
|
void dumpLogs(const QString &fileName, const QString &lines)
|
||||||
{
|
{
|
||||||
if (!lines.empty())
|
if (!lines.isEmpty())
|
||||||
{
|
{
|
||||||
ofstream outFile;
|
QFile outFile(fileName);
|
||||||
|
|
||||||
if (exists(fileName))
|
if (outFile.exists())
|
||||||
{
|
{
|
||||||
outFile.open(fileName.c_str(), ofstream::app);
|
outFile.open(QFile::Append);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
outFile.open(fileName.c_str());
|
outFile.open(QFile::WriteOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
outFile << lines;
|
outFile.write(lines.toUtf8());
|
||||||
|
|
||||||
outFile.close();
|
outFile.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void initLogFrontPage(const string &filePath, const string &logLinesFile)
|
void initLogFrontPage(const QString &filePath, const QString &logLinesFile)
|
||||||
{
|
{
|
||||||
if (!exists(filePath))
|
if (!QFile::exists(filePath))
|
||||||
{
|
{
|
||||||
string htmlText = "<!DOCTYPE html>\n";
|
QString htmlText = "<!DOCTYPE html>\n";
|
||||||
|
|
||||||
htmlText += "<html>\n";
|
htmlText += "<html>\n";
|
||||||
htmlText += "<script>\n";
|
htmlText += "<script>\n";
|
||||||
|
@ -106,10 +107,10 @@ void initLogFrontPage(const string &filePath, const string &logLinesFile)
|
||||||
htmlText += "</body>\n";
|
htmlText += "</body>\n";
|
||||||
htmlText += "</html>\n";
|
htmlText += "</html>\n";
|
||||||
|
|
||||||
ofstream outFile(filePath);
|
QFile outFile(filePath);
|
||||||
|
|
||||||
outFile << htmlText;
|
|
||||||
|
|
||||||
|
outFile.open(QFile::WriteOnly);
|
||||||
|
outFile.write(htmlText.toUtf8());
|
||||||
outFile.close();
|
outFile.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
src/logger.h
10
src/logger.h
|
@ -15,11 +15,11 @@
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
void recLog(const string &line, shared_t *share);
|
void recLog(const QString &line, shared_t *share);
|
||||||
void detLog(const string &line, shared_t *share);
|
void detLog(const QString &line, shared_t *share);
|
||||||
void upkLog(const string &line, shared_t *share);
|
void upkLog(const QString &line, shared_t *share);
|
||||||
void dumpLogs(const string &fileName, const string &lines);
|
void dumpLogs(const QString &fileName, const QString &lines);
|
||||||
void enforceMaxLogSize(const string &filePath, shared_t *share);
|
void enforceMaxLogSize(const QString &filePath, shared_t *share);
|
||||||
void initLogFrontPages(shared_t *share);
|
void initLogFrontPages(shared_t *share);
|
||||||
|
|
||||||
#endif // lOGGER_H
|
#endif // lOGGER_H
|
||||||
|
|
194
src/main.cpp
194
src/main.cpp
|
@ -13,189 +13,43 @@
|
||||||
#include "mo_detect.h"
|
#include "mo_detect.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "web.h"
|
#include "web.h"
|
||||||
|
#include "camera.h"
|
||||||
void timer(shared_t *share)
|
|
||||||
{
|
|
||||||
while (share->retCode == 0)
|
|
||||||
{
|
|
||||||
sleep(1);
|
|
||||||
|
|
||||||
share->postInd += 1;
|
|
||||||
share->evInd += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void detectMo(shared_t *share)
|
|
||||||
{
|
|
||||||
while (share->retCode == 0)
|
|
||||||
{
|
|
||||||
sleep(2);
|
|
||||||
detectMoInStream("stream.m3u8", share);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void eventLoop(shared_t *share)
|
|
||||||
{
|
|
||||||
while (share->retCode == 0)
|
|
||||||
{
|
|
||||||
if (!share->recList.empty())
|
|
||||||
{
|
|
||||||
auto event = share->recList[0];
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
recLog("attempting write out of event: " + event.evName, share);
|
|
||||||
|
|
||||||
createDirTree("events");
|
|
||||||
|
|
||||||
if (wrOutVod(event, share))
|
|
||||||
{
|
|
||||||
genHTMLvod(event.evName);
|
|
||||||
imwrite(string("events/" + event.evName + ".jpg").c_str(), event.thumbnail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (filesystem_error &ex)
|
|
||||||
{
|
|
||||||
recLog(string("err: ") + ex.what(), share);
|
|
||||||
}
|
|
||||||
|
|
||||||
share->recList.erase(share->recList.begin());
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void upkeep(shared_t *share)
|
|
||||||
{
|
|
||||||
while (share->retCode == 0)
|
|
||||||
{
|
|
||||||
createDirTree("live");
|
|
||||||
createDirTree("events");
|
|
||||||
createDirTree("logs");
|
|
||||||
|
|
||||||
enforceMaxLogSize(string("logs/") + REC_LOG_NAME, share);
|
|
||||||
enforceMaxLogSize(string("logs/") + DET_LOG_NAME, share);
|
|
||||||
enforceMaxLogSize(string("logs/") + UPK_LOG_NAME, share);
|
|
||||||
|
|
||||||
dumpLogs(string("logs/") + REC_LOG_NAME, share->recLog);
|
|
||||||
dumpLogs(string("logs/") + DET_LOG_NAME, share->detLog);
|
|
||||||
dumpLogs(string("logs/") + UPK_LOG_NAME, share->upkLog);
|
|
||||||
|
|
||||||
share->recLog.clear();
|
|
||||||
share->detLog.clear();
|
|
||||||
share->upkLog.clear();
|
|
||||||
|
|
||||||
initLogFrontPages(share);
|
|
||||||
enforceMaxEvents(share);
|
|
||||||
|
|
||||||
genHTMLul(".", share->camName, share);
|
|
||||||
|
|
||||||
upkLog("camera specific webroot page updated: " + share->outDir + "/index.html", share);
|
|
||||||
|
|
||||||
if (!exists("/tmp/mow-lock"))
|
|
||||||
{
|
|
||||||
system("touch /tmp/mow-lock");
|
|
||||||
|
|
||||||
genCSS(share);
|
|
||||||
genHTMLul(share->webRoot, string(APP_NAME) + " " + string(APP_VER), share);
|
|
||||||
|
|
||||||
remove("/tmp/mow-lock");
|
|
||||||
upkLog("webroot page updated: " + cleanDir(share->webRoot) + "/index.html", share);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
upkLog("skipping update of the webroot page, it is busy.", share);
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void rmLive()
|
|
||||||
{
|
|
||||||
if (exists("live"))
|
|
||||||
{
|
|
||||||
remove_all("live");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void recLoop(shared_t *share)
|
|
||||||
{
|
|
||||||
while (share->retCode == 0)
|
|
||||||
{
|
|
||||||
auto cmd = "ffmpeg -hide_banner -rtsp_transport tcp -timeout 3000000 -i " +
|
|
||||||
share->recordUrl +
|
|
||||||
" -strftime 1" +
|
|
||||||
" -strftime_mkdir 1" +
|
|
||||||
" -hls_segment_filename 'live/%Y-%j-%H-%M-%S.ts'" +
|
|
||||||
" -hls_flags delete_segments" +
|
|
||||||
" -y -vcodec copy" +
|
|
||||||
" -f hls -hls_time 2 -hls_list_size 1000" +
|
|
||||||
" stream.m3u8";
|
|
||||||
|
|
||||||
recLog("ffmpeg_run: " + cmd, share);
|
|
||||||
rmLive();
|
|
||||||
|
|
||||||
auto retCode = system(cmd.c_str());
|
|
||||||
|
|
||||||
recLog("ffmpeg_retcode: " + to_string(retCode), share);
|
|
||||||
|
|
||||||
if (retCode != 0)
|
|
||||||
{
|
|
||||||
recLog("err: ffmpeg returned non zero, indicating failure. please check stderr output.", share);
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
struct shared_t sharedRes;
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
sharedRes.conf = parseForParam("-c", argc, argv, false);
|
QCoreApplication::setApplicationName(APP_NAME);
|
||||||
|
QCoreApplication::setApplicationVersion(APP_VER);
|
||||||
|
|
||||||
if (parseForParam("-h", argc, argv, true) == "true")
|
auto args = QCoreApplication::arguments();
|
||||||
|
auto ret = 0;
|
||||||
|
|
||||||
|
if (args.contains("-h"))
|
||||||
{
|
{
|
||||||
cout << "Motion Watch " << APP_VER << endl << endl;
|
QTextStream(stdout) << "Motion Watch " << APP_VER << Qt::endl << Qt::endl;
|
||||||
cout << "Usage: mow <argument>" << endl << endl;
|
QTextStream(stdout) << "Usage: mow <argument>" << Qt::endl << Qt::endl;
|
||||||
cout << "-h : display usage information about this application." << endl;
|
QTextStream(stdout) << "-h : display usage information about this application." << Qt::endl;
|
||||||
cout << "-c : path to the config file." << endl;
|
QTextStream(stdout) << "-c : path to the config file." << Qt::endl;
|
||||||
cout << "-v : display the current version." << endl << endl;
|
QTextStream(stdout) << "-v : display the current version." << Qt::endl << Qt::endl;
|
||||||
}
|
}
|
||||||
else if (parseForParam("-v", argc, argv, true) == "true")
|
else if (args.contains("-v"))
|
||||||
{
|
{
|
||||||
cout << APP_VER << endl;
|
QTextStream(stdout) << APP_VER << Qt::endl;
|
||||||
}
|
}
|
||||||
else if (sharedRes.conf.empty())
|
else if (args.contains("-c"))
|
||||||
{
|
{
|
||||||
cerr << "err: no config file(s) were given in -c" << endl;
|
auto *cam = new Camera(&app);
|
||||||
|
|
||||||
|
if (cam->start(args))
|
||||||
|
{
|
||||||
|
ret = QCoreApplication::exec();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sharedRes.retCode = 0;
|
QTextStream(stderr) << "err: no config file(s) were given in -c" << Qt::endl;
|
||||||
sharedRes.maxScore = 0;
|
|
||||||
sharedRes.postInd = 0;
|
|
||||||
sharedRes.evInd = 0;
|
|
||||||
sharedRes.skipCmd = false;
|
|
||||||
|
|
||||||
rdConf(&sharedRes);
|
|
||||||
|
|
||||||
auto thr1 = thread(recLoop, &sharedRes);
|
|
||||||
auto thr2 = thread(upkeep, &sharedRes);
|
|
||||||
auto thr3 = thread(detectMo, &sharedRes);
|
|
||||||
auto thr4 = thread(eventLoop, &sharedRes);
|
|
||||||
auto thr5 = thread(timer, &sharedRes);
|
|
||||||
|
|
||||||
thr1.join();
|
|
||||||
thr2.join();
|
|
||||||
thr3.join();
|
|
||||||
thr4.join();
|
|
||||||
thr5.join();
|
|
||||||
|
|
||||||
return sharedRes.retCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return EINVAL;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,77 +12,6 @@
|
||||||
|
|
||||||
#include "mo_detect.h"
|
#include "mo_detect.h"
|
||||||
|
|
||||||
void detectMoInStream(const string &streamFile, shared_t *share)
|
|
||||||
{
|
|
||||||
if (share->postInd >= share->postSecs)
|
|
||||||
{
|
|
||||||
if (!share->postCmd.empty())
|
|
||||||
{
|
|
||||||
detLog("---POST_BREAK---", share);
|
|
||||||
|
|
||||||
if (!share->skipCmd)
|
|
||||||
{
|
|
||||||
detLog("no motion detected, running post command: " + share->postCmd, share);
|
|
||||||
system(share->postCmd.c_str());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
share->skipCmd = false;
|
|
||||||
|
|
||||||
detLog("motion detected, skipping the post command.", share);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
share->postInd = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (share->evInd >= share->evMaxSecs)
|
|
||||||
{
|
|
||||||
detLog("---EVENT_BREAK---", share);
|
|
||||||
|
|
||||||
if (!share->curEvent.srcPaths.empty())
|
|
||||||
{
|
|
||||||
share->curEvent.evName = genEventName(share->maxScore);
|
|
||||||
share->recList.push_back(share->curEvent);
|
|
||||||
|
|
||||||
detLog("motion detected in " + to_string(share->curEvent.srcPaths.size()) + " file(s) in " + to_string(share->evMaxSecs) + " secs", share);
|
|
||||||
detLog("all video clips queued for event generation under event name: " + share->curEvent.evName, share);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
detLog("no motion detected in all files. none queued for event generation.", share);
|
|
||||||
}
|
|
||||||
|
|
||||||
share->curEvent.srcPaths.clear();
|
|
||||||
share->curEvent.evName.clear();
|
|
||||||
share->curEvent.thumbnail.release();
|
|
||||||
|
|
||||||
share->evInd = 0;
|
|
||||||
share->maxScore = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ifstream fileIn(streamFile);
|
|
||||||
string tsPath;
|
|
||||||
|
|
||||||
for (string line; getline(fileIn, line); )
|
|
||||||
{
|
|
||||||
if (line.starts_with("live/"))
|
|
||||||
{
|
|
||||||
tsPath = line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tsPath.empty())
|
|
||||||
{
|
|
||||||
if (moDetect(tsPath, share))
|
|
||||||
{
|
|
||||||
share->curEvent.srcPaths.push_back(tsPath);
|
|
||||||
|
|
||||||
share->skipCmd = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool imgDiff(const Mat &prev, const Mat &next, int &score, shared_t *share)
|
bool imgDiff(const Mat &prev, const Mat &next, int &score, shared_t *share)
|
||||||
{
|
{
|
||||||
Mat prevGray;
|
Mat prevGray;
|
||||||
|
@ -98,12 +27,12 @@ bool imgDiff(const Mat &prev, const Mat &next, int &score, shared_t *share)
|
||||||
|
|
||||||
score = countNonZero(diff);
|
score = countNonZero(diff);
|
||||||
|
|
||||||
detLog("diff_score: " + to_string(score) + " thresh: " + to_string(share->imgThresh), share);
|
detLog("diff_score: " + QString::number(score) + " thresh: " + QString::number(share->imgThresh), share);
|
||||||
|
|
||||||
return score >= share->imgThresh;
|
return score >= share->imgThresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool moDetect(const string &buffFile, shared_t *share)
|
bool moDetect(const QString &buffFile, QThread *thr, shared_t *share)
|
||||||
{
|
{
|
||||||
auto score = 0;
|
auto score = 0;
|
||||||
auto mod = false;
|
auto mod = false;
|
||||||
|
@ -112,11 +41,11 @@ bool moDetect(const string &buffFile, shared_t *share)
|
||||||
|
|
||||||
VideoCapture capture;
|
VideoCapture capture;
|
||||||
|
|
||||||
if (!capture.open(buffFile.c_str(), CAP_FFMPEG))
|
if (!capture.open(buffFile.toUtf8().data(), CAP_FFMPEG))
|
||||||
{
|
{
|
||||||
usleep(500);
|
thr->usleep(500);
|
||||||
|
|
||||||
capture.open(buffFile.c_str(), CAP_FFMPEG);
|
capture.open(buffFile.toUtf8().data(), CAP_FFMPEG);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (capture.isOpened())
|
if (capture.isOpened())
|
||||||
|
@ -128,7 +57,7 @@ bool moDetect(const string &buffFile, shared_t *share)
|
||||||
|
|
||||||
for (auto gap = 0, frm = fps; capture.grab(); ++gap, ++frm)
|
for (auto gap = 0, frm = fps; capture.grab(); ++gap, ++frm)
|
||||||
{
|
{
|
||||||
if (frm == fps) sleep(1); frm = 1;
|
if (frm == fps) thr->sleep(1); frm = 1;
|
||||||
|
|
||||||
if (prev.empty())
|
if (prev.empty())
|
||||||
{
|
{
|
||||||
|
@ -173,45 +102,3 @@ bool moDetect(const string &buffFile, shared_t *share)
|
||||||
|
|
||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wrOutVod(const evt_t &event, shared_t *share)
|
|
||||||
{
|
|
||||||
auto cnt = 0;
|
|
||||||
auto concat = event.evName + ".tmp";
|
|
||||||
|
|
||||||
ofstream file(concat.c_str());
|
|
||||||
|
|
||||||
for (auto i = 0; i < event.srcPaths.size(); ++i)
|
|
||||||
{
|
|
||||||
recLog("event_src: " + event.srcPaths[i], share);
|
|
||||||
|
|
||||||
if (exists(event.srcPaths[i]))
|
|
||||||
{
|
|
||||||
file << "file '" << event.srcPaths[i] << "''" << endl; cnt++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
if (cnt == 0)
|
|
||||||
{
|
|
||||||
recLog("err: none of the event hls clips exists, canceling write out.", share);
|
|
||||||
|
|
||||||
if (exists(concat)) remove(concat);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto ret = system(string("ffmpeg -f concat -safe 0 -i " + concat + " -c copy events/" + event.evName + ".mp4").c_str());
|
|
||||||
|
|
||||||
if (ret != 0)
|
|
||||||
{
|
|
||||||
recLog("err: ffmpeg concat failure, canceling write out.", share);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exists(concat)) remove(concat);
|
|
||||||
|
|
||||||
return ret == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
bool imgDiff(const Mat &prev, const Mat &next, int &score, shared_t *share);
|
bool imgDiff(const Mat &prev, const Mat &next, int &score, shared_t *share);
|
||||||
bool moDetect(const string &buffFile, shared_t *share);
|
bool moDetect(const QString &buffFile, QThread *thr, shared_t *share);
|
||||||
void detectMoInStream(const string &streamFile, shared_t *share);
|
|
||||||
bool wrOutVod(const evt_t &pls, shared_t *share);
|
|
||||||
|
|
||||||
#endif // MO_DETECT_H
|
#endif // MO_DETECT_H
|
||||||
|
|
64
src/web.cpp
64
src/web.cpp
|
@ -12,13 +12,13 @@
|
||||||
|
|
||||||
#include "web.h"
|
#include "web.h"
|
||||||
|
|
||||||
void genHTMLul(const string &outputDir, const string &title, shared_t *share)
|
void genHTMLul(const QString &outputDir, const QString &title, shared_t *share)
|
||||||
{
|
{
|
||||||
vector<string> logNames;
|
QStringList logNames;
|
||||||
vector<string> eveNames;
|
QStringList eveNames;
|
||||||
vector<string> dirNames;
|
QStringList dirNames;
|
||||||
|
|
||||||
string htmlText = "<!DOCTYPE html>\n";
|
QString htmlText = "<!DOCTYPE html>\n";
|
||||||
|
|
||||||
htmlText += "<html>\n";
|
htmlText += "<html>\n";
|
||||||
htmlText += "<head>\n";
|
htmlText += "<head>\n";
|
||||||
|
@ -31,7 +31,7 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share)
|
||||||
htmlText += "<body>\n";
|
htmlText += "<body>\n";
|
||||||
htmlText += "<h3>" + title + "</h3>\n";
|
htmlText += "<h3>" + title + "</h3>\n";
|
||||||
|
|
||||||
if (exists(outputDir + "/live"))
|
if (QDir().exists(outputDir + "/live"))
|
||||||
{
|
{
|
||||||
eveNames = lsFilesInDir(outputDir + "/events", ".html");
|
eveNames = lsFilesInDir(outputDir + "/events", ".html");
|
||||||
logNames = lsFilesInDir(outputDir + "/logs", "_log.html");
|
logNames = lsFilesInDir(outputDir + "/logs", "_log.html");
|
||||||
|
@ -41,8 +41,9 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share)
|
||||||
|
|
||||||
for (auto &&logName : logNames)
|
for (auto &&logName : logNames)
|
||||||
{
|
{
|
||||||
// name.substr(0, name.size() - 9) removes _log.html
|
auto name = logName;
|
||||||
auto name = logName.substr(0, logName.size() - 9);
|
|
||||||
|
name.remove("_log.html");
|
||||||
|
|
||||||
htmlText += " <li><a href='logs/" + logName + "'>" + name + "</a></li>\n";
|
htmlText += " <li><a href='logs/" + logName + "'>" + name + "</a></li>\n";
|
||||||
}
|
}
|
||||||
|
@ -58,8 +59,9 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share)
|
||||||
|
|
||||||
for (auto &&eveName : eveNames)
|
for (auto &&eveName : eveNames)
|
||||||
{
|
{
|
||||||
// regName.substr(0, regName.size() - 5) removes .html
|
auto name = eveName;
|
||||||
auto name = eveName.substr(0, eveName.size() - 5);
|
|
||||||
|
name.remove(".html");
|
||||||
|
|
||||||
htmlText += "<a href='events/" + eveName + "'><img src='events/" + name + ".jpg" + "' style='width:25%;height:25%;'</a>\n";
|
htmlText += "<a href='events/" + eveName + "'><img src='events/" + name + ".jpg" + "' style='width:25%;height:25%;'</a>\n";
|
||||||
}
|
}
|
||||||
|
@ -81,16 +83,16 @@ void genHTMLul(const string &outputDir, const string &title, shared_t *share)
|
||||||
htmlText += "</body>\n";
|
htmlText += "</body>\n";
|
||||||
htmlText += "</html>";
|
htmlText += "</html>";
|
||||||
|
|
||||||
ofstream file(string(cleanDir(outputDir) + "/index.html").c_str());
|
QFile outFile(QDir().cleanPath(outputDir) + "/index.html");
|
||||||
|
|
||||||
file << htmlText << endl;
|
outFile.open(QFile::WriteOnly);
|
||||||
|
outFile.write(htmlText.toUtf8());
|
||||||
file.close();
|
outFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void genHTMLstream(const string &name)
|
void genHTMLstream(const QString &name)
|
||||||
{
|
{
|
||||||
string htmlText = "<!DOCTYPE html>\n";
|
QString htmlText = "<!DOCTYPE html>\n";
|
||||||
|
|
||||||
htmlText += "<html>\n";
|
htmlText += "<html>\n";
|
||||||
htmlText += "<head>\n";
|
htmlText += "<head>\n";
|
||||||
|
@ -128,16 +130,16 @@ void genHTMLstream(const string &name)
|
||||||
htmlText += "</body>\n";
|
htmlText += "</body>\n";
|
||||||
htmlText += "</html>";
|
htmlText += "</html>";
|
||||||
|
|
||||||
ofstream file(string(name + ".html").c_str());
|
QFile outFile(name + ".html");
|
||||||
|
|
||||||
file << htmlText << endl;
|
outFile.open(QFile::WriteOnly);
|
||||||
|
outFile.write(htmlText.toUtf8());
|
||||||
file.close();
|
outFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void genHTMLvod(const string &name)
|
void genHTMLvod(const QString &name)
|
||||||
{
|
{
|
||||||
string htmlText = "<!DOCTYPE html>\n";
|
QString htmlText = "<!DOCTYPE html>\n";
|
||||||
|
|
||||||
htmlText += "<html>\n";
|
htmlText += "<html>\n";
|
||||||
htmlText += "<head>\n";
|
htmlText += "<head>\n";
|
||||||
|
@ -154,16 +156,16 @@ void genHTMLvod(const string &name)
|
||||||
htmlText += "</body>\n";
|
htmlText += "</body>\n";
|
||||||
htmlText += "</html>";
|
htmlText += "</html>";
|
||||||
|
|
||||||
ofstream file(string("events/" + name + ".html").c_str());
|
QFile outFile("events/" + name + ".html");
|
||||||
|
|
||||||
file << htmlText << endl;
|
outFile.open(QFile::WriteOnly);
|
||||||
|
outFile.write(htmlText.toUtf8());
|
||||||
file.close();
|
outFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void genCSS(shared_t *share)
|
void genCSS(shared_t *share)
|
||||||
{
|
{
|
||||||
string cssText = "body {\n";
|
QString cssText = "body {\n";
|
||||||
|
|
||||||
cssText += " background-color: " + share->webBg + ";\n";
|
cssText += " background-color: " + share->webBg + ";\n";
|
||||||
cssText += " color: " + share->webTxt + ";\n";
|
cssText += " color: " + share->webTxt + ";\n";
|
||||||
|
@ -173,9 +175,9 @@ void genCSS(shared_t *share)
|
||||||
cssText += " color: " + share->webTxt + ";\n";
|
cssText += " color: " + share->webTxt + ";\n";
|
||||||
cssText += "}\n";
|
cssText += "}\n";
|
||||||
|
|
||||||
ofstream file(string(cleanDir(share->webRoot) + "/theme.css").c_str());
|
QFile outFile(QDir().cleanPath(share->webRoot) + "/theme.css");
|
||||||
|
|
||||||
file << cssText << endl;
|
outFile.open(QFile::WriteOnly);
|
||||||
|
outFile.write(cssText.toUtf8());
|
||||||
file.close();
|
outFile.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
void genHTMLul(const string &outputDir, const string &title, shared_t *share);
|
void genHTMLul(const QString &outputDir, const QString &title, shared_t *share);
|
||||||
void genHTMLstream(const string &name);
|
void genHTMLstream(const QString &name);
|
||||||
void genHTMLvod(const string &name);
|
void genHTMLvod(const QString &name);
|
||||||
void genCSS(shared_t *share);
|
void genCSS(shared_t *share);
|
||||||
|
|
||||||
#endif // WEB_H
|
#endif // WEB_H
|
||||||
|
|
Loading…
Reference in New Issue
Block a user