-updated the way the video clips from the live stream are named to
 simple unsigned int number increments. doing this makes the file
 names much more predictable and removes the need for filesystem
 monitoring.
This commit is contained in:
zii 2024-10-09 12:38:49 -04:00
parent 88ea1086f6
commit ba54b5b44a
6 changed files with 138 additions and 176 deletions

View File

@ -92,7 +92,7 @@ void enforceMaxImages(shared_t *share)
{ {
auto names = lsFilesInDir(share->buffPath + "/img", ".bmp"); auto names = lsFilesInDir(share->buffPath + "/img", ".bmp");
while (names.size() > MAX_IMAGES) while (names.size() > (share->liveSecs / 2))
{ {
QFile::remove(share->buffPath + "/img/" + names[0]); QFile::remove(share->buffPath + "/img/" + names[0]);
@ -104,7 +104,7 @@ void enforceMaxClips(shared_t *share)
{ {
auto names = lsFilesInDir(share->buffPath + "/live", share->streamExt); auto names = lsFilesInDir(share->buffPath + "/live", share->streamExt);
while (names.size() > (share->liveSecs / SEG_SIZE)) while (names.size() > (share->liveSecs / 2))
{ {
QFile::remove(share->buffPath + "/live/" + names[0]); QFile::remove(share->buffPath + "/live/" + names[0]);

View File

@ -36,11 +36,8 @@ using namespace std;
#define APP_NAME "JustMotion" #define APP_NAME "JustMotion"
#define APP_TARGET "jmotion" #define APP_TARGET "jmotion"
#define DATETIME_FMT "yyyyMMddhhmmss" #define DATETIME_FMT "yyyyMMddhhmmss"
#define STRFTIME_FMT "%Y%m%d%H%M%S"
#define PREV_IMG "&prev&" #define PREV_IMG "&prev&"
#define NEXT_IMG "&next&" #define NEXT_IMG "&next&"
#define SEG_SIZE 10
#define MAX_IMAGES 100
enum CmdExeType enum CmdExeType
{ {
@ -76,6 +73,7 @@ struct shared_t
QString thumbExt; QString thumbExt;
QString recScale; QString recScale;
QString imgScale; QString imgScale;
quint64 seed;
bool singleTenant; bool singleTenant;
bool skipCmd; bool skipCmd;
int liveSecs; int liveSecs;

View File

@ -15,6 +15,7 @@
DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(parent) DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QObject(parent)
{ {
pcTimer = 0; pcTimer = 0;
deTimer = 0;
shared = sharedRes; shared = sharedRes;
connect(thr, &QThread::started, this, &DetectLoop::init); connect(thr, &QThread::started, this, &DetectLoop::init);
@ -26,21 +27,15 @@ DetectLoop::DetectLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QOb
void DetectLoop::init() void DetectLoop::init()
{ {
pcTimer = new QTimer(this); pcTimer = new QTimer(this);
evTimer = new QTimer(this); deTimer = new QTimer(this);
dTime = QDateTime::currentDateTimeUtc();
connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak); connect(pcTimer, &QTimer::timeout, this, &DetectLoop::pcBreak);
connect(evTimer, &QTimer::timeout, this, &DetectLoop::evBreak); connect(deTimer, &QTimer::timeout, this, &DetectLoop::preExec);
connect(this, &DetectLoop::loop, this, &DetectLoop::exec);
pcTimer->start(shared->postSecs * 1000); pcTimer->start(shared->postSecs * 1000);
evTimer->start(shared->evMaxSecs * 1000); deTimer->start(2000);
setupBuffDir(shared->buffPath); setupBuffDir(shared->buffPath);
eTimer.start();
QTimer::singleShot(SEG_SIZE + 4, this, &DetectLoop::exec);
} }
void DetectLoop::reset() void DetectLoop::reset()
@ -54,6 +49,19 @@ void DetectLoop::reset()
eventQue.timeStamp.clear(); eventQue.timeStamp.clear();
} }
void DetectLoop::preExec()
{
vidAPath = shared->buffPath + "/live/" + QString::number(shared->seed - 2).leftJustified(21, '0') + shared->streamExt;
vidBPath = shared->buffPath + "/live/" + QString::number(shared->seed - 1).leftJustified(21, '0') + shared->streamExt;
eTimer.start();
if (QFileInfo::exists(vidAPath) && QFileInfo::exists(vidBPath))
{
exec();
}
}
void DetectLoop::pcBreak() void DetectLoop::pcBreak()
{ {
prevClips.clear(); prevClips.clear();
@ -84,16 +92,6 @@ void DetectLoop::pcBreak()
} }
} }
void DetectLoop::evBreak()
{
if (eventQue.inQue)
{
shared->recList.append(eventQue);
reset();
}
}
float DetectLoop::getFloatFromExe(const QByteArray &line) float DetectLoop::getFloatFromExe(const QByteArray &line)
{ {
QString strLine(line); QString strLine(line);
@ -136,9 +134,26 @@ QStringList DetectLoop::buildArgs(const QString &prev, const QString &next)
return args; 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() QString DetectLoop::statusLine()
{ {
if (eTimer.elapsed() >= 30000) if (eTimer.elapsed() >= 5000)
{ {
emit starving(); emit starving();
@ -152,111 +167,76 @@ QString DetectLoop::statusLine()
void DetectLoop::exec() void DetectLoop::exec()
{ {
auto vidPath = shared->buffPath + "/live/" + dTime.toString(DATETIME_FMT) + shared->streamExt; auto imgAPath = shared->buffPath + "/img/" + QFileInfo(vidAPath).baseName() + ".bmp";
auto found = false; auto imgBPath = shared->buffPath + "/img/" + QFileInfo(vidBPath).baseName() + ".bmp";
auto snapArgsA = buildSnapArgs(vidAPath, imgAPath);
auto snapArgsB = buildSnapArgs(vidBPath, imgBPath);
auto compArgs = buildArgs(imgAPath, imgBPath);
for (auto i = 0; i < 100; ++i) if (compArgs.isEmpty())
{ {
if (QFileInfo::exists(vidPath)) qCritical() << "err: could not parse a executable name from img_comp_cmd: " << shared->compCmd;
{
found = true; break;
}
else
{
dTime = dTime.addSecs(1);
vidPath = shared->buffPath + "/live/" + dTime.toString(DATETIME_FMT) + shared->streamExt;
}
}
if (found)
{
auto imgPath = shared->buffPath + "/img/" + QFileInfo(vidPath).baseName() + "-%03d" + ".bmp";
QProcess ffmpeg;
QStringList args;
QString imgA;
QString imgB;
args.append("ffmpeg");
args.append("-hide_banner");
//args.append("-loglevel");
//args.append("panic");
args.append("-y");
args.append("-i");
args.append(vidPath);
args.append("-vf");
args.append("fps=1/1");
args.append(imgPath);
ffmpeg.start(args[0], args.mid(1));
ffmpeg.waitForFinished();
for (auto i = 1; i <= SEG_SIZE; ++i)
{
auto imgNum = QString::number(i).rightJustified(3, '0');
auto imgPath = shared->buffPath + "/img/" + QFileInfo(vidPath).baseName() + "-" + imgNum + ".bmp";
if (QFileInfo::exists(imgPath))
{
if (imgA.isEmpty()) imgA = imgPath;
else if (imgB.isEmpty()) imgB = imgPath;
else
{
eTimer.restart();
QProcess extComp;
auto compArgs = buildArgs(imgA, imgB);
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 (score >= shared->imgThresh)
{
qInfo() << "--threshold_meet: " << QString::number(shared->imgThresh);
if (eventQue.score < score)
{
eventQue.imgPath = imgB;
eventQue.score = score;
}
eventQue.inQue = true;
eventQue.queAge = 0;
eventQue.timeStamp = QDateTime::currentDateTimeUtc().toString(DATETIME_FMT);
if (!eventQue.vidList.contains(vidPath))
{
eventQue.vidList.append(vidPath);
}
}
imgA.clear();
imgB.clear();
}
}
}
emit loop();
} }
else else
{ {
QTimer::singleShot(SEG_SIZE + 4, this, &DetectLoop::exec); QProcess::execute("ffmpeg", snapArgsA);
QProcess::execute("ffmpeg", snapArgsB);
dTime = QDateTime::currentDateTimeUtc(); 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);
}
}
} }
} }

View File

@ -23,37 +23,34 @@ private:
QString vidAPath; QString vidAPath;
QString vidBPath; QString vidBPath;
QString vidAName;
QString vidBName;
QStringList prevClips; QStringList prevClips;
QTimer *pcTimer; QTimer *pcTimer;
QTimer *evTimer; QTimer *deTimer;
QDateTime dTime;
QElapsedTimer eTimer; QElapsedTimer eTimer;
evt_t eventQue; evt_t eventQue;
shared_t *shared; shared_t *shared;
float getFloatFromExe(const QByteArray &line); float getFloatFromExe(const QByteArray &line);
QStringList buildArgs(const QString &prev, const QString &next); QStringList buildArgs(const QString &prev, const QString &next);
QStringList buildSnapArgs(const QString &vidSrc, const QString &imgPath);
private slots: private slots:
void init(); void init();
void reset(); void reset();
void pcBreak(); void pcBreak();
void evBreak(); void preExec();
void exec();
public: public:
explicit DetectLoop(shared_t *shared, QThread *thr, QObject *parent = nullptr); explicit DetectLoop(shared_t *shared, QThread *thr, QObject *parent = nullptr);
void exec();
QString statusLine(); QString statusLine();
signals: signals:
void starving(); void starving();
void loop();
}; };
#endif // DETECT_LOOP_H #endif // DETECT_LOOP_H

View File

@ -15,16 +15,16 @@
RecordLoop::RecordLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QProcess(parent) RecordLoop::RecordLoop(shared_t *sharedRes, QThread *thr, QObject *parent) : QProcess(parent)
{ {
checkTimer = 0; checkTimer = 0;
status = 0;
shared = sharedRes; shared = sharedRes;
sharedRes->seed = 0;
connect(thr, &QThread::started, this, &RecordLoop::init); connect(thr, &QThread::started, this, &RecordLoop::init);
connect(thr, &QThread::finished, this, &RecordLoop::deleteLater); connect(thr, &QThread::finished, this, &RecordLoop::deleteLater);
connect(this, &RecordLoop::readyReadStandardOutput, this, &RecordLoop::resetTime); connect(this, &RecordLoop::readyReadStandardOutput, this, &RecordLoop::resetTime);
connect(this, &RecordLoop::readyReadStandardError, this, &RecordLoop::resetTime); connect(this, &RecordLoop::readyReadStandardError, this, &RecordLoop::resetTime);
connect(this, &RecordLoop::started, this, &RecordLoop::resetTime); connect(this, &RecordLoop::started, this, &RecordLoop::resetTime);
connect(this, &RecordLoop::finished, this, &RecordLoop::segFinished);
moveToThread(thr); moveToThread(thr);
} }
@ -48,56 +48,38 @@ void RecordLoop::init()
restart(); restart();
} }
void RecordLoop::segFinished(int retCode) QString RecordLoop::camCmdFromConf()
{ {
status = retCode; auto ret = "ffmpeg -hide_banner -y -i '" + shared->recordUri + "' -strftime 1 -strftime_mkdir 1 ";
restart();
}
QStringList RecordLoop::camCmdFromConf()
{
auto tobj = QDateTime::currentDateTimeUtc();
auto ret = QStringList();
ret.append("ffmpeg");
ret.append("-hide_banner");
ret.append("-y");
ret.append("-i");
ret.append(shared->recordUri);
if (shared->recordUri.contains("rtsp")) if (shared->recordUri.contains("rtsp"))
{ {
ret.append("-rtsp_transport"); ret += "-rtsp_transport udp ";
ret.append("udp"); }
if (shared->seed == 18446744073709551615ULL)
{
setupBuffDir(shared->buffPath, true);
shared->seed = 0;
} }
if (shared->vidCodec != "copy") if (shared->vidCodec != "copy")
{ {
ret.append("-vf"); ret += "-vf fps=" + QString::number(shared->recFps) + ",scale=" + shared->recScale + " ";
ret.append("fps=" + QString::number(shared->recFps) + ",scale=" + shared->recScale);
} }
ret.append("-vcodec"); ret.append(shared->vidCodec); ret += "-vcodec " + shared->vidCodec + " ";
ret.append("-acodec"); ret.append(shared->audCodec); ret += "-acodec " + shared->audCodec + " ";
ret.append("-reset_timestamps"); ret += "-reset_timestamps 1 -sc_threshold 0 -g 2 -force_key_frames 'expr:gte(t, n_forced * 2)' -segment_time 2 -f segment ";
ret.append("1"); ret += shared->buffPath + "/live/" + QString::number(shared->seed++).leftJustified(21, '0') + shared->streamExt;
ret.append("-sc_threshold");
ret.append("0");
ret.append("-g");
ret.append("2");
ret.append("-force_key_frames");
ret.append("expr:gte(t, n_forced * 2)");
ret.append("-t");
ret.append(QString::number(SEG_SIZE));
ret.append(shared->buffPath + "/live/" + tobj.toString(DATETIME_FMT) + shared->streamExt);
return ret; return ret;
} }
QString RecordLoop::statusLine() QString RecordLoop::statusLine()
{ {
if ((status == 0) || (status == 255)) if (state() == QProcess::Running)
{ {
return "OK "; return "OK ";
} }
@ -117,13 +99,20 @@ void RecordLoop::restart()
if (state() == QProcess::Running) if (state() == QProcess::Running)
{ {
terminate(); 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 else
{ {
resetTime();
auto args = camCmdFromConf();
setProgram(args[0]); setProgram(args[0]);
setArguments(args.mid(1)); setArguments(args.mid(1));

View File

@ -28,14 +28,12 @@ private:
shared_t *shared; shared_t *shared;
QTimer *checkTimer; QTimer *checkTimer;
int status;
QStringList camCmdFromConf(); QString camCmdFromConf();
public slots: public slots:
void restart(); void restart();
void segFinished(int retCode);
public: public: