Compare commits
3 Commits
33519acf7a
...
c2b6575800
Author | SHA1 | Date | |
---|---|---|---|
|
c2b6575800 | ||
|
7046dd1162 | ||
|
daad0dffa2 |
|
@ -11,10 +11,16 @@ CONFIG -= app_bundle
|
|||
HEADERS += \
|
||||
src/common.h \
|
||||
src/camera.h \
|
||||
src/services.h
|
||||
src/detect_loop.h \
|
||||
src/event_loop.h \
|
||||
src/proc_control.h \
|
||||
src/record_loop.h
|
||||
|
||||
SOURCES += \
|
||||
src/common.cpp \
|
||||
src/camera.cpp \
|
||||
src/services.cpp \
|
||||
src/detect_loop.cpp \
|
||||
src/event_loop.cpp \
|
||||
src/proc_control.cpp \
|
||||
src/record_loop.cpp \
|
||||
src/main.cpp
|
||||
|
|
13
README.md
13
README.md
|
@ -11,7 +11,7 @@ extremely lightweight with the fact it doesn't attempt to re-implement much
|
|||
of it's functions internally but will instead rely on external applications
|
||||
that already implement the functions very well.
|
||||
|
||||
No user interface is implemented instead exteral applications are more than
|
||||
No user interface is implemented instead external applications are more than
|
||||
welcome to interface with the buffer/footage directories to implement a
|
||||
user interface of any flavor.
|
||||
|
||||
|
@ -156,13 +156,14 @@ img_scale = 320:240
|
|||
# rec_path. it uses width, height numeric strings seperated by a colon,
|
||||
# eg W:H.
|
||||
#
|
||||
service_user = mow
|
||||
service_user = jmotion
|
||||
# this sets the service local user of the application dictating the
|
||||
# amount privilege it will have on the host. if not defined, the
|
||||
# unprivileged user 'mow' will be used. which ever user is defined here
|
||||
# just make sure it has read/write access to buffer_path and rec_path.
|
||||
# amount of privilege it will have on the host. if not defined, the
|
||||
# unprivileged user 'jmotion' will be used. which ever user is defined
|
||||
# here just make sure it has read/write access to buffer_path and
|
||||
# rec_path.
|
||||
#
|
||||
service_group = mow
|
||||
service_group = jmotion
|
||||
# this sets the service local group of the application that can further
|
||||
# refine host privileges. if not defined, the name stored in
|
||||
# service_user will be used.
|
||||
|
|
15
build.py
15
build.py
|
@ -6,6 +6,7 @@ import subprocess
|
|||
import shutil
|
||||
import sys
|
||||
import platform
|
||||
import pwd
|
||||
|
||||
def get_app_target(text):
|
||||
return re.search(r'(APP_TARGET) +(\"(.*?)\")', text).group(3)
|
||||
|
@ -126,6 +127,7 @@ def linux_build_app_dir(app_ver, app_name, app_target, qt_bin):
|
|||
|
||||
verbose_copy("templates/linux_run_script.sh", "app_dir/" + app_target + ".sh")
|
||||
verbose_copy("templates/linux_uninstall.sh", "app_dir/uninstall.sh")
|
||||
verbose_copy("templates/linux_service.service", "app_dir/" + app_target + ".service")
|
||||
|
||||
complete(app_ver, app_target)
|
||||
|
||||
|
@ -168,6 +170,15 @@ def list_of_words_in_text(list_of_words, text_body):
|
|||
|
||||
return True
|
||||
|
||||
def user_exists(user_name):
|
||||
try:
|
||||
pwd.getpwnam(user_name)
|
||||
|
||||
return True
|
||||
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def platform_setup():
|
||||
ins_packages = list_installed_packages()
|
||||
like_distro = get_like_distro()
|
||||
|
@ -188,6 +199,10 @@ def platform_setup():
|
|||
subprocess.run(["sudo", "pacman", "-S", "--noconfirm"] + dep_pkgs_a)
|
||||
subprocess.run(["sudo", "pacman", "-S", "--noconfirm"] + dep_pkgs_b)
|
||||
|
||||
if not user_exists("jmotion"):
|
||||
subprocess.run(["sudo", "useradd", "-r", "jmotion"])
|
||||
subprocess.run(["sudo", "usermod", "-aG", "video", "jmotion"])
|
||||
|
||||
def main():
|
||||
platform_setup()
|
||||
|
||||
|
|
20
install.py
20
install.py
|
@ -8,6 +8,7 @@ import sys
|
|||
import zipfile
|
||||
import binascii
|
||||
import tempfile
|
||||
import pwd
|
||||
|
||||
def cd():
|
||||
current_dir = os.path.dirname(__file__)
|
||||
|
@ -109,6 +110,7 @@ def local_install(app_target, app_name):
|
|||
|
||||
if make_app_dirs(app_target):
|
||||
text_template_deploy("app_dir/" + app_target + ".sh", install_dir + "/" + app_target + ".sh", install_dir, app_name, app_target)
|
||||
text_template_deploy("app_dir/" + app_target + ".service", "/lib/systemd/system/" + app_target + ".service", install_dir, app_name, app_target)
|
||||
text_template_deploy("app_dir/uninstall.sh", install_dir + "/uninstall.sh", install_dir, app_name, app_target)
|
||||
|
||||
verbose_copy("app_dir/" + app_target, install_dir + "/" + app_target)
|
||||
|
@ -119,10 +121,18 @@ def local_install(app_target, app_name):
|
|||
subprocess.run(["chmod", "755", install_dir + "/" + app_target + ".sh"])
|
||||
subprocess.run(["chmod", "755", install_dir + "/" + app_target])
|
||||
subprocess.run(["chmod", "755", install_dir + "/uninstall.sh"])
|
||||
subprocess.run(["chmod", "644", "/lib/systemd/system/" + app_target + ".service"])
|
||||
|
||||
subprocess.run(["chmod", "777", "/var/buffer"])
|
||||
subprocess.run(["chmod", "777", "/var/footage"])
|
||||
|
||||
if not user_exists(app_target):
|
||||
subprocess.run(["sudo", "useradd", "-r", app_target])
|
||||
subprocess.run(["sudo", "usermod", "-aG", "video", app_target])
|
||||
|
||||
subprocess.run(["systemctl", "start", app_target])
|
||||
subprocess.run(["systemctl", "enable", app_target])
|
||||
|
||||
print("Installation finished. If you ever need to uninstall this application, run this command with root rights:")
|
||||
print(" sh " + install_dir + "/uninstall.sh\n")
|
||||
|
||||
|
@ -267,6 +277,15 @@ def list_of_words_in_text(list_of_words, text_body):
|
|||
|
||||
return True
|
||||
|
||||
def user_exists(user_name):
|
||||
try:
|
||||
pwd.getpwnam(user_name)
|
||||
|
||||
return True
|
||||
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def platform_setup():
|
||||
ins_packages = list_installed_packages()
|
||||
like_distro = get_like_distro()
|
||||
|
@ -303,6 +322,7 @@ def main(is_sfx):
|
|||
app_name = info[2]
|
||||
|
||||
if is_sfx:
|
||||
platform_setup()
|
||||
sfx()
|
||||
|
||||
elif "--local" in sys.argv:
|
||||
|
|
433
src/camera.cpp
433
src/camera.cpp
|
@ -12,355 +12,132 @@
|
|||
|
||||
#include "camera.h"
|
||||
|
||||
Camera::Camera(QObject *parent) : QObject(parent) {}
|
||||
|
||||
int Camera::start(const QStringList &args)
|
||||
Camera::Camera(const QString &confFile, const QString &statDir, ProcControl *proc, QCoreApplication *parent) : QObject(nullptr)
|
||||
{
|
||||
if (rdConf(getParam("-c", args), &shared))
|
||||
{
|
||||
auto thr1 = new QThread(nullptr);
|
||||
auto thr2 = new QThread(nullptr);
|
||||
Q_UNUSED(parent);
|
||||
|
||||
new EventLoop(&shared, thr1, nullptr);
|
||||
new DetectLoop(&shared, thr2, nullptr);
|
||||
fsW = new QFileSystemWatcher(this);
|
||||
statTimer = new QTimer(this);
|
||||
statPath = statDir;
|
||||
evtLoop = nullptr;
|
||||
detLoop = nullptr;
|
||||
recLoop = nullptr;
|
||||
delOnZero = false;
|
||||
objCount = 0;
|
||||
|
||||
statTimer->setInterval(5000);
|
||||
statTimer->start();
|
||||
proc->objPlusOne();
|
||||
|
||||
connect(fsW, &QFileSystemWatcher::fileChanged, this, &Camera::confChanged);
|
||||
connect(proc, &ProcControl::prepForClose, this, &Camera::prepForDel);
|
||||
connect(this, &Camera::destroyed, proc, &ProcControl::objMinusOne);
|
||||
connect(statTimer, &QTimer::timeout, this, &Camera::updateStat);
|
||||
|
||||
start(confFile);
|
||||
}
|
||||
|
||||
void Camera::objMinusOne()
|
||||
{
|
||||
objCount--;
|
||||
|
||||
if (objCount == 0)
|
||||
{
|
||||
thr1->deleteLater();
|
||||
thr2->deleteLater();
|
||||
thr3->deleteLater();
|
||||
|
||||
if (delOnZero)
|
||||
{
|
||||
QDir(shared.buffPath).removeRecursively();
|
||||
|
||||
deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::prepForDel()
|
||||
{
|
||||
statTimer->blockSignals(true);
|
||||
|
||||
delOnZero = true;
|
||||
|
||||
emit stop();
|
||||
}
|
||||
|
||||
void Camera::updateStat()
|
||||
{
|
||||
auto statFile = statPath + "/" + shared.camName;
|
||||
|
||||
QFile file(statFile);
|
||||
|
||||
file.open(QFile::WriteOnly);
|
||||
file.write(statusLine().toUtf8());
|
||||
file.close();
|
||||
}
|
||||
|
||||
int Camera::start(const QString &conf)
|
||||
{
|
||||
if (rdConf(conf, &shared))
|
||||
{
|
||||
setupBuffDir(shared.buffPath, true);
|
||||
|
||||
if (!fsW->files().contains(conf))
|
||||
{
|
||||
fsW->addPath(conf);
|
||||
}
|
||||
|
||||
thr1 = new QThread(nullptr);
|
||||
thr2 = new QThread(nullptr);
|
||||
thr3 = new QThread(nullptr);
|
||||
|
||||
evtLoop = new EventLoop(&shared, thr1, nullptr);
|
||||
detLoop = new DetectLoop(&shared, thr2, nullptr);
|
||||
recLoop = new RecordLoop(&shared, thr3, nullptr);
|
||||
|
||||
connect(this, &Camera::stop, thr1, &QThread::quit);
|
||||
connect(this, &Camera::stop, thr2, &QThread::quit);
|
||||
connect(this, &Camera::stop, thr3, &QThread::quit);
|
||||
|
||||
connect(thr1, &QThread::finished, this, &Camera::objMinusOne);
|
||||
connect(thr2, &QThread::finished, this, &Camera::objMinusOne);
|
||||
connect(thr3, &QThread::finished, this, &Camera::objMinusOne);
|
||||
|
||||
connect(detLoop, &DetectLoop::starving, recLoop, &RecordLoop::restart);
|
||||
|
||||
thr1->start();
|
||||
thr2->start();
|
||||
thr3->start();
|
||||
|
||||
objCount = 3;
|
||||
}
|
||||
|
||||
return shared.retCode;
|
||||
}
|
||||
|
||||
EventLoop::EventLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(parent)
|
||||
void Camera::confChanged(const QString &path)
|
||||
{
|
||||
shared = sharedRes;
|
||||
heartBeat = 2;
|
||||
loopTimer = 0;
|
||||
emit stop();
|
||||
|
||||
connect(thr, &QThread::started, this, &EventLoop::init);
|
||||
|
||||
moveToThread(thr);
|
||||
}
|
||||
|
||||
void EventLoop::init()
|
||||
{
|
||||
loopTimer = new QTimer(this);
|
||||
|
||||
connect(loopTimer, &QTimer::timeout, this, &EventLoop::loopSlot);
|
||||
|
||||
loopTimer->setSingleShot(false);
|
||||
loopTimer->start(heartBeat * 1000);
|
||||
|
||||
loopSlot();
|
||||
}
|
||||
|
||||
void EventLoop::loopSlot()
|
||||
{
|
||||
if (!exec())
|
||||
if (!QFileInfo::exists(path))
|
||||
{
|
||||
loopTimer->stop(); QCoreApplication::exit(shared->retCode);
|
||||
}
|
||||
}
|
||||
|
||||
bool EventLoop::wrOutVod(const evt_t &event)
|
||||
{
|
||||
auto ret = false;
|
||||
auto cnt = 0;
|
||||
auto concat = shared->buffPath + "/live/" + event.timeStamp + ".ctmp";
|
||||
|
||||
QFile file(concat, this);
|
||||
|
||||
file.open(QFile::WriteOnly);
|
||||
|
||||
for (auto &&vid : event.vidList)
|
||||
{
|
||||
QTextStream(stdout) << "event_src: " << vid << Qt::endl;
|
||||
|
||||
if (QFile::exists(vid))
|
||||
{
|
||||
file.write(QString("file '" + vid + "'\n").toUtf8()); cnt++;
|
||||
}
|
||||
else
|
||||
{
|
||||
QTextStream(stdout) << "warning: the event hls clip does not exists." << Qt::endl;
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
if (cnt == 0)
|
||||
{
|
||||
QTextStream(stderr) << "err: none of the event hls clips exists, cancelling write out." << Qt::endl;
|
||||
|
||||
QFile::remove(concat);
|
||||
deleteLater();
|
||||
}
|
||||
else
|
||||
{
|
||||
QStringList args;
|
||||
auto ret = start(path);
|
||||
|
||||
args << "-f";
|
||||
args << "concat";
|
||||
args << "-safe" << "0";
|
||||
args << "-i" << concat;
|
||||
args << "-c" << "copy";
|
||||
args << shared->recPath + "/" + event.timeStamp + shared->recExt;
|
||||
|
||||
if (QProcess::execute("ffmpeg", args) == 0)
|
||||
if (ret != 0)
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
|
||||
QFile::remove(concat);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool EventLoop::exec()
|
||||
{
|
||||
enforceMaxEvents(shared);
|
||||
enforceMaxImages(shared);
|
||||
enforceMaxClips(shared);
|
||||
|
||||
if (!shared->recList.isEmpty())
|
||||
{
|
||||
auto event = shared->recList.takeFirst();
|
||||
|
||||
QTextStream(stdout) << "attempting write out of event: " << event.timeStamp << Qt::endl;
|
||||
|
||||
if (wrOutVod(event))
|
||||
{
|
||||
QStringList args;
|
||||
|
||||
args << "convert";
|
||||
args << event.imgPath;
|
||||
args << shared->recPath + "/" + event.timeStamp + shared->thumbExt;
|
||||
|
||||
QProcess::execute("magick", args);
|
||||
}
|
||||
}
|
||||
|
||||
return shared->retCode == 0;
|
||||
}
|
||||
|
||||
DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QFileSystemWatcher(parent)
|
||||
{
|
||||
pcTimer = 0;
|
||||
shared = sharedRes;
|
||||
|
||||
connect(thr, &QThread::started, this, &DetectLoop::init);
|
||||
|
||||
moveToThread(thr);
|
||||
}
|
||||
|
||||
void DetectLoop::init()
|
||||
{
|
||||
pcTimer = new QTimer(this);
|
||||
|
||||
connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak);
|
||||
connect(this, &QFileSystemWatcher::directoryChanged, this, &DetectLoop::updated);
|
||||
|
||||
addPath(shared->buffPath + "/live");
|
||||
|
||||
pcTimer->start(shared->postSecs * 1000);
|
||||
}
|
||||
|
||||
void DetectLoop::reset()
|
||||
{
|
||||
eventQue.inQue = false;
|
||||
eventQue.score = 0;
|
||||
eventQue.queAge = 0;
|
||||
|
||||
eventQue.imgPath.clear();
|
||||
eventQue.vidList.clear();
|
||||
eventQue.timeStamp.clear();
|
||||
}
|
||||
|
||||
void DetectLoop::updated(const QString &path)
|
||||
{
|
||||
auto clips = lsFilesInDir(path, shared->streamExt);
|
||||
auto index = clips.indexOf(vidBName);
|
||||
|
||||
if (clips.size() - (index + 1) < 3)
|
||||
{
|
||||
thread()->sleep(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
vidAName = clips[clips.size() - 3];
|
||||
vidBName = clips[clips.size() - 2];
|
||||
|
||||
vidAPath = shared->buffPath + "/live/" + vidAName;
|
||||
vidBPath = shared->buffPath + "/live/" + vidBName;
|
||||
|
||||
exec(); thread()->sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void DetectLoop::pcBreak()
|
||||
{
|
||||
prevClips.clear();
|
||||
|
||||
if (!shared->postCmd.isEmpty())
|
||||
{
|
||||
QTextStream(stdout) << "---POST_BREAK---" << Qt::endl;
|
||||
|
||||
if (eventQue.inQue)
|
||||
{
|
||||
QTextStream(stdout) << "motion detected, skipping the post command." << Qt::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
QTextStream(stdout) << "no motion detected, running post command: " << shared->postCmd << Qt::endl;
|
||||
|
||||
auto args = parseArgs(shared->postCmd.toUtf8(), -1);
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
QTextStream(stderr) << "err: did not parse an executable from the post command line." << Qt::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
QProcess::execute(args[0], args.mid(1));
|
||||
}
|
||||
deleteLater();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float DetectLoop::getFloatFromExe(const QByteArray &line)
|
||||
QString Camera::statusLine()
|
||||
{
|
||||
QString strLine(line);
|
||||
QString strNum;
|
||||
|
||||
for (auto chr : strLine)
|
||||
{
|
||||
if (chr.isDigit() || (chr == '.'))
|
||||
{
|
||||
strNum.append(chr);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto ok = false;
|
||||
auto res = strNum.toFloat(&ok);
|
||||
|
||||
if (!ok || strNum.isEmpty())
|
||||
{
|
||||
QTextStream(stderr) << "err: the image comp command returned unexpected output and couldn't be converted to float." << Qt::endl;
|
||||
QTextStream(stderr) << " raw output: " << line << Qt::endl;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
QStringList DetectLoop::buildArgs(const QString &prev, const QString &next)
|
||||
{
|
||||
auto args = parseArgs(shared->compCmd.toUtf8(), -1);
|
||||
|
||||
for (auto i = 0; i < args.size(); ++i)
|
||||
{
|
||||
if (args[i] == PREV_IMG) args[i] = prev;
|
||||
if (args[i] == NEXT_IMG) args[i] = next;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
QStringList DetectLoop::buildSnapArgs(const QString &vidSrc, const QString &imgPath)
|
||||
{
|
||||
QStringList ret;
|
||||
|
||||
ret.append("-hide_banner");
|
||||
ret.append("-loglevel");
|
||||
ret.append("panic");
|
||||
ret.append("-y");
|
||||
ret.append("-i");
|
||||
ret.append(vidSrc);
|
||||
ret.append("-frames:v");
|
||||
ret.append("1");
|
||||
ret.append(imgPath);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DetectLoop::exec()
|
||||
{
|
||||
auto imgAPath = shared->buffPath + "/img/" + QFileInfo(vidAPath).baseName() + ".bmp";
|
||||
auto imgBPath = shared->buffPath + "/img/" + QFileInfo(vidBPath).baseName() + ".bmp";
|
||||
auto snapArgsA = buildSnapArgs(vidAPath, imgAPath);
|
||||
auto snapArgsB = buildSnapArgs(vidBPath, imgBPath);
|
||||
auto compArgs = buildArgs(imgAPath, imgBPath);
|
||||
|
||||
if (compArgs.isEmpty())
|
||||
{
|
||||
QTextStream(stderr) << "err: could not parse a executable name from img_comp_cmd: " << shared->compCmd << Qt::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
QProcess::execute("ffmpeg", snapArgsA);
|
||||
QProcess::execute("ffmpeg", snapArgsB);
|
||||
|
||||
if (QFile::exists(imgAPath) && QFile::exists(imgBPath))
|
||||
{
|
||||
QProcess extComp;
|
||||
|
||||
extComp.start(compArgs[0], compArgs.mid(1));
|
||||
extComp.waitForFinished();
|
||||
|
||||
float score = 0;
|
||||
|
||||
if (shared->outputType == "stdout")
|
||||
{
|
||||
score = getFloatFromExe(extComp.readAllStandardOutput());
|
||||
}
|
||||
else
|
||||
{
|
||||
score = getFloatFromExe(extComp.readAllStandardError());
|
||||
}
|
||||
|
||||
QTextStream(stdout) << compArgs.join(" ") << " --result: " << QString::number(score) << Qt::endl;
|
||||
|
||||
if (eventQue.inQue)
|
||||
{
|
||||
eventQue.queAge += 4;
|
||||
|
||||
if (eventQue.score < score)
|
||||
{
|
||||
eventQue.score = score;
|
||||
eventQue.imgPath = imgBPath;
|
||||
}
|
||||
|
||||
eventQue.vidList.append(vidAPath);
|
||||
eventQue.vidList.append(vidBPath);
|
||||
|
||||
if (eventQue.queAge >= shared->evMaxSecs)
|
||||
{
|
||||
eventQue.inQue = false;
|
||||
|
||||
shared->recList.append(eventQue);
|
||||
|
||||
reset();
|
||||
}
|
||||
}
|
||||
else if (score >= shared->imgThresh)
|
||||
{
|
||||
QTextStream(stdout) << "--threshold_meet: " << QString::number(shared->imgThresh) << Qt::endl;
|
||||
|
||||
eventQue.score = score;
|
||||
eventQue.imgPath = imgBPath;
|
||||
eventQue.inQue = true;
|
||||
eventQue.queAge = 0;
|
||||
eventQue.timeStamp = QDateTime::currentDateTime().toString(DATETIME_FMT);
|
||||
|
||||
eventQue.vidList.append(vidAPath);
|
||||
eventQue.vidList.append(vidBPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vidAPath.clear();
|
||||
vidBPath.clear();
|
||||
return "Detection: " + detLoop->statusLine() +
|
||||
" Event-Scraping: " + evtLoop->statusLine() +
|
||||
" Recording: " + recLoop->statusLine() +
|
||||
" Name: " + shared.camName + "\n";
|
||||
}
|
||||
|
|
80
src/camera.h
80
src/camera.h
|
@ -14,6 +14,10 @@
|
|||
// GNU General Public License for more details.
|
||||
|
||||
#include "common.h"
|
||||
#include "event_loop.h"
|
||||
#include "detect_loop.h"
|
||||
#include "record_loop.h"
|
||||
#include "proc_control.h"
|
||||
|
||||
class Camera : public QObject
|
||||
{
|
||||
|
@ -21,70 +25,36 @@ class Camera : public QObject
|
|||
|
||||
private:
|
||||
|
||||
shared_t shared;
|
||||
|
||||
public:
|
||||
|
||||
explicit Camera(QObject *parent = nullptr);
|
||||
|
||||
int start(const QStringList &args);
|
||||
};
|
||||
|
||||
class EventLoop : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QFileSystemWatcher *fsW;
|
||||
EventLoop *evtLoop;
|
||||
DetectLoop *detLoop;
|
||||
RecordLoop *recLoop;
|
||||
QThread *thr1;
|
||||
QThread *thr2;
|
||||
QThread *thr3;
|
||||
QTimer *statTimer;
|
||||
QString statPath;
|
||||
shared_t shared;
|
||||
uint objCount;
|
||||
bool delOnZero;
|
||||
|
||||
private slots:
|
||||
|
||||
void init();
|
||||
void loopSlot();
|
||||
|
||||
private:
|
||||
|
||||
shared_t *shared;
|
||||
QTimer *loopTimer;
|
||||
int heartBeat;
|
||||
|
||||
bool wrOutVod(const evt_t &event);
|
||||
void confChanged(const QString &path);
|
||||
void objMinusOne();
|
||||
void updateStat();
|
||||
void prepForDel();
|
||||
|
||||
public:
|
||||
|
||||
explicit EventLoop(shared_t *shared, QThread *thr, QObject *parent = nullptr);
|
||||
explicit Camera(const QString &confFile, const QString &statDir, ProcControl *proc, QCoreApplication *parent);
|
||||
|
||||
bool exec();
|
||||
};
|
||||
int start(const QString &conf);
|
||||
QString statusLine();
|
||||
|
||||
class DetectLoop : public QFileSystemWatcher
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
|
||||
private:
|
||||
|
||||
QString vidAPath;
|
||||
QString vidBPath;
|
||||
QString vidAName;
|
||||
QString vidBName;
|
||||
QStringList prevClips;
|
||||
QTimer *pcTimer;
|
||||
evt_t eventQue;
|
||||
shared_t *shared;
|
||||
|
||||
float getFloatFromExe(const QByteArray &line);
|
||||
QStringList buildArgs(const QString &prev, const QString &next);
|
||||
QStringList buildSnapArgs(const QString &vidSrc, const QString &imgPath);
|
||||
|
||||
private slots:
|
||||
|
||||
void init();
|
||||
void reset();
|
||||
void pcBreak();
|
||||
void updated(const QString &path);
|
||||
|
||||
public:
|
||||
|
||||
explicit DetectLoop(shared_t *shared, QThread *thr, QObject *parent = nullptr);
|
||||
|
||||
void exec();
|
||||
void stop();
|
||||
};
|
||||
|
||||
#endif // CAMERA_H
|
||||
|
|
|
@ -12,43 +12,6 @@
|
|||
|
||||
#include "common.h"
|
||||
|
||||
QString getParam(const QString &key, const QStringList &args)
|
||||
{
|
||||
// this can be used by command objects to pick out parameters
|
||||
// from a command line that are pointed by a name identifier
|
||||
// example: -i /etc/some_file, this function should pick out
|
||||
// "/etc/some_file" from args if "-i" is passed into key.
|
||||
|
||||
QString ret;
|
||||
|
||||
int pos = args.indexOf(QRegularExpression(key, QRegularExpression::CaseInsensitiveOption));
|
||||
|
||||
if (pos != -1)
|
||||
{
|
||||
// key found.
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
QStringList lsFilesInDir(const QString &path, const QString &ext)
|
||||
{
|
||||
QStringList filters;
|
||||
|
@ -212,7 +175,6 @@ bool rdConf(const QString &filePath, shared_t *share)
|
|||
share->camName.clear();
|
||||
share->buffPath.clear();
|
||||
share->recPath.clear();
|
||||
share->servGroup.clear();
|
||||
|
||||
share->retCode = 0;
|
||||
share->imgThresh = 8000;
|
||||
|
@ -232,7 +194,6 @@ bool rdConf(const QString &filePath, shared_t *share)
|
|||
share->liveSecs = 80;
|
||||
share->recScale = "1280:720";
|
||||
share->imgScale = "320:240";
|
||||
share->servUser = APP_TARGET;
|
||||
|
||||
QString line;
|
||||
|
||||
|
@ -261,8 +222,6 @@ bool rdConf(const QString &filePath, shared_t *share)
|
|||
rdLine("rec_fps = ", line, &share->recFps);
|
||||
rdLine("rec_scale = ", line, &share->recScale);
|
||||
rdLine("img_scale = ", line, &share->imgScale);
|
||||
rdLine("service_user = ", line, &share->servUser);
|
||||
rdLine("service_group = ", line, &share->servGroup);
|
||||
rdLine("live_secs = ", line, &share->liveSecs);
|
||||
}
|
||||
|
||||
|
@ -309,19 +268,34 @@ bool rdConf(const QString &filePath, shared_t *share)
|
|||
{
|
||||
share->evMaxSecs = share->liveSecs - 4;
|
||||
}
|
||||
|
||||
if (share->servGroup.isEmpty())
|
||||
{
|
||||
share->servGroup = share->servUser;
|
||||
}
|
||||
|
||||
share->servMainLoop = QString(APP_TARGET) + ".main_loop." + share->camName;
|
||||
share->servVidLoop = QString(APP_TARGET) + ".vid_loop." + share->camName;
|
||||
}
|
||||
|
||||
return share->retCode == 0;
|
||||
}
|
||||
|
||||
void setupBuffDir(const QString &path, bool del)
|
||||
{
|
||||
if (del)
|
||||
{
|
||||
QDir(path).removeRecursively();
|
||||
}
|
||||
|
||||
if (!QFileInfo::exists(path))
|
||||
{
|
||||
mkPath(path);
|
||||
}
|
||||
|
||||
if (!QFileInfo::exists(path + "/live"))
|
||||
{
|
||||
mkPath(path + "/live");
|
||||
}
|
||||
|
||||
if (!QFileInfo::exists(path + "/img"))
|
||||
{
|
||||
mkPath(path + "/img");
|
||||
}
|
||||
}
|
||||
|
||||
QString buildThreadCount(int count)
|
||||
{
|
||||
QString ret = "0";
|
||||
|
|
10
src/common.h
10
src/common.h
|
@ -26,11 +26,13 @@
|
|||
#include <QMutex>
|
||||
#include <QRegularExpression>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QDebug>
|
||||
#include <QElapsedTimer>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define APP_VERSION "3.4"
|
||||
#define APP_VERSION "3.5"
|
||||
#define APP_NAME "JustMotion"
|
||||
#define APP_TARGET "jmotion"
|
||||
#define DATETIME_FMT "yyyyMMddhhmmss"
|
||||
|
@ -72,10 +74,6 @@ struct shared_t
|
|||
QString thumbExt;
|
||||
QString recScale;
|
||||
QString imgScale;
|
||||
QString servMainLoop;
|
||||
QString servVidLoop;
|
||||
QString servUser;
|
||||
QString servGroup;
|
||||
bool singleTenant;
|
||||
bool skipCmd;
|
||||
int liveSecs;
|
||||
|
@ -87,7 +85,6 @@ struct shared_t
|
|||
int retCode;
|
||||
};
|
||||
|
||||
QString getParam(const QString &key, const QStringList &args);
|
||||
QString buildThreadCount(int count);
|
||||
QStringList lsFilesInDir(const QString &path, const QString &ext = QString());
|
||||
QStringList lsDirsInDir(const QString &path);
|
||||
|
@ -97,6 +94,7 @@ QStringList forwardFacingFiles(const QString &path, const QString &ext, const QD
|
|||
QStringList parseArgs(const QByteArray &data, int maxArgs, int *pos = nullptr);
|
||||
bool rdConf(const QString &filePath, shared_t *share);
|
||||
bool mkPath(const QString &path);
|
||||
void setupBuffDir(const QString &path, bool del = false);
|
||||
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, bool *value);
|
||||
|
|
253
src/detect_loop.cpp
Normal file
253
src/detect_loop.cpp
Normal file
|
@ -0,0 +1,253 @@
|
|||
// This file is part of JustMotion.
|
||||
|
||||
// JustMotion 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.
|
||||
|
||||
// JustMotion 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 "detect_loop.h"
|
||||
|
||||
DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QFileSystemWatcher(parent)
|
||||
{
|
||||
pcTimer = 0;
|
||||
shared = sharedRes;
|
||||
|
||||
connect(thr, &QThread::started, this, &DetectLoop::init);
|
||||
connect(thr, &QThread::finished, this, &DetectLoop::deleteLater);
|
||||
|
||||
moveToThread(thr);
|
||||
}
|
||||
|
||||
void DetectLoop::init()
|
||||
{
|
||||
pcTimer = new QTimer(this);
|
||||
|
||||
connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak);
|
||||
connect(this, &QFileSystemWatcher::directoryChanged, this, &DetectLoop::updated);
|
||||
|
||||
pcTimer->start(shared->postSecs * 1000);
|
||||
|
||||
setupBuffDir(shared->buffPath);
|
||||
addPath(shared->buffPath + "/live");
|
||||
}
|
||||
|
||||
void DetectLoop::reset()
|
||||
{
|
||||
eventQue.inQue = false;
|
||||
eventQue.score = 0;
|
||||
eventQue.queAge = 0;
|
||||
|
||||
eventQue.imgPath.clear();
|
||||
eventQue.vidList.clear();
|
||||
eventQue.timeStamp.clear();
|
||||
}
|
||||
|
||||
void DetectLoop::updated(const QString &path)
|
||||
{
|
||||
eTimer.start();
|
||||
|
||||
auto clips = lsFilesInDir(path, shared->streamExt);
|
||||
auto index = clips.indexOf(vidBName);
|
||||
|
||||
if (clips.size() - (index + 1) < 3)
|
||||
{
|
||||
thread()->sleep(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
vidAName = clips[clips.size() - 3];
|
||||
vidBName = clips[clips.size() - 2];
|
||||
|
||||
vidAPath = shared->buffPath + "/live/" + vidAName;
|
||||
vidBPath = shared->buffPath + "/live/" + vidBName;
|
||||
|
||||
exec(); thread()->sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void DetectLoop::pcBreak()
|
||||
{
|
||||
prevClips.clear();
|
||||
|
||||
if (!shared->postCmd.isEmpty())
|
||||
{
|
||||
qInfo() << "---POST_BREAK---";
|
||||
|
||||
if (eventQue.inQue)
|
||||
{
|
||||
qInfo() << "motion detected, skipping the post command";
|
||||
}
|
||||
else
|
||||
{
|
||||
qInfo() << "no motion detected, running post command: " << shared->postCmd;
|
||||
|
||||
auto args = parseArgs(shared->postCmd.toUtf8(), -1);
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
qCritical() << "err: did not parse an executable from the post command line.";
|
||||
}
|
||||
else
|
||||
{
|
||||
QProcess::execute(args[0], args.mid(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float DetectLoop::getFloatFromExe(const QByteArray &line)
|
||||
{
|
||||
QString strLine(line);
|
||||
QString strNum;
|
||||
|
||||
for (auto chr : strLine)
|
||||
{
|
||||
if (chr.isDigit() || (chr == '.'))
|
||||
{
|
||||
strNum.append(chr);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto ok = false;
|
||||
auto res = strNum.toFloat(&ok);
|
||||
|
||||
if (!ok || strNum.isEmpty())
|
||||
{
|
||||
qCritical() << "err: the image comp command returned unexpected output and couldn't be converted to float";
|
||||
qCritical() << " raw output: " << line;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
QStringList DetectLoop::buildArgs(const QString &prev, const QString &next)
|
||||
{
|
||||
auto args = parseArgs(shared->compCmd.toUtf8(), -1);
|
||||
|
||||
for (auto i = 0; i < args.size(); ++i)
|
||||
{
|
||||
if (args[i] == PREV_IMG) args[i] = prev;
|
||||
if (args[i] == NEXT_IMG) args[i] = next;
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
QStringList DetectLoop::buildSnapArgs(const QString &vidSrc, const QString &imgPath)
|
||||
{
|
||||
QStringList ret;
|
||||
|
||||
ret.append("-hide_banner");
|
||||
ret.append("-loglevel");
|
||||
ret.append("panic");
|
||||
ret.append("-y");
|
||||
ret.append("-i");
|
||||
ret.append(vidSrc);
|
||||
ret.append("-frames:v");
|
||||
ret.append("1");
|
||||
ret.append(imgPath);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString DetectLoop::statusLine()
|
||||
{
|
||||
if (eTimer.elapsed() >= 5000)
|
||||
{
|
||||
emit starving();
|
||||
|
||||
return "STARVED";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "OK ";
|
||||
}
|
||||
}
|
||||
|
||||
void DetectLoop::exec()
|
||||
{
|
||||
auto imgAPath = shared->buffPath + "/img/" + QFileInfo(vidAPath).baseName() + ".bmp";
|
||||
auto imgBPath = shared->buffPath + "/img/" + QFileInfo(vidBPath).baseName() + ".bmp";
|
||||
auto snapArgsA = buildSnapArgs(vidAPath, imgAPath);
|
||||
auto snapArgsB = buildSnapArgs(vidBPath, imgBPath);
|
||||
auto compArgs = buildArgs(imgAPath, imgBPath);
|
||||
|
||||
if (compArgs.isEmpty())
|
||||
{
|
||||
qCritical() << "err: could not parse a executable name from img_comp_cmd: " << shared->compCmd;
|
||||
}
|
||||
else
|
||||
{
|
||||
QProcess::execute("ffmpeg", snapArgsA);
|
||||
QProcess::execute("ffmpeg", snapArgsB);
|
||||
|
||||
if (QFile::exists(imgAPath) && QFile::exists(imgBPath))
|
||||
{
|
||||
QProcess extComp;
|
||||
|
||||
extComp.start(compArgs[0], compArgs.mid(1));
|
||||
extComp.waitForFinished();
|
||||
|
||||
float score = 0;
|
||||
|
||||
if (shared->outputType == "stdout")
|
||||
{
|
||||
score = getFloatFromExe(extComp.readAllStandardOutput());
|
||||
}
|
||||
else
|
||||
{
|
||||
score = getFloatFromExe(extComp.readAllStandardError());
|
||||
}
|
||||
|
||||
qInfo() << compArgs.join(" ") << " --result: " << QString::number(score);
|
||||
|
||||
if (eventQue.inQue)
|
||||
{
|
||||
eventQue.queAge += 4;
|
||||
|
||||
if (eventQue.score < score)
|
||||
{
|
||||
eventQue.score = score;
|
||||
eventQue.imgPath = imgBPath;
|
||||
}
|
||||
|
||||
eventQue.vidList.append(vidAPath);
|
||||
eventQue.vidList.append(vidBPath);
|
||||
|
||||
if (eventQue.queAge >= shared->evMaxSecs)
|
||||
{
|
||||
eventQue.inQue = false;
|
||||
|
||||
shared->recList.append(eventQue);
|
||||
|
||||
reset();
|
||||
}
|
||||
}
|
||||
else if (score >= shared->imgThresh)
|
||||
{
|
||||
qInfo() << "--threshold_meet: " << QString::number(shared->imgThresh);
|
||||
|
||||
eventQue.score = score;
|
||||
eventQue.imgPath = imgBPath;
|
||||
eventQue.inQue = true;
|
||||
eventQue.queAge = 0;
|
||||
eventQue.timeStamp = QDateTime::currentDateTime().toString(DATETIME_FMT);
|
||||
|
||||
eventQue.vidList.append(vidAPath);
|
||||
eventQue.vidList.append(vidBPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vidAPath.clear();
|
||||
vidBPath.clear();
|
||||
}
|
57
src/detect_loop.h
Normal file
57
src/detect_loop.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
#ifndef DETECT_LOOP_H
|
||||
#define DETECT_LOOP_H
|
||||
|
||||
// This file is part of JustMotion.
|
||||
|
||||
// JustMotion 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.
|
||||
|
||||
// JustMotion 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"
|
||||
|
||||
class DetectLoop : public QFileSystemWatcher
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
|
||||
QString vidAPath;
|
||||
QString vidBPath;
|
||||
QString vidAName;
|
||||
QString vidBName;
|
||||
QStringList prevClips;
|
||||
QTimer *pcTimer;
|
||||
QElapsedTimer eTimer;
|
||||
evt_t eventQue;
|
||||
shared_t *shared;
|
||||
|
||||
float getFloatFromExe(const QByteArray &line);
|
||||
QStringList buildArgs(const QString &prev, const QString &next);
|
||||
QStringList buildSnapArgs(const QString &vidSrc, const QString &imgPath);
|
||||
|
||||
private slots:
|
||||
|
||||
void init();
|
||||
void reset();
|
||||
void pcBreak();
|
||||
void updated(const QString &path);
|
||||
|
||||
public:
|
||||
|
||||
explicit DetectLoop(shared_t *shared, QThread *thr, QObject *parent = nullptr);
|
||||
|
||||
void exec();
|
||||
QString statusLine();
|
||||
|
||||
signals:
|
||||
|
||||
void starving();
|
||||
};
|
||||
|
||||
#endif // DETECT_LOOP_H
|
138
src/event_loop.cpp
Normal file
138
src/event_loop.cpp
Normal file
|
@ -0,0 +1,138 @@
|
|||
// This file is part of JustMotion.
|
||||
|
||||
// JustMotion 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.
|
||||
|
||||
// JustMotion 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 "event_loop.h"
|
||||
|
||||
EventLoop::EventLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(parent)
|
||||
{
|
||||
shared = sharedRes;
|
||||
heartBeat = 2;
|
||||
loopTimer = 0;
|
||||
|
||||
connect(thr, &QThread::started, this, &EventLoop::init);
|
||||
connect(thr, &QThread::finished, this, &EventLoop::deleteLater);
|
||||
|
||||
moveToThread(thr);
|
||||
}
|
||||
|
||||
void EventLoop::init()
|
||||
{
|
||||
loopTimer = new QTimer(this);
|
||||
|
||||
connect(loopTimer, &QTimer::timeout, this, &EventLoop::loopSlot);
|
||||
|
||||
loopTimer->setSingleShot(false);
|
||||
loopTimer->start(heartBeat * 1000);
|
||||
|
||||
loopSlot();
|
||||
}
|
||||
|
||||
void EventLoop::loopSlot()
|
||||
{
|
||||
if (!exec())
|
||||
{
|
||||
loopTimer->stop(); QCoreApplication::exit(shared->retCode);
|
||||
}
|
||||
}
|
||||
|
||||
bool EventLoop::wrOutVod(const evt_t &event)
|
||||
{
|
||||
auto ret = false;
|
||||
auto cnt = 0;
|
||||
auto concat = shared->buffPath + "/live/" + event.timeStamp + ".ctmp";
|
||||
|
||||
QFile file(concat, this);
|
||||
|
||||
file.open(QFile::WriteOnly);
|
||||
|
||||
for (auto &&vid : event.vidList)
|
||||
{
|
||||
qInfo() << "event_src: " << vid;
|
||||
|
||||
if (QFile::exists(vid))
|
||||
{
|
||||
file.write(QString("file '" + vid + "'\n").toUtf8()); cnt++;
|
||||
}
|
||||
else
|
||||
{
|
||||
qInfo() << "warning: the event video clip does not exists";
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
if (cnt == 0)
|
||||
{
|
||||
qCritical() << "err: none of the event hls clips exists, cancelling write out";
|
||||
|
||||
QFile::remove(concat);
|
||||
}
|
||||
else
|
||||
{
|
||||
QStringList args;
|
||||
|
||||
args << "-f";
|
||||
args << "concat";
|
||||
args << "-safe" << "0";
|
||||
args << "-i" << concat;
|
||||
args << "-c" << "copy";
|
||||
args << shared->recPath + "/" + event.timeStamp + shared->recExt;
|
||||
|
||||
if (QProcess::execute("ffmpeg", args) == 0)
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
|
||||
QFile::remove(concat);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString EventLoop::statusLine()
|
||||
{
|
||||
if (loopTimer->isActive())
|
||||
{
|
||||
return "OK ";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "FAIL";
|
||||
}
|
||||
}
|
||||
|
||||
bool EventLoop::exec()
|
||||
{
|
||||
enforceMaxEvents(shared);
|
||||
enforceMaxImages(shared);
|
||||
enforceMaxClips(shared);
|
||||
|
||||
if (!shared->recList.isEmpty())
|
||||
{
|
||||
auto event = shared->recList.takeFirst();
|
||||
|
||||
qInfo() << "attempting write out of event: " << event.timeStamp;
|
||||
|
||||
if (wrOutVod(event))
|
||||
{
|
||||
QStringList args;
|
||||
|
||||
args << "convert";
|
||||
args << event.imgPath;
|
||||
args << shared->recPath + "/" + event.timeStamp + shared->thumbExt;
|
||||
|
||||
QProcess::execute("magick", args);
|
||||
}
|
||||
}
|
||||
|
||||
return shared->retCode == 0;
|
||||
}
|
43
src/event_loop.h
Normal file
43
src/event_loop.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#ifndef EVENT_LOOP_H
|
||||
#define EVENT_LOOP_H
|
||||
|
||||
// This file is part of JustMotion.
|
||||
|
||||
// JustMotion 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.
|
||||
|
||||
// JustMotion 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"
|
||||
|
||||
class EventLoop : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
|
||||
void init();
|
||||
void loopSlot();
|
||||
|
||||
private:
|
||||
|
||||
shared_t *shared;
|
||||
QTimer *loopTimer;
|
||||
int heartBeat;
|
||||
|
||||
bool wrOutVod(const evt_t &event);
|
||||
|
||||
public:
|
||||
|
||||
explicit EventLoop(shared_t *shared, QThread *thr, QObject *parent = nullptr);
|
||||
|
||||
bool exec();
|
||||
QString statusLine();
|
||||
};
|
||||
|
||||
#endif // EVENT_LOOP_H
|
99
src/main.cpp
99
src/main.cpp
|
@ -12,24 +12,23 @@
|
|||
|
||||
#include "common.h"
|
||||
#include "camera.h"
|
||||
#include "services.h"
|
||||
#include "proc_control.h"
|
||||
|
||||
void showHelp(const QString etcDir)
|
||||
{
|
||||
QTextStream(stdout) << APP_NAME << " " << APP_VERSION << Qt::endl << Qt::endl;
|
||||
QTextStream(stdout) << "Usage: " << APP_TARGET << " <argument>" << Qt::endl << Qt::endl;
|
||||
QTextStream(stdout) << "-h : display usage information about this application." << Qt::endl;
|
||||
QTextStream(stdout) << "-c : path to the config file used to run a single main loop instance." << Qt::endl;
|
||||
QTextStream(stdout) << "-i : all valid config files found in " << etcDir << " will be used to create" << Qt::endl;
|
||||
QTextStream(stdout) << " a full instance; full meaning main and vid loop systemd services" << Qt::endl;
|
||||
QTextStream(stdout) << " will be created for each config file." << Qt::endl;
|
||||
QTextStream(stdout) << "-d : this is the same as -i except it will not auto start the services." << Qt::endl;
|
||||
QTextStream(stdout) << "-d : all valid config files found in " << etcDir << " will be used to" << Qt::endl;
|
||||
QTextStream(stdout) << " create camera instances. (this is blocking, meant to run with systemd)" << Qt::endl;
|
||||
QTextStream(stdout) << "-v : display the current version." << Qt::endl;
|
||||
QTextStream(stdout) << "-u : uninstall the entire app from your system, including all" << Qt::endl;
|
||||
QTextStream(stdout) << " systemd services related to it." << Qt::endl;
|
||||
QTextStream(stdout) << "-u : uninstall the entire app from your system, including the service. all" << Qt::endl;
|
||||
QTextStream(stdout) << " recorded footage will remain." << Qt::endl;
|
||||
QTextStream(stdout) << "-f : force an action without pausing for user confirmation." << Qt::endl;
|
||||
QTextStream(stdout) << "-l : list all attached services to this application along with statuses." << Qt::endl;
|
||||
QTextStream(stdout) << "-r : remove all attached services." << Qt::endl;
|
||||
QTextStream(stdout) << "-s : view the status of all camera instances." << Qt::endl;
|
||||
QTextStream(stdout) << "-q : kill all camera instances." << Qt::endl;
|
||||
QTextStream(stdout) << "-r : same as -d except it is non-blocking for starting all camera instances" << Qt::endl;
|
||||
QTextStream(stdout) << " via systemd. same as 'systemctl start " << APP_TARGET << "'" << Qt::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
|
@ -39,9 +38,16 @@ int main(int argc, char** argv)
|
|||
QCoreApplication::setApplicationName(APP_NAME);
|
||||
QCoreApplication::setApplicationVersion(APP_VERSION);
|
||||
|
||||
auto args = QCoreApplication::arguments();
|
||||
auto etcDir = "/etc/" + QString(APP_TARGET);
|
||||
auto ret = 0;
|
||||
auto args = QCoreApplication::arguments();
|
||||
auto etcDir = "/etc/" + QString(APP_TARGET);
|
||||
auto staDir = QDir::tempPath() + "/" + APP_TARGET + "-stats";
|
||||
auto procFile = QDir::tempPath() + "/" + APP_TARGET + "-proc";
|
||||
auto ret = 0;
|
||||
|
||||
if (!QFileInfo::exists(staDir))
|
||||
{
|
||||
mkPath(staDir);
|
||||
}
|
||||
|
||||
if (args.contains("-h"))
|
||||
{
|
||||
|
@ -51,44 +57,30 @@ int main(int argc, char** argv)
|
|||
{
|
||||
QTextStream(stdout) << APP_VERSION << Qt::endl;
|
||||
}
|
||||
else if (args.contains("-l"))
|
||||
else if (args.contains("-d"))
|
||||
{
|
||||
servStatByDir(etcDir);
|
||||
}
|
||||
else if (args.contains("-i") || args.contains("-d"))
|
||||
{
|
||||
ret = rmServiceByDir(etcDir);
|
||||
auto confs = lsFilesInDir(etcDir);
|
||||
auto proc = new ProcControl(procFile, staDir, &app);
|
||||
|
||||
if ((ret == 0) && args.contains("-i"))
|
||||
if (!proc->init())
|
||||
{
|
||||
ret = loadServiceByDir(etcDir, true);
|
||||
ret = EACCES;
|
||||
}
|
||||
else if (ret == 0)
|
||||
else
|
||||
{
|
||||
ret = loadServiceByDir(etcDir, false);
|
||||
}
|
||||
}
|
||||
else if (args.contains("-c"))
|
||||
{
|
||||
auto *cam = new Camera(&app);
|
||||
for (auto &&conf : confs)
|
||||
{
|
||||
new Camera(etcDir + "/" + conf, staDir, proc, &app);
|
||||
}
|
||||
|
||||
ret = cam->start(args);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = QCoreApplication::exec();
|
||||
ret = app.exec();
|
||||
}
|
||||
}
|
||||
else if (args.contains("-u"))
|
||||
{
|
||||
if (args.contains("-f"))
|
||||
{
|
||||
ret = rmServiceByDir(etcDir);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = QProcess::execute("/opt/" + QString(APP_TARGET) + "/uninstall.sh", QStringList());
|
||||
}
|
||||
ret = QProcess::execute("/opt/" + QString(APP_TARGET) + "/uninstall.sh", QStringList());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -99,18 +91,33 @@ int main(int argc, char** argv)
|
|||
|
||||
if (ans == 'y' || ans == 'Y')
|
||||
{
|
||||
ret = rmServiceByDir("/etc/mow");
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
ret = QProcess::execute("/opt/" + QString(APP_TARGET) + "/uninstall.sh", QStringList());
|
||||
}
|
||||
ret = QProcess::execute("/opt/" + QString(APP_TARGET) + "/uninstall.sh", QStringList());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (args.contains("-s"))
|
||||
{
|
||||
auto statFiles = lsFilesInDir(staDir);
|
||||
|
||||
for (auto &&statFile: statFiles)
|
||||
{
|
||||
QFile file(staDir + "/" + statFile);
|
||||
|
||||
file.open(QFile::ReadOnly);
|
||||
|
||||
std::cout << file.readAll().data();
|
||||
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
else if (args.contains("-q"))
|
||||
{
|
||||
QFile::remove(procFile);
|
||||
QThread::currentThread()->sleep(5);
|
||||
}
|
||||
else if (args.contains("-r"))
|
||||
{
|
||||
ret = rmServiceByDir(etcDir);
|
||||
QProcess::execute("systemctl start " + QString(APP_TARGET));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
108
src/proc_control.cpp
Normal file
108
src/proc_control.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
// This file is part of JustMotion.
|
||||
|
||||
// JustMotion 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.
|
||||
|
||||
// JustMotion 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 "proc_control.h"
|
||||
|
||||
ProcControl::ProcControl(const QString &procFile, const QString &statDir, QCoreApplication *parent) : QObject(parent)
|
||||
{
|
||||
fsMon = new QFileSystemWatcher(this);
|
||||
file = procFile;
|
||||
statPath = statDir;
|
||||
closeOnZero = false;
|
||||
objCount = 0;
|
||||
|
||||
connect(fsMon, &QFileSystemWatcher::fileChanged, this, &ProcControl::procFileUpdated);
|
||||
}
|
||||
|
||||
bool ProcControl::init()
|
||||
{
|
||||
auto ret = false;
|
||||
|
||||
if (!QFileInfo::exists(file))
|
||||
{
|
||||
QFile fObj(file);
|
||||
|
||||
if (!fObj.open(QFile::WriteOnly))
|
||||
{
|
||||
QTextStream(stderr) << "err: Failed to open process control file for writing: " << file << " reason: " << fObj.errorString();
|
||||
}
|
||||
else
|
||||
{
|
||||
fObj.write("##");
|
||||
fObj.close();
|
||||
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QTextStream(stdout) << "wrn: " << file << " already exists so it will be removed, this will cause any other existing instance to close.";
|
||||
|
||||
if (!QFile::remove(file))
|
||||
{
|
||||
QTextStream(stderr) << "err: Failed to remove process control file: " << file << " check permissions.";
|
||||
}
|
||||
else
|
||||
{
|
||||
thread()->sleep(3);
|
||||
|
||||
ret = init();
|
||||
}
|
||||
}
|
||||
|
||||
if (ret)
|
||||
{
|
||||
fsMon->addPath(file);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ProcControl::procFileUpdated(const QString &path)
|
||||
{
|
||||
fsMon->removePath(path);
|
||||
|
||||
QFile::remove(path);
|
||||
|
||||
closeApp();
|
||||
}
|
||||
|
||||
void ProcControl::closeApp()
|
||||
{
|
||||
if (objCount == 0)
|
||||
{
|
||||
QDir(statPath).removeRecursively();
|
||||
|
||||
QCoreApplication::instance()->quit();
|
||||
}
|
||||
else
|
||||
{
|
||||
closeOnZero = true;
|
||||
|
||||
emit prepForClose();
|
||||
}
|
||||
}
|
||||
|
||||
void ProcControl::objPlusOne()
|
||||
{
|
||||
objCount++;
|
||||
}
|
||||
|
||||
void ProcControl::objMinusOne()
|
||||
{
|
||||
objCount--;
|
||||
|
||||
if (closeOnZero && (objCount == 0))
|
||||
{
|
||||
closeApp();
|
||||
}
|
||||
}
|
51
src/proc_control.h
Normal file
51
src/proc_control.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
#ifndef PROC_CONTROL_H
|
||||
#define PROC_CONTROL_H
|
||||
|
||||
// This file is part of JustMotion.
|
||||
|
||||
// JustMotion 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.
|
||||
|
||||
// JustMotion 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"
|
||||
|
||||
class ProcControl : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
|
||||
QFileSystemWatcher *fsMon;
|
||||
QString file;
|
||||
QString statPath;
|
||||
bool closeOnZero;
|
||||
uint objCount;
|
||||
|
||||
private slots:
|
||||
|
||||
void procFileUpdated(const QString &path);
|
||||
void closeApp();
|
||||
|
||||
public:
|
||||
|
||||
explicit ProcControl(const QString &procFile, const QString &statDir, QCoreApplication *parent);
|
||||
|
||||
void objPlusOne();
|
||||
bool init();
|
||||
|
||||
public slots:
|
||||
|
||||
void objMinusOne();
|
||||
|
||||
signals:
|
||||
|
||||
void prepForClose();
|
||||
};
|
||||
|
||||
#endif // PROC_CONTROL_H
|
112
src/record_loop.cpp
Normal file
112
src/record_loop.cpp
Normal file
|
@ -0,0 +1,112 @@
|
|||
// This file is part of JustMotion.
|
||||
|
||||
// JustMotion 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.
|
||||
|
||||
// JustMotion 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 "record_loop.h"
|
||||
|
||||
RecordLoop::RecordLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QProcess(parent)
|
||||
{
|
||||
checkTimer = 0;
|
||||
shared = sharedRes;
|
||||
|
||||
connect(thr, &QThread::started, this, &RecordLoop::init);
|
||||
connect(thr, &QThread::finished, this, &RecordLoop::deleteLater);
|
||||
|
||||
connect(this, &RecordLoop::readyReadStandardOutput, this, &RecordLoop::resetTime);
|
||||
connect(this, &RecordLoop::readyReadStandardError, this, &RecordLoop::resetTime);
|
||||
connect(this, &RecordLoop::started, this, &RecordLoop::resetTime);
|
||||
|
||||
moveToThread(thr);
|
||||
}
|
||||
|
||||
RecordLoop::~RecordLoop()
|
||||
{
|
||||
terminate();
|
||||
waitForFinished();
|
||||
}
|
||||
|
||||
void RecordLoop::init()
|
||||
{
|
||||
checkTimer = new QTimer(this);
|
||||
|
||||
connect(checkTimer, &QTimer::timeout, this, &RecordLoop::restart);
|
||||
|
||||
checkTimer->setSingleShot(true);
|
||||
checkTimer->setInterval(3000);
|
||||
|
||||
setupBuffDir(shared->buffPath);
|
||||
restart();
|
||||
}
|
||||
|
||||
QString RecordLoop::camCmdFromConf()
|
||||
{
|
||||
auto ret = "ffmpeg -hide_banner -y -i '" + shared->recordUri + "' -strftime 1 -strftime_mkdir 1 ";
|
||||
|
||||
if (shared->recordUri.contains("rtsp"))
|
||||
{
|
||||
ret += "-rtsp_transport udp ";
|
||||
}
|
||||
|
||||
if (shared->vidCodec != "copy")
|
||||
{
|
||||
ret += "-vf fps=" + QString::number(shared->recFps) + ",scale=" + shared->recScale + " ";
|
||||
}
|
||||
|
||||
ret += "-vcodec " + shared->vidCodec + " ";
|
||||
ret += "-acodec " + shared->audCodec + " ";
|
||||
ret += "-reset_timestamps 1 -sc_threshold 0 -g 2 -force_key_frames 'expr:gte(t, n_forced * 2)' -segment_time 2 -f segment ";
|
||||
ret += shared->buffPath + "/live/" + QString(STRFTIME_FMT) + shared->streamExt;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString RecordLoop::statusLine()
|
||||
{
|
||||
if (state() == QProcess::Running)
|
||||
{
|
||||
return "OK ";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "FAIL";
|
||||
}
|
||||
}
|
||||
|
||||
void RecordLoop::resetTime()
|
||||
{
|
||||
checkTimer->start();
|
||||
}
|
||||
|
||||
void RecordLoop::restart()
|
||||
{
|
||||
if (state() == QProcess::Running)
|
||||
{
|
||||
terminate();
|
||||
waitForFinished();
|
||||
}
|
||||
|
||||
auto cmdLine = camCmdFromConf();
|
||||
auto args = parseArgs(cmdLine.toUtf8(), -1);
|
||||
|
||||
qInfo() << "start recording command: " << cmdLine;
|
||||
|
||||
if (args.isEmpty())
|
||||
{
|
||||
qCritical() << "err: couldn't parse a program name";
|
||||
}
|
||||
else
|
||||
{
|
||||
setProgram(args[0]);
|
||||
setArguments(args.mid(1));
|
||||
|
||||
start();
|
||||
}
|
||||
}
|
47
src/record_loop.h
Normal file
47
src/record_loop.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
#ifndef RECORD_LOOP_H
|
||||
#define RECORD_LOOP_H
|
||||
|
||||
// This file is part of JustMotion.
|
||||
|
||||
// JustMotion 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.
|
||||
|
||||
// JustMotion 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"
|
||||
|
||||
class RecordLoop : public QProcess
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
|
||||
void init();
|
||||
void resetTime();
|
||||
|
||||
private:
|
||||
|
||||
shared_t *shared;
|
||||
QTimer *checkTimer;
|
||||
|
||||
QString camCmdFromConf();
|
||||
|
||||
public slots:
|
||||
|
||||
void restart();
|
||||
|
||||
public:
|
||||
|
||||
explicit RecordLoop(shared_t *shared, QThread *thr, QObject *parent = nullptr);
|
||||
|
||||
~RecordLoop();
|
||||
|
||||
QString statusLine();
|
||||
};
|
||||
|
||||
#endif // RECORD_LOOP_H
|
261
src/services.cpp
261
src/services.cpp
|
@ -1,261 +0,0 @@
|
|||
// This file is part of JustMotion.
|
||||
|
||||
// JustMotion 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.
|
||||
|
||||
// JustMotion 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 "services.h"
|
||||
|
||||
int loadService(const QString &desc, const QString &user, const QString &servName, const QString &workDir, const QString &recPath, bool start)
|
||||
{
|
||||
Q_UNUSED(recPath);
|
||||
|
||||
QFile file("/lib/systemd/system/" + servName + ".service");
|
||||
|
||||
auto ret = 0;
|
||||
auto exists = file.exists();
|
||||
|
||||
if (!file.open(QFile::ReadWrite | QFile::Truncate))
|
||||
{
|
||||
QTextStream(stderr) << "err: failed to open service file: " << file.fileName() << " for writing. reason: " << file.errorString() << Qt::endl;
|
||||
|
||||
ret = EACCES;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (exists)
|
||||
{
|
||||
if (ret == 0) ret = QProcess::execute("systemctl", {"stop", servName});
|
||||
if (ret == 0) ret = QProcess::execute("systemctl", {"disable", servName});
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
file.write("[Unit]\n");
|
||||
file.write("Description=" + desc.toUtf8() + "\n");
|
||||
file.write("After=network.target\n\n");
|
||||
file.write("[Service]\n");
|
||||
file.write("WorkingDirectory=" + workDir.toUtf8() + "\n");
|
||||
file.write("Type=simple\n");
|
||||
file.write("User=" + user.toUtf8() + "\n");
|
||||
file.write("Restart=always\n");
|
||||
|
||||
if (servName.contains("vid_loop"))
|
||||
{
|
||||
file.write("RuntimeMaxSec=61\n");
|
||||
}
|
||||
|
||||
file.write("ExecStart=/usr/bin/env " + servName.toUtf8() + "\n\n");
|
||||
file.write("[Install]\n");
|
||||
file.write("WantedBy=multi-user.target\n");
|
||||
file.close();
|
||||
|
||||
if (ret == 0) ret = QProcess::execute("systemctl", {"daemon-reload"});
|
||||
|
||||
if (start)
|
||||
{
|
||||
if (ret == 0) ret = QProcess::execute("systemctl", {"enable", servName});
|
||||
if (ret == 0) ret = QProcess::execute("systemctl", {"start", servName});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int loadSh(const QString &name, const QString &exeCmd, const QString &buffDir, const QString &outDir, const QString &servUser, const QString &servGroup)
|
||||
{
|
||||
QFile file("/usr/bin/" + name);
|
||||
|
||||
auto ret = 0;
|
||||
|
||||
if (!file.open(QFile::ReadWrite | QFile::Truncate))
|
||||
{
|
||||
QTextStream(stderr) << "err: failed to open shell script file: " << file.fileName() << " for writing. reason: " << file.errorString() << Qt::endl;
|
||||
|
||||
ret = EACCES;
|
||||
}
|
||||
else
|
||||
{
|
||||
file.write("#!/bin/sh\n\n");
|
||||
file.write("cd " + buffDir.toUtf8() + "\n");
|
||||
file.write(exeCmd.toUtf8() + "\n");
|
||||
file.close();
|
||||
|
||||
mkPath(buffDir);
|
||||
mkPath(outDir);
|
||||
mkPath(buffDir + "/live");
|
||||
mkPath(buffDir + "/img");
|
||||
|
||||
QProcess::execute("chmod", {"-v", "+x", file.fileName()});
|
||||
QProcess::execute("chown", {servUser + ":" + servGroup, "-R", buffDir});
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString camCmdFromConf(shared_t *conf, CmdExeType type)
|
||||
{
|
||||
QString ret = "";
|
||||
|
||||
if (type == MAIN_LOOP)
|
||||
{
|
||||
ret += QString(APP_TARGET) + " -c " + conf->conf;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret += "ffmpeg -hide_banner -y -i '" + conf->recordUri + "' -strftime 1 -strftime_mkdir 1 ";
|
||||
|
||||
if (conf->recordUri.contains("rtsp"))
|
||||
{
|
||||
ret += "-rtsp_transport udp ";
|
||||
}
|
||||
|
||||
if (conf->vidCodec != "copy")
|
||||
{
|
||||
ret += "-vf fps=" + QString::number(conf->recFps) + ",scale=" + conf->recScale + " ";
|
||||
}
|
||||
|
||||
ret += "-vcodec " + conf->vidCodec + " ";
|
||||
ret += "-acodec " + conf->audCodec + " ";
|
||||
ret += "-reset_timestamps 1 -sc_threshold 0 -g 2 -force_key_frames \"expr:gte(t, n_forced * 2)\" -t 60 -segment_time 2 -f segment ";
|
||||
ret += conf->buffPath + "/live/" + QString(STRFTIME_FMT) + conf->streamExt;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int loadServiceByConf(const QString &confFile, bool start)
|
||||
{
|
||||
auto ret = 0;
|
||||
|
||||
shared_t conf;
|
||||
|
||||
if (!rdConf(confFile, &conf))
|
||||
{
|
||||
ret = conf.retCode;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto desc = QString(APP_NAME) + " - " + conf.camName;
|
||||
|
||||
if (ret == 0) ret = loadSh(conf.servMainLoop, camCmdFromConf(&conf, MAIN_LOOP), conf.buffPath, conf.recPath, conf.servUser, conf.servGroup);
|
||||
if (ret == 0) ret = loadSh(conf.servVidLoop, camCmdFromConf(&conf, VID_LOOP), conf.buffPath, conf.recPath, conf.servUser, conf.servGroup);
|
||||
|
||||
if (ret == 0) ret = loadService(desc, conf.servUser, conf.servMainLoop, conf.buffPath, conf.recPath, start);
|
||||
if (ret == 0) ret = loadService(desc, conf.servUser, conf.servVidLoop, conf.buffPath, conf.recPath, start);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int rmService(const QString &servName)
|
||||
{
|
||||
auto path = "/lib/systemd/system/" + servName + ".service";
|
||||
auto ret = 0;
|
||||
|
||||
if (QFile::exists(path))
|
||||
{
|
||||
if (ret == 0) ret = QProcess::execute("systemctl", {"stop", servName});
|
||||
if (ret == 0) ret = QProcess::execute("systemctl", {"disable", servName});
|
||||
|
||||
QFile::remove(path);
|
||||
QFile::remove("/usr/bin/" + servName);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int rmServiceByConf(const QString &confFile)
|
||||
{
|
||||
shared_t conf;
|
||||
|
||||
if (rdConf(confFile, &conf))
|
||||
{
|
||||
conf.retCode = rmService(conf.servMainLoop);
|
||||
conf.retCode = rmService(conf.servVidLoop);
|
||||
|
||||
QDir(conf.buffPath).removeRecursively();
|
||||
}
|
||||
|
||||
return conf.retCode;
|
||||
}
|
||||
|
||||
void servStatByConf(const QString &confFile)
|
||||
{
|
||||
shared_t conf;
|
||||
|
||||
if (rdConf(confFile, &conf))
|
||||
{
|
||||
QTextStream cout(stdout);
|
||||
|
||||
cout << "--" << conf.camName << Qt::endl;
|
||||
cout << " " << conf.servMainLoop << ": ";
|
||||
|
||||
if (QFile::exists("/lib/systemd/system/" + conf.servMainLoop + ".service"))
|
||||
{
|
||||
QProcess::execute("systemctl", {"is-enabled", conf.servMainLoop}); cout << Qt::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "Not Installed" << Qt::endl;
|
||||
}
|
||||
|
||||
cout << " " << conf.servVidLoop << ": ";
|
||||
|
||||
if (QFile::exists("/lib/systemd/system/" + conf.servVidLoop + ".service"))
|
||||
{
|
||||
QProcess::execute("systemctl", {"is-enabled", conf.servVidLoop}); cout << Qt::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
cout << "Not Installed" << Qt::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void servStatByDir(const QString &path)
|
||||
{
|
||||
auto files = lsFilesInDir(path);
|
||||
|
||||
for (auto &&conf : files)
|
||||
{
|
||||
servStatByConf(QDir::cleanPath(path) + "/" + conf);
|
||||
}
|
||||
}
|
||||
|
||||
int loadServiceByDir(const QString &path, bool start)
|
||||
{
|
||||
auto ret = 0;
|
||||
auto files = lsFilesInDir(path);
|
||||
|
||||
for (auto &&conf : files)
|
||||
{
|
||||
if (ret == 0 ) ret = loadServiceByConf(QDir::cleanPath(path) + "/" + conf, start);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int rmServiceByDir(const QString &path)
|
||||
{
|
||||
auto ret = 0;
|
||||
auto files = lsFilesInDir(path);
|
||||
|
||||
for (auto &&conf : files)
|
||||
{
|
||||
if (ret == 0 ) ret = rmServiceByConf(QDir::cleanPath(path) + "/" + conf);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
#ifndef SERVICES_H
|
||||
#define SERVICES_H
|
||||
|
||||
// This file is part of JustMotion.
|
||||
|
||||
// JustMotion 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.
|
||||
|
||||
// JustMotion 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"
|
||||
|
||||
QString camCmdFromConf(shared_t *conf, CmdExeType type);
|
||||
void servStatByDir(const QString &path);
|
||||
void servStatByConf(const QString &confFile);
|
||||
int loadService(const QString &desc, const QString &user, const QString &servName, const QString &workDir, const QString &recPath, bool start);
|
||||
int loadServiceByConf(const QString &confFile, bool start);
|
||||
int loadServiceByDir(const QString &path, bool start);
|
||||
int rmServiceByConf(const QString &confFile);
|
||||
int rmService(const QString &servName);
|
||||
int rmServiceByDir(const QString &path);
|
||||
int loadSh(const QString &name, const QString &exeCmd, const QString &buffDir, const QString &outDir, const QString &servUser, const QString &servGroup);
|
||||
|
||||
#endif // SERVICES_H
|
15
templates/linux_service.service
Normal file
15
templates/linux_service.service
Normal file
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=$app_name Daemon
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$app_target
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
TimeoutStopSec=infinity
|
||||
ExecStart=/usr/bin/env $app_target -d
|
||||
ExecStop=/usr/bin/env $app_target -q
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,6 +1,9 @@
|
|||
#!/bin/sh
|
||||
$app_target -r
|
||||
systemctl stop $app_target
|
||||
systemctl disable $app_target
|
||||
rm -v /lib/systemd/system/$app_target.service
|
||||
rm -v /usr/bin/$app_target
|
||||
rm -rv /tmp/$app_target-stats
|
||||
rm -rv $install_dir
|
||||
deluser $app_target
|
||||
echo "Uninstallation Complete"
|
||||
|
|
Loading…
Reference in New Issue
Block a user