strix

A simple web UI for motion
git clone https://www.brianlane.com/git/strix
Log | Files | Refs | LICENSE

commit dd1fc2d596c1989a1134208a22d0a4b8c89f0c7c
Author: Brian C. Lane <bcl@brianlane.com>
Date:   Sun, 18 Jun 2017 10:16:34 -0700

Motion configuration parsing and checks

Diffstat:
A.gitignore | 3+++
Abin/strix | 22++++++++++++++++++++++
Arequirements.txt | 0
Asrc/motion/config.py | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/strix/__init__.py | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/strix/api.py | 21+++++++++++++++++++++
Asrc/strix/cmdline.py | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/strix/queue.py | 22++++++++++++++++++++++
Atest/motion.conf | 28++++++++++++++++++++++++++++
Atest/thread-1.conf | 35+++++++++++++++++++++++++++++++++++
Atest/thread-2.conf | 35+++++++++++++++++++++++++++++++++++
11 files changed, 368 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +*.swp +__pycache__ +venv diff --git a/bin/strix b/bin/strix @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# +# strix +# +# Copyright (C) 2017 Brian C. Lane +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import strix + +if __name__ == '__main__': + strix.run() diff --git a/requirements.txt b/requirements.txt diff --git a/src/motion/config.py b/src/motion/config.py @@ -0,0 +1,54 @@ +import os + +class MotionConfig(): + """ Parse a motion configuration file into dicts + + Last key wins. Threads are stored in self.thread["thread-N"] + """ + config = {} + thread = {} + _thread_n = 0 + + def thread_n(self): + """ Return an incrementing thread-N string + + :returns: str + """ + self._thread_n += 1 + return "thread-%d" % self._thread_n + + def split(self, s): + """ Split the line into key and optional values. + + :returns: (k, v) where v may be "" + """ + try: + k, v = s.strip().split(" ", 1) + + # thread is a special case, can be more than 1 + if k == "thread": + k = self.thread_n() + except ValueError: + k = s + v = "" + return (k, v) + + def parse(self, config_path): + """ Parse a motion config file + + :returns: dict + """ + with open(config_path) as f: + return dict([ + self.split(line) + for line in f.readlines() + if line.strip() and not line.startswith("#")]) + + def __init__(self, config_path): + self.config = self.parse(config_path) + for t in filter(lambda k: k.startswith("thread"), self.config.keys()): + thread_path = self.config[t] + if not thread_path.startswith("/"): + # Turn the relative path into an absolute one using the config_path + thread_path = os.path.abspath(os.path.join(os.path.dirname(config_path), thread_path)) + self.thread[t] = self.parse(thread_path) diff --git a/src/strix/__init__.py b/src/strix/__init__.py @@ -0,0 +1,95 @@ +# strix/__init__.py +# +# Copyright (C) 2017 Brian C. Lane +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import re + +import strix.cmdline +import motion.config + +## Check the motion args +## Start the queue watcher thread +## Start the bottle/API thread +## Wait for a signal to shutdown + + +def check_motion_config(config_path): + """ Check the config file to make sure the settings match what Strix needs. + """ + picture_filename = "%Y-%m-%d/%v/%H-%M-%S-%q" + on_event_end_re = r".*touch (.*)/queue/Camera%t_%Y-%m-%d_%v" + target_dir_re = r"(.*)/Camera\d+" + + errors = [] + base_target_dir = "" + base_queue_dir = "" + found_pf = False + + cfg = motion.config.MotionConfig(config_path) + + # If there are threads, check their settings instead + for c in [cfg.config] + \ + [cfg.thread[cc] for cc in filter(lambda k: k.startswith("thread"), cfg.config.keys())]: + if c.get("picture_filename") == picture_filename: + found_pf = True + if c.get("on_event_end"): + m = re.match(on_event_end_re, c.get("on_event_end")) + if not m: + continue + if not m.groups(): + continue + # Above errors will be caught by not having base_queue_dir set. + if not base_queue_dir: + base_queue_dir = m.group(1) + elif base_queue_dir != m.group(1): + errors += ["All of the paths in on_event_end MUST match."] + if c.get("target_dir"): + m = re.match(target_dir_re, c.get("target_dir")) + if not m: + continue + if not m.groups(): + continue + # Above errors will be caught by not having base_target_dir set. + if not base_target_dir: + base_target_dir = m.group(1) + elif base_target_dir != m.group(1): + errors += ["All of the base paths in target_dir MUST match."] + + if not base_target_dir: + errors += ["Could not find a target_dir setting. The last directory must be /CameraX"] + if not base_queue_dir: + errors += ["Could not find an on_event_end setting. It must be set to /usr/bin/touch <TARGET_DIR>/queue/Camera%t_%Y-%m-%d_%v"] + if base_target_dir and base_queue_dir and base_target_dir != base_queue_dir: + errors += ["The target_dir and the base dir for on_event_end MUST match. eg. /var/lib/motion/"] + if not found_pf: + errors += ["picture_filename MUST be set to %s" % picture_filename] + + return (base_target_dir, errors) + + +def run(): + parser = strix.cmdline.parser() + opts = parser.parse_args() + + try: + (base_dir, errors) = check_motion_config(opts.config) + except Exception as e: + errors = [e] + + if errors: + def p_e(e): + print("ERROR: %s" % e) + list(map(p_e, errors)) + return False diff --git a/src/strix/api.py b/src/strix/api.py @@ -0,0 +1,21 @@ +# api.py +# +# Copyright (C) 2017 Brian C. Lane +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import threading +from bottle import route, run + +class Api(threading.Threading): + pass diff --git a/src/strix/cmdline.py b/src/strix/cmdline.py @@ -0,0 +1,53 @@ +# cmdline.py +# +# Copyright (C) 2017 Brian C. Lane +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import argparse + +version = "DEVEL" + +def parser(): + """ Return the ArgumentParser""" + + parser = argparse.ArgumentParser(description="Motion Camera Web Interface") + + required = parser.add_argument_group("required arguments") + required.add_argument("-c", "--config", + help="Path to Motion config files", + required=True, metavar="MOTION") + + # optional arguments + optional = parser.add_argument_group("optional arguments") + optional.add_argument("-H", "--host", + help="Host or IP to bind to (127.0.0.1)", + metavar="HOSTNAME|IP", + default="127.0.0.1") + optional.add_argument("-P", "--port", + help="Post to bind to (8000)", + metavar="PORT", + default=8000) + optional.add_argument("-n", "--noqueue", + help="Do not process queue events", + action="store_true", default=False) + optional.add_argument("-l", "--log", + help="Path to logfile (/var/tmp/strix.log)", + metavar="LOGFILE", + default="/var/tmp/strix.log") + + # add the show version option + parser.add_argument("-V", help="show program's version number and exit", + action="version", version=version) + + return parser diff --git a/src/strix/queue.py b/src/strix/queue.py @@ -0,0 +1,22 @@ +# queue.py +# +# Copyright (C) 2017 Brian C. Lane +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +import threading + +## Handle watching the queue and dispatching movie creation and directory moving + +class Queue(threading.Threading): + pass diff --git a/test/motion.conf b/test/motion.conf @@ -0,0 +1,28 @@ +webcontrol_html_output on +webcontrol_port 7999 +setup_mode off +webcontrol_localhost off + +logfile /var/logs/motion.log + +# Common settings +picture_filename %Y-%m-%d/%v/%H-%M-%S-%q +# Strix uses this to process events. It needs to match the picture directory +on_event_end /usr/bin/touch /var/lib/motion/queue/Camera%t_%Y-%m-%d_%v +exif_text %D-%N-%i-%J-%K-%L +event_gap 30 +output_debug_pictures on + +locate_motion_style redbox +locate_motion_mode off +netcam_keepalive on +netcam_tolerant_check on + +ffmpeg_output_movies off +movie_filename %Y-%m-%d/%v/%H-%M-%S + +quality 85 +stream_quality 50 + +thread ./thread-1.conf +thread ./thread-2.conf diff --git a/test/thread-1.conf b/test/thread-1.conf @@ -0,0 +1,35 @@ +despeckle_filter EedDl +height 1536 +width 2048 +target_dir /var/lib/motion/Camera1 +netcam_url rtsp://SOMEURL:554/ +netcam_userpass root:root +threshold 9000 +noise_level 8 +smart_mask_speed 0 +framerate 10 +minimum_motion_frames 5 +pre_capture 5 +post_capture 50 +noise_tune off +stream_maxrate 10 +output_pictures on +stream_localhost off +ffmpeg_variable_bitrate 584 +ffmpeg_video_codec mp4 +text_changes on +auto_brightness off +stream_port 8081 +rotate 0 +stream_auth_method 0 +lightswitch 0 +emulate_motion off +snapshot_filename +snapshot_interval 0 +stream_motion on +text_double off +stream_authentication user: +text_left +max_movie_time 0 +text_right +mask_file /etc/motion/mask_1.pgm diff --git a/test/thread-2.conf b/test/thread-2.conf @@ -0,0 +1,35 @@ +despeckle_filter EedDl +threshold 5000 +noise_level 8 +height 1536 +netcam_userpass root:root +smart_mask_speed 0 +pre_capture 5 +post_capture 50 +noise_tune on +stream_maxrate 5 +output_pictures on +stream_localhost off +ffmpeg_variable_bitrate 584 +ffmpeg_video_codec mp4 +text_changes on +auto_brightness off +stream_port 8082 +rotate 0 +stream_auth_method 0 +lightswitch 0 +framerate 10 +emulate_motion off +snapshot_filename +snapshot_interval 0 +minimum_motion_frames 3 +stream_motion on +target_dir /var/lib/motion/Camera2 +netcam_url rtsp://SOMEURL:554/ +text_double off +width 2048 +stream_authentication user: +text_left +max_movie_time 0 +text_right +mask_file /etc/motioneye/mask_2.pgm