commit dd1fc2d596c1989a1134208a22d0a4b8c89f0c7c
Author: Brian C. Lane <bcl@brianlane.com>
Date: Sun, 18 Jun 2017 10:16:34 -0700
Motion configuration parsing and checks
Diffstat:
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