JustMotion/src/camera.cpp

384 lines
9.4 KiB
C++
Raw Normal View History

// This file is part of Motion Watch.
// Motion Watch is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Motion Watch is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "camera.h"
Camera::Camera(QObject *parent) : QObject(parent) {}
int Camera::start(const QStringList &args)
{
if (rdConf(getParam("-c", args), &shared))
{
auto thr1 = new QThread(nullptr);
auto thr2 = new QThread(nullptr);
new EventLoop(&shared, thr1, nullptr);
new DetectLoop(&shared, thr2, nullptr);
thr1->start();
thr2->start();
}
return shared.retCode;
}
EventLoop::EventLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(parent)
{
shared = sharedRes;
heartBeat = 2;
loopTimer = 0;
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())
{
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);
}
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()
{
vidAPath.clear();
vidBPath.clear();
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);
if (clips.isEmpty())
{
thread()->sleep(5); reset();
}
else
{
auto latestPath = path + "/" + clips.last();
if (vidAPath.isEmpty())
{
vidAPath = latestPath;
}
else if (latestPath == vidAPath)
{
thread()->usleep(500);
}
else
{
vidBPath = latestPath;
thread()->sleep(1);
exec();
}
}
}
void DetectLoop::pcBreak()
{
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));
}
}
}
}
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())
{
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);
}
}
else
{
QTextStream(stdout) << "why?" << Qt::endl;
QTextStream(stdout) << "imgAPath: " << imgAPath << Qt::endl;
QTextStream(stdout) << "imgBPath: " << imgBPath << Qt::endl;
}
}
vidAPath.clear();
vidBPath.clear();
}