-moved buff_path setup code away from rdconf() to loadSh(). this
 insures root will never take ownership of the camera buffer.
-added service group option.
-updated the documentation in preperation of release.
This commit is contained in:
zii 2023-12-23 17:38:11 -05:00
parent bb8a1fad45
commit 7ca4fc3dbe
6 changed files with 91 additions and 71 deletions

View File

@ -30,30 +30,34 @@ parameters supported and descriptions of each.
# also note to avoid using empty lines. if you're going to need an empty # also note to avoid using empty lines. if you're going to need an empty
# line, start it with a '#' # line, start it with a '#'
# #
recording_stream = rtsp://1.2.3.4:554/h264 recording_uri = rtsp://1.2.3.4:554/h264
# this is the url to the main stream of the IP camera that will be used # this is the uri to the main stream of the IP camera that will be used
# to record footage. # to record footage. it can be a url to an rtsp stream or a direct device
# path such as /dev/video0.
# #
web_root = /var/opt/mow/web buffer_path = /var/buffer
# this is the output directory that will be used to store recorded footage
# from the cameras as well as the web interface for the application.
# warning: this will overwrite any existing index.html files so be sure
# to choose a directory that doesn't have an existing website.
#
buffer_path = /var/opt/mow/buf
# this is the work directory the app will use to store live footage and # this is the work directory the app will use to store live footage and
# image frames. it's recommended to use a ram disk for this since there # image frames. it's recommended to use a ram disk for this since there
# will be large amounts of io occuring here. 1g of space per camera is # will be large amounts of io occuring here. 1GB of space per camera is
# a good rule of thumb. # a good rule of thumb.
# #
rec_path = /var/footage
# this is video output directory that will be used to store any footage
# that contain any motion events.
#
live_secs = 30
# this is the maximum amount of seconds worth of live footage to keep in
# buffer_path before deleting the oldest 2 seconds worth of footage.
# note: each video clip in buffer_path should be 2 seconds long.
#
cam_name = cam-1 cam_name = cam-1
# this is the optional camera name parameter to identify the camera. this # this is the optional camera name parameter to identify the camera. this
# name will also be used to as the base directory in web_root. if not # name will also be used to as the base directory in buffer_path or
# defined, the name of the config file will be used. # rec_path. if not defined, the name of the config file will be used.
# #
max_event_secs = 30 max_event_secs = 30
# this is the maximum amount of secs of video footage that can be # this is the maximum amount of secs of video footage that can be recorded
# recorded in a motion event. # in a motion event.
# #
img_comp_cmd = magick compare -metric FUZZ &prev& &next& /dev/null img_comp_cmd = magick compare -metric FUZZ &prev& &next& /dev/null
# this is the command line template this application will use when calling # this is the command line template this application will use when calling
@ -74,14 +78,35 @@ img_comp_out = stderr
# use to output the comparison score. this can only be stderr or stdout, # use to output the comparison score. this can only be stderr or stdout,
# any other stream name is considered invalid. # any other stream name is considered invalid.
# #
stream_codec = copy
# this is the encoding codec to use when recording footage from the camera.
# the list of supported codecs entirely depend on the hosts' ffmpeg
# installation. run 'ffmpeg -codecs' to determine this list. if not
# defined, 'copy' will be used as in it will just copy the codec format
# from camera itself.
#
stream_ext = .avi
# this is the file extension that will be used to when recording footage
# from the camera in buffer_path. ffmpeg will also use this to determine
# what format container to use for the video clips.
#
thumbnail_ext = .jpg
# this the image format that will be used when creating the thumbnails
# for the videos clips recorded to rec_path.
#
rec_ext = .avi
# this the the file extension this will be used when storaging motion
# footage to rec_path. ffmpeg will also use this to determine what format
# container to use for the video clips.
#
img_thresh = 8000 img_thresh = 8000
# this parameter defines the score threshold from img_comp_cmd that will # this parameter defines the score threshold from img_comp_cmd that will
# be considered motion. any motion events will queue up max_event_secs # be considered motion. any motion events will queue up max_event_secs
# worth of hls clips to be written out to web_root. # worth of video clips to be written out to rec_path.
# #
max_events = 100 max_events = 100
# this indicates the maximum amount of motion event video clips to keep # this indicates the maximum amount of motion event video clips to keep
# before deleting the oldest clip. # in rec_path before deleting the oldest clip.
# #
post_secs = 60 post_secs = 60
# this is the amount of seconds to wait before running the command # this is the amount of seconds to wait before running the command
@ -93,23 +118,31 @@ post_cmd = move_the_ptz_camera.py
# is to move a ptz camera to the next position of it's patrol pattern. # is to move a ptz camera to the next position of it's patrol pattern.
# note: the call to this command will be delayed if motion was detected. # note: the call to this command will be delayed if motion was detected.
# #
max_log_size = 50000 rec_fps = 30
# this is the maximum byte size of all log files that can be stored in # this sets the recording frames per second for the footage recorded
# web_root. whenever this limit is met, the log file will be deleted and # from the camera. this has no affect of using 'copy' as the
# then eventually recreated blank. # stream_codec.
# #
web_text = #dee5ee rec_scale = 1280:720
# this can be used to customize the color of the text in the web # this sets the pixel scale of the recorded footage from the camera. it
# interface. it can be defined as any color understood by html5 standard. # uses width, height numeric strings seperated by a colon, eg W:H. this
# has no affect of using 'copy' as the stream_codec.
# #
web_bg = #485564 img_scale = 320:240
# this can be used to customize the background color of the web # this sets the pixel size of the thumbnails for recorded stored in
# interface. just like web_text, it also follows the html5 standard. # rec_path. it uses width, height numeric strings seperated by a colon,
# eg W:H.
# #
web_font = courier service_user = mow
# this will customize the text font family to use in the web interface. # this sets the service local user of the application dictating the
# it is recommended to use mono-spaced font because this is also used to # amount privilege it will have on the host. if not defined, the
# display logs and logs are best displayed in mono-spaced font. # unprivileged user 'mow' will be used. which ever user is defined here
# just make sure it has read/write access to buffer_path and rec_path.
#
service_group = mow
# this sets the service local group of the application that can further
# refine host privileges. if not defined, the name stored in
# service_user will be used.
``` ```
### Setup/Build/Install ### ### Setup/Build/Install ###

