.pydevproject
*.conf
.settings
+.idea
run
dist
motioneye.egg-info
# schedule the next call
io_loop.add_timeout(datetime.timedelta(seconds=settings.CLEANUP_INTERVAL), _run_process)
- if not running(): # check that the previous process has finished
+ if not running(): # check that the previous process has finished
logging.debug('running cleanup process...')
_process = multiprocessing.Process(target=_do_cleanup)
_CAMERA_CONFIG_FILE_NAME = 'thread-%(id)s.conf'
_MAIN_CONFIG_FILE_NAME = 'motion.conf'
-_ACTIONS = ['lock', 'unlock', 'light_on', 'light_off', 'alarm_on', 'alarm_off', 'up', 'right', 'down', 'left', 'zoom_in', 'zoom_out',
- 'preset1', 'preset2', 'preset3', 'preset4', 'preset5', 'preset6', 'preset7', 'preset8', 'preset9']
+_ACTIONS = ['lock', 'unlock', 'light_on', 'light_off', 'alarm_on', 'alarm_off', 'up',
+ 'right', 'down', 'left', 'zoom_in', 'zoom_out',
+ 'preset1', 'preset2', 'preset3', 'preset4', 'preset5', 'preset6', 'preset7', 'preset8', 'preset9']
_main_config_cache = None
_camera_config_cache = {}
# when using the following video codecs, the ffmpeg_variable_bitrate parameter appears to have an exponential effect
_EXPONENTIAL_QUALITY_CODECS = ['mpeg4', 'msmpeg4', 'swf', 'flv', 'mov', 'mkv']
-_EXPONENTIAL_QUALITY_FACTOR = 100000 # voodoo
-_EXPONENTIAL_DEF_QUALITY = 511 # about 75%
+_EXPONENTIAL_QUALITY_FACTOR = 100000 # voodoo
+_EXPONENTIAL_DEF_QUALITY = 511 # about 75%
_MAX_FFMPEG_VARIABLE_BITRATE = 32767
-_KNOWN_MOTION_OPTIONS = set([
+_KNOWN_MOTION_OPTIONS = {
'auto_brightness',
'brightness',
'contrast',
'text_right',
'threshold',
'videodevice',
- 'width',
-])
+ 'width'
+}
def additional_section(func):
lines = None
try:
- file = open(config_file_path, 'r')
+ f = open(config_file_path, 'r')
except IOError as e:
if e.errno == errno.ENOENT: # file does not exist
logging.info('main config file %(path)s does not exist, using default values' % {'path': config_file_path})
lines = []
+ f = None
else:
logging.error('could not open main config file %(path)s: %(msg)s' % {
raise
- if lines is None:
+ if lines is None and f:
try:
- lines = [l[:-1] for l in file.readlines()]
+ lines = [l[:-1] for l in f.readlines()]
except Exception as e:
logging.error('could not read main config file %(path)s: %(msg)s' % {
raise
finally:
- file.close()
+ f.close()
if as_lines:
return lines
logging.debug('writing main config to %(path)s...' % {'path': config_file_path})
try:
- file = open(config_file_path, 'w')
+ f = open(config_file_path, 'w')
except Exception as e:
logging.error('could not open main config file %(path)s for writing: %(msg)s' % {
lines = _dict_to_conf(lines, main_config, list_names=['thread'])
try:
- file.writelines([utils.make_str(l) + '\n' for l in lines])
+ f.writelines([utils.make_str(l) + '\n' for l in lines])
except Exception as e:
logging.error('could not write main config file %(path)s: %(msg)s' % {
raise
finally:
- file.close()
+ f.close()
def get_camera_ids(filter_valid=True):
logging.debug('reading camera config from %(path)s...' % {'path': camera_config_path})
try:
- file = open(camera_config_path, 'r')
+ f = open(camera_config_path, 'r')
except Exception as e:
logging.error('could not open camera config file: %(msg)s' % {'msg': unicode(e)})
raise
try:
- lines = [l.strip() for l in file.readlines()]
+ lines = [l.strip() for l in f.readlines()]
except Exception as e:
logging.error('could not read camera config file %(path)s: %(msg)s' % {
raise
finally:
- file.close()
+ f.close()
if as_lines:
return lines
_set_default_simple_mjpeg_camera(camera_id, camera_config)
- else: # incomplete configuration
+ else: # incomplete configuration
logging.warn('camera config file at %s is incomplete, ignoring' % camera_config_path)
return None
logging.debug('writing camera config to %(path)s...' % {'path': camera_config_path})
try:
- file = open(camera_config_path, 'w')
+ f = open(camera_config_path, 'w')
except Exception as e:
logging.error('could not open camera config file %(path)s for writing: %(msg)s' % {
lines = _dict_to_conf(lines, camera_config)
try:
- file.writelines([utils.make_str(l) + '\n' for l in lines])
+ f.writelines([utils.make_str(l) + '\n' for l in lines])
except Exception as e:
logging.error('could not write camera config file %(path)s: %(msg)s' % {
raise
finally:
- file.close()
+ f.close()
def add_camera(device_details):
else:
host = device_details['username'] + '@' + host
- device_details['url'] = urlparse.urlunparse((device_details['scheme'], host, device_details['path'], '', '', ''))
+ device_details['url'] = urlparse.urlunparse(
+ (device_details['scheme'], host, device_details['path'], '', '', ''))
# determine the last camera id
camera_ids = get_camera_ids()
camera_config['width'] = 640
camera_config['height'] = 480
- else: # assuming mjpeg
+ else: # assuming mjpeg
camera_config['@proto'] = 'mjpeg'
camera_config['@url'] = device_details['url']
import smbctl
old_config = dict(old_config or {})
- main_config = get_main() # needed for surveillance password
+ main_config = get_main() # needed for surveillance password
data = {
# device
else:
data['hue'] = max(1, int(round(int(ui['hue']) * 2.55)))
- else: # assuming netcam
+ else: # assuming netcam
if data.get('netcam_url', old_config.get('netcam_url', '')).startswith('rtsp'):
# motion uses the configured width and height for RTSP cameras
width = int(ui['resolution'].split('x')[0])
threshold = int(float(ui['frame_change_threshold']) * width * height / 100)
- else: # width & height are not available for other netcams
+ else: # width & height are not available for other netcams
threshold = int(float(ui['frame_change_threshold']) * 640 * 480 / 100)
data['threshold'] = threshold
os.makedirs(data['target_dir'])
logging.debug('created root directory %s for camera %s' % (data['target_dir'], data['@name']))
- except Exception as e:
+ except OSError as e:
if isinstance(e, OSError) and e.errno == errno.EEXIST:
- pass # already exists, things should be just fine
+ pass # already exists, things should be just fine
else:
logging.error('failed to create root directory "%s": %s' % (data['target_dir'], e), exc_info=True)
q = int(ui['movie_quality'])
if motionctl.needs_ffvb_quirks():
if data['ffmpeg_video_codec'] in _EXPONENTIAL_QUALITY_CODECS:
- vbr = max(1, _MAX_FFMPEG_VARIABLE_BITRATE * (1 - math.log(max(1, q * _EXPONENTIAL_QUALITY_FACTOR), _EXPONENTIAL_QUALITY_FACTOR * 100)))
+ vbr = max(1, _MAX_FFMPEG_VARIABLE_BITRATE * (1 - math.log(max(1, q * _EXPONENTIAL_QUALITY_FACTOR),
+ _EXPONENTIAL_QUALITY_FACTOR * 100)))
else:
vbr = 1 + (_MAX_FFMPEG_VARIABLE_BITRATE - 1) / 100.0 * (100 - q)
if data.get('rotate') in [90, 270]:
capture_width, capture_height = capture_height, capture_width
- data['mask_file'] = utils.build_editable_mask_file(old_config['@id'], ui['mask_lines'], capture_width, capture_height)
+ data['mask_file'] = utils.build_editable_mask_file(old_config['@id'], ui['mask_lines'],
+ capture_width, capture_height)
# working schedule
if ui['working_schedule']:
if ui['email_notifications_enabled']:
emails = re.sub('\\s', '', ui['email_notifications_addresses'])
- on_event_start.append("%(script)s '%(server)s' '%(port)s' '%(account)s' '%(password)s' '%(tls)s' '%(from)s' '%(to)s' 'motion_start' '%%t' '%%Y-%%m-%%dT%%H:%%M:%%S' '%(timespan)s'" % {
+ line = "%(script)s '%(server)s' '%(port)s' '%(account)s' '%(password)s' '%(tls)s' '%(from)s' '%(to)s' " \
+ "'motion_start' '%%t' '%%Y-%%m-%%dT%%H:%%M:%%S' '%(timespan)s'" % {
'script': meyectl.find_command('sendmail'),
'server': ui['email_notifications_smtp_server'],
'port': ui['email_notifications_smtp_port'],
'tls': ui['email_notifications_smtp_tls'],
'from': ui['email_notifications_from'],
'to': emails,
- 'timespan': ui['email_notifications_picture_time_span']})
+ 'timespan': ui['email_notifications_picture_time_span']}
+
+ on_event_start.append(line)
if ui['web_hook_notifications_enabled']:
url = re.sub('\\s', '+', ui['web_hook_notifications_url'])
'upload_subfolders': data['@upload_subfolders'],
'upload_username': data['@upload_username'],
'upload_password': data['@upload_password'],
- 'upload_authorization_key': '', # needed, otherwise the field is hidden
+ 'upload_authorization_key': '', # needed, otherwise the field is hidden
'web_hook_storage_enabled': False,
'command_storage_enabled': False,
threshold = data['threshold'] * 100.0 / (data['width'] * data['height'])
- else: # width & height are not available for other netcams
+ else: # width & height are not available for other netcams
# we have no other choice but use something like 640x480 as reference
threshold = data['threshold'] * 100.0 / (640 * 480)
- else: # assuming v4l2
+ else: # assuming v4l2
ui['device_url'] = data['videodevice']
ui['proto'] = 'v4l2'
# indicate the presence of these controls
# we must call v4l2ctl functions to determine the available controls
brightness = v4l2ctl.get_brightness(data['videodevice'])
- if brightness is not None: # has brightness control
+ if brightness is not None: # has brightness control
if data.get('brightness', 0) != 0:
ui['brightness'] = brightness
ui['brightness'] = 50
contrast = v4l2ctl.get_contrast(data['videodevice'])
- if contrast is not None: # has contrast control
+ if contrast is not None: # has contrast control
if data.get('contrast', 0) != 0:
ui['contrast'] = contrast
ui['contrast'] = 50
saturation = v4l2ctl.get_saturation(data['videodevice'])
- if saturation is not None: # has saturation control
+ if saturation is not None: # has saturation control
if data.get('saturation', 0) != 0:
ui['saturation'] = saturation
ui['saturation'] = 50
hue = v4l2ctl.get_hue(data['videodevice'])
- if hue is not None: # has hue control
+ if hue is not None: # has hue control
if data.get('hue', 0) != 0:
ui['hue'] = hue
ui['frame_change_threshold'] = threshold
if (data['@storage_device'] == 'network-share') and settings.SMB_SHARES:
- mount_point = smbctl.make_mount_point(data['@network_server'], data['@network_share_name'], data['@network_username'])
+ mount_point = smbctl.make_mount_point(data['@network_server'],
+ data['@network_share_name'],
+ data['@network_username'])
+
ui['root_directory'] = data['target_dir'][len(mount_point):] or '/'
elif data['@storage_device'].startswith('local-disk'):
ui['root_directory'] = data['target_dir'][len(partition['mount_point']):] or '/'
break
- else: # not found for some reason
- logging.error('could not find mounted partition for device "%s" and target dir "%s"' % (target_dev, data['target_dir']))
+ else: # not found for some reason
+ logging.error('could not find mounted partition for device "%s" and target dir "%s"' %
+ (target_dev, data['target_dir']))
+
ui['root_directory'] = data['target_dir']
else:
bitrate = data['ffmpeg_variable_bitrate']
if motionctl.needs_ffvb_quirks():
if data['ffmpeg_video_codec'] in _EXPONENTIAL_QUALITY_CODECS:
- q = (100 * _EXPONENTIAL_QUALITY_FACTOR) ** ((1 - float(bitrate) / _MAX_FFMPEG_VARIABLE_BITRATE)) / _EXPONENTIAL_QUALITY_FACTOR
+ q = (100 * _EXPONENTIAL_QUALITY_FACTOR) ** \
+ (1 - float(bitrate) / _MAX_FFMPEG_VARIABLE_BITRATE) / _EXPONENTIAL_QUALITY_FACTOR
else:
q = 100 - (bitrate - 1) * 100.0 / (_MAX_FFMPEG_VARIABLE_BITRATE - 1)
ui['web_hook_notifications_url'] = e[-1]
elif e.count('relayevent'):
- continue # ignore internal relay script
+ continue # ignore internal relay script
- else: # custom command
+ else: # custom command
command_notifications.append(e)
if command_notifications:
ui['web_hook_storage_url'] = e[-1]
elif e.count('relayevent'):
- continue # ignore internal relay script
+ continue # ignore internal relay script
- else: # custom command
+ else: # custom command
command_storage.append(e)
if command_storage:
for name, value in data.iteritems():
if name not in _KNOWN_MOTION_OPTIONS and not name.startswith('@'):
if isinstance(value, bool):
- value = ['off', 'on'][value] # boolean values should be transferred as on/off
+ value = ['off', 'on'][value] # boolean values should be transferred as on/off
extra_options.append((name, value))
logging.debug('generating config backup file')
if len(os.listdir(settings.CONF_PATH)) > 100:
- logging.debug('config path "%s" appears to be a system-wide config directory, performing a selective backup' % settings.CONF_PATH)
+ logging.debug('config path "%s" appears to be a system-wide config directory, performing a selective backup' %
+ settings.CONF_PATH)
+
cmd = ['tar', 'zc', 'motion.conf']
cmd += map(os.path.basename, glob.glob(os.path.join(settings.CONF_PATH, 'thread-*.conf')))
try:
return None
else:
- logging.debug('config path "%s" appears to be a motion-specific config directory, performing a full backup' % settings.CONF_PATH)
+ logging.debug('config path "%s" appears to be a motion-specific config directory, performing a full backup' %
+ settings.CONF_PATH)
try:
content = subprocess.check_output(['tar', 'zc', '.'], cwd=settings.CONF_PATH)
return value
-def _conf_to_dict(lines, list_names=[], no_convert=[]):
- data = utils.OrderedDict()
+def _conf_to_dict(lines, list_names=None, no_convert=None):
+ if list_names is None:
+ list_names = []
+
+ if no_convert is None:
+ no_convert = []
+
+ data = collections.OrderedDict()
for line in lines:
line = line.strip()
if len(line) == 0: # empty line
continue
- match = re.match('^\#\s*(\@\w+)\s*(.*)', line)
+ match = re.match('^#\s*(@\w+)\s*(.*)', line)
if match:
name, value = match.groups()[:2]
- elif line.startswith('#') or line.startswith(';'): # comment line
+ elif line.startswith('#') or line.startswith(';'): # comment line
continue
else:
parts = line.split(None, 1)
- if len(parts) == 1: # empty value
+ if len(parts) == 1: # empty value
parts.append('')
(name, value) = parts
return data
-def _dict_to_conf(lines, data, list_names=[]):
+def _dict_to_conf(lines, data, list_names=None):
+ if list_names is None:
+ list_names = []
+
conf_lines = []
- remaining = utils.OrderedDict(data)
+ remaining = collections.OrderedDict(data)
processed = set()
# parse existing lines and replace the values
conf_lines.append(line)
continue
- match = re.match('^\#\s*(\@\w+)\s*(.*)', line)
- if match: # @line
+ match = re.match('^#\s*(@\w+)\s*(.*)', line)
+ if match: # @line
(name, value) = match.groups()[:2]
elif line.startswith('#') or line.startswith(';'): # simple comment line
(name, value) = parts[0], ''
if name in processed:
- continue # name already processed
+ continue # name already processed
processed.add(name)
# add the remaining config values not covered by existing lines
if len(remaining) and len(lines):
- conf_lines.append('') # add a blank line
+ conf_lines.append('') # add a blank line
for (name, value) in remaining.iteritems():
if name.startswith('@_'):
- continue # ignore additional configs
+ continue # ignore additional configs
if name in list_names:
for v in value:
line = '# ' + line
elif i > 0 and conf_lines[i - 1].startswith('@'):
- lines.append('') # add a blank line between @lines and the rest
+ lines.append('') # add a blank line between @lines and the rest
lines.append(line)
data.setdefault('max_movie_time', 0)
data.setdefault('ffmpeg_output_movies', False)
if motionctl.has_new_movie_format_support():
- data.setdefault('ffmpeg_video_codec', 'mp4') # will use h264 codec
+ data.setdefault('ffmpeg_video_codec', 'mp4') # will use h264 codec
if motionctl.needs_ffvb_quirks():
- data.setdefault('ffmpeg_variable_bitrate', _MAX_FFMPEG_VARIABLE_BITRATE / 4) # 75%
+ data.setdefault('ffmpeg_variable_bitrate', _MAX_FFMPEG_VARIABLE_BITRATE / 4) # 75%
else:
- data.setdefault('ffmpeg_variable_bitrate', 75) # 75%
+ data.setdefault('ffmpeg_variable_bitrate', 75) # 75%
else:
data.setdefault('ffmpeg_video_codec', 'msmpeg4')
'with' if separators else 'without'))
# gather sections
- sections = utils.OrderedDict()
+ sections = collections.OrderedDict()
for func in _additional_section_funcs:
result = func()
if not result:
logging.debug('additional config section: %s' % result['name'])
- configs = utils.OrderedDict()
+ configs = collections.OrderedDict()
for func in _additional_config_funcs:
result = func()
if not result:
continue
if target in seen_targets:
- continue # probably a bind mount
+ continue # probably a bind mount
seen_targets.add(target)
if fstype == 'fuseblk':
- fstype = 'ntfs' # most likely
+ fstype = 'ntfs' # most likely
logging.debug('found mount "%s" at "%s"' % (target, mount_point))
disks = []
disk = None
- def add_disk(disk):
+ def add_disk(d):
logging.debug('found disk at "%s" on bus "%s": "%s %s"' %
- (disk['target'], disk['bus'], disk['vendor'], disk['model']))
+ (d['target'], d['bus'], d['vendor'], d['model']))
- for part in disk['partitions']:
+ for part in d['partitions']:
logging.debug('found partition "%s" at "%s" on bus "%s": "%s %s"' %
(part['part_no'], part['target'], part['bus'], part['vendor'], part['model']))
- disks.append(disk)
+ disks.append(d)
for line in output.split('\n'):
line = line.replace('*', '')
return self._json
- def get_argument(self, name, default=None):
- DEF = {}
- argument = RequestHandler.get_argument(self, name, default=DEF)
- if argument is DEF:
+ def get_argument(self, name, default=None, strip=True):
+ def_ = {}
+ argument = RequestHandler.get_argument(self, name, default=def_)
+ if argument is def_:
# try to find it in json body
data = self.get_json()
if data:
- argument = data.get(name, DEF)
+ argument = data.get(name, def_)
- if argument is DEF:
+ if argument is def_:
argument = default
return argument
content = template.render(template_name, **context)
self.finish(content)
-
- def finish_json(self, data={}):
+
+ def finish_json(self, data=None):
+ if data is None:
+ data = {}
+
self.set_header('Content-Type', 'application/json')
self.finish(json.dumps(data))
signature = self.get_argument('_signature', None)
login = self.get_argument('_login', None) == 'true'
if (username == main_config.get('@admin_username') and
- (signature == utils.compute_signature(self.request.method, self.request.uri, # backwards compatibility
+ (signature == utils.compute_signature(self.request.method, self.request.uri, # backwards compatibility
self.request.body, main_config['@admin_password']) or
signature == utils.compute_signature(self.request.method, self.request.uri,
self.request.body,
return 'admin'
- elif not username and not main_config.get('@normal_password'): # no authentication required for normal user
+ elif not username and not main_config.get('@normal_password'): # no authentication required for normal user
return 'normal'
elif (username == main_config.get('@normal_username') and
- (signature == utils.compute_signature(self.request.method, self.request.uri, # backwards compatibility
+ (signature == utils.compute_signature(self.request.method, self.request.uri, # backwards compatibility
self.request.body, main_config.get('@normal_password')) or
signature == utils.compute_signature(self.request.method, self.request.uri,
self.request.body,
if isinstance(exception, HTTPError):
logging.error(str(exception))
self.set_status(exception.status_code)
- self.finish_json({'error': exception.log_message or getattr(exception, 'reason', None) or str(exception)})
+ self.finish_json({'error': exception.log_message or
+ getattr(exception, 'reason', None) or str(exception)})
else:
logging.error(str(exception), exc_info=True)
self.finish_json({'error': 'internal server error'})
except RuntimeError:
- pass # nevermind
+ pass # nevermind
@staticmethod
def auth(admin=False, prompt=True):
import motioneye
self.set_header('Content-Type', 'application/manifest+json')
- self.set_header('Cache-Control', 'max-age=2592000') # 30 days
+ self.set_header('Cache-Control', 'max-age=2592000') # 30 days
self.render('manifest.json', version=motioneye.VERSION)
elif utils.is_remote_camera(local_config):
def on_response(remote_ui_config=None, error=None):
if error:
- return self.finish_json({'error': 'Failed to get remote camera configuration for %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(local_config), 'msg': error}})
+ msg = 'Failed to get remote camera configuration for %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(local_config), 'msg': error}
+
+ return self.finish_json({'error': msg})
for key, value in local_config.items():
remote_ui_config[key.replace('@', '')] = value
remote.get_config(local_config, on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
ui_config = config.simple_mjpeg_camera_dict_to_ui(local_config)
self.finish_json(ui_config)
config.set_camera(camera_id, local_config)
- on_finish(None, True) # (no error, motion needs restart)
+ on_finish(None, True) # (no error, motion needs restart)
elif utils.is_remote_camera(local_config):
# update the camera locally
local_config['@enabled'] = ui_config['enabled']
config.set_camera(camera_id, local_config)
- if ui_config.has_key('name'):
- def on_finish_wrapper(error=None):
- return on_finish(error, False)
+ if 'name' in ui_config:
+ def on_finish_wrapper(e=None):
+ return on_finish(e, False)
- ui_config['enabled'] = True # never disable the camera remotely
+ ui_config['enabled'] = True # never disable the camera remotely
remote.set_config(local_config, ui_config, on_finish_wrapper)
else:
# the camera was probably disabled due to errors
on_finish(None, False)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
local_config = config.simple_mjpeg_camera_ui_to_dict(ui_config, local_config)
config.set_camera(camera_id, local_config)
- on_finish(None, False) # (no error, motion doesn't need restart)
+ on_finish(None, False) # (no error, motion doesn't need restart)
def set_main_config(ui_config):
logging.debug('setting main config...')
old_main_config = config.get_main()
- old_admin_credentials = '%s:%s' % (old_main_config.get('@admin_username', ''), old_main_config.get('@admin_password', ''))
- old_normal_credentials = '%s:%s' % (old_main_config.get('@normal_username', ''), old_main_config.get('@normal_password', ''))
+ old_admin_credentials = '%s:%s' % (old_main_config.get('@admin_username', ''),
+ old_main_config.get('@admin_password', ''))
+ old_normal_credentials = '%s:%s' % (old_main_config.get('@normal_username', ''),
+ old_main_config.get('@normal_password', ''))
main_config = config.main_ui_to_dict(ui_config)
main_config.setdefault('thread', old_main_config.get('thread', []))
- admin_credentials = '%s:%s' % (main_config.get('@admin_username', ''), main_config.get('@admin_password', ''))
- normal_credentials = '%s:%s' % (main_config.get('@normal_username', ''), main_config.get('@normal_password', ''))
+ admin_credentials = '%s:%s' % (main_config.get('@admin_username', ''),
+ main_config.get('@admin_password', ''))
+ normal_credentials = '%s:%s' % (main_config.get('@normal_username', ''),
+ main_config.get('@normal_password', ''))
additional_configs = config.get_additional_structure(camera=False)[1]
reboot_config_names = [('@_' + c['name']) for c in additional_configs.values() if c.get('reboot')]
return {'reload': reload, 'reboot': reboot, 'restart': restart}
- reload = False # indicates that browser should reload the page
- reboot = [False] # indicates that the server will reboot immediately
+ reload = False # indicates that browser should reload the page
+ reboot = [False] # indicates that the server will reboot immediately
restart = [False] # indicates that the local motion instance was modified and needs to be restarted
error = [None]
self.finish({'reload': reload, 'reboot': reboot[0], 'error': error[0]})
if camera_id is not None:
- if camera_id == 0: # multiple camera configs
+ if camera_id == 0: # multiple camera configs
if len(ui_config) > 1:
logging.debug('setting multiple configs')
self.finish()
so_far = [0]
+
def check_finished(e, r):
restart[0] = restart[0] or r
error[0] = error[0] or e
so_far[0] += 1
- if so_far[0] >= len(ui_config): # finished
+ if so_far[0] >= len(ui_config): # finished
finish()
# make sure main config is handled first
else:
set_camera_config(int(key), cfg, check_finished)
- else: # single camera config
+ else: # single camera config
def on_finish(e, r):
error[0] = e
restart[0] = r
set_camera_config(camera_id, ui_config, on_finish)
- else: # main config
+ else: # main config
result = set_main_config(ui_config)
reload = result['reload']
reboot[0] = result['reboot']
remote.set_preview(camera_config, controls, on_response)
- else: # not supported
+ else: # not supported
self.finish_json({'error': True})
@BaseHandler.auth()
self.finish_json({'cameras': cameras})
if scheme in ['http', 'https']:
- utils.test_mjpeg_url(self.get_all_arguments(), auth_modes=['basic'], allow_jpeg=True, callback=on_response)
+ utils.test_mjpeg_url(self.get_all_arguments(), auth_modes=['basic'], allow_jpeg=True,
+ callback=on_response)
elif motionctl.get_rtsp_support() and scheme == 'rtsp':
utils.test_rtsp_url(self.get_all_arguments(), callback=on_response)
else:
self.finish_json({'cameras': cameras})
- utils.test_mjpeg_url(self.get_all_arguments(), auth_modes=['basic', 'digest'], allow_jpeg=False, callback=on_response)
+ utils.test_mjpeg_url(self.get_all_arguments(), auth_modes=['basic', 'digest'], allow_jpeg=False,
+ callback=on_response)
elif proto == 'v4l2':
configured_devices = set()
camera_ids = []
length = [len(camera_ids)]
+
def check_finished():
if len(cameras) == length[0]:
cameras.sort(key=lambda c: c['id'])
if local_config.get('@enabled') or self.get_argument('force', None) == 'true':
remote.get_config(local_config, on_response_builder(camera_id, local_config))
- else: # don't try to reach the remote of the camera is disabled
+ else: # don't try to reach the remote of the camera is disabled
on_response_builder(camera_id, local_config)(error=True)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
ui_config = config.simple_mjpeg_camera_dict_to_ui(local_config)
cameras.append(ui_config)
check_finished()
remote.get_config(camera_config, on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
ui_config = config.simple_mjpeg_camera_dict_to_ui(camera_config)
self.finish_json(ui_config)
logging.warn('accessing %s failed: %s' % (service_name, result))
request_handler.finish_json({'error': result})
-
@BaseHandler.auth(admin=True)
def test(self, camera_id):
what = self.get_argument('what')
service_name = data['service']
ConfigHandler._upload_service_test_info = (self, service_name)
- tasks.add(0, uploadservices.test_access, tag='uploadservices.test(%s)'% service_name,
+ tasks.add(0, uploadservices.test_access, tag='uploadservices.test(%s)' % service_name,
camera_id=camera_id, service_name=service_name, data=data, callback=self._on_test_result)
elif what == 'email':
old_timeout = settings.SMTP_TIMEOUT
settings.SMTP_TIMEOUT = 10
- sendmail.send_mail(data['smtp_server'], int(data['smtp_port']), data['smtp_account'], data['smtp_password'], data['smtp_tls'],
- data['from'], [data['addresses']], subject=subject, message=message, files=[])
+ sendmail.send_mail(data['smtp_server'], int(data['smtp_port']), data['smtp_account'],
+ data['smtp_password'], data['smtp_tls'], data['from'], [data['addresses']],
+ subject=subject, message=message, files=[])
+
settings.SMTP_TIMEOUT = old_timeout
self.finish_json()
logging.debug('testing access to network share //%s/%s' % (data['server'], data['share']))
try:
- smbctl.test_share(data['server'], data['share'], data['username'], data['password'], data['root_directory'])
+ smbctl.test_share(data['server'], data['share'], data['username'],
+ data['password'], data['root_directory'])
+
logging.debug('access to network share //%s/%s succeeded' % (data['server'], data['share']))
self.finish_json()
@asynchronous
def post(self, camera_id, op, filename=None, group=None):
- if group == '/': # ungrouped
+ if group == '/': # ungrouped
group = ''
if camera_id is not None:
remote.get_current_picture(camera_config, width=width, height=height, callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
-
@BaseHandler.auth()
def list(self, camera_id):
logging.debug('listing pictures for camera %(id)s' % {'id': camera_id})
self.finish_json(remote_list)
- remote.list_media(camera_config, media_type='picture', prefix=self.get_argument('prefix', None), callback=on_response)
+ remote.list_media(camera_config, media_type='picture', prefix=self.get_argument('prefix', None),
+ callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
def frame(self, camera_id):
camera_config = config.get_camera(camera_id)
- if utils.is_local_motion_camera(camera_config) or utils.is_simple_mjpeg_camera(camera_config) or self.get_argument('title', None) is not None:
+ if (utils.is_local_motion_camera(camera_config) or
+ utils.is_simple_mjpeg_camera(camera_config) or
+ self.get_argument('title', None) is not None):
+
self.render('main.html',
frame=True,
camera_id=camera_id,
return self.finish_json({'error': 'Failed to download picture from %(url)s: %(msg)s.' % {
'url': remote.pretty_camera_url(camera_config), 'msg': error}})
- pretty_filename = os.path.basename(filename) # no camera name available w/o additional request
+ pretty_filename = os.path.basename(filename) # no camera name available w/o additional request
self.set_header('Content-Type', 'image/jpeg')
self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + ';')
remote.get_media_content(camera_config, filename=filename, media_type='picture', callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
@BaseHandler.auth()
height=self.get_argument('height', None),
callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
@BaseHandler.auth(admin=True)
remote.del_media_content(camera_config, filename=filename, media_type='picture', callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
@BaseHandler.auth()
self.set_header('Content-Disposition', response['content_disposition'])
self.finish(response['data'])
- remote.get_zipped_content(camera_config, media_type='picture', key=key, group=group, callback=on_response)
+ remote.get_zipped_content(camera_config, media_type='picture', key=key, group=group,
+ callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
- else: # prepare
+ else: # prepare
logging.debug('preparing zip file for group "%(group)s" of camera %(id)s' % {
'group': group or 'ungrouped', 'id': camera_id})
remote.make_zipped_content(camera_config, media_type='picture', group=group, callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
@BaseHandler.auth()
check = self.get_argument('check', False)
camera_config = config.get_camera(camera_id)
- if key: # download
+ if key: # download
logging.debug('serving timelapse movie for group "%(group)s" of camera %(id)s with key %(key)s' % {
'group': group or 'ungrouped', 'id': camera_id, 'key': key})
elif utils.is_remote_camera(camera_config):
def on_response(response=None, error=None):
if error:
- return self.finish_json({'error': 'Failed to download timelapse movie from %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
+ msg = 'Failed to download timelapse movie from %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(camera_config), 'msg': error}
+
+ return self.finish_json({'error': msg})
self.set_header('Content-Type', response['content_type'])
self.set_header('Content-Disposition', response['content_disposition'])
remote.get_timelapse_movie(camera_config, key, group=group, callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
elif check:
elif utils.is_remote_camera(camera_config):
def on_response(response=None, error=None):
if error:
- return self.finish_json({'error': 'Failed to check timelapse movie progress at %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
+ msg = 'Failed to check timelapse movie progress at %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(camera_config), 'msg': error}
+
+ return self.finish_json({'error': msg})
if response['progress'] == -1 and response.get('key'):
self.finish_json({'key': response['key'], 'progress': -1})
remote.check_timelapse_movie(camera_config, group=group, callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
- else: # start timelapse
+ else: # start timelapse
interval = int(self.get_argument('interval'))
framerate = int(self.get_argument('framerate'))
- logging.debug('preparing timelapse movie for group "%(group)s" of camera %(id)s with rate %(framerate)s/%(int)s' % {
- 'group': group or 'ungrouped', 'id': camera_id, 'framerate': framerate, 'int': interval})
+ msg = 'preparing timelapse movie for group "%(group)s" of camera %(id)s with rate %(framerate)s/%(int)s' % {
+ 'group': group or 'ungrouped', 'id': camera_id, 'framerate': framerate, 'int': interval}
+ logging.debug(msg)
if utils.is_local_motion_camera(camera_config):
status = mediafiles.check_timelapse_movie()
if status['progress'] != -1:
- self.finish_json({'progress': status['progress']}) # timelapse already active
+ self.finish_json({'progress': status['progress']}) # timelapse already active
else:
mediafiles.make_timelapse_movie(camera_config, framerate, interval, group=group)
'url': remote.pretty_camera_url(camera_config), 'msg': error}})
if response['progress'] != -1:
- return self.finish_json({'progress': response['progress']}) # timelapse already active
+ return self.finish_json({'progress': response['progress']}) # timelapse already active
def on_make(response=None, error=None):
if error:
remote.check_timelapse_movie(camera_config, group=group, callback=on_status)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
@BaseHandler.auth(admin=True)
remote.del_media_group(camera_config, group=group, media_type='picture', callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
def try_finish(self, content):
@asynchronous
def post(self, camera_id, op, filename=None, group=None):
- if group == '/': # ungrouped
+ if group == '/': # ungrouped
group = ''
if camera_id is not None:
self.finish_json(remote_list)
- remote.list_media(camera_config, media_type='movie', prefix=self.get_argument('prefix', None), callback=on_response)
+ remote.list_media(camera_config, media_type='movie', prefix=self.get_argument('prefix', None),
+ callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
@BaseHandler.auth()
return self.finish_json({'error': 'Failed to download movie from %(url)s: %(msg)s.' % {
'url': remote.pretty_camera_url(camera_config), 'msg': error}})
- pretty_filename = os.path.basename(filename) # no camera name available w/o additional request
+ pretty_filename = os.path.basename(filename) # no camera name available w/o additional request
self.set_header('Content-Type', 'video/mpeg')
self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + ';')
remote.get_media_content(camera_config, filename=filename, media_type='movie', callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
@BaseHandler.auth()
height=self.get_argument('height', None),
callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
@BaseHandler.auth(admin=True)
remote.del_media_content(camera_config, filename=filename, media_type='movie', callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
@BaseHandler.auth(admin=True)
remote.del_media_group(camera_config, group=group, media_type='movie', callback=on_response)
- else: # assuming simple mjpeg camera
+ else: # assuming simple mjpeg camera
raise HTTPError(400, 'unknown operation')
if utils.is_remote_camera(local_config):
def on_response(error=None):
if error:
- return self.finish_json({'error': 'Failed to execute action on remote camera at %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(local_config), 'msg': error}})
+ msg = 'Failed to execute action on remote camera at %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(local_config), 'msg': error}
+
+ return self.finish_json({'error': msg})
self.finish_json()
return self.finish_json()
else:
- logging.debug('recevied relayed event %(event)s for thread id %(id)s (camera id %(cid)s)' % {
+ logging.debug('received relayed event %(event)s for thread id %(id)s (camera id %(cid)s)' % {
'event': event, 'id': thread_id, 'cid': camera_id})
camera_config = config.get_camera(camera_id)
self.set_header('Content-Type', 'text/plain')
self.set_header('Content-Disposition', 'attachment; filename=' + filename + ';')
- if path.startswith('/'): # an actual path
+ if path.startswith('/'): # an actual path
logging.debug('serving log file "%s" from "%s"' % (filename, path))
with open(path) as f:
self.finish(f.read())
- else: # a command to execute
+ else: # a command to execute
logging.debug('serving log file "%s" from command "%s"' % (filename, path))
try:
return files
-def _list_media_files(dir, exts, prefix=None):
+def _list_media_files(directory, exts, prefix=None):
media_files = []
if prefix is not None:
if prefix == 'ungrouped':
prefix = ''
- root = os.path.join(dir, prefix)
+ root = os.path.join(directory, prefix)
for name in os.listdir(root):
# ignore hidden files/dirs and other unwanted files
if name.startswith('.') or name == 'lastsnap.jpg':
logging.error('stat failed: ' + unicode(e))
continue
- if not stat.S_ISREG(st.st_mode): # not a regular file
+ if not stat.S_ISREG(st.st_mode): # not a regular file
continue
full_path_lower = full_path.lower()
media_files.append((full_path, st))
else:
- for full_path, name, st in findfiles(dir):
+ for full_path, name, st in findfiles(directory):
full_path_lower = full_path.lower()
if not [e for e in exts if full_path_lower.endswith(e)]:
continue
return media_files
-def _remove_older_files(dir, moment, exts):
- for (full_path, st) in _list_media_files(dir, exts):
+def _remove_older_files(directory, moment, exts):
+ for (full_path, st) in _list_media_files(directory, exts):
file_moment = datetime.datetime.fromtimestamp(st.st_mtime)
if file_moment < moment:
logging.debug('removing file %(path)s...' % {'path': full_path})
except OSError as e:
if e.errno == errno.ENOENT:
- pass # the file might have been removed in the meantime
+ pass # the file might have been removed in the meantime
else:
logging.error('failed to remove %s: %s' % (full_path, e))
listing = os.listdir(dir_path)
thumbs = [l for l in listing if l.endswith('.thumb')]
- if len(listing) == len(thumbs): # only thumbs
+ if len(listing) == len(thumbs): # only thumbs
for p in thumbs:
try:
os.remove(os.path.join(dir_path, p))
try:
return subprocess.check_output(['which', 'ffmpeg'], stderr=utils.DEV_NULL).strip()
- except subprocess.CalledProcessError: # not found
+ except subprocess.CalledProcessError: # not found
return None
if media_type == 'picture':
exts = _PICTURE_EXTS
- elif media_type == 'movie':
+ else: # media_type == 'movie'
exts = _MOVIE_EXTS + ['.thumb']
-
+
for camera_id in config.get_camera_ids():
camera_config = config.get_camera(camera_id)
if not utils.is_local_motion_camera(camera_config):
preserve_media = camera_config.get('@preserve_%(media_type)ss' % {'media_type': media_type}, 0)
if preserve_media == 0:
- continue # preserve forever
+ continue # preserve forever
still_images_enabled = bool(
((camera_config['emulate_motion'] or camera_config['output_pictures']) and camera_config['picture_filename']) or
movies_enabled = camera_config['ffmpeg_output_movies']
if media_type == 'picture' and not still_images_enabled:
- continue # only cleanup pictures for cameras with still images enabled
+ continue # only cleanup pictures for cameras with still images enabled
elif media_type == 'movie' and not movies_enabled:
- continue # only cleanup movies for cameras with movies enabled
+ continue # only cleanup movies for cameras with movies enabled
preserve_moment = datetime.datetime.now() - datetime.timedelta(days=preserve_media)
def poll_process():
io_loop = IOLoop.instance()
- if process.is_alive(): # not finished yet
+ if process.is_alive(): # not finished yet
now = datetime.datetime.now()
delta = now - started
if delta.seconds < settings.LIST_MEDIA_TIMEOUT:
io_loop.add_timeout(datetime.timedelta(seconds=0.5), poll_process)
read_media_list()
- else: # process did not finish in time
+ else: # process did not finish in time
logging.error('timeout waiting for the media listing process to finish')
try:
os.kill(process.pid, signal.SIGTERM)
except:
- pass # nevermind
+ pass # nevermind
callback(None)
- else: # finished
+ else: # finished
read_media_list()
logging.debug('media listing process has returned %(count)s files' % {'count': len(media_list)})
callback(media_list)
if delta.seconds < settings.ZIP_TIMEOUT:
io_loop.add_timeout(datetime.timedelta(seconds=0.5), poll_process)
- else: # process did not finish in time
+ else: # process did not finish in time
logging.error('timeout waiting for the zip process to finish')
try:
os.kill(process.pid, signal.SIGTERM)
except:
- pass # nevermind
+ pass # nevermind
callback(None)
- else: # finished
+ else: # finished
try:
data = parent_pipe.recv()
logging.debug('zip process has returned %d bytes' % len(data))
codec = camera_config.get('ffmpeg_video_codec')
codec = FFMPEG_CODEC_MAPPING.get(codec, codec)
- format = FFMPEG_FORMAT_MAPPING.get(codec, codec)
+ fmt = FFMPEG_FORMAT_MAPPING.get(codec, codec)
# create a subprocess to retrieve media files
def do_list_media(pipe):
def poll_media_list_process():
io_loop = IOLoop.instance()
- if _timelapse_process.is_alive(): # not finished yet
+ if _timelapse_process.is_alive(): # not finished yet
now = datetime.datetime.now()
delta = now - started[0]
- if delta.seconds < settings.TIMELAPSE_TIMEOUT: # the subprocess has limited time to complete its job
+ if delta.seconds < settings.TIMELAPSE_TIMEOUT: # the subprocess has limited time to complete its job
io_loop.add_timeout(datetime.timedelta(seconds=0.5), poll_media_list_process)
read_media_list()
- else: # process did not finish in time
+ else: # process did not finish in time
logging.error('timeout waiting for the media listing process to finish')
try:
os.kill(_timelapse_process.pid, signal.SIGTERM)
except:
- pass # nevermind
+ pass # nevermind
_timelapse_process.progress = -1
- else: # finished
+ else: # finished
read_media_list()
logging.debug('media listing process has returned %(count)s files' % {'count': len(media_list)})
selected = []
for i in xrange(max_idx + 1):
- slice = slices.get(i)
- if not slice:
+ s = slices.get(i)
+ if not s:
continue
- selected.append(min(slice, key=lambda m: m['delta']))
+ selected.append(min(s, key=lambda m: m['delta']))
logging.debug('selected %d/%d media files' % (len(selected), len(media_list)))
def make_movie(pictures):
global _timelapse_process
- cmd = 'rm -f %(tmp_filename)s;'
- cmd += 'cat %(jpegs)s | ffmpeg -framerate %(framerate)s -f image2pipe -vcodec mjpeg -i - -vcodec %(codec)s -format %(format)s -b:v %(bitrate)s -qscale:v 0.1 -f avi %(tmp_filename)s'
+ cmd = 'rm -f %(tmp_filename)s;'
+ cmd += 'cat %(jpegs)s | ffmpeg -framerate %(framerate)s -f image2pipe -vcodec mjpeg -i - -vcodec %(codec)s ' \
+ '-format %(format)s -b:v %(bitrate)s -qscale:v 0.1 -f avi %(tmp_filename)s'
bitrate = 9999999
'jpegs': ' '.join((('"' + p['path'] + '"') for p in pictures)),
'framerate': framerate,
'codec': codec,
- 'format': format,
+ 'format': fmt,
'bitrate': bitrate
}
logging.debug('executing "%s"' % cmd)
_timelapse_process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
- _timelapse_process.progress = 0.01 # 1%
+ _timelapse_process.progress = 0.01 # 1%
# make subprocess stdout pipe non-blocking
fd = _timelapse_process.stdout.fileno()
global _timelapse_data
io_loop = IOLoop.instance()
- if _timelapse_process.poll() is None: # not finished yet
+ if _timelapse_process.poll() is None: # not finished yet
io_loop.add_timeout(datetime.timedelta(seconds=0.5), functools.partial(poll_movie_process, pictures))
try:
logging.debug('timelapse progress: %s' % int(100 * _timelapse_process.progress))
- else: # finished
+ else: # finished
exit_code = _timelapse_process.poll()
_timelapse_process = None
listing = os.listdir(dir_path)
thumbs = [l for l in listing if l.endswith('.thumb')]
- if len(listing) == len(thumbs): # only thumbs
+ if len(listing) == len(thumbs): # only thumbs
for p in thumbs:
os.remove(os.path.join(dir_path, p))
if media_type == 'picture':
exts = _PICTURE_EXTS
- elif media_type == 'movie':
+ else: # media_type == 'movie'
exts = _MOVIE_EXTS
target_dir = camera_config.get('target_dir')
listing = os.listdir(full_path)
thumbs = [l for l in listing if l.endswith('.thumb')]
- if len(listing) == len(thumbs): # only thumbs
+ if len(listing) == len(thumbs): # only thumbs
for p in thumbs:
os.remove(os.path.join(full_path, p))
return None
if width is height is None:
- return jpg # no server-side resize needed
+ return jpg # no server-side resize needed
sio = StringIO.StringIO(jpg)
image = Image.open(sio)
- if width and width < 1: # given as percent
+ if width and width < 1: # given as percent
width = int(width * image.size[0])
- if height and height < 1: # given as percent
+ if height and height < 1: # given as percent
height = int(height * image.size[1])
width = width and int(width) or image.size[0]
height = min(max_height, height)
if width >= image.size[0] and height >= image.size[1]:
- return jpg # no enlarging of the picture on the server side
+ return jpg # no enlarging of the picture on the server side
image.thumbnail((width, height), Image.CUBIC)
if _prepared_files.pop(key, None) is not None:
logging.warn('key "%s" was still present in the prepared cache, removed' % key)
- timeout = 3600 # the user has 1 hour to download the file after creation
+ timeout = 3600 # the user has 1 hour to download the file after creation
io_loop = IOLoop.instance()
io_loop.add_timeout(datetime.timedelta(seconds=timeout), clear)
if command == 'relayevent':
relayevent_sh = os.path.join(os.path.dirname(__file__), 'scripts/relayevent.sh')
- cmd = relayevent_sh + ' "%s"' % (settings._config_file or '')
+ cmd = relayevent_sh + ' "%s"' % (settings.config_file or '')
else:
cmd = __file__
cmd = sys.executable + ' ' + cmd
- cmd = cmd.replace('-b', '') # remove server-specific options
+ cmd = cmd.replace('-b', '') # remove server-specific options
cmd += ' %s ' % command
cmd += ' '.join([pipes.quote(arg) for arg in sys.argv[2:]
if arg not in ['-b']])
# use the config file directory as base dir
# if not specified otherwise in the config file
base_dir = os.path.dirname(config_file)
- settings._config_file = config_file
+ settings.config_file = config_file
if not conf_path_given[0]:
settings.CONF_PATH = base_dir
def configure_logging(cmd, log_to_file=False):
if log_to_file or cmd != 'motioneye':
- format = '%(asctime)s: [{cmd}] %(levelname)8s: %(message)s'.format(cmd=cmd)
+ fmt = '%(asctime)s: [{cmd}] %(levelname)8s: %(message)s'.format(cmd=cmd)
else:
- format = '%(levelname)8s: %(message)s'.format(cmd=cmd)
+ fmt = '%(levelname)8s: %(message)s'.format(cmd=cmd)
for h in logging.getLogger().handlers:
logging.getLogger().removeHandler(h)
log_file = None
logging.basicConfig(filename=log_file, level=settings.LOG_LEVEL,
- format=format, datefmt='%Y-%m-%d %H:%M:%S')
+ format=fmt, datefmt='%Y-%m-%d %H:%M:%S')
except Exception as e:
sys.stderr.write('failed to configure logging: %s\n' % e)
class MjpgClient(IOStream):
_FPS_LEN = 4
- clients = {} # dictionary of clients indexed by camera id
- _last_erroneous_close_time = 0 # helps detecting erroneous connections and restart motion
+ clients = {} # dictionary of clients indexed by camera id
+ _last_erroneous_close_time = 0 # helps detecting erroneous connections and restart motion
def __init__(self, camera_id, port, username, password, auth_mode):
self._camera_id = camera_id
self._auth_mode = auth_mode
self._auth_digest_state = {}
- self._last_access = None
+ self._last_access = 0
self._last_jpg = None
self._last_jpg_times = []
self.set_close_callback(self.on_close)
- def connect(self):
+ def do_connect(self):
IOStream.connect(self, ('localhost', self._port), self._on_connect)
+
+ def get_port(self):
+ return self._port
def on_close(self):
logging.debug('connection closed for mjpg client for camera %(camera_id)s on port %(port)s' % {
if getattr(self, 'error', None) and self.error.errno != errno.ECONNREFUSED:
now = time.time()
if now - MjpgClient._last_erroneous_close_time < settings.MJPG_CLIENT_TIMEOUT:
- logging.error('connection problem detected for mjpg client for camera %(camera_id)s on port %(port)s' % {
- 'port': self._port, 'camera_id': self._camera_id})
+ msg = 'connection problem detected for mjpg client for camera %(camera_id)s on port %(port)s' % {
+ 'port': self._port, 'camera_id': self._camera_id}
+
+ logging.error(msg)
if settings.MOTION_RESTART_ON_ERRORS:
- motionctl.stop(invalidate=True) # this will close all the mjpg clients
+ motionctl.stop(invalidate=True) # this will close all the mjpg clients
motionctl.start(deferred=True)
MjpgClient._last_erroneous_close_time = now
def get_last_jpg(self):
self._last_access = time.time()
return self._last_jpg
-
+
+ def get_last_access(self):
+ return self._last_access
+
+ def get_last_jpg_time(self):
+ if not self._last_jpg_times:
+ self._last_jpg_times.append(time.time())
+
+ return self._last_jpg_times[-1]
+
def get_fps(self):
if len(self._last_jpg_times) < self._FPS_LEN:
return 0 # not enough "samples"
return True
error = getattr(self, 'error', None)
- if (error is None) or (getattr(error, 'errno', None) == 0): # error could also be ESUCCESS for some reason
+ if (error is None) or (getattr(error, 'errno', None) == 0): # error could also be ESUCCESS for some reason
return False
self._error(error)
auth_header = utils.build_basic_header(self._username, self._password)
self.write('GET / HTTP/1.0\r\n\r\nAuthorization: %s\r\n\r\n' % auth_header)
- else: # in digest auth mode, the header is built upon receiving 401
+ else: # in digest auth mode, the header is built upon receiving 401
self.write('GET / HTTP/1.0\r\n\r\n')
self._seek_http()
if data.endswith('401 '):
self._seek_www_authenticate()
- else: # no authorization required, skip to content length
+ else: # no authorization required, skip to content length
self._seek_content_length()
def _seek_www_authenticate(self):
auth_mode = 'digest' if camera_config.get('stream_auth_method') > 1 else 'basic'
client = MjpgClient(camera_id, port, username, password, auth_mode)
- client.connect()
+ client.do_connect()
MjpgClient.clients[camera_id] = client
now = time.time()
for camera_id, client in MjpgClient.clients.items():
- port = client._port
-
- # check for jpeg frame timeout
- if not client._last_jpg_times:
- client._last_jpg_times.append(now)
-
- last_jpg_time = client._last_jpg_times[-1]
+ port = client.get_port()
if client.closed():
continue
+ # check for jpeg frame timeout
+ last_jpg_time = client.get_last_jpg_time()
delta = now - last_jpg_time
if delta > settings.MJPG_CLIENT_TIMEOUT:
logging.error('mjpg client timed out receiving data for camera %(camera_id)s on port %(port)s' % {
'camera_id': camera_id, 'port': port})
if settings.MOTION_RESTART_ON_ERRORS:
- motionctl.stop(invalidate=True) # this will close all the mjpg clients
+ motionctl.stop(invalidate=True) # this will close all the mjpg clients
motionctl.start(deferred=True)
break
# check for last access timeout
- if client._last_access is None:
- continue
-
- delta = now - client._last_access
+ delta = now - client.get_last_access()
if settings.MJPG_CLIENT_IDLE_TIMEOUT and delta > settings.MJPG_CLIENT_IDLE_TIMEOUT:
- logging.debug('mjpg client for camera %(camera_id)s on port %(port)s has been idle for %(timeout)s seconds, removing it' % {
+ msg = ('mjpg client for camera %(camera_id)s on port %(port)s has been idle '
+ 'for %(timeout)s seconds, removing it' % {
'camera_id': camera_id, 'port': port, 'timeout': settings.MJPG_CLIENT_IDLE_TIMEOUT})
+ logging.debug(msg)
+
client.close()
continue
import config
-DEFAULT_INTERVAL = 1 # seconds
+DEFAULT_INTERVAL = 1 # seconds
-_monior_info_cache_by_camera_id = {}
+_monitor_info_cache_by_camera_id = {}
_last_call_time_by_camera_id = {}
_interval_by_camera_id = {}
def get_monitor_info(camera_id):
- global _command_cache_time
-
now = time.time()
command = config.get_monitor_command(camera_id)
if command is None:
return ''
- monitor_info = _monior_info_cache_by_camera_id.get(camera_id)
+ monitor_info = _monitor_info_cache_by_camera_id.get(camera_id)
last_call_time = _last_call_time_by_camera_id.get(camera_id, 0)
interval = _interval_by_camera_id.get(camera_id, DEFAULT_INTERVAL)
if monitor_info is None or now - last_call_time > interval:
monitor_info, interval = _exec_monitor_command(command)
monitor_info = urllib.quote(monitor_info, safe='')
_interval_by_camera_id[camera_id] = interval
- _monior_info_cache_by_camera_id[camera_id] = monitor_info
+ _monitor_info_cache_by_camera_id[camera_id] = monitor_info
_last_call_time_by_camera_id[camera_id] = now
return monitor_info
else:
return None, None
- else: # autodetect motion binary path
+ else: # autodetect motion binary path
try:
binary = subprocess.check_output(['which', 'motion'], stderr=utils.DEV_NULL).strip()
- except subprocess.CalledProcessError: # not found
+ except subprocess.CalledProcessError: # not found
return None, None
try:
help = subprocess.check_output(binary + ' -h || true', shell=True)
- except subprocess.CalledProcessError: # not found
+ except subprocess.CalledProcessError: # not found
return None, None
result = re.findall('motion Version ([^,]+)', help, re.IGNORECASE)
motion_log_path = os.path.join(settings.LOG_PATH, 'motion.log')
motion_pid_path = os.path.join(settings.RUN_PATH, 'motion.pid')
- args = [program,
- '-n',
- '-c', motion_config_path]
-
- args.append('-d')
+ args = [program, '-n', '-c', motion_config_path, '-d']
+
if settings.LOG_LEVEL <= logging.DEBUG:
args.append('9')
elif settings.LOG_LEVEL <= logging.ERROR:
args.append('4')
- else: # fatat, quiet
+ else: # fatal, quiet
args.append('1')
log_file = open(motion_log_path, 'w')
if not binary:
return False
- if version.startswith('trunkREV'): # e.g. "trunkREV599"
+ if version.startswith('trunkREV'): # e.g. "trunkREV599"
version = int(version[8:])
return version <= _LAST_OLD_CONFIG_VERSIONS[0]
- elif version.lower().count('git'): # e.g. "Unofficial-Git-a5b5f13" or "3.2.12+git20150927mrdave"
- return False # all git versions are assumed to be new
+ elif version.lower().count('git'): # e.g. "Unofficial-Git-a5b5f13" or "3.2.12+git20150927mrdave"
+ return False # all git versions are assumed to be new
- else: # stable release, should have the format "x.y.z"
+ else: # stable release, should have the format "x.y.z"
return update.compare_versions(version, _LAST_OLD_CONFIG_VERSIONS[1]) <= 0
if not binary:
return []
- if version.startswith('trunkREV'): # e.g. trunkREV599
+ if version.startswith('trunkREV'): # e.g. trunkREV599
version = int(version[8:])
if version > _LAST_OLD_CONFIG_VERSIONS[0]:
return ['tcp']
elif version.lower().count('git') or update.compare_versions(version, '3.4') >= 0:
- return ['tcp', 'udp'] # all git versions are assumed to support both transport protocols
+ return ['tcp', 'udp'] # all git versions are assumed to support both transport protocols
- else: # stable release, should be in the format x.y.z
+ else: # stable release, should be in the format x.y.z
return []
+++ /dev/null
-# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
-# Passes Python2.7's test suite and incorporates all the latest updates.
-
-try:
- from thread import get_ident as _get_ident
-except ImportError:
- from dummy_thread import get_ident as _get_ident
-
-try:
- from _abcoll import KeysView, ValuesView, ItemsView
-except ImportError:
- pass
-
-
-class OrderedDict(dict):
- 'Dictionary that remembers insertion order'
- # An inherited dict maps keys to values.
- # The inherited dict provides __getitem__, __len__, __contains__, and get.
- # The remaining methods are order-aware.
- # Big-O running times for all methods are the same as for regular dictionaries.
-
- # The internal self.__map dictionary maps keys to links in a doubly linked list.
- # The circular doubly linked list starts and ends with a sentinel element.
- # The sentinel element never gets deleted (this simplifies the algorithm).
- # Each link is stored as a list of length three: [PREV, NEXT, KEY].
-
- def __init__(self, *args, **kwds):
- '''Initialize an ordered dictionary. Signature is the same as for
- regular dictionaries, but keyword arguments are not recommended
- because their insertion order is arbitrary.
-
- '''
- if len(args) > 1:
- raise TypeError('expected at most 1 arguments, got %d' % len(args))
- try:
- self.__root
- except AttributeError:
- self.__root = root = [] # sentinel node
- root[:] = [root, root, None]
- self.__map = {}
- self.__update(*args, **kwds)
-
- def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
- 'od.__setitem__(i, y) <==> od[i]=y'
- # Setting a new item creates a new link which goes at the end of the linked
- # list, and the inherited dictionary is updated with the new key/value pair.
- if key not in self:
- root = self.__root
- last = root[0]
- last[1] = root[0] = self.__map[key] = [last, root, key]
- dict_setitem(self, key, value)
-
- def __delitem__(self, key, dict_delitem=dict.__delitem__):
- 'od.__delitem__(y) <==> del od[y]'
- # Deleting an existing item uses self.__map to find the link which is
- # then removed by updating the links in the predecessor and successor nodes.
- dict_delitem(self, key)
- link_prev, link_next, key = self.__map.pop(key)
- link_prev[1] = link_next
- link_next[0] = link_prev
-
- def __iter__(self):
- 'od.__iter__() <==> iter(od)'
- root = self.__root
- curr = root[1]
- while curr is not root:
- yield curr[2]
- curr = curr[1]
-
- def __reversed__(self):
- 'od.__reversed__() <==> reversed(od)'
- root = self.__root
- curr = root[0]
- while curr is not root:
- yield curr[2]
- curr = curr[0]
-
- def clear(self):
- 'od.clear() -> None. Remove all items from od.'
- try:
- for node in self.__map.itervalues():
- del node[:]
- root = self.__root
- root[:] = [root, root, None]
- self.__map.clear()
- except AttributeError:
- pass
- dict.clear(self)
-
- def popitem(self, last=True):
- '''od.popitem() -> (k, v), return and remove a (key, value) pair.
- Pairs are returned in LIFO order if last is true or FIFO order if false.
-
- '''
- if not self:
- raise KeyError('dictionary is empty')
- root = self.__root
- if last:
- link = root[0]
- link_prev = link[0]
- link_prev[1] = root
- root[0] = link_prev
- else:
- link = root[1]
- link_next = link[1]
- root[1] = link_next
- link_next[0] = root
- key = link[2]
- del self.__map[key]
- value = dict.pop(self, key)
- return key, value
-
- # -- the following methods do not depend on the internal structure --
-
- def keys(self):
- 'od.keys() -> list of keys in od'
- return list(self)
-
- def values(self):
- 'od.values() -> list of values in od'
- return [self[key] for key in self]
-
- def items(self):
- 'od.items() -> list of (key, value) pairs in od'
- return [(key, self[key]) for key in self]
-
- def iterkeys(self):
- 'od.iterkeys() -> an iterator over the keys in od'
- return iter(self)
-
- def itervalues(self):
- 'od.itervalues -> an iterator over the values in od'
- for k in self:
- yield self[k]
-
- def iteritems(self):
- 'od.iteritems -> an iterator over the (key, value) items in od'
- for k in self:
- yield (k, self[k])
-
- def update(*args, **kwds): #@NoSelf
- '''od.update(E, **F) -> None. Update od from dict/iterable E and F.
-
- If E is a dict instance, does: for k in E: od[k] = E[k]
- If E has a .keys() method, does: for k in E.keys(): od[k] = E[k]
- Or if E is an iterable of items, does: for k, v in E: od[k] = v
- In either case, this is followed by: for k, v in F.items(): od[k] = v
-
- '''
- if len(args) > 2:
- raise TypeError('update() takes at most 2 positional '
- 'arguments (%d given)' % (len(args),))
- elif not args:
- raise TypeError('update() takes at least 1 argument (0 given)')
- self = args[0]
- # Make progressively weaker assumptions about "other"
- other = ()
- if len(args) == 2:
- other = args[1]
- if isinstance(other, dict):
- for key in other:
- self[key] = other[key]
- elif hasattr(other, 'keys'):
- for key in other.keys():
- self[key] = other[key]
- else:
- for key, value in other:
- self[key] = value
- for key, value in kwds.items():
- self[key] = value
-
- __update = update # let subclasses override update without breaking __init__
-
- __marker = object()
-
- def pop(self, key, default=__marker):
- '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
- If key is not found, d is returned if given, otherwise KeyError is raised.
-
- '''
- if key in self:
- result = self[key]
- del self[key]
- return result
- if default is self.__marker:
- raise KeyError(key)
- return default
-
- def setdefault(self, key, default=None):
- 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
- if key in self:
- return self[key]
- self[key] = default
- return default
-
- def __repr__(self, _repr_running={}):
- 'od.__repr__() <==> repr(od)'
- call_key = id(self), _get_ident()
- if call_key in _repr_running:
- return '...'
- _repr_running[call_key] = 1
- try:
- if not self:
- return '%s()' % (self.__class__.__name__,)
- return '%s(%r)' % (self.__class__.__name__, self.items())
- finally:
- del _repr_running[call_key]
-
- def __reduce__(self):
- 'Return state information for pickling'
- items = [[k, self[k]] for k in self]
- inst_dict = vars(self).copy()
- for k in vars(OrderedDict()):
- inst_dict.pop(k, None)
- if inst_dict:
- return (self.__class__, (items,), inst_dict)
- return self.__class__, (items,)
-
- def copy(self):
- 'od.copy() -> a shallow copy of od'
- return self.__class__(self)
-
- @classmethod
- def fromkeys(cls, iterable, value=None):
- '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
- and values equal to v (which defaults to None).
-
- '''
- d = cls()
- for key in iterable:
- d[key] = value
- return d
-
- def __eq__(self, other):
- '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive
- while comparison to a regular mapping is order-insensitive.
-
- '''
- if isinstance(other, OrderedDict):
- return len(self)==len(other) and self.items() == other.items()
- return dict.__eq__(self, other)
-
- def __ne__(self, other):
- return not self == other
-
- # -- the following methods are only used in Python 2.7 --
-
- def viewkeys(self):
- "od.viewkeys() -> a set-like object providing a view on od's keys"
- return KeysView(self)
-
- def viewvalues(self):
- "od.viewvalues() -> an object providing a view on od's values"
- return ValuesView(self)
-
- def viewitems(self):
- "od.viewitems() -> a set-like object providing a view on od's items"
- return ItemsView(self)
try:
return subprocess.check_output(['which', prog], stderr=utils.DEV_NULL).strip()
- except subprocess.CalledProcessError: # not found
+ except subprocess.CalledProcessError: # not found
return None
logging.debug('loading preferences from "%s"...' % file_path)
try:
- file = open(file_path, 'r')
+ f = open(file_path, 'r')
except Exception as e:
logging.error('could not open preferences file "%s": %s' % (file_path, e))
return
try:
- _prefs = json.load(file)
+ _prefs = json.load(f)
except Exception as e:
- logging.error('could not read preferences from file "%s": %s'(file_path, e))
+ logging.error('could not read preferences from file "%s": %s' % (file_path, e))
finally:
- file.close()
+ f.close()
else:
logging.debug('preferences file "%s" does not exist, using default preferences' % file_path)
logging.debug('saving preferences to "%s"...' % file_path)
try:
- file = open(file_path, 'w')
+ f = open(file_path, 'w')
except Exception as e:
logging.error('could not open preferences file "%s": %s' % (file_path, e))
return
try:
- json.dump(_prefs, file, sort_keys=True, indent=4)
+ json.dump(_prefs, f, sort_keys=True, indent=4)
except Exception as e:
- logging.error('could not save preferences to file "%s": %s'(file_path, e))
+ logging.error('could not save preferences to file "%s": %s' % (file_path, e))
finally:
- file.close()
+ f.close()
def get(username, key=None):
_DOUBLE_SLASH_REGEX = re.compile('//+')
-def _make_request(scheme, host, port, username, password, path, method='GET', data=None, query=None, timeout=None, content_type=None):
+def _make_request(scheme, host, port, username, password, path, method='GET', data=None, query=None,
+ timeout=None, content_type=None):
+
path = _DOUBLE_SLASH_REGEX.sub('/', path)
url = '%(scheme)s://%(host)s%(port)s%(path)s' % {
'scheme': scheme,
query = dict(query or {})
query['_username'] = username or ''
- query['_admin'] = 'true' # always use the admin account
+ query['_admin'] = 'true' # always use the admin account
if url.count('?'):
url += '&'
headers['Content-Type'] = content_type
return HTTPRequest(url, method, body=data, connect_timeout=timeout, request_timeout=timeout, headers=headers,
- validate_cert=settings.VALIDATE_CERTS)
+ validate_cert=settings.VALIDATE_CERTS)
def _callback_wrapper(callback):
'url': pretty_camera_url(local_config, camera=False)})
request = _make_request(scheme, host, port, username, password,
- path + '/config/list/')
+ path + '/config/list/')
def on_response(response):
def make_camera_response(c):
cameras = response['cameras']
# filter out simple mjpeg cameras
- cameras = [make_camera_response(c) for c in cameras
- if c['proto'] != 'mjpeg' and c.get('enabled')]
+ cameras = [make_camera_response(c) for c in cameras if c['proto'] != 'mjpeg' and c.get('enabled')]
callback(cameras)
'url': pretty_camera_url(local_config)})
request = _make_request(scheme, host, port, username, password,
- path + '/config/%(id)s/get/' % {'id': camera_id})
+ path + '/config/%(id)s/get/' % {'id': camera_id})
def on_response(response):
if response.error:
'url': pretty_camera_url(local_config)})
ui_config = json.dumps(ui_config)
-
- request = _make_request(scheme, host, port, username, password,
- path + '/config/%(id)s/set/' % {'id': camera_id},
- method='POST', data=ui_config, content_type='application/json')
+
+ p = path + '/config/%(id)s/set/' % {'id': camera_id}
+ request = _make_request(scheme, host, port, username, password, p,
+ method='POST', data=ui_config, content_type='application/json')
def on_response(response):
if response.error:
'url': pretty_camera_url(local_config)})
data = json.dumps(controls)
-
- request = _make_request(scheme, host, port, username, password,
- path + '/config/%(id)s/set_preview/' % {'id': camera_id},
- method='POST', data=data, content_type='application/json')
+
+ p = path + '/config/%(id)s/set_preview/' % {'id': camera_id}
+ request = _make_request(scheme, host, port, username, password, p,
+ method='POST', data=data, content_type='application/json')
def on_response(response):
if response.error:
data = json.dumps(data)
- request = _make_request(scheme, host, port, username, password,
- path + '/config/%(id)s/test/' % {'id': camera_id},
- method='POST', data=data, content_type='application/json')
+ p = path + '/config/%(id)s/test/' % {'id': camera_id}
+ request = _make_request(scheme, host, port, username, password, p,
+ method='POST', data=data, content_type='application/json')
def on_response(response):
if response.error:
if height:
query['height'] = str(height)
-
- request = _make_request(scheme, host, port, username, password,
- path + '/picture/%(id)s/current/' % {'id': camera_id},
- query=query)
+
+ p = path + '/picture/%(id)s/current/' % {'id': camera_id}
+ request = _make_request(scheme, host, port, username, password, p, query=query)
def on_response(response):
cookies = utils.parse_cookies(response.headers.get_list('Set-Cookie'))
query['prefix'] = prefix
# timeout here is 10 times larger than usual - we expect a big delay when fetching the media list
- request = _make_request(scheme, host, port, username, password,
- path + '/%(media_type)s/%(id)s/list/' % {
- 'id': camera_id, 'media_type': media_type}, query=query,
- timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
+ p = path + '/%(media_type)s/%(id)s/list/' % {'id': camera_id, 'media_type': media_type}
+ request = _make_request(scheme, host, port, username, password, p, query=query,
+ timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
def on_response(response):
if response.error:
# timeout here is 10 times larger than usual - we expect a big delay when fetching the media list
request = _make_request(scheme, host, port, username, password,
- path, timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
+ path, timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
def on_response(response):
if response.error:
# timeout here is 100 times larger than usual - we expect a big delay
request = _make_request(scheme, host, port, username, password,
- prepare_path, timeout=100 * settings.REMOTE_REQUEST_TIMEOUT)
+ prepare_path, timeout=100 * settings.REMOTE_REQUEST_TIMEOUT)
def on_response(response):
if response.error:
- logging.error('failed to prepare zip file for group "%(group)s" of remote camera %(id)s on %(url)s: %(msg)s' % {
+ msg = 'failed to prepare zip file for group "%(group)s" ' \
+ 'of remote camera %(id)s on %(url)s: %(msg)s' % {
'group': group or 'ungrouped',
'id': camera_id,
'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
+ 'msg': utils.pretty_http_error(response)}
+
+ logging.error(msg)
return callback(error=utils.pretty_http_error(response))
logging.debug('downloading zip file for remote camera %(id)s on %(url)s' % {
'id': camera_id,
'url': pretty_camera_url(local_config)})
-
- request = _make_request(scheme, host, port, username, password,
- path + '/%(media_type)s/%(id)s/zipped/%(group)s/?key=%(key)s' % {
- 'media_type': media_type,
- 'group': group,
- 'id': camera_id,
- 'key': key},
- timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
+
+ p = path + '/%(media_type)s/%(id)s/zipped/%(group)s/?key=%(key)s' % {
+ 'media_type': media_type,
+ 'group': group,
+ 'id': camera_id,
+ 'key': key}
+
+ request = _make_request(scheme, host, port, username, password, p,
+ timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
def on_response(response):
if response.error:
def make_timelapse_movie(local_config, framerate, interval, group, callback):
scheme, host, port, username, password, path, camera_id = _remote_params(local_config)
- logging.debug('making timelapse movie for group "%(group)s" of remote camera %(id)s with rate %(framerate)s/%(int)s on %(url)s' % {
+ msg = 'making timelapse movie for group "%(group)s" of remote camera %(id)s ' \
+ 'with rate %(framerate)s/%(int)s on %(url)s' % {
'group': group or 'ungrouped',
'id': camera_id,
'framerate': framerate,
'int': interval,
- 'url': pretty_camera_url(local_config)})
+ 'url': pretty_camera_url(local_config)}
+
+ logging.debug(msg)
path += '/picture/%(id)s/timelapse/%(group)s/?interval=%(int)s&framerate=%(framerate)s' % {
'id': camera_id,
'group': group}
request = _make_request(scheme, host, port, username, password,
- path, timeout=100 * settings.REMOTE_REQUEST_TIMEOUT)
+ path, timeout=100 * settings.REMOTE_REQUEST_TIMEOUT)
def on_response(response):
if response.error:
- logging.error('failed to make timelapse movie for group "%(group)s" of remote camera %(id)s with rate %(framerate)s/%(int)s on %(url)s: %(msg)s' % {
+ msg = 'failed to make timelapse movie for group "%(group)s" of remote camera %(id)s ' \
+ 'with rate %(framerate)s/%(int)s on %(url)s: %(msg)s' % {
'group': group or 'ungrouped',
'id': camera_id,
'url': pretty_camera_url(local_config),
'int': interval,
'framerate': framerate,
- 'msg': utils.pretty_http_error(response)})
+ 'msg': utils.pretty_http_error(response)}
+
+ logging.error(msg)
return callback(error=utils.pretty_http_error(response))
logging.debug('checking timelapse movie status for remote camera %(id)s on %(url)s' % {
'id': camera_id,
'url': pretty_camera_url(local_config)})
-
- request = _make_request(scheme, host, port, username, password,
- path + '/picture/%(id)s/timelapse/%(group)s/?check=true' % {
- 'id': camera_id,
- 'group': group})
+
+ p = path + '/picture/%(id)s/timelapse/%(group)s/?check=true' % {
+ 'id': camera_id,
+ 'group': group}
+ request = _make_request(scheme, host, port, username, password, p)
def on_response(response):
if response.error:
logging.debug('downloading timelapse movie for remote camera %(id)s on %(url)s' % {
'id': camera_id,
'url': pretty_camera_url(local_config)})
-
- request = _make_request(scheme, host, port, username, password,
- path + '/picture/%(id)s/timelapse/%(group)s/?key=%(key)s' % {
- 'id': camera_id,
- 'group': group,
- 'key': key},
- timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
+
+ p = path + '/picture/%(id)s/timelapse/%(group)s/?key=%(key)s' % {
+ 'id': camera_id,
+ 'group': group,
+ 'key': key}
+
+ request = _make_request(scheme, host, port, username, password, p,
+ timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
def on_response(response):
if response.error:
if height:
query['height'] = str(height)
- request = _make_request(scheme, host, port, username, password,
- path, query=query)
+ request = _make_request(scheme, host, port, username, password, path, query=query)
def on_response(response):
if response.error:
'id': camera_id,
'filename': filename}
- request = _make_request(scheme, host, port, username, password,
- path, method='POST', data='{}',
- timeout=settings.REMOTE_REQUEST_TIMEOUT, content_type='application/json')
+ request = _make_request(scheme, host, port, username, password, path, method='POST', data='{}',
+ timeout=settings.REMOTE_REQUEST_TIMEOUT, content_type='application/json')
def on_response(response):
if response.error:
'id': camera_id,
'group': group}
- request = _make_request(scheme, host, port, username, password, path,
- method='POST', data='{}',
- timeout=settings.REMOTE_REQUEST_TIMEOUT, content_type='application/json')
+ request = _make_request(scheme, host, port, username, password, path, method='POST', data='{}',
+ timeout=settings.REMOTE_REQUEST_TIMEOUT, content_type='application/json')
def on_response(response):
if response.error:
'action': action,
'id': camera_id}
- request = _make_request(scheme, host, port, username, password, path,
- method='POST', data='{}',
- timeout=settings.REMOTE_REQUEST_TIMEOUT, content_type='application/json')
+ request = _make_request(scheme, host, port, username, password, path, method='POST', data='{}',
+ timeout=settings.REMOTE_REQUEST_TIMEOUT, content_type='application/json')
def on_response(response):
if response.error:
email['Date'] = formatdate(localtime=True)
email.attach(MIMEText(message))
- for file in reversed(files):
+ for f in reversed(files):
part = MIMEBase('image', 'jpeg')
- with open(file, 'rb') as f:
+ with open(f, 'rb') as f:
part.set_payload(f.read())
Encoders.encode_base64(part)
- part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file))
+ part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f))
email.attach(part)
if files:
if media_files:
logging.debug('got media files')
- media_files = [m for m in media_files if abs(m['timestamp'] - timestamp) < timespan] # filter out non-recent media files
+ media_files = [m for m in media_files if abs(m['timestamp'] - timestamp) < timespan] # filter out non-recent media files
media_files.sort(key=lambda m: m['timestamp'], reverse=True)
media_files = [os.path.join(camera_config['target_dir'], re.sub('^/', '', m['path'])) for m in media_files]
return on_media_files([])
logging.debug('waiting for pictures to be taken')
- time.sleep(timespan) # give motion some time to create motion pictures
+ time.sleep(timespan) # give motion some time to create motion pictures
logging.debug('creating email message')
mediafiles.list_media(camera_config, media_type='picture', callback=on_media_files)
# the motion daemon overrides SIGCHLD,
# so we must restore it here,
# or otherwise media listing won't work
- signal.signal(signal.SIGCHLD,signal.SIG_DFL)
+ signal.signal(signal.SIGCHLD, signal.SIG_DFL)
if len(args) == 12:
# backwards compatibility with older configs lacking "from" field
message = messages.get(options.msg_id)
subject = subjects.get(options.msg_id)
options.moment = datetime.datetime.strptime(options.moment, '%Y-%m-%dT%H:%M:%S')
- options.password = options.password.replace('\\;', ';') # unescape password
+ options.password = options.password.replace('\\;', ';') # unescape password
# do not wait too long for media list,
# email notifications are critical
def on_message(subject, message, files):
try:
send_mail(options.server, options.port, options.account, options.password,
- options.tls, _from, to, subject, message, files or [])
+ options.tls, _from, to, subject, message, files or [])
logging.info('email sent')
except Exception as e:
def daemonize(self):
# first fork
try:
- if os.fork() > 0: # parent
+ if os.fork() > 0: # parent
sys.exit(0)
except OSError, e:
# second fork
try:
- if os.fork() > 0: # parent
+ if os.fork() > 0: # parent
sys.exit(0)
except OSError, e:
def make_media_folders():
import config
- config.get_main() # just to have main config already loaded
+ config.get_main() # just to have main config already loaded
camera_ids = config.get_camera_ids()
for camera_id in camera_ids:
def parse_options(parser, args):
parser.add_argument('-b', help='start the server in background (daemonize)',
- action='store_true', dest='background', default=False)
+ action='store_true', dest='background', default=False)
return parser.parse_args(args)
template.add_context('static_path', 'static/')
application = Application(handler_mapping, debug=False, log_function=_log_request,
- static_path=settings.STATIC_PATH, static_url_prefix='/static/')
+ static_path=settings.STATIC_PATH, static_url_prefix='/static/')
application.listen(settings.PORT, settings.LISTEN)
logging.info('server started')
import motioneye
-_config_file = None
+config_file = None
# the root directory of the project
PROJECT_PATH = os.path.dirname(motioneye.__file__)
STATIC_PATH = os.path.join(PROJECT_PATH, 'static')
# path to the configuration directory (must be writable by motionEye)
-CONF_PATH = [sys.prefix, ''][sys.prefix == '/usr'] + '/etc/motioneye'
+CONF_PATH = [sys.prefix, ''][sys.prefix == '/usr'] + '/etc/motioneye'
# path to the directory where pid files go (must be writable by motionEye)
for d in ['/run', '/var/run', '/tmp', '/var/tmp']:
try:
return subprocess.check_output(['which', 'mount.cifs'], stderr=utils.DEV_NULL).strip()
- except subprocess.CalledProcessError: # not found
+ except subprocess.CalledProcessError: # not found
return None
continue
server, share = match.groups()
- share = share.replace('\\040', ' ') # spaces are reported oddly by /proc/mounts
+ share = share.replace('\\040', ' ') # spaces are reported oddly by /proc/mounts
match = re.search('username=([a-z][-\w]*)', opts)
if match:
mounts = list_mounts()
mounts = dict(((m['server'], m['share'], m['username'] or ''), False) for m in mounts)
- should_stop = False # indicates that motion should be stopped immediately
- should_start = True # indicates that motion can be started afterwards
+ should_stop = False # indicates that motion should be stopped immediately
+ should_start = True # indicates that motion can be started afterwards
for network_share in network_shares:
key = (network_share['server'], network_share['share'], network_share['username'] or '')
- if key in mounts: # found
+ if key in mounts: # found
mounts[key] = True
- else: # needs to be mounted
+ else: # needs to be mounted
should_stop = True
- if not _mount(network_share['server'], network_share['share'], network_share['username'], network_share['password']):
+ if not _mount(network_share['server'], network_share['share'],
+ network_share['username'], network_share['password']):
+
should_start = False
# unmount the no longer necessary mounts
_umount(server, share, username)
should_stop = True
- return (should_stop, should_start)
+ return should_stop, should_start
def test_share(server, share, username, password, root_directory):
def stop():
global _pool
- #_pool.terminate()
_pool = None
now = time.time()
- if isinstance(when, int): # delay, in seconds
+ if isinstance(when, int): # delay, in seconds
when += now
elif isinstance(when, datetime.timedelta):
logging.debug('loading tasks from "%s"...' % file_path)
try:
- file = open(file_path, 'r')
+ f = open(file_path, 'r')
except Exception as e:
logging.error('could not open tasks file "%s": %s' % (file_path, e))
return
try:
- _tasks = cPickle.load(file)
+ _tasks = cPickle.load(f)
except Exception as e:
logging.error('could not read tasks from file "%s": %s' % (file_path, e))
finally:
- file.close()
+ f.close()
def _save():
logging.debug('saving tasks to "%s"...' % file_path)
try:
- file = open(file_path, 'w')
+ f = open(file_path, 'w')
except Exception as e:
logging.error('could not open tasks file "%s": %s' % (file_path, e))
try:
# don't save tasks that have a callback
tasks = [t for t in _tasks if not t[3]]
- cPickle.dump(tasks, file)
+ cPickle.dump(tasks, f)
except Exception as e:
- logging.error('could not save tasks to file "%s": %s'% (file_path, e))
+ logging.error('could not save tasks to file "%s": %s' % (file_path, e))
finally:
- file.close()
+ f.close()
def _get_time_zone_symlink():
- file = settings.LOCAL_TIME_FILE
- if not file:
+ f = settings.LOCAL_TIME_FILE
+ if not f:
return None
- for i in xrange(8): # recursively follow the symlinks @UnusedVariable
+ for i in xrange(8): # recursively follow the symlinks @UnusedVariable
try:
- file = os.readlink(file)
+ f = os.readlink(f)
except OSError:
break
- if file and file.startswith('/usr/share/zoneinfo/'):
- file = file[20:]
+ if f and f.startswith('/usr/share/zoneinfo/'):
+ f = f[20:]
else:
- file = None
+ f = None
- time_zone = file or None
+ time_zone = f or None
if time_zone:
logging.debug('found time zone by symlink method: %s' % time_zone)
os.remove(settings.LOCAL_TIME_FILE)
except:
- pass # nevermind
+ pass # nevermind
try:
os.symlink(zoneinfo_file, settings.LOCAL_TIME_FILE)
return name, version
except:
- return ('Linux', '') # most likely :)
+ return 'Linux', '' # most likely :)
def compare_versions(version1, version2):
raise Exception(msg)
if st.st_size > self.MAX_FILE_SIZE:
- msg = 'file "%s" is too large (%sMB/%sMB)' % (filename, st.st_size / 1024 / 1024, self.MAX_FILE_SIZE / 1024 / 1024)
+ msg = 'file "%s" is too large (%sMB/%sMB)' % \
+ (filename, st.st_size / 1024 / 1024, self.MAX_FILE_SIZE / 1024 / 1024)
+
self.error(msg)
raise Exception(msg)
# retry the request with refreshed credentials
return self._request(url, body, headers, retry_auth=False)
- except Exception as e:
+ except Exception:
self.error('refreshing credentials failed')
raise
self.save()
# retry the request with refreshed credentials
- self._request(url, body, headers, retry_auth=False)
+ return self._request(url, body, headers, retry_auth=False)
- except Exception as e:
+ except Exception:
self.error('refreshing credentials failed')
raise
logging.debug('loading upload services state from "%s"...' % file_path)
try:
- file = open(file_path, 'r')
+ f = open(file_path, 'r')
except Exception as e:
logging.error('could not open upload services state file "%s": %s' % (file_path, e))
return services
try:
- data = json.load(file)
+ data = json.load(f)
except Exception as e:
logging.error('could not read upload services state from file "%s": %s' % (file_path, e))
return services
finally:
- file.close()
+ f.close()
for camera_id, d in data.iteritems():
for name, state in d.iteritems():
data.setdefault(str(camera_id), {})[name] = service.dump()
try:
- file = open(file_path, 'w')
+ f = open(file_path, 'w')
except Exception as e:
logging.error('could not open upload services state file "%s": %s' % (file_path, e))
return
try:
- json.dump(data, file, sort_keys=True, indent=4)
+ json.dump(data, f, sort_keys=True, indent=4)
except Exception as e:
- logging.error('could not save upload services state to file "%s": %s'(file_path, e))
+ logging.error('could not save upload services state to file "%s": %s' % (file_path, e))
finally:
- file.close()
+ f.close()
import settings
-try:
- from collections import OrderedDict # @UnusedImport
-except:
- from ordereddict import OrderedDict # @UnusedImport @Reimport
-
-
-_SIGNATURE_REGEX = re.compile('[^a-zA-Z0-9/?_.=&{}\[\]":, _-]')
+_SIGNATURE_REGEX = re.compile('[^a-zA-Z0-9/?_.=&{}\[\]":, -]')
_SPECIAL_COOKIE_NAMES = {'expires', 'domain', 'path', 'secure', 'httponly'}
MASK_WIDTH = 32
]
+def _(x):
+ return x # this could later be replaced by a proper translate function
+
+
def pretty_date_time(date_time, tzinfo=None, short=False):
if date_time is None:
- return '('+ _('never') + ')'
+ return '(' + _('never') + ')'
if isinstance(date_time, int):
return pretty_date_time(datetime.datetime.fromtimestamp(date_time))
def pretty_date(date):
if date is None:
- return '('+ _('never') + ')'
+ return '(' + _('never') + ')'
if isinstance(date, int):
return pretty_date(datetime.datetime.fromtimestamp(date))
return special_result
if days:
- format = "{d}d{h}h{m}m"
+ fmt = "{d}d{h}h{m}m"
elif hours:
- format = "{h}h{m}m"
+ fmt = "{h}h{m}m"
elif minutes:
- format = "{m}m"
+ fmt = "{m}m"
if seconds:
- format += "{s}s"
+ fmt += "{s}s"
else:
- format = "{s}s"
+ fmt = "{s}s"
if negative:
- format = '-' + format
+ fmt = '-' + fmt
- return format.format(d=days, h=hours, m=minutes, s=seconds)
+ return fmt.format(d=days, h=hours, m=minutes, s=seconds)
def pretty_size(size):
- if size < 1024: # less than 1kB
+ if size < 1024: # less than 1kB
size, unit = size, 'B'
- elif size < 1024 * 1024: # less than 1MB
+ elif size < 1024 * 1024: # less than 1MB
size, unit = size / 1024.0, 'kB'
- elif size < 1024 * 1024 * 1024: # less than 1GB
+ elif size < 1024 * 1024 * 1024: # less than 1GB
size, unit = size / 1024.0 / 1024, 'MB'
- else: # greater than or equal to 1GB
+ else: # greater than or equal to 1GB
size, unit = size / 1024.0 / 1024 / 1024, 'GB'
return '%.1f %s' % (size, unit)
total_size = total_blocks * block_size
used_size = total_size - free_size
- return (used_size, total_size)
+ return used_size, total_size
def is_local_motion_camera(config):
- '''Tells if a camera is managed by the local motion instance.'''
+ """Tells if a camera is managed by the local motion instance."""
return bool(config.get('videodevice') or config.get('netcam_url'))
def is_remote_camera(config):
- '''Tells if a camera is managed by a remote motionEye server.'''
+ """Tells if a camera is managed by a remote motionEye server."""
return config.get('@proto') == 'motioneye'
def is_v4l2_camera(config):
- '''Tells if a camera is a v4l2 device managed by the local motion instance.'''
+ """Tells if a camera is a v4l2 device managed by the local motion instance."""
return bool(config.get('videodevice'))
def is_net_camera(config):
- '''Tells if a camera is a network camera managed by the local motion instance.'''
+ """Tells if a camera is a network camera managed by the local motion instance."""
return bool(config.get('netcam_url'))
def is_simple_mjpeg_camera(config):
- '''Tells if a camera is a simple MJPEG camera not managed by any motion instance.'''
+ """Tells if a camera is a simple MJPEG camera not managed by any motion instance."""
return bool(config.get('@proto') == 'mjpeg')
logging.debug('testing (m)jpg netcam at %s using %s authentication' % (url, auth))
request = HTTPRequest(url, auth_username=username, auth_password=password, auth_mode=auth_modes.pop(0),
- connect_timeout=settings.REMOTE_REQUEST_TIMEOUT, request_timeout=settings.REMOTE_REQUEST_TIMEOUT,
- header_callback=on_header, validate_cert=settings.VALIDATE_CERTS)
+ connect_timeout=settings.REMOTE_REQUEST_TIMEOUT,
+ request_timeout=settings.REMOTE_REQUEST_TIMEOUT,
+ header_callback=on_header, validate_cert=settings.VALIDATE_CERTS)
http_client = AsyncHTTPClient(force_instance=True)
http_client.fetch(request, on_response)
stream.connect((host, int(port)), on_connect)
timeout[0] = io_loop.add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT),
- functools.partial(on_connect, _timeout=True))
+ functools.partial(on_connect, _timeout=True))
return stream
key = _SIGNATURE_REGEX.sub('-', key)
if body and body.startswith('---'):
- body = None # file attachment
+ body = None # file attachment
body = body and _SIGNATURE_REGEX.sub('-', body.decode('utf8'))
return parsed
+
def build_basic_header(username, password):
return 'Basic ' + base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
return hashlib.md5(x).hexdigest()
hash_utf8 = md5_utf8
- elif _algorithm == 'SHA':
+ else: # _algorithm == 'SHA'
def sha_utf8(x):
if isinstance(x, str):
x = x.encode('utf-8')
state['last_nonce'] = last_nonce
state['nonce_count'] = nonce_count
- return 'Digest %s' % (base)
+ return 'Digest %s' % base
def urlopen(*args, **kwargs):
mask_lines = mask_lines[2:]
logging.debug('building editable mask for camera with id %s (%sx%s)' %
- (camera_id, width, height))
+ (camera_id, width, height))
# horizontal rectangles
- nx = MASK_WIDTH # number of rectangles
+ nx = MASK_WIDTH # number of rectangles
if width % nx:
nx -= 1
- rx = width % nx # remainder
+ rx = width % nx # remainder
else:
rx = 0
- rw = width / nx # rectangle width
+ rw = width / nx # rectangle width
# vertical rectangles
- ny = mask_height = height * MASK_WIDTH / width # number of rectangles
+ ny = mask_height = height * MASK_WIDTH / width # number of rectangles
if height % ny:
ny -= 1
- ry = height % ny # remainder
+ ry = height % ny # remainder
else:
ry = 0
else:
line_index_func = lambda y: (len(mask_lines) - 1) * y / ny
- rh = height / ny # rectangle height
+ rh = height / ny # rectangle height
# draw the actual mask image content
- im = Image.new('L', (width, height), 255) # all white
+ im = Image.new('L', (width, height), 255) # all white
dr = ImageDraw.Draw(im)
for y in xrange(ny):
# resize the image if necessary
if capture_width and capture_height and im.size != (capture_width, capture_height):
logging.debug('editable mask needs resizing from %sx%s to %sx%s' %
- (im.size[0], im.size[1], capture_width, capture_height))
+ (im.size[0], im.size[1], capture_width, capture_height))
im = im.resize((capture_width, capture_height))
# resize the image if necessary
if im.size != (capture_width, capture_height):
logging.debug('editable mask needs resizing from %sx%s to %sx%s' %
- (im.size[0], im.size[1], capture_width, capture_height))
+ (im.size[0], im.size[1], capture_width, capture_height))
im = im.resize((capture_width, capture_height))
pixels = list(im.getdata())
# horizontal rectangles
- nx = MASK_WIDTH # number of rectangles
+ nx = MASK_WIDTH # number of rectangles
if width % nx:
nx -= 1
- rx = width % nx # remainder
+ rx = width % nx # remainder
else:
rx = 0
- rw = width / nx # rectangle width
+ rw = width / nx # rectangle width
# vertical rectangles
- ny = height * MASK_WIDTH / width # number of rectangles
+ ny = height * MASK_WIDTH / width # number of rectangles
if height % ny:
ny -= 1
- ry = height % ny # remainder
+ ry = height % ny # remainder
else:
ry = 0
- rh = height / ny # rectangle height
+ rh = height / ny # rectangle height
# parse the image contents and build the mask lines
mask_lines = [width, height]
try:
return subprocess.check_output(['which', 'v4l2-ctl'], stderr=utils.DEV_NULL).strip()
- except subprocess.CalledProcessError: # not found
+ except subprocess.CalledProcessError: # not found
return None
p.kill()
except:
- pass # nevermind
+ pass # nevermind
name = None
devices = []
resolutions = set()
output = ''
started = time.time()
- p = subprocess.Popen('v4l2-ctl -d %(device)s --list-formats-ext | grep -vi stepwise | grep -oE "[0-9]+x[0-9]+" || true' % {
- 'device': pipes.quote(device)}, shell=True, stdout=subprocess.PIPE, bufsize=1)
+ cmd = 'v4l2-ctl -d %(device)s --list-formats-ext | grep -vi stepwise | grep -oE "[0-9]+x[0-9]+" || true' % {
+ 'device': pipes.quote(device)}
+
+ p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, bufsize=1)
fd = p.stdout.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
p.kill()
except:
- pass # nevermind
+ pass # nevermind
for pair in output.split('\n'):
pair = pair.strip()
height = int(height)
if (width, height) in resolutions:
- continue # duplicate resolution
+ continue # duplicate resolution
- if width < 96 or height < 96: # some reasonable minimal values
+ if width < 96 or height < 96: # some reasonable minimal values
continue
if not motionctl.resolution_is_valid(width, height):
output = ''
started = time.time()
- p = subprocess.Popen('v4l2-ctl -d %(device)s --set-ctrl %(control)s=%(value)s' % {
- 'device': pipes.quote(device), 'control': pipes.quote(control), 'value': pipes.quote(str(value))}, shell=True, stdout=subprocess.PIPE, bufsize=1)
+ cmd = 'v4l2-ctl -d %(device)s --set-ctrl %(control)s=%(value)s' % {
+ 'device': pipes.quote(device), 'control': pipes.quote(control), 'value': pipes.quote(str(value))}
+ p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, bufsize=1)
fd = p.stdout.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
p.kill()
except:
- pass # nevermind
+ pass # nevermind
def _list_ctrls(device):
p.kill()
except:
- pass # nevermind
+ pass # nevermind
controls = {}
for line in output.split('\n'):
if not line:
continue
- match = re.match('^\s*(\w+)\s+\(\w+\)\s*\:\s*(.+)\s*', line)
+ match = re.match('^\s*(\w+)\s+\(\w+\)\s*:\s*(.+)\s*', line)
if not match:
continue
headers['Content-Type'] = 'text/plain'
data = ''
- elif options.method == 'POSTf': # form url-encoded
+ elif options.method == 'POSTf': # form url-encoded
headers['Content-Type'] = 'application/x-www-form-urlencoded'
data = parts.query
url = options.url.split('?')[0]
- elif options.method == 'POSTj': # json
+ elif options.method == 'POSTj': # json
headers['Content-Type'] = 'application/json'
data = urlparse.parse_qs(parts.query)
data = {k: v[0] for (k, v) in data.iteritems()}
data = json.dumps(data)
url = options.url.split('?')[0]
- else: # GET
+ else: # GET
pass
request = urllib2.Request(url, data, headers=headers)
lines[i] = ' key_mgmt=' + key_mgmt + '\n'
found_key_mgmt = True
- else: # wifi disabled
+ else: # wifi disabled
if re.match('ssid\s*=\s*".*?"', line) or re.match('psk\s*=\s*".*?"', line):
lines.pop(i)
i -= 1
def _during_working_schedule(now, working_schedule):
parts = working_schedule.split('|')
if len(parts) < 7:
- return False # invalid ws
+ return False # invalid ws
ws_day = parts[now.weekday()]
parts = ws_day.split('-')
if len(parts) != 2:
- return False # invalid ws
+ return False # invalid ws
_from, to = parts
if not _from or not to:
- return False # ws disabled for this day
+ return False # ws disabled for this day
_from = _from.split(':')
to = to.split(':')
if len(_from) != 2 or len(to) != 2:
- return False # invalid ws
+ return False # invalid ws
try:
from_h = int(_from[0])
to_m = int(to[1])
except ValueError:
- return False # invalid ws
+ return False # invalid ws
if now.hour < from_h or now.hour > to_h:
return False
return
def on_motion_detection_status(camera_id, must_be_enabled, working_schedule_type, enabled=None, error=None):
- if error: # could not detect current status
+ if error: # could not detect current status
return logging.warn('skipping motion detection status update for camera with id %(id)s: %(error)s' % {
'id': camera_id, 'error': error})
motion_detection = camera_config.get('@motion_detection')
working_schedule_type = camera_config.get('@working_schedule_type') or 'outside'
- if not working_schedule: # working schedule disabled, motion detection left untouched
+ if not working_schedule: # working schedule disabled, motion detection left untouched
continue
- if not motion_detection: # motion detection explicitly disabled
+ if not motion_detection: # motion detection explicitly disabled
continue
now_during = _during_working_schedule(now, working_schedule)
- must_be_enabled = (now_during and working_schedule_type == 'during') or (not now_during and working_schedule_type == 'outside')
+ must_be_enabled = ((now_during and working_schedule_type == 'during') or
+ (not now_during and working_schedule_type == 'outside'))
motionctl.get_motion_detection(camera_id, functools.partial(
on_motion_detection_status, camera_id, must_be_enabled, working_schedule_type))