JustMotion/server/src/record_loop.cpp
zii e72feb40ee server 1.2:
- removed -r command line option and renamed it to -s. then
   added -l as what -s used to be.

 - added detect_uri option to camera parameters so stream
   snapshots can now run a secondary stream that differs
   from the recording stream. also added sync logic so the
   recording loop and detection loop will run synchronously
   while still running in seperate threads.

 - live_secs renamed to live_mins. the amount of live
   footage to buffer in buffer_path is now based on minutes
   instead of seconds.

 - added live_clip_secs parameter so the amount of seconds
   each hls video clip should have is now adjustable.

 - remove max_event_secs. events are now transfered to
   rec_path based on minute increments.

 - added sec_per_image to make the amount of seconds
   between stream snapshots pulled from detect_uri
   adjustable.

 - added img_ext so the image snapshot format from
   detect_uri and then ultimately to img_comp_cmd can now
   be configured instead of being hardcoded to bmp.

 - added gray_image as a boolean parameter so the snapshots
   from detect_uri can be pulled in grayscale format or
   remain in color if desired.

 - removed img_scale since this parameter was not doing
   anything.

overview:

   use of QFileSystemWatcher on the detection loop has
   been eliminated and thus eliminated use of functions
   such as listFacingFiles(), backwardFacingFiles() and
   forwardFacingFiles(). Instead, the detection and
   recording loops will now run synchronously and use
   predictable up-to-the-minute file naming scheme.

client v1.1:

 - the build script will now include all imageformat
   plugins from the QT lib directory, adding support for
   jpg, svg, ico and gif image formats.

 - switched to 2 number versioning.

 - this client app will now support the server's new
   up-to-the-minute directory structure for live and
   recorded footage.

 - the version number is now be displayed on the main
   interface.
2025-10-11 08:05:51 -04:00

167 lines
4.6 KiB
C++

// 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);
connect(this, &RecordLoop::finished, this, &RecordLoop::restart);
moveToThread(thr);
}
RecordLoop::~RecordLoop()
{
disconnect(this, &RecordLoop::finished, this, &RecordLoop::restart);
terminate();
waitForFinished();
}
void RecordLoop::init()
{
checkTimer = new QTimer(this);
mkdirTimer = new QTimer(this);
sync = false;
connect(checkTimer, &QTimer::timeout, this, &RecordLoop::restart);
connect(mkdirTimer, &QTimer::timeout, this, &RecordLoop::mkdirs);
connect(this, &RecordLoop::finished, mkdirTimer, &QTimer::stop);
connect(this, &RecordLoop::finished, checkTimer, &QTimer::stop);
mkdirTimer->setInterval(60000);
checkTimer->setInterval(60000);
// stall timeout. ffmpeg has a tendency to just 'stop.' meaning it would just stop
// reording at random times without stop signals or even error messages. this timer,
// monitors for any text output from ffmpeg. if no updates in 60000 msecs, it will
// automatically restart ffmpeg.
setupBuffDir(shared->buffPath);
restart();
}
void RecordLoop::mkdirs()
{
auto timeStamp = QDateTime::currentDateTime();
auto path1 = shared->buffPath + "/vid/" + timeStamp.toString(DATETIME_FMT);
timeStamp = timeStamp.addSecs(60);
auto path2 = shared->buffPath + "/vid/" + timeStamp.toString(DATETIME_FMT);
if (!QFileInfo(path1).exists()) QDir().mkpath(path1);
if (!QFileInfo(path2).exists()) QDir().mkpath(path2);
}
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 + " ";
}
auto maxClips = (60 / shared->liveClipSecs) * shared->liveMins;
ret += "-vcodec " + shared->vidCodec + " ";
ret += "-acodec " + shared->audCodec + " ";
ret += "-hls_time " + QString::number(shared->liveClipSecs) + " -hls_list_size " + QString::number(maxClips) + " ";
ret += "-hls_flags delete_segments -hls_segment_filename vid/" + QString(STRFTIME_FMT) + "/%S" + shared->streamExt + " ";
ret += "pls.m3u8";
qInfo() << ret;
return ret;
}
QString RecordLoop::statusLine()
{
if (!sync)
{
return "WAIT ";
}
else
{
return getStatLineFromProc(this);
}
}
void RecordLoop::synced()
{
sync = true;
restart();
}
void RecordLoop::resetTime()
{
// reset the stall timer to prevent it from timing out when ffmpeg doesn't
// need to be restarted.
checkTimer->start();
}
void RecordLoop::restart()
{
auto sec = QTime::currentTime().second();
if (state() == QProcess::Running)
{
terminate();
}
else if (!sync && (sec != 0))
{
// start recording when the seconds mark hit zero in real life.
QTimer::singleShot((60 - sec) * 1000, this, &RecordLoop::synced);
}
else
{
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
{
setWorkingDirectory(shared->buffPath);
setProgram(args[0]);
setArguments(args.mid(1));
mkdirs();
mkdirTimer->start();
start();
}
}
}