Compare commits
No commits in common. "c2b6575800a566cdc9a628810832c4bead75cd2f" and "33519acf7a4880aa8173b0037301c5caef2c60c2" have entirely different histories.
c2b6575800
...
33519acf7a
|
@ -11,16 +11,10 @@ CONFIG -= app_bundle
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
src/common.h \
|
src/common.h \
|
||||||
src/camera.h \
|
src/camera.h \
|
||||||
src/detect_loop.h \
|
src/services.h
|
||||||
src/event_loop.h \
|
|
||||||
src/proc_control.h \
|
|
||||||
src/record_loop.h
|
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
src/common.cpp \
|
src/common.cpp \
|
||||||
src/camera.cpp \
|
src/camera.cpp \
|
||||||
src/detect_loop.cpp \
|
src/services.cpp \
|
||||||
src/event_loop.cpp \
|
|
||||||
src/proc_control.cpp \
|
|
||||||
src/record_loop.cpp \
|
|
||||||
src/main.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
|
of it's functions internally but will instead rely on external applications
|
||||||
that already implement the functions very well.
|
that already implement the functions very well.
|
||||||
|
|
||||||
No user interface is implemented instead external applications are more than
|
No user interface is implemented instead exteral applications are more than
|
||||||
welcome to interface with the buffer/footage directories to implement a
|
welcome to interface with the buffer/footage directories to implement a
|
||||||
user interface of any flavor.
|
user interface of any flavor.
|
||||||
|
|
||||||
|
@ -156,14 +156,13 @@ img_scale = 320:240
|
||||||
# rec_path. it uses width, height numeric strings seperated by a colon,
|
# rec_path. it uses width, height numeric strings seperated by a colon,
|
||||||
# eg W:H.
|
# eg W:H.
|
||||||
#
|
#
|
||||||
service_user = jmotion
|
service_user = mow
|
||||||
# this sets the service local user of the application dictating the
|
# this sets the service local user of the application dictating the
|
||||||
# amount of privilege it will have on the host. if not defined, the
|
# amount privilege it will have on the host. if not defined, the
|
||||||
# unprivileged user 'jmotion' will be used. which ever user is defined
|
# unprivileged user 'mow' will be used. which ever user is defined here
|
||||||
# here just make sure it has read/write access to buffer_path and
|
# just make sure it has read/write access to buffer_path and rec_path.
|
||||||
# rec_path.
|
|
||||||
#
|
#
|
||||||
service_group = jmotion
|
service_group = mow
|
||||||
# this sets the service local group of the application that can further
|
# this sets the service local group of the application that can further
|
||||||
# refine host privileges. if not defined, the name stored in
|
# refine host privileges. if not defined, the name stored in
|
||||||
# service_user will be used.
|
# service_user will be used.
|
||||||
|
|
15
build.py
15
build.py
|
@ -6,7 +6,6 @@ import subprocess
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
import pwd
|
|
||||||
|
|
||||||
def get_app_target(text):
|
def get_app_target(text):
|
||||||
return re.search(r'(APP_TARGET) +(\"(.*?)\")', text).group(3)
|
return re.search(r'(APP_TARGET) +(\"(.*?)\")', text).group(3)
|
||||||
|
@ -127,7 +126,6 @@ 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_run_script.sh", "app_dir/" + app_target + ".sh")
|
||||||
verbose_copy("templates/linux_uninstall.sh", "app_dir/uninstall.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)
|
complete(app_ver, app_target)
|
||||||
|
|
||||||
|
@ -170,15 +168,6 @@ def list_of_words_in_text(list_of_words, text_body):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def user_exists(user_name):
|
|
||||||
try:
|
|
||||||
pwd.getpwnam(user_name)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def platform_setup():
|
def platform_setup():
|
||||||
ins_packages = list_installed_packages()
|
ins_packages = list_installed_packages()
|
||||||
like_distro = get_like_distro()
|
like_distro = get_like_distro()
|
||||||
|
@ -199,10 +188,6 @@ def platform_setup():
|
||||||
subprocess.run(["sudo", "pacman", "-S", "--noconfirm"] + dep_pkgs_a)
|
subprocess.run(["sudo", "pacman", "-S", "--noconfirm"] + dep_pkgs_a)
|
||||||
subprocess.run(["sudo", "pacman", "-S", "--noconfirm"] + dep_pkgs_b)
|
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():
|
def main():
|
||||||
platform_setup()
|
platform_setup()
|
||||||
|
|
||||||
|
|
20
install.py
20
install.py
|
@ -8,7 +8,6 @@ import sys
|
||||||
import zipfile
|
import zipfile
|
||||||
import binascii
|
import binascii
|
||||||
import tempfile
|
import tempfile
|
||||||
import pwd
|
|
||||||
|
|
||||||
def cd():
|
def cd():
|
||||||
current_dir = os.path.dirname(__file__)
|
current_dir = os.path.dirname(__file__)
|
||||||
|
@ -110,7 +109,6 @@ def local_install(app_target, app_name):
|
||||||
|
|
||||||
if make_app_dirs(app_target):
|
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 + ".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)
|
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)
|
verbose_copy("app_dir/" + app_target, install_dir + "/" + app_target)
|
||||||
|
@ -121,18 +119,10 @@ def local_install(app_target, app_name):
|
||||||
subprocess.run(["chmod", "755", install_dir + "/" + app_target + ".sh"])
|
subprocess.run(["chmod", "755", install_dir + "/" + app_target + ".sh"])
|
||||||
subprocess.run(["chmod", "755", install_dir + "/" + app_target])
|
subprocess.run(["chmod", "755", install_dir + "/" + app_target])
|
||||||
subprocess.run(["chmod", "755", install_dir + "/uninstall.sh"])
|
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/buffer"])
|
||||||
subprocess.run(["chmod", "777", "/var/footage"])
|
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("Installation finished. If you ever need to uninstall this application, run this command with root rights:")
|
||||||
print(" sh " + install_dir + "/uninstall.sh\n")
|
print(" sh " + install_dir + "/uninstall.sh\n")
|
||||||
|
|
||||||
|
@ -277,15 +267,6 @@ def list_of_words_in_text(list_of_words, text_body):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def user_exists(user_name):
|
|
||||||
try:
|
|
||||||
pwd.getpwnam(user_name)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def platform_setup():
|
def platform_setup():
|
||||||
ins_packages = list_installed_packages()
|
ins_packages = list_installed_packages()
|
||||||
like_distro = get_like_distro()
|
like_distro = get_like_distro()
|
||||||
|
@ -322,7 +303,6 @@ def main(is_sfx):
|
||||||
app_name = info[2]
|
app_name = info[2]
|
||||||
|
|
||||||
if is_sfx:
|
if is_sfx:
|
||||||
platform_setup()
|
|
||||||
sfx()
|
sfx()
|
||||||
|
|
||||||
elif "--local" in sys.argv:
|
elif "--local" in sys.argv:
|
||||||
|
|
431
src/camera.cpp
431
src/camera.cpp
|
@ -12,132 +12,355 @@
|
||||||
|
|
||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
|
|
||||||
Camera::Camera(const QString &confFile, const QString &statDir, ProcControl *proc, QCoreApplication *parent) : QObject(nullptr)
|
Camera::Camera(QObject *parent) : QObject(parent) {}
|
||||||
|
|
||||||
|
int Camera::start(const QStringList &args)
|
||||||
{
|
{
|
||||||
Q_UNUSED(parent);
|
if (rdConf(getParam("-c", args), &shared))
|
||||||
|
|
||||||
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();
|
auto thr1 = new QThread(nullptr);
|
||||||
thr2->deleteLater();
|
auto thr2 = new QThread(nullptr);
|
||||||
thr3->deleteLater();
|
|
||||||
|
|
||||||
if (delOnZero)
|
new EventLoop(&shared, thr1, nullptr);
|
||||||
{
|
new DetectLoop(&shared, thr2, nullptr);
|
||||||
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();
|
thr1->start();
|
||||||
thr2->start();
|
thr2->start();
|
||||||
thr3->start();
|
|
||||||
|
|
||||||
objCount = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return shared.retCode;
|
return shared.retCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::confChanged(const QString &path)
|
EventLoop::EventLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
emit stop();
|
shared = sharedRes;
|
||||||
|
heartBeat = 2;
|
||||||
|
loopTimer = 0;
|
||||||
|
|
||||||
if (!QFileInfo::exists(path))
|
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())
|
||||||
{
|
{
|
||||||
deleteLater();
|
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
|
else
|
||||||
{
|
{
|
||||||
auto ret = start(path);
|
QTextStream(stdout) << "warning: the event hls clip does not exists." << Qt::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ret != 0)
|
file.close();
|
||||||
|
|
||||||
|
if (cnt == 0)
|
||||||
{
|
{
|
||||||
deleteLater();
|
QTextStream(stderr) << "err: none of the event hls clips exists, cancelling write out." << Qt::endl;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Camera::statusLine()
|
float DetectLoop::getFloatFromExe(const QByteArray &line)
|
||||||
{
|
{
|
||||||
return "Detection: " + detLoop->statusLine() +
|
QString strLine(line);
|
||||||
" Event-Scraping: " + evtLoop->statusLine() +
|
QString strNum;
|
||||||
" Recording: " + recLoop->statusLine() +
|
|
||||||
" Name: " + shared.camName + "\n";
|
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();
|
||||||
}
|
}
|
||||||
|
|
84
src/camera.h
84
src/camera.h
|
@ -14,10 +14,6 @@
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "event_loop.h"
|
|
||||||
#include "detect_loop.h"
|
|
||||||
#include "record_loop.h"
|
|
||||||
#include "proc_control.h"
|
|
||||||
|
|
||||||
class Camera : public QObject
|
class Camera : public QObject
|
||||||
{
|
{
|
||||||
|
@ -25,36 +21,70 @@ class Camera : public QObject
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
QFileSystemWatcher *fsW;
|
|
||||||
EventLoop *evtLoop;
|
|
||||||
DetectLoop *detLoop;
|
|
||||||
RecordLoop *recLoop;
|
|
||||||
QThread *thr1;
|
|
||||||
QThread *thr2;
|
|
||||||
QThread *thr3;
|
|
||||||
QTimer *statTimer;
|
|
||||||
QString statPath;
|
|
||||||
shared_t shared;
|
shared_t shared;
|
||||||
uint objCount;
|
|
||||||
bool delOnZero;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
|
|
||||||
void confChanged(const QString &path);
|
|
||||||
void objMinusOne();
|
|
||||||
void updateStat();
|
|
||||||
void prepForDel();
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
explicit Camera(const QString &confFile, const QString &statDir, ProcControl *proc, QCoreApplication *parent);
|
explicit Camera(QObject *parent = nullptr);
|
||||||
|
|
||||||
int start(const QString &conf);
|
int start(const QStringList &args);
|
||||||
QString statusLine();
|
};
|
||||||
|
|
||||||
signals:
|
class EventLoop : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
void stop();
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
class DetectLoop : public QFileSystemWatcher
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
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();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CAMERA_H
|
#endif // CAMERA_H
|
||||||
|
|
|
@ -12,6 +12,43 @@
|
||||||
|
|
||||||
#include "common.h"
|
#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 lsFilesInDir(const QString &path, const QString &ext)
|
||||||
{
|
{
|
||||||
QStringList filters;
|
QStringList filters;
|
||||||
|
@ -175,6 +212,7 @@ bool rdConf(const QString &filePath, shared_t *share)
|
||||||
share->camName.clear();
|
share->camName.clear();
|
||||||
share->buffPath.clear();
|
share->buffPath.clear();
|
||||||
share->recPath.clear();
|
share->recPath.clear();
|
||||||
|
share->servGroup.clear();
|
||||||
|
|
||||||
share->retCode = 0;
|
share->retCode = 0;
|
||||||
share->imgThresh = 8000;
|
share->imgThresh = 8000;
|
||||||
|
@ -194,6 +232,7 @@ bool rdConf(const QString &filePath, shared_t *share)
|
||||||
share->liveSecs = 80;
|
share->liveSecs = 80;
|
||||||
share->recScale = "1280:720";
|
share->recScale = "1280:720";
|
||||||
share->imgScale = "320:240";
|
share->imgScale = "320:240";
|
||||||
|
share->servUser = APP_TARGET;
|
||||||
|
|
||||||
QString line;
|
QString line;
|
||||||
|
|
||||||
|
@ -222,6 +261,8 @@ bool rdConf(const QString &filePath, shared_t *share)
|
||||||
rdLine("rec_fps = ", line, &share->recFps);
|
rdLine("rec_fps = ", line, &share->recFps);
|
||||||
rdLine("rec_scale = ", line, &share->recScale);
|
rdLine("rec_scale = ", line, &share->recScale);
|
||||||
rdLine("img_scale = ", line, &share->imgScale);
|
rdLine("img_scale = ", line, &share->imgScale);
|
||||||
|
rdLine("service_user = ", line, &share->servUser);
|
||||||
|
rdLine("service_group = ", line, &share->servGroup);
|
||||||
rdLine("live_secs = ", line, &share->liveSecs);
|
rdLine("live_secs = ", line, &share->liveSecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,34 +309,19 @@ bool rdConf(const QString &filePath, shared_t *share)
|
||||||
{
|
{
|
||||||
share->evMaxSecs = share->liveSecs - 4;
|
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;
|
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 buildThreadCount(int count)
|
||||||
{
|
{
|
||||||
QString ret = "0";
|
QString ret = "0";
|
||||||
|
|
10
src/common.h
10
src/common.h
|
@ -26,13 +26,11 @@
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
#include <QDebug>
|
|
||||||
#include <QElapsedTimer>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
#define APP_VERSION "3.5"
|
#define APP_VERSION "3.4"
|
||||||
#define APP_NAME "JustMotion"
|
#define APP_NAME "JustMotion"
|
||||||
#define APP_TARGET "jmotion"
|
#define APP_TARGET "jmotion"
|
||||||
#define DATETIME_FMT "yyyyMMddhhmmss"
|
#define DATETIME_FMT "yyyyMMddhhmmss"
|
||||||
|
@ -74,6 +72,10 @@ struct shared_t
|
||||||
QString thumbExt;
|
QString thumbExt;
|
||||||
QString recScale;
|
QString recScale;
|
||||||
QString imgScale;
|
QString imgScale;
|
||||||
|
QString servMainLoop;
|
||||||
|
QString servVidLoop;
|
||||||
|
QString servUser;
|
||||||
|
QString servGroup;
|
||||||
bool singleTenant;
|
bool singleTenant;
|
||||||
bool skipCmd;
|
bool skipCmd;
|
||||||
int liveSecs;
|
int liveSecs;
|
||||||
|
@ -85,6 +87,7 @@ struct shared_t
|
||||||
int retCode;
|
int retCode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QString getParam(const QString &key, const QStringList &args);
|
||||||
QString buildThreadCount(int count);
|
QString buildThreadCount(int count);
|
||||||
QStringList lsFilesInDir(const QString &path, const QString &ext = QString());
|
QStringList lsFilesInDir(const QString &path, const QString &ext = QString());
|
||||||
QStringList lsDirsInDir(const QString &path);
|
QStringList lsDirsInDir(const QString &path);
|
||||||
|
@ -94,7 +97,6 @@ QStringList forwardFacingFiles(const QString &path, const QString &ext, const QD
|
||||||
QStringList parseArgs(const QByteArray &data, int maxArgs, int *pos = nullptr);
|
QStringList parseArgs(const QByteArray &data, int maxArgs, int *pos = nullptr);
|
||||||
bool rdConf(const QString &filePath, shared_t *share);
|
bool rdConf(const QString &filePath, shared_t *share);
|
||||||
bool mkPath(const QString &path);
|
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, QString *value);
|
||||||
void rdLine(const QString ¶m, const QString &line, int *value);
|
void rdLine(const QString ¶m, const QString &line, int *value);
|
||||||
void rdLine(const QString ¶m, const QString &line, bool *value);
|
void rdLine(const QString ¶m, const QString &line, bool *value);
|
||||||
|
|
|
@ -1,253 +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 "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();
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
#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
|
|
|
@ -1,138 +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 "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;
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
#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
|
|
91
src/main.cpp
91
src/main.cpp
|
@ -12,23 +12,24 @@
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
#include "proc_control.h"
|
#include "services.h"
|
||||||
|
|
||||||
void showHelp(const QString etcDir)
|
void showHelp(const QString etcDir)
|
||||||
{
|
{
|
||||||
QTextStream(stdout) << APP_NAME << " " << APP_VERSION << Qt::endl << Qt::endl;
|
QTextStream(stdout) << APP_NAME << " " << APP_VERSION << Qt::endl << Qt::endl;
|
||||||
QTextStream(stdout) << "Usage: " << APP_TARGET << " <argument>" << 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) << "-h : display usage information about this application." << Qt::endl;
|
||||||
QTextStream(stdout) << "-d : all valid config files found in " << etcDir << " will be used to" << Qt::endl;
|
QTextStream(stdout) << "-c : path to the config file used to run a single main loop instance." << Qt::endl;
|
||||||
QTextStream(stdout) << " create camera instances. (this is blocking, meant to run with systemd)" << 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) << "-v : display the current version." << Qt::endl;
|
QTextStream(stdout) << "-v : display the current version." << Qt::endl;
|
||||||
QTextStream(stdout) << "-u : uninstall the entire app from your system, including the service. all" << Qt::endl;
|
QTextStream(stdout) << "-u : uninstall the entire app from your system, including all" << Qt::endl;
|
||||||
QTextStream(stdout) << " recorded footage will remain." << Qt::endl;
|
QTextStream(stdout) << " systemd services related to it." << Qt::endl;
|
||||||
QTextStream(stdout) << "-f : force an action without pausing for user confirmation." << Qt::endl;
|
QTextStream(stdout) << "-f : force an action without pausing for user confirmation." << Qt::endl;
|
||||||
QTextStream(stdout) << "-s : view the status of all camera instances." << Qt::endl;
|
QTextStream(stdout) << "-l : list all attached services to this application along with statuses." << Qt::endl;
|
||||||
QTextStream(stdout) << "-q : kill all camera instances." << Qt::endl;
|
QTextStream(stdout) << "-r : remove all attached services." << 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)
|
int main(int argc, char** argv)
|
||||||
|
@ -40,15 +41,8 @@ int main(int argc, char** argv)
|
||||||
|
|
||||||
auto args = QCoreApplication::arguments();
|
auto args = QCoreApplication::arguments();
|
||||||
auto etcDir = "/etc/" + QString(APP_TARGET);
|
auto etcDir = "/etc/" + QString(APP_TARGET);
|
||||||
auto staDir = QDir::tempPath() + "/" + APP_TARGET + "-stats";
|
|
||||||
auto procFile = QDir::tempPath() + "/" + APP_TARGET + "-proc";
|
|
||||||
auto ret = 0;
|
auto ret = 0;
|
||||||
|
|
||||||
if (!QFileInfo::exists(staDir))
|
|
||||||
{
|
|
||||||
mkPath(staDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.contains("-h"))
|
if (args.contains("-h"))
|
||||||
{
|
{
|
||||||
showHelp(etcDir);
|
showHelp(etcDir);
|
||||||
|
@ -57,31 +51,45 @@ int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
QTextStream(stdout) << APP_VERSION << Qt::endl;
|
QTextStream(stdout) << APP_VERSION << Qt::endl;
|
||||||
}
|
}
|
||||||
else if (args.contains("-d"))
|
else if (args.contains("-l"))
|
||||||
{
|
{
|
||||||
auto confs = lsFilesInDir(etcDir);
|
servStatByDir(etcDir);
|
||||||
auto proc = new ProcControl(procFile, staDir, &app);
|
|
||||||
|
|
||||||
if (!proc->init())
|
|
||||||
{
|
|
||||||
ret = EACCES;
|
|
||||||
}
|
}
|
||||||
else
|
else if (args.contains("-i") || args.contains("-d"))
|
||||||
{
|
{
|
||||||
for (auto &&conf : confs)
|
ret = rmServiceByDir(etcDir);
|
||||||
{
|
|
||||||
new Camera(etcDir + "/" + conf, staDir, proc, &app);
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = app.exec();
|
if ((ret == 0) && args.contains("-i"))
|
||||||
|
{
|
||||||
|
ret = loadServiceByDir(etcDir, true);
|
||||||
|
}
|
||||||
|
else if (ret == 0)
|
||||||
|
{
|
||||||
|
ret = loadServiceByDir(etcDir, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (args.contains("-c"))
|
||||||
|
{
|
||||||
|
auto *cam = new Camera(&app);
|
||||||
|
|
||||||
|
ret = cam->start(args);
|
||||||
|
|
||||||
|
if (ret == 0)
|
||||||
|
{
|
||||||
|
ret = QCoreApplication::exec();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (args.contains("-u"))
|
else if (args.contains("-u"))
|
||||||
{
|
{
|
||||||
if (args.contains("-f"))
|
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
|
else
|
||||||
{
|
{
|
||||||
char ans;
|
char ans;
|
||||||
|
@ -90,34 +98,19 @@ int main(int argc, char** argv)
|
||||||
std::cin >> ans;
|
std::cin >> ans;
|
||||||
|
|
||||||
if (ans == 'y' || ans == 'Y')
|
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"))
|
else if (args.contains("-r"))
|
||||||
{
|
{
|
||||||
QProcess::execute("systemctl start " + QString(APP_TARGET));
|
ret = rmServiceByDir(etcDir);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,108 +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 "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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
#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
|
|
|
@ -1,112 +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 "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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
#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
Normal file
261
src/services.cpp
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
// 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;
|
||||||
|
}
|
29
src/services.h
Normal file
29
src/services.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#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
|
|
@ -1,15 +0,0 @@
|
||||||
[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,9 +1,6 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
systemctl stop $app_target
|
$app_target -r
|
||||||
systemctl disable $app_target
|
|
||||||
rm -v /lib/systemd/system/$app_target.service
|
|
||||||
rm -v /usr/bin/$app_target
|
rm -v /usr/bin/$app_target
|
||||||
rm -rv /tmp/$app_target-stats
|
|
||||||
rm -rv $install_dir
|
rm -rv $install_dir
|
||||||
deluser $app_target
|
deluser $app_target
|
||||||
echo "Uninstallation Complete"
|
echo "Uninstallation Complete"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user