View File

@ -3,6 +3,10 @@ if [ -f "/opt/mow/uninst" ]; then
mow -u -f mow -u -f
fi fi
if [ ! -d "/opt" ]; then
mkdir /opt
fi
if [ ! -d "/opt/mow" ]; then if [ ! -d "/opt/mow" ]; then
mkdir /opt/mow mkdir /opt/mow
fi fi

View File

@ -212,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();
auto thrCount = QThread::idealThreadCount() / 2; auto thrCount = QThread::idealThreadCount() / 2;
@ -228,8 +229,6 @@ bool rdConf(const QString &filePath, shared_t *share)
share->streamExt = ".avi"; share->streamExt = ".avi";
share->recExt = ".avi"; share->recExt = ".avi";
share->thumbExt = ".jpg"; share->thumbExt = ".jpg";
share->imgThreads = thrCount;
share->recThreads = thrCount;
share->recFps = 30; share->recFps = 30;
share->liveSecs = 80; share->liveSecs = 80;
share->recScale = "1280:720"; share->recScale = "1280:720";
@ -259,12 +258,11 @@ bool rdConf(const QString &filePath, shared_t *share)
rdLine("stream_ext = ", line, &share->streamExt); rdLine("stream_ext = ", line, &share->streamExt);
rdLine("rec_ext = ", line, &share->recExt); rdLine("rec_ext = ", line, &share->recExt);
rdLine("thumbnail_ext = ", line, &share->thumbExt); rdLine("thumbnail_ext = ", line, &share->thumbExt);
rdLine("img_threads = ", line, &share->imgThreads);
rdLine("rec_threads = ", line, &share->recThreads);
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_user = ", line, &share->servUser);
rdLine("service_group = ", line, &share->servGroup);
rdLine("live_secs = ", line, &share->liveSecs); rdLine("live_secs = ", line, &share->liveSecs);
} }
@ -311,31 +309,14 @@ bool rdConf(const QString &filePath, shared_t *share)
{ {
share->evMaxSecs = share->liveSecs - 4; share->evMaxSecs = share->liveSecs - 4;
} }
share->retCode = EACCES; if (share->servGroup.isEmpty())
if (!mkPath(share->recPath))
{ {
QTextStream(stderr) << "err: failed to create recording directory - " << share->recPath << " check for write permissions." << Qt::endl; share->servGroup = share->servUser;
}
else if (!mkPath(share->buffPath))
{
QTextStream(stderr) << "err: failed to create buffer directory - " << share->buffPath << " check for write permissions." << Qt::endl;
}
else if (!mkPath(share->buffPath + "/img"))
{
QTextStream(stderr) << "err: failed to create 'img' in the buffer directory - " << share->buffPath << "/img" << " check for write permissions." << Qt::endl;
}
else if (!mkPath(share->buffPath + "/live"))
{
QTextStream(stderr) << "err: failed to create 'live' in the buffer directory - " << share->buffPath << "/live" << " check for write permissions." << Qt::endl;
}
else
{
share->retCode = 0;
share->servMainLoop = QString(APP_BIN) + ".main_loop." + share->camName;
share->servVidLoop = QString(APP_BIN) + ".vid_loop." + share->camName;
} }
share->servMainLoop = QString(APP_BIN) + ".main_loop." + share->camName;
share->servVidLoop = QString(APP_BIN) + ".vid_loop." + share->camName;
} }
return share->retCode == 0; return share->retCode == 0;

