strix

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

__init__.py (6336B)


      1 # strix/__init__.py
      2 #
      3 # Copyright (C) 2017 Brian C. Lane
      4 #
      5 # This program is free software; you can redistribute it and/or modify
      6 # it under the terms of the GNU General Public License as published by
      7 # the Free Software Foundation; either version 2 of the License, or
      8 # (at your option) any later version.
      9 #
     10 # This program is distributed in the hope that it will be useful,
     11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 # GNU General Public License for more details.
     14 #
     15 # You should have received a copy of the GNU General Public License
     16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 import multiprocessing as mp
     18 import os
     19 import re
     20 import time
     21 
     22 from . import api
     23 from . import cmdline
     24 from . import events
     25 from . import queue
     26 from . import logger
     27 from . import motion
     28 
     29 ## Check the motion args
     30 ## Start the queue watcher thread
     31 ## Start the bottle/API thread
     32 ## Wait for a signal to shutdown
     33 
     34 def check_motion_config(config_path):
     35     """ Check the config file to make sure the settings match what Strix needs.
     36     """
     37     picture_filename = "%Y-%m-%d/%v/%H-%M-%S-%q"
     38     on_event_end_re = r".*touch (.*)/queue/Camera%t_%Y-%m-%d_%v"
     39     target_dir_re = r"(.*)/Camera\d+"
     40 
     41     errors = []
     42     base_target_dir = ""
     43     base_queue_dir = ""
     44     cameras = []
     45     found_pf = False
     46 
     47     cfg = motion.config.MotionConfig(config_path)
     48 
     49     # If there are threads, check their settings instead
     50     for c in [cfg.config] + \
     51              [cfg.thread[cc] for cc in filter(lambda k: k.startswith("thread"), cfg.config.keys())]:
     52         if c.get("picture_filename", "") == picture_filename:
     53             found_pf = True
     54 
     55         on_event_end = c.get("on_event_end", "")
     56         if on_event_end:
     57             em = re.match(on_event_end_re, on_event_end)
     58             if not em or not em.groups():
     59                 continue
     60             # Above errors will be caught by not having base_queue_dir set.
     61             if not base_queue_dir:
     62                 base_queue_dir = em.group(1)
     63             elif base_queue_dir != em.group(1):
     64                 errors += ["All of the paths in on_event_end MUST match."]
     65 
     66         target_dir = c.get("target_dir", "")
     67         if target_dir:
     68             tm = re.match(target_dir_re, target_dir)
     69             if not tm or not tm.groups():
     70                 continue
     71             # Above errors will be caught by not having base_target_dir set.
     72             if not base_target_dir:
     73                 base_target_dir = tm.group(1)
     74             elif base_target_dir != tm.group(1):
     75                 errors += ["All of the base paths in target_dir MUST match."]
     76 
     77         # Gather the info about the cameras and ports
     78         camera_name = c.get("camera_name", "")
     79         stream_port = c.get("stream_port", 8080)
     80         if camera_name:
     81             cameras.append((camera_name, stream_port))
     82 
     83     if not base_target_dir:
     84         errors += ["Could not find a target_dir setting. The last directory must be /CameraX"]
     85     if not base_queue_dir:
     86         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"]
     87     if base_target_dir and base_queue_dir and base_target_dir != base_queue_dir:
     88         errors += ["The target_dir and the base dir for on_event_end MUST match. eg. /var/lib/motion/"]
     89     if not found_pf:
     90         errors += ["picture_filename MUST be set to %s" % picture_filename]
     91 
     92     # Cameras are sorted by their stream_port
     93     cameras = sorted(cameras, key=lambda c: c[1])
     94     return (base_target_dir, cameras, errors)
     95 
     96 
     97 def run():
     98     parser = cmdline.parser(queue.max_cores())
     99     opts = parser.parse_args()
    100 
    101     try:
    102         (base_dir, cameras, errors) = check_motion_config(opts.config)
    103     except Exception as e:
    104         errors = [str(e)]
    105 
    106     if errors:
    107         def p_e(e):
    108             print("ERROR: %s" % e)
    109         list(map(p_e, errors))
    110         return False
    111 
    112     # Start logger thread
    113     logger_queue = mp.JoinableQueue()
    114     logger_quit = mp.Event()
    115     logger_thread = mp.Process(name="logger-thread",
    116                                 target=logger.listener,
    117                                 args=(logger_queue, logger_quit, opts.log))
    118     logger_thread.start()
    119     running_threads = [(logger_thread, logger_quit)]
    120 
    121     # Setup a console logger for the startup messages
    122     import logging
    123     log = logging.getLogger("startup-logging")
    124     log.setLevel(level=logging.DEBUG)
    125     ch = logging.StreamHandler()
    126     ch.setLevel(logging.DEBUG)
    127     log.addHandler(ch)
    128 
    129     # Initialize Event Cache settings
    130     events.EventCache.logger(log)
    131     events.EventCache.base_dir(base_dir)
    132     events.EventCache.keep(opts.keep_days)
    133     events.EventCache.check_cache(opts.check_cache)
    134     events.EventCache.cleanup_dq()
    135     events.preload_cache(log, base_dir)
    136 
    137     # Start queue monitor and processing thread (starts its own Multiprocessing threads)
    138     queue_path = os.path.abspath(os.path.join(base_dir, "queue/"))
    139     if not os.path.exists(queue_path):
    140         print("ERROR: %s does not exist. Is motion running?" % queue_path)
    141         return False
    142     queue_quit = mp.Event()
    143     queue_rx, queue_tx = mp.Pipe(False)
    144     queue_thread = mp.Process(name="queue-thread",
    145                               target=queue.monitor_queue,
    146                               args=(logger_queue, base_dir, queue_quit, opts.max_cores, queue_tx))
    147     queue_thread.start()
    148     running_threads += [(queue_thread, queue_quit)]
    149 
    150     # Start API thread (may start its own threads to handle requests)
    151     api_quit = mp.Event()
    152     api_thread = mp.Process(name="api-thread",
    153                             target=api.run_api,
    154                             args=(logger_queue, base_dir, cameras, opts.host, opts.port, opts.debug, queue_rx))
    155     api_thread.start()
    156     running_threads += [(api_thread, api_quit)]
    157 
    158     # Wait until it is told to exit
    159     try:
    160         while True:
    161             time.sleep(10)
    162     except Exception as e:
    163         print("ERROR: %s" % e)
    164     except KeyboardInterrupt:
    165         print("Exiting due to ^C")
    166 
    167     # Tell the threads to quit
    168     for _thread, event in running_threads:
    169         event.set()
    170 
    171     # Wait until everything is done
    172     print("Waiting for threads to quit")
    173     for thread, _event in running_threads:
    174         thread.join()
    175 
    176     return True