Compare commits
No commits in common. "33519acf7a4880aa8173b0037301c5caef2c60c2" and "970b9b5fc5647b6762a7f14636c447cce05b2911" have entirely different histories.
33519acf7a
...
970b9b5fc5
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -30,7 +30,6 @@ Makefile*
|
||||||
*build-*
|
*build-*
|
||||||
/build
|
/build
|
||||||
/app_dir
|
/app_dir
|
||||||
/installers
|
|
||||||
/release
|
/release
|
||||||
/debug
|
/debug
|
||||||
|
|
||||||
|
@ -55,3 +54,7 @@ compile_commands.json
|
||||||
|
|
||||||
# VSCode
|
# VSCode
|
||||||
/.vscode
|
/.vscode
|
||||||
|
|
||||||
|
# Build folders
|
||||||
|
/.build-mow
|
||||||
|
/.build-imagemagick
|
||||||
|
|
29
CMakeLists.txt
Normal file
29
CMakeLists.txt
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
cmake_minimum_required(VERSION 3.14)
|
||||||
|
|
||||||
|
project(MotionWatch LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
|
|
||||||
|
find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED)
|
||||||
|
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED)
|
||||||
|
|
||||||
|
add_executable(mow
|
||||||
|
src/main.cpp
|
||||||
|
src/common.h
|
||||||
|
src/common.cpp
|
||||||
|
src/camera.h
|
||||||
|
src/camera.cpp
|
||||||
|
src/services.h
|
||||||
|
src/services.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(mow Qt${QT_VERSION_MAJOR}::Core ${OpenCV_LIBS})
|
|
@ -1,20 +0,0 @@
|
||||||
QT -= gui
|
|
||||||
|
|
||||||
CONFIG += c++11 console
|
|
||||||
CONFIG -= app_bundle
|
|
||||||
|
|
||||||
TARGET = build/jmotion
|
|
||||||
OBJECTS_DIR = build
|
|
||||||
MOC_DIR = build
|
|
||||||
RCC_DIR = build
|
|
||||||
|
|
||||||
HEADERS += \
|
|
||||||
src/common.h \
|
|
||||||
src/camera.h \
|
|
||||||
src/services.h
|
|
||||||
|
|
||||||
SOURCES += \
|
|
||||||
src/common.cpp \
|
|
||||||
src/camera.cpp \
|
|
||||||
src/services.cpp \
|
|
||||||
src/main.cpp
|
|
74
README.md
74
README.md
|
@ -1,38 +1,21 @@
|
||||||
# JustMotion #
|
# Motion Watch #
|
||||||
|
|
||||||
JustMotion is a video surveillance application that monitors the video feeds
|
Motion Watch is a video surveillance application that monitors the video feeds
|
||||||
of an IP or local camera and records only footage that contains motion. The
|
of an IP camera and records only footage that contains motion. The main
|
||||||
main advantage of this is reduced storage requirements as opposed to
|
advantage of this is reduced storage requirements as opposed to continuous
|
||||||
continuous recording because only video footage of interest is recorded to
|
recording because only video footage of interest is recorded to storage.
|
||||||
storage.
|
|
||||||
|
|
||||||
Sticking to the principle of doing the least as needed, this application is
|
|
||||||
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
|
|
||||||
that already implement the functions very well.
|
|
||||||
|
|
||||||
No user interface is implemented instead exteral applications are more than
|
|
||||||
welcome to interface with the buffer/footage directories to implement a
|
|
||||||
user interface of any flavor.
|
|
||||||
|
|
||||||
### Usage ###
|
### Usage ###
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: jmotion <argument>
|
Usage: mow <argument>
|
||||||
|
|
||||||
-h : display usage information about this application.
|
-h : display usage information about this application.
|
||||||
-c : path to the config file used to run a single main loop instance.
|
-c : path to the config file used to run a single camera instance.
|
||||||
-i : all valid config files found in /etc/jmotion will be used to create
|
-d : path to a directory that can contain multiple config files.
|
||||||
a full instance; full meaning main and vid loop systemd services
|
each file found in the directory will be used to run a
|
||||||
will be created for each config file.
|
camera instance.
|
||||||
-d : this is the same as -i except it will not auto start the services.
|
|
||||||
-v : display the current version.
|
-v : display the current version.
|
||||||
-u : uninstall the entire app from your system, including all
|
|
||||||
systemd services related to it.
|
|
||||||
-f : force an action without pausing for user confirmation.
|
|
||||||
-l : list all jmotion services along with statuses.
|
|
||||||
-r : remove all jmotion services.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Config File ###
|
### Config File ###
|
||||||
|
@ -95,20 +78,14 @@ 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.
|
||||||
#
|
#
|
||||||
vid_codec = copy
|
stream_codec = copy
|
||||||
# this is the encoding codec to use when recording footage from the camera.
|
# this is the encoding codec to use when recording footage from the camera.
|
||||||
# the list of supported codecs entirely depend on the hosts' ffmpeg
|
# the list of supported codecs entirely depend on the hosts' ffmpeg
|
||||||
# installation. run 'ffmpeg -codecs' to determine this list. if not
|
# installation. run 'ffmpeg -codecs' to determine this list. if not
|
||||||
# defined, 'copy' will be used as in it will just copy the codec format
|
# defined, 'copy' will be used as in it will just copy the codec format
|
||||||
# from the camera itself.
|
# from camera itself.
|
||||||
#
|
#
|
||||||
aud_codec = copy
|
stream_ext = .avi
|
||||||
# this is the audio encoding codec to use when recording from the camera.
|
|
||||||
# the list of supported audio codes can be determined by running 'ffmpeg
|
|
||||||
# -codecs' on the host machine. if not defined, 'copy' will be used as in
|
|
||||||
# it will directly copy the audio stream from the camera if present.
|
|
||||||
#
|
|
||||||
stream_ext = .mkv
|
|
||||||
# this is the file extension that will be used to when recording footage
|
# 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
|
# from the camera in buffer_path. ffmpeg will also use this to determine
|
||||||
# what format container to use for the video clips.
|
# what format container to use for the video clips.
|
||||||
|
@ -117,7 +94,7 @@ thumbnail_ext = .jpg
|
||||||
# this the image format that will be used when creating the thumbnails
|
# this the image format that will be used when creating the thumbnails
|
||||||
# for the videos clips recorded to rec_path.
|
# for the videos clips recorded to rec_path.
|
||||||
#
|
#
|
||||||
rec_ext = .mkv
|
rec_ext = .avi
|
||||||
# this the the file extension that will be used when storing motion footage
|
# this the the file extension that will be used when storing motion footage
|
||||||
# to rec_path. ffmpeg will also use this to determine what format container
|
# to rec_path. ffmpeg will also use this to determine what format container
|
||||||
# to use for the video clips.
|
# to use for the video clips.
|
||||||
|
@ -144,12 +121,13 @@ post_cmd = move_the_ptz_camera.py
|
||||||
#
|
#
|
||||||
rec_fps = 30
|
rec_fps = 30
|
||||||
# this sets the recording frames per second for the footage recorded
|
# this sets the recording frames per second for the footage recorded
|
||||||
# from the camera. this has no affect if using 'copy' as the vid_codec.
|
# from the camera. this has no affect if using 'copy' as the
|
||||||
|
# stream_codec.
|
||||||
#
|
#
|
||||||
rec_scale = 1280:720
|
rec_scale = 1280:720
|
||||||
# this sets the pixel scale of the recorded footage from the camera. it
|
# this sets the pixel scale of the recorded footage from the camera. it
|
||||||
# uses width, height numeric strings seperated by a colon, eg W:H. this
|
# uses width, height numeric strings seperated by a colon, eg W:H. this
|
||||||
# has no affect of using 'copy' as the vid_codec.
|
# has no affect of using 'copy' as the stream_codec.
|
||||||
#
|
#
|
||||||
img_scale = 320:240
|
img_scale = 320:240
|
||||||
# this sets the pixel size of the thumbnails for recorded stored in
|
# this sets the pixel size of the thumbnails for recorded stored in
|
||||||
|
@ -168,20 +146,18 @@ service_group = mow
|
||||||
# service_user will be used.
|
# service_user will be used.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build/Install ###
|
### Setup/Build/Install ###
|
||||||
|
|
||||||
This application is currently only compatible with a Linux based operating
|
This application is currently only compatible with a Linux based operating
|
||||||
systems that are capable of installing python3 and the QT API (QT6.X.X or
|
systems that are capable of installing the QT API.
|
||||||
better).
|
|
||||||
|
|
||||||
```
|
```
|
||||||
./build.py <--run this first
|
sh ./setup.sh <--- only need to run this once if compiling for the first
|
||||||
./install.py <--run this next
|
sh ./build.sh time or if upgrading from the ground up.
|
||||||
|
sh ./install.sh
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
note 1: the build script will search for the QT api installed in your
|
note 1: be sure to run setup.sh and install.sh as root (or use sudo).
|
||||||
system. if not found, it will ask you where it is. either way
|
note 2: if building from scratch the following scripts will need to
|
||||||
it is recommended to install the QT API before running this
|
be run in this order - setup.sh -> build.sh -> install.sh.
|
||||||
script.
|
|
||||||
note 2: both scripts assume python3 is already installed.
|
|
||||||
```
|
```
|
||||||
|
|
233
build.py
233
build.py
|
@ -1,233 +0,0 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
import platform
|
|
||||||
|
|
||||||
def get_app_target(text):
|
|
||||||
return re.search(r'(APP_TARGET) +(\"(.*?)\")', text).group(3)
|
|
||||||
|
|
||||||
def get_app_ver(text):
|
|
||||||
return re.search(r'(APP_VERSION) +(\"(.*?)\")', text).group(3)
|
|
||||||
|
|
||||||
def get_app_name(text):
|
|
||||||
return re.search(r'(APP_NAME) +(\"(.*?)\")', text).group(3)
|
|
||||||
|
|
||||||
def get_qt_path():
|
|
||||||
try:
|
|
||||||
return str(subprocess.check_output(["qtpaths", "--binaries-dir"]), 'utf-8').strip()
|
|
||||||
|
|
||||||
except:
|
|
||||||
print("A direct call to 'qtpaths' has failed so automatic retrieval of the QT bin folder is not possible.")
|
|
||||||
|
|
||||||
return input("Please enter the QT bin path (leave blank to cancel the build): ")
|
|
||||||
|
|
||||||
def get_qt_from_cli():
|
|
||||||
for arg in sys.argv:
|
|
||||||
if arg == "-qt_dir":
|
|
||||||
index = sys.argv.index(arg)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return sys.argv[index + 1]
|
|
||||||
|
|
||||||
except:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def get_ver_header():
|
|
||||||
current_dir = os.path.dirname(__file__)
|
|
||||||
|
|
||||||
if current_dir == "":
|
|
||||||
return "src" + os.sep + "common.h"
|
|
||||||
else:
|
|
||||||
return current_dir + os.sep + "src" + os.sep + "common.h"
|
|
||||||
|
|
||||||
def get_nearest_subdir(path, sub_name):
|
|
||||||
dir_list = os.listdir(path)
|
|
||||||
ret = ""
|
|
||||||
|
|
||||||
for entry in dir_list:
|
|
||||||
if sub_name in entry:
|
|
||||||
ret = entry
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def cd():
|
|
||||||
current_dir = os.path.dirname(__file__)
|
|
||||||
|
|
||||||
if current_dir != "":
|
|
||||||
os.chdir(current_dir)
|
|
||||||
|
|
||||||
def verbose_copy(src, dst):
|
|
||||||
print("cpy: " + src + " --> " + dst)
|
|
||||||
|
|
||||||
if os.path.isdir(src):
|
|
||||||
if os.path.exists(dst) and os.path.isdir(dst):
|
|
||||||
shutil.rmtree(dst)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# ignore errors thrown by shutil.copytree()
|
|
||||||
# it's likely not actually failing to copy
|
|
||||||
# the directory but still throws errors if
|
|
||||||
# it fails to apply the same file stats as
|
|
||||||
# the source. this type of error can be
|
|
||||||
# ignored.
|
|
||||||
shutil.copytree(src, dst)
|
|
||||||
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif os.path.exists(src):
|
|
||||||
shutil.copyfile(src, dst)
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("wrn: " + src + " does not exists. skipping.")
|
|
||||||
|
|
||||||
def linux_build_app_dir(app_ver, app_name, app_target, qt_bin):
|
|
||||||
if not os.path.exists("app_dir/lib"):
|
|
||||||
os.makedirs("app_dir/lib")
|
|
||||||
|
|
||||||
verbose_copy("build/" + app_target, "app_dir/" + app_target)
|
|
||||||
|
|
||||||
shutil.copyfile("build/" + app_target, "/tmp/" + app_target)
|
|
||||||
# copying the executable file from the build folder to
|
|
||||||
# temp bypasses any -noexe retrictions a linux file
|
|
||||||
# system may have. there is a chance temp is also
|
|
||||||
# restricted in this way but that kind of config is
|
|
||||||
# rare. ldd will not run correctly with -noexe
|
|
||||||
# enabled.
|
|
||||||
|
|
||||||
lines = str(subprocess.check_output(["ldd", "/tmp/" + app_target]), 'utf-8').split("\n")
|
|
||||||
|
|
||||||
os.remove("/tmp/" + app_target)
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
if " => " in line and "libc" not in line:
|
|
||||||
#if ("libQt" in line) or ("libicu" in line) or ("libGL.so" in line) or ("libpcre16.so" in line) or ("libpcre.so" in line):
|
|
||||||
if " (0x0" in line:
|
|
||||||
start_index = line.index("> ") + 2
|
|
||||||
end_index = line.index(" (0x0")
|
|
||||||
src_file = line[start_index:end_index]
|
|
||||||
file_name = os.path.basename(src_file)
|
|
||||||
|
|
||||||
verbose_copy(src_file, "app_dir/lib/" + file_name)
|
|
||||||
|
|
||||||
if "/usr/lib/x86_64-linux-gnu/qt6/bin" == qt_bin:
|
|
||||||
verbose_copy(qt_bin + "/../../libQt6DBus.so.6", "app_dir/lib/libQt6DBus.so.6")
|
|
||||||
|
|
||||||
else:
|
|
||||||
verbose_copy(qt_bin + "/../lib/libQt6DBus.so.6", "app_dir/lib/libQt6DBus.so.6")
|
|
||||||
|
|
||||||
verbose_copy("templates/linux_run_script.sh", "app_dir/" + app_target + ".sh")
|
|
||||||
verbose_copy("templates/linux_uninstall.sh", "app_dir/uninstall.sh")
|
|
||||||
|
|
||||||
complete(app_ver, app_target)
|
|
||||||
|
|
||||||
def complete(app_ver, app_target):
|
|
||||||
print("Build complete for version: " + app_ver)
|
|
||||||
print("You can now run the install.py script to install onto this machine or create an installer.")
|
|
||||||
|
|
||||||
def get_like_distro():
|
|
||||||
info = platform.freedesktop_os_release()
|
|
||||||
ids = [info["ID"]]
|
|
||||||
|
|
||||||
if "ID_LIKE" in info:
|
|
||||||
# ids are space separated and ordered by precedence
|
|
||||||
ids.extend(info["ID_LIKE"].split())
|
|
||||||
|
|
||||||
return ids
|
|
||||||
|
|
||||||
def list_installed_packages():
|
|
||||||
like_distro = get_like_distro()
|
|
||||||
|
|
||||||
if ("ubuntu" in like_distro) or ("debian" in like_distro) or ("linuxmint" in like_distro):
|
|
||||||
return str(subprocess.check_output(["apt", "list", "--installed"]), 'utf-8')
|
|
||||||
|
|
||||||
elif ("fedora" in like_distro) or ("rhel" in like_distro):
|
|
||||||
return str(subprocess.check_output(["dnf", "list", "installed"]), 'utf-8')
|
|
||||||
|
|
||||||
elif ("arch" in like_distro):
|
|
||||||
return str(subprocess.check_output(["pacman", "-Q"]), 'utf-8')
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("Warning: unable to determine a package manager for this platform.")
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def list_of_words_in_text(list_of_words, text_body):
|
|
||||||
for word in list_of_words:
|
|
||||||
if not word in text_body:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def platform_setup():
|
|
||||||
ins_packages = list_installed_packages()
|
|
||||||
like_distro = get_like_distro()
|
|
||||||
dep_pkgs_a = ["pkg-config", "make", "g++"]
|
|
||||||
dep_pkgs_b = ["ffmpeg", "libfuse-dev", "fuse3", "imagemagick"]
|
|
||||||
|
|
||||||
if not list_of_words_in_text(dep_pkgs_a, ins_packages) or not list_of_words_in_text(dep_pkgs_b, ins_packages):
|
|
||||||
if ("ubuntu" in like_distro) or ("debian" in like_distro) or ("linuxmint" in like_distro):
|
|
||||||
subprocess.run(["sudo", "apt", "update", "-y"])
|
|
||||||
subprocess.run(["sudo", "apt", "install", "-y"] + dep_pkgs_a)
|
|
||||||
subprocess.run(["sudo", "apt", "install", "-y"] + dep_pkgs_b)
|
|
||||||
|
|
||||||
elif ("fedora" in like_distro) or ("rhel" in like_distro):
|
|
||||||
subprocess.run(["sudo", "dnf", "install", "-y"] + dep_pkgs_a)
|
|
||||||
subprocess.run(["sudo", "dnf", "install", "-y"] + dep_pkgs_b)
|
|
||||||
|
|
||||||
elif ("arch" in like_distro):
|
|
||||||
subprocess.run(["sudo", "pacman", "-S", "--noconfirm"] + dep_pkgs_a)
|
|
||||||
subprocess.run(["sudo", "pacman", "-S", "--noconfirm"] + dep_pkgs_b)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
platform_setup()
|
|
||||||
|
|
||||||
with open(get_ver_header()) as file:
|
|
||||||
text = file.read()
|
|
||||||
|
|
||||||
app_target = get_app_target(text)
|
|
||||||
app_ver = get_app_ver(text)
|
|
||||||
app_name = get_app_name(text)
|
|
||||||
qt_bin = get_qt_from_cli()
|
|
||||||
|
|
||||||
if qt_bin == "":
|
|
||||||
qt_bin = get_qt_path()
|
|
||||||
|
|
||||||
if qt_bin != "":
|
|
||||||
print("app_target = " + app_target)
|
|
||||||
print("app_version = " + app_ver)
|
|
||||||
print("app_name = " + app_name)
|
|
||||||
print("qt_bin = " + qt_bin)
|
|
||||||
|
|
||||||
cd()
|
|
||||||
|
|
||||||
result = subprocess.run([qt_bin + os.sep + "qmake", "-config", "release"])
|
|
||||||
|
|
||||||
if result.returncode == 0:
|
|
||||||
result = subprocess.run(["make"])
|
|
||||||
|
|
||||||
if result.returncode == 0:
|
|
||||||
if os.path.exists("app_dir"):
|
|
||||||
shutil.rmtree("app_dir")
|
|
||||||
|
|
||||||
os.makedirs("app_dir")
|
|
||||||
|
|
||||||
with open("app_dir" + os.sep + "info.txt", "w") as info_file:
|
|
||||||
info_file.write(app_target + "\n")
|
|
||||||
info_file.write(app_ver + "\n")
|
|
||||||
info_file.write(app_name + "\n")
|
|
||||||
|
|
||||||
linux_build_app_dir(app_ver, app_name, app_target, qt_bin)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
5
build.sh
Normal file
5
build.sh
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/bin/sh
|
||||||
|
mkdir -p ./.build-mow
|
||||||
|
cd ./.build-mow
|
||||||
|
cmake ..
|
||||||
|
make -j4
|
13
imgmagick_build.sh
Normal file
13
imgmagick_build.sh
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/sh
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
if [ ! -f "/usr/local/bin/magick" ]
|
||||||
|
then
|
||||||
|
apt install -y git
|
||||||
|
git clone https://github.com/ImageMagick/ImageMagick.git .build-imagemagick
|
||||||
|
cd .build-imagemagick
|
||||||
|
./configure
|
||||||
|
make
|
||||||
|
make install
|
||||||
|
ldconfig /usr/local/lib
|
||||||
|
fi
|
336
install.py
336
install.py
|
@ -1,336 +0,0 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import shutil
|
|
||||||
import platform
|
|
||||||
import sys
|
|
||||||
import zipfile
|
|
||||||
import binascii
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
def cd():
|
|
||||||
current_dir = os.path.dirname(__file__)
|
|
||||||
|
|
||||||
if current_dir != "":
|
|
||||||
os.chdir(current_dir)
|
|
||||||
|
|
||||||
def get_default_installer_path(app_ver, app_name):
|
|
||||||
if not os.path.exists("installers"):
|
|
||||||
os.makedirs("installers")
|
|
||||||
|
|
||||||
return "installers/" + app_name + "-" + app_ver + "-" + platform.system() + "-" + platform.machine() + ".run"
|
|
||||||
|
|
||||||
def make_install_dir(path, false_on_fail):
|
|
||||||
try:
|
|
||||||
if not os.path.exists(path):
|
|
||||||
os.makedirs(path)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
except:
|
|
||||||
if false_on_fail:
|
|
||||||
print("Failed to create directory: " + path + ", please make sure you are runnning this script with admin rights.")
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def make_app_dirs(app_target):
|
|
||||||
return make_install_dir("/etc/" + app_target, True) and make_install_dir("/opt/" + app_target, True) and make_install_dir("/var/buffer", False) and make_install_dir("/var/footage", False)
|
|
||||||
|
|
||||||
def replace_bin(binary, old_bin, new_bin, offs):
|
|
||||||
while(True):
|
|
||||||
try:
|
|
||||||
index = binary.index(old_bin, offs)
|
|
||||||
binary = binary[:index] + new_bin + binary[index + len(old_bin):]
|
|
||||||
|
|
||||||
except ValueError:
|
|
||||||
break
|
|
||||||
|
|
||||||
return binary
|
|
||||||
|
|
||||||
def bin_sub_copy_file(src, dst, old_bin, new_bin, offs):
|
|
||||||
binary = bytearray()
|
|
||||||
|
|
||||||
with open(src, "rb") as rd_file:
|
|
||||||
binary = rd_file.read()
|
|
||||||
binary = replace_bin(binary, old_bin, new_bin, offs)
|
|
||||||
|
|
||||||
with open(dst, "wb") as wr_file:
|
|
||||||
wr_file.write(binary)
|
|
||||||
|
|
||||||
def text_sub_copy_file(src, dst, old_text, new_text, offs):
|
|
||||||
bin_sub_copy_file(src, dst, old_text.encode("utf-8"), new_text.encode("utf-8"), offs)
|
|
||||||
|
|
||||||
def text_template_deploy(src, dst, install_dir, app_name, app_target):
|
|
||||||
print("dep: " + dst)
|
|
||||||
|
|
||||||
text_sub_copy_file(src, dst, "$install_dir", install_dir, 0)
|
|
||||||
text_sub_copy_file(dst, dst, "$app_name", app_name, 0)
|
|
||||||
text_sub_copy_file(dst, dst, "$app_target", app_target, 0)
|
|
||||||
|
|
||||||
def verbose_copy(src, dst):
|
|
||||||
print("cpy: " + src + " --> " + dst)
|
|
||||||
|
|
||||||
if os.path.isdir(src):
|
|
||||||
files = os.listdir(src)
|
|
||||||
|
|
||||||
if not os.path.exists(dst):
|
|
||||||
os.makedirs(dst)
|
|
||||||
|
|
||||||
for file in files:
|
|
||||||
tree_src = src + os.path.sep + file
|
|
||||||
tree_dst = dst + os.path.sep + file
|
|
||||||
|
|
||||||
if os.path.isdir(tree_src):
|
|
||||||
if not os.path.exists(tree_dst):
|
|
||||||
os.makedirs(tree_dst)
|
|
||||||
|
|
||||||
verbose_copy(tree_src, tree_dst)
|
|
||||||
|
|
||||||
else:
|
|
||||||
shutil.copyfile(src, dst)
|
|
||||||
|
|
||||||
def verbose_create_symmlink(src, dst):
|
|
||||||
print("lnk: " + src + " --> " + dst)
|
|
||||||
|
|
||||||
if os.path.exists(dst):
|
|
||||||
os.remove(dst)
|
|
||||||
|
|
||||||
os.symlink(src, dst)
|
|
||||||
|
|
||||||
def local_install(app_target, app_name):
|
|
||||||
install_dir = "/opt/" + app_target
|
|
||||||
|
|
||||||
if os.path.exists(install_dir + "/uninstall.sh"):
|
|
||||||
subprocess.run([install_dir + "/uninstall.sh"])
|
|
||||||
|
|
||||||
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/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/lib", install_dir + "/lib")
|
|
||||||
|
|
||||||
verbose_create_symmlink(install_dir + "/" + app_target + ".sh", "/usr/bin/" + app_target)
|
|
||||||
|
|
||||||
subprocess.run(["chmod", "755", install_dir + "/" + app_target + ".sh"])
|
|
||||||
subprocess.run(["chmod", "755", install_dir + "/" + app_target])
|
|
||||||
subprocess.run(["chmod", "755", install_dir + "/uninstall.sh"])
|
|
||||||
|
|
||||||
subprocess.run(["chmod", "777", "/var/buffer"])
|
|
||||||
subprocess.run(["chmod", "777", "/var/footage"])
|
|
||||||
|
|
||||||
print("Installation finished. If you ever need to uninstall this application, run this command with root rights:")
|
|
||||||
print(" sh " + install_dir + "/uninstall.sh\n")
|
|
||||||
|
|
||||||
def dir_tree(path):
|
|
||||||
ret = []
|
|
||||||
|
|
||||||
if os.path.isdir(path):
|
|
||||||
for entry in os.listdir(path):
|
|
||||||
full_path = os.path.join(path, entry)
|
|
||||||
|
|
||||||
if os.path.isdir(full_path):
|
|
||||||
for sub_dir_file in dir_tree(full_path):
|
|
||||||
ret.append(sub_dir_file)
|
|
||||||
|
|
||||||
else:
|
|
||||||
ret.append(full_path)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def to_hex(data):
|
|
||||||
return str(binascii.hexlify(data))[2:-1]
|
|
||||||
|
|
||||||
def from_hex(text_line):
|
|
||||||
return binascii.unhexlify(text_line)
|
|
||||||
|
|
||||||
def make_install(app_ver, app_name):
|
|
||||||
path = get_default_installer_path(app_ver, app_name)
|
|
||||||
|
|
||||||
with zipfile.ZipFile("app_dir.zip", "w", compression=zipfile.ZIP_DEFLATED) as zip_file:
|
|
||||||
print("Compressing app_dir --")
|
|
||||||
|
|
||||||
for file in dir_tree("app_dir"):
|
|
||||||
print("adding file: " + file)
|
|
||||||
zip_file.write(file)
|
|
||||||
|
|
||||||
text_sub_copy_file(__file__, path, "main(is_sfx=False)", "main(is_sfx=True)\n\n\n", 10322)
|
|
||||||
|
|
||||||
with open(path, "a") as dst_file, open("app_dir.zip", "rb") as src_file:
|
|
||||||
print("Packing the compressed app_dir into the sfx script file --")
|
|
||||||
|
|
||||||
dst_file.write("# APP_DIR\n")
|
|
||||||
|
|
||||||
stat = os.stat("app_dir.zip")
|
|
||||||
|
|
||||||
while(True):
|
|
||||||
buffer = src_file.read(4000000)
|
|
||||||
|
|
||||||
if len(buffer) != 0:
|
|
||||||
dst_file.write("# " + to_hex(buffer) + "\n")
|
|
||||||
|
|
||||||
print(str(src_file.tell()) + "/" + str(stat.st_size))
|
|
||||||
|
|
||||||
if len(buffer) < 4000000:
|
|
||||||
break
|
|
||||||
|
|
||||||
os.remove("app_dir.zip")
|
|
||||||
|
|
||||||
subprocess.run(["chmod", "+x", path])
|
|
||||||
|
|
||||||
print("Finished packing the app.")
|
|
||||||
print("Installer: " + path)
|
|
||||||
|
|
||||||
def sfx():
|
|
||||||
abs_sfx_path = os.path.abspath(__file__)
|
|
||||||
mark_found = False
|
|
||||||
|
|
||||||
os.chdir(tempfile.gettempdir())
|
|
||||||
|
|
||||||
with open(abs_sfx_path) as packed_file, open("app_dir.zip", "wb") as zip_file:
|
|
||||||
stat = os.stat(abs_sfx_path)
|
|
||||||
|
|
||||||
print("Unpacking the app_dir compressed file from the sfx script.")
|
|
||||||
|
|
||||||
while(True):
|
|
||||||
line = packed_file.readline()
|
|
||||||
|
|
||||||
if not line:
|
|
||||||
break
|
|
||||||
|
|
||||||
elif mark_found:
|
|
||||||
zip_file.write(from_hex(line[2:-1]))
|
|
||||||
|
|
||||||
print(str(packed_file.tell()) + "/" + str(stat.st_size))
|
|
||||||
|
|
||||||
else:
|
|
||||||
if line == "# APP_DIR\n":
|
|
||||||
mark_found = True
|
|
||||||
|
|
||||||
print("Done.")
|
|
||||||
|
|
||||||
if not mark_found:
|
|
||||||
print("The app_dir mark was not found, unable to continue.")
|
|
||||||
|
|
||||||
else:
|
|
||||||
with zipfile.ZipFile("app_dir.zip", "r", compression=zipfile.ZIP_DEFLATED) as zip_file:
|
|
||||||
print("De-compressing app_dir --")
|
|
||||||
|
|
||||||
zip_file.extractall()
|
|
||||||
|
|
||||||
print("Preparing for installation.")
|
|
||||||
|
|
||||||
os.remove("app_dir.zip")
|
|
||||||
|
|
||||||
with open("app_dir" + os.sep + "info.txt") as info_file:
|
|
||||||
info = info_file.read().split("\n")
|
|
||||||
|
|
||||||
local_install(info[0], info[2])
|
|
||||||
shutil.rmtree("app_dir")
|
|
||||||
|
|
||||||
def get_like_distro():
|
|
||||||
info = platform.freedesktop_os_release()
|
|
||||||
ids = [info["ID"]]
|
|
||||||
|
|
||||||
if "ID_LIKE" in info:
|
|
||||||
# ids are space separated and ordered by precedence
|
|
||||||
ids.extend(info["ID_LIKE"].split())
|
|
||||||
|
|
||||||
return ids
|
|
||||||
|
|
||||||
def list_installed_packages():
|
|
||||||
like_distro = get_like_distro()
|
|
||||||
|
|
||||||
if ("ubuntu" in like_distro) or ("debian" in like_distro) or ("linuxmint" in like_distro):
|
|
||||||
return str(subprocess.check_output(["apt", "list", "--installed"]), 'utf-8')
|
|
||||||
|
|
||||||
elif ("fedora" in like_distro) or ("rhel" in like_distro):
|
|
||||||
return str(subprocess.check_output(["dnf", "list", "installed"]), 'utf-8')
|
|
||||||
|
|
||||||
elif ("arch" in like_distro):
|
|
||||||
return str(subprocess.check_output(["pacman", "-Q"]), 'utf-8')
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("Warning: unable to determine a package manager for this platform.")
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def list_of_words_in_text(list_of_words, text_body):
|
|
||||||
for word in list_of_words:
|
|
||||||
if not word in text_body:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def platform_setup():
|
|
||||||
ins_packages = list_installed_packages()
|
|
||||||
like_distro = get_like_distro()
|
|
||||||
dep_pkgs_a = ["pkg-config"]
|
|
||||||
dep_pkgs_b = ["ffmpeg", "libfuse-dev", "fuse3", "imagemagick"]
|
|
||||||
|
|
||||||
if not list_of_words_in_text(dep_pkgs_a, ins_packages) or not list_of_words_in_text(dep_pkgs_b, ins_packages):
|
|
||||||
if ("ubuntu" in like_distro) or ("debian" in like_distro) or ("linuxmint" in like_distro):
|
|
||||||
subprocess.run(["sudo", "apt", "update", "-y"])
|
|
||||||
subprocess.run(["sudo", "apt", "install", "-y"] + dep_pkgs_a)
|
|
||||||
subprocess.run(["sudo", "apt", "install", "-y"] + dep_pkgs_b)
|
|
||||||
|
|
||||||
elif ("fedora" in like_distro) or ("rhel" in like_distro):
|
|
||||||
subprocess.run(["sudo", "dnf", "install", "-y"] + dep_pkgs_a)
|
|
||||||
subprocess.run(["sudo", "dnf", "install", "-y"] + dep_pkgs_b)
|
|
||||||
|
|
||||||
elif ("arch" in like_distro):
|
|
||||||
subprocess.run(["sudo", "pacman", "-S", "--noconfirm"] + dep_pkgs_a)
|
|
||||||
subprocess.run(["sudo", "pacman", "-S", "--noconfirm"] + dep_pkgs_b)
|
|
||||||
|
|
||||||
def main(is_sfx):
|
|
||||||
cd()
|
|
||||||
|
|
||||||
app_target = ""
|
|
||||||
app_ver = ""
|
|
||||||
app_name = ""
|
|
||||||
|
|
||||||
if not is_sfx:
|
|
||||||
with open("app_dir" + os.sep + "info.txt") as info_file:
|
|
||||||
info = info_file.read().split("\n")
|
|
||||||
|
|
||||||
app_target = info[0]
|
|
||||||
app_ver = info[1]
|
|
||||||
app_name = info[2]
|
|
||||||
|
|
||||||
if is_sfx:
|
|
||||||
sfx()
|
|
||||||
|
|
||||||
elif "--local" in sys.argv:
|
|
||||||
platform_setup()
|
|
||||||
local_install(app_target, app_name)
|
|
||||||
|
|
||||||
elif "--installer" in sys.argv:
|
|
||||||
make_install(app_ver, app_name)
|
|
||||||
|
|
||||||
else:
|
|
||||||
print("Do you want to install onto this machine or create an installer?")
|
|
||||||
print("[1] local machine")
|
|
||||||
print("[2] create installer")
|
|
||||||
print("[3] exit")
|
|
||||||
|
|
||||||
while(True):
|
|
||||||
opt = input("select an option: ")
|
|
||||||
|
|
||||||
if opt == "1":
|
|
||||||
subprocess.run(["sudo", "python3", "install.py", "--local"])
|
|
||||||
break
|
|
||||||
|
|
||||||
elif opt == "2":
|
|
||||||
subprocess.run(["python3", "install.py", "--installer"])
|
|
||||||
break
|
|
||||||
|
|
||||||
elif opt == "3":
|
|
||||||
break
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main(is_sfx=False)
|
|
54
install.sh
Normal file
54
install.sh
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#!/bin/sh
|
||||||
|
if [ -f "/opt/mow/uninst" ]; then
|
||||||
|
mow -u -f
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "/opt" ]; then
|
||||||
|
mkdir /opt
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "/opt/mow" ]; then
|
||||||
|
mkdir /opt/mow
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "/var/footage" ]; then
|
||||||
|
mkdir /var/footage
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "/etc/mow" ]; then
|
||||||
|
mkdir /etc/mow
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "/var/buffer" ]; then
|
||||||
|
mkdir /var/buffer
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp -v ./.build-mow/mow /opt/mow/bin
|
||||||
|
|
||||||
|
echo "writing /opt/mow/run"
|
||||||
|
printf "#!/bin/sh\n" > /opt/mow/run
|
||||||
|
printf "/opt/mow/bin \$1 \$2 \$3\n" >> /opt/mow/run
|
||||||
|
|
||||||
|
echo "writing /opt/mow/uninst"
|
||||||
|
printf "#!/bin/sh\n" > /opt/mow/uninst
|
||||||
|
printf "mow -r\n" >> /opt/mow/uninst
|
||||||
|
printf "rm -v /opt/mow/bin\n" >> /opt/mow/uninst
|
||||||
|
printf "rm -v /opt/mow/run\n" >> /opt/mow/uninst
|
||||||
|
printf "rm -v /opt/mow/uninst\n" >> /opt/mow/uninst
|
||||||
|
printf "rm -v /usr/bin/mow\n" >> /opt/mow/uninst
|
||||||
|
printf "rm -rv /opt/mow\n" >> /opt/mow/uninst
|
||||||
|
printf "deluser mow\n" >> /opt/mow/uninst
|
||||||
|
|
||||||
|
useradd -r mow
|
||||||
|
usermod -aG video mow
|
||||||
|
|
||||||
|
chown -R mow:mow /var/footage
|
||||||
|
chown -R mow:mow /var/buffer
|
||||||
|
|
||||||
|
chmod -v +x /opt/mow/run
|
||||||
|
chmod -v +x /opt/mow/bin
|
||||||
|
chmod -v +x /opt/mow/uninst
|
||||||
|
|
||||||
|
ln -sv /opt/mow/run /usr/bin/mow
|
||||||
|
|
||||||
|
sh imgmagick_build.sh
|
26
setup.sh
Normal file
26
setup.sh
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#!/bin/sh
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt update -y
|
||||||
|
apt install -y pkg-config cmake make g++
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]
|
||||||
|
then
|
||||||
|
apt install -y ffmpeg
|
||||||
|
apt install -y libavcodec-dev
|
||||||
|
apt install -y libavformat-dev
|
||||||
|
apt install -y libavutil-dev
|
||||||
|
apt install -y libswscale-dev
|
||||||
|
apt install -y x264
|
||||||
|
apt install -y libx264-dev
|
||||||
|
apt install -y libilmbase-dev
|
||||||
|
apt install -y qt6-base-dev
|
||||||
|
apt install -y qtchooser
|
||||||
|
apt install -y qmake6
|
||||||
|
apt install -y qt6-base-dev-tools
|
||||||
|
apt install -y libxkbcommon-dev
|
||||||
|
apt install -y libfuse-dev
|
||||||
|
apt install -y fuse3
|
||||||
|
sh imgmagick_build.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// This file is part of JustMotion.
|
// This file is part of Motion Watch.
|
||||||
|
|
||||||
// JustMotion is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
|
|
||||||
// JustMotion is distributed in the hope that it will be useful,
|
// Motion Watch is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
#ifndef CAMERA_H
|
#ifndef CAMERA_H
|
||||||
#define CAMERA_H
|
#define CAMERA_H
|
||||||
|
|
||||||
// This file is part of JustMotion.
|
// This file is part of Motion Watch.
|
||||||
|
|
||||||
// JustMotion is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
|
|
||||||
// JustMotion is distributed in the hope that it will be useful,
|
// Motion Watch is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// This file is part of JustMotion.
|
// This file is part of Motion Watch.
|
||||||
|
|
||||||
// JustMotion is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
|
|
||||||
// JustMotion is distributed in the hope that it will be useful,
|
// Motion Watch is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
@ -214,25 +214,26 @@ bool rdConf(const QString &filePath, shared_t *share)
|
||||||
share->recPath.clear();
|
share->recPath.clear();
|
||||||
share->servGroup.clear();
|
share->servGroup.clear();
|
||||||
|
|
||||||
share->retCode = 0;
|
auto thrCount = QThread::idealThreadCount() / 2;
|
||||||
share->imgThresh = 8000;
|
|
||||||
share->maxEvents = 30;
|
share->retCode = 0;
|
||||||
share->skipCmd = false;
|
share->imgThresh = 8000;
|
||||||
share->postSecs = 60;
|
share->maxEvents = 30;
|
||||||
share->evMaxSecs = 30;
|
share->skipCmd = false;
|
||||||
share->conf = filePath;
|
share->postSecs = 60;
|
||||||
share->outputType = "stderr";
|
share->evMaxSecs = 30;
|
||||||
share->compCmd = "magick compare -metric FUZZ " + QString(PREV_IMG) + " " + QString(NEXT_IMG) + " /dev/null";
|
share->conf = filePath;
|
||||||
share->vidCodec = "copy";
|
share->outputType = "stderr";
|
||||||
share->audCodec = "copy";
|
share->compCmd = "magick compare -metric FUZZ " + QString(PREV_IMG) + " " + QString(NEXT_IMG) + " /dev/null";
|
||||||
share->streamExt = ".mkv";
|
share->streamCodec = "copy";
|
||||||
share->recExt = ".mkv";
|
share->streamExt = ".avi";
|
||||||
share->thumbExt = ".jpg";
|
share->recExt = ".avi";
|
||||||
share->recFps = 30;
|
share->thumbExt = ".jpg";
|
||||||
share->liveSecs = 80;
|
share->recFps = 30;
|
||||||
share->recScale = "1280:720";
|
share->liveSecs = 80;
|
||||||
share->imgScale = "320:240";
|
share->recScale = "1280:720";
|
||||||
share->servUser = APP_TARGET;
|
share->imgScale = "320:240";
|
||||||
|
share->servUser = APP_BIN;
|
||||||
|
|
||||||
QString line;
|
QString line;
|
||||||
|
|
||||||
|
@ -253,8 +254,7 @@ bool rdConf(const QString &filePath, shared_t *share)
|
||||||
rdLine("max_events = ", line, &share->maxEvents);
|
rdLine("max_events = ", line, &share->maxEvents);
|
||||||
rdLine("img_comp_out = ", line, &share->outputType);
|
rdLine("img_comp_out = ", line, &share->outputType);
|
||||||
rdLine("img_comp_cmd = ", line, &share->compCmd);
|
rdLine("img_comp_cmd = ", line, &share->compCmd);
|
||||||
rdLine("vid_codec = ", line, &share->vidCodec);
|
rdLine("stream_codec = ", line, &share->streamCodec);
|
||||||
rdLine("aud_codec = ", line, &share->audCodec);
|
|
||||||
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);
|
||||||
|
@ -315,8 +315,8 @@ bool rdConf(const QString &filePath, shared_t *share)
|
||||||
share->servGroup = share->servUser;
|
share->servGroup = share->servUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
share->servMainLoop = QString(APP_TARGET) + ".main_loop." + share->camName;
|
share->servMainLoop = QString(APP_BIN) + ".main_loop." + share->camName;
|
||||||
share->servVidLoop = QString(APP_TARGET) + ".vid_loop." + share->camName;
|
share->servVidLoop = QString(APP_BIN) + ".vid_loop." + share->camName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return share->retCode == 0;
|
return share->retCode == 0;
|
||||||
|
|
15
src/common.h
15
src/common.h
|
@ -1,14 +1,14 @@
|
||||||
#ifndef COMMON_H
|
#ifndef COMMON_H
|
||||||
#define COMMON_H
|
#define COMMON_H
|
||||||
|
|
||||||
// This file is part of JustMotion.
|
// This file is part of Motion Watch.
|
||||||
|
|
||||||
// JustMotion is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
|
|
||||||
// JustMotion is distributed in the hope that it will be useful,
|
// Motion Watch is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
@ -30,9 +30,9 @@
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
#define APP_VERSION "3.4"
|
#define APP_VER "3.3"
|
||||||
#define APP_NAME "JustMotion"
|
#define APP_NAME "Motion Watch"
|
||||||
#define APP_TARGET "jmotion"
|
#define APP_BIN "mow"
|
||||||
#define DATETIME_FMT "yyyyMMddhhmmss"
|
#define DATETIME_FMT "yyyyMMddhhmmss"
|
||||||
#define STRFTIME_FMT "%Y%m%d%H%M%S"
|
#define STRFTIME_FMT "%Y%m%d%H%M%S"
|
||||||
#define PREV_IMG "&prev&"
|
#define PREV_IMG "&prev&"
|
||||||
|
@ -65,8 +65,7 @@ struct shared_t
|
||||||
QString recPath;
|
QString recPath;
|
||||||
QString outputType;
|
QString outputType;
|
||||||
QString compCmd;
|
QString compCmd;
|
||||||
QString vidCodec;
|
QString streamCodec;
|
||||||
QString audCodec;
|
|
||||||
QString streamExt;
|
QString streamExt;
|
||||||
QString recExt;
|
QString recExt;
|
||||||
QString thumbExt;
|
QString thumbExt;
|
||||||
|
|
47
src/main.cpp
47
src/main.cpp
|
@ -1,11 +1,11 @@
|
||||||
// This file is part of JustMotion.
|
// This file is part of Motion Watch.
|
||||||
|
|
||||||
// JustMotion is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
|
|
||||||
// JustMotion is distributed in the hope that it will be useful,
|
// Motion Watch is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
@ -14,13 +14,13 @@
|
||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
#include "services.h"
|
#include "services.h"
|
||||||
|
|
||||||
void showHelp(const QString etcDir)
|
void showHelp()
|
||||||
{
|
{
|
||||||
QTextStream(stdout) << APP_NAME << " " << APP_VERSION << Qt::endl << Qt::endl;
|
QTextStream(stdout) << APP_NAME << " " << APP_VER << Qt::endl << Qt::endl;
|
||||||
QTextStream(stdout) << "Usage: " << APP_TARGET << " <argument>" << Qt::endl << Qt::endl;
|
QTextStream(stdout) << "Usage: mow <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) << "-c : path to the config file used to run a single main loop instance." << Qt::endl;
|
QTextStream(stdout) << "-c : path to the config file used to run a single main loop instance." << Qt::endl;
|
||||||
QTextStream(stdout) << "-i : all valid config files found in " << etcDir << " will be used to create" << Qt::endl;
|
QTextStream(stdout) << "-i : all valid config files found in /etc/mow will be used to create" << Qt::endl;
|
||||||
QTextStream(stdout) << " a full instance; full meaning main and vid loop systemd services" << 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) << " 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) << "-d : this is the same as -i except it will not auto start the services." << Qt::endl;
|
||||||
|
@ -28,8 +28,8 @@ void showHelp(const QString etcDir)
|
||||||
QTextStream(stdout) << "-u : uninstall the entire app from your system, including all" << Qt::endl;
|
QTextStream(stdout) << "-u : uninstall the entire app from your system, including all" << Qt::endl;
|
||||||
QTextStream(stdout) << " systemd services related to it." << 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) << "-l : list all attached services to this application along with statuses." << Qt::endl;
|
QTextStream(stdout) << "-l : list all mow services along with statuses." << Qt::endl;
|
||||||
QTextStream(stdout) << "-r : remove all attached services." << Qt::endl;
|
QTextStream(stdout) << "-r : remove all mow services." << Qt::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
|
@ -37,35 +37,34 @@ int main(int argc, char** argv)
|
||||||
QCoreApplication app(argc, argv);
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
QCoreApplication::setApplicationName(APP_NAME);
|
QCoreApplication::setApplicationName(APP_NAME);
|
||||||
QCoreApplication::setApplicationVersion(APP_VERSION);
|
QCoreApplication::setApplicationVersion(APP_VER);
|
||||||
|
|
||||||
auto args = QCoreApplication::arguments();
|
auto args = QCoreApplication::arguments();
|
||||||
auto etcDir = "/etc/" + QString(APP_TARGET);
|
auto ret = 0;
|
||||||
auto ret = 0;
|
|
||||||
|
|
||||||
if (args.contains("-h"))
|
if (args.contains("-h"))
|
||||||
{
|
{
|
||||||
showHelp(etcDir);
|
showHelp();
|
||||||
}
|
}
|
||||||
else if (args.contains("-v"))
|
else if (args.contains("-v"))
|
||||||
{
|
{
|
||||||
QTextStream(stdout) << APP_VERSION << Qt::endl;
|
QTextStream(stdout) << APP_VER << Qt::endl;
|
||||||
}
|
}
|
||||||
else if (args.contains("-l"))
|
else if (args.contains("-l"))
|
||||||
{
|
{
|
||||||
servStatByDir(etcDir);
|
servStatByDir("/etc/mow");
|
||||||
}
|
}
|
||||||
else if (args.contains("-i") || args.contains("-d"))
|
else if (args.contains("-i") || args.contains("-d"))
|
||||||
{
|
{
|
||||||
ret = rmServiceByDir(etcDir);
|
ret = rmServiceByDir("/etc/mow");
|
||||||
|
|
||||||
if ((ret == 0) && args.contains("-i"))
|
if ((ret == 0) && args.contains("-i"))
|
||||||
{
|
{
|
||||||
ret = loadServiceByDir(etcDir, true);
|
ret = loadServiceByDir("/etc/mow", true);
|
||||||
}
|
}
|
||||||
else if (ret == 0)
|
else if (ret == 0)
|
||||||
{
|
{
|
||||||
ret = loadServiceByDir(etcDir, false);
|
ret = loadServiceByDir("/etc/mow", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (args.contains("-c"))
|
else if (args.contains("-c"))
|
||||||
|
@ -83,11 +82,11 @@ int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
if (args.contains("-f"))
|
if (args.contains("-f"))
|
||||||
{
|
{
|
||||||
ret = rmServiceByDir(etcDir);
|
ret = rmServiceByDir("/etc/mow");
|
||||||
|
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
ret = QProcess::execute("/opt/" + QString(APP_TARGET) + "/uninstall.sh", QStringList());
|
ret = QProcess::execute("/opt/mow/uninst", QStringList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -103,18 +102,18 @@ int main(int argc, char** argv)
|
||||||
|
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
ret = QProcess::execute("/opt/" + QString(APP_TARGET) + "/uninstall.sh", QStringList());
|
ret = QProcess::execute("/opt/mow/uninst", QStringList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (args.contains("-r"))
|
else if (args.contains("-r"))
|
||||||
{
|
{
|
||||||
ret = rmServiceByDir(etcDir);
|
ret = rmServiceByDir("/etc/mow");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
showHelp(etcDir);
|
showHelp();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// This file is part of JustMotion.
|
// This file is part of Motion Watch.
|
||||||
|
|
||||||
// JustMotion is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
|
|
||||||
// JustMotion is distributed in the hope that it will be useful,
|
// Motion Watch is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
@ -14,8 +14,6 @@
|
||||||
|
|
||||||
int loadService(const QString &desc, const QString &user, const QString &servName, const QString &workDir, const QString &recPath, bool start)
|
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");
|
QFile file("/lib/systemd/system/" + servName + ".service");
|
||||||
|
|
||||||
auto ret = 0;
|
auto ret = 0;
|
||||||
|
@ -45,12 +43,7 @@ int loadService(const QString &desc, const QString &user, const QString &servNam
|
||||||
file.write("Type=simple\n");
|
file.write("Type=simple\n");
|
||||||
file.write("User=" + user.toUtf8() + "\n");
|
file.write("User=" + user.toUtf8() + "\n");
|
||||||
file.write("Restart=always\n");
|
file.write("Restart=always\n");
|
||||||
|
file.write("TimeoutStopSec=infinity\n");
|
||||||
if (servName.contains("vid_loop"))
|
|
||||||
{
|
|
||||||
file.write("RuntimeMaxSec=61\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
file.write("ExecStart=/usr/bin/env " + servName.toUtf8() + "\n\n");
|
file.write("ExecStart=/usr/bin/env " + servName.toUtf8() + "\n\n");
|
||||||
file.write("[Install]\n");
|
file.write("[Install]\n");
|
||||||
file.write("WantedBy=multi-user.target\n");
|
file.write("WantedBy=multi-user.target\n");
|
||||||
|
@ -97,6 +90,7 @@ int loadSh(const QString &name, const QString &exeCmd, const QString &buffDir, c
|
||||||
|
|
||||||
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", buffDir});
|
||||||
|
QProcess::execute("chown", {servUser + ":" + servGroup, "-R", outDir});
|
||||||
}
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
@ -110,7 +104,7 @@ QString camCmdFromConf(shared_t *conf, CmdExeType type)
|
||||||
|
|
||||||
if (type == MAIN_LOOP)
|
if (type == MAIN_LOOP)
|
||||||
{
|
{
|
||||||
ret += QString(APP_TARGET) + " -c " + conf->conf;
|
ret += QString(APP_BIN) + " -c " + conf->conf;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -118,16 +112,15 @@ QString camCmdFromConf(shared_t *conf, CmdExeType type)
|
||||||
|
|
||||||
if (conf->recordUri.contains("rtsp"))
|
if (conf->recordUri.contains("rtsp"))
|
||||||
{
|
{
|
||||||
ret += "-rtsp_transport udp ";
|
ret += "-rtsp_transport tcp ";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conf->vidCodec != "copy")
|
if (conf->streamCodec != "copy")
|
||||||
{
|
{
|
||||||
ret += "-vf fps=" + QString::number(conf->recFps) + ",scale=" + conf->recScale + " ";
|
ret += "-vf fps=" + QString::number(conf->recFps) + ",scale=" + conf->recScale + " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
ret += "-vcodec " + conf->vidCodec + " ";
|
ret += "-vcodec " + conf->streamCodec + " ";
|
||||||
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 += "-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;
|
ret += conf->buffPath + "/live/" + QString(STRFTIME_FMT) + conf->streamExt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
#ifndef SERVICES_H
|
#ifndef SERVICES_H
|
||||||
#define SERVICES_H
|
#define SERVICES_H
|
||||||
|
|
||||||
// This file is part of JustMotion.
|
// This file is part of Motion Watch.
|
||||||
|
|
||||||
// JustMotion is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
|
|
||||||
// JustMotion is distributed in the hope that it will be useful,
|
// Motion Watch is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
export QTDIR=$install_dir
|
|
||||||
export QT_PLUGIN_PATH=$install_dir
|
|
||||||
export LD_LIBRARY_PATH=$install_dir/lib
|
|
||||||
$install_dir/$app_target $1 $2 $3
|
|
|
@ -1,6 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
$app_target -r
|
|
||||||
rm -v /usr/bin/$app_target
|
|
||||||
rm -rv $install_dir
|
|
||||||
deluser $app_target
|
|
||||||
echo "Uninstallation Complete"
|
|
Loading…
Reference in New Issue
Block a user