View File

@ -30,7 +30,7 @@
using namespace std; using namespace std;
#define APP_VER "3.3.t8" #define APP_VER "3.3.t9"
#define APP_NAME "Motion Watch" #define APP_NAME "Motion Watch"
#define APP_BIN "mow" #define APP_BIN "mow"
#define DATETIME_FMT "yyyyMMddhhmmss" #define DATETIME_FMT "yyyyMMddhhmmss"
@ -74,12 +74,11 @@ struct shared_t
QString servMainLoop; QString servMainLoop;
QString servVidLoop; QString servVidLoop;
QString servUser; QString servUser;
QString servGroup;
bool singleTenant; bool singleTenant;
bool skipCmd; bool skipCmd;
int liveSecs; int liveSecs;
int recFps; int recFps;
int imgThreads;
int recThreads;
int evMaxSecs; int evMaxSecs;
int postSecs; int postSecs;
int imgThresh; int imgThresh;

View File

@ -64,7 +64,7 @@ int loadService(const QString &desc, const QString &user, const QString &servNam
return ret; return ret;
} }
int loadSh(const QString &name, const QString &exeCmd, const QString &workDir) 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); QFile file("/usr/bin/" + name);
@ -79,11 +79,18 @@ int loadSh(const QString &name, const QString &exeCmd, const QString &workDir)
else else
{ {
file.write("#!/bin/sh\n\n"); file.write("#!/bin/sh\n\n");
file.write("cd " + workDir.toUtf8() + "\n"); file.write("cd " + buffDir.toUtf8() + "\n");
file.write(exeCmd.toUtf8() + "\n"); file.write(exeCmd.toUtf8() + "\n");
file.close(); file.close();
mkPath(buffDir);
mkPath(outDir);
mkPath(buffDir + "/live");
mkPath(buffDir + "/img");
QProcess::execute("chmod", {"-v", "+x", file.fileName()}); QProcess::execute("chmod", {"-v", "+x", file.fileName()});
QProcess::execute("chown", {servUser + ":" + servGroup, "-R", buffDir});
QProcess::execute("chown", {servUser + ":" + servGroup, "-R", outDir});
} }
file.close(); file.close();
@ -135,8 +142,8 @@ int loadServiceByConf(const QString &confFile, bool start)
{ {
auto desc = QString(APP_NAME) + " - " + conf.camName; auto desc = QString(APP_NAME) + " - " + conf.camName;
if (ret == 0) ret = loadSh(conf.servMainLoop, camCmdFromConf(&conf, MAIN_LOOP), conf.buffPath); 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); 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.servMainLoop, conf.buffPath, conf.recPath, start);
if (ret == 0) ret = loadService(desc, conf.servUser, conf.servVidLoop, conf.buffPath, conf.recPath, start); if (ret == 0) ret = loadService(desc, conf.servUser, conf.servVidLoop, conf.buffPath, conf.recPath, start);
@ -171,11 +178,7 @@ int rmServiceByConf(const QString &confFile)
conf.retCode = rmService(conf.servMainLoop); conf.retCode = rmService(conf.servMainLoop);
conf.retCode = rmService(conf.servVidLoop); conf.retCode = rmService(conf.servVidLoop);
QDir live(conf.buffPath + "/live"); QDir(conf.buffPath).removeRecursively();
QDir img(conf.buffPath + "/img");
live.removeRecursively();
img.removeRecursively();
} }
return conf.retCode; return conf.retCode;

View File

@ -24,6 +24,6 @@ int loadServiceByDir(const QString &path, bool start);
int rmServiceByConf(const QString &confFile); int rmServiceByConf(const QString &confFile);
int rmService(const QString &servName); int rmService(const QString &servName);
int rmServiceByDir(const QString &path); int rmServiceByDir(const QString &path);
int loadSh(const QString &name, const QString &exeCmd, const QString &workDir); int loadSh(const QString &name, const QString &exeCmd, const QString &buffDir, const QString &outDir, const QString &servUser, const QString &servGroup);
#endif // SERVICES_H #endif // SERVICES_H