*.conf
*.json
.settings
-settings.py
conf
run
media
-log
\ No newline at end of file
+log
--- /dev/null
+include README.md
+
* ffmpeg\r
* v4l-utils\r
\r
-On a debian-based system you could run (as root):\r
-\r
- apt-get install motion ffmpeg v4l-utils python-pip\r
- pip install python-imaging jinja2 pycurl tornado\r
-\r
-## Browser Compatibility ##\r
-\r
-motionEye works fine with most modern browsers, including IE9+.\r
-Being designed with responsiveness in mind, it will also work nicely on mobile devices and tablets.\r
-\r
## Installation ##\r
\r
1. download the latest version from [bitbucket](https://bitbucket.org/ccrisan/motioneye/downloads) (use the *Tags* tab).\r
\r
./motioneye.py\r
\r
- 5. point your favourite browser to <http://localhost:8765>
\ No newline at end of file
+ 5. point your favourite browser to <http://localhost:8765>\r
+++ /dev/null
-#!/usr/bin/env python
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import errno
-import json
-import logging
-import os.path
-import sys
-import urllib
-
-sys.path.append(os.path.join(os.path.dirname(sys.argv[0]),'src'))
-
-import settings
-import utils
-
-from motioneye import _configure_settings, _configure_logging
-
-
-_configure_settings()
-_configure_logging(module='eventrelay')
-
-
-def print_usage():
- print 'Usage: eventrelay.py <event> <thread_id>'
-
-
-def get_admin_credentials():
- # this shortcut function is a bit faster than using the config module functions
- config_file_path = os.path.join(settings.CONF_PATH, 'motion.conf')
-
- logging.debug('reading main config from file %(path)s...' % {'path': config_file_path})
-
- lines = None
- try:
- file = 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 = []
-
- else:
- logging.error('could not open main config file %(path)s: %(msg)s' % {
- 'path': config_file_path, 'msg': unicode(e)})
-
- raise
-
- if lines is None:
- try:
- lines = [l[:-1] for l in file.readlines()]
-
- except Exception as e:
- logging.error('could not read main config file %(path)s: %(msg)s' % {
- 'path': config_file_path, 'msg': unicode(e)})
-
- raise
-
- finally:
- file.close()
-
- admin_username = 'admin'
- admin_password = ''
- for line in lines:
- line = line.strip()
- if not line.startswith('#'):
- continue
-
- line = line[1:].strip()
- if line.startswith('@admin_username'):
- parts = line.split(' ', 1)
- admin_username = parts[1] if len(parts) > 1 else ''
-
- continue
-
- if line.startswith('@admin_password'):
- parts = line.split(' ', 1)
- admin_password = parts[1] if len(parts) > 1 else ''
-
- continue
-
- return admin_username, admin_password
-
-
-# def compute_signature(method, uri, body, key):
-# parts = list(urlparse.urlsplit(uri))
-# query = [q for q in urlparse.parse_qsl(parts[3]) if (q[0] != 'signature')]
-# query.sort(key=lambda q: q[0])
-# query = urllib.urlencode(query)
-# parts[0] = parts[1] = ''
-# parts[3] = query
-# uri = urlparse.urlunsplit(parts)
-#
-# return hashlib.sha1('%s:%s:%s:%s' % (method, uri, body or '', key)).hexdigest().lower()
-
-
-if __name__ == '__main__':
- if len(sys.argv) < 3:
- print_usage()
- sys.exit(-1)
-
- event = sys.argv[1]
- thread_id = sys.argv[2]
-
- logging.debug('hello!')
- logging.debug('event = %s' % event)
- logging.debug('thread_id = %s' % thread_id)
-
- admin_username, admin_password = get_admin_credentials()
-
- uri = '/_relay_event/?event=%(event)s&thread_id=%(thread_id)s&_username=%(username)s' % {
- 'username': admin_username,
- 'thread_id': thread_id,
- 'event': event}
-
- signature = utils.compute_signature('POST', uri, '', admin_password)
-
- url = 'http://127.0.0.1:%(port)s' + uri + '&_signature=' + signature
- url = url % {'port': settings.PORT}
-
- try:
- response = urllib.urlopen(url, data='')
- response = json.load(response)
- if response.get('error'):
- raise Exception(response['error'])
-
- logging.debug('event successfully relayed')
-
- except Exception as e:
- logging.error('failed to relay event: %s' % e)
-
- logging.debug('bye!')
+++ /dev/null
-#!/usr/bin/env python
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import datetime
-import imp
-import inspect
-import logging
-import multiprocessing
-import os.path
-import re
-import signal
-import sys
-
-from tornado.httpclient import AsyncHTTPClient
-
-# test if a --settings directive has been supplied
-for i in xrange(1, len(sys.argv) - 1):
- if sys.argv[i] == '--settings':
- settings_module = sys.argv[i + 1]
- imp.load_source('settings', settings_module)
-
-sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), 'src'))
-
-import settings
-import update
-
-VERSION = '0.25.2'
-
-
-def _configure_settings():
- def set_default_setting(name, value):
- if not hasattr(settings, name):
- setattr(settings, name, value)
-
- set_default_setting('PROJECT_PATH', os.path.dirname(sys.argv[0]))
- set_default_setting('TEMPLATE_PATH', os.path.join(settings.PROJECT_PATH, 'templates'))
- set_default_setting('STATIC_PATH', os.path.join(settings.PROJECT_PATH, 'static'))
- set_default_setting('STATIC_URL', '/static/')
- set_default_setting('CONF_PATH', os.path.join(settings.PROJECT_PATH, 'conf'))
- set_default_setting('RUN_PATH', os.path.join(settings.PROJECT_PATH, 'run'))
- set_default_setting('LOG_PATH', os.path.join(settings.PROJECT_PATH, 'log'))
- set_default_setting('MEDIA_PATH', os.path.join(settings.PROJECT_PATH, 'media'))
- set_default_setting('MOTION_BINARY', None)
- set_default_setting('LOG_LEVEL', logging.INFO)
- set_default_setting('LISTEN', '0.0.0.0')
- set_default_setting('PORT', 8765)
- set_default_setting('MOUNT_CHECK_INTERVAL', 300)
- set_default_setting('MOTION_CHECK_INTERVAL', 10)
- set_default_setting('CLEANUP_INTERVAL', 43200)
- set_default_setting('THUMBNAILER_INTERVAL', 60)
- set_default_setting('REMOTE_REQUEST_TIMEOUT', 10)
- set_default_setting('MJPG_CLIENT_TIMEOUT', 10)
- set_default_setting('MJPG_CLIENT_IDLE_TIMEOUT', 10)
- set_default_setting('SMB_SHARES', False)
- set_default_setting('SMB_MOUNT_ROOT', '/media')
- set_default_setting('WPA_SUPPLICANT_CONF', None)
- set_default_setting('LOCAL_TIME_FILE', None)
- set_default_setting('ENABLE_REBOOT', False)
- set_default_setting('SMTP_TIMEOUT', 60)
- set_default_setting('ZIP_TIMEOUT', 500)
- set_default_setting('ADD_REMOVE_CAMERAS', True)
-
- length = len(sys.argv) - 1
- for i in xrange(length):
- arg = sys.argv[i + 1]
-
- if not arg.startswith('--'):
- continue
-
- next_arg = None
- if i < length - 1:
- next_arg = sys.argv[i + 2]
-
- name = arg[2:].upper().replace('-', '_')
-
- if name == 'HELP':
- _print_help()
- sys.exit(0)
-
- if hasattr(settings, name):
- curr_value = getattr(settings, name)
-
- if next_arg.lower() == 'debug':
- next_arg = logging.DEBUG
-
- elif next_arg.lower() == 'info':
- next_arg = logging.INFO
-
- elif next_arg.lower() == 'warn':
- next_arg = logging.WARN
-
- elif next_arg.lower() == 'error':
- next_arg = logging.ERROR
-
- elif next_arg.lower() == 'fatal':
- next_arg = logging.FATAL
-
- elif next_arg.lower() == 'true':
- next_arg = True
-
- elif next_arg.lower() == 'false':
- next_arg = False
-
- elif isinstance(curr_value, int):
- next_arg = int(next_arg)
-
- elif isinstance(curr_value, float):
- next_arg = float(next_arg)
-
- setattr(settings, name, next_arg)
-
- else:
- return arg[2:]
-
- try:
- os.makedirs(settings.CONF_PATH)
-
- except:
- pass
-
- try:
- os.makedirs(settings.RUN_PATH)
-
- except:
- pass
-
- try:
- os.makedirs(settings.LOG_PATH)
-
- except:
- pass
-
- try:
- os.makedirs(settings.MEDIA_PATH)
-
- except:
- pass
-
-
-def _test_requirements():
- if os.geteuid() != 0:
- if settings.SMB_SHARES:
- print('SMB_SHARES require root privileges')
- return False
-
- if settings.ENABLE_REBOOT:
- print('reboot requires root privileges')
- return False
-
- try:
- import tornado # @UnusedImport
- has_tornado = True
-
- except ImportError:
- has_tornado = False
-
- if update.compare_versions(tornado.version, '3.1') < 0:
- has_tornado = False
-
- try:
- import jinja2 # @UnusedImport
- has_jinja2 = True
-
- except ImportError:
- has_jinja2 = False
-
- try:
- import PIL.Image # @UnusedImport
- has_pil = True
-
- except ImportError:
- has_pil = False
-
- try:
- import pycurl # @UnusedImport
- has_pycurl = True
-
- except ImportError:
- has_pycurl = False
-
- import mediafiles
- has_ffmpeg = mediafiles.find_ffmpeg() is not None
-
- import motionctl
- has_motion = motionctl.find_motion() is not None
-
- import v4l2ctl
- has_v4lutils = v4l2ctl.find_v4l2_ctl() is not None
-
- import smbctl
- has_mount_cifs = smbctl.find_mount_cifs() is not None
-
- ok = True
- if not has_tornado:
- print('please install tornado (python-tornado), version 3.1 or greater')
- ok = False
-
- if not has_jinja2:
- print('please install jinja2 (python-jinja2)')
- ok = False
-
- if not has_pil:
- print('please install PIL (python-imaging)')
- ok = False
-
- if not has_pycurl:
- print('please install pycurl (python-pycurl)')
- ok = False
-
- if not has_ffmpeg:
- print('please install ffmpeg')
- ok = False
-
- if not has_motion:
- print('please install motion')
- ok = False
-
- if not has_v4lutils:
- print('please install v4l-utils')
- ok = False
-
- if settings.SMB_SHARES and not has_mount_cifs:
- print('please install cifs-utils')
- ok = False
-
- return ok
-
-
-def _configure_signals():
- def bye_handler(signal, frame):
- import tornado.ioloop
-
- logging.info('interrupt signal received, shutting down...')
-
- # shut down the IO loop if it has been started
- ioloop = tornado.ioloop.IOLoop.instance()
- ioloop.stop()
-
- def child_handler(signal, frame):
- # this is required for the multiprocessing mechanism to work
- multiprocessing.active_children()
-
- signal.signal(signal.SIGINT, bye_handler)
- signal.signal(signal.SIGTERM, bye_handler)
- signal.signal(signal.SIGCHLD, child_handler)
-
-
-def _configure_logging(module=None):
- if module:
- format = '%(asctime)s: [{module}] %(levelname)s: %(message)s'.format(module=module)
-
- else:
- format = '%(asctime)s: %(levelname)s: %(message)s'
-
- logging.basicConfig(filename=None, level=settings.LOG_LEVEL,
- format=format, datefmt='%Y-%m-%d %H:%M:%S')
-
- logging.getLogger('tornado').setLevel(logging.WARN)
-
-
-def _configure_tornado():
- AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient', max_clients=16)
-
-
-def _print_help():
- print('Usage: ' + sys.argv[0] + ' [option1 value1] ...')
- print('available options: ')
-
- options = list(inspect.getmembers(settings))
-
- print(' --settings <module>')
-
- for (name, value) in sorted(options):
- if name.upper() != name:
- continue
-
- if not re.match('^[A-Z0-9_]+$', name):
- continue
-
- name = '--' + name.lower().replace('_', '-')
- if value is not None:
- value = type(value).__name__
-
- line = ' ' + name
- if value:
- line += ' <' + value + '>'
- print(line)
-
- print('')
-
-
-def _run_server():
- import cleanup
- import motionctl
- import thumbnailer
- import tornado.ioloop
- import server
- import smbctl
-
- server.application.listen(settings.PORT, settings.LISTEN)
- logging.info('server started')
-
- tornado.ioloop.IOLoop.instance().start()
-
- logging.info('server stopped')
-
- if thumbnailer.running():
- thumbnailer.stop()
- logging.info('thumbnailer stopped')
-
- if cleanup.running():
- cleanup.stop()
- logging.info('cleanup stopped')
-
- if motionctl.running():
- motionctl.stop()
- logging.info('motion stopped')
-
- if settings.SMB_SHARES:
- smbctl.umount_all()
- logging.info('SMB shares unmounted')
-
-
-def _start_motion():
- import tornado.ioloop
- import config
- import motionctl
-
- ioloop = tornado.ioloop.IOLoop.instance()
-
- # add a motion running checker
- def checker():
- if ioloop._stopped:
- return
-
- if not motionctl.running() and motionctl.started() and config.get_enabled_local_motion_cameras():
- try:
- logging.error('motion not running, starting it')
- motionctl.start()
-
- except Exception as e:
- logging.error('failed to start motion: %(msg)s' % {
- 'msg': unicode(e)}, exc_info=True)
-
- ioloop.add_timeout(datetime.timedelta(seconds=settings.MOTION_CHECK_INTERVAL), checker)
-
- motionctl.start()
-
- ioloop.add_timeout(datetime.timedelta(seconds=settings.MOTION_CHECK_INTERVAL), checker)
-
-
-def _start_cleanup():
- import cleanup
-
- cleanup.start()
- logging.info('cleanup started')
-
-
-def _start_wsswitch():
- import wsswitch
-
- wsswitch.start()
- logging.info('wsswitch started')
-
-
-def _start_thumbnailer():
- import thumbnailer
-
- thumbnailer.start()
- logging.info('thumbnailer started')
-
-
-if __name__ == '__main__':
- cmd = _configure_settings()
-
- _configure_signals()
- _configure_logging()
-
- if not _test_requirements():
- sys.exit(-1)
-
- _configure_tornado()
-
- logging.info('hello! this is motionEye %s' % VERSION)
-
- if settings.SMB_SHARES:
- import smbctl
-
- stop, start = smbctl.update_mounts()
- if start:
- _start_motion()
-
- else:
- _start_motion()
-
- _start_cleanup()
- _start_wsswitch()
-
- if settings.THUMBNAILER_INTERVAL:
- _start_thumbnailer()
-
- _run_server()
-
- logging.info('bye!')
\ No newline at end of file
--- /dev/null
+
+VERSION = '0.26'
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+import logging
+import multiprocessing
+import os
+import signal
+import tornado
+
+import mediafiles
+import settings
+import thumbnailer
+
+
+_process = None
+
+
+def start():
+ # schedule the first call a bit later to improve performance at startup
+ ioloop = tornado.ioloop.IOLoop.instance()
+ ioloop.add_timeout(datetime.timedelta(seconds=60), _run_process)
+
+
+def stop():
+ global _process
+
+ if not running():
+ _process = None
+ return
+
+ if _process.is_alive():
+ _process.join(timeout=10)
+
+ if _process.is_alive():
+ logging.error('cleanup process did not finish in time, killing it...')
+ os.kill(_process.pid, signal.SIGKILL)
+
+ _process = None
+
+
+def running():
+ return _process is not None and _process.is_alive()
+
+
+def _run_process():
+ global _process
+
+ ioloop = tornado.ioloop.IOLoop.instance()
+
+ if thumbnailer.running():
+ # postpone if thumbnailer is currently running
+ ioloop.add_timeout(datetime.timedelta(seconds=60), _run_process)
+
+ return
+
+ else:
+ # schedule the next call
+ ioloop.add_timeout(datetime.timedelta(seconds=settings.CLEANUP_INTERVAL), _run_process)
+
+ if not running(): # check that the previous process has finished
+ logging.debug('running cleanup process...')
+
+ _process = multiprocessing.Process(target=_do_cleanup)
+ _process.start()
+
+
+def _do_cleanup():
+ # this will be executed in a separate subprocess
+
+ # ignore the terminate and interrupt signals in this subprocess
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
+ signal.signal(signal.SIGTERM, signal.SIG_IGN)
+
+ try:
+ mediafiles.cleanup_media('picture')
+ mediafiles.cleanup_media('movie')
+ logging.debug('cleanup done')
+
+ except Exception as e:
+ logging.error('failed to cleanup media files: %(msg)s' % {
+ 'msg': unicode(e)}, exc_info=True)
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import collections
+import datetime
+import errno
+import logging
+import os.path
+import re
+import shlex
+import subprocess
+import urlparse
+
+from tornado.ioloop import IOLoop
+
+import diskctl
+import powerctl
+import settings
+import update
+import utils
+import v4l2ctl
+
+from utils import OrderedDict
+
+
+_CAMERA_CONFIG_FILE_NAME = 'thread-%(id)s.conf'
+_MAIN_CONFIG_FILE_NAME = 'motion.conf'
+
+_main_config_cache = None
+_camera_config_cache = {}
+_camera_ids_cache = None
+_additional_section_funcs = []
+_additional_config_funcs = []
+_additional_structure_cache = {}
+
+# starting with r490 motion config directives have changed a bit
+_LAST_OLD_CONFIG_VERSIONS = (490, '3.2.12')
+
+_KNOWN_MOTION_OPTIONS = set([
+ 'auto_brightness', 'brightness', 'contrast', 'emulate_motion', 'event_gap', 'ffmpeg_bps', 'ffmpeg_output_movies', 'ffmpeg_variable_bitrate', 'ffmpeg_video_codec',
+ 'framerate', 'height', 'hue', 'lightswitch', 'locate_motion_mode', 'locate_motion_style', 'minimum_motion_frames', 'movie_filename', 'max_movie_time', 'max_mpeg_time',
+ 'noise_level', 'noise_tune', 'on_event_end', 'on_event_start', 'output_pictures', 'picture_filename', 'post_capture', 'pre_capture', 'quality', 'rotate', 'saturation',
+ 'snapshot_filename', 'snapshot_interval', 'stream_auth_method', 'stream_authentication', 'stream_localhost', 'stream_maxrate', 'stream_motion', 'stream_port', 'stream_quality',
+ 'target_dir', 'text_changes', 'text_double', 'text_left', 'text_right', 'threshold', 'videodevice', 'width',
+ 'webcam_localhost', 'webcam_port', 'webcam_maxrate', 'webcam_quality', 'webcam_motion', 'ffmpeg_cap_new', 'output_normal', 'output_motion', 'jpeg_filename', 'output_all', 'gap', 'locate',
+ 'netcam_url', 'netcam_userpass', 'netcam_http', 'netcam_tolerant_check', 'netcam_keepalive', 'rtsp_uses_tcp'
+])
+
+
+def additional_section(func):
+ _additional_section_funcs.append(func)
+
+
+def additional_config(func):
+ _additional_config_funcs.append(func)
+
+
+import wifictl # @UnusedImport
+import tzctl # @UnusedImport
+
+
+def get_main(as_lines=False):
+ global _main_config_cache
+
+ if not as_lines and _main_config_cache is not None:
+ return _main_config_cache
+
+ config_file_path = os.path.join(settings.CONF_PATH, _MAIN_CONFIG_FILE_NAME)
+
+ logging.debug('reading main config from file %(path)s...' % {'path': config_file_path})
+
+ lines = None
+ try:
+ file = 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 = []
+
+ else:
+ logging.error('could not open main config file %(path)s: %(msg)s' % {
+ 'path': config_file_path, 'msg': unicode(e)})
+
+ raise
+
+ if lines is None:
+ try:
+ lines = [l[:-1] for l in file.readlines()]
+
+ except Exception as e:
+ logging.error('could not read main config file %(path)s: %(msg)s' % {
+ 'path': config_file_path, 'msg': unicode(e)})
+
+ raise
+
+ finally:
+ file.close()
+
+ if as_lines:
+ return lines
+
+ main_config = _conf_to_dict(lines,
+ list_names=['thread'],
+ no_convert=['@admin_username', '@admin_password', '@normal_username', '@normal_password'])
+
+ _get_additional_config(main_config)
+ _set_default_motion(main_config, old_motion=is_old_motion())
+
+ _main_config_cache = main_config
+
+ return main_config
+
+
+def set_main(main_config):
+ global _main_config_cache
+
+ main_config = dict(main_config)
+ _set_default_motion(main_config, old_motion=is_old_motion())
+ for n, v in _main_config_cache.iteritems():
+ main_config.setdefault(n, v)
+ _main_config_cache = main_config
+
+ main_config = dict(main_config)
+ _set_additional_config(main_config)
+
+ config_file_path = os.path.join(settings.CONF_PATH, _MAIN_CONFIG_FILE_NAME)
+
+ # read the actual configuration from file
+ lines = get_main(as_lines=True)
+
+ # write the configuration to file
+ logging.debug('writing main config to %(path)s...' % {'path': config_file_path})
+
+ try:
+ file = open(config_file_path, 'w')
+
+ except Exception as e:
+ logging.error('could not open main config file %(path)s for writing: %(msg)s' % {
+ 'path': config_file_path, 'msg': unicode(e)})
+
+ raise
+
+ lines = _dict_to_conf(lines, main_config, list_names=['thread'])
+
+ try:
+ file.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' % {
+ 'path': config_file_path, 'msg': unicode(e)})
+
+ raise
+
+ finally:
+ file.close()
+
+
+def get_camera_ids(filter_valid=True):
+ global _camera_ids_cache
+
+ if _camera_ids_cache is not None:
+ return _camera_ids_cache
+
+ config_path = settings.CONF_PATH
+
+ logging.debug('listing config dir %(path)s...' % {'path': config_path})
+
+ try:
+ ls = os.listdir(config_path)
+
+ except Exception as e:
+ logging.error('failed to list config dir %(path)s: %(msg)s', {
+ 'path': config_path, 'msg': unicode(e)})
+
+ raise
+
+ camera_ids = []
+
+ pattern = '^' + _CAMERA_CONFIG_FILE_NAME.replace('%(id)s', '(\d+)') + '$'
+ for name in ls:
+ match = re.match(pattern, name)
+ if match:
+ camera_id = int(match.groups()[0])
+ logging.debug('found camera with id %(id)s' % {
+ 'id': camera_id})
+
+ camera_ids.append(camera_id)
+
+ camera_ids.sort()
+
+ if not filter_valid:
+ return camera_ids
+
+ filtered_camera_ids = []
+ for camera_id in camera_ids:
+ if get_camera(camera_id):
+ filtered_camera_ids.append(camera_id)
+
+ _camera_ids_cache = filtered_camera_ids
+
+ return filtered_camera_ids
+
+
+def get_enabled_local_motion_cameras():
+ if not get_main().get('@enabled'):
+ return []
+
+ camera_ids = get_camera_ids()
+ cameras = [get_camera(camera_id) for camera_id in camera_ids]
+ return [c for c in cameras if c.get('@enabled') and utils.local_motion_camera(c)]
+
+
+def get_network_shares():
+ if not get_main().get('@enabled'):
+ return []
+
+ camera_ids = get_camera_ids()
+ cameras = [get_camera(camera_id) for camera_id in camera_ids]
+
+ mounts = []
+ for camera in cameras:
+ if camera.get('@storage_device') != 'network-share':
+ continue
+
+ mounts.append({
+ 'server': camera['@network_server'],
+ 'share': camera['@network_share_name'],
+ 'username': camera['@network_username'],
+ 'password': camera['@network_password'],
+ })
+
+ return mounts
+
+
+def get_camera(camera_id, as_lines=False):
+ global _camera_config_cache
+
+ if not as_lines and camera_id in _camera_config_cache:
+ return _camera_config_cache[camera_id]
+
+ camera_config_path = os.path.join(settings.CONF_PATH, _CAMERA_CONFIG_FILE_NAME) % {'id': camera_id}
+
+ logging.debug('reading camera config from %(path)s...' % {'path': camera_config_path})
+
+ try:
+ file = 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()]
+
+ except Exception as e:
+ logging.error('could not read camera config file %(path)s: %(msg)s' % {
+ 'path': camera_config_path, 'msg': unicode(e)})
+
+ raise
+
+ finally:
+ file.close()
+
+ if as_lines:
+ return lines
+
+ camera_config = _conf_to_dict(lines,
+ no_convert=['@name', '@network_share_name', '@network_server',
+ '@network_username', '@network_password', '@storage_device'])
+
+ if utils.local_motion_camera(camera_config):
+ # determine the enabled status
+ main_config = get_main()
+ threads = main_config.get('thread', [])
+ camera_config['@enabled'] = _CAMERA_CONFIG_FILE_NAME % {'id': camera_id} in threads
+ camera_config['@id'] = camera_id
+
+ old_motion = is_old_motion()
+
+ # adapt directives from old configuration, if needed
+ if old_motion:
+ logging.debug('using old motion config directives')
+
+ if 'output_normal' in camera_config:
+ camera_config['output_pictures'] = camera_config.pop('output_normal')
+ if 'output_all' in camera_config:
+ camera_config['emulate_motion'] = camera_config.pop('output_all')
+ if 'ffmpeg_cap_new' in camera_config:
+ camera_config['ffmpeg_output_movies'] = camera_config.pop('ffmpeg_cap_new')
+ if 'locate' in camera_config:
+ camera_config['locate_motion_mode'] = camera_config.pop('locate')
+ if 'jpeg_filename' in camera_config:
+ camera_config['picture_filename'] = camera_config.pop('jpeg_filename')
+ if 'max_mpeg_time' in camera_config:
+ camera_config['max_movie_time'] = camera_config.pop('max_mpeg_time')
+ if 'webcam_port' in camera_config:
+ camera_config['stream_port'] = camera_config.pop('webcam_port')
+ if 'webcam_quality' in camera_config:
+ camera_config['stream_quality'] = camera_config.pop('webcam_quality')
+ if 'webcam_motion' in camera_config:
+ camera_config['stream_motion'] = camera_config.pop('webcam_motion')
+ if 'webcam_maxrate' in camera_config:
+ camera_config['stream_maxrate'] = camera_config.pop('webcam_maxrate')
+ if 'webcam_localhost' in camera_config:
+ camera_config['stream_localhost'] = camera_config.pop('webcam_localhost')
+ if 'gap' in camera_config:
+ camera_config['event_gap'] = camera_config.pop('gap')
+ if 'netcam_http' in camera_config:
+ camera_config['netcam_keepalive'] = camera_config.pop('netcam_http') in ['1.1', 'keepalive']
+
+ _get_additional_config(camera_config, camera_id=camera_id)
+ _set_default_motion_camera(camera_id, camera_config)
+
+ elif utils.remote_camera(camera_config):
+ pass
+
+ elif utils.simple_mjpeg_camera(camera_config):
+ _get_additional_config(camera_config, camera_id=camera_id)
+
+ else: # incomplete configuration
+ logging.warn('camera config file at %s is incomplete, ignoring' % camera_config_path)
+
+ return None
+
+ _camera_config_cache[camera_id] = dict(camera_config)
+
+ return camera_config
+
+
+def set_camera(camera_id, camera_config):
+ global _camera_config_cache
+
+ camera_config['@id'] = camera_id
+ _camera_config_cache[camera_id] = camera_config
+
+ camera_config = dict(camera_config)
+
+ if utils.local_motion_camera(camera_config):
+ old_motion = is_old_motion()
+
+ # adapt directives to old configuration, if needed
+ if old_motion:
+ logging.debug('using old motion config directives')
+
+ if 'output_pictures' in camera_config:
+ camera_config['output_normal'] = camera_config.pop('output_pictures')
+ if 'emulate_motion' in camera_config:
+ camera_config['output_all'] = camera_config.pop('emulate_motion')
+ if 'ffmpeg_output_movies' in camera_config:
+ camera_config['ffmpeg_cap_new'] = camera_config.pop('ffmpeg_output_movies')
+ if 'locate_motion_mode' in camera_config:
+ camera_config['locate'] = camera_config.pop('locate_motion_mode')
+ if 'picture_filename' in camera_config:
+ camera_config['jpeg_filename'] = camera_config.pop('picture_filename')
+ if 'max_movie_time' in camera_config:
+ camera_config['max_mpeg_time'] = camera_config.pop('max_movie_time')
+ if 'stream_port' in camera_config:
+ camera_config['webcam_port'] = camera_config.pop('stream_port')
+ if 'stream_quality' in camera_config:
+ camera_config['webcam_quality'] = camera_config.pop('stream_quality')
+ if 'stream_motion' in camera_config:
+ camera_config['webcam_motion'] = camera_config.pop('stream_motion')
+ if 'stream_maxrate' in camera_config:
+ camera_config['webcam_maxrate'] = camera_config.pop('stream_maxrate')
+ if 'stream_localhost' in camera_config:
+ camera_config['webcam_localhost'] = camera_config.pop('stream_localhost')
+ if 'stream_auth_method' in camera_config:
+ camera_config.pop('stream_auth_method')
+ if 'stream_authentication' in camera_config:
+ camera_config.pop('stream_authentication')
+ if 'event_gap' in camera_config:
+ camera_config['gap'] = camera_config.pop('event_gap')
+ if 'netcam_keepalive' in camera_config:
+ camera_config['netcam_http'] = '1.1' if camera_config.pop('netcam_keepalive') else '1.0'
+
+ _set_default_motion_camera(camera_id, camera_config, old_motion)
+
+ # set the enabled status in main config
+ main_config = get_main()
+ threads = main_config.setdefault('thread', [])
+ config_file_name = _CAMERA_CONFIG_FILE_NAME % {'id': camera_id}
+ if camera_config['@enabled'] and config_file_name not in threads:
+ threads.append(config_file_name)
+
+ elif not camera_config['@enabled']:
+ threads = [t for t in threads if t != config_file_name]
+
+ main_config['thread'] = threads
+
+ set_main(main_config)
+ _set_additional_config(camera_config, camera_id=camera_id)
+
+ elif utils.remote_camera(camera_config):
+ pass
+
+ elif utils.simple_mjpeg_camera(camera_config):
+ _set_additional_config(camera_config, camera_id=camera_id)
+
+ # read the actual configuration from file
+ config_file_path = os.path.join(settings.CONF_PATH, _CAMERA_CONFIG_FILE_NAME) % {'id': camera_id}
+ if os.path.isfile(config_file_path):
+ lines = get_camera(camera_id, as_lines=True)
+
+ else:
+ lines = []
+
+ # write the configuration to file
+ camera_config_path = os.path.join(settings.CONF_PATH, _CAMERA_CONFIG_FILE_NAME) % {'id': camera_id}
+ logging.debug('writing camera config to %(path)s...' % {'path': camera_config_path})
+
+ try:
+ file = open(camera_config_path, 'w')
+
+ except Exception as e:
+ logging.error('could not open camera config file %(path)s for writing: %(msg)s' % {
+ 'path': camera_config_path, 'msg': unicode(e)})
+
+ raise
+
+ lines = _dict_to_conf(lines, camera_config)
+
+ try:
+ file.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' % {
+ 'path': camera_config_path, 'msg': unicode(e)})
+
+ raise
+
+ finally:
+ file.close()
+
+
+def add_camera(device_details):
+ global _camera_ids_cache
+ global _camera_config_cache
+
+ proto = device_details['proto']
+ if proto in ['netcam', 'mjpeg']:
+ host = device_details['host']
+ if device_details['port']:
+ host += ':' + str(device_details['port'])
+
+ if device_details['username'] and proto == 'mjpeg':
+ if device_details['password']:
+ host = device_details['username'] + ':' + device_details['password'] + '@' + host
+
+ else:
+ host = device_details['username'] + '@' + host
+
+ device_details['url'] = urlparse.urlunparse((device_details['scheme'], host, device_details['uri'], '', '', ''))
+
+ # determine the last camera id
+ camera_ids = get_camera_ids()
+
+ camera_id = 1
+ while camera_id in camera_ids:
+ camera_id += 1
+
+ logging.info('adding new camera with id %(id)s...' % {'id': camera_id})
+
+ # prepare a default camera config
+ camera_config = {'@enabled': True}
+ if proto == 'v4l2':
+ # find a suitable resolution
+ for (w, h) in v4l2ctl.list_resolutions(device_details['uri']):
+ if w > 300:
+ camera_config['width'] = w
+ camera_config['height'] = h
+ break
+
+ camera_config['videodevice'] = device_details['uri']
+ _set_default_motion_camera(camera_id, camera_config)
+
+ elif proto == 'motioneye':
+ camera_config['@proto'] = 'motioneye'
+ camera_config['@scheme'] = device_details['scheme']
+ camera_config['@host'] = device_details['host']
+ camera_config['@port'] = device_details['port']
+ camera_config['@uri'] = device_details['uri']
+ camera_config['@username'] = device_details['username']
+ camera_config['@password'] = device_details['password']
+ camera_config['@remote_camera_id'] = device_details['remote_camera_id']
+
+ elif proto == 'netcam':
+ camera_config['netcam_url'] = device_details['url']
+ camera_config['text_double'] = True
+
+ if device_details['username']:
+ camera_config['netcam_userpass'] = device_details['username'] + ':' + device_details['password']
+
+ camera_config['netcam_keepalive'] = device_details.get('keep_alive')
+ camera_config['netcam_tolerant_check'] = True
+
+ if device_details.get('camera_index') == 'udp':
+ camera_config['rtsp_uses_tcp'] = False
+
+ if camera_config['netcam_url'].startswith('rtsp'):
+ camera_config['width'] = 640
+ camera_config['height'] = 480
+
+ _set_default_motion_camera(camera_id, camera_config)
+
+ else: # assuming mjpeg
+ camera_config['@proto'] = 'mjpeg'
+ camera_config['@url'] = device_details['url']
+ _set_default_simple_mjpeg_camera(camera_id, camera_config)
+
+ # write the configuration to file
+ set_camera(camera_id, camera_config)
+
+ _camera_ids_cache = None
+ _camera_config_cache = {}
+
+ camera_config = get_camera(camera_id)
+
+ return camera_config
+
+
+def rem_camera(camera_id):
+ global _camera_ids_cache
+ global _camera_config_cache
+
+ camera_config_name = _CAMERA_CONFIG_FILE_NAME % {'id': camera_id}
+ camera_config_path = os.path.join(settings.CONF_PATH, _CAMERA_CONFIG_FILE_NAME) % {'id': camera_id}
+
+ # remove the camera from the main config
+ main_config = get_main()
+ threads = main_config.setdefault('thread', [])
+ threads = [t for t in threads if t != camera_config_name]
+
+ main_config['thread'] = threads
+
+ set_main(main_config)
+
+ logging.info('removing camera config file %(path)s...' % {'path': camera_config_path})
+
+ _camera_ids_cache = None
+ _camera_config_cache = {}
+
+ try:
+ os.remove(camera_config_path)
+
+ except Exception as e:
+ logging.error('could not remove camera config file %(path)s: %(msg)s' % {
+ 'path': camera_config_path, 'msg': unicode(e)})
+
+ raise
+
+
+def main_ui_to_dict(ui):
+ data = {
+ '@enabled': ui['enabled'],
+
+ '@show_advanced': ui['show_advanced'],
+ '@admin_username': ui['admin_username'],
+ '@admin_password': ui['admin_password'],
+ '@normal_username': ui['normal_username'],
+ '@normal_password': ui['normal_password']
+ }
+
+ # additional configs
+ for name, value in ui.iteritems():
+ if not name.startswith('_'):
+ continue
+
+ data['@' + name] = value
+
+ return data
+
+
+def main_dict_to_ui(data):
+ ui = {
+ 'enabled': data['@enabled'],
+
+ 'show_advanced': data['@show_advanced'],
+ 'admin_username': data['@admin_username'],
+ 'admin_password': data['@admin_password'],
+ 'normal_username': data['@normal_username'],
+ 'normal_password': data['@normal_password']
+ }
+
+ # additional configs
+ for name, value in data.iteritems():
+ if not name.startswith('@_'):
+ continue
+
+ ui[name[1:]] = value
+
+ return ui
+
+
+def motion_camera_ui_to_dict(ui, old_config=None):
+ import smbctl
+
+ old_config = dict(old_config or {})
+ main_config = get_main() # needed for surveillance password
+
+ data = {
+ # device
+ '@name': ui['name'],
+ '@enabled': ui['enabled'],
+ 'lightswitch': int(ui['light_switch_detect']) * 50,
+ 'auto_brightness': ui['auto_brightness'],
+ 'framerate': int(ui['framerate']),
+ 'rotate': int(ui['rotation']),
+
+ # file storage
+ '@storage_device': ui['storage_device'],
+ '@network_server': ui['network_server'],
+ '@network_share_name': ui['network_share_name'],
+ '@network_username': ui['network_username'],
+ '@network_password': ui['network_password'],
+
+ # text overlay
+ 'text_left': '',
+ 'text_right': '',
+ 'text_double': False,
+
+ # streaming
+ 'stream_localhost': not ui['video_streaming'],
+ 'stream_port': int(ui['streaming_port']),
+ 'stream_maxrate': int(ui['streaming_framerate']),
+ 'stream_quality': max(1, int(ui['streaming_quality'])),
+ '@webcam_resolution': max(1, int(ui['streaming_resolution'])),
+ '@webcam_server_resize': ui['streaming_server_resize'],
+ 'stream_motion': ui['streaming_motion'],
+ 'stream_auth_method': {'disabled': 0, 'basic': 1, 'digest': 2}.get(ui['streaming_auth_mode'], 0),
+ 'stream_authentication': main_config['@normal_username'] + ':' + main_config['@normal_password'],
+
+ # still images
+ 'output_pictures': False,
+ 'emulate_motion': False,
+ 'snapshot_interval': 0,
+ 'picture_filename': '',
+ 'snapshot_filename': '',
+ '@preserve_pictures': int(ui['preserve_pictures']),
+
+ # motion detection
+ '@motion_detection': ui['motion_detection'],
+ 'text_changes': ui['show_frame_changes'],
+ 'locate_motion_mode': ui['show_frame_changes'],
+ 'noise_tune': ui['auto_noise_detect'],
+ 'noise_level': max(1, int(round(int(ui['noise_level']) * 2.55))),
+ 'event_gap': int(ui['event_gap']),
+ 'pre_capture': int(ui['pre_capture']),
+ 'post_capture': int(ui['post_capture']),
+ 'minimum_motion_frames': int(ui['minimum_motion_frames']),
+
+ # movies
+ 'ffmpeg_output_movies': ui['motion_movies'],
+ 'movie_filename': ui['movie_file_name'],
+ 'ffmpeg_bps': 44000, # a quality of about 85% for 320x240x2fps
+ 'max_movie_time': ui['max_movie_length'],
+ '@preserve_movies': int(ui['preserve_movies']),
+
+ # working schedule
+ '@working_schedule': '',
+
+ # events
+ 'on_event_start': '',
+ 'on_event_end': ''
+ }
+
+ if utils.v4l2_camera(old_config):
+ proto = 'v4l2'
+
+ else:
+ proto = 'netcam'
+
+ if proto == 'v4l2':
+ # leave videodevice unchanged
+
+ # resolution
+ if not ui['resolution']:
+ ui['resolution'] = '320x240'
+
+ width = int(ui['resolution'].split('x')[0])
+ height = int(ui['resolution'].split('x')[1])
+ data['width'] = width
+ data['height'] = height
+
+ threshold = int(float(ui['frame_change_threshold']) * width * height / 100)
+
+ if 'brightness' in ui:
+ if int(ui['brightness']) == 50:
+ data['brightness'] = 0
+
+ else:
+ data['brightness'] = max(1, int(round(int(ui['brightness']) * 2.55)))
+
+ if 'contrast' in ui:
+ if int(ui['contrast']) == 50:
+ data['contrast'] = 0
+
+ else:
+ data['contrast'] = max(1, int(round(int(ui['contrast']) * 2.55)))
+
+ if 'saturation' in ui:
+ if int(ui['saturation']) == 50:
+ data['saturation'] = 0
+
+ else:
+ data['saturation'] = max(1, int(round(int(ui['saturation']) * 2.55)))
+
+ if 'hue' in ui:
+ if int(ui['hue']) == 50:
+ data['hue'] = 0
+
+ else:
+ data['hue'] = max(1, int(round(int(ui['hue']) * 2.55)))
+
+ 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])
+ height = int(ui['resolution'].split('x')[1])
+ data['width'] = width
+ data['height'] = height
+
+ threshold = int(float(ui['frame_change_threshold']) * width * height / 100)
+
+ else: # width & height are not available for other netcams
+ threshold = int(float(ui['frame_change_threshold']) * 640 * 480 / 100)
+
+ data['threshold'] = threshold
+
+ if (ui['storage_device'] == 'network-share') and settings.SMB_SHARES:
+ mount_point = smbctl.make_mount_point(ui['network_server'], ui['network_share_name'], ui['network_username'])
+ if ui['root_directory'].startswith('/'):
+ ui['root_directory'] = ui['root_directory'][1:]
+ data['target_dir'] = os.path.normpath(os.path.join(mount_point, ui['root_directory']))
+
+ elif ui['storage_device'].startswith('local-disk'):
+ target_dev = ui['storage_device'][10:].replace('-', '/')
+ mounted_partitions = diskctl.list_mounted_partitions()
+ partition = mounted_partitions[target_dev]
+ mount_point = partition['mount_point']
+
+ if ui['root_directory'].startswith('/'):
+ ui['root_directory'] = ui['root_directory'][1:]
+ data['target_dir'] = os.path.normpath(os.path.join(mount_point, ui['root_directory']))
+
+ else:
+ data['target_dir'] = ui['root_directory']
+
+ if ui['text_overlay']:
+ left_text = ui['left_text']
+ if left_text == 'camera-name':
+ data['text_left'] = ui['name']
+
+ elif left_text == 'timestamp':
+ data['text_left'] = '%Y-%m-%d\\n%T'
+
+ elif left_text == 'disabled':
+ data['text_left'] = ''
+
+ else:
+ data['text_left'] = ui['custom_left_text']
+
+ right_text = ui['right_text']
+ if right_text == 'camera-name':
+ data['text_right'] = ui['name']
+
+ elif right_text == 'timestamp':
+ data['text_right'] = '%Y-%m-%d\\n%T'
+
+ elif right_text == 'disabled':
+ data['text_right'] = ''
+
+ else:
+ data['text_right'] = ui['custom_right_text']
+
+ if proto == 'netcam' or data['width'] > 320:
+ data['text_double'] = True
+
+ if ui['still_images']:
+ capture_mode = ui['capture_mode']
+ if capture_mode == 'motion-triggered':
+ data['output_pictures'] = True
+ data['picture_filename'] = ui['image_file_name']
+
+ elif capture_mode == 'interval-snapshots':
+ data['snapshot_interval'] = int(ui['snapshot_interval'])
+ data['snapshot_filename'] = ui['image_file_name']
+
+ elif capture_mode == 'all-frames':
+ data['output_pictures'] = True
+ data['emulate_motion'] = True
+ data['picture_filename'] = ui['image_file_name']
+
+ data['quality'] = max(1, int(ui['image_quality']))
+
+ if proto == 'v4l2':
+ max_val = data['width'] * data['height'] * data['framerate'] / 3
+
+ else: # always assume a netcam image size of 640x480, since we have no means to know it at this point
+ max_val = 640 * 480 * data['framerate'] / 3
+
+ max_val = min(max_val, 9999999)
+
+ data['ffmpeg_bps'] = int(ui['movie_quality']) * max_val / 100
+
+ # working schedule
+ if ui['working_schedule']:
+ data['@working_schedule'] = (
+ ui['monday_from'] + '-' + ui['monday_to'] + '|' +
+ ui['tuesday_from'] + '-' + ui['tuesday_to'] + '|' +
+ ui['wednesday_from'] + '-' + ui['wednesday_to'] + '|' +
+ ui['thursday_from'] + '-' + ui['thursday_to'] + '|' +
+ ui['friday_from'] + '-' + ui['friday_to'] + '|' +
+ ui['saturday_from'] + '-' + ui['saturday_to'] + '|' +
+ ui['sunday_from'] + '-' + ui['sunday_to'])
+
+ data['@working_schedule_type'] = ui['working_schedule_type']
+
+ # event start
+ event_relay_path = os.path.join(settings.PROJECT_PATH, 'eventrelay.py')
+ event_relay_path = os.path.abspath(event_relay_path)
+
+ on_event_start = ['%(script)s start %%t' % {'script': event_relay_path}]
+ if ui['email_notifications_enabled']:
+ send_mail_path = os.path.join(settings.PROJECT_PATH, 'sendmail.py')
+ send_mail_path = os.path.abspath(send_mail_path)
+ emails = re.sub('\\s', '', ui['email_notifications_addresses'])
+
+ on_event_start.append("%(script)s '%(server)s' '%(port)s' '%(account)s' '%(password)s' '%(tls)s' '%(to)s' 'motion_start' '%%t' '%%Y-%%m-%%dT%%H:%%M:%%S' '%(timespan)s'" % {
+ 'script': send_mail_path,
+ 'server': ui['email_notifications_smtp_server'],
+ 'port': ui['email_notifications_smtp_port'],
+ 'account': ui['email_notifications_smtp_account'],
+ 'password': ui['email_notifications_smtp_password'],
+ 'tls': ui['email_notifications_smtp_tls'],
+ 'to': emails,
+ 'timespan': ui['email_notifications_picture_time_span']})
+
+ if ui['web_hook_notifications_enabled']:
+ web_hook_path = os.path.join(settings.PROJECT_PATH, 'webhook.py')
+ web_hook_path = os.path.abspath(web_hook_path)
+ url = re.sub('\\s', '+', ui['web_hook_notifications_url'])
+
+ on_event_start.append("%(script)s '%(method)s' '%(url)s'" % {
+ 'script': web_hook_path,
+ 'method': ui['web_hook_notifications_http_method'],
+ 'url': url})
+
+ if ui['command_notifications_enabled']:
+ commands = ui['command_notifications_exec'].split(';')
+ on_event_start += [c.strip() for c in commands]
+
+ data['on_event_start'] = '; '.join(on_event_start)
+
+ # event end
+ on_event_end = ['%(script)s stop %%t' % {'script': event_relay_path}]
+
+ data['on_event_end'] = '; '.join(on_event_end)
+
+ # additional configs
+ for name, value in ui.iteritems():
+ if not name.startswith('_'):
+ continue
+
+ data['@' + name] = value
+
+ # extra motion options
+ for name in old_config.keys():
+ if name not in _KNOWN_MOTION_OPTIONS and not name.startswith('@'):
+ old_config.pop(name)
+
+ extra_options = ui.get('extra_options', [])
+ for name, value in extra_options:
+ data[name] = value or ''
+
+ old_config.update(data)
+
+ return old_config
+
+
+def motion_camera_dict_to_ui(data):
+ import smbctl
+
+ ui = {
+ # device
+ 'name': data['@name'],
+ 'enabled': data['@enabled'],
+ 'id': data['@id'],
+ 'light_switch_detect': data['lightswitch'] > 0,
+ 'auto_brightness': data['auto_brightness'],
+ 'framerate': int(data['framerate']),
+ 'rotation': int(data['rotate']),
+
+ # file storage
+ 'smb_shares': settings.SMB_SHARES,
+ 'storage_device': data['@storage_device'],
+ 'network_server': data['@network_server'],
+ 'network_share_name': data['@network_share_name'],
+ 'network_username': data['@network_username'],
+ 'network_password': data['@network_password'],
+ 'disk_used': 0,
+ 'disk_total': 0,
+ 'available_disks': diskctl.list_mounted_disks(),
+
+ # text overlay
+ 'text_overlay': False,
+ 'left_text': 'camera-name',
+ 'right_text': 'timestamp',
+ 'custom_left_text': '',
+ 'custom_right_text': '',
+
+ # streaming
+ 'video_streaming': not data['stream_localhost'],
+ 'streaming_framerate': int(data['stream_maxrate']),
+ 'streaming_quality': int(data['stream_quality']),
+ 'streaming_resolution': int(data['@webcam_resolution']),
+ 'streaming_server_resize': data['@webcam_server_resize'],
+ 'streaming_port': int(data['stream_port']),
+ 'streaming_auth_mode': {0: 'disabled', 1: 'basic', 2: 'digest'}.get(data.get('stream_auth_method'), 'disabled'),
+ 'streaming_motion': int(data['stream_motion']),
+
+ # still images
+ 'still_images': False,
+ 'capture_mode': 'motion-triggered',
+ 'image_file_name': '%Y-%m-%d/%H-%M-%S',
+ 'image_quality': 85,
+ 'snapshot_interval': 0,
+ 'preserve_pictures': data['@preserve_pictures'],
+
+ # motion detection
+ 'motion_detection': data['@motion_detection'],
+ 'show_frame_changes': data['text_changes'] or data['locate_motion_mode'],
+ 'auto_noise_detect': data['noise_tune'],
+ 'noise_level': int(int(data['noise_level']) / 2.55),
+ 'event_gap': int(data['event_gap']),
+ 'pre_capture': int(data['pre_capture']),
+ 'post_capture': int(data['post_capture']),
+ 'minimum_motion_frames': int(data['minimum_motion_frames']),
+
+ # motion movies
+ 'motion_movies': data['ffmpeg_output_movies'],
+ 'movie_file_name': data['movie_filename'],
+ 'max_movie_length': data['max_movie_time'],
+ 'preserve_movies': data['@preserve_movies'],
+
+ # motion notifications
+ 'email_notifications_enabled': False,
+ 'web_hook_notifications_enabled': False,
+ 'command_notifications_enabled': False,
+
+ # working schedule
+ 'working_schedule': False,
+ 'working_schedule_type': 'during',
+ 'monday_from': '', 'monday_to': '',
+ 'tuesday_from': '', 'tuesday_to': '',
+ 'wednesday_from': '', 'wednesday_to': '',
+ 'thursday_from': '', 'thursday_to': '',
+ 'friday_from': '', 'friday_to': '',
+ 'saturday_from': '', 'saturday_to': '',
+ 'sunday_from': '', 'sunday_to': ''
+ }
+
+ if utils.net_camera(data):
+ ui['device_url'] = data['netcam_url']
+ ui['proto'] = 'netcam'
+
+ # resolutions
+ if data['netcam_url'].startswith('rtsp'):
+ # motion uses the configured width and height for RTSP cameras
+ resolutions = utils.COMMON_RESOLUTIONS
+ ui['available_resolutions'] = [(str(w) + 'x' + str(h)) for (w, h) in resolutions]
+ ui['resolution'] = str(data['width']) + 'x' + str(data['height'])
+
+ threshold = data['threshold'] * 100.0 / (data['width'] * data['height'])
+
+ 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
+ ui['device_url'] = data['videodevice']
+ ui['proto'] = 'v4l2'
+
+ # resolutions
+ resolutions = v4l2ctl.list_resolutions(data['videodevice'])
+ ui['available_resolutions'] = [(str(w) + 'x' + str(h)) for (w, h) in resolutions]
+ ui['resolution'] = str(data['width']) + 'x' + str(data['height'])
+
+ # the brightness & co. keys in the ui dictionary
+ # 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 data.get('brightness', 0) != 0:
+ ui['brightness'] = brightness
+
+ else:
+ ui['brightness'] = 50
+
+ contrast = v4l2ctl.get_contrast(data['videodevice'])
+ if contrast is not None: # has contrast control
+ if data.get('contrast', 0) != 0:
+ ui['contrast'] = contrast
+
+ else:
+ ui['contrast'] = 50
+
+ saturation = v4l2ctl.get_saturation(data['videodevice'])
+ if saturation is not None: # has saturation control
+ if data.get('saturation', 0) != 0:
+ ui['saturation'] = saturation
+
+ else:
+ ui['saturation'] = 50
+
+ hue = v4l2ctl.get_hue(data['videodevice'])
+ if hue is not None: # has hue control
+ if data.get('hue', 0) != 0:
+ ui['hue'] = hue
+
+ else:
+ ui['hue'] = 50
+
+ threshold = data['threshold'] * 100.0 / (data['width'] * data['height'])
+
+ 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'])
+ ui['root_directory'] = data['target_dir'][len(mount_point):] or '/'
+
+ elif data['@storage_device'].startswith('local-disk'):
+ target_dev = data['@storage_device'][10:].replace('-', '/')
+ mounted_partitions = diskctl.list_mounted_partitions()
+ for partition in mounted_partitions.values():
+ if partition['target'] == target_dev and data['target_dir'].startswith(partition['mount_point']):
+ 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']))
+ ui['root_directory'] = data['target_dir']
+
+ else:
+ ui['root_directory'] = data['target_dir']
+
+ # disk usage
+ usage = utils.get_disk_usage(data['target_dir'])
+ if usage:
+ ui['disk_used'], ui['disk_total'] = usage
+
+ text_left = data['text_left']
+ text_right = data['text_right']
+ if text_left or text_right:
+ ui['text_overlay'] = True
+
+ if text_left == data['@name']:
+ ui['left_text'] = 'camera-name'
+
+ elif text_left == '%Y-%m-%d\\n%T':
+ ui['left_text'] = 'timestamp'
+
+ elif text_left == '':
+ ui['left_text'] = 'disabled'
+
+ else:
+ ui['left_text'] = 'custom-text'
+ ui['custom_left_text'] = text_left
+
+ if text_right == data['@name']:
+ ui['right_text'] = 'camera-name'
+
+ elif text_right == '%Y-%m-%d\\n%T':
+ ui['right_text'] = 'timestamp'
+
+ elif text_right == '':
+ ui['right_text'] = 'disabled'
+
+ else:
+ ui['right_text'] = 'custom-text'
+ ui['custom_right_text'] = text_right
+
+ emulate_motion = data['emulate_motion']
+ output_pictures = data['output_pictures']
+ picture_filename = data['picture_filename']
+ snapshot_interval = data['snapshot_interval']
+ snapshot_filename = data['snapshot_filename']
+
+ if (((emulate_motion or output_pictures) and picture_filename) or
+ (snapshot_interval and snapshot_filename)):
+
+ ui['still_images'] = True
+
+ if emulate_motion:
+ ui['capture_mode'] = 'all-frames'
+ ui['image_file_name'] = picture_filename
+
+ elif snapshot_interval:
+ ui['capture_mode'] = 'interval-snapshots'
+ ui['image_file_name'] = snapshot_filename
+ ui['snapshot_interval'] = snapshot_interval
+
+ elif output_pictures:
+ ui['capture_mode'] = 'motion-triggered'
+ ui['image_file_name'] = picture_filename
+
+ ui['image_quality'] = data['quality']
+
+ ffmpeg_bps = data['ffmpeg_bps']
+ if ffmpeg_bps is not None:
+ if utils.v4l2_camera(data):
+ max_val = data['width'] * data['height'] * data['framerate'] / 3
+
+ else: # net camera
+ max_val = 640 * 480 * data['framerate'] / 3
+
+ max_val = min(max_val, 9999999)
+
+ ui['movie_quality'] = min(100, int(round(ffmpeg_bps * 100.0 / max_val)))
+
+ # working schedule
+ working_schedule = data['@working_schedule']
+ if working_schedule:
+ days = working_schedule.split('|')
+ ui['working_schedule'] = True
+ ui['monday_from'], ui['monday_to'] = days[0].split('-')
+ ui['tuesday_from'], ui['tuesday_to'] = days[1].split('-')
+ ui['wednesday_from'], ui['wednesday_to'] = days[2].split('-')
+ ui['thursday_from'], ui['thursday_to'] = days[3].split('-')
+ ui['friday_from'], ui['friday_to'] = days[4].split('-')
+ ui['saturday_from'], ui['saturday_to'] = days[5].split('-')
+ ui['sunday_from'], ui['sunday_to'] = days[6].split('-')
+ ui['working_schedule_type'] = data['@working_schedule_type']
+
+ # event start
+ on_event_start = data.get('on_event_start') or []
+ if on_event_start:
+ on_event_start = [e.strip() for e in on_event_start.split(';')]
+
+ ui['email_notifications_picture_time_span'] = 0
+ command_notifications = []
+ for e in on_event_start:
+ if e.count('sendmail.py') and e.count('motion_start'):
+ e = shlex.split(e)
+ if len(e) < 10:
+ continue
+
+ ui['email_notifications_enabled'] = True
+ ui['email_notifications_smtp_server'] = e[1]
+ ui['email_notifications_smtp_port'] = e[2]
+ ui['email_notifications_smtp_account'] = e[3]
+ ui['email_notifications_smtp_password'] = e[4]
+ ui['email_notifications_smtp_tls'] = e[5].lower() == 'true'
+ ui['email_notifications_addresses'] = e[6]
+ try:
+ ui['email_notifications_picture_time_span'] = int(e[10])
+
+ except:
+ ui['email_notifications_picture_time_span'] = 0
+
+ elif e.count('webhook.py'):
+ e = shlex.split(e)
+ if len(e) != 3:
+ continue
+
+ ui['web_hook_notifications_enabled'] = True
+ ui['web_hook_notifications_http_method'] = e[1]
+ ui['web_hook_notifications_url'] = e[2]
+
+ elif e.count('eventrelay.py'):
+ continue # ignore internal relay script
+
+ else: # custom command
+ command_notifications.append(e)
+
+ if command_notifications:
+ ui['command_notifications_enabled'] = True
+ ui['command_notifications_exec'] = '; '.join(command_notifications)
+
+ # additional configs
+ for name, value in data.iteritems():
+ if not name.startswith('@_'):
+ continue
+
+ ui[name[1:]] = value
+
+ # extra motion options
+ extra_options = []
+ for name, value in data.iteritems():
+ if name not in _KNOWN_MOTION_OPTIONS and not name.startswith('@'):
+ extra_options.append((name, value))
+
+ ui['extra_options'] = extra_options
+
+ return ui
+
+
+def simple_mjpeg_camera_ui_to_dict(ui, old_config=None):
+ old_config = dict(old_config or {})
+
+ data = {
+ # device
+ '@name': ui['name'],
+ '@enabled': ui['enabled'],
+ }
+
+ # additional configs
+ for name, value in ui.iteritems():
+ if not name.startswith('_'):
+ continue
+
+ data['@' + name] = value
+
+ old_config.update(data)
+
+ return old_config
+
+
+def simple_mjpeg_camera_dict_to_ui(data):
+ ui = {
+ 'name': data['@name'],
+ 'enabled': data['@enabled'],
+ 'id': data['@id'],
+ 'proto': 'mjpeg',
+ 'url': data['@url']
+ }
+
+ # additional configs
+ for name, value in data.iteritems():
+ if not name.startswith('@_'):
+ continue
+
+ ui[name[1:]] = value
+
+ return ui
+
+
+def backup():
+ 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)
+ cmd = 'cd "%s" && tar zc motion.conf thread-*.conf' % settings.CONF_PATH
+ try:
+ content = subprocess.check_output(cmd, shell=True)
+ logging.debug('backup file created (%s bytes)' % len(content))
+
+ return content
+
+ except Exception as e:
+ logging.error('backup failed: %s' % e, exc_info=True)
+
+ return None
+
+ else:
+ logging.debug('config path "%s" appears to be a motion-specific config directory, performing a full backup' % settings.CONF_PATH)
+
+ cmd = 'cd "%s" && tar zc .' % settings.CONF_PATH
+ try:
+ content = subprocess.check_output(cmd, shell=True)
+ logging.debug('backup file created (%s bytes)' % len(content))
+
+ return content
+
+ except Exception as e:
+ logging.error('backup failed: %s' % e, exc_info=True)
+
+ return None
+
+
+def restore(content):
+ global _main_config_cache
+ global _camera_config_cache
+ global _camera_ids_cache
+ global _additional_structure_cache
+
+ logging.info('restoring config from backup file')
+
+ cmd = 'tar zxC "%s" || true' % settings.CONF_PATH
+
+ try:
+ p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ msg = p.communicate(content)[0]
+ if msg:
+ logging.error('failed to restore configuration: %s' % msg)
+ return False
+
+ logging.debug('configuration restored successfully')
+
+ if settings.ENABLE_REBOOT:
+ def later():
+ powerctl.reboot()
+
+ IOLoop.instance().add_timeout(datetime.timedelta(seconds=2), later)
+
+ else:
+ logging.info('invalidating config cache')
+ invalidate()
+
+ return {'reboot': settings.ENABLE_REBOOT}
+
+ except Exception as e:
+ logging.error('failed to restore configuration: %s' % e, exc_info=True)
+
+ return None
+
+
+def is_old_motion():
+ import motionctl
+
+ try:
+ binary, version = motionctl.find_motion() # @UnusedVariable
+
+ if version.startswith('trunkREV'): # e.g. trunkREV599
+ version = int(version[8:])
+ return version <= _LAST_OLD_CONFIG_VERSIONS[0]
+
+ elif version.count('Git'): # e.g. Unofficial-Git-a5b5f13
+ return False # all git versions are assumed to be new
+
+ else: # stable release, should be in the format x.y.z
+ return update.compare_versions(version, _LAST_OLD_CONFIG_VERSIONS[1]) <= 0
+
+ except:
+ return False
+
+
+def motion_rtsp_support():
+ import motionctl
+
+ try:
+ binary, version = motionctl.find_motion() # @UnusedVariable
+
+ if version.startswith('trunkREV'): # e.g. trunkREV599
+ version = int(version[8:])
+ if version > _LAST_OLD_CONFIG_VERSIONS[0]:
+ return ['tcp']
+
+ elif version.count('Git'): # e.g. Unofficial-Git-a5b5f13
+ return ['tcp', 'udp'] # all git versions are assumed to support both transport protocols
+
+ else: # stable release, should be in the format x.y.z
+ return []
+
+ except:
+ return []
+
+
+def invalidate():
+ global _main_config_cache
+ global _camera_config_cache
+ global _camera_ids_cache
+ global _additional_structure_cache
+
+ logging.debug('invalidating config cache')
+ _main_config_cache = None
+ _camera_config_cache = {}
+ _camera_ids_cache = None
+ _additional_structure_cache = {}
+
+
+def _value_to_python(value):
+ value_lower = value.lower()
+ if value_lower == 'off':
+ return False
+
+ elif value_lower == 'on':
+ return True
+
+ try:
+ return int(value)
+
+ except ValueError:
+ try:
+ return float(value)
+
+ except ValueError:
+ return value
+
+
+def _python_to_value(value):
+ if value is True:
+ return 'on'
+
+ elif value is False:
+ return 'off'
+
+ elif isinstance(value, (int, float)):
+ return str(value)
+
+ else:
+ return value
+
+
+def _conf_to_dict(lines, list_names=[], no_convert=[]):
+ data = OrderedDict()
+
+ for line in lines:
+ line = line.strip()
+ if len(line) == 0: # empty line
+ continue
+
+ if line.startswith(';'): # comment line
+ continue
+
+ match = re.match('^\#\s*(\@\w+)\s*(.*)', line)
+ if match:
+ name, value = match.groups()[:2]
+
+ elif line.startswith('#') or line.startswith(';'): # comment line
+ continue
+
+ else:
+ parts = line.split(None, 1)
+ if len(parts) == 1: # empty value
+ parts.append('')
+
+ (name, value) = parts
+ value = value.strip()
+
+ if name not in no_convert:
+ value = _value_to_python(value)
+
+ if name in list_names:
+ data.setdefault(name, []).append(value)
+
+ else:
+ data[name] = value
+
+ return data
+
+
+def _dict_to_conf(lines, data, list_names=[]):
+ conf_lines = []
+ remaining = OrderedDict(data)
+ processed = set()
+
+ # parse existing lines and replace the values
+
+ for line in lines:
+ line = line.strip()
+ if len(line) == 0: # empty line
+ conf_lines.append(line)
+ continue
+
+ if line.startswith(';'): # simple comment line
+ conf_lines.append(line)
+ continue
+
+ match = re.match('^\#\s*(\@\w+)\s*(.*)', line)
+ if match: # @line
+ (name, value) = match.groups()[:2]
+
+ elif line.startswith('#'): # simple comment line
+ conf_lines.append(line)
+ continue
+
+ else:
+ parts = line.split(None, 1)
+ if len(parts) == 2:
+ (name, value) = parts
+
+ else:
+ (name, value) = parts[0], ''
+
+ if name in processed:
+ continue # name already processed
+
+ processed.add(name)
+
+ if name in list_names:
+ new_value = data.get(name)
+ if new_value is not None:
+ for v in new_value:
+ if v is None:
+ continue
+
+ line = name + ' ' + _python_to_value(v)
+ conf_lines.append(line)
+
+ else:
+ line = name + ' ' + value
+ conf_lines.append(line)
+
+ else:
+ new_value = data.get(name)
+ if new_value is not None:
+ value = _python_to_value(new_value)
+ line = name + ' ' + value
+ conf_lines.append(line)
+
+ remaining.pop(name, None)
+
+ # add the remaining config values not covered by existing lines
+
+ if len(remaining) and len(lines):
+ conf_lines.append('') # add a blank line
+
+ for (name, value) in remaining.iteritems():
+ if name.startswith('@_'):
+ continue # ignore additional configs
+
+ if name in list_names:
+ for v in value:
+ if v is None:
+ continue
+
+ line = name + ' ' + _python_to_value(v)
+ conf_lines.append(line)
+
+ else:
+ line = name + ' ' + _python_to_value(value)
+ conf_lines.append(line)
+
+ # build the final config lines
+ conf_lines.sort(key=lambda l: not l.startswith('@'))
+
+ lines = []
+ for i, line in enumerate(conf_lines):
+ # squeeze successive blank lines
+ if i > 0 and len(line.strip()) == 0 and len(conf_lines[i - 1].strip()) == 0:
+ continue
+
+ if line.startswith('@'):
+ line = '# ' + line
+
+ elif i > 0 and conf_lines[i - 1].startswith('@'):
+ lines.append('') # add a blank line between @lines and the rest
+
+ lines.append(line)
+
+ return lines
+
+
+def _set_default_motion(data, old_motion):
+ data.setdefault('@enabled', True)
+
+ data.setdefault('@show_advanced', False)
+ data.setdefault('@admin_username', 'admin')
+ data.setdefault('@admin_password', '')
+ data.setdefault('@normal_username', 'user')
+ data.setdefault('@normal_password', '')
+
+ if old_motion:
+ data.setdefault('control_port', 7999)
+
+ else:
+ data.setdefault('webcontrol_port', 7999)
+
+
+def _set_default_motion_camera(camera_id, data, old_motion=False):
+ data.setdefault('@name', 'Camera' + str(camera_id))
+ data.setdefault('@enabled', False)
+ data.setdefault('@id', camera_id)
+
+ if not utils.net_camera(data):
+ data.setdefault('videodevice', '/dev/video0')
+ data.setdefault('brightness', 0)
+ data.setdefault('contrast', 0)
+ data.setdefault('saturation', 0)
+ data.setdefault('hue', 0)
+ data.setdefault('width', 352)
+ data.setdefault('height', 288)
+
+ data.setdefault('lightswitch', 50)
+ data.setdefault('auto_brightness', False)
+ data.setdefault('framerate', 2)
+ data.setdefault('rotate', 0)
+
+ data.setdefault('@storage_device', 'custom-path')
+ data.setdefault('@network_server', '')
+ data.setdefault('@network_share_name', '')
+ data.setdefault('@network_username', '')
+ data.setdefault('@network_password', '')
+ data.setdefault('target_dir', settings.MEDIA_PATH)
+
+ if old_motion:
+ data.setdefault('webcam_localhost', False)
+ data.setdefault('webcam_port', int('808' + str(camera_id)))
+ data.setdefault('webcam_maxrate', 5)
+ data.setdefault('webcam_quality', 85)
+ data.setdefault('webcam_motion', False)
+
+ else:
+ data.setdefault('stream_localhost', False)
+ data.setdefault('stream_port', int('808' + str(camera_id)))
+ data.setdefault('stream_maxrate', 5)
+ data.setdefault('stream_quality', 85)
+ data.setdefault('stream_motion', False)
+ data.setdefault('stream_auth_method', 0)
+
+ data.setdefault('@webcam_resolution', 100)
+ data.setdefault('@webcam_server_resize', False)
+
+ data.setdefault('text_left', data['@name'])
+ data.setdefault('text_right', '%Y-%m-%d\\n%T')
+ data.setdefault('text_double', False)
+
+ data.setdefault('@motion_detection', True)
+ data.setdefault('text_changes', False)
+ if old_motion:
+ data.setdefault('locate', False)
+
+ else:
+ data.setdefault('locate_motion_mode', False)
+ data.setdefault('locate_motion_style', 'redbox')
+
+ data.setdefault('threshold', 2000)
+ data.setdefault('noise_tune', True)
+ data.setdefault('noise_level', 32)
+ data.setdefault('minimum_motion_frames', 1)
+
+ data.setdefault('pre_capture', 2)
+ data.setdefault('post_capture', 4)
+ data.setdefault('minimum_motion_frames', 1)
+
+ if old_motion:
+ data.setdefault('output_normal', False)
+ data.setdefault('jpeg_filename', '')
+ data.setdefault('output_all', False)
+ data.setdefault('gap', 10)
+
+ else:
+ data.setdefault('output_pictures', False)
+ data.setdefault('picture_filename', '')
+ data.setdefault('emulate_motion', False)
+ data.setdefault('event_gap', 10)
+
+ data.setdefault('snapshot_interval', 0)
+ data.setdefault('snapshot_filename', '')
+ data.setdefault('quality', 85)
+ data.setdefault('@preserve_pictures', 0)
+
+ data.setdefault('ffmpeg_variable_bitrate', 0)
+ data.setdefault('ffmpeg_bps', 44000) # a quality of about 85%
+ data.setdefault('movie_filename', '%Y-%m-%d/%H-%M-%S')
+ if old_motion:
+ data.setdefault('max_mpeg_time', 0)
+ data.setdefault('ffmpeg_cap_new', False)
+
+ else:
+ data.setdefault('max_movie_time', 0)
+ data.setdefault('ffmpeg_output_movies', False)
+ data.setdefault('ffmpeg_video_codec', 'msmpeg4')
+ data.setdefault('@preserve_movies', 0)
+
+ data.setdefault('@working_schedule', '')
+ data.setdefault('@working_schedule_type', 'outside')
+
+ data.setdefault('on_event_start', '')
+ data.setdefault('on_event_end', '')
+
+
+def _set_default_simple_mjpeg_camera(camera_id, data):
+ data.setdefault('@name', 'Camera' + str(camera_id))
+ data.setdefault('@enabled', False)
+ data.setdefault('@id', camera_id)
+
+
+def get_additional_structure(camera, separators=False):
+ if _additional_structure_cache.get((camera, separators)) is None:
+ logging.debug('loading additional config structure for %s, %s separators' % (
+ 'camera' if camera else 'main',
+ 'with' if separators else 'without'))
+
+ # gather sections
+ sections = OrderedDict()
+ for func in _additional_section_funcs:
+ result = func()
+ if not result:
+ continue
+
+ if result.get('reboot') and not settings.ENABLE_REBOOT:
+ continue
+
+ if bool(result.get('camera')) != bool(camera):
+ continue
+
+ result['name'] = func.func_name
+ sections[func.func_name] = result
+
+ logging.debug('additional config section: %s' % result['name'])
+
+ configs = OrderedDict()
+ for func in _additional_config_funcs:
+ result = func()
+ if not result:
+ continue
+
+ if result.get('reboot') and not settings.ENABLE_REBOOT:
+ continue
+
+ if bool(result.get('camera')) != bool(camera):
+ continue
+
+ if result['type'] == 'separator' and not separators:
+ continue
+
+ result['name'] = func.func_name
+ configs[func.func_name] = result
+
+ section = sections.setdefault(result.get('section'), {})
+ section.setdefault('configs', []).append(result)
+
+ logging.debug('additional config item: %s' % result['name'])
+
+ _additional_structure_cache[(camera, separators)] = sections, configs
+
+ return _additional_structure_cache[(camera, separators)]
+
+
+def _get_additional_config(data, camera_id=None):
+ args = [camera_id] if camera_id else []
+
+ (sections, configs) = get_additional_structure(camera=bool(camera_id))
+ get_funcs = set([c.get('get') for c in configs.itervalues() if c.get('get')])
+ get_func_values = collections.OrderedDict((f, f(*args)) for f in get_funcs)
+
+ for name, section in sections.iteritems():
+ if not section.get('get'):
+ continue
+
+ if section.get('get_set_dict'):
+ data['@_' + name] = get_func_values.get(section['get'], {}).get(name)
+
+ else:
+ data['@_' + name] = get_func_values.get(section['get'])
+
+ for name, config in configs.iteritems():
+ if not config.get('get'):
+ continue
+
+ if config.get('get_set_dict'):
+ data['@_' + name] = get_func_values.get(config['get'], {}).get(name)
+
+ else:
+ data['@_' + name] = get_func_values.get(config['get'])
+
+
+def _set_additional_config(data, camera_id=None):
+ args = [camera_id] if camera_id else []
+
+ (sections, configs) = get_additional_structure(camera=bool(camera_id))
+
+ set_func_values = collections.OrderedDict()
+ for name, section in sections.iteritems():
+ if not section.get('set'):
+ continue
+
+ if ('@_' + name) not in data:
+ continue
+
+ if section.get('get_set_dict'):
+ set_func_values.setdefault(section['set'], {})[name] = data['@_' + name]
+
+ else:
+ set_func_values[section['set']] = data['@_' + name]
+
+ for name, config in configs.iteritems():
+ if not config.get('set'):
+ continue
+
+ if ('@_' + name) not in data:
+ continue
+
+ if config.get('get_set_dict'):
+ set_func_values.setdefault(config['set'], {})[name] = data['@_' + name]
+
+ else:
+ set_func_values[config['set']] = data['@_' + name]
+
+ for func, value in set_func_values.iteritems():
+ func(*(args + [value]))
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import os
+import re
+import subprocess
+
+
+def _list_mounts():
+ logging.debug('listing mounts...')
+
+ seen_targets = set()
+
+ mounts = []
+ with open('/proc/mounts', 'r') as f:
+ for line in f:
+ line = line.strip()
+ if not line:
+ continue
+ parts = line.split()
+ if len(parts) < 4:
+ continue
+
+ target = parts[0]
+ mount_point = parts[1]
+ fstype = parts[2]
+ opts = parts[3]
+
+ if not os.access(mount_point, os.W_OK):
+ continue
+
+ if target in seen_targets:
+ continue # probably a bind mount
+
+ seen_targets.add(target)
+
+ if fstype == 'fuseblk':
+ fstype = 'ntfs' # most likely
+
+ logging.debug('found mount "%s" at "%s"' % (target, mount_point))
+
+ mounts.append({
+ 'target': target,
+ 'mount_point': mount_point,
+ 'fstype': fstype,
+ 'opts': opts,
+ })
+
+ return mounts
+
+
+def _list_disks():
+ if os.path.exists('/dev/disk/by-id/'):
+ return _list_disks_dev_by_id()
+
+ else: # fall back to fdisk -l
+ return _list_disks_fdisk()
+
+
+def _list_disks_dev_by_id():
+ logging.debug('listing disks using /dev/disk/by-id/')
+
+ disks_by_dev = {}
+ partitions_by_dev = {}
+
+ for entry in os.listdir('/dev/disk/by-id/'):
+ parts = entry.split('-', 1)
+ if len(parts) < 2:
+ continue
+
+ target = os.path.realpath(os.path.join('/dev/disk/by-id/', entry))
+
+ bus, entry = parts
+ m = re.search('-part(\d+)$', entry)
+ if m:
+ part_no = int(m.group(1))
+ entry = re.sub('-part\d+$', '', entry)
+
+ else:
+ part_no = None
+
+ parts = entry.split('_')
+ if len(parts) < 2:
+ vendor = parts[0]
+ model = ''
+
+ else:
+ vendor, model = parts[:2]
+
+ if part_no is not None:
+ logging.debug('found partition "%s" at "%s" on bus "%s": "%s %s"' % (part_no, target, bus, vendor, model))
+
+ partitions_by_dev[target] = {
+ 'target': target,
+ 'bus': bus,
+ 'vendor': vendor,
+ 'model': model,
+ 'part_no': part_no,
+ 'unmatched': True
+ }
+
+ else:
+ logging.debug('found disk at "%s" on bus "%s": "%s %s"' % (target, bus, vendor, model))
+
+ disks_by_dev[target] = {
+ 'target': target,
+ 'bus': bus,
+ 'vendor': vendor,
+ 'model': model,
+ 'partitions': []
+ }
+
+ # group partitions by disk
+ for dev, partition in partitions_by_dev.items():
+ for disk_dev, disk in disks_by_dev.items():
+ if dev.startswith(disk_dev):
+ disk['partitions'].append(partition)
+ partition.pop('unmatched')
+
+ # add separate partitions that did not match any disk
+ for partition in partitions_by_dev.values():
+ if partition.pop('unmatched', False):
+ disks_by_dev[partition['target']] = partition
+ partition['partitions'] = [dict(partition)]
+
+ # prepare flat list of disks
+ disks = disks_by_dev.values()
+ disks.sort(key=lambda d: d['vendor'])
+
+ for disk in disks:
+ disk['partitions'].sort(key=lambda p: p['part_no'])
+
+ return disks
+
+
+def _list_disks_fdisk():
+ try:
+ output = subprocess.check_output('fdisk -l 2>/dev/null', shell=True)
+
+ except Exception as e:
+ logging.error('failed to list disks using "fdisk -l": %s' % e, exc_info=True)
+
+ return []
+
+ disks = []
+ disk = None
+
+ def add_disk(disk):
+ logging.debug('found disk at "%s" on bus "%s": "%s %s"' %
+ (disk['target'], disk['bus'], disk['vendor'], disk['model']))
+
+ for part in disk['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)
+
+ for line in output.split('\n'):
+ line = line.replace('*', '')
+ line = re.sub('\s+', ' ', line.strip())
+ if not line:
+ continue
+
+ if line.startswith('Disk /dev/'):
+ if disk and disk['partitions']:
+ add_disk(disk)
+
+ parts = line.split()
+
+ disk = {
+ 'target': parts[1].strip(':'),
+ 'bus': '',
+ 'vendor': '',
+ 'model': parts[2] + ' ' + parts[3].strip(','),
+ 'partitions': []
+ }
+
+ elif line.startswith('/dev/') and disk:
+ parts = line.split()
+ part_no = re.findall('\d+$', parts[0])
+ partition = {
+ 'part_no': int(part_no[0]) if part_no else None,
+ 'target': parts[0],
+ 'bus': '',
+ 'vendor': '',
+ 'model': parts[4] + ' ' + ' '.join(parts[6:]),
+ }
+
+ disk['partitions'].append(partition)
+
+ if disk and disk['partitions']:
+ add_disk(disk)
+
+ disks.sort(key=lambda d: d['target'])
+
+ for disk in disks:
+ disk['partitions'].sort(key=lambda p: p['part_no'])
+
+ return disks
+
+
+def list_mounted_disks():
+ mounted_disks = []
+
+ try:
+ disks = _list_disks()
+ mounts_by_target = dict((m['target'], m) for m in _list_mounts())
+
+ for disk in disks:
+ for partition in disk['partitions']:
+ mount = mounts_by_target.get(partition['target'])
+ if mount:
+ partition.update(mount)
+
+ # filter out unmounted partitions
+ disk['partitions'] = [p for p in disk['partitions'] if p.get('mount_point')]
+
+ # filter out unmounted disks
+ mounted_disks = [d for d in disks if d['partitions']]
+
+ except Exception as e:
+ logging.error('failed to list mounted disks: %s' % e, exc_info=True)
+
+ return mounted_disks
+
+
+def list_mounted_partitions():
+ mounted_partitions = {}
+
+ try:
+ disks = _list_disks()
+ mounts_by_target = dict((m['target'], m) for m in _list_mounts())
+
+ for disk in disks:
+ for partition in disk['partitions']:
+ mount = mounts_by_target.get(partition['target'])
+ if mount:
+ partition.update(mount)
+ mounted_partitions[partition['target']] = partition
+
+ except Exception as e:
+ logging.error('failed to list mounted partitions: %s' % e, exc_info=True)
+
+ return mounted_partitions
--- /dev/null
+#!/usr/bin/env python
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import errno
+import json
+import logging
+import os.path
+import sys
+import urllib
+
+sys.path.append(os.path.join(os.path.dirname(sys.argv[0]),'src'))
+
+import settings
+import utils
+
+from motioneye import _configure_settings, _configure_logging
+
+
+_configure_settings()
+_configure_logging(module='eventrelay')
+
+
+def print_usage():
+ print 'Usage: eventrelay.py <event> <thread_id>'
+
+
+def get_admin_credentials():
+ # this shortcut function is a bit faster than using the config module functions
+ config_file_path = os.path.join(settings.CONF_PATH, 'motion.conf')
+
+ logging.debug('reading main config from file %(path)s...' % {'path': config_file_path})
+
+ lines = None
+ try:
+ file = 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 = []
+
+ else:
+ logging.error('could not open main config file %(path)s: %(msg)s' % {
+ 'path': config_file_path, 'msg': unicode(e)})
+
+ raise
+
+ if lines is None:
+ try:
+ lines = [l[:-1] for l in file.readlines()]
+
+ except Exception as e:
+ logging.error('could not read main config file %(path)s: %(msg)s' % {
+ 'path': config_file_path, 'msg': unicode(e)})
+
+ raise
+
+ finally:
+ file.close()
+
+ admin_username = 'admin'
+ admin_password = ''
+ for line in lines:
+ line = line.strip()
+ if not line.startswith('#'):
+ continue
+
+ line = line[1:].strip()
+ if line.startswith('@admin_username'):
+ parts = line.split(' ', 1)
+ admin_username = parts[1] if len(parts) > 1 else ''
+
+ continue
+
+ if line.startswith('@admin_password'):
+ parts = line.split(' ', 1)
+ admin_password = parts[1] if len(parts) > 1 else ''
+
+ continue
+
+ return admin_username, admin_password
+
+
+# def compute_signature(method, uri, body, key):
+# parts = list(urlparse.urlsplit(uri))
+# query = [q for q in urlparse.parse_qsl(parts[3]) if (q[0] != 'signature')]
+# query.sort(key=lambda q: q[0])
+# query = urllib.urlencode(query)
+# parts[0] = parts[1] = ''
+# parts[3] = query
+# uri = urlparse.urlunsplit(parts)
+#
+# return hashlib.sha1('%s:%s:%s:%s' % (method, uri, body or '', key)).hexdigest().lower()
+
+
+if __name__ == '__main__':
+ if len(sys.argv) < 3:
+ print_usage()
+ sys.exit(-1)
+
+ event = sys.argv[1]
+ thread_id = sys.argv[2]
+
+ logging.debug('hello!')
+ logging.debug('event = %s' % event)
+ logging.debug('thread_id = %s' % thread_id)
+
+ admin_username, admin_password = get_admin_credentials()
+
+ uri = '/_relay_event/?event=%(event)s&thread_id=%(thread_id)s&_username=%(username)s' % {
+ 'username': admin_username,
+ 'thread_id': thread_id,
+ 'event': event}
+
+ signature = utils.compute_signature('POST', uri, '', admin_password)
+
+ url = 'http://127.0.0.1:%(port)s' + uri + '&_signature=' + signature
+ url = url % {'port': settings.PORT}
+
+ try:
+ response = urllib.urlopen(url, data='')
+ response = json.load(response)
+ if response.get('error'):
+ raise Exception(response['error'])
+
+ logging.debug('event successfully relayed')
+
+ except Exception as e:
+ logging.error('failed to relay event: %s' % e)
+
+ logging.debug('bye!')
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+import json
+import logging
+import os
+import re
+import socket
+import subprocess
+
+from tornado.web import RequestHandler, HTTPError, asynchronous
+from tornado.ioloop import IOLoop
+
+import config
+import mediafiles
+import motionctl
+import powerctl
+import remote
+import settings
+import smbctl
+import template
+import update
+import utils
+import v4l2ctl
+
+
+class BaseHandler(RequestHandler):
+ def get_data(self):
+ keys = self.request.arguments.keys()
+ data = dict([(key, self.get_argument(key)) for key in keys])
+
+ for key in self.request.files:
+ files = self.request.files[key]
+ if len(files) > 1:
+ data[key] = files
+
+ elif len(files) > 0:
+ data[key] = files[0]
+
+ else:
+ continue
+
+ return data
+
+ def render(self, template_name, content_type='text/html', **context):
+ self.set_header('Content-Type', content_type)
+
+ content = template.render(template_name, **context)
+ self.finish(content)
+
+ def finish_json(self, data={}):
+ self.set_header('Content-Type', 'application/json')
+ self.finish(json.dumps(data))
+
+ def get_current_user(self):
+ main_config = config.get_main()
+
+ username = self.get_argument('_username', None)
+ 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, self.request.body, main_config.get('@admin_password'))):
+
+ return 'admin'
+
+ 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, self.request.body, main_config.get('@normal_password'))):
+
+ return 'normal'
+
+ elif username and username != '_' and login:
+ logging.error('authentication failed for user %(user)s' % {'user': username})
+
+ return None
+
+ def _handle_request_exception(self, exception):
+ try:
+ 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)})
+
+ else:
+ logging.error(str(exception), exc_info=True)
+ self.set_status(500)
+ self.finish_json({'error': 'internal server error'})
+
+ except RuntimeError:
+ pass # nevermind
+
+ @staticmethod
+ def auth(admin=False, prompt=True):
+ def decorator(func):
+ def wrapper(self, *args, **kwargs):
+ _admin = self.get_argument('_admin', None) == 'true'
+
+ user = self.current_user
+ if (user is None) or (user != 'admin' and (admin or _admin)):
+ self.set_header('Content-Type', 'application/json')
+
+ return self.finish_json({'error': 'unauthorized', 'prompt': prompt})
+
+ return func(self, *args, **kwargs)
+
+ return wrapper
+
+ return decorator
+
+ def get(self, *args, **kwargs):
+ raise HTTPError(400, 'method not allowed')
+
+ def post(self, *args, **kwargs):
+ raise HTTPError(400, 'method not allowed')
+
+
+class NotFoundHandler(BaseHandler):
+ def get(self):
+ raise HTTPError(404, 'not found')
+
+ def post(self):
+ raise HTTPError(404, 'not found')
+
+
+class MainHandler(BaseHandler):
+ def get(self):
+ import motioneye
+
+ # additional config
+ main_sections = config.get_additional_structure(camera=False, separators=True)[0]
+ camera_sections = config.get_additional_structure(camera=True, separators=True)[0]
+
+ self.render('main.html',
+ frame=False,
+ version=motioneye.VERSION,
+ enable_update=False,
+ enable_reboot=settings.ENABLE_REBOOT,
+ add_remove_cameras=settings.ADD_REMOVE_CAMERAS,
+ main_sections=main_sections,
+ camera_sections=camera_sections,
+ hostname=socket.gethostname(),
+ title=self.get_argument('title', None),
+ admin_username=config.get_main().get('@admin_username'),
+ old_motion=config.is_old_motion())
+
+
+class ConfigHandler(BaseHandler):
+ @asynchronous
+ def get(self, camera_id=None, op=None):
+ if camera_id is not None:
+ camera_id = int(camera_id)
+
+ if op == 'get':
+ self.get_config(camera_id)
+
+ elif op == 'list':
+ self.list()
+
+ elif op == 'backup':
+ self.backup()
+
+ else:
+ raise HTTPError(400, 'unknown operation')
+
+ @asynchronous
+ def post(self, camera_id=None, op=None):
+ if camera_id is not None:
+ camera_id = int(camera_id)
+
+ if op == 'set':
+ self.set_config(camera_id)
+
+ elif op == 'set_preview':
+ self.set_preview(camera_id)
+
+ elif op == 'add':
+ self.add_camera()
+
+ elif op == 'rem':
+ self.rem_camera(camera_id)
+
+ elif op == 'restore':
+ self.restore()
+
+ else:
+ raise HTTPError(400, 'unknown operation')
+
+ @BaseHandler.auth(admin=True)
+ def get_config(self, camera_id):
+ if camera_id:
+ logging.debug('getting config for camera %(id)s' % {'id': camera_id})
+
+ if camera_id not in config.get_camera_ids():
+ raise HTTPError(404, 'no such camera')
+
+ local_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(local_config):
+ ui_config = config.motion_camera_dict_to_ui(local_config)
+
+ self.finish_json(ui_config)
+
+ elif utils.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}})
+
+ for key, value in local_config.items():
+ remote_ui_config[key.replace('@', '')] = value
+
+ # replace the real device URI with the remote camera URL
+ remote_ui_config['device_url'] = remote.pretty_camera_url(local_config)
+ self.finish_json(remote_ui_config)
+
+ remote.get_config(local_config, on_response)
+
+ else: # assuming simple mjpeg camera
+ ui_config = config.simple_mjpeg_camera_dict_to_ui(local_config)
+
+ self.finish_json(ui_config)
+
+ else:
+ logging.debug('getting main config')
+
+ ui_config = config.main_dict_to_ui(config.get_main())
+ self.finish_json(ui_config)
+
+ @BaseHandler.auth(admin=True)
+ def set_config(self, camera_id):
+ try:
+ ui_config = json.loads(self.request.body)
+
+ except Exception as e:
+ logging.error('could not decode json: %(msg)s' % {'msg': unicode(e)})
+
+ raise
+
+ camera_ids = config.get_camera_ids()
+
+ def set_camera_config(camera_id, ui_config, on_finish):
+ logging.debug('setting config for camera %(id)s...' % {'id': camera_id})
+
+ if camera_id not in camera_ids:
+ raise HTTPError(404, 'no such camera')
+
+ local_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(local_config):
+ local_config = config.motion_camera_ui_to_dict(ui_config, local_config)
+
+ config.set_camera(camera_id, local_config)
+
+ on_finish(None, True) # (no error, motion needs restart)
+
+ elif utils.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)
+
+ ui_config['enabled'] = True # never disable the camera remotely
+ remote.set_config(local_config, ui_config, on_finish_wrapper)
+
+ else:
+ # when the ui config supplied has only the enabled state
+ # and no useful fields (such as "name"),
+ # the camera was probably disabled due to errors
+ on_finish(None, False)
+
+ 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)
+
+ 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', ''))
+
+ 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', ''))
+
+ additional_configs = config.get_additional_structure(camera=False)[1]
+ reboot_config_names = [('@_' + c['name']) for c in additional_configs.values() if c.get('reboot')]
+ reboot_config_names.append('@admin_password')
+ reboot = bool([k for k in reboot_config_names if old_main_config.get(k) != main_config.get(k)])
+
+ config.set_main(main_config)
+
+ reload = False
+ restart = False
+
+ if admin_credentials != old_admin_credentials:
+ logging.debug('admin credentials changed, reload needed')
+
+ reload = True
+
+ if normal_credentials != old_normal_credentials:
+ logging.debug('surveillance credentials changed, all camera configs must be updated')
+
+ # reconfigure all local cameras to update the stream authentication options
+ for camera_id in config.get_camera_ids():
+ local_config = config.get_camera(camera_id)
+ if not utils.local_motion_camera(local_config):
+ continue
+
+ ui_config = config.motion_camera_dict_to_ui(local_config)
+ local_config = config.motion_camera_ui_to_dict(ui_config, local_config)
+
+ config.set_camera(camera_id, local_config)
+
+ restart = True
+
+ if reboot and settings.ENABLE_REBOOT:
+ logging.debug('system settings changed, reboot needed')
+
+ else:
+ reboot = False
+
+ 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
+ restart = [False] # indicates that the local motion instance was modified and needs to be restarted
+ error = [None]
+
+ def finish():
+ if reboot[0]:
+ if settings.ENABLE_REBOOT:
+ def call_reboot():
+ powerctl.reboot()
+
+ ioloop = IOLoop.instance()
+ ioloop.add_timeout(datetime.timedelta(seconds=2), call_reboot)
+ return self.finish({'reload': False, 'reboot': True, 'error': None})
+
+ else:
+ reboot[0] = False
+
+ if restart[0]:
+ logging.debug('motion needs to be restarted')
+
+ motionctl.stop()
+
+ if settings.SMB_SHARES:
+ logging.debug('updating SMB mounts')
+ stop, start = smbctl.update_mounts() # @UnusedVariable
+
+ if start:
+ motionctl.start()
+
+ else:
+ motionctl.start()
+
+ self.finish({'reload': reload, 'reboot': reboot[0], 'error': error[0]})
+
+ if camera_id is not None:
+ if camera_id == 0: # multiple camera configs
+ if len(ui_config) > 1:
+ logging.debug('setting multiple configs')
+
+ elif len(ui_config) == 0:
+ logging.warn('no configuration to set')
+
+ 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
+ finish()
+
+ # make sure main config is handled first
+ items = ui_config.items()
+ items.sort(key=lambda (key, cfg): key != 'main')
+
+ for key, cfg in items:
+ if key == 'main':
+ result = set_main_config(cfg)
+ reload = result['reload'] or reload
+ reboot[0] = result['reboot'] or reboot[0]
+ restart[0] = result['restart'] or restart[0]
+ check_finished(None, reload)
+
+ else:
+ set_camera_config(int(key), cfg, check_finished)
+
+ else: # single camera config
+ def on_finish(e, r):
+ error[0] = e
+ restart[0] = r
+ finish()
+
+ set_camera_config(camera_id, ui_config, on_finish)
+
+ else: # main config
+ result = set_main_config(ui_config)
+ reload = result['reload']
+ reboot[0] = result['reboot']
+ restart[0] = result['restart']
+
+ @BaseHandler.auth(admin=True)
+ def set_preview(self, camera_id):
+ try:
+ controls = json.loads(self.request.body)
+
+ except Exception as e:
+ logging.error('could not decode json: %(msg)s' % {'msg': unicode(e)})
+
+ raise
+
+ camera_config = config.get_camera(camera_id)
+ if utils.v4l2_camera(camera_config):
+ device = camera_config['videodevice']
+
+ if 'brightness' in controls:
+ value = int(controls['brightness'])
+ logging.debug('setting brightness to %(value)s...' % {'value': value})
+
+ v4l2ctl.set_brightness(device, value)
+
+ if 'contrast' in controls:
+ value = int(controls['contrast'])
+ logging.debug('setting contrast to %(value)s...' % {'value': value})
+
+ v4l2ctl.set_contrast(device, value)
+
+ if 'saturation' in controls:
+ value = int(controls['saturation'])
+ logging.debug('setting saturation to %(value)s...' % {'value': value})
+
+ v4l2ctl.set_saturation(device, value)
+
+ if 'hue' in controls:
+ value = int(controls['hue'])
+ logging.debug('setting hue to %(value)s...' % {'value': value})
+
+ v4l2ctl.set_hue(device, value)
+
+ self.finish_json({})
+
+ elif utils.remote_camera(camera_config):
+ def on_response(error=None):
+ if error:
+ self.finish_json({'error': error})
+
+ else:
+ self.finish_json()
+
+ remote.set_preview(camera_config, controls, on_response)
+
+ else: # not supported
+ self.finish_json({'error': True})
+
+ @BaseHandler.auth()
+ def list(self):
+ logging.debug('listing cameras')
+
+ proto = self.get_data().get('proto')
+ if proto == 'motioneye': # remote listing
+ def on_response(cameras=None, error=None):
+ if error:
+ self.finish_json({'error': error})
+
+ else:
+ cameras = [c for c in cameras if c.get('enabled')]
+ self.finish_json({'cameras': cameras})
+
+ remote.list(self.get_data(), on_response)
+
+ elif proto == 'netcam':
+ scheme = self.get_data().get('scheme', 'http')
+
+ def on_response(cameras=None, error=None):
+ if error:
+ self.finish_json({'error': error})
+
+ else:
+ self.finish_json({'cameras': cameras})
+
+ if scheme in ['http', 'https']:
+ utils.test_mjpeg_url(self.get_data(), auth_modes=['basic'], allow_jpeg=True, callback=on_response)
+
+ elif config.motion_rtsp_support() and scheme == 'rtsp':
+ utils.test_rtsp_url(self.get_data(), callback=on_response)
+
+ else:
+ on_response(error='protocol %s not supported' % scheme)
+
+ elif proto == 'mjpeg':
+ def on_response(cameras=None, error=None):
+ if error:
+ self.finish_json({'error': error})
+
+ else:
+ self.finish_json({'cameras': cameras})
+
+ utils.test_mjpeg_url(self.get_data(), auth_modes=['basic', 'digest'], allow_jpeg=False, callback=on_response)
+
+ elif proto == 'v4l2':
+ configured_devices = set()
+ for camera_id in config.get_camera_ids():
+ data = config.get_camera(camera_id)
+ if utils.v4l2_camera(data):
+ configured_devices.add(data['videodevice'])
+
+ cameras = [{'id': d[1], 'name': d[2]} for d in v4l2ctl.list_devices()
+ if (d[0] not in configured_devices) and (d[1] not in configured_devices)]
+
+ self.finish_json({'cameras': cameras})
+
+ else: # assuming local motionEye camera listing
+ cameras = []
+ camera_ids = config.get_camera_ids()
+ if not config.get_main().get('@enabled'):
+ camera_ids = []
+
+ length = [len(camera_ids)]
+ def check_finished():
+ if len(cameras) == length[0]:
+ cameras.sort(key=lambda c: c['id'])
+ self.finish_json({'cameras': cameras})
+
+ def on_response_builder(camera_id, local_config):
+ def on_response(remote_ui_config=None, error=None):
+ if error:
+ cameras.append({
+ 'id': camera_id,
+ 'name': '<' + remote.pretty_camera_url(local_config) + '>',
+ 'enabled': False,
+ 'streaming_framerate': 1,
+ 'framerate': 1
+ })
+
+ else:
+ remote_ui_config['id'] = camera_id
+
+ if not remote_ui_config['enabled'] and local_config['@enabled']:
+ # if a remote camera is disabled, make sure it's disabled locally as well
+ local_config['@enabled'] = False
+ config.set_camera(camera_id, local_config)
+
+ elif remote_ui_config['enabled'] and not local_config['@enabled']:
+ # if a remote camera is locally disabled, make sure the remote config says the same thing
+ remote_ui_config['enabled'] = False
+
+ for key, value in local_config.items():
+ remote_ui_config[key.replace('@', '')] = value
+
+ cameras.append(remote_ui_config)
+
+ check_finished()
+
+ return on_response
+
+ for camera_id in camera_ids:
+ local_config = config.get_camera(camera_id)
+ if local_config is None:
+ continue
+
+ if utils.local_motion_camera(local_config):
+ ui_config = config.motion_camera_dict_to_ui(local_config)
+ cameras.append(ui_config)
+ check_finished()
+
+ elif utils.remote_camera(local_config):
+ 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
+ on_response_builder(camera_id, local_config)(error=True)
+
+ else: # assuming simple mjpeg camera
+ ui_config = config.simple_mjpeg_camera_dict_to_ui(local_config)
+ cameras.append(ui_config)
+ check_finished()
+
+ if length[0] == 0:
+ self.finish_json({'cameras': []})
+
+ @BaseHandler.auth(admin=True)
+ def add_camera(self):
+ logging.debug('adding new camera')
+
+ try:
+ device_details = json.loads(self.request.body)
+
+ except Exception as e:
+ logging.error('could not decode json: %(msg)s' % {'msg': unicode(e)})
+
+ raise
+
+ camera_config = config.add_camera(device_details)
+
+ if utils.local_motion_camera(camera_config):
+ motionctl.stop()
+
+ if settings.SMB_SHARES:
+ stop, start = smbctl.update_mounts() # @UnusedVariable
+
+ if start:
+ motionctl.start()
+
+ else:
+ motionctl.start()
+
+ ui_config = config.motion_camera_dict_to_ui(camera_config)
+
+ self.finish_json(ui_config)
+
+ elif utils.remote_camera(camera_config):
+ def on_response(remote_ui_config=None, error=None):
+ if error:
+ return self.finish_json({'error': error})
+
+ for key, value in camera_config.items():
+ remote_ui_config[key.replace('@', '')] = value
+
+ self.finish_json(remote_ui_config)
+
+ remote.get_config(camera_config, on_response)
+
+ else: # assuming simple mjpeg camera
+ ui_config = config.simple_mjpeg_camera_dict_to_ui(camera_config)
+
+ self.finish_json(ui_config)
+
+ @BaseHandler.auth(admin=True)
+ def rem_camera(self, camera_id):
+ logging.debug('removing camera %(id)s' % {'id': camera_id})
+
+ local = utils.local_motion_camera(config.get_camera(camera_id))
+ config.rem_camera(camera_id)
+
+ if local:
+ motionctl.stop()
+ motionctl.start()
+
+ self.finish_json()
+
+ @BaseHandler.auth(admin=True)
+ def backup(self):
+ content = config.backup()
+
+ filename = 'motioneye-config.tar.gz'
+ self.set_header('Content-Type', 'application/x-compressed')
+ self.set_header('Content-Disposition', 'attachment; filename=' + filename + ';')
+
+ self.finish(content)
+
+ @BaseHandler.auth(admin=True)
+ def restore(self):
+ try:
+ content = self.request.files['files'][0]['body']
+
+ except KeyError:
+ raise HTTPError(400, 'file attachment required')
+
+ result = config.restore(content)
+ if result:
+ self.finish_json({'ok': True, 'reboot': result['reboot']})
+
+ else:
+ self.finish_json({'ok': False})
+
+
+class PictureHandler(BaseHandler):
+ @asynchronous
+ def get(self, camera_id, op, filename=None, group=None):
+ if camera_id is not None:
+ camera_id = int(camera_id)
+ if camera_id not in config.get_camera_ids():
+ raise HTTPError(404, 'no such camera')
+
+ if op == 'current':
+ self.current(camera_id)
+
+ elif op == 'list':
+ self.list(camera_id)
+
+ elif op == 'frame':
+ self.frame(camera_id)
+
+ elif op == 'download':
+ self.download(camera_id, filename)
+
+ elif op == 'preview':
+ self.preview(camera_id, filename)
+
+ elif op == 'zipped':
+ self.zipped(camera_id, group)
+
+ elif op == 'timelapse':
+ self.timelapse(camera_id, group)
+
+ else:
+ raise HTTPError(400, 'unknown operation')
+
+ @asynchronous
+ def post(self, camera_id, op, filename=None, group=None):
+ if camera_id is not None:
+ camera_id = int(camera_id)
+ if camera_id not in config.get_camera_ids():
+ raise HTTPError(404, 'no such camera')
+
+ if op == 'delete':
+ self.delete(camera_id, filename)
+
+ elif op == 'delete_all':
+ self.delete_all(camera_id, group)
+
+ else:
+ raise HTTPError(400, 'unknown operation')
+
+ @BaseHandler.auth(prompt=False)
+ def current(self, camera_id):
+ self.set_header('Content-Type', 'image/jpeg')
+
+ width = self.get_argument('width', None)
+ height = self.get_argument('height', None)
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(camera_config):
+ picture = mediafiles.get_current_picture(camera_config,
+ width=width,
+ height=height)
+
+ self.set_cookie('motion_detected_' + str(camera_id), str(motionctl.is_motion_detected(camera_id)).lower())
+ self.try_finish(picture)
+
+ elif utils.remote_camera(camera_config):
+ def on_response(motion_detected=False, picture=None, error=None):
+ self.set_cookie('motion_detected_' + str(camera_id), str(motion_detected).lower())
+ self.try_finish(picture)
+
+ remote.get_current_picture(camera_config, width=width, height=height, callback=on_response)
+
+ 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})
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(camera_config):
+ def on_media_list(media_list):
+ if media_list is None:
+ return self.finish_json({'error': 'Failed to get pictures list.'})
+
+ self.finish_json({
+ 'mediaList': media_list,
+ 'cameraName': camera_config['@name']
+ })
+
+ mediafiles.list_media(camera_config, media_type='picture',
+ callback=on_media_list, prefix=self.get_argument('prefix', None))
+
+ elif utils.remote_camera(camera_config):
+ def on_response(remote_list=None, error=None):
+ if error:
+ return self.finish_json({'error': 'Failed to get picture list for %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
+
+ self.finish_json(remote_list)
+
+ remote.list_media(camera_config, media_type='picture', prefix=self.get_argument('prefix', None), callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ def frame(self, camera_id):
+ camera_config = config.get_camera(camera_id)
+
+ if utils.local_motion_camera(camera_config) or utils.simple_mjpeg_camera(camera_config) or self.get_argument('title', None) is not None:
+ self.render('main.html',
+ frame=True,
+ camera_id=camera_id,
+ camera_config=camera_config,
+ title=self.get_argument('title', camera_config.get('@name', '')),
+ admin_username=config.get_main().get('@admin_username'))
+
+ elif utils.remote_camera(camera_config):
+ def on_response(remote_ui_config=None, error=None):
+ if error:
+ return self.render('main.html',
+ frame=True,
+ camera_id=camera_id,
+ camera_config=camera_config,
+ title=self.get_argument('title', ''))
+
+ # issue a fake motion_camera_ui_to_dict() call to transform
+ # the remote UI values into motion config directives
+ remote_config = config.motion_camera_ui_to_dict(remote_ui_config)
+
+ self.render('main.html',
+ frame=True,
+ camera_id=camera_id,
+ camera_config=remote_config,
+ title=self.get_argument('title', remote_config['@name']),
+ admin_username=config.get_main().get('@admin_username'))
+
+ remote.get_config(camera_config, on_response)
+
+ @BaseHandler.auth()
+ def download(self, camera_id, filename):
+ logging.debug('downloading picture %(filename)s of camera %(id)s' % {
+ 'filename': filename, 'id': camera_id})
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(camera_config):
+ content = mediafiles.get_media_content(camera_config, filename, 'picture')
+
+ pretty_filename = camera_config['@name'] + '_' + os.path.basename(filename)
+ self.set_header('Content-Type', 'image/jpeg')
+ self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + ';')
+
+ self.finish(content)
+
+ elif utils.remote_camera(camera_config):
+ def on_response(response=None, error=None):
+ if error:
+ 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
+ self.set_header('Content-Type', 'image/jpeg')
+ self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + ';')
+
+ self.finish(response)
+
+ remote.get_media_content(camera_config, filename=filename, media_type='picture', callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ @BaseHandler.auth()
+ def preview(self, camera_id, filename):
+ logging.debug('previewing picture %(filename)s of camera %(id)s' % {
+ 'filename': filename, 'id': camera_id})
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(camera_config):
+ content = mediafiles.get_media_preview(camera_config, filename, 'picture',
+ width=self.get_argument('width', None),
+ height=self.get_argument('height', None))
+
+ if content:
+ self.set_header('Content-Type', 'image/jpeg')
+
+ else:
+ self.set_header('Content-Type', 'image/svg+xml')
+ content = open(os.path.join(settings.STATIC_PATH, 'img', 'no-preview.svg')).read()
+
+ self.finish(content)
+
+ elif utils.remote_camera(camera_config):
+ def on_response(content=None, error=None):
+ if content:
+ self.set_header('Content-Type', 'image/jpeg')
+
+ else:
+ self.set_header('Content-Type', 'image/svg+xml')
+ content = open(os.path.join(settings.STATIC_PATH, 'img', 'no-preview.svg')).read()
+
+ self.finish(content)
+
+ remote.get_media_preview(camera_config, filename=filename, media_type='picture',
+ width=self.get_argument('width', None),
+ height=self.get_argument('height', None),
+ callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ @BaseHandler.auth(admin=True)
+ def delete(self, camera_id, filename):
+ logging.debug('deleting picture %(filename)s of camera %(id)s' % {
+ 'filename': filename, 'id': camera_id})
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(camera_config):
+ try:
+ mediafiles.del_media_content(camera_config, filename, 'picture')
+ self.finish_json()
+
+ except Exception as e:
+ self.finish_json({'error': unicode(e)})
+
+ elif utils.remote_camera(camera_config):
+ def on_response(response=None, error=None):
+ if error:
+ return self.finish_json({'error': 'Failed to delete picture from %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
+
+ self.finish_json()
+
+ remote.del_media_content(camera_config, filename=filename, media_type='picture', callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ @BaseHandler.auth()
+ def zipped(self, camera_id, group):
+ key = self.get_argument('key', None)
+ camera_config = config.get_camera(camera_id)
+
+ if key:
+ logging.debug('serving zip file for group %(group)s of camera %(id)s with key %(key)s' % {
+ 'group': group, 'id': camera_id, 'key': key})
+
+ if utils.local_motion_camera(camera_config):
+ data = mediafiles.get_prepared_cache(key)
+ if not data:
+ logging.error('prepared cache data for key "%s" does not exist' % key)
+
+ raise HTTPError(404, 'no such key')
+
+ pretty_filename = camera_config['@name'] + '_' + group
+ pretty_filename = re.sub('[^a-zA-Z0-9]', '_', pretty_filename)
+
+ self.set_header('Content-Type', 'application/zip')
+ self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + '.zip;')
+ self.finish(data)
+
+ elif utils.remote_camera(camera_config):
+ def on_response(response=None, error=None):
+ if error:
+ return self.finish_json({'error': 'Failed to download zip file from %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
+
+ self.set_header('Content-Type', response['content_type'])
+ 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)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ else: # prepare
+ logging.debug('preparing zip file for group %(group)s of camera %(id)s' % {
+ 'group': group, 'id': camera_id})
+
+ if utils.local_motion_camera(camera_config):
+ def on_zip(data):
+ if data is None:
+ return self.finish_json({'error': 'Failed to create zip file.'})
+
+ key = mediafiles.set_prepared_cache(data)
+ logging.debug('prepared zip file for group %(group)s of camera %(id)s with key %(key)s' % {
+ 'group': group, 'id': camera_id, 'key': key})
+ self.finish_json({'key': key})
+
+ mediafiles.get_zipped_content(camera_config, media_type='picture', group=group, callback=on_zip)
+
+ elif utils.remote_camera(camera_config):
+ def on_response(response=None, error=None):
+ if error:
+ return self.finish_json({'error': 'Failed to make zip file at %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
+
+ self.finish_json({'key': response['key']})
+
+ remote.make_zipped_content(camera_config, media_type='picture', group=group, callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ @BaseHandler.auth()
+ def timelapse(self, camera_id, group):
+ key = self.get_argument('key', None)
+ check = self.get_argument('check', False)
+ camera_config = config.get_camera(camera_id)
+
+ if key: # download
+ logging.debug('serving timelapse movie for group %(group)s of camera %(id)s with key %(key)s' % {
+ 'group': group, 'id': camera_id, 'key': key})
+
+ if utils.local_motion_camera(camera_config):
+ data = mediafiles.get_prepared_cache(key)
+ if data is None:
+ logging.error('prepared cache data for key "%s" does not exist' % key)
+
+ raise HTTPError(404, 'no such key')
+
+ pretty_filename = camera_config['@name'] + '_' + group
+ pretty_filename = re.sub('[^a-zA-Z0-9]', '_', pretty_filename)
+
+ self.set_header('Content-Type', 'video/x-msvideo')
+ self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + '.avi;')
+ self.finish(data)
+
+ elif utils.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}})
+
+ self.set_header('Content-Type', response['content_type'])
+ self.set_header('Content-Disposition', response['content_disposition'])
+ self.finish(response['data'])
+
+ remote.get_timelapse_movie(camera_config, key, group=group, callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ elif check:
+ logging.debug('checking timelapse movie status for group %(group)s of camera %(id)s' % {
+ 'group': group, 'id': camera_id})
+
+ if utils.local_motion_camera(camera_config):
+ status = mediafiles.check_timelapse_movie()
+ if status['progress'] == -1 and status['data']:
+ key = mediafiles.set_prepared_cache(status['data'])
+ logging.debug('prepared timelapse movie for group %(group)s of camera %(id)s with key %(key)s' % {
+ 'group': group, 'id': camera_id, 'key': key})
+ self.finish_json({'key': key, 'progress': -1})
+
+ else:
+ self.finish_json(status)
+
+ elif utils.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}})
+
+ if response['progress'] == -1 and response.get('key'):
+ self.finish_json({'key': response['key'], 'progress': -1})
+
+ else:
+ self.finish_json(response)
+
+ remote.check_timelapse_movie(camera_config, group=group, callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ 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, 'id': camera_id, 'framerate': framerate, 'int': interval})
+
+ if utils.local_motion_camera(camera_config):
+ status = mediafiles.check_timelapse_movie()
+ if status['progress'] != -1:
+ self.finish_json({'progress': status['progress']}) # timelapse already active
+
+ else:
+ mediafiles.make_timelapse_movie(camera_config, framerate, interval, group=group)
+ self.finish_json({'progress': -1})
+
+ elif utils.remote_camera(camera_config):
+ def on_status(response=None, error=None):
+ if error:
+ return self.finish_json({'error': 'Failed to make timelapse movie at %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
+
+ if response['progress'] != -1:
+ return self.finish_json({'progress': response['progress']}) # timelapse already active
+
+ def on_make(response=None, error=None):
+ if error:
+ return self.finish_json({'error': 'Failed to make timelapse movie at %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
+
+ self.finish_json({'progress': -1})
+
+ remote.make_timelapse_movie(camera_config, framerate, interval, group=group, callback=on_make)
+
+ remote.check_timelapse_movie(camera_config, group=group, callback=on_status)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ @BaseHandler.auth(admin=True)
+ def delete_all(self, camera_id, group):
+ logging.debug('deleting picture group %(group)s of camera %(id)s' % {
+ 'group': group, 'id': camera_id})
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(camera_config):
+ try:
+ mediafiles.del_media_group(camera_config, group, 'picture')
+ self.finish_json()
+
+ except Exception as e:
+ self.finish_json({'error': unicode(e)})
+
+ elif utils.remote_camera(camera_config):
+ def on_response(response=None, error=None):
+ if error:
+ return self.finish_json({'error': 'Failed to delete picture group from %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
+
+ self.finish_json()
+
+ remote.del_media_group(camera_config, group=group, media_type='picture', callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ def try_finish(self, content):
+ try:
+ self.finish(content)
+
+ except IOError as e:
+ logging.warning('could not write response: %(msg)s' % {'msg': unicode(e)})
+
+
+class MovieHandler(BaseHandler):
+ @asynchronous
+ def get(self, camera_id, op, filename=None):
+ if camera_id is not None:
+ camera_id = int(camera_id)
+ if camera_id not in config.get_camera_ids():
+ raise HTTPError(404, 'no such camera')
+
+ if op == 'list':
+ self.list(camera_id)
+
+ elif op == 'download':
+ self.download(camera_id, filename)
+
+ elif op == 'preview':
+ self.preview(camera_id, filename)
+
+ else:
+ raise HTTPError(400, 'unknown operation')
+
+ @asynchronous
+ def post(self, camera_id, op, filename=None, group=None):
+ if camera_id is not None:
+ camera_id = int(camera_id)
+ if camera_id not in config.get_camera_ids():
+ raise HTTPError(404, 'no such camera')
+
+ if op == 'delete':
+ self.delete(camera_id, filename)
+
+ elif op == 'delete_all':
+ self.delete_all(camera_id, group)
+
+ else:
+ raise HTTPError(400, 'unknown operation')
+
+ @BaseHandler.auth()
+ def list(self, camera_id):
+ logging.debug('listing movies for camera %(id)s' % {'id': camera_id})
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(camera_config):
+ def on_media_list(media_list):
+ if media_list is None:
+ return self.finish_json({'error': 'Failed to get movies list.'})
+
+ self.finish_json({
+ 'mediaList': media_list,
+ 'cameraName': camera_config['@name']
+ })
+
+ mediafiles.list_media(camera_config, media_type='movie',
+ callback=on_media_list, prefix=self.get_argument('prefix', None))
+
+ elif utils.remote_camera(camera_config):
+ def on_response(remote_list=None, error=None):
+ if error:
+ return self.finish_json({'error': 'Failed to get movie list for %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
+
+ self.finish_json(remote_list)
+
+ remote.list_media(camera_config, media_type='movie', prefix=self.get_argument('prefix', None), callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ @BaseHandler.auth()
+ def download(self, camera_id, filename):
+ logging.debug('downloading movie %(filename)s of camera %(id)s' % {
+ 'filename': filename, 'id': camera_id})
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(camera_config):
+ content = mediafiles.get_media_content(camera_config, filename, 'movie')
+
+ pretty_filename = camera_config['@name'] + '_' + os.path.basename(filename)
+ self.set_header('Content-Type', 'video/mpeg')
+ self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + ';')
+
+ self.finish(content)
+
+ elif utils.remote_camera(camera_config):
+ def on_response(response=None, error=None):
+ if error:
+ 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
+ self.set_header('Content-Type', 'video/mpeg')
+ self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + ';')
+
+ self.finish(response)
+
+ remote.get_media_content(camera_config, filename=filename, media_type='movie', callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ @BaseHandler.auth()
+ def preview(self, camera_id, filename):
+ logging.debug('previewing movie %(filename)s of camera %(id)s' % {
+ 'filename': filename, 'id': camera_id})
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(camera_config):
+ content = mediafiles.get_media_preview(camera_config, filename, 'movie',
+ width=self.get_argument('width', None),
+ height=self.get_argument('height', None))
+
+ if content:
+ self.set_header('Content-Type', 'image/jpeg')
+
+ else:
+ self.set_header('Content-Type', 'image/svg+xml')
+ content = open(os.path.join(settings.STATIC_PATH, 'img', 'no-preview.svg')).read()
+
+ self.finish(content)
+
+ elif utils.remote_camera(camera_config):
+ def on_response(content=None, error=None):
+ if content:
+ self.set_header('Content-Type', 'image/jpeg')
+
+ else:
+ self.set_header('Content-Type', 'image/svg+xml')
+ content = open(os.path.join(settings.STATIC_PATH, 'img', 'no-preview.svg')).read()
+
+ self.finish(content)
+
+ remote.get_media_preview(camera_config, filename=filename, media_type='movie',
+ width=self.get_argument('width', None),
+ height=self.get_argument('height', None),
+ callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ @BaseHandler.auth(admin=True)
+ def delete(self, camera_id, filename):
+ logging.debug('deleting movie %(filename)s of camera %(id)s' % {
+ 'filename': filename, 'id': camera_id})
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(camera_config):
+ try:
+ mediafiles.del_media_content(camera_config, filename, 'movie')
+ self.finish_json()
+
+ except Exception as e:
+ self.finish_json({'error': unicode(e)})
+
+ elif utils.remote_camera(camera_config):
+ def on_response(response=None, error=None):
+ if error:
+ return self.finish_json({'error': 'Failed to delete movie from %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
+
+ self.finish_json()
+
+ remote.del_media_content(camera_config, filename=filename, media_type='movie', callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+ @BaseHandler.auth(admin=True)
+ def delete_all(self, camera_id, group):
+ logging.debug('deleting movie group %(group)s of camera %(id)s' % {
+ 'group': group, 'id': camera_id})
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_motion_camera(camera_config):
+ try:
+ mediafiles.del_media_group(camera_config, group, 'movie')
+ self.finish_json()
+
+ except Exception as e:
+ self.finish_json({'error': unicode(e)})
+
+ elif utils.remote_camera(camera_config):
+ def on_response(response=None, error=None):
+ if error:
+ return self.finish_json({'error': 'Failed to delete movie group from %(url)s: %(msg)s.' % {
+ 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
+
+ self.finish_json()
+
+ remote.del_media_group(camera_config, group=group, media_type='movie', callback=on_response)
+
+ else: # assuming simple mjpeg camera
+ raise HTTPError(400, 'unknown operation')
+
+
+class RelayEventHandler(BaseHandler):
+ @BaseHandler.auth(admin=True)
+ def post(self):
+ event = self.get_argument('event')
+ thread_id = int(self.get_argument('thread_id'))
+ logging.debug('recevied relayed event %(event)s for thread id %(id)s' % {'event': event, 'id': thread_id})
+
+ camera_id = motionctl.thread_id_to_camera_id(thread_id)
+ try:
+ camera_config = config.get_camera(camera_id)
+
+ except:
+ logging.warn('ignoring event for remote camera with id %s (probably removed)' % camera_id)
+ return self.finish_json()
+
+ if not utils.local_motion_camera(camera_config):
+ logging.warn('ignoring event for non-local camera with id %s' % camera_id)
+ return self.finish_json()
+
+ if event == 'start':
+ if not camera_config['@motion_detection']:
+ logging.debug('ignoring start event for camera with id %s and motion detection disabled' % camera_id)
+ return self.finish_json()
+
+ motionctl.set_motion_detected(camera_id, True)
+
+ elif event == 'stop':
+ motionctl.set_motion_detected(camera_id, False)
+
+ else:
+ logging.warn('unknown event %s' % event)
+
+ self.finish_json()
+
+
+class LogHandler(BaseHandler):
+ LOGS = {
+ 'motion': (os.path.join(settings.LOG_PATH, 'motion.log'), 'motion.log'),
+ }
+
+ @BaseHandler.auth(admin=True)
+ def get(self, name):
+ log = self.LOGS.get(name)
+ if log is None:
+ raise HTTPError(404, 'no such log')
+
+ (path, filename) = log
+
+ self.set_header('Content-Type', 'text/plain')
+ self.set_header('Content-Disposition', 'attachment; filename=' + filename + ';')
+
+ 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
+ logging.debug('serving log file "%s" from command "%s"' % (filename, path))
+
+ try:
+ output = subprocess.check_output(path, shell=True)
+
+ except Exception as e:
+ output = 'failed to execute command: %s' % e
+
+ self.finish(output)
+
+
+class UpdateHandler(BaseHandler):
+ @BaseHandler.auth(admin=True)
+ def get(self):
+ logging.debug('listing versions')
+
+ versions = update.get_all_versions()
+ current_version = update.get_version()
+ update_version = None
+ if versions and update.compare_versions(versions[-1], current_version) > 0:
+ update_version = versions[-1]
+
+ self.finish_json({
+ 'update_version': update_version,
+ 'current_version': current_version
+ })
+
+ @BaseHandler.auth(admin=True)
+ def post(self):
+ version = self.get_argument('version')
+
+ logging.debug('performing update to version %(version)s' % {'version': version})
+
+ result = update.perform_update(version)
+
+ self.finish_json(result)
+
+
+class PowerHandler(BaseHandler):
+ @BaseHandler.auth(admin=True)
+ def post(self, op):
+ if op == 'shutdown':
+ self.shut_down()
+
+ elif op == 'reboot':
+ self.reboot()
+
+ def shut_down(self):
+ IOLoop.instance().add_timeout(datetime.timedelta(seconds=2), powerctl.shut_down)
+
+ def reboot(self):
+ IOLoop.instance().add_timeout(datetime.timedelta(seconds=2), powerctl.reboot)
+
+
+class VersionHandler(BaseHandler):
+ def get(self):
+ self.render('version.html',
+ version=update.get_version(),
+ hostname=socket.gethostname())
+
+ post = get
+
+
+# this will only trigger the login mechanism on the client side, if required
+class LoginHandler(BaseHandler):
+ @BaseHandler.auth()
+ def get(self):
+ self.finish_json()
+
+ def post(self):
+ self.set_header('Content-Type', 'text/html')
+ self.finish()
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+import errno
+import fcntl
+import functools
+import hashlib
+import logging
+import multiprocessing
+import os.path
+import re
+import stat
+import StringIO
+import subprocess
+import time
+import tornado
+import zipfile
+
+from PIL import Image
+from tornado import ioloop
+
+import config
+import settings
+import utils
+
+
+_PICTURE_EXTS = ['.jpg']
+_MOVIE_EXTS = ['.avi', '.mp4']
+
+# a cache list of paths to movies without preview
+_previewless_movie_files = []
+
+# a cache of prepared files (whose preparing time is significant)
+_prepared_files = {}
+
+_timelapse_process = None
+_timelapse_data = None
+
+
+def _list_media_files(dir, exts, prefix=None):
+ media_files = []
+
+ if prefix is not None:
+ if prefix == 'ungrouped':
+ prefix = ''
+
+ root = os.path.join(dir, prefix)
+ for name in os.listdir(root):
+ if name == 'lastsnap.jpg' or name.startswith('.'): # ignore the lastsnap.jpg and hidden files
+ continue
+
+ full_path = os.path.join(root, name)
+ try:
+ st = os.stat(full_path)
+
+ except Exception as e:
+ logging.error('stat failed: ' + unicode(e))
+ continue
+
+ if not stat.S_ISREG(st.st_mode): # not a regular file
+ continue
+
+ full_path_lower = full_path.lower()
+ if not [e for e in exts if full_path_lower.endswith(e)]:
+ continue
+
+ media_files.append((full_path, st))
+
+ else:
+ for root, dirs, files in os.walk(dir): # @UnusedVariable # TODO os.walk can be rewritten to return stat info
+ for name in files:
+ if name == 'lastsnap.jpg' or name.startswith('.'): # ignore the lastsnap.jpg and hidden files
+ continue
+
+ full_path = os.path.join(root, name)
+ try:
+ st = os.stat(full_path)
+
+ except Exception as e:
+ logging.error('stat failed: ' + unicode(e))
+ continue
+
+ if not stat.S_ISREG(st.st_mode): # not a regular file
+ continue
+
+ full_path_lower = full_path.lower()
+ if not [e for e in exts if full_path_lower.endswith(e)]:
+ continue
+
+ media_files.append((full_path, st))
+
+ return media_files
+
+
+def _remove_older_files(dir, moment, exts):
+ for (full_path, st) in _list_media_files(dir, exts):
+ file_moment = datetime.datetime.fromtimestamp(st.st_mtime)
+ if file_moment < moment:
+ logging.debug('removing file %(path)s...' % {'path': full_path})
+
+ # remove the file itself
+ try:
+ os.remove(full_path)
+
+ except OSError as e:
+ if e.errno == errno.ENOENT:
+ pass # the file might have been removed in the meantime
+
+ else:
+ logging.error('failed to remove %s: %s' % (full_path, e))
+
+ # remove the parent directories if empty or contain only thumb files
+ dir_path = os.path.dirname(full_path)
+ if not os.path.exists(dir_path):
+ continue
+
+ listing = os.listdir(dir_path)
+ thumbs = [l for l in listing if l.endswith('.thumb')]
+
+ if len(listing) == len(thumbs): # only thumbs
+ for p in thumbs:
+ try:
+ os.remove(os.path.join(dir_path, p))
+
+ except:
+ logging.error('failed to remove %s: %s' % (p, e))
+
+ if not listing or len(listing) == len(thumbs):
+ # this will possibly cause following paths that are in the media files for loop
+ # to be removed in advance; the os.remove call will raise ENOENT which is silently ignored
+ logging.debug('removing empty directory %(path)s...' % {'path': dir_path})
+ try:
+ os.removedirs(dir_path)
+
+ except:
+ logging.error('failed to remove %s: %s' % (dir_path, e))
+
+
+def find_ffmpeg():
+ try:
+ return subprocess.check_output('which ffmpeg', shell=True).strip()
+
+ except subprocess.CalledProcessError: # not found
+ return None
+
+
+def cleanup_media(media_type):
+ logging.debug('cleaning up %(media_type)ss...' % {'media_type': media_type})
+
+ if media_type == 'picture':
+ exts = _PICTURE_EXTS
+
+ elif 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.local_motion_camera(camera_config):
+ continue
+
+ preserve_media = camera_config.get('@preserve_%(media_type)ss' % {'media_type': media_type}, 0)
+ if preserve_media == 0:
+ return # preserve forever
+
+ still_images_enabled = bool(
+ ((camera_config['emulate_motion'] or camera_config['output_pictures']) and camera_config['picture_filename']) or
+ (camera_config['snapshot_interval'] and camera_config['snapshot_filename']))
+
+ 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
+
+ elif media_type == 'movie' and not movies_enabled:
+ continue # only cleanup movies for cameras with movies enabled
+
+ preserve_moment = datetime.datetime.now() - datetime.timedelta(days=preserve_media)
+
+ target_dir = camera_config.get('target_dir')
+ _remove_older_files(target_dir, preserve_moment, exts=exts)
+
+
+def make_movie_preview(camera_config, full_path):
+ framerate = camera_config['framerate']
+ pre_capture = camera_config['pre_capture']
+ offs = pre_capture / framerate
+ offs = max(4, offs * 2)
+
+ logging.debug('creating movie preview for %(path)s with an offset of %(offs)s seconds...' % {
+ 'path': full_path, 'offs': offs})
+
+ cmd = 'ffmpeg -i "%(path)s" -f mjpeg -vframes 1 -ss %(offs)s -y %(path)s.thumb'
+
+ try:
+ subprocess.check_output(cmd % {'path': full_path, 'offs': offs}, shell=True, stderr=subprocess.STDOUT)
+
+ except subprocess.CalledProcessError as e:
+ logging.error('failed to create movie preview for %(path)s: %(msg)s' % {
+ 'path': full_path, 'msg': unicode(e)})
+
+ return None
+
+ try:
+ st = os.stat(full_path + '.thumb')
+
+ except os.error:
+ logging.error('failed to create movie preview for %(path)s: ffmpeg error' % {
+ 'path': full_path})
+
+ return None
+
+ if st.st_size == 0:
+ logging.debug('movie is too short, grabbing first frame from %(path)s...' % {'path': full_path})
+
+ # try again, this time grabbing the very first frame
+ try:
+ subprocess.check_output(cmd % {'path': full_path, 'offs': 0}, shell=True, stderr=subprocess.STDOUT)
+
+ except subprocess.CalledProcessError as e:
+ logging.error('failed to create movie preview for %(path)s: %(msg)s' % {
+ 'path': full_path, 'msg': unicode(e)})
+
+ return None
+
+ return full_path + '.thumb'
+
+
+def make_next_movie_preview():
+ global _previewless_movie_files
+
+ logging.debug('making preview for the next movie...')
+
+ if _previewless_movie_files:
+ (camera_config, path) = _previewless_movie_files.pop(0)
+
+ make_movie_preview(camera_config, path)
+
+ else:
+ logging.debug('gathering movies without preview...')
+
+ count = 0
+ for camera_id in config.get_camera_ids():
+ camera_config = config.get_camera(camera_id)
+ if not utils.local_motion_camera(camera_config):
+ continue
+
+ target_dir = camera_config['target_dir']
+
+ for (full_path, st) in _list_media_files(target_dir, _MOVIE_EXTS): # @UnusedVariable
+ if os.path.exists(full_path + '.thumb'):
+ continue
+
+ logging.debug('found a movie without preview: %(path)s' % {
+ 'path': full_path})
+
+ _previewless_movie_files.append((camera_config, full_path))
+ count += 1
+
+ logging.debug('found %(count)d movies without preview' % {'count': count})
+
+ if count:
+ make_next_movie_preview()
+
+
+def list_media(camera_config, media_type, callback, prefix=None):
+ target_dir = camera_config.get('target_dir')
+
+ if media_type == 'picture':
+ exts = _PICTURE_EXTS
+
+ elif media_type == 'movie':
+ exts = _MOVIE_EXTS
+
+ # create a subprocess to retrieve media files
+ def do_list_media(pipe):
+ mf = _list_media_files(target_dir, exts=exts, prefix=prefix)
+ for (p, st) in mf:
+ path = p[len(target_dir):]
+ if not path.startswith('/'):
+ path = '/' + path
+
+ timestamp = st.st_mtime
+ size = st.st_size
+
+ pipe.send({
+ 'path': path,
+ 'momentStr': utils.pretty_date_time(datetime.datetime.fromtimestamp(timestamp)),
+ 'momentStrShort': utils.pretty_date_time(datetime.datetime.fromtimestamp(timestamp), short=True),
+ 'sizeStr': utils.pretty_size(size),
+ 'timestamp': timestamp
+ })
+
+ pipe.close()
+
+ logging.debug('starting media listing process...')
+
+ (parent_pipe, child_pipe) = multiprocessing.Pipe(duplex=False)
+ process = multiprocessing.Process(target=do_list_media, args=(child_pipe, ))
+ process.start()
+
+ # poll the subprocess to see when it has finished
+ started = datetime.datetime.now()
+ media_list = []
+
+ def read_media_list():
+ while parent_pipe.poll():
+ media_list.append(parent_pipe.recv())
+
+ def poll_process():
+ ioloop = tornado.ioloop.IOLoop.instance()
+ if process.is_alive(): # not finished yet
+ now = datetime.datetime.now()
+ delta = now - started
+ if delta.seconds < 120:
+ ioloop.add_timeout(datetime.timedelta(seconds=0.5), poll_process)
+ read_media_list()
+
+ else: # process did not finish within 2 minutes
+ logging.error('timeout waiting for the media listing process to finish')
+
+ callback(None)
+
+ else: # finished
+ read_media_list()
+ logging.debug('media listing process has returned %(count)s files' % {'count': len(media_list)})
+ callback(media_list)
+
+ poll_process()
+
+
+def get_media_content(camera_config, path, media_type):
+ target_dir = camera_config.get('target_dir')
+
+ full_path = os.path.join(target_dir, path)
+
+ try:
+ with open(full_path) as f:
+ return f.read()
+
+ except Exception as e:
+ logging.error('failed to read file %(path)s: %(msg)s' % {
+ 'path': full_path, 'msg': unicode(e)})
+
+ return None
+
+
+def get_zipped_content(camera_config, media_type, group, callback):
+ target_dir = camera_config.get('target_dir')
+
+ if media_type == 'picture':
+ exts = _PICTURE_EXTS
+
+ elif media_type == 'movie':
+ exts = _MOVIE_EXTS
+
+ working = multiprocessing.Value('b')
+ working.value = True
+
+ # create a subprocess to add files to zip
+ def do_zip(pipe):
+ mf = _list_media_files(target_dir, exts=exts, prefix=group)
+ paths = []
+ for (p, st) in mf: # @UnusedVariable
+ path = p[len(target_dir):]
+ if path.startswith('/'):
+ path = path[1:]
+
+ paths.append(path)
+
+ zip_filename = os.path.join(settings.MEDIA_PATH, '.zip-%s' % int(time.time()))
+ logging.debug('adding %d files to zip file "%s"' % (len(paths), zip_filename))
+
+ try:
+ with zipfile.ZipFile(zip_filename, mode='w') as f:
+ for path in paths:
+ full_path = os.path.join(target_dir, path)
+ f.write(full_path, path)
+
+ except Exception as e:
+ logging.error('failed to create zip file "%s": %s' % (zip_filename, e))
+
+ working.value = False
+ pipe.close()
+ return
+
+ logging.debug('reading zip file "%s" into memory' % zip_filename)
+
+ try:
+ with open(zip_filename, mode='r') as f:
+ data = f.read()
+
+ working.value = False
+ pipe.send(data)
+ logging.debug('zip data ready')
+
+ except Exception as e:
+ logging.error('failed to read zip file "%s": %s' % (zip_filename, e))
+ working.value = False
+
+ finally:
+ os.remove(zip_filename)
+ pipe.close()
+
+ logging.debug('starting zip process...')
+
+ (parent_pipe, child_pipe) = multiprocessing.Pipe(duplex=False)
+ process = multiprocessing.Process(target=do_zip, args=(child_pipe, ))
+ process.start()
+
+ # poll the subprocess to see when it has finished
+ started = datetime.datetime.now()
+
+ def poll_process():
+ ioloop = tornado.ioloop.IOLoop.instance()
+ if working.value:
+ now = datetime.datetime.now()
+ delta = now - started
+ if delta.seconds < settings.ZIP_TIMEOUT:
+ ioloop.add_timeout(datetime.timedelta(seconds=0.5), poll_process)
+
+ else: # process did not finish within 2 minutes
+ logging.error('timeout waiting for the zip process to finish')
+
+ callback(None)
+
+ else: # finished
+ try:
+ data = parent_pipe.recv()
+ logging.debug('zip process has returned %d bytes' % len(data))
+
+ except:
+ data = None
+
+ callback(data)
+
+ poll_process()
+
+
+def make_timelapse_movie(camera_config, framerate, interval, group):
+ global _timelapse_process
+ global _timelapse_data
+
+ target_dir = camera_config.get('target_dir')
+
+ # create a subprocess to retrieve media files
+ def do_list_media(pipe):
+ mf = _list_media_files(target_dir, exts=_PICTURE_EXTS, prefix=group)
+ for (p, st) in mf:
+ timestamp = st.st_mtime
+
+ pipe.send({
+ 'path': p,
+ 'timestamp': timestamp
+ })
+
+ pipe.close()
+
+ logging.debug('starting media listing process...')
+
+ (parent_pipe, child_pipe) = multiprocessing.Pipe(duplex=False)
+ _timelapse_process = multiprocessing.Process(target=do_list_media, args=(child_pipe, ))
+ _timelapse_process.progress = 0
+ _timelapse_process.start()
+ _timelapse_data = None
+
+ started = [datetime.datetime.now()]
+ media_list = []
+
+ tmp_filename = os.path.join(settings.MEDIA_PATH, '.%s.avi' % int(time.time()))
+
+ def read_media_list():
+ while parent_pipe.poll():
+ media_list.append(parent_pipe.recv())
+
+ def poll_media_list_process():
+ ioloop = tornado.ioloop.IOLoop.instance()
+ if _timelapse_process.is_alive(): # not finished yet
+ now = datetime.datetime.now()
+ delta = now - started[0]
+ if delta.seconds < 300: # the subprocess has 5 minutes to complete its job
+ ioloop.add_timeout(datetime.timedelta(seconds=0.5), poll_media_list_process)
+ read_media_list()
+
+ else: # process did not finish within 2 minutes
+ logging.error('timeout waiting for the media listing process to finish')
+
+ _timelapse_process.progress = -1
+
+ else: # finished
+ read_media_list()
+ logging.debug('media listing process has returned %(count)s files' % {'count': len(media_list)})
+
+ if not media_list:
+ _timelapse_process.progress = -1
+
+ return
+
+ pictures = select_pictures(media_list)
+ make_movie(pictures)
+
+ def select_pictures(media_list):
+ media_list.sort(key=lambda e: e['timestamp'])
+ start = media_list[0]['timestamp']
+ slices = {}
+ max_idx = 0
+ for m in media_list:
+ offs = m['timestamp'] - start
+ pos = float(offs) / interval - 0.5
+ idx = int(round(pos))
+ max_idx = idx
+ m['delta'] = abs(pos - idx)
+ slices.setdefault(idx, []).append(m)
+
+ selected = []
+ for i in xrange(max_idx + 1):
+ slice = slices.get(i)
+ if not slice:
+ continue
+
+ selected.append(min(slice, key=lambda m: m['delta']))
+
+ logging.debug('selected %d/%d media files' % (len(selected), len(media_list)))
+
+ return selected
+
+ 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 mpeg4 -b:v %(bitrate)s -qscale:v 0.1 -f avi %(tmp_filename)s'
+
+ bitrate = 9999999
+
+ cmd = cmd % {
+ 'tmp_filename': tmp_filename,
+ 'jpegs': ' '.join((('"' + p['path'] + '"') for p in pictures)),
+ 'framerate': framerate,
+ '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%
+
+ # make subprocess stdout pipe non-blocking
+ fd = _timelapse_process.stdout.fileno()
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ poll_movie_process(pictures)
+
+ def poll_movie_process(pictures):
+ global _timelapse_process
+ global _timelapse_data
+
+ ioloop = tornado.ioloop.IOLoop.instance()
+ if _timelapse_process.poll() is None: # not finished yet
+ ioloop.add_timeout(datetime.timedelta(seconds=0.5), functools.partial(poll_movie_process, pictures))
+
+ try:
+ output = _timelapse_process.stdout.read()
+
+ except IOError as e:
+ if e.errno == errno.EAGAIN:
+ output = ''
+
+ else:
+ raise
+
+ frame_index = re.findall('frame=\s*(\d+)', output)
+ try:
+ frame_index = int(frame_index[-1])
+
+ except (IndexError, ValueError):
+ return
+
+ _timelapse_process.progress = max(0.01, float(frame_index) / len(pictures))
+
+ logging.debug('timelapse progress: %s' % int(100 * _timelapse_process.progress))
+
+ else: # finished
+ exit_code = _timelapse_process.poll()
+ _timelapse_process = None
+
+ if exit_code != 0:
+ logging.error('ffmpeg process failed')
+ _timelapse_data = None
+
+ try:
+ os.remove(tmp_filename)
+
+ except:
+ pass
+
+ else:
+ logging.debug('reading timelapse movie file "%s" into memory' % tmp_filename)
+
+ try:
+ with open(tmp_filename, mode='r') as f:
+ _timelapse_data = f.read()
+
+ logging.debug('timelapse movie process has returned %d bytes' % len(_timelapse_data))
+
+ except Exception as e:
+ logging.error('failed to read timelapse movie file "%s": %s' % (tmp_filename, e))
+
+ finally:
+ try:
+ os.remove(tmp_filename)
+
+ except:
+ pass
+
+ poll_media_list_process()
+
+
+def check_timelapse_movie():
+ if _timelapse_process:
+ if ((hasattr(_timelapse_process, 'poll') and _timelapse_process.poll() is None) or
+ (hasattr(_timelapse_process, 'is_alive') and _timelapse_process.is_alive())):
+
+ return {'progress': _timelapse_process.progress, 'data': None}
+
+ else:
+ return {'progress': _timelapse_process.progress, 'data': _timelapse_data}
+
+ else:
+ return {'progress': -1, 'data': _timelapse_data}
+
+
+def get_media_preview(camera_config, path, media_type, width, height):
+ target_dir = camera_config.get('target_dir')
+ full_path = os.path.join(target_dir, path)
+
+ if media_type == 'movie':
+ if not os.path.exists(full_path + '.thumb'):
+ if not make_movie_preview(camera_config, full_path):
+ return None
+
+ full_path += '.thumb'
+
+ try:
+ with open(full_path) as f:
+ content = f.read()
+
+ except Exception as e:
+ logging.error('failed to read file %(path)s: %(msg)s' % {
+ 'path': full_path, 'msg': unicode(e)})
+
+ return None
+
+ if width is height is None:
+ return content
+
+ sio = StringIO.StringIO(content)
+ image = Image.open(sio)
+ width = width and int(width) or image.size[0]
+ height = height and int(height) or image.size[1]
+
+ image.thumbnail((width, height), Image.LINEAR)
+
+ sio = StringIO.StringIO()
+ image.save(sio, format='JPEG')
+
+ return sio.getvalue()
+
+
+def del_media_content(camera_config, path, media_type):
+ target_dir = camera_config.get('target_dir')
+
+ full_path = os.path.join(target_dir, path)
+
+ try:
+ # remove the file itself
+ os.remove(full_path)
+
+ # remove the parent directories if empty or contains only thumb files
+ dir_path = os.path.dirname(full_path)
+ listing = os.listdir(dir_path)
+ thumbs = [l for l in listing if l.endswith('.thumb')]
+
+ if len(listing) == len(thumbs): # only thumbs
+ for p in thumbs:
+ os.remove(os.path.join(dir_path, p))
+
+ if not listing or len(listing) == len(thumbs):
+ logging.debug('removing empty directory %(path)s...' % {'path': dir_path})
+ os.removedirs(dir_path)
+
+ except Exception as e:
+ logging.error('failed to remove file %(path)s: %(msg)s' % {
+ 'path': full_path, 'msg': unicode(e)})
+
+ raise
+
+
+def del_media_group(camera_config, group, media_type):
+ if media_type == 'picture':
+ exts = _PICTURE_EXTS
+
+ elif media_type == 'movie':
+ exts = _MOVIE_EXTS
+
+ target_dir = camera_config.get('target_dir')
+ full_path = os.path.join(target_dir, group)
+
+ mf = _list_media_files(target_dir, exts=exts, prefix=group)
+ for (path, st) in mf: # @UnusedVariable
+ try:
+ os.remove(path)
+
+ except Exception as e:
+ logging.error('failed to remove file %(path)s: %(msg)s' % {
+ 'path': full_path, 'msg': unicode(e)})
+
+ raise
+
+ # remove the group directory if empty or contains only thumb files
+ listing = os.listdir(full_path)
+ thumbs = [l for l in listing if l.endswith('.thumb')]
+
+ if len(listing) == len(thumbs): # only thumbs
+ for p in thumbs:
+ os.remove(os.path.join(full_path, p))
+
+ if not listing or len(listing) == len(thumbs):
+ logging.debug('removing empty directory %(path)s...' % {'path': full_path})
+ os.removedirs(full_path)
+
+
+def get_current_picture(camera_config, width, height):
+ import mjpgclient
+
+ jpg = mjpgclient.get_jpg(camera_config['@id'])
+
+ if jpg is None:
+ return None
+
+ if width is height is None:
+ return jpg # no server-side resize needed
+
+ sio = StringIO.StringIO(jpg)
+ image = Image.open(sio)
+
+ width = width and int(width) or image.size[0]
+ height = height and int(height) or image.size[1]
+
+ webcam_resolution = camera_config['@webcam_resolution']
+ max_width = image.size[0] * webcam_resolution / 100
+ max_height = image.size[1] * webcam_resolution / 100
+
+ width = min(max_width, width)
+ 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
+
+ image.thumbnail((width, height), Image.CUBIC)
+
+ sio = StringIO.StringIO()
+ image.save(sio, format='JPEG')
+
+ return sio.getvalue()
+
+
+def get_prepared_cache(key):
+ return _prepared_files.pop(key, None)
+
+
+def set_prepared_cache(data):
+ key = hashlib.sha1(str(time.time())).hexdigest()
+
+ if key in _prepared_files:
+ logging.warn('key "%s" already present in prepared cache' % key)
+
+ _prepared_files[key] = data
+
+ def clear():
+ 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
+ ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=timeout), clear)
+
+ return key
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+import errno
+import logging
+import re
+import socket
+import time
+
+from tornado import iostream, ioloop
+
+import config
+import motionctl
+import settings
+import utils
+
+
+class MjpgClient(iostream.IOStream):
+ clients = {} # dictionary of clients indexed by camera id
+ last_jpgs = {} # dictionary of jpg contents indexed by camera id
+ last_jpg_moment = {} # dictionary of moments of the last received jpeg indexed by camera id
+ last_access = {} # dictionary of access moments indexed by camera id
+ last_erroneous_close_time = 0 # helps detecting erroneous connections and restart motion
+
+ def __init__(self, camera_id, port, username, password):
+ self._camera_id = camera_id
+ self._port = port
+ self._username = (username or '').encode('utf8')
+ self._password = (password or '').encode('utf8')
+ self._auth_digest_state = {}
+
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ iostream.IOStream.__init__(self, s)
+
+ self.set_close_callback(self.on_close)
+
+ def connect(self):
+ iostream.IOStream.connect(self, ('localhost', self._port), self._on_connect)
+ MjpgClient.clients[self._camera_id] = self
+
+ logging.debug('mjpg client for camera %(camera_id)s connecting on port %(port)s...' % {
+ 'port': self._port, 'camera_id': self._camera_id})
+
+ def on_close(self):
+ logging.debug('connection closed for mjpg client for camera %(camera_id)s on port %(port)s' % {
+ 'port': self._port, 'camera_id': self._camera_id})
+
+ if MjpgClient.clients.pop(self._camera_id, None):
+ MjpgClient.last_access.pop(self._camera_id, None)
+ MjpgClient.last_jpg_moment.pop(self._camera_id, None)
+
+ logging.debug('mjpg client for camera %(camera_id)s on port %(port)s removed' % {
+ 'port': self._port, 'camera_id': self._camera_id})
+
+ 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})
+
+ motionctl.stop(invalidate=True) # this will close all the mjpg clients
+ motionctl.start(deferred=True)
+
+ MjpgClient.last_erroneous_close_time = now
+
+ def _check_error(self):
+ if self.socket is None:
+ logging.warning('mjpg client connection for camera %(camera_id)s on port %(port)s is closed' % {
+ 'port': self._port, 'camera_id': self._camera_id})
+
+ self.close()
+
+ 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
+ return False
+
+ self._error(error)
+
+ return True
+
+ def _error(self, error):
+ logging.error('mjpg client for camera %(camera_id)s on port %(port)s error: %(msg)s' % {
+ 'port': self._port, 'camera_id': self._camera_id, 'msg': unicode(error)})
+
+ try:
+ self.close()
+
+ except:
+ pass
+
+ def _on_connect(self):
+ logging.debug('mjpg client for camera %(camera_id)s connected on port %(port)s' % {
+ 'port': self._port, 'camera_id': self._camera_id})
+
+ if self._username:
+ 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:
+ self.write('GET / HTTP/1.0\r\n\r\n')
+
+ self._seek_http()
+
+ def _seek_http(self):
+ if self._check_error():
+ return
+
+ self.read_until_regex('HTTP/1.\d \d+ ', self._on_http)
+
+ def _on_http(self, data):
+ if data.endswith('401 '):
+ self._seek_www_authenticate()
+
+ else: # no authorization required, skip to content length
+ self._seek_content_length()
+
+ def _seek_www_authenticate(self):
+ if self._check_error():
+ return
+
+ self.read_until('WWW-Authenticate:', self._on_before_www_authenticate)
+
+ def _on_before_www_authenticate(self, data):
+ if self._check_error():
+ return
+
+ self.read_until('\r\n', self._on_www_authenticate)
+
+ def _on_www_authenticate(self, data):
+ if self._check_error():
+ return
+
+ m = re.match('Basic\s*realm="([a-zA-Z0-9\-\s]+)"', data.strip())
+ if m:
+ logging.debug('mjpgclient: using basic authentication')
+
+ 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)
+ self._seek_http()
+
+ return
+
+ m = re.match('Digest\s*realm="([a-zA-Z0-9\-\s]+)",\s*nonce="([a-zA-Z0-9]+)"', data.strip())
+ if m:
+ logging.debug('mjpgclient: using digest authentication')
+
+ realm, nonce = m.groups()
+ self._auth_digest_state['realm'] = realm
+ self._auth_digest_state['nonce'] = nonce
+
+ auth_header = utils.build_digest_header('GET', '/', self._username, self._password, self._auth_digest_state)
+ self.write('GET / HTTP/1.0\r\n\r\nAuthorization: %s\r\n\r\n' % auth_header)
+ self._seek_http()
+
+ return
+
+ logging.error('mjpgclient: unknown authentication header: "%s"' % data)
+ self._seek_content_length()
+
+ def _seek_content_length(self):
+ if self._check_error():
+ return
+
+ self.read_until('Content-Length:', self._on_before_content_length)
+
+ def _on_before_content_length(self, data):
+ if self._check_error():
+ return
+
+ self.read_until('\r\n\r\n', self._on_content_length)
+
+ def _on_content_length(self, data):
+ if self._check_error():
+ return
+
+ matches = re.findall('(\d+)', data)
+ if not matches:
+ self._error('could not find content length in mjpg header line "%(header)s"' % {
+ 'header': data})
+
+ return
+
+ length = int(matches[0])
+
+ self.read_bytes(length, self._on_jpg)
+
+ def _on_jpg(self, data):
+ MjpgClient.last_jpgs[self._camera_id] = data
+ MjpgClient.last_jpg_moment[self._camera_id] = datetime.datetime.utcnow()
+ self._seek_content_length()
+
+
+def _garbage_collector():
+ logging.debug('running garbage collector for mjpg clients...')
+
+ now = datetime.datetime.utcnow()
+ for client in MjpgClient.clients.values():
+ camera_id = client._camera_id
+ port = client._port
+
+ # check for last jpg moment timeout
+ last_jpg_moment = MjpgClient.last_jpg_moment.get(camera_id)
+ if last_jpg_moment is None:
+ MjpgClient.last_jpg_moment[camera_id] = now
+
+ continue
+
+ if client.closed():
+ continue
+
+ delta = now - last_jpg_moment
+ delta = delta.days * 86400 + delta.seconds
+
+ 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})
+
+ motionctl.stop(invalidate=True) # this will close all the mjpg clients
+ motionctl.start(deferred=True)
+
+ break
+
+ # check for last access timeout
+ last_access = MjpgClient.last_access.get(camera_id)
+ if last_access is None:
+ continue
+
+ delta = now - last_access
+ delta = delta.days * 86400 + delta.seconds
+
+ 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' % {
+ 'camera_id': camera_id, 'port': port, 'timeout': settings.MJPG_CLIENT_IDLE_TIMEOUT})
+
+ client.close()
+
+ continue
+
+ io_loop = ioloop.IOLoop.instance()
+ io_loop.add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT), _garbage_collector)
+
+
+def get_jpg(camera_id):
+ if camera_id not in MjpgClient.clients:
+ # mjpg client not started yet for this camera
+
+ logging.debug('creating mjpg client for camera %(camera_id)s' % {
+ 'camera_id': camera_id})
+
+ camera_config = config.get_camera(camera_id)
+ if not camera_config['@enabled'] or not utils.local_motion_camera(camera_config):
+ logging.error('could not start mjpg client for camera id %(camera_id)s: not enabled or not local' % {
+ 'camera_id': camera_id})
+
+ return None
+
+ port = camera_config['stream_port']
+ username, password = None, None
+ if camera_config.get('stream_auth_method') > 0:
+ username, password = camera_config.get('stream_authentication', ':').split(':')
+
+ client = MjpgClient(camera_id, port, username, password)
+ client.connect()
+
+ MjpgClient.last_access[camera_id] = datetime.datetime.utcnow()
+
+ return MjpgClient.last_jpgs.get(camera_id)
+
+
+def close_all(invalidate=False):
+ for client in MjpgClient.clients.values():
+ client.close()
+
+ if invalidate:
+ MjpgClient.clients = {}
+ MjpgClient.last_jpgs = {}
+ MjpgClient.last_jpg_moment = {}
+ MjpgClient.last_access = {}
+ MjpgClient.last_erroneous_close_time = 0
+
+
+# schedule the garbage collector
+io_loop = ioloop.IOLoop.instance()
+io_loop.add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT), _garbage_collector)
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import errno
+import logging
+import os.path
+import re
+import signal
+import subprocess
+import time
+
+from tornado.httpclient import HTTPClient, AsyncHTTPClient, HTTPRequest
+from tornado.ioloop import IOLoop
+
+import config
+import mjpgclient
+import powerctl
+import settings
+import utils
+
+
+_started = False
+_motion_binary_cache = None
+_motion_detected = {}
+
+
+def find_motion():
+ global _motion_binary_cache
+ if _motion_binary_cache:
+ return _motion_binary_cache
+
+ if settings.MOTION_BINARY:
+ if os.path.exists(settings.MOTION_BINARY):
+ binary = settings.MOTION_BINARY
+
+ else:
+ return None
+
+ else: # autodetect motion binary path
+ try:
+ binary = subprocess.check_output('which motion', shell=True).strip()
+
+ except subprocess.CalledProcessError: # not found
+ return None
+
+ try:
+ help = subprocess.check_output(binary + ' -h || true', shell=True)
+
+ except subprocess.CalledProcessError: # not found
+ return None
+
+ result = re.findall('^motion Version ([^,]+)', help)
+ version = result and result[0] or ''
+
+ _motion_binary_cache = (binary, version)
+
+ return _motion_binary_cache
+
+
+def _disable_initial_motion_detection():
+ for camera_id in config.get_camera_ids():
+ camera_config = config.get_camera(camera_id)
+ if not utils.local_motion_camera(camera_config):
+ continue
+
+ if not camera_config['@motion_detection']:
+ logging.debug('motion detection disabled by config for camera with id %s' % camera_id)
+ set_motion_detection(camera_id, False)
+
+
+def start(deferred=False):
+ if deferred:
+ return IOLoop.instance().add_callback(start, deferred=False)
+
+ global _started
+
+ _started = True
+
+ enabled_local_motion_cameras = config.get_enabled_local_motion_cameras()
+ if running() or not enabled_local_motion_cameras:
+ return
+
+ logging.debug('starting motion')
+
+ program = find_motion()
+ if not program:
+ raise Exception('motion executable could not be found')
+
+ program, version = program # @UnusedVariable
+
+ logging.debug('using motion binary "%s"' % program)
+
+ motion_config_path = os.path.join(settings.CONF_PATH, 'motion.conf')
+ motion_log_path = os.path.join(settings.LOG_PATH, 'motion.log')
+ motion_pid_path = os.path.join(settings.RUN_PATH, 'motion.pid')
+
+ args = [program,
+ '-c', motion_config_path,
+ '-n',
+ '-d']
+
+ if settings.LOG_LEVEL == logging.DEBUG:
+ args.append('9')
+
+ else:
+ args.append('1')
+
+ log_file = open(motion_log_path, 'w')
+
+ process = subprocess.Popen(args, stdout=log_file, stderr=log_file, close_fds=True, cwd=settings.CONF_PATH)
+
+ # wait 2 seconds to see that the process has successfully started
+ for i in xrange(20): # @UnusedVariable
+ time.sleep(0.1)
+ exit_code = process.poll()
+ if exit_code is not None and exit_code != 0:
+ raise Exception('motion failed to start')
+
+ pid = process.pid
+
+ # write the pid to file
+ with open(motion_pid_path, 'w') as f:
+ f.write(str(pid) + '\n')
+
+ _disable_initial_motion_detection()
+
+ # if mjpg client idle timeout is disabled, create mjpg clients for all cameras by default
+ if not settings.MJPG_CLIENT_IDLE_TIMEOUT:
+ logging.debug('creating default mjpg clients for local cameras')
+ for camera in enabled_local_motion_cameras:
+ mjpgclient.get_jpg(camera['@id'])
+
+
+def stop(invalidate=False):
+ global _started
+
+ _started = False
+
+ if not running():
+ return
+
+ logging.debug('stopping motion')
+
+ mjpgclient.close_all(invalidate=invalidate)
+
+ pid = _get_pid()
+ if pid is not None:
+ try:
+ # send the TERM signal once
+ os.kill(pid, signal.SIGTERM)
+
+ # wait 5 seconds for the process to exit
+ for i in xrange(50): # @UnusedVariable
+ os.waitpid(pid, os.WNOHANG)
+ time.sleep(0.1)
+
+ # send the KILL signal once
+ os.kill(pid, signal.SIGKILL)
+
+ # wait 2 seconds for the process to exit
+ for i in xrange(20): # @UnusedVariable
+ time.sleep(0.1)
+ os.waitpid(pid, os.WNOHANG)
+
+ # the process still did not exit
+ if settings.ENABLE_REBOOT:
+ logging.error('could not terminate the motion process')
+ powerctl.reboot()
+
+ else:
+ raise Exception('could not terminate the motion process')
+
+ except OSError as e:
+ if e.errno not in (errno.ESRCH, errno.ECHILD):
+ raise
+
+
+def running():
+ pid = _get_pid()
+ if pid is None:
+ return False
+
+ try:
+ os.waitpid(pid, os.WNOHANG)
+ os.kill(pid, 0)
+
+ # the process is running
+ return True
+
+ except OSError as e:
+ if e.errno not in (errno.ESRCH, errno.ECHILD):
+ raise
+
+ return False
+
+
+def started():
+ return _started
+
+
+def get_motion_detection(camera_id):
+ thread_id = camera_id_to_thread_id(camera_id)
+ if thread_id is None:
+ return logging.error('could not find thread id for camera with id %s' % camera_id)
+
+ url = 'http://127.0.0.1:7999/%(id)s/detection/status' % {'id': thread_id}
+
+ request = HTTPRequest(url, connect_timeout=5, request_timeout=5)
+ http_client = HTTPClient()
+ try:
+ response = http_client.fetch(request)
+ if response.error:
+ raise response.error
+
+ except Exception as e:
+ logging.error('failed to get motion detection status for camera with id %(id)s: %(msg)s' % {
+ 'id': camera_id,
+ 'msg': unicode(e)})
+
+ return None
+
+ enabled = bool(response.body.lower().count('active'))
+
+ logging.debug('motion detection is %(what)s for camera with id %(id)s' % {
+ 'what': ['disabled', 'enabled'][enabled],
+ 'id': camera_id})
+
+ return enabled
+
+
+def set_motion_detection(camera_id, enabled):
+ thread_id = camera_id_to_thread_id(camera_id)
+ if thread_id is None:
+ return logging.error('could not find thread id for camera with id %s' % camera_id)
+
+ if not enabled:
+ _motion_detected[camera_id] = False
+
+ logging.debug('%(what)s motion detection for camera with id %(id)s' % {
+ 'what': ['disabling', 'enabling'][enabled],
+ 'id': camera_id})
+
+ url = 'http://127.0.0.1:7999/%(id)s/detection/%(enabled)s' % {
+ 'id': thread_id,
+ 'enabled': ['pause', 'start'][enabled]}
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to %(what)s motion detection for camera with id %(id)s: %(msg)s' % {
+ 'what': ['disable', 'enable'][enabled],
+ 'id': camera_id,
+ 'msg': utils.pretty_http_error(response)})
+
+ else:
+ logging.debug('successfully %(what)s motion detection for camera with id %(id)s' % {
+ 'what': ['disabled', 'enabled'][enabled],
+ 'id': camera_id})
+
+ request = HTTPRequest(url, connect_timeout=4, request_timeout=4)
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, on_response)
+
+
+def is_motion_detected(camera_id):
+ return _motion_detected.get(camera_id, False)
+
+
+def set_motion_detected(camera_id, motion_detected):
+ if motion_detected:
+ logging.debug('marking motion detected for camera with id %s' % camera_id)
+
+ else:
+ logging.debug('clearing motion detected for camera with id %s' % camera_id)
+
+ _motion_detected[camera_id] = motion_detected
+
+
+def camera_id_to_thread_id(camera_id):
+ # find the corresponding thread_id
+ # (which can be different from camera_id)
+ camera_ids = config.get_camera_ids()
+ thread_id = 0
+ for cid in camera_ids:
+ camera_config = config.get_camera(cid)
+ if utils.local_motion_camera(camera_config):
+ thread_id += 1
+
+ if cid == camera_id:
+ return thread_id or None
+
+ return None
+
+
+def thread_id_to_camera_id(thread_id):
+ # find the corresponding camera_id
+ # (which can be different from thread_id)
+ camera_ids = config.get_camera_ids()
+ tid = 0
+ for cid in camera_ids:
+ camera_config = config.get_camera(cid)
+ if utils.local_motion_camera(camera_config):
+ tid += 1
+ if tid == thread_id:
+ return cid
+
+ return None
+
+
+def _get_pid():
+ motion_pid_path = os.path.join(settings.RUN_PATH, 'motion.pid')
+
+ # read the pid from file
+ try:
+ with open(motion_pid_path, 'r') as f:
+ return int(f.readline().strip())
+
+ except (IOError, ValueError):
+ return None
--- /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)
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import os
+import subprocess
+
+
+def _find_prog(prog):
+ try:
+ return subprocess.check_output('which %s' % prog, shell=True).strip()
+
+ except subprocess.CalledProcessError: # not found
+ return None
+
+
+def _exec_prog(prog):
+ logging.info('executing "%s"' % prog)
+
+ return os.system(prog) == 0
+
+
+def shut_down():
+ logging.info('shutting down')
+
+ prog = _find_prog('poweroff')
+ if prog:
+ return _exec_prog(prog)
+
+ prog = _find_prog('shutdown')
+ if prog:
+ return _exec_prog(prog + ' -h now')
+
+ prog = _find_prog('systemctl')
+ if prog:
+ return _exec_prog(prog + ' poweroff')
+
+ prog = _find_prog('init')
+ if prog:
+ return _exec_prog(prog + ' 0')
+
+ return False
+
+
+def reboot():
+ logging.info('rebooting')
+
+ prog = _find_prog('reboot')
+ if prog:
+ return _exec_prog(prog)
+
+ prog = _find_prog('shutdown')
+ if prog:
+ return _exec_prog(prog + ' -r now')
+
+ prog = _find_prog('systemctl')
+ if prog:
+ return _exec_prog(prog + ' reboot')
+
+ prog = _find_prog('init')
+ if prog:
+ return _exec_prog(prog + ' 6')
+
+ return False
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import functools
+import json
+import logging
+import re
+
+from tornado.httpclient import AsyncHTTPClient, HTTPRequest
+
+import settings
+import utils
+
+_DOUBLE_SLASH_REGEX = re.compile('//+')
+
+
+def _make_request(scheme, host, port, username, password, uri, method='GET', data=None, query=None, timeout=None):
+ uri = _DOUBLE_SLASH_REGEX.sub('/', uri)
+ url = '%(scheme)s://%(host)s%(port)s%(uri)s' % {
+ 'scheme': scheme,
+ 'host': host,
+ 'port': ':' + str(port) if port else '',
+ 'uri': uri or ''}
+
+ query = dict(query or {})
+ query['_username'] = username or ''
+ query['_admin'] = 'true' # always use the admin account
+
+ if url.count('?'):
+ url += '&'
+
+ else:
+ url += '?'
+
+ url += '&'.join([(n + '=' + v) for (n, v) in query.iteritems()])
+ url += '&_signature=' + utils.compute_signature(method, url, data, password)
+
+ if timeout is None:
+ timeout = settings.REMOTE_REQUEST_TIMEOUT
+
+ return HTTPRequest(url, method, body=data, connect_timeout=timeout, request_timeout=timeout)
+
+
+def _callback_wrapper(callback):
+ @functools.wraps(callback)
+ def wrapper(response):
+ try:
+ decoded = json.loads(response.body)
+ if decoded['error'] == 'unauthorized':
+ response.error = 'Authentication Error'
+
+ elif decoded['error']:
+ response.error = decoded['error']
+
+ except:
+ pass
+
+ return callback(response)
+
+ return wrapper
+
+
+def pretty_camera_url(local_config, camera=True):
+ scheme = local_config.get('@scheme', local_config.get('scheme')) or 'http'
+ host = local_config.get('@host', local_config.get('host'))
+ port = local_config.get('@port', local_config.get('port'))
+ uri = local_config.get('@uri', local_config.get('uri')) or ''
+
+ url = scheme + '://' + host
+ if port and str(port) not in ['80', '443']:
+ url += ':' + str(port)
+
+ if uri:
+ url += uri
+
+ if url.endswith('/'):
+ url = url[:-1]
+
+ if camera:
+ if camera is True:
+ url += '/config/' + str(local_config.get('@remote_camera_id', local_config.get('remote_camera_id')))
+
+ else:
+ url += '/config/' + str(camera)
+
+ return url
+
+
+def _remote_params(local_config):
+ return (
+ local_config.get('@scheme', local_config.get('scheme')) or 'http',
+ local_config.get('@host', local_config.get('host')),
+ local_config.get('@port', local_config.get('port')),
+ local_config.get('@username', local_config.get('username')),
+ local_config.get('@password', local_config.get('password')),
+ local_config.get('@uri', local_config.get('uri')) or '',
+ local_config.get('@remote_camera_id', local_config.get('remote_camera_id')))
+
+
+def list(local_config, callback):
+ scheme, host, port, username, password, uri, _ = _remote_params(local_config)
+
+ logging.debug('listing remote cameras on %(url)s' % {
+ 'url': pretty_camera_url(local_config, camera=False)})
+
+ request = _make_request(scheme, host, port, username, password, uri + '/config/list/')
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to list remote cameras on %(url)s: %(msg)s' % {
+ 'url': pretty_camera_url(local_config, camera=False),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ try:
+ response = json.loads(response.body)
+
+ except Exception as e:
+ logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
+ 'url': pretty_camera_url(local_config, camera=False),
+ 'msg': unicode(e)})
+
+ return callback(error=unicode(e))
+
+ cameras = response['cameras']
+
+ # filter out simple mjpeg cameras
+ cameras = [c for c in cameras if c['proto'] != 'mjpeg']
+
+ callback(cameras)
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def get_config(local_config, callback):
+ scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
+
+ logging.debug('getting config 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, uri + '/config/%(id)s/get/' % {'id': camera_id})
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to get config for remote camera %(id)s on %(url)s: %(msg)s' % {
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ try:
+ response = json.loads(response.body)
+
+ except Exception as e:
+ logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
+ 'url': pretty_camera_url(local_config),
+ 'msg': unicode(e)})
+
+ return callback(error=unicode(e))
+
+ response['host'] = host
+ response['port'] = port
+
+ callback(response)
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def set_config(local_config, ui_config, callback):
+ scheme = local_config.get('@scheme', local_config.get('scheme'))
+ host = local_config.get('@host', local_config.get('host'))
+ port = local_config.get('@port', local_config.get('port'))
+ username = local_config.get('@username', local_config.get('username'))
+ password = local_config.get('@password', local_config.get('password'))
+ uri = local_config.get('@uri', local_config.get('uri')) or ''
+ camera_id = local_config.get('@remote_camera_id', local_config.get('remote_camera_id'))
+
+ logging.debug('setting config for remote camera %(id)s on %(url)s' % {
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config)})
+
+ ui_config = json.dumps(ui_config)
+
+ request = _make_request(scheme, host, port, username, password, uri + '/config/%(id)s/set/' % {'id': camera_id}, method='POST', data=ui_config)
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to set config for remote camera %(id)s on %(url)s: %(msg)s' % {
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ callback()
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def set_preview(local_config, controls, callback):
+ scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
+
+ logging.debug('setting preview for remote camera %(id)s on %(url)s' % {
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config)})
+
+ data = json.dumps(controls)
+
+ request = _make_request(scheme, host, port, username, password, uri + '/config/%(id)s/set_preview/' % {'id': camera_id}, method='POST', data=data)
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to set preview for remote camera %(id)s on %(url)s: %(msg)s' % {
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ callback()
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def get_current_picture(local_config, width, height, callback):
+ scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
+
+ logging.debug('getting current picture for remote camera %(id)s on %(url)s' % {
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config)})
+
+ query = {}
+
+ if width:
+ query['width'] = str(width)
+
+ if height:
+ query['height'] = str(height)
+
+ request = _make_request(scheme, host, port, username, password, uri + '/picture/%(id)s/current/' % {'id': camera_id}, query=query)
+
+ def on_response(response):
+ motion_detected = False
+
+ cookies = response.headers.get('Set-Cookie')
+ if cookies:
+ cookies = cookies.split(';')
+ cookies = [[i.strip() for i in c.split('=')] for c in cookies]
+ cookies = dict([c for c in cookies if len(c) == 2])
+ motion_detected = cookies.get('motion_detected_' + str(camera_id)) == 'true'
+
+ if response.error:
+ logging.error('failed to get current picture for remote camera %(id)s on %(url)s: %(msg)s' % {
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ callback(motion_detected, response.body)
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def list_media(local_config, media_type, prefix, callback):
+ scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
+
+ logging.debug('getting media list for remote camera %(id)s on %(url)s' % {
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config)})
+
+ query = {}
+ if prefix is not None:
+ 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, uri + '/%(media_type)s/%(id)s/list/' % {
+ 'id': camera_id, 'media_type': media_type}, query=query, timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to get media list for remote camera %(id)s on %(url)s: %(msg)s' % {
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ try:
+ response = json.loads(response.body)
+
+ except Exception as e:
+ logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
+ 'url': pretty_camera_url(local_config),
+ 'msg': unicode(e)})
+
+ return callback(error=unicode(e))
+
+ return callback(response)
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def get_media_content(local_config, filename, media_type, callback):
+ scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
+
+ logging.debug('downloading file %(filename)s of remote camera %(id)s on %(url)s' % {
+ 'filename': filename,
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config)})
+
+ uri += '/%(media_type)s/%(id)s/download/%(filename)s' % {
+ 'media_type': media_type,
+ 'id': camera_id,
+ 'filename': filename}
+
+ # 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, uri, timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to download file %(filename)s of remote camera %(id)s on %(url)s: %(msg)s' % {
+ 'filename': filename,
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ return callback(response.body)
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def make_zipped_content(local_config, media_type, group, callback):
+ scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
+
+ logging.debug('preparing zip file for group %(group)s of remote camera %(id)s on %(url)s' % {
+ 'group': group,
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config)})
+
+ prepare_uri = uri + '/%(media_type)s/%(id)s/zipped/%(group)s/' % {
+ 'media_type': media_type,
+ 'id': camera_id,
+ 'group': group}
+
+ # timeout here is 100 times larger than usual - we expect a big delay
+ request = _make_request(scheme, host, port, username, password, prepare_uri, 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' % {
+ 'group': group,
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ try:
+ key = json.loads(response.body)['key']
+
+ except Exception as e:
+ logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
+ 'url': pretty_camera_url(local_config),
+ 'msg': unicode(e)})
+
+ return callback(error=unicode(e))
+
+ callback({'key': key})
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def get_zipped_content(local_config, media_type, key, group, callback):
+ scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
+
+ 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, uri + '/%(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)
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to download zip file for remote camera %(id)s on %(url)s: %(msg)s' % {
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ callback({
+ 'data': response.body,
+ 'content_type': response.headers.get('Content-Type'),
+ 'content_disposition': response.headers.get('Content-Disposition')
+ })
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def make_timelapse_movie(local_config, framerate, interval, group, callback):
+ scheme, host, port, username, password, uri, 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' % {
+ 'group': group,
+ 'id': camera_id,
+ 'framerate': framerate,
+ 'int': interval,
+ 'url': pretty_camera_url(local_config)})
+
+ uri += '/picture/%(id)s/timelapse/%(group)s/?interval=%(int)s&framerate=%(framerate)s' % {
+ 'id': camera_id,
+ 'int': interval,
+ 'framerate': framerate,
+ 'group': group}
+
+ request = _make_request(scheme, host, port, username, password, uri, 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' % {
+ 'group': group,
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'int': interval,
+ 'framerate': framerate,
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ try:
+ response = json.loads(response.body)
+
+ except Exception as e:
+ logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
+ 'url': pretty_camera_url(local_config),
+ 'msg': unicode(e)})
+
+ return callback(error=unicode(e))
+
+ callback(response)
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def check_timelapse_movie(local_config, group, callback):
+ scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
+
+ 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, uri + '/picture/%(id)s/timelapse/%(group)s/?check=true' % {
+ 'id': camera_id,
+ 'group': group})
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to check timelapse movie status for remote camera %(id)s on %(url)s: %(msg)s' % {
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ try:
+ response = json.loads(response.body)
+
+ except Exception as e:
+ logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
+ 'url': pretty_camera_url(local_config),
+ 'msg': unicode(e)})
+
+ return callback(error=unicode(e))
+
+ callback(response)
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def get_timelapse_movie(local_config, key, group, callback):
+ scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
+
+ 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, uri + '/picture/%(id)s/timelapse/%(group)s/?key=%(key)s' % {
+ 'id': camera_id,
+ 'group': group,
+ 'key': key},
+ timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to download timelapse movie for remote camera %(id)s on %(url)s: %(msg)s' % {
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ callback({
+ 'data': response.body,
+ 'content_type': response.headers.get('Content-Type'),
+ 'content_disposition': response.headers.get('Content-Disposition')
+ })
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def get_media_preview(local_config, filename, media_type, width, height, callback):
+ scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
+
+ logging.debug('getting file preview for %(filename)s of remote camera %(id)s on %(url)s' % {
+ 'filename': filename,
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config)})
+
+ uri += '/%(media_type)s/%(id)s/preview/%(filename)s' % {
+ 'media_type': media_type,
+ 'id': camera_id,
+ 'filename': filename}
+
+ query = {}
+
+ if width:
+ query['width'] = str(width)
+
+ if height:
+ query['height'] = str(height)
+
+ request = _make_request(scheme, host, port, username, password, uri, query=query)
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to get file preview for %(filename)s of remote camera %(id)s on %(url)s: %(msg)s' % {
+ 'filename': filename,
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ callback(response.body)
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def del_media_content(local_config, filename, media_type, callback):
+ scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
+
+ logging.debug('deleting file %(filename)s of remote camera %(id)s on %(url)s' % {
+ 'filename': filename,
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config)})
+
+ uri += '/%(media_type)s/%(id)s/delete/%(filename)s' % {
+ 'media_type': media_type,
+ 'id': camera_id,
+ 'filename': filename}
+
+ request = _make_request(scheme, host, port, username, password, uri, method='POST', data='{}', timeout=settings.REMOTE_REQUEST_TIMEOUT)
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to delete file %(filename)s of remote camera %(id)s on %(url)s: %(msg)s' % {
+ 'filename': filename,
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ callback()
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
+
+
+def del_media_group(local_config, group, media_type, callback):
+ scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
+
+ logging.debug('deleting group %(group)s of remote camera %(id)s on %(url)s' % {
+ 'group': group,
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config)})
+
+ uri += '/%(media_type)s/%(id)s/delete_all/%(group)s/' % {
+ 'media_type': media_type,
+ 'id': camera_id,
+ 'group': group}
+
+ request = _make_request(scheme, host, port, username, password, uri, method='POST', data='{}', timeout=settings.REMOTE_REQUEST_TIMEOUT)
+
+ def on_response(response):
+ if response.error:
+ logging.error('failed to delete group %(group)s of remote camera %(id)s on %(url)s: %(msg)s' % {
+ 'group': group,
+ 'id': camera_id,
+ 'url': pretty_camera_url(local_config),
+ 'msg': utils.pretty_http_error(response)})
+
+ return callback(error=utils.pretty_http_error(response))
+
+ callback()
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, _callback_wrapper(on_response))
--- /dev/null
+#!/usr/bin/env python
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+import logging
+import os
+import re
+import smtplib
+import socket
+import sys
+import time
+
+from email import Encoders
+from email.mime.text import MIMEText
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEBase import MIMEBase
+from tornado.ioloop import IOLoop
+
+import settings
+
+from motioneye import _configure_settings, _configure_logging, _configure_signals
+
+_configure_settings()
+_configure_signals()
+_configure_logging(module='sendmail')
+
+import config
+import mediafiles
+import tzctl
+
+
+messages = {
+ 'motion_start': 'Motion has been detected by camera "%(camera)s/%(hostname)s" at %(moment)s (%(timezone)s).'
+}
+
+subjects = {
+ 'motion_start': 'motionEye: motion detected by "%(camera)s"'
+}
+
+
+def send_mail(server, port, account, password, tls, to, subject, message, files):
+ conn = smtplib.SMTP(server, port, timeout=getattr(settings, 'SMTP_TIMEOUT', 60))
+ if tls:
+ conn.starttls()
+
+ if account and password:
+ conn.login(account, password)
+
+ _from = account or 'motioneye@' + socket.gethostname()
+
+ email = MIMEMultipart()
+ email['Subject'] = subject
+ email['From'] = _from
+ email['To'] = ', '.join(to)
+ email.attach(MIMEText(message))
+
+ for file in reversed(files):
+ part = MIMEBase('image', 'jpeg')
+ with open(file, 'rb') as f:
+ part.set_payload(f.read())
+
+ Encoders.encode_base64(part)
+ part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file))
+ email.attach(part)
+
+ if files:
+ logging.debug('attached %d pictures' % len(files))
+
+ logging.debug('sending email message')
+ conn.sendmail(_from, to, email.as_string())
+ conn.quit()
+
+
+def make_message(subject, message, camera_id, moment, timespan, callback):
+ camera_config = config.get_camera(camera_id)
+
+ def on_media_files(media_files):
+ logging.debug('got media files')
+
+ timestamp = time.mktime(moment.timetuple())
+
+ 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]
+
+ logging.debug('selected %d pictures' % len(media_files))
+
+ format_dict = {
+ 'camera': camera_config['@name'],
+ 'hostname': socket.gethostname(),
+ 'moment': moment.strftime('%Y-%m-%d %H:%M:%S'),
+ }
+
+ if settings.LOCAL_TIME_FILE:
+ format_dict['timezone'] = tzctl._get_time_zone()
+
+ else:
+ format_dict['timezone'] = 'local time'
+
+ m = message % format_dict
+ s = subject % format_dict
+ s = s.replace('\n', ' ')
+
+ m += '\n\n'
+ m += 'motionEye.'
+
+ callback(s, m, media_files)
+
+ if not timespan:
+ return on_media_files([])
+
+ logging.debug('creating email message')
+
+ time.sleep(timespan) # give motion some time to create motion pictures
+ mediafiles.list_media(camera_config, media_type='picture', callback=on_media_files)
+
+
+def print_usage():
+ print 'Usage: sendmail.py <server> <port> <account> <password> <tls> <to> <msg_id> <camera_id> <moment> <frames> [timespan]'
+
+
+if __name__ == '__main__':
+ if len(sys.argv) < 10:
+ print_usage()
+ sys.exit(-1)
+
+ server = sys.argv[1]
+ port = int(sys.argv[2])
+ account = sys.argv[3]
+ password = sys.argv[4]
+ tls = sys.argv[5].lower() == 'true'
+ to = sys.argv[6]
+ msg_id = sys.argv[7]
+ camera_id = sys.argv[8]
+ moment = sys.argv[9]
+ try:
+ timespan = int(sys.argv[10])
+
+ except:
+ timespan = 0
+
+ logging.debug('hello!')
+
+ message = messages.get(msg_id)
+ subject = subjects.get(msg_id)
+ if not message or not subject:
+ logging.error('unknown message id')
+ sys.exit(-1)
+
+ moment = datetime.datetime.strptime(moment, '%Y-%m-%dT%H:%M:%S')
+
+ logging.debug('server = %s' % server)
+ logging.debug('port = %s' % port)
+ logging.debug('account = %s' % account)
+ logging.debug('password = ******')
+ logging.debug('server = %s' % server)
+ logging.debug('tls = %s' % tls)
+ logging.debug('to = %s' % to)
+ logging.debug('msg_id = %s' % msg_id)
+ logging.debug('camera_id = %s' % camera_id)
+ logging.debug('moment = %s' % moment.strftime('%Y-%m-%d %H:%M:%S'))
+ logging.debug('smtp timeout = %d' % settings.SMTP_TIMEOUT)
+ logging.debug('timespan = %d' % timespan)
+
+ if not to:
+ logging.info('no email address specified')
+ sys.exit(0)
+
+ to = [t.strip() for t in re.split('[,;| ]', to)]
+ to = [t for t in to if t]
+
+ io_loop = IOLoop.instance()
+
+ def on_message(subject, message, files):
+ try:
+ send_mail(server, port, account, password, tls, to, subject, message, files)
+ logging.info('email sent')
+
+ except Exception as e:
+ logging.error('failed to send mail: %s' % e, exc_info=True)
+
+ io_loop.stop()
+
+ def ioloop_timeout():
+ io_loop.stop()
+
+ make_message(subject, message, camera_id, moment, timespan, on_message)
+
+ io_loop.add_timeout(datetime.timedelta(seconds=settings.SMTP_TIMEOUT), ioloop_timeout)
+ io_loop.start()
+
+ logging.debug('bye!')
\ No newline at end of file
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+import multiprocessing
+import os.path
+import signal
+import sys
+
+from tornado.httpclient import AsyncHTTPClient
+from tornado.web import Application
+
+import handlers
+import logging
+import settings
+import template
+
+
+def load_settings():
+ # TODO use optparse
+# length = len(sys.argv) - 1
+# for i in xrange(length):
+# arg = sys.argv[i + 1]
+#
+# if not arg.startswith('--'):
+# continue
+#
+# next_arg = None
+# if i < length - 1:
+# next_arg = sys.argv[i + 2]
+#
+# name = arg[2:].upper().replace('-', '_')
+#
+# if hasattr(settings, name):
+# curr_value = getattr(settings, name)
+#
+# if next_arg.lower() == 'debug':
+# next_arg = logging.DEBUG
+#
+# elif next_arg.lower() == 'info':
+# next_arg = logging.INFO
+#
+# elif next_arg.lower() == 'warn':
+# next_arg = logging.WARN
+#
+# elif next_arg.lower() == 'error':
+# next_arg = logging.ERROR
+#
+# elif next_arg.lower() == 'fatal':
+# next_arg = logging.FATAL
+#
+# elif next_arg.lower() == 'true':
+# next_arg = True
+#
+# elif next_arg.lower() == 'false':
+# next_arg = False
+#
+# elif isinstance(curr_value, int):
+# next_arg = int(next_arg)
+#
+# elif isinstance(curr_value, float):
+# next_arg = float(next_arg)
+#
+# setattr(settings, name, next_arg)
+#
+# else:
+# return arg[2:]
+
+ if not os.path.exists(settings.CONF_PATH):
+ logging.fatal('config directory "%s" does not exist' % settings.CONF_PATH)
+ sys.exit(-1)
+
+ if not os.path.exists(settings.RUN_PATH):
+ logging.fatal('pid directory "%s" does not exist' % settings.RUN_PATH)
+ sys.exit(-1)
+
+ if not os.path.exists(settings.LOG_PATH):
+ logging.fatal('log directory "%s" does not exist' % settings.LOG_PATH)
+ sys.exit(-1)
+
+ if not os.path.exists(settings.MEDIA_PATH):
+ logging.fatal('media directory "%s" does not exist' % settings.MEDIA_PATH)
+ sys.exit(-1)
+
+
+def configure_signals():
+ def bye_handler(signal, frame):
+ import tornado.ioloop
+
+ logging.info('interrupt signal received, shutting down...')
+
+ # shut down the IO loop if it has been started
+ ioloop = tornado.ioloop.IOLoop.instance()
+ ioloop.stop()
+
+ def child_handler(signal, frame):
+ # this is required for the multiprocessing mechanism to work
+ multiprocessing.active_children()
+
+ signal.signal(signal.SIGINT, bye_handler)
+ signal.signal(signal.SIGTERM, bye_handler)
+ signal.signal(signal.SIGCHLD, child_handler)
+
+
+def configure_logging(module=None):
+ if module:
+ format = '%(asctime)s: [{module}] %(levelname)s: %(message)s'.format(module=module)
+
+ else:
+ format = '%(asctime)s: %(levelname)s: %(message)s'
+
+ logging.basicConfig(filename=None, level=settings.LOG_LEVEL,
+ format=format, datefmt='%Y-%m-%d %H:%M:%S')
+
+ logging.getLogger('tornado').setLevel(logging.WARN)
+
+
+def configure_tornado():
+ AsyncHTTPClient.configure('tornado.curl_httpclient.CurlAsyncHTTPClient', max_clients=16)
+
+
+def test_requirements():
+ if os.geteuid() != 0:
+ if settings.SMB_SHARES:
+ print('SMB_SHARES require root privileges')
+ return False
+
+ if settings.ENABLE_REBOOT:
+ print('reboot requires root privileges')
+ return False
+
+ try:
+ import tornado # @UnusedImport
+
+ except ImportError:
+ logging.fatal('please install tornado version 3.1 or greater')
+ sys.exit(-1)
+
+ try:
+ import jinja2 # @UnusedImport
+
+ except ImportError:
+ logging.fatal('please install jinja2')
+ sys.exit(-1)
+
+ try:
+ import PIL.Image # @UnusedImport
+
+ except ImportError:
+ logging.fatal('please install pillow or PIL')
+ sys.exit(-1)
+
+ try:
+ import pycurl # @UnusedImport
+
+ except ImportError:
+ logging.fatal('please install pycurl')
+ sys.exit(-1)
+
+ import mediafiles
+ has_ffmpeg = mediafiles.find_ffmpeg() is not None
+
+ import motionctl
+ has_motion = motionctl.find_motion() is not None
+
+ import v4l2ctl
+ has_v4lutils = v4l2ctl.find_v4l2_ctl() is not None
+
+ import smbctl
+ if settings.SMB_SHARES and smbctl.find_mount_cifs() is None:
+ logging.fatal('please install cifs-utils')
+ sys.exit(-1)
+
+ if not has_ffmpeg:
+ logging.info('ffmpeg not installed')
+
+ if not has_motion:
+ logging.info('motion not installed')
+
+ if not has_v4lutils:
+ logging.info('v4l-utils not installed')
+
+
+def start_motion():
+ import tornado.ioloop
+ import config
+ import motionctl
+
+ ioloop = tornado.ioloop.IOLoop.instance()
+
+ # add a motion running checker
+ def checker():
+ if ioloop._stopped:
+ return
+
+ if not motionctl.running() and motionctl.started() and config.get_enabled_local_motion_cameras():
+ try:
+ logging.error('motion not running, starting it')
+ motionctl.start()
+
+ except Exception as e:
+ logging.error('failed to start motion: %(msg)s' % {
+ 'msg': unicode(e)}, exc_info=True)
+
+ ioloop.add_timeout(datetime.timedelta(seconds=settings.MOTION_CHECK_INTERVAL), checker)
+
+ motionctl.start()
+
+ ioloop.add_timeout(datetime.timedelta(seconds=settings.MOTION_CHECK_INTERVAL), checker)
+
+
+def start_cleanup():
+ import cleanup
+
+ cleanup.start()
+ logging.info('cleanup started')
+
+
+def start_wsswitch():
+ import wsswitch
+
+ wsswitch.start()
+ logging.info('wsswitch started')
+
+
+def start_thumbnailer():
+ import thumbnailer
+
+ thumbnailer.start()
+ logging.info('thumbnailer started')
+
+
+def run_server():
+ import cleanup
+ import motionctl
+ import thumbnailer
+ import tornado.ioloop
+ import smbctl
+
+ application.listen(settings.PORT, settings.LISTEN)
+ logging.info('server started')
+
+ tornado.ioloop.IOLoop.instance().start()
+
+ logging.info('server stopped')
+
+ if thumbnailer.running():
+ thumbnailer.stop()
+ logging.info('thumbnailer stopped')
+
+ if cleanup.running():
+ cleanup.stop()
+ logging.info('cleanup stopped')
+
+ if motionctl.running():
+ motionctl.stop()
+ logging.info('motion stopped')
+
+ if settings.SMB_SHARES:
+ smbctl.umount_all()
+ logging.info('SMB shares unmounted')
+
+
+def log_request(handler):
+ if handler.get_status() < 400:
+ log_method = logging.debug
+
+ elif handler.get_status() < 500:
+ log_method = logging.warning
+
+ else:
+ log_method = logging.error
+
+ request_time = 1000.0 * handler.request.request_time()
+ log_method("%d %s %.2fms", handler.get_status(),
+ handler._request_summary(), request_time)
+
+
+application = Application(
+ [
+ (r'^/$', handlers.MainHandler),
+ (r'^/config/main/(?P<op>set|get)/?$', handlers.ConfigHandler),
+ (r'^/config/(?P<camera_id>\d+)/(?P<op>get|set|rem|set_preview)/?$', handlers.ConfigHandler),
+ (r'^/config/(?P<op>add|list|backup|restore)/?$', handlers.ConfigHandler),
+ (r'^/picture/(?P<camera_id>\d+)/(?P<op>current|list|frame)/?$', handlers.PictureHandler),
+ (r'^/picture/(?P<camera_id>\d+)/(?P<op>download|preview|delete)/(?P<filename>.+?)/?$', handlers.PictureHandler),
+ (r'^/picture/(?P<camera_id>\d+)/(?P<op>zipped|timelapse|delete_all)/(?P<group>.+?)/?$', handlers.PictureHandler),
+ (r'^/movie/(?P<camera_id>\d+)/(?P<op>list)/?$', handlers.MovieHandler),
+ (r'^/movie/(?P<camera_id>\d+)/(?P<op>download|preview|delete)/(?P<filename>.+?)/?$', handlers.MovieHandler),
+ (r'^/movie/(?P<camera_id>\d+)/(?P<op>delete_all)/(?P<group>.+?)/?$', handlers.MovieHandler),
+ (r'^/_relay_event/?$', handlers.RelayEventHandler),
+ (r'^/log/(?P<name>\w+)/?$', handlers.LogHandler),
+ (r'^/update/?$', handlers.UpdateHandler),
+ (r'^/power/(?P<op>shutdown|reboot)/?$', handlers.PowerHandler),
+ (r'^/version/?$', handlers.VersionHandler),
+ (r'^/login/?$', handlers.LoginHandler),
+ (r'^.*$', handlers.NotFoundHandler),
+ ],
+ debug=False,
+ log_function=log_request,
+ static_path=settings.STATIC_PATH,
+ static_url_prefix=settings.STATIC_URL
+)
+
+template.add_context('STATIC_URL', settings.STATIC_URL)
+
+
+def main():
+ import motioneye
+
+ load_settings()
+ configure_signals()
+ configure_logging()
+ test_requirements()
+ configure_tornado()
+
+ logging.info('hello! this is motionEye %s' % motioneye.VERSION)
+
+ if settings.SMB_SHARES:
+ import smbctl
+
+ stop, start = smbctl.update_mounts() # @UnusedVariable
+ if start:
+ start_motion()
+
+ else:
+ start_motion()
+
+ start_cleanup()
+ start_wsswitch()
+
+ if settings.THUMBNAILER_INTERVAL:
+ start_thumbnailer()
+
+ run_server()
+
+ logging.info('bye!')
--- /dev/null
+
+import logging
+import os.path
+import sys
+
+import motioneye
+
+# the root directory of the project
+PROJECT_PATH = os.path.dirname(motioneye.__file__)
+
+# the templates directory
+TEMPLATE_PATH = os.path.join(PROJECT_PATH, 'templates')
+
+# the static files directory
+STATIC_PATH = os.path.join(PROJECT_PATH, 'static')
+
+# static files (.css, .js etc) are served at this root url
+STATIC_URL = '/static/'
+
+# path to the config directory; must be writable
+CONF_PATH = [sys.prefix, ''][sys.prefix == '/usr'] + '/etc/motioneye'
+
+# pid files go here
+for d in ['/run', '/var/run', '/tmp', '/var/tmp']:
+ if os.path.exists(d):
+ RUN_PATH = d
+ break
+
+else:
+ RUN_PATH = PROJECT_PATH
+
+# log files go here
+for d in ['/log', '/var/log', '/tmp', '/var/tmp']:
+ if os.path.exists(d):
+ LOG_PATH = d
+ break
+
+else:
+ LOG_PATH = RUN_PATH
+
+# default output path for media files
+MEDIA_PATH = RUN_PATH
+
+# path to motion binary (automatically detected if not set)
+MOTION_BINARY = None
+
+# the log level
+LOG_LEVEL = logging.INFO
+
+# IP addresses to listen on
+LISTEN = '0.0.0.0'
+
+# the TCP port to listen on
+PORT = 8765
+
+# interval in seconds at which motionEye checks the SMB mounts
+MOUNT_CHECK_INTERVAL = 300
+
+# interval in seconds at which motionEye checks if motion is running
+MOTION_CHECK_INTERVAL = 10
+
+# interval in seconds at which the janitor is called to remove old pictures and movies
+CLEANUP_INTERVAL = 43200
+
+# interval in seconds at which the thumbnail mechanism runs (set to 0 to disable)
+THUMBNAILER_INTERVAL = 60
+
+# timeout in seconds when waiting for response from a remote motionEye server
+REMOTE_REQUEST_TIMEOUT = 10
+
+# timeout in seconds when waiting for mjpg data from the motion daemon
+MJPG_CLIENT_TIMEOUT = 10
+
+# timeout in seconds after which an idle mjpg client is removed (set to 0 to disable)
+MJPG_CLIENT_IDLE_TIMEOUT = 10
+
+# enable SMB shares (requires root)
+SMB_SHARES = False
+
+# the directory where the SMB mounts will be created
+SMB_MOUNT_ROOT = '/media'
+
+# path to a wpa_supplicant.conf file if wifi settings UI is desired
+WPA_SUPPLICANT_CONF = None
+
+# path to a localtime file if time zone settings UI is desired
+LOCAL_TIME_FILE = None
+
+# enables shutdown and rebooting after changing system settings (such as wifi settings or system updates)
+ENABLE_REBOOT = False
+
+# the timeout in seconds to use when talking to a SMTP server
+SMTP_TIMEOUT = 60
+
+# the time to wait for zip file creation
+ZIP_TIMEOUT = 500
+
+# enable adding and removing cameras from UI
+ADD_REMOVE_CAMERAS = True
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+import logging
+import os
+import re
+import subprocess
+import time
+
+from tornado import ioloop
+
+import config
+import settings
+
+
+def find_mount_cifs():
+ try:
+ return subprocess.check_output('which mount.cifs', shell=True).strip()
+
+ except subprocess.CalledProcessError: # not found
+ return None
+
+
+def make_mount_point(server, share, username):
+ server = re.sub('[^a-zA-Z0-9]', '_', server).lower()
+ share = re.sub('[^a-zA-Z0-9]', '_', share).lower()
+
+ if username:
+ username = re.sub('[^a-zA-Z0-9]', '_', username).lower()
+ mount_point = os.path.join(settings.SMB_MOUNT_ROOT, 'motioneye_%s_%s_%s' % (server, share, username))
+
+ else:
+ mount_point = os.path.join(settings.SMB_MOUNT_ROOT, 'motioneye_%s_%s' % (server, share))
+
+ return mount_point
+
+
+def _is_motioneye_mount(mount_point):
+ mount_point_root = os.path.join(settings.SMB_MOUNT_ROOT, 'motioneye_')
+ return bool(re.match('^' + mount_point_root + '\w+$', mount_point))
+
+
+def list_mounts():
+ logging.debug('listing smb mounts...')
+
+ mounts = []
+ with open('/proc/mounts', 'r') as f:
+ for line in f:
+ line = line.strip()
+ if not line:
+ continue
+ parts = line.split()
+ if len(parts) < 4:
+ continue
+
+ target = parts[0]
+ mount_point = parts[1]
+ fstype = parts[2]
+ opts = ' '.join(parts[3:])
+
+ if fstype != 'cifs':
+ continue
+
+ if not _is_motioneye_mount(mount_point):
+ continue
+
+ match = re.match('//([^/]+)/(.+)', target)
+ if not match:
+ continue
+
+ if len(match.groups()) != 2:
+ continue
+
+ server, share = match.groups()
+ share = share.replace('\\040', ' ') # spaces are reported oddly by /proc/mounts
+
+ match = re.search('username=([\w\s]+)', opts)
+ if match:
+ username = match.group(1)
+
+ else:
+ username = None
+
+ logging.debug('found smb mount "//%s/%s" at "%s"' % (server, share, mount_point))
+
+ mounts.append({
+ 'server': server,
+ 'share': share,
+ 'username': username,
+ 'mount_point': mount_point
+ })
+
+ return mounts
+
+
+def mount(server, share, username, password):
+ mount_point = make_mount_point(server, share, username)
+
+ logging.debug('making sure mount point "%s" exists' % mount_point)
+
+ if not os.path.exists(mount_point):
+ os.makedirs(mount_point)
+
+ if username:
+ opts = 'username=%s,password=%s' % (username, password)
+ sec_types = ['ntlm', 'ntlmv2', 'none']
+
+ else:
+ opts = 'guest'
+ sec_types = ['none', 'ntlm', 'ntlmv2']
+
+ for sec in sec_types:
+ actual_opts = opts + ',sec=' + sec
+ try:
+ logging.debug('mounting "//%s/%s" at "%s" (sec=%s)' % (server, share, mount_point, sec))
+ subprocess.check_call('mount.cifs "//%s/%s" "%s" -o "%s"' % (server, share, mount_point, actual_opts), shell=True)
+ break
+
+ except subprocess.CalledProcessError:
+ pass
+
+ else:
+ logging.error('failed to mount smb share "//%s/%s" at "%s"' % (server, share, mount_point))
+ return None
+
+ # test to see if mount point is writable
+ try:
+ path = os.path.join(mount_point, '.motioneye_' + str(int(time.time())))
+ os.mkdir(path)
+ os.rmdir(path)
+ logging.debug('directory at "%s" is writable' % mount_point)
+
+ except:
+ logging.error('directory at "%s" is not writable' % mount_point)
+
+ return None
+
+ return mount_point
+
+
+def umount(server, share, username):
+ mount_point = make_mount_point(server, share, username)
+ logging.debug('unmounting "//%s/%s" from "%s"' % (server, share, mount_point))
+
+ try:
+ subprocess.check_call('umount "%s"' % mount_point, shell=True)
+
+ except subprocess.CalledProcessError:
+ logging.error('failed to unmount smb share "//%s/%s" from "%s"' % (server, share, mount_point))
+
+ return False
+
+ try:
+ os.rmdir(mount_point)
+
+ except Exception as e:
+ logging.error('failed to remove smb mount point "%s": %s' % (mount_point, e))
+
+ return False
+
+ return True
+
+
+def update_mounts():
+ network_shares = config.get_network_shares()
+
+ 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
+ for network_share in network_shares:
+ key = (network_share['server'], network_share['share'], network_share['username'] or '')
+ if key in mounts: # found
+ mounts[key] = True
+
+ else: # needs to be mounted
+ should_stop = True
+ if not mount(network_share['server'], network_share['share'], network_share['username'], network_share['password']):
+ should_start = False
+
+ # unmount the no longer necessary mounts
+ for (server, share, username), required in mounts.items():
+ if not required:
+ umount(server, share, username)
+ should_stop = True
+
+ return (should_stop, should_start)
+
+
+def umount_all():
+ for mount in list_mounts():
+ umount(mount['server'], mount['share'], mount['username'])
+
+
+def _check_mounts():
+ import motionctl
+
+ logging.debug('checking SMB mounts...')
+
+ stop, start = update_mounts()
+ if stop:
+ motionctl.stop()
+
+ if start:
+ motionctl.start()
+
+ io_loop = ioloop.IOLoop.instance()
+ io_loop.add_timeout(datetime.timedelta(seconds=settings.MOUNT_CHECK_INTERVAL), _check_mounts)
+
+
+if settings.SMB_SHARES:
+ # schedule the mount checker
+ io_loop = ioloop.IOLoop.instance()
+ io_loop.add_timeout(datetime.timedelta(seconds=settings.MOUNT_CHECK_INTERVAL), _check_mounts)
--- /dev/null
+
+
+ /* basic */
+
+body {
+ color: #dddddd;
+ background-color: #212121;
+}
+
+
+ /* camera frame */
+
+div.camera-frame {
+ position: relative;
+ padding: 0px;
+ margin: 0px;
+ width: 100%;
+ height: 100%;
+}
+
+div.camera-container {
+ height: 100%;
+ text-align: center;
+ overflow: hidden;
+}
+
+img.camera {
+ height: auto;
+ margin: auto;
+}
+
+img.camera.error,
+img.camera.loading {
+ height: 100% !important;
+}
+
+div.camera-placeholder {
+ overflow: hidden;
+}
+
+div.camera-progress {
+ cursor: default;
+}
--- /dev/null
+\r
+.ui-timepicker-wrapper {\r
+ overflow-y: auto;\r
+ height: 150px;\r
+ width: 6.5em;\r
+ background: #414141;\r
+ border: 1px solid #515151;\r
+ -webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);\r
+ -moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);\r
+ box-shadow:0 5px 10px rgba(0,0,0,0.2);\r
+ outline: none;\r
+ z-index: 10001;\r
+ margin: 0;\r
+}\r
+\r
+.ui-timepicker-wrapper.ui-timepicker-with-duration {\r
+ width: 11em;\r
+}\r
+\r
+.ui-timepicker-list {\r
+ margin: 0;\r
+ padding: 0;\r
+ list-style: none;\r
+}\r
+\r
+.ui-timepicker-duration {\r
+ margin-left: 5px; color: #888;\r
+}\r
+\r
+.ui-timepicker-list:hover .ui-timepicker-duration {\r
+ color: #888;\r
+}\r
+\r
+.ui-timepicker-list li {\r
+ padding: 3px 0 3px 5px;\r
+ cursor: pointer;\r
+ white-space: nowrap;\r
+ color: white;\r
+ list-style: none;\r
+ font-size: 0.8em;\r
+ margin: 0;\r
+}\r
+\r
+.ui-timepicker-list:hover .ui-timepicker-selected {\r
+ background: #aaa; color: black;\r
+}\r
+\r
+li.ui-timepicker-selected,\r
+.ui-timepicker-list li:hover,\r
+.ui-timepicker-list .ui-timepicker-selected:hover {\r
+ background: #1980EC; color: #fff;\r
+}\r
+\r
+li.ui-timepicker-selected .ui-timepicker-duration,\r
+.ui-timepicker-list li:hover .ui-timepicker-duration {\r
+ color: #ccc;\r
+}\r
+\r
+.ui-timepicker-list li.ui-timepicker-disabled,\r
+.ui-timepicker-list li.ui-timepicker-disabled:hover,\r
+.ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {\r
+ color: #888;\r
+ cursor: default;\r
+}\r
+\r
+.ui-timepicker-list li.ui-timepicker-disabled:hover,\r
+.ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {\r
+ background: #f2f2f2;\r
+}\r
--- /dev/null
+
+
+ /* basic */
+
+* {
+ padding: 0px;
+ border: 0px solid black;
+ margin: 0px;
+ outline: 0px;
+ border-spacing: 0px;
+ border-collapse: separate;
+}
+
+html {
+ height: 100%;
+}
+
+body {
+ height: 100%;
+ color: #dddddd;
+ font-size: 22px;
+ background-color: #212121;
+}
+
+select,
+input[type=text],
+input[type=password],
+textarea {
+ box-sizing: border-box;
+}
+
+
+ /* fonts */
+
+@font-face {
+ font-family: 'Maven Pro';
+ src: url('../fnt/mavenpro-regular-webfont.eot');
+ src: url('../fnt/mavenpro-regular-webfont.eot?#iefix') format('embedded-opentype'),
+ url('../fnt/mavenpro-regular-webfont.woff') format('woff'),
+ url('../fnt/mavenpro-regular-webfont.ttf') format('truetype'),
+ url('../fnt/mavenpro-regular-webfont.svg#maven_proregular') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Maven Pro';
+ src: url('../fnt/mavenpro-bold-webfont.eot');
+ src: url('../fnt/mavenpro-bold-webfont.eot?#iefix') format('embedded-opentype'),
+ url('../fnt/mavenpro-bold-webfont.woff') format('woff'),
+ url('../fnt/mavenpro-bold-webfont.ttf') format('truetype'),
+ url('../fnt/mavenpro-bold-webfont.svg#maven_probold') format('svg');
+ font-weight: bold;
+ font-style: normal;
+}
+
+
+ /* layout */
+
+html {
+ font-family: 'Maven Pro';
+}
+
+div.page,
+div.header-container {
+ position: relative;
+ min-width: 320px;
+ width: 100%;
+}
+
+div.page {
+ font-size: 1em;
+ transition: all 0.5s linear;
+ min-height: 100%;
+}
+
+div.header {
+ background-color: rgba(64, 64, 64, 0.5);
+ box-shadow: 0px 0px 5px rgba(0,0,0,0.3);
+ top: 0px;
+ width: 100%;
+ height: 50px;
+ position: fixed;
+ overflow: hidden;
+ z-index: 10000;
+}
+
+div.header-container {
+ transition: all 0.5s linear;
+}
+
+div.footer {
+ position: absolute;
+ bottom: 5px;
+ width: 100%;
+ height: 3em;
+ font-size: 0.7em;
+ color: #aaa;
+ text-align: center;
+}
+
+div.copyright-note {
+ border-top: 1px solid #333;
+ padding-top: 0.2em;
+ margin: 0px 15%;
+}
+
+div.page-container {
+ transition: all 0.2s linear;
+ padding: 55px 5px 3em 2%;
+}
+
+div.page-container.stretched {
+ margin-left: 40%;
+ padding-left: 5px;
+}
+
+
+ /* icons & icon buttons */
+
+div.button.settings-button {
+ margin: 1px;
+ vertical-align: middle;
+ background-image: url(../img/settings.svg);
+ width: 48px;
+ height: 48px;
+}
+
+div.button.logout-button {
+ margin: 1px;
+ vertical-align: middle;
+ background-image: url(../img/logout.svg);
+ width: 48px;
+ height: 48px;
+}
+
+body.admin div.logout-button {
+ display: none;
+}
+
+body.admin div.settings-top-bar.closed div.logout-button {
+ display: inline-block;
+}
+
+body:not(.admin) div.settings-top-bar div.logout-button {
+ display: none;
+}
+
+div.button.rem-camera-button {
+ display: none;
+ margin: 1px;
+ vertical-align: middle;
+ background-image: url(../img/settings.svg);
+ width: 48px;
+ height: 48px;
+ background-position: -48px 0px;
+}
+
+div.settings-top-bar.open div.button.rem-camera-button {
+ display: inline-block;
+}
+
+div.logo {
+ float: right;
+ display: inline-block;
+ white-space: nowrap;
+ opacity: 0.86;
+}
+
+span.logo {
+ color: white;
+ vertical-align: middle;
+ font-size: 27px;
+ font-weight: bold;
+ position: relative;
+ top: 3px;
+}
+
+img.logo {
+ width: 36px;
+ height: 36px;
+ padding: 7px 3px;
+ vertical-align: middle;
+}
+
+img.background-logo {
+ position: absolute;
+ width: 30%;
+ left: 35%;
+ top: 10em;
+ opacity: 0.03;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+img.main-loading-progress {
+ display: block;
+ margin: auto;
+ margin-top: 50px;
+}
+
+div.add-camera-message {
+ text-align: center;
+ margin-top: 30px;
+}
+
+div.hostname {
+ vertical-align: middle;
+ display: inline-block;
+ font-size: 27px;
+}
+
+
+ /* settings */
+
+div.settings {
+ background-color: #313131;
+ position: fixed;
+ z-index: 1;
+ top: 50px;
+ left: 0px;
+ width: 0px;
+ bottom: 0px;
+ transition: all 0.2s linear;
+ overflow: auto;
+}
+
+div.settings.open {
+ width: 40%;
+ min-width: 320px;
+}
+
+body:not(.admin) div.settings {
+ display: none !important;
+}
+
+div.settings-container {
+ position: relative;
+ padding-top: 10px;
+ display: none;
+ white-space: nowrap;
+}
+
+div.settings.open div.settings-container {
+ display: block;
+}
+
+div.settings-progress {
+ position: absolute;
+ top: 0px;
+ width: 0px;
+ bottom: 0px;
+ left: 0px;
+ background-color: #313131;
+ opacity: 0;
+ transition: opacity 0.1s linear;
+}
+
+div.settings-top-bar {
+ position: relative;
+ display: inline-block;
+ width: 40%;
+ height: 50px;
+}
+
+div.settings-top-bar.open {
+ background-color: #414141;
+ min-width: 320px;
+}
+
+body:not(.admin) div.settings-top-bar {
+ display: none !important;
+}
+
+div.settings-top-bar.closed div.apply-button {
+ display: none !important;
+}
+
+div.settings-section-title {
+ position: relative;
+ text-align: right;
+ background-color: rgba(100, 100, 100, 0.3);
+ padding: 5px 0.5em 5px 5px;
+}
+
+a.settings-section-title {
+}
+
+table.settings {
+ width: 100%;
+ padding: 0.5em 0.5em 1em 0.5em;
+}
+
+td.settings-item-label {
+ width: 50%;
+ text-align: right;
+ padding-right: 5px;
+}
+
+td.settings-item-value {
+ width: 50%;
+ text-align: left;
+ padding-left: 5px;
+}
+
+span.settings-item-label {
+ font-size: 0.9em;
+}
+
+span.settings-item-unit {
+ font-size: 0.6em;
+ padding: 0px 0.2em;
+}
+
+div.settings-item-separator {
+ height: 1px;
+ border-top: 1px solid #414141;
+ margin: 0.5em 1em;
+}
+
+#cameraSelect {
+ display: none;
+ padding: 4px 1.5em 4px 4px;
+ vertical-align: middle;
+ font-size: 1.1em;
+ width: auto;
+ max-width: 35%;
+}
+
+div.apply-button {
+ position: relative;
+ display: none;
+ opacity: 0;
+ float: right;
+ width: 4em;
+ height: 30px;
+ line-height: 30px;
+ text-align: center;
+ margin: 10px;
+ color: white;
+ background-color: #FF6F00;
+ border-radius: 3px;
+ transition: all 0.1s linear;
+}
+
+div.apply-button:HOVER {
+ background-color: #FF7D19;
+}
+
+div.apply-button:ACTIVE {
+ background-color: #F06800;
+}
+
+div.apply-button.progress {
+ background-color: #FF6F00;
+}
+
+img.apply-progress {
+ margin-top: 3px;
+}
+
+div.normal-button {
+ position: relative;
+ height: 1.5em;
+ line-height: 1.5em;
+ text-align: center;
+ margin: 2px 0px;
+ color: white;
+ font-size: 0.9em;
+ border-radius: 3px;
+ transition: all 0.1s linear;
+ width: 7em;
+}
+
+div.update-button,
+div.backup-button,
+div.restore-button {
+ background: #317CAD;
+}
+
+div.shut-down-button,
+div.reboot-button {
+ background: #c0392b;
+}
+
+div.update-button:HOVER,
+div.backup-button:HOVER,
+div.restore-button:HOVER {
+ background: #3498db;
+}
+
+div.shut-down-button:HOVER,
+div.reboot-button:HOVER {
+ background: #D43F2F;
+}
+
+div.update-button:ACTIVE,
+div.backup-button:ACTIVE,
+div.restore-button:ACTIVE {
+ background: #317CAD;
+}
+
+div.shut-down-button:ACTIVE,
+div.reboot-button:ACTIVE {
+ background: #B03427;
+}
+
+div.settings-top-bar.open #cameraSelect {
+ display: inline;
+}
+
+div.settings-top-bar.open div.logout-button {
+ display: none;
+}
+
+div.check-box.section {
+ margin: 0px;
+ float: left;
+}
+
+div.check-box.section div.check-box-button {
+ background-color: #515151;
+}
+
+div.check-box.on.section div.check-box-button {
+ background-color: #317CAD;
+}
+
+div.check-box.on.section:FOCUS div.check-box-button,
+div.check-box.on.section:HOVER div.check-box-button {
+ background-color: #3498db;
+}
+
+input[type=text].working-schedule.number {
+ width: 50px;
+}
+
+#diskUsageProgressBar {
+ width: 90%;
+}
+
+div.hidden,
+tr.hidden {
+ display: none !important;
+}
+
+span.help-mark {
+ display: inline-block;
+ visibility: hidden;
+ text-align: center;
+ background-color: #414141;
+ color: #3498db;
+ font-size: 0.75em;
+ font-family: monospace;
+ width: 1.2em;
+ height: 1.2em;
+ border-radius: 100em;
+ cursor: pointer;
+ vertical-align: middle;
+ position: relative;
+ top: -0.1em;
+}
+
+div.settings-section-title > span.help-mark {
+ background-color: #515151;
+}
+
+div.settings-section-title:HOVER > span.help-mark,
+tr:HOVER span.help-mark {
+ visibility: visible;
+}
+
+span.minimize {
+ display: inline-block;
+ background-image: url(../img/combo-box-arrow.svg);
+ background-size: cover;
+ width: 0.8em;
+ height: 0.8em;
+ cursor: pointer;
+ vertical-align: middle;
+ position: relative;
+ top: -0.1em;
+ transition: transform 0.1s linear;
+ -webkit-transform: rotate(90deg);
+ -moz-transform: rotate(90deg);
+ -ms-transform: rotate(90deg);
+ -o-transform: rotate(90deg);
+ transform: rotate(90deg);
+}
+
+span.minimize.open {
+ -webkit-transform: rotate(0deg);
+ -moz-transform: rotate(0deg);
+ -ms-transform: rotate(0deg);
+ -o-transform: rotate(0deg);
+ transform: rotate(0deg);
+}
+
+
+ /* dialogs */
+
+table.login-dialog {
+ margin: auto;
+ font-size: 22px; /* always bigger, regardless of screen size */
+}
+
+table.add-camera-dialog {
+ margin: auto;
+}
+
+table.add-camera-dialog select,
+table.add-camera-dialog input[type=text],
+table.add-camera-dialog input[type=password] {
+ width: 17em;
+}
+
+span#cameraMsgLabel {
+ color: red;
+ font-size: 0.7em;
+}
+
+div#addCameraInfo {
+ font-size: 0.7em;
+ max-width: 33em;
+}
+
+div.media-dialog {
+}
+
+div.media-dialog-groups {
+ float: left;
+ width: 11em;
+ text-align: center;
+ overflow: auto;
+ white-space: nowrap;
+}
+
+div.media-dialog-groups.small-screen {
+ float: none;
+}
+
+div.media-dialog-group-button {
+ height: 1.5em;
+ width: 10.5em;
+ box-sizing: border-box;
+ line-height: 1.5em;
+ text-align: center;
+ margin: 0em 0.2em 0.2em 0.2em;
+ padding: 0px 0.5em;
+ background-color: #414141;
+ color: #3498db;
+ border-radius: 3px;
+ transition: all 0.1s linear;
+ cursor: pointer;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+div.media-dialog-groups.small-screen div.media-dialog-group-button {
+ display: inline-block;
+}
+
+div.media-dialog-group-button:HOVER {
+ background-color: #515151;
+}
+
+div.media-dialog-group-button:ACTIVE {
+ background-color: #414141;
+}
+
+div.media-dialog-group-button.current {
+ background-color: #317CAD;
+ color: white;
+}
+
+div.media-dialog-group-button.current:HOVER {
+ background-color: #3498db;
+}
+
+div.media-dialog-group-button.current:ACTIVE {
+ background-color: #317CAD;
+}
+
+div.media-dialog-list {
+ overflow: auto;
+ position: relative;
+}
+
+div.media-list-group-title {
+ background-color: #313131;
+ font-size: 1.3em;
+ font-weight: bold;
+ text-align: center;
+ padding: 1em 0px 0.2em 0px;
+}
+
+img.media-list-progress {
+ position: relative;
+ top: 35%;
+ display: block;
+ margin: auto;
+}
+
+div.media-list-entry {
+ height: 4em;
+ background-color: #414141;
+ border-bottom: 1px solid #313131;
+ cursor: pointer;
+ transition: background-color 0.1s linear;
+}
+
+div.media-list-entry:HOVER {
+ background-color: #494949;
+}
+
+div.media-list-entry:ACTIVE {
+ background-color: #3b3b3b;
+}
+
+img.media-list-preview {
+ float: left;
+ height: 3em;
+ margin: 0.45em;
+ border: 1px solid #212121;
+ box-shadow: 1px 1px 6px rgba(0,0,0,0.3);
+}
+
+div.media-list-entry-name {
+ font-weight: bold;
+ font-size: 1.3em;
+ padding: 0.4em 0em;
+ text-align: center;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+div.media-list-entry-details {
+ font-size: 1em;
+ text-align: center;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+div.media-list-entry-details span.details-moment-short {
+ display: none;
+}
+
+div.media-list-download-button,
+div.media-list-delete-button {
+ float: right;
+ clear: right;
+ height: 1.5em;
+ width: 5em;
+ line-height: 1.5em;
+ text-align: center;
+ margin: 0px 0.5em;
+ padding: 0px 0.5em;
+ color: white;
+ border-radius: 3px;
+ transition: all 0.1s linear;
+}
+
+div.media-list-download-button {
+ margin-top: 0.4em;
+ margin-bottom: 0.1em;
+ background: #317CAD;
+}
+
+div.media-list-download-button:HOVER {
+ background-color: #3498db;
+}
+
+div.media-list-download-button:ACTIVE {
+ background-color: #317CAD;
+}
+
+div.media-list-delete-button {
+ margin-top: 0.1em;
+ margin-bottom: 0.4em;
+ background: #c0392b;
+}
+
+div.media-list-delete-button:HOVER {
+ background-color: #D43F2F;
+}
+
+div.media-list-delete-button:ACTIVE {
+ background-color: #B03427;
+}
+
+div.media-dialog-buttons {
+ margin: 0.5em 0px 0px 0px;
+ text-align: center;
+}
+
+div.media-dialog-button {
+ cursor: pointer;
+ display: inline-block;
+ height: 1.5em;
+ line-height: 1.5em;
+ text-align: center;
+ padding: 0px 0.5em;
+ margin: 0px 5px 0px 0px;
+ color: white;
+ background-color: #317CAD;
+ border-radius: 3px;
+ transition: all 0.1s linear;
+}
+
+div.media-dialog-button:HOVER {
+ background-color: #3498db;
+}
+
+div.media-dialog-button:ACTIVE {
+ background-color: #317CAD;
+}
+
+div.picture-dialog-content {
+ position: relative;
+ text-align: center;
+ min-height: 100px;
+}
+
+div.picture-dialog-prev-arrow,
+div.picture-dialog-next-arrow {
+ position: absolute;
+ top: 45%;
+ background-color: rgba(0, 0, 0, 0.6);
+ background-image: url(../img/arrows.svg);
+ background-size: cover;
+ width: 3em;
+ height: 3em;
+ border-radius: 0.3em;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ cursor: pointer;
+}
+
+div.picture-dialog-prev-arrow {
+ left: 1em;
+}
+
+div.picture-dialog-next-arrow {
+ right: 1em;
+ background-position: -100% 0%;
+}
+
+img.picture-dialog-content {
+ border: 1px solid #292929;
+}
+
+img.picture-dialog-progress {
+ position: absolute;
+ background-color: #313131;
+ padding: 10px;
+ border-radius: 10px;
+ opacity: 0.7;
+}
+
+table.timelapse-dialog select {
+ width: 10em;
+}
+
+td.timelapse-warning {
+ font-size: 80%;
+ display: none;
+ color: red;
+ max-width: 20em;
+ text-align: center;
+ white-space: normal;
+ padding-bottom: 1em;
+}
+
+div.media-dialog-delete-all-button {
+ margin-top: 0.1em;
+ margin-bottom: 0.4em;
+ background: #c0392b;
+}
+
+div.media-dialog-delete-all-button:HOVER {
+ background-color: #D43F2F;
+}
+
+div.media-dialog-delete-all-button:ACTIVE {
+ background-color: #B03427;
+}
+
+td.login-dialog-error {
+ color: red;
+ display: none;
+}
+
+
+ /* camera frames */
+
+div.camera-list {
+ text-align: center;
+}
+
+div.camera-frame,
+div.camera-frame-place-holder {
+ position: relative;
+ width: 32%;
+ text-align: left;
+ background-color: #313131;
+ display: inline-block;
+ padding: 0px 3px 3px 3px;
+ border-radius: 3px;
+ transition: all 0.2s, opacity 0s;
+ margin: 2px;
+ opacity: 0;
+ vertical-align: top;
+}
+
+div.camera-frame:only-child,
+div.camera-frame-place-holder:only-child {
+ width: 48%;
+}
+
+div.camera-frame-place-holder {
+ visibility: hidden;
+}
+
+div.camera-frame.motion-detected {
+ background-color: #712727;
+}
+
+div.modal-container div.camera-frame {
+ width: auto;
+ padding: 0px;
+ margin: -7px;
+ background-color: #414141;
+}
+
+div.camera-frame:HOVER {
+ background-color: #414141;
+}
+
+div.camera-frame.motion-detected:HOVER {
+ background-color: #8B3636;
+}
+
+div.camera-top-bar {
+ padding: 3px 0px;
+ font-size: 20px;
+ height: 25px;
+}
+
+div.modal-container div.camera-top-bar {
+ display: none;
+}
+
+span.camera-name {
+ float: left;
+ line-height: 25px;
+}
+
+div.camera-buttons {
+ float: right;
+}
+
+div.camera-button {
+ display: inline-block;
+ width: 24px;
+ height: 24px;
+ background-image: url(../img/top-bar-buttons.svg);
+ background-size: cover;
+ margin-left: 3px;
+ cursor: pointer;
+ transition: all 0.1s linear;
+}
+
+div.camera-button.close {
+ background-position: 0px 0px;
+}
+
+div.camera-button.full-screen {
+ background-position: -200% 0px;
+}
+
+div.camera-button.configure {
+ background-position: -100% 0px;
+}
+
+div.camera-button.media-pictures {
+ background-position: -300% 0px;
+}
+
+div.camera-button.media-movies {
+ background-position: -400% 0px;
+}
+
+div.camera-container {
+ position: relative;
+ padding: 0px;
+}
+
+img.camera {
+ position: relative;
+ width: 100%;
+ display: block;
+ transition: opacity 0.2s linear;
+ opacity: 1;
+ min-height: 160px;
+}
+
+img.camera.error,
+img.camera.loading {
+ opacity: 0;
+}
+
+div.camera-placeholder {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ bottom: 0px;
+ left: 0px;
+ text-align: center;
+ transition: opacity 0.2s linear;
+}
+
+img.no-camera {
+ margin-top: 20%;
+ width: 30%;
+ opacity: 0.8;
+}
+
+div.camera-progress {
+ background: rgba(0, 0, 0, 0.001); /* otherwise IE would not extend this as expected */
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ bottom: 0px;
+ left: 0px;
+ opacity: 0;
+ transition: all 0.2s linear;
+ text-align: center;
+ cursor: pointer;
+}
+
+div.camera-progress.visible {
+ opacity: 0.4;
+}
+
+img.camera-progress {
+ border: 10px solid white;
+ border-radius: 10px;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ bottom: 0px;
+ right: 0px;
+ margin: auto;
+}
+
+
+ /* media queries */
+
+@media all and (max-width: 1440px) {
+ /* smaller screens */
+
+ body {
+ font-size: 17px;
+ }
+}
+
+@media all and (max-width: 1000px) {
+ /* small screens (mobile devices) */
+
+ div.page-container.stretched {
+ margin-left: 0px;
+ }
+
+ div.settings.open {
+ box-shadow: 0px 0px 10px rgba(0,0,0,0.5);
+ background-color: rgba(40, 40, 40, 0.9);
+ }
+
+ div.hostname {
+ display: none;
+ }
+
+ div.media-list-entry-name {
+ font-size: 1em;
+ padding: 0.2em 0em 0em 0em;
+ }
+
+ div.media-list-entry-details {
+ padding-top: 0.2em;
+ font-size: 1em;
+ text-align: center;
+ white-space: normal;
+ }
+
+ div.media-list-entry-details span.details-moment {
+ display: none;
+ }
+
+ div.media-list-entry-details span.details-moment-short {
+ display: block;
+ }
+}
+
+@media all and (max-width: 400px) {
+ /* very small screens */
+
+ body {
+ font-size: 13px;
+ }
+
+ div.camera-button {
+ background-size: cover;
+ width: 24px;
+ height: 24px;
+ }
+}
+
+@media all and (max-width: 1900px) {
+ div.camera-frame,
+ div.camera-frame-place-holder {
+ width: 48%;
+ }
+}
+
+@media all and (max-width: 1200px) {
+ div.page-container {
+ padding-left: 1%;
+ }
+
+ div.camera-frame,
+ div.camera-frame-place-holder {
+ width: 98%;
+ }
+
+ div.camera-frame:only-child,
+ div.camera-frame-place-holder:only-child {
+ width: 98%;
+ }
+}
--- /dev/null
+
+ /* general */
+
+::selection,
+::-moz-selection,
+::-webkit-selection {
+ background: #3498db;
+}
+
+option::selection,
+option::-moz-selection,
+option::-webkit-selection {
+ background: transparent;
+}
+
+input[type=checkbox].styled {
+ display: none;
+}
+
+a {
+ color: #3498db;
+ text-decoration: inherit;
+ transition: color 0.1s ease;
+ cursor: pointer;
+}
+
+a:HOVER {
+}
+
+a:ACTIVE {
+ color: #317CAD;
+}
+
+
+ /* buttons */
+
+div.button {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ cursor: pointer;
+ display: inline-block;
+}
+
+div.button.dialog {
+ background-color: #414141;
+ min-width: 60px;
+ height: 1.2em;
+ line-height: 1.2em;
+ text-align: center;
+ padding: 0.2em 0.4em;
+ border: 1px solid #317CAD;
+ border-radius: 2px;
+ color: white;
+ transition: all 0.1s ease;
+}
+
+div.button.dialog.default {
+ background-color: #317CAD;
+}
+
+div.button.mouse-effect {
+ opacity: 0.7;
+ transition: opacity 0.1s ease;
+}
+
+div.button.mouse-effect:HOVER {
+ opacity: 1;
+}
+
+div.button.mouse-effect:ACTIVE {
+ opacity: 0.7;
+}
+
+
+ /* check box */
+
+div.check-box {
+ display: inline-block;
+ position: relative;
+ width: 2.5em;
+ height: 1em;
+ border: 1px solid #317CAD;
+ border-radius: 2px;
+ color: #aaaaaa;
+ vertical-align: middle;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ margin: 2px;
+ transition: all 0.2s ease;
+}
+
+div.check-box:FOCUS,
+div.check-box:HOVER {
+ border-color: #3498db;
+}
+
+div.check-box-button {
+ width: 50%;
+ height: 100%;
+ left: 0px;
+ background-color: #414141;
+ color: #aaaaaa;
+ position: absolute;
+ text-align: center;
+ line-height: 1em;
+ transition: all 0.1s ease;
+}
+
+span.check-box-text {
+ font-size: 0.5em;
+ font-weight: bold;
+ vertical-align: top;
+}
+
+div.check-box.on div.check-box-button {
+ left: 50%;
+ background-color: #317CAD;
+ color: white;
+}
+
+div.check-box.on:FOCUS div.check-box-button,
+div.check-box.on:HOVER div.check-box-button {
+ background-color: #3498db;
+}
+
+
+ /* input boxes */
+
+input[type=password].styled,
+input[type=text].styled,
+textarea.styled {
+ width: 90%;
+ border: 1px solid #317CAD;
+ border-radius: 2px;
+ background-color: transparent;
+ padding: 1px;
+ color: #dddddd;
+ font-family: inherit;
+ font-size: 0.8em;
+ margin: 2px;
+ transition: all 0.1s ease;
+ resize: none;
+ vertical-align: middle;
+ white-space: nowrap;
+}
+
+input[type=password].styled:FOCUS,
+input[type=text].styled:FOCUS {
+ background-color: #414141;
+}
+
+input[type=password].styled:HOVER,
+input[type=password].styled:FOCUS,
+input[type=text].styled:HOVER,
+input[type=text].styled:FOCUS {
+ border-color: #3498db;
+ color: white;
+}
+
+input[type=text].number {
+ width: 5em;
+}
+
+input[type=text].error,
+input[type=password].error,
+input[type=file].error,
+select.error {
+ background-image: url(../img/validation-error.svg) !important;
+ background-position: center right;
+ background-repeat: no-repeat;
+}
+
+input[type=text].time {
+ width: 3.5em;
+}
+
+input[readonly] {
+ border: 1px solid #555 !important;
+}
+
+
+ /* combo box */
+
+select.styled {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 90%;
+ border: 1px solid #317CAD;
+ border-radius: 2px;
+ background-color: transparent;
+ padding: 1px 1.25em 1px 1px;
+ color: #dddddd;
+ font-family: inherit;
+ font-size: 0.8em;
+ margin: 2px;
+ background-image: url(../img/combo-box-arrow.svg);
+ background-repeat: no-repeat;
+ background-position: right center;
+ cursor: pointer;
+ vertical-align: middle;
+}
+
+select.styled:FOCUS {
+ background-color: #414141;
+ appearance: auto;
+}
+
+select.styled:HOVER,
+select.styled:FOCUS {
+ border-color: #3498db;
+ color: white;
+}
+
+.ff select.styled {
+ background-image: none;
+ padding-right: 1px;
+}
+
+
+ /* slider */
+
+input[type=text].range {
+ display: none;
+}
+
+div.slider {
+ width: 82%;
+ height: 1.7em;
+ position: relative;
+ padding: 0.2em 0px;
+ margin-left: 5%;
+}
+
+div.slider-labels {
+ position: relative;
+ width: 100%;
+ height: 0.5em;
+}
+
+span.slider-label {
+ display: inline-block;
+ width: 20%;
+ text-align: center;
+ overflow: visible;
+ position: absolute;
+ font-size: 0.5em;
+}
+
+div.slider-bar {
+ position: relative;
+ top: 7px;
+ left: -5px;
+ width: 100%;
+}
+
+div.slider-bar-inside {
+ border: 1px solid #317CAD;
+ height: 3px;
+ position: relative;
+ top: 3px;
+ left: 7px;
+ transition: all 0.1s ease;
+}
+
+div.slider:HOVER div.slider-bar-inside,
+div.slider:FOCUS div.slider-bar-inside {
+ border-color: #3498db;
+}
+
+div.slider:FOCUS div.slider-bar-inside {
+ background-color: #414141;
+}
+
+div.slider-cursor {
+ background-image: url(../img/slider-arrow.svg);
+ width: 11px;
+ height: 18px;
+ position: absolute;
+ left: 0px;
+ top: -5px;
+ cursor: pointer;
+}
+
+div.slider-cursor-label {
+ font-size: 0.6em;
+ margin-left: 1.5em;
+ margin-top: 0.15em;
+ background: rgba(30, 30, 30, 0.8);
+ vertical-align: top;
+ padding: 1px 2px;
+ border-radius: 3px;
+ display: none;
+}
+
+
+ /* progress bar */
+
+div.progress-bar-container {
+ position: relative;
+ height: 1em;
+ border: 1px solid #555;
+ vertical-align: middle;
+ margin: 0px 0.2em;
+ text-align: center;
+ line-height: 1em;
+}
+
+div.progress-bar-fill {
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ bottom: 0px;
+ width: 0%;
+ background-color: #555;
+ transition: width 0.1s ease;
+}
+
+span.progress-bar-text {
+ vertical-align: middle;
+ font-size: 0.8em;
+ position: relative;
+}
+
+
+ /* modal dialogs */
+
+div.modal-glass {
+ display: none;
+ position: fixed;
+ z-index: 10000;
+ top: 0px;
+ right: 0px;
+ bottom: 0px;
+ left: 0px;
+ background-color: black;
+ opacity: 0;
+}
+
+div.modal-container {
+ position: fixed;
+ display: none;
+ z-index: 10001;
+ background-color: #313131;
+ border-radius: 3px;
+ opacity: 0;
+ padding: 5px;
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
+}
+
+div.modal-title-bar {
+ min-height: 1.5em;
+ line-height: 1.5em;
+ text-align: center;
+ position: relative;
+}
+
+span.modal-title {
+ color: white;
+ font-size: 1.2em;
+ line-height: 1.2em;
+}
+
+div.modal-close-button {
+ position: absolute;
+ top: 0.2em;
+ right: 0.3em;
+ width: 1.1em;
+ height: 1.1em;
+ background-image: url(../img/top-bar-buttons.svg);
+ background-size: cover;
+ cursor: pointer;
+}
+
+table.modal-buttons-container {
+ width: 100%;
+ margin: auto;
+ text-align: center;
+ table-layout: fixed;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+}
+
+table.modal-buttons-container td:not(:FIRST-CHILD) {
+ padding-left: 5px;
+}
+
+table.modal-buttons-container div.button.dialog {
+ display: block;
+}
+
+div.modal-progress {
+ border-radius: 10px;
+ background-image: url(../img/modal-progress.gif);
+ width: 64px;
+ height: 64px;
+ margin: auto;
+}
+
+td.dialog-item-label {
+ text-align: right;
+ padding-right: 5px;
+}
+
+td.dialog-item-value {
+ text-align: left;
+ padding-left: 5px;
+}
+
+span.dialog-item-label {
+ font-size: 0.9em;
+}
+
+div.dialog-item-separator {
+ height: 1px;
+ border-top: 1px solid #414141;
+ margin: 0.5em 1em;
+}
+
+
+ /* popup message */
+
+div.popup-message-container {
+ position: fixed;
+ display: none;
+ z-index: 10002;
+ background-color: #313131;
+ border-radius: 3px;
+ opacity: 0;
+ padding: 5px;
+ box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
+ top: 60px;
+}
+
+span.popup-message {
+
+}
+
+span.popup-message.info {
+ color: white;
+}
+
+span.popup-message.error {
+ color: #FF6D55;
+}
+
+
+ /* media queries */
+
+@media all and (max-width: 400px) {
+ span.modal-title {
+ font-size: 1.5em;
+
+ }
+
+ div.modal-title-bar {
+ min-height: 2em;
+ line-height: 2em;
+ }
+
+ div.modal-close-button {
+ background-size: cover;
+ width: 24px;
+ height: 24px;
+ }
+}
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="maven_problack" horiz-adv-x="1159" >
+<font-face units-per-em="2048" ascent="1638" descent="-410" />
+<missing-glyph horiz-adv-x="692" />
+<glyph horiz-adv-x="2048" />
+<glyph horiz-adv-x="2048" />
+<glyph unicode="
" horiz-adv-x="682" />
+<glyph unicode=" " horiz-adv-x="692" />
+<glyph unicode="	" horiz-adv-x="692" />
+<glyph unicode=" " horiz-adv-x="692" />
+<glyph unicode="!" horiz-adv-x="882" d="M279 1366h354l-45 -975h-264zM297 0v262h317v-262h-317z" />
+<glyph unicode=""" horiz-adv-x="899" d="M166 1384h254l-35 -411h-182zM477 1384h254l-35 -411h-182z" />
+<glyph unicode="#" horiz-adv-x="1288" d="M152 248v186h176q4 27 19 110l24 132h-164v184h194l37 209h197l-37 -209h217q33 170 37 209h197l-39 -209h125v-184h-154q-4 -27 -20.5 -111t-22.5 -131h143v-186h-174q-6 -37 -19.5 -111t-17.5 -98h-196l37 209h-219q-8 -43 -35 -209h-197l39 209h-147zM524 434h221 l39 242h-217q-6 -47 -22.5 -131t-20.5 -111z" />
+<glyph unicode="$" horiz-adv-x="1212" d="M147 952q0 193 123 299.5t316 120.5v217h125v-215q168 -14 311 -92v-307q-162 96 -311 98v-244q88 -25 148.5 -50t129 -73.5t104 -129t35.5 -189.5q0 -164 -118.5 -272.5t-298.5 -128.5v-207h-125v203q-250 12 -422 133v329q189 -157 419 -157h3v243q-94 23 -154.5 45.5 t-134.5 68.5t-112 123t-38 185zM457 958q0 -57 129 -96v203q-129 -23 -129 -107zM711 299q120 27 120 101q0 60 -120 100v-201z" />
+<glyph unicode="%" horiz-adv-x="1890" d="M147 983q0 377 289 377q291 0 291 -377t-291 -377q-289 0 -289 377zM354 983q0 -145 82 -145q84 0 84 145q0 88 -22.5 116.5t-61.5 28.5q-82 0 -82 -145zM387 0l858 1366h234l-858 -1366h-234zM1141 383q0 377 289 377q291 0 291 -376q-1 -378 -291 -378q-289 0 -289 377 zM1346 383q0 -145 84 -145q82 0 82 143q0 147 -82 147q-84 0 -84 -145z" />
+<glyph unicode="&" horiz-adv-x="1337" d="M113 397q0 113 45 207t192 189q0 2 -9 13l-15 20q-7 8 -18 24t-19 30.5t-17.5 36t-15.5 41t-10 43t-4 46.5q0 137 86 232t247 95q160 0 249 -91t89 -239q0 -111 -47 -179t-161 -140l22 -27q14 -18 59 -70l107 -126l31 131h235q-32 -195 -104 -328l254 -305h-308l-92 117 q-147 -133 -352 -133q-115 0 -204 41t-139 105.5t-75.5 133t-25.5 133.5zM342 391q0 -23 9 -51.5t32 -60t69 -53t109 -21.5q119 0 207 84l-268 323q-57 -31 -92 -63.5t-48.5 -64t-15.5 -48t-2 -45.5zM469 1049q0 -41 82 -144q82 47 108.5 80t26.5 72q0 43 -31.5 73.5 t-77 30.5t-77 -32.5t-31.5 -79.5z" />
+<glyph unicode="'" horiz-adv-x="583" d="M166 1384h254l-35 -411h-182z" />
+<glyph unicode="(" horiz-adv-x="796" d="M190 627q0 440 211 856h258q-210 -414 -210 -852q0 -440 210 -862h-258q-211 422 -211 858z" />
+<glyph unicode=")" horiz-adv-x="796" d="M133 -231q211 422 211 858q0 440 -211 856h258q211 -416 211 -856q0 -436 -211 -858h-258z" />
+<glyph unicode="*" horiz-adv-x="845" d="M172 1133l158 57l-158 57l78 139l110 -79l-12 131h160l-10 -131l108 79l78 -139l-156 -57l156 -57l-78 -140l-108 80l10 -131h-160l12 131l-110 -80z" />
+<glyph unicode="+" horiz-adv-x="1128" d="M190 498v229h259v258h229v-258h256v-229h-256v-256h-229v256h-259z" />
+<glyph unicode="," horiz-adv-x="751" d="M152 -229l151 401h285l-197 -401h-239z" />
+<glyph unicode="-" horiz-adv-x="950" d="M162 514v240h624v-240h-624z" />
+<glyph unicode="." horiz-adv-x="811" d="M260 0v283h295v-283h-295z" />
+<glyph unicode="/" horiz-adv-x="948" d="M-29 -201l752 1749h256l-754 -1749h-254z" />
+<glyph unicode="0" horiz-adv-x="1345" d="M119 684q0 700 553 700t553 -700t-553 -700t-553 700zM473 684q0 -229 55.5 -312t143.5 -83q90 0 146 83t56 312q0 399 -202 399q-199 0 -199 -399z" />
+<glyph unicode="1" horiz-adv-x="1040" d="M211 840v346l285 180h288v-1366h-352v975z" />
+<glyph unicode="2" horiz-adv-x="1269" d="M158 0v279q579 433 579 642q0 62 -45 100t-133 38q-180 0 -391 -168v356q223 137 424 137q254 0 373.5 -128t119.5 -308q0 -158 -116.5 -325.5t-272.5 -305.5h398v-317h-936z" />
+<glyph unicode="3" horiz-adv-x="1228" d="M139 98v338q229 -137 402 -137q88 0 144 41t56 102.5t-47 105.5t-159 44h-224v254h228q72 0 107.5 32.5t35.5 79.5q0 49 -55.5 76t-126.5 27h-28h-30q-8 0 -28.5 -1t-32.5 -3t-33.5 -5t-39.5 -8l-42 -13q-24 -7 -50 -17t-52 -23v297q182 96 375 96q88 0 170 -18 t158.5 -58t122.5 -117t46 -181q0 -96 -65.5 -174.5t-147.5 -94.5q121 -35 195 -131t74 -227q0 -117 -49.5 -199t-133.5 -124t-170 -59t-184 -17q-190 0 -416 114z" />
+<glyph unicode="4" horiz-adv-x="1265" d="M80 348v270l598 748h307v-719h164v-299h-164v-348h-350v348h-555zM455 647h180v240z" />
+<glyph unicode="5" horiz-adv-x="1280" d="M162 707l74 661h806v-315h-499l-11 -119q31 4 99 4q229 0 356 -125t127 -385q0 -127 -41 -218t-112.5 -138t-151.5 -67.5t-174 -20.5q-213 0 -442 114v334q213 -135 395 -135q88 0 132 49t44 113q0 82 -45 124t-117 42h-17q-136 0 -229 -60z" />
+<glyph unicode="6" horiz-adv-x="1234" d="M111 630q0 196 49 341t117.5 221t163 122t165 58t142.5 12q176 0 278 -49v-297q-145 45 -250 45q-115 0 -211 -51t-110 -158q67 27 190 27q96 0 170 -15.5t148.5 -55t115.5 -129t41 -220.5q0 -90 -27.5 -175t-85 -158.5t-158.5 -118.5t-233 -45q-505 0 -505 646zM461 629 q0 -160 43 -247q46 -95 112 -95q61 0 98.5 45t44.5 83t7 68q0 78 -38 137.5t-116 59.5q-98 0 -151 -23v-28z" />
+<glyph unicode="7" horiz-adv-x="1226" d="M135 1016v350h1004v-258l-537 -1108h-407l495 1016h-555z" />
+<glyph unicode="8" horiz-adv-x="1261" d="M119 397q0 123 78.5 215.5t193.5 122.5q-88 25 -153.5 98.5t-65.5 174.5q0 376 459 376h2q459 0 459 -376q0 -100 -65 -175t-153 -98q113 -31 193 -123t80 -215q0 -205 -135 -309t-379 -104t-379 104.5t-135 308.5zM473 442q0 -74 40 -122t120 -48q78 0 118 48.5 t40 121.5q0 51 -41 93.5t-117 42.5q-78 0 -119 -42t-41 -94zM514 979q0 -53 37 -83t82 -30q43 0 80 30t37 83q0 43 -31 80t-86 37q-57 0 -88 -37t-31 -80z" />
+<glyph unicode="9" horiz-adv-x="1259" d="M111 887q0 90 27.5 175t85 158.5t158.5 118.5t232 45q506 0 506 -649q0 -193 -49 -338t-117.5 -221t-163 -122t-165 -58t-142.5 -12q-176 0 -278 49v297q145 -45 250 -45q115 0 211 51t110 158q-67 -27 -190 -27q-96 0 -170 15.5t-148.5 55.5t-115.5 129t-41 220z M465 885q0 -78 38 -137.5t115 -59.5q98 0 152 23v28q0 159 -43 247q-47 95 -113 95q-61 0 -98 -45t-44 -83t-7 -68z" />
+<glyph unicode=":" horiz-adv-x="868" d="M281 72v282h294v-282h-294zM281 670v282h294v-282h-294z" />
+<glyph unicode=";" horiz-adv-x="913" d="M166 -229l151 401h285l-196 -401h-240zM309 670v282h295v-282h-295z" />
+<glyph unicode="<" horiz-adv-x="1368" d="M246 455v186l862 383v-279l-504 -196l504 -199v-278z" />
+<glyph unicode="=" horiz-adv-x="1306" d="M250 285v239h805v-239h-805zM250 698v240h805v-240h-805z" />
+<glyph unicode=">" horiz-adv-x="1351" d="M238 72v278l503 199l-503 196v279l862 -383v-186z" />
+<glyph unicode="?" horiz-adv-x="1075" d="M164 1024v262q59 41 155.5 68.5t196.5 27.5q115 0 200 -33.5t132 -92t69.5 -126t22.5 -145.5q0 -70 -22.5 -126t-56.5 -92l-73 -74l-72 -73q-34 -34 -56.5 -87.5t-22.5 -118.5v-21h-281v27q0 104 31 184t75 127t89 86t75.5 81t30.5 89q0 45 -15 76t-46 43t-58.5 16 t-66.5 4q-180 0 -307 -102zM338 0v262h317v-262h-317z" />
+<glyph unicode="@" horiz-adv-x="1869" d="M166 373q0 182 79 346t203.5 274.5t280 175t309.5 64.5q106 0 220 -38t220.5 -112.5t175 -211t68.5 -308.5q0 -207 -92 -362.5t-268 -155.5q-68 0 -114 41t-64 90q-31 -57 -103.5 -94t-175.5 -37q-156 0 -246 70.5t-90 193.5q0 53 17.5 100.5t63.5 95.5t143.5 76.5 t238.5 28.5q35 0 105 -4q-2 66 -35 99.5t-129 33.5q-162 0 -285 -45l29 178q168 53 274 54q340 0 340 -308q0 -49 -15.5 -151t-15.5 -160q0 -123 82 -123q70 0 139.5 99.5t69.5 279.5q0 162 -88 288t-213 186.5t-258 60.5q-287 0 -509 -218t-222 -513q0 -125 44 -226.5 t107.5 -158t142.5 -94.5t138.5 -51t102.5 -13q258 0 469 104l-23 -149q-184 -86 -444 -86q-106 0 -217 38t-216.5 113.5t-172 213t-66.5 315.5zM766 313q0 -71 139 -71q111 0 154 46t53 158q-27 2 -80 3q-266 0 -266 -136z" />
+<glyph unicode="A" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM612 588h277l-137 424z" />
+<glyph unicode="B" horiz-adv-x="1349" d="M178 0v1366h463q541 0 541 -383q0 -66 -21.5 -116t-59.5 -79.5t-64.5 -43t-61.5 -25.5q274 -78 274 -342q0 -377 -567 -377h-504zM530 289h201h7h4q67 0 114 35q51 38 51 114t-57 111t-125 35h-195v-295zM530 831h181q49 0 89 31t40 84q0 70 -46 101.5t-104 31.5h-160 v-248z" />
+<glyph unicode="C" horiz-adv-x="1314" d="M125 684q0 207 57.5 351.5t155.5 216t201.5 101t226.5 29.5q197 0 438 -96v-305q-205 98 -418 98q-141 0 -225 -86t-84 -309t84 -309t225 -86q213 0 418 98v-305q-242 -96 -438 -96q-123 0 -226.5 29.5t-201.5 101t-155.5 216t-57.5 351.5z" />
+<glyph unicode="D" horiz-adv-x="1443" d="M178 0v1366h492q317 0 483 -174t166 -510q0 -334 -166 -508t-483 -174h-492zM530 289h140q297 0 297 393q0 397 -297 397h-140v-790z" />
+<glyph unicode="E" horiz-adv-x="1247" d="M178 0v1366h952v-287h-600v-252h521v-288h-521v-250h600v-289h-952z" />
+<glyph unicode="F" horiz-adv-x="1187" d="M178 0v1366h928v-287h-576v-276h498v-289h-498v-514h-352z" />
+<glyph unicode="G" horiz-adv-x="1427" d="M125 682q0 207 57.5 351.5t155.5 216t201.5 101t226.5 29.5q244 0 471 -102v-283q-207 84 -418 84q-344 0 -344 -395q0 -223 90 -309t242 -86q119 0 188 30v226h-243v262h524v-690q-223 -131 -510 -131q-96 0 -180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293z" />
+<glyph unicode="H" horiz-adv-x="1503" d="M180 0v1366h352v-539h439v539h352v-1366h-352v539h-439v-539h-352z" />
+<glyph unicode="I" horiz-adv-x="708" d="M180 0v1366h352v-1366h-352z" />
+<glyph unicode="J" horiz-adv-x="1060" d="M59 92v326q141 -129 293 -129q72 0 117 41t45 125v911h352v-889q0 -123 -32.5 -215t-79.5 -143t-114 -83t-122 -41t-115 -9q-176 0 -344 106z" />
+<glyph unicode="K" horiz-adv-x="1505" d="M178 0v1366h352v-481l375 481h475l-536 -594l575 -772h-444l-348 532l-97 -108v-424h-352z" />
+<glyph unicode="L" horiz-adv-x="1179" d="M178 0v1366h352v-1049h607v-317h-959z" />
+<glyph unicode="M" horiz-adv-x="1695" d="M180 0v1366h346l322 -465l321 465h347v-1366h-353v846l-315 -453l-316 453v-846h-352z" />
+<glyph unicode="N" horiz-adv-x="1619" d="M178 0v1366h352l555 -819v819h353v-1366h-348l-560 813v-813h-352z" />
+<glyph unicode="O" horiz-adv-x="1538" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293t-39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16t-180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM477 684q0 -223 79 -309t210 -86 t210 86t79 309t-79 309t-210 86t-210 -86t-79 -309z" />
+<glyph unicode="P" horiz-adv-x="1337" d="M176 0v1366h553q248 0 385 -126t137 -353q0 -229 -137 -355t-385 -126h-201v-406h-352zM528 696h168q106 0 155.5 56.5t49.5 134.5q0 76 -49 134t-156 58h-168v-383z" />
+<glyph unicode="Q" horiz-adv-x="1529" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293q0 -604 -494 -684q8 -162 213 -162v-227q-500 0 -505 387q-496 80 -496 686zM477 684q0 -223 79 -309t210 -86t210 86t79 309t-79 309t-210 86t-210 -86 t-79 -309z" />
+<glyph unicode="R" horiz-adv-x="1396" d="M178 0v1366h539q250 0 400.5 -116.5t150.5 -327.5q0 -162 -75 -266.5t-200 -145.5l330 -510h-393l-254 457h-146v-457h-352zM530 721h209q72 0 124 50t52 128q0 80 -52 130t-124 50h-209v-358z" />
+<glyph unicode="S" horiz-adv-x="1232" d="M127 954q0 211 145.5 317.5t360.5 106.5q195 0 368 -94v-307q-166 98 -317 98q-80 0 -142.5 -26.5t-62.5 -87.5q0 -39 46 -65t117 -39t151.5 -43t151.5 -73t117 -133t46 -219q0 -180 -143.5 -292.5t-348.5 -112.5q-281 0 -473 133v329q189 -157 419 -157h3 q78 0 134.5 29.5t56.5 84.5q0 37 -35 63t-91 41l-122 31q-66 16 -133.5 45t-122.5 71t-90 119t-35 181z" />
+<glyph unicode="T" horiz-adv-x="1191" d="M25 1047v319h1142v-319h-397v-1047h-352v1047h-393z" />
+<glyph unicode="U" horiz-adv-x="1533" d="M178 588v778h352v-819q0 -131 56.5 -194.5t175.5 -63.5t175 63.5t56 194.5v819h353v-778q0 -287 -154 -444.5t-430.5 -157.5t-430 157.5t-153.5 444.5z" />
+<glyph unicode="V" horiz-adv-x="1413" d="M25 1366h376l306 -967l305 967h377l-521 -1366h-319z" />
+<glyph unicode="W" horiz-adv-x="2045" d="M27 1366h368l234 -862l243 862h304l243 -862l234 862h368l-440 -1366h-321l-236 782l-236 -782h-321z" />
+<glyph unicode="X" horiz-adv-x="1443" d="M25 0l485 692l-475 674h430l258 -369l260 369h403l-462 -653l503 -713h-430l-288 410l-289 -410h-395z" />
+<glyph unicode="Y" horiz-adv-x="1492" d="M25 1366h428l290 -520l291 520h428l-542 -791v-575h-353v575z" />
+<glyph unicode="Z" horiz-adv-x="1337" d="M133 0v291l617 762h-611v313h1047v-285l-598 -772h598v-309h-1053z" />
+<glyph unicode="[" horiz-adv-x="849" d="M244 -238v1663h436v-247h-209v-1168h209v-248h-436z" />
+<glyph unicode="\" horiz-adv-x="880" d="M-76 1548h256l752 -1749h-254z" />
+<glyph unicode="]" horiz-adv-x="849" d="M172 10h209v1168h-209v247h436v-1663h-436v248z" />
+<glyph unicode="^" horiz-adv-x="1210" d="M246 958l282 455h148l289 -455h-230l-131 244l-131 -244h-227z" />
+<glyph unicode="_" horiz-adv-x="1118" d="M0 -49h1118v-209h-1118v209z" />
+<glyph unicode="`" horiz-adv-x="571" d="M92 1393h240l127 -240h-176z" />
+<glyph unicode="a" horiz-adv-x="1142" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM459 362q0 -108 98 -108h2q49 0 76 39t27 82v86 q-49 4 -68 4q-135 0 -135 -103z" />
+<glyph unicode="b" horiz-adv-x="1230" d="M160 348v1065h350v-383q29 4 133 4q223 0 344 -133t121 -383q0 -534 -502 -534h-2q-137 0 -229 30.5t-136 87t-61.5 114.5t-17.5 132zM510 287q0 -29 26.5 -51.5t81.5 -22.5q72 0 105 90t33 227q0 133 -45 209t-133 76q-18 0 -68 -4v-524z" />
+<glyph unicode="c" horiz-adv-x="993" d="M119 510q0 254 126 390t361 136q178 0 322 -69v-262q-139 63 -258 63q-90 0 -144.5 -64.5t-54.5 -193.5t54.5 -193.5t144.5 -64.5q119 0 258 63v-262q-141 -69 -317 -69h-5q-236 0 -361.5 136t-125.5 390z" />
+<glyph unicode="d" horiz-adv-x="1222" d="M129 518q0 250 121 383t344 133q104 0 133 -4v383h350v-1065q0 -74 -17.5 -132t-61.5 -114.5t-136 -87t-229 -30.5q-504 0 -504 534zM481 530q0 -137 32.5 -227t104.5 -90q55 0 82 22.5t27 51.5v524q-49 4 -68 4q-88 0 -133 -75.5t-45 -209.5z" />
+<glyph unicode="e" horiz-adv-x="1169" d="M131 502q0 244 113.5 390t355.5 146q229 0 347 -144t118 -353q0 -61 -6 -111h-582q0 -72 67.5 -121t151.5 -49q188 0 322 80v-248q-166 -106 -402 -106q-68 0 -129 11t-128.5 46t-115.5 89t-80 149.5t-32 220.5zM485 641h230q-4 59 -35 116.5t-80 57.5t-80 -53t-35 -121z " />
+<glyph unicode="f" horiz-adv-x="776" d="M82 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-207v-774h-352v774h-141z" />
+<glyph unicode="g" horiz-adv-x="1153" d="M127 516q0 520 491 520q117 0 202 -30.5t128 -84t62.5 -112.5t19.5 -131v-637q0 -51 -10 -102.5t-42 -115t-82 -110.5t-139 -79.5t-204 -32.5q-156 0 -322 53v250q115 -66 252 -66q102 0 145.5 48t49.5 130q-53 -4 -98 -4q-211 0 -332 131t-121 373zM479 510 q0 -127 40 -209t110 -82q24 0 51 6v490q0 80 -80 80q-57 0 -89 -81t-32 -204z" />
+<glyph unicode="h" horiz-adv-x="1316" d="M207 0v1411h352v-387q74 12 174 12q213 0 333 -117.5t120 -316.5v-602h-352v657q0 113 -181 113q-55 0 -94 -14v-756h-352z" />
+<glyph unicode="i" horiz-adv-x="735" d="M193 1139v268h360v-268h-360zM197 0v1022h352v-1022h-352z" />
+<glyph unicode="j" horiz-adv-x="667" d="M6 -141q168 0 168 125v1038h352v-1008q0 -119 -40 -201.5t-114.5 -126.5t-162.5 -62.5t-203 -18.5v254zM170 1139v268h360v-268h-360z" />
+<glyph unicode="k" horiz-adv-x="1318" d="M178 0v1405h352v-631l215 248h451l-416 -451l461 -571h-426l-250 330l-35 -37v-293h-352z" />
+<glyph unicode="l" horiz-adv-x="761" d="M207 0v1411h352v-1411h-352z" />
+<glyph unicode="m" horiz-adv-x="1939" d="M203 0v930l14 6l42 16q26 10 60.5 20.5t78.5 22t92.5 20.5t105.5 15t113 6q147 0 249 -59q175 59 355 59q215 0 337 -110.5t122 -323.5v-602h-353v616q0 158 -141 158q-80 0 -141 -20q26 -77 26 -147v-5v-602h-352v621q0 149 -131 149h-2q-55 0 -123 -18v-752h-352z" />
+<glyph unicode="n" horiz-adv-x="1325" d="M178 0v913q246 123 508 123q217 0 344 -115.5t127 -318.5v-602h-352v657q0 31 -8.5 51.5t-48 41t-115.5 20.5q-55 0 -103 -12v-758h-352z" />
+<glyph unicode="o" d="M92 510q0 254 128 389t363.5 135t363.5 -135t128 -389t-128 -389t-363.5 -135t-363.5 135t-128 389zM444 510q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270z" />
+<glyph unicode="p" horiz-adv-x="1232" d="M170 -379v1051q0 74 17.5 132t61.5 114.5t136 87t229 30.5q504 0 504 -534q0 -250 -121 -383t-344 -133q-104 0 -133 4v-369h-350zM520 209q49 -4 68 -4q88 0 133 75.5t45 208.5q0 137 -32.5 227.5t-104.5 90.5q-55 0 -82 -22.5t-27 -51.5v-524z" />
+<glyph unicode="q" horiz-adv-x="1228" d="M115 502q0 534 501 534h2q137 0 229.5 -30.5t136.5 -87t61.5 -115t17.5 -131.5v-1051h-350v369q-29 -4 -133 -4q-223 0 -344 133t-121 383zM467 489q0 -133 45 -208.5t133 -75.5q18 0 68 4v524q0 29 -27 51.5t-82 22.5q-72 0 -104.5 -90t-32.5 -228z" />
+<glyph unicode="r" horiz-adv-x="813" d="M164 0v922q264 109 584 116v-295q-127 -2 -232 -18v-725h-352z" />
+<glyph unicode="s" horiz-adv-x="962" d="M90 694q0 166 115 254t293 88q156 0 293 -84v-233q-133 74 -236 74q-111 0 -111 -70q0 -20 45.5 -40.5t110 -46.5t128 -61.5t108.5 -105.5t45 -164q0 -141 -113 -230t-293 -89q-190 0 -346 100v272q160 -125 309 -125q92 0 92 56q0 27 -45 48t-109.5 45t-130 57.5 t-110.5 99t-45 155.5z" />
+<glyph unicode="t" horiz-adv-x="839" d="M49 786v236h139v303h353v-303h213v-236h-213v-495q0 -23 18.5 -39t44.5 -16q63 0 150 51v-252q-88 -49 -205 -49q-178 0 -269.5 94t-91.5 264v442h-139z" />
+<glyph unicode="u" horiz-adv-x="1280" d="M172 461v561h352v-623q0 -150 122 -150l1 1q123 0 123 149v623h352v-561q0 -475 -475 -475t-475 475z" />
+<glyph unicode="v" horiz-adv-x="1224" d="M66 1022h374l170 -629l172 629h377l-391 -1022h-311z" />
+<glyph unicode="w" horiz-adv-x="1763" d="M82 1022h373l133 -616l162 616h262l162 -616l133 616h372l-362 -1022h-309l-127 459l-127 -459h-310z" />
+<glyph unicode="x" horiz-adv-x="1249" d="M53 0l363 518l-353 504h414l148 -207l145 207h389l-350 -479l387 -543h-416l-172 252l-166 -252h-389z" />
+<glyph unicode="y" horiz-adv-x="1169" d="M63 1022h365l156 -588l153 588h363l-373 -1002q-90 -231 -177 -327t-212 -96q-133 0 -223 43v256q70 -25 135 -25q47 0 92 56.5t78 170.5z" />
+<glyph unicode="z" horiz-adv-x="1030" d="M94 0v289l424 461h-414v272h805v-285l-381 -444h381v-293h-815z" />
+<glyph unicode="{" horiz-adv-x="804" d="M186 485v238q4 0 11.5 2t25 10t30.5 19.5t24.5 33t11.5 48.5v270q0 145 81 235.5t242 90.5h62v-236h-45q-45 0 -68.5 -22.5t-27 -45t-3.5 -67.5v-238q0 -94 -56 -147t-138 -72q82 -18 138 -71.5t56 -147.5v-238q0 -45 3.5 -67.5t27 -45t68.5 -22.5h45v-235h-62 q-162 0 -242.5 90t-80.5 235v271q0 41 -25.5 69.5t-50.5 36.5z" />
+<glyph unicode="|" horiz-adv-x="1005" d="M379 -203v1569h270v-1569h-270z" />
+<glyph unicode="}" horiz-adv-x="804" d="M156 12h45q45 0 68.5 22.5t26.5 45t3 67.5v238q0 94 56.5 147.5t138.5 71.5q-82 18 -138.5 71.5t-56.5 147.5v238q0 45 -3 67.5t-26.5 45t-68.5 22.5h-45v236h61q162 0 243 -90.5t81 -235.5v-270q0 -41 25.5 -70t51.5 -37l25 -6v-238q-4 0 -11 -2t-24.5 -10t-31 -19.5 t-24.5 -33t-11 -47.5v-271q0 -145 -81 -235t-243 -90h-61v235z" />
+<glyph unicode="~" horiz-adv-x="1071" d="M201 457v186q57 68 129 74q11 1 23 1q59 0 116 -26l135 -60q57 -25 115 -25q12 0 23 1q71 6 128 74v-186q-57 -68 -128 -74q-11 -1 -23 -1q-58 0 -115 25l-135 60q-56 26 -115 26q-12 0 -24 -2q-72 -6 -129 -73z" />
+<glyph unicode="¡" horiz-adv-x="882" d="M270 -344l45 975h265l45 -975h-355zM289 760v262h317v-262h-317z" />
+<glyph unicode="¢" horiz-adv-x="1007" d="M117 682q0 254 126 390t361 136h29v158h131v-174q90 -16 162 -53v-262q-90 43 -162 55v-500q70 14 162 55v-262q-74 -35 -162 -53v-172h-131v156h-29q-236 0 -361.5 136t-125.5 390zM469 682q0 -115 45 -178.5t119 -75.5v510q-164 -29 -164 -256z" />
+<glyph unicode="£" horiz-adv-x="1153" d="M100 -31v307q80 55 158 74v203h-158v268h158v168q0 162 105.5 277.5t328.5 115.5q162 0 346 -106v-281q-141 119 -299 119q-90 0 -144 -35t-54 -104v-154h340v-268h-340v-211q117 -53 227 -53q141 0 270 86v-297q-82 -76 -181 -90q-35 -6 -70 -6q-62 0 -123 17l-187 51 q-60 17 -122 17q-36 0 -73 -5q-100 -15 -182 -93z" />
+<glyph unicode="¥" horiz-adv-x="1492" d="M37 1366h428l291 -520l291 520h428l-408 -594h137v-201h-272v-155h272v-201h-272v-215h-352v215h-273v201h273v155h-273v201h137z" />
+<glyph unicode="§" horiz-adv-x="1247" d="M127 707q0 98 91 145t192 49q-43 4 -95.5 60.5t-52.5 121.5q0 86 39 148.5t106.5 94.5t141.5 45t160 13q180 0 362 -104v-248q-160 92 -311 92q-170 0 -170 -80q0 -31 84 -63.5t184 -62t184 -100.5t84 -171q0 -61 -48 -100t-94 -50.5t-93 -13.5q49 -27 74.5 -87 t25.5 -122q0 -137 -119.5 -213.5t-328.5 -76.5q-213 0 -410 131v276q171 -153 361 -153h2q66 0 120 17t54 58q0 27 -56.5 49.5t-136.5 46t-158.5 56.5t-135 95.5t-56.5 146.5zM453 715q0 -18 58 -43t210 -76q78 14 78 45q0 18 -49.5 38.5t-123 43t-89.5 29.5 q-84 -10 -84 -37z" />
+<glyph unicode="¨" horiz-adv-x="899" d="M141 1139v223h246v-223h-246zM489 1139v223h246v-223h-246z" />
+<glyph unicode="©" horiz-adv-x="1593" d="M98 682q0 291 205 495.5t494 204.5q291 0 495.5 -204.5t204.5 -495.5q0 -289 -205 -493.5t-495 -204.5q-289 0 -494 204.5t-205 493.5zM227 682q0 -236 167 -402.5t402.5 -166.5t403.5 166.5t168 402.5q0 238 -167 404.5t-404 166.5q-236 0 -403 -166.5t-167 -404.5z M430 684q0 121 34 206t91 126t117.5 58.5t132.5 17.5q117 0 258 -56v-178q-123 57 -246 57q-180 0 -180 -231t180 -231q123 0 246 57v-178q-139 -57 -258 -58q-72 0 -132.5 17.5t-117.5 59.5t-91 127t-34 206z" />
+<glyph unicode="ª" horiz-adv-x="833" d="M154 970q0 24 8 48t31.5 55t79 50.5t137.5 19.5h51q0 86 -78 86q-80 0 -174 -35v147q102 41 188 41q125 0 195 -64.5t70 -182.5v-136q0 -106 -57.5 -155t-205.5 -49q-245 0 -245 175zM350 1006q0 -61 58 -62q27 0 42 22.5t15 45.5v49q-8 0 -20.5 1t-18.5 1q-76 0 -76 -57 z" />
+<glyph unicode="«" horiz-adv-x="1347" d="M70 522l346 389h338l-373 -389l373 -391h-338zM565 522l346 389h338l-372 -389l372 -391h-338z" />
+<glyph unicode="¬" horiz-adv-x="1300" d="M199 465v219h903v-389h-211v170h-692z" />
+<glyph unicode="­" horiz-adv-x="950" d="M162 514v240h624v-240h-624z" />
+<glyph unicode="®" horiz-adv-x="1593" d="M100 682q0 291 205 495.5t494 204.5q291 0 495.5 -204.5t204.5 -495.5q0 -289 -204.5 -493.5t-495.5 -204.5q-289 0 -494 204.5t-205 493.5zM229 682q0 -236 167 -402.5t402.5 -166.5t403.5 166.5t168 402.5q0 238 -167 404.5t-404 166.5q-236 0 -403 -166.5t-167 -404.5 zM498 285v796h315q145 0 233.5 -67.5t88.5 -190.5q0 -94 -44 -155t-118 -84l192 -299h-229l-148 266h-83v-266h-207zM705 707h120q41 0 72 28.5t31 73.5q0 47 -31 76.5t-72 29.5h-120v-208z" />
+<glyph unicode="¯" horiz-adv-x="825" d="M176 1139v180h479v-180h-479z" />
+<glyph unicode="°" horiz-adv-x="792" d="M168 1143q0 111 57.5 169t166 58t165.5 -58.5t57 -168.5q0 -109 -57 -167t-165.5 -58t-166 58t-57.5 167zM293 1145q0 -57 23.5 -83t74.5 -26t73.5 25.5t22.5 83t-22.5 82t-73.5 24.5q-98 0 -98 -106z" />
+<glyph unicode="±" horiz-adv-x="1198" d="M236 12v215h743v-215h-743zM236 608v230h258v258h229v-258h256v-230h-256v-256h-229v256h-258z" />
+<glyph unicode="²" horiz-adv-x="696" d="M102 948v150q311 231 312 346q0 74 -97 74q-98 0 -211 -91v193q121 74 230 74q137 0 201.5 -69t64.5 -167q0 -154 -211 -340h215v-170h-504z" />
+<glyph unicode="³" horiz-adv-x="686" d="M100 999v183q121 -74 215 -74q49 0 79 21.5t30 54.5q0 82 -111 82h-120v135h122q76 0 76 61q0 27 -29.5 41.5t-66.5 14.5q-98 0 -180 -39v159q100 53 200 54q106 0 186.5 -47.5t80.5 -155.5q0 -51 -36 -93t-79 -50q66 -18 105.5 -70.5t39.5 -122.5q0 -119 -86 -167 t-202 -48q-96 0 -224 61z" />
+<glyph unicode="´" horiz-adv-x="559" d="M121 1139l127 239h239l-190 -239h-176z" />
+<glyph unicode="µ" horiz-adv-x="1325" d="M203 -430v1452h352v-657q0 -31 8 -51.5t48 -41t116 -20.5q55 0 102 12v758h353v-913q-246 -123 -508 -123q-57 0 -119 10v-426h-352z" />
+<glyph unicode="¶" horiz-adv-x="1083" d="M80 870q0 240 143.5 368t413.5 128h354v-1366h-274v371h-80q-270 0 -413.5 129t-143.5 370z" />
+<glyph unicode="·" horiz-adv-x="741" d="M215 420v282h295v-282h-295z" />
+<glyph unicode="¸" horiz-adv-x="585" d="M106 -250l191 240h176l-127 -240h-240z" />
+<glyph unicode="¹" horiz-adv-x="512" d="M92 1401v186l154 96h155v-735h-190v525z" />
+<glyph unicode="º" horiz-adv-x="868" d="M152 1090q0 143 71.5 218.5t204.5 75.5q131 0 202.5 -75.5t71.5 -218.5q0 -141 -71.5 -217t-202.5 -76q-133 0 -204.5 75.5t-71.5 217.5zM348 1090q0 -150 80 -150q78 0 78 150q0 151 -77 151h-1q-80 0 -80 -151z" />
+<glyph unicode="»" horiz-adv-x="1374" d="M106 131l373 391l-373 389h338l347 -389l-347 -391h-338zM602 131l373 391l-373 389h338l346 -389l-346 -391h-338z" />
+<glyph unicode="¼" horiz-adv-x="1476" d="M92 1098v186l154 96h155v-735h-190v524zM170 -45l895 1448h192l-895 -1448h-192zM840 186v146l321 403h166v-387h88v-162h-88v-186h-188v186h-299zM1040 348h99v129z" />
+<glyph unicode="½" horiz-adv-x="1550" d="M92 1098v186l154 96h155v-735h-190v524zM170 -45l895 1448h192l-895 -1448h-192zM946 0v150q311 231 311 346q0 73 -93 73h-3q-98 0 -211 -90v193q119 73 227 73h3q137 0 201.5 -68.5t64.5 -166.5q0 -154 -211 -340h215v-170h-504z" />
+<glyph unicode="¾" horiz-adv-x="1757" d="M137 696v183q121 -74 215 -74q49 0 79 21.5t30 54.5q0 82 -111 82h-121v135h123q76 0 76 61q0 27 -29.5 41t-66.5 14q-98 0 -180 -38v159q100 53 200 54q106 0 186 -47.5t80 -155.5q0 -51 -35.5 -93.5t-78.5 -50.5q66 -18 105.5 -70t39.5 -122q0 -119 -86 -167t-203 -48 q-96 0 -223 61zM387 -45l895 1448h193l-895 -1448h-193zM1067 186v146l322 403h165v-387h88v-162h-88v-186h-188v186h-299zM1268 348h98v129z" />
+<glyph unicode="¿" horiz-adv-x="1075" d="M164 37q0 70 22.5 126t56 92t72.5 74l73 73q34 34 56.5 87.5t22.5 118.5v21h281v-27q0 -104 -31 -184t-75 -127t-89 -86t-76 -81t-31 -89q0 -45 15.5 -76t46.5 -43t58.5 -16t66.5 -4q180 0 307 102v-262q-59 -41 -155.5 -68.5t-196.5 -27.5q-115 0 -200 33.5t-132 92 t-69.5 126t-22.5 145.5zM449 760v262h317v-262h-317z" />
+<glyph unicode="À" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM477 1722h240l127 -239h-176zM612 588h277l-137 424z" />
+<glyph unicode="Á" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM612 588h277l-137 424zM659 1483l127 239h240l-190 -239h-177z" />
+<glyph unicode="Â" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM457 1483l245 250h99l246 -250h-209l-86 90l-86 -90h-209zM612 588h277l-137 424z" />
+<glyph unicode="Ã" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM440 1497v174q57 68 124 74q11 1 22 1q55 0 106 -26l121 -60q51 -25 105 -25q11 0 22 1q66 6 123 74v-174q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -105 25l-121 60q-51 26 -106 26q-11 0 -22 -1 q-66 -6 -124 -74zM612 588h277l-137 424z" />
+<glyph unicode="Ä" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM455 1483v223h245v-223h-245zM612 588h277l-137 424zM803 1483v223h246v-223h-246z" />
+<glyph unicode="Å" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM573 1618q0 84 47.5 130t131.5 46q86 0 132 -46t46 -130t-46 -130t-132 -46q-84 0 -131.5 46t-47.5 130zM612 588h277l-137 424zM700 1618q0 -37 11.5 -50.5t40 -13.5t40 13.5t11.5 50.5q0 63 -51.5 63 t-51.5 -63z" />
+<glyph unicode="Æ" horiz-adv-x="2217" d="M51 0l961 1366h1065v-287h-600v-252h520v-288h-520v-250h600v-289h-953v246h-469l-168 -246h-436zM831 504h293v428z" />
+<glyph unicode="Ç" horiz-adv-x="1314" d="M125 684q0 207 57.5 351.5t155.5 216t201.5 101t226.5 29.5q197 0 438 -96v-305q-205 98 -418 98q-141 0 -225 -86t-84 -309t84 -309t225 -86q213 0 418 98v-305q-195 -78 -356 -92l-125 -240h-240l189 240q-109 10 -200 48t-173 112.5t-128 211t-46 322.5z" />
+<glyph unicode="È" horiz-adv-x="1247" d="M178 0v1366h952v-287h-600v-252h521v-288h-521v-250h600v-289h-952zM424 1722h240l127 -239h-177z" />
+<glyph unicode="É" horiz-adv-x="1247" d="M178 0v1366h952v-287h-600v-252h521v-288h-521v-250h600v-289h-952zM561 1483l127 239h240l-191 -239h-176z" />
+<glyph unicode="Ê" horiz-adv-x="1247" d="M178 0v1366h952v-287h-600v-252h521v-288h-521v-250h600v-289h-952zM375 1483l246 250h98l246 -250h-209l-86 90l-86 -90h-209z" />
+<glyph unicode="Ë" horiz-adv-x="1247" d="M178 0v1366h952v-287h-600v-252h521v-288h-521v-250h600v-289h-952zM383 1483v223h246v-223h-246zM731 1483v223h246v-223h-246z" />
+<glyph unicode="Ì" horiz-adv-x="708" d="M76 1722h239l127 -239h-176zM180 0h352v1366h-352v-1366z" />
+<glyph unicode="Í" horiz-adv-x="708" d="M180 0h352v1366h-352v-1366zM270 1483l127 239h240l-191 -239h-176z" />
+<glyph unicode="Î" horiz-adv-x="708" d="M61 1483l246 250h99l245 -250h-209l-86 90l-86 -90h-209zM180 0h352v1366h-352v-1366z" />
+<glyph unicode="Ï" horiz-adv-x="708" d="M59 1483v223h246v-223h-246zM180 0h352v1366h-352v-1366zM408 1483v223h245v-223h-245z" />
+<glyph unicode="Ð" horiz-adv-x="1527" d="M106 582v200h166v584h492q317 0 483 -174t166 -510q0 -334 -166 -508t-483 -174h-492v582h-166zM625 289h139q297 0 297 393q0 397 -297 397h-139v-297h217v-200h-217v-293z" />
+<glyph unicode="Ñ" horiz-adv-x="1619" d="M178 0v1366h352l555 -819v819h353v-1366h-348l-560 813v-813h-352zM510 1497v174q57 68 124 74q10 1 21 1q55 0 105 -26l122 -60q52 -25 106 -25q11 0 22 1q65 6 123 74v-174q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -106 25l-122 60q-50 26 -105 26q-11 0 -21 -1 q-67 -6 -124 -74z" />
+<glyph unicode="Ò" horiz-adv-x="1538" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293t-39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16t-180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM477 684q0 -223 79 -309t210 -86 t210 86t79 309t-79 309t-210 86t-210 -86t-79 -309zM500 1722h239l127 -239h-176z" />
+<glyph unicode="Ó" horiz-adv-x="1538" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293t-39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16t-180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM477 684q0 -223 79 -309t210 -86 t210 86t79 309t-79 309t-210 86t-210 -86t-79 -309zM666 1483l127 239h239l-190 -239h-176z" />
+<glyph unicode="Ô" horiz-adv-x="1538" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293t-39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16t-180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM471 1483l246 250h98l246 -250h-209 l-86 90l-86 -90h-209zM477 684q0 -223 79 -309t210 -86t210 86t79 309t-79 309t-210 86t-210 -86t-79 -309z" />
+<glyph unicode="Õ" horiz-adv-x="1538" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293t-39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16t-180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM459 1497v174q57 68 123 74q11 1 22 1 q55 0 106 -26l122 -60q51 -25 105 -25q11 0 21 1q66 6 123 74v-174q-57 -68 -123 -74q-10 -1 -21 -1q-54 0 -105 25l-122 60q-51 26 -106 26q-11 0 -22 -1q-66 -6 -123 -74zM477 684q0 -223 79 -309t210 -86t210 86t79 309t-79 309t-210 86t-210 -86t-79 -309z" />
+<glyph unicode="Ö" horiz-adv-x="1538" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293t-39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16t-180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM469 1483v223h246v-223h-246zM477 684 q0 -223 79 -309t210 -86t210 86t79 309t-79 309t-210 86t-210 -86t-79 -309zM817 1483v223h246v-223h-246z" />
+<glyph unicode="×" horiz-adv-x="1093" d="M147 154l287 370l-284 371h225l174 -223l176 223h223l-284 -371l284 -370h-223l-176 223l-174 -223h-228z" />
+<glyph unicode="Ø" horiz-adv-x="1529" d="M14 0l211 246q-100 168 -100 436q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16q215 0 360 -84l62 72h328l-211 -246q102 -168 102 -438q0 -166 -39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16q-217 0 -363 86l-61 -72h-328zM477 682q0 -82 8 -133 l426 495q-57 33 -145 33q-131 0 -210 -86t-79 -309zM618 319q55 -32 143 -32h5q131 0 210 86t79 309q0 70 -11 135z" />
+<glyph unicode="Ù" horiz-adv-x="1533" d="M178 588v778h352v-819q0 -131 56.5 -194.5t175.5 -63.5t175 63.5t56 194.5v819h353v-778q0 -287 -154 -444.5t-430.5 -157.5t-430 157.5t-153.5 444.5zM506 1722h239l127 -239h-176z" />
+<glyph unicode="Ú" horiz-adv-x="1533" d="M178 588v778h352v-819q0 -131 56.5 -194.5t175.5 -63.5t175 63.5t56 194.5v819h353v-778q0 -287 -154 -444.5t-430.5 -157.5t-430 157.5t-153.5 444.5zM651 1483l127 239h240l-191 -239h-176z" />
+<glyph unicode="Û" horiz-adv-x="1533" d="M178 588v778h352v-819q0 -131 56.5 -194.5t175.5 -63.5t175 63.5t56 194.5v819h353v-778q0 -287 -154 -444.5t-430.5 -157.5t-430 157.5t-153.5 444.5zM467 1483l246 250h98l246 -250h-209l-86 90l-86 -90h-209z" />
+<glyph unicode="Ü" horiz-adv-x="1533" d="M178 588v778h352v-819q0 -131 56.5 -194.5t175.5 -63.5t175 63.5t56 194.5v819h353v-778q0 -287 -154 -444.5t-430.5 -157.5t-430 157.5t-153.5 444.5zM465 1483v223h246v-223h-246zM813 1483v223h246v-223h-246z" />
+<glyph unicode="Ý" horiz-adv-x="1492" d="M25 1366h428l290 -520l291 520h428l-542 -791v-575h-353v575zM635 1483l127 239h239l-190 -239h-176z" />
+<glyph unicode="Þ" horiz-adv-x="1337" d="M176 0v1366h352v-203h177q248 0 385 -126t137 -353q0 -229 -137.5 -355t-384.5 -126h-177v-203h-352zM528 494h144q106 0 155.5 56t49.5 134q0 76 -49.5 134.5t-155.5 58.5h-144v-383z" />
+<glyph unicode="ß" horiz-adv-x="1282" d="M180 0v846q0 160 41 271.5t110.5 166.5t143.5 77.5t162 22.5q236 0 357.5 -109.5t121.5 -266.5q0 -106 -60 -187.5t-130 -101.5q250 -74 250 -313q0 -221 -167 -318q-149 -87 -391 -87q-30 0 -61 1v256h12q124 0 217 38q97 40 97 134q0 51 -32 84t-91.5 47t-100.5 19.5 t-102 7.5v233q43 0 84 6.5t88 21.5t74.5 48t27.5 82q0 162 -167 162q-190 0 -191 -267v-874h-293z" />
+<glyph unicode="à" horiz-adv-x="1128" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM326 1378h239l127 -239h-176zM459 362 q0 -108 98 -108h2q49 0 76 39t27 82v86q-49 4 -68 4q-135 0 -135 -103z" />
+<glyph unicode="á" horiz-adv-x="1128" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM442 1139l127 239h240l-191 -239h-176zM459 362 q0 -108 98 -108h2q49 0 76 39t27 82v86q-49 4 -68 4q-135 0 -135 -103z" />
+<glyph unicode="â" horiz-adv-x="1128" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM268 1139l246 250h98l246 -250h-209l-86 90l-86 -90 h-209zM459 362q0 -108 98 -108h2q49 0 76 39t27 82v86q-49 4 -68 4q-135 0 -135 -103z" />
+<glyph unicode="ã" horiz-adv-x="1128" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM252 1153v174q57 68 124 74q10 1 21 1q55 0 105 -26 l122 -60q52 -25 106 -25q11 0 22 1q65 6 122 74v-174q-57 -68 -122 -74q-12 -1 -23 -1q-53 0 -105 25l-122 60q-50 26 -105 26q-11 0 -21 -1q-67 -6 -124 -74zM459 362q0 -108 98 -108h2q49 0 76 39t27 82v86q-49 4 -68 4q-135 0 -135 -103z" />
+<glyph unicode="ä" horiz-adv-x="1128" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM266 1139v223h246v-223h-246zM459 362 q0 -108 98 -108h2q49 0 76 39t27 82v86q-49 4 -68 4q-135 0 -135 -103zM614 1139v223h246v-223h-246z" />
+<glyph unicode="å" horiz-adv-x="1128" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM385 1272q0 84 47 130t131 46q86 0 132 -46t46 -130 t-46 -130t-132 -46q-84 0 -131 46t-47 130zM459 362q0 -108 98 -108h2q49 0 76 39t27 82v86q-49 4 -68 4q-135 0 -135 -103zM512 1272q0 -37 11.5 -50.5t40 -13.5t39.5 13.5t11 50.5q0 63 -51 63t-51 -63z" />
+<glyph unicode="æ" horiz-adv-x="1792" d="M127 297q0 31 7 63.5t35 78.5t73 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -312 -64v267q176 71 332 71h4q92 0 175 -44t124 -91q35 51 113 94t170 43q229 0 347 -144t118 -353q0 -61 -6 -111h-582q0 -72 67.5 -121t151.5 -49q188 0 322 80v-248 q-166 -106 -389 -106q-96 0 -175 48t-112 109q-89 -157 -319 -157h-3q-438 0 -438 315zM479 358q0 -108 99 -108h2q49 0 75.5 39t26.5 82v86q-49 4 -68 4q-135 0 -135 -103zM1030 637h230q-4 59 -35 116.5t-80 57.5t-80 -53t-35 -121z" />
+<glyph unicode="ç" horiz-adv-x="993" d="M119 510q0 254 126 390t361 136q178 0 322 -69v-262q-139 63 -258 63q-90 0 -144.5 -64.5t-54.5 -193.5t54.5 -193.5t144.5 -64.5q119 0 258 63v-262q-106 -51 -238 -63l-125 -240h-239l188 240q-193 25 -294 158t-101 362z" />
+<glyph unicode="è" d="M131 502q0 244 113.5 390t355.5 146q229 0 347 -144t118 -353q0 -61 -6 -111h-582q0 -72 67.5 -121t151.5 -49q188 0 322 80v-248q-166 -106 -402 -106q-68 0 -129 11t-128.5 46t-115.5 89t-80 149.5t-32 220.5zM338 1393h240l127 -240h-177zM485 641h230 q-4 59 -35 116.5t-80 57.5t-80 -53t-35 -121z" />
+<glyph unicode="é" d="M131 502q0 244 113.5 390t355.5 146q229 0 347 -144t118 -353q0 -61 -6 -111h-582q0 -72 67.5 -121t151.5 -49q188 0 322 80v-248q-166 -106 -402 -106q-68 0 -129 11t-128.5 46t-115.5 89t-80 149.5t-32 220.5zM475 1139l127 239h240l-191 -239h-176zM485 641h230 q-4 59 -35 116.5t-80 57.5t-80 -53t-35 -121z" />
+<glyph unicode="ê" d="M131 502q0 244 113.5 390t355.5 146q229 0 347 -144t118 -353q0 -61 -6 -111h-582q0 -72 67.5 -121t151.5 -49q188 0 322 80v-248q-166 -106 -402 -106q-68 0 -129 11t-128.5 46t-115.5 89t-80 149.5t-32 220.5zM301 1139l246 250h98l246 -250h-209l-86 90l-86 -90h-209z M485 641h230q-4 59 -35 116.5t-80 57.5t-80 -53t-35 -121z" />
+<glyph unicode="ë" d="M131 502q0 244 113.5 390t355.5 146q229 0 347 -144t118 -353q0 -61 -6 -111h-582q0 -72 67.5 -121t151.5 -49q188 0 322 80v-248q-166 -106 -402 -106q-68 0 -129 11t-128.5 46t-115.5 89t-80 149.5t-32 220.5zM299 1139v223h246v-223h-246zM485 641h230 q-4 59 -35 116.5t-80 57.5t-80 -53t-35 -121zM647 1139v223h246v-223h-246z" />
+<glyph unicode="ì" horiz-adv-x="735" d="M92 1393h240l127 -240h-176zM197 0h352v1022h-352v-1022z" />
+<glyph unicode="í" horiz-adv-x="735" d="M197 0h352v1022h-352v-1022zM287 1139l127 239h239l-190 -239h-176z" />
+<glyph unicode="î" horiz-adv-x="735" d="M78 1139l246 250h98l246 -250h-209l-86 90l-86 -90h-209zM197 0h352v1022h-352v-1022z" />
+<glyph unicode="ï" horiz-adv-x="735" d="M76 1139v223h246v-223h-246zM197 0h352v1022h-352v-1022zM424 1139v223h246v-223h-246z" />
+<glyph unicode="ð" horiz-adv-x="1271" d="M117 481q0 131 41 220.5t115.5 129t148.5 55t170 15.5q123 0 190 -27q-14 109 -123 164l-151 -118l-92 114l61 49h-16q-90 0 -199 -30v301q91 30 219 30h8q162 0 291 -59l107 84l92 -117l-68 -53q215 -193 215 -606q0 -649 -505 -649q-131 0 -232.5 45t-159 118.5 t-85 158.5t-27.5 175zM471 483q0 -31 7 -68.5t44.5 -82.5t98.5 -45q66 0 113 95q42 87 42 247v28q-53 23 -151 23q-78 0 -116 -59.5t-38 -137.5z" />
+<glyph unicode="ñ" horiz-adv-x="1308" d="M178 0v913q246 123 508 123q217 0 344 -115.5t127 -318.5v-602h-352v657q0 31 -8.5 51.5t-48 41t-115.5 20.5q-55 0 -103 -12v-758h-352zM342 1153v174q57 68 124 74q10 1 21 1q55 0 105 -26l122 -60q52 -25 106 -25q11 0 22 1q65 6 123 74v-174q-57 -68 -123 -74 q-11 -1 -22 -1q-54 0 -106 25l-122 60q-50 26 -105 26q-11 0 -21 -1q-67 -6 -124 -74z" />
+<glyph unicode="ò" d="M92 510q0 254 128 389t363.5 135t363.5 -135t128 -389t-128 -389t-363.5 -135t-363.5 135t-128 389zM332 1378h239l127 -239h-176zM444 510q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270z" />
+<glyph unicode="ó" d="M92 510q0 254 128 389t363.5 135t363.5 -135t128 -389t-128 -389t-363.5 -135t-363.5 135t-128 389zM444 510q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270zM469 1139l127 239h240l-191 -239h-176z" />
+<glyph unicode="ô" d="M92 510q0 254 128 389t363.5 135t363.5 -135t128 -389t-128 -389t-363.5 -135t-363.5 135t-128 389zM289 1139l246 250h98l246 -250h-209l-86 90l-86 -90h-209zM444 510q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270z" />
+<glyph unicode="õ" d="M92 510q0 254 128 389t363.5 135t363.5 -135t128 -389t-128 -389t-363.5 -135t-363.5 135t-128 389zM276 1153v174q57 68 124 74q11 1 22 1q55 0 106 -26l122 -60q51 -25 105 -25q11 0 21 1q66 6 123 74v-174q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60 q-51 26 -106 26q-11 0 -22 -1q-66 -6 -124 -74zM444 510q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270z" />
+<glyph unicode="ö" d="M92 510q0 254 128 389t363.5 135t363.5 -135t128 -389t-128 -389t-363.5 -135t-363.5 135t-128 389zM287 1139v223h245v-223h-245zM444 510q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270zM635 1139v223h246v-223h-246z" />
+<glyph unicode="÷" horiz-adv-x="1286" d="M188 418v231h916v-231h-916zM520 47v240h252v-240h-252zM520 782v240h252v-240h-252z" />
+<glyph unicode="ø" horiz-adv-x="1255" d="M23 0l178 209q-68 119 -68 303q0 254 128 389t364 135q141 0 243 -49l31 35h330l-178 -207q65 -119 65 -298v-5q0 -254 -128 -389t-363 -135q-139 0 -244 49l-31 -37h-327zM487 541l193 227q-28 14 -55 14q-131 0 -138 -241zM569 256q23 -14 56 -14q131 0 139 239z" />
+<glyph unicode="ù" horiz-adv-x="1269" d="M172 461v561h352v-623q0 -150 122 -150q1 0 1 1q123 0 123 149v623h352v-561q0 -475 -475 -475t-475 475zM391 1378h240l127 -239h-176z" />
+<glyph unicode="ú" horiz-adv-x="1269" d="M172 461v561h352v-623q0 -150 122 -150q1 0 1 1q123 0 123 149v623h352v-561q0 -475 -475 -475t-475 475zM537 1139l127 239h239l-190 -239h-176z" />
+<glyph unicode="û" horiz-adv-x="1269" d="M172 461v561h352v-623q0 -150 122 -150q1 0 1 1q123 0 123 149v623h352v-561q0 -475 -475 -475t-475 475zM352 1139l246 250h98l246 -250h-209l-86 90l-86 -90h-209z" />
+<glyph unicode="ü" horiz-adv-x="1269" d="M172 461v561h352v-623q0 -150 122 -150q1 0 1 1q123 0 123 149v623h352v-561q0 -475 -475 -475t-475 475zM350 1139v223h246v-223h-246zM698 1139v223h246v-223h-246z" />
+<glyph unicode="ý" horiz-adv-x="1169" d="M63 1022h373l148 -588l145 588h371l-373 -1002q-90 -231 -177 -327t-212 -96q-133 0 -223 43v256q70 -25 135 -25q47 0 92 56.5t78 170.5zM485 1139l127 239h240l-190 -239h-177z" />
+<glyph unicode="þ" horiz-adv-x="1218" d="M172 -375v1778h350v-379q43 6 133 6q223 0 344 -133t121 -383t-120.5 -383t-344.5 -133q-90 0 -133 6v-379h-350zM522 221q50 -4 68 -4q178 12 178 297t-178 297q-18 0 -68 -4v-586z" />
+<glyph unicode="ÿ" horiz-adv-x="1169" d="M63 1022h373l148 -588l145 588h371l-373 -1002q-90 -231 -177 -327t-212 -96q-133 0 -223 43v256q70 -25 135 -25q47 0 92 56.5t78 170.5zM287 1139v223h245v-223h-245zM635 1139v223h246v-223h-246z" />
+<glyph unicode="Œ" horiz-adv-x="2160" d="M123 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16q168 0 293 -51v37h952v-287h-600v-252h520v-288h-520v-250h600v-289h-952v39q-125 -51 -293 -51q-96 0 -180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM475 684q0 -223 79 -309t210 -86t210 86t79 309 t-79 309t-210 86t-210 -86t-79 -309z" />
+<glyph unicode="œ" horiz-adv-x="1853" d="M125 512q0 254 128 389t363 135q190 0 308 -86q117 88 297 88q229 0 347 -144t118 -353q0 -61 -7 -111h-581q0 -72 67.5 -121t151.5 -49q188 0 321 80v-248q-166 -106 -401 -106q-193 0 -311 88q-119 -86 -310 -86q-236 0 -363.5 135t-127.5 389zM477 512 q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270zM1106 641h229q-4 59 -34.5 116.5t-80 57.5t-80 -53t-34.5 -121z" />
+<glyph unicode="Ÿ" horiz-adv-x="1492" d="M25 1366h428l290 -520l291 520h428l-542 -791v-575h-353v575zM449 1483v223h245v-223h-245zM797 1483v223h245v-223h-245z" />
+<glyph unicode="ˆ" horiz-adv-x="874" d="M137 1155l246 250h98l246 -250h-209l-86 90l-86 -90h-209z" />
+<glyph unicode="˜" horiz-adv-x="1058" d="M215 1153v174q57 68 124 74q11 1 22 1q55 0 105 -26l122 -60q51 -25 105 -25q11 0 21 1q66 6 124 74v-174q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -105 25l-122 60q-50 26 -105 26q-11 0 -22 -1q-67 -6 -124 -74z" />
+<glyph unicode=" " horiz-adv-x="897" />
+<glyph unicode=" " horiz-adv-x="1794" />
+<glyph unicode=" " horiz-adv-x="897" />
+<glyph unicode=" " horiz-adv-x="1794" />
+<glyph unicode=" " horiz-adv-x="598" />
+<glyph unicode=" " horiz-adv-x="448" />
+<glyph unicode=" " horiz-adv-x="299" />
+<glyph unicode=" " horiz-adv-x="299" />
+<glyph unicode=" " horiz-adv-x="224" />
+<glyph unicode=" " horiz-adv-x="358" />
+<glyph unicode=" " horiz-adv-x="99" />
+<glyph unicode="‐" horiz-adv-x="950" d="M162 514v240h624v-240h-624z" />
+<glyph unicode="‑" horiz-adv-x="950" d="M162 514v240h624v-240h-624z" />
+<glyph unicode="‒" horiz-adv-x="950" d="M162 514v240h624v-240h-624z" />
+<glyph unicode="–" horiz-adv-x="1146" d="M141 475v240h860v-240h-860z" />
+<glyph unicode="—" horiz-adv-x="1824" d="M162 475v240h1501v-240h-1501z" />
+<glyph unicode="‘" horiz-adv-x="616" d="M96 1384h285l151 -401h-239z" />
+<glyph unicode="’" horiz-adv-x="616" d="M96 983l152 401h284l-196 -401h-240z" />
+<glyph unicode="‚" horiz-adv-x="698" d="M147 -229l152 401h285l-197 -401h-240z" />
+<glyph unicode="“" horiz-adv-x="980" d="M98 1384h285l152 -401h-240zM455 1384h284l152 -401h-240z" />
+<glyph unicode="”" horiz-adv-x="980" d="M98 983l152 401h285l-197 -401h-240zM455 983l151 401h285l-197 -401h-239z" />
+<glyph unicode="„" horiz-adv-x="1056" d="M147 -229l152 401h285l-197 -401h-240zM504 -229l151 401h285l-197 -401h-239z" />
+<glyph unicode="•" horiz-adv-x="1085" d="M172 665.5q0 153.5 109.5 262t263 108.5t261 -107.5t107.5 -263t-107.5 -263t-261 -107.5t-263 108.5t-109.5 262z" />
+<glyph unicode="…" horiz-adv-x="1849" d="M219 0v283h295v-283h-295zM780 0v283h295v-283h-295zM1343 0v283h295v-283h-295z" />
+<glyph unicode=" " horiz-adv-x="358" />
+<glyph unicode="‹" horiz-adv-x="819" d="M63 522l347 389h338l-373 -389l373 -391h-338z" />
+<glyph unicode="›" horiz-adv-x="843" d="M80 131l373 391l-373 389h338l346 -389l-346 -391h-338z" />
+<glyph unicode=" " horiz-adv-x="448" />
+<glyph unicode="€" horiz-adv-x="1466" d="M98 408v190h166q-2 27 -2 86t2 88h-166v191h197q117 419 606 419h2q199 0 438 -96v-305q-204 98 -417 98q-174 0 -248 -116h397v-191h-455q-4 -53 -4 -88q0 -33 4 -86h455v-190h-397q74 -119 248 -119q213 0 417 98v-305q-240 -96 -438 -96q-492 0 -608 422h-197z" />
+<glyph unicode="™" horiz-adv-x="1581" d="M100 1206v160h572v-160h-199v-522h-176v522h-197zM772 684v682h174l160 -233l160 233h174v-682h-176v422l-158 -227l-158 227v-422h-176z" />
+<glyph unicode="" horiz-adv-x="1020" d="M0 0v1020h1020v-1020h-1020z" />
+<glyph unicode="fi" horiz-adv-x="1462" d="M84 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-206v-774h-353v774h-141zM981 1139h360v268h-360v-268zM985 0h352v1022h-352v-1022z" />
+<glyph unicode="fl" horiz-adv-x="1462" d="M82 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-207v-774h-352v774h-141zM983 0h352v1411h-352v-1411z" />
+<glyph unicode="ffi" horiz-adv-x="2287" d="M82 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-207v-774h-352v774h-141zM858 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-207v-774h-352v774h-141z M1745 1139v268h360v-268h-360zM1749 0v1022h352v-1022h-352z" />
+<glyph unicode="ffl" horiz-adv-x="2313" d="M82 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-207v-774h-352v774h-141zM858 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-207v-774h-352v774h-141z M1759 0v1411h352v-1411h-352z" />
+</font>
+</defs></svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="maven_probold" horiz-adv-x="1441" >
+<font-face units-per-em="2048" ascent="1638" descent="-410" />
+<missing-glyph horiz-adv-x="692" />
+<glyph horiz-adv-x="2048" />
+<glyph horiz-adv-x="2048" />
+<glyph unicode="
" horiz-adv-x="682" />
+<glyph unicode=" " horiz-adv-x="692" />
+<glyph unicode="	" horiz-adv-x="692" />
+<glyph unicode=" " horiz-adv-x="692" />
+<glyph unicode="!" horiz-adv-x="825" d="M283 1366h280l-45 -973h-190zM303 0v227h240v-227h-240z" />
+<glyph unicode=""" horiz-adv-x="825" d="M166 1384h205l-35 -411h-133zM461 1384h205l-35 -411h-133z" />
+<glyph unicode="#" horiz-adv-x="1216" d="M152 256v152h180l51 292h-174v152h201l36 217h162l-39 -217h232l39 217h159l-38 -217h133v-152h-158l-51 -292h153v-152h-178l-39 -217h-162l39 217h-231l-37 -217h-162l39 217h-155zM494 408h231l49 292h-229z" />
+<glyph unicode="$" horiz-adv-x="1181" d="M150 987q0 174 111.5 273.5t289.5 119.5v209h119v-207q168 -6 334 -92v-239q-160 102 -334 104v-340q74 -23 120 -39t109.5 -53t100 -81t63.5 -116t27 -164q0 -154 -121 -257t-299 -117v-209h-119v209q-219 14 -383 114v250q174 -129 383 -131v379q-76 23 -117 37 t-104.5 47t-96 70t-58 97t-25.5 136zM395 993q0 -53 39 -84.5t117 -58.5v293q-156 -33 -156 -150zM670 229q174 27 174 150q0 121 -174 184v-334z" />
+<glyph unicode="%" horiz-adv-x="1777" d="M145 983q0 377 289 377q291 0 291 -377t-291 -377q-289 0 -289 377zM305 983q0 -133 36 -179t93.5 -46t93 46t35.5 179q0 135 -35.5 180t-93 45t-93.5 -45t-36 -180zM381 0l858 1366h174l-858 -1366h-174zM1065 383q0 377 289 377q291 0 291 -377t-291 -377 q-289 0 -289 377zM1225 383q0 -133 35.5 -179t93 -46t93.5 46t36 179q0 135 -36 180t-93.5 45t-93 -45t-35.5 -180z" />
+<glyph unicode="&" horiz-adv-x="1310" d="M113 389q0 53 7 94t31.5 96.5t79 108.5t138.5 103q-125 131 -125 249q0 137 85 235.5t240 98.5q139 0 232.5 -92t93.5 -227q0 -104 -50 -174t-173 -152l229 -274q29 72 43 155h201q-29 -180 -109 -319l244 -291h-262l-105 129q-152 -145 -364 -145q-113 0 -201 41 t-137 103t-73.5 130t-24.5 131zM307 383q0 -39 17.5 -83t77 -87t151.5 -43q135 0 242 106l-301 361q-66 -35 -108 -72t-57.5 -72.5t-18.5 -56t-3 -53.5zM438 1059q0 -14 5.5 -31.5t10.5 -29t18 -32t19.5 -27.5t25 -29.5t22.5 -26.5q96 57 129.5 97t33.5 87q0 53 -37.5 90 t-93 37t-94.5 -39t-39 -96z" />
+<glyph unicode="'" horiz-adv-x="534" d="M166 1384h205l-35 -411h-133z" />
+<glyph unicode="(" horiz-adv-x="751" d="M190 627q0 440 211 856h213q-211 -416 -211 -856q0 -436 211 -858h-213q-211 422 -211 858z" />
+<glyph unicode=")" horiz-adv-x="751" d="M133 -231q211 412 211 858q0 449 -211 856h213q211 -416 211 -856q0 -436 -211 -858h-213z" />
+<glyph unicode="*" horiz-adv-x="845" d="M172 1133l166 57l-166 57l78 139l123 -104l-25 156h160l-25 -156l123 104l78 -139l-166 -57l166 -57l-78 -140l-123 105l25 -156h-160l25 156l-123 -105z" />
+<glyph unicode="+" horiz-adv-x="1128" d="M199 520v182h272v275h182v-275h273v-182h-273v-270h-182v270h-272z" />
+<glyph unicode="," horiz-adv-x="677" d="M152 -229l151 401h215l-182 -401h-184z" />
+<glyph unicode="-" horiz-adv-x="950" d="M162 514v193h624v-193h-624z" />
+<glyph unicode="." horiz-adv-x="759" d="M260 0v227h240v-227h-240z" />
+<glyph unicode="/" horiz-adv-x="897" d="M-29 -201l752 1749h205l-754 -1749h-203z" />
+<glyph unicode="0" horiz-adv-x="1345" d="M133 677.5q0 700.5 538.5 700.5t538.5 -700.5t-538.5 -700.5t-538.5 700.5zM379 678q0 -156 24.5 -260.5t71.5 -152.5t92 -64.5t104.5 -16.5t104.5 16.5t92 64.5t72 152.5t25 260.5q0 125 -17.5 218t-45.5 144.5t-68.5 82t-77.5 39.5t-84 9t-84 -9t-78 -39.5t-68.5 -82 t-45 -144.5t-17.5 -218z" />
+<glyph unicode="1" horiz-adv-x="993" d="M211 936v262l266 168h223v-1366h-245v1085z" />
+<glyph unicode="2" horiz-adv-x="1226" d="M158 0v201q94 70 192.5 154.5t205 193t174 222.5t67.5 204q0 70 -56.5 117t-185.5 47q-100 0 -205.5 -34t-179.5 -85v244q195 120 392 120h3q242 0 358.5 -120.5t116.5 -288.5q0 -182 -137 -373.5t-336 -357.5h494v-244h-903z" />
+<glyph unicode="3" horiz-adv-x="1228" d="M166 84v250q180 -106 373 -107q290 0 290 188q0 91 -68.5 149.5t-230.5 58.5h-174v202h181q115 0 171 47.5t56 116.5q0 82 -73 116t-169 34q-178 0 -327 -90v251q178 84 342 84q205 0 339 -92t134 -295q0 -168 -168 -260q104 -37 167.5 -121t63.5 -204q0 -96 -32.5 -172 t-85 -123t-123 -78t-138 -43t-139.5 -12q-203 0 -389 100z" />
+<glyph unicode="4" horiz-adv-x="1234" d="M78 385v203l622 778h234v-762h178v-219h-178v-385h-246v385h-610zM365 604h323v420z" />
+<glyph unicode="5" horiz-adv-x="1245" d="M170 709l74 657h751v-244h-542q-4 -51 -12.5 -126.5t-12.5 -123.5q96 18 184 18q2 1 4 1q86 0 158 -19q74 -20 146.5 -67t113.5 -141.5t41 -227.5q0 -135 -41 -229t-110.5 -140.5t-143 -65.5t-162.5 -19q-227 0 -415 96v248q190 -121 401 -121q227 0 227 211v20 q-10 219 -223 223h-9q-174 0 -292 -73z" />
+<glyph unicode="6" horiz-adv-x="1222" d="M131 629q0 195 49 340t117 222t162 123t162.5 58t140.5 12q100 0 235 -38v-218q-82 27 -221 27q-47 0 -98 -12.5t-113.5 -42t-112 -96t-65.5 -160.5q90 69 250 69q221 0 342 -120.5t121 -331.5q0 -86 -26.5 -167t-81 -152.5t-152 -114.5t-224.5 -43q-485 0 -485 645z M378 585q0 -103 20 -179q22 -91 64 -132t78.5 -57t75.5 -16q76 0 128.5 31.5t73 80.5t27.5 84t7 66q0 104 -61.5 166.5t-174.5 62.5q-133 0 -237 -69q-1 -19 -1 -38z" />
+<glyph unicode="7" horiz-adv-x="1202" d="M141 1120v246h965v-195l-563 -1171h-291l545 1120h-656z" />
+<glyph unicode="8" horiz-adv-x="1236" d="M129 408q0 74 25.5 134t61.5 94t73 57.5t63 31.5l25 8q-8 2 -21.5 7t-48.5 26.5t-61.5 49.5t-48 81t-21.5 119q0 172 119 270t325.5 98t325.5 -98t119 -270q0 -188 -158 -266l-41 -17q10 -2 26.5 -8t58.5 -30.5t75 -58.5t59.5 -94.5t26.5 -133.5q0 -199 -129 -311.5 t-362.5 -112.5t-362.5 112.5t-129 311.5zM375 416q0 -111 61.5 -175.5t184 -64.5t184 63.5t61.5 176.5q0 82 -63.5 141t-182 59t-182 -59t-63.5 -141zM422 1010q0 -98 59.5 -140.5t139 -42.5t139 42t59.5 141q0 74 -51 124t-147.5 50t-147.5 -50.5t-51 -123.5z" />
+<glyph unicode="9" horiz-adv-x="1234" d="M117 907q0 86 26.5 167t81 152.5t151.5 114.5t224 43q485 0 485 -645q0 -195 -49 -340t-116.5 -222t-161.5 -123t-163 -58t-140 -12q-100 0 -236 39v217q82 -27 221 -27q47 0 98.5 12.5t114 42t111.5 96t65 160.5q-89 -69 -249 -69q-221 0 -342 120.5t-121 331.5z M365 905q0 -104 61 -166.5t174 -62.5q133 0 238 69q1 19 1 38q0 103 -19 179q-23 91 -65 132t-79 57t-76 16q-76 0 -128 -31.5t-72.5 -80.5t-27.5 -84t-7 -66z" />
+<glyph unicode=":" horiz-adv-x="813" d="M293 78v223h235v-223h-235zM293 723v223h235v-223h-235z" />
+<glyph unicode=";" horiz-adv-x="858" d="M182 -229l152 401h215l-182 -401h-185zM322 723v223h235v-223h-235z" />
+<glyph unicode="<" horiz-adv-x="1351" d="M246 477v148l850 379v-211l-617 -242l617 -242v-211z" />
+<glyph unicode="=" horiz-adv-x="1306" d="M260 348v176h789v-176h-789zM260 698v176h789v-176h-789z" />
+<glyph unicode=">" horiz-adv-x="1329" d="M270 98v211l617 242l-617 242v211l850 -379v-148z" />
+<glyph unicode="?" horiz-adv-x="1042" d="M164 1059v227q150 96 321 96q115 0 200 -33.5t132 -92t69.5 -126t22.5 -145.5q0 -70 -22.5 -126t-56 -92t-72.5 -74l-73 -73q-34 -34 -56.5 -87.5t-22.5 -118.5v-21h-217v27q0 104 31 184t76 127t89 86t74.5 81t30.5 89q0 182 -221 182q-180 0 -305 -110zM379 0v227h239 v-227h-239z" />
+<glyph unicode="@" horiz-adv-x="1869" d="M170 371q0 184 79 349t205 276.5t282.5 176t310.5 64.5q84 0 174 -22.5t183 -75t165.5 -127t119 -191t46.5 -258.5q0 -207 -92.5 -362.5t-266.5 -155.5q-82 0 -127 54.5t-51 97.5q-27 -66 -101.5 -109t-185.5 -43q-154 0 -242.5 69.5t-88.5 190.5q0 53 17 99.5t62.5 94.5 t142.5 76.5t238 28.5q45 0 115 -4q0 76 -34 114t-140 38q-166 0 -279 -43q4 29 13.5 82t13.5 81q158 51 270 52q166 0 252 -77t86 -226q0 -51 -16 -156t-16 -162q0 -133 92 -133q74 0 147.5 103.5t73.5 289.5q0 127 -53.5 234.5t-137.5 175t-185 105.5t-202 38 q-295 0 -522 -224t-227 -527q0 -129 46 -233.5t111.5 -163t145.5 -97.5t141.5 -52.5t106.5 -13.5q262 0 465 99l-21 -131q-188 -84 -442 -84q-106 0 -217 37.5t-216.5 113.5t-173 214.5t-67.5 316.5zM762 309q-1 -80 149 -80q119 0 165 50.5t54 175.5q-27 2 -90 2 q-279 0 -278 -148z" />
+<glyph unicode="A" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM516 551h408l-203 536z" />
+<glyph unicode="B" horiz-adv-x="1323" d="M178 0v1366h492q94 0 174 -19.5t149.5 -61.5t109.5 -120t40 -184q0 -180 -162 -264q238 -86 238 -316q0 -55 -9.5 -103t-42 -105.5t-85 -97.5t-145.5 -67.5t-218 -27.5h-541zM424 215h283q266 0 266 199q0 178 -275 178h-274v-377zM424 809h260q213 0 213 164 q0 180 -231 180h-242v-344z" />
+<glyph unicode="C" horiz-adv-x="1286" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16q209 0 414 -81v-230q-195 90 -392 90q-92 0 -157.5 -16.5t-128 -63.5t-95 -147t-32.5 -252t32.5 -252t95 -147t128 -63.5t157.5 -16.5q197 0 392 90v-229q-205 -82 -414 -82q-96 0 -179 16t-170 63.5 t-148.5 123t-100.5 202.5t-39 295z" />
+<glyph unicode="D" horiz-adv-x="1419" d="M178 0v1366h471q317 0 481 -174t164 -510q0 -334 -163.5 -508t-481.5 -174h-471zM424 223h225q213 0 306.5 107.5t93.5 351.5q0 246 -93.5 353.5t-306.5 107.5h-225v-920z" />
+<glyph unicode="E" horiz-adv-x="1204" d="M178 0v1366h909v-221h-663v-346h575v-232h-575v-346h663v-221h-909z" />
+<glyph unicode="F" horiz-adv-x="1142" d="M178 0v1366h883v-221h-637v-346h551v-232h-551v-567h-246z" />
+<glyph unicode="G" horiz-adv-x="1406" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16q217 0 450 -92v-225q-221 98 -426 98q-72 0 -127 -9t-111 -39t-93 -81t-60.5 -140t-23.5 -210q0 -123 23.5 -212t60.5 -140t93 -81t111.5 -39t126.5 -9q113 0 242 35v327h-321v217h548v-682 q-238 -116 -489 -116h-4q-96 0 -179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294z" />
+<glyph unicode="H" horiz-adv-x="1429" d="M178 0v1366h246v-567h580v567h245v-1366h-245v567h-580v-567h-246z" />
+<glyph unicode="I" horiz-adv-x="600" d="M178 0v1366h246v-1366h-246z" />
+<glyph unicode="J" horiz-adv-x="1003" d="M59 72v243q125 -102 281 -102q115 0 169 48t54 188v917h246v-909q0 -145 -44 -245.5t-117 -147t-139.5 -63.5t-139.5 -17q-168 0 -310 88z" />
+<glyph unicode="K" horiz-adv-x="1415" d="M178 0v1366h246v-578l516 578h330l-551 -600l608 -766h-307l-459 592l-137 -150v-442h-246z" />
+<glyph unicode="L" horiz-adv-x="1124" d="M178 0v1366h246v-1133h657v-233h-903z" />
+<glyph unicode="M" horiz-adv-x="1695" d="M178 0v1366h273l395 -571l395 571h272v-1366h-245v1010l-422 -604l-422 604v-1010h-246z" />
+<glyph unicode="N" horiz-adv-x="1587" d="M178 0v1366h279l702 -1012v1012h246v-1366h-275l-706 1020v-1020h-246z" />
+<glyph unicode="O" horiz-adv-x="1538" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16t-179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM371 684q0 -154 30.5 -255 t90 -148.5t122 -64.5t148.5 -17q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 152 -30.5 253t-91 148.5t-122 63.5t-149.5 16q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212z" />
+<glyph unicode="P" horiz-adv-x="1277" d="M176 0v1366h510q242 0 374 -120t132 -339t-132 -338.5t-374 -119.5h-264v-449h-246zM422 672h246q162 0 221 59.5t59 175.5q0 115 -59.5 173.5t-220.5 58.5h-246v-467z" />
+<glyph unicode="Q" horiz-adv-x="1529" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t171 63.5t180 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -152 -32 -269.5t-84 -193.5t-126 -127t-148.5 -74.5t-160.5 -31.5q0 -94 69.5 -136.5t165.5 -42.5v-188q-438 0 -438 369q-106 14 -193 53 t-165 115.5t-122 211t-44 314.5zM371 682q0 -154 30.5 -254t91 -148.5t122 -64.5t149.5 -16q86 0 148.5 16t123 64.5t91 148.5t30.5 254t-30.5 254t-91 148.5t-123 64.5t-148.5 16q-88 0 -149.5 -16t-122 -64.5t-91 -148.5t-30.5 -254z" />
+<glyph unicode="R" horiz-adv-x="1337" d="M178 0v1366h524q240 0 373 -113.5t133 -324.5q0 -314 -299 -406l359 -522h-297l-326 481h-221v-481h-246zM424 707h272q125 0 196 45t71 170q0 127 -71 171t-196 44h-272v-430z" />
+<glyph unicode="S" horiz-adv-x="1189" d="M125 985q0 197 139 297t348 100q184 0 367 -94v-239q-158 104 -338 104q-113 0 -191.5 -39t-78.5 -123q0 -68 71.5 -106.5t173 -65t204 -66.5t174 -138.5t71.5 -254.5q0 -166 -137 -271t-334 -105q-260 0 -451 116v250q176 -131 394 -131q282 0 282 157q0 71 -51 118 t-129 69.5t-167 53t-167 66.5t-129 112.5t-51 189.5z" />
+<glyph unicode="T" horiz-adv-x="1169" d="M25 1120v246h1120v-246h-438v-1120h-246v1120h-436z" />
+<glyph unicode="U" horiz-adv-x="1511" d="M178 578v788h246v-809q0 -180 78 -264t248 -84t248.5 84t78.5 264v809h246v-788q0 -283 -150.5 -438.5t-423 -155.5t-422 155.5t-149.5 438.5z" />
+<glyph unicode="V" horiz-adv-x="1347" d="M25 1366h262l387 -1061l387 1061h262l-522 -1366h-252z" />
+<glyph unicode="W" horiz-adv-x="2045" d="M25 1366h258l307 -1010l325 1010h215l326 -1010l309 1010h256l-438 -1366h-254l-305 952l-307 -952h-254z" />
+<glyph unicode="X" horiz-adv-x="1329" d="M25 0l485 690l-473 676h299l326 -461l323 461h289l-469 -666l491 -700h-299l-344 487l-344 -487h-284z" />
+<glyph unicode="Y" horiz-adv-x="1368" d="M25 1366h297l362 -555l360 555h297l-534 -780v-586h-246v586z" />
+<glyph unicode="Z" horiz-adv-x="1271" d="M131 0v225l684 895h-676v246h981v-219l-688 -901h688v-246h-989z" />
+<glyph unicode="[" horiz-adv-x="808" d="M244 -238v1663h395v-190h-207v-1282h207v-191h-395z" />
+<glyph unicode="\" horiz-adv-x="829" d="M-76 1548h205l752 -1749h-203z" />
+<glyph unicode="]" horiz-adv-x="808" d="M172 -47h207v1282h-207v190h395v-1663h-395v191z" />
+<glyph unicode="^" horiz-adv-x="1146" d="M264 969l223 444h113l225 -444h-170l-110 260l-111 -260h-170z" />
+<glyph unicode="_" horiz-adv-x="1118" d="M0 -49h1118v-141h-1118v141z" />
+<glyph unicode="`" horiz-adv-x="522" d="M92 1395h201l127 -240h-137z" />
+<glyph unicode="a" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM358 365q0 -164 193 -164h2 q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133z" />
+<glyph unicode="b" horiz-adv-x="1181" d="M170 328v1085h246v-391q88 16 168 16q229 0 352 -133t123 -383q0 -137 -36 -241.5t-85 -158.5t-117.5 -88t-119 -42t-101.5 -8q-219 0 -324.5 101t-105.5 243zM416 305q0 -110 184 -110q117 0 164 79.5t47 260.5q0 172 -60.5 240.5t-175.5 68.5q-90 0 -159 -17v-522z" />
+<glyph unicode="c" horiz-adv-x="966" d="M119 510q0 256 125 392t358 136q150 0 295 -57v-205q-121 49 -254 49q-141 0 -209.5 -64.5t-68.5 -250.5q0 -184 68.5 -249.5t209.5 -65.5q127 0 254 51v-205q-145 -57 -295 -57q-233 0 -358 136t-125 390z" />
+<glyph unicode="d" horiz-adv-x="1167" d="M129 522q0 250 122 383t351 133q82 0 170 -16v391h246v-1085q0 -141 -106.5 -242.5t-325.5 -101.5q-51 0 -101.5 8t-118 42t-117.5 88t-85 158.5t-35 241.5zM375 535q0 -180 47 -260t164 -80q186 0 186 110v522q-72 16 -162 17q-115 0 -175 -68.5t-60 -240.5z" />
+<glyph unicode="e" horiz-adv-x="1144" d="M129 500q0 246 109.5 393t347.5 147q225 0 340.5 -143t115.5 -352q0 -86 -4 -127h-657q10 -133 79.5 -178t180.5 -45q180 0 348 79v-198q-170 -92 -385 -92q-68 0 -128 11t-125.5 46t-113 89t-78 149.5t-30.5 220.5zM381 588h412q-10 106 -62.5 179t-144.5 73 t-143.5 -59.5t-61.5 -192.5z" />
+<glyph unicode="f" horiz-adv-x="731" d="M82 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174z" />
+<glyph unicode="g" horiz-adv-x="1122" d="M123 514q0 135 35 235.5t83 153.5t113.5 86t114.5 41t98 8q215 0 316.5 -98t101.5 -238v-727q0 -49 -9 -93t-38 -98t-77 -92t-135 -64.5t-202 -26.5q-162 0 -295 41v219q123 -41 267 -41q86 0 140 13t73.5 41t24.5 47.5t5 53.5v56q-82 -16 -155 -17q-223 0 -342 129 t-119 371zM369 510q0 -172 55 -233.5t168 -61.5q75 0 147 14v476q0 114 -169 114h-3q-98 0 -148 -71.5t-50 -237.5z" />
+<glyph unicode="h" horiz-adv-x="1228" d="M193 0v1411h245v-407q88 34 205 34q213 0 333 -111.5t120 -312.5v-614h-244v635q0 41 -7 68.5t-30.5 57.5t-77 45t-135.5 15q-96 0 -164 -30v-791h-245z" />
+<glyph unicode="i" horiz-adv-x="638" d="M193 1161v225h251v-225h-251zM197 0v1022h245v-1022h-245z" />
+<glyph unicode="j" horiz-adv-x="593" d="M6 -205q70 4 112 17.5t57 42t18.5 49t3.5 65.5v1053h245v-1036q0 -66 -7 -113t-33.5 -99.5t-72.5 -85t-128 -56t-195 -27.5v190zM193 1161v225h251v-225h-251z" />
+<glyph unicode="k" horiz-adv-x="1226" d="M188 0v1405h246v-758l344 375h326l-445 -455l492 -567h-311l-338 403l-68 -69v-334h-246z" />
+<glyph unicode="l" horiz-adv-x="659" d="M207 0v1411h246v-1411h-246z" />
+<glyph unicode="m" horiz-adv-x="1908" d="M193 0v924q227 114 446 114h4q166 0 277 -75q190 75 368 75q207 0 322.5 -112.5t115.5 -311.5v-614h-245v631q0 98 -49.5 148t-184.5 50q-106 0 -209 -24q43 -49 43 -191v-614h-243v637q0 94 -48.5 141t-181.5 47q-88 0 -170 -16v-809h-245z" />
+<glyph unicode="n" horiz-adv-x="1273" d="M168 0v926q225 112 469 112q213 0 333 -111.5t120 -312.5v-614h-244v635q0 41 -7.5 68.5t-30 57.5t-73.5 45t-131 15q-90 0 -190 -18v-803h-246z" />
+<glyph unicode="o" horiz-adv-x="1159" d="M100 510q0 254 125 390t358.5 136t358.5 -136t125 -390t-125 -390t-358.5 -136t-358.5 136t-125 390zM346 509.5q0 -190.5 58.5 -257t179.5 -66.5t179 66.5t58 257t-58 257.5t-179 67t-179.5 -67t-58.5 -257.5z" />
+<glyph unicode="p" horiz-adv-x="1175" d="M168 -379v1071q0 141 105.5 242.5t324.5 101.5q51 0 101.5 -8t119 -42t117.5 -88t85 -158.5t36 -241.5q0 -250 -123 -384t-352 -134q-86 0 -168 18v-377h-246zM414 182q85 -12 151 -12q119 0 181.5 69.5t62.5 245.5q0 180 -47 260t-164 80q-184 0 -184 -110v-533z" />
+<glyph unicode="q" horiz-adv-x="1171" d="M115 498q0 137 34.5 241.5t85 158.5t118 88t117.5 42t101 8q219 0 326 -101t107 -243v-1071h-246v377q-82 -18 -170 -18q-229 0 -351 134t-122 384zM360 485q0 -176 62.5 -245.5t181.5 -69.5q68 0 154 12v533q0 110 -184 110h-3q-117 0 -164 -80t-47 -260z" />
+<glyph unicode="r" horiz-adv-x="741" d="M166 0v930q25 12 69 28.5t177 47t264 32.5v-213q-154 -2 -264 -34v-791h-246z" />
+<glyph unicode="s" horiz-adv-x="923" d="M100 719q0 154 108.5 236.5t270.5 82.5q152 0 279 -73v-187q-104 49 -236 49q-176 0 -176 -86q0 -43 50 -67.5t122 -43t143.5 -50t122 -106.5t50.5 -191q0 -129 -104.5 -214t-276.5 -85q-176 0 -324 84v219q135 -90 289 -90q106 0 138 24.5t32 73.5q0 45 -50.5 72.5 t-122 48t-143 48.5t-122 92.5t-50.5 162.5z" />
+<glyph unicode="t" horiz-adv-x="794" d="M49 811v211h172v303h246v-303h242v-211h-242v-504q0 -66 27.5 -89t97.5 -23q66 0 117 22v-203q-72 -30 -157 -30h-5q-152 0 -239 90t-87 248v489h-172z" />
+<glyph unicode="u" horiz-adv-x="1259" d="M168 463v559h248v-592q0 -123 50 -177t163 -54q115 0 165 54t50 177v592h248v-559q0 -225 -123 -352t-340 -127t-339 127t-122 352z" />
+<glyph unicode="v" horiz-adv-x="1169" d="M66 1022h262l256 -772l256 772h264l-391 -1022h-256z" />
+<glyph unicode="w" horiz-adv-x="1710" d="M78 1022h260l217 -737l195 737h211l194 -737l217 737h260l-362 -1022h-246l-168 588l-170 -588h-246z" />
+<glyph unicode="x" horiz-adv-x="1142" d="M57 0l363 516l-354 506h301l204 -291l205 291h273l-342 -483l378 -539h-301l-229 326l-231 -326h-267z" />
+<glyph unicode="y" horiz-adv-x="1107" d="M63 1022h261l229 -713l225 713h260l-389 -1047q-82 -223 -178 -300.5t-199 -77.5q-76 0 -155 28v205q59 -25 116 -25q106 0 197 248z" />
+<glyph unicode="z" horiz-adv-x="999" d="M92 0v229l471 570h-461v223h777v-227l-478 -572h478v-223h-787z" />
+<glyph unicode="{" horiz-adv-x="784" d="M186 506v194q12 2 28.5 7.5t45.5 34t29 71.5v293q0 145 81 235.5t242 90.5h41v-185h-24q-43 0 -70 -11t-37 -34.5t-13 -41t-3 -48.5v-289q0 -86 -52.5 -142t-117.5 -79q66 -20 118 -76.5t52 -142.5v-289q0 -31 3 -48t13 -40.5t37 -35t70 -11.5h24v-184h-41 q-162 0 -242.5 90t-80.5 235v293q0 41 -25.5 70t-50.5 37z" />
+<glyph unicode="|" horiz-adv-x="954" d="M379 -203v1569h219v-1569h-219z" />
+<glyph unicode="}" horiz-adv-x="784" d="M156 -41h22q43 0 69.5 11.5t37 35t13.5 40.5t3 48v289q0 86 52.5 142.5t117.5 76.5q-66 23 -118 79t-52 142v289q0 31 -3 48.5t-13.5 41t-37 34.5t-69.5 11h-22v185h39q164 0 244.5 -89.5t80.5 -236.5v-293q0 -43 25.5 -70.5t52.5 -33.5l25 -9v-194q-4 0 -11.5 -2 t-25 -10.5t-30.5 -19.5t-24.5 -32.5t-11.5 -48.5v-293q0 -147 -81 -236t-244 -89h-39v184z" />
+<glyph unicode="~" horiz-adv-x="1071" d="M201 457v168q57 68 129 74q11 1 23 1q59 0 116 -26l135 -60q57 -25 115 -25q12 0 23 1q71 6 128 74v-168q-57 -68 -128 -74q-11 -1 -23 -1q-58 0 -115 25l-135 60q-56 26 -115 26q-12 0 -24 -2q-72 -6 -129 -73z" />
+<glyph unicode="¡" horiz-adv-x="825" d="M283 -344l45 973h190l45 -973h-280zM303 795v227h240v-227h-240z" />
+<glyph unicode="¢" horiz-adv-x="976" d="M117 682q0 246 115.5 382t334.5 146v156h119v-160q98 -10 209 -53v-205q-98 41 -209 47v-626q104 6 209 49v-205q-111 -43 -209 -53v-160h-119v156q-219 10 -334.5 145t-115.5 381zM362 682q0 -158 51.5 -228.5t153.5 -82.5v622q-102 -12 -153.5 -81.5t-51.5 -229.5z" />
+<glyph unicode="£" horiz-adv-x="1153" d="M100 -31v209q51 39 174 68v319h-174v195h174v235q0 160 105.5 273.5t327.5 113.5q168 0 331 -94v-211q-149 113 -313 113q-117 0 -178.5 -56.5t-61.5 -152.5v-221h377v-195h-377v-325q6 -2 75 -20.5t85.5 -21.5t74.5 -14q49 -10 76 -10h8q27 2 74.5 4t85 15.5t74.5 35.5 v-215q-86 -44 -182 -44h-4q-98 1 -186 25.5t-178 46.5q-54 13 -112 13q-38 0 -78 -6q-101 -14 -198 -80z" />
+<glyph unicode="¥" horiz-adv-x="1396" d="M37 1366h297l362 -555l361 555h297l-410 -598h162v-184h-287v-160h287v-184h-287v-240h-246v240h-288v184h288v160h-288v184h161z" />
+<glyph unicode="§" horiz-adv-x="1247" d="M137 709q0 98 79 152t175 59q-47 20 -78.5 69t-31.5 103q0 106 67.5 174.5t158.5 93t204 24.5q184 0 344 -92v-211q-164 78 -314 78q-74 0 -135 -21.5t-61 -72.5q0 -27 42 -50.5t105.5 -41t136 -46t136 -61t105.5 -90t42 -131.5q0 -86 -65.5 -137t-161.5 -62 q84 -51 84 -167q0 -133 -117 -214t-315 -81q-211 0 -394 120v230q164 -133 369 -133q82 0 141.5 22.5t59.5 69.5q0 35 -59.5 60.5t-143.5 50t-169 56.5t-144.5 95t-59.5 154zM401 719q0 -43 68 -75t254 -93q61 6 93 33.5t32 54.5q0 37 -55.5 66.5t-147.5 57.5t-106 34 q-45 -4 -91.5 -24.5t-46.5 -53.5z" />
+<glyph unicode="¨" horiz-adv-x="856" d="M162 1155v195h207v-195h-207zM489 1155v195h207v-195h-207z" />
+<glyph unicode="©" horiz-adv-x="1593" d="M98 682q0 291 205 495.5t494 204.5q291 0 495.5 -204.5t204.5 -495.5q0 -289 -205 -493.5t-495 -204.5q-289 0 -494 204.5t-205 493.5zM203 682q0 -246 174 -420t420 -174q248 0 422 174t174 420q0 248 -174.5 422t-421.5 174q-246 0 -420 -174t-174 -422zM440 682 q0 121 33 206t90.5 127t116.5 59.5t131 17.5q123 0 242 -48v-135q-117 53 -229 53q-1 1 -2 1q-112 0 -174 -57q-64 -56 -64 -224t63 -223.5t176 -55.5q119 0 230 54v-135q-119 -47 -242 -48q-72 0 -131 17.5t-116.5 58.5t-90.5 126t-33 206z" />
+<glyph unicode="ª" horiz-adv-x="808" d="M154 989q0 25 7 48.5t28.5 57t74.5 54t133 20.5q39 0 101 -8q0 106 -121 107q-98 0 -166 -29v111q78 32 174 32q121 0 186.5 -62.5t65.5 -178.5v-154q0 -80 -58.5 -136t-181.5 -56q-125 0 -184 57t-59 137zM289 1008q0 -92 106 -93q105 1 105 89v71q-74 6 -82 6 q-129 0 -129 -73z" />
+<glyph unicode="«" horiz-adv-x="1247" d="M66 522l346 389h286l-372 -389l372 -391h-286zM522 522l346 389h287l-373 -389l373 -391h-287z" />
+<glyph unicode="¬" horiz-adv-x="1273" d="M199 492v192h876v-389h-176v197h-700z" />
+<glyph unicode="­" horiz-adv-x="950" d="M162 514v193h624v-193h-624z" />
+<glyph unicode="®" horiz-adv-x="1593" d="M100 682q0 291 205 495.5t494 204.5q291 0 495.5 -204.5t204.5 -495.5q0 -289 -204.5 -493.5t-495.5 -204.5q-289 0 -494 204.5t-205 493.5zM205 682q0 -246 174 -420t420 -174q248 0 422 174t174 420q0 248 -174 422t-422 174q-246 0 -420 -174t-174 -422zM528 285v796 h306q139 0 216.5 -66.5t77.5 -189.5q0 -184 -174 -235l209 -305h-174l-188 280h-129v-280h-144zM672 696h157q72 0 114 26t42 99.5t-42 100t-114 26.5h-157v-252z" />
+<glyph unicode="¯" horiz-adv-x="825" d="M176 1180v145h479v-145h-479z" />
+<glyph unicode="°" horiz-adv-x="792" d="M168 1143q0 111 57.5 169t166 58t165.5 -58.5t57 -168.5q0 -109 -57 -167t-165.5 -58t-166 58t-57.5 167zM276 1145q0 -68 28 -97.5t87.5 -29.5t86 29.5t26.5 97.5q0 66 -26.5 95.5t-86 29.5t-87.5 -30t-28 -95z" />
+<glyph unicode="±" horiz-adv-x="1198" d="M242 61v166h727v-166h-727zM242 604v182h272v275h182v-275h273v-182h-273v-270h-182v270h-272z" />
+<glyph unicode="²" horiz-adv-x="696" d="M109 948v109q125 92 234.5 209.5t109.5 208.5q0 88 -129 88q-119 0 -209 -66v133q106 63 213 64q131 0 193.5 -64.5t62.5 -154.5q0 -180 -254 -396h266v-131h-487z" />
+<glyph unicode="³" horiz-adv-x="686" d="M111 993v135q96 -57 200 -57q158 0 158 100q0 113 -162 113h-94v109h98q61 0 91 25.5t30 62.5q0 80 -129 80q-100 0 -176 -48v136q98 45 184 45q109 0 181.5 -49.5t72.5 -159.5q0 -90 -90 -139q125 -43 125 -177q0 -121 -85 -175t-196 -54q-109 0 -208 53z" />
+<glyph unicode="´" horiz-adv-x="528" d="M121 1155l127 240h201l-191 -240h-137z" />
+<glyph unicode="µ" horiz-adv-x="1323" d="M203 -430v1452h243v-635q0 -41 7.5 -68.5t30 -57.5t73.5 -45t131 -15q90 0 191 18v803h245v-926q-223 -112 -465 -112h-4q-127 0 -209 51v-465h-243z" />
+<glyph unicode="¶" horiz-adv-x="911" d="M78 946q0 203 121 311.5t350 108.5h299v-1366h-232v524h-67q-229 0 -350 108.5t-121 313.5z" />
+<glyph unicode="·" horiz-adv-x="663" d="M211 444v228h240v-228h-240z" />
+<glyph unicode="¸" horiz-adv-x="563" d="M119 -256l178 246h160l-127 -246h-211z" />
+<glyph unicode="¹" horiz-adv-x="512" d="M109 1452v141l143 90h121v-735h-133v586z" />
+<glyph unicode="º" horiz-adv-x="854" d="M152 1092q0 141 69.5 216.5t200.5 75.5t200.5 -75.5t69.5 -216.5q0 -143 -69.5 -219t-200.5 -76t-200.5 75.5t-69.5 219.5zM289 1092q0 -106 32.5 -144.5t100.5 -38.5q66 0 99.5 38t33.5 145q0 104 -34 142t-99 38q-68 0 -100.5 -38t-32.5 -142z" />
+<glyph unicode="»" horiz-adv-x="1277" d="M106 131l373 389l-373 391h287l346 -391l-346 -389h-287zM563 131l373 389l-373 391h287l346 -391l-346 -389h-287z" />
+<glyph unicode="¼" horiz-adv-x="1476" d="M109 1149v141l143 90h121v-735h-133v586zM168 -45l895 1448h164l-895 -1448h-164zM825 209v108l336 418h125v-409h96v-117h-96v-209h-131v209h-330zM981 326h174v227z" />
+<glyph unicode="½" horiz-adv-x="1527" d="M109 1149v141l143 90h121v-735h-133v586zM168 -45l895 1448h164l-895 -1448h-164zM936 0v109q125 92 234.5 209.5t109.5 207.5q0 88 -129 88q-119 0 -209 -65v133q106 63 213 63q131 0 193.5 -64.5t62.5 -154.5q0 -180 -254 -395h266v-131h-487z" />
+<glyph unicode="¾" horiz-adv-x="1691" d="M135 688v135q96 -57 201 -57q158 0 158 100q0 113 -162 113h-94v108h98q61 0 91 26t30 63q0 79 -126 79h-3q-100 0 -176 -47v135q98 45 184 46q109 0 181.5 -49.5t72.5 -159.5q0 -91 -90 -140q125 -43 125 -176q0 -121 -85 -175t-196 -54q-109 0 -209 53zM369 -45 l895 1448h163l-895 -1448h-163zM1032 209v108l336 418h125v-409h96v-117h-96v-209h-131v209h-330zM1188 326h174v227z" />
+<glyph unicode="¿" horiz-adv-x="1042" d="M156 37q0 70 22.5 126t56 92t72.5 74l73 73q34 34 56.5 87.5t22.5 118.5v21h217v-27q0 -104 -31 -184t-76 -127t-89 -86t-74.5 -81t-30.5 -89q0 -182 221 -182q180 0 305 110v-227q-150 -96 -321 -96q-115 0 -200 33.5t-132 92t-69.5 126t-22.5 145.5zM446 795v227h240 v-227h-240z" />
+<glyph unicode="À" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM459 1722h200l127 -239h-137zM516 551h408l-203 536z" />
+<glyph unicode="Á" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM516 551h408l-203 536zM655 1483l127 239h201l-190 -239h-138z" />
+<glyph unicode="Â" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM485 1483l187 250h98l188 -250h-163l-74 114l-74 -114h-162zM516 551h408l-203 536z" />
+<glyph unicode="Ã" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM410 1481v155q57 68 124 74q10 1 21 1q55 0 105 -25l122 -61q51 -25 104 -25q12 0 24 2q65 6 122 73v-155q-57 -68 -122 -74q-12 -1 -23 -1q-53 0 -105 25l-122 60q-50 26 -104 26q-11 0 -22 -2 q-67 -6 -124 -73zM516 551h408l-203 536z" />
+<glyph unicode="Ä" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM455 1483v194h207v-194h-207zM516 551h408l-203 536zM782 1483v194h207v-194h-207z" />
+<glyph unicode="Å" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM516 551h408l-203 536zM543 1599.5q0 83.5 47 130t131 46.5q86 0 132 -46.5t46 -130t-46 -130t-132 -46.5q-84 0 -131 46.5t-47 130zM651 1599.5q0 -49.5 15.5 -69t54.5 -19.5t54.5 19.5t15.5 69 t-15.5 69t-54.5 19.5t-54.5 -19.5t-15.5 -69z" />
+<glyph unicode="Æ" horiz-adv-x="2039" d="M51 0l864 1366h1002v-221h-664v-346h576v-232h-576v-346h664v-221h-909v307h-478l-188 -307h-291zM655 512h353v578z" />
+<glyph unicode="Ç" horiz-adv-x="1286" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16q211 0 412 -81v-230q-191 90 -390 90q-92 0 -157.5 -16.5t-128 -63.5t-95 -147t-32.5 -252t32.5 -252t95 -147t128 -63.5t157.5 -16.5q199 0 390 90v-229q-170 -72 -349 -80l-123 -242h-213l177 244 q-109 12 -198 50t-171 113.5t-127 212t-45 320.5z" />
+<glyph unicode="È" horiz-adv-x="1204" d="M178 0v1366h909v-221h-663v-346h575v-232h-575v-346h663v-221h-909zM397 1722h201l127 -239h-137z" />
+<glyph unicode="É" horiz-adv-x="1204" d="M178 0v1366h909v-221h-663v-346h575v-232h-575v-346h663v-221h-909zM555 1483l127 239h201l-191 -239h-137z" />
+<glyph unicode="Ê" horiz-adv-x="1204" d="M178 0v1366h909v-221h-663v-346h575v-232h-575v-346h663v-221h-909zM414 1483l186 250h98l189 -250h-164l-74 114l-74 -114h-161z" />
+<glyph unicode="Ë" horiz-adv-x="1204" d="M178 0v1366h909v-221h-663v-346h575v-232h-575v-346h663v-221h-909zM389 1483v194h207v-194h-207zM717 1483v194h207v-194h-207z" />
+<glyph unicode="Ì" horiz-adv-x="600" d="M35 1722h201l126 -239h-137zM178 0h246v1366h-246v-1366z" />
+<glyph unicode="Í" horiz-adv-x="600" d="M178 0h246v1366h-246v-1366zM240 1483l127 239h200l-190 -239h-137z" />
+<glyph unicode="Î" horiz-adv-x="600" d="M63 1483l187 250h98l189 -250h-164l-74 114l-74 -114h-162zM178 0h246v1366h-246v-1366z" />
+<glyph unicode="Ï" horiz-adv-x="600" d="M33 1483v194h207v-194h-207zM178 0h246v1366h-246v-1366zM360 1483v194h207v-194h-207z" />
+<glyph unicode="Ð" horiz-adv-x="1509" d="M106 598v168h162v600h471q317 0 481 -174t164 -510q0 -334 -163.5 -508t-481.5 -174h-471v598h-162zM514 223h225q213 0 306.5 107.5t93.5 351.5q0 246 -93.5 353.5t-306.5 107.5h-225v-377h242v-168h-242v-375z" />
+<glyph unicode="Ñ" horiz-adv-x="1587" d="M178 0v1366h279l702 -1012v1012h246v-1366h-275l-706 1020v-1020h-246zM481 1481v155q57 68 124 74q11 1 22 1q55 0 105 -25l122 -61q50 -25 104 -25q11 0 23 2q66 6 123 73v-155q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -105 25l-122 60q-50 26 -104 26q-11 0 -22 -2 q-67 -6 -125 -73z" />
+<glyph unicode="Ò" horiz-adv-x="1538" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16t-179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM371 684q0 -154 30.5 -255 t90 -148.5t122 -64.5t148.5 -17q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 152 -30.5 253t-91 148.5t-122 63.5t-149.5 16q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212zM518 1722h201l127 -239h-137z" />
+<glyph unicode="Ó" horiz-adv-x="1538" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16t-179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM371 684q0 -154 30.5 -255 t90 -148.5t122 -64.5t148.5 -17q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 152 -30.5 253t-91 148.5t-122 63.5t-149.5 16q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212zM680 1483l127 239h201l-191 -239h-137z" />
+<glyph unicode="Ô" horiz-adv-x="1538" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16t-179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM371 684q0 -154 30.5 -255 t90 -148.5t122 -64.5t148.5 -17q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 152 -30.5 253t-91 148.5t-122 63.5t-149.5 16q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212zM526 1483l187 250h98l188 -250h-163l-74 114l-74 -114h-162z" />
+<glyph unicode="Õ" horiz-adv-x="1538" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16t-179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM371 684q0 -154 30.5 -255 t90 -148.5t122 -64.5t148.5 -17q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 152 -30.5 253t-91 148.5t-122 63.5t-149.5 16q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212zM451 1481v155q57 68 123 74q11 1 22 1q55 0 106 -25l122 -61q50 -25 103 -25 q12 0 23 2q66 6 123 73v-155q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60q-51 26 -105 26q-11 0 -23 -2q-66 -6 -123 -73z" />
+<glyph unicode="Ö" horiz-adv-x="1538" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16t-179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM371 684q0 -154 30.5 -255 t90 -148.5t122 -64.5t148.5 -17q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 152 -30.5 253t-91 148.5t-122 63.5t-149.5 16q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212zM496 1483v194h206v-194h-206zM823 1483v194h207v-194h-207z" />
+<glyph unicode="×" horiz-adv-x="1062" d="M156 160l282 364l-282 365h184l190 -246l193 246h182l-280 -365l280 -364h-182l-193 243l-190 -243h-184z" />
+<glyph unicode="Ø" horiz-adv-x="1529" d="M68 0l178 211q-121 174 -121 471q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16q233 0 385 -102l74 86h237l-180 -213q123 -178 123 -471q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16q-233 0 -387 100l-70 -84h-237zM371 684q0 -168 39 -278 l594 702q-84 57 -242 57q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212zM520 256q84 -57 242 -57q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 162 -41 274z" />
+<glyph unicode="Ù" horiz-adv-x="1511" d="M178 578v788h246v-809q0 -180 78 -264t248 -84t248.5 84t78.5 264v809h246v-788q0 -283 -150.5 -438.5t-423 -155.5t-422 155.5t-149.5 438.5zM506 1722h201l127 -239h-138z" />
+<glyph unicode="Ú" horiz-adv-x="1511" d="M178 578v788h246v-809q0 -180 78 -264t248 -84t248.5 84t78.5 264v809h246v-788q0 -283 -150.5 -438.5t-423 -155.5t-422 155.5t-149.5 438.5zM668 1483l127 239h200l-190 -239h-137z" />
+<glyph unicode="Û" horiz-adv-x="1511" d="M178 578v788h246v-809q0 -180 78 -264t248 -84t248.5 84t78.5 264v809h246v-788q0 -283 -150.5 -438.5t-423 -155.5t-422 155.5t-149.5 438.5zM514 1483l186 250h99l188 -250h-164l-73 114l-74 -114h-162z" />
+<glyph unicode="Ü" horiz-adv-x="1511" d="M178 578v788h246v-809q0 -180 78 -264t248 -84t248.5 84t78.5 264v809h246v-788q0 -283 -150.5 -438.5t-423 -155.5t-422 155.5t-149.5 438.5zM483 1483v194h207v-194h-207zM811 1483v194h207v-194h-207z" />
+<glyph unicode="Ý" horiz-adv-x="1368" d="M25 1366h297l362 -555l360 555h297l-534 -780v-586h-246v586zM594 1483l127 239h201l-191 -239h-137z" />
+<glyph unicode="Þ" horiz-adv-x="1253" d="M176 0v1366h246v-172h209q242 0 374 -120t132 -339t-132.5 -339t-373.5 -120h-209v-276h-246zM422 500h190q162 0 221.5 59t59.5 176q0 115 -59.5 173.5t-221.5 58.5h-190v-467z" />
+<glyph unicode="ß" horiz-adv-x="1255" d="M180 0v856q0 156 40 265.5t108.5 162.5t140.5 75.5t156 22.5q223 0 344 -113t121 -265q0 -109 -63.5 -187t-133.5 -104q37 -10 70.5 -27.5t82 -51.5t77 -93.5t28.5 -134.5q0 -201 -160 -310q-144 -97 -364 -97q-24 0 -49 1v199h12q334 0 334 215q0 193 -346 204v181 q66 0 124 11t113 58t55 127q0 213 -219 213q-51 0 -89 -13t-75.5 -48t-58 -109.5t-20.5 -187.5v-850h-228z" />
+<glyph unicode="à" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM313 1395h201l127 -240h-137z M358 365q0 -164 193 -164h2q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133z" />
+<glyph unicode="á" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM358 365q0 -164 193 -164h2 q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133zM459 1155l127 240h200l-190 -240h-137z" />
+<glyph unicode="â" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM338 1155l186 250h99l188 -250h-164 l-74 115l-73 -115h-162zM358 365q0 -164 193 -164h2q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133z" />
+<glyph unicode="ã" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM258 1153v156q57 68 124 74 q11 1 22 1q55 0 105 -26l121 -60q52 -25 105 -25q11 0 23 1q65 6 123 74v-156q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -106 25l-121 60q-50 26 -105 26q-11 0 -22 -1q-67 -6 -124 -74zM358 365q0 -164 193 -164h2q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133z" />
+<glyph unicode="ä" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM287 1155v195h207v-195h-207z M358 365q0 -164 193 -164h2q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133zM614 1155v195h207v-195h-207z" />
+<glyph unicode="å" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM358 365q0 -164 193 -164h2 q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133zM389 1272q0 84 47 130t131 46q86 0 132 -46t46 -130t-46 -130t-132 -46q-84 0 -131 46t-47 130zM498 1272q0 -49 15 -68.5t54 -19.5t54.5 19.5t15.5 68.5t-15.5 68.5t-54.5 19.5t-54 -19.5t-15 -68.5z" />
+<glyph unicode="æ" horiz-adv-x="1792" d="M109 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q236 0 338 -143q35 57 121 101t199 44q225 0 340.5 -143t115.5 -352q0 -86 -4 -127h-657q10 -133 79.5 -178t180.5 -45q180 0 348 79v-198 q-170 -92 -385 -92q-104 0 -193 43t-135 102q-106 -145 -327 -145q-225 0 -331.5 102t-106.5 248zM352 365q0 -164 193 -164h2q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133zM979 588h412q-10 106 -62.5 179t-144.5 73t-143.5 -59.5t-61.5 -192.5z" />
+<glyph unicode="ç" horiz-adv-x="966" d="M119 510q0 256 125 392t358 136q150 0 295 -57v-205q-121 49 -254 49q-141 0 -209.5 -64.5t-68.5 -250.5q0 -184 68.5 -249.5t209.5 -65.5q127 0 254 51v-205q-106 -45 -227 -53l-123 -244h-213l176 246q-190 25 -290.5 158t-100.5 362z" />
+<glyph unicode="è" horiz-adv-x="1144" d="M129 500q0 246 109.5 393t347.5 147q225 0 340.5 -143t115.5 -352q0 -86 -4 -127h-657q10 -133 79.5 -178t180.5 -45q180 0 348 79v-198q-170 -92 -385 -92q-68 0 -128 11t-125.5 46t-113 89t-78 149.5t-30.5 220.5zM334 1395h201l127 -240h-138zM381 588h412 q-10 106 -62.5 179t-144.5 73t-143.5 -59.5t-61.5 -192.5z" />
+<glyph unicode="é" horiz-adv-x="1144" d="M129 500q0 246 109.5 393t347.5 147q225 0 340.5 -143t115.5 -352q0 -86 -4 -127h-657q10 -133 79.5 -178t180.5 -45q180 0 348 79v-198q-170 -92 -385 -92q-68 0 -128 11t-125.5 46t-113 89t-78 149.5t-30.5 220.5zM381 588h412q-10 106 -62.5 179t-144.5 73 t-143.5 -59.5t-61.5 -192.5zM496 1155l127 240h200l-190 -240h-137z" />
+<glyph unicode="ê" horiz-adv-x="1144" d="M129 500q0 246 109.5 393t347.5 147q225 0 340.5 -143t115.5 -352q0 -86 -4 -127h-657q10 -133 79.5 -178t180.5 -45q180 0 348 79v-198q-170 -92 -385 -92q-68 0 -128 11t-125.5 46t-113 89t-78 149.5t-30.5 220.5zM346 1155l186 250h99l188 -250h-164l-73 115l-74 -115 h-162zM381 588h412q-10 106 -62.5 179t-144.5 73t-143.5 -59.5t-61.5 -192.5z" />
+<glyph unicode="ë" horiz-adv-x="1144" d="M129 500q0 246 109.5 393t347.5 147q225 0 340.5 -143t115.5 -352q0 -86 -4 -127h-657q10 -133 79.5 -178t180.5 -45q180 0 348 79v-198q-170 -92 -385 -92q-68 0 -128 11t-125.5 46t-113 89t-78 149.5t-30.5 220.5zM309 1155v195h207v-195h-207zM381 588h412 q-10 106 -62.5 179t-144.5 73t-143.5 -59.5t-61.5 -192.5zM637 1155v195h207v-195h-207z" />
+<glyph unicode="ì" horiz-adv-x="638" d="M63 1395h201l127 -240h-137zM197 0h245v1022h-245v-1022z" />
+<glyph unicode="í" horiz-adv-x="638" d="M197 0h245v1022h-245v-1022zM248 1155l127 240h200l-190 -240h-137z" />
+<glyph unicode="î" horiz-adv-x="638" d="M84 1155l186 250h99l188 -250h-164l-74 115l-73 -115h-162zM197 0h245v1022h-245v-1022z" />
+<glyph unicode="ï" horiz-adv-x="638" d="M53 1155v195h207v-195h-207zM197 0h245v1022h-245v-1022zM381 1155v195h207v-195h-207z" />
+<glyph unicode="ð" horiz-adv-x="1232" d="M117 461q0 211 120.5 331.5t342.5 120.5q160 0 249 -69q-27 152 -145 237l-180 -123l-78 113l104 74q-41 10 -90 10q-82 0 -145 -10v219q92 20 160 20q178 0 315 -75l162 112l78 -114l-123 -84q199 -195 198 -594q0 -645 -485 -645q-127 0 -224 43t-151.5 114.5 t-81 152.5t-26.5 167zM365 463q0 -31 7 -66t27.5 -84t72.5 -80.5t128 -31.5q39 0 76 16t79 57t65 132q19 76 19 179q0 19 -1 38q-103 69 -234 69h-4q-113 0 -174 -62.5t-61 -166.5z" />
+<glyph unicode="ñ" horiz-adv-x="1273" d="M168 0v926q225 112 469 112q213 0 333 -111.5t120 -312.5v-614h-244v635q0 41 -7.5 68.5t-30 57.5t-73.5 45t-131 15q-90 0 -190 -18v-803h-246zM326 1153v156q57 68 124 74q10 1 21 1q55 0 105 -26l122 -60q52 -25 105 -25q11 0 23 1q65 6 122 74v-156q-57 -68 -122 -74 q-12 -1 -23 -1q-53 0 -105 25l-122 60q-50 26 -105 26q-11 0 -21 -1q-67 -6 -124 -74z" />
+<glyph unicode="ò" horiz-adv-x="1159" d="M100 510q0 254 125 390t358.5 136t358.5 -136t125 -390t-125 -390t-358.5 -136t-358.5 136t-125 390zM326 1395h200l127 -240h-137zM346 509.5q0 -190.5 58.5 -257t179.5 -66.5t179 66.5t58 257t-58 257.5t-179 67t-179.5 -67t-58.5 -257.5z" />
+<glyph unicode="ó" horiz-adv-x="1159" d="M100 510q0 254 125 390t358.5 136t358.5 -136t125 -390t-125 -390t-358.5 -136t-358.5 136t-125 390zM346 509.5q0 -190.5 58.5 -257t179.5 -66.5t179 66.5t58 257t-58 257.5t-179 67t-179.5 -67t-58.5 -257.5zM514 1155l127 240h201l-191 -240h-137z" />
+<glyph unicode="ô" horiz-adv-x="1159" d="M100 510q0 254 125 390t358.5 136t358.5 -136t125 -390t-125 -390t-358.5 -136t-358.5 136t-125 390zM346 509.5q0 -190.5 58.5 -257t179.5 -66.5t179 66.5t58 257t-58 257.5t-179 67t-179.5 -67t-58.5 -257.5zM348 1155l187 250h98l188 -250h-164l-73 115l-74 -115h-162 z" />
+<glyph unicode="õ" horiz-adv-x="1159" d="M100 510q0 254 125 390t358.5 136t358.5 -136t125 -390t-125 -390t-358.5 -136t-358.5 136t-125 390zM272 1153v156q57 68 124 74q11 1 22 1q55 0 106 -26l122 -60q51 -25 104 -25q11 0 22 1q66 6 123 74v-156q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60 q-51 26 -106 26q-11 0 -22 -1q-66 -6 -124 -74zM346 509.5q0 -190.5 58.5 -257t179.5 -66.5t179 66.5t58 257t-58 257.5t-179 67t-179.5 -67t-58.5 -257.5z" />
+<glyph unicode="ö" horiz-adv-x="1159" d="M100 510q0 254 125 390t358.5 136t358.5 -136t125 -390t-125 -390t-358.5 -136t-358.5 136t-125 390zM317 1155v195h207v-195h-207zM346 509.5q0 -190.5 58.5 -257t179.5 -66.5t179 66.5t58 257t-58 257.5t-179 67t-179.5 -67t-58.5 -257.5zM645 1155v195h207v-195h-207z " />
+<glyph unicode="÷" horiz-adv-x="1286" d="M211 446v164h866v-164h-866zM541 129v193h207v-193h-207zM541 737v193h207v-193h-207z" />
+<glyph unicode="ø" horiz-adv-x="1175" d="M39 0l147 176q-82 133 -82 334q0 254 125 390t359 136q164 0 270 -63l43 49h238l-150 -176q82 -127 82 -336q0 -254 -125 -390t-358 -136q-158 0 -271 65l-41 -49h-237zM350 510q0 -68 10 -129l361 426q-47 27 -133 27q-121 0 -179.5 -67t-58.5 -257zM457 215 q51 -29 131 -29q121 0 179 67t58 257q0 82 -8 131z" />
+<glyph unicode="ù" horiz-adv-x="1259" d="M168 463v559h248v-592q0 -123 50 -177t163 -54q115 0 165 54t50 177v592h248v-559q0 -225 -123 -352t-340 -127t-339 127t-122 352zM381 1395h201l127 -240h-138z" />
+<glyph unicode="ú" horiz-adv-x="1259" d="M168 463v559h248v-592q0 -123 50 -177t163 -54q115 0 165 54t50 177v592h248v-559q0 -225 -123 -352t-340 -127t-339 127t-122 352zM551 1155l127 240h201l-191 -240h-137z" />
+<glyph unicode="û" horiz-adv-x="1259" d="M168 463v559h248v-592q0 -123 50 -177t163 -54q115 0 165 54t50 177v592h248v-559q0 -225 -123 -352t-340 -127t-339 127t-122 352zM393 1155l187 250h98l188 -250h-164l-73 115l-74 -115h-162z" />
+<glyph unicode="ü" horiz-adv-x="1259" d="M168 463v559h248v-592q0 -123 50 -177t163 -54q115 0 165 54t50 177v592h248v-559q0 -225 -123 -352t-340 -127t-339 127t-122 352zM362 1155v195h207v-195h-207zM690 1155v195h207v-195h-207z" />
+<glyph unicode="ý" horiz-adv-x="1107" d="M63 1022h261l229 -713l225 713h260l-389 -1047q-82 -223 -178 -300.5t-199 -77.5q-76 0 -155 28v205q59 -25 116 -25q106 0 197 248zM473 1155l127 240h201l-191 -240h-137z" />
+<glyph unicode="þ" horiz-adv-x="1157" d="M170 -377v1778h246v-389q88 16 168 16q229 0 352 -133t123 -383t-123 -383t-352 -133q-80 0 -168 16v-389h-246zM416 207q69 -17 159 -17q115 0 175.5 71t60.5 251t-60.5 251t-175.5 71q-90 0 -159 -17v-610z" />
+<glyph unicode="ÿ" horiz-adv-x="1107" d="M63 1022h261l229 -713l225 713h260l-389 -1047q-82 -223 -178 -300.5t-199 -77.5q-76 0 -155 28v205q59 -25 116 -25q106 0 197 248zM285 1155v195h207v-195h-207zM612 1155v195h207v-195h-207z" />
+<glyph unicode="Œ" horiz-adv-x="2074" d="M123 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16q147 0 270 -43v27h909v-221h-663v-346h575v-232h-575v-346h663v-221h-909v27q-115 -43 -270 -43q-96 0 -179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM369 684q0 -154 33.5 -255t98 -148.5 t131.5 -64.5t161 -17q145 0 237 37v884q-111 45 -237 45q-94 0 -161 -16t-131.5 -63.5t-98 -148.5t-33.5 -253z" />
+<glyph unicode="œ" horiz-adv-x="1853" d="M98 512q0 254 125 390t359 136q229 0 356 -133q119 135 338 135q225 0 341 -143t116 -352q0 -86 -4 -127h-658q10 -133 80 -178t180 -45q180 0 348 79v-198q-170 -92 -385 -92q-233 0 -356 135q-123 -133 -356.5 -133t-358.5 136t-125 390zM344 512q0 -190 58.5 -257 t179 -67t179 67t58.5 257t-58.5 257t-179 67t-179 -67t-58.5 -257zM1071 588h412q-10 106 -62.5 179t-144.5 73t-143.5 -59.5t-61.5 -192.5z" />
+<glyph unicode="Ÿ" horiz-adv-x="1368" d="M25 1366h297l362 -555l360 555h297l-534 -780v-586h-246v586zM418 1483v194h207v-194h-207zM745 1483v194h207v-194h-207z" />
+<glyph unicode="ˆ" horiz-adv-x="874" d="M201 1155l186 250h98l189 -250h-164l-74 115l-74 -115h-161z" />
+<glyph unicode="˜" horiz-adv-x="1058" d="M215 1153v156q57 68 124 74q11 1 22 1q55 0 105 -26l122 -60q51 -25 104 -25q11 0 22 1q66 6 124 74v-156q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -105 25l-122 60q-50 26 -105 26q-11 0 -22 -1q-67 -6 -124 -74z" />
+<glyph unicode=" " horiz-adv-x="888" />
+<glyph unicode=" " horiz-adv-x="1776" />
+<glyph unicode=" " horiz-adv-x="888" />
+<glyph unicode=" " horiz-adv-x="1776" />
+<glyph unicode=" " horiz-adv-x="592" />
+<glyph unicode=" " horiz-adv-x="444" />
+<glyph unicode=" " horiz-adv-x="296" />
+<glyph unicode=" " horiz-adv-x="296" />
+<glyph unicode=" " horiz-adv-x="222" />
+<glyph unicode=" " horiz-adv-x="355" />
+<glyph unicode=" " horiz-adv-x="98" />
+<glyph unicode="‐" horiz-adv-x="950" d="M162 514v193h624v-193h-624z" />
+<glyph unicode="‑" horiz-adv-x="950" d="M162 514v193h624v-193h-624z" />
+<glyph unicode="‒" horiz-adv-x="950" d="M162 514v193h624v-193h-624z" />
+<glyph unicode="–" horiz-adv-x="1146" d="M141 500v192h860v-192h-860z" />
+<glyph unicode="—" horiz-adv-x="1824" d="M162 500v192h1501v-192h-1501z" />
+<glyph unicode="‘" horiz-adv-x="552" d="M96 1384h215l152 -401h-184z" />
+<glyph unicode="’" horiz-adv-x="552" d="M96 983l152 401h215l-182 -401h-185z" />
+<glyph unicode="‚" horiz-adv-x="651" d="M147 -229l152 401h215l-182 -401h-185z" />
+<glyph unicode="“" horiz-adv-x="858" d="M96 1384h215l152 -401h-184zM408 1384h215l151 -401h-184z" />
+<glyph unicode="”" horiz-adv-x="858" d="M96 983l152 401h215l-182 -401h-185zM408 983l151 401h215l-182 -401h-184z" />
+<glyph unicode="„" horiz-adv-x="948" d="M147 -229l152 401h215l-182 -401h-185zM459 -229l151 401h215l-182 -401h-184z" />
+<glyph unicode="•" horiz-adv-x="890" d="M172 661.5q0 112.5 80 192.5t194 80q113 0 192 -80t79 -192.5t-79 -192.5t-192 -80q-115 0 -194.5 80t-79.5 192.5z" />
+<glyph unicode="…" horiz-adv-x="1726" d="M219 0v227h240v-227h-240zM743 0v227h240v-227h-240zM1266 0v227h239v-227h-239z" />
+<glyph unicode=" " horiz-adv-x="355" />
+<glyph unicode="‹" horiz-adv-x="770" d="M63 522l347 389h286l-372 -389l372 -391h-286z" />
+<glyph unicode="›" horiz-adv-x="815" d="M111 131l372 389l-372 391h286l346 -391l-346 -389h-286z" />
+<glyph unicode=" " horiz-adv-x="444" />
+<glyph unicode="€" d="M102 412v178h162q-4 57 -4 94t4 94h-162v178h191q115 428 604 428q209 0 414 -81v-230q-194 90 -391 90q-137 0 -231.5 -43t-141.5 -164h530v-178h-567q-4 -57 -4 -94t4 -94h567v-178h-530q47 -121 141 -164t232 -43q197 0 391 90v-229q-205 -82 -414 -82 q-492 0 -604 428h-191z" />
+<glyph unicode="™" horiz-adv-x="1572" d="M100 1245v121h559v-121h-219v-561h-121v561h-219zM762 684v682h137l197 -285l196 285h138v-682h-123v506l-211 -303l-211 303v-506h-123z" />
+<glyph unicode="" horiz-adv-x="1020" d="M0 0v1020h1020v-1020h-1020z" />
+<glyph unicode="fi" horiz-adv-x="1333" d="M82 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174zM950 1161h252v225h-252v-225zM954 0h246v1022h-246v-1022z" />
+<glyph unicode="fl" horiz-adv-x="1333" d="M82 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174zM958 0h246v1411h-246v-1411z" />
+<glyph unicode="ffi" horiz-adv-x="2100" d="M82 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174zM813 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174zM1655 1161v225 h251v-225h-251zM1659 0v1022h245v-1022h-245z" />
+<glyph unicode="ffl" horiz-adv-x="2121" d="M82 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174zM813 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174zM1669 0v1411h246 v-1411h-246z" />
+</font>
+</defs></svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="maven_promedium" horiz-adv-x="1529" >
+<font-face units-per-em="2048" ascent="1638" descent="-410" />
+<missing-glyph horiz-adv-x="692" />
+<glyph horiz-adv-x="2048" />
+<glyph horiz-adv-x="2048" />
+<glyph unicode="
" horiz-adv-x="682" />
+<glyph unicode=" " horiz-adv-x="692" />
+<glyph unicode="	" horiz-adv-x="692" />
+<glyph unicode=" " horiz-adv-x="692" />
+<glyph unicode="!" horiz-adv-x="800" d="M299 1366h195l-17 -1032h-162zM303 0v174h186v-174h-186z" />
+<glyph unicode=""" horiz-adv-x="821" d="M166 1384h190l-34 -411h-119zM461 1384h190l-37 -411h-118z" />
+<glyph unicode="#" horiz-adv-x="1210" d="M145 260v135h183l55 318h-180v135h203l38 221h144l-39 -221h256l39 221h143l-39 -221h137v-135h-159l-56 -318h158v-135h-180l-39 -221h-143l39 221h-259l-36 -221h-144l39 221h-160zM471 395h256l55 318h-256z" />
+<glyph unicode="$" horiz-adv-x="1198" d="M158 993q0 145 96 256t299 131v209h127v-207q160 -10 307 -86v-188q-141 86 -307 90v-418q86 -31 145.5 -60.5t122 -78.5t95 -120t32.5 -163q0 -156 -103.5 -252t-291.5 -116v-211h-127v207q-193 6 -377 98v197q180 -109 377 -115v467q-76 23 -118 38t-102.5 46t-93 64.5 t-57 89t-24.5 122.5zM348 993q0 -57 51.5 -94t153.5 -74v367q-205 -35 -205 -199zM680 174q205 35 205 184q0 86 -53.5 138.5t-151.5 91.5v-414z" />
+<glyph unicode="%" horiz-adv-x="1814" d="M145 981q0 178 75 278.5t222.5 100.5t222 -100.5t74.5 -278.5t-74.5 -277.5t-222 -99.5t-222.5 99.5t-75 277.5zM276 981q0 -133 38 -197.5t128 -64.5t128 64.5t38 197.5q0 135 -38 198.5t-128 63.5t-128 -63.5t-38 -198.5zM410 0l858 1366h151l-858 -1366h-151z M1087 385q0 176 75 276.5t222.5 100.5t222 -100.5t74.5 -276.5q0 -178 -74.5 -278.5t-222 -100.5t-222.5 100.5t-75 278.5zM1219 385q0 -135 37.5 -198.5t128 -63.5t128 63.5t37.5 198.5q0 133 -37.5 197.5t-128 64.5t-128 -64.5t-37.5 -197.5z" />
+<glyph unicode="&" horiz-adv-x="1325" d="M111 385q0 115 54 216t210 192q-127 158 -127 272q0 131 93 225t226 94q135 0 227.5 -90t92.5 -221q0 -109 -56.5 -183.5t-183.5 -148.5l266 -319q37 84 54 176h176q-27 -168 -115 -313l238 -285h-228l-116 141q-157 -157 -374 -157h-3q-113 0 -200 39.5t-136 103 t-73.5 129.5t-24.5 129zM281 379q0 -31 11 -65.5t38.5 -75.5t84 -68t134.5 -27q156 0 270 123l-327 396q-74 -39 -121 -79t-64.5 -81t-21.5 -64.5t-4 -58.5zM416 1077.5q0 -65.5 114 -203.5q119 68 155 111t36 100q0 63 -43 105.5t-109 42.5q-63 0 -108 -45t-45 -110.5z" />
+<glyph unicode="'" horiz-adv-x="524" d="M166 1384h190l-34 -411h-119z" />
+<glyph unicode="(" horiz-adv-x="722" d="M190 627q0 440 211 856h185q-211 -416 -211 -856q0 -436 211 -858h-185q-211 422 -211 858z" />
+<glyph unicode=")" horiz-adv-x="722" d="M133 -231q211 412 211 858q0 449 -211 856h184q211 -416 211 -856q0 -436 -211 -858h-184z" />
+<glyph unicode="*" horiz-adv-x="841" d="M172 1118l162 70l-162 72l64 108l139 -100l-19 168h125l-14 -168l139 100l64 -108l-160 -72l160 -70l-64 -112l-139 102l14 -166h-125l19 166l-139 -102z" />
+<glyph unicode="+" horiz-adv-x="1150" d="M199 545v141h297v297h141v-297h295v-141h-295v-295h-141v295h-297z" />
+<glyph unicode="," horiz-adv-x="667" d="M152 -229l151 401h193l-172 -401h-172z" />
+<glyph unicode="-" horiz-adv-x="950" d="M162 530v160h624v-160h-624z" />
+<glyph unicode="." horiz-adv-x="712" d="M260 0v182h195v-182h-195z" />
+<glyph unicode="/" horiz-adv-x="870" d="M-29 -201l752 1749h178l-754 -1749h-176z" />
+<glyph unicode="0" horiz-adv-x="1351" d="M133 684q0 166 34 294t86 202.5t126 122t145.5 63.5t153.5 16q104 0 191 -29.5t171 -101t132.5 -216t48.5 -351.5q0 -168 -34 -295t-86 -202.5t-126 -123t-144.5 -63.5t-152.5 -16t-153.5 16t-145.5 63.5t-126 123t-86 202.5t-34 295zM324 684q0 -135 21.5 -234.5 t54 -154.5t82.5 -88t94.5 -42t101.5 -9q55 0 100 9t94.5 42t82 88t54 154.5t21.5 234.5t-21.5 233.5t-54 153.5t-82 88t-94.5 42t-100 9q-57 0 -101.5 -9t-94.5 -42t-82.5 -88t-54 -153.5t-21.5 -233.5z" />
+<glyph unicode="1" horiz-adv-x="925" d="M211 1040v199l223 127h207v-1366h-190v1180z" />
+<glyph unicode="2" horiz-adv-x="1189" d="M158 0v152q111 84 214 177t210.5 208.5t172 234.5t64.5 215q0 219 -303 219q-184 0 -336 -102v180q170 98 361 98q238 0 351.5 -115.5t113.5 -277.5q0 -346 -519 -799h533v-190h-862z" />
+<glyph unicode="3" horiz-adv-x="1193" d="M166 63v187q160 -92 334 -92q338 0 338 239.5t-359 239.5h-119v168h115q295 0 295 199q0 200 -292 200h-3q-152 0 -278 -69v182q139 65 296 65h5q238 0 350.5 -110.5t112.5 -267.5q0 -177 -162 -273q229 -102 229 -332q0 -80 -27.5 -150.5t-86 -132t-161 -97 t-237.5 -35.5q-178 0 -350 79z" />
+<glyph unicode="4" horiz-adv-x="1210" d="M78 430v170l600 766h213v-766h194v-170h-194v-430h-191v430h-622zM303 600h397v494z" />
+<glyph unicode="5" horiz-adv-x="1228" d="M178 739l70 627h756v-176h-590l-33 -348q96 26 199 26h8q238 0 361.5 -112.5t123.5 -329.5t-124 -329.5t-361.5 -112.5t-401.5 102v205q133 -125 385 -125q180 0 246 65.5t66 194.5q0 131 -69 196.5t-263 65.5q-150 0 -277 -49z" />
+<glyph unicode="6" horiz-adv-x="1202" d="M133 627q0 141 25.5 256.5t65.5 191.5t96.5 134.5t111.5 90t119.5 51t112 25.5t94.5 6q106 0 211 -28v-172q-81 24 -175 24h-8q-180 0 -295.5 -100t-154.5 -287q111 72 285 72q240 0 348 -128t108 -310q0 -193 -120.5 -331t-350.5 -138q-473 0 -473 643zM324 616 q2 -131 24.5 -224t50 -138t72.5 -68.5t70.5 -26.5t64.5 -3q88 0 149.5 32.5t87 85t35 94.5t9.5 85q0 264 -281 264q-176 0 -282 -101z" />
+<glyph unicode="7" horiz-adv-x="1189" d="M143 1178v188h951v-180l-572 -1186h-211l580 1178h-748z" />
+<glyph unicode="8" horiz-adv-x="1196" d="M125 416q0 104 57.5 188t147.5 131q-154 98 -154 283q0 150 105.5 257t324.5 107t324.5 -107.5t105.5 -258.5q0 -186 -151 -279q200 -105 200 -319q0 -180 -122.5 -307t-356 -127t-357.5 128t-124 304zM315 414q0 -37 9.5 -74t37 -82t90 -72.5t154.5 -27.5t154.5 27.5 t90.5 72.5t37 82t9 74q0 225 -291 225t-291 -225zM365 1022q0 -31 8 -60.5t30.5 -67.5t74.5 -60.5t128 -22.5t128 22.5t73.5 60.5t30 67.5t8.5 60.5q0 186 -240 186q-241 0 -241 -186z" />
+<glyph unicode="9" horiz-adv-x="1212" d="M119 913q0 193 120.5 331t352.5 138q471 0 471 -643q0 -141 -25.5 -256.5t-65.5 -191.5t-96.5 -134.5t-111.5 -90t-119.5 -51t-112 -25.5t-94.5 -6q-106 0 -211 28v172q85 -24 176 -24h7q180 0 295.5 100t154.5 287q-111 -72 -285 -72q-240 0 -348 128t-108 310zM309 913 q0 -264 283 -264q176 0 282 101q-2 131 -24.5 224t-50 138t-72.5 68.5t-70.5 26.5t-64.5 3q-90 0 -151.5 -32.5t-87 -85t-35 -94.5t-9.5 -85z" />
+<glyph unicode=":" horiz-adv-x="813" d="M313 94v180h193v-180h-193zM313 741v181h193v-181h-193z" />
+<glyph unicode=";" horiz-adv-x="858" d="M184 -229l152 401h192l-172 -401h-172zM340 741v181h192v-181h-192z" />
+<glyph unicode="<" horiz-adv-x="1357" d="M246 477v148l850 379v-181l-643 -272l643 -272v-181z" />
+<glyph unicode="=" horiz-adv-x="1306" d="M260 365v159h789v-159h-789zM260 698v160h789v-160h-789z" />
+<glyph unicode=">" horiz-adv-x="1337" d="M270 98v181l641 272l-641 272v181l848 -379v-148z" />
+<glyph unicode="?" horiz-adv-x="1042" d="M164 1083v203q150 96 321 96q115 0 200 -33.5t132 -92t69.5 -126t22.5 -145.5q0 -82 -30.5 -146.5t-75.5 -106.5l-90 -83q-45 -41 -76 -100.5t-31 -134.5v-70h-180v76q0 104 30.5 184t76 127t89.5 86t74.5 81t30.5 89q0 219 -258 219q-166 0 -305 -123zM416 0v186h200 v-186h-200z" />
+<glyph unicode="@" horiz-adv-x="1869" d="M170 371q0 184 79 348t204 273.5t280 174t311 64.5q82 0 171.5 -21.5t180.5 -74t162.5 -126t117.5 -188t46 -254.5q0 -129 -33.5 -239.5t-113.5 -188.5t-193 -78q-137 0 -186 127q-90 -127 -293 -127q-143 0 -227 64.5t-84 175.5q0 51 16.5 96t59.5 95.5t138 80t232 29.5 q84 0 142 -8q0 4 1 15.5t1 17.5q0 35 -6.5 59.5t-26 50t-62.5 37.5t-110 12q-131 0 -252 -49l18 129q135 45 252 45q156 0 239 -72.5t83 -211.5q0 -53 -9 -129l-16 -125q-4 -37 -4 -74v-25q2 -49 26.5 -72.5t76.5 -23.5q88 0 151.5 97.5t63.5 287.5q0 143 -56.5 255 t-145.5 173.5t-183.5 92t-184.5 30.5q-184 0 -357 -98t-284.5 -273.5t-111.5 -377.5q0 -127 45 -229.5t109.5 -162t145.5 -99.5t144.5 -54.5t114.5 -14.5q250 0 442 103l-18 -133q-184 -82 -428 -82q-82 0 -169 22.5t-177.5 74.5t-161 128t-115.5 193.5t-45 259.5zM725 305 q0 -108 175 -108h3q141 0 188.5 51t61.5 153q4 49 8 74q-55 6 -123 6q-313 0 -313 -176z" />
+<glyph unicode="A" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM463 561h434l-217 567z" />
+<glyph unicode="B" horiz-adv-x="1280" d="M178 0v1366h473q139 0 234.5 -37t138.5 -98.5t59.5 -117.5t16.5 -120q0 -59 -16.5 -109t-40 -80t-48 -51.5t-41.5 -29.5l-16 -8l26 -11q16 -6 57.5 -32.5t73 -59t58.5 -92t27 -129.5q0 -68 -18.5 -127t-65.5 -123.5t-150.5 -102.5t-255.5 -38h-512zM369 168l309 2 q158 0 234.5 53t76.5 176q0 127 -84 173t-241 46h-295v-450zM369 788h280q127 0 193.5 42t66.5 157q0 117 -70.5 163t-207.5 46h-262v-408z" />
+<glyph unicode="C" horiz-adv-x="1286" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16q213 0 418 -81v-177q-193 86 -396 86q-242 0 -351 -124.5t-109 -403.5q0 -276 109 -402t351 -126q203 0 396 86v-176q-205 -82 -418 -82q-94 0 -177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295z" />
+<glyph unicode="D" horiz-adv-x="1380" d="M178 0v1366h436q315 0 478 -174t163 -510t-162.5 -509t-478.5 -173h-436zM369 168h215q250 0 366.5 124t116.5 390q0 268 -116.5 392t-366.5 124h-215v-1030z" />
+<glyph unicode="E" horiz-adv-x="1187" d="M178 0v1366h891v-178h-700v-416h616v-178h-616v-416h700v-178h-891z" />
+<glyph unicode="F" horiz-adv-x="1124" d="M178 0v1366h860v-178h-669v-416h589v-178h-589v-594h-191z" />
+<glyph unicode="G" horiz-adv-x="1386" d="M125 684q0 166 39 294t100.5 202.5t147.5 122t169 63.5t179 16q227 0 430 -79v-175q-199 82 -412 82q-74 0 -132 -9t-122.5 -42t-107.5 -88t-72 -153.5t-29 -233.5t29 -234.5t72 -154.5t108.5 -88t125 -42t132.5 -9q135 0 275 39v385h-279v178h463v-666 q-227 -108 -476 -108h-5q-96 0 -179 16t-169 63.5t-147.5 123t-100.5 202.5t-39 295z" />
+<glyph unicode="H" horiz-adv-x="1388" d="M178 0v1366h189v-594h655v594h188v-1366h-188v594h-655v-594h-189z" />
+<glyph unicode="I" horiz-adv-x="544" d="M178 0v1366h189v-1366h-189z" />
+<glyph unicode="J" horiz-adv-x="931" d="M61 66v198q119 -96 269 -96q117 0 174 49t57 186v963h189v-950q0 -133 -40 -225.5t-106.5 -134.5t-127 -58t-128.5 -16q-158 0 -287 84z" />
+<glyph unicode="K" horiz-adv-x="1380" d="M178 0v1366h189v-661l626 661h240l-580 -618l631 -748h-235l-521 614l-161 -172v-442h-189z" />
+<glyph unicode="L" horiz-adv-x="1089" d="M178 0v1366h189v-1188h680v-178h-869z" />
+<glyph unicode="M" horiz-adv-x="1665" d="M178 0v1366h205l448 -590l451 590h205v-1366h-189v1077l-467 -612l-464 612v-1077h-189z" />
+<glyph unicode="N" horiz-adv-x="1511" d="M178 0v1366h217l764 -1087v1087h191v-1366h-211l-770 1094v-1094h-191z" />
+<glyph unicode="O" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16t177 -16t170 -63.5t148.5 -123t100.5 -202.5t39 -295t-39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16t-177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5 t103.5 -88t117.5 -42t126.5 -9t127 9t118 42t103.5 88t68.5 154.5t27.5 234.5t-27.5 234.5t-68.5 154.5t-103.5 88t-118 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5z" />
+<glyph unicode="P" horiz-adv-x="1228" d="M176 0v1366h492q227 0 352 -112.5t125 -317.5t-125 -317.5t-352 -112.5h-301v-506h-191zM367 674h284q182 0 243.5 70.5t61.5 189.5t-61 189.5t-244 70.5h-284v-520z" />
+<glyph unicode="Q" d="M125 684q0 166 39 294t100.5 202.5t148.5 122t170 63.5t177 16q96 0 179 -16t169 -63.5t147.5 -122t100.5 -202.5t39 -294q0 -182 -45 -317.5t-124 -212t-167 -114.5t-195 -50q0 -82 54.5 -143.5t150.5 -61.5v-145q-170 0 -280.5 101t-110.5 247q-111 10 -202 46 t-174 111.5t-130 213t-47 325.5zM315 684q0 -135 28 -234.5t69 -154.5t103.5 -88t118.5 -42t125.5 -9t126 9t119 42t104.5 88t69.5 154.5t27.5 234.5t-27.5 233.5t-68.5 153.5t-104.5 88t-119 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -153.5t-28 -233.5z" />
+<glyph unicode="R" horiz-adv-x="1298" d="M178 0v1366h504q229 0 360.5 -107.5t131.5 -306.5q0 -168 -87.5 -267t-218.5 -136l363 -549h-225l-330 518h-307v-518h-191zM369 698h305q178 0 243.5 69t65.5 179q0 250 -317 250h-297v-498z" />
+<glyph unicode="S" horiz-adv-x="1155" d="M125 993q0 162 116.5 275.5t360.5 113.5q184 0 352 -86v-188q-150 90 -329 90q-310 0 -310 -205q0 -55 54.5 -96t134.5 -65.5t175 -63.5t175 -83t134 -129t54 -198q0 -176 -131 -275t-362 -99q-205 0 -406 100v197q190 -115 389 -115q319 0 320 192q0 82 -54 140t-134 84 l-176 59q-96 33 -175.5 67.5t-133.5 107.5t-54 177z" />
+<glyph unicode="T" horiz-adv-x="1163" d="M25 1188v178h1112v-178h-463v-1188h-189v1188h-460z" />
+<glyph unicode="U" horiz-adv-x="1474" d="M178 563v803h191v-819q0 -201 90 -297t278.5 -96t278.5 96t90 297v819h190v-803q0 -276 -146 -428.5t-412.5 -152.5t-413 152.5t-146.5 428.5z" />
+<glyph unicode="V" horiz-adv-x="1288" d="M25 1366h202l416 -1126l418 1126h203l-525 -1366h-190z" />
+<glyph unicode="W" horiz-adv-x="1961" d="M25 1366h198l338 -1090l336 1090h162l338 -1090l336 1090h198l-438 -1366h-195l-319 1026l-322 -1026h-194z" />
+<glyph unicode="X" horiz-adv-x="1253" d="M25 0l489 684l-477 682h231l359 -510l358 510h232l-484 -664l496 -702h-234l-368 526l-371 -526h-231z" />
+<glyph unicode="Y" horiz-adv-x="1275" d="M25 1366h229l383 -559l383 559h231l-520 -754v-612h-188v610z" />
+<glyph unicode="Z" horiz-adv-x="1247" d="M131 0v166l721 1010h-700v190h946v-170l-717 -1008h717v-188h-967z" />
+<glyph unicode="[" horiz-adv-x="784" d="M244 -238v1663h370v-151h-213v-1360h213v-152h-370z" />
+<glyph unicode="\" horiz-adv-x="802" d="M-76 1548h178l752 -1749h-176z" />
+<glyph unicode="]" horiz-adv-x="780" d="M172 -86h211v1360h-211v151h369v-1663h-369v152z" />
+<glyph unicode="^" horiz-adv-x="1146" d="M272 969l215 444h113l217 -444h-143l-129 303l-129 -303h-144z" />
+<glyph unicode="_" horiz-adv-x="1118" d="M0 -49h1118v-115h-1118v115z" />
+<glyph unicode="`" horiz-adv-x="491" d="M90 1395h184l127 -240h-116z" />
+<glyph unicode="a" horiz-adv-x="1093" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q145 55 309 55q213 0 329 -110.5t116 -317.5v-295q0 -33 -6.5 -65.5t-31 -83.5t-66.5 -89t-126 -65.5t-194 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M301 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88z" />
+<glyph unicode="b" horiz-adv-x="1167" d="M170 315v1096h186v-403q106 30 219 30q88 0 164 -21.5t149 -74.5t114.5 -159.5t41.5 -258.5q0 -135 -31.5 -236.5t-77.5 -157.5t-110.5 -91t-119 -45t-113.5 -10q-111 0 -194 27.5t-125 65.5t-66.5 89t-30.5 84t-6 65zM356 303q0 -153 236 -153q53 0 94 13t83 49 t64.5 115t22.5 197q0 111 -22.5 183.5t-64.5 106.5t-87 46t-107 12q-115 0 -219 -43v-526z" />
+<glyph unicode="c" horiz-adv-x="966" d="M119 512q0 256 124 391t357 135q156 0 301 -57v-178q-139 67 -281 67h-4q-141 0 -221 -72.5t-80 -285.5q0 -113 23.5 -187.5t70 -110.5t94 -48t113.5 -12q147 0 285 65v-178q-145 -57 -301 -57q-233 0 -357 136t-124 392z" />
+<glyph unicode="d" horiz-adv-x="1150" d="M129 524q0 152 42 258.5t114.5 159.5t148.5 74.5t164 21.5q111 0 219 -30v403h187v-1096q0 -33 -6.5 -65.5t-31 -83.5t-66.5 -89t-125 -65.5t-193 -27.5q-59 0 -113.5 10t-119 45t-110.5 91t-78 157.5t-32 236.5zM315 524q0 -119 22.5 -197.5t64.5 -114.5t83 -49t97 -13 q236 0 235 153v526q-104 43 -219 43q-61 0 -106.5 -12t-88.5 -46t-65.5 -106.5t-22.5 -183.5z" />
+<glyph unicode="e" horiz-adv-x="1136" d="M129 498q0 540 451 540h2q205 0 327.5 -134t122.5 -359q0 -74 -6 -142h-704q34 -260 288 -260q207 0 353 76v-170q-158 -67 -357 -67h-6q-90 0 -166 22.5t-148.5 74.5t-114.5 159.5t-42 259.5zM319 573h525q-4 51 -17.5 97.5t-41 94.5t-79.5 76.5t-124 28.5 q-129 0 -189.5 -66.5t-73.5 -230.5z" />
+<glyph unicode="f" horiz-adv-x="675" d="M82 858v164h143v49q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5t-14 -122v-49h264v-164h-264v-858h-187v858h-143z" />
+<glyph unicode="g" horiz-adv-x="1101" d="M123 518q0 238 107.5 380t330.5 142q186 0 298 -84t112 -239v-760q0 -68 -19.5 -126t-65.5 -113.5t-138.5 -87t-225.5 -31.5q-154 0 -303 43v178q145 -45 287 -45q98 0 159.5 17.5t85 50t28.5 55t5 59.5v90q-104 -29 -206 -29q-219 0 -337 129t-118 371zM311 518 q0 -199 71 -264.5t196 -65.5q110 0 206 39v502q0 23 -4 39t-22.5 45t-68.5 44t-128 15q-51 0 -89 -12t-78 -46t-61.5 -108.5t-21.5 -187.5z" />
+<glyph unicode="h" horiz-adv-x="1187" d="M193 0v1411h188v-424q131 51 270 51q190 0 295 -113.5t105 -322.5v-602h-189v608q0 150 -63.5 200t-165.5 50q-125 0 -252 -49v-809h-188z" />
+<glyph unicode="i" horiz-adv-x="589" d="M195 1180v198h200v-198h-200zM201 0v1022h188v-1022h-188z" />
+<glyph unicode="j" horiz-adv-x="526" d="M6 -242q109 0 142.5 47.5t33.5 135.5v1081h187v-1081q0 -342 -363 -342v159zM176 1180v198h199v-198h-199z" />
+<glyph unicode="k" horiz-adv-x="1204" d="M188 0v1411h187v-817l442 428h254l-444 -426l503 -596h-249l-379 475l-127 -123v-352h-187z" />
+<glyph unicode="l" horiz-adv-x="602" d="M207 0v1411h188v-1411h-188z" />
+<glyph unicode="m" horiz-adv-x="1853" d="M193 0v938q205 102 425 102q166 0 273 -77q193 77 375 77q193 0 301 -101t108 -282v-657h-186v670q0 88 -43 142t-168 54q-135 0 -274 -57q24 -65 24 -146v-6v-657h-188v668q0 90 -47.5 142t-188.5 52q-109 0 -223 -31v-831h-188z" />
+<glyph unicode="n" horiz-adv-x="1198" d="M168 0v934q213 106 436 106q193 0 302.5 -101t109.5 -280v-659h-189v672q0 90 -46 142t-185 52q-125 0 -240 -37v-829h-188z" />
+<glyph unicode="o" horiz-adv-x="1161" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170 22.5t170 -22.5t151.5 -76.5t117 -162.5t43 -264.5t-43 -264.5t-117 -162.5t-151.5 -76.5t-170 -22.5t-170 22.5t-151.5 76.5t-117 162.5t-43 264.5zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12 t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5z" />
+<glyph unicode="p" horiz-adv-x="1167" d="M168 -389v1098q0 33 6 65.5t30.5 82.5t66.5 88t126 65.5t195 27.5q57 0 111.5 -10t119 -45t111.5 -91t78.5 -157.5t31.5 -234.5q0 -152 -41.5 -259.5t-114.5 -159.5t-148.5 -74.5t-166.5 -22.5q-113 0 -217 30v-403h-188zM356 193q106 -41 217 -41q63 0 107.5 12t87.5 46 t65.5 106.5t22.5 183.5q0 119 -22.5 197.5t-64.5 114.5t-83 49t-94 13q-236 0 -236 -153v-528z" />
+<glyph unicode="q" horiz-adv-x="1157" d="M115 500q0 133 31.5 234.5t77.5 157.5t110.5 91t119 45t111.5 10q111 0 195 -27.5t126 -65.5t66.5 -88t30.5 -83t6 -65v-1098h-188v403q-104 -30 -217 -30q-90 0 -166 21.5t-147.5 74.5t-113.5 160.5t-42 259.5zM301 500q0 -111 22.5 -183.5t65.5 -106.5t87 -46t108 -12 q113 0 217 41v528q0 153 -236 153q-53 0 -94 -13t-83 -49t-64.5 -115t-22.5 -197z" />
+<glyph unicode="r" horiz-adv-x="702" d="M166 0v936q256 102 498 102v-172q-152 -2 -310 -43v-823h-188z" />
+<glyph unicode="s" horiz-adv-x="892" d="M102 733q0 127 90.5 216t276.5 89q139 0 274 -67v-162q-133 61 -251 61q-203 0 -203 -131q0 -43 54 -71.5t130 -50t152.5 -52t131 -102.5t54.5 -178q0 -123 -93 -212t-288 -89q-164 0 -295 71v170q133 -75 283 -75h4q98 0 149.5 35.5t51.5 90.5q0 63 -53.5 101.5 t-130.5 60t-153.5 48t-130 88t-53.5 159.5z" />
+<glyph unicode="t" horiz-adv-x="765" d="M49 864v158h178v305h187v-305h272v-158h-272v-594q0 -120 151 -120h2q70 0 119 30v-168q-70 -28 -140 -28h-5q-37 0 -79 9t-100.5 35.5t-96.5 92t-38 162.5v581h-178z" />
+<glyph unicode="u" horiz-adv-x="1208" d="M168 430v592h186v-606q0 -139 59.5 -204t186.5 -65t185.5 64.5t58.5 204.5v606h188v-592q0 -213 -113.5 -330.5t-318.5 -117.5t-318.5 117.5t-113.5 330.5z" />
+<glyph unicode="v" horiz-adv-x="1107" d="M66 1022h200l287 -797l289 797h200l-393 -1022h-192z" />
+<glyph unicode="w" horiz-adv-x="1656" d="M80 1022h207l229 -756l225 756h174l228 -756l227 756h207l-338 -1022h-186l-224 711l-225 -711h-186z" />
+<glyph unicode="x" horiz-adv-x="1073" d="M57 0l369 510l-360 512h229l242 -342l241 342h230l-361 -498l369 -524h-230l-249 354l-250 -354h-230z" />
+<glyph unicode="y" horiz-adv-x="1073" d="M63 1022h201l275 -766l270 766h201l-420 -1131q-109 -294 -317 -294h-1q-68 0 -143 16v168q61 -16 115 -17q127 0 205 236z" />
+<glyph unicode="z" horiz-adv-x="962" d="M92 0v172l520 676h-508v174h748v-172l-518 -674h518v-176h-760z" />
+<glyph unicode="{" horiz-adv-x="763" d="M186 535v137q109 31 109 127v313q0 156 82 238t237 82h17v-158q-84 0 -120 -32t-36 -124v-307q0 -123 -119 -207q119 -88 119 -209v-307q0 -92 36 -123t120 -33v-157h-17q-156 0 -237.5 83t-81.5 238v312q0 96 -109 127z" />
+<glyph unicode="|" horiz-adv-x="946" d="M379 -203v1569h188v-1569h-188z" />
+<glyph unicode="}" horiz-adv-x="759" d="M156 -68q84 2 119.5 33t35.5 123v307q0 121 119 209q-119 84 -119 207v307q0 94 -35.5 125t-119.5 31v158h18q154 0 237 -82t83 -238v-313q0 -94 106 -127v-137q-106 -33 -106 -127v-312q0 -156 -83 -238.5t-237 -82.5h-18v157z" />
+<glyph unicode="~" horiz-adv-x="1024" d="M201 473v135q57 68 123 74q11 1 22 1q55 0 106 -25l122 -61q50 -25 103 -25q12 0 23 2q66 6 123 73v-135q-57 -68 -123 -74q-10 -1 -21 -1q-54 0 -105 25l-122 60q-51 26 -106 26q-11 0 -22 -1q-66 -6 -123 -74z" />
+<glyph unicode="¡" horiz-adv-x="788" d="M299 -344l16 1030h162l17 -1030h-195zM303 848v174h186v-174h-186z" />
+<glyph unicode="¢" horiz-adv-x="976" d="M117 684q0 238 108.5 373t313.5 151v158h123v-158q131 -12 237 -55v-178q-123 57 -237 65v-712q121 8 237 63v-178q-117 -47 -237 -55v-158h-123v158q-205 16 -313.5 152t-108.5 374zM313 684q0 -182 59.5 -260t166.5 -94v708q-106 -16 -166 -94t-60 -260z" />
+<glyph unicode="£" horiz-adv-x="1155" d="M104 -53v182q82 66 181 82v360h-181v168h181v258q0 74 23.5 139.5t71.5 122t134 89t201 32.5q172 0 325 -90v-192q-143 108 -307 108q-264 0 -264 -209v-258h389v-168h-389v-366q45 -8 139 -33t150 -35q34 -6 75 -6q28 0 61 3q79 7 146 44v-182q-86 -47 -186 -47 t-191.5 25.5t-183.5 47.5q-56 14 -114 14q-38 0 -77 -6q-98 -14 -184 -83z" />
+<glyph unicode="¥" horiz-adv-x="1298" d="M37 1366h229l383 -559l383 559h232l-435 -629h215v-153h-301v-162h301v-154h-301v-268h-188v268h-301v154h301v162h-301v153h215z" />
+<glyph unicode="§" horiz-adv-x="1198" d="M131 725q0 160 225 225q-72 55 -71 140q0 125 100 209.5t313 84.5q166 0 322 -77v-181q-145 80 -291 80q-248 0 -248 -125q0 -31 45 -54t112 -39.5t144.5 -43t144 -60.5t111.5 -96.5t45 -144.5q0 -90 -64.5 -145.5t-160.5 -79.5q72 -53 72 -131q0 -125 -101.5 -214 t-314.5 -89q-190 0 -375 106v191q174 -117 348 -117q246 0 246 123q0 31 -45 54.5t-111.5 39.5t-144.5 43t-144.5 60.5t-111.5 96t-45 144.5zM328 723q0 -33 19.5 -59.5t61.5 -46t79.5 -34t101 -34t100.5 -33.5q90 8 143.5 40t53.5 81q0 33 -19.5 58.5t-61.5 46t-80 35 t-101.5 33.5t-100.5 34q-90 -8 -143 -41t-53 -80z" />
+<glyph unicode="¨" horiz-adv-x="831" d="M162 1155v174h186v-174h-186zM465 1155v174h186v-174h-186z" />
+<glyph unicode="©" horiz-adv-x="1593" d="M98 680q0 289 204 493.5t492.5 204.5t493.5 -204.5t205 -493.5t-205 -492.5t-493.5 -203.5t-492.5 203.5t-204 492.5zM188 680q0 -250 178.5 -428t428.5 -178q252 0 430 178t178 428q0 252 -178 430t-430 178q-250 0 -428.5 -178t-178.5 -430zM432 680q0 121 33 206 t90 127t117.5 59.5t130.5 17.5q127 0 244 -48v-104q-113 51 -232 51q-141 0 -204.5 -72.5t-63.5 -236.5q0 -162 63.5 -234.5t204.5 -72.5q115 0 232 49v-103q-117 -47 -244 -47q-55 0 -103.5 9.5t-99.5 37t-87 71.5t-58.5 118t-22.5 172z" />
+<glyph unicode="ª" horiz-adv-x="813" d="M156 985q0 84 71.5 132t196.5 48q53 0 102 -6q-4 72 -39.5 103.5t-117.5 31.5q-70 0 -156 -28v88q86 30 172 30q119 0 182.5 -61t63.5 -176v-164q0 -25 -7.5 -49.5t-28 -58t-72.5 -55t-128 -21.5q-63 0 -110 15t-71 36.5t-38 50.5t-17 47.5t-3 36.5zM260 987 q0 -96 135 -96q131 0 131 92v92q-35 8 -98 8q-33 0 -66.5 -6t-67.5 -29.5t-34 -60.5z" />
+<glyph unicode="«" horiz-adv-x="1171" d="M63 522l347 389h223l-365 -389l365 -391h-223zM498 522l346 389h223l-365 -389l365 -391h-223z" />
+<glyph unicode="¬" horiz-adv-x="1273" d="M199 528v156h876v-360h-151v204h-725z" />
+<glyph unicode="­" horiz-adv-x="950" d="M162 530v160h624v-160h-624z" />
+<glyph unicode="®" horiz-adv-x="1593" d="M98 684q0 289 205 494.5t496 205.5t495.5 -205.5t204.5 -494.5q0 -291 -204.5 -495.5t-495.5 -204.5t-496 204.5t-205 495.5zM188 684q0 -254 178.5 -432t432.5 -178q252 0 430 178t178 432q0 252 -178 430t-430 178q-254 0 -432.5 -178t-178.5 -430zM528 285v796h295 q133 0 210 -62.5t77 -178.5q0 -98 -51 -156t-127 -78l211 -321h-131l-193 303h-180v-303h-111zM639 692h178q180 0 180 145.5t-184 145.5h-174v-291z" />
+<glyph unicode="¯" horiz-adv-x="825" d="M176 1180v123h477v-123h-477z" />
+<glyph unicode="°" horiz-adv-x="792" d="M168 1143q0 111 57.5 169t166 58t165.5 -58.5t57 -168.5q0 -109 -57 -167t-165.5 -58t-166 58t-57.5 167zM260 1143q0 -76 31 -109t100.5 -33t99 33t29.5 109t-29.5 108.5t-99 32.5t-100.5 -32.5t-31 -108.5z" />
+<glyph unicode="±" horiz-adv-x="1214" d="M231 55v150h754v-150h-754zM231 612v150h304v301h149v-301h301v-150h-301v-303h-149v303h-304z" />
+<glyph unicode="²" horiz-adv-x="671" d="M109 948v82q137 102 246.5 226t109.5 223q0 118 -161 118h-3q-104 0 -180 -55v96q88 53 194 54q127 0 188.5 -62.5t61.5 -148.5q0 -184 -278 -430h274v-103h-452z" />
+<glyph unicode="³" horiz-adv-x="661" d="M109 983v100q86 -49 180 -49q182 0 182 127q0 129 -192 129h-62v92h59q158 0 158 106.5t-158 106.5q-86 0 -159 -41v99q74 39 172 39q127 0 187.5 -59.5t60.5 -143.5q0 -95 -86 -148q122 -53 122 -176q0 -94 -66.5 -159.5t-207.5 -65.5q-104 0 -190 43z" />
+<glyph unicode="´" horiz-adv-x="518" d="M123 1155l127 240h184l-194 -240h-117z" />
+<glyph unicode="µ" horiz-adv-x="1253" d="M203 -430v1454h188v-672q0 -90 46 -142t186 -52q125 0 239 37v829h189v-934q-213 -106 -437 -106q-129 0 -223 47v-461h-188z" />
+<glyph unicode="¶" horiz-adv-x="940" d="M78 936q0 207 123.5 318.5t359.5 111.5h238v-1366h-170v504h-68q-236 0 -359.5 111.5t-123.5 320.5z" />
+<glyph unicode="·" horiz-adv-x="663" d="M238 471v174h186v-174h-186z" />
+<glyph unicode="¸" horiz-adv-x="555" d="M115 -262l182 252h145l-141 -252h-186z" />
+<glyph unicode="¹" horiz-adv-x="491" d="M109 1509v107l118 67h113v-735h-102v635z" />
+<glyph unicode="º" horiz-adv-x="864" d="M156 1090q0 115 44 184t99 89.5t125 20.5q113 0 191.5 -64.5t78.5 -229.5q0 -164 -78.5 -229.5t-191.5 -65.5q-111 0 -189.5 65.5t-78.5 229.5zM260 1089.5q0 -120.5 43 -160.5t121 -40t121 40t43 160.5t-43 161.5t-121 41t-121 -41t-43 -161.5z" />
+<glyph unicode="»" horiz-adv-x="1212" d="M137 131l365 391l-365 389h223l347 -389l-347 -391h-223zM571 131l365 391l-365 389h224l346 -389l-346 -391h-224z" />
+<glyph unicode="¼" horiz-adv-x="1447" d="M106 1206v107l119 67h111v-735h-103v635zM145 -45l893 1448h131l-895 -1448h-129zM799 231v93l323 413h115v-413h104v-93h-104v-229h-102v229h-336zM920 324h215v266z" />
+<glyph unicode="½" horiz-adv-x="1482" d="M106 1206v107l119 67h111v-735h-103v635zM145 -45l893 1448h131l-895 -1448h-129zM895 2v82q135 102 244.5 226t109.5 222q0 119 -164 119q-92 0 -180 -55v96q88 53 195 53q127 0 188.5 -61t61.5 -149q0 -188 -279 -431h277v-102h-453z" />
+<glyph unicode="¾" horiz-adv-x="1662" d="M135 680v100q86 -49 180 -49q182 0 183 127q0 129 -193 129h-61v92h59q160 0 160 106.5t-160 106.5q-86 0 -160 -41v99q82 41 172 41q127 0 187.5 -60.5t60.5 -144.5q0 -95 -86 -148q123 -53 123 -178q0 -92 -66.5 -157.5t-207.5 -65.5q-100 0 -191 43zM369 -45l895 1448 h129l-893 -1448h-131zM1024 231v93l322 413h116v-413h105v-93h-105v-229h-102v229h-336zM1145 324h215v266z" />
+<glyph unicode="¿" horiz-adv-x="1052" d="M174 37q0 82 31 146.5t76 106.5l90 83q45 41 75.5 100t30.5 135v70h180v-76q0 -104 -30.5 -184t-75.5 -127t-89 -86t-75 -81t-31 -89q0 -219 258 -219q166 0 306 123v-203q-150 -96 -322 -96q-115 0 -200 33.5t-132 92t-69.5 126t-22.5 145.5zM467 836v186h201v-186h-201 z" />
+<glyph unicode="À" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM424 1722h182l129 -239h-117zM463 561h434l-217 567z" />
+<glyph unicode="Á" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM463 561h434l-217 567zM625 1483l129 239h182l-195 -239h-116z" />
+<glyph unicode="Â" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM451 1483l180 250h98l182 -250h-145l-86 133l-86 -133h-143zM463 561h434l-217 567z" />
+<glyph unicode="Ã" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM369 1481v135q57 68 123 74q11 1 22 1q55 0 106 -26l122 -61q51 -24 104 -24q11 0 22 1q66 6 123 74v-135q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60q-51 26 -105 26q-11 0 -23 -2 q-66 -6 -123 -73zM463 561h434l-217 567z" />
+<glyph unicode="Ä" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM434 1483v174h189v-174h-189zM463 561h434l-217 567zM737 1483v174h189v-174h-189z" />
+<glyph unicode="Å" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM463 561h434l-217 567zM502 1599q0 82 47 128.5t131 46.5t131 -46.5t47 -128.5q0 -84 -47 -130t-131 -46t-131 46.5t-47 129.5zM600 1599.5q0 -51.5 17.5 -72t62.5 -20.5t62.5 20.5t17.5 72t-17.5 72 t-62.5 20.5t-62.5 -20.5t-17.5 -72z" />
+<glyph unicode="Æ" horiz-adv-x="2031" d="M51 0l858 1366h983v-178h-700v-416h616v-178h-616v-416h700v-178h-891v414h-481l-258 -414h-211zM633 592h368v588z" />
+<glyph unicode="Ç" horiz-adv-x="1286" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16q213 0 418 -81v-177q-193 86 -396 86q-242 0 -351 -124.5t-109 -403.5q0 -276 109.5 -402t350.5 -126q203 0 396 86v-176q-176 -72 -361 -80l-137 -248h-188l184 250q-111 10 -201 47t-173 112.5t-130 213 t-47 323.5z" />
+<glyph unicode="È" horiz-adv-x="1187" d="M178 0v1366h891v-178h-700v-416h616v-178h-616v-416h700v-178h-891zM412 1722h184l127 -239h-117z" />
+<glyph unicode="É" horiz-adv-x="1187" d="M178 0v1366h891v-178h-700v-416h616v-178h-616v-416h700v-178h-891zM582 1483l127 239h184l-195 -239h-116z" />
+<glyph unicode="Ê" horiz-adv-x="1187" d="M178 0v1366h891v-178h-700v-416h616v-178h-616v-416h700v-178h-891zM416 1483l180 250h98l183 -250h-146l-86 133l-86 -133h-143z" />
+<glyph unicode="Ë" horiz-adv-x="1187" d="M178 0v1366h891v-178h-700v-416h616v-178h-616v-416h700v-178h-891zM397 1483v174h187v-174h-187zM700 1483v174h187v-174h-187z" />
+<glyph unicode="Ì" horiz-adv-x="544" d="M14 1722h185l127 -239h-117zM178 0h189v1366h-189v-1366z" />
+<glyph unicode="Í" horiz-adv-x="544" d="M178 0v1366h189v-1366h-189zM217 1483l127 239h184l-194 -239h-117z" />
+<glyph unicode="Î" horiz-adv-x="544" d="M41 1483l180 250h98l183 -250h-146l-86 133l-86 -133h-143zM178 0h189v1366h-189v-1366z" />
+<glyph unicode="Ï" horiz-adv-x="544" d="M27 1483v174h186v-174h-186zM178 0h189v1366h-189v-1366zM330 1483v174h186v-174h-186z" />
+<glyph unicode="Ð" horiz-adv-x="1480" d="M109 606v154h163v606h437q315 0 479 -174t164 -510t-164 -509t-479 -173h-437v606h-163zM463 168h215q252 0 368.5 124t116.5 390q0 268 -116.5 392t-368.5 124h-215v-438h305v-154h-305v-438z" />
+<glyph unicode="Ñ" horiz-adv-x="1511" d="M178 0v1366h217l764 -1087v1087h191v-1366h-211l-770 1094v-1094h-191zM453 1481v135q57 68 123 74q11 1 22 1q55 0 106 -26l122 -61q51 -24 104 -24q11 0 22 1q66 6 123 74v-135q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60q-51 26 -105 26q-11 0 -23 -2 q-66 -6 -123 -73z" />
+<glyph unicode="Ò" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16t177 -16t170 -63.5t148.5 -123t100.5 -202.5t39 -295t-39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16t-177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5 t103.5 -88t117.5 -42t126.5 -9t127 9t118 42t103.5 88t68.5 154.5t27.5 234.5t-27.5 234.5t-68.5 154.5t-103.5 88t-118 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5zM524 1722h185l127 -239h-117z" />
+<glyph unicode="Ó" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16t177 -16t170 -63.5t148.5 -123t100.5 -202.5t39 -295t-39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16t-177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5 t103.5 -88t117.5 -42t126.5 -9t127 9t118 42t103.5 88t68.5 154.5t27.5 234.5t-27.5 234.5t-68.5 154.5t-103.5 88t-118 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5zM684 1483l127 239h184l-194 -239h-117z" />
+<glyph unicode="Ô" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16t177 -16t170 -63.5t148.5 -123t100.5 -202.5t39 -295t-39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16t-177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5 t103.5 -88t117.5 -42t126.5 -9t127 9t118 42t103.5 88t68.5 154.5t27.5 234.5t-27.5 234.5t-68.5 154.5t-103.5 88t-118 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5zM528 1483l181 250h98l182 -250h-145l-86 133l-86 -133h-144z" />
+<glyph unicode="Õ" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16t177 -16t170 -63.5t148.5 -123t100.5 -202.5t39 -295t-39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16t-177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5 t103.5 -88t117.5 -42t126.5 -9t127 9t118 42t103.5 88t68.5 154.5t27.5 234.5t-27.5 234.5t-68.5 154.5t-103.5 88t-118 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5zM449 1481v135q57 68 123 74q11 1 22 1q55 0 106 -26l122 -61q51 -24 104 -24 q11 0 22 1q66 6 123 74v-135q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60q-51 26 -105 26q-11 0 -23 -2q-66 -6 -123 -73z" />
+<glyph unicode="Ö" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16t177 -16t170 -63.5t148.5 -123t100.5 -202.5t39 -295t-39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16t-177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5 t103.5 -88t117.5 -42t126.5 -9t127 9t118 42t103.5 88t68.5 154.5t27.5 234.5t-27.5 234.5t-68.5 154.5t-103.5 88t-118 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5zM514 1483v174h186v-174h-186zM817 1483v174h187v-174h-187z" />
+<glyph unicode="×" horiz-adv-x="1050" d="M158 160l282 364l-282 365h164l200 -258l203 258h162l-281 -365l281 -364h-162l-203 258l-200 -258h-164z" />
+<glyph unicode="Ø" d="M88 0l168 201q-131 174 -131 483q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16q244 0 399 -112l82 94h191l-168 -199q131 -174 131 -483q0 -168 -39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16q-244 0 -400 112l-81 -96h-191zM315 684q0 -209 64 -336 l665 789q-99 75 -279 75h-5q-72 0 -127 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5zM475 231q97 -75 280 -75h5q72 0 127 9t117.5 42t103.5 88t68.5 154.5t27.5 234.5q0 209 -63 336z" />
+<glyph unicode="Ù" horiz-adv-x="1476" d="M178 563v803h191v-819q0 -201 90 -297t278.5 -96t278.5 96t90 297v819h190v-803q0 -276 -146 -428.5t-412.5 -152.5t-413 152.5t-146.5 428.5zM498 1722h184l127 -239h-117z" />
+<glyph unicode="Ú" horiz-adv-x="1476" d="M178 563v803h191v-819q0 -201 90 -297t278.5 -96t278.5 96t90 297v819h190v-803q0 -276 -146 -428.5t-412.5 -152.5t-413 152.5t-146.5 428.5zM666 1483l127 239h184l-195 -239h-116z" />
+<glyph unicode="Û" horiz-adv-x="1476" d="M178 563v803h191v-819q0 -201 90 -297t278.5 -96t278.5 96t90 297v819h190v-803q0 -276 -146 -428.5t-412.5 -152.5t-413 152.5t-146.5 428.5zM506 1483l180 250h98l183 -250h-146l-86 133l-86 -133h-143z" />
+<glyph unicode="Ü" horiz-adv-x="1476" d="M178 563v803h191v-819q0 -201 90 -297t278.5 -96t278.5 96t90 297v819h190v-803q0 -276 -146 -428.5t-412.5 -152.5t-413 152.5t-146.5 428.5zM492 1483v174h186v-174h-186zM795 1483v174h186v-174h-186z" />
+<glyph unicode="Ý" horiz-adv-x="1275" d="M25 1366h229l383 -559l383 559h231l-520 -754v-612h-188v610zM561 1483l127 239h184l-194 -239h-117z" />
+<glyph unicode="Þ" horiz-adv-x="1187" d="M176 0v1366h191v-205h231q227 0 352 -112.5t125 -317.5t-125 -317.5t-352 -112.5h-231v-301h-191zM367 469h215q182 0 243.5 70.5t61.5 189.5v2v2q0 119 -61.5 189.5t-243.5 70.5h-215v-524z" />
+<glyph unicode="ß" horiz-adv-x="1255" d="M180 0v862q0 154 39 262.5t106.5 161.5t137 75.5t153.5 22.5q236 0 347.5 -114.5t111.5 -276.5q0 -188 -188 -280q254 -74 254 -316q0 -186 -150.5 -291.5t-400.5 -105.5v174q61 0 115.5 10.5t113 33t93 70.5t34.5 118q0 217 -356 231v143q293 4 293 219q0 219 -267 220 q-53 0 -92 -13.5t-75.5 -49.5t-56 -111.5t-19.5 -190.5v-854h-193z" />
+<glyph unicode="à" horiz-adv-x="1099" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q159 55 309 55q213 0 328 -110.5t115 -317.5v-295q0 -33 -5.5 -65.5t-31 -83.5t-67.5 -89t-125 -65.5t-193 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M301 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88zM315 1395h185l127 -240h-117z" />
+<glyph unicode="á" horiz-adv-x="1099" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q159 55 309 55q213 0 328 -110.5t115 -317.5v-295q0 -33 -5.5 -65.5t-31 -83.5t-67.5 -89t-125 -65.5t-193 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M301 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88zM461 1155l127 240h184l-194 -240h-117z" />
+<glyph unicode="â" horiz-adv-x="1099" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q159 55 309 55q213 0 328 -110.5t115 -317.5v-295q0 -33 -5.5 -65.5t-31 -83.5t-67.5 -89t-125 -65.5t-193 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M301 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88zM338 1155l180 250h98l183 -250h-146l-86 133l-86 -133h-143z" />
+<glyph unicode="ã" horiz-adv-x="1099" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q159 55 309 55q213 0 328 -110.5t115 -317.5v-295q0 -33 -5.5 -65.5t-31 -83.5t-67.5 -89t-125 -65.5t-193 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M250 1153v135q57 68 124 74q10 1 21 1q55 0 105 -25l122 -61q51 -25 104 -25q12 0 24 2q65 6 122 73v-135q-57 -68 -122 -74q-12 -1 -23 -1q-53 0 -105 25l-122 60q-50 26 -105 26q-11 0 -21 -1q-67 -6 -124 -74zM301 324q0 -174 246 -174h3q232 0 232 167v166 q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88z" />
+<glyph unicode="ä" horiz-adv-x="1099" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q159 55 309 55q213 0 328 -110.5t115 -317.5v-295q0 -33 -5.5 -65.5t-31 -83.5t-67.5 -89t-125 -65.5t-193 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M301 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88zM324 1155v174h186v-174h-186zM627 1155v174h186v-174h-186z" />
+<glyph unicode="å" horiz-adv-x="1099" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q159 55 309 55q213 0 328 -110.5t115 -317.5v-295q0 -33 -5.5 -65.5t-31 -83.5t-67.5 -89t-125 -65.5t-193 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M301 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88zM379 1272q0 84 47 130t131 46q86 0 132 -46t46 -130t-46 -130t-132 -46q-84 0 -131 46t-47 130zM477 1272q0 -51 17.5 -71.5t62.5 -20.5t62.5 20.5t17.5 71.5 t-17.5 71.5t-62.5 20.5t-62.5 -20.5t-17.5 -71.5z" />
+<glyph unicode="æ" horiz-adv-x="1802" d="M111 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q160 55 309 55q244 0 357 -143q113 143 348 143q205 0 327.5 -134t122.5 -359q0 -74 -6 -142h-704q35 -260 288 -260q197 0 353 76v-170q-158 -67 -357 -67h-6 q-229 0 -350 131q-106 -129 -352 -129q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67zM297 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88zM965 573h524q-27 297 -262 297q-129 0 -189.5 -66.5t-72.5 -230.5z " />
+<glyph unicode="ç" horiz-adv-x="966" d="M119 512q0 256 124 391t357 135q156 0 301 -57v-178q-142 67 -285 67q-141 0 -221 -72.5t-80 -285.5q0 -113 23.5 -187.5t70 -110.5t94.5 -48t113 -12q147 0 285 65v-178q-104 -41 -223 -53l-139 -254h-189l182 254q-201 18 -307 152t-106 372z" />
+<glyph unicode="è" horiz-adv-x="1136" d="M129 500q0 540 451 540h2q205 0 327.5 -134t122.5 -359q0 -74 -6 -141h-704q35 -260 288 -261q201 0 353 74v-168q-158 -67 -357 -67h-6q-90 0 -166 22.5t-148.5 74.5t-114.5 159.5t-42 259.5zM319 575h525q-4 51 -17.5 97.5t-41 94.5t-80 76.5t-123.5 28.5 q-129 0 -190 -66.5t-73 -230.5zM346 1395h184l127 -240h-116z" />
+<glyph unicode="é" horiz-adv-x="1136" d="M129 500q0 540 451 540h2q205 0 327.5 -134t122.5 -359q0 -74 -6 -141h-704q35 -260 288 -261q201 0 353 74v-168q-158 -67 -357 -67h-6q-90 0 -166 22.5t-148.5 74.5t-114.5 159.5t-42 259.5zM319 575h525q-4 51 -17.5 97.5t-41 94.5t-80 76.5t-123.5 28.5 q-129 0 -190 -66.5t-73 -230.5zM506 1155l127 240h184l-194 -240h-117z" />
+<glyph unicode="ê" horiz-adv-x="1136" d="M129 500q0 540 451 540h2q205 0 327.5 -134t122.5 -359q0 -74 -6 -141h-704q35 -260 288 -261q201 0 353 74v-168q-158 -67 -357 -67h-6q-90 0 -166 22.5t-148.5 74.5t-114.5 159.5t-42 259.5zM319 575h525q-4 51 -17.5 97.5t-41 94.5t-80 76.5t-123.5 28.5 q-129 0 -190 -66.5t-73 -230.5zM350 1155l180 250h99l182 -250h-145l-86 133l-86 -133h-144z" />
+<glyph unicode="ë" horiz-adv-x="1136" d="M129 500q0 540 451 540h2q205 0 327.5 -134t122.5 -359q0 -74 -6 -141h-704q35 -260 288 -261q201 0 353 74v-168q-158 -67 -357 -67h-6q-90 0 -166 22.5t-148.5 74.5t-114.5 159.5t-42 259.5zM319 575h525q-4 51 -17.5 97.5t-41 94.5t-80 76.5t-123.5 28.5 q-129 0 -190 -66.5t-73 -230.5zM336 1155v174h186v-174h-186zM639 1155v174h186v-174h-186z" />
+<glyph unicode="ì" horiz-adv-x="589" d="M41 1395h184l127 -240h-116zM201 0h188v1022h-188v-1022z" />
+<glyph unicode="í" horiz-adv-x="589" d="M201 0v1022h188v-1022h-188zM236 1155l126 240h185l-195 -240h-116z" />
+<glyph unicode="î" horiz-adv-x="589" d="M63 1155l181 250h98l182 -250h-145l-86 133l-86 -133h-144zM201 0h188v1022h-188v-1022z" />
+<glyph unicode="ï" horiz-adv-x="589" d="M49 1155v174h187v-174h-187zM201 0h188v1022h-188v-1022zM352 1155v174h187v-174h-187z" />
+<glyph unicode="ð" horiz-adv-x="1212" d="M119 453q0 182 108.5 310t347.5 128q170 0 285 -72q-41 193 -162 293l-217 -131l-67 113l151 90q-63 22 -149 22h-8q-74 0 -156 -18v172q81 22 178 22h8q184 0 322 -79l178 108l68 -113l-136 -81q193 -197 193 -590q0 -643 -473 -643q-229 0 -350 138t-121 331zM309 453 q0 -43 9.5 -85t35 -94.5t87 -85t149.5 -32.5q39 0 64.5 3t70.5 26.5t72.5 68.5t50 138t24.5 224q-106 100 -282 101q-281 0 -281 -264z" />
+<glyph unicode="ñ" horiz-adv-x="1198" d="M168 0v934q213 106 436 106q193 0 302.5 -101t109.5 -280v-659h-189v672q0 90 -46 142t-185 52q-125 0 -240 -37v-829h-188zM281 1153v135q57 68 123 74q11 1 22 1q55 0 106 -25l122 -61q50 -25 103 -25q12 0 23 2q66 6 123 73v-135q-57 -68 -123 -74q-11 -1 -22 -1 q-53 0 -104 25l-122 60q-51 26 -106 26q-11 0 -22 -1q-66 -6 -123 -74z" />
+<glyph unicode="ò" horiz-adv-x="1155" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170 22.5t170 -22.5t151.5 -76.5t117 -162.5t43 -264.5t-43 -264.5t-117 -162.5t-151.5 -76.5t-170 -22.5t-170 22.5t-151.5 76.5t-117 162.5t-43 264.5zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12 t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5zM319 1395h185l127 -240h-117z" />
+<glyph unicode="ó" horiz-adv-x="1155" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170 22.5t170 -22.5t151.5 -76.5t117 -162.5t43 -264.5t-43 -264.5t-117 -162.5t-151.5 -76.5t-170 -22.5t-170 22.5t-151.5 76.5t-117 162.5t-43 264.5zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12 t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5zM532 1155l127 240h185l-195 -240h-117z" />
+<glyph unicode="ô" horiz-adv-x="1155" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170 22.5t170 -22.5t151.5 -76.5t117 -162.5t43 -264.5t-43 -264.5t-117 -162.5t-151.5 -76.5t-170 -22.5t-170 22.5t-151.5 76.5t-117 162.5t-43 264.5zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12 t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5zM350 1155l180 250h99l182 -250h-145l-86 133l-86 -133h-144z" />
+<glyph unicode="õ" horiz-adv-x="1155" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170 22.5t170 -22.5t151.5 -76.5t117 -162.5t43 -264.5t-43 -264.5t-117 -162.5t-151.5 -76.5t-170 -22.5t-170 22.5t-151.5 76.5t-117 162.5t-43 264.5zM270 1153v135q57 68 124 74q11 1 22 1q55 0 106 -25l122 -61 q50 -25 103 -25q12 0 23 2q66 6 123 73v-135q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60q-51 26 -106 26q-11 0 -22 -1q-66 -6 -124 -74zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5 t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5z" />
+<glyph unicode="ö" horiz-adv-x="1155" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170 22.5t170 -22.5t151.5 -76.5t117 -162.5t43 -264.5t-43 -264.5t-117 -162.5t-151.5 -76.5t-170 -22.5t-170 22.5t-151.5 76.5t-117 162.5t-43 264.5zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12 t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5zM336 1155v174h186v-174h-186zM639 1155v174h186v-174h-186z" />
+<glyph unicode="÷" horiz-adv-x="1286" d="M227 453v151h832v-151h-832zM549 143v174h186v-174h-186zM549 739v174h186v-174h-186z" />
+<glyph unicode="ø" horiz-adv-x="1175" d="M55 0l135 160q-90 131 -90 350q0 156 43 264.5t117 162.5t151.5 76.5t170.5 22.5q170 0 284 -73l52 59h190l-135 -162q90 -131 90 -350q0 -156 -43 -264.5t-117 -162.5t-151.5 -76.5t-169.5 -22.5q-172 0 -287 73l-49 -57h-191zM289 510q0 -119 26 -199l441 520 q-61 39 -174 39q-66 0 -112 -12t-91 -48t-67.5 -110.5t-22.5 -189.5zM406 188q59 -38 170 -38h6q66 0 111.5 12t90.5 48t67.5 110.5t22.5 189.5q0 125 -28 201z" />
+<glyph unicode="ù" horiz-adv-x="1208" d="M168 430v592h188v-606q0 -139 58.5 -204t185.5 -65t186.5 64.5t59.5 204.5v606h186v-592q0 -213 -113.5 -330.5t-318.5 -117.5t-318.5 117.5t-113.5 330.5zM358 1395h185l127 -240h-117z" />
+<glyph unicode="ú" horiz-adv-x="1208" d="M168 430v592h186v-606q0 -139 59.5 -204t186.5 -65t185.5 64.5t58.5 204.5v606h188v-592q0 -213 -113.5 -330.5t-318.5 -117.5t-318.5 117.5t-113.5 330.5zM528 1155l127 240h185l-195 -240h-117z" />
+<glyph unicode="û" horiz-adv-x="1208" d="M168 430v592h186v-606q0 -139 59.5 -204t186.5 -65t185.5 64.5t58.5 204.5v606h188v-592q0 -213 -113.5 -330.5t-318.5 -117.5t-318.5 117.5t-113.5 330.5zM369 1155l180 250h98l182 -250h-145l-86 133l-86 -133h-143z" />
+<glyph unicode="ü" horiz-adv-x="1208" d="M168 430v592h186v-606q0 -139 59.5 -204t186.5 -65t185.5 64.5t58.5 204.5v606h188v-592q0 -213 -113.5 -330.5t-318.5 -117.5t-318.5 117.5t-113.5 330.5zM354 1155v174h187v-174h-187zM657 1155v174h187v-174h-187z" />
+<glyph unicode="ý" horiz-adv-x="1073" d="M63 1022h201l275 -766l270 766h201l-420 -1131q-109 -294 -317 -294h-1q-68 0 -143 16v168q61 -16 115 -17q127 0 205 236zM467 1155l127 240h184l-194 -240h-117z" />
+<glyph unicode="þ" horiz-adv-x="1142" d="M170 -377v1774h186v-404q106 31 219 31q88 0 164 -21.5t149 -75t114.5 -159.5t41.5 -258t-41.5 -258.5t-114.5 -159.5t-148.5 -74.5t-164.5 -21.5q-113 0 -219 31v-404h-186zM356 205q104 -43 219 -43q61 0 106.5 12t88.5 46t65.5 106.5t22.5 183.5t-22.5 183.5 t-65.5 106.5t-88 46t-107 12q-115 0 -219 -43v-610z" />
+<glyph unicode="ÿ" horiz-adv-x="1069" d="M63 1022h201l275 -766l270 766h201l-420 -1131q-109 -294 -317 -294h-1q-68 0 -143 16v168q61 -16 115 -17q127 0 205 236zM291 1155v174h186v-174h-186zM594 1155v174h186v-174h-186z" />
+<glyph unicode="Œ" horiz-adv-x="2056" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16q152 0 272 -45v27h891v-178h-700v-416h616v-178h-616v-416h700v-178h-891v29q-121 -45 -272 -45q-94 0 -177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5t103.5 -88 t117.5 -42t127 -9q174 0 272 67v922q-97 67 -267 67h-5q-72 0 -127 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5z" />
+<glyph unicode="œ" horiz-adv-x="1886" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170.5 22.5q256 0 381 -159q112 163 359 163h3q205 0 328 -134t123 -359q0 -74 -7 -141h-704q35 -260 289 -261q201 0 352 74v-168q-158 -67 -357 -67h-6q-260 0 -378 166q-121 -166 -383 -166q-92 0 -170 22.5t-152 76.5 t-117 162.5t-43 264.5zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5zM1063 575h524q-27 297 -262 297q-129 0 -189.5 -66.5 t-72.5 -230.5z" />
+<glyph unicode="Ÿ" horiz-adv-x="1277" d="M25 1366h229l383 -559l383 559h231l-520 -754v-612h-188v610zM393 1483v174h187v-174h-187zM696 1483v174h187v-174h-187z" />
+<glyph unicode="ˆ" horiz-adv-x="882" d="M207 1155l180 250h98l183 -250h-146l-86 133l-86 -133h-143z" />
+<glyph unicode="˜" horiz-adv-x="1079" d="M215 1153v135q57 68 124 74q11 1 22 1q55 0 105 -25l122 -61q50 -25 103 -25q12 0 23 2q66 6 124 73v-135q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -105 25l-122 60q-50 26 -105 26q-11 0 -22 -1q-67 -6 -124 -74z" />
+<glyph unicode=" " horiz-adv-x="887" />
+<glyph unicode=" " horiz-adv-x="1774" />
+<glyph unicode=" " horiz-adv-x="887" />
+<glyph unicode=" " horiz-adv-x="1774" />
+<glyph unicode=" " horiz-adv-x="591" />
+<glyph unicode=" " horiz-adv-x="443" />
+<glyph unicode=" " horiz-adv-x="295" />
+<glyph unicode=" " horiz-adv-x="295" />
+<glyph unicode=" " horiz-adv-x="221" />
+<glyph unicode=" " horiz-adv-x="354" />
+<glyph unicode=" " horiz-adv-x="98" />
+<glyph unicode="‐" horiz-adv-x="950" d="M162 530v160h624v-160h-624z" />
+<glyph unicode="‑" horiz-adv-x="950" d="M162 530v160h624v-160h-624z" />
+<glyph unicode="‒" horiz-adv-x="950" d="M162 530v160h624v-160h-624z" />
+<glyph unicode="–" horiz-adv-x="1146" d="M141 516v160h860v-160h-860z" />
+<glyph unicode="—" horiz-adv-x="1824" d="M162 516v160h1501v-160h-1501z" />
+<glyph unicode="‘" horiz-adv-x="552" d="M96 1384h193l151 -401h-172z" />
+<glyph unicode="’" horiz-adv-x="552" d="M96 983l152 401h192l-172 -401h-172z" />
+<glyph unicode="‚" horiz-adv-x="643" d="M147 -229l152 401h193l-173 -401h-172z" />
+<glyph unicode="“" horiz-adv-x="843" d="M96 1384h193l151 -401h-172zM401 1384h193l151 -401h-172z" />
+<glyph unicode="”" horiz-adv-x="827" d="M96 983l152 401h192l-172 -401h-172zM401 983l152 401h192l-172 -401h-172z" />
+<glyph unicode="„" horiz-adv-x="940" d="M147 -229l152 401h193l-173 -401h-172zM453 -229l151 401h193l-172 -401h-172z" />
+<glyph unicode="•" horiz-adv-x="864" d="M170 684q0 104 73.5 177t178.5 73q102 0 176 -73t74 -177t-74 -178t-176 -74q-104 0 -178 74t-74 178z" />
+<glyph unicode="…" horiz-adv-x="1607" d="M219 0v174h187v-174h-187zM711 0v174h186v-174h-186zM1200 0v174h186v-174h-186z" />
+<glyph unicode=" " horiz-adv-x="354" />
+<glyph unicode="‹" horiz-adv-x="733" d="M63 522l347 389h223l-365 -389l365 -391h-223z" />
+<glyph unicode="›" horiz-adv-x="792" d="M137 131l365 391l-365 389h223l347 -389l-347 -391h-223z" />
+<glyph unicode=" " horiz-adv-x="443" />
+<glyph unicode="€" horiz-adv-x="1400" d="M102 451v141h142q-2 29 -2 92q0 33 4 94h-144v142h162q29 135 94.5 231t153.5 144t176 68.5t189 20.5q213 0 417 -81v-177q-192 86 -395 86q-184 0 -290.5 -70.5t-145.5 -221.5h614v-142h-635q-4 -61 -4 -94q0 -63 4 -92h635v-141h-614q39 -154 145.5 -224.5t290.5 -70.5 q203 0 395 86v-176q-205 -82 -417 -82q-512 0 -613 467h-162z" />
+<glyph unicode="™" horiz-adv-x="1568" d="M100 1278v88h555v-88h-231v-594h-94v594h-230zM762 684v682h102l223 -295l226 295h102v-682h-94v539l-234 -308l-231 308v-539h-94z" />
+<glyph unicode="" horiz-adv-x="1020" d="M0 0v1020h1020v-1020h-1020z" />
+<glyph unicode="fi" horiz-adv-x="1202" d="M82 858v166h143v47q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5t-14 -122v-47h264v-166h-264v-858h-187v858h-143zM874 1180v198h201v-198h-201zM881 0v1022h188v-1022h-188z" />
+<glyph unicode="fl" horiz-adv-x="1220" d="M82 858v166h143v47q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5t-14 -122v-47h264v-166h-264v-858h-187v858h-143zM881 0v1411h188v-1411h-188z" />
+<glyph unicode="ffi" horiz-adv-x="1939" d="M82 858v164h143v49q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5t-14 -122v-49h264v-164h-264v-858h-187v858h-143zM757 858v164h143v49q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5 t-14 -122v-49h264v-164h-264v-858h-187v858h-143zM1545 1180v198h200v-198h-200zM1551 0v1022h188v-1022h-188z" />
+<glyph unicode="ffl" horiz-adv-x="1952" d="M82 858v164h143v49q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5t-14 -122v-49h264v-164h-264v-858h-187v858h-143zM757 858v164h143v49q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5 t-14 -122v-49h264v-164h-264v-858h-187v858h-143zM1557 0v1411h188v-1411h-188z" />
+</font>
+</defs></svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="maven_proregular" horiz-adv-x="1529" >
+<font-face units-per-em="2048" ascent="1638" descent="-410" />
+<missing-glyph horiz-adv-x="692" />
+<glyph horiz-adv-x="2048" />
+<glyph horiz-adv-x="2048" />
+<glyph unicode="
" horiz-adv-x="682" />
+<glyph unicode=" " horiz-adv-x="692" />
+<glyph unicode="	" horiz-adv-x="692" />
+<glyph unicode=" " horiz-adv-x="692" />
+<glyph unicode="!" horiz-adv-x="761" d="M303 0v164h164v-164h-164zM311 1366h148l-15 -1022h-118z" />
+<glyph unicode=""" horiz-adv-x="780" d="M166 1384h170l-35 -411h-98zM440 1384h170l-37 -411h-98z" />
+<glyph unicode="#" horiz-adv-x="1210" d="M145 258v123h189l55 326h-186v122h209l41 242h125l-41 -242h270l43 242h125l-43 -242h147v-122h-168l-57 -326h166v-123h-186l-37 -219h-125l37 219h-271l-39 -219h-125l39 219h-168zM459 381h270l57 326h-272z" />
+<glyph unicode="$" horiz-adv-x="1181" d="M154 1004q0 143 95 249.5t294 126.5v209h110v-205q166 -8 314 -81v-152q-150 84 -314 86v-469q88 -29 147.5 -55.5t124 -73.5t97.5 -120t33 -171q0 -156 -105.5 -250t-296.5 -110v-209h-110v205q-207 6 -377 96v160q158 -104 377 -109v520q-76 23 -117 37t-101.5 44 t-92 63.5t-55 87t-23.5 121.5zM301 1004q0 -78 61.5 -121t180.5 -80v428q-242 -34 -242 -227zM653 135q254 33 254 213q0 106 -65.5 165.5t-188.5 102.5v-481z" />
+<glyph unicode="%" horiz-adv-x="1777" d="M145 983q0 111 27 189.5t73 116.5t93 54.5t104 16.5q123 0 209 -83t86 -294q0 -113 -26.5 -190.5t-71.5 -116.5t-93 -55.5t-104 -16.5q-57 0 -104 16.5t-93 55.5t-73 116.5t-27 190.5zM258 983q0 -162 50 -217t134 -55t133.5 55t49.5 217t-49.5 216t-133.5 54 q-86 0 -135 -54t-49 -216zM395 0l858 1366h131l-858 -1366h-131zM1042 385q0 113 27 190.5t73 116.5t93 55.5t104 16.5q55 0 103.5 -16.5t93.5 -55.5t71.5 -116.5t26.5 -190.5t-26.5 -190.5t-71.5 -116.5t-93.5 -55.5t-103.5 -16.5q-57 0 -104 16.5t-93 55.5t-73 116.5 t-27 190.5zM1155 385q0 -162 50 -217t134 -55t133.5 55t49.5 217t-49.5 217t-133.5 55t-134 -55t-50 -217z" />
+<glyph unicode="&" horiz-adv-x="1310" d="M111 377q0 119 56 220t220 191q-135 168 -135 283q0 127 91 218t218 91q131 0 221 -87t90 -214q0 -109 -59 -183.5t-192 -152.5l297 -354q57 100 83 205h134q-33 -177 -121 -322l227 -272h-192l-125 152q-162 -168 -385 -168q-111 0 -198 38.5t-134 101t-71.5 127 t-24.5 126.5zM256 381q0 -33 12.5 -71t42 -82t90 -72.5t144.5 -28.5q172 0 291 129l-357 428q-78 -41 -128 -85t-68.5 -87t-22.5 -67.5t-4 -63.5zM395 1069q0 -72 129 -213q131 70 171 116t40 105q0 68 -49 112t-121 44q-70 0 -120 -48t-50 -116z" />
+<glyph unicode="'" horiz-adv-x="509" d="M166 1384h170l-35 -411h-98z" />
+<glyph unicode="(" horiz-adv-x="706" d="M190 625q1 450 228 858h155q-29 -47 -59.5 -112.5t-74.5 -180.5t-72.5 -264.5t-28.5 -300.5q0 -150 28.5 -300.5t72.5 -264t76 -180t58 -111.5h-155q-227 408 -228 856z" />
+<glyph unicode=")" horiz-adv-x="706" d="M133 -231q29 47 59.5 111.5t75.5 180t74 265t29 301.5q0 195 -53.5 396.5t-98.5 296.5t-86 163h158q227 -408 227 -856q0 -451 -227 -858h-158z" />
+<glyph unicode="*" horiz-adv-x="841" d="M172 1112l178 78l-178 80l55 94l156 -111l-21 187h111l-16 -187l153 111l56 -94l-177 -80l177 -78l-56 -98l-153 112l16 -184h-111l21 184l-156 -112z" />
+<glyph unicode="+" horiz-adv-x="1128" d="M199 551v123h303v303h123v-303h301v-123h-301v-301h-123v301h-303z" />
+<glyph unicode="," horiz-adv-x="641" d="M152 -229l151 401h168l-172 -401h-147z" />
+<glyph unicode="-" horiz-adv-x="950" d="M162 539v143h624v-143h-624z" />
+<glyph unicode="." horiz-adv-x="671" d="M260 0v164h164v-164h-164z" />
+<glyph unicode="/" horiz-adv-x="827" d="M-29 -201l752 1749h135l-754 -1749h-133z" />
+<glyph unicode="0" horiz-adv-x="1345" d="M133 677.5q0 700.5 538.5 700.5t538.5 -700.5t-538.5 -700.5t-538.5 700.5zM281 678q0 -145 23.5 -250.5t60 -165t92 -94.5t104.5 -45t110.5 -10t110.5 10t104.5 45t92.5 94.5t60.5 164.5t23.5 251q0 178 -34 298t-95.5 174t-121.5 73.5t-140 19.5t-140.5 -19.5 t-122 -73.5t-95 -174t-33.5 -298z" />
+<glyph unicode="1" horiz-adv-x="923" d="M211 1032v174l252 160h168v-1366h-148v1212z" />
+<glyph unicode="2" horiz-adv-x="1189" d="M158 0v129q102 76 208.5 169t223.5 215t189.5 252t72.5 234q0 238 -322 238q-190 0 -358 -119v162q172 104 371 104q233 0 344.5 -112.5t111.5 -272.5q0 -111 -56 -232.5t-151.5 -236t-190 -206t-200.5 -177.5h619v-147h-862z" />
+<glyph unicode="3" horiz-adv-x="1193" d="M166 70v151q160 -90 346 -90q182 0 278.5 67.5t96.5 194.5t-98.5 194.5t-284.5 67.5h-150v142h156q150 0 228.5 56t78.5 162.5t-83 164t-240 57.5q-152 0 -295 -72v150q145 69 306 69h5q233 0 344 -108.5t111 -259.5q0 -172 -148 -260l-29 -17q10 -4 26.5 -12t58.5 -39 t75 -67.5t59.5 -99t26.5 -132.5q0 -78 -27.5 -147.5t-84 -129t-157.5 -94t-235 -34.5q-197 0 -364 86z" />
+<glyph unicode="4" horiz-adv-x="1210" d="M78 410v157l645 799h166v-819h194v-137h-194v-410h-148v410h-663zM242 547h499v608z" />
+<glyph unicode="5" horiz-adv-x="1202" d="M178 735l70 631h702v-145h-575l-39 -406q121 37 266 37q84 0 153.5 -18.5t136 -63.5t105.5 -135t39 -217t-39 -217t-105.5 -135.5t-136 -63.5t-153.5 -18q-236 0 -391 84v153q150 -94 381 -94q80 0 138 19.5t89 46t47.5 69.5t20.5 77t4 79q0 92 -24.5 152.5t-71.5 88 t-93.5 37t-109.5 9.5q-96 0 -177 -16.5t-112 -33.5l-33 -14z" />
+<glyph unicode="6" horiz-adv-x="1193" d="M133 625q0 141 25.5 257.5t65.5 193.5t96.5 135.5t111.5 90t118.5 51t111 25.5t94.5 6q109 0 198 -26v-137q-98 24 -176 24h-6q-47 0 -92 -6t-113.5 -32.5t-122 -73t-97.5 -136.5t-58 -213q133 90 309 90q215 0 340 -115.5t125 -314.5q0 -84 -25.5 -162.5t-78 -147 t-145.5 -109.5t-216 -41q-465 0 -465 641zM281 621q2 -141 27.5 -241.5t57 -149t83 -75t79 -29.5t70.5 -3q102 0 172 38t98.5 98t37.5 103.5t9 83.5q0 139 -81.5 215t-235.5 76q-201 0 -317 -116z" />
+<glyph unicode="7" horiz-adv-x="1175" d="M143 1219v147h932v-137l-592 -1229h-164l596 1219h-772z" />
+<glyph unicode="8" horiz-adv-x="1191" d="M125 406q0 203 184 307q51 25 51 24l-20 11q-12 6 -44 30.5t-56.5 55t-44 84t-19.5 112.5q0 145 103.5 249.5t318.5 104.5t318.5 -103t103.5 -251q0 -184 -148 -274l-30 -17q10 -4 25.5 -11t54.5 -34.5t69.5 -62.5t55 -95.5t24.5 -129.5q0 -78 -25.5 -149t-79 -134.5 t-148.5 -101t-220 -37.5q-240 0 -356.5 127t-116.5 295zM272 406q0 -27 4.5 -55.5t23.5 -71.5t52 -76t96.5 -56.5t149.5 -23.5t149.5 23.5t96.5 56.5t52 76t23.5 71.5t4.5 55.5q0 123 -84 188t-242 65t-242 -65.5t-84 -187.5zM324 1030q0 -111 72.5 -173t201.5 -62 t201.5 62.5t72.5 172.5q0 209 -274 209t-274 -209z" />
+<glyph unicode="9" horiz-adv-x="1202" d="M119 924q0 84 25.5 162.5t77.5 147.5t145.5 109.5t216.5 40.5q465 0 465 -643q0 -195 -48.5 -340t-116 -223t-160.5 -124t-160.5 -58t-137.5 -12q-104 0 -199 24v139q81 -24 175 -24h8q47 0 92 6t113.5 32.5t120.5 73t97.5 136.5t57.5 213q-121 -90 -307 -90 q-215 0 -340 115.5t-125 314.5zM266 922q0 -139 82 -215t236 -76q207 0 317 117q-2 141 -27.5 241.5t-57.5 148.5t-83 74.5t-78.5 29.5t-70.5 3q-102 0 -172 -38t-98.5 -98t-38 -103t-9.5 -84z" />
+<glyph unicode=":" horiz-adv-x="800" d="M324 98v164h163v-164h-163zM324 754v164h163v-164h-163z" />
+<glyph unicode=";" horiz-adv-x="854" d="M199 -229l151 401h168l-172 -401h-147zM352 754v164h164v-164h-164z" />
+<glyph unicode="<" horiz-adv-x="1351" d="M246 481v140l844 383v-164l-672 -289l672 -291v-162z" />
+<glyph unicode="=" horiz-adv-x="1306" d="M260 381v143h789v-143h-789zM260 698v142h789v-142h-789z" />
+<glyph unicode=">" horiz-adv-x="1329" d="M270 98v162l670 291l-670 289v164l842 -383v-140z" />
+<glyph unicode="?" horiz-adv-x="993" d="M164 1145v160q113 77 285 77q61 0 120.5 -14t129 -50t112.5 -117t43 -197q0 -82 -31.5 -142.5t-76.5 -98.5l-91 -76q-45 -38 -76.5 -101.5t-31.5 -151.5v-90h-148v100q0 104 32 181t77 120t90 78t77 79t32 102q0 231 -269 231q-156 0 -274 -90zM391 0v164h164v-164h-164z " />
+<glyph unicode="@" horiz-adv-x="1869" d="M170 371q0 244 133 445.5t334 309t410 107.5q82 0 171 -21.5t180 -73t162.5 -126t116.5 -189t45 -254.5q0 -221 -91 -362t-242 -141q-137 0 -191 100q-96 -100 -295 -100q-143 0 -225 62t-82 171q0 49 16.5 94t59.5 94.5t138 79t230 29.5q74 0 152 -10q2 14 2 43 q0 35 -6 59.5t-23.5 50t-60.5 39t-109 13.5q-143 0 -256 -43l17 118q123 41 239 41q156 0 237 -70.5t81 -207.5q0 -59 -11.5 -142.5t-17.5 -137.5q-4 -40 -4 -81v-26q2 -54 30.5 -79.5t86.5 -25.5q90 0 169 114t79 312q0 145 -55.5 262t-144.5 186.5t-191.5 106.5t-206.5 37 q-311 0 -554 -230.5t-243 -553.5q0 -154 51 -270.5t123 -177t163 -98.5t153.5 -47t113.5 -9q254 0 430 100l-18 -92q-172 -82 -428 -82q-86 0 -174.5 22.5t-178.5 74.5t-158.5 127t-112.5 192.5t-44 259.5zM715 300q0 -116 188 -116q49 0 88 8.5t65.5 17.5t47 31.5t31 37 t18.5 46t11 46t6.5 51.5t5.5 47q-55 8 -136 8q-325 0 -325 -177z" />
+<glyph unicode="A" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM422 567h479l-239 625z" />
+<glyph unicode="B" horiz-adv-x="1271" d="M178 0v1366h453q454 0 454 -365q0 -57 -15 -106t-36.5 -78t-42 -50.5t-37.5 -29.5l-14 -10q10 -2 24.5 -8t54.5 -33t69.5 -61.5t54 -98t24.5 -139.5q0 -387 -495 -387h-494zM326 137h358q156 0 246 54.5t90 203.5q0 63 -21.5 108.5t-52.5 71t-80 39t-91 17.5t-97 4h-352 v-498zM326 774h338q125 0 199.5 43t74.5 172q0 133 -72.5 184.5t-220.5 51.5h-319v-451z" />
+<glyph unicode="C" horiz-adv-x="1286" d="M125 684q0 700 633 700q219 0 416 -79v-142q-195 82 -400 82q-260 0 -381 -135t-121 -426t121 -426t381 -135q209 0 400 80v-142q-196 -77 -411 -77h-5q-633 0 -633 700z" />
+<glyph unicode="D" horiz-adv-x="1372" d="M178 0v1366h428q641 0 641 -682q0 -684 -641 -684h-428zM326 139h270q262 0 383 131t121 414t-121 414t-383 131h-270v-1090z" />
+<glyph unicode="E" horiz-adv-x="1159" d="M178 0v1366h862v-139h-714v-471h628v-144h-628v-473h714v-139h-862z" />
+<glyph unicode="F" horiz-adv-x="1097" d="M178 0v1366h838v-139h-690v-471h606v-144h-606v-612h-148z" />
+<glyph unicode="G" horiz-adv-x="1386" d="M125 684q0 700 633 700q227 0 430 -86v-143q-199 88 -414 88q-260 0 -381 -135t-121 -424q0 -291 121 -425t381 -134q164 0 313 49v426h-329v143h477v-653q-221 -106 -477 -106q-633 0 -633 700z" />
+<glyph unicode="H" horiz-adv-x="1380" d="M178 0v1366h148v-612h727v612h147v-1366h-147v612h-727v-612h-148z" />
+<glyph unicode="I" horiz-adv-x="501" d="M178 0v1366h148v-1366h-148z" />
+<glyph unicode="J" horiz-adv-x="915" d="M59 55v154q117 -82 252 -82q139 0 200.5 68.5t61.5 224.5v946h148v-958q0 -125 -35 -213.5t-95.5 -131.5t-124 -61t-136.5 -18q-147 0 -271 71z" />
+<glyph unicode="K" horiz-adv-x="1294" d="M178 0v1366h148v-704l632 704h199l-586 -639l633 -727h-194l-535 623l-149 -164v-459h-148z" />
+<glyph unicode="L" horiz-adv-x="1067" d="M178 0v1366h148v-1223h698v-143h-846z" />
+<glyph unicode="M" horiz-adv-x="1619" d="M178 0v1366h160l469 -694l471 694h160v-1366h-148v1124l-483 -696l-481 696v-1124h-148z" />
+<glyph unicode="N" horiz-adv-x="1490" d="M178 0v1366h168l815 -1151v1151h148v-1366h-166l-817 1151v-1151h-148z" />
+<glyph unicode="O" d="M125 684q0 700 633 700q635 0 635 -700t-635 -700q-633 0 -633 700zM272 684q0 -291 117 -426t369 -135q254 0 370.5 135t116.5 426t-116.5 426t-370.5 135q-252 0 -369 -135t-117 -426z" />
+<glyph unicode="P" horiz-adv-x="1220" d="M176 0v1366h486q229 0 351 -108.5t122 -313.5t-122 -313.5t-351 -108.5h-338v-522h-148zM324 664h319q109 0 181.5 23.5t105.5 68.5t45 88t12 100t-12 100t-45 88.5t-105.5 69t-181.5 23.5h-319v-561z" />
+<glyph unicode="Q" d="M125 684q0 700 633 700q635 0 635 -700q0 -651 -553 -698q41 -170 268 -176v-134q-113 0 -196 30t-127 81t-65.5 98t-29.5 101q-565 37 -565 698zM272 684q0 -291 117 -426t369 -135q254 0 370.5 135t116.5 426t-116.5 426t-370.5 135q-252 0 -369 -135t-117 -426z" />
+<glyph unicode="R" horiz-adv-x="1271" d="M178 0v1366h502q223 0 343 -101.5t120 -295.5q0 -88 -28 -160t-63.5 -112t-84.5 -69.5t-80 -39t-58 -15.5l375 -573h-174l-356 551h-348v-551h-148zM326 690h346q102 0 170.5 23.5t99.5 65.5t42 83t11 96.5t-11 96.5t-42 83t-99.5 65.5t-170.5 23.5h-346v-537z" />
+<glyph unicode="S" horiz-adv-x="1150" d="M125 1004q0 158 114.5 269t356.5 111q174 0 342 -81v-152q-152 86 -328 86q-164 0 -251 -60t-87 -173q0 -70 56.5 -117t139.5 -69.5t181.5 -57.5t181 -76t139 -127t56.5 -209q0 -170 -128 -267t-357 -97q-223 0 -404 96v160q162 -109 393 -109q348 0 349 217 q0 92 -56.5 154.5t-139.5 91.5l-181 60q-98 32 -181 65t-139.5 105.5t-56.5 179.5z" />
+<glyph unicode="T" horiz-adv-x="1163" d="M25 1223v143h1112v-143h-482v-1223h-147v1223h-483z" />
+<glyph unicode="U" horiz-adv-x="1476" d="M178 561v805h148v-813q0 -434 407.5 -434t407.5 434v813h147v-805q0 -281 -143.5 -429t-411.5 -148t-411.5 148.5t-143.5 428.5z" />
+<glyph unicode="V" horiz-adv-x="1251" d="M25 1366h157l441 -1155l438 1155h158l-525 -1366h-145z" />
+<glyph unicode="W" horiz-adv-x="1947" d="M25 1366h155l359 -1143l360 1143h148l358 -1143l360 1143h156l-440 -1366h-150l-358 1114l-359 -1114h-151z" />
+<glyph unicode="X" horiz-adv-x="1206" d="M25 0l485 692l-473 674h176l387 -549l385 549h180l-475 -676l484 -690h-177l-397 563l-395 -563h-180z" />
+<glyph unicode="Y" horiz-adv-x="1224" d="M25 1366h178l405 -596l404 596h178l-508 -739v-627h-147v627z" />
+<glyph unicode="Z" horiz-adv-x="1247" d="M131 0v129l780 1090h-759v147h952v-125l-785 -1094h785v-147h-973z" />
+<glyph unicode="[" horiz-adv-x="784" d="M244 -238v1663h370v-139h-223v-1384h223v-140h-370z" />
+<glyph unicode="\" horiz-adv-x="753" d="M-76 1548h135l752 -1749h-133z" />
+<glyph unicode="]" horiz-adv-x="780" d="M172 -98h221v1384h-221v139h369v-1663h-369v140z" />
+<glyph unicode="^" horiz-adv-x="1146" d="M274 969l213 444h113l213 -444h-123l-145 325l-148 -325h-123z" />
+<glyph unicode="_" horiz-adv-x="1118" d="M0 -49h1118v-90h-1118v90z" />
+<glyph unicode="`" horiz-adv-x="448" d="M94 1391h150l116 -236h-96z" />
+<glyph unicode="a" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t94 59t101 36t104.5 17.5t86 7t60.5 1l164 -10q0 137 -75.5 195.5t-223.5 58.5q-166 0 -299 -49v143q151 53 307 53q215 0 328 -108.5t113 -317.5v-307q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 313q0 -192 278 -192h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -203z" />
+<glyph unicode="b" horiz-adv-x="1167" d="M170 307v1106h147v-414q123 41 252 41q70 0 131.5 -12t125 -47t109.5 -90t74.5 -149.5t28.5 -217.5q0 -135 -31.5 -236.5t-77.5 -157.5t-110.5 -91t-119 -45t-111.5 -10q-111 0 -193 27.5t-123 63.5t-66.5 86t-30.5 82.5t-5 63.5zM317 299q1 -180 271 -180q57 0 103 13 t95.5 51t77 125t27.5 216q0 199 -76 289t-246 90q-127 0 -252 -51v-553z" />
+<glyph unicode="c" horiz-adv-x="966" d="M119 512q0 156 43 265.5t116.5 163.5t150.5 76.5t169 22.5q154 0 303 -57v-141q-139 57 -287 57q-182 0 -265 -92t-83 -295q0 -205 83 -296t265 -91q154 0 287 57v-143q-143 -55 -303 -55q-72 0 -134.5 12t-128 48t-111.5 92.5t-75.5 152.5t-29.5 223z" />
+<glyph unicode="d" horiz-adv-x="1150" d="M129 524q0 152 41 258.5t113.5 159.5t147.5 74.5t165 21.5q135 0 254 -39v412h147v-1106q0 -41 -12 -84t-50 -102.5t-130 -97t-227 -37.5q-57 0 -111.5 10t-118 45t-109.5 91t-78 157.5t-32 236.5zM276 524q0 -129 28 -216t77 -125t94 -51t103 -13q272 0 272 180v553 q-125 51 -254 51q-168 0 -244 -90t-76 -289z" />
+<glyph unicode="e" horiz-adv-x="1136" d="M129 498v7v8q0 527 449 527q94 0 170.5 -29.5t121.5 -69.5t79 -104.5t48.5 -109.5t22.5 -109q8 -62 8 -78v-44l-4 -72h-745q14 -154 94 -223.5t237 -69.5q223 0 351 76v-152q-148 -71 -359 -71h-6q-90 0 -165 21.5t-147.5 74.5t-113.5 159.5t-41 258.5zM279 555h598 q0 35 -8.5 78t-34 110.5t-92 112.5t-164.5 45q-152 0 -220.5 -79t-78.5 -267z" />
+<glyph unicode="f" horiz-adv-x="675" d="M82 887v135h176v55q0 106 27.5 182t62.5 112t89.5 56.5t83 22.5t67.5 2q55 0 98 -12v-131q-41 8 -74 8q-49 0 -74.5 -4t-61.5 -24.5t-53 -73t-17 -136.5v-57h280v-135h-280v-887h-148v887h-176z" />
+<glyph unicode="g" horiz-adv-x="1101" d="M123 516q0 160 43 271.5t115.5 162.5t138 70.5t137.5 19.5q131 0 220 -36.5t125 -94t48.5 -99.5t12.5 -83v-770q0 -68 -19.5 -125t-64.5 -111.5t-137.5 -85t-223.5 -30.5q-143 0 -291 37v139q131 -37 275 -37q98 0 164.5 17.5t96.5 50t41 64.5t11 77v102 q-115 -37 -240 -37q-68 0 -127 11.5t-120.5 45.5t-105.5 88t-71.5 144t-27.5 209zM270 516q0 -115 24.5 -189.5t72 -110.5t95.5 -48t113 -12q123 0 240 47v530q0 172 -258 172q-55 0 -98 -13t-90 -50t-73 -120t-26 -206z" />
+<glyph unicode="h" horiz-adv-x="1181" d="M193 0v1411h147v-436q155 65 309 65q188 0 290.5 -113.5t102.5 -320.5v-606h-147v606q0 287 -262 287q-143 0 -293 -62v-831h-147z" />
+<glyph unicode="i" horiz-adv-x="540" d="M193 1204v162h163v-162h-163zM201 0h147v1022h-147v-1022z" />
+<glyph unicode="j" horiz-adv-x="514" d="M6 -258q127 2 168 50t41 157v1073h147v-1081q0 -336 -356 -336v137zM207 1204v162h164v-162h-164z" />
+<glyph unicode="k" horiz-adv-x="1124" d="M188 0v1411h148v-891l459 502h200l-436 -469l492 -553h-197l-391 449l-127 -138v-311h-148z" />
+<glyph unicode="l" horiz-adv-x="552" d="M207 0v1411h147v-1411h-147z" />
+<glyph unicode="m" horiz-adv-x="1824" d="M193 0v942q197 98 409 98q168 0 270 -84q197 84 379 84q186 0 288.5 -98t102.5 -276v-666h-147v666q0 43 -8 76.5t-32.5 71.5t-79 58.5t-134.5 20.5q-129 0 -283 -51q33 -74 33 -176v-666h-147v666q0 45 -8.5 78.5t-33 70.5t-80.5 57.5t-138 20.5q-125 0 -244 -37v-856 h-147z" />
+<glyph unicode="n" horiz-adv-x="1189" d="M168 0v940q201 100 432 100q193 0 299.5 -98t106.5 -276v-666h-148v666q0 43 -9 76.5t-35 71.5t-84 58.5t-146 20.5q-129 0 -269 -41v-852h-147z" />
+<glyph unicode="o" horiz-adv-x="1155" d="M100 512q0 156 43 265.5t117 163.5t150.5 76.5t169 22.5t170 -22.5t151.5 -76.5t117 -163.5t43 -265.5t-43 -265.5t-117 -163.5t-151.5 -76.5t-170 -22.5t-169 22.5t-150.5 76.5t-117 163.5t-43 265.5zM248 512q0 -205 79 -299t253 -94t253.5 93t79.5 300t-79.5 300 t-253.5 93t-253 -94t-79 -299z" />
+<glyph unicode="p" horiz-adv-x="1159" d="M168 -379v1096q0 41 12.5 84t50 103.5t130 98t227.5 37.5q57 0 111.5 -10t118 -45t109.5 -92t77.5 -158.5t31.5 -236.5q0 -152 -41 -258.5t-113.5 -159.5t-147.5 -74.5t-165 -21.5q-125 0 -254 39v-402h-147zM315 172q117 -51 254 -51q168 0 244 89t76 290 q0 129 -27.5 215t-77 125t-94.5 52t-102 13q-272 0 -273 -182v-551z" />
+<glyph unicode="q" horiz-adv-x="1150" d="M115 498q0 135 31.5 236.5t77.5 158.5t109.5 92t118 45t111.5 10q111 0 193 -27.5t123 -63.5t66.5 -86t30.5 -82.5t5 -63.5v-1096h-147v402q-117 -39 -252 -39q-90 0 -165 21.5t-147.5 74.5t-113.5 159.5t-41 258.5zM262 500q0 -201 76 -290t244 -89q135 0 252 51v551 q0 182 -271 182q-57 0 -102 -13t-94.5 -52t-77 -125t-27.5 -215z" />
+<glyph unicode="r" horiz-adv-x="702" d="M166 0v940q231 98 496 98v-141q-168 -4 -349 -53v-844h-147z" />
+<glyph unicode="s" horiz-adv-x="872" d="M100 743q0 123 89.5 210t273.5 87q131 0 262 -63v-148q-117 70 -246 70q-231 0 -231 -156q0 -53 56 -85.5t137 -54t162 -51.5t137.5 -101.5t56.5 -182.5q0 -133 -99.5 -208.5t-275.5 -75.5q-170 0 -309 77v156q147 -90 303 -90q233 0 233 141q0 76 -56 119t-137 64.5 t-162 47t-137.5 86t-56.5 158.5z" />
+<glyph unicode="t" horiz-adv-x="706" d="M49 887v135h178v289h148v-289h264v-135h-264v-623q0 -78 32.5 -109.5t114.5 -31.5q63 0 117 24v-139q-65 -24 -130 -24h-5q-41 0 -80 9t-88 35.5t-79 89t-30 154.5v615h-178z" />
+<glyph unicode="u" horiz-adv-x="1202" d="M168 430v592h147v-600q0 -156 68 -228.5t213 -72.5q143 0 212 72.5t69 228.5v600h147v-592q0 -213 -111.5 -329.5t-316.5 -116.5t-316.5 116.5t-111.5 329.5z" />
+<glyph unicode="v" horiz-adv-x="1062" d="M66 1022h157l309 -838l310 838h157l-393 -1022h-149z" />
+<glyph unicode="w" horiz-adv-x="1603" d="M78 1022h155l261 -791l235 791h141l238 -791l260 791h156l-340 -1022h-150l-233 764l-234 -764h-149z" />
+<glyph unicode="x" horiz-adv-x="1032" d="M57 0l365 518l-354 504h174l268 -379l268 379h180l-356 -504l-2 -4l363 -514h-177l-274 389l-274 -389h-181z" />
+<glyph unicode="y" horiz-adv-x="1028" d="M63 1022h158l291 -809l295 809h158l-418 -1122q-109 -295 -322 -295q-49 0 -96 18v135q41 -18 88 -18q39 0 77 28.5t62.5 68.5t45 81t28.5 70l10 28z" />
+<glyph unicode="z" horiz-adv-x="950" d="M92 0v133l557 748h-543v141h734v-129l-561 -752h561v-141h-748z" />
+<glyph unicode="{" horiz-adv-x="763" d="M186 551v100q129 33 129 148v319q0 150 79 229.5t229 79.5v-139q-82 -4 -121 -36.5t-39 -123.5v-329q0 -119 -119 -199q119 -80 119 -199v-329q0 -88 39 -122t121 -38v-137q-150 0 -229 78.5t-79 228.5v319q0 111 -129 150z" />
+<glyph unicode="|" horiz-adv-x="907" d="M379 -203v1565h147v-1565h-147z" />
+<glyph unicode="}" horiz-adv-x="753" d="M156 -88q82 4 120.5 38t38.5 122v329q0 117 121 199q-121 82 -121 199v329q0 90 -37.5 123t-121.5 37v139q150 0 228.5 -79.5t78.5 -229.5v-319q0 -115 129 -148v-100q-129 -39 -129 -150v-319q0 -150 -79 -228.5t-228 -78.5v137z" />
+<glyph unicode="~" horiz-adv-x="1024" d="M201 473v125q57 72 123 79q11 1 22 1q54 0 104 -27l124 -65q50 -27 103 -27q12 0 23 1q66 7 123 79v-123q-57 -72 -123 -79q-11 -1 -22 -1q-54 0 -104 27l-124 65q-50 25 -103 25q-12 0 -23 -1q-66 -7 -123 -79z" />
+<glyph unicode="¡" horiz-adv-x="741" d="M303 860v162h164v-162h-164zM311 -344l15 1022h118l15 -1022h-148z" />
+<glyph unicode="¢" horiz-adv-x="976" d="M117 682q0 270 116.5 390t294.5 136v158h103v-156q131 -2 266 -55v-141q-125 51 -266 55v-772q143 4 266 57v-143q-125 -49 -266 -55v-156h-103v158q-411 41 -411 524zM264 684q0 -178 63.5 -270t200.5 -111v762q-137 -20 -200.5 -111.5t-63.5 -269.5z" />
+<glyph unicode="£" horiz-adv-x="1136" d="M104 -31v150q80 39 177 45v411h-177v136h177v293q0 158 105.5 269t328.5 111q162 0 309 -79v-150q-139 88 -297 88q-299 0 -299 -233v-299h412v-136h-412v-415q51 -6 145.5 -25.5t154.5 -30.5q47 -8 107 -8q18 0 36 1q83 3 153 30v-150q-80 -31 -180 -31q-24 0 -49 2 q-129 9 -230.5 37.5t-229.5 31.5h-14q-120 0 -217 -48z" />
+<glyph unicode="¥" horiz-adv-x="1232" d="M37 1366h180l404 -588l403 588h180l-444 -645h256v-123h-322v-192h322v-123h-322v-283h-147v283h-322v123h322v192h-322v123h256z" />
+<glyph unicode="§" horiz-adv-x="1112" d="M119 727q0 164 186 223q-82 51 -82 144q0 119 100.5 204.5t309.5 85.5q156 0 297 -61v-145q-129 69 -281 69h-4q-274 0 -274 -153q0 -25 8 -40.5t68.5 -44t179.5 -55.5q84 -18 140 -38.5t116.5 -56.5t92.5 -94t32 -136q0 -76 -51.5 -130.5t-125.5 -78.5q72 -59 72 -158 q0 -131 -111.5 -204.5t-310.5 -73.5q-203 0 -350 73v152q135 -84 340 -84q285 0 285 137q0 61 -48.5 102.5t-218.5 79.5q-184 43 -277 101.5t-93 181.5zM266 727q0 -25 8.5 -40t70 -44t179.5 -55q135 -29 219 -74q117 35 117 115q0 61 -49 102t-217 80q-98 23 -176 49 q-152 -29 -152 -133z" />
+<glyph unicode="¨" horiz-adv-x="788" d="M162 1155v162h162v-162h-162zM465 1155v162h162v-162h-162z" />
+<glyph unicode="©" horiz-adv-x="1593" d="M98 682q0 291 206 495.5t495 204.5q291 0 495.5 -204.5t204.5 -495.5q0 -289 -204.5 -493.5t-495.5 -204.5q-289 0 -495 204.5t-206 493.5zM172 682q0 -258 184.5 -442.5t442.5 -184.5q260 0 443 184.5t183 442.5q0 260 -183 444.5t-443 184.5q-258 0 -442.5 -184.5 t-184.5 -444.5zM449 682q0 410 368 410q129 0 244 -45v-82q-109 45 -234 45q-152 0 -222 -79t-70 -249t70.5 -249t221.5 -79q121 0 234 47v-82q-115 -45 -244 -45q-369 0 -368 408z" />
+<glyph unicode="ª" horiz-adv-x="806" d="M156 979q0 186 297 186l92 -6q0 143 -168 144q-92 0 -164 -29v80q72 28 168 28q246 0 246 -235v-172q0 -72 -46 -115t-92.5 -53t-95.5 -10t-96 10t-94 54t-47 118zM238 982q0 -110 155 -110q152 1 152 105v108q-37 6 -101 7q-206 0 -206 -110z" />
+<glyph unicode="«" horiz-adv-x="1132" d="M63 522l347 389h198l-364 -389l364 -391h-198zM453 522l346 389h198l-364 -389l364 -391h-198z" />
+<glyph unicode="¬" horiz-adv-x="1273" d="M199 528v136h876v-340h-135v204h-741z" />
+<glyph unicode="­" horiz-adv-x="950" d="M162 539v143h624v-143h-624z" />
+<glyph unicode="®" horiz-adv-x="1593" d="M98 682q0 291 206 495.5t495 204.5q291 0 495.5 -204.5t204.5 -495.5q0 -289 -204.5 -493.5t-495.5 -204.5q-289 0 -495 204.5t-206 493.5zM172 682q0 -258 184.5 -442.5t442.5 -184.5q260 0 443 184.5t183 442.5q0 260 -183 444.5t-443 184.5q-258 0 -442.5 -184.5 t-184.5 -444.5zM545 283v798h293q131 0 201.5 -58t70.5 -173q0 -63 -22.5 -111.5t-57.5 -72t-58 -33t-46 -15.5l219 -335h-103l-206 323h-205v-323h-86zM631 688h203q113 0 150.5 43t37.5 112.5t-38 112.5t-150 43h-203v-311z" />
+<glyph unicode="¯" horiz-adv-x="825" d="M176 1180v108h479v-108h-479z" />
+<glyph unicode="°" horiz-adv-x="792" d="M168 1142q0 228 223 228q109 0 166 -58.5t57 -168.5q0 -227 -223 -227q0 -1 -1 -1q-222 0 -222 227zM242 1143q0 -82 34.5 -118t114.5 -36q78 0 114 36t36 118q0 80 -36 116.5t-114 36.5q-80 0 -114.5 -36.5t-34.5 -116.5z" />
+<glyph unicode="±" horiz-adv-x="1198" d="M231 68v122h727v-122h-727zM231 598v123h304v301h122v-301h301v-123h-301v-303h-122v303h-304z" />
+<glyph unicode="²" horiz-adv-x="671" d="M109 948v70q133 98 252.5 230t119.5 239q0 125 -172 125q-106 0 -192 -62v86q90 55 198 56q125 0 185.5 -60.5t60.5 -145.5q0 -197 -321 -458h331v-80h-462z" />
+<glyph unicode="³" horiz-adv-x="661" d="M109 985v82q90 -49 186 -49q203 0 203 141t-207 141h-80v76h84q164 0 164 119q0 117 -174 117q-88 0 -158 -37v80q82 37 168 37q125 0 184.5 -58.5t59.5 -138.5q0 -49 -23.5 -86t-48.5 -49l-23 -14q6 -2 14.5 -6.5t31 -20.5t40 -36.5t31.5 -54.5t14 -71 q0 -90 -66.5 -153.5t-203.5 -63.5q-102 0 -196 45z" />
+<glyph unicode="´" horiz-adv-x="483" d="M123 1155l117 236h151l-170 -236h-98z" />
+<glyph unicode="µ" horiz-adv-x="1247" d="M203 -430v1452h147v-664q0 -45 9.5 -78.5t35 -70.5t83 -57.5t145.5 -20.5q135 0 268 39v852h147v-938q-193 -100 -430 -100q-152 2 -258 82v-496h-147z" />
+<glyph unicode="¶" horiz-adv-x="911" d="M78 946q0 203 121 311.5t350 108.5h203v-1366h-136v524h-67q-229 0 -350 108.5t-121 313.5z" />
+<glyph unicode="·" horiz-adv-x="636" d="M238 475v164h161v-164h-161z" />
+<glyph unicode="¸" horiz-adv-x="518" d="M115 -246l168 236h116l-129 -236h-155z" />
+<glyph unicode="¹" horiz-adv-x="491" d="M109 1503v94l135 86h90v-735h-78v651z" />
+<glyph unicode="º" horiz-adv-x="854" d="M156 1090q0 86 23.5 147t64.5 92t84 43t94 12q266 0 266 -294q0 -293 -266 -293q-113 0 -189.5 64.5t-76.5 228.5zM238 1089.5q0 -114.5 44 -166t140 -51.5q98 0 141 51.5t43 166t-43 167t-141 52.5q-96 0 -140 -52.5t-44 -167z" />
+<glyph unicode="»" horiz-adv-x="1132" d="M137 131l363 391l-363 389h197l348 -389l-348 -391h-197zM526 131l363 391l-363 389h197l348 -389l-348 -391h-197z" />
+<glyph unicode="¼" horiz-adv-x="1433" d="M106 1200v94l134 86h92v-733h-80v651zM147 -45l895 1448h97l-895 -1448h-97zM788 221v84l347 428h90v-438h104v-74h-104v-221h-80v221h-357zM877 295h268v326z" />
+<glyph unicode="½" horiz-adv-x="1460" d="M106 1200v94l134 86h92v-733h-80v651zM147 -45l895 1448h97l-895 -1448h-97zM870 0v70q133 98 252 229t119 238q0 127 -172 127q-104 0 -192 -64v88q98 55 198 55q127 0 186.5 -60t59.5 -146q0 -195 -322 -457h332v-80h-461z" />
+<glyph unicode="¾" horiz-adv-x="1654" d="M135 684v80q94 -47 189 -47q200 0 200 140q0 69 -53 105.5t-154 36.5h-79v74h84q164 0 164 118q0 120 -175 120q-84 0 -157 -39v82q82 37 168 37q125 0 184 -58.5t59 -140.5q0 -49 -23.5 -86t-45.5 -49l-25 -15q6 -2 14.5 -6t31 -20.5t39.5 -35.5t31.5 -53t14.5 -71 q0 -90 -65.5 -154.5t-204.5 -64.5q-98 0 -197 47zM369 -45l895 1448h96l-895 -1448h-96zM1010 221v84l346 428h90v-438h104v-74h-104v-221h-80v221h-356zM1098 295h268v326z" />
+<glyph unicode="¿" horiz-adv-x="989" d="M147 20q0 82 32 141.5t77 97.5l90 76q45 38 77 101.5t32 151.5v92h147v-102q0 -104 -31.5 -181.5t-76.5 -119t-90.5 -77.5t-77 -80t-31.5 -100q0 -233 270 -233q141 0 275 90v-160q-115 -77 -287 -77q-47 0 -93 7t-105.5 31.5t-102.5 64.5t-74 111.5t-31 165.5zM446 860 v162h164v-162h-164z" />
+<glyph unicode="À" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM422 567h479l-239 625zM438 1718h152l117 -235h-99z" />
+<glyph unicode="Á" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM422 567h479l-239 625zM614 1483l117 235h152l-170 -235h-99z" />
+<glyph unicode="Â" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM422 567h479l-239 625zM455 1483l163 248h87l163 -248h-114l-92 145l-93 -145h-114z" />
+<glyph unicode="Ã" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM373 1485v117q53 66 115 72h19q51 0 97 -24l112 -60q46 -24 97 -24q10 0 21 1q61 6 114 71v-112q-53 -66 -114 -73q-12 -1 -24 -1q-49 0 -94 24l-112 59q-44 24 -92 24q-12 0 -24 -1q-62 -8 -115 -73z M422 567h479l-239 625z" />
+<glyph unicode="Ä" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM422 567h479l-239 625zM428 1483v164h164v-164h-164zM731 1483v164h164v-164h-164z" />
+<glyph unicode="Å" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM422 567h479l-239 625zM496 1591q0 82 43 125t122.5 43t122.5 -43t43 -125t-43 -125t-122.5 -43t-122.5 43t-43 125zM569 1591.5q0 -53.5 21 -76t72 -22.5q49 0 70.5 22.5t21.5 76t-21.5 77t-70.5 23.5 q-51 0 -72 -23.5t-21 -77z" />
+<glyph unicode="Æ" horiz-adv-x="1939" d="M51 0l811 1366h955v-141h-691v-471h607v-144h-607v-469h691v-141h-838v428h-506l-252 -428h-170zM559 575h420v644h-45z" />
+<glyph unicode="Ç" horiz-adv-x="1286" d="M125 686q0 700 633 700q217 0 416 -77v-142q-187 80 -400 80q-260 0 -381 -134t-121 -425t121 -426t381 -135q209 0 400 80v-141q-172 -68 -357 -78l-125 -232h-155l163 232q-575 35 -575 698z" />
+<glyph unicode="È" horiz-adv-x="1159" d="M178 0v1366h862v-139h-714v-471h628v-144h-628v-473h714v-139h-862zM444 1718h152l117 -235h-99z" />
+<glyph unicode="É" horiz-adv-x="1159" d="M178 0v1366h862v-139h-714v-471h628v-144h-628v-473h714v-139h-862zM565 1483l115 235h151l-169 -235h-97z" />
+<glyph unicode="Ê" horiz-adv-x="1159" d="M178 0v1366h862v-139h-714v-471h628v-144h-628v-473h714v-139h-862zM412 1483l166 248h84l165 -248h-114l-92 145l-95 -145h-114z" />
+<glyph unicode="Ë" horiz-adv-x="1159" d="M178 0v1366h862v-139h-714v-471h628v-144h-628v-473h714v-139h-862zM395 1483v164h162v-164h-162zM698 1483v164h162v-164h-162z" />
+<glyph unicode="Ì" horiz-adv-x="501" d="M33 1718h151l117 -235h-98zM178 0v1366h148v-1366h-148z" />
+<glyph unicode="Í" horiz-adv-x="501" d="M178 0h148v1366h-148v-1366zM203 1483l116 235h152l-170 -235h-98z" />
+<glyph unicode="Î" horiz-adv-x="501" d="M45 1483l164 248h86l164 -248h-115l-92 145l-92 -145h-115zM178 0h148v1366h-148v-1366z" />
+<glyph unicode="Ï" horiz-adv-x="501" d="M18 1483v164h164v-164h-164zM178 0h148v1366h-148v-1366zM322 1483v164h163v-164h-163z" />
+<glyph unicode="Ð" horiz-adv-x="1468" d="M109 621v122h159v623h428q641 0 641 -682q0 -684 -641 -684h-428v621h-159zM416 139h270q262 0 383 131t121 414t-121 414t-383 131h-270v-486h350v-122h-350v-482z" />
+<glyph unicode="Ñ" horiz-adv-x="1490" d="M178 0v1366h168l815 -1151v1151h148v-1366h-166l-817 1151v-1151h-148zM455 1485v117q53 66 115 72h19q51 0 98 -24l113 -60q46 -24 97 -24q10 0 21 1q61 6 114 71v-112q-53 -66 -114 -73q-12 -1 -24 -1q-49 0 -94 24l-113 59q-45 24 -93 24q-12 0 -24 -1 q-62 -8 -115 -73z" />
+<glyph unicode="Ò" d="M125 684q0 700 633 700t633 -700t-633 -700t-633 700zM272 684q0 -291 117 -426t369 -135t368.5 135t116.5 426t-116.5 426t-368.5 135t-369 -135t-117 -426zM563 1718h150l116 -235h-96z" />
+<glyph unicode="Ó" d="M125 684q0 700 633 700t633 -700t-633 -700t-633 700zM272 684q0 -291 117 -426t369 -135t368.5 135t116.5 426t-116.5 426t-368.5 135t-369 -135t-117 -426zM686 1483l115 235h151l-170 -235h-96z" />
+<glyph unicode="Ô" d="M125 684q0 700 633 700t633 -700t-633 -700t-633 700zM272 684q0 -291 117 -426t369 -135t368.5 135t116.5 426t-116.5 426t-368.5 135t-369 -135t-117 -426zM551 1483l164 248h86l164 -248h-115l-92 145l-92 -145h-115z" />
+<glyph unicode="Õ" d="M125 684q0 700 633 700q635 0 635 -700t-635 -700q-633 0 -633 700zM272 684q0 -291 117 -426t369 -135q254 0 370.5 135t116.5 426t-116.5 426t-370.5 135q-252 0 -369 -135t-117 -426zM471 1485v117q53 66 113 72h19q51 0 99 -24l112 -60q47 -24 97 -24q10 0 21 1 q61 6 115 71v-112q-53 -66 -115 -73q-12 -1 -24 -1q-49 0 -94 24l-112 59q-46 24 -93 24q-12 0 -25 -1q-60 -8 -113 -73z" />
+<glyph unicode="Ö" d="M125 684q0 700 633 700t633 -700t-633 -700t-633 700zM272 684q0 -291 117 -426t369 -135t368.5 135t116.5 426t-116.5 426t-368.5 135t-369 -135t-117 -426zM524 1483v164h164v-164h-164zM827 1483v164h164v-164h-164z" />
+<glyph unicode="×" horiz-adv-x="985" d="M158 160l272 364l-272 365h131l207 -277l209 277h131l-273 -365l273 -364h-131l-209 276l-207 -276h-131z" />
+<glyph unicode="Ø" d="M102 0l158 188q-135 172 -135 496q0 700 633 700q256 0 411 -118l86 100h160l-160 -188q137 -174 138 -494q0 -702 -635 -702q-254 0 -410 118l-86 -100h-160zM272 684q0 -252 84 -383l721 856q-115 88 -319 88q-252 0 -369 -135t-117 -426zM440 209q115 -88 318 -88 q254 0 370.5 135t116.5 428q0 244 -84 381z" />
+<glyph unicode="Ù" horiz-adv-x="1476" d="M178 561v805h148v-813q0 -434 407.5 -434t407.5 434v813h147v-805q0 -281 -143.5 -429t-411.5 -148t-411.5 148.5t-143.5 428.5zM520 1718h152l116 -235h-96z" />
+<glyph unicode="Ú" horiz-adv-x="1476" d="M178 561v805h148v-813q0 -434 407.5 -434t407.5 434v813h147v-805q0 -281 -143.5 -429t-411.5 -148t-411.5 148.5t-143.5 428.5zM678 1483l117 235h151l-170 -235h-98z" />
+<glyph unicode="Û" horiz-adv-x="1476" d="M178 561v805h148v-813q0 -434 407.5 -434t407.5 434v813h147v-805q0 -281 -143.5 -429t-411.5 -148t-411.5 148.5t-143.5 428.5zM526 1483l164 248h86l164 -248h-115l-92 145l-92 -145h-115z" />
+<glyph unicode="Ü" horiz-adv-x="1476" d="M178 561v805h148v-813q0 -434 407.5 -434t407.5 434v813h147v-805q0 -281 -143.5 -429t-411.5 -148t-411.5 148.5t-143.5 428.5zM500 1483v164h164v-164h-164zM803 1483v164h164v-164h-164z" />
+<glyph unicode="Ý" horiz-adv-x="1224" d="M25 1366h178l405 -596l404 596h178l-508 -739v-627h-147v627zM539 1483l116 235h150l-170 -235h-96z" />
+<glyph unicode="Þ" horiz-adv-x="1167" d="M176 0v1366h148v-207h266q227 0 349 -109.5t122 -314.5t-122 -313.5t-349 -108.5h-266v-313h-148zM324 455h245q109 0 181.5 23.5t105.5 68.5t45 88t12 100.5t-12 100.5t-45 88t-105.5 68.5t-181.5 23.5h-245v-561z" />
+<glyph unicode="ß" horiz-adv-x="1255" d="M180 0v868q0 113 23.5 203t73 162t136.5 111.5t208 39.5q229 0 341.5 -112.5t112.5 -267.5q0 -51 -19.5 -100.5t-46 -84.5t-54 -61.5t-46.5 -40.5l-20 -12q10 -4 27.5 -12.5t61.5 -39t76.5 -66.5t60.5 -94.5t28 -123.5q0 -170 -150.5 -269.5t-384.5 -99.5v133 q162 0 274.5 61.5t112.5 188.5q0 129 -120.5 188.5t-266.5 59.5v129q145 0 232.5 62.5t87.5 181.5q0 123 -85 183t-222 60q-141 0 -217 -87t-76 -300v-860h-148z" />
+<glyph unicode="à" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t94 59t101 36t104.5 17.5t86 7t60.5 1l164 -10q0 137 -75.5 195.5t-223.5 58.5q-166 0 -299 -49v143q151 53 307 53q215 0 328 -108.5t113 -315.5v-309q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 313q0 -192 278 -192h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -203zM356 1391h150l117 -236h-97z" />
+<glyph unicode="á" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t94 59t101 36t104.5 17.5t86 7t60.5 1l164 -10q0 137 -75.5 195.5t-223.5 58.5q-166 0 -299 -49v143q151 53 307 53q215 0 328 -108.5t113 -315.5v-309q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 313q0 -192 278 -192h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -203zM467 1155l117 236h151l-170 -236h-98z" />
+<glyph unicode="â" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t94 59t101 36t104.5 17.5t86 7t60.5 1l164 -10q0 137 -75.5 195.5t-223.5 58.5q-166 0 -299 -49v143q151 53 307 53q215 0 328 -108.5t113 -315.5v-309q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 313q0 -192 278 -192h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -203zM362 1155l164 246h86l164 -246h-114l-93 143l-92 -143h-115z" />
+<glyph unicode="ã" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t93 59t101 36t105.5 17.5t85 7t59.5 1l166 -10q0 137 -75.5 195.5t-223.5 58.5q-150 0 -299 -47v141q141 53 307 53q215 0 328 -108.5t113 -315.5v-309q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 315q0 -194 278 -194h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -201zM268 1157v117q53 66 114 72q11 1 21 1q50 0 97 -25l114 -60q46 -24 97 -24q10 0 21 1q61 6 114 72v-113q-53 -66 -114 -73q-12 -1 -24 -1q-49 0 -94 24 l-114 60q-45 23 -94 23q-11 0 -23 -1q-61 -7 -115 -73z" />
+<glyph unicode="ä" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t94 59t101 36t104.5 17.5t86 7t60.5 1l164 -10q0 137 -75.5 195.5t-223.5 58.5q-166 0 -299 -49v143q151 53 307 53q215 0 328 -108.5t113 -315.5v-309q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 313q0 -192 278 -192h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -203zM336 1155v162h164v-162h-164zM639 1155v162h164v-162h-164z" />
+<glyph unicode="å" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t94 59t101 36t104.5 17.5t86 7t60.5 1l164 -10q0 137 -75.5 195.5t-223.5 58.5q-166 0 -299 -49v143q151 53 307 53q215 0 328 -108.5t113 -315.5v-309q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 313q0 -192 278 -192h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -203zM393 1264q0 82 43 125t123 43t123 -43t43 -125t-43 -125t-123 -43t-123 43t-43 125zM467 1263.5q0 -53.5 20.5 -77t71.5 -23.5q49 0 70.5 23.5t21.5 77 t-21.5 76t-70.5 22.5q-51 0 -71.5 -22.5t-20.5 -76z" />
+<glyph unicode="æ" horiz-adv-x="1824" d="M111 313q0 70 25.5 125.5t62.5 90t93 59t101 36t105.5 17.5t85 7t59.5 1l166 -10q0 137 -76 195.5t-223 58.5q-166 0 -299 -49v143q152 53 305 53q268 0 377 -168q112 168 362 168q94 0 171 -29.5t122 -69.5t79 -104.5t48.5 -109.5t22.5 -108.5t8 -78.5v-44l-4 -72h-746 q14 -154 94 -223.5t240 -69.5q219 0 350 76v-152q-152 -71 -360 -71h-6q-248 0 -365 151q-103 -151 -367 -151h-3q-47 0 -96.5 6t-112 27.5t-108.5 55t-78.5 96t-32.5 144.5zM258 313q0 -192 278 -192h3q270 0 270 186v197q-66 12 -180 12q-371 0 -371 -203zM956 555h598 q0 35 -8 78t-33.5 110.5t-92 112.5t-165.5 45q-150 0 -219.5 -79t-79.5 -267z" />
+<glyph unicode="ç" horiz-adv-x="966" d="M119 512q0 156 43 265.5t116.5 163.5t150.5 76.5t169 22.5q154 0 303 -57v-141q-140 57 -287 57q-182 0 -265 -92t-83 -295q0 -205 83 -296t265 -91q154 0 287 57v-143q-115 -41 -225 -53l-127 -230h-156l164 228q-86 4 -157.5 30.5t-138 83t-104.5 162t-38 252.5z" />
+<glyph unicode="è" horiz-adv-x="1136" d="M129 498v7v8q0 527 449 527q94 0 170.5 -29.5t121.5 -69.5t79 -104.5t48.5 -109.5t22.5 -108.5t8 -78.5v-44l-4 -72h-745q14 -154 94 -223.5t237 -69.5q223 0 351 76v-152q-148 -71 -359 -71h-6q-90 0 -165 21.5t-147.5 74.5t-113.5 159.5t-41 258.5zM279 555h598 q0 35 -8.5 78t-34 110.5t-92 112.5t-164.5 45q-152 0 -220.5 -79t-78.5 -267zM395 1391h152l117 -236h-99z" />
+<glyph unicode="é" horiz-adv-x="1136" d="M129 498v7v8q0 527 449 527q94 0 170.5 -29.5t121.5 -69.5t79 -104.5t48.5 -109.5t22.5 -108.5t8 -78.5v-44l-4 -72h-745q14 -154 94 -223.5t237 -69.5q223 0 351 76v-152q-148 -71 -359 -71h-6q-90 0 -165 21.5t-147.5 74.5t-113.5 159.5t-41 258.5zM279 555h598 q0 35 -8.5 78t-34 110.5t-92 112.5t-164.5 45q-152 0 -220.5 -79t-78.5 -267zM506 1155l117 236h151l-170 -236h-98z" />
+<glyph unicode="ê" horiz-adv-x="1136" d="M129 498v7v8q0 527 449 527q94 0 170.5 -29.5t121.5 -69.5t79 -104.5t48.5 -109.5t22.5 -108.5t8 -78.5v-44l-4 -72h-745q14 -154 94 -223.5t237 -69.5q223 0 351 76v-152q-148 -71 -359 -71h-6q-90 0 -165 21.5t-147.5 74.5t-113.5 159.5t-41 258.5zM279 555h598 q0 35 -8.5 78t-34 110.5t-92 112.5t-164.5 45q-152 0 -220.5 -79t-78.5 -267zM371 1155l164 246h86l163 -246h-114l-92 143l-93 -143h-114z" />
+<glyph unicode="ë" horiz-adv-x="1136" d="M129 498v7v8q0 527 449 527q94 0 170.5 -29.5t121.5 -69.5t79 -104.5t48.5 -109.5t22.5 -108.5t8 -78.5v-44l-4 -72h-745q14 -154 94 -223.5t237 -69.5q223 0 351 76v-152q-148 -71 -359 -71h-6q-90 0 -165 21.5t-147.5 74.5t-113.5 159.5t-41 258.5zM279 555h598 q0 35 -8.5 78t-34 110.5t-92 112.5t-164.5 45q-152 0 -220.5 -79t-78.5 -267zM344 1155v162h164v-162h-164zM647 1155v162h164v-162h-164z" />
+<glyph unicode="ì" horiz-adv-x="552" d="M57 1391h152l117 -236h-99zM203 0v1022h147v-1022h-147z" />
+<glyph unicode="í" horiz-adv-x="552" d="M203 0h147v1022h-147v-1022zM227 1155l115 236h152l-170 -236h-97z" />
+<glyph unicode="î" horiz-adv-x="552" d="M68 1155l165 246h84l166 -246h-114l-93 143l-94 -143h-114zM203 0h147v1022h-147v-1022z" />
+<glyph unicode="ï" horiz-adv-x="552" d="M43 1155v162h162v-162h-162zM203 0h147v1022h-147v-1022zM346 1155v162h162v-162h-162z" />
+<glyph unicode="ð" horiz-adv-x="1193" d="M119 444q0 199 125 314.5t340 115.5q186 0 307 -90q-25 244 -174 365l-230 -129l-55 98l170 94q-86 33 -192 33q-41 0 -111 -8v137q66 10 127 10q184 0 328 -86l190 107l53 -98l-145 -84q197 -193 197 -596q0 -643 -465 -643q-123 0 -216.5 41t-145.5 109.5t-77.5 147 t-25.5 162.5zM266 446q0 -41 9.5 -84t38 -103t98.5 -98t172 -38q43 0 70.5 3t78.5 29.5t83 75t57.5 148.5t27.5 242q-110 116 -314 116h-3q-154 0 -236 -75.5t-82 -215.5z" />
+<glyph unicode="ñ" horiz-adv-x="1189" d="M168 0v940q201 100 432 100q193 0 299.5 -98t106.5 -276v-666h-148v666q0 43 -9 76.5t-35 71.5t-84 58.5t-146 20.5q-129 0 -269 -41v-852h-147zM299 1157v117q53 66 113 72q11 1 21 1q49 0 97 -25l112 -60q47 -24 97 -24q10 0 21 1q61 6 114 72v-113q-53 -66 -114 -73 q-12 -1 -24 -1q-49 0 -94 24l-112 60q-46 23 -94 23q-12 0 -24 -1q-60 -7 -113 -73z" />
+<glyph unicode="ò" horiz-adv-x="1155" d="M100 512q0 156 43 265.5t117 163.5t151.5 76.5t170 22.5t169 -22.5t150.5 -76.5t117 -163.5t43 -265.5t-43 -265.5t-117 -163.5t-150.5 -76.5t-169 -22.5t-170 22.5t-151.5 76.5t-117 163.5t-43 265.5zM248 512q0 -207 80 -300t254 -93t252.5 94t78.5 299t-78.5 299 t-252.5 94t-254 -93t-80 -300zM362 1391h152l117 -236h-99z" />
+<glyph unicode="ó" horiz-adv-x="1155" d="M100 512q0 156 43 265.5t117 163.5t151.5 76.5t170 22.5t169 -22.5t150.5 -76.5t117 -163.5t43 -265.5t-43 -265.5t-117 -163.5t-150.5 -76.5t-169 -22.5t-170 22.5t-151.5 76.5t-117 163.5t-43 265.5zM248 512q0 -207 80 -300t254 -93t252.5 94t78.5 299t-78.5 299 t-252.5 94t-254 -93t-80 -300zM530 1155l117 236h152l-170 -236h-99z" />
+<glyph unicode="ô" horiz-adv-x="1155" d="M100 512q0 156 43 265.5t117 163.5t151.5 76.5t170 22.5t169 -22.5t150.5 -76.5t117 -163.5t43 -265.5t-43 -265.5t-117 -163.5t-150.5 -76.5t-169 -22.5t-170 22.5t-151.5 76.5t-117 163.5t-43 265.5zM248 512q0 -207 80 -300t254 -93t252.5 94t78.5 299t-78.5 299 t-252.5 94t-254 -93t-80 -300zM373 1155l166 246h84l165 -246h-114l-92 143l-95 -143h-114z" />
+<glyph unicode="õ" horiz-adv-x="1155" d="M100 512q0 156 43 265.5t117 163.5t150.5 76.5t169 22.5t170 -22.5t151.5 -76.5t117 -163.5t43 -265.5t-43 -265.5t-117 -163.5t-151.5 -76.5t-170 -22.5t-169 22.5t-150.5 76.5t-117 163.5t-43 265.5zM248 512q0 -207 79 -300t253 -93t253.5 93t79.5 300t-79.5 300 t-253.5 93t-253 -93t-79 -300zM301 1159v115q53 66 113 73q12 1 23 1q49 0 95 -24l112 -60q46 -25 95 -25q11 0 23 1q61 7 115 73v-115q-53 -66 -115 -73q-12 -1 -23 -1q-50 0 -95 24l-112 60q-47 24 -97 24q-10 0 -21 -1q-60 -6 -113 -72z" />
+<glyph unicode="ö" horiz-adv-x="1155" d="M100 512q0 156 43 265.5t117 163.5t151.5 76.5t170 22.5t169 -22.5t150.5 -76.5t117 -163.5t43 -265.5t-43 -265.5t-117 -163.5t-150.5 -76.5t-169 -22.5t-170 22.5t-151.5 76.5t-117 163.5t-43 265.5zM248 512q0 -207 80 -300t254 -93t252.5 94t78.5 299t-78.5 299 t-252.5 94t-254 -93t-80 -300zM348 1155v162h162v-162h-162zM651 1155v162h162v-162h-162z" />
+<glyph unicode="÷" horiz-adv-x="1286" d="M227 465h832v123h-832v-123zM561 152v163h164v-163h-164zM561 735v164h164v-164h-164z" />
+<glyph unicode="ø" horiz-adv-x="1175" d="M76 0l127 152q-96 133 -97 360q0 156 43.5 265.5t117 163.5t151.5 76.5t170 22.5q180 0 297 -82l53 64h160l-125 -150q94 -131 94 -360q0 -156 -43 -265.5t-116.5 -163.5t-150.5 -76.5t-169 -22.5q-182 0 -297 79l-55 -63h-160zM254 512q0 -158 45 -246l496 586 q-78 53 -207 53q-174 0 -254 -93t-80 -300zM381 170q76 -51 207 -51q174 0 253 93t79 300q0 156 -46 246z" />
+<glyph unicode="ù" horiz-adv-x="1202" d="M168 430v592h147v-600q0 -156 68 -228.5t213 -72.5q143 0 212 72.5t69 228.5v600h147v-592q0 -213 -111.5 -329.5t-316.5 -116.5t-316.5 116.5t-111.5 329.5zM383 1391h152l116 -236h-98z" />
+<glyph unicode="ú" horiz-adv-x="1202" d="M168 430v592h147v-600q0 -156 68 -228.5t213 -72.5q143 0 212 72.5t69 228.5v600h147v-592q0 -213 -111.5 -329.5t-316.5 -116.5t-316.5 116.5t-111.5 329.5zM541 1155l116 236h150l-170 -236h-96z" />
+<glyph unicode="û" horiz-adv-x="1202" d="M168 430v592h147v-600q0 -156 68 -228.5t213 -72.5q143 0 212 72.5t69 228.5v600h147v-592q0 -213 -111.5 -329.5t-316.5 -116.5t-316.5 116.5t-111.5 329.5zM389 1155l164 246h84l166 -246h-115l-92 143l-92 -143h-115z" />
+<glyph unicode="ü" horiz-adv-x="1202" d="M168 430v592h147v-600q0 -156 68 -228.5t213 -72.5q143 0 212 72.5t69 228.5v600h147v-592q0 -213 -111.5 -329.5t-316.5 -116.5t-316.5 116.5t-111.5 329.5zM362 1155v162h164v-162h-164zM666 1155v162h163v-162h-163z" />
+<glyph unicode="ý" horiz-adv-x="1028" d="M63 1022h158l293 -809l293 809h158l-418 -1122q-109 -295 -320 -295q-51 0 -98 18v135q41 -18 88 -18q63 0 118.5 68.5t80.5 138.5l24 69zM449 1155l116 236h152l-170 -236h-98z" />
+<glyph unicode="þ" horiz-adv-x="1136" d="M170 -377v1776h147v-414q123 41 252 41q70 0 131.5 -12.5t125 -47t109.5 -90t74.5 -149.5t28.5 -217q0 -152 -42 -258.5t-114.5 -159.5t-148.5 -74.5t-164 -21.5q-129 0 -252 41v-414h-147zM317 184q117 -51 252 -51q170 0 246 89t76 288t-76 289t-246 90 q-135 0 -252 -51v-654z" />
+<glyph unicode="ÿ" horiz-adv-x="1028" d="M63 1022h158l293 -809l293 809h158l-418 -1122q-109 -295 -320 -295q-51 0 -98 18v135q41 -18 88 -18q63 0 118.5 68.5t80.5 138.5l24 69zM279 1155v162h161v-162h-161zM582 1155v162h161v-162h-161z" />
+<glyph unicode="Œ" horiz-adv-x="2029" d="M123 684q0 700 633 700q160 0 274 -45v27h864v-139h-716v-471h628v-144h-628v-473h716v-139h-864v27q-117 -43 -274 -43q-633 0 -633 700zM270 684q0 -291 117 -426t369 -135q170 0 274 59v1004q-109 59 -274 59q-252 0 -369 -135t-117 -426z" />
+<glyph unicode="œ" horiz-adv-x="1921" d="M102 512q0 156 43 265.5t117 163.5t150.5 76.5t169.5 22.5q279 0 397 -186q113 186 377 186q94 0 170 -29.5t122 -69.5t78.5 -104.5t47 -109.5t22.5 -108.5t8 -78.5v-44l-2 -72h-745q14 -154 94 -223.5t238 -69.5q219 0 350 76v-152q-148 -71 -359 -71h-6 q-276 0 -393 190q-119 -190 -399 -190q-92 0 -169 22.5t-151 76.5t-117 163.5t-43 265.5zM250 512q0 -207 79 -300t253 -93t253.5 93t79.5 300t-79.5 300t-253.5 93t-253 -93t-79 -300zM1055 555h600q0 33 -8.5 77t-35 111.5t-93 112.5t-162.5 45q-152 0 -221.5 -79 t-79.5 -267z" />
+<glyph unicode="Ÿ" horiz-adv-x="1224" d="M25 1366h178l405 -596l404 596h178l-508 -739v-627h-147v627zM375 1483v164h164v-164h-164zM678 1483v164h164v-164h-164z" />
+<glyph unicode="ˆ" horiz-adv-x="823" d="M207 1155l166 246h84l164 -246h-115l-92 143l-92 -143h-115z" />
+<glyph unicode="˜" horiz-adv-x="1038" d="M215 1157v117q53 66 113 72q11 1 21 1q49 0 97 -25l112 -60q47 -24 97 -24q10 0 21 1q61 6 115 72v-113q-53 -66 -115 -73q-12 -1 -23 -1q-50 0 -95 24l-112 60q-46 23 -94 23q-12 0 -24 -1q-60 -7 -113 -73z" />
+<glyph unicode=" " horiz-adv-x="879" />
+<glyph unicode=" " horiz-adv-x="1759" />
+<glyph unicode=" " horiz-adv-x="879" />
+<glyph unicode=" " horiz-adv-x="1759" />
+<glyph unicode=" " horiz-adv-x="586" />
+<glyph unicode=" " horiz-adv-x="439" />
+<glyph unicode=" " horiz-adv-x="293" />
+<glyph unicode=" " horiz-adv-x="293" />
+<glyph unicode=" " horiz-adv-x="219" />
+<glyph unicode=" " horiz-adv-x="351" />
+<glyph unicode=" " horiz-adv-x="97" />
+<glyph unicode="‐" horiz-adv-x="950" d="M162 539v143h624v-143h-624z" />
+<glyph unicode="‑" horiz-adv-x="950" d="M162 539v143h624v-143h-624z" />
+<glyph unicode="‒" horiz-adv-x="950" d="M162 539v143h624v-143h-624z" />
+<glyph unicode="–" horiz-adv-x="1146" d="M141 528v127h860v-127h-860z" />
+<glyph unicode="—" horiz-adv-x="1824" d="M162 528v136h1501v-136h-1501z" />
+<glyph unicode="‘" horiz-adv-x="522" d="M96 1384h168l152 -401h-148z" />
+<glyph unicode="’" horiz-adv-x="522" d="M96 983l152 401h168l-172 -401h-148z" />
+<glyph unicode="‚" horiz-adv-x="614" d="M147 -229l152 401h166l-170 -401h-148z" />
+<glyph unicode="“" horiz-adv-x="800" d="M96 1384h168l152 -401h-148zM375 1384h168l151 -401h-147z" />
+<glyph unicode="”" horiz-adv-x="792" d="M96 983l152 401h168l-172 -401h-148zM375 983l151 401h168l-172 -401h-147z" />
+<glyph unicode="„" horiz-adv-x="892" d="M147 -229l152 401h166l-170 -401h-148zM426 -229l152 401h167l-172 -401h-147z" />
+<glyph unicode="•" horiz-adv-x="849" d="M184 684q0 98 70 167t168 69q96 0 165.5 -69t69.5 -167t-69.5 -168t-165.5 -70q-98 0 -168 70t-70 168z" />
+<glyph unicode="…" horiz-adv-x="1576" d="M219 0v164h164v-164h-164zM711 0v164h161v-164h-161zM1200 0v164h162v-164h-162z" />
+<glyph unicode=" " horiz-adv-x="351" />
+<glyph unicode="‹" horiz-adv-x="710" d="M63 522l347 389h198l-364 -389l364 -391h-198z" />
+<glyph unicode="›" horiz-adv-x="776" d="M137 131l363 391l-363 389h197l348 -389l-348 -391h-197z" />
+<glyph unicode=" " horiz-adv-x="439" />
+<glyph unicode="€" horiz-adv-x="1390" d="M102 467v123h136q-4 61 -4 94q-1 3 -1 6q0 33 5 88h-136v123h150q90 483 614 483q215 0 416 -79v-142q-194 82 -399 82q-207 0 -325 -84t-157 -260h658v-123h-674q-4 -61 -4 -94q0 -37 4 -94h674v-123h-658q39 -176 157 -260t325 -84q209 0 399 80v-142 q-200 -77 -411 -77h-5q-524 0 -614 483h-150z" />
+<glyph unicode="™" horiz-adv-x="1550" d="M100 1294v72h557v-72h-241v-610h-74v610h-242zM762 684v682h80l235 -346l236 346h80v-682h-74v561l-242 -348l-241 348v-561h-74z" />
+<glyph unicode="" horiz-adv-x="1020" d="M0 0v1020h1020v-1020h-1020z" />
+<glyph unicode="fi" horiz-adv-x="1202" d="M84 887v137h176v53q0 106 28.5 182t63.5 112t89.5 56.5t82 22.5t66.5 2q53 0 100 -10v-133q-41 8 -74 8q-39 0 -59 -2t-54 -14.5t-52.5 -35t-33 -70.5t-14.5 -116v-55h287v-137h-287v-887h-143v887h-176zM885 1204v162h164v-162h-164zM893 0h147v1022h-147v-1022z" />
+<glyph unicode="fl" horiz-adv-x="1220" d="M84 887v137h176v53q0 106 28.5 182t63.5 112t89.5 56.5t82 22.5t66.5 2q53 0 100 -10v-133q-41 8 -74 8q-39 0 -59 -2t-54 -14.5t-52.5 -35t-33 -70.5t-14.5 -116v-55h287v-137h-287v-887h-143v887h-176zM893 0h147v1411h-147v-1411z" />
+<glyph unicode="ffi" horiz-adv-x="1890" d="M82 887v135h176v55q0 106 27.5 182t62.5 112t89.5 56.5t83 22.5t67.5 2q55 0 98 -12v-131q-41 8 -74 8q-49 0 -74.5 -4t-61.5 -24.5t-53 -73t-17 -136.5v-57h280v-135h-280v-887h-148v887h-176zM757 887v135h176v55q0 106 27.5 182t62.5 112t89.5 56.5t83 22.5t67.5 2 q55 0 98 -12v-131q-41 8 -74 8q-49 0 -74.5 -4t-61.5 -24.5t-53 -73t-17 -136.5v-57h280v-135h-280v-887h-148v887h-176zM1543 1204v162h163v-162h-163zM1551 0h147v1022h-147v-1022z" />
+<glyph unicode="ffl" horiz-adv-x="1902" d="M82 887v135h176v55q0 106 27.5 182t62.5 112t89.5 56.5t83 22.5t67.5 2q55 0 98 -12v-131q-41 8 -74 8q-49 0 -74.5 -4t-61.5 -24.5t-53 -73t-17 -136.5v-57h280v-135h-280v-887h-148v887h-176zM757 887v135h176v55q0 106 27.5 182t62.5 112t89.5 56.5t83 22.5t67.5 2 q55 0 98 -12v-131q-41 8 -74 8q-49 0 -74.5 -4t-61.5 -24.5t-53 -73t-17 -136.5v-57h280v-135h-280v-887h-148v887h-176zM1557 0v1411h147v-1411h-147z" />
+</font>
+</defs></svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="96"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="arrows.svg"
+ inkscape:export-filename="/media/data/projects/motioneye/static/img/settings.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.0625"
+ inkscape:cx="43.687152"
+ inkscape:cy="14.670355"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1030"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-nodes="true"
+ inkscape:bbox-paths="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-page="true"
+ inkscape:snap-intersection-paths="true"
+ showguides="true"
+ inkscape:guide-bbox="true" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1004.3622)">
+ <path
+ style="fill:none;stroke:#3498db;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 30,1012.3622 -17,17 17,15"
+ id="path3755"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ <path
+ sodipodi:nodetypes="ccc"
+ inkscape:connector-curvature="0"
+ id="path3757"
+ d="m 66,1012.3622 17,17 -17,15"
+ style="fill:none;stroke:#3498db;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="20"
+ height="16"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="combo-box-arrow.svg"
+ inkscape:export-filename="/media/data/projects/motioneye/static/img/combo-box-arrow.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="7.1473113"
+ inkscape:cy="7.817561"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1027"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-nodes="true"
+ inkscape:bbox-paths="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-page="true"
+ inkscape:snap-intersection-paths="true" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1036.3622)">
+ <path
+ style="fill:#3498db;fill-opacity:1;stroke:#3498db;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ d="m 1,1040.8622 14,0 -7,8 z"
+ id="path3809"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="svg2"
+ xml:space="preserve"
+ viewBox="0 0 64 64"
+ version="1.1"
+ inkscape:version="0.48.5 r10040"
+ width="100%"
+ height="100%"
+ sodipodi:docname="logout.svg"><defs
+ id="defs8" /><sodipodi:namedview
+ pagecolor="#666666"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1077"
+ inkscape:window-height="782"
+ id="namedview6"
+ showgrid="false"
+ inkscape:zoom="5.1943359"
+ inkscape:cx="24.357877"
+ inkscape:cy="26.948648"
+ inkscape:window-x="258"
+ inkscape:window-y="33"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg2" /><metadata
+ id="metadata8"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><path
+ sodipodi:nodetypes="ssccccccsccccc"
+ style="fill:#3498db;fill-opacity:1"
+ id="path3"
+ d="m 21.267338,42.732446 c 3.741665,3.741354 9.807034,3.741354 13.548699,0 2.776803,-2.77657 3.490747,-6.8354 2.144089,-10.273324 l 5.763494,-5.763661 c 1.49694,-1.496847 1.496489,-3.922567 0,-5.419481 -1.496034,-1.496525 -3.922182,-1.496525 -5.419118,0 l -5.763946,5.764034 c -3.437493,-1.346908 -7.495965,-0.633431 -10.27367,2.144832 -3.740312,3.740676 -3.740765,9.806754 4.52e-4,13.5476 z m 6.435428,-6.434662 c 0.898073,0.897093 0.898073,2.353493 0,3.2516 -0.897621,0.897939 -2.35394,0.897939 -3.251561,0 -0.89717,-0.898107 -0.89717,-2.354507 0,-3.2516 0.897621,-0.898107 2.353489,-0.898107 3.251561,0 z"
+ inkscape:connector-curvature="0" /><path
+ sodipodi:type="arc"
+ style="fill:none;stroke:#3498db;stroke-width:1.80392128;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path4097"
+ sodipodi:cx="16"
+ sodipodi:cy="16.5"
+ sodipodi:rx="14"
+ sodipodi:ry="12.5"
+ d="m 30,16.5 a 14,12.5 0 1 1 -28,0 14,12.5 0 1 1 28,0 z"
+ transform="matrix(1.5714286,0,0,1.7600001,6.8571426,2.9599978)" /></svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ width="64"
+ height="64"
+ xml:space="preserve"
+ sodipodi:docname="motioneye-icon.svg"
+ inkscape:export-filename="/home/ccrisan/projects/motioneye/static/img/motioneye-logo.png"
+ inkscape:export-xdpi="960"
+ inkscape:export-ydpi="960"><metadata
+ id="metadata8"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs6" /><sodipodi:namedview
+ pagecolor="#969696"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1030"
+ id="namedview4"
+ showgrid="false"
+ inkscape:zoom="3.2304687"
+ inkscape:cx="-17.830274"
+ inkscape:cy="37.141088"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g10"
+ showguides="true"
+ inkscape:guide-bbox="true" /><g
+ id="g10"
+ inkscape:groupmode="layer"
+ inkscape:label="ink_ext_XXXXXX"
+ transform="matrix(1.25,0,0,-1.25,0,64)"><path
+ style="fill:#515151;fill-opacity:1;stroke:none"
+ d="m 32,6 c -5.264933,0 -10.15584,1.684955 -14.25,4.375 4.426748,0.474546 8.794165,1.616509 13.75,4.0625 5.014819,-2.5549 10.152222,-3.621792 14.78125,-4.15625 C 42.179879,7.5768125 37.280255,6 32,6 z m 17,5.59375 c -5.21085,0.03453 -12.330126,1.666208 -17.5,4.03125 -5.435679,-1.964064 -11.012887,-4.147356 -17.3125,-4 -0.419974,0.0099 -0.823167,0.03114 -1.25,0.0625 L 8,12.09375 l 4.6875,1.5625 c 6.429941,2.095118 12.084993,4.395085 17.0625,13.03125 1.154473,0 3.425649,5e-4 4.5,5e-4 4.957862,-8.587269 10.904708,-10.983331 17.03125,-13.03175 l 4.625,-1.5 -4.8125,-0.46875 C 50.437916,11.625136 49.744407,11.588818 49,11.59375 z M 12.65625,14.6875 C 8.5126665,19.293305 6,25.316864 6,32 6,46.359404 17.640596,58 32,58 46.359404,58 58,46.359404 58,32 58,25.341351 55.491547,19.287285 51.375,14.6875 49.406504,15.410957 47.442694,16.253379 45.5,17.34375 49.260018,18.43176 52,21.91463 52,26 c 0,4.945036 -4.027245,9 -9,9 -4.217962,0 -7.765819,-2.914031 -8.75,-6.8125 -1.243768,0 -3.31109,0 -4.5,0 C 28.774204,32.087724 25.219217,35 21,35 c -4.972755,0 -9.03125,-4.054964 -9.03125,-9 0,-4.133444 2.851912,-7.644 6.6875,-8.6875 -1.945667,-1.058808 -3.952016,-1.881003 -6,-2.625 z M 21,23 c -1.656854,0 -3,1.343146 -3,3 0,1.656854 1.343146,3 3,3 1.656854,0 3,-1.343146 3,-3 0,-1.656854 -1.343146,-3 -3,-3 z m 22,0 c -1.656854,0 -3,1.343146 -3,3 0,1.656854 1.343146,3 3,3 1.656854,0 3,-1.343146 3,-3 0,-1.656854 -1.343146,-3 -3,-3 z m -11,8 c 0.618234,2.299881 1.278866,4.581981 4,6 l -4,6 -4,-6 c 2.383634,-1.6888 3.483037,-3.758112 4,-6 z"
+ transform="matrix(0.8,0,0,-0.8,0,51.2)"
+ id="path3938"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="scccssccccccccccscsssccssccssccssssssssssccccc" /></g></svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 64 64" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata id="metadata8"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><g id="g10" transform="matrix(1.25,0,0,-1.25,0,64)"><path id="path3938" d="m32,6c-5.2649,0-10.156,1.685-14.25,4.375,4.4267,0.47455,8.7942,1.6165,13.75,4.0625,5.0148-2.5549,10.152-3.6218,14.781-4.1562-4.101-2.7052-9.001-4.282-14.281-4.282zm17,5.5938c-5.2108,0.03453-12.33,1.6662-17.5,4.0312-5.4357-1.9641-11.013-4.1474-17.312-4-0.41997,0.0099-0.82317,0.03114-1.25,0.0625l-4.938,0.406,4.6875,1.5625c6.4299,2.0951,12.085,4.3951,17.062,13.031,1.1545,0,3.4256,0.0005,4.5,0.0005,4.9579-8.5873,10.905-10.983,17.031-13.032l4.625-1.5-4.8125-0.46875c-0.656-0.062-1.35-0.098-2.094-0.093zm-36.344,3.094c-4.1433,4.605-6.656,10.629-6.656,17.312,0,14.359,11.641,26,26,26s26-11.641,26-26c0-6.659-2.508-12.713-6.625-17.312-1.968,0.723-3.932,1.565-5.875,2.656,3.76,1.088,6.5,4.571,6.5,8.656,0,4.945-4.0272,9-9,9-4.218,0-7.7658-2.914-8.75-6.8125h-4.5c-0.976,3.9-4.531,6.812-8.75,6.812-4.9728,0-9.0312-4.055-9.0312-9,0-4.1334,2.8519-7.644,6.6875-8.6875-1.9457-1.0588-3.952-1.881-6-2.625zm8.344,8.312c-1.6569,0-3,1.3431-3,3s1.3431,3,3,3,3-1.3431,3-3-1.3431-3-3-3zm22,0c-1.6569,0-3,1.3431-3,3s1.3431,3,3,3,3-1.3431,3-3-1.3431-3-3-3zm-11,8c0.61823,2.2999,1.2789,4.582,4,6l-4,6-4-6c2.3836-1.6888,3.483-3.7581,4-6z" transform="matrix(0.8,0,0,-0.8,0,51.2)" fill="#FFF"/></g></svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ width="160"
+ height="160"
+ xml:space="preserve"
+ sodipodi:docname="no-camera.svg"><metadata
+ id="metadata8"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs6" /><sodipodi:namedview
+ pagecolor="#969696"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1027"
+ id="namedview4"
+ showgrid="false"
+ inkscape:zoom="1"
+ inkscape:cx="194.39325"
+ inkscape:cy="58.632885"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g10" /><g
+ id="g10"
+ inkscape:groupmode="layer"
+ inkscape:label="ink_ext_XXXXXX"
+ transform="matrix(1.25,0,0,-1.25,0,160)"><g
+ id="g3916"
+ transform="matrix(0.23474178,0,0,0.23474178,-6.596479,-6.504096)"><path
+ id="path28"
+ transform="matrix(0.8,0,0,-0.8,0,599)"
+ d="m 370.46875,230.75 c -25.42532,0 -48.59369,9.59353 -66.125,25.34375 l 57.96875,57.96875 c 3.96533,-0.60105 8.02296,-0.9375 12.15625,-0.9375 44.43125,0 80.4375,36.00625 80.4375,80.4375 0,4.1334 -0.33645,8.19036 -0.9375,12.15625 L 554.5,506.25 c 14.98807,-4.91141 25.8125,-18.99541 25.8125,-35.625 l 0,-162.5 c 0,-20.71 -16.79,-37.5 -37.5,-37.5 l -20.875,0 0,-7.1875 c 0,-8.9975 -7.31375,-16.28125 -16.3125,-16.28125 -8.99625,0 -16.28125,7.28375 -16.28125,16.28125 l 0,7.1875 -39.4375,0 C 431.86011,246.414 402.99373,230.75 370.46875,230.75 z m -13.59375,13 27.5,0 c 8.63,0 15.625,6.99625 15.625,15.625 0,4.41759 -1.83365,8.40792 -4.78125,11.25 l -49.1875,0 c -2.94775,-2.84208 -4.78125,-6.83241 -4.78125,-11.25 0,-8.62875 6.99625,-15.625 15.625,-15.625 z"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ inkscape:connector-curvature="0" /><path
+ id="path3901"
+ d="m 164.25,382.5 c -16.569,0 -30,-13.432 -30,-30 l 0,-130 c 0,-16.568 13.431,-30 30,-30 l 203.075,0 -35.8,35.775 c -9.41466,-5.39918 -20.31889,-8.5 -31.95,-8.5 -35.543,0 -64.35,28.831 -64.35,64.375 0,11.62338 3.11098,22.51462 8.5,31.925 L 177.3,382.5 l -13.05,0 z"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ inkscape:connector-curvature="0" /><path
+ id="path36"
+ transform="matrix(0.8,0,0,-0.8,0,599)"
+ d="m 374.0625,54.96875 c -176.1485,0 -319.5,143.28875 -319.5,319.4375 0,176.15678 143.35153,319.5 319.5,319.5 176.15077,0 319.5,-143.34335 319.5,-319.5 0,-176.14864 -143.3492,-319.4375 -319.5,-319.4375 z m 0,50 c 149.1292,0 269.5,120.31114 269.5,269.4375 0,64.41395 -22.47282,123.45239 -60,169.75 L 204.34375,164.9375 c 46.29727,-37.51599 105.31199,-59.96875 169.71875,-59.96875 z m -209.5,99.71875 379.21875,379.21875 c -46.2979,37.52569 -105.30826,60 -169.71875,60 -149.12653,0 -269.5,-120.36428 -269.5,-269.5 0,-64.4098 22.47246,-123.42872 60,-169.71875 z"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ inkscape:connector-curvature="0" /></g></g></svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ width="64"
+ height="64"
+ xml:space="preserve"
+ sodipodi:docname="no-preview.svg"
+ inkscape:export-filename="/home/ccrisan/projects/motioneye/static/img/motioneye-logo.png"
+ inkscape:export-xdpi="960"
+ inkscape:export-ydpi="960"><metadata
+ id="metadata8"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+ id="defs6" /><sodipodi:namedview
+ pagecolor="#969696"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="1"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1030"
+ id="namedview4"
+ showgrid="false"
+ inkscape:zoom="5.0625"
+ inkscape:cx="53.237951"
+ inkscape:cy="28.106834"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g10"
+ showguides="true"
+ inkscape:guide-bbox="true" /><g
+ id="g10"
+ inkscape:groupmode="layer"
+ inkscape:label="ink_ext_XXXXXX"
+ transform="matrix(1.25,0,0,-1.25,0,64)"><g
+ id="g3757"
+ style="opacity:0.7"><rect
+ transform="scale(1,-1)"
+ y="-42.400002"
+ x="4"
+ height="34.400002"
+ width="43.200001"
+ id="rect3751"
+ style="fill:none;stroke:#ffffff;stroke-width:1.60000001999999997;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /><path
+ sodipodi:nodetypes="cccccc"
+ transform="matrix(0.8,0,0,-0.8,0,51.2)"
+ inkscape:connector-curvature="0"
+ id="path3753"
+ d="m 11.5,46.5 11,-14 6,6 11,-14 13,22 z"
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
+ transform="matrix(1.0251562,0,0,-1.0251562,-4.199999,56.75)"
+ d="m 20.484682,24.142662 a 2.3411067,2.3411067 0 1 1 -4.682213,0 2.3411067,2.3411067 0 1 1 4.682213,0 z"
+ sodipodi:ry="2.3411067"
+ sodipodi:rx="2.3411067"
+ sodipodi:cy="24.142662"
+ sodipodi:cx="18.143576"
+ id="path3755"
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.56073773000000005;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+ sodipodi:type="arc" /></g></g></svg>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="96"
+ height="48"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="settings.svg"
+ inkscape:export-filename="/media/data/projects/motioneye/static/img/settings.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="17.085938"
+ inkscape:cx="93.239037"
+ inkscape:cy="25.695745"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1027"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-nodes="true"
+ inkscape:bbox-paths="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-page="true"
+ inkscape:snap-intersection-paths="true"
+ showguides="true"
+ inkscape:guide-bbox="true" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1004.3622)">
+ <path
+ style="fill:#3498db;fill-opacity:1;stroke:none"
+ d="m 23,30 c -2.045693,0 -3.788228,1.240478 -4.5625,3 L 5,33 c -0.554,0 -1,0.446 -1,1 l 0,2 c 0,0.554 0.446,1 1,1 l 13.4375,0 c 0.774272,1.759522 2.516807,3 4.5625,3 2.045693,0 3.788228,-1.240478 4.5625,-3 L 43,37 c 0.554,0 1,-0.446 1,-1 l 0,-2 c 0,-0.554 -0.446,-1 -1,-1 L 27.5625,33 C 26.788228,31.240478 25.045693,30 23,30 z m 0,3 c 1.10457,0 2,0.89543 2,2 0,1.10457 -0.89543,2 -2,2 -1.10457,0 -2,-0.89543 -2,-2 0,-1.10457 0.89543,-2 2,-2 z"
+ transform="translate(0,1004.3622)"
+ id="rect3755"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#3498db;fill-opacity:1;stroke:none"
+ d="m 36,20 c -2.045693,0 -3.788228,1.240478 -4.5625,3 L 5,23 c -0.554,0 -1,0.446 -1,1 l 0,2 c 0,0.554 0.446,1 1,1 l 26.4375,0 c 0.774272,1.759522 2.516807,3 4.5625,3 2.045693,0 3.788228,-1.240478 4.5625,-3 L 43,27 c 0.554,0 1,-0.446 1,-1 l 0,-2 c 0,-0.554 -0.446,-1 -1,-1 l -2.4375,0 C 39.788228,21.240478 38.045693,20 36,20 z m 0,3 c 1.10457,0 2,0.89543 2,2 0,1.10457 -0.89543,2 -2,2 -1.10457,0 -2,-0.89543 -2,-2 0,-1.10457 0.89543,-2 2,-2 z"
+ transform="translate(0,1004.3622)"
+ id="rect3757"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#3498db;fill-opacity:1;stroke:none"
+ d="m 15,10 c -2.045692,0 -3.788228,1.240478 -4.5625,3 L 5,13 c -0.554,0 -1,0.446 -1,1 l 0,2 c 0,0.554 0.446,1 1,1 l 5.4375,0 c 0.774272,1.759522 2.516808,3 4.5625,3 2.045692,0 3.788228,-1.240478 4.5625,-3 L 43,17 c 0.554,0 1,-0.446 1,-1 l 0,-2 c 0,-0.554 -0.446,-1 -1,-1 L 19.5625,13 C 18.788228,11.240478 17.045692,10 15,10 z m 0,3 c 1.10457,0 2,0.89543 2,2 0,1.10457 -0.89543,2 -2,2 -1.10457,0 -2,-0.89543 -2,-2 0,-1.10457 0.89543,-2 2,-2 z"
+ transform="translate(0,1004.3622)"
+ id="rect3759"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;stroke:#3498db;stroke-width:2.6408689;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path3764"
+ sodipodi:cx="67.901237"
+ sodipodi:cy="6.4814816"
+ sodipodi:rx="20.098766"
+ sodipodi:ry="19.518518"
+ d="m 88.000004,6.4814816 a 20.098766,19.518518 0 1 1 -40.197533,0 20.098766,19.518518 0 1 1 40.197533,0 z"
+ transform="matrix(0.74631447,0,0,0.76850096,21.324322,1023.3813)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path3766"
+ d="m 82,1030.3622 c 0.554,0 1,-0.446 1,-1 l 0,-2 c 0,-0.554 -0.446,-1 -1,-1 -13.004,0 -8.7461,0 -20,0 -0.554,0 -1,0.446 -1,1 l 0,2 c 0,0.554 0.446,1 1,1 12.4816,0 8.4097,0 20,0 z"
+ style="fill:#3498db;fill-opacity:1;stroke:none"
+ sodipodi:nodetypes="cssccsscc" />
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="11"
+ height="18"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="slider-arrow.svg"
+ inkscape:export-filename="/media/data/projects/motioneye/static/img/slider-arrow.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="20.539051"
+ inkscape:cy="-1.1141645"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1027"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-nodes="true"
+ inkscape:bbox-paths="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-page="true"
+ inkscape:snap-intersection-paths="true" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1034.3622)">
+ <path
+ style="fill:#3498db;fill-opacity:1;stroke:#3498db;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 1,1040.3622 0,10 9,0 0,-10 -4.5,-4.5 z"
+ id="path3809"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccc" />
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="80"
+ height="16"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="top-bar-buttons.svg"
+ inkscape:export-filename="/media/data/projects/motioneye/static/img/top-bar-buttons.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#b39a9a"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.42745098"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.25"
+ inkscape:cx="111.39282"
+ inkscape:cy="5.668431"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1030"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-nodes="true"
+ inkscape:bbox-paths="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-page="true"
+ inkscape:snap-intersection-paths="true"
+ showguides="true"
+ inkscape:guide-bbox="true" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1036.3622)">
+ <path
+ sodipodi:type="arc"
+ style="fill:none;stroke:#3498db;stroke-width:0.84620845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path3752"
+ sodipodi:cx="7.8036885"
+ sodipodi:cy="8.2353296"
+ sodipodi:rx="5.6186557"
+ sodipodi:ry="5.3845448"
+ d="m 13.422344,8.2353296 a 5.6186557,5.3845448 0 1 1 -11.2373112,0 5.6186557,5.3845448 0 1 1 11.2373112,0 z"
+ transform="matrix(1.1568604,0,0,1.2071587,-1.0277779,1034.4208)" />
+ <path
+ style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ d="m 5.5,5.5 5,5"
+ id="path3756"
+ inkscape:connector-curvature="0"
+ transform="translate(0,1036.3622)"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path3758"
+ d="m 10.5,1041.8622 -5,5"
+ style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;stroke:#3498db;stroke-width:0.84620845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path3792"
+ sodipodi:cx="7.8036885"
+ sodipodi:cy="8.2353296"
+ sodipodi:rx="5.6186557"
+ sodipodi:ry="5.3845448"
+ d="m 13.422344,8.2353296 a 5.6186557,5.3845448 0 1 1 -11.2373112,0 5.6186557,5.3845448 0 1 1 11.2373112,0 z"
+ transform="matrix(-1.1568604,0,0,1.2071587,33.027778,1034.4209)" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path3794"
+ d="m 21.5,1046.8622 3,-3"
+ style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ d="m 24.5,1043.8622 0,-2"
+ id="path3796"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path3798"
+ d="m 24.5,1043.8622 2,0"
+ style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ d="m 27.5,1042.8622 -1,1"
+ id="path3800"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path3802"
+ d="m 25.5,1040.8622 -1,1"
+ style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ transform="matrix(-1.1568604,0,0,1.2071587,49.027778,1034.4209)"
+ d="m 13.422344,8.2353296 a 5.6186557,5.3845448 0 1 1 -11.2373112,0 5.6186557,5.3845448 0 1 1 11.2373112,0 z"
+ sodipodi:ry="5.3845448"
+ sodipodi:rx="5.6186557"
+ sodipodi:cy="8.2353296"
+ sodipodi:cx="7.8036885"
+ id="path3759"
+ style="fill:none;stroke:#3498db;stroke-width:0.84620845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:nodetypes="ccccc"
+ inkscape:connector-curvature="0"
+ id="path3761"
+ d="m 36.5,1041.8622 7,0 0,5 -7,0 z"
+ style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
+ <path
+ style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#3498db;fill-opacity:1;stroke:none;stroke-width:8.06000042;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
+ d="m 56.025,1041.1703 c -0.717378,0 -1.358863,0.3074 -1.769063,0.7819 l -1.988437,0 c -0.436275,0 -0.7875,0.3512 -0.7875,0.7875 l 0,3.375 c 0,0.4363 0.351225,0.7875 0.7875,0.7875 l 7.425,0 c 0.436275,0 0.7875,-0.3512 0.7875,-0.7875 l 0,-3.375 c 0,-0.4363 -0.351225,-0.7875 -0.7875,-0.7875 l -0.2925,0 0,-0.1012 a 0.450045,0.450045 0 1 0 -0.9,0 l 0,0.1012 -0.705938,0 c -0.409047,-0.4745 -1.051682,-0.7819 -1.769062,-0.7819 z m -0.36,0.3319 0.72,0 c 0.12465,0 0.225,0.1004 0.225,0.225 l 0,0.09 c 0,0.1247 -0.10035,0.225 -0.225,0.225 l -0.72,0 c -0.12465,0 -0.225,-0.1003 -0.225,-0.225 l 0,-0.09 c 0,-0.1246 0.10035,-0.225 0.225,-0.225 z m 0.36,1.35 c 0.919555,0 1.665,0.7455 1.665,1.665 0,0.9196 -0.745445,1.665 -1.665,1.665 -0.919553,0 -1.665,-0.7454 -1.665,-1.665 0,-0.9195 0.745447,-1.665 1.665,-1.665 z"
+ id="path3803"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;stroke:#3498db;stroke-width:0.84620845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path3815"
+ sodipodi:cx="7.8036885"
+ sodipodi:cy="8.2353296"
+ sodipodi:rx="5.6186557"
+ sodipodi:ry="5.3845448"
+ d="m 13.422344,8.2353296 a 5.6186557,5.3845448 0 1 1 -11.2373112,0 5.6186557,5.3845448 0 1 1 11.2373112,0 z"
+ transform="matrix(-1.1568604,0,0,1.2071587,65.027778,1034.4209)" />
+ <path
+ transform="matrix(-1.1568604,0,0,1.2071587,81.027778,1034.4209)"
+ d="m 13.422344,8.2353296 a 5.6186557,5.3845448 0 1 1 -11.2373112,0 5.6186557,5.3845448 0 1 1 11.2373112,0 z"
+ sodipodi:ry="5.3845448"
+ sodipodi:rx="5.6186557"
+ sodipodi:cy="8.2353296"
+ sodipodi:cx="7.8036885"
+ id="path3817"
+ style="fill:none;stroke:#3498db;stroke-width:0.84620845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ sodipodi:type="arc" />
+ <path
+ style="fill:#3498db;fill-opacity:1;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
+ d="m 69.5,1040.8622 6,3.5 -6,3.5 z"
+ id="path3819"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+ </g>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="20"
+ height="20"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="validation-error.svg"
+ inkscape:export-filename="/media/data/projects/motioneye/static/img/validation-error.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.390625"
+ inkscape:cx="-0.48299409"
+ inkscape:cy="28.256052"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1027"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:window-maximized="1"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-nodes="true"
+ inkscape:bbox-paths="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:object-paths="true"
+ inkscape:object-nodes="true"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-center="true"
+ inkscape:snap-page="true"
+ inkscape:snap-intersection-paths="true"
+ showguides="true"
+ inkscape:guide-bbox="true" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1032.3622)">
+ <path
+ style="fill:#c11c00;fill-opacity:1;stroke:none"
+ d="M 7 3 C 3.1340068 3 0 6.1340067 0 10 C -5.9211895e-16 13.865993 3.1340068 17 7 17 C 10.865993 17 14 13.865993 14 10 C 14 6.1340067 10.865993 3 7 3 z M 6 5.25 L 8.125 5.25 L 8.125 8.625 L 7.8125 11.0625 L 6.3125 11.0625 L 6 8.625 L 6 5.25 z M 6 11.90625 L 8.125 11.90625 L 8.125 14 L 6 14 L 6 11.90625 z "
+ transform="translate(0,1032.3622)"
+ id="path3751" />
+ <g
+ style="font-size:13px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
+ id="text3753" />
+ </g>
+</svg>
--- /dev/null
+/*
+CSS Browser Selector js v0.5.3 (July 2, 2013)
+
+-- original --
+Rafael Lima (http://rafael.adm.br)
+http://rafael.adm.br/css_browser_selector
+License: http://creativecommons.org/licenses/by/2.5/
+Contributors: http://rafael.adm.br/css_browser_selector#contributors
+-- /original --
+
+Fork project: http://code.google.com/p/css-browser-selector/
+Song Hyo-Jin (shj at xenosi.de)
+*/
+function css_browser_selector(n){var b=n.toLowerCase(),f=function(c){return b.indexOf(c)>-1},h="gecko",k="webkit",p="safari",j="chrome",d="opera",e="mobile",l=0,a=window.devicePixelRatio?(window.devicePixelRatio+"").replace(".","_"):"1";var i=[(!(/opera|webtv/.test(b))&&/msie\s(\d+)/.test(b)&&(l=RegExp.$1*1))?("ie ie"+l+((l==6||l==7)?" ie67 ie678 ie6789":(l==8)?" ie678 ie6789":(l==9)?" ie6789 ie9m":(l>9)?" ie9m":"")):(/firefox\/(\d+)\.(\d+)/.test(b)&&(re=RegExp))?h+" ff ff"+re.$1+" ff"+re.$1+"_"+re.$2:f("gecko/")?h:f(d)?d+(/version\/(\d+)/.test(b)?" "+d+RegExp.$1:(/opera(\s|\/)(\d+)/.test(b)?" "+d+RegExp.$2:"")):f("konqueror")?"konqueror":f("blackberry")?e+" blackberry":(f(j)||f("crios"))?k+" "+j:f("iron")?k+" iron":!f("cpu os")&&f("applewebkit/")?k+" "+p:f("mozilla/")?h:"",f("android")?e+" android":"",f("tablet")?"tablet":"",f("j2me")?e+" j2me":f("ipad; u; cpu os")?e+" chrome android tablet":f("ipad;u;cpu os")?e+" chromedef android tablet":f("iphone")?e+" ios iphone":f("ipod")?e+" ios ipod":f("ipad")?e+" ios ipad tablet":f("mac")?"mac":f("darwin")?"mac":f("webtv")?"webtv":f("win")?"win"+(f("windows nt 6.0")?" vista":""):f("freebsd")?"freebsd":(f("x11")||f("linux"))?"linux":"",(a!="1")?" retina ratio"+a:"","js portrait"].join(" ");if(window.jQuery&&!window.jQuery.browser){window.jQuery.browser=l?{msie:1,version:l}:{}}return i}(function(j,b){var c=css_browser_selector(navigator.userAgent);var g=j.documentElement;g.className+=" "+c;var a=c.replace(/^\s*|\s*$/g,"").split(/ +/);b.CSSBS=1;for(var f=0;f<a.length;f++){b["CSSBS_"+a[f]]=1}var e=function(d){return j.documentElement[d]||j.body[d]};if(b.jQuery){(function(q){var h="portrait",k="landscape";var i="smartnarrow",u="smartwide",x="tabletnarrow",r="tabletwide",w=i+" "+u+" "+x+" "+r+" pc";var v=q(g);var s=0,o=0;function d(){try{var l=e("clientWidth"),p=e("clientHeight");if(l>p){v.removeClass(h).addClass(k)}else{v.removeClass(k).addClass(h)}if(l==o){return}o=l;clearTimeout(s)}catch(m){}s=setTimeout(n,100)}function n(){try{v.removeClass(w);v.addClass((o<=360)?i:(o<=640)?u:(o<=768)?x:(o<=1024)?r:"pc")}catch(l){}}q(b).on("resize orientationchange",d).trigger("resize")})(b.jQuery)}})(document,window);
--- /dev/null
+
+var refreshDisabled = false;
+
+
+ /* camera frame */
+
+function setupCameraFrame() {
+ var cameraFrameDiv = $('div.camera-frame')
+ var cameraPlaceholder = cameraFrameDiv.find('div.camera-placeholder');
+ var cameraProgress = cameraFrameDiv.find('div.camera-progress');
+ var cameraImg = cameraFrameDiv.find('img.camera');
+ var cameraId = cameraFrameDiv.attr('id').substring(6);
+ var progressImg = cameraFrameDiv.find('img.camera-progress');
+ var body = $('body');
+
+ cameraFrameDiv[0].refreshDivider = 0;
+ cameraFrameDiv[0].streamingFramerate = parseInt(cameraFrameDiv.attr('streaming_framerate')) || 1;
+ cameraFrameDiv[0].streamingServerResize = cameraFrameDiv.attr('streaming_server_resize') == 'True';
+ cameraFrameDiv[0].proto = cameraFrameDiv.attr('proto');
+ cameraFrameDiv[0].url = cameraFrameDiv.attr('url');
+ progressImg.attr('src', staticUrl + 'img/camera-progress.gif');
+
+ cameraProgress.addClass('visible');
+ cameraPlaceholder.css('opacity', '0');
+
+ /* fade in */
+ cameraFrameDiv.animate({'opacity': 1}, 100);
+
+ /* error and load handlers */
+ cameraImg.error(function () {
+ this.error = true;
+ this.loading = 0;
+
+ cameraImg.addClass('error').removeClass('loading');
+ cameraPlaceholder.css('opacity', 1);
+ cameraProgress.removeClass('visible');
+ cameraFrameDiv.removeClass('motion-detected');
+ });
+ cameraImg.load(function () {
+ if (refreshDisabled) {
+ return; /* refresh temporarily disabled for updating */
+ }
+
+ this.error = false;
+ this.loading = 0;
+
+ cameraImg.removeClass('error').removeClass('loading');
+ cameraPlaceholder.css('opacity', 0);
+ cameraProgress.removeClass('visible');
+
+ /* there's no point in looking for a cookie update more often than once every second */
+ var now = new Date().getTime();
+ if ((!this.lastCookieTime || now - this.lastCookieTime > 1000) && (cameraFrameDiv[0].proto != 'mjpeg')) {
+ if (getCookie('motion_detected_' + cameraId) == 'true') {
+ cameraFrameDiv.addClass('motion-detected');
+ }
+ else {
+ cameraFrameDiv.removeClass('motion-detected');
+ }
+
+ this.lastCookieTime = now;
+ }
+
+ if (this.naturalWidth / this.naturalHeight > body.width() / body.height()) {
+ cameraImg.css('width', '100%');
+ cameraImg.css('height', 'auto');
+ }
+ else {
+ cameraImg.css('width', 'auto');
+ cameraImg.css('height', '100%');
+ }
+ });
+
+ cameraImg.addClass('loading');
+}
+
+function refreshCameraFrame() {
+ var $cameraFrame = $('div.camera-frame');
+ var cameraFrame = $cameraFrame[0];
+ var img = $cameraFrame.find('img.camera')[0];
+ var cameraId = cameraFrame.id.substring(6);
+
+ if (cameraFrame.proto == 'mjpeg') {
+ /* no manual refresh for simple mjpeg cameras */
+ var url = cameraFrame.url.replace('127.0.0.1', window.location.host.split(':')[0]);
+ url += (url.indexOf('?') > 0 ? '&' : '?') + '_=' + new Date().getTime();
+ img.src = url;
+ return;
+ }
+
+ /* at a refresh interval of 50ms, the refresh rate is limited to 20 fps */
+ var count = 1000 / (refreshInterval * cameraFrame.streamingFramerate);
+ if (count <= 2) {
+ /* skipping frames (showing the same frame twice) at this rate won't be visible,
+ * while the effective framerate will be as close as possible to the motion's one */
+ count -= 1;
+ }
+
+ if (img.error) {
+ /* in case of error, decrease the refresh rate to 1 fps */
+ count = 1000 / refreshInterval;
+ }
+
+ if (cameraFrame.refreshDivider < count) {
+ cameraFrame.refreshDivider++;
+ }
+ else {
+ (function () {
+ if (refreshDisabled) {
+ /* camera refreshing disabled, retry later */
+
+ return;
+ }
+
+ if (img.loading) {
+ img.loading++; /* increases each time the camera would refresh but is still loading */
+
+ if (img.loading > 2 * 1000 / refreshInterval) { /* limits the retry at one every two seconds */
+ img.loading = 0;
+ }
+ else {
+ return; /* wait for the previous frame to finish loading */
+ }
+ }
+
+ var timestamp = new Date().getTime();
+ var uri = baseUri + 'picture/' + cameraId + '/current/?_=' + timestamp;
+ if (cameraFrame.serverSideResize) {
+ uri += '&width=' + img.width;
+ }
+
+ uri = addAuthParams('GET', uri);
+ img.src = uri;
+ img.loading = 1;
+
+ cameraFrame.refreshDivider = 0;
+ })();
+ }
+
+ setTimeout(refreshCameraFrame, refreshInterval);
+}
+
+
+ /* startup function */
+
+$(document).ready(function () {
+ setupCameraFrame();
+ refreshCameraFrame();
+});
+
--- /dev/null
+/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
+*/
+(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="<div class='a'></div><div class='a i'></div>",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav></:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="<div></div>",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t
+}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/<tbody/i,wt=/<|&#?\w+;/,Tt=/<(?:script|style|link)/i,Ct=/^(?:checkbox|radio)$/i,Nt=/checked\s*(?:[^=]|=\s*.checked.)/i,kt=/^$|\/(?:java|ecma)script/i,Et=/^true\/(.*)/,St=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,At={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle);
+u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(Pt[0].contentWindow||Pt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=un(e,t),Pt.detach()),Gt[e]=n),n}function un(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,n){x.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(x.css(e,"display"))?x.swap(e,Qt,function(){return sn(e,n,i)}):sn(e,n,i):t},set:function(e,t,r){var i=r&&Rt(e);return on(e,t,r?an(e,n,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x.support.opacity||(x.cssHooks.opacity={get:function(e,t){return It.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=x.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===x.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,n){return n?x.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,n){x.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?x(e).position()[n]+"px":r):t}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!x.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||x.css(e,"display"))},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+Zt[r]+t]=o[r]||o[r-2]||o[0];return i}},Ut.test(e)||(x.cssHooks[e+t].set=on)});var cn=/%20/g,pn=/\[\]$/,fn=/\r?\n/g,dn=/^(?:submit|button|image|reset|file)$/i,hn=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Ct.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),x.param=function(e,n){var r,i=[],o=function(e,t){t=x.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){o(this.name,this.value)});else for(r in e)gn(r,e[r],n,o);return i.join("&").replace(cn,"+")};function gn(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||pn.test(e)?r(e,i):gn(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}x.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var mn,yn,vn=x.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Cn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Nn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=x.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=o.href}catch(Ln){yn=a.createElement("a"),yn.href="",yn=yn.href}mn=En.exec(yn.toLowerCase())||[];function Hn(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(T)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function qn(e,n,r,i){var o={},a=e===jn;function s(l){var u;return o[l]=!0,x.each(e[l]||[],function(e,l){var c=l(n,r,i);return"string"!=typeof c||a||o[c]?a?!(u=c):t:(n.dataTypes.unshift(c),s(c),!1)}),u}return s(n.dataTypes[0])||!o["*"]&&s("*")}function _n(e,n){var r,i,o=x.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,l=e.indexOf(" ");return l>=0&&(i=e.slice(l,e.length),e=e.slice(0,l)),x.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&x.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?x("<div>").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window);
--- /dev/null
+/*! Copyright (c) 2013 Brandon Aaron (http://brandonaaron.net)
+ * Licensed under the MIT License (LICENSE.txt).
+ *
+ * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
+ * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
+ * Thanks to: Seamus Leahy for adding deltaX and deltaY
+ *
+ * Version: 3.1.3
+ *
+ * Requires: 1.2.2+
+ */
+
+(function (factory) {
+ if ( typeof define === 'function' && define.amd ) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS style for Browserify
+ module.exports = factory;
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+}(function ($) {
+
+ var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
+ var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
+ var lowestDelta, lowestDeltaXY;
+
+ if ( $.event.fixHooks ) {
+ for ( var i = toFix.length; i; ) {
+ $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
+ }
+ }
+
+ $.event.special.mousewheel = {
+ setup: function() {
+ if ( this.addEventListener ) {
+ for ( var i = toBind.length; i; ) {
+ this.addEventListener( toBind[--i], handler, false );
+ }
+ } else {
+ this.onmousewheel = handler;
+ }
+ },
+
+ teardown: function() {
+ if ( this.removeEventListener ) {
+ for ( var i = toBind.length; i; ) {
+ this.removeEventListener( toBind[--i], handler, false );
+ }
+ } else {
+ this.onmousewheel = null;
+ }
+ }
+ };
+
+ $.fn.extend({
+ mousewheel: function(fn) {
+ return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
+ },
+
+ unmousewheel: function(fn) {
+ return this.unbind("mousewheel", fn);
+ }
+ });
+
+
+ function handler(event) {
+ var orgEvent = event || window.event,
+ args = [].slice.call(arguments, 1),
+ delta = 0,
+ deltaX = 0,
+ deltaY = 0,
+ absDelta = 0,
+ absDeltaXY = 0,
+ fn;
+ event = $.event.fix(orgEvent);
+ event.type = "mousewheel";
+
+ // Old school scrollwheel delta
+ if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
+ if ( orgEvent.detail ) { delta = orgEvent.detail * -1; }
+
+ // New school wheel delta (wheel event)
+ if ( orgEvent.deltaY ) {
+ deltaY = orgEvent.deltaY * -1;
+ delta = deltaY;
+ }
+ if ( orgEvent.deltaX ) {
+ deltaX = orgEvent.deltaX;
+ delta = deltaX * -1;
+ }
+
+ // Webkit
+ if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
+ if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX * -1; }
+
+ // Look for lowest delta to normalize the delta values
+ absDelta = Math.abs(delta);
+ if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
+ absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
+ if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
+
+ // Get a whole value for the deltas
+ fn = delta > 0 ? 'floor' : 'ceil';
+ delta = Math[fn](delta / lowestDelta);
+ deltaX = Math[fn](deltaX / lowestDeltaXY);
+ deltaY = Math[fn](deltaY / lowestDeltaXY);
+
+ // Add event and delta to the front of the arguments
+ args.unshift(event, delta, deltaX, deltaY);
+
+ return ($.event.dispatch || $.event.handle).apply(this, args);
+ }
+
+}));
--- /dev/null
+!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){function b(a){if(a.minTime&&(a.minTime=s(a.minTime)),a.maxTime&&(a.maxTime=s(a.maxTime)),a.durationTime&&"function"!=typeof a.durationTime&&(a.durationTime=s(a.durationTime)),a.disableTimeRanges.length>0){for(var b in a.disableTimeRanges)a.disableTimeRanges[b]=[s(a.disableTimeRanges[b][0]),s(a.disableTimeRanges[b][1])];a.disableTimeRanges=a.disableTimeRanges.sort(function(a,b){return a[0]-b[0]})}return a}function c(b){var c=b.data("timepicker-settings"),d=b.data("timepicker-list");d&&d.length&&(d.remove(),b.data("timepicker-list",!1)),d=a("<ul />",{"class":"ui-timepicker-list"});var e=a("<div />",{"class":"ui-timepicker-wrapper",tabindex:-1});e.css({display:"none",position:"absolute"}).append(d),c.className&&e.addClass(c.className),null===c.minTime&&null===c.durationTime||!c.showDuration||e.addClass("ui-timepicker-with-duration");var f=c.minTime;"function"==typeof c.durationTime?f=s(c.durationTime()):null!==c.durationTime&&(f=c.durationTime);var h=null!==c.minTime?c.minTime:0,j=null!==c.maxTime?c.maxTime:h+v-1;h>=j&&(j+=v);for(var k=c.disableTimeRanges,l=0,m=k.length,n=h;j>=n;n+=60*c.step){var o=n%v,t=a("<li />");if(t.data("time",o),t.text(r(o,c.timeFormat)),(null!==c.minTime||null!==c.durationTime)&&c.showDuration){var u=a("<span />");u.addClass("ui-timepicker-duration"),u.text(" ("+q(n-f)+")"),t.append(u)}m>l&&(o>=k[l][1]&&(l+=1),k[l]&&o>=k[l][0]&&o<k[l][1]&&t.addClass("ui-timepicker-disabled")),d.append(t)}e.data("timepicker-input",b),b.data("timepicker-list",e);var w=c.appendTo;"string"==typeof w?w=a(w):"function"==typeof w&&(w=w(b)),w.append(e),i(b,d),d.on("click","li",function(){b.off("focus.timepicker"),b.on("focus.timepicker-ie-hack",function(){b.off("focus.timepicker-ie-hack"),b.on("focus.timepicker",y.show)}),g(b)||b[0].focus(),d.find("li").removeClass("ui-timepicker-selected"),a(this).addClass("ui-timepicker-selected"),p(b)&&(b.trigger("hideTimepicker"),e.hide())})}function d(){return new Date(1970,1,1,0,0,0)}function e(b){"ontouchstart"in document?a("body").on("touchstart.ui-timepicker",f):(a("body").on("mousedown.ui-timepicker",f),b.closeOnWindowScroll&&a(window).on("scroll.ui-timepicker",f))}function f(b){var c=a(b.target),d=c.closest(".ui-timepicker-input");0===d.length&&0===c.closest(".ui-timepicker-wrapper").length&&(y.hide(),a("body").unbind(".ui-timepicker"),a(window).unbind(".ui-timepicker"))}function g(a){var b=a.data("timepicker-settings");return(window.navigator.msMaxTouchPoints||"ontouchstart"in document)&&b.disableTouchKeyboard}function h(b,c,d){if(!d&&0!==d)return!1;var e=b.data("timepicker-settings"),f=!1,g=30*e.step;return c.find("li").each(function(b,c){var e=a(c),h=e.data("time")-d;return Math.abs(h)<g||h==g?(f=e,!1):void 0}),f}function i(a,b){b.find("li").removeClass("ui-timepicker-selected");var c=s(k(a));if(c){var d=h(a,b,c);if(d){var e=d.offset().top-b.offset().top;(e+d.outerHeight()>b.outerHeight()||0>e)&&b.scrollTop(b.scrollTop()+d.position().top-d.outerHeight()),d.addClass("ui-timepicker-selected")}}}function j(){if(""!==this.value){var b=a(this),c=b.data("timepicker-list");if(!c||!c.is(":visible")){var d=s(this.value);if(null===d)return b.trigger("timeFormatError"),void 0;var e=b.data("timepicker-settings"),f=!1;if(null!==e.minTime&&d<e.minTime?f=!0:null!==e.maxTime&&d>e.maxTime&&(f=!0),a.each(e.disableTimeRanges,function(){return d>=this[0]&&d<this[1]?(f=!0,!1):void 0}),e.forceRoundTime){var g=d%(60*e.step);g>=30*e.step?d+=60*e.step-g:d-=g}var h=r(d,e.timeFormat);f?l(b,h,"error")&&b.trigger("timeRangeError"):l(b,h)}}}function k(a){return a.is("input")?a.val():a.data("ui-timepicker-value")}function l(a,b,c){return a.data("ui-timepicker-value")!=b?(a.data("ui-timepicker-value",b),a.is("input")&&a.val(b),"select"==c?a.trigger("selectTime").trigger("changeTime").trigger("change"):"error"!=c&&a.trigger("changeTime"),!0):(a.trigger("selectTime"),!1)}function m(b){var c=a(this),d=c.data("timepicker-list");if(!d||!d.is(":visible")){if(40!=b.keyCode)return n(b,c);g(c)||c.focus()}switch(b.keyCode){case 13:return p(c)&&y.hide.apply(this),b.preventDefault(),!1;case 38:var e=d.find(".ui-timepicker-selected");return e.length?e.is(":first-child")||(e.removeClass("ui-timepicker-selected"),e.prev().addClass("ui-timepicker-selected"),e.prev().position().top<e.outerHeight()&&d.scrollTop(d.scrollTop()-e.outerHeight())):(d.find("li").each(function(b,c){return a(c).position().top>0?(e=a(c),!1):void 0}),e.addClass("ui-timepicker-selected")),!1;case 40:return e=d.find(".ui-timepicker-selected"),0===e.length?(d.find("li").each(function(b,c){return a(c).position().top>0?(e=a(c),!1):void 0}),e.addClass("ui-timepicker-selected")):e.is(":last-child")||(e.removeClass("ui-timepicker-selected"),e.next().addClass("ui-timepicker-selected"),e.next().position().top+2*e.outerHeight()>d.outerHeight()&&d.scrollTop(d.scrollTop()+e.outerHeight())),!1;case 27:d.find("li").removeClass("ui-timepicker-selected"),y.hide();break;case 9:y.hide();break;default:return n(b,c)}}function n(a,b){return!b.data("timepicker-settings").disableTextInput||a.ctrlKey||a.altKey||a.metaKey||2!=a.keyCode&&a.keyCode<46}function o(b){var c=a(this),d=c.data("timepicker-list");if(!d||!d.is(":visible"))return!0;switch(b.keyCode){case 96:case 97:case 98:case 99:case 100:case 101:case 102:case 103:case 104:case 105:case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:case 65:case 77:case 80:case 186:case 8:case 46:i(c,d);break;default:return}}function p(a){var b=a.data("timepicker-settings"),c=a.data("timepicker-list"),d=null,e=c.find(".ui-timepicker-selected");if(e.hasClass("ui-timepicker-disabled"))return!1;if(e.length?d=e.data("time"):k(a)&&(d=s(k(a)),i(a,c)),null!==d){var f=r(d,b.timeFormat);l(a,f,"select")}return!0}function q(a){var b,c=Math.round(a/60);if(Math.abs(c)<60)b=[c,x.mins];else if(60==c)b=["1",x.hr];else{var d=(c/60).toFixed(1);"."!=x.decimal&&(d=d.replace(".",x.decimal)),b=[d,x.hrs]}return b.join(" ")}function r(a,b){if(null!==a){for(var c,d,e=new Date(u.valueOf()+1e3*a),f="",g=0;g<b.length;g++)switch(d=b.charAt(g)){case"a":f+=e.getHours()>11?"pm":"am";break;case"A":f+=e.getHours()>11?"PM":"AM";break;case"g":c=e.getHours()%12,f+=0===c?"12":c;break;case"G":f+=e.getHours();break;case"h":c=e.getHours()%12,0!==c&&10>c&&(c="0"+c),f+=0===c?"12":c;break;case"H":c=e.getHours(),f+=c>9?c:"0"+c;break;case"i":var h=e.getMinutes();f+=h>9?h:"0"+h;break;case"s":a=e.getSeconds(),f+=a>9?a:"0"+a;break;default:f+=d}return f}}function s(a){if(""===a)return null;if(!a||a+0==a)return a;"object"==typeof a&&(a=a.getHours()+":"+t(a.getMinutes())+":"+t(a.getSeconds())),a=a.toLowerCase(),new Date(0);var b;if(-1===a.indexOf(":")?(b=a.match(/^([0-9]):?([0-5][0-9])?:?([0-5][0-9])?\s*([pa]?)m?$/),b||(b=a.match(/^([0-2][0-9]):?([0-5][0-9])?:?([0-5][0-9])?\s*([pa]?)m?$/))):b=a.match(/^(\d{1,2})(?::([0-5][0-9]))?(?::([0-5][0-9]))?\s*([pa]?)m?$/),!b)return null;var c,d=parseInt(1*b[1],10);c=b[4]?12==d?"p"==b[4]?12:0:d+("p"==b[4]?12:0):d;var e=1*b[2]||0,f=1*b[3]||0;return 3600*c+60*e+f}function t(a){return("0"+a).slice(-2)}var u=d(),v=86400,w={className:null,minTime:null,maxTime:null,durationTime:null,step:30,showDuration:!1,timeFormat:"g:ia",scrollDefaultNow:!1,scrollDefaultTime:!1,selectOnBlur:!1,disableTouchKeyboard:!0,forceRoundTime:!1,appendTo:"body",disableTimeRanges:[],closeOnWindowScroll:!1,disableTextInput:!1},x={decimal:".",mins:"mins",hr:"hr",hrs:"hrs"},y={init:function(c){return this.each(function(){var d=a(this);if("SELECT"==d[0].tagName){for(var e={type:"text",value:d.val()},f=d[0].attributes,g=0;g<f.length;g++)e[f[g].nodeName]=f[g].nodeValue;var h=a("<input />",e);d.replaceWith(h),d=h}var i=a.extend({},w);c&&(i=a.extend(i,c)),i.lang&&(x=a.extend(x,i.lang)),i=b(i),d.data("timepicker-settings",i),d.prop("autocomplete","off"),d.on("click.timepicker focus.timepicker",y.show),d.on("change.timepicker",j),d.on("keydown.timepicker",m),d.on("keyup.timepicker",o),d.addClass("ui-timepicker-input"),j.call(d.get(0))})},show:function(){var b=a(this),d=b.data("timepicker-settings");g(b)&&b.blur();var f=b.data("timepicker-list");if(!b.prop("readonly")&&(f&&0!==f.length&&"function"!=typeof d.durationTime||(c(b),f=b.data("timepicker-list")),!f.is(":visible"))){y.hide(),f.show(),b.offset().top+b.outerHeight(!0)+f.outerHeight()>a(window).height()+a(window).scrollTop()?f.offset({left:b.offset().left+parseInt(f.css("marginLeft").replace("px",""),10),top:b.offset().top-f.outerHeight()+parseInt(f.css("marginTop").replace("px",""),10)}):f.offset({left:b.offset().left+parseInt(f.css("marginLeft").replace("px",""),10),top:b.offset().top+b.outerHeight()+parseInt(f.css("marginTop").replace("px",""),10)});var i=f.find(".ui-timepicker-selected");if(i.length||(k(b)?i=h(b,f,s(k(b))):d.scrollDefaultNow?i=h(b,f,s(new Date)):d.scrollDefaultTime!==!1&&(i=h(b,f,s(d.scrollDefaultTime)))),i&&i.length){var j=f.scrollTop()+i.position().top-i.outerHeight();f.scrollTop(j)}else f.scrollTop(0);e(d),b.trigger("showTimepicker")}},hide:function(){a(".ui-timepicker-wrapper:visible").each(function(){var b=a(this),c=b.data("timepicker-input"),d=c.data("timepicker-settings");d&&d.selectOnBlur&&p(c),b.hide(),c.trigger("hideTimepicker")})},option:function(c,d){var e=this,f=e.data("timepicker-settings"),g=e.data("timepicker-list");if("object"==typeof c)f=a.extend(f,c);else if("string"==typeof c&&"undefined"!=typeof d)f[c]=d;else if("string"==typeof c)return f[c];return f=b(f),e.data("timepicker-settings",f),g&&(g.remove(),e.data("timepicker-list",!1)),e},getSecondsFromMidnight:function(){return s(k(this))},getTime:function(){var a=this,b=new Date;return b.setHours(0,0,0,0),new Date(b.valueOf()+1e3*s(k(a)))},setTime:function(a){var b=this,c=r(s(a),b.data("timepicker-settings").timeFormat);l(b,c)},remove:function(){var a=this;a.hasClass("ui-timepicker-input")&&(a.removeAttr("autocomplete","off"),a.removeClass("ui-timepicker-input"),a.removeData("timepicker-settings"),a.off(".timepicker"),a.data("timepicker-list")&&a.data("timepicker-list").remove(),a.removeData("timepicker-list"))}};a.fn.timepicker=function(b){return y[b]?y[b].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof b&&b?(a.error("Method "+b+" does not exist on jQuery.timepicker"),void 0):y.init.apply(this,arguments)}});
\ No newline at end of file
--- /dev/null
+
+var pushConfigs = {};
+var pushConfigReboot = false;
+var refreshDisabled = {}; /* dictionary indexed by cameraId, tells if refresh is disabled for a given camera */
+var fullScreenCameraId = null;
+var inProgress = false;
+var refreshInterval = 50; /* milliseconds */
+var username = '';
+var password = '';
+var baseUri = null;
+var signatureRegExp = new RegExp('[^a-zA-Z0-9/?_.=&{}\\[\\]":, _-]', 'g');
+var initialConfigFetched = false; /* used to workaround browser extensions that trigger stupid change events */
+
+
+ /* utils */
+
+var sha1 = (function () {
+ function hash(msg) {
+ var K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];
+
+ msg += String.fromCharCode(0x80);
+
+ var l = msg.length / 4 + 2;
+ var N = Math.ceil(l / 16);
+ var M = new Array(N);
+
+ for (var i = 0; i < N; i++) {
+ M[i] = new Array(16);
+ for (var j = 0; j < 16; j++) {
+ M[i][j] = (msg.charCodeAt(i * 64 + j * 4) << 24) | (msg.charCodeAt(i * 64 + j * 4 + 1) << 16) |
+ (msg.charCodeAt(i * 64 + j * 4 + 2) << 8) | (msg.charCodeAt(i * 64 + j * 4 + 3));
+ }
+ }
+ M[N-1][14] = ((msg.length-1) * 8) / Math.pow(2, 32);
+ M[N-1][14] = Math.floor(M[N-1][14]);
+ M[N-1][15] = ((msg.length-1) * 8) & 0xffffffff;
+
+ var H0 = 0x67452301;
+ var H1 = 0xefcdab89;
+ var H2 = 0x98badcfe;
+ var H3 = 0x10325476;
+ var H4 = 0xc3d2e1f0;
+
+ var W = new Array(80);
+ var a, b, c, d, e;
+ for (var i = 0; i < N; i++) {
+ for (var t = 0; t < 16; t++) W[t] = M[i][t];
+ for (var t = 16; t < 80; t++) W[t] = ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
+
+ a = H0; b = H1; c = H2; d = H3; e = H4;
+
+ for (var t = 0; t < 80; t++) {
+ var s = Math.floor(t / 20);
+ var T = (ROTL(a, 5) + f(s, b, c, d) + e + K[s] + W[t]) & 0xffffffff;
+ e = d;
+ d = c;
+ c = ROTL(b, 30);
+ b = a;
+ a = T;
+ }
+
+ H0 = (H0 + a) & 0xffffffff;
+ H1 = (H1 + b) & 0xffffffff;
+ H2 = (H2 + c) & 0xffffffff;
+ H3 = (H3 + d) & 0xffffffff;
+ H4 = (H4 + e) & 0xffffffff;
+ }
+
+ return toHexStr(H0) + toHexStr(H1) + toHexStr(H2) + toHexStr(H3) + toHexStr(H4);
+ }
+
+ function f(s, x, y, z) {
+ switch (s) {
+ case 0: return (x & y) ^ (~x & z);
+ case 1: return x ^ y ^ z;
+ case 2: return (x & y) ^ (x & z) ^ (y & z);
+ case 3: return x ^ y ^ z;
+ }
+ }
+
+ function ROTL(x, n) {
+ return (x << n) | (x >>> (32 - n));
+ }
+
+ function toHexStr(n) {
+ var s = "", v;
+ for (var i = 7; i >= 0; i--) {
+ v = (n >>> (i * 4)) & 0xf;
+ s += v.toString(16);
+ }
+ return s;
+ }
+
+ return hash;
+}());
+
+function splitUrl(url) {
+ if (!url) {
+ url = window.location.href;
+ }
+
+ var parts = url.split('?');
+ if (parts.length < 2 || parts[1].length === 0) {
+ return {baseUrl: parts[0], params: {}};
+ }
+
+ var baseUrl = parts[0];
+ var paramStr = parts[1];
+
+ parts = paramStr.split('&');
+ var params = {};
+
+ for (var i = 0; i < parts.length; i++) {
+ var pair = parts[i].split('=');
+ params[pair[0]] = pair[1];
+ }
+
+ return {baseUrl: baseUrl, params: params};
+}
+
+function qualifyUrl(url) {
+ var a = document.createElement('a');
+ a.href = url;
+ return a.href;
+}
+
+function qualifyUri(uri) {
+ var url = qualifyUrl(uri);
+ var pos = url.indexOf('//');
+ if (pos === -1) { /* not a full url */
+ return url;
+ }
+
+ url = url.substring(pos + 2);
+ pos = url.indexOf('/');
+ if (pos === -1) { /* root with no trailing slash */
+ return '';
+ }
+
+ return url.substring(pos);
+}
+
+function computeSignature(method, uri, body) {
+ uri = qualifyUri(uri);
+
+ var parts = splitUrl(uri);
+ var query = parts.params;
+ var baseUrl = parts.baseUrl;
+
+ /* sort query arguments alphabetically */
+ query = Object.keys(query).map(function (key) {return {key: key, value: decodeURIComponent(query[key])};});
+ query = query.filter(function (q) {return q.key !== '_signature';});
+ query.sortKey(function (q) {return q.key;});
+ query = query.map(function (q) {return q.key + '=' + encodeURIComponent(q.value);}).join('&');
+ uri = baseUrl + '?' + query;
+ uri = uri.replace(signatureRegExp, '-');
+ body = body && body.replace(signatureRegExp, '-');
+ var password = window.password.replace(signatureRegExp, '-');
+
+ return sha1(method + ':' + uri + ':' + (body || '') + ':' + password).toLowerCase();
+}
+
+function addAuthParams(method, url, body) {
+ if (!window.username) {
+ return url;
+ }
+
+ if (url.indexOf('?') < 0) {
+ url += '?';
+ }
+ else {
+ url += '&';
+ }
+
+ url += '_username=' + window.username;
+ if (window._loginDialogSubmitted) {
+ url += '&_login=true';
+ delete _loginDialogSubmitted;
+ }
+ var signature = computeSignature(method, url, body);
+ url += '&_signature=' + signature;
+
+ return url;
+}
+
+function isAdmin() {
+ return username === adminUsername;
+}
+
+function ajax(method, url, data, callback, error, timeout) {
+ var origUrl = url;
+ var origData = data;
+
+ if (url.indexOf('?') < 0) {
+ url += '?';
+ }
+ else {
+ url += '&';
+ }
+
+ url += '_=' + new Date().getTime();
+
+ var json = false;
+ var processData = true;
+ if (method == 'POST') {
+ if (window.FormData && (data instanceof FormData)) {
+ json = false;
+ processData = false;
+ }
+ else if (typeof data == 'object') {
+ data = JSON.stringify(data);
+ json = true;
+ }
+ }
+ else { /* assuming GET */
+ if (data) {
+ url += '&' + $.param(data);
+ data = null;
+ }
+ }
+
+ url = addAuthParams(method, url, processData ? data : null);
+
+ var options = {
+ type: method,
+ url: url,
+ data: data,
+ timeout: timeout || 300 * 1000,
+ success: function (data) {
+ if (data && data.error == 'unauthorized') {
+ if (data.prompt) {
+ runLoginDialog(function () {
+ ajax(method, origUrl, origData, callback, error);
+ });
+ }
+
+ window._loginRetry = true;
+ }
+ else {
+ delete window._loginRetry;
+ if (callback) {
+ $('body').toggleClass('admin', isAdmin());
+ callback(data);
+ }
+ }
+ },
+ contentType: json ? 'application/json' : false,
+ processData: processData,
+ error: error || function (request, options, error) {
+ showErrorMessage();
+ if (callback) {
+ callback();
+ }
+ }
+ };
+
+ $.ajax(options);
+}
+
+Object.keys = Object.keys || (function () {
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
+ var hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString');
+ var dontEnums = [
+ 'toString',
+ 'toLocaleString',
+ 'valueOf',
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'constructor'
+ ];
+ var dontEnumsLength = dontEnums.length;
+
+ return function (obj) {
+ if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) {
+ return [];
+ }
+
+ var result = [];
+ for (var prop in obj) {
+ if (hasOwnProperty.call(obj, prop)) {
+ result.push(prop);
+ }
+ }
+
+ if (hasDontEnumBug) {
+ for (var i = 0; i < dontEnumsLength; i++) {
+ if (hasOwnProperty.call(obj, dontEnums[i])) {
+ result.push(dontEnums[i]);
+ }
+ }
+ }
+
+ return result;
+ };
+})();
+
+Object.update = function (dest, source) {
+ for (var key in source) {
+ if (!source.hasOwnProperty(key)) {
+ continue;
+ }
+
+ dest[key] = source[key];
+ }
+};
+
+Array.prototype.indexOf = Array.prototype.indexOf || function (obj) {
+ for (var i = 0; i < this.length; i++) {
+ if (this[i] === obj) {
+ return i;
+ }
+ }
+
+ return -1;
+};
+
+Array.prototype.forEach = Array.prototype.forEach || function (callback, thisArg) {
+ for (var i = 0; i < this.length; i++) {
+ callback.call(thisArg, this[i], i, this);
+ }
+};
+
+Array.prototype.every = Array.prototype.every || function (callback, thisArg) {
+ for (var i = 0; i < this.length; i++) {
+ if (!callback.call(thisArg, this[i], i, this)) {
+ return false;
+ }
+ }
+
+ return true;
+};
+
+Array.prototype.unique = function (callback, thisArg) {
+ var uniqueElements = [];
+ this.forEach(function (element) {
+ if (uniqueElements.indexOf(element, Utils.equals) === -1) {
+ uniqueElements.push(element);
+ }
+ });
+
+ return uniqueElements;
+};
+
+Array.prototype.filter = function (func, thisArg) {
+ var filtered = [];
+ for (var i = 0; i < this.length; i++) {
+ if (func.call(thisArg, this[i], i, this)) {
+ filtered.push(this[i]);
+ }
+ }
+
+ return filtered;
+};
+
+Array.prototype.map = function (func, thisArg) {
+ var mapped = [];
+ for (var i = 0; i < this.length; i++) {
+ mapped.push(func.call(thisArg, this[i], i, this));
+ }
+
+ return mapped;
+};
+
+Array.prototype.sortKey = function (keyFunc, reverse) {
+ this.sort(function (e1, e2) {
+ var k1 = keyFunc(e1);
+ var k2 = keyFunc(e2);
+
+ if ((k1 < k2 && !reverse) || (k1 > k2 && reverse)) {
+ return -1;
+ }
+ else if ((k1 > k2 && !reverse) || (k1 < k2 && reverse)) {
+ return 1;
+ }
+ else {
+ return 0;
+ }
+ });
+};
+
+String.prototype.startsWith = String.prototype.startsWith || function (str) {
+ return (this.substr(0, str.length) === str);
+};
+
+String.prototype.endsWith = String.prototype.endsWith || function (str) {
+ return (this.substr(this.length - str.length) === str);
+};
+
+String.prototype.trim = String.prototype.trim || function () {
+ return this.replace(new RegExp('^\\s*'), '').replace(new RegExp('\\s*$'), '');
+};
+
+String.prototype.replaceAll = String.prototype.replaceAll || function (oldStr, newStr) {
+ var p, s = this;
+ while ((p = s.indexOf(oldStr)) >= 0) {
+ s = s.substring(0, p) + newStr + s.substring(p + oldStr.length, s.length);
+ }
+
+ return s.toString();
+};
+
+function getCookie(name) {
+ if (document.cookie.length <= 0) {
+ return null;
+ }
+
+ var start = document.cookie.indexOf(name + '=');
+ if (start == -1) {
+ return null;
+ }
+
+ var start = start + name.length + 1;
+ var end = document.cookie.indexOf(';', start);
+ if (end == -1) {
+ end = document.cookie.length;
+ }
+
+ return unescape(document.cookie.substring(start, end));
+}
+
+function setCookie(name, value, days) {
+ var date, expires;
+ if (days) {
+ date = new Date();
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
+ expires = 'expires=' + date.toGMTString();
+ }
+ else {
+ expires = '';
+ }
+
+ document.cookie = name + '=' + value + '; ' + expires + '; path=/';
+}
+
+function remCookie(name) {
+ document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
+}
+
+function showErrorMessage(message) {
+ if (message == null || message == true) {
+ message = 'An error occurred. Refreshing is recommended.';
+ }
+
+ showPopupMessage(message, 'error');
+}
+
+function doLogout() {
+ setCookie('username', '_');
+ window.location.reload(true);
+}
+
+
+/* UI initialization */
+
+function initUI() {
+ /* checkboxes */
+ makeCheckBox($('input[type=checkbox].styled'));
+
+ /* sliders */
+ $('input[type=text].range.styled').each(function () {
+ var $this = $(this);
+ var $tr = $this.parent().parent();
+ var ticks = null;
+ var ticksAttr = $tr.attr('ticks');
+ if (ticksAttr) {
+ ticks = ticksAttr.split('|').map(function (t) {
+ var parts = t.split(',');
+ if (parts.length < 2) {
+ parts.push(parts[0]);
+ }
+ return {value: Number(parts[0]), label: parts[1]};
+ });
+ }
+ makeSlider($this, Number($tr.attr('min')), Number($tr.attr('max')),
+ Number($tr.attr('snap')), ticks, Number($tr.attr('ticksnum')), Number($tr.attr('decimals')), $tr.attr('unit'));
+ });
+
+ /* progress bars */
+ makeProgressBar($('div.progress-bar'));
+
+ /* text validators */
+ makeTextValidator($('tr[required=true] input[type=text]'), true);
+ makeTextValidator($('tr[required=true] input[type=password]'), true);
+
+ /* number validators */
+ $('input[type=text].number').each(function () {
+ var $this = $(this);
+ var $tr = $this.parent().parent();
+ makeNumberValidator($this, Number($tr.attr('min')), Number($tr.attr('max')),
+ Boolean($tr.attr('floating')), Boolean($tr.attr('sign')), Boolean($tr.attr('required')));
+ });
+
+ /* time validators */
+ makeTimeValidator($('input[type=text].time'));
+
+ /* custom validators */
+ makeCustomValidator($('#deviceNameEntry'), function (value) {
+ if (!value) {
+ return 'this field is required';
+ }
+
+ if (!value.toLowerCase().match(new RegExp('^[a-z0-9\-\_\+\ ]*$'))) {
+ return "special characters are not allowed in camera's name";
+ }
+
+ return true;
+ }, '');
+ makeCustomValidator($('#rootDirectoryEntry'), function (value) {
+ if ($('#storageDeviceSelect').val() == 'custom-path' && String(value).trim() == '/') {
+ return 'files cannot be created directly on the root of your system';
+ }
+
+ return true;
+ }, '');
+ makeCustomValidator($('#emailAddressesEntry'), function (value) {
+ if (!value.toLowerCase().match(new RegExp('^[a-z0-9\-\_\+\.\@\^\~\, ]+$'))) {
+ return 'enter a list of comma-separated valid email addresses';
+ }
+
+ return true;
+ }, '');
+ $('tr[validate] input[type=text]').each(function () {
+ var $this = $(this);
+ var $tr = $this.parent().parent();
+ var required = $tr.attr('required');
+ var validate = $tr.attr('validate');
+ if (!validate) {
+ return;
+ }
+
+ makeCustomValidator($this, function (value) {
+ if (!value && required) {
+ return 'this field is required';
+ }
+
+ if (!value.toLowerCase().match(new RegExp(validate))) {
+ return 'enter a valid value';
+ }
+
+ return true;
+ }, '');
+ });
+
+ /* input value processors */
+ makeStrippedInput($('tr[strip=true] input[type=text]'));
+ makeStrippedInput($('tr[strip=true] input[type=password]'));
+
+ function checkMinimizeSection() {
+ var $switch = $(this);
+ var $sectionDiv = $switch.parents('div.settings-section-title:eq(0)');
+
+ var $minimizeSpan = $switch.parent().find('span.minimize');
+ if ($switch.is(':checked') && !$minimizeSpan.hasClass('open')) {
+ $minimizeSpan.addClass('open');
+ }
+ else if (!$switch.is(':checked') && $minimizeSpan.hasClass('open') && !$sectionDiv.attr('minimize-switch-independent')) {
+ $minimizeSpan.removeClass('open');
+ }
+ }
+
+ /* ui elements that enable/disable other ui elements */
+ $('#motionEyeSwitch').change(updateConfigUi);
+ $('#showAdvancedSwitch').change(updateConfigUi);
+ $('#storageDeviceSelect').change(updateConfigUi);
+ $('#resolutionSelect').change(updateConfigUi);
+ $('#leftTextSelect').change(updateConfigUi);
+ $('#rightTextSelect').change(updateConfigUi);
+ $('#captureModeSelect').change(updateConfigUi);
+ $('#autoNoiseDetectSwitch').change(updateConfigUi);
+ $('#videoDeviceSwitch').change(checkMinimizeSection).change(updateConfigUi);
+ $('#textOverlaySwitch').change(checkMinimizeSection).change(updateConfigUi);
+ $('#videoStreamingSwitch').change(checkMinimizeSection).change(updateConfigUi);
+ $('#streamingServerResizeSwitch').change(updateConfigUi);
+ $('#stillImagesSwitch').change(checkMinimizeSection).change(updateConfigUi);
+ $('#preservePicturesSelect').change(updateConfigUi);
+ $('#motionDetectionSwitch').change(checkMinimizeSection).change(updateConfigUi);
+ $('#motionMoviesSwitch').change(checkMinimizeSection).change(updateConfigUi);
+ $('#preserveMoviesSelect').change(updateConfigUi);
+ $('#emailNotificationsSwitch').change(updateConfigUi);
+ $('#webHookNotificationsSwitch').change(updateConfigUi);
+ $('#commandNotificationsSwitch').change(updateConfigUi);
+ $('#workingScheduleSwitch').change(checkMinimizeSection).change(updateConfigUi);
+
+ $('#mondayEnabledSwitch').change(updateConfigUi);
+ $('#tuesdayEnabledSwitch').change(updateConfigUi);
+ $('#wednesdayEnabledSwitch').change(updateConfigUi);
+ $('#thursdayEnabledSwitch').change(updateConfigUi);
+ $('#fridayEnabledSwitch').change(updateConfigUi);
+ $('#saturdayEnabledSwitch').change(updateConfigUi);
+ $('#sundayEnabledSwitch').change(updateConfigUi);
+
+ /* minimizable sections */
+ $('span.minimize').click(function () {
+ $(this).toggleClass('open');
+
+ /* enable the section switch when unminimizing */
+ if ($(this).hasClass('open')) {
+ var sectionSwitch = $(this).parent().find('input[type=checkbox]');
+ var sectionSwitchDiv = $(this).parent().find('div.check-box');
+ var sectionDiv = $(this).parents('div.settings-section-title:eq(0)');
+ if (sectionSwitch.length && !sectionSwitch.is(':checked') &&
+ !sectionSwitchDiv[0]._hideNull && !sectionDiv.attr('minimize-switch-independent')) {
+
+ sectionSwitch[0].checked = true;
+ sectionSwitch.change();
+ }
+ }
+
+ updateConfigUi();
+ });
+
+ $('a.settings-section-title').click(function () {
+ $(this).parent().find('span.minimize').click();
+ });
+
+ /* additional configs */
+ var seenDependNames = {};
+ $('tr[depends]').each(function () {
+ var $tr = $(this);
+ var depends = $tr.attr('depends').split(' ');
+ depends.forEach(function (depend) {
+ depend = depend.split('=')[0];
+ depend = depend.replace(new RegExp('[^a-zA-Z0-9_]', 'g'), '');
+
+ if (depend in seenDependNames) {
+ return;
+ }
+
+ seenDependNames[depend] = true;
+
+ var control = $('#' + depend + 'Entry, #' + depend + 'Select, #' + depend + 'Slider, #' + depend + 'Switch');
+ control.change(updateConfigUi);
+ });
+ });
+
+ $('#storageDeviceSelect').change(function () {
+ $('#rootDirectoryEntry').val('/');
+ });
+
+ $('#rootDirectoryEntry').change(function () {
+ this.value = this.value.trim();
+ });
+
+ $('#rootDirectoryEntry').change(function () {
+ if (this.value.charAt(0) !== '/') {
+ this.value = '/' + this.value;
+ }
+ });
+
+ /* fetch & push handlers */
+ $('#cameraSelect').focus(function () {
+ /* remember the previously selected index */
+ this._prevSelectedIndex = this.selectedIndex;
+
+ }).change(function () {
+ if ($('#cameraSelect').val() === 'add') {
+ runAddCameraDialog();
+ this.selectedIndex = this._prevSelectedIndex;
+ }
+ else {
+ this._prevSelectedIndex = this.selectedIndex;
+ beginProgress([$(this).val()]);
+ fetchCurrentCameraConfig(endProgress);
+ }
+ });
+ $('input.main-config, select.main-config, textarea.main-config').change(function () {
+ pushMainConfig($(this).parents('tr:eq(0)').attr('reboot') == 'true');
+ });
+ $('input.camera-config, select.camera-config, textarea.camera-config').change(function () {
+ pushCameraConfig($(this).parents('tr:eq(0)').attr('reboot') == 'true');
+ });
+
+ /* streaming framerate must be >= device framerate */
+ $('#framerateSlider').change(function (val) {
+ var value = Number($('#framerateSlider').val());
+ var streamingValue = Number($('#streamingFramerateSlider').val());
+
+ if (streamingValue < value) {
+ $('#streamingFramerateSlider').val(value).change();
+ }
+ });
+
+ /* preview controls */
+ $('#brightnessSlider').change(function () {pushPreview('brightness');});
+ $('#contrastSlider').change(function () {pushPreview('contrast');});
+ $('#saturationSlider').change(function () {pushPreview('saturation');});
+ $('#hueSlider').change(function () {pushPreview('hue');});
+
+ /* apply button */
+ $('#applyButton').click(function () {
+ if ($(this).hasClass('progress')) {
+ return; /* in progress */
+ }
+
+ doApply();
+ });
+
+ /* shut down button */
+ $('#shutDownButton').click(function () {
+ doShutDown();
+ });
+
+ /* reboot button */
+ $('#rebootButton').click(function () {
+ doReboot();
+ });
+
+ /* whenever the window is resized,
+ * if a modal dialog is visible, it should be repositioned */
+ $(window).resize(updateModalDialogPosition);
+
+ /* remove camera button */
+ $('div.button.rem-camera-button').click(doRemCamera);
+
+ /* logout button */
+ $('div.button.logout-button').click(doLogout);
+
+ /* autoselect urls in read-only entries */
+ $('#streamingSnapshotUrlEntry:text, #streamingMjpgUrlEntry:text, #streamingEmbedUrlEntry:text').click(function () {
+ this.select();
+ });
+}
+
+
+ /* settings */
+
+function openSettings(cameraId) {
+ if (cameraId != null) {
+ $('#cameraSelect').val(cameraId).change();
+ }
+
+ $('div.settings').addClass('open').removeClass('closed');
+ $('div.page-container').addClass('stretched');
+ $('div.settings-top-bar').addClass('open').removeClass('closed');
+
+ updateConfigUi();
+}
+
+function closeSettings() {
+ hideApply();
+ pushConfigs = {};
+ pushConfigReboot = false;
+
+ $('div.settings').removeClass('open').addClass('closed');
+ $('div.page-container').removeClass('stretched');
+ $('div.settings-top-bar').removeClass('open').addClass('closed');
+}
+
+function isSettingsOpen() {
+ return $('div.settings').hasClass('open');
+}
+
+function updateConfigUi() {
+ var objs = $('tr.settings-item, div.advanced-setting, table.advanced-setting, div.settings-section-title, table.settings, ' +
+ 'div.check-box.camera-config, div.check-box.main-config');
+
+ function markHideLogic() {
+ this._hideLogic = true;
+ }
+
+ function markHideAdvanced() {
+ this._hideAdvanced = true;
+ }
+
+ function markHideMinimized() {
+ this._hideMinimized = true;
+ }
+
+ function unmarkHide() {
+ this._hideLogic = false;
+ this._hideAdvanced = false;
+ this._hideMinimized = false;
+ }
+
+ objs.each(unmarkHide);
+
+ /* hide sliders that, for some reason, don't have a value */
+ $('input.range').each(function () {
+ if (this.value == '') {
+ $(this).parents('tr:eq(0)').each(markHideLogic);
+ }
+ });
+
+ /* minimizable sections */
+ $('span.minimize').each(function () {
+ var $this = $(this);
+ if (!$this.hasClass('open')) {
+ $this.parent().next('table.settings').find('tr').each(markHideMinimized);
+ }
+ });
+
+ /* general enable switch */
+ var motionEyeEnabled = $('#motionEyeSwitch').get(0).checked;
+ if (!motionEyeEnabled) {
+ objs.not($('#motionEyeSwitch').parents('div').get(0)).each(markHideLogic);
+ }
+
+ if ($('#cameraSelect').find('option').length < 2) { /* no camera configured */
+ $('#videoDeviceSwitch').parent().each(markHideLogic);
+ $('#videoDeviceSwitch').parent().nextAll('div.settings-section-title, table.settings').each(markHideLogic);
+ }
+
+ if ($('#videoDeviceSwitch')[0].error) { /* config error */
+ $('#videoDeviceSwitch').parent().nextAll('div.settings-section-title, table.settings').each(markHideLogic);
+ }
+
+ /* advanced settings */
+ var showAdvanced = $('#showAdvancedSwitch').get(0).checked;
+ if (!showAdvanced) {
+ $('tr.advanced-setting, div.advanced-setting, table.advanced-setting').each(markHideAdvanced);
+ }
+
+ /* hide resolution select if no resolution is selected (none matches) */
+ if ($('#resolutionSelect')[0].selectedIndex == -1) {
+ $('#resolutionSelect').parents('tr:eq(0)').each(markHideLogic);
+ }
+
+ /* storage device */
+ if ($('#storageDeviceSelect').val() !== 'network-share') {
+ $('#networkServerEntry').parents('tr:eq(0)').each(markHideLogic);
+ $('#networkUsernameEntry').parents('tr:eq(0)').each(markHideLogic);
+ $('#networkPasswordEntry').parents('tr:eq(0)').each(markHideLogic);
+ $('#networkShareNameEntry').parents('tr:eq(0)').each(markHideLogic);
+ }
+
+ /* text */
+ if ($('#leftTextSelect').val() !== 'custom-text') {
+ $('#leftTextEntry').parents('tr:eq(0)').each(markHideLogic);
+ }
+ if ($('#rightTextSelect').val() !== 'custom-text') {
+ $('#rightTextEntry').parents('tr:eq(0)').each(markHideLogic);
+ }
+
+ /* still images capture mode */
+ if ($('#captureModeSelect').val() !== 'interval-snapshots') {
+ $('#snapshotIntervalEntry').parents('tr:eq(0)').each(markHideLogic);
+ }
+
+ /* auto noise level */
+ if ($('#autoNoiseDetectSwitch').get(0).checked) {
+ $('#noiseLevelSlider').parents('tr:eq(0)').each(markHideLogic);
+ }
+
+ /* video device switch */
+ if (!$('#videoDeviceSwitch').get(0).checked) {
+ $('#videoDeviceSwitch').parent().nextAll('div.settings-section-title, table.settings').each(markHideLogic);
+ }
+
+ /* text overlay switch */
+ if (!$('#textOverlaySwitch').get(0).checked) {
+ $('#textOverlaySwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
+ }
+
+ /* video streaming */
+ if (!$('#videoStreamingSwitch').get(0).checked) {
+ $('#videoStreamingSwitch').parent().next('table.settings').find('tr.settings-item').not('.localhost-streaming').each(markHideLogic);
+ }
+ if (!$('#streamingServerResizeSwitch').get(0).checked) {
+ $('#streamingResolutionSlider').parents('tr:eq(0)').each(markHideLogic);
+ }
+
+ /* still images switch */
+ if (!$('#stillImagesSwitch').get(0).checked) {
+ $('#stillImagesSwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
+ }
+
+ /* preserve pictures */
+ if ($('#preservePicturesSelect').val() != '-1') {
+ $('#picturesLifetimeEntry').parents('tr:eq(0)').each(markHideLogic);
+ }
+
+ /* motion detection switch */
+ if (!$('#motionDetectionSwitch').get(0).checked) {
+ $('#motionDetectionSwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
+
+ /* hide the entire motion movies section */
+ $('#motionMoviesSwitch').parent().each(markHideLogic);
+ $('#motionMoviesSwitch').parent().next('table.settings').each(markHideLogic);
+
+ /* hide the entire notifications section */
+ $('#emailNotificationsSwitch').parents('table.settings').prev().each(markHideLogic);
+ $('#emailNotificationsSwitch').parents('table.settings').each(markHideLogic);
+
+ /* hide the entire working schedule section */
+ $('#workingScheduleSwitch').parent().each(markHideLogic);
+ $('#workingScheduleSwitch').parent().next('table.settings').each(markHideLogic);
+ }
+
+ /* motion movies switch */
+ if (!$('#motionMoviesSwitch').get(0).checked) {
+ $('#motionMoviesSwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
+ }
+
+ /* preserve movies */
+ if ($('#preserveMoviesSelect').val() != '-1') {
+ $('#moviesLifetimeEntry').parents('tr:eq(0)').each(markHideLogic);
+ }
+
+ /* event notifications */
+ if (!$('#emailNotificationsSwitch').get(0).checked) {
+ $('#emailAddressesEntry').parents('tr:eq(0)').each(markHideLogic);
+ $('#smtpServerEntry').parents('tr:eq(0)').each(markHideLogic);
+ $('#smtpPortEntry').parents('tr:eq(0)').each(markHideLogic);
+ $('#smtpAccountEntry').parents('tr:eq(0)').each(markHideLogic);
+ $('#smtpPasswordEntry').parents('tr:eq(0)').each(markHideLogic);
+ $('#smtpTlsSwitch').parents('tr:eq(0)').each(markHideLogic);
+ $('#emailPictureTimeSpanEntry').parents('tr:eq(0)').each(markHideLogic);
+ }
+
+ if (!$('#webHookNotificationsSwitch').get(0).checked) {
+ $('#webHookUrlEntry').parents('tr:eq(0)').each(markHideLogic);
+ $('#webHookHttpMethodSelect').parents('tr:eq(0)').each(markHideLogic);
+ }
+
+ if (!$('#commandNotificationsSwitch').get(0).checked) {
+ $('#commandNotificationsEntry').parents('tr:eq(0)').each(markHideLogic);
+ }
+
+ /* working schedule */
+ if (!$('#workingScheduleSwitch').get(0).checked) {
+ $('#workingScheduleSwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
+ }
+
+ /* additional configs */
+ $('tr[depends]').each(function () {
+ var $tr = $(this);
+ var depends = $tr.attr('depends').split(' ');
+ var conditionOk = true;
+ depends.every(function (depend) {
+ var neg = depend.indexOf('!') >= 0;
+ var parts = depend.split('=');
+ var boolCheck = parts.length == 1;
+ depend = parts[0].replace(new RegExp('[^a-zA-Z0-9_$]', 'g'), '');
+
+ var control = $('#' + depend + 'Entry, #' + depend + 'Select, #' + depend + 'Slider');
+ var val = false;
+ if (control.length) {
+ val = control.val();
+ }
+ else { /* maybe it's a checkbox */
+ control = $('#' + depend + 'Switch');
+ if (control.length) {
+ val = control.get(0).checked;
+ }
+ }
+
+ if (boolCheck) {
+ if (neg) {
+ val = !val;
+ }
+
+ if (!val) {
+ conditionOk = false;
+ return false;
+ }
+ }
+ else { /* comparison */
+ var equal = parts[parts.length - 1] == val;
+ if (equal == neg) {
+ conditionOk = false;
+ return false;
+ }
+ }
+
+ return true;
+ });
+
+ if (!conditionOk) {
+ $tr.each(markHideLogic);
+ }
+ });
+
+ /* hide sections that have no visible configs and no switch */
+ $('div.settings-section-title').each(function () {
+ var $this = $(this);
+ var $table = $this.next();
+ var controls = $table.find('input, select');
+
+ var switchButton = $this.children('div.check-box');
+ if (switchButton.length && !switchButton[0]._hideNull) {
+ return; /* has visible switch */
+ }
+
+ for (var i = 0; i < controls.length; i++) {
+ var control = $(controls[i]);
+ var tr = control.parents('tr:eq(0)')[0];
+ if (!tr._hideLogic && !tr._hideAdvanced && !tr._hideNull) {
+ return; /* has visible controls */
+ }
+ }
+
+ $table.find('div.settings-item-separator').each(function () {
+ $(this).parent().parent().each(markHideLogic);
+ });
+
+ $this.each(markHideLogic);
+ $table.each(markHideLogic);
+ });
+
+ /* hide useless separators */
+ $('div.settings-container table.settings').each(function () {
+ var $table = $(this);
+
+ /* filter visible rows */
+ var visibleTrs = $table.find('tr').filter(function () {
+ return !this._hideLogic && !this._hideAdvanced && !this._hideNull;
+ }).map(function () {
+ var $tr = $(this);
+ $tr.isSeparator = $tr.find('div.settings-item-separator').length > 0;
+
+ return $tr;
+ }).get();
+
+ for (var i = 1; i < visibleTrs.length; i++) {
+ var $prevTr = visibleTrs[i - 1];
+ var $tr = visibleTrs[i];
+ if ($prevTr.isSeparator && $tr.isSeparator) {
+ $tr.each(markHideLogic);
+ }
+ }
+
+ /* filter visible rows again */
+ visibleTrs = $table.find('tr').filter(function () {
+ return !this._hideLogic && !this._hideAdvanced && !this._hideNull;
+ }).map(function () {
+ var $tr = $(this);
+ $tr.isSeparator = $tr.find('div.settings-item-separator').length > 0;
+
+ return $tr;
+ }).get();
+
+ if (visibleTrs.length) {
+ /* test first row */
+ if (visibleTrs[0].isSeparator) {
+ visibleTrs[0].each(markHideLogic);
+ }
+
+ /* test last row */
+ if (visibleTrs[visibleTrs.length - 1].isSeparator) {
+ visibleTrs[visibleTrs.length - 1].each(markHideLogic);
+ }
+ }
+ });
+
+ var weekDays = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
+ weekDays.forEach(function (weekDay) {
+ var check = $('#' + weekDay + 'EnabledSwitch');
+ if (check.get(0).checked) {
+ check.parent().find('.time').show();
+ }
+ else {
+ check.parent().find('.time').hide();
+ }
+ });
+
+ objs.each(function () {
+ if (this._hideLogic || this._hideAdvanced || this._hideMinimized || this._hideNull /* from dict2ui */) {
+ $(this).hide(200);
+ }
+ else {
+ $(this).show(200);
+ }
+ });
+
+ /* re-validate all the validators */
+ $('div.settings').find('.validator').each(function () {
+ this.validate();
+ });
+
+ /* update all checkboxes and sliders */
+ $('div.settings').find('input[type=checkbox], input.range').each(function () {
+ this.update();
+ });
+
+ /* select the first option for the selects with no current selection */
+ $('div.settings').find('select').not('#cameraSelect').each(function () {
+ if (this.selectedIndex === -1) {
+ this.selectedIndex = 0;
+ }
+ });
+}
+
+function configUiValid() {
+ /* re-validate all the validators */
+ $('div.settings').find('.validator').each(function () {
+ this.validate();
+ });
+
+ var valid = true;
+ $('div.settings input, select').each(function () {
+ if (this.invalid) {
+ valid = false;
+ return false;
+ }
+ });
+
+ return valid;
+}
+
+function mainUi2Dict() {
+ var dict = {
+ 'enabled': $('#motionEyeSwitch')[0].checked,
+
+ 'show_advanced': $('#showAdvancedSwitch')[0].checked,
+ 'admin_username': $('#adminUsernameEntry').val(),
+ 'admin_password': $('#adminPasswordEntry').val(),
+ 'normal_username': $('#normalUsernameEntry').val(),
+ 'normal_password': $('#normalPasswordEntry').val()
+ };
+
+ /* additional sections */
+ $('input[type=checkbox].additional-section.main-config').each(function () {
+ dict['_' + this.id.substring(0, this.id.length - 6)] = this.checked;
+ });
+
+ /* additional configs */
+ $('tr.additional-config').each(function () {
+ var $this = $(this);
+ var control = $this.find('input, select');
+
+ if (!control.hasClass('main-config')) {
+ return;
+ }
+
+ var id = control.attr('id');
+ var name, value;
+ if (id.endsWith('Entry')) {
+ name = id.substring(0, id.length - 5);
+ value = control.val();
+ if (control.hasClass('number')) {
+ value = Number(value);
+ }
+ }
+ else if (id.endsWith('Select')) {
+ name = id.substring(0, id.length - 6);
+ value = control.val();
+ }
+ else if (id.endsWith('Slider')) {
+ name = id.substring(0, id.length - 6);
+ value = Number(control.val());
+ }
+ else if (id.endsWith('Switch')) {
+ name = id.substring(0, id.length - 6);
+ value = control[0].checked;
+ }
+
+ dict['_' + name] = value;
+ });
+
+ return dict;
+}
+
+function dict2MainUi(dict) {
+ function markHideIfNull(field, elemId) {
+ var elem = $('#' + elemId);
+ var sectionDiv = elem.parents('div.settings-section-title:eq(0)');
+ var hideNull = (field === true) || (typeof field == 'string' && dict[field] == null);
+
+ if (sectionDiv.length) { /* element is a section */
+ sectionDiv.find('div.check-box').each(function () {this._hideNull = hideNull;});
+ if (hideNull) {
+ sectionDiv.find('input[type=checkbox]').each(function () {this.checked = true;});
+ }
+ }
+ else { /* element is a config option */
+ elem.parents('tr:eq(0)').each(function () {this._hideNull = hideNull;});
+ }
+ }
+
+ $('#motionEyeSwitch')[0].checked = dict['enabled'];
+
+ $('#showAdvancedSwitch')[0].checked = dict['show_advanced']; markHideIfNull('show_advanced', 'showAdvancedSwitch');
+ $('#adminUsernameEntry').val(dict['admin_username']); markHideIfNull('admin_username', 'adminUsernameEntry');
+ $('#adminPasswordEntry').val(dict['admin_password']); markHideIfNull('admin_password', 'adminPasswordEntry');
+ $('#normalUsernameEntry').val(dict['normal_username']); markHideIfNull('normal_username', 'normalUsernameEntry');
+ $('#normalPasswordEntry').val(dict['normal_password']); markHideIfNull('normal_password', 'normalPasswordEntry');
+
+ /* additional sections */
+ $('input[type=checkbox].additional-section.main-config').each(function () {
+ var name = this.id.substring(0, this.id.length - 6);
+ this.checked = dict[name];
+ markHideIfNull(name, this.id);
+ });
+
+ /* additional configs */
+ $('tr.additional-config').each(function () {
+ var $this = $(this);
+ var control = $this.find('input, select, textarea, div.html');
+
+ if (!control.hasClass('main-config')) {
+ return;
+ }
+
+ var id = control.attr('id');
+ var name;
+ if (id.endsWith('Entry')) {
+ name = id.substring(0, id.length - 5);
+ control.val(dict['_' + name]);
+ }
+ else if (id.endsWith('Select')) {
+ name = id.substring(0, id.length - 6);
+ control.val(dict['_' + name]);
+ }
+ else if (id.endsWith('Slider')) {
+ name = id.substring(0, id.length - 6);
+ control.val(dict['_' + name]);
+ }
+ else if (id.endsWith('Switch')) {
+ name = id.substring(0, id.length - 6);
+ control[0].checked = dict['_' + name];
+ }
+ else if (id.endsWith('Html')) {
+ name = id.substring(0, id.length - 4);
+ control.html(dict['_' + name]);
+ }
+
+ markHideIfNull('_' + name, id);
+ });
+
+ updateConfigUi();
+}
+
+function cameraUi2Dict() {
+ if ($('#videoDeviceSwitch')[0].error) { /* config error */
+ return {
+ 'enabled': $('#videoDeviceSwitch')[0].checked,
+ };
+ }
+
+ var dict = {
+ 'enabled': $('#videoDeviceSwitch')[0].checked,
+ 'name': $('#deviceNameEntry').val(),
+ 'proto': $('#deviceTypeEntry')[0].proto,
+
+ /* video device */
+ 'light_switch_detect': $('#lightSwitchDetectSwitch')[0].checked,
+ 'auto_brightness': $('#autoBrightnessSwitch')[0].checked,
+ 'rotation': $('#rotationSelect').val(),
+ 'framerate': $('#framerateSlider').val(),
+ 'extra_options': $('#extraOptionsEntry').val().split(new RegExp('(\n)|(\r\n)|(\n\r)')).map(function (o) {
+ if (!o) {
+ return null;
+ }
+
+ o = o.trim();
+ if (!o.length) {
+ return null;
+ }
+
+ var parts = o.replace(new RegExp('\\s+', 'g'), ' ').split(' ');
+ if (parts.length < 2) {
+ return [parts[0], ''];
+ }
+ else if (parts.length == 2) {
+ return parts;
+ }
+ else {
+ return [parts[0], parts.slice(1).join(' ')];
+ }
+ }).filter(function (e) {return e;}),
+
+ /* file storage */
+ 'storage_device': $('#storageDeviceSelect').val(),
+ 'network_server': $('#networkServerEntry').val(),
+ 'network_share_name': $('#networkShareNameEntry').val(),
+ 'network_username': $('#networkUsernameEntry').val(),
+ 'network_password': $('#networkPasswordEntry').val(),
+ 'root_directory': $('#rootDirectoryEntry').val(),
+
+ /* text overlay */
+ 'text_overlay': $('#textOverlaySwitch')[0].checked,
+ 'left_text': $('#leftTextSelect').val(),
+ 'custom_left_text': $('#leftTextEntry').val(),
+ 'right_text': $('#rightTextSelect').val(),
+ 'custom_right_text': $('#rightTextEntry').val(),
+
+ /* video streaming */
+ 'video_streaming': $('#videoStreamingSwitch')[0].checked,
+ 'streaming_framerate': $('#streamingFramerateSlider').val(),
+ 'streaming_quality': $('#streamingQualitySlider').val(),
+ 'streaming_resolution': $('#streamingResolutionSlider').val(),
+ 'streaming_server_resize': $('#streamingServerResizeSwitch')[0].checked,
+ 'streaming_port': $('#streamingPortEntry').val(),
+ 'streaming_auth_mode': $('#streamingAuthModeSelect').val() || 'disabled', /* compatibility with old motion */
+ 'streaming_motion': $('#streamingMotion')[0].checked,
+
+ /* still images */
+ 'still_images': $('#stillImagesSwitch')[0].checked,
+ 'image_file_name': $('#imageFileNameEntry').val(),
+ 'image_quality': $('#imageQualitySlider').val(),
+ 'capture_mode': $('#captureModeSelect').val(),
+ 'snapshot_interval': $('#snapshotIntervalEntry').val(),
+ 'preserve_pictures': $('#preservePicturesSelect').val() >= 0 ? $('#preservePicturesSelect').val() : $('#picturesLifetimeEntry').val(),
+
+ /* motion detection */
+ 'motion_detection': $('#motionDetectionSwitch')[0].checked,
+ 'show_frame_changes': $('#showFrameChangesSwitch')[0].checked,
+ 'frame_change_threshold': $('#frameChangeThresholdSlider').val(),
+ 'auto_noise_detect': $('#autoNoiseDetectSwitch')[0].checked,
+ 'noise_level': $('#noiseLevelSlider').val(),
+ 'event_gap': $('#eventGapEntry').val(),
+ 'pre_capture': $('#preCaptureEntry').val(),
+ 'post_capture': $('#postCaptureEntry').val(),
+ 'minimum_motion_frames': $('#minimumMotionFramesEntry').val(),
+
+ /* motion movies */
+ 'motion_movies': $('#motionMoviesSwitch')[0].checked,
+ 'movie_file_name': $('#movieFileNameEntry').val(),
+ 'movie_quality': $('#movieQualitySlider').val(),
+ 'max_movie_length': $('#maxMovieLengthEntry').val(),
+ 'preserve_movies': $('#preserveMoviesSelect').val() >= 0 ? $('#preserveMoviesSelect').val() : $('#moviesLifetimeEntry').val(),
+
+ /* motion notifications */
+ 'email_notifications_enabled': $('#emailNotificationsSwitch')[0].checked,
+ 'email_notifications_addresses': $('#emailAddressesEntry').val(),
+ 'email_notifications_smtp_server': $('#smtpServerEntry').val(),
+ 'email_notifications_smtp_port': $('#smtpPortEntry').val(),
+ 'email_notifications_smtp_account': $('#smtpAccountEntry').val(),
+ 'email_notifications_smtp_password': $('#smtpPasswordEntry').val(),
+ 'email_notifications_smtp_tls': $('#smtpTlsSwitch')[0].checked,
+ 'email_notifications_picture_time_span': $('#emailPictureTimeSpanEntry').val(),
+ 'web_hook_notifications_enabled': $('#webHookNotificationsSwitch')[0].checked,
+ 'web_hook_notifications_url': $('#webHookUrlEntry').val(),
+ 'web_hook_notifications_http_method': $('#webHookHttpMethodSelect').val(),
+ 'command_notifications_enabled': $('#commandNotificationsSwitch')[0].checked,
+ 'command_notifications_exec': $('#commandNotificationsEntry').val(),
+
+ /* working schedule */
+ 'working_schedule': $('#workingScheduleSwitch')[0].checked,
+ 'monday_from': $('#mondayEnabledSwitch')[0].checked ? $('#mondayFromEntry').val() : '',
+ 'monday_to':$('#mondayEnabledSwitch')[0].checked ? $('#mondayToEntry').val() : '',
+ 'tuesday_from': $('#tuesdayEnabledSwitch')[0].checked ? $('#tuesdayFromEntry').val() : '',
+ 'tuesday_to': $('#tuesdayEnabledSwitch')[0].checked ? $('#tuesdayToEntry').val() : '',
+ 'wednesday_from': $('#wednesdayEnabledSwitch')[0].checked ? $('#wednesdayFromEntry').val() : '',
+ 'wednesday_to': $('#wednesdayEnabledSwitch')[0].checked ? $('#wednesdayToEntry').val() : '',
+ 'thursday_from': $('#thursdayEnabledSwitch')[0].checked ? $('#thursdayFromEntry').val() : '',
+ 'thursday_to': $('#thursdayEnabledSwitch')[0].checked ? $('#thursdayToEntry').val() : '',
+ 'friday_from': $('#fridayEnabledSwitch')[0].checked ? $('#fridayFromEntry').val() : '',
+ 'friday_to': $('#fridayEnabledSwitch')[0].checked ? $('#fridayToEntry').val() :'',
+ 'saturday_from': $('#saturdayEnabledSwitch')[0].checked ? $('#saturdayFromEntry').val() : '',
+ 'saturday_to': $('#saturdayEnabledSwitch')[0].checked ? $('#saturdayToEntry').val() : '',
+ 'sunday_from': $('#sundayEnabledSwitch')[0].checked ? $('#sundayFromEntry').val() : '',
+ 'sunday_to': $('#sundayEnabledSwitch')[0].checked ? $('#sundayToEntry').val() : '',
+ 'working_schedule_type': $('#workingScheduleTypeSelect').val(),
+ };
+
+ if ($('#resolutionSelect')[0].selectedIndex != -1) {
+ dict.resolution = $('#resolutionSelect').val();
+ }
+
+ if ($('#brightnessSlider').val() !== '') {
+ dict.brightness = $('#brightnessSlider').val();
+ }
+
+ if ($('#contrastSlider').val() !== '') {
+ dict.contrast = $('#contrastSlider').val();
+ }
+
+ if ($('#saturationSlider').val() !== '') {
+ dict.saturation = $('#saturationSlider').val();
+ }
+
+ if ($('#hueSlider').val() !== '') {
+ dict.hue = $('#hueSlider').val();
+ }
+
+ /* additional sections */
+ $('input[type=checkbox].additional-section.camera-config').each(function () {
+ dict['_' + this.id.substring(0, this.id.length - 6)] = this.checked;
+ });
+
+ /* additional configs */
+ $('tr.additional-config').each(function () {
+ var $this = $(this);
+ var control = $this.find('input, select');
+
+ if (!control.hasClass('camera-config')) {
+ return;
+ }
+
+ var id = control.attr('id');
+ var name, value;
+ if (id.endsWith('Entry')) {
+ name = id.substring(0, id.length - 5);
+ value = control.val();
+ if (control.hasClass('number')) {
+ value = Number(value);
+ }
+ }
+ else if (id.endsWith('Select')) {
+ name = id.substring(0, id.length - 6);
+ value = control.val();
+ }
+ else if (id.endsWith('Slider')) {
+ name = id.substring(0, id.length - 6);
+ value = Number(control.val());
+ }
+ else if (id.endsWith('Switch')) {
+ name = id.substring(0, id.length - 6);
+ value = control[0].checked;
+ }
+
+ dict['_' + name] = value;
+ });
+
+ return dict;
+}
+
+function dict2CameraUi(dict) {
+ if (dict == null) {
+ /* errors while getting the configuration */
+
+ $('#videoDeviceSwitch')[0].error = true;
+ $('#videoDeviceSwitch')[0].checked = true; /* so that the user can explicitly disable the camera */
+ updateConfigUi();
+
+ return;
+ }
+ else {
+ $('#videoDeviceSwitch')[0].error = false;
+ }
+
+ function markHideIfNull(field, elemId) {
+ var elem = $('#' + elemId);
+ var sectionDiv = elem.parents('div.settings-section-title:eq(0)');
+ var hideNull = (field === true) || (typeof field == 'string' && dict[field] == null);
+
+ if (sectionDiv.length) { /* element is a section */
+ sectionDiv.find('div.check-box').each(function () {this._hideNull = hideNull;});
+ if (hideNull) {
+ sectionDiv.find('input[type=checkbox]').each(function () {this.checked = true;});
+ }
+ }
+ else { /* element is a config option */
+ elem.parents('tr:eq(0)').each(function () {this._hideNull = hideNull;});
+ }
+ }
+
+ /* video device */
+ var prettyType = '';
+ switch (dict['proto']) {
+ case 'v4l2':
+ prettyType = 'V4L2 Camera';
+ break;
+
+ case 'netcam':
+ prettyType = 'Network Camera';
+ break;
+
+ case 'motioneye':
+ prettyType = 'Remote motionEye Camera';
+ break;
+
+ case 'mjpeg':
+ prettyType = 'Simple MJPEG Camera';
+ break;
+ }
+
+ $('#videoDeviceSwitch')[0].checked = dict['enabled']; markHideIfNull('enabled', 'videoDeviceSwitch');
+ $('#deviceNameEntry').val(dict['name']); markHideIfNull('name', 'deviceNameEntry');
+ $('#deviceUriEntry').val(dict['device_url']); markHideIfNull('device_url', 'deviceUriEntry');
+ $('#deviceTypeEntry').val(prettyType); markHideIfNull(!prettyType, 'deviceTypeEntry');
+ $('#deviceTypeEntry')[0].proto = dict['proto'];
+ $('#lightSwitchDetectSwitch')[0].checked = dict['light_switch_detect']; markHideIfNull('light_switch_detect', 'lightSwitchDetectSwitch');
+ $('#autoBrightnessSwitch')[0].checked = dict['auto_brightness']; markHideIfNull('auto_brightness', 'autoBrightnessSwitch');
+
+ $('#brightnessSlider').val(dict['brightness']); markHideIfNull('brightness', 'brightnessSlider');
+ $('#contrastSlider').val(dict['contrast']); markHideIfNull('contrast', 'contrastSlider');
+ $('#saturationSlider').val(dict['saturation']); markHideIfNull('saturation', 'saturationSlider');
+ $('#hueSlider').val(dict['hue']); markHideIfNull('hue', 'hueSlider');
+
+ $('#resolutionSelect').html('');
+ if (dict['available_resolutions']) {
+ dict['available_resolutions'].forEach(function (resolution) {
+ $('#resolutionSelect').append('<option value="' + resolution + '">' + resolution + '</option>');
+ });
+ }
+ $('#resolutionSelect').val(dict['resolution']); markHideIfNull('available_resolutions', 'resolutionSelect');
+
+ $('#rotationSelect').val(dict['rotation']); markHideIfNull('rotation', 'rotationSelect');
+ $('#framerateSlider').val(dict['framerate']); markHideIfNull('framerate', 'framerateSlider');
+ $('#extraOptionsEntry').val(dict['extra_options'] ? (dict['extra_options'].map(function (o) {
+ return o.join(' ');
+ }).join('\r\n')) : ''); markHideIfNull('extra_options', 'extraOptionsEntry');
+
+ /* file storage */
+ $('#storageDeviceSelect').empty();
+ dict['available_disks'] = dict['available_disks'] || [];
+ var storageDeviceOptions = {'network-share': true};
+ dict['available_disks'].forEach(function (disk) {
+ disk.partitions.forEach(function (partition) {
+ var target = partition.target.replaceAll('/', '-');
+ var option = 'local-disk' + target;
+ var label = partition.vendor;
+ if (partition.model) {
+ label += ' ' + partition.model;
+ }
+ if (disk.partitions.length > 1) {
+ label += '/part' + partition.part_no;
+ }
+ label += ' (' + partition.target + ')';
+
+ storageDeviceOptions[option] = true;
+
+ $('#storageDeviceSelect').append('<option value="' + option + '">' + label + '</option>');
+ });
+ });
+ $('#storageDeviceSelect').append('<option value="custom-path">Custom Path</option>');
+ if (dict['smb_shares']) {
+ $('#storageDeviceSelect').append('<option value="network-share">Network Share</option>');
+ }
+
+ if (storageDeviceOptions[dict['storage_device']]) {
+ $('#storageDeviceSelect').val(dict['storage_device']);
+ }
+ else {
+ $('#storageDeviceSelect').val('custom-path');
+ }
+ markHideIfNull('storage_device', 'storageDeviceSelect');
+ $('#networkServerEntry').val(dict['network_server']); markHideIfNull('network_server', 'networkServerEntry');
+ $('#networkShareNameEntry').val(dict['network_share_name']); markHideIfNull('network_share_name', 'networkShareNameEntry');
+ $('#networkUsernameEntry').val(dict['network_username']); markHideIfNull('network_username', 'networkUsernameEntry');
+ $('#networkPasswordEntry').val(dict['network_password']); markHideIfNull('network_password', 'networkPasswordEntry');
+ $('#rootDirectoryEntry').val(dict['root_directory']); markHideIfNull('root_directory', 'rootDirectoryEntry');
+ var percent = 0;
+ if (dict['disk_total'] != 0) {
+ percent = parseInt(dict['disk_used'] * 100 / dict['disk_total']);
+ }
+
+ $('#diskUsageProgressBar').each(function () {
+ this.setProgress(percent);
+ this.setText((dict['disk_used'] / 1073741824).toFixed(1) + '/' + (dict['disk_total'] / 1073741824).toFixed(1) + ' GB (' + percent + '%)');
+ }); markHideIfNull('disk_used', 'diskUsageProgressBar');
+
+ /* text overlay */
+ $('#textOverlaySwitch')[0].checked = dict['text_overlay']; markHideIfNull('text_overlay', 'textOverlaySwitch');
+ $('#leftTextSelect').val(dict['left_text']); markHideIfNull('left_text', 'leftTextSelect');
+ $('#leftTextEntry').val(dict['custom_left_text']); markHideIfNull('custom_left_text', 'leftTextEntry');
+ $('#rightTextSelect').val(dict['right_text']); markHideIfNull('right_text', 'rightTextSelect');
+ $('#rightTextEntry').val(dict['custom_right_text']); markHideIfNull('custom_right_text', 'rightTextEntry');
+
+ /* video streaming */
+ $('#videoStreamingSwitch')[0].checked = dict['video_streaming']; markHideIfNull('video_streaming', 'videoStreamingSwitch');
+ $('#streamingFramerateSlider').val(dict['streaming_framerate']); markHideIfNull('streaming_framerate', 'streamingFramerateSlider');
+ $('#streamingQualitySlider').val(dict['streaming_quality']); markHideIfNull('streaming_quality', 'streamingQualitySlider');
+ $('#streamingResolutionSlider').val(dict['streaming_resolution']); markHideIfNull('streaming_resolution', 'streamingResolutionSlider');
+ $('#streamingServerResizeSwitch')[0].checked = dict['streaming_server_resize']; markHideIfNull('streaming_server_resize', 'streamingServerResizeSwitch');
+ $('#streamingPortEntry').val(dict['streaming_port']); markHideIfNull('streaming_port', 'streamingPortEntry');
+ $('#streamingAuthModeSelect').val(dict['streaming_auth_mode']); markHideIfNull('streaming_auth_mode', 'streamingAuthModeSelect');
+ $('#streamingMotion')[0].checked = dict['streaming_motion']; markHideIfNull('streaming_motion', 'streamingMotion');
+
+ var cameraUrl = location.protocol + '//' + location.host + '/picture/' + dict.id + '/';
+
+ var snapshotUrl = null;
+ var mjpgUrl = null;
+ var embedUrl = null;
+
+ if (dict['proto'] == 'mjpeg') {
+ mjpgUrl = dict['url'];
+ mjpgUrl = mjpgUrl.replace('127.0.0.1', window.location.host.split(':')[0]);
+ embedUrl = cameraUrl + 'frame/';
+ }
+ else {
+ snapshotUrl = cameraUrl + 'current/';
+ mjpgUrl = location.protocol + '//' + location.host.split(':')[0] + ':' + dict.streaming_port;
+ embedUrl = cameraUrl + 'frame/';
+ }
+
+ if (dict.proto == 'motioneye') {
+ /* cannot tell the mjpg streaming url for a remote motionEye camera */
+ mjpgUrl = '';
+ }
+
+ if ($('#normalPasswordEntry').val()) { /* anonymous access is disabled */
+ if (snapshotUrl) {
+ snapshotUrl = addAuthParams('GET', snapshotUrl);
+ }
+ }
+
+ $('#streamingSnapshotUrlEntry').val(snapshotUrl); markHideIfNull(!snapshotUrl, 'streamingSnapshotUrlEntry');
+ $('#streamingMjpgUrlEntry').val(mjpgUrl); markHideIfNull(!mjpgUrl, 'streamingMjpgUrlEntry');
+ $('#streamingEmbedUrlEntry').val(embedUrl); markHideIfNull(!embedUrl, 'streamingEmbedUrlEntry');
+
+ /* still images */
+ $('#stillImagesSwitch')[0].checked = dict['still_images']; markHideIfNull('still_images', 'stillImagesSwitch');
+ $('#imageFileNameEntry').val(dict['image_file_name']); markHideIfNull('image_file_name', 'imageFileNameEntry');
+ $('#imageQualitySlider').val(dict['image_quality']); markHideIfNull('image_quality', 'imageQualitySlider');
+ $('#captureModeSelect').val(dict['capture_mode']); markHideIfNull('capture_mode', 'captureModeSelect');
+ $('#snapshotIntervalEntry').val(dict['snapshot_interval']); markHideIfNull('snapshot_interval', 'snapshotIntervalEntry');
+ $('#preservePicturesSelect').val(dict['preserve_pictures']);
+ if ($('#preservePicturesSelect').val() == null) {
+ $('#preservePicturesSelect').val('-1');
+ }
+ markHideIfNull('preserve_pictures', 'preservePicturesSelect');
+ $('#picturesLifetimeEntry').val(dict['preserve_pictures']); markHideIfNull('preserve_pictures', 'picturesLifetimeEntry');
+
+ /* motion detection */
+ $('#motionDetectionSwitch')[0].checked = dict['motion_detection']; markHideIfNull('motion_detection', 'motionDetectionSwitch');
+ $('#showFrameChangesSwitch')[0].checked = dict['show_frame_changes']; markHideIfNull('show_frame_changes', 'showFrameChangesSwitch');
+ $('#frameChangeThresholdSlider').val(dict['frame_change_threshold']); markHideIfNull('frame_change_threshold', 'frameChangeThresholdSlider');
+ $('#autoNoiseDetectSwitch')[0].checked = dict['auto_noise_detect']; markHideIfNull('auto_noise_detect', 'autoNoiseDetectSwitch');
+ $('#noiseLevelSlider').val(dict['noise_level']); markHideIfNull('noise_level', 'noiseLevelSlider');
+ $('#eventGapEntry').val(dict['event_gap']); markHideIfNull('event_gap', 'eventGapEntry');
+ $('#preCaptureEntry').val(dict['pre_capture']); markHideIfNull('pre_capture', 'preCaptureEntry');
+ $('#postCaptureEntry').val(dict['post_capture']); markHideIfNull('post_capture', 'postCaptureEntry');
+ $('#minimumMotionFramesEntry').val(dict['minimum_motion_frames']); markHideIfNull('minimum_motion_frames', 'minimumMotionFramesEntry');
+
+ /* motion movies */
+ $('#motionMoviesSwitch')[0].checked = dict['motion_movies']; markHideIfNull('motion_movies', 'motionMoviesSwitch');
+ $('#movieFileNameEntry').val(dict['movie_file_name']); markHideIfNull('movie_file_name', 'movieFileNameEntry');
+ $('#movieQualitySlider').val(dict['movie_quality']); markHideIfNull('movie_quality', 'movieQualitySlider');
+ $('#maxMovieLengthEntry').val(dict['max_movie_length']); markHideIfNull('max_movie_length', 'maxMovieLengthEntry');
+ $('#preserveMoviesSelect').val(dict['preserve_movies']);
+ if ($('#preserveMoviesSelect').val() == null) {
+ $('#preserveMoviesSelect').val('-1');
+ }
+ markHideIfNull('preserve_movies', 'preserveMoviesSelect');
+ $('#moviesLifetimeEntry').val(dict['preserve_movies']); markHideIfNull('preserve_movies', 'moviesLifetimeEntry');
+
+ /* motion notifications */
+ $('#emailNotificationsSwitch')[0].checked = dict['email_notifications_enabled']; markHideIfNull('email_notifications_enabled', 'emailNotificationsSwitch');
+ $('#emailAddressesEntry').val(dict['email_notifications_addresses']);
+ $('#smtpServerEntry').val(dict['email_notifications_smtp_server']);
+ $('#smtpPortEntry').val(dict['email_notifications_smtp_port']);
+ $('#smtpAccountEntry').val(dict['email_notifications_smtp_account']);
+ $('#smtpPasswordEntry').val(dict['email_notifications_smtp_password']);
+ $('#smtpTlsSwitch')[0].checked = dict['email_notifications_smtp_tls'];
+ $('#emailPictureTimeSpanEntry').val(dict['email_notifications_picture_time_span']);
+ $('#webHookNotificationsSwitch')[0].checked = dict['web_hook_notifications_enabled']; markHideIfNull('web_hook_notifications_enabled', 'webHookNotificationsSwitch');
+ $('#webHookUrlEntry').val(dict['web_hook_notifications_url']);
+ $('#webHookHttpMethodSelect').val(dict['web_hook_notifications_http_method']);
+ $('#commandNotificationsSwitch')[0].checked = dict['command_notifications_enabled']; markHideIfNull('command_notifications_enabled', 'commandNotificationsSwitch');
+ $('#commandNotificationsEntry').val(dict['command_notifications_exec']);
+
+ /* working schedule */
+ $('#workingScheduleSwitch')[0].checked = dict['working_schedule']; markHideIfNull('working_schedule', 'workingScheduleSwitch');
+ $('#mondayEnabledSwitch')[0].checked = Boolean(dict['monday_from'] && dict['monday_to']); markHideIfNull('monday_from', 'mondayEnabledSwitch');
+ $('#mondayFromEntry').val(dict['monday_from']); markHideIfNull('monday_from', 'mondayFromEntry');
+ $('#mondayToEntry').val(dict['monday_to']); markHideIfNull('monday_to', 'mondayToEntry');
+
+ $('#tuesdayEnabledSwitch')[0].checked = Boolean(dict['tuesday_from'] && dict['tuesday_to']); markHideIfNull('tuesday_from', 'tuesdayEnabledSwitch');
+ $('#tuesdayFromEntry').val(dict['tuesday_from']); markHideIfNull('tuesday_from', 'tuesdayFromEntry');
+ $('#tuesdayToEntry').val(dict['tuesday_to']); markHideIfNull('tuesday_to', 'tuesdayToEntry');
+
+ $('#wednesdayEnabledSwitch')[0].checked = Boolean(dict['wednesday_from'] && dict['wednesday_to']); markHideIfNull('wednesday_from', 'wednesdayEnabledSwitch');
+ $('#wednesdayFromEntry').val(dict['wednesday_from']); markHideIfNull('wednesday_from', 'wednesdayFromEntry');
+ $('#wednesdayToEntry').val(dict['wednesday_to']); markHideIfNull('wednesday_to', 'wednesdayToEntry');
+
+ $('#thursdayEnabledSwitch')[0].checked = Boolean(dict['thursday_from'] && dict['thursday_to']); markHideIfNull('thursday_from', 'thursdayEnabledSwitch');
+ $('#thursdayFromEntry').val(dict['thursday_from']); markHideIfNull('thursday_from', 'thursdayFromEntry');
+ $('#thursdayToEntry').val(dict['thursday_to']); markHideIfNull('thursday_to', 'thursdayToEntry');
+
+ $('#fridayEnabledSwitch')[0].checked = Boolean(dict['friday_from'] && dict['friday_to']); markHideIfNull('friday_from', 'fridayEnabledSwitch');
+ $('#fridayFromEntry').val(dict['friday_from']); markHideIfNull('friday_from', 'fridayFromEntry');
+ $('#fridayToEntry').val(dict['friday_to']); markHideIfNull('friday_to', 'fridayToEntry');
+
+ $('#saturdayEnabledSwitch')[0].checked = Boolean(dict['saturday_from'] && dict['saturday_to']); markHideIfNull('saturday_from', 'saturdayEnabledSwitch');
+ $('#saturdayFromEntry').val(dict['saturday_from']); markHideIfNull('saturday_from', 'saturdayFromEntry');
+ $('#saturdayToEntry').val(dict['saturday_to']); markHideIfNull('saturday_to', 'saturdayToEntry');
+
+ $('#sundayEnabledSwitch')[0].checked = Boolean(dict['sunday_from'] && dict['sunday_to']); markHideIfNull('sunday_from', 'sundayEnabledSwitch');
+ $('#sundayFromEntry').val(dict['sunday_from']); markHideIfNull('sunday_from', 'sundayFromEntry');
+ $('#sundayToEntry').val(dict['sunday_to']); markHideIfNull('sunday_to', 'sundayToEntry');
+ $('#workingScheduleTypeSelect').val(dict['working_schedule_type']); markHideIfNull('working_schedule_type', 'workingScheduleTypeSelect');
+
+ /* additional sections */
+ $('input[type=checkbox].additional-section.main-config').each(function () {
+ var name = this.id.substring(0, this.id.length - 6);
+ this.checked = dict[name];
+ markHideIfNull(name, this.id);
+ });
+
+ /* additional configs */
+ $('tr.additional-config').each(function () {
+ var $this = $(this);
+ var control = $this.find('input, select, textarea, div.html');
+
+ if (!control.hasClass('camera-config')) {
+ return;
+ }
+
+ var id = control.attr('id');
+ var name;
+ if (id.endsWith('Entry')) {
+ name = id.substring(0, id.length - 5);
+ control.val(dict['_' + name]);
+ }
+ else if (id.endsWith('Select')) {
+ name = id.substring(0, id.length - 6);
+ control.val(dict['_' + name]);
+ }
+ else if (id.endsWith('Slider')) {
+ name = id.substring(0, id.length - 6);
+ control.val(dict['_' + name]);
+ }
+ else if (id.endsWith('Switch')) {
+ name = id.substring(0, id.length - 6);
+ control[0].checked = dict['_' + name];
+ }
+ else if (id.endsWith('Html')) {
+ name = id.substring(0, id.length - 4);
+ control.html(dict['_' + name]);
+ }
+
+ markHideIfNull('_' + name, id);
+ });
+
+ updateConfigUi();
+}
+
+
+ /* progress */
+
+function beginProgress(cameraIds) {
+ if (inProgress) {
+ return; /* already in progress */
+ }
+
+ inProgress = true;
+
+ /* replace the main page message with a progress indicator */
+ $('div.add-camera-message').replaceWith('<img class="main-loading-progress" src="' + staticUrl + 'img/main-loading-progress.gif">');
+
+ /* show the apply button progress indicator */
+ $('#applyButton').html('<img class="apply-progress" src="' + staticUrl + 'img/apply-progress.gif">');
+
+ /* show the camera progress indicators */
+ if (cameraIds) {
+ cameraIds.forEach(function (cameraId) {
+ $('div.camera-frame#camera' + cameraId + ' div.camera-progress').addClass('visible');
+ });
+ }
+ else {
+ $('div.camera-progress').addClass('visible');
+ }
+
+ /* remove the settings progress lock */
+ $('div.settings-progress').css('width', '100%').css('opacity', '0.9');
+}
+
+function endProgress() {
+ if (!inProgress) {
+ return; /* not in progress */
+ }
+
+ inProgress = false;
+
+ /* deal with the apply button */
+ if (Object.keys(pushConfigs).length === 0) {
+ hideApply();
+ }
+ else {
+ showApply();
+ }
+
+ /* hide the settings progress lock */
+ $('div.settings-progress').css('opacity', '0');
+
+ /* hide the camera progress indicator */
+ $('div.camera-progress').removeClass('visible');
+
+ setTimeout(function () {
+ $('div.settings-progress').css('width', '0px');
+ }, 500);
+}
+
+function downloadFile(uri) {
+ uri = baseUri + uri;
+
+ var url = window.location.href;
+ var parts = url.split('/');
+ url = parts.slice(0, 3).join('/') + uri;
+ url = addAuthParams('GET', url);
+
+ /* download the file by creating a temporary iframe */
+ var frame = $('<iframe style="display: none;"></iframe>');
+ frame.attr('src', url);
+ $('body').append(frame);
+}
+
+function uploadFile(uri, input, callback) {
+ if (!window.FormData) {
+ showErrorMessage("Your browser doesn't implement this function!");s
+ callback();
+ }
+
+ var formData = new FormData();
+ var files = input[0].files;
+ formData.append('files', files[0], files[0].name);
+
+ ajax('POST', uri, formData, callback);
+}
+
+
+ /* apply button */
+
+function showApply() {
+ var applyButton = $('#applyButton');
+
+ applyButton.html('Apply');
+ applyButton.css('display', 'inline-block');
+ applyButton.removeClass('progress');
+ setTimeout(function () {
+ applyButton.css('opacity', '1');
+ }, 10);
+}
+
+function hideApply() {
+ var applyButton = $('#applyButton');
+
+ applyButton.css('opacity', '0');
+ applyButton.removeClass('progress');
+
+ setTimeout(function () {
+ applyButton.css('display', 'none');
+ }, 500);
+}
+
+function isApplyVisible() {
+ var applyButton = $('#applyButton');
+
+ return applyButton.is(':visible');
+}
+
+function doApply() {
+ if (!configUiValid()) {
+ runAlertDialog('Make sure all the configuration options are valid!');
+ return;
+ }
+
+ function actualApply() {
+ /* gather the affected motion instances */
+ var affectedInstances = {};
+ Object.keys(pushConfigs).forEach(function (key) {
+ var config = pushConfigs[key];
+ if (key === 'main') {
+ return;
+ }
+
+ var instance;
+ if (config.proto == 'netcam' || config.proto == 'v4l2') {
+ instance = '';
+ }
+ else if (config.proto == 'motioneye') { /* motioneye */
+ instance = config.host || '';
+ if (config.port) {
+ instance += ':' + config.port;
+ }
+ }
+
+ affectedInstances[instance] = true;
+ });
+ affectedInstances = Object.keys(affectedInstances);
+
+ /* compute the affected camera ids */
+ var cameraIdsByInstance = getCameraIdsByInstance();
+ var affectedCameraIds = [];
+
+ affectedInstances.forEach(function (instance) {
+ affectedCameraIds = affectedCameraIds.concat(cameraIdsByInstance[instance] || []);
+ });
+
+ beginProgress(affectedCameraIds);
+ affectedCameraIds.forEach(function (cameraId) {
+ refreshDisabled[cameraId] |= 0;
+ refreshDisabled[cameraId]++;
+ });
+
+ ajax('POST', baseUri + 'config/0/set/', pushConfigs, function (data) {
+ affectedCameraIds.forEach(function (cameraId) {
+ refreshDisabled[cameraId]--;
+ });
+
+ if (data == null || data.error) {
+ endProgress();
+ showErrorMessage(data && data.error);
+ return;
+ }
+
+ if (data.reboot) {
+ var count = 0;
+ function checkServerReboot() {
+ ajax('GET', baseUri + 'config/0/get/', null,
+ function () {
+ window.location.reload(true);
+ },
+ function () {
+ if (count < 25) {
+ count += 1;
+ setTimeout(checkServerReboot, 2000);
+ }
+ else {
+ window.location.reload(true);
+ }
+ }
+ );
+ }
+
+ setTimeout(checkServerReboot, 15000);
+
+ return;
+ }
+
+ if (data.reload) {
+ window.location.reload(true);
+ return;
+ }
+
+ /* update the camera name in the device select
+ * and frame title bar */
+ Object.keys(pushConfigs).forEach(function (key) {
+ var config = pushConfigs[key];
+ if (config.key !== 'main') {
+ $('#cameraSelect').find('option[value=' + key + ']').html(config.name);
+ }
+
+ $('#camera' + key).find('span.camera-name').html(config.name);
+ });
+
+ pushConfigs = {};
+ pushConfigReboot = false;
+ endProgress();
+ recreateCameraFrames(); /* a camera could have been disabled */
+ });
+ }
+
+ if (pushConfigReboot) {
+ runConfirmDialog('This will reboot the system. Continue?', function () {
+ actualApply();
+ });
+ }
+ else {
+ actualApply();
+ }
+}
+
+function doShutDown() {
+ runConfirmDialog('Really shut down?', function () {
+ ajax('POST', baseUri + 'power/shutdown/');
+ setTimeout(function () {
+ refreshInterval = 1000000;
+ showModalDialog('<div class="modal-progress"></div>');
+
+ function checkServer() {
+ ajax('GET', baseUri, null,
+ function () {
+ setTimeout(checkServer, 1000);
+ },
+ function () {
+ showModalDialog('Powered Off');
+ setTimeout(function () {
+ $('div.modal-glass').animate({'opacity': '1', 'background-color': '#212121'}, 200);
+ },100);
+ },
+ 10000 /* timeout = 10s */
+ );
+ }
+
+ checkServer();
+ }, 10);
+ });
+}
+
+function doReboot() {
+ runConfirmDialog('Really reboot?', function () {
+ ajax('POST', baseUri + 'power/reboot/');
+ setTimeout(function () {
+ refreshInterval = 1000000;
+ showModalDialog('<div class="modal-progress"></div>');
+ var shutDown = false;
+
+ function checkServer() {
+ ajax('GET', baseUri, null,
+ function () {
+ if (!shutDown) {
+ setTimeout(checkServer, 1000);
+ }
+ else {
+ runAlertDialog('The system has been rebooted!', function () {
+ window.location.reload(true);
+ });
+ }
+ },
+ function () {
+ shutDown = true; /* the first error indicates the system was shut down */
+ setTimeout(checkServer, 1000);
+ },
+ 5 * 1000 /* timeout = 5s */
+ );
+ }
+
+ checkServer();
+ }, 10);
+ });
+}
+
+function doRemCamera() {
+ if (Object.keys(pushConfigs).length) {
+ return runAlertDialog('Please apply the modified settings first!');
+ }
+
+ var cameraId = $('#cameraSelect').val();
+ if (cameraId == null || cameraId === 'add') {
+ runAlertDialog('No camera to remove!');
+ return;
+ }
+
+ var deviceName = $('#cameraSelect').find('option[value=' + cameraId + ']').text();
+
+ runConfirmDialog('Remove camera ' + deviceName + '?', function () {
+ /* disable further refreshing of this camera */
+ var img = $('div.camera-frame#camera' + cameraId).find('img.camera');
+ if (img.length) {
+ img[0].loading = 1;
+ }
+
+ beginProgress();
+ ajax('POST', baseUri + 'config/' + cameraId + '/rem/', null, function (data) {
+ if (data == null || data.error) {
+ endProgress();
+ showErrorMessage(data && data.error);
+ return;
+ }
+
+ fetchCurrentConfig(endProgress);
+ });
+ });
+}
+
+function doUpdate() {
+ if (Object.keys(pushConfigs).length) {
+ return runAlertDialog('Please apply the modified settings first!');
+ }
+
+ showModalDialog('<div class="modal-progress"></div>');
+ ajax('GET', baseUri + 'update/', null, function (data) {
+ if (data.update_version == null) {
+ runAlertDialog('motionEye is up to date (current version: ' + data.current_version + ')');
+ }
+ else {
+ runConfirmDialog('New version available: ' + data.update_version + '. Update?', function () {
+ refreshInterval = 1000000;
+ showModalDialog('<div style="text-align: center;"><span>Updating. This may take a few minutes.</span><div class="modal-progress"></div></div>');
+ ajax('POST', baseUri + 'update/?version=' + data.update_version, null, function () {
+ var count = 0;
+ function checkServer() {
+ ajax('GET', baseUri + 'config/0/get/', null,
+ function () {
+ runAlertDialog('motionEye was successfully updated!', function () {
+ window.location.reload(true);
+ });
+ },
+ function () {
+ if (count < 60) {
+ count += 1;
+ setTimeout(checkServer, 5000);
+ }
+ else {
+ runAlertDialog('Update failed!', function () {
+ window.location.reload(true);
+ });
+ }
+ }
+ );
+ }
+
+ setTimeout(checkServer, 10000);
+
+ }, function (e) { /* error */
+ runAlertDialog('The update process has failed!', function () {
+ window.location.reload(true);
+ });
+ });
+
+ return false; /* prevents hiding the modal container */
+ });
+ }
+ });
+}
+
+function doBackup() {
+ downloadFile('config/backup/');
+}
+
+function doRestore() {
+ var content =
+ $('<table class="restore-dialog">' +
+ '<tr>' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">Backup File</span></td>' +
+ '<td class="dialog-item-value"><form><input type="file" class="styled" id="fileInput"></form></td>' +
+ '<td><span class="help-mark" title="the backup file you have previously downloaded">?</span></td>' +
+ '</tr>' +
+ '</table>');
+
+ /* collect ui widgets */
+ var fileInput = content.find('#fileInput');
+
+ /* make validators */
+ makeFileValidator(fileInput, true);
+
+ function uiValid() {
+ /* re-validate all the validators */
+ content.find('.validator').each(function () {
+ this.validate();
+ });
+
+ var valid = true;
+ var query = content.find('input, select');
+ query.each(function () {
+ if (this.invalid) {
+ valid = false;
+ return false;
+ }
+ });
+
+ return valid;
+ }
+
+ runModalDialog({
+ title: 'Restore Configuration',
+ closeButton: true,
+ buttons: 'okcancel',
+ content: content,
+ onOk: function () {
+ if (!uiValid(true)) {
+ return false;
+ }
+
+ refreshInterval = 1000000;
+
+ setTimeout(function () {
+ showModalDialog('<div style="text-align: center;"><span>Restoring configuration...</span><div class="modal-progress"></div></div>');
+ uploadFile(baseUri + 'config/restore/', fileInput, function (data) {
+ if (data && data.ok) {
+ var count = 0;
+ function checkServer() {
+ ajax('GET', baseUri + 'config/0/get/', null,
+ function () {
+ runAlertDialog('The configuration has been restored!', function () {
+ window.location.reload(true);
+ });
+ },
+ function () {
+ if (count < 25) {
+ count += 1;
+ setTimeout(checkServer, 2000);
+ }
+ else {
+ runAlertDialog('Failed to restore the configuration!', function () {
+ window.location.reload(true);
+ });
+ }
+ }
+ );
+ }
+
+ if (data.reboot) {
+ setTimeout(checkServer, 10000);
+ }
+ else {
+ setTimeout(function () {
+ window.location.reload();
+ }, 5000);
+ }
+ }
+ else {
+ hideModalDialog();
+ showErrorMessage('Failed to restore the configuration!');
+ }
+ });
+ }, 10);
+ }
+ });
+}
+
+function doDownloadZipped(cameraId, groupKey) {
+ showModalDialog('<div class="modal-progress"></div>', null, null, true);
+ ajax('GET', baseUri + 'picture/' + cameraId + '/zipped/' + groupKey + '/', null, function (data) {
+ if (data.error) {
+ hideModalDialog(); /* progress */
+ showErrorMessage(data.error);
+ }
+ else {
+ hideModalDialog(); /* progress */
+ downloadFile('picture/' + cameraId + '/zipped/' + groupKey + '/?key=' + data.key);
+ }
+ });
+}
+
+function doDeleteFile(uri, callback) {
+ var url = window.location.href;
+ var parts = url.split('/');
+ url = parts.slice(0, 3).join('/') + uri;
+
+ runConfirmDialog('Really delete this file?', function () {
+ showModalDialog('<div class="modal-progress"></div>', null, null, true);
+ ajax('POST', url, null, function (data) {
+ hideModalDialog(); /* progress */
+ hideModalDialog(); /* confirm */
+
+ if (data == null || data.error) {
+ showErrorMessage(data && data.error);
+ return;
+ }
+
+ if (callback) {
+ callback();
+ }
+ });
+
+ return false;
+ }, {stack: true});
+}
+
+function doDeleteAllFiles(mediaType, cameraId, groupKey, callback) {
+ runConfirmDialog('Really delete all ' + mediaType + 's in ' + groupKey + '?', function () {
+ showModalDialog('<div class="modal-progress"></div>', null, null, true);
+ ajax('POST', baseUri + mediaType + '/' + cameraId + '/delete_all/' + groupKey + '/', null, function (data) {
+ hideModalDialog(); /* progress */
+ hideModalDialog(); /* confirm */
+
+ if (data == null || data.error) {
+ showErrorMessage(data && data.error);
+ return;
+ }
+
+ if (callback) {
+ callback();
+ }
+ });
+
+ return false;
+ }, {stack: true});
+}
+
+
+ /* fetch & push */
+
+function fetchCurrentConfig(onFetch) {
+ function fetchCameraList() {
+ /* fetch the camera list */
+ ajax('GET', baseUri + 'config/list/', null, function (data) {
+ if (data == null || data.error) {
+ showErrorMessage(data && data.error);
+ data = {cameras: []};
+ if (onFetch) {
+ onFetch(null);
+ }
+ }
+
+ initialConfigFetched = true;
+
+ var i, cameras = data.cameras;
+
+ if (isAdmin()) {
+ var cameraSelect = $('#cameraSelect');
+ cameraSelect.html('');
+ for (i = 0; i < cameras.length; i++) {
+ var camera = cameras[i];
+ cameraSelect.append('<option value="' + camera['id'] + '">' + camera['name'] + '</option>');
+ }
+ cameraSelect.append('<option value="add">add camera...</option>');
+
+ var enabledCameras = cameras.filter(function (camera) {return camera['enabled'];});
+ if (enabledCameras.length > 0) { /* prefer the first enabled camera */
+ cameraSelect[0].selectedIndex = cameras.indexOf(enabledCameras[0]);
+ fetchCurrentCameraConfig(onFetch);
+ }
+ else if (cameras.length) { /* only disabled cameras */
+ cameraSelect[0].selectedIndex = 0;
+ fetchCurrentCameraConfig(onFetch);
+ }
+ else { /* no camera at all */
+ cameraSelect[0].selectedIndex = -1;
+
+ if (onFetch) {
+ onFetch(data);
+ }
+ }
+
+ updateConfigUi();
+ }
+ else { /* normal user */
+ if (!cameras.length) {
+ /* normal user with no cameras doesn't make too much sense - force login */
+ doLogout();
+ }
+
+ if (onFetch) {
+ onFetch(data);
+ }
+ }
+
+ var mainLoadingProgressImg = $('img.main-loading-progress');
+ if (mainLoadingProgressImg.length) {
+ mainLoadingProgressImg.animate({'opacity': 0}, 200, function () {
+ recreateCameraFrames(cameras);
+ mainLoadingProgressImg.remove();
+ });
+ }
+ else {
+ recreateCameraFrames(cameras);
+ }
+ });
+ }
+
+ /* add a progress indicator */
+ $('div.page-container').append('<img class="main-loading-progress" src="' + staticUrl + 'img/main-loading-progress.gif">');
+
+ if (isAdmin()) {
+ /* fetch the main configuration */
+ ajax('GET', baseUri + 'config/main/get/', null, function (data) {
+ if (data == null || data.error) {
+ showErrorMessage(data && data.error);
+ return;
+ }
+
+ dict2MainUi(data);
+ fetchCameraList();
+ });
+ }
+ else {
+ fetchCameraList();
+ }
+}
+
+function fetchCurrentCameraConfig(onFetch) {
+ var cameraId = $('#cameraSelect').val();
+ if (cameraId != null) {
+ ajax('GET', baseUri + 'config/' + cameraId + '/get/?force=true', null, function (data) {
+ if (data == null || data.error) {
+ showErrorMessage(data && data.error);
+ dict2CameraUi(null);
+ if (onFetch) {
+ onFetch(null);
+ }
+
+ return;
+ }
+
+ dict2CameraUi(data);
+ if (onFetch) {
+ onFetch(data);
+ }
+ });
+ }
+ else {
+ dict2CameraUi({});
+ if (onFetch) {
+ onFetch({});
+ }
+ }
+}
+
+function pushMainConfig(reboot) {
+ if (!initialConfigFetched) {
+ return;
+ }
+
+ var mainConfig = mainUi2Dict();
+
+ pushConfigReboot = pushConfigReboot || reboot;
+ pushConfigs['main'] = mainConfig;
+ if (!isApplyVisible()) {
+ showApply();
+ }
+}
+
+function pushCameraConfig(reboot) {
+ if (!initialConfigFetched) {
+ return;
+ }
+
+ var cameraId = $('#cameraSelect').val();
+ if (!cameraId) {
+ return; /* event triggered without a selected camera */
+ }
+
+ var cameraConfig = cameraUi2Dict();
+
+ pushConfigReboot = pushConfigReboot || reboot;
+ pushConfigs[cameraId] = cameraConfig;
+ if (!isApplyVisible()) {
+ showApply();
+ }
+
+ /* also update the config stored in the camera frame div */
+ var cameraFrame = $('div.camera-frame#camera' + cameraId);
+ if (cameraFrame.length) {
+ Object.update(cameraFrame[0].config, cameraConfig);
+ }
+}
+
+function pushPreview(control) {
+ var cameraId = $('#cameraSelect').val();
+
+ var brightness = $('#brightnessSlider').val();
+ var contrast= $('#contrastSlider').val();
+ var saturation = $('#saturationSlider').val();
+ var hue = $('#hueSlider').val();
+
+ var data = {};
+
+ if (brightness !== '' && (!control || control == 'brightness')) {
+ data.brightness = brightness;
+ }
+
+ if (contrast !== '' && (!control || control == 'contrast')) {
+ data.contrast = contrast;
+ }
+
+ if (saturation !== '' && (!control || control == 'saturation')) {
+ data.saturation = saturation;
+ }
+
+ if (hue !== '' && (!control || control == 'hue')) {
+ data.hue = hue;
+ }
+
+ refreshDisabled[cameraId] |= 0;
+ refreshDisabled[cameraId]++;
+
+ ajax('POST', baseUri + 'config/' + cameraId + '/set_preview/', data, function (data) {
+ refreshDisabled[cameraId]--;
+
+ if (data == null || data.error) {
+ showErrorMessage(data && data.error);
+ return;
+ }
+ });
+}
+
+function getCameraIdsByInstance() {
+ /* a motion instance is identified by the (host, port) pair;
+ * the local instance has both the host and the port set to empty string */
+
+ var cameraIdsByInstance = {};
+ $('div.camera-frame').each(function () {
+ var instance;
+ if (this.config.proto == 'netcam' || this.config.proto == 'v4l2') {
+ instance = '';
+ }
+ else if (this.config.proto == 'motioneye') {
+ instance = this.config.host || '';
+ if (this.config.port) {
+ instance += ':' + this.config.port;
+ }
+ }
+ else { /* assuming simple mjpeg camera */
+ return;
+ }
+
+ (cameraIdsByInstance[instance] = cameraIdsByInstance[instance] || []).push(this.config.id);
+ });
+
+ return cameraIdsByInstance;
+}
+
+
+ /* dialogs */
+
+function runAlertDialog(message, onOk, options) {
+ var params = {
+ title: message,
+ buttons: 'ok',
+ onOk: onOk
+ };
+
+ if (options) {
+ Object.update(params, options);
+ }
+
+ runModalDialog(params);
+}
+
+function runConfirmDialog(message, onYes, options) {
+ var params = {
+ title: message,
+ buttons: 'yesno',
+ onYes: onYes
+ };
+
+ if (options) {
+ Object.update(params, options);
+ }
+
+ runModalDialog(params);
+}
+
+function runLoginDialog(retry) {
+ /* a workaround so that browsers will remember the credentials */
+ var tempFrame = $('<iframe name="temp" id="temp" style="display: none;"></iframe>');
+ $('body').append(tempFrame);
+
+ var form =
+ $('<form action="' + baseUri + 'login/" target="temp" method="POST"><table class="login-dialog">' +
+ '<tr>' +
+ '<td class="login-dialog-error" colspan="100"></td>' +
+ '</tr>' +
+ '<tr>' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">Username</span></td>' +
+ '<td class="dialog-item-value"><input type="text" name="username" class="styled" id="usernameEntry"></td>' +
+ '</tr>' +
+ '<tr>' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">Password</span></td>' +
+ '<td class="dialog-item-value"><input type="password" name="password" class="styled" id="passwordEntry"></td>' +
+ '<input type="submit" style="display: none;" name="login" value="login">' +
+ '</tr>' +
+ '</table></form>');
+
+ var usernameEntry = form.find('#usernameEntry');
+ var passwordEntry = form.find('#passwordEntry');
+ var errorTd = form.find('td.login-dialog-error');
+
+ if (window._loginRetry) {
+ errorTd.css('display', 'table-cell');
+ errorTd.html('Invalid credentials.');
+ }
+
+ var params = {
+ title: 'Login',
+ content: form,
+ buttons: [
+ {caption: 'Cancel', isCancel: true, click: function () {
+ tempFrame.remove();
+ }},
+ {caption: 'Login', isDefault: true, click: function () {
+ window.username = usernameEntry.val();
+ window.password = passwordEntry.val();
+ window._loginDialogSubmitted = true;
+
+ setCookie('username', window.username);
+
+ form.submit();
+ setTimeout(function () {
+ tempFrame.remove();
+ }, 5000);
+
+ if (retry) {
+ retry();
+ }
+ }}
+ ],
+ };
+
+ runModalDialog(params);
+}
+
+function runPictureDialog(entries, pos, mediaType) {
+ var content = $('<div class="picture-dialog-content"></div>');
+
+ var img = $('<img class="picture-dialog-content">');
+ content.append(img);
+
+ var prevArrow = $('<div class="picture-dialog-prev-arrow button mouse-effect" title="previous picture"></div>');
+ content.append(prevArrow);
+
+ var nextArrow = $('<div class="picture-dialog-next-arrow button mouse-effect" title="next picture"></div>');
+ content.append(nextArrow);
+
+ var progressImg = $('<img class="picture-dialog-progress" src="' + staticUrl + 'img/modal-progress.gif">');
+
+ function updatePicture() {
+ var entry = entries[pos];
+
+ var windowWidth = $(window).width();
+ var windowHeight = $(window).height();
+ var widthCoef = windowWidth < 1000 ? 0.8 : 0.5;
+ var heightCoef = 0.75;
+
+ var width = parseInt(windowWidth * widthCoef);
+ var height = parseInt(windowHeight * heightCoef);
+
+ prevArrow.css('display', 'none');
+ nextArrow.css('display', 'none');
+ img.parent().append(progressImg);
+ updateModalDialogPosition();
+ progressImg.css('left', (img.parent().width() - progressImg.width()) / 2);
+ progressImg.css('top', (img.parent().height() - progressImg.height()) / 2);
+
+ img.attr('src', addAuthParams('GET', baseUri + mediaType + '/' + entry.cameraId + '/preview' + entry.path));
+ img.load(function () {
+ var aspectRatio = this.naturalWidth / this.naturalHeight;
+ var sizeWidth = width * width / aspectRatio;
+ var sizeHeight = height * aspectRatio * height;
+
+ if (sizeWidth < sizeHeight) {
+ img.width(width);
+ }
+ else {
+ img.height(height);
+ }
+ updateModalDialogPosition();
+ prevArrow.css('display', pos > 0 ? '' : 'none');
+ nextArrow.css('display', pos < entries.length - 1 ? '' : 'none');
+ progressImg.remove();
+ });
+
+ $('div.modal-container').find('span.modal-title:last').html(entry.name);
+ updateModalDialogPosition();
+ }
+
+ prevArrow.click(function () {
+ if (pos > 0) {
+ pos--;
+ }
+
+ updatePicture();
+ });
+
+ nextArrow.click(function () {
+ if (pos < entries.length - 1) {
+ pos++;
+ }
+
+ updatePicture();
+ });
+
+ function bodyKeyDown(e) {
+ switch (e.which) {
+ case 37:
+ if (prevArrow.is(':visible')) {
+ prevArrow.click();
+ }
+ break;
+
+ case 39:
+ if (nextArrow.is(':visible')) {
+ nextArrow.click();
+ }
+ break;
+ }
+ }
+
+ $('body').on('keydown', bodyKeyDown);
+
+ img.load(updateModalDialogPosition);
+
+ runModalDialog({
+ title: ' ',
+ closeButton: true,
+ buttons: [
+ {caption: 'Close'},
+ {caption: 'Download', isDefault: true, click: function () {
+ var entry = entries[pos];
+ downloadFile(mediaType + '/' + entry.cameraId + '/download' + entry.path);
+
+ return false;
+ }}
+ ],
+ content: content,
+ stack: true,
+ onShow: updatePicture,
+ onClose: function () {
+ $('body').off('keydown', bodyKeyDown);
+ }
+ });
+}
+
+function runAddCameraDialog() {
+ if (!$('#motionEyeSwitch')[0].checked) {
+ return runAlertDialog('Please enable motionEye first!');
+ }
+
+ if (Object.keys(pushConfigs).length) {
+ return runAlertDialog('Please apply the modified settings first!');
+ }
+
+ var content =
+ $('<table class="add-camera-dialog">' +
+ '<tr>' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">Camera Type</span></td>' +
+ '<td class="dialog-item-value"><select class="styled" id="typeSelect">' +
+ '<option value="v4l2">Local Camera</option>' +
+ '<option value="netcam">Network Camera</option>' +
+ '<option value="motioneye">Remote motionEye Camera</option>' +
+ '<option value="mjpeg">Simple MJPEG Camera</option>' +
+ '</select></td>' +
+ '<td><span class="help-mark" title="the type of camera you wish to add">?</span></td>' +
+ '</tr>' +
+ '<tr class="motioneye netcam mjpeg">' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">URL</span></td>' +
+ '<td class="dialog-item-value"><input type="text" class="styled" id="urlEntry" placeholder="http://example.com:8080/cams/..."></td>' +
+ '<td><span class="help-mark" title="the camera URL (e.g. http://example.com:8080/cam/)">?</span></td>' +
+ '</tr>' +
+ '<tr class="motioneye netcam mjpeg">' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">Username</span></td>' +
+ '<td class="dialog-item-value"><input type="text" class="styled" id="usernameEntry" placeholder="username..."></td>' +
+ '<td><span class="help-mark" title="the username for the URL, if required (e.g. admin)">?</span></td>' +
+ '</tr>' +
+ '<tr class="motioneye netcam mjpeg">' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">Password</span></td>' +
+ '<td class="dialog-item-value"><input type="password" class="styled" id="passwordEntry" placeholder="password..."></td>' +
+ '<td><span class="help-mark" title="the password for the URL, if required">?</span></td>' +
+ '</tr>' +
+ '<tr class="v4l2 motioneye netcam mjpeg">' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">Camera</span></td>' +
+ '<td class="dialog-item-value"><select class="styled" id="addCameraSelect"></select><span id="cameraMsgLabel"></span></td>' +
+ '<td><span class="help-mark" title="the camera you wish to add">?</span></td>' +
+ '</tr>' +
+ '<tr class="v4l2 motioneye netcam mjpeg">' +
+ '<td colspan="100"><div class="dialog-item-separator"></div></td>' +
+ '</tr>' +
+ '<tr class="v4l2 motioneye netcam mjpeg">' +
+ '<td class="dialog-item-value" colspan="100"><div id="addCameraInfo"></div></td>' +
+ '</tr>' +
+ '</table>');
+
+ /* collect ui widgets */
+ var typeSelect = content.find('#typeSelect');
+ var urlEntry = content.find('#urlEntry');
+ var usernameEntry = content.find('#usernameEntry');
+ var passwordEntry = content.find('#passwordEntry');
+ var addCameraSelect = content.find('#addCameraSelect');
+ var addCameraInfo = content.find('#addCameraInfo');
+ var cameraMsgLabel = content.find('#cameraMsgLabel');
+
+ /* make validators */
+ makeUrlValidator(urlEntry, true);
+ makeTextValidator(usernameEntry, false);
+ makeTextValidator(typeSelect, false);
+ makeComboValidator(addCameraSelect, true);
+
+ /* ui interaction */
+ function updateUi() {
+ content.find('tr.v4l2, tr.motioneye, tr.netcam, tr.mjpeg').css('display', 'none');
+
+ if (typeSelect.val() == 'motioneye') {
+ content.find('tr.motioneye').css('display', 'table-row');
+ usernameEntry.val('admin');
+ usernameEntry.attr('readonly', 'readonly');
+ addCameraInfo.html(
+ 'Remote motionEye cameras are cameras installed behind another motionEye server. ' +
+ 'Adding them here will allow you to view and manage them remotely.');
+ }
+ else if (typeSelect.val() == 'netcam') {
+ usernameEntry.removeAttr('readonly');
+
+ /* make sure there is one trailing slash so that
+ * an URI can be detected */
+ var url = urlEntry.val().trim();
+ var m = url.match(new RegExp('/', 'g'));
+ if (m && m.length < 3 && !url.endsWith('/')) {
+ urlEntry.val(url + '/');
+ }
+
+ content.find('tr.netcam').css('display', 'table-row');
+ addCameraInfo.html(
+ 'Network cameras (or IP cameras) are devices that natively stream RTSP or MJPEG videos or plain JPEG images. ' +
+ "Consult your device's manual to find out the correct RTSP, MJPEG or JPEG URL.");
+ }
+ else if (typeSelect.val() == 'mjpeg') {
+ usernameEntry.removeAttr('readonly');
+
+ /* make sure there is one trailing slash so that
+ * an URI can be detected */
+ var url = urlEntry.val().trim();
+ var m = url.match(new RegExp('/', 'g'));
+ if (m && m.length < 3 && !url.endsWith('/')) {
+ urlEntry.val(url + '/');
+ }
+
+ content.find('tr.mjpeg').css('display', 'table-row');
+ addCameraInfo.html(
+ 'Adding your device as a simple MJPEG camera instead of as a network camera will improve the framerate, ' +
+ 'but no motion detection, picture capturing or movie recording will be available for it. ' +
+ 'The camera must be accessible to both your server and your browser. ' +
+ 'This type of camera is not compatible with Internet Explorer.');
+ }
+ else { /* assuming v4l2 */
+ content.find('tr.v4l2').css('display', 'table-row');
+ addCameraInfo.html(
+ 'Local cameras are camera devices that are connected directly to your motionEye system. ' +
+ 'These are usually USB webcams or board-specific cameras.');
+ }
+
+ updateModalDialogPosition();
+
+ /* re-validate all the validators */
+ content.find('.validator').each(function () {
+ this.validate();
+ });
+
+ if (uiValid()) {
+ listCameras();
+ }
+ }
+
+ function uiValid(includeCameraSelect) {
+ var query = content.find('input, select');
+ if (!includeCameraSelect) {
+ query = query.not('#addCameraSelect');
+ }
+ else {
+ if (cameraMsgLabel.html() || !addCameraSelect.val()) {
+ return false;
+ }
+ }
+
+ /* re-validate all the validators */
+ content.find('.validator').each(function () {
+ this.validate();
+ });
+
+ var valid = true;
+ query.each(function () {
+ if (this.invalid) {
+ valid = false;
+ return false;
+ }
+ });
+
+ return valid;
+ }
+
+ function splitCameraUrl(url) {
+ var parts = url.split('://');
+ var scheme = parts[0];
+ var index = parts[1].indexOf('/');
+ var host = null;
+ var uri = '';
+ if (index >= 0) {
+ host = parts[1].substring(0, index);
+ uri = parts[1].substring(index);
+ }
+ else {
+ host = parts[1];
+ }
+
+ var port = '';
+ parts = host.split(':');
+ if (parts.length >= 2) {
+ host = parts[0];
+ port = parts[1];
+ }
+
+ if (uri == '') {
+ uri = '/';
+ }
+
+ return {
+ scheme: scheme,
+ host: host,
+ port: port,
+ uri: uri
+ };
+ }
+
+ function listCameras() {
+ var progress = $('<div style="text-align: center; margin: 2px;"><img src="' + staticUrl + 'img/small-progress.gif"></div>');
+
+ addCameraSelect.html('');
+ addCameraSelect.hide();
+ addCameraSelect.parent().find('div').remove(); /* remove any previous progress div */
+ addCameraSelect.before(progress);
+
+ var data = {};
+ if (urlEntry.is(':visible') && urlEntry.val()) {
+ data = splitCameraUrl(urlEntry.val());
+ }
+ data.username = usernameEntry.val();
+ data.password = passwordEntry.val();
+ data.proto = typeSelect.val();
+
+ cameraMsgLabel.html('');
+
+ ajax('GET', baseUri + 'config/list/', data, function (data) {
+ progress.remove();
+
+ if (data == null || data.error) {
+ cameraMsgLabel.html(data && data.error);
+
+ return;
+ }
+
+ if (data.error || !data.cameras) {
+ return;
+ }
+
+ data.cameras.forEach(function (info) {
+ var option = $('<option value="' + info.id + '">' + info.name + '</option>');
+ option[0]._extra_attrs = {};
+ Object.keys(info).forEach(function (key) {
+ if (key == 'id' || key == 'name') {
+ return;
+ }
+
+ var value = info[key];
+ option[0]._extra_attrs[key] = value;
+ });
+
+ addCameraSelect.append(option);
+ });
+
+ if (!data.cameras || !data.cameras.length) {
+ addCameraSelect.append('<option value="">(no cameras)</option>');
+ }
+
+ addCameraSelect.show();
+ addCameraSelect[0].validate();
+ });
+ }
+
+ typeSelect.change(function () {
+ addCameraSelect.html('');
+ });
+
+ typeSelect.change(updateUi);
+ urlEntry.change(updateUi);
+ usernameEntry.change(updateUi);
+ passwordEntry.change(updateUi);
+ updateUi();
+
+ runModalDialog({
+ title: 'Add Camera...',
+ closeButton: true,
+ buttons: 'okcancel',
+ content: content,
+ onOk: function () {
+ if (!uiValid(true)) {
+ return false;
+ }
+
+ var data = {};
+
+ if (typeSelect.val() == 'motioneye') {
+ data = splitCameraUrl(urlEntry.val());
+ data.proto = 'motioneye';
+ data.username = usernameEntry.val();
+ data.password = passwordEntry.val();
+ data.remote_camera_id = addCameraSelect.val();
+ }
+ else if (typeSelect.val() == 'netcam') {
+ data = splitCameraUrl(urlEntry.val());
+ data.username = usernameEntry.val();
+ data.password = passwordEntry.val();
+ data.proto = 'netcam';
+ data.camera_index = addCameraSelect.val();
+ }
+ else if (typeSelect.val() == 'mjpeg') {
+ data = splitCameraUrl(urlEntry.val());
+ data.username = usernameEntry.val();
+ data.password = passwordEntry.val();
+ data.proto = 'mjpeg';
+ }
+ else { /* assuming v4l2 */
+ data.proto = 'v4l2';
+ data.uri = addCameraSelect.val();
+ }
+
+ /* add all extra attributes */
+ var option = addCameraSelect.find('option:eq(' + addCameraSelect[0].selectedIndex + ')')[0];
+ Object.keys(option._extra_attrs).forEach(function (key) {
+ var value = option._extra_attrs[key];
+ data[key] = value;
+ });
+
+ beginProgress();
+ ajax('POST', baseUri + 'config/add/', data, function (data) {
+ endProgress();
+
+ if (data == null || data.error) {
+ showErrorMessage(data && data.error);
+ return;
+ }
+
+ var cameraOption = $('#cameraSelect').find('option[value=add]');
+ cameraOption.before('<option value="' + data.id + '">' + data.name + '</option>');
+ $('#cameraSelect').val(data.id).change();
+ recreateCameraFrames();
+ });
+ }
+ });
+}
+
+function runTimelapseDialog(cameraId, groupKey, group) {
+ var content =
+ $('<table class="timelapse-dialog">' +
+ '<tr><td colspan="2" class="timelapse-warning"></td></tr>' +
+ '<tr>' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">Group</span></td>' +
+ '<td class="dialog-item-value">' + groupKey + '</td>' +
+ '</tr>' +
+ '<tr>' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">Include a picture taken every</span></td>' +
+ '<td class="dialog-item-value">' +
+ '<select class="styled timelapse" id="intervalSelect">' +
+ '<option value="1">second</option>' +
+ '<option value="5">5 seconds</option>' +
+ '<option value="10">10 seconds</option>' +
+ '<option value="30">30 seconds</option>' +
+ '<option value="60">minute</option>' +
+ '<option value="300">5 minutes</option>' +
+ '<option value="600">10 minutes</option>' +
+ '<option value="1800">30 minutes</option>' +
+ '<option value="3600">hour</option>' +
+ '</select>' +
+ '</td>' +
+ '<td><span class="help-mark" title="choose the interval of time between two selected pictures">?</span></td>' +
+ '</tr>' +
+ '<tr>' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">Movie framerate</span></td>' +
+ '<td class="dialog-item-value"><input type="text" class="styled range" id="framerateSlider"></td>' +
+ '<td><span class="help-mark" title="choose how fast you want the timelapse playback to be">?</span></td>' +
+ '</tr>' +
+ '</table>');
+
+ var intervalSelect = content.find('#intervalSelect');
+ var framerateSlider = content.find('#framerateSlider');
+ var timelapseWarning = content.find('td.timelapse-warning');
+
+ if (group.length > 1440) { /* one day worth of pictures, taken 1 minute apart */
+ timelapseWarning.html('Given the large number of pictures, creating your timelapse might take a while!');
+ timelapseWarning.css('display', 'table-cell');
+ }
+
+ makeSlider(framerateSlider, 1, 100, 0, [
+ {value: 1, label: '1'},
+ {value: 20, label: '20'},
+ {value: 40, label: '40'},
+ {value: 60, label: '60'},
+ {value: 80, label: '80'},
+ {value: 100, label: '100'}
+ ], null, 0);
+
+ intervalSelect.val(60);
+ framerateSlider.val(20).each(function () {this.update()});
+
+ runModalDialog({
+ title: 'Create Timelapse Movie',
+ closeButton: true,
+ buttons: 'okcancel',
+ content: content,
+ onOk: function () {
+ var progressBar = $('<div style=""></div>');
+ makeProgressBar(progressBar);
+
+ runModalDialog({
+ title: 'Creating Timelapse Movie...',
+ content: progressBar,
+ stack: true,
+ noKeys: true
+ });
+
+ var url = baseUri + 'picture/' + cameraId + '/timelapse/' + groupKey + '/';
+ var data = {interval: intervalSelect.val(), framerate: framerateSlider.val()};
+ var first = true;
+
+ function checkTimelapse() {
+ var actualUrl = url;
+ if (!first) {
+ actualUrl += '?check=true';
+ }
+
+ ajax('GET', actualUrl, data, function (data) {
+ if (data == null || data.error) {
+ hideModalDialog(); /* progress */
+ hideModalDialog(); /* timelapse dialog */
+ showErrorMessage(data && data.error);
+ return;
+ }
+
+ if (data.progress != -1 && first) {
+ showPopupMessage('A timelapse movie is already being created.');
+ }
+
+ if (data.progress == -1 && !first && !data.key) {
+ hideModalDialog(); /* progress */
+ hideModalDialog(); /* timelapse dialog */
+ showErrorMessage('The timelapse movie could not be created.');
+ return;
+ }
+
+ if (data.progress == -1) {
+ data.progress = 0;
+ }
+
+ if (data.key) {
+ progressBar[0].setProgress(100);
+ progressBar[0].setText('100%');
+
+ setTimeout(function () {
+ hideModalDialog(); /* progress */
+ hideModalDialog(); /* timelapse dialog */
+ downloadFile('picture/' + cameraId + '/timelapse/' + groupKey + '/?key=' + data.key);
+ }, 500);
+ }
+ else {
+ progressBar[0].setProgress(data.progress * 100);
+ progressBar[0].setText(parseInt(data.progress * 100) + '%');
+ setTimeout(checkTimelapse, 1000);
+ }
+
+ first = false;
+ });
+ }
+
+ checkTimelapse();
+
+ return false;
+ },
+ stack: true
+ });
+}
+
+function runMediaDialog(cameraId, mediaType) {
+ var dialogDiv = $('<div class="media-dialog"></div>');
+ var mediaListDiv = $('<div class="media-dialog-list"></div>');
+ var groupsDiv = $('<div class="media-dialog-groups"></div>');
+ var buttonsDiv = $('<div class="media-dialog-buttons"></div>');
+
+ var groups = {};
+ var groupKey = null;
+
+ dialogDiv.append(groupsDiv);
+ dialogDiv.append(mediaListDiv);
+ dialogDiv.append(buttonsDiv);
+
+ /* add a temporary div to compute 3em in px */
+ var tempDiv = $('<div style="width: 3em; height: 3em;"></div>');
+ $('div.modal-container').append(tempDiv);
+ var height = tempDiv.height();
+ tempDiv.remove();
+
+ function showGroup(key) {
+ groupKey = key;
+
+ if (mediaListDiv.find('img.media-list-progress').length) {
+ return; /* already in progress of loading */
+ }
+
+ /* (re)set the current state of the group buttons */
+ groupsDiv.find('div.media-dialog-group-button').each(function () {
+ var $this = $(this);
+ if (this.key == key) {
+ $this.addClass('current');
+ }
+ else {
+ $this.removeClass('current');
+ }
+ });
+
+ var mediaListByName = {};
+ var entries = groups[key];
+
+ /* cleanup the media list */
+ mediaListDiv.children('div.media-list-entry').detach();
+ mediaListDiv.html('');
+
+ function addEntries() {
+ /* add the entries to the media list */
+ entries.forEach(function (entry, i) {
+ var entryDiv = entry.div;
+ var detailsDiv = null;
+
+ if (!entryDiv) {
+ entryDiv = $('<div class="media-list-entry"></div>');
+
+ var previewImg = $('<img class="media-list-preview" src="' + staticUrl + 'img/modal-progress.gif"/>');
+ entryDiv.append(previewImg);
+ previewImg[0]._src = addAuthParams('GET', baseUri + mediaType + '/' + cameraId + '/preview' + entry.path + '?height=' + height);
+
+ var downloadButton = $('<div class="media-list-download-button button">Download</div>');
+ entryDiv.append(downloadButton);
+
+ var deleteButton = $('<div class="media-list-delete-button button">Delete</div>');
+ if (isAdmin()) {
+ entryDiv.append(deleteButton);
+ }
+
+ var nameDiv = $('<div class="media-list-entry-name">' + entry.name + '</div>');
+ entryDiv.append(nameDiv);
+
+ detailsDiv = $('<div class="media-list-entry-details"></div>');
+ entryDiv.append(detailsDiv);
+
+ downloadButton.click(function () {
+ downloadFile(mediaType + '/' + cameraId + '/download' + entry.path);
+ return false;
+ });
+
+ deleteButton.click(function () {
+ doDeleteFile(baseUri + mediaType + '/' + cameraId + '/delete' + entry.path, function () {
+ entryDiv.remove();
+ entries.splice(i, 1); /* remove entry from group */
+
+ /* update text on group button */
+ groupsDiv.find('div.media-dialog-group-button').each(function () {
+ var $this = $(this);
+ if (this.key == groupKey) {
+ var text = this.innerHTML;
+ text = text.substring(0, text.lastIndexOf(' '));
+ text += ' (' + entries.length + ')';
+ this.innerHTML = text;
+ }
+ });
+ });
+
+ return false;
+ });
+
+ entryDiv.click(function () {
+ var pos = entries.indexOf(entry);
+ runPictureDialog(entries, pos, mediaType);
+ });
+
+ entry.div = entryDiv;
+ }
+ else {
+ detailsDiv = entry.div.find('div.media-list-entry-details');
+ }
+
+ var momentSpan = $('<span class="details-moment">' + entry.momentStr + ', </span>');
+ var momentShortSpan = $('<span class="details-moment-short">' + entry.momentStrShort + '</span>');
+ var sizeSpan = $('<span class="details-size">' + entry.sizeStr + '</span>');
+ detailsDiv.empty();
+ detailsDiv.append(momentSpan);
+ detailsDiv.append(momentShortSpan);
+ detailsDiv.append(sizeSpan);
+ mediaListDiv.append(entryDiv);
+ });
+
+ /* trigger a scroll event */
+ mediaListDiv.scroll();
+ }
+
+ /* if details are already fetched, simply add the entries and return */
+ if (entries[0].timestamp) {
+ return addEntries();
+ }
+
+ var previewImg = $('<img class="media-list-progress" src="' + staticUrl + 'img/modal-progress.gif"/>');
+ mediaListDiv.append(previewImg);
+
+ var url = baseUri + mediaType + '/' + cameraId + '/list/?prefix=' + (key || 'ungrouped');
+ ajax('GET', url, null, function (data) {
+ previewImg.remove();
+
+ if (data == null || data.error) {
+ hideModalDialog();
+ showErrorMessage(data && data.error);
+ return;
+ }
+
+ /* index the media list by name */
+ data.mediaList.forEach(function (media) {
+ var path = media.path;
+ var parts = path.split('/');
+ var name = parts[parts.length - 1];
+
+ mediaListByName[name] = media;
+ });
+
+ /* assign details to entries */
+ entries.forEach(function (entry) {
+ var media = mediaListByName[entry.name];
+ if (media) {
+ entry.momentStr = media.momentStr;
+ entry.momentStrShort = media.momentStrShort;
+ entry.sizeStr = media.sizeStr;
+ entry.timestamp = media.timestamp;
+ }
+ });
+
+ /* sort the entries by timestamp */
+ entries.sortKey(function (e) {return e.timestamp || e.name;}, true);
+
+ addEntries();
+ });
+ }
+
+ if (mediaType == 'picture') {
+ var zippedButton = $('<div class="media-dialog-button">Zipped</div>');
+ buttonsDiv.append(zippedButton);
+
+ zippedButton.click(function () {
+ if (groupKey != null) {
+ doDownloadZipped(cameraId, groupKey);
+ }
+ });
+
+ var timelapseButton = $('<div class="media-dialog-button">Timelapse</div>');
+ buttonsDiv.append(timelapseButton);
+
+ timelapseButton.click(function () {
+ if (groupKey != null) {
+ runTimelapseDialog(cameraId, groupKey, groups[groupKey]);
+ }
+ });
+ }
+
+ if (isAdmin()) {
+ var deleteAllButton = $('<div class="media-dialog-button media-dialog-delete-all-button">Delete All</div>');
+ buttonsDiv.append(deleteAllButton);
+
+ deleteAllButton.click(function () {
+ if (groupKey != null) {
+ doDeleteAllFiles(mediaType, cameraId, groupKey, function () {
+ /* delete th group button */
+ groupsDiv.find('div.media-dialog-group-button').each(function () {
+ var $this = $(this);
+ if (this.key == groupKey) {
+ $this.remove();
+ }
+ });
+
+ /* delete the group itself */
+ delete groups[groupKey];
+
+ /* show the first existing group, if any */
+ var keys = Object.keys(groups);
+ if (keys.length) {
+ showGroup(keys[0]);
+ }
+ else {
+ hideModalDialog();
+ }
+ });
+ }
+ });
+ }
+
+ function updateDialogSize() {
+ var windowWidth = $(window).width();
+ var windowHeight = $(window).height();
+
+ if (Object.keys(groups).length == 0) {
+ groupsDiv.width('auto');
+ groupsDiv.height('auto');
+ groupsDiv.addClass('small-screen');
+ mediaListDiv.width('auto');
+ mediaListDiv.height('auto');
+ buttonsDiv.hide();
+
+ return;
+ }
+
+ buttonsDiv.show();
+
+ if (windowWidth < 1000) {
+ mediaListDiv.width(parseInt(windowWidth * 0.8));
+ mediaListDiv.height(parseInt(windowHeight * 0.7));
+ groupsDiv.width(parseInt(windowWidth * 0.8));
+ groupsDiv.height('');
+ groupsDiv.addClass('small-screen');
+ }
+ else {
+ mediaListDiv.width(parseInt(windowWidth * 0.7));
+ mediaListDiv.height(parseInt(windowHeight * 0.7));
+ groupsDiv.height(parseInt(windowHeight * 0.7));
+ groupsDiv.width('');
+ groupsDiv.removeClass('small-screen');
+ }
+ }
+
+ function onResize() {
+ updateDialogSize();
+ updateModalDialogPosition();
+ }
+
+ $(window).resize(onResize);
+
+ updateDialogSize();
+
+ showModalDialog('<div class="modal-progress"></div>');
+
+ /* fetch the media list */
+ ajax('GET', baseUri + mediaType + '/' + cameraId + '/list/', null, function (data) {
+ if (data == null || data.error) {
+ hideModalDialog();
+ showErrorMessage(data && data.error);
+ return;
+ }
+
+ /* group the media */
+ data.mediaList.forEach(function (media) {
+ var path = media.path;
+ var parts = path.split('/');
+ var keyParts = parts.splice(0, parts.length - 1);
+ var key = keyParts.join('/');
+
+ if (key.indexOf('/') === 0) {
+ key = key.substring(1);
+ }
+
+ var list = (groups[key] = groups[key] || []);
+
+ list.push({
+ 'path': path,
+ 'group': key,
+ 'name': parts[parts.length - 1],
+ 'cameraId': cameraId
+ });
+ });
+
+ updateDialogSize();
+
+ var keys = Object.keys(groups);
+ keys.sort();
+ keys.reverse();
+
+ if (keys.length) {
+ keys.forEach(function (key) {
+ var groupButton = $('<div class="media-dialog-group-button"></div>');
+ groupButton.text((key || '(ungrouped)') + ' (' + groups[key].length + ')');
+ groupButton[0].key = key;
+
+ groupButton.click(function () {
+ showGroup(key);
+ });
+
+ groupsDiv.append(groupButton);
+ });
+
+ /* add tooltips to larger group buttons */
+ setTimeout(function () {
+ groupsDiv.find('div.media-dialog-group-button').each(function () {
+ if (this.scrollWidth > this.offsetWidth) {
+ this.title = this.innerHTML;
+ }
+ });
+ }, 10);
+ }
+ else {
+ groupsDiv.html('(no media files)');
+ mediaListDiv.remove();
+ }
+
+ var title;
+ if ($(window).width() < 1000) {
+ title = data.cameraName;
+ }
+ else if (mediaType === 'picture') {
+ title = 'Pictures taken by ' + data.cameraName;
+ }
+ else {
+ title = 'Movies recorded by ' + data.cameraName;
+ }
+
+ runModalDialog({
+ title: title,
+ closeButton: true,
+ buttons: '',
+ content: dialogDiv,
+ onShow: function () {
+ //dialogDiv.scrollTop(dialogDiv.prop('scrollHeight'));
+ if (keys.length) {
+ showGroup(keys[0]);
+ }
+ },
+ onClose: function () {
+ $(window).unbind('resize', onResize);
+ }
+ });
+ });
+
+ /* install the media list scroll event handler */
+ mediaListDiv.scroll(function () {
+ var height = mediaListDiv.height();
+
+ mediaListDiv.find('img.media-list-preview').each(function () {
+ if (!this._src) {
+ return;
+ }
+
+ var $this = $(this);
+ var entryDiv = $this.parent();
+
+ var top1 = entryDiv.position().top;
+ var top2 = top1 + entryDiv.height();
+
+ if ((top1 >= 0 && top1 <= height) ||
+ (top2 >= 0 && top2 <= height)) {
+
+ this.src = this._src;
+ delete this._src;
+ }
+ });
+ });
+}
+
+
+ /* camera frames */
+
+function addCameraFrameUi(cameraConfig) {
+ var pageContainer = $('div.page-container');
+
+ if (cameraConfig == null) {
+ var cameraFrameDivPlaceHolder = $('<div class="camera-frame-place-holder"></div>');
+ pageContainer.append(cameraFrameDivPlaceHolder);
+
+ return;
+ }
+
+ var cameraId = cameraConfig.id;
+
+ var cameraFrameDiv = $(
+ '<div class="camera-frame">' +
+ '<div class="camera-top-bar">' +
+ '<span class="camera-name"></span>' +
+ '<div class="camera-buttons">' +
+ '<div class="button camera-button mouse-effect full-screen" title="full-screen window"></div>' +
+ '<div class="button camera-button mouse-effect media-pictures" title="pictures"></div>' +
+ '<div class="button camera-button mouse-effect media-movies" title="movies"></div>' +
+ '<div class="button camera-button mouse-effect configure" title="configure"></div>' +
+ '</div>' +
+ '</div>' +
+ '<div class="camera-container">' +
+ '<div class="camera-placeholder"><img class="no-camera" src="' + staticUrl + 'img/no-camera.svg"></div>' +
+ '<img class="camera">' +
+ '<div class="camera-progress"><img class="camera-progress"></div>' +
+ '</div>' +
+ '</div>');
+
+ var nameSpan = cameraFrameDiv.find('span.camera-name');
+ var configureButton = cameraFrameDiv.find('div.camera-button.configure');
+ var picturesButton = cameraFrameDiv.find('div.camera-button.media-pictures');
+ var moviesButton = cameraFrameDiv.find('div.camera-button.media-movies');
+ var fullScreenButton = cameraFrameDiv.find('div.camera-button.full-screen');
+ var cameraPlaceholder = cameraFrameDiv.find('div.camera-placeholder');
+ var cameraProgress = cameraFrameDiv.find('div.camera-progress');
+ var cameraImg = cameraFrameDiv.find('img.camera');
+ var progressImg = cameraFrameDiv.find('img.camera-progress');
+
+ /* no camera buttons if not admin */
+ if (!isAdmin()) {
+ configureButton.hide();
+ }
+
+ /* no media buttons for simple mjpeg cameras */
+ if (cameraConfig['proto'] == 'mjpeg') {
+ picturesButton.hide();
+ moviesButton.hide();
+ }
+
+ cameraFrameDiv.attr('id', 'camera' + cameraId);
+ cameraFrameDiv[0].refreshDivider = 0;
+ cameraFrameDiv[0].config = cameraConfig;
+ nameSpan.html(cameraConfig.name);
+ progressImg.attr('src', staticUrl + 'img/camera-progress.gif');
+
+ cameraProgress.click(function () {
+ doFullScreenCamera(cameraId);
+ });
+
+ cameraProgress.addClass('visible');
+ cameraPlaceholder.css('opacity', '0');
+
+ /* insert the new camera frame at the right position,
+ * with respect to the camera id */
+ var cameraFrames = pageContainer.find('div.camera-frame');
+ var cameraIds = cameraFrames.map(function () {return parseInt(this.id.substring(6));});
+ cameraIds.sort();
+
+ var index = 0; /* find the first position that is greater than the current camera id */
+ while (index < cameraIds.length && cameraIds[index] < cameraId) {
+ index++;
+ }
+
+ if (index < cameraIds.length) {
+ var beforeCameraFrame = pageContainer.find('div.camera-frame#camera' + cameraIds[index]);
+ cameraFrameDiv.insertAfter(beforeCameraFrame);
+ }
+ else {
+ pageContainer.append(cameraFrameDiv);
+ }
+
+ /* fade in */
+ cameraFrameDiv.animate({'opacity': 1}, 100);
+
+ /* add the button handlers */
+ configureButton.click(function () {
+ doConfigureCamera(cameraId);
+ });
+
+ picturesButton.click(function (cameraId) {
+ return function () {
+ runMediaDialog(cameraId, 'picture');
+ };
+ }(cameraId));
+
+ moviesButton.click(function (cameraId) {
+ return function () {
+ runMediaDialog(cameraId, 'movie');
+ };
+ }(cameraId));
+
+ fullScreenButton.click(function (cameraId) {
+ return function () {
+ var url = baseUri + 'picture/' + cameraId + '/frame/';
+ window.open(url, '_blank');
+ };
+ }(cameraId));
+
+ /* error and load handlers */
+ cameraImg.error(function () {
+ this.error = true;
+ this.loading = 0;
+
+ cameraImg.addClass('error').removeClass('loading');
+ cameraImg.height(Math.round(cameraImg.width() * 0.75));
+ cameraPlaceholder.css('opacity', 1);
+ cameraProgress.removeClass('visible');
+ cameraFrameDiv.removeClass('motion-detected');
+ });
+ cameraImg.load(function () {
+ if (refreshDisabled[cameraId]) {
+ return; /* refresh temporarily disabled for updating */
+ }
+
+ this.error = false;
+ this.loading = 0;
+
+ cameraImg.removeClass('error').removeClass('loading');
+ cameraImg.css('height', '');
+ cameraPlaceholder.css('opacity', 0);
+ cameraProgress.removeClass('visible');
+
+ /* there's no point in looking for a cookie update more often than once every second */
+ var now = new Date().getTime();
+ if ((!this.lastCookieTime || now - this.lastCookieTime > 1000) && (cameraFrameDiv[0].config['proto'] != 'mjpeg')) {
+ if (getCookie('motion_detected_' + cameraId) == 'true') {
+ cameraFrameDiv.addClass('motion-detected');
+ }
+ else {
+ cameraFrameDiv.removeClass('motion-detected');
+ }
+
+ this.lastCookieTime = now;
+ }
+
+ if (fullScreenCameraId) {
+ /* update the modal dialog position when image is loaded */
+ updateModalDialogPosition();
+ }
+ });
+
+ cameraImg.addClass('loading');
+ cameraImg.height(Math.round(cameraImg.width() * 0.75));
+}
+
+function remCameraFrameUi(cameraId) {
+ var pageContainer = $('div.page-container');
+ var cameraFrameDiv = pageContainer.find('div.camera-frame#camera' + cameraId);
+ cameraFrameDiv.animate({'opacity': 0}, 100, function () {
+ cameraFrameDiv.remove();
+ });
+}
+
+function recreateCameraFrames(cameras) {
+ var pageContainer = $('div.page-container');
+
+ function updateCameras(cameras) {
+ cameras = cameras.filter(function (camera) {return camera.enabled;});
+ var i, camera;
+
+ /* remove everything on the page */
+ pageContainer.children().remove();
+
+ /* add camera frames */
+ for (i = 0; i < cameras.length; i++) {
+ camera = cameras[i];
+ addCameraFrameUi(camera);
+ }
+
+ if ($('#cameraSelect').find('option').length < 2 && isAdmin() && $('#motionEyeSwitch')[0].checked) {
+ /* invite the user to add a camera */
+ var addCameraLink = $('<div class="add-camera-message">' +
+ '<a href="javascript:runAddCameraDialog()">You have not configured any camera yet. Click here to add one...</a></div>');
+ pageContainer.append(addCameraLink);
+ }
+ }
+
+ if (cameras != null) {
+ updateCameras(cameras);
+ }
+ else {
+ ajax('GET', baseUri + 'config/list/', null, function (data) {
+ if (data == null || data.error) {
+ showErrorMessage(data && data.error);
+ return;
+ }
+
+ updateCameras(data.cameras);
+ });
+ }
+
+ /* update settings panel */
+ var cameraId = $('#cameraSelect').val();
+ if (cameraId && cameraId != 'add') {
+ openSettings(cameraId);
+ }
+}
+
+
+function doConfigureCamera(cameraId) {
+ if (inProgress) {
+ return;
+ }
+
+ hideApply();
+ pushConfigs = {};
+ pushConfigReboot = false;
+
+ openSettings(cameraId);
+}
+
+function doFullScreenCamera(cameraId) {
+ if (inProgress || refreshDisabled[cameraId]) {
+ return;
+ }
+
+ if (fullScreenCameraId != null) {
+ return; /* a camera is already in full screen */
+ }
+
+ fullScreenCameraId = -1; /* avoids successive fast toggles of fullscreen */
+
+ var cameraFrameDiv = $('#camera' + cameraId);
+ var cameraName = cameraFrameDiv.find('span.camera-name').text();
+ var frameImg = cameraFrameDiv.find('img.camera');
+ var aspectRatio = frameImg.width() / frameImg.height();
+ var windowWidth = $(window).width();
+ var windowHeight = $(window).height();
+ var windowAspectRatio = windowWidth / windowHeight;
+ var frameIndex = cameraFrameDiv.index();
+ var pageContainer = $('div.page-container');
+
+ if (frameImg.hasClass('error')) {
+ return; /* no full screen for erroneous cameras */
+ }
+
+ var width;
+ if (windowAspectRatio > aspectRatio) {
+ width = aspectRatio * Math.round(0.8 * windowHeight);
+ }
+ else {
+ width = Math.round(0.9 * windowWidth);
+ }
+
+ cameraFrameDiv.find('div.camera-progress').addClass('visible');
+
+ var cameraImg = cameraFrameDiv.find('img.camera');
+ cameraImg.load(function showFullScreenCamera() {
+ cameraFrameDiv.css('width', width);
+ fullScreenCameraId = cameraId;
+
+ runModalDialog({
+ title: cameraName,
+ closeButton: true,
+ content: cameraFrameDiv,
+ onShow: function () {
+ cameraImg.unbind('load', showFullScreenCamera);
+ },
+ onClose: function () {
+ fullScreenCameraId = null;
+ cameraFrameDiv.css('width', '');
+ var nextFrame = pageContainer.children('div:eq(' + frameIndex + ')');
+ if (nextFrame.length) {
+ nextFrame.before(cameraFrameDiv);
+ }
+ else {
+ pageContainer.append(cameraFrameDiv);
+ }
+ }
+ });
+ });
+
+ if (cameraFrameDiv[0].config['proto'] == 'mjpeg') {
+ /* manually trigger the load event on simple mjpeg cameras */
+ cameraImg.load();
+ }
+}
+
+function refreshCameraFrames() {
+ function refreshCameraFrame(cameraId, img, serverSideResize) {
+ if (refreshDisabled[cameraId]) {
+ /* camera refreshing disabled, retry later */
+
+ return;
+ }
+
+ if (img.loading) {
+ img.loading++; /* increases each time the camera would refresh but is still loading */
+
+ if (img.loading > 2 * 1000 / refreshInterval) { /* limits the retries to one every two seconds */
+ img.loading = 0;
+ }
+ else {
+ return; /* wait for the previous frame to finish loading */
+ }
+ }
+
+ var timestamp = new Date().getTime();
+ var uri = baseUri + 'picture/' + cameraId + '/current/?_=' + timestamp;
+ if (serverSideResize) {
+ uri += '&width=' + img.width;
+ }
+
+ uri = addAuthParams('GET', uri);
+
+ img.src = uri;
+ img.loading = 1;
+ }
+
+ var cameraFrames;
+ if (fullScreenCameraId != null && fullScreenCameraId >= 0) {
+ cameraFrames = $('#camera' + fullScreenCameraId);
+ }
+ else {
+ cameraFrames = $('div.page-container').find('div.camera-frame');
+ }
+
+ cameraFrames.each(function () {
+ if (!this.img) {
+ this.img = $(this).find('img.camera')[0];
+ if (this.config['proto'] == 'mjpeg') {
+ var url = this.config['url'].replace('127.0.0.1', window.location.host.split(':')[0]);
+ url += (url.indexOf('?') > 0 ? '&' : '?') + '_=' + new Date().getTime();
+ this.img.src = url;
+ }
+ }
+
+ if (this.config['proto'] == 'mjpeg') {
+ return; /* no manual refresh for simple mjpeg cameras */
+ }
+
+ /* at a refresh interval of 50ms, the refresh rate is limited to 20 fps */
+ var count = 1000 / (refreshInterval * this.config['streaming_framerate']);
+ var serverSideResize = this.config['streaming_server_resize'];
+
+ if (count <= 2) {
+ /* skipping frames (showing the same frame twice) at this rate won't be visible,
+ * while the effective framerate will be as close as possible to the motion's one */
+ count -= 1;
+ }
+
+ if (this.img.error) {
+ /* in case of error, decrease the refresh rate to 1 fps */
+ count = 1000 / refreshInterval;
+ }
+
+ if (this.refreshDivider < count) {
+ this.refreshDivider++;
+ }
+ else {
+ var cameraId = this.id.substring(6);
+ refreshCameraFrame(cameraId, this.img, serverSideResize);
+
+ this.refreshDivider = 0;
+ }
+ });
+
+ setTimeout(refreshCameraFrames, refreshInterval);
+}
+
+function checkCameraErrors() {
+ /* properly triggers the onerror event on the cameras whose imgs were not successfully loaded,
+ * but the onerror event hasn't been triggered, for some reason (seems to happen in Chrome) */
+ var cameraFrames = $('div.page-container').find('img.camera');
+
+ cameraFrames.each(function () {
+ if (this.complete === true && this.naturalWidth === 0 && !this.error && this.src) {
+ $(this).error();
+ }
+ });
+
+ setTimeout(checkCameraErrors, 500);
+}
+
+
+ /* startup function */
+
+$(document).ready(function () {
+ /* detect base uri */
+ if (frame) {
+ baseUri = qualifyUri('../../../');
+
+ }
+ else {
+ baseUri = splitUrl(qualifyUri('')).baseUrl;
+
+ /* restore the username from cookie */
+ window.username = getCookie('username');
+ }
+
+ /* open/close settings */
+ $('div.settings-button').click(function () {
+ if (isSettingsOpen()) {
+ closeSettings();
+ }
+ else {
+ openSettings();
+ }
+ });
+
+ /* software update button */
+ $('div#updateButton').click(doUpdate);
+
+ /* backup/restore */
+ $('div#backupButton').click(doBackup);
+ $('div#restoreButton').click(doRestore);
+
+ /* prevent scroll events on settings div from propagating TODO this does not actually work */
+ $('div.settings').mousewheel(function (e, d) {
+ var t = $(this);
+ if (d > 0 && t.scrollTop() === 0) {
+ e.preventDefault();
+ }
+ else if (d < 0 && (t.scrollTop() === t.get(0).scrollHeight - t.innerHeight())) {
+ e.preventDefault();
+ }
+ });
+
+ initUI();
+ beginProgress();
+
+ ajax('GET', baseUri + 'login/', null, function () {
+ if (!frame) {
+ fetchCurrentConfig(endProgress);
+ }
+ });
+
+ refreshCameraFrames();
+ checkCameraErrors();
+});
+
--- /dev/null
+
+var _modalDialogContexts = [];
+
+
+ /* UI widgets */
+
+function makeCheckBox($input) {
+ $input.each(function () {
+ var $this = $(this);
+
+ var mainDiv = $('<div class="check-box"></div>');
+ var buttonDiv = $('<div class="check-box-button"></div>');
+ var text = $('<span class="check-box-text"><span>');
+
+ function setOn() {
+ text.html('ON');
+ mainDiv.addClass('on');
+ }
+
+ function setOff() {
+ text.html('OFF');
+ mainDiv.removeClass('on');
+ }
+
+ buttonDiv.append(text);
+ mainDiv.append(buttonDiv);
+
+ /* transfer the CSS classes */
+ mainDiv[0].className += ' ' + $this[0].className;
+
+ /* add the element */
+ $this.after(mainDiv);
+
+ function update() {
+ if ($this[0].checked) {
+ setOn();
+ }
+ else {
+ setOff();
+ }
+ }
+
+ /* add event handers */
+ $this.change(update).change();
+
+ mainDiv.click(function () {
+ $this[0].checked = !$this[0].checked;
+ $this.change();
+ });
+
+ /* make the element focusable */
+ mainDiv[0].tabIndex = 0;
+
+ /* handle the key events */
+ mainDiv.keydown(function (e) {
+ if (e.which === 13 || e.which === 32) {
+ $this[0].checked = !$this[0].checked;
+ $this.change();
+
+ return false;
+ }
+ });
+
+ this.update = update;
+ });
+}
+
+function makeSlider($input, minVal, maxVal, snapMode, ticks, ticksNumber, decimals, unit) {
+ unit = unit || '';
+
+ $input.each(function () {
+ var $this = $(this);
+ var slider = $('<div class="slider"></div>');
+
+ var labels = $('<div class="slider-labels"></div>');
+ slider.append(labels);
+
+ var bar = $('<div class="slider-bar"></div>');
+ slider.append(bar);
+
+ bar.append('<div class="slider-bar-inside"></div>');
+
+ var cursor = $('<div class="slider-cursor"></div>');
+ bar.append(cursor);
+
+ var cursorLabel = $('<div class="slider-cursor-label"></div>');
+ cursor.append(cursorLabel);
+
+ function bestPos(pos) {
+ if (pos < 0) {
+ pos = 0;
+ }
+ if (pos > 100) {
+ pos = 100;
+ }
+
+ if (snapMode > 0) {
+ var minDif = Infinity;
+ var bestPos = null;
+ for (var i = 0; i < ticks.length; i++) {
+ var tick = ticks[i];
+ var p = valToPos(tick.value);
+ var dif = Math.abs(p - pos);
+ if ((dif < minDif) && (snapMode == 1 || dif < 5)) {
+ minDif = dif;
+ bestPos = p;
+ }
+ }
+
+ if (bestPos != null) {
+ pos = bestPos;
+ }
+ }
+
+ return pos;
+ }
+
+ function getPos() {
+ return parseInt(cursor.position().left * 100 / bar.width());
+ }
+
+ function valToPos(val) {
+ return (val - minVal) * 100 / (maxVal - minVal);
+ }
+
+ function posToVal(pos) {
+ return minVal + pos * (maxVal - minVal) / 100;
+ }
+
+ function sliderChange(val) {
+ $this.val(val.toFixed(decimals));
+ cursorLabel.html('' + val.toFixed(decimals) + unit);
+ }
+
+ function bodyMouseMove(e) {
+ if (bar[0]._mouseDown) {
+ var offset = bar.offset();
+ var pos = e.pageX - offset.left - 5;
+ pos = pos / slider.width() * 100;
+ pos = bestPos(pos);
+ var val = posToVal(pos);
+
+ cursor.css('left', pos + '%');
+ sliderChange(val);
+ }
+ }
+
+ function bodyMouseUp(e) {
+ bar[0]._mouseDown = false;
+
+ $('body').unbind('mousemove', bodyMouseMove);
+ $('body').unbind('mouseup', bodyMouseUp);
+
+ cursorLabel.css('display', 'none');
+
+ $this.change();
+ }
+
+ bar.mousedown(function (e) {
+ if (e.which > 1) {
+ return;
+ }
+
+ this._mouseDown = true;
+ bodyMouseMove(e);
+
+ $('body').mousemove(bodyMouseMove);
+ $('body').mouseup(bodyMouseUp);
+
+ slider.focus();
+ cursorLabel.css('display', 'inline-block');
+
+ return false;
+ });
+
+ /* ticks */
+ var autoTicks = (ticks == null);
+
+ function makeTicks() {
+ if (ticksNumber == null) {
+ ticksNumber = 11;
+ }
+
+ labels.html('');
+
+ if (autoTicks) {
+ ticks = [];
+ var i;
+ for (i = 0; i < ticksNumber; i++) {
+ var val = minVal + i * (maxVal - minVal) / (ticksNumber - 1);
+ var valStr;
+ if (Math.round(val) == val) {
+ valStr = '' + val;
+ }
+ else {
+ valStr = val.toFixed(decimals);
+ }
+ ticks.push({value: val, label: valStr + unit});
+ }
+ }
+
+ for (i = 0; i < ticks.length; i++) {
+ var tick = ticks[i];
+ var pos = valToPos(tick.value);
+ var span = $('<span class="slider-label" style="left: -9999px;">' + tick.label + '</span>');
+
+ labels.append(span);
+ span.css('left', (pos - 10) + '%');
+ }
+
+ return ticks;
+ }
+
+ makeTicks();
+
+ function input2slider() {
+ var value = parseFloat($this.val());
+ if (isNaN(value)) {
+ value = minVal;
+ }
+
+ var pos = valToPos(value);
+ pos = bestPos(pos);
+ cursor.css('left', pos + '%');
+ cursorLabel.html($this.val() + unit);
+ }
+
+ /* transfer the CSS classes */
+ slider.addClass($this.attr('class'));
+
+ /* handle input events */
+ $this.change(input2slider).change();
+
+ /* add the slider to the parent of the input */
+ $this.after(slider);
+
+ /* make the slider focusable */
+ slider.attr('tabIndex', 0);
+
+ /* handle key events */
+ slider.keydown(function (e) {
+ switch (e.which) {
+ case 37: /* left */
+ if (snapMode == 1) { /* strict snapping */
+ // TODO implement me
+ }
+ else {
+ var step = (maxVal - minVal) / 200;
+ var val = Math.max(minVal, parseFloat($this.val()) - step);
+ if (decimals == 0) {
+ val = Math.floor(val);
+ }
+
+ var origSnapMode = snapMode;
+ snapMode = 0;
+ $this.val(val).change();
+ snapMode = origSnapMode;
+ }
+
+ break;
+
+ case 39: /* right */
+ if (snapMode == 1) { /* strict snapping */
+ // TODO implement me
+ }
+ else {
+ var step = (maxVal - minVal) / 200;
+ var val = Math.min(maxVal, parseFloat($this.val()) + step);
+ if (decimals == 0) {
+ val = Math.ceil(val);
+ }
+
+ var origSnapMode = snapMode;
+ snapMode = 0;
+ $this.val(val).change();
+ snapMode = origSnapMode;
+ }
+
+ break;
+ }
+ });
+
+ this.update = input2slider;
+
+ slider[0].setMinVal = function (mv) {
+ minVal = mv;
+
+ makeTicks();
+ };
+
+ slider[0].setMaxVal = function (mv) {
+ maxVal = mv;
+
+ makeTicks();
+
+ input2slider();
+ };
+ });
+}
+
+function makeProgressBar($div) {
+ $div.each(function () {
+ var $this = $(this);
+
+ $this.addClass('progress-bar-container');
+ var fillDiv = $('<div class="progress-bar-fill"></div>');
+ var textSpan = $('<span class="progress-bar-text"></span>');
+
+ $this.append(fillDiv);
+ $this.append(textSpan);
+
+ this.setProgress = function (progress) {
+ $this.progress = progress;
+ fillDiv.width(progress + '%');
+ };
+
+ this.setText = function (text) {
+ textSpan.html(text);
+ };
+ });
+}
+
+
+ /* validators */
+
+function makeTextValidator($input, required) {
+ if (required == null) {
+ required = true;
+ }
+
+ $input.each(function () {
+ var $this = $(this);
+
+ function isValid(strVal) {
+ if (!$this.is(':visible')) {
+ return true; /* an invisible element is considered always valid */
+ }
+
+ if (strVal.length === 0 && required) {
+ return false;
+ }
+
+ return true;
+ }
+
+ var msg = 'this field is required';
+
+ function validate() {
+ var strVal = $this.val();
+ if (isValid(strVal)) {
+ $this.attr('title', '');
+ $this.removeClass('error');
+ $this[0].invalid = false;
+ }
+ else {
+ $this.attr('title', msg);
+ $this.addClass('error');
+ $this[0].invalid = true;
+ }
+ }
+
+ $this.keyup(validate);
+ $this.blur(validate);
+ $this.change(validate).change();
+
+ $this.addClass('validator');
+ $this.addClass('text-validator');
+ $this.each(function () {
+ var oldValidate = this.validate;
+ this.validate = function () {
+ if (oldValidate) {
+ oldValidate.call(this);
+ }
+ validate();
+ }
+ });
+ });
+}
+
+function makeComboValidator($select, required) {
+ if (required == null) {
+ required = true;
+ }
+
+ $select.each(function () {
+ $this = $(this);
+
+ function isValid(strVal) {
+ if (!$this.is(':visible')) {
+ return true; /* an invisible element is considered always valid */
+ }
+
+ if (strVal.length === 0 && required) {
+ return false;
+ }
+
+ return true;
+ }
+
+ var msg = 'this field is required';
+
+ function validate() {
+ var strVal = $this.val() || '';
+ if (isValid(strVal)) {
+ $this.attr('title', '');
+ $this.removeClass('error');
+ $this[0].invalid = false;
+ }
+ else {
+ $this.attr('title', msg);
+ $this.addClass('error');
+ $this[0].invalid = true;
+ }
+ }
+
+ $this.keyup(validate);
+ $this.blur(validate);
+ $this.change(validate).change();
+
+ $this.addClass('validator');
+ $this.addClass('combo-validator');
+ $this.each(function () {
+ var oldValidate = this.validate;
+ this.validate = function () {
+ if (oldValidate) {
+ oldValidate.call(this);
+ }
+ validate();
+ }
+ });
+ });
+}
+
+function makeNumberValidator($input, minVal, maxVal, floating, sign, required) {
+ if (minVal == null) {
+ minVal = -Infinity;
+ }
+ if (maxVal == null) {
+ maxVal = Infinity;
+ }
+ if (floating == null) {
+ floating = false;
+ }
+ if (sign == null) {
+ sign = false;
+ }
+ if (required == null) {
+ required = true;
+ }
+
+ $input.each(function () {
+ var $this = $(this);
+
+ function isValid(strVal) {
+ if (!$this.is(':visible')) {
+ return true; /* an invisible element is considered always valid */
+ }
+
+ if (strVal.length === 0 && !required) {
+ return true;
+ }
+
+ var numVal = parseInt(strVal);
+ if ('' + numVal != strVal) {
+ return false;
+ }
+
+ if (numVal < minVal || numVal > maxVal) {
+ return false;
+ }
+
+ if (!sign && numVal < 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ var msg = '';
+ if (!sign) {
+ msg = 'enter a positive';
+ }
+ else {
+ msg = 'enter a';
+ }
+ if (floating) {
+ msg += ' number';
+ }
+ else {
+ msg += ' integer number';
+ }
+ if (isFinite(minVal)) {
+ if (isFinite(maxVal)) {
+ msg += ' between ' + minVal + ' and ' + maxVal;
+ }
+ else {
+ msg += ' greater than ' + minVal;
+ }
+ }
+ else {
+ if (isFinite(maxVal)) {
+ msg += ' smaller than ' + maxVal;
+ }
+ }
+
+ function validate() {
+ var strVal = $this.val();
+ if (isValid(strVal)) {
+ $this.attr('title', '');
+ $this.removeClass('error');
+ $this[0].invalid = false;
+ }
+ else {
+ $this.attr('title', msg);
+ $this.addClass('error');
+ $this[0].invalid = true;
+ }
+ }
+
+ $this.keyup(validate);
+ $this.blur(validate);
+ $this.change(validate).change();
+
+ $this.addClass('validator');
+ $this.addClass('number-validator');
+ $this.each(function () {
+ var oldValidate = this.validate;
+ this.validate = function () {
+ if (oldValidate) {
+ oldValidate.call(this);
+ }
+ validate();
+ }
+ });
+ });
+
+ makeStrippedInput($input);
+}
+
+function makeTimeValidator($input) {
+ $input.each(function () {
+ var $this = $(this);
+
+ function isValid(strVal) {
+ if (!$this.is(':visible')) {
+ return true; /* an invisible element is considered always valid */
+ }
+
+ return strVal.match(new RegExp('^[0-2][0-9]:[0-5][0-9]$')) != null;
+ }
+
+ var msg = 'enter a valid time in the following format: HH:MM';
+
+ function validate() {
+ var strVal = $this.val();
+ if (isValid(strVal)) {
+ $this.attr('title', '');
+ $this.removeClass('error');
+ $this[0].invalid = false;
+ }
+ else {
+ $this.attr('title', msg);
+ $this.addClass('error');
+ $this[0].invalid = true;
+ }
+ }
+
+ $this.keyup(validate);
+ $this.blur(validate);
+ $this.change(validate).change();
+ $this.timepicker({
+ closeOnWindowScroll: true,
+ selectOnBlur: true,
+ timeFormat: 'H:i',
+ });
+
+ $this.addClass('validator');
+ $this.addClass('time-validator');
+ $this.each(function () {
+ var oldValidate = this.validate;
+ this.validate = function () {
+ if (oldValidate) {
+ oldValidate.call(this);
+ }
+ validate();
+ }
+ });
+ });
+
+ makeStrippedInput($input);
+}
+
+function makeUrlValidator($input) {
+ $input.each(function () {
+ var $this = $(this);
+
+ function isValid(strVal) {
+ if (!$this.is(':visible')) {
+ return true; /* an invisible element is considered always valid */
+ }
+
+ return strVal.match(new RegExp('^([a-zA-Z]+)://([\\w\-.]+)(:\\d+)?(/.*)?$')) != null;
+ }
+
+ var msg = 'enter a valid URL (e.g. http://example.com:8080/cams/)';
+
+ function validate() {
+ var strVal = $this.val();
+ if (isValid(strVal)) {
+ $this.attr('title', '');
+ $this.removeClass('error');
+ $this[0].invalid = false;
+ }
+ else {
+ $this.attr('title', msg);
+ $this.addClass('error');
+ $this[0].invalid = true;
+ }
+ }
+
+ $this.keyup(validate);
+ $this.blur(validate);
+ $this.change(validate).change();
+
+ $this.addClass('validator');
+ $this.addClass('url-validator');
+ $this.each(function () {
+ var oldValidate = this.validate;
+ this.validate = function () {
+ if (oldValidate) {
+ oldValidate.call(this);
+ }
+ validate();
+ }
+ });
+ });
+}
+
+function makeFileValidator($input, required) {
+ if (required == null) {
+ required = true;
+ }
+
+ $input.each(function () {
+ var $this = $(this);
+
+ function isValid(strVal) {
+ if (!$this.is(':visible')) {
+ return true; /* an invisible element is considered always valid */
+ }
+
+ if (strVal.length === 0 && required) {
+ return false;
+ }
+
+ return true;
+ }
+
+ var msg = 'this field is required';
+
+ function validate() {
+ var strVal = $this.val();
+ if (isValid(strVal)) {
+ $this.attr('title', '');
+ $this.removeClass('error');
+ $this[0].invalid = false;
+ }
+ else {
+ $this.attr('title', msg);
+ $this.addClass('error');
+ $this[0].invalid = true;
+ }
+ }
+
+ $this.keyup(validate);
+ $this.blur(validate);
+ $this.change(validate).change();
+
+ $this.addClass('validator');
+ $this.addClass('file-validator');
+ $this.each(function () {
+ var oldValidate = this.validate;
+ this.validate = function () {
+ if (oldValidate) {
+ oldValidate.call(this);
+ }
+ validate();
+ }
+ });
+ });
+}
+function makeCustomValidator($input, isValidFunc) {
+ $input.each(function () {
+ var $this = $(this);
+
+ function isValid(strVal) {
+ if (!$this.is(':visible')) {
+ return true; /* an invisible element is considered always valid */
+ }
+
+ return isValidFunc(strVal);
+ }
+
+ function validate() {
+ var strVal = $this.val();
+ var valid = isValid(strVal);
+ if (valid == true) {
+ $this.attr('title', '');
+ $this.removeClass('error');
+ $this[0].invalid = false;
+ }
+ else {
+ $this.attr('title', valid || 'enter a valid value');
+ $this.addClass('error');
+ $this[0].invalid = true;
+ }
+ }
+
+ $this.keyup(validate);
+ $this.blur(validate);
+ $this.change(validate).change();
+
+ $this.addClass('validator');
+ $this.addClass('custom-validator');
+ $this.each(function () {
+ var oldValidate = this.validate;
+ this.validate = function () {
+ if (oldValidate) {
+ oldValidate.call(this);
+ }
+ validate();
+ }
+ });
+ });
+}
+
+
+ /* other input value processors */
+
+function makeStrippedInput($input) {
+ $input.change(function () {
+ this.value = $.trim(this.value);
+ });
+}
+
+function makeCharReplacer($input, oldChars, newStr) {
+ $input.change(function () {
+ this.value = this.value.replace(new RegExp('[' + oldChars + ']', 'g'), newStr);
+ });
+}
+
+
+ /* modal dialog */
+
+function showModalDialog(content, onClose, onShow, stack) {
+ var glass = $('div.modal-glass');
+ var container = $('div.modal-container');
+
+ if (container.is(':animated')) {
+ return setTimeout(function () {
+ showModalDialog(content, onClose, onShow, stack);
+ }, 100);
+ }
+
+ if (container.is(':visible') && stack) {
+ /* the modal dialog is already visible,
+ * we just replace the content */
+
+ var children = container.children(':visible');
+ _modalDialogContexts.push({
+ children: children,
+ onClose: container[0]._onClose,
+ });
+
+ children.css('display', 'none');
+ updateModalDialogPosition();
+
+ container[0]._onClose = onClose; /* set the new onClose handler */
+ container.append(content);
+ updateModalDialogPosition();
+
+ if (onShow) {
+ onShow();
+ }
+
+ return;
+ }
+
+ glass.css('display', 'block');
+ glass.animate({'opacity': '0.7'}, 200);
+
+ container[0]._onClose = onClose; /* remember the onClose handler */
+ container.html(content);
+
+ container.css('display', 'block');
+ updateModalDialogPosition();
+ container.animate({'opacity': '1'}, 200);
+
+ if (onShow) {
+ onShow();
+ }
+}
+
+function hideModalDialog() {
+ var glass = $('div.modal-glass');
+ var container = $('div.modal-container');
+
+ if (container.is(':animated')) {
+ return setTimeout(function () {
+ hideModalDialog();
+ }, 100);
+ }
+
+ if (_modalDialogContexts.length) {
+ if (container[0]._onClose) {
+ container[0]._onClose();
+ }
+
+ container.children(':visible').remove();
+
+ var context = _modalDialogContexts.pop();
+ context.children.css('display', '');
+ container[0]._onClose = context.onClose;
+ updateModalDialogPosition();
+
+ return;
+ }
+
+ glass.animate({'opacity': '0'}, 200, function () {
+ glass.css('display', 'none');
+ });
+
+ container.animate({'opacity': '0'}, 200, function () {
+ container.css('display', 'none');
+ container.html('');
+ });
+
+ /* run the onClose handler, if supplied */
+ if (container[0]._onClose) {
+ container[0]._onClose();
+ }
+}
+
+function updateModalDialogPosition() {
+ var container = $('div.modal-container');
+ if (!container.is(':visible')) {
+ return;
+ }
+
+ var windowWidth = $(window).width();
+ var windowHeight = $(window).height();
+ var modalWidth, modalHeight, i;
+
+ /* repeat the operation multiple times, the size might change */
+ for (i = 0; i < 3; i++) {
+ modalWidth = container.outerWidth();
+ modalHeight = container.outerHeight();
+
+ container.css('left', Math.floor((windowWidth - modalWidth) / 2));
+ container.css('top', Math.floor((windowHeight - modalHeight) / 2));
+ }
+}
+
+function makeModalDialogButtons(buttonsInfo) {
+ /* buttonsInfo is an array of:
+ * * caption: String
+ * * isDefault: Boolean
+ * * click: Function
+ */
+
+ var buttonsContainer = $('<table class="modal-buttons-container"><tr></tr></table>');
+ var tr = buttonsContainer.find('tr');
+
+ buttonsInfo.forEach(function (info) {
+ var buttonDiv = $('<div class="button dialog mouse-effect"></div>');
+
+ buttonDiv.attr('tabIndex', '0'); /* make button focusable */
+ buttonDiv.html(info.caption);
+
+ if (info.isDefault) {
+ buttonDiv.addClass('default');
+ }
+
+ if (info.click) {
+ var oldClick = info.click;
+ info.click = function () {
+ if (oldClick() == false) {
+ return false;
+ }
+
+ hideModalDialog();
+
+ return false;
+ };
+ }
+ else {
+ info.click = hideModalDialog; /* every button closes the dialog */
+ }
+
+ buttonDiv.click(info.click);
+
+ var td = $('<td></td>');
+ td.append(buttonDiv);
+ tr.append(td);
+ });
+
+ /* limit the size of the buttons container */
+ buttonsContainer.css('max-width', (buttonsInfo.length * 10) + 'em');
+
+ return buttonsContainer;
+}
+
+function makeModalDialogTitleBar(options) {
+ /* available options:
+ * * title: String
+ * * closeButton: Boolean
+ */
+
+ var titleBar = $('<div class="modal-title-bar"></div>');
+
+ var titleSpan = $('<span class="modal-title"></span>');
+ titleSpan.html(options.title || '');
+ if (options.closeButton) {
+ titleSpan.css('margin', '0px 1.5em');
+ }
+
+ titleBar.append(titleSpan);
+
+ if (options.closeButton) {
+ var closeButton = $('<div class="button modal-close-button mouse-effect" title="close"></div>');
+ closeButton.click(hideModalDialog);
+ titleBar.append(closeButton);
+ }
+
+ return titleBar;
+}
+
+function runModalDialog(options) {
+ /* available options:
+ * * title: String
+ * * closeButton: Boolean
+ * * content: any
+ * * buttons: 'ok'|'yesno'|'okcancel'|Array
+ * * onYes: Function
+ * * onNo: Function
+ * * onOk: Function
+ * * onCancel: Function
+ * * onClose: Function
+ * * onShow: Function
+ * * stack: Boolean
+ * * noKeys: Boolean
+ */
+
+ var content = $('<div></div>');
+ var titleBar = null;
+ var buttonsDiv = null;
+ var defaultClick = null;
+ var cancelClick = null;
+
+ /* add title bar */
+ if (options.title) {
+ titleBar = makeModalDialogTitleBar({title: options.title, closeButton: options.closeButton});
+ content.append(titleBar);
+ }
+
+ /* add supplied content */
+ if (options.content) {
+ var contentWrapper = $('<div style="padding: 10px;"></div>');
+ contentWrapper.append(options.content);
+ content.append(contentWrapper);
+ }
+
+ /* add buttons */
+ if (options.buttons === 'yesno') {
+ options.buttons = [
+ {caption: 'No', click: options.onNo},
+ {caption: 'Yes', isDefault: true, click: options.onYes}
+ ];
+ }
+ if (options.buttons === 'yesnocancel') {
+ options.buttons = [
+ {caption: 'Cancel', isCancel: true, click: options.onCancel},
+ {caption: 'No', click: options.onNo},
+ {caption: 'Yes', isDefault: true, click: options.onYes}
+ ];
+ }
+ else if (options.buttons === 'okcancel') {
+ options.buttons = [
+ {caption: 'Cancel', isCancel:true, click: options.onCancel},
+ {caption: 'OK', isDefault: true, click: options.onOk}
+ ];
+ }
+ else if (options.buttons === 'ok') {
+ options.buttons = [
+ {caption: 'OK', isDefault: true, click: options.onOk}
+ ];
+ }
+
+ if (options.buttons) {
+ buttonsDiv = makeModalDialogButtons(options.buttons);
+ content.append(buttonsDiv);
+
+ options.buttons.forEach(function (info) {
+ if (info.isDefault) {
+ defaultClick = info.click;
+ }
+ else if (info.isCancel) {
+ cancelClick = info.click;
+ }
+ });
+ }
+
+ /* add some margins */
+ if ((buttonsDiv || options.content) && titleBar) {
+ titleBar.css('margin-bottom', '5px');
+ }
+
+ if (buttonsDiv && options.content) {
+ buttonsDiv.css('margin-top', '5px');
+ }
+
+ var handleKeyUp = !options.noKeys && function (e) {
+ if (!content.is(':visible')) {
+ return;
+ }
+
+ switch (e.which) {
+ case 13:
+ if (defaultClick && defaultClick() == false) {
+ return;
+ }
+
+ hideModalDialog();
+
+ break;
+
+ case 27:
+ if (cancelClick && cancelClick() == false) {
+ return;
+ }
+
+ hideModalDialog();
+
+ break;
+ }
+ };
+
+ var onClose = function () {
+ if (options.onClose) {
+ options.onClose();
+ }
+
+ /* unbind html handlers */
+
+ $('html').unbind('keyup', handleKeyUp);
+ };
+
+ /* bind key handlers */
+ $('html').bind('keyup', handleKeyUp);
+
+ /* and finally, show the dialog */
+
+ showModalDialog(content, onClose, options.onShow, options.stack);
+
+ /* focus the default button if nothing else is focused */
+ if (content.find('*:focus').length === 0) {
+ content.find('div.button.default').focus();
+ }
+}
+
+
+ /* popup message */
+
+function showPopupMessage(message, type) {
+ var container = $('div.popup-message-container');
+ var content = $('<span class="popup-message"></span>');
+
+ if (window._popupMessageTimeout) {
+ clearTimeout(window._popupMessageTimeout);
+ }
+
+ content.html(message);
+ content.addClass(type);
+ container.html(content);
+
+ var windowWidth = $(window).width();
+ var messageWidth = container.width();
+
+ container.css('display', 'block');
+ container.css('left', (windowWidth - messageWidth) / 2);
+
+ container.animate({'opacity': '1'}, 200);
+
+ window._popupMessageTimeout = setTimeout(function () {
+ window._popupMessageTimeout = null;
+ container.animate({'opacity': '0'}, 200, function () {
+ container.css('display', 'none');
+ });
+ }, 5000);
+}
--- /dev/null
+
+$(window).load(function () {
+ if (window.parent && window.parent.postMessage) {
+ window.parent.postMessage({'hostname': hostname, 'version': version, 'url': window.location.href.replace('version/', '')}, '*');
+ }
+});
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from jinja2 import Environment, FileSystemLoader
+
+import settings
+import utils
+
+
+_jinja_env = None
+
+
+def _init_jinja():
+ global _jinja_env
+
+ _jinja_env = Environment(
+ loader=FileSystemLoader(settings.TEMPLATE_PATH),
+ trim_blocks=False)
+
+ # globals
+ _jinja_env.globals['settings'] = settings
+
+ # filters
+ _jinja_env.filters['pretty_date_time'] = utils.pretty_date_time
+ _jinja_env.filters['pretty_date'] = utils.pretty_date
+ _jinja_env.filters['pretty_time'] = utils.pretty_time
+ _jinja_env.filters['pretty_duration'] = utils.pretty_duration
+
+
+def add_template_path(path):
+ global _jinja_env
+ if _jinja_env is None:
+ _init_jinja()
+
+ _jinja_env.loader.searchpath.append(path)
+
+
+def add_context(name, value):
+ global _jinja_env
+ if _jinja_env is None:
+ _init_jinja()
+
+ _jinja_env.globals[name] = value
+
+
+def render(template_name, **context):
+ global _jinja_env
+ if _jinja_env is None:
+ _init_jinja()
+
+ template = _jinja_env.get_template(template_name)
+ return template.render(**context)
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ {% block meta %}
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=Edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
+ {% endblock %}
+ <title>{% block title %}{% endblock %}</title>
+ {% block style %}
+ <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}css/jquery.timepicker.css" />
+ <link rel="shortcut icon" href="/static/favicon.ico" />
+ <link rel="apple-touch-icon" href="/static/favicon.ico" />
+ {% endblock %}
+ {% block script %}
+ <script type="text/javascript" src="{{STATIC_URL}}js/css-browser-selector.js"></script>
+ <script type="text/javascript" src="{{STATIC_URL}}js/jquery.min.js"></script>
+ <script type="text/javascript" src="{{STATIC_URL}}js/jquery.timepicker.min.js"></script>
+ <script type="text/javascript" src="{{STATIC_URL}}js/jquery.mousewheel.js"></script>
+
+ <script type="text/javascript">
+ var staticUrl = '{{STATIC_URL}}';
+ </script>
+ {% endblock %}
+ </head>
+
+ <body>
+ {% block body %}{% endblock %}
+ </body>
+</html>
--- /dev/null
+{% extends "base.html" %}
+
+{% macro config_item(config) -%}
+ <tr class="settings-item additional-config {% if config['advanced'] %}advanced-setting{% endif %}"
+ {% if config.get('reboot') and enable_reboot %}reboot="true"{% endif %}
+ {% if config.get('required') %}required="true"{% endif %}
+ {% if config.get('strip') %}strip="true"{% endif %}
+ {% if config.get('depends') %}depends="{{' '.join(config['depends'])}}"{% endif %}
+ {% if config.get('min') is not none %}min="{{config['min']}}"{% endif %}
+ {% if config.get('max') is not none %}max="{{config['max']}}"{% endif %}
+ {% if config.get('floating') %}floating="true"{% endif %}
+ {% if config.get('sign') %}sign="true"{% endif %}
+ {% if config.get('snap') is not none %}snap="{{config['snap']}}"{% endif %}
+ {% if config.get('ticks') %}ticks="{{config['ticks']}}"{% endif %}
+ {% if config.get('ticksnum') is not none %}ticksnum="{{config['ticksnum']}}"{% endif %}
+ {% if config.get('decimals') is not none %}decimals="{{config['decimals']}}"{% endif %}
+ {% if config.get('unit') %}unit="{{config['unit']}}"{% endif %}
+ {% if config.get('validate') %}validate="{{config['validate']}}"{% endif %}>
+ {% if config['type'] == 'separator' %}
+ <td colspan="100"><div class="settings-item-separator"></div></td>
+ {% else %}
+ <td class="settings-item-label"><span class="settings-item-label">{{config['label']}}</span></td>
+ <td class="settings-item-value">
+ {% if config['type'] == 'str' %}
+ <input type="text" class="styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Entry">
+ {% elif config['type'] == 'pwd' %}
+ <input type="password" class="styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Entry">
+ {% elif config['type'] == 'number' %}
+ <input type="text" class="number styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Entry">
+ {% elif config['type'] == 'range' %}
+ <input type="text" class="range styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Slider">
+ {% elif config['type'] == 'bool' %}
+ <input type="checkbox" class="styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Switch">
+ {% elif config['type'] == 'choices' %}
+ <select class="styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Select">
+ {% for choice in config['choices'] %}
+ <option value="{{choice[0]}}">{{choice[1]}}</option>
+ {% endfor %}
+ </select>
+ {% elif config['type'] == 'html' %}
+ <div class="html styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Html">
+ {% endif %}
+ </td>
+ <td>{% if config.get('description') %}<span class="help-mark" title="{{config['description']}}">?</span>{% endif %}</td>
+ {% endif %}
+ </tr>
+{%- endmacro %}
+
+{% block title %}{% if title %}{{title}}{% else %}{{hostname}}{% endif %}{% endblock %}
+
+{% block style %}
+ {{super()}}
+ <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}css/ui.css" />
+ <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}css/main.css" />
+ {% if frame %}
+ <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}css/frame.css" />
+ {% endif %}
+{% endblock %}
+
+{% block script %}
+ {{super()}}
+ <script type="text/javascript" src="{{STATIC_URL}}js/ui.js"></script>
+ <script type="text/javascript" src="{{STATIC_URL}}js/main.js"></script>
+ {% if frame %}
+ <script type="text/javascript" src="{{STATIC_URL}}js/frame.js"></script>
+ {% endif %}
+ <script type="text/javascript">
+ var adminUsername = '{{admin_username}}';
+ var frame = {% if frame %}true{% else %}false{% endif %};
+ var baseUri = null;
+ </script>
+{% endblock %}
+
+{% block body %}
+ {% if not frame %}
+ <div class="header">
+ <div class="header-container">
+ <div class="settings-top-bar closed">
+ <div class="button settings-button mouse-effect" title="settings"></div>
+ <div class="button logout-button mouse-effect" title="switch user"></div>
+ <select class="styled" id="cameraSelect" {% if not add_remove_cameras %}style="display: none;"{% endif %}></select>
+ <div class="button rem-camera-button mouse-effect" id="remCameraButton" title="remove camera" {% if not add_remove_cameras %}style="display: none;"{% endif %}></div>
+ <div class="button apply-button" id="applyButton">Apply</div>
+ {% if hostname %}<div class="hostname">{{hostname}}</div>{% endif %}
+ </div>
+ <div class="button logout-button mouse-effect" title="switch user"></div>
+ <div class="logo">
+ <a href="/">
+ <span class="logo">motionEye</span>
+ <img class="logo" src="{{STATIC_URL}}img/motioneye-logo.svg">
+ </a>
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% if not frame %}
+ <div class="page">
+ <div class="settings closed">
+ <div class="settings-container">
+ <div class="settings-section-title">
+ <input type="checkbox" class="styled section general main-config" id="motionEyeSwitch">
+ <span class="help-mark" title="general settings, not related to any camera">?</span>
+ <a class="settings-section-title">General Settings</a>
+ <span class="minimize open"></span>
+ </div>
+ <table class="settings">
+ <tr class="settings-item">
+ <td class="settings-item-label"><span class="settings-item-label">Show Advanced Settings</span></td>
+ <td class="settings-item-value"><input type="checkbox" class="styled general main-config" id="showAdvancedSwitch"></td>
+ <td><span class="help-mark" title="enable this to be able to access all the advanced settings">?</span></td>
+ </tr>
+ <tr class="settings-item" required="true" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Administrator Username</span></td>
+ <td class="settings-item-value"><input type="text" class="styled general main-config" id="adminUsernameEntry" readonly="readonly"></td>
+ <td><span class="help-mark" title="the username to be used for configuring the system">?</span></td>
+ </tr>
+ <tr class="settings-item" strip="true" {% if enable_reboot %}reboot="true"{% endif %}>
+ <td class="settings-item-label"><span class="settings-item-label">Administrator Password</span></td>
+ <td class="settings-item-value"><input type="password" class="styled general main-config" id="adminPasswordEntry"></td>
+ <td><span class="help-mark" title="administrator's password">?</span></td>
+ </tr>
+ <tr class="settings-item" required="true" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Surveillance Username</span></td>
+ <td class="settings-item-value"><input type="text" class="styled general main-config" id="normalUsernameEntry"></td>
+ <td><span class="help-mark" title="the username to be used for video surveillance">?</span></td>
+ </tr>
+ <tr class="settings-item" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Surveillance Password</span></td>
+ <td class="settings-item-value"><input type="password" class="styled general main-config" id="normalPasswordEntry"></td>
+ <td><span class="help-mark" title="the password for the surveillance user (leave empty for passwordless surveillance)">?</span></td>
+ </tr>
+ {% for config in main_sections.get('general', {}).get('configs', []) %}
+ {{config_item(config)}}
+ {% endfor %}
+ <tr class="settings-item advanced-setting">
+ <td colspan="100"><div class="settings-item-separator"></div></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Current Version</span></td>
+ <td class="settings-item-value"><span class="settings-item-label">{{version}}</span></td>
+ </tr>
+ {% if enable_update %}
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Software Update</span></td>
+ <td class="settings-item-value"><div class="button normal-button update-button" id="updateButton">Check</div></td>
+ <td><span class="help-mark" title="checks for new versions and performs updates">?</span></td>
+ </tr>
+ {% endif %}
+ <tr class="settings-item advanced-setting{% if not enable_reboot %} hidden{% endif %}">
+ <td class="settings-item-label"><span class="settings-item-label">Power</span></td>
+ <td class="settings-item-value"><div class="button normal-button shut-down-button" id="shutDownButton">Shut Down</div></td>
+ <td><span class="help-mark" title="shuts down the system">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting{% if not enable_reboot %} hidden{% endif %}">
+ <td class="settings-item-label"><span class="settings-item-label"></span></td>
+ <td class="settings-item-value"><div class="button normal-button reboot-button" id="rebootButton">Reboot</div></td>
+ <td><span class="help-mark" title="reboots the system">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td colspan="100"><div class="settings-item-separator"></div></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Configuration</span></td>
+ <td class="settings-item-value"><div class="button normal-button backup-button" id="backupButton">Backup</div></td>
+ <td><span class="help-mark" title="creates a file with the current configuration for you to save it locally">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label"></span></td>
+ <td class="settings-item-value"><div class="button normal-button restore-button" id="restoreButton">Restore</div></td>
+ <td><span class="help-mark" title="restores the configuration from a previously saved backup file">?</span></td>
+ </tr>
+ </table>
+
+ {% for section in main_sections.values() %}
+ {% if section.get('label') and section.get('configs') %}
+ <div class="settings-section-title {% if section['advanced'] %}advanced-setting{% endif %}">
+ {% if section.get('onoff') %}<input type="checkbox" class="styled section additional-section {{section['name']}} main-config" id="{{section['name']}}Switch">{% endif %}
+ {% if section.get('description') %}<span class="help-mark" title="{{section['description']}}">?</span>{% endif %}
+ <a class="settings-section-title">{{section['label']}}</a>
+ <span class="minimize {% if section.get('open') %}open{% endif %}"></span>
+ </div>
+ <table class="settings {% if section.get('advanced') %}advanced-setting{% endif %}">
+ {% for config in section['configs'] %}
+ {{config_item(config)}}
+ {% endfor %}
+ </table>
+ {% endif %}
+ {% endfor %}
+
+ <tr class="settings-item advanced-setting">
+ <td colspan="100"><div class="settings-item-separator" style="margin: 0px 0px 1.5em 0px;"></div></td>
+ </tr>
+
+ <div class="settings-section-title">
+ <input type="checkbox" class="styled section device camera-config" id="videoDeviceSwitch">
+ <span class="help-mark" title="enable this if you want to use this camera device">?</span>
+ <a class="settings-section-title">Video Device</a>
+ <span class="minimize open"></span>
+ </div>
+ <table class="settings">
+ <tr class="settings-item" required="true" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Camera Name</span></td>
+ <td class="settings-item-value"><input type="text" class="styled device camera-config" id="deviceNameEntry" placeholder="camera name..."></td>
+ <td><span class="help-mark" title="an alias for this camera device">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Camera Device</span></td>
+ <td class="settings-item-value"><input type="text" class="styled device camera-config" id="deviceUriEntry" readonly="readonly"></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Camera Type</span></td>
+ <td class="settings-item-value"><input type="text" class="styled device camera-config" id="deviceTypeEntry" readonly="readonly"></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td colspan="100"><div class="settings-item-separator"></div></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Light Switch Detection</span></td>
+ <td class="settings-item-value"><input type="checkbox" class="styled device camera-config" id="lightSwitchDetectSwitch"></td>
+ <td><span class="help-mark" title="enable this if you want sudden changes in light to not be treated as motion">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Automatic Brightness</span></td>
+ <td class="settings-item-value"><input type="checkbox" class="styled device camera-config" id="autoBrightnessSwitch"></td>
+ <td><span class="help-mark" title="enables software automatic brightness (only recommended for cameras without autobrightness)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
+ <td class="settings-item-label"><span class="settings-item-label">Brightness</span></td>
+ <td class="settings-item-value"><input type="text" class="range styled device camera-config" id="brightnessSlider"></td>
+ <td><span class="help-mark" title="sets a desired brightness level for this camera">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
+ <td class="settings-item-label"><span class="settings-item-label">Contrast</span></td>
+ <td class="settings-item-value"><input type="text" class="range styled device camera-config" id="contrastSlider"></td>
+ <td><span class="help-mark" title="sets a desired contrast level for this camera">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
+ <td class="settings-item-label"><span class="settings-item-label">Saturation</span></td>
+ <td class="settings-item-value"><input type="text" class="range styled device camera-config" id="saturationSlider"></td>
+ <td><span class="help-mark" title="sets a desired saturation level for this camera">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
+ <td class="settings-item-label"><span class="settings-item-label">Hue</span></td>
+ <td class="settings-item-value"><input type="text" class="range styled device camera-config" id="hueSlider"></td>
+ <td><span class="help-mark" title="sets a desired hue for this camera">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td colspan="100"><div class="settings-item-separator"></div></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Video Resolution</span></td>
+ <td class="settings-item-value">
+ <select class="video-resolution styled device camera-config" id="resolutionSelect">
+ </select>
+ </td>
+ <td><span class="help-mark" title="the video resolution (larger values produce better quality but require more CPU power, larger storage space and bandwidth)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Video Rotation</span></td>
+ <td class="settings-item-value">
+ <select class="rotation styled device camera-config" id="rotationSelect">
+ <option value="0">0°</option>
+ <option value="90">90°</option>
+ <option value="180">180°</option>
+ <option value="270">270°</option>
+ </select>
+ </td>
+ <td><span class="help-mark" title="use this to rotate the captured image, if your camera is not positioned correctly">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="2" max="30" snap="0" ticks="2|5|10|15|20|25|30" decimals="0">
+ <td class="settings-item-label"><span class="settings-item-label">Frame Rate</span></td>
+ <td class="settings-item-value"><input type="text" class="range styled device camera-config" id="framerateSlider"></td>
+ <td><span class="help-mark" title="sets the number of frames captured by the camera every second (higher values produce smoother videos but require more CPU power, larger storage space and bandwidth)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Extra Motion Options</span></td>
+ <td class="settings-item-value"><textarea class="styled device camera-config" id="extraOptionsEntry" rows="3"></textarea></td>
+ <td><span class="help-mark" title="you can add here any extra options for the motion daemon (use the "name value" format, one option per line)">?</span></td>
+ </tr>
+ {% for config in camera_sections.get('device', {}).get('configs', []) %}
+ {{config_item(config)}}
+ {% endfor %}
+ </table>
+
+ <div class="settings-section-title advanced-setting">
+ <span class="help-mark" title="choose where and how your media files are saved">?</span>
+ <a class="settings-section-title">File Storage</a>
+ <span class="minimize"></span>
+ </div>
+ <table class="settings advanced-setting">
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Storage Device</span></td>
+ <td class="settings-item-value">
+ <select class="styled storage camera-config" id="storageDeviceSelect">
+ </select>
+ </td>
+ <td><span class="help-mark" title="indicates the storage device where the image and video files will be saved">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" required="true" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Network Server</span></td>
+ <td class="settings-item-value"><input type="text" class="styled storage camera-config" id="networkServerEntry"></td>
+ <td><span class="help-mark" title="the address of the network server (IP address or hostname)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" required="true" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Share Name</span></td>
+ <td class="settings-item-value"><input type="text" class="styled storage camera-config" id="networkShareNameEntry"></td>
+ <td><span class="help-mark" title="the name of the network share">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Share Username</span></td>
+ <td class="settings-item-value"><input type="text" class="styled storage camera-config" id="networkUsernameEntry"></td>
+ <td><span class="help-mark" title="the username to be supplied when accessing the network share (leave empty if no username is required)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Share Password</span></td>
+ <td class="settings-item-value"><input type="password" class="styled storage camera-config" id="networkPasswordEntry"></td>
+ <td><span class="help-mark" title="the password required by the network share (leave empty if no password is required)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" required="true" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Root Directory</span></td>
+ <td class="settings-item-value"><input type="text" class="styled storage camera-config" id="rootDirectoryEntry"></td>
+ <td><span class="help-mark" title="the root path (on the selected storage device) where the files will be saved">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td colspan="100"><div class="settings-item-separator"></div></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Disk Usage</span></td>
+ <td class="settings-item-value">
+ <div id="diskUsageProgressBar" class="progress-bar"></div>
+ </td>
+ <td><span class="help-mark" title="the used/total size of the disk where the root directory resides">?</span></td>
+ </tr>
+ {% for config in camera_sections.get('storage', {}).get('configs', []) %}
+ {{config_item(config)}}
+ {% endfor %}
+ </table>
+
+ <div class="settings-section-title advanced-setting">
+ <input type="checkbox" class="styled section text-overlay camera-config" id="textOverlaySwitch">
+ <span class="help-mark" title="choose what information is displayed on the captured frames">?</span>
+ <a class="settings-section-title">Text Overlay</a>
+ <span class="minimize"></span>
+ </div>
+ <table class="settings advanced-setting">
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Left Text</span></td>
+ <td class="settings-item-value">
+ <select class="styled text-overlay camera-config" id="leftTextSelect">
+ <option value="camera-name">Camera Name</option>
+ <option value="timestamp">Timestamp</option>
+ <option value="custom-text">Custom Text</option>
+ <option value="disabled">Disabled</option>
+ </select>
+ </td>
+ <td><span class="help-mark" title="sets the text displayed on the movies and images, on the lower left corner">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" required="true" strip="true">
+ <td class="settings-item-label"></td>
+ <td class="settings-item-value"><input type="text" class="styled text-overlay camera-config" id="leftTextEntry" placeholder="custom text..."></td>
+ <td><span class="help-mark" title="sets a custom left text; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %T = HH:MM:SS, %q = frame number, \n = new line">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Right Text</span></td>
+ <td class="settings-item-value">
+ <select class="styled text-overlay camera-config" id="rightTextSelect">
+ <option value="camera-name">Camera Name</option>
+ <option value="timestamp">Timestamp</option>
+ <option value="custom-text">Custom Text</option>
+ <option value="disabled">Disabled</option>
+ </select>
+ </td>
+ <td><span class="help-mark" title="sets the text displayed on the movies and images, on the lower right corner">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" required="true" strip="true">
+ <td class="settings-item-label"></td>
+ <td class="settings-item-value"><input type="text" class="styled text-overlay camera-config" id="rightTextEntry" placeholder="custom text..."></td>
+ <td><span class="help-mark" title="sets a custom right text; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %T = HH:MM:SS, %q = frame number, \n = new line">?</span></td>
+ </tr>
+ {% for config in camera_sections.get('text-overlay', {}).get('configs', []) %}
+ {{config_item(config)}}
+ {% endfor %}
+ </table>
+
+ <div class="settings-section-title" minimize-switch-independent="true">
+ <input type="checkbox" class="styled section streaming camera-config" id="videoStreamingSwitch">
+ <span class="help-mark" title="enable this if you want video streaming for this camera">?</span>
+ <a class="settings-section-title">Video Streaming</a>
+ <span class="minimize"></span>
+ </div>
+ <table class="settings">
+ <tr class="settings-item advanced-setting localhost-streaming" min="1" max="30" snap="0" ticks="1|5|10|15|20|25|30" decimals="0">
+ <td class="settings-item-label"><span class="settings-item-label">Streaming Frame Rate</span></td>
+ <td class="settings-item-value"><input type="text" class="range styled streaming camera-config" id="streamingFramerateSlider"></td>
+ <td><span class="help-mark" title="sets the number of frames transmitted every second on the live streaming">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting localhost-streaming" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
+ <td class="settings-item-label"><span class="settings-item-label">Streaming Quality</span></td>
+ <td class="settings-item-value"><input type="text" class="range styled streaming camera-config" id="streamingQualitySlider"></td>
+ <td><span class="help-mark" title="sets the live streaming quality (higher values produce a better video quality but require more bandwidth)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting localhost-streaming">
+ <td class="settings-item-label"><span class="settings-item-label">Streaming Image Resizing</span></td>
+ <td class="settings-item-value"><input type="checkbox" class="styled streaming camera-config" id="streamingServerResizeSwitch"></td>
+ <td><span class="help-mark" title="when this is enabled, the images are resized before they are sent to the browser (disable when running on a slow CPU)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting localhost-streaming" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
+ <td class="settings-item-label"><span class="settings-item-label">Streaming Resolution</span></td>
+ <td class="settings-item-value"><input type="text" class="range styled streaming camera-config" id="streamingResolutionSlider"></td>
+ <td><span class="help-mark" title="the streaming resolution given as percent of the video device resolution (higher values produce better video quality but require more bandwidth)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="1024" max="65535" required="true">
+ <td class="settings-item-label"><span class="settings-item-label">Streaming Port</span></td>
+ <td class="settings-item-value"><input type="text" class="styled streaming camera-config" id="streamingPortEntry"></td>
+ <td><span class="help-mark" title="sets the TCP port on which the webcam streaming server listens">?</span></td>
+ </tr>
+ {% if not old_motion %}
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Authentication Mode</span></td>
+ <td class="settings-item-value">
+ <select class="styled streaming camera-config" id="streamingAuthModeSelect">
+ <option value="disabled">Disabled</option>
+ <option value="basic">Basic</option>
+ <option value="digest">Digest</option>
+ </select>
+ </td>
+ <td><span class="help-mark" title="the authentication mode to use when accessing the stream (use Basic instead of Digest if you encounter issues with third party apps)">?</span></td>
+ </tr>
+ {% endif %}
+ <tr class="settings-item advanced-setting localhost-streaming">
+ <td class="settings-item-label"><span class="settings-item-label">Motion Optimization</span></td>
+ <td class="settings-item-value"><input type="checkbox" class="styled streaming camera-config" id="streamingMotion"></td>
+ <td><span class="help-mark" title="enable this if you want a lower frame rate for the live streaming when no motion is detected">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td colspan="100"><div class="settings-item-separator"></div></td>
+ </tr>
+ <tr class="settings-item advanced-setting localhost-streaming">
+ <td class="settings-item-label"><span class="settings-item-label">Snapshot URL</span></td>
+ <td class="settings-item-value"><input type="text" class="styled streaming camera-config" id="streamingSnapshotUrlEntry" readonly="readonly"></td>
+ <td><span class="help-mark" title="a URL that provides a JPEG image with the most recent snapshot of the camera">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Streaming URL</span></td>
+ <td class="settings-item-value"><input type="text" class="styled streaming camera-config" id="streamingMjpgUrlEntry" readonly="readonly"></td>
+ <td><span class="help-mark" title="a URL that provides a MJPEG stream of the camera (there is no password protection for this URL!)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting localhost-streaming">
+ <td class="settings-item-label"><span class="settings-item-label">Embed URL</span></td>
+ <td class="settings-item-value"><input type="text" class="styled streaming camera-config" id="streamingEmbedUrlEntry" readonly="readonly"></td>
+ <td><span class="help-mark" title="a URL that provides a minimal HTML document containing the camera frame, ready to be embedded">?</span></td>
+ </tr>
+ {% for config in camera_sections.get('streaming', {}).get('configs', []) %}
+ {{config_item(config)}}
+ {% endfor %}
+ </table>
+
+ <div class="settings-section-title">
+ <input type="checkbox" class="styled section still-images camera-config" id="stillImagesSwitch">
+ <span class="help-mark" title="enable this if you want to capture still images (pictures)">?</span>
+ <a class="settings-section-title">Still Images</a>
+ <span class="minimize"></span>
+ </div>
+ <table class="settings">
+ <tr class="settings-item advanced-setting" required="true" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Image File Name</span></td>
+ <td class="settings-item-value"><input type="text" class="styled still-images camera-config" id="imageFileNameEntry" placeholder="file name pattern..."></td>
+ <td><span class="help-mark" title="sets the name pattern for the image (JPEG) files; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %q = frame number, %v = event number / = subfolder">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
+ <td class="settings-item-label"><span class="settings-item-label">Image Quality</span></td>
+ <td class="settings-item-value"><input type="text" class="range styled still-images camera-config" id="imageQualitySlider"></td>
+ <td><span class="help-mark" title="sets the JPEG image quality (higher values produce a better image quality but require more storage space)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Capture Mode</span></td>
+ <td class="settings-item-value">
+ <select class="styled still-images camera-config" id="captureModeSelect">
+ <option value="motion-triggered">Motion Triggered</option>
+ <option value="interval-snapshots">Interval Snapshots</option>
+ <option value="all-frames">All Frames</option>
+ </select>
+ </td>
+ <td><span class="help-mark" title="sets the image capture mode: Motion Triggered = an image captured whenever motion is detected, Automated Snapshots = an image captured every x seconds, All Frames = saves each frame into an image file">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="1" max="86400" required="true">
+ <td class="settings-item-label"><span class="settings-item-label">Snapshot Interval</span></td>
+ <td class="settings-item-value"><input type="text" class="styled number still-images camera-config" id="snapshotIntervalEntry"><span class="settings-item-unit">seconds</span></td>
+ <td><span class="help-mark" title="sets the interval (in seconds) for the automated snapshots">?</span></td>
+ </tr>
+ <tr class="settings-item">
+ <td class="settings-item-label"><span class="settings-item-label">Preserve Pictures</span></td>
+ <td class="settings-item-value">
+ <select class="styled still-images camera-config" id="preservePicturesSelect">
+ <option value="1">For One Day</option>
+ <option value="7">For One Week</option>
+ <option value="30">For One Month</option>
+ <option value="365">For One Year</option>
+ <option value="0">Forever</option>
+ <option value="-1">Custom</option>
+ </select>
+ </td>
+ <td><span class="help-mark" title="images older than the specified duration are automatically deleted to free storage space">?</span></td>
+ </tr>
+ <tr class="settings-item" min="1" max="3650" required="true">
+ <td class="settings-item-label"><span class="settings-item-label">Pictures Lifetime</span></td>
+ <td class="settings-item-value"><input type="text" class="styled number still-images camera-config" id="picturesLifetimeEntry"><span class="settings-item-unit">days</span></td>
+ <td><span class="help-mark" title="sets the number of days after which the pictures will be deleted automatically">?</span></td>
+ </tr>
+ {% for config in camera_sections.get('still-images', {}).get('configs', []) %}
+ {{config_item(config)}}
+ {% endfor %}
+ </table>
+
+ <div class="settings-section-title advanced-setting">
+ <input type="checkbox" class="styled section motion-detection camera-config" id="motionDetectionSwitch">
+ <span class="help-mark" title="enable this to use and configure the motion detection mechanism">?</span>
+ <a class="settings-section-title">Motion Detection</a>
+ <span class="minimize"></span>
+ </div>
+ <table class="settings advanced-setting">
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Show Frame Changes</span></td>
+ <td class="settings-item-value"><input type="checkbox" class="styled motion-detection camera-config" id="showFrameChangesSwitch"></td>
+ <td><span class="help-mark" title="if this is enabled, frame changes (number of pixels as well as the changed area) are shown on the video; temporarily enable this option to help adjust the motion detection parameters">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="0" max="20" snap="0" ticksnum="5" decimals="1" unit="%">
+ <td class="settings-item-label"><span class="settings-item-label">Frame Change Threshold</span></td>
+ <td class="settings-item-value"><input type="text" class="range styled motion-detection camera-config" id="frameChangeThresholdSlider"></td>
+ <td><span class="help-mark" title="indicates the minimal percent of the image that must change between two successive frames in order for motion to be detected (smaller values give a more sensitive detection, but are prone to false positives)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Auto Noise Detection</span></td>
+ <td class="settings-item-value"><input type="checkbox" class="styled motion-detection camera-config" id="autoNoiseDetectSwitch"></td>
+ <td><span class="help-mark" title="enable this to automatically adjust the noise level">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="0" max="25" snap="0" ticksnum="6" decimals="0" unit="%">
+ <td class="settings-item-label"><span class="settings-item-label">Noise Level</span></td>
+ <td class="settings-item-value"><input type="text" class="range styled motion-detection camera-config" id="noiseLevelSlider"></td>
+ <td><span class="help-mark" title="manually sets the noise level to a fixed value">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td colspan="100"><div class="settings-item-separator"></div></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="1" max="86400" required="true">
+ <td class="settings-item-label"><span class="settings-item-label">Motion Gap</span></td>
+ <td class="settings-item-value"><input type="text" class="number styled motion-detection camera-config" id="eventGapEntry"><span class="settings-item-unit">seconds</span></td>
+ <td><span class="help-mark" title="sets the number of seconds of silence (i.e. no motion) that mark the end of a motion event">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="0" max="100" required="true">
+ <td class="settings-item-label"><span class="settings-item-label">Captured Before</span></td>
+ <td class="settings-item-value"><input type="text" class="number styled motion-detection camera-config" id="preCaptureEntry"><span class="settings-item-unit">frames</span></td>
+ <td><span class="help-mark" title="sets the number of frames to be captured (and included in the movie) before a motion event is detected">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="0" max="100" required="true">
+ <td class="settings-item-label"><span class="settings-item-label">Captured After</span></td>
+ <td class="settings-item-value"><input type="text" class="number styled motion-detection camera-config" id="postCaptureEntry"><span class="settings-item-unit">frames</span></td>
+ <td><span class="help-mark" title="sets the number of frames to be captured (and included in the movie) after a motion event is detected">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="1" max="1000" required="true">
+ <td class="settings-item-label"><span class="settings-item-label">Minimum Motion Frames</span></td>
+ <td class="settings-item-value"><input type="text" class="number styled motion-detection camera-config" id="minimumMotionFramesEntry"><span class="settings-item-unit">frames</span></td>
+ <td><span class="help-mark" title="sets the minimum number of successive motion frames required to start a motion event">?</span></td>
+ </tr>
+ {% for config in camera_sections.get('motion-detection', {}).get('configs', []) %}
+ {{config_item(config)}}
+ {% endfor %}
+ </table>
+
+ <div class="settings-section-title">
+ <input type="checkbox" class="styled section motion-movies camera-config" id="motionMoviesSwitch">
+ <span class="help-mark" title="enable this if you want to record motion movies">?</span>
+ <a class="settings-section-title">Motion Movies</a>
+ <span class="minimize"></span>
+ </div>
+ <table class="settings">
+ <tr class="settings-item advanced-setting" required="true" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Movie File Name</span></td>
+ <td class="settings-item-value"><input type="text" class="styled motion-movies camera-config" id="movieFileNameEntry" placeholder="file name pattern..."></td>
+ <td><span class="help-mark" title="sets the name pattern for the movie (MPEG) files; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %q = frame number, / = subfolder">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
+ <td class="settings-item-label"><span class="settings-item-label">Movie Quality</span></td>
+ <td class="settings-item-value"><input type="text" class="range styled motion-movies camera-config" id="movieQualitySlider"></td>
+ <td><span class="help-mark" title="sets the MPEG video quality (higher values produce a better video quality but require more storage space)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="0" max="86400" required="true">
+ <td class="settings-item-label"><span class="settings-item-label">Maximum Movie Length</span></td>
+ <td class="settings-item-value"><input type="text" class="number styled motion-movies camera-config" id="maxMovieLengthEntry"><span class="settings-item-unit">seconds</span></td>
+ <td><span class="help-mark" title="sets the maximum length of motion movies, in seconds; if the motion event lasts longer, a new movie file is created; use 0 for infinite length">?</span></td>
+ </tr>
+ <tr class="settings-item">
+ <td class="settings-item-label"><span class="settings-item-label">Preserve Movies</span></td>
+ <td class="settings-item-value">
+ <select class="styled motion-movies camera-config" id="preserveMoviesSelect">
+ <option value="1">For One Day</option>
+ <option value="7">For One Week</option>
+ <option value="30">For One Month</option>
+ <option value="365">For One Year</option>
+ <option value="0">Forever</option>
+ <option value="-1">Custom</option>
+ </select>
+ </td>
+ <td><span class="help-mark" title="movies older than the specified duration are automatically deleted to free storage space">?</span></td>
+ </tr>
+ <tr class="settings-item" min="1" max="3650" required="true">
+ <td class="settings-item-label"><span class="settings-item-label">Movies Lifetime</span></td>
+ <td class="settings-item-value"><input type="text" class="styled number motion-movies camera-config" id="moviesLifetimeEntry"><span class="settings-item-unit">days</span></td>
+ <td><span class="help-mark" title="sets the number of days after which the movies will be deleted automatically">?</span></td>
+ </tr>
+ {% for config in camera_sections.get('motion-movies', {}).get('configs', []) %}
+ {{config_item(config)}}
+ {% endfor %}
+ </table>
+ <div class="settings-section-title advanced-setting">
+ <span class="help-mark" title="enable this if you want to be notified when motion is detected">?</span>
+ <a class="settings-section-title">Motion Notifications</a>
+ <span class="minimize"></span>
+ </div>
+ <table class="settings">
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Email Notifications</span></td>
+ <td class="settings-item-value"><input type="checkbox" class="styled notifications camera-config" id="emailNotificationsSwitch"></td>
+ <td><span class="help-mark" title="enable this if you want to receive email notifications whenever a motion event is detected">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" required="true" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Email Addresses</span></td>
+ <td class="settings-item-value"><input type="text" class="styled notifications camera-config" id="emailAddressesEntry" placeholder="email addresses..."></td>
+ <td><span class="help-mark" title="email addresses (separated by comma) that are added here will receive notifications whenever a motion event is detected">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" required="true" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">SMTP Server</span></td>
+ <td class="settings-item-value"><input type="text" class="styled notifications camera-config" id="smtpServerEntry" placeholder="e.g. smtp.gmail.com"></td>
+ <td><span class="help-mark" title="enter the hostname or IP address of your SMTP server (for Gmail use smtp.gmail.com)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="1" max="65535" required="true">
+ <td class="settings-item-label"><span class="settings-item-label">SMTP Port</span></td>
+ <td class="settings-item-value"><input type="text" class="styled number notifications camera-config" id="smtpPortEntry" placeholder="e.g. 587"></td>
+ <td><span class="help-mark" title="enter the port used by your SMTP server (usually 465 for non-TLS connections and 587 for TLS connections)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">SMTP Account</span></td>
+ <td class="settings-item-value"><input type="text" class="styled notifications camera-config" id="smtpAccountEntry" placeholder="account@gmail.com..."></td>
+ <td><span class="help-mark" title="enter your SMTP account (normally your email address)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">SMTP Password</span></td>
+ <td class="settings-item-value"><input type="password" class="styled notifications camera-config" id="smtpPasswordEntry"></td>
+ <td><span class="help-mark" title="enter your SMTP account password (for Gmail use your Google password or an app-specific generated password)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Use TLS</span></td>
+ <td class="settings-item-value"><input type="checkbox" class="styled notifications camera-config" id="smtpTlsSwitch"></td>
+ <td><span class="help-mark" title="enable this if your SMTP server requires TLS (Gmail needs this to be enabled)">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" min="0" max="60" required="true">
+ <td class="settings-item-label"><span class="settings-item-label">Attached Pictures Time Span</span></td>
+ <td class="settings-item-value"><input type="text" class="number styled notifications camera-config" id="emailPictureTimeSpanEntry"><span class="settings-item-unit">seconds</span></td>
+ <td><span class="help-mark" title="defines the picture search time interval to use when creating email attachments (higher values generate emails with more pictures at the cost of an increased notification delay); set to 0 to disable picture attachments">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td colspan="100"><div class="settings-item-separator"></div></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Web Hook Notifications</span></td>
+ <td class="settings-item-value"><input type="checkbox" class="styled notifications camera-config" id="webHookNotificationsSwitch"></td>
+ <td><span class="help-mark" title="enable this if you want a URL to be requested whenever a motion event is detected">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" required="true" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Web Hook URL</span></td>
+ <td class="settings-item-value"><input type="text" class="styled notifications camera-config" id="webHookUrlEntry" placeholder="e.g. http://example.com/notify/"></td>
+ <td><span class="help-mark" title="a URL to be requested when motion is detected; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %q = frame number">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">HTTP Method</span></td>
+ <td class="settings-item-value">
+ <select class="styled notifications camera-config" id="webHookHttpMethodSelect">
+ <option value="GET">GET</option>
+ <option value="POST">POST</option>
+ </select>
+ </td>
+ <td><span class="help-mark" title="the HTTP method to use when requesting the web hook URL">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td colspan="100"><div class="settings-item-separator"></div></td>
+ </tr>
+ <tr class="settings-item advanced-setting">
+ <td class="settings-item-label"><span class="settings-item-label">Run A Command</span></td>
+ <td class="settings-item-value"><input type="checkbox" class="styled notifications camera-config" id="commandNotificationsSwitch"></td>
+ <td><span class="help-mark" title="enable this if you want to execute a command whenever a motion event is detected">?</span></td>
+ </tr>
+ <tr class="settings-item advanced-setting" required="true" strip="true">
+ <td class="settings-item-label"><span class="settings-item-label">Command</span></td>
+ <td class="settings-item-value"><input type="text" class="styled notifications camera-config" id="commandNotificationsEntry" placeholder="command..."></td>
+ <td><span class="help-mark" title="a command to be executed when motion is detected; multiple commands can be separated by a semicolon; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %q = frame number">?</span></td>
+ </tr>
+ {% for config in camera_sections.get('notifications', {}).get('configs', []) %}
+ {{config_item(config)}}
+ {% endfor %}
+ </table>
+
+ <div class="settings-section-title">
+ <input type="checkbox" class="styled section working-schedule camera-config" id="workingScheduleSwitch">
+ <span class="help-mark" title="enable this if you want to define a weekly working schedule for motion detection">?</span>
+ <a class="settings-section-title">Working Schedule</a>
+ <span class="minimize"></span>
+ </div>
+ <table class="settings">
+ <tr class="settings-item">
+ <td class="settings-item-label"><span class="settings-item-label">Monday</span></td>
+ <td class="settings-item-value">
+ <input type="checkbox" class="styled working-schedule camera-config" id="mondayEnabledSwitch">
+ <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="mondayFromEntry">
+ <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="mondayToEntry">
+ </td>
+ <td><span class="help-mark" title="sets the working schedule time interval for Mondays">?</span></td>
+ </tr>
+ <tr class="settings-item">
+ <td class="settings-item-label"><span class="settings-item-label">Tuesday</span></td>
+ <td class="settings-item-value">
+ <input type="checkbox" class="styled working-schedule camera-config" id="tuesdayEnabledSwitch">
+ <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="tuesdayFromEntry">
+ <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="tuesdayToEntry">
+ </td>
+ <td><span class="help-mark" title="sets the working schedule time interval for Tuesdays">?</span></td>
+ </tr>
+ <tr class="settings-item">
+ <td class="settings-item-label"><span class="settings-item-label">Wednesday</span></td>
+ <td class="settings-item-value">
+ <input type="checkbox" class="styled working-schedule camera-config" id="wednesdayEnabledSwitch">
+ <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="wednesdayFromEntry">
+ <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="wednesdayToEntry">
+ </td>
+ <td><span class="help-mark" title="sets the working schedule time interval for Wednesdays">?</span></td>
+ </tr>
+ <tr class="settings-item">
+ <td class="settings-item-label"><span class="settings-item-label">Thursday</span></td>
+ <td class="settings-item-value">
+ <input type="checkbox" class="styled working-schedule camera-config" id="thursdayEnabledSwitch">
+ <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="thursdayFromEntry">
+ <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="thursdayToEntry">
+ </td>
+ <td><span class="help-mark" title="sets the working schedule time interval for Thursdays">?</span></td>
+ </tr>
+ <tr class="settings-item">
+ <td class="settings-item-label"><span class="settings-item-label">Friday</span></td>
+ <td class="settings-item-value">
+ <input type="checkbox" class="styled working-schedule camera-config" id="fridayEnabledSwitch">
+ <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="fridayFromEntry">
+ <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="fridayToEntry">
+ </td>
+ <td><span class="help-mark" title="sets the working schedule time interval for Friday">?</span></td>
+ </tr>
+ <tr class="settings-item">
+ <td class="settings-item-label"><span class="settings-item-label">Saturday</span></td>
+ <td class="settings-item-value">
+ <input type="checkbox" class="styled working-schedule camera-config" id="saturdayEnabledSwitch">
+ <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="saturdayFromEntry">
+ <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="saturdayToEntry">
+ </td>
+ <td><span class="help-mark" title="sets the working schedule time interval for Saturday">?</span></td>
+ </tr>
+ <tr class="settings-item">
+ <td class="settings-item-label"><span class="settings-item-label">Sunday</span></td>
+ <td class="settings-item-value">
+ <input type="checkbox" class="styled working-schedule camera-config" id="sundayEnabledSwitch">
+ <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="sundayFromEntry">
+ <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="sundayToEntry">
+ </td>
+ <td><span class="help-mark" title="sets the working schedule time interval for Sunday">?</span></td>
+ </tr>
+ <tr class="settings-item">
+ <td class="settings-item-label"><span class="settings-item-label">Detect Motion</span></td>
+ <td class="settings-item-value">
+ <select class="styled working-schedule camera-config" id="workingScheduleTypeSelect">
+ <option value="during">During Working Schedule</option>
+ <option value="outside">Outside Working Schedule</option>
+ </select>
+ </td>
+ <td><span class="help-mark" title="sets whether motion detection should be active during or outside the working schedule">?</span></td>
+ </tr>
+ {% for config in camera_sections.get('working-schedule', {}).get('configs', []) %}
+ {{config_item(config)}}
+ {% endfor %}
+ </table>
+
+ {% for section in camera_sections.values() %}
+ {% if section.get('label') and section.get('configs') %}
+ <div class="settings-section-title {% if section['advanced'] %}advanced-setting{% endif %}">
+ {% if section.get('onoff') %}<input type="checkbox" class="styled section additional-section {{section['name']}} camera-config" id="{{section['name']}}Switch">{% endif %}
+ {% if section.get('description') %}<span class="help-mark" title="{{section['description']}}">?</span>{% endif %}
+ <a class="settings-section-title">{{section['label']}}</a>
+ <span class="minimize {% if section.get('open') %}open{% endif %}"></span>
+ </div>
+ <table class="settings">
+ {% for config in section['configs'] %}
+ {{config_item(config)}}
+ {% endfor %}
+ </table>
+ {% endif %}
+ {% endfor %}
+
+ <div class="settings-progress"></div>
+ </div>
+ </div>
+ <img class="background-logo" src="{{STATIC_URL}}img/motioneye-logo.svg" onmousedown="return false;">
+ <div class="page-container"></div>
+ <div class="footer">
+ <div class="copyright-note">copyright © Calin Crisan</div>
+ </div>
+ </div>
+ {% else %}
+ <div class="camera-frame" id="camera{{camera_id}}"
+ streaming_framerate="{{camera_config['stream_maxrate']}}" streaming_server_resize="{{camera_config['@webcam_server_resize']}}"
+ proto="{{camera_config['@proto']}}" url="{{camera_config['@url']}}">
+
+ <div class="camera-container">
+ <div class="camera-placeholder"><img class="no-camera" src="{{STATIC_URL}}img/no-camera.svg"></div><img
+ class="camera"><div class="camera-progress"><img class="camera-progress"></div>
+ </div>
+ </div>
+ {% endif %}
+ <div class="modal-glass"></div>
+ <div class="modal-container"></div>
+ <div class="popup-message-container"></div>
+{% endblock %}
--- /dev/null
+{% extends "base.html" %}
+
+{% block script %}
+ {{super()}}
+ <script type="text/javascript">
+ var hostname = '{{hostname}}';
+ var version = '{{version}}';
+ </script>
+ <script type="text/javascript" src="{{STATIC_URL}}js/version.js"></script>
+{% endblock %}
+
+{% block body %}
+hostname = "{{hostname}}"<br>
+version = "{{version}}"
+{% endblock %}
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+import logging
+import multiprocessing
+import os
+import signal
+import tornado
+
+import cleanup
+import mediafiles
+import settings
+
+
+_process = None
+
+
+def start():
+ # schedule the first call a bit later to improve performance at startup
+ ioloop = tornado.ioloop.IOLoop.instance()
+ ioloop.add_timeout(datetime.timedelta(seconds=30), _run_process)
+
+
+def stop():
+ global _process
+
+ if not running():
+ _process = None
+ return
+
+ if _process.is_alive():
+ _process.join(timeout=10)
+
+ if _process.is_alive():
+ logging.error('thumbnailer process did not finish in time, killing it...')
+ os.kill(_process.pid, signal.SIGKILL)
+
+ _process = None
+
+
+def running():
+ return _process is not None and _process.is_alive()
+
+
+def _run_process():
+ global _process
+
+ # schedule the next call
+ ioloop = tornado.ioloop.IOLoop.instance()
+ ioloop.add_timeout(datetime.timedelta(seconds=settings.THUMBNAILER_INTERVAL), _run_process)
+
+ if not running() and not cleanup.running(): # check that the previous process has finished and that cleanup is not running
+ logging.debug('running thumbnailer process...')
+
+ _process = multiprocessing.Process(target=_do_next_movie_thumbail)
+ _process.start()
+
+
+def _do_next_movie_thumbail():
+ # this will be executed in a separate subprocess
+
+ # ignore the terminate and interrupt signals in this subprocess
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
+ signal.signal(signal.SIGTERM, signal.SIG_IGN)
+
+ try:
+ mediafiles.make_next_movie_preview()
+
+ except Exception as e:
+ logging.error('failed to make movie thumbnail: %(msg)s' % {
+ 'msg': unicode(e)}, exc_info=True)
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import hashlib
+import logging
+import os
+import settings
+import subprocess
+
+from config import additional_config
+
+
+LOCAL_TIME_FILE = settings.LOCAL_TIME_FILE # @UndefinedVariable
+
+
+def _get_time_zone_symlink():
+ file = settings.LOCAL_TIME_FILE
+ if not file:
+ return None
+
+ for i in xrange(8): # recursively follow the symlinks @UnusedVariable
+ try:
+ file = os.readlink(file)
+
+ except OSError:
+ break
+
+ if file and file.startswith('/usr/share/zoneinfo/'):
+ file = file[20:]
+
+ else:
+ file = None
+
+ time_zone = file or None
+ if time_zone:
+ logging.debug('found time zone by symlink method: %s' % time_zone)
+
+ return time_zone
+
+
+def _get_time_zone_md5():
+ if settings.LOCAL_TIME_FILE:
+ return None
+
+ try:
+ output = subprocess.check_output('cd /usr/share/zoneinfo; find * -type f | xargs md5sum', shell=True)
+
+ except Exception as e:
+ logging.error('getting md5 of zoneinfo files failed: %s' % e)
+
+ return None
+
+ lines = [l for l in output.split('\n') if l]
+ lines = [l.split(None, 1) for l in lines]
+ time_zone_by_md5 = dict(lines)
+
+ try:
+ with open(settings.LOCAL_TIME_FILE, 'r') as f:
+ data = f.read()
+
+ except Exception as e:
+ logging.error('failed to read local time file: %s' % e)
+
+ return None
+
+ md5 = hashlib.md5(data).hexdigest()
+ time_zone = time_zone_by_md5.get(md5)
+
+ if time_zone:
+ logging.debug('found time zone by md5 method: %s' % time_zone)
+
+ return time_zone
+
+
+def _get_time_zone():
+ return _get_time_zone_symlink() or _get_time_zone_md5() or 'UTC'
+
+
+def _set_time_zone(time_zone):
+ time_zone = time_zone or 'UTC'
+
+ zoneinfo_file = '/usr/share/zoneinfo/' + time_zone
+ if not os.path.exists(zoneinfo_file):
+ logging.error('%s file does not exist' % zoneinfo_file)
+
+ return False
+
+ logging.debug('linking "%s" to "%s"' % (settings.LOCAL_TIME_FILE, zoneinfo_file))
+
+ try:
+ os.remove(settings.LOCAL_TIME_FILE)
+
+ except:
+ pass # nevermind
+
+ try:
+ os.symlink(zoneinfo_file, settings.LOCAL_TIME_FILE)
+
+ return True
+
+ except Exception as e:
+ logging.error('failed to link "%s" to "%s": %s' % (settings.LOCAL_TIME_FILE, zoneinfo_file, e))
+
+ return False
+
+
+@additional_config
+def timeZone():
+ if not LOCAL_TIME_FILE:
+ return
+
+ import pytz
+ timezones = pytz.common_timezones
+
+ return {
+ 'label': 'Time Zone',
+ 'description': 'selecting the right timezone assures a correct timestamp displayed on pictures and movies',
+ 'type': 'choices',
+ 'choices': [(t, t) for t in timezones],
+ 'section': 'general',
+ 'advanced': True,
+ 'reboot': True,
+ 'get': _get_time_zone,
+ 'set': _set_time_zone
+ }
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import re
+
+
+# versions
+
+def get_version():
+ import motioneye
+
+ return motioneye.VERSION
+
+
+def get_all_versions():
+ return []
+
+
+def compare_versions(version1, version2):
+ version1 = re.sub('[^0-9.]', '', version1)
+ version2 = re.sub('[^0-9.]', '', version2)
+
+ version1 = [int(n) for n in version1.split('.')]
+ version2 = [int(n) for n in version2.split('.')]
+
+ len1 = len(version1)
+ len2 = len(version2)
+ length = min(len1, len2)
+ for i in xrange(length):
+ p1 = version1[i]
+ p2 = version2[i]
+
+ if p1 < p2:
+ return -1
+
+ elif p1 > p2:
+ return 1
+
+ if len1 < len2:
+ return -1
+
+ elif len1 > len2:
+ return 1
+
+ else:
+ return 0
+
+
+def perform_update(version):
+ logging.error('updating is not implemented')
+
+ return False
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import base64
+import datetime
+import functools
+import hashlib
+import logging
+import os
+import re
+import socket
+import time
+import urllib
+import urlparse
+
+from tornado.httpclient import AsyncHTTPClient, HTTPRequest
+from tornado.iostream import IOStream
+from tornado.ioloop import IOLoop
+
+import settings
+
+
+try:
+ from collections import OrderedDict # @UnusedImport
+
+except:
+ from ordereddict import OrderedDict # @UnusedImport @Reimport
+
+
+_SIGNATURE_REGEX = re.compile('[^a-zA-Z0-9/?_.=&{}\[\]":, _-]')
+
+
+COMMON_RESOLUTIONS = [
+ (320, 240),
+ (640, 480),
+ (800, 480),
+ (1024, 576),
+ (1024, 768),
+ (1280, 720),
+ (1280, 800),
+ (1280, 960),
+ (1280, 1024),
+ (1440, 960),
+ (1440, 1024),
+ (1600, 1200)
+]
+
+
+def pretty_date_time(date_time, tzinfo=None, short=False):
+ if date_time is None:
+ return '('+ _('never') + ')'
+
+ if isinstance(date_time, int):
+ return pretty_date_time(datetime.datetime.fromtimestamp(date_time))
+
+ if short:
+ text = u'{day} {month}, {hm}'.format(
+ day=date_time.day,
+ month=date_time.strftime('%b'),
+ hm=date_time.strftime('%H:%M')
+ )
+
+ else:
+ text = u'{day} {month} {year}, {hm}'.format(
+ day=date_time.day,
+ month=date_time.strftime('%B'),
+ year=date_time.year,
+ hm=date_time.strftime('%H:%M')
+ )
+
+ if tzinfo:
+ offset = tzinfo.utcoffset(datetime.datetime.utcnow()).seconds
+ tz = 'GMT'
+ if offset >= 0:
+ tz += '+'
+
+ else:
+ tz += '-'
+ offset = -offset
+
+ tz += '%.2d' % (offset / 3600) + ':%.2d' % ((offset % 3600) / 60)
+
+ text += ' (' + tz + ')'
+
+ return text
+
+
+def pretty_date(date):
+ if date is None:
+ return '('+ _('never') + ')'
+
+ if isinstance(date, int):
+ return pretty_date(datetime.datetime.fromtimestamp(date))
+
+ return u'{day} {month} {year}'.format(
+ day=date.day,
+ month=_(date.strftime('%B')),
+ year=date.year
+ )
+
+
+def pretty_time(time):
+ if time is None:
+ return ''
+
+ if isinstance(time, datetime.timedelta):
+ hour = time.seconds / 3600
+ minute = (time.seconds % 3600) / 60
+ time = datetime.time(hour, minute)
+
+ return '{hm}'.format(
+ hm=time.strftime('%H:%M')
+ )
+
+
+def pretty_duration(duration):
+ if duration is None:
+ duration = 0
+
+ if isinstance(duration, datetime.timedelta):
+ duration = duration.seconds + duration.days * 86400
+
+ if duration < 0:
+ negative = True
+ duration = -duration
+
+ else:
+ negative = False
+
+ days = int(duration / 86400)
+ duration %= 86400
+ hours = int(duration / 3600)
+ duration %= 3600
+ minutes = int(duration / 60)
+ duration %= 60
+ seconds = duration
+
+ # treat special cases
+ special_result = None
+ if days != 0 and hours == 0 and minutes == 0 and seconds == 0:
+ if days == 1:
+ special_result = str(days) + ' ' + _('day')
+
+ elif days == 7:
+ special_result = '1 ' + _('week')
+
+ elif days in [30, 31, 32]:
+ special_result = '1 ' + _('month')
+
+ elif days in [365, 366]:
+ special_result = '1 ' + _('year')
+
+ else:
+ special_result = str(days) + ' ' + _('days')
+
+ elif days == 0 and hours != 0 and minutes == 0 and seconds == 0:
+ if hours == 1:
+ special_result = str(hours) + ' ' + _('hour')
+
+ else:
+ special_result = str(hours) + ' ' + _('hours')
+
+ elif days == 0 and hours == 0 and minutes != 0 and seconds == 0:
+ if minutes == 1:
+ special_result = str(minutes) + ' ' + _('minute')
+
+ else:
+ special_result = str(minutes) + ' ' + _('minutes')
+
+ elif days == 0 and hours == 0 and minutes == 0 and seconds != 0:
+ if seconds == 1:
+ special_result = str(seconds) + ' ' + _('second')
+
+ else:
+ special_result = str(seconds) + ' ' + _('seconds')
+
+ elif days == 0 and hours == 0 and minutes == 0 and seconds == 0:
+ special_result = str(0) + ' ' + _('seconds')
+
+ if special_result:
+ if negative:
+ special_result = _('minus') + ' ' + special_result
+
+ return special_result
+
+ if days:
+ format = "{d}d{h}h{m}m"
+
+ elif hours:
+ format = "{h}h{m}m"
+
+ elif minutes:
+ format = "{m}m"
+ if seconds:
+ format += "{s}s"
+
+ else:
+ format = "{s}s"
+
+ if negative:
+ format = '-' + format
+
+ return format.format(d=days, h=hours, m=minutes, s=seconds)
+
+
+def pretty_size(size):
+ if size < 1024: # less than 1kB
+ size, unit = size, 'B'
+
+ elif size < 1024 * 1024: # less than 1MB
+ size, unit = size / 1024.0, 'kB'
+
+ elif size < 1024 * 1024 * 1024: # less than 1GB
+ size, unit = size / 1024.0 / 1024, 'MB'
+
+ else: # greater than or equal to 1GB
+ size, unit = size / 1024.0 / 1024 / 1024, 'GB'
+
+ return '%.1f %s' % (size, unit)
+
+
+def pretty_http_error(response):
+ if response.code == 401 or response.error == 'Authentication Error':
+ return 'authentication failed'
+
+ if not response.error:
+ return 'ok'
+
+ msg = unicode(response.error)
+ if msg.startswith('HTTP '):
+ msg = msg.split(':', 1)[-1].strip()
+
+ if msg.startswith('[Errno '):
+ msg = msg.split(']', 1)[-1].strip()
+
+ if 'timeout' in msg.lower() or 'timed out' in msg.lower():
+ msg = 'request timed out'
+
+ return msg
+
+
+def make_str(s):
+ if isinstance(s, str):
+ return s
+
+ try:
+ return str(s)
+
+ except:
+ try:
+ return unicode(s, encoding='utf8').encode('utf8')
+
+ except:
+ return unicode(s).encode('utf8')
+
+
+def make_unicode(s):
+ if isinstance(s, unicode):
+ return s
+
+ try:
+ return unicode(s, encoding='utf8')
+
+ except:
+ try:
+ return unicode(s)
+
+ except:
+ return str(s).decode('utf8')
+
+
+def get_disk_usage(path):
+ logging.debug('getting disk usage for path %(path)s...' % {
+ 'path': path})
+
+ try:
+ result = os.statvfs(path)
+
+ except OSError as e:
+ logging.error('failed to execute statvfs: %(msg)s' % {'msg': unicode(e)})
+
+ return None
+
+ block_size = result.f_frsize
+ free_blocks = result.f_bfree
+ total_blocks = result.f_blocks
+
+ free_size = free_blocks * block_size
+ total_size = total_blocks * block_size
+ used_size = total_size - free_size
+
+ return (used_size, total_size)
+
+
+def local_motion_camera(config):
+ '''Tells if a camera is managed by the local motion instance.'''
+ return bool(config.get('videodevice') or config.get('netcam_url'))
+
+
+def remote_camera(config):
+ '''Tells if a camera is managed by a remote motionEye server.'''
+ return config.get('@proto') == 'motioneye'
+
+
+def v4l2_camera(config):
+ '''Tells if a camera is a v4l2 device managed by the local motion instance.'''
+ return bool(config.get('videodevice'))
+
+
+def net_camera(config):
+ '''Tells if a camera is a network camera managed by the local motion instance.'''
+ return bool(config.get('netcam_url'))
+
+
+def simple_mjpeg_camera(config):
+ '''Tells if a camera is a simple MJPEG camera not managed by any motion instance.'''
+ return bool(config.get('@proto') == 'mjpeg')
+
+
+def test_mjpeg_url(data, auth_modes, allow_jpeg, callback):
+ data = dict(data)
+ data.setdefault('scheme', 'http')
+ data.setdefault('host', '127.0.0.1')
+ data.setdefault('port', '80')
+ data.setdefault('uri', '')
+ data.setdefault('username', None)
+ data.setdefault('password', None)
+
+ url = '%(scheme)s://%(host)s%(port)s%(uri)s' % {
+ 'scheme': data['scheme'],
+ 'host': data['host'],
+ 'port': ':' + str(data['port']) if data['port'] else '',
+ 'uri': data['uri'] or ''}
+
+ called = [False]
+ status_2xx = [False]
+ http_11 = [False]
+
+ def do_request(on_response):
+ if data['username']:
+ auth = auth_modes[0]
+
+ else:
+ auth = 'no'
+
+ 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)
+
+ http_client = AsyncHTTPClient(force_instance=True)
+ http_client.fetch(request, on_response)
+
+ def on_header(header):
+ header = header.lower()
+ if header.startswith('content-type') and status_2xx[0]:
+ content_type = header.split(':')[1].strip()
+ called[0] = True
+
+ if content_type in ['image/jpg', 'image/jpeg', 'image/pjpg'] and allow_jpeg:
+ callback([{'id': 1, 'name': 'JPEG Network Camera', 'keep_alive': http_11[0]}])
+
+ elif content_type.startswith('multipart/x-mixed-replace'):
+ callback([{'id': 1, 'name': 'MJPEG Network Camera', 'keep_alive': http_11[0]}])
+
+ else:
+ callback(error='not a supported network camera')
+
+ else:
+ # check for the status header
+ m = re.match('^http/1.(\d) (\d+) ', header)
+ if m:
+ if int(m.group(2)) / 100 == 2:
+ status_2xx[0] = True
+
+ if m.group(1) == '1':
+ http_11[0] = True
+
+ def on_response(response):
+ if not called[0]:
+ if response.code == 401 and auth_modes and data['username']:
+ status_2xx[0] = False
+ do_request(on_response)
+
+ else:
+ called[0] = True
+ callback(error=pretty_http_error(response) if response.error else 'not a supported network camera')
+
+ username = data['username'] or None
+ password = data['password'] or None
+
+ do_request(on_response)
+
+
+def test_rtsp_url(data, callback):
+ import config
+
+ data = dict(data)
+ data.setdefault('scheme', 'rtsp')
+ data.setdefault('host', '127.0.0.1')
+ data['port'] = data.get('port') or '554'
+ data.setdefault('uri', '')
+ data.setdefault('username', None)
+ data.setdefault('password', None)
+
+ url = '%(scheme)s://%(host)s%(port)s%(uri)s' % {
+ 'scheme': data['scheme'],
+ 'host': data['host'],
+ 'port': ':' + str(data['port']) if data['port'] else '',
+ 'uri': data['uri'] or ''}
+
+ called = [False]
+ timeout = [None]
+ stream = None
+
+ def connect():
+ logging.debug('testing rtsp netcam at %s' % url)
+
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ s.settimeout(settings.MJPG_CLIENT_TIMEOUT)
+ stream = IOStream(s)
+ stream.set_close_callback(on_close)
+ stream.connect((data['host'], int(data['port'])), on_connect)
+
+ timeout[0] = IOLoop.instance().add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT),
+ functools.partial(on_connect, _timeout=True))
+
+ return stream
+
+ def on_connect(_timeout=False):
+ IOLoop.instance().remove_timeout(timeout[0])
+
+ if _timeout:
+ return handle_error('timeout connecting to rtsp netcam')
+
+ if not stream:
+ return handle_error('failed to connect to rtsp netcam')
+
+ logging.debug('connected to rtsp netcam')
+
+ stream.write('\r\n'.join([
+ 'OPTIONS %s RTSP/1.0' % url.encode('utf8'),
+ 'CSeq: 1',
+ 'User-Agent: motionEye',
+ '',
+ ''
+ ]))
+
+ seek_rtsp()
+
+ def seek_rtsp():
+ if check_error():
+ return
+
+ stream.read_until_regex('RTSP/1.0 \d+ ', on_rtsp)
+ timeout[0] = IOLoop.instance().add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT), on_rtsp)
+
+ def on_rtsp(data):
+ IOLoop.instance().remove_timeout(timeout[0])
+
+ if data:
+ if data.endswith('200 '):
+ seek_server()
+
+ else:
+ handle_error('rtsp netcam returned erroneous response: %s' % data)
+
+ else:
+ handle_error('timeout waiting for rtsp netcam response')
+
+ def seek_server():
+ if check_error():
+ return
+
+ stream.read_until_regex('Server: .*', on_server)
+ timeout[0] = IOLoop.instance().add_timeout(datetime.timedelta(seconds=1), on_server)
+
+ def on_server(data=None):
+ IOLoop.instance().remove_timeout(timeout[0])
+
+ if data:
+ identifier = re.findall('Server: (.*)', data)[0].strip()
+ logging.debug('rtsp netcam identifier is "%s"' % identifier)
+
+ else:
+ identifier = None
+ logging.debug('no rtsp netcam identifier')
+
+ handle_success(identifier)
+
+ def on_close():
+ if called[0]:
+ return
+
+ if not check_error():
+ handle_error('connection closed')
+
+ def handle_success(identifier):
+ if called[0]:
+ return
+
+ called[0] = True
+ cameras = []
+ rtsp_support = config.motion_rtsp_support()
+ if identifier:
+ identifier = ' ' + identifier
+
+ else:
+ identifier = ''
+
+ if 'udp' in rtsp_support:
+ cameras.append({'id': 'udp', 'name': '%sRTSP/UDP Camera' % identifier})
+
+ if 'tcp' in rtsp_support:
+ cameras.append({'id': 'tcp', 'name': '%sRTSP/TCP Camera' % identifier})
+
+ callback(cameras)
+
+ def handle_error(e):
+ if called[0]:
+ return
+
+ called[0] = True
+ logging.error('rtsp client error: %s' % unicode(e))
+
+ try:
+ stream.close()
+
+ except:
+ pass
+
+ callback(error=unicode(e))
+
+ def check_error():
+ error = getattr(stream, 'error', None)
+ if error and getattr(error, 'errno', None) != 0:
+ handle_error(error.strerror)
+ return True
+
+ if stream and stream.socket is None:
+ handle_error('connection closed')
+ stream.close()
+
+ return True
+
+ return False
+
+ stream = connect()
+
+
+def compute_signature(method, uri, body, key):
+ parts = list(urlparse.urlsplit(uri))
+ query = [q for q in urlparse.parse_qsl(parts[3], keep_blank_values=True) if (q[0] != '_signature')]
+ query.sort(key=lambda q: q[0])
+ # "safe" characters here are set to match the encodeURIComponent JavaScript counterpart
+ query = [(n, urllib.quote(v, safe="!'()*~")) for (n, v) in query]
+ query = '&'.join([(q[0] + '=' + q[1]) for q in query])
+ parts[0] = parts[1] = ''
+ parts[3] = query
+ uri = urlparse.urlunsplit(parts)
+ uri = _SIGNATURE_REGEX.sub('-', uri)
+ key = _SIGNATURE_REGEX.sub('-', key)
+
+ if body and body.startswith('---'):
+ body = None # file attachment
+
+ body = body and _SIGNATURE_REGEX.sub('-', body.decode('utf8'))
+
+ return hashlib.sha1('%s:%s:%s:%s' % (method, uri, body or '', key)).hexdigest().lower()
+
+
+def build_basic_header(username, password):
+ return 'Basic ' + base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
+
+
+def build_digest_header(method, url, username, password, state):
+ realm = state['realm']
+ nonce = state['nonce']
+ last_nonce = state.get('last_nonce', '')
+ nonce_count = state.get('nonce_count', 0)
+ qop = state.get('qop')
+ algorithm = state.get('algorithm')
+ opaque = state.get('opaque')
+
+ if algorithm is None:
+ _algorithm = 'MD5'
+
+ else:
+ _algorithm = algorithm.upper()
+
+ if _algorithm == 'MD5' or _algorithm == 'MD5-SESS':
+ def md5_utf8(x):
+ if isinstance(x, str):
+ x = x.encode('utf-8')
+ return hashlib.md5(x).hexdigest()
+ hash_utf8 = md5_utf8
+
+ elif _algorithm == 'SHA':
+ def sha_utf8(x):
+ if isinstance(x, str):
+ x = x.encode('utf-8')
+ return hashlib.sha1(x).hexdigest()
+ hash_utf8 = sha_utf8
+
+ KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
+
+ if hash_utf8 is None:
+ return None
+
+ entdig = None
+ p_parsed = urlparse.urlparse(url)
+ path = p_parsed.path
+ if p_parsed.query:
+ path += '?' + p_parsed.query
+
+ A1 = '%s:%s:%s' % (username, realm, password)
+ A2 = '%s:%s' % (method, path)
+
+ HA1 = hash_utf8(A1)
+ HA2 = hash_utf8(A2)
+
+ if nonce == last_nonce:
+ nonce_count += 1
+
+ else:
+ nonce_count = 1
+
+ ncvalue = '%08x' % nonce_count
+ s = str(nonce_count).encode('utf-8')
+ s += nonce.encode('utf-8')
+ s += time.ctime().encode('utf-8')
+ s += os.urandom(8)
+
+ cnonce = (hashlib.sha1(s).hexdigest()[:16])
+ if _algorithm == 'MD5-SESS':
+ HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
+
+ if qop is None:
+ respdig = KD(HA1, "%s:%s" % (nonce, HA2))
+
+ elif qop == 'auth' or 'auth' in qop.split(','):
+ noncebit = "%s:%s:%s:%s:%s" % (
+ nonce, ncvalue, cnonce, 'auth', HA2
+ )
+ respdig = KD(HA1, noncebit)
+
+ else:
+ return None
+
+ last_nonce = nonce
+
+ base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
+ 'response="%s"' % (username, realm, nonce, path, respdig)
+ if opaque:
+ base += ', opaque="%s"' % opaque
+ if algorithm:
+ base += ', algorithm="%s"' % algorithm
+ if entdig:
+ base += ', digest="%s"' % entdig
+ if qop:
+ base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce)
+
+ state['last_nonce'] = last_nonce
+ state['nonce_count'] = nonce_count
+
+ return 'Digest %s' % (base)
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import fcntl
+import logging
+import os.path
+import re
+import stat
+import subprocess
+import time
+import utils
+
+
+_resolutions_cache = {}
+_ctrls_cache = {}
+_ctrl_values_cache = {}
+
+_DEV_V4L_BY_ID = '/dev/v4l/by-id/'
+
+
+def find_v4l2_ctl():
+ try:
+ return subprocess.check_output('which v4l2-ctl', shell=True).strip()
+
+ except subprocess.CalledProcessError: # not found
+ return None
+
+
+def list_devices():
+ global _resolutions_cache, _ctrls_cache, _ctrl_values_cache
+
+ logging.debug('listing v4l2 devices...')
+
+ try:
+ output = ''
+ started = time.time()
+ p = subprocess.Popen('v4l2-ctl --list-devices 2>/dev/null', shell=True, stdout=subprocess.PIPE, bufsize=1)
+
+ fd = p.stdout.fileno()
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ while True:
+ try:
+ data = p.stdout.read(1024)
+ if not data:
+ break
+
+ except IOError:
+ data = ''
+ time.sleep(0.01)
+
+ output += data
+
+ if len(output) > 10240:
+ logging.warn('v4l2-ctl command returned more than 10k of output')
+ break
+
+ if time.time() - started > 3:
+ logging.warn('v4l2-ctl command ran for more than 3 seconds')
+ break
+
+ except subprocess.CalledProcessError:
+ logging.debug('failed to list devices (probably no devices installed)')
+ return []
+
+ try:
+ # try to kill the v4l2-ctl subprocess
+ p.kill()
+
+ except:
+ pass # nevermind
+
+ name = None
+ devices = []
+ for line in output.split('\n'):
+ if line.startswith('\t'):
+ device = line.strip()
+ persistent_device = find_persistent_device(device)
+ devices.append((device, persistent_device, name))
+
+ logging.debug('found device %(name)s: %(device)s, %(persistent_device)s' % {
+ 'name': name, 'device': device, 'persistent_device': persistent_device})
+
+ else:
+ name = line.split('(')[0].strip()
+
+ # clear the cache
+ _resolutions_cache = {}
+ _ctrls_cache = {}
+ _ctrl_values_cache = {}
+
+ return devices
+
+
+def list_resolutions(device):
+ global _resolutions_cache
+
+ device = utils.make_str(device)
+
+ if device in _resolutions_cache:
+ return _resolutions_cache[device]
+
+ logging.debug('listing resolutions of device %(device)s...' % {'device': device})
+
+ 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': device}, shell=True, stdout=subprocess.PIPE, bufsize=1)
+
+ fd = p.stdout.fileno()
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ while True:
+ try:
+ data = p.stdout.read(1024)
+ if not data:
+ break
+
+ except IOError:
+ data = ''
+ time.sleep(0.01)
+
+ output += data
+
+ if len(output) > 10240:
+ logging.warn('v4l2-ctl command returned more than 10k of output')
+ break
+
+ if time.time() - started > 3:
+ logging.warn('v4l2-ctl command ran for more than 3 seconds')
+ break
+
+ try:
+ # try to kill the v4l2-ctl subprocess
+ p.kill()
+
+ except:
+ pass # nevermind
+
+ for pair in output.split('\n'):
+ pair = pair.strip()
+ if not pair:
+ continue
+
+ width, height = pair.split('x')
+ width = int(width)
+ height = int(height)
+
+ if (width, height) in resolutions:
+ continue # duplicate resolution
+
+ if width < 96 or height < 96: # some reasonable minimal values
+ continue
+
+ if width % 16 or height % 16: # ignore non-modulo 16 resolutions
+ continue
+
+ resolutions.add((width, height))
+
+ logging.debug('found resolution %(width)sx%(height)s for device %(device)s' % {
+ 'device': device, 'width': width, 'height': height})
+
+ if not resolutions:
+ logging.debug('no resolutions found for device %(device)s, using common values' % {'device': device})
+
+ # no resolution returned by v4l2-ctl call, add common default resolutions
+ resolutions = utils.COMMON_RESOLUTIONS
+
+ resolutions = list(sorted(resolutions, key=lambda r: (r[0], r[1])))
+ _resolutions_cache[device] = resolutions
+
+ return resolutions
+
+
+def device_present(device):
+ device = utils.make_str(device)
+
+ try:
+ st = os.stat(device)
+ return stat.S_ISCHR(st.st_mode)
+
+ except:
+ return False
+
+
+def find_persistent_device(device):
+ device = utils.make_str(device)
+
+ try:
+ devs_by_id = os.listdir(_DEV_V4L_BY_ID)
+
+ except OSError:
+ return device
+
+ for p in devs_by_id:
+ p = os.path.join(_DEV_V4L_BY_ID, p)
+ if os.path.realpath(p) == device:
+ return p
+
+ return device
+
+
+def get_brightness(device):
+ return _get_ctrl(device, 'brightness')
+
+
+def set_brightness(device, value):
+ _set_ctrl(device, 'brightness', value)
+
+
+def get_contrast(device):
+ return _get_ctrl(device, 'contrast')
+
+
+def set_contrast(device, value):
+ _set_ctrl(device, 'contrast', value)
+
+
+def get_saturation(device):
+ return _get_ctrl(device, 'saturation')
+
+
+def set_saturation(device, value):
+ _set_ctrl(device, 'saturation', value)
+
+
+def get_hue(device):
+ return _get_ctrl(device, 'hue')
+
+
+def set_hue(device, value):
+ _set_ctrl(device, 'hue', value)
+
+
+def _get_ctrl(device, control):
+ global _ctrl_values_cache
+
+ device = utils.make_str(device)
+
+ if not device_present(device):
+ return None
+
+ if device in _ctrl_values_cache and control in _ctrl_values_cache[device]:
+ return _ctrl_values_cache[device][control]
+
+ controls = _list_ctrls(device)
+ properties = controls.get(control)
+ if properties is None:
+ logging.warn('control %(control)s not found for device %(device)s' % {
+ 'control': control, 'device': device})
+
+ return None
+
+ value = int(properties['value'])
+
+ # adjust the value range
+ if 'min' in properties and 'max' in properties:
+ min_value = int(properties['min'])
+ max_value = int(properties['max'])
+
+ value = int(round((value - min_value) * 100.0 / (max_value - min_value)))
+
+ else:
+ logging.warn('min and max values not found for control %(control)s of device %(device)s' % {
+ 'control': control, 'device': device})
+
+ logging.debug('control %(control)s of device %(device)s is %(value)s%%' % {
+ 'control': control, 'device': device, 'value': value})
+
+ return value
+
+
+def _set_ctrl(device, control, value):
+ global _ctrl_values_cache
+
+ device = utils.make_str(device)
+
+ if not device_present(device):
+ return
+
+ controls = _list_ctrls(device)
+ properties = controls.get(control)
+ if properties is None:
+ logging.warn('control %(control)s not found for device %(device)s' % {
+ 'control': control, 'device': device})
+
+ return
+
+ _ctrl_values_cache.setdefault(device, {})[control] = value
+
+ # adjust the value range
+ if 'min' in properties and 'max' in properties:
+ min_value = int(properties['min'])
+ max_value = int(properties['max'])
+
+ value = int(round(min_value + value * (max_value - min_value) / 100.0))
+
+ else:
+ logging.warn('min and max values not found for control %(control)s of device %(device)s' % {
+ 'control': control, 'device': device})
+
+ logging.debug('setting control %(control)s of device %(device)s to %(value)s' % {
+ 'control': control, 'device': device, 'value': value})
+
+ output = ''
+ started = time.time()
+ p = subprocess.Popen('v4l2-ctl -d "%(device)s" --set-ctrl %(control)s=%(value)s' % {
+ 'device': device, 'control': control, 'value': value}, shell=True, stdout=subprocess.PIPE, bufsize=1)
+
+ fd = p.stdout.fileno()
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ while True:
+ try:
+ data = p.stdout.read(1024)
+ if not data:
+ break
+
+ except IOError:
+ data = ''
+ time.sleep(0.01)
+
+ output += data
+
+ if len(output) > 10240:
+ logging.warn('v4l2-ctl command returned more than 10k of output')
+ break
+
+ if time.time() - started > 3:
+ logging.warn('v4l2-ctl command ran for more than 3 seconds')
+ break
+
+ try:
+ # try to kill the v4l2-ctl subprocess
+ p.kill()
+
+ except:
+ pass # nevermind
+
+
+def _list_ctrls(device):
+ global _ctrls_cache
+
+ device = utils.make_str(device)
+
+ if device in _ctrls_cache:
+ return _ctrls_cache[device]
+
+ output = ''
+ started = time.time()
+ p = subprocess.Popen('v4l2-ctl -d "%(device)s" --list-ctrls' % {
+ 'device': device}, shell=True, stdout=subprocess.PIPE, bufsize=1)
+
+ fd = p.stdout.fileno()
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ while True:
+ try:
+ data = p.stdout.read(1024)
+ if not data:
+ break
+
+ except IOError:
+ data = ''
+ time.sleep(0.01)
+
+ output += data
+
+ if len(output) > 10240:
+ logging.warn('v4l2-ctl command returned more than 10k of output')
+ break
+
+ if time.time() - started > 3:
+ logging.warn('v4l2-ctl command ran for more than 3 seconds')
+ break
+
+ try:
+ # try to kill the v4l2-ctl subprocess
+ p.kill()
+
+ except:
+ pass # nevermind
+
+ controls = {}
+ for line in output.split('\n'):
+ if not line:
+ continue
+
+ match = re.match('^\s*(\w+)\s+\(\w+\)\s+\:\s*(.+)', line)
+ if not match:
+ continue
+
+ (control, properties) = match.groups()
+ properties = dict([v.split('=', 1) for v in properties.split(' ') if v.count('=')])
+ controls[control] = properties
+
+ _ctrls_cache[device] = controls
+
+ return controls
--- /dev/null
+#!/usr/bin/env python
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import sys
+import urllib2
+import urlparse
+
+import settings
+
+from motioneye import _configure_settings, _configure_logging
+
+
+_configure_settings()
+_configure_logging()
+
+
+def print_usage():
+ print 'Usage: webhook.py <method> <url>'
+
+
+if __name__ == '__main__':
+ if len(sys.argv) < 3:
+ print_usage()
+ sys.exit(-1)
+
+ method = sys.argv[1]
+ url = sys.argv[2]
+
+ logging.debug('method = %s' % method)
+ logging.debug('url = %s' % url)
+
+ if method == 'POST':
+ parts = urlparse.urlparse(url)
+ data = parts.query
+
+ else:
+ data = None
+
+ request = urllib2.Request(url, data)
+ try:
+ urllib2.urlopen(request, timeout=settings.REMOTE_REQUEST_TIMEOUT)
+ logging.debug('webhook successfully called')
+
+ except Exception as e:
+ logging.error('failed to call webhook: %s' % e)
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import re
+import settings
+
+from config import additional_config, additional_section
+
+
+WPA_SUPPLICANT_CONF = settings.WPA_SUPPLICANT_CONF # @UndefinedVariable
+
+
+def _get_wifi_settings():
+ # will return the first configured network
+
+ logging.debug('reading wifi settings from %s' % WPA_SUPPLICANT_CONF)
+
+ try:
+ conf_file = open(WPA_SUPPLICANT_CONF, 'r')
+
+ except Exception as e:
+ logging.error('could open wifi settings file %(path)s: %(msg)s' % {
+ 'path': WPA_SUPPLICANT_CONF, 'msg': unicode(e)})
+
+ return {
+ 'wifiEnabled': False,
+ 'wifiNetworkName': '',
+ 'wifiNetworkKey': ''
+ }
+
+ lines = conf_file.readlines()
+ conf_file.close()
+
+ ssid = psk = ''
+ in_section = False
+ for line in lines:
+ line = line.strip()
+ if line.startswith('#'):
+ continue
+
+ if '{' in line:
+ in_section = True
+
+ elif '}' in line:
+ in_section = False
+ break
+
+ elif in_section:
+ m = re.search('ssid\s*=\s*"(.*?)"', line)
+ if m:
+ ssid = m.group(1)
+
+ m = re.search('psk\s*=\s*"(.*?)"', line)
+ if m:
+ psk = m.group(1)
+
+ if ssid:
+ logging.debug('wifi is enabled (ssid = "%s")' % ssid)
+
+ return {
+ 'wifiEnabled': True,
+ 'wifiNetworkName': ssid,
+ 'wifiNetworkKey': psk
+ }
+
+ else:
+ logging.debug('wifi is disabled')
+
+ return {
+ 'wifiEnabled': False,
+ 'wifiNetworkName': ssid,
+ 'wifiNetworkKey': psk
+ }
+
+
+def _set_wifi_settings(s):
+ s.setdefault('wifiEnabled', False)
+ s.setdefault('wifiNetworkName', '')
+ s.setdefault('wifiNetworkKey', '')
+
+ logging.debug('writing wifi settings to %s: enabled=%s, ssid="%s"' % (
+ WPA_SUPPLICANT_CONF, s['wifiEnabled'], s['wifiNetworkName']))
+
+ enabled = s['wifiEnabled']
+ ssid = s['wifiNetworkName']
+ psk = s['wifiNetworkKey']
+
+ # will update the first configured network
+ try:
+ conf_file = open(WPA_SUPPLICANT_CONF, 'r')
+
+ except Exception as e:
+ logging.error('could open wifi settings file %(path)s: %(msg)s' % {
+ 'path': WPA_SUPPLICANT_CONF, 'msg': unicode(e)})
+
+ return
+
+ lines = conf_file.readlines()
+ conf_file.close()
+
+ in_section = False
+ found_ssid = False
+ found_psk = False
+ i = 0
+ while i < len(lines):
+ line = lines[i].strip()
+ if line.startswith('#'):
+ i += 1
+ continue
+
+ if '{' in line:
+ in_section = True
+
+ elif '}' in line:
+ in_section = False
+ if enabled and ssid and not found_ssid:
+ lines.insert(i, ' ssid="' + ssid + '"\n')
+ if enabled and psk and not found_psk:
+ lines.insert(i, ' psk="' + psk + '"\n')
+
+ found_psk = found_ssid = True
+
+ break
+
+ elif in_section:
+ if enabled:
+ if re.match('ssid\s*=\s*".*?"', line):
+ lines[i] = ' ssid="' + ssid + '"\n'
+ found_ssid = True
+
+ elif re.match('psk\s*=\s*".*?"', line):
+ if psk:
+ lines[i] = ' psk="' + psk + '"\n'
+ found_psk = True
+
+ else:
+ lines.pop(i)
+ i -= 1
+
+ else: # wifi disabled
+ if re.match('ssid\s*=\s*".*?"', line) or re.match('psk\s*=\s*".*?"', line):
+ lines.pop(i)
+ i -= 1
+
+ i += 1
+
+ if enabled and not found_ssid:
+ lines.append('network={\n')
+ lines.append(' scan_ssid=1\n')
+ lines.append(' ssid="' + ssid + '"\n')
+ lines.append(' psk="' + psk + '"\n')
+ lines.append('}\n\n')
+
+ try:
+ conf_file = open(WPA_SUPPLICANT_CONF, 'w')
+
+ except Exception as e:
+ logging.error('could open wifi settings file %(path)s: %(msg)s' % {
+ 'path': WPA_SUPPLICANT_CONF, 'msg': unicode(e)})
+
+ return
+
+ for line in lines:
+ conf_file.write(line)
+
+ conf_file.close()
+
+
+@additional_section
+def network():
+ return {
+ 'label': 'Network',
+ 'description': 'configure the network connection',
+ 'advanced': True
+ }
+
+
+@additional_config
+def wifiEnabled():
+ if not WPA_SUPPLICANT_CONF:
+ return
+
+ return {
+ 'label': 'Wireless Network',
+ 'description': 'enable this if you want to connect to a wireless network',
+ 'type': 'bool',
+ 'section': 'network',
+ 'advanced': True,
+ 'reboot': True,
+ 'get': _get_wifi_settings,
+ 'set': _set_wifi_settings,
+ 'get_set_dict': True
+ }
+
+
+@additional_config
+def wifiNetworkName():
+ if not WPA_SUPPLICANT_CONF:
+ return
+
+ return {
+ 'label': 'Wireless Network Name',
+ 'description': 'the name (SSID) of your wireless network',
+ 'type': 'str',
+ 'section': 'network',
+ 'advanced': True,
+ 'required': True,
+ 'reboot': True,
+ 'depends': ['wifiEnabled'],
+ 'get': _get_wifi_settings,
+ 'set': _set_wifi_settings,
+ 'get_set_dict': True
+ }
+
+
+@additional_config
+def wifiNetworkKey():
+ if not WPA_SUPPLICANT_CONF:
+ return
+
+ return {
+ 'label': 'Wireless Network Key',
+ 'description': 'the key (PSK) required to connect to your wireless network',
+ 'type': 'pwd',
+ 'section': 'network',
+ 'advanced': True,
+ 'required': False,
+ 'reboot': True,
+ 'depends': ['wifiEnabled'],
+ 'get': _get_wifi_settings,
+ 'set': _set_wifi_settings,
+ 'get_set_dict': True
+ }
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import datetime
+import logging
+import tornado
+
+import config
+import motionctl
+import utils
+
+
+def start():
+ ioloop = tornado.ioloop.IOLoop.instance()
+ ioloop.add_timeout(datetime.timedelta(seconds=10), _check_ws)
+
+
+def _during_working_schedule(now, working_schedule):
+ parts = working_schedule.split('|')
+ if len(parts) < 7:
+ return False # invalid ws
+
+ ws_day = parts[now.weekday()]
+ parts = ws_day.split('-')
+ if len(parts) != 2:
+ return False # invalid ws
+
+ _from, to = parts
+ if not _from or not to:
+ 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
+
+ try:
+ from_h = int(_from[0])
+ from_m = int(_from[1])
+ to_h = int(to[0])
+ to_m = int(to[1])
+
+ except ValueError:
+ return False # invalid ws
+
+ if now.hour < from_h or now.hour > to_h:
+ return False
+
+ if now.hour == from_h and now.minute < from_m:
+ return False
+
+ if now.hour == to_h and now.minute > to_m:
+ return False
+
+ return True
+
+
+def _check_ws():
+ # schedule the next call
+ ioloop = tornado.ioloop.IOLoop.instance()
+ ioloop.add_timeout(datetime.timedelta(seconds=10), _check_ws)
+
+ if not motionctl.running():
+ return
+
+ now = datetime.datetime.now()
+ for camera_id in config.get_camera_ids():
+ camera_config = config.get_camera(camera_id)
+ if not utils.local_motion_camera(camera_config):
+ continue
+
+ working_schedule = camera_config.get('@working_schedule')
+ 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
+ continue
+
+ 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')
+
+ currently_enabled = motionctl.get_motion_detection(camera_id)
+ if currently_enabled is None: # could not detect current status
+ logging.warn('skipping motion detection status update for camera with id %(id)s' % {'id': camera_id})
+ continue
+
+ if currently_enabled and not must_be_enabled:
+ logging.debug('must disable motion detection for camera with id %(id)s (%(what)s working schedule)' % {
+ 'id': camera_id,
+ 'what': working_schedule_type})
+
+ motionctl.set_motion_detection(camera_id, False)
+
+ elif not currently_enabled and must_be_enabled:
+ logging.debug('must enable motion detection for camera with id %(id)s (%(what)s working schedule)' % {
+ 'id': camera_id,
+ 'what': working_schedule_type})
+
+ motionctl.set_motion_detection(camera_id, True)
+++ /dev/null
-#!/usr/bin/env python
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import datetime
-import logging
-import os
-import re
-import smtplib
-import socket
-import sys
-import time
-
-from email import Encoders
-from email.mime.text import MIMEText
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEBase import MIMEBase
-from tornado.ioloop import IOLoop
-
-import settings
-
-from motioneye import _configure_settings, _configure_logging, _configure_signals
-
-_configure_settings()
-_configure_signals()
-_configure_logging(module='sendmail')
-
-import config
-import mediafiles
-import tzctl
-
-
-messages = {
- 'motion_start': 'Motion has been detected by camera "%(camera)s/%(hostname)s" at %(moment)s (%(timezone)s).'
-}
-
-subjects = {
- 'motion_start': 'motionEye: motion detected by "%(camera)s"'
-}
-
-
-def send_mail(server, port, account, password, tls, to, subject, message, files):
- conn = smtplib.SMTP(server, port, timeout=getattr(settings, 'SMTP_TIMEOUT', 60))
- if tls:
- conn.starttls()
-
- if account and password:
- conn.login(account, password)
-
- _from = account or 'motioneye@' + socket.gethostname()
-
- email = MIMEMultipart()
- email['Subject'] = subject
- email['From'] = _from
- email['To'] = ', '.join(to)
- email.attach(MIMEText(message))
-
- for file in reversed(files):
- part = MIMEBase('image', 'jpeg')
- with open(file, 'rb') as f:
- part.set_payload(f.read())
-
- Encoders.encode_base64(part)
- part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(file))
- email.attach(part)
-
- if files:
- logging.debug('attached %d pictures' % len(files))
-
- logging.debug('sending email message')
- conn.sendmail(_from, to, email.as_string())
- conn.quit()
-
-
-def make_message(subject, message, camera_id, moment, timespan, callback):
- camera_config = config.get_camera(camera_id)
-
- def on_media_files(media_files):
- logging.debug('got media files')
-
- timestamp = time.mktime(moment.timetuple())
-
- 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]
-
- logging.debug('selected %d pictures' % len(media_files))
-
- format_dict = {
- 'camera': camera_config['@name'],
- 'hostname': socket.gethostname(),
- 'moment': moment.strftime('%Y-%m-%d %H:%M:%S'),
- }
-
- if settings.LOCAL_TIME_FILE:
- format_dict['timezone'] = tzctl._get_time_zone()
-
- else:
- format_dict['timezone'] = 'local time'
-
- m = message % format_dict
- s = subject % format_dict
- s = s.replace('\n', ' ')
-
- m += '\n\n'
- m += 'motionEye.'
-
- callback(s, m, media_files)
-
- if not timespan:
- return on_media_files([])
-
- logging.debug('creating email message')
-
- time.sleep(timespan) # give motion some time to create motion pictures
- mediafiles.list_media(camera_config, media_type='picture', callback=on_media_files)
-
-
-def print_usage():
- print 'Usage: sendmail.py <server> <port> <account> <password> <tls> <to> <msg_id> <camera_id> <moment> <frames> [timespan]'
-
-
-if __name__ == '__main__':
- if len(sys.argv) < 10:
- print_usage()
- sys.exit(-1)
-
- server = sys.argv[1]
- port = int(sys.argv[2])
- account = sys.argv[3]
- password = sys.argv[4]
- tls = sys.argv[5].lower() == 'true'
- to = sys.argv[6]
- msg_id = sys.argv[7]
- camera_id = sys.argv[8]
- moment = sys.argv[9]
- try:
- timespan = int(sys.argv[10])
-
- except:
- timespan = 0
-
- logging.debug('hello!')
-
- message = messages.get(msg_id)
- subject = subjects.get(msg_id)
- if not message or not subject:
- logging.error('unknown message id')
- sys.exit(-1)
-
- moment = datetime.datetime.strptime(moment, '%Y-%m-%dT%H:%M:%S')
-
- logging.debug('server = %s' % server)
- logging.debug('port = %s' % port)
- logging.debug('account = %s' % account)
- logging.debug('password = ******')
- logging.debug('server = %s' % server)
- logging.debug('tls = %s' % tls)
- logging.debug('to = %s' % to)
- logging.debug('msg_id = %s' % msg_id)
- logging.debug('camera_id = %s' % camera_id)
- logging.debug('moment = %s' % moment.strftime('%Y-%m-%d %H:%M:%S'))
- logging.debug('smtp timeout = %d' % settings.SMTP_TIMEOUT)
- logging.debug('timespan = %d' % timespan)
-
- if not to:
- logging.info('no email address specified')
- sys.exit(0)
-
- to = [t.strip() for t in re.split('[,;| ]', to)]
- to = [t for t in to if t]
-
- io_loop = IOLoop.instance()
-
- def on_message(subject, message, files):
- try:
- send_mail(server, port, account, password, tls, to, subject, message, files)
- logging.info('email sent')
-
- except Exception as e:
- logging.error('failed to send mail: %s' % e, exc_info=True)
-
- io_loop.stop()
-
- def ioloop_timeout():
- io_loop.stop()
-
- make_message(subject, message, camera_id, moment, timespan, on_message)
-
- io_loop.add_timeout(datetime.timedelta(seconds=settings.SMTP_TIMEOUT), ioloop_timeout)
- io_loop.start()
-
- logging.debug('bye!')
\ No newline at end of file
+++ /dev/null
-
-import logging
-import os.path
-import sys
-
-# you normally don't have to change these
-PROJECT_PATH = os.path.dirname(sys.argv[0])
-TEMPLATE_PATH = os.path.join(PROJECT_PATH, 'templates')
-STATIC_PATH = os.path.join(PROJECT_PATH, 'static')
-
-# static files (.css, .js etc) are served at this root url
-STATIC_URL = '/static/'
-
-# path to the config directory; must be writable
-CONF_PATH = os.path.abspath(os.path.join(PROJECT_PATH, 'conf'))
-
-# pid files go here
-RUN_PATH = os.path.abspath(os.path.join(PROJECT_PATH, 'run'))
-
-# log files go here
-LOG_PATH = os.path.abspath(os.path.join(PROJECT_PATH, 'log'))
-
-# default output path for media files
-MEDIA_PATH = os.path.abspath(os.path.join(PROJECT_PATH, 'media'))
-
-# path to motion binary (automatically detected if not set)
-MOTION_BINARY = None
-
-# set to logging.DEBUG for verbose output
-LOG_LEVEL = logging.INFO
-
-# set to 127.0.0.1 to restrict access to localhost
-LISTEN = '0.0.0.0'
-
-# change the port according to your requirements/restrictions
-PORT = 8765
-
-# interval in seconds at which motionEye checks the SMB mounts
-MOUNT_CHECK_INTERVAL = 300
-
-# interval in seconds at which motionEye checks if motion is running
-MOTION_CHECK_INTERVAL = 10
-
-# interval in seconds at which the janitor is called to remove old pictures and movies
-CLEANUP_INTERVAL = 43200
-
-# interval in seconds at which the thumbnail mechanism runs (set to 0 to disable)
-THUMBNAILER_INTERVAL = 60
-
-# timeout in seconds when waiting for response from a remote motionEye server
-REMOTE_REQUEST_TIMEOUT = 10
-
-# timeout in seconds when waiting for mjpg data from the motion daemon
-MJPG_CLIENT_TIMEOUT = 10
-
-# timeout in seconds after which an idle mjpg client is removed (set to 0 to disable)
-MJPG_CLIENT_IDLE_TIMEOUT = 10
-
-# enable SMB shares (requires root)
-SMB_SHARES = False
-
-# the directory where the SMB mounts will be created
-SMB_MOUNT_ROOT = '/media'
-
-# path to a wpa_supplicant.conf file if wifi settings UI is desired
-WPA_SUPPLICANT_CONF = None
-
-# path to a localtime file if time zone settings UI is desired
-LOCAL_TIME_FILE = None
-
-# enables shutdown and rebooting after changing system settings (such as wifi settings or system updates)
-ENABLE_REBOOT = False
-
-# the timeout in seconds to use when talking to a SMTP server
-SMTP_TIMEOUT = 60
-
-# the time to wait for zip file creation
-ZIP_TIMEOUT = 500
-
-# enable adding and removing cameras from UI
-ADD_REMOVE_CAMERAS = True
--- /dev/null
+
+# Always prefer setuptools over distutils
+from setuptools import setup
+# To use a consistent encoding
+from codecs import open
+from os import path
+
+here = path.abspath(path.dirname(__file__))
+
+with open(path.join(here, 'README.md'), encoding='utf-8') as f:
+ long_description = f.read()
+
+setup(
+ name='motioneye',
+
+ version='0.25.2',
+
+ description='motionEye server',
+ long_description=long_description,
+
+ url='https://bitbucket.org/ccrisan/motioneye/',
+
+ author='Calin Crisan',
+ author_email='ccrisan@gmail.com',
+
+ license='GPLv3',
+
+ # See https://pypi.python.org/pypi?%3Aaction=list_classifiers
+ classifiers=[
+ 'Development Status :: 3 - Beta',
+
+ 'Intended Audience :: End Users/Desktop',
+ 'Topic :: Multimedia :: Video',
+
+ 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
+
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7'
+ ],
+
+ keywords='motion video surveillance frontend',
+
+ packages=['motioneye'],
+
+ install_requires=['tornado>=3.1', 'jinja2', 'pillow', 'pycurl'],
+
+ package_data={
+ 'motioneye': [
+ 'static/*',
+ 'static/*/*',
+ 'templates/*'
+ ]
+ },
+
+ data_files=[],
+
+ entry_points={
+ 'console_scripts': [
+ 'motioneye=motioneye.motioneye:main',
+ ],
+ },
+)
+
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import datetime
-import logging
-import multiprocessing
-import os
-import signal
-import tornado
-
-import mediafiles
-import settings
-import thumbnailer
-
-
-_process = None
-
-
-def start():
- # schedule the first call a bit later to improve performance at startup
- ioloop = tornado.ioloop.IOLoop.instance()
- ioloop.add_timeout(datetime.timedelta(seconds=60), _run_process)
-
-
-def stop():
- global _process
-
- if not running():
- _process = None
- return
-
- if _process.is_alive():
- _process.join(timeout=10)
-
- if _process.is_alive():
- logging.error('cleanup process did not finish in time, killing it...')
- os.kill(_process.pid, signal.SIGKILL)
-
- _process = None
-
-
-def running():
- return _process is not None and _process.is_alive()
-
-
-def _run_process():
- global _process
-
- ioloop = tornado.ioloop.IOLoop.instance()
-
- if thumbnailer.running():
- # postpone if thumbnailer is currently running
- ioloop.add_timeout(datetime.timedelta(seconds=60), _run_process)
-
- return
-
- else:
- # schedule the next call
- ioloop.add_timeout(datetime.timedelta(seconds=settings.CLEANUP_INTERVAL), _run_process)
-
- if not running(): # check that the previous process has finished
- logging.debug('running cleanup process...')
-
- _process = multiprocessing.Process(target=_do_cleanup)
- _process.start()
-
-
-def _do_cleanup():
- # this will be executed in a separate subprocess
-
- # ignore the terminate and interrupt signals in this subprocess
- signal.signal(signal.SIGINT, signal.SIG_IGN)
- signal.signal(signal.SIGTERM, signal.SIG_IGN)
-
- try:
- mediafiles.cleanup_media('picture')
- mediafiles.cleanup_media('movie')
- logging.debug('cleanup done')
-
- except Exception as e:
- logging.error('failed to cleanup media files: %(msg)s' % {
- 'msg': unicode(e)}, exc_info=True)
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import collections
-import datetime
-import errno
-import logging
-import os.path
-import re
-import shlex
-import subprocess
-import urlparse
-
-from tornado.ioloop import IOLoop
-
-import diskctl
-import powerctl
-import settings
-import update
-import utils
-import v4l2ctl
-
-from utils import OrderedDict
-
-
-_CAMERA_CONFIG_FILE_NAME = 'thread-%(id)s.conf'
-_MAIN_CONFIG_FILE_NAME = 'motion.conf'
-
-_main_config_cache = None
-_camera_config_cache = {}
-_camera_ids_cache = None
-_additional_section_funcs = []
-_additional_config_funcs = []
-_additional_structure_cache = {}
-
-# starting with r490 motion config directives have changed a bit
-_LAST_OLD_CONFIG_VERSIONS = (490, '3.2.12')
-
-_KNOWN_MOTION_OPTIONS = set([
- 'auto_brightness', 'brightness', 'contrast', 'emulate_motion', 'event_gap', 'ffmpeg_bps', 'ffmpeg_output_movies', 'ffmpeg_variable_bitrate', 'ffmpeg_video_codec',
- 'framerate', 'height', 'hue', 'lightswitch', 'locate_motion_mode', 'locate_motion_style', 'minimum_motion_frames', 'movie_filename', 'max_movie_time', 'max_mpeg_time',
- 'noise_level', 'noise_tune', 'on_event_end', 'on_event_start', 'output_pictures', 'picture_filename', 'post_capture', 'pre_capture', 'quality', 'rotate', 'saturation',
- 'snapshot_filename', 'snapshot_interval', 'stream_auth_method', 'stream_authentication', 'stream_localhost', 'stream_maxrate', 'stream_motion', 'stream_port', 'stream_quality',
- 'target_dir', 'text_changes', 'text_double', 'text_left', 'text_right', 'threshold', 'videodevice', 'width',
- 'webcam_localhost', 'webcam_port', 'webcam_maxrate', 'webcam_quality', 'webcam_motion', 'ffmpeg_cap_new', 'output_normal', 'output_motion', 'jpeg_filename', 'output_all', 'gap', 'locate',
- 'netcam_url', 'netcam_userpass', 'netcam_http', 'netcam_tolerant_check', 'netcam_keepalive', 'rtsp_uses_tcp'
-])
-
-
-def additional_section(func):
- _additional_section_funcs.append(func)
-
-
-def additional_config(func):
- _additional_config_funcs.append(func)
-
-
-import wifictl # @UnusedImport
-import tzctl # @UnusedImport
-
-
-def get_main(as_lines=False):
- global _main_config_cache
-
- if not as_lines and _main_config_cache is not None:
- return _main_config_cache
-
- config_file_path = os.path.join(settings.CONF_PATH, _MAIN_CONFIG_FILE_NAME)
-
- logging.debug('reading main config from file %(path)s...' % {'path': config_file_path})
-
- lines = None
- try:
- file = 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 = []
-
- else:
- logging.error('could not open main config file %(path)s: %(msg)s' % {
- 'path': config_file_path, 'msg': unicode(e)})
-
- raise
-
- if lines is None:
- try:
- lines = [l[:-1] for l in file.readlines()]
-
- except Exception as e:
- logging.error('could not read main config file %(path)s: %(msg)s' % {
- 'path': config_file_path, 'msg': unicode(e)})
-
- raise
-
- finally:
- file.close()
-
- if as_lines:
- return lines
-
- main_config = _conf_to_dict(lines,
- list_names=['thread'],
- no_convert=['@admin_username', '@admin_password', '@normal_username', '@normal_password'])
-
- _get_additional_config(main_config)
- _set_default_motion(main_config, old_motion=is_old_motion())
-
- _main_config_cache = main_config
-
- return main_config
-
-
-def set_main(main_config):
- global _main_config_cache
-
- main_config = dict(main_config)
- _set_default_motion(main_config, old_motion=is_old_motion())
- for n, v in _main_config_cache.iteritems():
- main_config.setdefault(n, v)
- _main_config_cache = main_config
-
- main_config = dict(main_config)
- _set_additional_config(main_config)
-
- config_file_path = os.path.join(settings.CONF_PATH, _MAIN_CONFIG_FILE_NAME)
-
- # read the actual configuration from file
- lines = get_main(as_lines=True)
-
- # write the configuration to file
- logging.debug('writing main config to %(path)s...' % {'path': config_file_path})
-
- try:
- file = open(config_file_path, 'w')
-
- except Exception as e:
- logging.error('could not open main config file %(path)s for writing: %(msg)s' % {
- 'path': config_file_path, 'msg': unicode(e)})
-
- raise
-
- lines = _dict_to_conf(lines, main_config, list_names=['thread'])
-
- try:
- file.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' % {
- 'path': config_file_path, 'msg': unicode(e)})
-
- raise
-
- finally:
- file.close()
-
-
-def get_camera_ids(filter_valid=True):
- global _camera_ids_cache
-
- if _camera_ids_cache is not None:
- return _camera_ids_cache
-
- config_path = settings.CONF_PATH
-
- logging.debug('listing config dir %(path)s...' % {'path': config_path})
-
- try:
- ls = os.listdir(config_path)
-
- except Exception as e:
- logging.error('failed to list config dir %(path)s: %(msg)s', {
- 'path': config_path, 'msg': unicode(e)})
-
- raise
-
- camera_ids = []
-
- pattern = '^' + _CAMERA_CONFIG_FILE_NAME.replace('%(id)s', '(\d+)') + '$'
- for name in ls:
- match = re.match(pattern, name)
- if match:
- camera_id = int(match.groups()[0])
- logging.debug('found camera with id %(id)s' % {
- 'id': camera_id})
-
- camera_ids.append(camera_id)
-
- camera_ids.sort()
-
- if not filter_valid:
- return camera_ids
-
- filtered_camera_ids = []
- for camera_id in camera_ids:
- if get_camera(camera_id):
- filtered_camera_ids.append(camera_id)
-
- _camera_ids_cache = filtered_camera_ids
-
- return filtered_camera_ids
-
-
-def get_enabled_local_motion_cameras():
- if not get_main().get('@enabled'):
- return []
-
- camera_ids = get_camera_ids()
- cameras = [get_camera(camera_id) for camera_id in camera_ids]
- return [c for c in cameras if c.get('@enabled') and utils.local_motion_camera(c)]
-
-
-def get_network_shares():
- if not get_main().get('@enabled'):
- return []
-
- camera_ids = get_camera_ids()
- cameras = [get_camera(camera_id) for camera_id in camera_ids]
-
- mounts = []
- for camera in cameras:
- if camera.get('@storage_device') != 'network-share':
- continue
-
- mounts.append({
- 'server': camera['@network_server'],
- 'share': camera['@network_share_name'],
- 'username': camera['@network_username'],
- 'password': camera['@network_password'],
- })
-
- return mounts
-
-
-def get_camera(camera_id, as_lines=False):
- global _camera_config_cache
-
- if not as_lines and camera_id in _camera_config_cache:
- return _camera_config_cache[camera_id]
-
- camera_config_path = os.path.join(settings.CONF_PATH, _CAMERA_CONFIG_FILE_NAME) % {'id': camera_id}
-
- logging.debug('reading camera config from %(path)s...' % {'path': camera_config_path})
-
- try:
- file = 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()]
-
- except Exception as e:
- logging.error('could not read camera config file %(path)s: %(msg)s' % {
- 'path': camera_config_path, 'msg': unicode(e)})
-
- raise
-
- finally:
- file.close()
-
- if as_lines:
- return lines
-
- camera_config = _conf_to_dict(lines,
- no_convert=['@name', '@network_share_name', '@network_server',
- '@network_username', '@network_password', '@storage_device'])
-
- if utils.local_motion_camera(camera_config):
- # determine the enabled status
- main_config = get_main()
- threads = main_config.get('thread', [])
- camera_config['@enabled'] = _CAMERA_CONFIG_FILE_NAME % {'id': camera_id} in threads
- camera_config['@id'] = camera_id
-
- old_motion = is_old_motion()
-
- # adapt directives from old configuration, if needed
- if old_motion:
- logging.debug('using old motion config directives')
-
- if 'output_normal' in camera_config:
- camera_config['output_pictures'] = camera_config.pop('output_normal')
- if 'output_all' in camera_config:
- camera_config['emulate_motion'] = camera_config.pop('output_all')
- if 'ffmpeg_cap_new' in camera_config:
- camera_config['ffmpeg_output_movies'] = camera_config.pop('ffmpeg_cap_new')
- if 'locate' in camera_config:
- camera_config['locate_motion_mode'] = camera_config.pop('locate')
- if 'jpeg_filename' in camera_config:
- camera_config['picture_filename'] = camera_config.pop('jpeg_filename')
- if 'max_mpeg_time' in camera_config:
- camera_config['max_movie_time'] = camera_config.pop('max_mpeg_time')
- if 'webcam_port' in camera_config:
- camera_config['stream_port'] = camera_config.pop('webcam_port')
- if 'webcam_quality' in camera_config:
- camera_config['stream_quality'] = camera_config.pop('webcam_quality')
- if 'webcam_motion' in camera_config:
- camera_config['stream_motion'] = camera_config.pop('webcam_motion')
- if 'webcam_maxrate' in camera_config:
- camera_config['stream_maxrate'] = camera_config.pop('webcam_maxrate')
- if 'webcam_localhost' in camera_config:
- camera_config['stream_localhost'] = camera_config.pop('webcam_localhost')
- if 'gap' in camera_config:
- camera_config['event_gap'] = camera_config.pop('gap')
- if 'netcam_http' in camera_config:
- camera_config['netcam_keepalive'] = camera_config.pop('netcam_http') in ['1.1', 'keepalive']
-
- _get_additional_config(camera_config, camera_id=camera_id)
- _set_default_motion_camera(camera_id, camera_config)
-
- elif utils.remote_camera(camera_config):
- pass
-
- elif utils.simple_mjpeg_camera(camera_config):
- _get_additional_config(camera_config, camera_id=camera_id)
-
- else: # incomplete configuration
- logging.warn('camera config file at %s is incomplete, ignoring' % camera_config_path)
-
- return None
-
- _camera_config_cache[camera_id] = dict(camera_config)
-
- return camera_config
-
-
-def set_camera(camera_id, camera_config):
- global _camera_config_cache
-
- camera_config['@id'] = camera_id
- _camera_config_cache[camera_id] = camera_config
-
- camera_config = dict(camera_config)
-
- if utils.local_motion_camera(camera_config):
- old_motion = is_old_motion()
-
- # adapt directives to old configuration, if needed
- if old_motion:
- logging.debug('using old motion config directives')
-
- if 'output_pictures' in camera_config:
- camera_config['output_normal'] = camera_config.pop('output_pictures')
- if 'emulate_motion' in camera_config:
- camera_config['output_all'] = camera_config.pop('emulate_motion')
- if 'ffmpeg_output_movies' in camera_config:
- camera_config['ffmpeg_cap_new'] = camera_config.pop('ffmpeg_output_movies')
- if 'locate_motion_mode' in camera_config:
- camera_config['locate'] = camera_config.pop('locate_motion_mode')
- if 'picture_filename' in camera_config:
- camera_config['jpeg_filename'] = camera_config.pop('picture_filename')
- if 'max_movie_time' in camera_config:
- camera_config['max_mpeg_time'] = camera_config.pop('max_movie_time')
- if 'stream_port' in camera_config:
- camera_config['webcam_port'] = camera_config.pop('stream_port')
- if 'stream_quality' in camera_config:
- camera_config['webcam_quality'] = camera_config.pop('stream_quality')
- if 'stream_motion' in camera_config:
- camera_config['webcam_motion'] = camera_config.pop('stream_motion')
- if 'stream_maxrate' in camera_config:
- camera_config['webcam_maxrate'] = camera_config.pop('stream_maxrate')
- if 'stream_localhost' in camera_config:
- camera_config['webcam_localhost'] = camera_config.pop('stream_localhost')
- if 'stream_auth_method' in camera_config:
- camera_config.pop('stream_auth_method')
- if 'stream_authentication' in camera_config:
- camera_config.pop('stream_authentication')
- if 'event_gap' in camera_config:
- camera_config['gap'] = camera_config.pop('event_gap')
- if 'netcam_keepalive' in camera_config:
- camera_config['netcam_http'] = '1.1' if camera_config.pop('netcam_keepalive') else '1.0'
-
- _set_default_motion_camera(camera_id, camera_config, old_motion)
-
- # set the enabled status in main config
- main_config = get_main()
- threads = main_config.setdefault('thread', [])
- config_file_name = _CAMERA_CONFIG_FILE_NAME % {'id': camera_id}
- if camera_config['@enabled'] and config_file_name not in threads:
- threads.append(config_file_name)
-
- elif not camera_config['@enabled']:
- threads = [t for t in threads if t != config_file_name]
-
- main_config['thread'] = threads
-
- set_main(main_config)
- _set_additional_config(camera_config, camera_id=camera_id)
-
- elif utils.remote_camera(camera_config):
- pass
-
- elif utils.simple_mjpeg_camera(camera_config):
- _set_additional_config(camera_config, camera_id=camera_id)
-
- # read the actual configuration from file
- config_file_path = os.path.join(settings.CONF_PATH, _CAMERA_CONFIG_FILE_NAME) % {'id': camera_id}
- if os.path.isfile(config_file_path):
- lines = get_camera(camera_id, as_lines=True)
-
- else:
- lines = []
-
- # write the configuration to file
- camera_config_path = os.path.join(settings.CONF_PATH, _CAMERA_CONFIG_FILE_NAME) % {'id': camera_id}
- logging.debug('writing camera config to %(path)s...' % {'path': camera_config_path})
-
- try:
- file = open(camera_config_path, 'w')
-
- except Exception as e:
- logging.error('could not open camera config file %(path)s for writing: %(msg)s' % {
- 'path': camera_config_path, 'msg': unicode(e)})
-
- raise
-
- lines = _dict_to_conf(lines, camera_config)
-
- try:
- file.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' % {
- 'path': camera_config_path, 'msg': unicode(e)})
-
- raise
-
- finally:
- file.close()
-
-
-def add_camera(device_details):
- global _camera_ids_cache
- global _camera_config_cache
-
- proto = device_details['proto']
- if proto in ['netcam', 'mjpeg']:
- host = device_details['host']
- if device_details['port']:
- host += ':' + str(device_details['port'])
-
- if device_details['username'] and proto == 'mjpeg':
- if device_details['password']:
- host = device_details['username'] + ':' + device_details['password'] + '@' + host
-
- else:
- host = device_details['username'] + '@' + host
-
- device_details['url'] = urlparse.urlunparse((device_details['scheme'], host, device_details['uri'], '', '', ''))
-
- # determine the last camera id
- camera_ids = get_camera_ids()
-
- camera_id = 1
- while camera_id in camera_ids:
- camera_id += 1
-
- logging.info('adding new camera with id %(id)s...' % {'id': camera_id})
-
- # prepare a default camera config
- camera_config = {'@enabled': True}
- if proto == 'v4l2':
- # find a suitable resolution
- for (w, h) in v4l2ctl.list_resolutions(device_details['uri']):
- if w > 300:
- camera_config['width'] = w
- camera_config['height'] = h
- break
-
- camera_config['videodevice'] = device_details['uri']
- _set_default_motion_camera(camera_id, camera_config)
-
- elif proto == 'motioneye':
- camera_config['@proto'] = 'motioneye'
- camera_config['@scheme'] = device_details['scheme']
- camera_config['@host'] = device_details['host']
- camera_config['@port'] = device_details['port']
- camera_config['@uri'] = device_details['uri']
- camera_config['@username'] = device_details['username']
- camera_config['@password'] = device_details['password']
- camera_config['@remote_camera_id'] = device_details['remote_camera_id']
-
- elif proto == 'netcam':
- camera_config['netcam_url'] = device_details['url']
- camera_config['text_double'] = True
-
- if device_details['username']:
- camera_config['netcam_userpass'] = device_details['username'] + ':' + device_details['password']
-
- camera_config['netcam_keepalive'] = device_details.get('keep_alive')
- camera_config['netcam_tolerant_check'] = True
-
- if device_details.get('camera_index') == 'udp':
- camera_config['rtsp_uses_tcp'] = False
-
- if camera_config['netcam_url'].startswith('rtsp'):
- camera_config['width'] = 640
- camera_config['height'] = 480
-
- _set_default_motion_camera(camera_id, camera_config)
-
- else: # assuming mjpeg
- camera_config['@proto'] = 'mjpeg'
- camera_config['@url'] = device_details['url']
- _set_default_simple_mjpeg_camera(camera_id, camera_config)
-
- # write the configuration to file
- set_camera(camera_id, camera_config)
-
- _camera_ids_cache = None
- _camera_config_cache = {}
-
- camera_config = get_camera(camera_id)
-
- return camera_config
-
-
-def rem_camera(camera_id):
- global _camera_ids_cache
- global _camera_config_cache
-
- camera_config_name = _CAMERA_CONFIG_FILE_NAME % {'id': camera_id}
- camera_config_path = os.path.join(settings.CONF_PATH, _CAMERA_CONFIG_FILE_NAME) % {'id': camera_id}
-
- # remove the camera from the main config
- main_config = get_main()
- threads = main_config.setdefault('thread', [])
- threads = [t for t in threads if t != camera_config_name]
-
- main_config['thread'] = threads
-
- set_main(main_config)
-
- logging.info('removing camera config file %(path)s...' % {'path': camera_config_path})
-
- _camera_ids_cache = None
- _camera_config_cache = {}
-
- try:
- os.remove(camera_config_path)
-
- except Exception as e:
- logging.error('could not remove camera config file %(path)s: %(msg)s' % {
- 'path': camera_config_path, 'msg': unicode(e)})
-
- raise
-
-
-def main_ui_to_dict(ui):
- data = {
- '@enabled': ui['enabled'],
-
- '@show_advanced': ui['show_advanced'],
- '@admin_username': ui['admin_username'],
- '@admin_password': ui['admin_password'],
- '@normal_username': ui['normal_username'],
- '@normal_password': ui['normal_password']
- }
-
- # additional configs
- for name, value in ui.iteritems():
- if not name.startswith('_'):
- continue
-
- data['@' + name] = value
-
- return data
-
-
-def main_dict_to_ui(data):
- ui = {
- 'enabled': data['@enabled'],
-
- 'show_advanced': data['@show_advanced'],
- 'admin_username': data['@admin_username'],
- 'admin_password': data['@admin_password'],
- 'normal_username': data['@normal_username'],
- 'normal_password': data['@normal_password']
- }
-
- # additional configs
- for name, value in data.iteritems():
- if not name.startswith('@_'):
- continue
-
- ui[name[1:]] = value
-
- return ui
-
-
-def motion_camera_ui_to_dict(ui, old_config=None):
- import smbctl
-
- old_config = dict(old_config or {})
- main_config = get_main() # needed for surveillance password
-
- data = {
- # device
- '@name': ui['name'],
- '@enabled': ui['enabled'],
- 'lightswitch': int(ui['light_switch_detect']) * 50,
- 'auto_brightness': ui['auto_brightness'],
- 'framerate': int(ui['framerate']),
- 'rotate': int(ui['rotation']),
-
- # file storage
- '@storage_device': ui['storage_device'],
- '@network_server': ui['network_server'],
- '@network_share_name': ui['network_share_name'],
- '@network_username': ui['network_username'],
- '@network_password': ui['network_password'],
-
- # text overlay
- 'text_left': '',
- 'text_right': '',
- 'text_double': False,
-
- # streaming
- 'stream_localhost': not ui['video_streaming'],
- 'stream_port': int(ui['streaming_port']),
- 'stream_maxrate': int(ui['streaming_framerate']),
- 'stream_quality': max(1, int(ui['streaming_quality'])),
- '@webcam_resolution': max(1, int(ui['streaming_resolution'])),
- '@webcam_server_resize': ui['streaming_server_resize'],
- 'stream_motion': ui['streaming_motion'],
- 'stream_auth_method': {'disabled': 0, 'basic': 1, 'digest': 2}.get(ui['streaming_auth_mode'], 0),
- 'stream_authentication': main_config['@normal_username'] + ':' + main_config['@normal_password'],
-
- # still images
- 'output_pictures': False,
- 'emulate_motion': False,
- 'snapshot_interval': 0,
- 'picture_filename': '',
- 'snapshot_filename': '',
- '@preserve_pictures': int(ui['preserve_pictures']),
-
- # motion detection
- '@motion_detection': ui['motion_detection'],
- 'text_changes': ui['show_frame_changes'],
- 'locate_motion_mode': ui['show_frame_changes'],
- 'noise_tune': ui['auto_noise_detect'],
- 'noise_level': max(1, int(round(int(ui['noise_level']) * 2.55))),
- 'event_gap': int(ui['event_gap']),
- 'pre_capture': int(ui['pre_capture']),
- 'post_capture': int(ui['post_capture']),
- 'minimum_motion_frames': int(ui['minimum_motion_frames']),
-
- # movies
- 'ffmpeg_output_movies': ui['motion_movies'],
- 'movie_filename': ui['movie_file_name'],
- 'ffmpeg_bps': 44000, # a quality of about 85% for 320x240x2fps
- 'max_movie_time': ui['max_movie_length'],
- '@preserve_movies': int(ui['preserve_movies']),
-
- # working schedule
- '@working_schedule': '',
-
- # events
- 'on_event_start': '',
- 'on_event_end': ''
- }
-
- if utils.v4l2_camera(old_config):
- proto = 'v4l2'
-
- else:
- proto = 'netcam'
-
- if proto == 'v4l2':
- # leave videodevice unchanged
-
- # resolution
- if not ui['resolution']:
- ui['resolution'] = '320x240'
-
- width = int(ui['resolution'].split('x')[0])
- height = int(ui['resolution'].split('x')[1])
- data['width'] = width
- data['height'] = height
-
- threshold = int(float(ui['frame_change_threshold']) * width * height / 100)
-
- if 'brightness' in ui:
- if int(ui['brightness']) == 50:
- data['brightness'] = 0
-
- else:
- data['brightness'] = max(1, int(round(int(ui['brightness']) * 2.55)))
-
- if 'contrast' in ui:
- if int(ui['contrast']) == 50:
- data['contrast'] = 0
-
- else:
- data['contrast'] = max(1, int(round(int(ui['contrast']) * 2.55)))
-
- if 'saturation' in ui:
- if int(ui['saturation']) == 50:
- data['saturation'] = 0
-
- else:
- data['saturation'] = max(1, int(round(int(ui['saturation']) * 2.55)))
-
- if 'hue' in ui:
- if int(ui['hue']) == 50:
- data['hue'] = 0
-
- else:
- data['hue'] = max(1, int(round(int(ui['hue']) * 2.55)))
-
- 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])
- height = int(ui['resolution'].split('x')[1])
- data['width'] = width
- data['height'] = height
-
- threshold = int(float(ui['frame_change_threshold']) * width * height / 100)
-
- else: # width & height are not available for other netcams
- threshold = int(float(ui['frame_change_threshold']) * 640 * 480 / 100)
-
- data['threshold'] = threshold
-
- if (ui['storage_device'] == 'network-share') and settings.SMB_SHARES:
- mount_point = smbctl.make_mount_point(ui['network_server'], ui['network_share_name'], ui['network_username'])
- if ui['root_directory'].startswith('/'):
- ui['root_directory'] = ui['root_directory'][1:]
- data['target_dir'] = os.path.normpath(os.path.join(mount_point, ui['root_directory']))
-
- elif ui['storage_device'].startswith('local-disk'):
- target_dev = ui['storage_device'][10:].replace('-', '/')
- mounted_partitions = diskctl.list_mounted_partitions()
- partition = mounted_partitions[target_dev]
- mount_point = partition['mount_point']
-
- if ui['root_directory'].startswith('/'):
- ui['root_directory'] = ui['root_directory'][1:]
- data['target_dir'] = os.path.normpath(os.path.join(mount_point, ui['root_directory']))
-
- else:
- data['target_dir'] = ui['root_directory']
-
- if ui['text_overlay']:
- left_text = ui['left_text']
- if left_text == 'camera-name':
- data['text_left'] = ui['name']
-
- elif left_text == 'timestamp':
- data['text_left'] = '%Y-%m-%d\\n%T'
-
- elif left_text == 'disabled':
- data['text_left'] = ''
-
- else:
- data['text_left'] = ui['custom_left_text']
-
- right_text = ui['right_text']
- if right_text == 'camera-name':
- data['text_right'] = ui['name']
-
- elif right_text == 'timestamp':
- data['text_right'] = '%Y-%m-%d\\n%T'
-
- elif right_text == 'disabled':
- data['text_right'] = ''
-
- else:
- data['text_right'] = ui['custom_right_text']
-
- if proto == 'netcam' or data['width'] > 320:
- data['text_double'] = True
-
- if ui['still_images']:
- capture_mode = ui['capture_mode']
- if capture_mode == 'motion-triggered':
- data['output_pictures'] = True
- data['picture_filename'] = ui['image_file_name']
-
- elif capture_mode == 'interval-snapshots':
- data['snapshot_interval'] = int(ui['snapshot_interval'])
- data['snapshot_filename'] = ui['image_file_name']
-
- elif capture_mode == 'all-frames':
- data['output_pictures'] = True
- data['emulate_motion'] = True
- data['picture_filename'] = ui['image_file_name']
-
- data['quality'] = max(1, int(ui['image_quality']))
-
- if proto == 'v4l2':
- max_val = data['width'] * data['height'] * data['framerate'] / 3
-
- else: # always assume a netcam image size of 640x480, since we have no means to know it at this point
- max_val = 640 * 480 * data['framerate'] / 3
-
- max_val = min(max_val, 9999999)
-
- data['ffmpeg_bps'] = int(ui['movie_quality']) * max_val / 100
-
- # working schedule
- if ui['working_schedule']:
- data['@working_schedule'] = (
- ui['monday_from'] + '-' + ui['monday_to'] + '|' +
- ui['tuesday_from'] + '-' + ui['tuesday_to'] + '|' +
- ui['wednesday_from'] + '-' + ui['wednesday_to'] + '|' +
- ui['thursday_from'] + '-' + ui['thursday_to'] + '|' +
- ui['friday_from'] + '-' + ui['friday_to'] + '|' +
- ui['saturday_from'] + '-' + ui['saturday_to'] + '|' +
- ui['sunday_from'] + '-' + ui['sunday_to'])
-
- data['@working_schedule_type'] = ui['working_schedule_type']
-
- # event start
- event_relay_path = os.path.join(settings.PROJECT_PATH, 'eventrelay.py')
- event_relay_path = os.path.abspath(event_relay_path)
-
- on_event_start = ['%(script)s start %%t' % {'script': event_relay_path}]
- if ui['email_notifications_enabled']:
- send_mail_path = os.path.join(settings.PROJECT_PATH, 'sendmail.py')
- send_mail_path = os.path.abspath(send_mail_path)
- emails = re.sub('\\s', '', ui['email_notifications_addresses'])
-
- on_event_start.append("%(script)s '%(server)s' '%(port)s' '%(account)s' '%(password)s' '%(tls)s' '%(to)s' 'motion_start' '%%t' '%%Y-%%m-%%dT%%H:%%M:%%S' '%(timespan)s'" % {
- 'script': send_mail_path,
- 'server': ui['email_notifications_smtp_server'],
- 'port': ui['email_notifications_smtp_port'],
- 'account': ui['email_notifications_smtp_account'],
- 'password': ui['email_notifications_smtp_password'],
- 'tls': ui['email_notifications_smtp_tls'],
- 'to': emails,
- 'timespan': ui['email_notifications_picture_time_span']})
-
- if ui['web_hook_notifications_enabled']:
- web_hook_path = os.path.join(settings.PROJECT_PATH, 'webhook.py')
- web_hook_path = os.path.abspath(web_hook_path)
- url = re.sub('\\s', '+', ui['web_hook_notifications_url'])
-
- on_event_start.append("%(script)s '%(method)s' '%(url)s'" % {
- 'script': web_hook_path,
- 'method': ui['web_hook_notifications_http_method'],
- 'url': url})
-
- if ui['command_notifications_enabled']:
- commands = ui['command_notifications_exec'].split(';')
- on_event_start += [c.strip() for c in commands]
-
- data['on_event_start'] = '; '.join(on_event_start)
-
- # event end
- on_event_end = ['%(script)s stop %%t' % {'script': event_relay_path}]
-
- data['on_event_end'] = '; '.join(on_event_end)
-
- # additional configs
- for name, value in ui.iteritems():
- if not name.startswith('_'):
- continue
-
- data['@' + name] = value
-
- # extra motion options
- for name in old_config.keys():
- if name not in _KNOWN_MOTION_OPTIONS and not name.startswith('@'):
- old_config.pop(name)
-
- extra_options = ui.get('extra_options', [])
- for name, value in extra_options:
- data[name] = value or ''
-
- old_config.update(data)
-
- return old_config
-
-
-def motion_camera_dict_to_ui(data):
- import smbctl
-
- ui = {
- # device
- 'name': data['@name'],
- 'enabled': data['@enabled'],
- 'id': data['@id'],
- 'light_switch_detect': data['lightswitch'] > 0,
- 'auto_brightness': data['auto_brightness'],
- 'framerate': int(data['framerate']),
- 'rotation': int(data['rotate']),
-
- # file storage
- 'smb_shares': settings.SMB_SHARES,
- 'storage_device': data['@storage_device'],
- 'network_server': data['@network_server'],
- 'network_share_name': data['@network_share_name'],
- 'network_username': data['@network_username'],
- 'network_password': data['@network_password'],
- 'disk_used': 0,
- 'disk_total': 0,
- 'available_disks': diskctl.list_mounted_disks(),
-
- # text overlay
- 'text_overlay': False,
- 'left_text': 'camera-name',
- 'right_text': 'timestamp',
- 'custom_left_text': '',
- 'custom_right_text': '',
-
- # streaming
- 'video_streaming': not data['stream_localhost'],
- 'streaming_framerate': int(data['stream_maxrate']),
- 'streaming_quality': int(data['stream_quality']),
- 'streaming_resolution': int(data['@webcam_resolution']),
- 'streaming_server_resize': data['@webcam_server_resize'],
- 'streaming_port': int(data['stream_port']),
- 'streaming_auth_mode': {0: 'disabled', 1: 'basic', 2: 'digest'}.get(data.get('stream_auth_method'), 'disabled'),
- 'streaming_motion': int(data['stream_motion']),
-
- # still images
- 'still_images': False,
- 'capture_mode': 'motion-triggered',
- 'image_file_name': '%Y-%m-%d/%H-%M-%S',
- 'image_quality': 85,
- 'snapshot_interval': 0,
- 'preserve_pictures': data['@preserve_pictures'],
-
- # motion detection
- 'motion_detection': data['@motion_detection'],
- 'show_frame_changes': data['text_changes'] or data['locate_motion_mode'],
- 'auto_noise_detect': data['noise_tune'],
- 'noise_level': int(int(data['noise_level']) / 2.55),
- 'event_gap': int(data['event_gap']),
- 'pre_capture': int(data['pre_capture']),
- 'post_capture': int(data['post_capture']),
- 'minimum_motion_frames': int(data['minimum_motion_frames']),
-
- # motion movies
- 'motion_movies': data['ffmpeg_output_movies'],
- 'movie_file_name': data['movie_filename'],
- 'max_movie_length': data['max_movie_time'],
- 'preserve_movies': data['@preserve_movies'],
-
- # motion notifications
- 'email_notifications_enabled': False,
- 'web_hook_notifications_enabled': False,
- 'command_notifications_enabled': False,
-
- # working schedule
- 'working_schedule': False,
- 'working_schedule_type': 'during',
- 'monday_from': '', 'monday_to': '',
- 'tuesday_from': '', 'tuesday_to': '',
- 'wednesday_from': '', 'wednesday_to': '',
- 'thursday_from': '', 'thursday_to': '',
- 'friday_from': '', 'friday_to': '',
- 'saturday_from': '', 'saturday_to': '',
- 'sunday_from': '', 'sunday_to': ''
- }
-
- if utils.net_camera(data):
- ui['device_url'] = data['netcam_url']
- ui['proto'] = 'netcam'
-
- # resolutions
- if data['netcam_url'].startswith('rtsp'):
- # motion uses the configured width and height for RTSP cameras
- resolutions = utils.COMMON_RESOLUTIONS
- ui['available_resolutions'] = [(str(w) + 'x' + str(h)) for (w, h) in resolutions]
- ui['resolution'] = str(data['width']) + 'x' + str(data['height'])
-
- threshold = data['threshold'] * 100.0 / (data['width'] * data['height'])
-
- 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
- ui['device_url'] = data['videodevice']
- ui['proto'] = 'v4l2'
-
- # resolutions
- resolutions = v4l2ctl.list_resolutions(data['videodevice'])
- ui['available_resolutions'] = [(str(w) + 'x' + str(h)) for (w, h) in resolutions]
- ui['resolution'] = str(data['width']) + 'x' + str(data['height'])
-
- # the brightness & co. keys in the ui dictionary
- # 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 data.get('brightness', 0) != 0:
- ui['brightness'] = brightness
-
- else:
- ui['brightness'] = 50
-
- contrast = v4l2ctl.get_contrast(data['videodevice'])
- if contrast is not None: # has contrast control
- if data.get('contrast', 0) != 0:
- ui['contrast'] = contrast
-
- else:
- ui['contrast'] = 50
-
- saturation = v4l2ctl.get_saturation(data['videodevice'])
- if saturation is not None: # has saturation control
- if data.get('saturation', 0) != 0:
- ui['saturation'] = saturation
-
- else:
- ui['saturation'] = 50
-
- hue = v4l2ctl.get_hue(data['videodevice'])
- if hue is not None: # has hue control
- if data.get('hue', 0) != 0:
- ui['hue'] = hue
-
- else:
- ui['hue'] = 50
-
- threshold = data['threshold'] * 100.0 / (data['width'] * data['height'])
-
- 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'])
- ui['root_directory'] = data['target_dir'][len(mount_point):] or '/'
-
- elif data['@storage_device'].startswith('local-disk'):
- target_dev = data['@storage_device'][10:].replace('-', '/')
- mounted_partitions = diskctl.list_mounted_partitions()
- for partition in mounted_partitions.values():
- if partition['target'] == target_dev and data['target_dir'].startswith(partition['mount_point']):
- 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']))
- ui['root_directory'] = data['target_dir']
-
- else:
- ui['root_directory'] = data['target_dir']
-
- # disk usage
- usage = utils.get_disk_usage(data['target_dir'])
- if usage:
- ui['disk_used'], ui['disk_total'] = usage
-
- text_left = data['text_left']
- text_right = data['text_right']
- if text_left or text_right:
- ui['text_overlay'] = True
-
- if text_left == data['@name']:
- ui['left_text'] = 'camera-name'
-
- elif text_left == '%Y-%m-%d\\n%T':
- ui['left_text'] = 'timestamp'
-
- elif text_left == '':
- ui['left_text'] = 'disabled'
-
- else:
- ui['left_text'] = 'custom-text'
- ui['custom_left_text'] = text_left
-
- if text_right == data['@name']:
- ui['right_text'] = 'camera-name'
-
- elif text_right == '%Y-%m-%d\\n%T':
- ui['right_text'] = 'timestamp'
-
- elif text_right == '':
- ui['right_text'] = 'disabled'
-
- else:
- ui['right_text'] = 'custom-text'
- ui['custom_right_text'] = text_right
-
- emulate_motion = data['emulate_motion']
- output_pictures = data['output_pictures']
- picture_filename = data['picture_filename']
- snapshot_interval = data['snapshot_interval']
- snapshot_filename = data['snapshot_filename']
-
- if (((emulate_motion or output_pictures) and picture_filename) or
- (snapshot_interval and snapshot_filename)):
-
- ui['still_images'] = True
-
- if emulate_motion:
- ui['capture_mode'] = 'all-frames'
- ui['image_file_name'] = picture_filename
-
- elif snapshot_interval:
- ui['capture_mode'] = 'interval-snapshots'
- ui['image_file_name'] = snapshot_filename
- ui['snapshot_interval'] = snapshot_interval
-
- elif output_pictures:
- ui['capture_mode'] = 'motion-triggered'
- ui['image_file_name'] = picture_filename
-
- ui['image_quality'] = data['quality']
-
- ffmpeg_bps = data['ffmpeg_bps']
- if ffmpeg_bps is not None:
- if utils.v4l2_camera(data):
- max_val = data['width'] * data['height'] * data['framerate'] / 3
-
- else: # net camera
- max_val = 640 * 480 * data['framerate'] / 3
-
- max_val = min(max_val, 9999999)
-
- ui['movie_quality'] = min(100, int(round(ffmpeg_bps * 100.0 / max_val)))
-
- # working schedule
- working_schedule = data['@working_schedule']
- if working_schedule:
- days = working_schedule.split('|')
- ui['working_schedule'] = True
- ui['monday_from'], ui['monday_to'] = days[0].split('-')
- ui['tuesday_from'], ui['tuesday_to'] = days[1].split('-')
- ui['wednesday_from'], ui['wednesday_to'] = days[2].split('-')
- ui['thursday_from'], ui['thursday_to'] = days[3].split('-')
- ui['friday_from'], ui['friday_to'] = days[4].split('-')
- ui['saturday_from'], ui['saturday_to'] = days[5].split('-')
- ui['sunday_from'], ui['sunday_to'] = days[6].split('-')
- ui['working_schedule_type'] = data['@working_schedule_type']
-
- # event start
- on_event_start = data.get('on_event_start') or []
- if on_event_start:
- on_event_start = [e.strip() for e in on_event_start.split(';')]
-
- ui['email_notifications_picture_time_span'] = 0
- command_notifications = []
- for e in on_event_start:
- if e.count('sendmail.py') and e.count('motion_start'):
- e = shlex.split(e)
- if len(e) < 10:
- continue
-
- ui['email_notifications_enabled'] = True
- ui['email_notifications_smtp_server'] = e[1]
- ui['email_notifications_smtp_port'] = e[2]
- ui['email_notifications_smtp_account'] = e[3]
- ui['email_notifications_smtp_password'] = e[4]
- ui['email_notifications_smtp_tls'] = e[5].lower() == 'true'
- ui['email_notifications_addresses'] = e[6]
- try:
- ui['email_notifications_picture_time_span'] = int(e[10])
-
- except:
- ui['email_notifications_picture_time_span'] = 0
-
- elif e.count('webhook.py'):
- e = shlex.split(e)
- if len(e) != 3:
- continue
-
- ui['web_hook_notifications_enabled'] = True
- ui['web_hook_notifications_http_method'] = e[1]
- ui['web_hook_notifications_url'] = e[2]
-
- elif e.count('eventrelay.py'):
- continue # ignore internal relay script
-
- else: # custom command
- command_notifications.append(e)
-
- if command_notifications:
- ui['command_notifications_enabled'] = True
- ui['command_notifications_exec'] = '; '.join(command_notifications)
-
- # additional configs
- for name, value in data.iteritems():
- if not name.startswith('@_'):
- continue
-
- ui[name[1:]] = value
-
- # extra motion options
- extra_options = []
- for name, value in data.iteritems():
- if name not in _KNOWN_MOTION_OPTIONS and not name.startswith('@'):
- extra_options.append((name, value))
-
- ui['extra_options'] = extra_options
-
- return ui
-
-
-def simple_mjpeg_camera_ui_to_dict(ui, old_config=None):
- old_config = dict(old_config or {})
-
- data = {
- # device
- '@name': ui['name'],
- '@enabled': ui['enabled'],
- }
-
- # additional configs
- for name, value in ui.iteritems():
- if not name.startswith('_'):
- continue
-
- data['@' + name] = value
-
- old_config.update(data)
-
- return old_config
-
-
-def simple_mjpeg_camera_dict_to_ui(data):
- ui = {
- 'name': data['@name'],
- 'enabled': data['@enabled'],
- 'id': data['@id'],
- 'proto': 'mjpeg',
- 'url': data['@url']
- }
-
- # additional configs
- for name, value in data.iteritems():
- if not name.startswith('@_'):
- continue
-
- ui[name[1:]] = value
-
- return ui
-
-
-def backup():
- 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)
- cmd = 'cd "%s" && tar zc motion.conf thread-*.conf' % settings.CONF_PATH
- try:
- content = subprocess.check_output(cmd, shell=True)
- logging.debug('backup file created (%s bytes)' % len(content))
-
- return content
-
- except Exception as e:
- logging.error('backup failed: %s' % e, exc_info=True)
-
- return None
-
- else:
- logging.debug('config path "%s" appears to be a motion-specific config directory, performing a full backup' % settings.CONF_PATH)
-
- cmd = 'cd "%s" && tar zc .' % settings.CONF_PATH
- try:
- content = subprocess.check_output(cmd, shell=True)
- logging.debug('backup file created (%s bytes)' % len(content))
-
- return content
-
- except Exception as e:
- logging.error('backup failed: %s' % e, exc_info=True)
-
- return None
-
-
-def restore(content):
- global _main_config_cache
- global _camera_config_cache
- global _camera_ids_cache
- global _additional_structure_cache
-
- logging.info('restoring config from backup file')
-
- cmd = 'tar zxC "%s" || true' % settings.CONF_PATH
-
- try:
- p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- msg = p.communicate(content)[0]
- if msg:
- logging.error('failed to restore configuration: %s' % msg)
- return False
-
- logging.debug('configuration restored successfully')
-
- if settings.ENABLE_REBOOT:
- def later():
- powerctl.reboot()
-
- IOLoop.instance().add_timeout(datetime.timedelta(seconds=2), later)
-
- else:
- logging.info('invalidating config cache')
- invalidate()
-
- return {'reboot': settings.ENABLE_REBOOT}
-
- except Exception as e:
- logging.error('failed to restore configuration: %s' % e, exc_info=True)
-
- return None
-
-
-def is_old_motion():
- import motionctl
-
- try:
- binary, version = motionctl.find_motion() # @UnusedVariable
-
- if version.startswith('trunkREV'): # e.g. trunkREV599
- version = int(version[8:])
- return version <= _LAST_OLD_CONFIG_VERSIONS[0]
-
- elif version.count('Git'): # e.g. Unofficial-Git-a5b5f13
- return False # all git versions are assumed to be new
-
- else: # stable release, should be in the format x.y.z
- return update.compare_versions(version, _LAST_OLD_CONFIG_VERSIONS[1]) <= 0
-
- except:
- return False
-
-
-def motion_rtsp_support():
- import motionctl
-
- try:
- binary, version = motionctl.find_motion() # @UnusedVariable
-
- if version.startswith('trunkREV'): # e.g. trunkREV599
- version = int(version[8:])
- if version > _LAST_OLD_CONFIG_VERSIONS[0]:
- return ['tcp']
-
- elif version.count('Git'): # e.g. Unofficial-Git-a5b5f13
- return ['tcp', 'udp'] # all git versions are assumed to support both transport protocols
-
- else: # stable release, should be in the format x.y.z
- return []
-
- except:
- return []
-
-
-def invalidate():
- global _main_config_cache
- global _camera_config_cache
- global _camera_ids_cache
- global _additional_structure_cache
-
- logging.debug('invalidating config cache')
- _main_config_cache = None
- _camera_config_cache = {}
- _camera_ids_cache = None
- _additional_structure_cache = {}
-
-
-def _value_to_python(value):
- value_lower = value.lower()
- if value_lower == 'off':
- return False
-
- elif value_lower == 'on':
- return True
-
- try:
- return int(value)
-
- except ValueError:
- try:
- return float(value)
-
- except ValueError:
- return value
-
-
-def _python_to_value(value):
- if value is True:
- return 'on'
-
- elif value is False:
- return 'off'
-
- elif isinstance(value, (int, float)):
- return str(value)
-
- else:
- return value
-
-
-def _conf_to_dict(lines, list_names=[], no_convert=[]):
- data = OrderedDict()
-
- for line in lines:
- line = line.strip()
- if len(line) == 0: # empty line
- continue
-
- if line.startswith(';'): # comment line
- continue
-
- match = re.match('^\#\s*(\@\w+)\s*(.*)', line)
- if match:
- name, value = match.groups()[:2]
-
- elif line.startswith('#') or line.startswith(';'): # comment line
- continue
-
- else:
- parts = line.split(None, 1)
- if len(parts) == 1: # empty value
- parts.append('')
-
- (name, value) = parts
- value = value.strip()
-
- if name not in no_convert:
- value = _value_to_python(value)
-
- if name in list_names:
- data.setdefault(name, []).append(value)
-
- else:
- data[name] = value
-
- return data
-
-
-def _dict_to_conf(lines, data, list_names=[]):
- conf_lines = []
- remaining = OrderedDict(data)
- processed = set()
-
- # parse existing lines and replace the values
-
- for line in lines:
- line = line.strip()
- if len(line) == 0: # empty line
- conf_lines.append(line)
- continue
-
- if line.startswith(';'): # simple comment line
- conf_lines.append(line)
- continue
-
- match = re.match('^\#\s*(\@\w+)\s*(.*)', line)
- if match: # @line
- (name, value) = match.groups()[:2]
-
- elif line.startswith('#'): # simple comment line
- conf_lines.append(line)
- continue
-
- else:
- parts = line.split(None, 1)
- if len(parts) == 2:
- (name, value) = parts
-
- else:
- (name, value) = parts[0], ''
-
- if name in processed:
- continue # name already processed
-
- processed.add(name)
-
- if name in list_names:
- new_value = data.get(name)
- if new_value is not None:
- for v in new_value:
- if v is None:
- continue
-
- line = name + ' ' + _python_to_value(v)
- conf_lines.append(line)
-
- else:
- line = name + ' ' + value
- conf_lines.append(line)
-
- else:
- new_value = data.get(name)
- if new_value is not None:
- value = _python_to_value(new_value)
- line = name + ' ' + value
- conf_lines.append(line)
-
- remaining.pop(name, None)
-
- # add the remaining config values not covered by existing lines
-
- if len(remaining) and len(lines):
- conf_lines.append('') # add a blank line
-
- for (name, value) in remaining.iteritems():
- if name.startswith('@_'):
- continue # ignore additional configs
-
- if name in list_names:
- for v in value:
- if v is None:
- continue
-
- line = name + ' ' + _python_to_value(v)
- conf_lines.append(line)
-
- else:
- line = name + ' ' + _python_to_value(value)
- conf_lines.append(line)
-
- # build the final config lines
- conf_lines.sort(key=lambda l: not l.startswith('@'))
-
- lines = []
- for i, line in enumerate(conf_lines):
- # squeeze successive blank lines
- if i > 0 and len(line.strip()) == 0 and len(conf_lines[i - 1].strip()) == 0:
- continue
-
- if line.startswith('@'):
- line = '# ' + line
-
- elif i > 0 and conf_lines[i - 1].startswith('@'):
- lines.append('') # add a blank line between @lines and the rest
-
- lines.append(line)
-
- return lines
-
-
-def _set_default_motion(data, old_motion):
- data.setdefault('@enabled', True)
-
- data.setdefault('@show_advanced', False)
- data.setdefault('@admin_username', 'admin')
- data.setdefault('@admin_password', '')
- data.setdefault('@normal_username', 'user')
- data.setdefault('@normal_password', '')
-
- if old_motion:
- data.setdefault('control_port', 7999)
-
- else:
- data.setdefault('webcontrol_port', 7999)
-
-
-def _set_default_motion_camera(camera_id, data, old_motion=False):
- data.setdefault('@name', 'Camera' + str(camera_id))
- data.setdefault('@enabled', False)
- data.setdefault('@id', camera_id)
-
- if not utils.net_camera(data):
- data.setdefault('videodevice', '/dev/video0')
- data.setdefault('brightness', 0)
- data.setdefault('contrast', 0)
- data.setdefault('saturation', 0)
- data.setdefault('hue', 0)
- data.setdefault('width', 352)
- data.setdefault('height', 288)
-
- data.setdefault('lightswitch', 50)
- data.setdefault('auto_brightness', False)
- data.setdefault('framerate', 2)
- data.setdefault('rotate', 0)
-
- data.setdefault('@storage_device', 'custom-path')
- data.setdefault('@network_server', '')
- data.setdefault('@network_share_name', '')
- data.setdefault('@network_username', '')
- data.setdefault('@network_password', '')
- data.setdefault('target_dir', settings.MEDIA_PATH)
-
- if old_motion:
- data.setdefault('webcam_localhost', False)
- data.setdefault('webcam_port', int('808' + str(camera_id)))
- data.setdefault('webcam_maxrate', 5)
- data.setdefault('webcam_quality', 85)
- data.setdefault('webcam_motion', False)
-
- else:
- data.setdefault('stream_localhost', False)
- data.setdefault('stream_port', int('808' + str(camera_id)))
- data.setdefault('stream_maxrate', 5)
- data.setdefault('stream_quality', 85)
- data.setdefault('stream_motion', False)
- data.setdefault('stream_auth_method', 0)
-
- data.setdefault('@webcam_resolution', 100)
- data.setdefault('@webcam_server_resize', False)
-
- data.setdefault('text_left', data['@name'])
- data.setdefault('text_right', '%Y-%m-%d\\n%T')
- data.setdefault('text_double', False)
-
- data.setdefault('@motion_detection', True)
- data.setdefault('text_changes', False)
- if old_motion:
- data.setdefault('locate', False)
-
- else:
- data.setdefault('locate_motion_mode', False)
- data.setdefault('locate_motion_style', 'redbox')
-
- data.setdefault('threshold', 2000)
- data.setdefault('noise_tune', True)
- data.setdefault('noise_level', 32)
- data.setdefault('minimum_motion_frames', 1)
-
- data.setdefault('pre_capture', 2)
- data.setdefault('post_capture', 4)
- data.setdefault('minimum_motion_frames', 1)
-
- if old_motion:
- data.setdefault('output_normal', False)
- data.setdefault('jpeg_filename', '')
- data.setdefault('output_all', False)
- data.setdefault('gap', 10)
-
- else:
- data.setdefault('output_pictures', False)
- data.setdefault('picture_filename', '')
- data.setdefault('emulate_motion', False)
- data.setdefault('event_gap', 10)
-
- data.setdefault('snapshot_interval', 0)
- data.setdefault('snapshot_filename', '')
- data.setdefault('quality', 85)
- data.setdefault('@preserve_pictures', 0)
-
- data.setdefault('ffmpeg_variable_bitrate', 0)
- data.setdefault('ffmpeg_bps', 44000) # a quality of about 85%
- data.setdefault('movie_filename', '%Y-%m-%d/%H-%M-%S')
- if old_motion:
- data.setdefault('max_mpeg_time', 0)
- data.setdefault('ffmpeg_cap_new', False)
-
- else:
- data.setdefault('max_movie_time', 0)
- data.setdefault('ffmpeg_output_movies', False)
- data.setdefault('ffmpeg_video_codec', 'msmpeg4')
- data.setdefault('@preserve_movies', 0)
-
- data.setdefault('@working_schedule', '')
- data.setdefault('@working_schedule_type', 'outside')
-
- data.setdefault('on_event_start', '')
- data.setdefault('on_event_end', '')
-
-
-def _set_default_simple_mjpeg_camera(camera_id, data):
- data.setdefault('@name', 'Camera' + str(camera_id))
- data.setdefault('@enabled', False)
- data.setdefault('@id', camera_id)
-
-
-def get_additional_structure(camera, separators=False):
- if _additional_structure_cache.get((camera, separators)) is None:
- logging.debug('loading additional config structure for %s, %s separators' % (
- 'camera' if camera else 'main',
- 'with' if separators else 'without'))
-
- # gather sections
- sections = OrderedDict()
- for func in _additional_section_funcs:
- result = func()
- if not result:
- continue
-
- if result.get('reboot') and not settings.ENABLE_REBOOT:
- continue
-
- if bool(result.get('camera')) != bool(camera):
- continue
-
- result['name'] = func.func_name
- sections[func.func_name] = result
-
- logging.debug('additional config section: %s' % result['name'])
-
- configs = OrderedDict()
- for func in _additional_config_funcs:
- result = func()
- if not result:
- continue
-
- if result.get('reboot') and not settings.ENABLE_REBOOT:
- continue
-
- if bool(result.get('camera')) != bool(camera):
- continue
-
- if result['type'] == 'separator' and not separators:
- continue
-
- result['name'] = func.func_name
- configs[func.func_name] = result
-
- section = sections.setdefault(result.get('section'), {})
- section.setdefault('configs', []).append(result)
-
- logging.debug('additional config item: %s' % result['name'])
-
- _additional_structure_cache[(camera, separators)] = sections, configs
-
- return _additional_structure_cache[(camera, separators)]
-
-
-def _get_additional_config(data, camera_id=None):
- args = [camera_id] if camera_id else []
-
- (sections, configs) = get_additional_structure(camera=bool(camera_id))
- get_funcs = set([c.get('get') for c in configs.itervalues() if c.get('get')])
- get_func_values = collections.OrderedDict((f, f(*args)) for f in get_funcs)
-
- for name, section in sections.iteritems():
- if not section.get('get'):
- continue
-
- if section.get('get_set_dict'):
- data['@_' + name] = get_func_values.get(section['get'], {}).get(name)
-
- else:
- data['@_' + name] = get_func_values.get(section['get'])
-
- for name, config in configs.iteritems():
- if not config.get('get'):
- continue
-
- if config.get('get_set_dict'):
- data['@_' + name] = get_func_values.get(config['get'], {}).get(name)
-
- else:
- data['@_' + name] = get_func_values.get(config['get'])
-
-
-def _set_additional_config(data, camera_id=None):
- args = [camera_id] if camera_id else []
-
- (sections, configs) = get_additional_structure(camera=bool(camera_id))
-
- set_func_values = collections.OrderedDict()
- for name, section in sections.iteritems():
- if not section.get('set'):
- continue
-
- if ('@_' + name) not in data:
- continue
-
- if section.get('get_set_dict'):
- set_func_values.setdefault(section['set'], {})[name] = data['@_' + name]
-
- else:
- set_func_values[section['set']] = data['@_' + name]
-
- for name, config in configs.iteritems():
- if not config.get('set'):
- continue
-
- if ('@_' + name) not in data:
- continue
-
- if config.get('get_set_dict'):
- set_func_values.setdefault(config['set'], {})[name] = data['@_' + name]
-
- else:
- set_func_values[config['set']] = data['@_' + name]
-
- for func, value in set_func_values.iteritems():
- func(*(args + [value]))
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import logging
-import os
-import re
-import subprocess
-
-
-def _list_mounts():
- logging.debug('listing mounts...')
-
- seen_targets = set()
-
- mounts = []
- with open('/proc/mounts', 'r') as f:
- for line in f:
- line = line.strip()
- if not line:
- continue
- parts = line.split()
- if len(parts) < 4:
- continue
-
- target = parts[0]
- mount_point = parts[1]
- fstype = parts[2]
- opts = parts[3]
-
- if not os.access(mount_point, os.W_OK):
- continue
-
- if target in seen_targets:
- continue # probably a bind mount
-
- seen_targets.add(target)
-
- if fstype == 'fuseblk':
- fstype = 'ntfs' # most likely
-
- logging.debug('found mount "%s" at "%s"' % (target, mount_point))
-
- mounts.append({
- 'target': target,
- 'mount_point': mount_point,
- 'fstype': fstype,
- 'opts': opts,
- })
-
- return mounts
-
-
-def _list_disks():
- if os.path.exists('/dev/disk/by-id/'):
- return _list_disks_dev_by_id()
-
- else: # fall back to fdisk -l
- return _list_disks_fdisk()
-
-
-def _list_disks_dev_by_id():
- logging.debug('listing disks using /dev/disk/by-id/')
-
- disks_by_dev = {}
- partitions_by_dev = {}
-
- for entry in os.listdir('/dev/disk/by-id/'):
- parts = entry.split('-', 1)
- if len(parts) < 2:
- continue
-
- target = os.path.realpath(os.path.join('/dev/disk/by-id/', entry))
-
- bus, entry = parts
- m = re.search('-part(\d+)$', entry)
- if m:
- part_no = int(m.group(1))
- entry = re.sub('-part\d+$', '', entry)
-
- else:
- part_no = None
-
- parts = entry.split('_')
- if len(parts) < 2:
- vendor = parts[0]
- model = ''
-
- else:
- vendor, model = parts[:2]
-
- if part_no is not None:
- logging.debug('found partition "%s" at "%s" on bus "%s": "%s %s"' % (part_no, target, bus, vendor, model))
-
- partitions_by_dev[target] = {
- 'target': target,
- 'bus': bus,
- 'vendor': vendor,
- 'model': model,
- 'part_no': part_no,
- 'unmatched': True
- }
-
- else:
- logging.debug('found disk at "%s" on bus "%s": "%s %s"' % (target, bus, vendor, model))
-
- disks_by_dev[target] = {
- 'target': target,
- 'bus': bus,
- 'vendor': vendor,
- 'model': model,
- 'partitions': []
- }
-
- # group partitions by disk
- for dev, partition in partitions_by_dev.items():
- for disk_dev, disk in disks_by_dev.items():
- if dev.startswith(disk_dev):
- disk['partitions'].append(partition)
- partition.pop('unmatched')
-
- # add separate partitions that did not match any disk
- for partition in partitions_by_dev.values():
- if partition.pop('unmatched', False):
- disks_by_dev[partition['target']] = partition
- partition['partitions'] = [dict(partition)]
-
- # prepare flat list of disks
- disks = disks_by_dev.values()
- disks.sort(key=lambda d: d['vendor'])
-
- for disk in disks:
- disk['partitions'].sort(key=lambda p: p['part_no'])
-
- return disks
-
-
-def _list_disks_fdisk():
- try:
- output = subprocess.check_output('fdisk -l 2>/dev/null', shell=True)
-
- except Exception as e:
- logging.error('failed to list disks using "fdisk -l": %s' % e, exc_info=True)
-
- return []
-
- disks = []
- disk = None
-
- def add_disk(disk):
- logging.debug('found disk at "%s" on bus "%s": "%s %s"' %
- (disk['target'], disk['bus'], disk['vendor'], disk['model']))
-
- for part in disk['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)
-
- for line in output.split('\n'):
- line = line.replace('*', '')
- line = re.sub('\s+', ' ', line.strip())
- if not line:
- continue
-
- if line.startswith('Disk /dev/'):
- if disk and disk['partitions']:
- add_disk(disk)
-
- parts = line.split()
-
- disk = {
- 'target': parts[1].strip(':'),
- 'bus': '',
- 'vendor': '',
- 'model': parts[2] + ' ' + parts[3].strip(','),
- 'partitions': []
- }
-
- elif line.startswith('/dev/') and disk:
- parts = line.split()
- part_no = re.findall('\d+$', parts[0])
- partition = {
- 'part_no': int(part_no[0]) if part_no else None,
- 'target': parts[0],
- 'bus': '',
- 'vendor': '',
- 'model': parts[4] + ' ' + ' '.join(parts[6:]),
- }
-
- disk['partitions'].append(partition)
-
- if disk and disk['partitions']:
- add_disk(disk)
-
- disks.sort(key=lambda d: d['target'])
-
- for disk in disks:
- disk['partitions'].sort(key=lambda p: p['part_no'])
-
- return disks
-
-
-def list_mounted_disks():
- mounted_disks = []
-
- try:
- disks = _list_disks()
- mounts_by_target = dict((m['target'], m) for m in _list_mounts())
-
- for disk in disks:
- for partition in disk['partitions']:
- mount = mounts_by_target.get(partition['target'])
- if mount:
- partition.update(mount)
-
- # filter out unmounted partitions
- disk['partitions'] = [p for p in disk['partitions'] if p.get('mount_point')]
-
- # filter out unmounted disks
- mounted_disks = [d for d in disks if d['partitions']]
-
- except Exception as e:
- logging.error('failed to list mounted disks: %s' % e, exc_info=True)
-
- return mounted_disks
-
-
-def list_mounted_partitions():
- mounted_partitions = {}
-
- try:
- disks = _list_disks()
- mounts_by_target = dict((m['target'], m) for m in _list_mounts())
-
- for disk in disks:
- for partition in disk['partitions']:
- mount = mounts_by_target.get(partition['target'])
- if mount:
- partition.update(mount)
- mounted_partitions[partition['target']] = partition
-
- except Exception as e:
- logging.error('failed to list mounted partitions: %s' % e, exc_info=True)
-
- return mounted_partitions
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import datetime
-import json
-import logging
-import os
-import re
-import socket
-import subprocess
-
-from tornado.web import RequestHandler, HTTPError, asynchronous
-from tornado.ioloop import IOLoop
-
-import config
-import mediafiles
-import motionctl
-import powerctl
-import remote
-import settings
-import smbctl
-import template
-import update
-import utils
-import v4l2ctl
-
-
-class BaseHandler(RequestHandler):
- def get_data(self):
- keys = self.request.arguments.keys()
- data = dict([(key, self.get_argument(key)) for key in keys])
-
- for key in self.request.files:
- files = self.request.files[key]
- if len(files) > 1:
- data[key] = files
-
- elif len(files) > 0:
- data[key] = files[0]
-
- else:
- continue
-
- return data
-
- def render(self, template_name, content_type='text/html', **context):
- self.set_header('Content-Type', content_type)
-
- content = template.render(template_name, **context)
- self.finish(content)
-
- def finish_json(self, data={}):
- self.set_header('Content-Type', 'application/json')
- self.finish(json.dumps(data))
-
- def get_current_user(self):
- main_config = config.get_main()
-
- username = self.get_argument('_username', None)
- 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, self.request.body, main_config.get('@admin_password'))):
-
- return 'admin'
-
- 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, self.request.body, main_config.get('@normal_password'))):
-
- return 'normal'
-
- elif username and username != '_' and login:
- logging.error('authentication failed for user %(user)s' % {'user': username})
-
- return None
-
- def _handle_request_exception(self, exception):
- try:
- 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)})
-
- else:
- logging.error(str(exception), exc_info=True)
- self.set_status(500)
- self.finish_json({'error': 'internal server error'})
-
- except RuntimeError:
- pass # nevermind
-
- @staticmethod
- def auth(admin=False, prompt=True):
- def decorator(func):
- def wrapper(self, *args, **kwargs):
- _admin = self.get_argument('_admin', None) == 'true'
-
- user = self.current_user
- if (user is None) or (user != 'admin' and (admin or _admin)):
- self.set_header('Content-Type', 'application/json')
-
- return self.finish_json({'error': 'unauthorized', 'prompt': prompt})
-
- return func(self, *args, **kwargs)
-
- return wrapper
-
- return decorator
-
- def get(self, *args, **kwargs):
- raise HTTPError(400, 'method not allowed')
-
- def post(self, *args, **kwargs):
- raise HTTPError(400, 'method not allowed')
-
-
-class NotFoundHandler(BaseHandler):
- def get(self):
- raise HTTPError(404, 'not found')
-
- def post(self):
- raise HTTPError(404, 'not found')
-
-
-class MainHandler(BaseHandler):
- def get(self):
- import motioneye
-
- # additional config
- main_sections = config.get_additional_structure(camera=False, separators=True)[0]
- camera_sections = config.get_additional_structure(camera=True, separators=True)[0]
-
- self.render('main.html',
- frame=False,
- version=motioneye.VERSION,
- enable_update=False,
- enable_reboot=settings.ENABLE_REBOOT,
- add_remove_cameras=settings.ADD_REMOVE_CAMERAS,
- main_sections=main_sections,
- camera_sections=camera_sections,
- hostname=socket.gethostname(),
- title=self.get_argument('title', None),
- admin_username=config.get_main().get('@admin_username'),
- old_motion=config.is_old_motion())
-
-
-class ConfigHandler(BaseHandler):
- @asynchronous
- def get(self, camera_id=None, op=None):
- if camera_id is not None:
- camera_id = int(camera_id)
-
- if op == 'get':
- self.get_config(camera_id)
-
- elif op == 'list':
- self.list()
-
- elif op == 'backup':
- self.backup()
-
- else:
- raise HTTPError(400, 'unknown operation')
-
- @asynchronous
- def post(self, camera_id=None, op=None):
- if camera_id is not None:
- camera_id = int(camera_id)
-
- if op == 'set':
- self.set_config(camera_id)
-
- elif op == 'set_preview':
- self.set_preview(camera_id)
-
- elif op == 'add':
- self.add_camera()
-
- elif op == 'rem':
- self.rem_camera(camera_id)
-
- elif op == 'restore':
- self.restore()
-
- else:
- raise HTTPError(400, 'unknown operation')
-
- @BaseHandler.auth(admin=True)
- def get_config(self, camera_id):
- if camera_id:
- logging.debug('getting config for camera %(id)s' % {'id': camera_id})
-
- if camera_id not in config.get_camera_ids():
- raise HTTPError(404, 'no such camera')
-
- local_config = config.get_camera(camera_id)
- if utils.local_motion_camera(local_config):
- ui_config = config.motion_camera_dict_to_ui(local_config)
-
- self.finish_json(ui_config)
-
- elif utils.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}})
-
- for key, value in local_config.items():
- remote_ui_config[key.replace('@', '')] = value
-
- # replace the real device URI with the remote camera URL
- remote_ui_config['device_url'] = remote.pretty_camera_url(local_config)
- self.finish_json(remote_ui_config)
-
- remote.get_config(local_config, on_response)
-
- else: # assuming simple mjpeg camera
- ui_config = config.simple_mjpeg_camera_dict_to_ui(local_config)
-
- self.finish_json(ui_config)
-
- else:
- logging.debug('getting main config')
-
- ui_config = config.main_dict_to_ui(config.get_main())
- self.finish_json(ui_config)
-
- @BaseHandler.auth(admin=True)
- def set_config(self, camera_id):
- try:
- ui_config = json.loads(self.request.body)
-
- except Exception as e:
- logging.error('could not decode json: %(msg)s' % {'msg': unicode(e)})
-
- raise
-
- camera_ids = config.get_camera_ids()
-
- def set_camera_config(camera_id, ui_config, on_finish):
- logging.debug('setting config for camera %(id)s...' % {'id': camera_id})
-
- if camera_id not in camera_ids:
- raise HTTPError(404, 'no such camera')
-
- local_config = config.get_camera(camera_id)
- if utils.local_motion_camera(local_config):
- local_config = config.motion_camera_ui_to_dict(ui_config, local_config)
-
- config.set_camera(camera_id, local_config)
-
- on_finish(None, True) # (no error, motion needs restart)
-
- elif utils.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)
-
- ui_config['enabled'] = True # never disable the camera remotely
- remote.set_config(local_config, ui_config, on_finish_wrapper)
-
- else:
- # when the ui config supplied has only the enabled state
- # and no useful fields (such as "name"),
- # the camera was probably disabled due to errors
- on_finish(None, False)
-
- 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)
-
- 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', ''))
-
- 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', ''))
-
- additional_configs = config.get_additional_structure(camera=False)[1]
- reboot_config_names = [('@_' + c['name']) for c in additional_configs.values() if c.get('reboot')]
- reboot_config_names.append('@admin_password')
- reboot = bool([k for k in reboot_config_names if old_main_config.get(k) != main_config.get(k)])
-
- config.set_main(main_config)
-
- reload = False
- restart = False
-
- if admin_credentials != old_admin_credentials:
- logging.debug('admin credentials changed, reload needed')
-
- reload = True
-
- if normal_credentials != old_normal_credentials:
- logging.debug('surveillance credentials changed, all camera configs must be updated')
-
- # reconfigure all local cameras to update the stream authentication options
- for camera_id in config.get_camera_ids():
- local_config = config.get_camera(camera_id)
- if not utils.local_motion_camera(local_config):
- continue
-
- ui_config = config.motion_camera_dict_to_ui(local_config)
- local_config = config.motion_camera_ui_to_dict(ui_config, local_config)
-
- config.set_camera(camera_id, local_config)
-
- restart = True
-
- if reboot and settings.ENABLE_REBOOT:
- logging.debug('system settings changed, reboot needed')
-
- else:
- reboot = False
-
- 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
- restart = [False] # indicates that the local motion instance was modified and needs to be restarted
- error = [None]
-
- def finish():
- if reboot[0]:
- if settings.ENABLE_REBOOT:
- def call_reboot():
- powerctl.reboot()
-
- ioloop = IOLoop.instance()
- ioloop.add_timeout(datetime.timedelta(seconds=2), call_reboot)
- return self.finish({'reload': False, 'reboot': True, 'error': None})
-
- else:
- reboot[0] = False
-
- if restart[0]:
- logging.debug('motion needs to be restarted')
-
- motionctl.stop()
-
- if settings.SMB_SHARES:
- logging.debug('updating SMB mounts')
- stop, start = smbctl.update_mounts() # @UnusedVariable
-
- if start:
- motionctl.start()
-
- else:
- motionctl.start()
-
- self.finish({'reload': reload, 'reboot': reboot[0], 'error': error[0]})
-
- if camera_id is not None:
- if camera_id == 0: # multiple camera configs
- if len(ui_config) > 1:
- logging.debug('setting multiple configs')
-
- elif len(ui_config) == 0:
- logging.warn('no configuration to set')
-
- 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
- finish()
-
- # make sure main config is handled first
- items = ui_config.items()
- items.sort(key=lambda (key, cfg): key != 'main')
-
- for key, cfg in items:
- if key == 'main':
- result = set_main_config(cfg)
- reload = result['reload'] or reload
- reboot[0] = result['reboot'] or reboot[0]
- restart[0] = result['restart'] or restart[0]
- check_finished(None, reload)
-
- else:
- set_camera_config(int(key), cfg, check_finished)
-
- else: # single camera config
- def on_finish(e, r):
- error[0] = e
- restart[0] = r
- finish()
-
- set_camera_config(camera_id, ui_config, on_finish)
-
- else: # main config
- result = set_main_config(ui_config)
- reload = result['reload']
- reboot[0] = result['reboot']
- restart[0] = result['restart']
-
- @BaseHandler.auth(admin=True)
- def set_preview(self, camera_id):
- try:
- controls = json.loads(self.request.body)
-
- except Exception as e:
- logging.error('could not decode json: %(msg)s' % {'msg': unicode(e)})
-
- raise
-
- camera_config = config.get_camera(camera_id)
- if utils.v4l2_camera(camera_config):
- device = camera_config['videodevice']
-
- if 'brightness' in controls:
- value = int(controls['brightness'])
- logging.debug('setting brightness to %(value)s...' % {'value': value})
-
- v4l2ctl.set_brightness(device, value)
-
- if 'contrast' in controls:
- value = int(controls['contrast'])
- logging.debug('setting contrast to %(value)s...' % {'value': value})
-
- v4l2ctl.set_contrast(device, value)
-
- if 'saturation' in controls:
- value = int(controls['saturation'])
- logging.debug('setting saturation to %(value)s...' % {'value': value})
-
- v4l2ctl.set_saturation(device, value)
-
- if 'hue' in controls:
- value = int(controls['hue'])
- logging.debug('setting hue to %(value)s...' % {'value': value})
-
- v4l2ctl.set_hue(device, value)
-
- self.finish_json({})
-
- elif utils.remote_camera(camera_config):
- def on_response(error=None):
- if error:
- self.finish_json({'error': error})
-
- else:
- self.finish_json()
-
- remote.set_preview(camera_config, controls, on_response)
-
- else: # not supported
- self.finish_json({'error': True})
-
- @BaseHandler.auth()
- def list(self):
- logging.debug('listing cameras')
-
- proto = self.get_data().get('proto')
- if proto == 'motioneye': # remote listing
- def on_response(cameras=None, error=None):
- if error:
- self.finish_json({'error': error})
-
- else:
- cameras = [c for c in cameras if c.get('enabled')]
- self.finish_json({'cameras': cameras})
-
- remote.list(self.get_data(), on_response)
-
- elif proto == 'netcam':
- scheme = self.get_data().get('scheme', 'http')
-
- def on_response(cameras=None, error=None):
- if error:
- self.finish_json({'error': error})
-
- else:
- self.finish_json({'cameras': cameras})
-
- if scheme in ['http', 'https']:
- utils.test_mjpeg_url(self.get_data(), auth_modes=['basic'], allow_jpeg=True, callback=on_response)
-
- elif config.motion_rtsp_support() and scheme == 'rtsp':
- utils.test_rtsp_url(self.get_data(), callback=on_response)
-
- else:
- on_response(error='protocol %s not supported' % scheme)
-
- elif proto == 'mjpeg':
- def on_response(cameras=None, error=None):
- if error:
- self.finish_json({'error': error})
-
- else:
- self.finish_json({'cameras': cameras})
-
- utils.test_mjpeg_url(self.get_data(), auth_modes=['basic', 'digest'], allow_jpeg=False, callback=on_response)
-
- elif proto == 'v4l2':
- configured_devices = set()
- for camera_id in config.get_camera_ids():
- data = config.get_camera(camera_id)
- if utils.v4l2_camera(data):
- configured_devices.add(data['videodevice'])
-
- cameras = [{'id': d[1], 'name': d[2]} for d in v4l2ctl.list_devices()
- if (d[0] not in configured_devices) and (d[1] not in configured_devices)]
-
- self.finish_json({'cameras': cameras})
-
- else: # assuming local motionEye camera listing
- cameras = []
- camera_ids = config.get_camera_ids()
- if not config.get_main().get('@enabled'):
- camera_ids = []
-
- length = [len(camera_ids)]
- def check_finished():
- if len(cameras) == length[0]:
- cameras.sort(key=lambda c: c['id'])
- self.finish_json({'cameras': cameras})
-
- def on_response_builder(camera_id, local_config):
- def on_response(remote_ui_config=None, error=None):
- if error:
- cameras.append({
- 'id': camera_id,
- 'name': '<' + remote.pretty_camera_url(local_config) + '>',
- 'enabled': False,
- 'streaming_framerate': 1,
- 'framerate': 1
- })
-
- else:
- remote_ui_config['id'] = camera_id
-
- if not remote_ui_config['enabled'] and local_config['@enabled']:
- # if a remote camera is disabled, make sure it's disabled locally as well
- local_config['@enabled'] = False
- config.set_camera(camera_id, local_config)
-
- elif remote_ui_config['enabled'] and not local_config['@enabled']:
- # if a remote camera is locally disabled, make sure the remote config says the same thing
- remote_ui_config['enabled'] = False
-
- for key, value in local_config.items():
- remote_ui_config[key.replace('@', '')] = value
-
- cameras.append(remote_ui_config)
-
- check_finished()
-
- return on_response
-
- for camera_id in camera_ids:
- local_config = config.get_camera(camera_id)
- if local_config is None:
- continue
-
- if utils.local_motion_camera(local_config):
- ui_config = config.motion_camera_dict_to_ui(local_config)
- cameras.append(ui_config)
- check_finished()
-
- elif utils.remote_camera(local_config):
- 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
- on_response_builder(camera_id, local_config)(error=True)
-
- else: # assuming simple mjpeg camera
- ui_config = config.simple_mjpeg_camera_dict_to_ui(local_config)
- cameras.append(ui_config)
- check_finished()
-
- if length[0] == 0:
- self.finish_json({'cameras': []})
-
- @BaseHandler.auth(admin=True)
- def add_camera(self):
- logging.debug('adding new camera')
-
- try:
- device_details = json.loads(self.request.body)
-
- except Exception as e:
- logging.error('could not decode json: %(msg)s' % {'msg': unicode(e)})
-
- raise
-
- camera_config = config.add_camera(device_details)
-
- if utils.local_motion_camera(camera_config):
- motionctl.stop()
-
- if settings.SMB_SHARES:
- stop, start = smbctl.update_mounts() # @UnusedVariable
-
- if start:
- motionctl.start()
-
- else:
- motionctl.start()
-
- ui_config = config.motion_camera_dict_to_ui(camera_config)
-
- self.finish_json(ui_config)
-
- elif utils.remote_camera(camera_config):
- def on_response(remote_ui_config=None, error=None):
- if error:
- return self.finish_json({'error': error})
-
- for key, value in camera_config.items():
- remote_ui_config[key.replace('@', '')] = value
-
- self.finish_json(remote_ui_config)
-
- remote.get_config(camera_config, on_response)
-
- else: # assuming simple mjpeg camera
- ui_config = config.simple_mjpeg_camera_dict_to_ui(camera_config)
-
- self.finish_json(ui_config)
-
- @BaseHandler.auth(admin=True)
- def rem_camera(self, camera_id):
- logging.debug('removing camera %(id)s' % {'id': camera_id})
-
- local = utils.local_motion_camera(config.get_camera(camera_id))
- config.rem_camera(camera_id)
-
- if local:
- motionctl.stop()
- motionctl.start()
-
- self.finish_json()
-
- @BaseHandler.auth(admin=True)
- def backup(self):
- content = config.backup()
-
- filename = 'motioneye-config.tar.gz'
- self.set_header('Content-Type', 'application/x-compressed')
- self.set_header('Content-Disposition', 'attachment; filename=' + filename + ';')
-
- self.finish(content)
-
- @BaseHandler.auth(admin=True)
- def restore(self):
- try:
- content = self.request.files['files'][0]['body']
-
- except KeyError:
- raise HTTPError(400, 'file attachment required')
-
- result = config.restore(content)
- if result:
- self.finish_json({'ok': True, 'reboot': result['reboot']})
-
- else:
- self.finish_json({'ok': False})
-
-
-class PictureHandler(BaseHandler):
- @asynchronous
- def get(self, camera_id, op, filename=None, group=None):
- if camera_id is not None:
- camera_id = int(camera_id)
- if camera_id not in config.get_camera_ids():
- raise HTTPError(404, 'no such camera')
-
- if op == 'current':
- self.current(camera_id)
-
- elif op == 'list':
- self.list(camera_id)
-
- elif op == 'frame':
- self.frame(camera_id)
-
- elif op == 'download':
- self.download(camera_id, filename)
-
- elif op == 'preview':
- self.preview(camera_id, filename)
-
- elif op == 'zipped':
- self.zipped(camera_id, group)
-
- elif op == 'timelapse':
- self.timelapse(camera_id, group)
-
- else:
- raise HTTPError(400, 'unknown operation')
-
- @asynchronous
- def post(self, camera_id, op, filename=None, group=None):
- if camera_id is not None:
- camera_id = int(camera_id)
- if camera_id not in config.get_camera_ids():
- raise HTTPError(404, 'no such camera')
-
- if op == 'delete':
- self.delete(camera_id, filename)
-
- elif op == 'delete_all':
- self.delete_all(camera_id, group)
-
- else:
- raise HTTPError(400, 'unknown operation')
-
- @BaseHandler.auth(prompt=False)
- def current(self, camera_id):
- self.set_header('Content-Type', 'image/jpeg')
-
- width = self.get_argument('width', None)
- height = self.get_argument('height', None)
-
- camera_config = config.get_camera(camera_id)
- if utils.local_motion_camera(camera_config):
- picture = mediafiles.get_current_picture(camera_config,
- width=width,
- height=height)
-
- self.set_cookie('motion_detected_' + str(camera_id), str(motionctl.is_motion_detected(camera_id)).lower())
- self.try_finish(picture)
-
- elif utils.remote_camera(camera_config):
- def on_response(motion_detected=False, picture=None, error=None):
- self.set_cookie('motion_detected_' + str(camera_id), str(motion_detected).lower())
- self.try_finish(picture)
-
- remote.get_current_picture(camera_config, width=width, height=height, callback=on_response)
-
- 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})
-
- camera_config = config.get_camera(camera_id)
- if utils.local_motion_camera(camera_config):
- def on_media_list(media_list):
- if media_list is None:
- return self.finish_json({'error': 'Failed to get pictures list.'})
-
- self.finish_json({
- 'mediaList': media_list,
- 'cameraName': camera_config['@name']
- })
-
- mediafiles.list_media(camera_config, media_type='picture',
- callback=on_media_list, prefix=self.get_argument('prefix', None))
-
- elif utils.remote_camera(camera_config):
- def on_response(remote_list=None, error=None):
- if error:
- return self.finish_json({'error': 'Failed to get picture list for %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
-
- self.finish_json(remote_list)
-
- remote.list_media(camera_config, media_type='picture', prefix=self.get_argument('prefix', None), callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- def frame(self, camera_id):
- camera_config = config.get_camera(camera_id)
-
- if utils.local_motion_camera(camera_config) or utils.simple_mjpeg_camera(camera_config) or self.get_argument('title', None) is not None:
- self.render('main.html',
- frame=True,
- camera_id=camera_id,
- camera_config=camera_config,
- title=self.get_argument('title', camera_config.get('@name', '')),
- admin_username=config.get_main().get('@admin_username'))
-
- elif utils.remote_camera(camera_config):
- def on_response(remote_ui_config=None, error=None):
- if error:
- return self.render('main.html',
- frame=True,
- camera_id=camera_id,
- camera_config=camera_config,
- title=self.get_argument('title', ''))
-
- # issue a fake motion_camera_ui_to_dict() call to transform
- # the remote UI values into motion config directives
- remote_config = config.motion_camera_ui_to_dict(remote_ui_config)
-
- self.render('main.html',
- frame=True,
- camera_id=camera_id,
- camera_config=remote_config,
- title=self.get_argument('title', remote_config['@name']),
- admin_username=config.get_main().get('@admin_username'))
-
- remote.get_config(camera_config, on_response)
-
- @BaseHandler.auth()
- def download(self, camera_id, filename):
- logging.debug('downloading picture %(filename)s of camera %(id)s' % {
- 'filename': filename, 'id': camera_id})
-
- camera_config = config.get_camera(camera_id)
- if utils.local_motion_camera(camera_config):
- content = mediafiles.get_media_content(camera_config, filename, 'picture')
-
- pretty_filename = camera_config['@name'] + '_' + os.path.basename(filename)
- self.set_header('Content-Type', 'image/jpeg')
- self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + ';')
-
- self.finish(content)
-
- elif utils.remote_camera(camera_config):
- def on_response(response=None, error=None):
- if error:
- 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
- self.set_header('Content-Type', 'image/jpeg')
- self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + ';')
-
- self.finish(response)
-
- remote.get_media_content(camera_config, filename=filename, media_type='picture', callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- @BaseHandler.auth()
- def preview(self, camera_id, filename):
- logging.debug('previewing picture %(filename)s of camera %(id)s' % {
- 'filename': filename, 'id': camera_id})
-
- camera_config = config.get_camera(camera_id)
- if utils.local_motion_camera(camera_config):
- content = mediafiles.get_media_preview(camera_config, filename, 'picture',
- width=self.get_argument('width', None),
- height=self.get_argument('height', None))
-
- if content:
- self.set_header('Content-Type', 'image/jpeg')
-
- else:
- self.set_header('Content-Type', 'image/svg+xml')
- content = open(os.path.join(settings.STATIC_PATH, 'img', 'no-preview.svg')).read()
-
- self.finish(content)
-
- elif utils.remote_camera(camera_config):
- def on_response(content=None, error=None):
- if content:
- self.set_header('Content-Type', 'image/jpeg')
-
- else:
- self.set_header('Content-Type', 'image/svg+xml')
- content = open(os.path.join(settings.STATIC_PATH, 'img', 'no-preview.svg')).read()
-
- self.finish(content)
-
- remote.get_media_preview(camera_config, filename=filename, media_type='picture',
- width=self.get_argument('width', None),
- height=self.get_argument('height', None),
- callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- @BaseHandler.auth(admin=True)
- def delete(self, camera_id, filename):
- logging.debug('deleting picture %(filename)s of camera %(id)s' % {
- 'filename': filename, 'id': camera_id})
-
- camera_config = config.get_camera(camera_id)
- if utils.local_motion_camera(camera_config):
- try:
- mediafiles.del_media_content(camera_config, filename, 'picture')
- self.finish_json()
-
- except Exception as e:
- self.finish_json({'error': unicode(e)})
-
- elif utils.remote_camera(camera_config):
- def on_response(response=None, error=None):
- if error:
- return self.finish_json({'error': 'Failed to delete picture from %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
-
- self.finish_json()
-
- remote.del_media_content(camera_config, filename=filename, media_type='picture', callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- @BaseHandler.auth()
- def zipped(self, camera_id, group):
- key = self.get_argument('key', None)
- camera_config = config.get_camera(camera_id)
-
- if key:
- logging.debug('serving zip file for group %(group)s of camera %(id)s with key %(key)s' % {
- 'group': group, 'id': camera_id, 'key': key})
-
- if utils.local_motion_camera(camera_config):
- data = mediafiles.get_prepared_cache(key)
- if not data:
- logging.error('prepared cache data for key "%s" does not exist' % key)
-
- raise HTTPError(404, 'no such key')
-
- pretty_filename = camera_config['@name'] + '_' + group
- pretty_filename = re.sub('[^a-zA-Z0-9]', '_', pretty_filename)
-
- self.set_header('Content-Type', 'application/zip')
- self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + '.zip;')
- self.finish(data)
-
- elif utils.remote_camera(camera_config):
- def on_response(response=None, error=None):
- if error:
- return self.finish_json({'error': 'Failed to download zip file from %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
-
- self.set_header('Content-Type', response['content_type'])
- 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)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- else: # prepare
- logging.debug('preparing zip file for group %(group)s of camera %(id)s' % {
- 'group': group, 'id': camera_id})
-
- if utils.local_motion_camera(camera_config):
- def on_zip(data):
- if data is None:
- return self.finish_json({'error': 'Failed to create zip file.'})
-
- key = mediafiles.set_prepared_cache(data)
- logging.debug('prepared zip file for group %(group)s of camera %(id)s with key %(key)s' % {
- 'group': group, 'id': camera_id, 'key': key})
- self.finish_json({'key': key})
-
- mediafiles.get_zipped_content(camera_config, media_type='picture', group=group, callback=on_zip)
-
- elif utils.remote_camera(camera_config):
- def on_response(response=None, error=None):
- if error:
- return self.finish_json({'error': 'Failed to make zip file at %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
-
- self.finish_json({'key': response['key']})
-
- remote.make_zipped_content(camera_config, media_type='picture', group=group, callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- @BaseHandler.auth()
- def timelapse(self, camera_id, group):
- key = self.get_argument('key', None)
- check = self.get_argument('check', False)
- camera_config = config.get_camera(camera_id)
-
- if key: # download
- logging.debug('serving timelapse movie for group %(group)s of camera %(id)s with key %(key)s' % {
- 'group': group, 'id': camera_id, 'key': key})
-
- if utils.local_motion_camera(camera_config):
- data = mediafiles.get_prepared_cache(key)
- if data is None:
- logging.error('prepared cache data for key "%s" does not exist' % key)
-
- raise HTTPError(404, 'no such key')
-
- pretty_filename = camera_config['@name'] + '_' + group
- pretty_filename = re.sub('[^a-zA-Z0-9]', '_', pretty_filename)
-
- self.set_header('Content-Type', 'video/x-msvideo')
- self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + '.avi;')
- self.finish(data)
-
- elif utils.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}})
-
- self.set_header('Content-Type', response['content_type'])
- self.set_header('Content-Disposition', response['content_disposition'])
- self.finish(response['data'])
-
- remote.get_timelapse_movie(camera_config, key, group=group, callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- elif check:
- logging.debug('checking timelapse movie status for group %(group)s of camera %(id)s' % {
- 'group': group, 'id': camera_id})
-
- if utils.local_motion_camera(camera_config):
- status = mediafiles.check_timelapse_movie()
- if status['progress'] == -1 and status['data']:
- key = mediafiles.set_prepared_cache(status['data'])
- logging.debug('prepared timelapse movie for group %(group)s of camera %(id)s with key %(key)s' % {
- 'group': group, 'id': camera_id, 'key': key})
- self.finish_json({'key': key, 'progress': -1})
-
- else:
- self.finish_json(status)
-
- elif utils.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}})
-
- if response['progress'] == -1 and response.get('key'):
- self.finish_json({'key': response['key'], 'progress': -1})
-
- else:
- self.finish_json(response)
-
- remote.check_timelapse_movie(camera_config, group=group, callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- 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, 'id': camera_id, 'framerate': framerate, 'int': interval})
-
- if utils.local_motion_camera(camera_config):
- status = mediafiles.check_timelapse_movie()
- if status['progress'] != -1:
- self.finish_json({'progress': status['progress']}) # timelapse already active
-
- else:
- mediafiles.make_timelapse_movie(camera_config, framerate, interval, group=group)
- self.finish_json({'progress': -1})
-
- elif utils.remote_camera(camera_config):
- def on_status(response=None, error=None):
- if error:
- return self.finish_json({'error': 'Failed to make timelapse movie at %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
-
- if response['progress'] != -1:
- return self.finish_json({'progress': response['progress']}) # timelapse already active
-
- def on_make(response=None, error=None):
- if error:
- return self.finish_json({'error': 'Failed to make timelapse movie at %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
-
- self.finish_json({'progress': -1})
-
- remote.make_timelapse_movie(camera_config, framerate, interval, group=group, callback=on_make)
-
- remote.check_timelapse_movie(camera_config, group=group, callback=on_status)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- @BaseHandler.auth(admin=True)
- def delete_all(self, camera_id, group):
- logging.debug('deleting picture group %(group)s of camera %(id)s' % {
- 'group': group, 'id': camera_id})
-
- camera_config = config.get_camera(camera_id)
- if utils.local_motion_camera(camera_config):
- try:
- mediafiles.del_media_group(camera_config, group, 'picture')
- self.finish_json()
-
- except Exception as e:
- self.finish_json({'error': unicode(e)})
-
- elif utils.remote_camera(camera_config):
- def on_response(response=None, error=None):
- if error:
- return self.finish_json({'error': 'Failed to delete picture group from %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
-
- self.finish_json()
-
- remote.del_media_group(camera_config, group=group, media_type='picture', callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- def try_finish(self, content):
- try:
- self.finish(content)
-
- except IOError as e:
- logging.warning('could not write response: %(msg)s' % {'msg': unicode(e)})
-
-
-class MovieHandler(BaseHandler):
- @asynchronous
- def get(self, camera_id, op, filename=None):
- if camera_id is not None:
- camera_id = int(camera_id)
- if camera_id not in config.get_camera_ids():
- raise HTTPError(404, 'no such camera')
-
- if op == 'list':
- self.list(camera_id)
-
- elif op == 'download':
- self.download(camera_id, filename)
-
- elif op == 'preview':
- self.preview(camera_id, filename)
-
- else:
- raise HTTPError(400, 'unknown operation')
-
- @asynchronous
- def post(self, camera_id, op, filename=None, group=None):
- if camera_id is not None:
- camera_id = int(camera_id)
- if camera_id not in config.get_camera_ids():
- raise HTTPError(404, 'no such camera')
-
- if op == 'delete':
- self.delete(camera_id, filename)
-
- elif op == 'delete_all':
- self.delete_all(camera_id, group)
-
- else:
- raise HTTPError(400, 'unknown operation')
-
- @BaseHandler.auth()
- def list(self, camera_id):
- logging.debug('listing movies for camera %(id)s' % {'id': camera_id})
-
- camera_config = config.get_camera(camera_id)
- if utils.local_motion_camera(camera_config):
- def on_media_list(media_list):
- if media_list is None:
- return self.finish_json({'error': 'Failed to get movies list.'})
-
- self.finish_json({
- 'mediaList': media_list,
- 'cameraName': camera_config['@name']
- })
-
- mediafiles.list_media(camera_config, media_type='movie',
- callback=on_media_list, prefix=self.get_argument('prefix', None))
-
- elif utils.remote_camera(camera_config):
- def on_response(remote_list=None, error=None):
- if error:
- return self.finish_json({'error': 'Failed to get movie list for %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
-
- self.finish_json(remote_list)
-
- remote.list_media(camera_config, media_type='movie', prefix=self.get_argument('prefix', None), callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- @BaseHandler.auth()
- def download(self, camera_id, filename):
- logging.debug('downloading movie %(filename)s of camera %(id)s' % {
- 'filename': filename, 'id': camera_id})
-
- camera_config = config.get_camera(camera_id)
- if utils.local_motion_camera(camera_config):
- content = mediafiles.get_media_content(camera_config, filename, 'movie')
-
- pretty_filename = camera_config['@name'] + '_' + os.path.basename(filename)
- self.set_header('Content-Type', 'video/mpeg')
- self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + ';')
-
- self.finish(content)
-
- elif utils.remote_camera(camera_config):
- def on_response(response=None, error=None):
- if error:
- 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
- self.set_header('Content-Type', 'video/mpeg')
- self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + ';')
-
- self.finish(response)
-
- remote.get_media_content(camera_config, filename=filename, media_type='movie', callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- @BaseHandler.auth()
- def preview(self, camera_id, filename):
- logging.debug('previewing movie %(filename)s of camera %(id)s' % {
- 'filename': filename, 'id': camera_id})
-
- camera_config = config.get_camera(camera_id)
- if utils.local_motion_camera(camera_config):
- content = mediafiles.get_media_preview(camera_config, filename, 'movie',
- width=self.get_argument('width', None),
- height=self.get_argument('height', None))
-
- if content:
- self.set_header('Content-Type', 'image/jpeg')
-
- else:
- self.set_header('Content-Type', 'image/svg+xml')
- content = open(os.path.join(settings.STATIC_PATH, 'img', 'no-preview.svg')).read()
-
- self.finish(content)
-
- elif utils.remote_camera(camera_config):
- def on_response(content=None, error=None):
- if content:
- self.set_header('Content-Type', 'image/jpeg')
-
- else:
- self.set_header('Content-Type', 'image/svg+xml')
- content = open(os.path.join(settings.STATIC_PATH, 'img', 'no-preview.svg')).read()
-
- self.finish(content)
-
- remote.get_media_preview(camera_config, filename=filename, media_type='movie',
- width=self.get_argument('width', None),
- height=self.get_argument('height', None),
- callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- @BaseHandler.auth(admin=True)
- def delete(self, camera_id, filename):
- logging.debug('deleting movie %(filename)s of camera %(id)s' % {
- 'filename': filename, 'id': camera_id})
-
- camera_config = config.get_camera(camera_id)
- if utils.local_motion_camera(camera_config):
- try:
- mediafiles.del_media_content(camera_config, filename, 'movie')
- self.finish_json()
-
- except Exception as e:
- self.finish_json({'error': unicode(e)})
-
- elif utils.remote_camera(camera_config):
- def on_response(response=None, error=None):
- if error:
- return self.finish_json({'error': 'Failed to delete movie from %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
-
- self.finish_json()
-
- remote.del_media_content(camera_config, filename=filename, media_type='movie', callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
- @BaseHandler.auth(admin=True)
- def delete_all(self, camera_id, group):
- logging.debug('deleting movie group %(group)s of camera %(id)s' % {
- 'group': group, 'id': camera_id})
-
- camera_config = config.get_camera(camera_id)
- if utils.local_motion_camera(camera_config):
- try:
- mediafiles.del_media_group(camera_config, group, 'movie')
- self.finish_json()
-
- except Exception as e:
- self.finish_json({'error': unicode(e)})
-
- elif utils.remote_camera(camera_config):
- def on_response(response=None, error=None):
- if error:
- return self.finish_json({'error': 'Failed to delete movie group from %(url)s: %(msg)s.' % {
- 'url': remote.pretty_camera_url(camera_config), 'msg': error}})
-
- self.finish_json()
-
- remote.del_media_group(camera_config, group=group, media_type='movie', callback=on_response)
-
- else: # assuming simple mjpeg camera
- raise HTTPError(400, 'unknown operation')
-
-
-class RelayEventHandler(BaseHandler):
- @BaseHandler.auth(admin=True)
- def post(self):
- event = self.get_argument('event')
- thread_id = int(self.get_argument('thread_id'))
- logging.debug('recevied relayed event %(event)s for thread id %(id)s' % {'event': event, 'id': thread_id})
-
- camera_id = motionctl.thread_id_to_camera_id(thread_id)
- try:
- camera_config = config.get_camera(camera_id)
-
- except:
- logging.warn('ignoring event for remote camera with id %s (probably removed)' % camera_id)
- return self.finish_json()
-
- if not utils.local_motion_camera(camera_config):
- logging.warn('ignoring event for non-local camera with id %s' % camera_id)
- return self.finish_json()
-
- if event == 'start':
- if not camera_config['@motion_detection']:
- logging.debug('ignoring start event for camera with id %s and motion detection disabled' % camera_id)
- return self.finish_json()
-
- motionctl.set_motion_detected(camera_id, True)
-
- elif event == 'stop':
- motionctl.set_motion_detected(camera_id, False)
-
- else:
- logging.warn('unknown event %s' % event)
-
- self.finish_json()
-
-
-class LogHandler(BaseHandler):
- LOGS = {
- 'motion': (os.path.join(settings.LOG_PATH, 'motion.log'), 'motion.log'),
- }
-
- @BaseHandler.auth(admin=True)
- def get(self, name):
- log = self.LOGS.get(name)
- if log is None:
- raise HTTPError(404, 'no such log')
-
- (path, filename) = log
-
- self.set_header('Content-Type', 'text/plain')
- self.set_header('Content-Disposition', 'attachment; filename=' + filename + ';')
-
- 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
- logging.debug('serving log file "%s" from command "%s"' % (filename, path))
-
- try:
- output = subprocess.check_output(path, shell=True)
-
- except Exception as e:
- output = 'failed to execute command: %s' % e
-
- self.finish(output)
-
-
-class UpdateHandler(BaseHandler):
- @BaseHandler.auth(admin=True)
- def get(self):
- logging.debug('listing versions')
-
- versions = update.get_all_versions()
- current_version = update.get_version()
- update_version = None
- if versions and update.compare_versions(versions[-1], current_version) > 0:
- update_version = versions[-1]
-
- self.finish_json({
- 'update_version': update_version,
- 'current_version': current_version
- })
-
- @BaseHandler.auth(admin=True)
- def post(self):
- version = self.get_argument('version')
-
- logging.debug('performing update to version %(version)s' % {'version': version})
-
- result = update.perform_update(version)
-
- self.finish_json(result)
-
-
-class PowerHandler(BaseHandler):
- @BaseHandler.auth(admin=True)
- def post(self, op):
- if op == 'shutdown':
- self.shut_down()
-
- elif op == 'reboot':
- self.reboot()
-
- def shut_down(self):
- IOLoop.instance().add_timeout(datetime.timedelta(seconds=2), powerctl.shut_down)
-
- def reboot(self):
- IOLoop.instance().add_timeout(datetime.timedelta(seconds=2), powerctl.reboot)
-
-
-class VersionHandler(BaseHandler):
- def get(self):
- self.render('version.html',
- version=update.get_version(),
- hostname=socket.gethostname())
-
- post = get
-
-
-# this will only trigger the login mechanism on the client side, if required
-class LoginHandler(BaseHandler):
- @BaseHandler.auth()
- def get(self):
- self.finish_json()
-
- def post(self):
- self.set_header('Content-Type', 'text/html')
- self.finish()
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import datetime
-import errno
-import fcntl
-import functools
-import hashlib
-import logging
-import multiprocessing
-import os.path
-import re
-import stat
-import StringIO
-import subprocess
-import time
-import tornado
-import zipfile
-
-from PIL import Image
-from tornado import ioloop
-
-import config
-import settings
-import utils
-
-
-_PICTURE_EXTS = ['.jpg']
-_MOVIE_EXTS = ['.avi', '.mp4']
-
-# a cache list of paths to movies without preview
-_previewless_movie_files = []
-
-# a cache of prepared files (whose preparing time is significant)
-_prepared_files = {}
-
-_timelapse_process = None
-_timelapse_data = None
-
-
-def _list_media_files(dir, exts, prefix=None):
- media_files = []
-
- if prefix is not None:
- if prefix == 'ungrouped':
- prefix = ''
-
- root = os.path.join(dir, prefix)
- for name in os.listdir(root):
- if name == 'lastsnap.jpg' or name.startswith('.'): # ignore the lastsnap.jpg and hidden files
- continue
-
- full_path = os.path.join(root, name)
- try:
- st = os.stat(full_path)
-
- except Exception as e:
- logging.error('stat failed: ' + unicode(e))
- continue
-
- if not stat.S_ISREG(st.st_mode): # not a regular file
- continue
-
- full_path_lower = full_path.lower()
- if not [e for e in exts if full_path_lower.endswith(e)]:
- continue
-
- media_files.append((full_path, st))
-
- else:
- for root, dirs, files in os.walk(dir): # @UnusedVariable # TODO os.walk can be rewritten to return stat info
- for name in files:
- if name == 'lastsnap.jpg' or name.startswith('.'): # ignore the lastsnap.jpg and hidden files
- continue
-
- full_path = os.path.join(root, name)
- try:
- st = os.stat(full_path)
-
- except Exception as e:
- logging.error('stat failed: ' + unicode(e))
- continue
-
- if not stat.S_ISREG(st.st_mode): # not a regular file
- continue
-
- full_path_lower = full_path.lower()
- if not [e for e in exts if full_path_lower.endswith(e)]:
- continue
-
- media_files.append((full_path, st))
-
- return media_files
-
-
-def _remove_older_files(dir, moment, exts):
- for (full_path, st) in _list_media_files(dir, exts):
- file_moment = datetime.datetime.fromtimestamp(st.st_mtime)
- if file_moment < moment:
- logging.debug('removing file %(path)s...' % {'path': full_path})
-
- # remove the file itself
- try:
- os.remove(full_path)
-
- except OSError as e:
- if e.errno == errno.ENOENT:
- pass # the file might have been removed in the meantime
-
- else:
- logging.error('failed to remove %s: %s' % (full_path, e))
-
- # remove the parent directories if empty or contain only thumb files
- dir_path = os.path.dirname(full_path)
- if not os.path.exists(dir_path):
- continue
-
- listing = os.listdir(dir_path)
- thumbs = [l for l in listing if l.endswith('.thumb')]
-
- if len(listing) == len(thumbs): # only thumbs
- for p in thumbs:
- try:
- os.remove(os.path.join(dir_path, p))
-
- except:
- logging.error('failed to remove %s: %s' % (p, e))
-
- if not listing or len(listing) == len(thumbs):
- # this will possibly cause following paths that are in the media files for loop
- # to be removed in advance; the os.remove call will raise ENOENT which is silently ignored
- logging.debug('removing empty directory %(path)s...' % {'path': dir_path})
- try:
- os.removedirs(dir_path)
-
- except:
- logging.error('failed to remove %s: %s' % (dir_path, e))
-
-
-def find_ffmpeg():
- try:
- return subprocess.check_output('which ffmpeg', shell=True).strip()
-
- except subprocess.CalledProcessError: # not found
- return None
-
-
-def cleanup_media(media_type):
- logging.debug('cleaning up %(media_type)ss...' % {'media_type': media_type})
-
- if media_type == 'picture':
- exts = _PICTURE_EXTS
-
- elif 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.local_motion_camera(camera_config):
- continue
-
- preserve_media = camera_config.get('@preserve_%(media_type)ss' % {'media_type': media_type}, 0)
- if preserve_media == 0:
- return # preserve forever
-
- still_images_enabled = bool(
- ((camera_config['emulate_motion'] or camera_config['output_pictures']) and camera_config['picture_filename']) or
- (camera_config['snapshot_interval'] and camera_config['snapshot_filename']))
-
- 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
-
- elif media_type == 'movie' and not movies_enabled:
- continue # only cleanup movies for cameras with movies enabled
-
- preserve_moment = datetime.datetime.now() - datetime.timedelta(days=preserve_media)
-
- target_dir = camera_config.get('target_dir')
- _remove_older_files(target_dir, preserve_moment, exts=exts)
-
-
-def make_movie_preview(camera_config, full_path):
- framerate = camera_config['framerate']
- pre_capture = camera_config['pre_capture']
- offs = pre_capture / framerate
- offs = max(4, offs * 2)
-
- logging.debug('creating movie preview for %(path)s with an offset of %(offs)s seconds...' % {
- 'path': full_path, 'offs': offs})
-
- cmd = 'ffmpeg -i "%(path)s" -f mjpeg -vframes 1 -ss %(offs)s -y %(path)s.thumb'
-
- try:
- subprocess.check_output(cmd % {'path': full_path, 'offs': offs}, shell=True, stderr=subprocess.STDOUT)
-
- except subprocess.CalledProcessError as e:
- logging.error('failed to create movie preview for %(path)s: %(msg)s' % {
- 'path': full_path, 'msg': unicode(e)})
-
- return None
-
- try:
- st = os.stat(full_path + '.thumb')
-
- except os.error:
- logging.error('failed to create movie preview for %(path)s: ffmpeg error' % {
- 'path': full_path})
-
- return None
-
- if st.st_size == 0:
- logging.debug('movie is too short, grabbing first frame from %(path)s...' % {'path': full_path})
-
- # try again, this time grabbing the very first frame
- try:
- subprocess.check_output(cmd % {'path': full_path, 'offs': 0}, shell=True, stderr=subprocess.STDOUT)
-
- except subprocess.CalledProcessError as e:
- logging.error('failed to create movie preview for %(path)s: %(msg)s' % {
- 'path': full_path, 'msg': unicode(e)})
-
- return None
-
- return full_path + '.thumb'
-
-
-def make_next_movie_preview():
- global _previewless_movie_files
-
- logging.debug('making preview for the next movie...')
-
- if _previewless_movie_files:
- (camera_config, path) = _previewless_movie_files.pop(0)
-
- make_movie_preview(camera_config, path)
-
- else:
- logging.debug('gathering movies without preview...')
-
- count = 0
- for camera_id in config.get_camera_ids():
- camera_config = config.get_camera(camera_id)
- if not utils.local_motion_camera(camera_config):
- continue
-
- target_dir = camera_config['target_dir']
-
- for (full_path, st) in _list_media_files(target_dir, _MOVIE_EXTS): # @UnusedVariable
- if os.path.exists(full_path + '.thumb'):
- continue
-
- logging.debug('found a movie without preview: %(path)s' % {
- 'path': full_path})
-
- _previewless_movie_files.append((camera_config, full_path))
- count += 1
-
- logging.debug('found %(count)d movies without preview' % {'count': count})
-
- if count:
- make_next_movie_preview()
-
-
-def list_media(camera_config, media_type, callback, prefix=None):
- target_dir = camera_config.get('target_dir')
-
- if media_type == 'picture':
- exts = _PICTURE_EXTS
-
- elif media_type == 'movie':
- exts = _MOVIE_EXTS
-
- # create a subprocess to retrieve media files
- def do_list_media(pipe):
- mf = _list_media_files(target_dir, exts=exts, prefix=prefix)
- for (p, st) in mf:
- path = p[len(target_dir):]
- if not path.startswith('/'):
- path = '/' + path
-
- timestamp = st.st_mtime
- size = st.st_size
-
- pipe.send({
- 'path': path,
- 'momentStr': utils.pretty_date_time(datetime.datetime.fromtimestamp(timestamp)),
- 'momentStrShort': utils.pretty_date_time(datetime.datetime.fromtimestamp(timestamp), short=True),
- 'sizeStr': utils.pretty_size(size),
- 'timestamp': timestamp
- })
-
- pipe.close()
-
- logging.debug('starting media listing process...')
-
- (parent_pipe, child_pipe) = multiprocessing.Pipe(duplex=False)
- process = multiprocessing.Process(target=do_list_media, args=(child_pipe, ))
- process.start()
-
- # poll the subprocess to see when it has finished
- started = datetime.datetime.now()
- media_list = []
-
- def read_media_list():
- while parent_pipe.poll():
- media_list.append(parent_pipe.recv())
-
- def poll_process():
- ioloop = tornado.ioloop.IOLoop.instance()
- if process.is_alive(): # not finished yet
- now = datetime.datetime.now()
- delta = now - started
- if delta.seconds < 120:
- ioloop.add_timeout(datetime.timedelta(seconds=0.5), poll_process)
- read_media_list()
-
- else: # process did not finish within 2 minutes
- logging.error('timeout waiting for the media listing process to finish')
-
- callback(None)
-
- else: # finished
- read_media_list()
- logging.debug('media listing process has returned %(count)s files' % {'count': len(media_list)})
- callback(media_list)
-
- poll_process()
-
-
-def get_media_content(camera_config, path, media_type):
- target_dir = camera_config.get('target_dir')
-
- full_path = os.path.join(target_dir, path)
-
- try:
- with open(full_path) as f:
- return f.read()
-
- except Exception as e:
- logging.error('failed to read file %(path)s: %(msg)s' % {
- 'path': full_path, 'msg': unicode(e)})
-
- return None
-
-
-def get_zipped_content(camera_config, media_type, group, callback):
- target_dir = camera_config.get('target_dir')
-
- if media_type == 'picture':
- exts = _PICTURE_EXTS
-
- elif media_type == 'movie':
- exts = _MOVIE_EXTS
-
- working = multiprocessing.Value('b')
- working.value = True
-
- # create a subprocess to add files to zip
- def do_zip(pipe):
- mf = _list_media_files(target_dir, exts=exts, prefix=group)
- paths = []
- for (p, st) in mf: # @UnusedVariable
- path = p[len(target_dir):]
- if path.startswith('/'):
- path = path[1:]
-
- paths.append(path)
-
- zip_filename = os.path.join(settings.MEDIA_PATH, '.zip-%s' % int(time.time()))
- logging.debug('adding %d files to zip file "%s"' % (len(paths), zip_filename))
-
- try:
- with zipfile.ZipFile(zip_filename, mode='w') as f:
- for path in paths:
- full_path = os.path.join(target_dir, path)
- f.write(full_path, path)
-
- except Exception as e:
- logging.error('failed to create zip file "%s": %s' % (zip_filename, e))
-
- working.value = False
- pipe.close()
- return
-
- logging.debug('reading zip file "%s" into memory' % zip_filename)
-
- try:
- with open(zip_filename, mode='r') as f:
- data = f.read()
-
- working.value = False
- pipe.send(data)
- logging.debug('zip data ready')
-
- except Exception as e:
- logging.error('failed to read zip file "%s": %s' % (zip_filename, e))
- working.value = False
-
- finally:
- os.remove(zip_filename)
- pipe.close()
-
- logging.debug('starting zip process...')
-
- (parent_pipe, child_pipe) = multiprocessing.Pipe(duplex=False)
- process = multiprocessing.Process(target=do_zip, args=(child_pipe, ))
- process.start()
-
- # poll the subprocess to see when it has finished
- started = datetime.datetime.now()
-
- def poll_process():
- ioloop = tornado.ioloop.IOLoop.instance()
- if working.value:
- now = datetime.datetime.now()
- delta = now - started
- if delta.seconds < settings.ZIP_TIMEOUT:
- ioloop.add_timeout(datetime.timedelta(seconds=0.5), poll_process)
-
- else: # process did not finish within 2 minutes
- logging.error('timeout waiting for the zip process to finish')
-
- callback(None)
-
- else: # finished
- try:
- data = parent_pipe.recv()
- logging.debug('zip process has returned %d bytes' % len(data))
-
- except:
- data = None
-
- callback(data)
-
- poll_process()
-
-
-def make_timelapse_movie(camera_config, framerate, interval, group):
- global _timelapse_process
- global _timelapse_data
-
- target_dir = camera_config.get('target_dir')
-
- # create a subprocess to retrieve media files
- def do_list_media(pipe):
- mf = _list_media_files(target_dir, exts=_PICTURE_EXTS, prefix=group)
- for (p, st) in mf:
- timestamp = st.st_mtime
-
- pipe.send({
- 'path': p,
- 'timestamp': timestamp
- })
-
- pipe.close()
-
- logging.debug('starting media listing process...')
-
- (parent_pipe, child_pipe) = multiprocessing.Pipe(duplex=False)
- _timelapse_process = multiprocessing.Process(target=do_list_media, args=(child_pipe, ))
- _timelapse_process.progress = 0
- _timelapse_process.start()
- _timelapse_data = None
-
- started = [datetime.datetime.now()]
- media_list = []
-
- tmp_filename = os.path.join(settings.MEDIA_PATH, '.%s.avi' % int(time.time()))
-
- def read_media_list():
- while parent_pipe.poll():
- media_list.append(parent_pipe.recv())
-
- def poll_media_list_process():
- ioloop = tornado.ioloop.IOLoop.instance()
- if _timelapse_process.is_alive(): # not finished yet
- now = datetime.datetime.now()
- delta = now - started[0]
- if delta.seconds < 300: # the subprocess has 5 minutes to complete its job
- ioloop.add_timeout(datetime.timedelta(seconds=0.5), poll_media_list_process)
- read_media_list()
-
- else: # process did not finish within 2 minutes
- logging.error('timeout waiting for the media listing process to finish')
-
- _timelapse_process.progress = -1
-
- else: # finished
- read_media_list()
- logging.debug('media listing process has returned %(count)s files' % {'count': len(media_list)})
-
- if not media_list:
- _timelapse_process.progress = -1
-
- return
-
- pictures = select_pictures(media_list)
- make_movie(pictures)
-
- def select_pictures(media_list):
- media_list.sort(key=lambda e: e['timestamp'])
- start = media_list[0]['timestamp']
- slices = {}
- max_idx = 0
- for m in media_list:
- offs = m['timestamp'] - start
- pos = float(offs) / interval - 0.5
- idx = int(round(pos))
- max_idx = idx
- m['delta'] = abs(pos - idx)
- slices.setdefault(idx, []).append(m)
-
- selected = []
- for i in xrange(max_idx + 1):
- slice = slices.get(i)
- if not slice:
- continue
-
- selected.append(min(slice, key=lambda m: m['delta']))
-
- logging.debug('selected %d/%d media files' % (len(selected), len(media_list)))
-
- return selected
-
- 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 mpeg4 -b:v %(bitrate)s -qscale:v 0.1 -f avi %(tmp_filename)s'
-
- bitrate = 9999999
-
- cmd = cmd % {
- 'tmp_filename': tmp_filename,
- 'jpegs': ' '.join((('"' + p['path'] + '"') for p in pictures)),
- 'framerate': framerate,
- '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%
-
- # make subprocess stdout pipe non-blocking
- fd = _timelapse_process.stdout.fileno()
- fl = fcntl.fcntl(fd, fcntl.F_GETFL)
- fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
-
- poll_movie_process(pictures)
-
- def poll_movie_process(pictures):
- global _timelapse_process
- global _timelapse_data
-
- ioloop = tornado.ioloop.IOLoop.instance()
- if _timelapse_process.poll() is None: # not finished yet
- ioloop.add_timeout(datetime.timedelta(seconds=0.5), functools.partial(poll_movie_process, pictures))
-
- try:
- output = _timelapse_process.stdout.read()
-
- except IOError as e:
- if e.errno == errno.EAGAIN:
- output = ''
-
- else:
- raise
-
- frame_index = re.findall('frame=\s*(\d+)', output)
- try:
- frame_index = int(frame_index[-1])
-
- except (IndexError, ValueError):
- return
-
- _timelapse_process.progress = max(0.01, float(frame_index) / len(pictures))
-
- logging.debug('timelapse progress: %s' % int(100 * _timelapse_process.progress))
-
- else: # finished
- exit_code = _timelapse_process.poll()
- _timelapse_process = None
-
- if exit_code != 0:
- logging.error('ffmpeg process failed')
- _timelapse_data = None
-
- try:
- os.remove(tmp_filename)
-
- except:
- pass
-
- else:
- logging.debug('reading timelapse movie file "%s" into memory' % tmp_filename)
-
- try:
- with open(tmp_filename, mode='r') as f:
- _timelapse_data = f.read()
-
- logging.debug('timelapse movie process has returned %d bytes' % len(_timelapse_data))
-
- except Exception as e:
- logging.error('failed to read timelapse movie file "%s": %s' % (tmp_filename, e))
-
- finally:
- try:
- os.remove(tmp_filename)
-
- except:
- pass
-
- poll_media_list_process()
-
-
-def check_timelapse_movie():
- if _timelapse_process:
- if ((hasattr(_timelapse_process, 'poll') and _timelapse_process.poll() is None) or
- (hasattr(_timelapse_process, 'is_alive') and _timelapse_process.is_alive())):
-
- return {'progress': _timelapse_process.progress, 'data': None}
-
- else:
- return {'progress': _timelapse_process.progress, 'data': _timelapse_data}
-
- else:
- return {'progress': -1, 'data': _timelapse_data}
-
-
-def get_media_preview(camera_config, path, media_type, width, height):
- target_dir = camera_config.get('target_dir')
- full_path = os.path.join(target_dir, path)
-
- if media_type == 'movie':
- if not os.path.exists(full_path + '.thumb'):
- if not make_movie_preview(camera_config, full_path):
- return None
-
- full_path += '.thumb'
-
- try:
- with open(full_path) as f:
- content = f.read()
-
- except Exception as e:
- logging.error('failed to read file %(path)s: %(msg)s' % {
- 'path': full_path, 'msg': unicode(e)})
-
- return None
-
- if width is height is None:
- return content
-
- sio = StringIO.StringIO(content)
- image = Image.open(sio)
- width = width and int(width) or image.size[0]
- height = height and int(height) or image.size[1]
-
- image.thumbnail((width, height), Image.LINEAR)
-
- sio = StringIO.StringIO()
- image.save(sio, format='JPEG')
-
- return sio.getvalue()
-
-
-def del_media_content(camera_config, path, media_type):
- target_dir = camera_config.get('target_dir')
-
- full_path = os.path.join(target_dir, path)
-
- try:
- # remove the file itself
- os.remove(full_path)
-
- # remove the parent directories if empty or contains only thumb files
- dir_path = os.path.dirname(full_path)
- listing = os.listdir(dir_path)
- thumbs = [l for l in listing if l.endswith('.thumb')]
-
- if len(listing) == len(thumbs): # only thumbs
- for p in thumbs:
- os.remove(os.path.join(dir_path, p))
-
- if not listing or len(listing) == len(thumbs):
- logging.debug('removing empty directory %(path)s...' % {'path': dir_path})
- os.removedirs(dir_path)
-
- except Exception as e:
- logging.error('failed to remove file %(path)s: %(msg)s' % {
- 'path': full_path, 'msg': unicode(e)})
-
- raise
-
-
-def del_media_group(camera_config, group, media_type):
- if media_type == 'picture':
- exts = _PICTURE_EXTS
-
- elif media_type == 'movie':
- exts = _MOVIE_EXTS
-
- target_dir = camera_config.get('target_dir')
- full_path = os.path.join(target_dir, group)
-
- mf = _list_media_files(target_dir, exts=exts, prefix=group)
- for (path, st) in mf: # @UnusedVariable
- try:
- os.remove(path)
-
- except Exception as e:
- logging.error('failed to remove file %(path)s: %(msg)s' % {
- 'path': full_path, 'msg': unicode(e)})
-
- raise
-
- # remove the group directory if empty or contains only thumb files
- listing = os.listdir(full_path)
- thumbs = [l for l in listing if l.endswith('.thumb')]
-
- if len(listing) == len(thumbs): # only thumbs
- for p in thumbs:
- os.remove(os.path.join(full_path, p))
-
- if not listing or len(listing) == len(thumbs):
- logging.debug('removing empty directory %(path)s...' % {'path': full_path})
- os.removedirs(full_path)
-
-
-def get_current_picture(camera_config, width, height):
- import mjpgclient
-
- jpg = mjpgclient.get_jpg(camera_config['@id'])
-
- if jpg is None:
- return None
-
- if width is height is None:
- return jpg # no server-side resize needed
-
- sio = StringIO.StringIO(jpg)
- image = Image.open(sio)
-
- width = width and int(width) or image.size[0]
- height = height and int(height) or image.size[1]
-
- webcam_resolution = camera_config['@webcam_resolution']
- max_width = image.size[0] * webcam_resolution / 100
- max_height = image.size[1] * webcam_resolution / 100
-
- width = min(max_width, width)
- 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
-
- image.thumbnail((width, height), Image.CUBIC)
-
- sio = StringIO.StringIO()
- image.save(sio, format='JPEG')
-
- return sio.getvalue()
-
-
-def get_prepared_cache(key):
- return _prepared_files.pop(key, None)
-
-
-def set_prepared_cache(data):
- key = hashlib.sha1(str(time.time())).hexdigest()
-
- if key in _prepared_files:
- logging.warn('key "%s" already present in prepared cache' % key)
-
- _prepared_files[key] = data
-
- def clear():
- 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
- ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=timeout), clear)
-
- return key
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import datetime
-import errno
-import logging
-import re
-import socket
-import time
-
-from tornado import iostream, ioloop
-
-import config
-import motionctl
-import settings
-import utils
-
-
-class MjpgClient(iostream.IOStream):
- clients = {} # dictionary of clients indexed by camera id
- last_jpgs = {} # dictionary of jpg contents indexed by camera id
- last_jpg_moment = {} # dictionary of moments of the last received jpeg indexed by camera id
- last_access = {} # dictionary of access moments indexed by camera id
- last_erroneous_close_time = 0 # helps detecting erroneous connections and restart motion
-
- def __init__(self, camera_id, port, username, password):
- self._camera_id = camera_id
- self._port = port
- self._username = (username or '').encode('utf8')
- self._password = (password or '').encode('utf8')
- self._auth_digest_state = {}
-
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
- iostream.IOStream.__init__(self, s)
-
- self.set_close_callback(self.on_close)
-
- def connect(self):
- iostream.IOStream.connect(self, ('localhost', self._port), self._on_connect)
- MjpgClient.clients[self._camera_id] = self
-
- logging.debug('mjpg client for camera %(camera_id)s connecting on port %(port)s...' % {
- 'port': self._port, 'camera_id': self._camera_id})
-
- def on_close(self):
- logging.debug('connection closed for mjpg client for camera %(camera_id)s on port %(port)s' % {
- 'port': self._port, 'camera_id': self._camera_id})
-
- if MjpgClient.clients.pop(self._camera_id, None):
- MjpgClient.last_access.pop(self._camera_id, None)
- MjpgClient.last_jpg_moment.pop(self._camera_id, None)
-
- logging.debug('mjpg client for camera %(camera_id)s on port %(port)s removed' % {
- 'port': self._port, 'camera_id': self._camera_id})
-
- 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})
-
- motionctl.stop(invalidate=True) # this will close all the mjpg clients
- motionctl.start(deferred=True)
-
- MjpgClient.last_erroneous_close_time = now
-
- def _check_error(self):
- if self.socket is None:
- logging.warning('mjpg client connection for camera %(camera_id)s on port %(port)s is closed' % {
- 'port': self._port, 'camera_id': self._camera_id})
-
- self.close()
-
- 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
- return False
-
- self._error(error)
-
- return True
-
- def _error(self, error):
- logging.error('mjpg client for camera %(camera_id)s on port %(port)s error: %(msg)s' % {
- 'port': self._port, 'camera_id': self._camera_id, 'msg': unicode(error)})
-
- try:
- self.close()
-
- except:
- pass
-
- def _on_connect(self):
- logging.debug('mjpg client for camera %(camera_id)s connected on port %(port)s' % {
- 'port': self._port, 'camera_id': self._camera_id})
-
- if self._username:
- 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:
- self.write('GET / HTTP/1.0\r\n\r\n')
-
- self._seek_http()
-
- def _seek_http(self):
- if self._check_error():
- return
-
- self.read_until_regex('HTTP/1.\d \d+ ', self._on_http)
-
- def _on_http(self, data):
- if data.endswith('401 '):
- self._seek_www_authenticate()
-
- else: # no authorization required, skip to content length
- self._seek_content_length()
-
- def _seek_www_authenticate(self):
- if self._check_error():
- return
-
- self.read_until('WWW-Authenticate:', self._on_before_www_authenticate)
-
- def _on_before_www_authenticate(self, data):
- if self._check_error():
- return
-
- self.read_until('\r\n', self._on_www_authenticate)
-
- def _on_www_authenticate(self, data):
- if self._check_error():
- return
-
- m = re.match('Basic\s*realm="([a-zA-Z0-9\-\s]+)"', data.strip())
- if m:
- logging.debug('mjpgclient: using basic authentication')
-
- 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)
- self._seek_http()
-
- return
-
- m = re.match('Digest\s*realm="([a-zA-Z0-9\-\s]+)",\s*nonce="([a-zA-Z0-9]+)"', data.strip())
- if m:
- logging.debug('mjpgclient: using digest authentication')
-
- realm, nonce = m.groups()
- self._auth_digest_state['realm'] = realm
- self._auth_digest_state['nonce'] = nonce
-
- auth_header = utils.build_digest_header('GET', '/', self._username, self._password, self._auth_digest_state)
- self.write('GET / HTTP/1.0\r\n\r\nAuthorization: %s\r\n\r\n' % auth_header)
- self._seek_http()
-
- return
-
- logging.error('mjpgclient: unknown authentication header: "%s"' % data)
- self._seek_content_length()
-
- def _seek_content_length(self):
- if self._check_error():
- return
-
- self.read_until('Content-Length:', self._on_before_content_length)
-
- def _on_before_content_length(self, data):
- if self._check_error():
- return
-
- self.read_until('\r\n\r\n', self._on_content_length)
-
- def _on_content_length(self, data):
- if self._check_error():
- return
-
- matches = re.findall('(\d+)', data)
- if not matches:
- self._error('could not find content length in mjpg header line "%(header)s"' % {
- 'header': data})
-
- return
-
- length = int(matches[0])
-
- self.read_bytes(length, self._on_jpg)
-
- def _on_jpg(self, data):
- MjpgClient.last_jpgs[self._camera_id] = data
- MjpgClient.last_jpg_moment[self._camera_id] = datetime.datetime.utcnow()
- self._seek_content_length()
-
-
-def _garbage_collector():
- logging.debug('running garbage collector for mjpg clients...')
-
- now = datetime.datetime.utcnow()
- for client in MjpgClient.clients.values():
- camera_id = client._camera_id
- port = client._port
-
- # check for last jpg moment timeout
- last_jpg_moment = MjpgClient.last_jpg_moment.get(camera_id)
- if last_jpg_moment is None:
- MjpgClient.last_jpg_moment[camera_id] = now
-
- continue
-
- if client.closed():
- continue
-
- delta = now - last_jpg_moment
- delta = delta.days * 86400 + delta.seconds
-
- 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})
-
- motionctl.stop(invalidate=True) # this will close all the mjpg clients
- motionctl.start(deferred=True)
-
- break
-
- # check for last access timeout
- last_access = MjpgClient.last_access.get(camera_id)
- if last_access is None:
- continue
-
- delta = now - last_access
- delta = delta.days * 86400 + delta.seconds
-
- 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' % {
- 'camera_id': camera_id, 'port': port, 'timeout': settings.MJPG_CLIENT_IDLE_TIMEOUT})
-
- client.close()
-
- continue
-
- io_loop = ioloop.IOLoop.instance()
- io_loop.add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT), _garbage_collector)
-
-
-def get_jpg(camera_id):
- if camera_id not in MjpgClient.clients:
- # mjpg client not started yet for this camera
-
- logging.debug('creating mjpg client for camera %(camera_id)s' % {
- 'camera_id': camera_id})
-
- camera_config = config.get_camera(camera_id)
- if not camera_config['@enabled'] or not utils.local_motion_camera(camera_config):
- logging.error('could not start mjpg client for camera id %(camera_id)s: not enabled or not local' % {
- 'camera_id': camera_id})
-
- return None
-
- port = camera_config['stream_port']
- username, password = None, None
- if camera_config.get('stream_auth_method') > 0:
- username, password = camera_config.get('stream_authentication', ':').split(':')
-
- client = MjpgClient(camera_id, port, username, password)
- client.connect()
-
- MjpgClient.last_access[camera_id] = datetime.datetime.utcnow()
-
- return MjpgClient.last_jpgs.get(camera_id)
-
-
-def close_all(invalidate=False):
- for client in MjpgClient.clients.values():
- client.close()
-
- if invalidate:
- MjpgClient.clients = {}
- MjpgClient.last_jpgs = {}
- MjpgClient.last_jpg_moment = {}
- MjpgClient.last_access = {}
- MjpgClient.last_erroneous_close_time = 0
-
-
-# schedule the garbage collector
-io_loop = ioloop.IOLoop.instance()
-io_loop.add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT), _garbage_collector)
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import errno
-import logging
-import os.path
-import re
-import signal
-import subprocess
-import time
-
-from tornado.httpclient import HTTPClient, AsyncHTTPClient, HTTPRequest
-from tornado.ioloop import IOLoop
-
-import config
-import mjpgclient
-import powerctl
-import settings
-import utils
-
-
-_started = False
-_motion_binary_cache = None
-_motion_detected = {}
-
-
-def find_motion():
- global _motion_binary_cache
- if _motion_binary_cache:
- return _motion_binary_cache
-
- if settings.MOTION_BINARY:
- if os.path.exists(settings.MOTION_BINARY):
- binary = settings.MOTION_BINARY
-
- else:
- return None
-
- else: # autodetect motion binary path
- try:
- binary = subprocess.check_output('which motion', shell=True).strip()
-
- except subprocess.CalledProcessError: # not found
- return None
-
- try:
- help = subprocess.check_output(binary + ' -h || true', shell=True)
-
- except subprocess.CalledProcessError: # not found
- return None
-
- result = re.findall('^motion Version ([^,]+)', help)
- version = result and result[0] or ''
-
- _motion_binary_cache = (binary, version)
-
- return _motion_binary_cache
-
-
-def _disable_initial_motion_detection():
- for camera_id in config.get_camera_ids():
- camera_config = config.get_camera(camera_id)
- if not utils.local_motion_camera(camera_config):
- continue
-
- if not camera_config['@motion_detection']:
- logging.debug('motion detection disabled by config for camera with id %s' % camera_id)
- set_motion_detection(camera_id, False)
-
-
-def start(deferred=False):
- if deferred:
- return IOLoop.instance().add_callback(start, deferred=False)
-
- global _started
-
- _started = True
-
- enabled_local_motion_cameras = config.get_enabled_local_motion_cameras()
- if running() or not enabled_local_motion_cameras:
- return
-
- logging.debug('starting motion')
-
- program = find_motion()
- if not program:
- raise Exception('motion executable could not be found')
-
- program, version = program # @UnusedVariable
-
- logging.debug('using motion binary "%s"' % program)
-
- motion_config_path = os.path.join(settings.CONF_PATH, 'motion.conf')
- motion_log_path = os.path.join(settings.LOG_PATH, 'motion.log')
- motion_pid_path = os.path.join(settings.RUN_PATH, 'motion.pid')
-
- args = [program,
- '-c', motion_config_path,
- '-n',
- '-d']
-
- if settings.LOG_LEVEL == logging.DEBUG:
- args.append('9')
-
- else:
- args.append('1')
-
- log_file = open(motion_log_path, 'w')
-
- process = subprocess.Popen(args, stdout=log_file, stderr=log_file, close_fds=True, cwd=settings.CONF_PATH)
-
- # wait 2 seconds to see that the process has successfully started
- for i in xrange(20): # @UnusedVariable
- time.sleep(0.1)
- exit_code = process.poll()
- if exit_code is not None and exit_code != 0:
- raise Exception('motion failed to start')
-
- pid = process.pid
-
- # write the pid to file
- with open(motion_pid_path, 'w') as f:
- f.write(str(pid) + '\n')
-
- _disable_initial_motion_detection()
-
- # if mjpg client idle timeout is disabled, create mjpg clients for all cameras by default
- if not settings.MJPG_CLIENT_IDLE_TIMEOUT:
- logging.debug('creating default mjpg clients for local cameras')
- for camera in enabled_local_motion_cameras:
- mjpgclient.get_jpg(camera['@id'])
-
-
-def stop(invalidate=False):
- global _started
-
- _started = False
-
- if not running():
- return
-
- logging.debug('stopping motion')
-
- mjpgclient.close_all(invalidate=invalidate)
-
- pid = _get_pid()
- if pid is not None:
- try:
- # send the TERM signal once
- os.kill(pid, signal.SIGTERM)
-
- # wait 5 seconds for the process to exit
- for i in xrange(50): # @UnusedVariable
- os.waitpid(pid, os.WNOHANG)
- time.sleep(0.1)
-
- # send the KILL signal once
- os.kill(pid, signal.SIGKILL)
-
- # wait 2 seconds for the process to exit
- for i in xrange(20): # @UnusedVariable
- time.sleep(0.1)
- os.waitpid(pid, os.WNOHANG)
-
- # the process still did not exit
- if settings.ENABLE_REBOOT:
- logging.error('could not terminate the motion process')
- powerctl.reboot()
-
- else:
- raise Exception('could not terminate the motion process')
-
- except OSError as e:
- if e.errno not in (errno.ESRCH, errno.ECHILD):
- raise
-
-
-def running():
- pid = _get_pid()
- if pid is None:
- return False
-
- try:
- os.waitpid(pid, os.WNOHANG)
- os.kill(pid, 0)
-
- # the process is running
- return True
-
- except OSError as e:
- if e.errno not in (errno.ESRCH, errno.ECHILD):
- raise
-
- return False
-
-
-def started():
- return _started
-
-
-def get_motion_detection(camera_id):
- thread_id = camera_id_to_thread_id(camera_id)
- if thread_id is None:
- return logging.error('could not find thread id for camera with id %s' % camera_id)
-
- url = 'http://127.0.0.1:7999/%(id)s/detection/status' % {'id': thread_id}
-
- request = HTTPRequest(url, connect_timeout=5, request_timeout=5)
- http_client = HTTPClient()
- try:
- response = http_client.fetch(request)
- if response.error:
- raise response.error
-
- except Exception as e:
- logging.error('failed to get motion detection status for camera with id %(id)s: %(msg)s' % {
- 'id': camera_id,
- 'msg': unicode(e)})
-
- return None
-
- enabled = bool(response.body.lower().count('active'))
-
- logging.debug('motion detection is %(what)s for camera with id %(id)s' % {
- 'what': ['disabled', 'enabled'][enabled],
- 'id': camera_id})
-
- return enabled
-
-
-def set_motion_detection(camera_id, enabled):
- thread_id = camera_id_to_thread_id(camera_id)
- if thread_id is None:
- return logging.error('could not find thread id for camera with id %s' % camera_id)
-
- if not enabled:
- _motion_detected[camera_id] = False
-
- logging.debug('%(what)s motion detection for camera with id %(id)s' % {
- 'what': ['disabling', 'enabling'][enabled],
- 'id': camera_id})
-
- url = 'http://127.0.0.1:7999/%(id)s/detection/%(enabled)s' % {
- 'id': thread_id,
- 'enabled': ['pause', 'start'][enabled]}
-
- def on_response(response):
- if response.error:
- logging.error('failed to %(what)s motion detection for camera with id %(id)s: %(msg)s' % {
- 'what': ['disable', 'enable'][enabled],
- 'id': camera_id,
- 'msg': utils.pretty_http_error(response)})
-
- else:
- logging.debug('successfully %(what)s motion detection for camera with id %(id)s' % {
- 'what': ['disabled', 'enabled'][enabled],
- 'id': camera_id})
-
- request = HTTPRequest(url, connect_timeout=4, request_timeout=4)
- http_client = AsyncHTTPClient()
- http_client.fetch(request, on_response)
-
-
-def is_motion_detected(camera_id):
- return _motion_detected.get(camera_id, False)
-
-
-def set_motion_detected(camera_id, motion_detected):
- if motion_detected:
- logging.debug('marking motion detected for camera with id %s' % camera_id)
-
- else:
- logging.debug('clearing motion detected for camera with id %s' % camera_id)
-
- _motion_detected[camera_id] = motion_detected
-
-
-def camera_id_to_thread_id(camera_id):
- # find the corresponding thread_id
- # (which can be different from camera_id)
- camera_ids = config.get_camera_ids()
- thread_id = 0
- for cid in camera_ids:
- camera_config = config.get_camera(cid)
- if utils.local_motion_camera(camera_config):
- thread_id += 1
-
- if cid == camera_id:
- return thread_id or None
-
- return None
-
-
-def thread_id_to_camera_id(thread_id):
- # find the corresponding camera_id
- # (which can be different from thread_id)
- camera_ids = config.get_camera_ids()
- tid = 0
- for cid in camera_ids:
- camera_config = config.get_camera(cid)
- if utils.local_motion_camera(camera_config):
- tid += 1
- if tid == thread_id:
- return cid
-
- return None
-
-
-def _get_pid():
- motion_pid_path = os.path.join(settings.RUN_PATH, 'motion.pid')
-
- # read the pid from file
- try:
- with open(motion_pid_path, 'r') as f:
- return int(f.readline().strip())
-
- except (IOError, ValueError):
- return None
+++ /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)
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import logging
-import os
-import subprocess
-
-
-def _find_prog(prog):
- try:
- return subprocess.check_output('which %s' % prog, shell=True).strip()
-
- except subprocess.CalledProcessError: # not found
- return None
-
-
-def _exec_prog(prog):
- logging.info('executing "%s"' % prog)
-
- return os.system(prog) == 0
-
-
-def shut_down():
- logging.info('shutting down')
-
- prog = _find_prog('poweroff')
- if prog:
- return _exec_prog(prog)
-
- prog = _find_prog('shutdown')
- if prog:
- return _exec_prog(prog + ' -h now')
-
- prog = _find_prog('systemctl')
- if prog:
- return _exec_prog(prog + ' poweroff')
-
- prog = _find_prog('init')
- if prog:
- return _exec_prog(prog + ' 0')
-
- return False
-
-
-def reboot():
- logging.info('rebooting')
-
- prog = _find_prog('reboot')
- if prog:
- return _exec_prog(prog)
-
- prog = _find_prog('shutdown')
- if prog:
- return _exec_prog(prog + ' -r now')
-
- prog = _find_prog('systemctl')
- if prog:
- return _exec_prog(prog + ' reboot')
-
- prog = _find_prog('init')
- if prog:
- return _exec_prog(prog + ' 6')
-
- return False
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import functools
-import json
-import logging
-import re
-
-from tornado.httpclient import AsyncHTTPClient, HTTPRequest
-
-import settings
-import utils
-
-_DOUBLE_SLASH_REGEX = re.compile('//+')
-
-
-def _make_request(scheme, host, port, username, password, uri, method='GET', data=None, query=None, timeout=None):
- uri = _DOUBLE_SLASH_REGEX.sub('/', uri)
- url = '%(scheme)s://%(host)s%(port)s%(uri)s' % {
- 'scheme': scheme,
- 'host': host,
- 'port': ':' + str(port) if port else '',
- 'uri': uri or ''}
-
- query = dict(query or {})
- query['_username'] = username or ''
- query['_admin'] = 'true' # always use the admin account
-
- if url.count('?'):
- url += '&'
-
- else:
- url += '?'
-
- url += '&'.join([(n + '=' + v) for (n, v) in query.iteritems()])
- url += '&_signature=' + utils.compute_signature(method, url, data, password)
-
- if timeout is None:
- timeout = settings.REMOTE_REQUEST_TIMEOUT
-
- return HTTPRequest(url, method, body=data, connect_timeout=timeout, request_timeout=timeout)
-
-
-def _callback_wrapper(callback):
- @functools.wraps(callback)
- def wrapper(response):
- try:
- decoded = json.loads(response.body)
- if decoded['error'] == 'unauthorized':
- response.error = 'Authentication Error'
-
- elif decoded['error']:
- response.error = decoded['error']
-
- except:
- pass
-
- return callback(response)
-
- return wrapper
-
-
-def pretty_camera_url(local_config, camera=True):
- scheme = local_config.get('@scheme', local_config.get('scheme')) or 'http'
- host = local_config.get('@host', local_config.get('host'))
- port = local_config.get('@port', local_config.get('port'))
- uri = local_config.get('@uri', local_config.get('uri')) or ''
-
- url = scheme + '://' + host
- if port and str(port) not in ['80', '443']:
- url += ':' + str(port)
-
- if uri:
- url += uri
-
- if url.endswith('/'):
- url = url[:-1]
-
- if camera:
- if camera is True:
- url += '/config/' + str(local_config.get('@remote_camera_id', local_config.get('remote_camera_id')))
-
- else:
- url += '/config/' + str(camera)
-
- return url
-
-
-def _remote_params(local_config):
- return (
- local_config.get('@scheme', local_config.get('scheme')) or 'http',
- local_config.get('@host', local_config.get('host')),
- local_config.get('@port', local_config.get('port')),
- local_config.get('@username', local_config.get('username')),
- local_config.get('@password', local_config.get('password')),
- local_config.get('@uri', local_config.get('uri')) or '',
- local_config.get('@remote_camera_id', local_config.get('remote_camera_id')))
-
-
-def list(local_config, callback):
- scheme, host, port, username, password, uri, _ = _remote_params(local_config)
-
- logging.debug('listing remote cameras on %(url)s' % {
- 'url': pretty_camera_url(local_config, camera=False)})
-
- request = _make_request(scheme, host, port, username, password, uri + '/config/list/')
-
- def on_response(response):
- if response.error:
- logging.error('failed to list remote cameras on %(url)s: %(msg)s' % {
- 'url': pretty_camera_url(local_config, camera=False),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- try:
- response = json.loads(response.body)
-
- except Exception as e:
- logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
- 'url': pretty_camera_url(local_config, camera=False),
- 'msg': unicode(e)})
-
- return callback(error=unicode(e))
-
- cameras = response['cameras']
-
- # filter out simple mjpeg cameras
- cameras = [c for c in cameras if c['proto'] != 'mjpeg']
-
- callback(cameras)
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def get_config(local_config, callback):
- scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
-
- logging.debug('getting config 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, uri + '/config/%(id)s/get/' % {'id': camera_id})
-
- def on_response(response):
- if response.error:
- logging.error('failed to get config for remote camera %(id)s on %(url)s: %(msg)s' % {
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- try:
- response = json.loads(response.body)
-
- except Exception as e:
- logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
- 'url': pretty_camera_url(local_config),
- 'msg': unicode(e)})
-
- return callback(error=unicode(e))
-
- response['host'] = host
- response['port'] = port
-
- callback(response)
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def set_config(local_config, ui_config, callback):
- scheme = local_config.get('@scheme', local_config.get('scheme'))
- host = local_config.get('@host', local_config.get('host'))
- port = local_config.get('@port', local_config.get('port'))
- username = local_config.get('@username', local_config.get('username'))
- password = local_config.get('@password', local_config.get('password'))
- uri = local_config.get('@uri', local_config.get('uri')) or ''
- camera_id = local_config.get('@remote_camera_id', local_config.get('remote_camera_id'))
-
- logging.debug('setting config for remote camera %(id)s on %(url)s' % {
- 'id': camera_id,
- 'url': pretty_camera_url(local_config)})
-
- ui_config = json.dumps(ui_config)
-
- request = _make_request(scheme, host, port, username, password, uri + '/config/%(id)s/set/' % {'id': camera_id}, method='POST', data=ui_config)
-
- def on_response(response):
- if response.error:
- logging.error('failed to set config for remote camera %(id)s on %(url)s: %(msg)s' % {
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- callback()
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def set_preview(local_config, controls, callback):
- scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
-
- logging.debug('setting preview for remote camera %(id)s on %(url)s' % {
- 'id': camera_id,
- 'url': pretty_camera_url(local_config)})
-
- data = json.dumps(controls)
-
- request = _make_request(scheme, host, port, username, password, uri + '/config/%(id)s/set_preview/' % {'id': camera_id}, method='POST', data=data)
-
- def on_response(response):
- if response.error:
- logging.error('failed to set preview for remote camera %(id)s on %(url)s: %(msg)s' % {
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- callback()
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def get_current_picture(local_config, width, height, callback):
- scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
-
- logging.debug('getting current picture for remote camera %(id)s on %(url)s' % {
- 'id': camera_id,
- 'url': pretty_camera_url(local_config)})
-
- query = {}
-
- if width:
- query['width'] = str(width)
-
- if height:
- query['height'] = str(height)
-
- request = _make_request(scheme, host, port, username, password, uri + '/picture/%(id)s/current/' % {'id': camera_id}, query=query)
-
- def on_response(response):
- motion_detected = False
-
- cookies = response.headers.get('Set-Cookie')
- if cookies:
- cookies = cookies.split(';')
- cookies = [[i.strip() for i in c.split('=')] for c in cookies]
- cookies = dict([c for c in cookies if len(c) == 2])
- motion_detected = cookies.get('motion_detected_' + str(camera_id)) == 'true'
-
- if response.error:
- logging.error('failed to get current picture for remote camera %(id)s on %(url)s: %(msg)s' % {
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- callback(motion_detected, response.body)
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def list_media(local_config, media_type, prefix, callback):
- scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
-
- logging.debug('getting media list for remote camera %(id)s on %(url)s' % {
- 'id': camera_id,
- 'url': pretty_camera_url(local_config)})
-
- query = {}
- if prefix is not None:
- 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, uri + '/%(media_type)s/%(id)s/list/' % {
- 'id': camera_id, 'media_type': media_type}, query=query, timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
-
- def on_response(response):
- if response.error:
- logging.error('failed to get media list for remote camera %(id)s on %(url)s: %(msg)s' % {
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- try:
- response = json.loads(response.body)
-
- except Exception as e:
- logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
- 'url': pretty_camera_url(local_config),
- 'msg': unicode(e)})
-
- return callback(error=unicode(e))
-
- return callback(response)
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def get_media_content(local_config, filename, media_type, callback):
- scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
-
- logging.debug('downloading file %(filename)s of remote camera %(id)s on %(url)s' % {
- 'filename': filename,
- 'id': camera_id,
- 'url': pretty_camera_url(local_config)})
-
- uri += '/%(media_type)s/%(id)s/download/%(filename)s' % {
- 'media_type': media_type,
- 'id': camera_id,
- 'filename': filename}
-
- # 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, uri, timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
-
- def on_response(response):
- if response.error:
- logging.error('failed to download file %(filename)s of remote camera %(id)s on %(url)s: %(msg)s' % {
- 'filename': filename,
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- return callback(response.body)
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def make_zipped_content(local_config, media_type, group, callback):
- scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
-
- logging.debug('preparing zip file for group %(group)s of remote camera %(id)s on %(url)s' % {
- 'group': group,
- 'id': camera_id,
- 'url': pretty_camera_url(local_config)})
-
- prepare_uri = uri + '/%(media_type)s/%(id)s/zipped/%(group)s/' % {
- 'media_type': media_type,
- 'id': camera_id,
- 'group': group}
-
- # timeout here is 100 times larger than usual - we expect a big delay
- request = _make_request(scheme, host, port, username, password, prepare_uri, 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' % {
- 'group': group,
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- try:
- key = json.loads(response.body)['key']
-
- except Exception as e:
- logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
- 'url': pretty_camera_url(local_config),
- 'msg': unicode(e)})
-
- return callback(error=unicode(e))
-
- callback({'key': key})
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def get_zipped_content(local_config, media_type, key, group, callback):
- scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
-
- 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, uri + '/%(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)
-
- def on_response(response):
- if response.error:
- logging.error('failed to download zip file for remote camera %(id)s on %(url)s: %(msg)s' % {
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- callback({
- 'data': response.body,
- 'content_type': response.headers.get('Content-Type'),
- 'content_disposition': response.headers.get('Content-Disposition')
- })
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def make_timelapse_movie(local_config, framerate, interval, group, callback):
- scheme, host, port, username, password, uri, 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' % {
- 'group': group,
- 'id': camera_id,
- 'framerate': framerate,
- 'int': interval,
- 'url': pretty_camera_url(local_config)})
-
- uri += '/picture/%(id)s/timelapse/%(group)s/?interval=%(int)s&framerate=%(framerate)s' % {
- 'id': camera_id,
- 'int': interval,
- 'framerate': framerate,
- 'group': group}
-
- request = _make_request(scheme, host, port, username, password, uri, 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' % {
- 'group': group,
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'int': interval,
- 'framerate': framerate,
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- try:
- response = json.loads(response.body)
-
- except Exception as e:
- logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
- 'url': pretty_camera_url(local_config),
- 'msg': unicode(e)})
-
- return callback(error=unicode(e))
-
- callback(response)
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def check_timelapse_movie(local_config, group, callback):
- scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
-
- 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, uri + '/picture/%(id)s/timelapse/%(group)s/?check=true' % {
- 'id': camera_id,
- 'group': group})
-
- def on_response(response):
- if response.error:
- logging.error('failed to check timelapse movie status for remote camera %(id)s on %(url)s: %(msg)s' % {
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- try:
- response = json.loads(response.body)
-
- except Exception as e:
- logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
- 'url': pretty_camera_url(local_config),
- 'msg': unicode(e)})
-
- return callback(error=unicode(e))
-
- callback(response)
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def get_timelapse_movie(local_config, key, group, callback):
- scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
-
- 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, uri + '/picture/%(id)s/timelapse/%(group)s/?key=%(key)s' % {
- 'id': camera_id,
- 'group': group,
- 'key': key},
- timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
-
- def on_response(response):
- if response.error:
- logging.error('failed to download timelapse movie for remote camera %(id)s on %(url)s: %(msg)s' % {
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- callback({
- 'data': response.body,
- 'content_type': response.headers.get('Content-Type'),
- 'content_disposition': response.headers.get('Content-Disposition')
- })
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def get_media_preview(local_config, filename, media_type, width, height, callback):
- scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
-
- logging.debug('getting file preview for %(filename)s of remote camera %(id)s on %(url)s' % {
- 'filename': filename,
- 'id': camera_id,
- 'url': pretty_camera_url(local_config)})
-
- uri += '/%(media_type)s/%(id)s/preview/%(filename)s' % {
- 'media_type': media_type,
- 'id': camera_id,
- 'filename': filename}
-
- query = {}
-
- if width:
- query['width'] = str(width)
-
- if height:
- query['height'] = str(height)
-
- request = _make_request(scheme, host, port, username, password, uri, query=query)
-
- def on_response(response):
- if response.error:
- logging.error('failed to get file preview for %(filename)s of remote camera %(id)s on %(url)s: %(msg)s' % {
- 'filename': filename,
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- callback(response.body)
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def del_media_content(local_config, filename, media_type, callback):
- scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
-
- logging.debug('deleting file %(filename)s of remote camera %(id)s on %(url)s' % {
- 'filename': filename,
- 'id': camera_id,
- 'url': pretty_camera_url(local_config)})
-
- uri += '/%(media_type)s/%(id)s/delete/%(filename)s' % {
- 'media_type': media_type,
- 'id': camera_id,
- 'filename': filename}
-
- request = _make_request(scheme, host, port, username, password, uri, method='POST', data='{}', timeout=settings.REMOTE_REQUEST_TIMEOUT)
-
- def on_response(response):
- if response.error:
- logging.error('failed to delete file %(filename)s of remote camera %(id)s on %(url)s: %(msg)s' % {
- 'filename': filename,
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- callback()
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
-
-
-def del_media_group(local_config, group, media_type, callback):
- scheme, host, port, username, password, uri, camera_id = _remote_params(local_config)
-
- logging.debug('deleting group %(group)s of remote camera %(id)s on %(url)s' % {
- 'group': group,
- 'id': camera_id,
- 'url': pretty_camera_url(local_config)})
-
- uri += '/%(media_type)s/%(id)s/delete_all/%(group)s/' % {
- 'media_type': media_type,
- 'id': camera_id,
- 'group': group}
-
- request = _make_request(scheme, host, port, username, password, uri, method='POST', data='{}', timeout=settings.REMOTE_REQUEST_TIMEOUT)
-
- def on_response(response):
- if response.error:
- logging.error('failed to delete group %(group)s of remote camera %(id)s on %(url)s: %(msg)s' % {
- 'group': group,
- 'id': camera_id,
- 'url': pretty_camera_url(local_config),
- 'msg': utils.pretty_http_error(response)})
-
- return callback(error=utils.pretty_http_error(response))
-
- callback()
-
- http_client = AsyncHTTPClient()
- http_client.fetch(request, _callback_wrapper(on_response))
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-from tornado.web import Application
-
-import handlers
-import logging
-import settings
-import template
-
-
-def log_request(handler):
- if handler.get_status() < 400:
- log_method = logging.debug
-
- elif handler.get_status() < 500:
- log_method = logging.warning
-
- else:
- log_method = logging.error
-
- request_time = 1000.0 * handler.request.request_time()
- log_method("%d %s %.2fms", handler.get_status(),
- handler._request_summary(), request_time)
-
-
-application = Application(
- [
- (r'^/$', handlers.MainHandler),
- (r'^/config/main/(?P<op>set|get)/?$', handlers.ConfigHandler),
- (r'^/config/(?P<camera_id>\d+)/(?P<op>get|set|rem|set_preview)/?$', handlers.ConfigHandler),
- (r'^/config/(?P<op>add|list|backup|restore)/?$', handlers.ConfigHandler),
- (r'^/picture/(?P<camera_id>\d+)/(?P<op>current|list|frame)/?$', handlers.PictureHandler),
- (r'^/picture/(?P<camera_id>\d+)/(?P<op>download|preview|delete)/(?P<filename>.+?)/?$', handlers.PictureHandler),
- (r'^/picture/(?P<camera_id>\d+)/(?P<op>zipped|timelapse|delete_all)/(?P<group>.+?)/?$', handlers.PictureHandler),
- (r'^/movie/(?P<camera_id>\d+)/(?P<op>list)/?$', handlers.MovieHandler),
- (r'^/movie/(?P<camera_id>\d+)/(?P<op>download|preview|delete)/(?P<filename>.+?)/?$', handlers.MovieHandler),
- (r'^/movie/(?P<camera_id>\d+)/(?P<op>delete_all)/(?P<group>.+?)/?$', handlers.MovieHandler),
- (r'^/_relay_event/?$', handlers.RelayEventHandler),
- (r'^/log/(?P<name>\w+)/?$', handlers.LogHandler),
- (r'^/update/?$', handlers.UpdateHandler),
- (r'^/power/(?P<op>shutdown|reboot)/?$', handlers.PowerHandler),
- (r'^/version/?$', handlers.VersionHandler),
- (r'^/login/?$', handlers.LoginHandler),
- (r'^.*$', handlers.NotFoundHandler),
- ],
- debug=False,
- log_function=log_request,
- static_path=settings.STATIC_PATH,
- static_url_prefix=settings.STATIC_URL
-)
-
-template.add_context('STATIC_URL', settings.STATIC_URL)
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import datetime
-import logging
-import os
-import re
-import subprocess
-import time
-
-from tornado import ioloop
-
-import config
-import settings
-
-
-def find_mount_cifs():
- try:
- return subprocess.check_output('which mount.cifs', shell=True).strip()
-
- except subprocess.CalledProcessError: # not found
- return None
-
-
-def make_mount_point(server, share, username):
- server = re.sub('[^a-zA-Z0-9]', '_', server).lower()
- share = re.sub('[^a-zA-Z0-9]', '_', share).lower()
-
- if username:
- username = re.sub('[^a-zA-Z0-9]', '_', username).lower()
- mount_point = os.path.join(settings.SMB_MOUNT_ROOT, 'motioneye_%s_%s_%s' % (server, share, username))
-
- else:
- mount_point = os.path.join(settings.SMB_MOUNT_ROOT, 'motioneye_%s_%s' % (server, share))
-
- return mount_point
-
-
-def _is_motioneye_mount(mount_point):
- mount_point_root = os.path.join(settings.SMB_MOUNT_ROOT, 'motioneye_')
- return bool(re.match('^' + mount_point_root + '\w+$', mount_point))
-
-
-def list_mounts():
- logging.debug('listing smb mounts...')
-
- mounts = []
- with open('/proc/mounts', 'r') as f:
- for line in f:
- line = line.strip()
- if not line:
- continue
- parts = line.split()
- if len(parts) < 4:
- continue
-
- target = parts[0]
- mount_point = parts[1]
- fstype = parts[2]
- opts = ' '.join(parts[3:])
-
- if fstype != 'cifs':
- continue
-
- if not _is_motioneye_mount(mount_point):
- continue
-
- match = re.match('//([^/]+)/(.+)', target)
- if not match:
- continue
-
- if len(match.groups()) != 2:
- continue
-
- server, share = match.groups()
- share = share.replace('\\040', ' ') # spaces are reported oddly by /proc/mounts
-
- match = re.search('username=([\w\s]+)', opts)
- if match:
- username = match.group(1)
-
- else:
- username = None
-
- logging.debug('found smb mount "//%s/%s" at "%s"' % (server, share, mount_point))
-
- mounts.append({
- 'server': server,
- 'share': share,
- 'username': username,
- 'mount_point': mount_point
- })
-
- return mounts
-
-
-def mount(server, share, username, password):
- mount_point = make_mount_point(server, share, username)
-
- logging.debug('making sure mount point "%s" exists' % mount_point)
-
- if not os.path.exists(mount_point):
- os.makedirs(mount_point)
-
- if username:
- opts = 'username=%s,password=%s' % (username, password)
- sec_types = ['ntlm', 'ntlmv2', 'none']
-
- else:
- opts = 'guest'
- sec_types = ['none', 'ntlm', 'ntlmv2']
-
- for sec in sec_types:
- actual_opts = opts + ',sec=' + sec
- try:
- logging.debug('mounting "//%s/%s" at "%s" (sec=%s)' % (server, share, mount_point, sec))
- subprocess.check_call('mount.cifs "//%s/%s" "%s" -o "%s"' % (server, share, mount_point, actual_opts), shell=True)
- break
-
- except subprocess.CalledProcessError:
- pass
-
- else:
- logging.error('failed to mount smb share "//%s/%s" at "%s"' % (server, share, mount_point))
- return None
-
- # test to see if mount point is writable
- try:
- path = os.path.join(mount_point, '.motioneye_' + str(int(time.time())))
- os.mkdir(path)
- os.rmdir(path)
- logging.debug('directory at "%s" is writable' % mount_point)
-
- except:
- logging.error('directory at "%s" is not writable' % mount_point)
-
- return None
-
- return mount_point
-
-
-def umount(server, share, username):
- mount_point = make_mount_point(server, share, username)
- logging.debug('unmounting "//%s/%s" from "%s"' % (server, share, mount_point))
-
- try:
- subprocess.check_call('umount "%s"' % mount_point, shell=True)
-
- except subprocess.CalledProcessError:
- logging.error('failed to unmount smb share "//%s/%s" from "%s"' % (server, share, mount_point))
-
- return False
-
- try:
- os.rmdir(mount_point)
-
- except Exception as e:
- logging.error('failed to remove smb mount point "%s": %s' % (mount_point, e))
-
- return False
-
- return True
-
-
-def update_mounts():
- network_shares = config.get_network_shares()
-
- 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
- for network_share in network_shares:
- key = (network_share['server'], network_share['share'], network_share['username'] or '')
- if key in mounts: # found
- mounts[key] = True
-
- else: # needs to be mounted
- should_stop = True
- if not mount(network_share['server'], network_share['share'], network_share['username'], network_share['password']):
- should_start = False
-
- # unmount the no longer necessary mounts
- for (server, share, username), required in mounts.items():
- if not required:
- umount(server, share, username)
- should_stop = True
-
- return (should_stop, should_start)
-
-
-def umount_all():
- for mount in list_mounts():
- umount(mount['server'], mount['share'], mount['username'])
-
-
-def _check_mounts():
- import motionctl
-
- logging.debug('checking SMB mounts...')
-
- stop, start = update_mounts()
- if stop:
- motionctl.stop()
-
- if start:
- motionctl.start()
-
- io_loop = ioloop.IOLoop.instance()
- io_loop.add_timeout(datetime.timedelta(seconds=settings.MOUNT_CHECK_INTERVAL), _check_mounts)
-
-
-if settings.SMB_SHARES:
- # schedule the mount checker
- io_loop = ioloop.IOLoop.instance()
- io_loop.add_timeout(datetime.timedelta(seconds=settings.MOUNT_CHECK_INTERVAL), _check_mounts)
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-from jinja2 import Environment, FileSystemLoader
-
-import settings
-import utils
-
-
-_jinja_env = None
-
-
-def _init_jinja():
- global _jinja_env
-
- _jinja_env = Environment(
- loader=FileSystemLoader(settings.TEMPLATE_PATH),
- trim_blocks=False)
-
- # globals
- _jinja_env.globals['settings'] = settings
-
- # filters
- _jinja_env.filters['pretty_date_time'] = utils.pretty_date_time
- _jinja_env.filters['pretty_date'] = utils.pretty_date
- _jinja_env.filters['pretty_time'] = utils.pretty_time
- _jinja_env.filters['pretty_duration'] = utils.pretty_duration
-
-
-def add_template_path(path):
- global _jinja_env
- if _jinja_env is None:
- _init_jinja()
-
- _jinja_env.loader.searchpath.append(path)
-
-
-def add_context(name, value):
- global _jinja_env
- if _jinja_env is None:
- _init_jinja()
-
- _jinja_env.globals[name] = value
-
-
-def render(template_name, **context):
- global _jinja_env
- if _jinja_env is None:
- _init_jinja()
-
- template = _jinja_env.get_template(template_name)
- return template.render(**context)
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import datetime
-import logging
-import multiprocessing
-import os
-import signal
-import tornado
-
-import cleanup
-import mediafiles
-import settings
-
-
-_process = None
-
-
-def start():
- # schedule the first call a bit later to improve performance at startup
- ioloop = tornado.ioloop.IOLoop.instance()
- ioloop.add_timeout(datetime.timedelta(seconds=30), _run_process)
-
-
-def stop():
- global _process
-
- if not running():
- _process = None
- return
-
- if _process.is_alive():
- _process.join(timeout=10)
-
- if _process.is_alive():
- logging.error('thumbnailer process did not finish in time, killing it...')
- os.kill(_process.pid, signal.SIGKILL)
-
- _process = None
-
-
-def running():
- return _process is not None and _process.is_alive()
-
-
-def _run_process():
- global _process
-
- # schedule the next call
- ioloop = tornado.ioloop.IOLoop.instance()
- ioloop.add_timeout(datetime.timedelta(seconds=settings.THUMBNAILER_INTERVAL), _run_process)
-
- if not running() and not cleanup.running(): # check that the previous process has finished and that cleanup is not running
- logging.debug('running thumbnailer process...')
-
- _process = multiprocessing.Process(target=_do_next_movie_thumbail)
- _process.start()
-
-
-def _do_next_movie_thumbail():
- # this will be executed in a separate subprocess
-
- # ignore the terminate and interrupt signals in this subprocess
- signal.signal(signal.SIGINT, signal.SIG_IGN)
- signal.signal(signal.SIGTERM, signal.SIG_IGN)
-
- try:
- mediafiles.make_next_movie_preview()
-
- except Exception as e:
- logging.error('failed to make movie thumbnail: %(msg)s' % {
- 'msg': unicode(e)}, exc_info=True)
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import hashlib
-import logging
-import os
-import settings
-import subprocess
-
-from config import additional_config
-
-
-LOCAL_TIME_FILE = settings.LOCAL_TIME_FILE # @UndefinedVariable
-
-
-def _get_time_zone_symlink():
- file = settings.LOCAL_TIME_FILE
- if not file:
- return None
-
- for i in xrange(8): # recursively follow the symlinks @UnusedVariable
- try:
- file = os.readlink(file)
-
- except OSError:
- break
-
- if file and file.startswith('/usr/share/zoneinfo/'):
- file = file[20:]
-
- else:
- file = None
-
- time_zone = file or None
- if time_zone:
- logging.debug('found time zone by symlink method: %s' % time_zone)
-
- return time_zone
-
-
-def _get_time_zone_md5():
- if settings.LOCAL_TIME_FILE:
- return None
-
- try:
- output = subprocess.check_output('cd /usr/share/zoneinfo; find * -type f | xargs md5sum', shell=True)
-
- except Exception as e:
- logging.error('getting md5 of zoneinfo files failed: %s' % e)
-
- return None
-
- lines = [l for l in output.split('\n') if l]
- lines = [l.split(None, 1) for l in lines]
- time_zone_by_md5 = dict(lines)
-
- try:
- with open(settings.LOCAL_TIME_FILE, 'r') as f:
- data = f.read()
-
- except Exception as e:
- logging.error('failed to read local time file: %s' % e)
-
- return None
-
- md5 = hashlib.md5(data).hexdigest()
- time_zone = time_zone_by_md5.get(md5)
-
- if time_zone:
- logging.debug('found time zone by md5 method: %s' % time_zone)
-
- return time_zone
-
-
-def _get_time_zone():
- return _get_time_zone_symlink() or _get_time_zone_md5() or 'UTC'
-
-
-def _set_time_zone(time_zone):
- time_zone = time_zone or 'UTC'
-
- zoneinfo_file = '/usr/share/zoneinfo/' + time_zone
- if not os.path.exists(zoneinfo_file):
- logging.error('%s file does not exist' % zoneinfo_file)
-
- return False
-
- logging.debug('linking "%s" to "%s"' % (settings.LOCAL_TIME_FILE, zoneinfo_file))
-
- try:
- os.remove(settings.LOCAL_TIME_FILE)
-
- except:
- pass # nevermind
-
- try:
- os.symlink(zoneinfo_file, settings.LOCAL_TIME_FILE)
-
- return True
-
- except Exception as e:
- logging.error('failed to link "%s" to "%s": %s' % (settings.LOCAL_TIME_FILE, zoneinfo_file, e))
-
- return False
-
-
-@additional_config
-def timeZone():
- if not LOCAL_TIME_FILE:
- return
-
- import pytz
- timezones = pytz.common_timezones
-
- return {
- 'label': 'Time Zone',
- 'description': 'selecting the right timezone assures a correct timestamp displayed on pictures and movies',
- 'type': 'choices',
- 'choices': [(t, t) for t in timezones],
- 'section': 'general',
- 'advanced': True,
- 'reboot': True,
- 'get': _get_time_zone,
- 'set': _set_time_zone
- }
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import logging
-import re
-
-
-# versions
-
-def get_version():
- import motioneye
-
- return motioneye.VERSION
-
-
-def get_all_versions():
- return []
-
-
-def compare_versions(version1, version2):
- version1 = re.sub('[^0-9.]', '', version1)
- version2 = re.sub('[^0-9.]', '', version2)
-
- version1 = [int(n) for n in version1.split('.')]
- version2 = [int(n) for n in version2.split('.')]
-
- len1 = len(version1)
- len2 = len(version2)
- length = min(len1, len2)
- for i in xrange(length):
- p1 = version1[i]
- p2 = version2[i]
-
- if p1 < p2:
- return -1
-
- elif p1 > p2:
- return 1
-
- if len1 < len2:
- return -1
-
- elif len1 > len2:
- return 1
-
- else:
- return 0
-
-
-def perform_update(version):
- logging.error('updating is not implemented')
-
- return False
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import base64
-import datetime
-import functools
-import hashlib
-import logging
-import os
-import re
-import socket
-import time
-import urllib
-import urlparse
-
-from tornado.httpclient import AsyncHTTPClient, HTTPRequest
-from tornado.iostream import IOStream
-from tornado.ioloop import IOLoop
-
-import settings
-
-
-try:
- from collections import OrderedDict # @UnusedImport
-
-except:
- from ordereddict import OrderedDict # @UnusedImport @Reimport
-
-
-_SIGNATURE_REGEX = re.compile('[^a-zA-Z0-9/?_.=&{}\[\]":, _-]')
-
-
-COMMON_RESOLUTIONS = [
- (320, 240),
- (640, 480),
- (800, 480),
- (1024, 576),
- (1024, 768),
- (1280, 720),
- (1280, 800),
- (1280, 960),
- (1280, 1024),
- (1440, 960),
- (1440, 1024),
- (1600, 1200)
-]
-
-
-def pretty_date_time(date_time, tzinfo=None, short=False):
- if date_time is None:
- return '('+ _('never') + ')'
-
- if isinstance(date_time, int):
- return pretty_date_time(datetime.datetime.fromtimestamp(date_time))
-
- if short:
- text = u'{day} {month}, {hm}'.format(
- day=date_time.day,
- month=date_time.strftime('%b'),
- hm=date_time.strftime('%H:%M')
- )
-
- else:
- text = u'{day} {month} {year}, {hm}'.format(
- day=date_time.day,
- month=date_time.strftime('%B'),
- year=date_time.year,
- hm=date_time.strftime('%H:%M')
- )
-
- if tzinfo:
- offset = tzinfo.utcoffset(datetime.datetime.utcnow()).seconds
- tz = 'GMT'
- if offset >= 0:
- tz += '+'
-
- else:
- tz += '-'
- offset = -offset
-
- tz += '%.2d' % (offset / 3600) + ':%.2d' % ((offset % 3600) / 60)
-
- text += ' (' + tz + ')'
-
- return text
-
-
-def pretty_date(date):
- if date is None:
- return '('+ _('never') + ')'
-
- if isinstance(date, int):
- return pretty_date(datetime.datetime.fromtimestamp(date))
-
- return u'{day} {month} {year}'.format(
- day=date.day,
- month=_(date.strftime('%B')),
- year=date.year
- )
-
-
-def pretty_time(time):
- if time is None:
- return ''
-
- if isinstance(time, datetime.timedelta):
- hour = time.seconds / 3600
- minute = (time.seconds % 3600) / 60
- time = datetime.time(hour, minute)
-
- return '{hm}'.format(
- hm=time.strftime('%H:%M')
- )
-
-
-def pretty_duration(duration):
- if duration is None:
- duration = 0
-
- if isinstance(duration, datetime.timedelta):
- duration = duration.seconds + duration.days * 86400
-
- if duration < 0:
- negative = True
- duration = -duration
-
- else:
- negative = False
-
- days = int(duration / 86400)
- duration %= 86400
- hours = int(duration / 3600)
- duration %= 3600
- minutes = int(duration / 60)
- duration %= 60
- seconds = duration
-
- # treat special cases
- special_result = None
- if days != 0 and hours == 0 and minutes == 0 and seconds == 0:
- if days == 1:
- special_result = str(days) + ' ' + _('day')
-
- elif days == 7:
- special_result = '1 ' + _('week')
-
- elif days in [30, 31, 32]:
- special_result = '1 ' + _('month')
-
- elif days in [365, 366]:
- special_result = '1 ' + _('year')
-
- else:
- special_result = str(days) + ' ' + _('days')
-
- elif days == 0 and hours != 0 and minutes == 0 and seconds == 0:
- if hours == 1:
- special_result = str(hours) + ' ' + _('hour')
-
- else:
- special_result = str(hours) + ' ' + _('hours')
-
- elif days == 0 and hours == 0 and minutes != 0 and seconds == 0:
- if minutes == 1:
- special_result = str(minutes) + ' ' + _('minute')
-
- else:
- special_result = str(minutes) + ' ' + _('minutes')
-
- elif days == 0 and hours == 0 and minutes == 0 and seconds != 0:
- if seconds == 1:
- special_result = str(seconds) + ' ' + _('second')
-
- else:
- special_result = str(seconds) + ' ' + _('seconds')
-
- elif days == 0 and hours == 0 and minutes == 0 and seconds == 0:
- special_result = str(0) + ' ' + _('seconds')
-
- if special_result:
- if negative:
- special_result = _('minus') + ' ' + special_result
-
- return special_result
-
- if days:
- format = "{d}d{h}h{m}m"
-
- elif hours:
- format = "{h}h{m}m"
-
- elif minutes:
- format = "{m}m"
- if seconds:
- format += "{s}s"
-
- else:
- format = "{s}s"
-
- if negative:
- format = '-' + format
-
- return format.format(d=days, h=hours, m=minutes, s=seconds)
-
-
-def pretty_size(size):
- if size < 1024: # less than 1kB
- size, unit = size, 'B'
-
- elif size < 1024 * 1024: # less than 1MB
- size, unit = size / 1024.0, 'kB'
-
- elif size < 1024 * 1024 * 1024: # less than 1GB
- size, unit = size / 1024.0 / 1024, 'MB'
-
- else: # greater than or equal to 1GB
- size, unit = size / 1024.0 / 1024 / 1024, 'GB'
-
- return '%.1f %s' % (size, unit)
-
-
-def pretty_http_error(response):
- if response.code == 401 or response.error == 'Authentication Error':
- return 'authentication failed'
-
- if not response.error:
- return 'ok'
-
- msg = unicode(response.error)
- if msg.startswith('HTTP '):
- msg = msg.split(':', 1)[-1].strip()
-
- if msg.startswith('[Errno '):
- msg = msg.split(']', 1)[-1].strip()
-
- if 'timeout' in msg.lower() or 'timed out' in msg.lower():
- msg = 'request timed out'
-
- return msg
-
-
-def make_str(s):
- if isinstance(s, str):
- return s
-
- try:
- return str(s)
-
- except:
- try:
- return unicode(s, encoding='utf8').encode('utf8')
-
- except:
- return unicode(s).encode('utf8')
-
-
-def make_unicode(s):
- if isinstance(s, unicode):
- return s
-
- try:
- return unicode(s, encoding='utf8')
-
- except:
- try:
- return unicode(s)
-
- except:
- return str(s).decode('utf8')
-
-
-def get_disk_usage(path):
- logging.debug('getting disk usage for path %(path)s...' % {
- 'path': path})
-
- try:
- result = os.statvfs(path)
-
- except OSError as e:
- logging.error('failed to execute statvfs: %(msg)s' % {'msg': unicode(e)})
-
- return None
-
- block_size = result.f_frsize
- free_blocks = result.f_bfree
- total_blocks = result.f_blocks
-
- free_size = free_blocks * block_size
- total_size = total_blocks * block_size
- used_size = total_size - free_size
-
- return (used_size, total_size)
-
-
-def local_motion_camera(config):
- '''Tells if a camera is managed by the local motion instance.'''
- return bool(config.get('videodevice') or config.get('netcam_url'))
-
-
-def remote_camera(config):
- '''Tells if a camera is managed by a remote motionEye server.'''
- return config.get('@proto') == 'motioneye'
-
-
-def v4l2_camera(config):
- '''Tells if a camera is a v4l2 device managed by the local motion instance.'''
- return bool(config.get('videodevice'))
-
-
-def net_camera(config):
- '''Tells if a camera is a network camera managed by the local motion instance.'''
- return bool(config.get('netcam_url'))
-
-
-def simple_mjpeg_camera(config):
- '''Tells if a camera is a simple MJPEG camera not managed by any motion instance.'''
- return bool(config.get('@proto') == 'mjpeg')
-
-
-def test_mjpeg_url(data, auth_modes, allow_jpeg, callback):
- data = dict(data)
- data.setdefault('scheme', 'http')
- data.setdefault('host', '127.0.0.1')
- data.setdefault('port', '80')
- data.setdefault('uri', '')
- data.setdefault('username', None)
- data.setdefault('password', None)
-
- url = '%(scheme)s://%(host)s%(port)s%(uri)s' % {
- 'scheme': data['scheme'],
- 'host': data['host'],
- 'port': ':' + str(data['port']) if data['port'] else '',
- 'uri': data['uri'] or ''}
-
- called = [False]
- status_2xx = [False]
- http_11 = [False]
-
- def do_request(on_response):
- if data['username']:
- auth = auth_modes[0]
-
- else:
- auth = 'no'
-
- 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)
-
- http_client = AsyncHTTPClient(force_instance=True)
- http_client.fetch(request, on_response)
-
- def on_header(header):
- header = header.lower()
- if header.startswith('content-type') and status_2xx[0]:
- content_type = header.split(':')[1].strip()
- called[0] = True
-
- if content_type in ['image/jpg', 'image/jpeg', 'image/pjpg'] and allow_jpeg:
- callback([{'id': 1, 'name': 'JPEG Network Camera', 'keep_alive': http_11[0]}])
-
- elif content_type.startswith('multipart/x-mixed-replace'):
- callback([{'id': 1, 'name': 'MJPEG Network Camera', 'keep_alive': http_11[0]}])
-
- else:
- callback(error='not a supported network camera')
-
- else:
- # check for the status header
- m = re.match('^http/1.(\d) (\d+) ', header)
- if m:
- if int(m.group(2)) / 100 == 2:
- status_2xx[0] = True
-
- if m.group(1) == '1':
- http_11[0] = True
-
- def on_response(response):
- if not called[0]:
- if response.code == 401 and auth_modes and data['username']:
- status_2xx[0] = False
- do_request(on_response)
-
- else:
- called[0] = True
- callback(error=pretty_http_error(response) if response.error else 'not a supported network camera')
-
- username = data['username'] or None
- password = data['password'] or None
-
- do_request(on_response)
-
-
-def test_rtsp_url(data, callback):
- import config
-
- data = dict(data)
- data.setdefault('scheme', 'rtsp')
- data.setdefault('host', '127.0.0.1')
- data['port'] = data.get('port') or '554'
- data.setdefault('uri', '')
- data.setdefault('username', None)
- data.setdefault('password', None)
-
- url = '%(scheme)s://%(host)s%(port)s%(uri)s' % {
- 'scheme': data['scheme'],
- 'host': data['host'],
- 'port': ':' + str(data['port']) if data['port'] else '',
- 'uri': data['uri'] or ''}
-
- called = [False]
- timeout = [None]
- stream = None
-
- def connect():
- logging.debug('testing rtsp netcam at %s' % url)
-
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
- s.settimeout(settings.MJPG_CLIENT_TIMEOUT)
- stream = IOStream(s)
- stream.set_close_callback(on_close)
- stream.connect((data['host'], int(data['port'])), on_connect)
-
- timeout[0] = IOLoop.instance().add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT),
- functools.partial(on_connect, _timeout=True))
-
- return stream
-
- def on_connect(_timeout=False):
- IOLoop.instance().remove_timeout(timeout[0])
-
- if _timeout:
- return handle_error('timeout connecting to rtsp netcam')
-
- if not stream:
- return handle_error('failed to connect to rtsp netcam')
-
- logging.debug('connected to rtsp netcam')
-
- stream.write('\r\n'.join([
- 'OPTIONS %s RTSP/1.0' % url.encode('utf8'),
- 'CSeq: 1',
- 'User-Agent: motionEye',
- '',
- ''
- ]))
-
- seek_rtsp()
-
- def seek_rtsp():
- if check_error():
- return
-
- stream.read_until_regex('RTSP/1.0 \d+ ', on_rtsp)
- timeout[0] = IOLoop.instance().add_timeout(datetime.timedelta(seconds=settings.MJPG_CLIENT_TIMEOUT), on_rtsp)
-
- def on_rtsp(data):
- IOLoop.instance().remove_timeout(timeout[0])
-
- if data:
- if data.endswith('200 '):
- seek_server()
-
- else:
- handle_error('rtsp netcam returned erroneous response: %s' % data)
-
- else:
- handle_error('timeout waiting for rtsp netcam response')
-
- def seek_server():
- if check_error():
- return
-
- stream.read_until_regex('Server: .*', on_server)
- timeout[0] = IOLoop.instance().add_timeout(datetime.timedelta(seconds=1), on_server)
-
- def on_server(data=None):
- IOLoop.instance().remove_timeout(timeout[0])
-
- if data:
- identifier = re.findall('Server: (.*)', data)[0].strip()
- logging.debug('rtsp netcam identifier is "%s"' % identifier)
-
- else:
- identifier = None
- logging.debug('no rtsp netcam identifier')
-
- handle_success(identifier)
-
- def on_close():
- if called[0]:
- return
-
- if not check_error():
- handle_error('connection closed')
-
- def handle_success(identifier):
- if called[0]:
- return
-
- called[0] = True
- cameras = []
- rtsp_support = config.motion_rtsp_support()
- if identifier:
- identifier = ' ' + identifier
-
- else:
- identifier = ''
-
- if 'udp' in rtsp_support:
- cameras.append({'id': 'udp', 'name': '%sRTSP/UDP Camera' % identifier})
-
- if 'tcp' in rtsp_support:
- cameras.append({'id': 'tcp', 'name': '%sRTSP/TCP Camera' % identifier})
-
- callback(cameras)
-
- def handle_error(e):
- if called[0]:
- return
-
- called[0] = True
- logging.error('rtsp client error: %s' % unicode(e))
-
- try:
- stream.close()
-
- except:
- pass
-
- callback(error=unicode(e))
-
- def check_error():
- error = getattr(stream, 'error', None)
- if error and getattr(error, 'errno', None) != 0:
- handle_error(error.strerror)
- return True
-
- if stream and stream.socket is None:
- handle_error('connection closed')
- stream.close()
-
- return True
-
- return False
-
- stream = connect()
-
-
-def compute_signature(method, uri, body, key):
- parts = list(urlparse.urlsplit(uri))
- query = [q for q in urlparse.parse_qsl(parts[3], keep_blank_values=True) if (q[0] != '_signature')]
- query.sort(key=lambda q: q[0])
- # "safe" characters here are set to match the encodeURIComponent JavaScript counterpart
- query = [(n, urllib.quote(v, safe="!'()*~")) for (n, v) in query]
- query = '&'.join([(q[0] + '=' + q[1]) for q in query])
- parts[0] = parts[1] = ''
- parts[3] = query
- uri = urlparse.urlunsplit(parts)
- uri = _SIGNATURE_REGEX.sub('-', uri)
- key = _SIGNATURE_REGEX.sub('-', key)
-
- if body and body.startswith('---'):
- body = None # file attachment
-
- body = body and _SIGNATURE_REGEX.sub('-', body.decode('utf8'))
-
- return hashlib.sha1('%s:%s:%s:%s' % (method, uri, body or '', key)).hexdigest().lower()
-
-
-def build_basic_header(username, password):
- return 'Basic ' + base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
-
-
-def build_digest_header(method, url, username, password, state):
- realm = state['realm']
- nonce = state['nonce']
- last_nonce = state.get('last_nonce', '')
- nonce_count = state.get('nonce_count', 0)
- qop = state.get('qop')
- algorithm = state.get('algorithm')
- opaque = state.get('opaque')
-
- if algorithm is None:
- _algorithm = 'MD5'
-
- else:
- _algorithm = algorithm.upper()
-
- if _algorithm == 'MD5' or _algorithm == 'MD5-SESS':
- def md5_utf8(x):
- if isinstance(x, str):
- x = x.encode('utf-8')
- return hashlib.md5(x).hexdigest()
- hash_utf8 = md5_utf8
-
- elif _algorithm == 'SHA':
- def sha_utf8(x):
- if isinstance(x, str):
- x = x.encode('utf-8')
- return hashlib.sha1(x).hexdigest()
- hash_utf8 = sha_utf8
-
- KD = lambda s, d: hash_utf8("%s:%s" % (s, d))
-
- if hash_utf8 is None:
- return None
-
- entdig = None
- p_parsed = urlparse.urlparse(url)
- path = p_parsed.path
- if p_parsed.query:
- path += '?' + p_parsed.query
-
- A1 = '%s:%s:%s' % (username, realm, password)
- A2 = '%s:%s' % (method, path)
-
- HA1 = hash_utf8(A1)
- HA2 = hash_utf8(A2)
-
- if nonce == last_nonce:
- nonce_count += 1
-
- else:
- nonce_count = 1
-
- ncvalue = '%08x' % nonce_count
- s = str(nonce_count).encode('utf-8')
- s += nonce.encode('utf-8')
- s += time.ctime().encode('utf-8')
- s += os.urandom(8)
-
- cnonce = (hashlib.sha1(s).hexdigest()[:16])
- if _algorithm == 'MD5-SESS':
- HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
-
- if qop is None:
- respdig = KD(HA1, "%s:%s" % (nonce, HA2))
-
- elif qop == 'auth' or 'auth' in qop.split(','):
- noncebit = "%s:%s:%s:%s:%s" % (
- nonce, ncvalue, cnonce, 'auth', HA2
- )
- respdig = KD(HA1, noncebit)
-
- else:
- return None
-
- last_nonce = nonce
-
- base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
- 'response="%s"' % (username, realm, nonce, path, respdig)
- if opaque:
- base += ', opaque="%s"' % opaque
- if algorithm:
- base += ', algorithm="%s"' % algorithm
- if entdig:
- base += ', digest="%s"' % entdig
- if qop:
- base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce)
-
- state['last_nonce'] = last_nonce
- state['nonce_count'] = nonce_count
-
- return 'Digest %s' % (base)
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import fcntl
-import logging
-import os.path
-import re
-import stat
-import subprocess
-import time
-import utils
-
-
-_resolutions_cache = {}
-_ctrls_cache = {}
-_ctrl_values_cache = {}
-
-_DEV_V4L_BY_ID = '/dev/v4l/by-id/'
-
-
-def find_v4l2_ctl():
- try:
- return subprocess.check_output('which v4l2-ctl', shell=True).strip()
-
- except subprocess.CalledProcessError: # not found
- return None
-
-
-def list_devices():
- global _resolutions_cache, _ctrls_cache, _ctrl_values_cache
-
- logging.debug('listing v4l2 devices...')
-
- try:
- output = ''
- started = time.time()
- p = subprocess.Popen('v4l2-ctl --list-devices 2>/dev/null', shell=True, stdout=subprocess.PIPE, bufsize=1)
-
- fd = p.stdout.fileno()
- fl = fcntl.fcntl(fd, fcntl.F_GETFL)
- fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
-
- while True:
- try:
- data = p.stdout.read(1024)
- if not data:
- break
-
- except IOError:
- data = ''
- time.sleep(0.01)
-
- output += data
-
- if len(output) > 10240:
- logging.warn('v4l2-ctl command returned more than 10k of output')
- break
-
- if time.time() - started > 3:
- logging.warn('v4l2-ctl command ran for more than 3 seconds')
- break
-
- except subprocess.CalledProcessError:
- logging.debug('failed to list devices (probably no devices installed)')
- return []
-
- try:
- # try to kill the v4l2-ctl subprocess
- p.kill()
-
- except:
- pass # nevermind
-
- name = None
- devices = []
- for line in output.split('\n'):
- if line.startswith('\t'):
- device = line.strip()
- persistent_device = find_persistent_device(device)
- devices.append((device, persistent_device, name))
-
- logging.debug('found device %(name)s: %(device)s, %(persistent_device)s' % {
- 'name': name, 'device': device, 'persistent_device': persistent_device})
-
- else:
- name = line.split('(')[0].strip()
-
- # clear the cache
- _resolutions_cache = {}
- _ctrls_cache = {}
- _ctrl_values_cache = {}
-
- return devices
-
-
-def list_resolutions(device):
- global _resolutions_cache
-
- device = utils.make_str(device)
-
- if device in _resolutions_cache:
- return _resolutions_cache[device]
-
- logging.debug('listing resolutions of device %(device)s...' % {'device': device})
-
- 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': device}, shell=True, stdout=subprocess.PIPE, bufsize=1)
-
- fd = p.stdout.fileno()
- fl = fcntl.fcntl(fd, fcntl.F_GETFL)
- fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
-
- while True:
- try:
- data = p.stdout.read(1024)
- if not data:
- break
-
- except IOError:
- data = ''
- time.sleep(0.01)
-
- output += data
-
- if len(output) > 10240:
- logging.warn('v4l2-ctl command returned more than 10k of output')
- break
-
- if time.time() - started > 3:
- logging.warn('v4l2-ctl command ran for more than 3 seconds')
- break
-
- try:
- # try to kill the v4l2-ctl subprocess
- p.kill()
-
- except:
- pass # nevermind
-
- for pair in output.split('\n'):
- pair = pair.strip()
- if not pair:
- continue
-
- width, height = pair.split('x')
- width = int(width)
- height = int(height)
-
- if (width, height) in resolutions:
- continue # duplicate resolution
-
- if width < 96 or height < 96: # some reasonable minimal values
- continue
-
- if width % 16 or height % 16: # ignore non-modulo 16 resolutions
- continue
-
- resolutions.add((width, height))
-
- logging.debug('found resolution %(width)sx%(height)s for device %(device)s' % {
- 'device': device, 'width': width, 'height': height})
-
- if not resolutions:
- logging.debug('no resolutions found for device %(device)s, using common values' % {'device': device})
-
- # no resolution returned by v4l2-ctl call, add common default resolutions
- resolutions = utils.COMMON_RESOLUTIONS
-
- resolutions = list(sorted(resolutions, key=lambda r: (r[0], r[1])))
- _resolutions_cache[device] = resolutions
-
- return resolutions
-
-
-def device_present(device):
- device = utils.make_str(device)
-
- try:
- st = os.stat(device)
- return stat.S_ISCHR(st.st_mode)
-
- except:
- return False
-
-
-def find_persistent_device(device):
- device = utils.make_str(device)
-
- try:
- devs_by_id = os.listdir(_DEV_V4L_BY_ID)
-
- except OSError:
- return device
-
- for p in devs_by_id:
- p = os.path.join(_DEV_V4L_BY_ID, p)
- if os.path.realpath(p) == device:
- return p
-
- return device
-
-
-def get_brightness(device):
- return _get_ctrl(device, 'brightness')
-
-
-def set_brightness(device, value):
- _set_ctrl(device, 'brightness', value)
-
-
-def get_contrast(device):
- return _get_ctrl(device, 'contrast')
-
-
-def set_contrast(device, value):
- _set_ctrl(device, 'contrast', value)
-
-
-def get_saturation(device):
- return _get_ctrl(device, 'saturation')
-
-
-def set_saturation(device, value):
- _set_ctrl(device, 'saturation', value)
-
-
-def get_hue(device):
- return _get_ctrl(device, 'hue')
-
-
-def set_hue(device, value):
- _set_ctrl(device, 'hue', value)
-
-
-def _get_ctrl(device, control):
- global _ctrl_values_cache
-
- device = utils.make_str(device)
-
- if not device_present(device):
- return None
-
- if device in _ctrl_values_cache and control in _ctrl_values_cache[device]:
- return _ctrl_values_cache[device][control]
-
- controls = _list_ctrls(device)
- properties = controls.get(control)
- if properties is None:
- logging.warn('control %(control)s not found for device %(device)s' % {
- 'control': control, 'device': device})
-
- return None
-
- value = int(properties['value'])
-
- # adjust the value range
- if 'min' in properties and 'max' in properties:
- min_value = int(properties['min'])
- max_value = int(properties['max'])
-
- value = int(round((value - min_value) * 100.0 / (max_value - min_value)))
-
- else:
- logging.warn('min and max values not found for control %(control)s of device %(device)s' % {
- 'control': control, 'device': device})
-
- logging.debug('control %(control)s of device %(device)s is %(value)s%%' % {
- 'control': control, 'device': device, 'value': value})
-
- return value
-
-
-def _set_ctrl(device, control, value):
- global _ctrl_values_cache
-
- device = utils.make_str(device)
-
- if not device_present(device):
- return
-
- controls = _list_ctrls(device)
- properties = controls.get(control)
- if properties is None:
- logging.warn('control %(control)s not found for device %(device)s' % {
- 'control': control, 'device': device})
-
- return
-
- _ctrl_values_cache.setdefault(device, {})[control] = value
-
- # adjust the value range
- if 'min' in properties and 'max' in properties:
- min_value = int(properties['min'])
- max_value = int(properties['max'])
-
- value = int(round(min_value + value * (max_value - min_value) / 100.0))
-
- else:
- logging.warn('min and max values not found for control %(control)s of device %(device)s' % {
- 'control': control, 'device': device})
-
- logging.debug('setting control %(control)s of device %(device)s to %(value)s' % {
- 'control': control, 'device': device, 'value': value})
-
- output = ''
- started = time.time()
- p = subprocess.Popen('v4l2-ctl -d "%(device)s" --set-ctrl %(control)s=%(value)s' % {
- 'device': device, 'control': control, 'value': value}, shell=True, stdout=subprocess.PIPE, bufsize=1)
-
- fd = p.stdout.fileno()
- fl = fcntl.fcntl(fd, fcntl.F_GETFL)
- fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
-
- while True:
- try:
- data = p.stdout.read(1024)
- if not data:
- break
-
- except IOError:
- data = ''
- time.sleep(0.01)
-
- output += data
-
- if len(output) > 10240:
- logging.warn('v4l2-ctl command returned more than 10k of output')
- break
-
- if time.time() - started > 3:
- logging.warn('v4l2-ctl command ran for more than 3 seconds')
- break
-
- try:
- # try to kill the v4l2-ctl subprocess
- p.kill()
-
- except:
- pass # nevermind
-
-
-def _list_ctrls(device):
- global _ctrls_cache
-
- device = utils.make_str(device)
-
- if device in _ctrls_cache:
- return _ctrls_cache[device]
-
- output = ''
- started = time.time()
- p = subprocess.Popen('v4l2-ctl -d "%(device)s" --list-ctrls' % {
- 'device': device}, shell=True, stdout=subprocess.PIPE, bufsize=1)
-
- fd = p.stdout.fileno()
- fl = fcntl.fcntl(fd, fcntl.F_GETFL)
- fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
-
- while True:
- try:
- data = p.stdout.read(1024)
- if not data:
- break
-
- except IOError:
- data = ''
- time.sleep(0.01)
-
- output += data
-
- if len(output) > 10240:
- logging.warn('v4l2-ctl command returned more than 10k of output')
- break
-
- if time.time() - started > 3:
- logging.warn('v4l2-ctl command ran for more than 3 seconds')
- break
-
- try:
- # try to kill the v4l2-ctl subprocess
- p.kill()
-
- except:
- pass # nevermind
-
- controls = {}
- for line in output.split('\n'):
- if not line:
- continue
-
- match = re.match('^\s*(\w+)\s+\(\w+\)\s+\:\s*(.+)', line)
- if not match:
- continue
-
- (control, properties) = match.groups()
- properties = dict([v.split('=', 1) for v in properties.split(' ') if v.count('=')])
- controls[control] = properties
-
- _ctrls_cache[device] = controls
-
- return controls
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import logging
-import re
-import settings
-
-from config import additional_config, additional_section
-
-
-WPA_SUPPLICANT_CONF = settings.WPA_SUPPLICANT_CONF # @UndefinedVariable
-
-
-def _get_wifi_settings():
- # will return the first configured network
-
- logging.debug('reading wifi settings from %s' % WPA_SUPPLICANT_CONF)
-
- try:
- conf_file = open(WPA_SUPPLICANT_CONF, 'r')
-
- except Exception as e:
- logging.error('could open wifi settings file %(path)s: %(msg)s' % {
- 'path': WPA_SUPPLICANT_CONF, 'msg': unicode(e)})
-
- return {
- 'wifiEnabled': False,
- 'wifiNetworkName': '',
- 'wifiNetworkKey': ''
- }
-
- lines = conf_file.readlines()
- conf_file.close()
-
- ssid = psk = ''
- in_section = False
- for line in lines:
- line = line.strip()
- if line.startswith('#'):
- continue
-
- if '{' in line:
- in_section = True
-
- elif '}' in line:
- in_section = False
- break
-
- elif in_section:
- m = re.search('ssid\s*=\s*"(.*?)"', line)
- if m:
- ssid = m.group(1)
-
- m = re.search('psk\s*=\s*"(.*?)"', line)
- if m:
- psk = m.group(1)
-
- if ssid:
- logging.debug('wifi is enabled (ssid = "%s")' % ssid)
-
- return {
- 'wifiEnabled': True,
- 'wifiNetworkName': ssid,
- 'wifiNetworkKey': psk
- }
-
- else:
- logging.debug('wifi is disabled')
-
- return {
- 'wifiEnabled': False,
- 'wifiNetworkName': ssid,
- 'wifiNetworkKey': psk
- }
-
-
-def _set_wifi_settings(s):
- s.setdefault('wifiEnabled', False)
- s.setdefault('wifiNetworkName', '')
- s.setdefault('wifiNetworkKey', '')
-
- logging.debug('writing wifi settings to %s: enabled=%s, ssid="%s"' % (
- WPA_SUPPLICANT_CONF, s['wifiEnabled'], s['wifiNetworkName']))
-
- enabled = s['wifiEnabled']
- ssid = s['wifiNetworkName']
- psk = s['wifiNetworkKey']
-
- # will update the first configured network
- try:
- conf_file = open(WPA_SUPPLICANT_CONF, 'r')
-
- except Exception as e:
- logging.error('could open wifi settings file %(path)s: %(msg)s' % {
- 'path': WPA_SUPPLICANT_CONF, 'msg': unicode(e)})
-
- return
-
- lines = conf_file.readlines()
- conf_file.close()
-
- in_section = False
- found_ssid = False
- found_psk = False
- i = 0
- while i < len(lines):
- line = lines[i].strip()
- if line.startswith('#'):
- i += 1
- continue
-
- if '{' in line:
- in_section = True
-
- elif '}' in line:
- in_section = False
- if enabled and ssid and not found_ssid:
- lines.insert(i, ' ssid="' + ssid + '"\n')
- if enabled and psk and not found_psk:
- lines.insert(i, ' psk="' + psk + '"\n')
-
- found_psk = found_ssid = True
-
- break
-
- elif in_section:
- if enabled:
- if re.match('ssid\s*=\s*".*?"', line):
- lines[i] = ' ssid="' + ssid + '"\n'
- found_ssid = True
-
- elif re.match('psk\s*=\s*".*?"', line):
- if psk:
- lines[i] = ' psk="' + psk + '"\n'
- found_psk = True
-
- else:
- lines.pop(i)
- i -= 1
-
- else: # wifi disabled
- if re.match('ssid\s*=\s*".*?"', line) or re.match('psk\s*=\s*".*?"', line):
- lines.pop(i)
- i -= 1
-
- i += 1
-
- if enabled and not found_ssid:
- lines.append('network={\n')
- lines.append(' scan_ssid=1\n')
- lines.append(' ssid="' + ssid + '"\n')
- lines.append(' psk="' + psk + '"\n')
- lines.append('}\n\n')
-
- try:
- conf_file = open(WPA_SUPPLICANT_CONF, 'w')
-
- except Exception as e:
- logging.error('could open wifi settings file %(path)s: %(msg)s' % {
- 'path': WPA_SUPPLICANT_CONF, 'msg': unicode(e)})
-
- return
-
- for line in lines:
- conf_file.write(line)
-
- conf_file.close()
-
-
-@additional_section
-def network():
- return {
- 'label': 'Network',
- 'description': 'configure the network connection',
- 'advanced': True
- }
-
-
-@additional_config
-def wifiEnabled():
- if not WPA_SUPPLICANT_CONF:
- return
-
- return {
- 'label': 'Wireless Network',
- 'description': 'enable this if you want to connect to a wireless network',
- 'type': 'bool',
- 'section': 'network',
- 'advanced': True,
- 'reboot': True,
- 'get': _get_wifi_settings,
- 'set': _set_wifi_settings,
- 'get_set_dict': True
- }
-
-
-@additional_config
-def wifiNetworkName():
- if not WPA_SUPPLICANT_CONF:
- return
-
- return {
- 'label': 'Wireless Network Name',
- 'description': 'the name (SSID) of your wireless network',
- 'type': 'str',
- 'section': 'network',
- 'advanced': True,
- 'required': True,
- 'reboot': True,
- 'depends': ['wifiEnabled'],
- 'get': _get_wifi_settings,
- 'set': _set_wifi_settings,
- 'get_set_dict': True
- }
-
-
-@additional_config
-def wifiNetworkKey():
- if not WPA_SUPPLICANT_CONF:
- return
-
- return {
- 'label': 'Wireless Network Key',
- 'description': 'the key (PSK) required to connect to your wireless network',
- 'type': 'pwd',
- 'section': 'network',
- 'advanced': True,
- 'required': False,
- 'reboot': True,
- 'depends': ['wifiEnabled'],
- 'get': _get_wifi_settings,
- 'set': _set_wifi_settings,
- 'get_set_dict': True
- }
+++ /dev/null
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import datetime
-import logging
-import tornado
-
-import config
-import motionctl
-import utils
-
-
-def start():
- ioloop = tornado.ioloop.IOLoop.instance()
- ioloop.add_timeout(datetime.timedelta(seconds=10), _check_ws)
-
-
-def _during_working_schedule(now, working_schedule):
- parts = working_schedule.split('|')
- if len(parts) < 7:
- return False # invalid ws
-
- ws_day = parts[now.weekday()]
- parts = ws_day.split('-')
- if len(parts) != 2:
- return False # invalid ws
-
- _from, to = parts
- if not _from or not to:
- 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
-
- try:
- from_h = int(_from[0])
- from_m = int(_from[1])
- to_h = int(to[0])
- to_m = int(to[1])
-
- except ValueError:
- return False # invalid ws
-
- if now.hour < from_h or now.hour > to_h:
- return False
-
- if now.hour == from_h and now.minute < from_m:
- return False
-
- if now.hour == to_h and now.minute > to_m:
- return False
-
- return True
-
-
-def _check_ws():
- # schedule the next call
- ioloop = tornado.ioloop.IOLoop.instance()
- ioloop.add_timeout(datetime.timedelta(seconds=10), _check_ws)
-
- if not motionctl.running():
- return
-
- now = datetime.datetime.now()
- for camera_id in config.get_camera_ids():
- camera_config = config.get_camera(camera_id)
- if not utils.local_motion_camera(camera_config):
- continue
-
- working_schedule = camera_config.get('@working_schedule')
- 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
- continue
-
- 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')
-
- currently_enabled = motionctl.get_motion_detection(camera_id)
- if currently_enabled is None: # could not detect current status
- logging.warn('skipping motion detection status update for camera with id %(id)s' % {'id': camera_id})
- continue
-
- if currently_enabled and not must_be_enabled:
- logging.debug('must disable motion detection for camera with id %(id)s (%(what)s working schedule)' % {
- 'id': camera_id,
- 'what': working_schedule_type})
-
- motionctl.set_motion_detection(camera_id, False)
-
- elif not currently_enabled and must_be_enabled:
- logging.debug('must enable motion detection for camera with id %(id)s (%(what)s working schedule)' % {
- 'id': camera_id,
- 'what': working_schedule_type})
-
- motionctl.set_motion_detection(camera_id, True)
+++ /dev/null
-
-
- /* basic */
-
-body {
- color: #dddddd;
- background-color: #212121;
-}
-
-
- /* camera frame */
-
-div.camera-frame {
- position: relative;
- padding: 0px;
- margin: 0px;
- width: 100%;
- height: 100%;
-}
-
-div.camera-container {
- height: 100%;
- text-align: center;
- overflow: hidden;
-}
-
-img.camera {
- height: auto;
- margin: auto;
-}
-
-img.camera.error,
-img.camera.loading {
- height: 100% !important;
-}
-
-div.camera-placeholder {
- overflow: hidden;
-}
-
-div.camera-progress {
- cursor: default;
-}
+++ /dev/null
-\r
-.ui-timepicker-wrapper {\r
- overflow-y: auto;\r
- height: 150px;\r
- width: 6.5em;\r
- background: #414141;\r
- border: 1px solid #515151;\r
- -webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);\r
- -moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);\r
- box-shadow:0 5px 10px rgba(0,0,0,0.2);\r
- outline: none;\r
- z-index: 10001;\r
- margin: 0;\r
-}\r
-\r
-.ui-timepicker-wrapper.ui-timepicker-with-duration {\r
- width: 11em;\r
-}\r
-\r
-.ui-timepicker-list {\r
- margin: 0;\r
- padding: 0;\r
- list-style: none;\r
-}\r
-\r
-.ui-timepicker-duration {\r
- margin-left: 5px; color: #888;\r
-}\r
-\r
-.ui-timepicker-list:hover .ui-timepicker-duration {\r
- color: #888;\r
-}\r
-\r
-.ui-timepicker-list li {\r
- padding: 3px 0 3px 5px;\r
- cursor: pointer;\r
- white-space: nowrap;\r
- color: white;\r
- list-style: none;\r
- font-size: 0.8em;\r
- margin: 0;\r
-}\r
-\r
-.ui-timepicker-list:hover .ui-timepicker-selected {\r
- background: #aaa; color: black;\r
-}\r
-\r
-li.ui-timepicker-selected,\r
-.ui-timepicker-list li:hover,\r
-.ui-timepicker-list .ui-timepicker-selected:hover {\r
- background: #1980EC; color: #fff;\r
-}\r
-\r
-li.ui-timepicker-selected .ui-timepicker-duration,\r
-.ui-timepicker-list li:hover .ui-timepicker-duration {\r
- color: #ccc;\r
-}\r
-\r
-.ui-timepicker-list li.ui-timepicker-disabled,\r
-.ui-timepicker-list li.ui-timepicker-disabled:hover,\r
-.ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {\r
- color: #888;\r
- cursor: default;\r
-}\r
-\r
-.ui-timepicker-list li.ui-timepicker-disabled:hover,\r
-.ui-timepicker-list li.ui-timepicker-selected.ui-timepicker-disabled {\r
- background: #f2f2f2;\r
-}\r
+++ /dev/null
-
-
- /* basic */
-
-* {
- padding: 0px;
- border: 0px solid black;
- margin: 0px;
- outline: 0px;
- border-spacing: 0px;
- border-collapse: separate;
-}
-
-html {
- height: 100%;
-}
-
-body {
- height: 100%;
- color: #dddddd;
- font-size: 22px;
- background-color: #212121;
-}
-
-select,
-input[type=text],
-input[type=password],
-textarea {
- box-sizing: border-box;
-}
-
-
- /* fonts */
-
-@font-face {
- font-family: 'Maven Pro';
- src: url('../fnt/mavenpro-regular-webfont.eot');
- src: url('../fnt/mavenpro-regular-webfont.eot?#iefix') format('embedded-opentype'),
- url('../fnt/mavenpro-regular-webfont.woff') format('woff'),
- url('../fnt/mavenpro-regular-webfont.ttf') format('truetype'),
- url('../fnt/mavenpro-regular-webfont.svg#maven_proregular') format('svg');
- font-weight: normal;
- font-style: normal;
-}
-
-@font-face {
- font-family: 'Maven Pro';
- src: url('../fnt/mavenpro-bold-webfont.eot');
- src: url('../fnt/mavenpro-bold-webfont.eot?#iefix') format('embedded-opentype'),
- url('../fnt/mavenpro-bold-webfont.woff') format('woff'),
- url('../fnt/mavenpro-bold-webfont.ttf') format('truetype'),
- url('../fnt/mavenpro-bold-webfont.svg#maven_probold') format('svg');
- font-weight: bold;
- font-style: normal;
-}
-
-
- /* layout */
-
-html {
- font-family: 'Maven Pro';
-}
-
-div.page,
-div.header-container {
- position: relative;
- min-width: 320px;
- width: 100%;
-}
-
-div.page {
- font-size: 1em;
- transition: all 0.5s linear;
- min-height: 100%;
-}
-
-div.header {
- background-color: rgba(64, 64, 64, 0.5);
- box-shadow: 0px 0px 5px rgba(0,0,0,0.3);
- top: 0px;
- width: 100%;
- height: 50px;
- position: fixed;
- overflow: hidden;
- z-index: 10000;
-}
-
-div.header-container {
- transition: all 0.5s linear;
-}
-
-div.footer {
- position: absolute;
- bottom: 5px;
- width: 100%;
- height: 3em;
- font-size: 0.7em;
- color: #aaa;
- text-align: center;
-}
-
-div.copyright-note {
- border-top: 1px solid #333;
- padding-top: 0.2em;
- margin: 0px 15%;
-}
-
-div.page-container {
- transition: all 0.2s linear;
- padding: 55px 5px 3em 2%;
-}
-
-div.page-container.stretched {
- margin-left: 40%;
- padding-left: 5px;
-}
-
-
- /* icons & icon buttons */
-
-div.button.settings-button {
- margin: 1px;
- vertical-align: middle;
- background-image: url(../img/settings.svg);
- width: 48px;
- height: 48px;
-}
-
-div.button.logout-button {
- margin: 1px;
- vertical-align: middle;
- background-image: url(../img/logout.svg);
- width: 48px;
- height: 48px;
-}
-
-body.admin div.logout-button {
- display: none;
-}
-
-body.admin div.settings-top-bar.closed div.logout-button {
- display: inline-block;
-}
-
-body:not(.admin) div.settings-top-bar div.logout-button {
- display: none;
-}
-
-div.button.rem-camera-button {
- display: none;
- margin: 1px;
- vertical-align: middle;
- background-image: url(../img/settings.svg);
- width: 48px;
- height: 48px;
- background-position: -48px 0px;
-}
-
-div.settings-top-bar.open div.button.rem-camera-button {
- display: inline-block;
-}
-
-div.logo {
- float: right;
- display: inline-block;
- white-space: nowrap;
- opacity: 0.86;
-}
-
-span.logo {
- color: white;
- vertical-align: middle;
- font-size: 27px;
- font-weight: bold;
- position: relative;
- top: 3px;
-}
-
-img.logo {
- width: 36px;
- height: 36px;
- padding: 7px 3px;
- vertical-align: middle;
-}
-
-img.background-logo {
- position: absolute;
- width: 30%;
- left: 35%;
- top: 10em;
- opacity: 0.03;
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
-}
-
-img.main-loading-progress {
- display: block;
- margin: auto;
- margin-top: 50px;
-}
-
-div.add-camera-message {
- text-align: center;
- margin-top: 30px;
-}
-
-div.hostname {
- vertical-align: middle;
- display: inline-block;
- font-size: 27px;
-}
-
-
- /* settings */
-
-div.settings {
- background-color: #313131;
- position: fixed;
- z-index: 1;
- top: 50px;
- left: 0px;
- width: 0px;
- bottom: 0px;
- transition: all 0.2s linear;
- overflow: auto;
-}
-
-div.settings.open {
- width: 40%;
- min-width: 320px;
-}
-
-body:not(.admin) div.settings {
- display: none !important;
-}
-
-div.settings-container {
- position: relative;
- padding-top: 10px;
- display: none;
- white-space: nowrap;
-}
-
-div.settings.open div.settings-container {
- display: block;
-}
-
-div.settings-progress {
- position: absolute;
- top: 0px;
- width: 0px;
- bottom: 0px;
- left: 0px;
- background-color: #313131;
- opacity: 0;
- transition: opacity 0.1s linear;
-}
-
-div.settings-top-bar {
- position: relative;
- display: inline-block;
- width: 40%;
- height: 50px;
-}
-
-div.settings-top-bar.open {
- background-color: #414141;
- min-width: 320px;
-}
-
-body:not(.admin) div.settings-top-bar {
- display: none !important;
-}
-
-div.settings-top-bar.closed div.apply-button {
- display: none !important;
-}
-
-div.settings-section-title {
- position: relative;
- text-align: right;
- background-color: rgba(100, 100, 100, 0.3);
- padding: 5px 0.5em 5px 5px;
-}
-
-a.settings-section-title {
-}
-
-table.settings {
- width: 100%;
- padding: 0.5em 0.5em 1em 0.5em;
-}
-
-td.settings-item-label {
- width: 50%;
- text-align: right;
- padding-right: 5px;
-}
-
-td.settings-item-value {
- width: 50%;
- text-align: left;
- padding-left: 5px;
-}
-
-span.settings-item-label {
- font-size: 0.9em;
-}
-
-span.settings-item-unit {
- font-size: 0.6em;
- padding: 0px 0.2em;
-}
-
-div.settings-item-separator {
- height: 1px;
- border-top: 1px solid #414141;
- margin: 0.5em 1em;
-}
-
-#cameraSelect {
- display: none;
- padding: 4px 1.5em 4px 4px;
- vertical-align: middle;
- font-size: 1.1em;
- width: auto;
- max-width: 35%;
-}
-
-div.apply-button {
- position: relative;
- display: none;
- opacity: 0;
- float: right;
- width: 4em;
- height: 30px;
- line-height: 30px;
- text-align: center;
- margin: 10px;
- color: white;
- background-color: #FF6F00;
- border-radius: 3px;
- transition: all 0.1s linear;
-}
-
-div.apply-button:HOVER {
- background-color: #FF7D19;
-}
-
-div.apply-button:ACTIVE {
- background-color: #F06800;
-}
-
-div.apply-button.progress {
- background-color: #FF6F00;
-}
-
-img.apply-progress {
- margin-top: 3px;
-}
-
-div.normal-button {
- position: relative;
- height: 1.5em;
- line-height: 1.5em;
- text-align: center;
- margin: 2px 0px;
- color: white;
- font-size: 0.9em;
- border-radius: 3px;
- transition: all 0.1s linear;
- width: 7em;
-}
-
-div.update-button,
-div.backup-button,
-div.restore-button {
- background: #317CAD;
-}
-
-div.shut-down-button,
-div.reboot-button {
- background: #c0392b;
-}
-
-div.update-button:HOVER,
-div.backup-button:HOVER,
-div.restore-button:HOVER {
- background: #3498db;
-}
-
-div.shut-down-button:HOVER,
-div.reboot-button:HOVER {
- background: #D43F2F;
-}
-
-div.update-button:ACTIVE,
-div.backup-button:ACTIVE,
-div.restore-button:ACTIVE {
- background: #317CAD;
-}
-
-div.shut-down-button:ACTIVE,
-div.reboot-button:ACTIVE {
- background: #B03427;
-}
-
-div.settings-top-bar.open #cameraSelect {
- display: inline;
-}
-
-div.settings-top-bar.open div.logout-button {
- display: none;
-}
-
-div.check-box.section {
- margin: 0px;
- float: left;
-}
-
-div.check-box.section div.check-box-button {
- background-color: #515151;
-}
-
-div.check-box.on.section div.check-box-button {
- background-color: #317CAD;
-}
-
-div.check-box.on.section:FOCUS div.check-box-button,
-div.check-box.on.section:HOVER div.check-box-button {
- background-color: #3498db;
-}
-
-input[type=text].working-schedule.number {
- width: 50px;
-}
-
-#diskUsageProgressBar {
- width: 90%;
-}
-
-div.hidden,
-tr.hidden {
- display: none !important;
-}
-
-span.help-mark {
- display: inline-block;
- visibility: hidden;
- text-align: center;
- background-color: #414141;
- color: #3498db;
- font-size: 0.75em;
- font-family: monospace;
- width: 1.2em;
- height: 1.2em;
- border-radius: 100em;
- cursor: pointer;
- vertical-align: middle;
- position: relative;
- top: -0.1em;
-}
-
-div.settings-section-title > span.help-mark {
- background-color: #515151;
-}
-
-div.settings-section-title:HOVER > span.help-mark,
-tr:HOVER span.help-mark {
- visibility: visible;
-}
-
-span.minimize {
- display: inline-block;
- background-image: url(../img/combo-box-arrow.svg);
- background-size: cover;
- width: 0.8em;
- height: 0.8em;
- cursor: pointer;
- vertical-align: middle;
- position: relative;
- top: -0.1em;
- transition: transform 0.1s linear;
- -webkit-transform: rotate(90deg);
- -moz-transform: rotate(90deg);
- -ms-transform: rotate(90deg);
- -o-transform: rotate(90deg);
- transform: rotate(90deg);
-}
-
-span.minimize.open {
- -webkit-transform: rotate(0deg);
- -moz-transform: rotate(0deg);
- -ms-transform: rotate(0deg);
- -o-transform: rotate(0deg);
- transform: rotate(0deg);
-}
-
-
- /* dialogs */
-
-table.login-dialog {
- margin: auto;
- font-size: 22px; /* always bigger, regardless of screen size */
-}
-
-table.add-camera-dialog {
- margin: auto;
-}
-
-table.add-camera-dialog select,
-table.add-camera-dialog input[type=text],
-table.add-camera-dialog input[type=password] {
- width: 17em;
-}
-
-span#cameraMsgLabel {
- color: red;
- font-size: 0.7em;
-}
-
-div#addCameraInfo {
- font-size: 0.7em;
- max-width: 33em;
-}
-
-div.media-dialog {
-}
-
-div.media-dialog-groups {
- float: left;
- width: 11em;
- text-align: center;
- overflow: auto;
- white-space: nowrap;
-}
-
-div.media-dialog-groups.small-screen {
- float: none;
-}
-
-div.media-dialog-group-button {
- height: 1.5em;
- width: 10.5em;
- box-sizing: border-box;
- line-height: 1.5em;
- text-align: center;
- margin: 0em 0.2em 0.2em 0.2em;
- padding: 0px 0.5em;
- background-color: #414141;
- color: #3498db;
- border-radius: 3px;
- transition: all 0.1s linear;
- cursor: pointer;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-div.media-dialog-groups.small-screen div.media-dialog-group-button {
- display: inline-block;
-}
-
-div.media-dialog-group-button:HOVER {
- background-color: #515151;
-}
-
-div.media-dialog-group-button:ACTIVE {
- background-color: #414141;
-}
-
-div.media-dialog-group-button.current {
- background-color: #317CAD;
- color: white;
-}
-
-div.media-dialog-group-button.current:HOVER {
- background-color: #3498db;
-}
-
-div.media-dialog-group-button.current:ACTIVE {
- background-color: #317CAD;
-}
-
-div.media-dialog-list {
- overflow: auto;
- position: relative;
-}
-
-div.media-list-group-title {
- background-color: #313131;
- font-size: 1.3em;
- font-weight: bold;
- text-align: center;
- padding: 1em 0px 0.2em 0px;
-}
-
-img.media-list-progress {
- position: relative;
- top: 35%;
- display: block;
- margin: auto;
-}
-
-div.media-list-entry {
- height: 4em;
- background-color: #414141;
- border-bottom: 1px solid #313131;
- cursor: pointer;
- transition: background-color 0.1s linear;
-}
-
-div.media-list-entry:HOVER {
- background-color: #494949;
-}
-
-div.media-list-entry:ACTIVE {
- background-color: #3b3b3b;
-}
-
-img.media-list-preview {
- float: left;
- height: 3em;
- margin: 0.45em;
- border: 1px solid #212121;
- box-shadow: 1px 1px 6px rgba(0,0,0,0.3);
-}
-
-div.media-list-entry-name {
- font-weight: bold;
- font-size: 1.3em;
- padding: 0.4em 0em;
- text-align: center;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-div.media-list-entry-details {
- font-size: 1em;
- text-align: center;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
-div.media-list-entry-details span.details-moment-short {
- display: none;
-}
-
-div.media-list-download-button,
-div.media-list-delete-button {
- float: right;
- clear: right;
- height: 1.5em;
- width: 5em;
- line-height: 1.5em;
- text-align: center;
- margin: 0px 0.5em;
- padding: 0px 0.5em;
- color: white;
- border-radius: 3px;
- transition: all 0.1s linear;
-}
-
-div.media-list-download-button {
- margin-top: 0.4em;
- margin-bottom: 0.1em;
- background: #317CAD;
-}
-
-div.media-list-download-button:HOVER {
- background-color: #3498db;
-}
-
-div.media-list-download-button:ACTIVE {
- background-color: #317CAD;
-}
-
-div.media-list-delete-button {
- margin-top: 0.1em;
- margin-bottom: 0.4em;
- background: #c0392b;
-}
-
-div.media-list-delete-button:HOVER {
- background-color: #D43F2F;
-}
-
-div.media-list-delete-button:ACTIVE {
- background-color: #B03427;
-}
-
-div.media-dialog-buttons {
- margin: 0.5em 0px 0px 0px;
- text-align: center;
-}
-
-div.media-dialog-button {
- cursor: pointer;
- display: inline-block;
- height: 1.5em;
- line-height: 1.5em;
- text-align: center;
- padding: 0px 0.5em;
- margin: 0px 5px 0px 0px;
- color: white;
- background-color: #317CAD;
- border-radius: 3px;
- transition: all 0.1s linear;
-}
-
-div.media-dialog-button:HOVER {
- background-color: #3498db;
-}
-
-div.media-dialog-button:ACTIVE {
- background-color: #317CAD;
-}
-
-div.picture-dialog-content {
- position: relative;
- text-align: center;
- min-height: 100px;
-}
-
-div.picture-dialog-prev-arrow,
-div.picture-dialog-next-arrow {
- position: absolute;
- top: 45%;
- background-color: rgba(0, 0, 0, 0.6);
- background-image: url(../img/arrows.svg);
- background-size: cover;
- width: 3em;
- height: 3em;
- border-radius: 0.3em;
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
- cursor: pointer;
-}
-
-div.picture-dialog-prev-arrow {
- left: 1em;
-}
-
-div.picture-dialog-next-arrow {
- right: 1em;
- background-position: -100% 0%;
-}
-
-img.picture-dialog-content {
- border: 1px solid #292929;
-}
-
-img.picture-dialog-progress {
- position: absolute;
- background-color: #313131;
- padding: 10px;
- border-radius: 10px;
- opacity: 0.7;
-}
-
-table.timelapse-dialog select {
- width: 10em;
-}
-
-td.timelapse-warning {
- font-size: 80%;
- display: none;
- color: red;
- max-width: 20em;
- text-align: center;
- white-space: normal;
- padding-bottom: 1em;
-}
-
-div.media-dialog-delete-all-button {
- margin-top: 0.1em;
- margin-bottom: 0.4em;
- background: #c0392b;
-}
-
-div.media-dialog-delete-all-button:HOVER {
- background-color: #D43F2F;
-}
-
-div.media-dialog-delete-all-button:ACTIVE {
- background-color: #B03427;
-}
-
-td.login-dialog-error {
- color: red;
- display: none;
-}
-
-
- /* camera frames */
-
-div.camera-list {
- text-align: center;
-}
-
-div.camera-frame,
-div.camera-frame-place-holder {
- position: relative;
- width: 32%;
- text-align: left;
- background-color: #313131;
- display: inline-block;
- padding: 0px 3px 3px 3px;
- border-radius: 3px;
- transition: all 0.2s, opacity 0s;
- margin: 2px;
- opacity: 0;
- vertical-align: top;
-}
-
-div.camera-frame:only-child,
-div.camera-frame-place-holder:only-child {
- width: 48%;
-}
-
-div.camera-frame-place-holder {
- visibility: hidden;
-}
-
-div.camera-frame.motion-detected {
- background-color: #712727;
-}
-
-div.modal-container div.camera-frame {
- width: auto;
- padding: 0px;
- margin: -7px;
- background-color: #414141;
-}
-
-div.camera-frame:HOVER {
- background-color: #414141;
-}
-
-div.camera-frame.motion-detected:HOVER {
- background-color: #8B3636;
-}
-
-div.camera-top-bar {
- padding: 3px 0px;
- font-size: 20px;
- height: 25px;
-}
-
-div.modal-container div.camera-top-bar {
- display: none;
-}
-
-span.camera-name {
- float: left;
- line-height: 25px;
-}
-
-div.camera-buttons {
- float: right;
-}
-
-div.camera-button {
- display: inline-block;
- width: 24px;
- height: 24px;
- background-image: url(../img/top-bar-buttons.svg);
- background-size: cover;
- margin-left: 3px;
- cursor: pointer;
- transition: all 0.1s linear;
-}
-
-div.camera-button.close {
- background-position: 0px 0px;
-}
-
-div.camera-button.full-screen {
- background-position: -200% 0px;
-}
-
-div.camera-button.configure {
- background-position: -100% 0px;
-}
-
-div.camera-button.media-pictures {
- background-position: -300% 0px;
-}
-
-div.camera-button.media-movies {
- background-position: -400% 0px;
-}
-
-div.camera-container {
- position: relative;
- padding: 0px;
-}
-
-img.camera {
- position: relative;
- width: 100%;
- display: block;
- transition: opacity 0.2s linear;
- opacity: 1;
- min-height: 160px;
-}
-
-img.camera.error,
-img.camera.loading {
- opacity: 0;
-}
-
-div.camera-placeholder {
- position: absolute;
- top: 0px;
- right: 0px;
- bottom: 0px;
- left: 0px;
- text-align: center;
- transition: opacity 0.2s linear;
-}
-
-img.no-camera {
- margin-top: 20%;
- width: 30%;
- opacity: 0.8;
-}
-
-div.camera-progress {
- background: rgba(0, 0, 0, 0.001); /* otherwise IE would not extend this as expected */
- position: absolute;
- top: 0px;
- right: 0px;
- bottom: 0px;
- left: 0px;
- opacity: 0;
- transition: all 0.2s linear;
- text-align: center;
- cursor: pointer;
-}
-
-div.camera-progress.visible {
- opacity: 0.4;
-}
-
-img.camera-progress {
- border: 10px solid white;
- border-radius: 10px;
- position: absolute;
- top: 0px;
- left: 0px;
- bottom: 0px;
- right: 0px;
- margin: auto;
-}
-
-
- /* media queries */
-
-@media all and (max-width: 1440px) {
- /* smaller screens */
-
- body {
- font-size: 17px;
- }
-}
-
-@media all and (max-width: 1000px) {
- /* small screens (mobile devices) */
-
- div.page-container.stretched {
- margin-left: 0px;
- }
-
- div.settings.open {
- box-shadow: 0px 0px 10px rgba(0,0,0,0.5);
- background-color: rgba(40, 40, 40, 0.9);
- }
-
- div.hostname {
- display: none;
- }
-
- div.media-list-entry-name {
- font-size: 1em;
- padding: 0.2em 0em 0em 0em;
- }
-
- div.media-list-entry-details {
- padding-top: 0.2em;
- font-size: 1em;
- text-align: center;
- white-space: normal;
- }
-
- div.media-list-entry-details span.details-moment {
- display: none;
- }
-
- div.media-list-entry-details span.details-moment-short {
- display: block;
- }
-}
-
-@media all and (max-width: 400px) {
- /* very small screens */
-
- body {
- font-size: 13px;
- }
-
- div.camera-button {
- background-size: cover;
- width: 24px;
- height: 24px;
- }
-}
-
-@media all and (max-width: 1900px) {
- div.camera-frame,
- div.camera-frame-place-holder {
- width: 48%;
- }
-}
-
-@media all and (max-width: 1200px) {
- div.page-container {
- padding-left: 1%;
- }
-
- div.camera-frame,
- div.camera-frame-place-holder {
- width: 98%;
- }
-
- div.camera-frame:only-child,
- div.camera-frame-place-holder:only-child {
- width: 98%;
- }
-}
+++ /dev/null
-
- /* general */
-
-::selection,
-::-moz-selection,
-::-webkit-selection {
- background: #3498db;
-}
-
-option::selection,
-option::-moz-selection,
-option::-webkit-selection {
- background: transparent;
-}
-
-input[type=checkbox].styled {
- display: none;
-}
-
-a {
- color: #3498db;
- text-decoration: inherit;
- transition: color 0.1s ease;
- cursor: pointer;
-}
-
-a:HOVER {
-}
-
-a:ACTIVE {
- color: #317CAD;
-}
-
-
- /* buttons */
-
-div.button {
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
- cursor: pointer;
- display: inline-block;
-}
-
-div.button.dialog {
- background-color: #414141;
- min-width: 60px;
- height: 1.2em;
- line-height: 1.2em;
- text-align: center;
- padding: 0.2em 0.4em;
- border: 1px solid #317CAD;
- border-radius: 2px;
- color: white;
- transition: all 0.1s ease;
-}
-
-div.button.dialog.default {
- background-color: #317CAD;
-}
-
-div.button.mouse-effect {
- opacity: 0.7;
- transition: opacity 0.1s ease;
-}
-
-div.button.mouse-effect:HOVER {
- opacity: 1;
-}
-
-div.button.mouse-effect:ACTIVE {
- opacity: 0.7;
-}
-
-
- /* check box */
-
-div.check-box {
- display: inline-block;
- position: relative;
- width: 2.5em;
- height: 1em;
- border: 1px solid #317CAD;
- border-radius: 2px;
- color: #aaaaaa;
- vertical-align: middle;
- cursor: pointer;
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
- margin: 2px;
- transition: all 0.2s ease;
-}
-
-div.check-box:FOCUS,
-div.check-box:HOVER {
- border-color: #3498db;
-}
-
-div.check-box-button {
- width: 50%;
- height: 100%;
- left: 0px;
- background-color: #414141;
- color: #aaaaaa;
- position: absolute;
- text-align: center;
- line-height: 1em;
- transition: all 0.1s ease;
-}
-
-span.check-box-text {
- font-size: 0.5em;
- font-weight: bold;
- vertical-align: top;
-}
-
-div.check-box.on div.check-box-button {
- left: 50%;
- background-color: #317CAD;
- color: white;
-}
-
-div.check-box.on:FOCUS div.check-box-button,
-div.check-box.on:HOVER div.check-box-button {
- background-color: #3498db;
-}
-
-
- /* input boxes */
-
-input[type=password].styled,
-input[type=text].styled,
-textarea.styled {
- width: 90%;
- border: 1px solid #317CAD;
- border-radius: 2px;
- background-color: transparent;
- padding: 1px;
- color: #dddddd;
- font-family: inherit;
- font-size: 0.8em;
- margin: 2px;
- transition: all 0.1s ease;
- resize: none;
- vertical-align: middle;
- white-space: nowrap;
-}
-
-input[type=password].styled:FOCUS,
-input[type=text].styled:FOCUS {
- background-color: #414141;
-}
-
-input[type=password].styled:HOVER,
-input[type=password].styled:FOCUS,
-input[type=text].styled:HOVER,
-input[type=text].styled:FOCUS {
- border-color: #3498db;
- color: white;
-}
-
-input[type=text].number {
- width: 5em;
-}
-
-input[type=text].error,
-input[type=password].error,
-input[type=file].error,
-select.error {
- background-image: url(../img/validation-error.svg) !important;
- background-position: center right;
- background-repeat: no-repeat;
-}
-
-input[type=text].time {
- width: 3.5em;
-}
-
-input[readonly] {
- border: 1px solid #555 !important;
-}
-
-
- /* combo box */
-
-select.styled {
- -webkit-appearance: none;
- appearance: none;
- width: 90%;
- border: 1px solid #317CAD;
- border-radius: 2px;
- background-color: transparent;
- padding: 1px 1.25em 1px 1px;
- color: #dddddd;
- font-family: inherit;
- font-size: 0.8em;
- margin: 2px;
- background-image: url(../img/combo-box-arrow.svg);
- background-repeat: no-repeat;
- background-position: right center;
- cursor: pointer;
- vertical-align: middle;
-}
-
-select.styled:FOCUS {
- background-color: #414141;
- appearance: auto;
-}
-
-select.styled:HOVER,
-select.styled:FOCUS {
- border-color: #3498db;
- color: white;
-}
-
-.ff select.styled {
- background-image: none;
- padding-right: 1px;
-}
-
-
- /* slider */
-
-input[type=text].range {
- display: none;
-}
-
-div.slider {
- width: 82%;
- height: 1.7em;
- position: relative;
- padding: 0.2em 0px;
- margin-left: 5%;
-}
-
-div.slider-labels {
- position: relative;
- width: 100%;
- height: 0.5em;
-}
-
-span.slider-label {
- display: inline-block;
- width: 20%;
- text-align: center;
- overflow: visible;
- position: absolute;
- font-size: 0.5em;
-}
-
-div.slider-bar {
- position: relative;
- top: 7px;
- left: -5px;
- width: 100%;
-}
-
-div.slider-bar-inside {
- border: 1px solid #317CAD;
- height: 3px;
- position: relative;
- top: 3px;
- left: 7px;
- transition: all 0.1s ease;
-}
-
-div.slider:HOVER div.slider-bar-inside,
-div.slider:FOCUS div.slider-bar-inside {
- border-color: #3498db;
-}
-
-div.slider:FOCUS div.slider-bar-inside {
- background-color: #414141;
-}
-
-div.slider-cursor {
- background-image: url(../img/slider-arrow.svg);
- width: 11px;
- height: 18px;
- position: absolute;
- left: 0px;
- top: -5px;
- cursor: pointer;
-}
-
-div.slider-cursor-label {
- font-size: 0.6em;
- margin-left: 1.5em;
- margin-top: 0.15em;
- background: rgba(30, 30, 30, 0.8);
- vertical-align: top;
- padding: 1px 2px;
- border-radius: 3px;
- display: none;
-}
-
-
- /* progress bar */
-
-div.progress-bar-container {
- position: relative;
- height: 1em;
- border: 1px solid #555;
- vertical-align: middle;
- margin: 0px 0.2em;
- text-align: center;
- line-height: 1em;
-}
-
-div.progress-bar-fill {
- position: absolute;
- left: 0px;
- top: 0px;
- bottom: 0px;
- width: 0%;
- background-color: #555;
- transition: width 0.1s ease;
-}
-
-span.progress-bar-text {
- vertical-align: middle;
- font-size: 0.8em;
- position: relative;
-}
-
-
- /* modal dialogs */
-
-div.modal-glass {
- display: none;
- position: fixed;
- z-index: 10000;
- top: 0px;
- right: 0px;
- bottom: 0px;
- left: 0px;
- background-color: black;
- opacity: 0;
-}
-
-div.modal-container {
- position: fixed;
- display: none;
- z-index: 10001;
- background-color: #313131;
- border-radius: 3px;
- opacity: 0;
- padding: 5px;
- box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
-}
-
-div.modal-title-bar {
- min-height: 1.5em;
- line-height: 1.5em;
- text-align: center;
- position: relative;
-}
-
-span.modal-title {
- color: white;
- font-size: 1.2em;
- line-height: 1.2em;
-}
-
-div.modal-close-button {
- position: absolute;
- top: 0.2em;
- right: 0.3em;
- width: 1.1em;
- height: 1.1em;
- background-image: url(../img/top-bar-buttons.svg);
- background-size: cover;
- cursor: pointer;
-}
-
-table.modal-buttons-container {
- width: 100%;
- margin: auto;
- text-align: center;
- table-layout: fixed;
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
-}
-
-table.modal-buttons-container td:not(:FIRST-CHILD) {
- padding-left: 5px;
-}
-
-table.modal-buttons-container div.button.dialog {
- display: block;
-}
-
-div.modal-progress {
- border-radius: 10px;
- background-image: url(../img/modal-progress.gif);
- width: 64px;
- height: 64px;
- margin: auto;
-}
-
-td.dialog-item-label {
- text-align: right;
- padding-right: 5px;
-}
-
-td.dialog-item-value {
- text-align: left;
- padding-left: 5px;
-}
-
-span.dialog-item-label {
- font-size: 0.9em;
-}
-
-div.dialog-item-separator {
- height: 1px;
- border-top: 1px solid #414141;
- margin: 0.5em 1em;
-}
-
-
- /* popup message */
-
-div.popup-message-container {
- position: fixed;
- display: none;
- z-index: 10002;
- background-color: #313131;
- border-radius: 3px;
- opacity: 0;
- padding: 5px;
- box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
- top: 60px;
-}
-
-span.popup-message {
-
-}
-
-span.popup-message.info {
- color: white;
-}
-
-span.popup-message.error {
- color: #FF6D55;
-}
-
-
- /* media queries */
-
-@media all and (max-width: 400px) {
- span.modal-title {
- font-size: 1.5em;
-
- }
-
- div.modal-title-bar {
- min-height: 2em;
- line-height: 2em;
- }
-
- div.modal-close-button {
- background-size: cover;
- width: 24px;
- height: 24px;
- }
-}
+++ /dev/null
-<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
-<svg xmlns="http://www.w3.org/2000/svg">
-<metadata></metadata>
-<defs>
-<font id="maven_problack" horiz-adv-x="1159" >
-<font-face units-per-em="2048" ascent="1638" descent="-410" />
-<missing-glyph horiz-adv-x="692" />
-<glyph horiz-adv-x="2048" />
-<glyph horiz-adv-x="2048" />
-<glyph unicode="
" horiz-adv-x="682" />
-<glyph unicode=" " horiz-adv-x="692" />
-<glyph unicode="	" horiz-adv-x="692" />
-<glyph unicode=" " horiz-adv-x="692" />
-<glyph unicode="!" horiz-adv-x="882" d="M279 1366h354l-45 -975h-264zM297 0v262h317v-262h-317z" />
-<glyph unicode=""" horiz-adv-x="899" d="M166 1384h254l-35 -411h-182zM477 1384h254l-35 -411h-182z" />
-<glyph unicode="#" horiz-adv-x="1288" d="M152 248v186h176q4 27 19 110l24 132h-164v184h194l37 209h197l-37 -209h217q33 170 37 209h197l-39 -209h125v-184h-154q-4 -27 -20.5 -111t-22.5 -131h143v-186h-174q-6 -37 -19.5 -111t-17.5 -98h-196l37 209h-219q-8 -43 -35 -209h-197l39 209h-147zM524 434h221 l39 242h-217q-6 -47 -22.5 -131t-20.5 -111z" />
-<glyph unicode="$" horiz-adv-x="1212" d="M147 952q0 193 123 299.5t316 120.5v217h125v-215q168 -14 311 -92v-307q-162 96 -311 98v-244q88 -25 148.5 -50t129 -73.5t104 -129t35.5 -189.5q0 -164 -118.5 -272.5t-298.5 -128.5v-207h-125v203q-250 12 -422 133v329q189 -157 419 -157h3v243q-94 23 -154.5 45.5 t-134.5 68.5t-112 123t-38 185zM457 958q0 -57 129 -96v203q-129 -23 -129 -107zM711 299q120 27 120 101q0 60 -120 100v-201z" />
-<glyph unicode="%" horiz-adv-x="1890" d="M147 983q0 377 289 377q291 0 291 -377t-291 -377q-289 0 -289 377zM354 983q0 -145 82 -145q84 0 84 145q0 88 -22.5 116.5t-61.5 28.5q-82 0 -82 -145zM387 0l858 1366h234l-858 -1366h-234zM1141 383q0 377 289 377q291 0 291 -376q-1 -378 -291 -378q-289 0 -289 377 zM1346 383q0 -145 84 -145q82 0 82 143q0 147 -82 147q-84 0 -84 -145z" />
-<glyph unicode="&" horiz-adv-x="1337" d="M113 397q0 113 45 207t192 189q0 2 -9 13l-15 20q-7 8 -18 24t-19 30.5t-17.5 36t-15.5 41t-10 43t-4 46.5q0 137 86 232t247 95q160 0 249 -91t89 -239q0 -111 -47 -179t-161 -140l22 -27q14 -18 59 -70l107 -126l31 131h235q-32 -195 -104 -328l254 -305h-308l-92 117 q-147 -133 -352 -133q-115 0 -204 41t-139 105.5t-75.5 133t-25.5 133.5zM342 391q0 -23 9 -51.5t32 -60t69 -53t109 -21.5q119 0 207 84l-268 323q-57 -31 -92 -63.5t-48.5 -64t-15.5 -48t-2 -45.5zM469 1049q0 -41 82 -144q82 47 108.5 80t26.5 72q0 43 -31.5 73.5 t-77 30.5t-77 -32.5t-31.5 -79.5z" />
-<glyph unicode="'" horiz-adv-x="583" d="M166 1384h254l-35 -411h-182z" />
-<glyph unicode="(" horiz-adv-x="796" d="M190 627q0 440 211 856h258q-210 -414 -210 -852q0 -440 210 -862h-258q-211 422 -211 858z" />
-<glyph unicode=")" horiz-adv-x="796" d="M133 -231q211 422 211 858q0 440 -211 856h258q211 -416 211 -856q0 -436 -211 -858h-258z" />
-<glyph unicode="*" horiz-adv-x="845" d="M172 1133l158 57l-158 57l78 139l110 -79l-12 131h160l-10 -131l108 79l78 -139l-156 -57l156 -57l-78 -140l-108 80l10 -131h-160l12 131l-110 -80z" />
-<glyph unicode="+" horiz-adv-x="1128" d="M190 498v229h259v258h229v-258h256v-229h-256v-256h-229v256h-259z" />
-<glyph unicode="," horiz-adv-x="751" d="M152 -229l151 401h285l-197 -401h-239z" />
-<glyph unicode="-" horiz-adv-x="950" d="M162 514v240h624v-240h-624z" />
-<glyph unicode="." horiz-adv-x="811" d="M260 0v283h295v-283h-295z" />
-<glyph unicode="/" horiz-adv-x="948" d="M-29 -201l752 1749h256l-754 -1749h-254z" />
-<glyph unicode="0" horiz-adv-x="1345" d="M119 684q0 700 553 700t553 -700t-553 -700t-553 700zM473 684q0 -229 55.5 -312t143.5 -83q90 0 146 83t56 312q0 399 -202 399q-199 0 -199 -399z" />
-<glyph unicode="1" horiz-adv-x="1040" d="M211 840v346l285 180h288v-1366h-352v975z" />
-<glyph unicode="2" horiz-adv-x="1269" d="M158 0v279q579 433 579 642q0 62 -45 100t-133 38q-180 0 -391 -168v356q223 137 424 137q254 0 373.5 -128t119.5 -308q0 -158 -116.5 -325.5t-272.5 -305.5h398v-317h-936z" />
-<glyph unicode="3" horiz-adv-x="1228" d="M139 98v338q229 -137 402 -137q88 0 144 41t56 102.5t-47 105.5t-159 44h-224v254h228q72 0 107.5 32.5t35.5 79.5q0 49 -55.5 76t-126.5 27h-28h-30q-8 0 -28.5 -1t-32.5 -3t-33.5 -5t-39.5 -8l-42 -13q-24 -7 -50 -17t-52 -23v297q182 96 375 96q88 0 170 -18 t158.5 -58t122.5 -117t46 -181q0 -96 -65.5 -174.5t-147.5 -94.5q121 -35 195 -131t74 -227q0 -117 -49.5 -199t-133.5 -124t-170 -59t-184 -17q-190 0 -416 114z" />
-<glyph unicode="4" horiz-adv-x="1265" d="M80 348v270l598 748h307v-719h164v-299h-164v-348h-350v348h-555zM455 647h180v240z" />
-<glyph unicode="5" horiz-adv-x="1280" d="M162 707l74 661h806v-315h-499l-11 -119q31 4 99 4q229 0 356 -125t127 -385q0 -127 -41 -218t-112.5 -138t-151.5 -67.5t-174 -20.5q-213 0 -442 114v334q213 -135 395 -135q88 0 132 49t44 113q0 82 -45 124t-117 42h-17q-136 0 -229 -60z" />
-<glyph unicode="6" horiz-adv-x="1234" d="M111 630q0 196 49 341t117.5 221t163 122t165 58t142.5 12q176 0 278 -49v-297q-145 45 -250 45q-115 0 -211 -51t-110 -158q67 27 190 27q96 0 170 -15.5t148.5 -55t115.5 -129t41 -220.5q0 -90 -27.5 -175t-85 -158.5t-158.5 -118.5t-233 -45q-505 0 -505 646zM461 629 q0 -160 43 -247q46 -95 112 -95q61 0 98.5 45t44.5 83t7 68q0 78 -38 137.5t-116 59.5q-98 0 -151 -23v-28z" />
-<glyph unicode="7" horiz-adv-x="1226" d="M135 1016v350h1004v-258l-537 -1108h-407l495 1016h-555z" />
-<glyph unicode="8" horiz-adv-x="1261" d="M119 397q0 123 78.5 215.5t193.5 122.5q-88 25 -153.5 98.5t-65.5 174.5q0 376 459 376h2q459 0 459 -376q0 -100 -65 -175t-153 -98q113 -31 193 -123t80 -215q0 -205 -135 -309t-379 -104t-379 104.5t-135 308.5zM473 442q0 -74 40 -122t120 -48q78 0 118 48.5 t40 121.5q0 51 -41 93.5t-117 42.5q-78 0 -119 -42t-41 -94zM514 979q0 -53 37 -83t82 -30q43 0 80 30t37 83q0 43 -31 80t-86 37q-57 0 -88 -37t-31 -80z" />
-<glyph unicode="9" horiz-adv-x="1259" d="M111 887q0 90 27.5 175t85 158.5t158.5 118.5t232 45q506 0 506 -649q0 -193 -49 -338t-117.5 -221t-163 -122t-165 -58t-142.5 -12q-176 0 -278 49v297q145 -45 250 -45q115 0 211 51t110 158q-67 -27 -190 -27q-96 0 -170 15.5t-148.5 55.5t-115.5 129t-41 220z M465 885q0 -78 38 -137.5t115 -59.5q98 0 152 23v28q0 159 -43 247q-47 95 -113 95q-61 0 -98 -45t-44 -83t-7 -68z" />
-<glyph unicode=":" horiz-adv-x="868" d="M281 72v282h294v-282h-294zM281 670v282h294v-282h-294z" />
-<glyph unicode=";" horiz-adv-x="913" d="M166 -229l151 401h285l-196 -401h-240zM309 670v282h295v-282h-295z" />
-<glyph unicode="<" horiz-adv-x="1368" d="M246 455v186l862 383v-279l-504 -196l504 -199v-278z" />
-<glyph unicode="=" horiz-adv-x="1306" d="M250 285v239h805v-239h-805zM250 698v240h805v-240h-805z" />
-<glyph unicode=">" horiz-adv-x="1351" d="M238 72v278l503 199l-503 196v279l862 -383v-186z" />
-<glyph unicode="?" horiz-adv-x="1075" d="M164 1024v262q59 41 155.5 68.5t196.5 27.5q115 0 200 -33.5t132 -92t69.5 -126t22.5 -145.5q0 -70 -22.5 -126t-56.5 -92l-73 -74l-72 -73q-34 -34 -56.5 -87.5t-22.5 -118.5v-21h-281v27q0 104 31 184t75 127t89 86t75.5 81t30.5 89q0 45 -15 76t-46 43t-58.5 16 t-66.5 4q-180 0 -307 -102zM338 0v262h317v-262h-317z" />
-<glyph unicode="@" horiz-adv-x="1869" d="M166 373q0 182 79 346t203.5 274.5t280 175t309.5 64.5q106 0 220 -38t220.5 -112.5t175 -211t68.5 -308.5q0 -207 -92 -362.5t-268 -155.5q-68 0 -114 41t-64 90q-31 -57 -103.5 -94t-175.5 -37q-156 0 -246 70.5t-90 193.5q0 53 17.5 100.5t63.5 95.5t143.5 76.5 t238.5 28.5q35 0 105 -4q-2 66 -35 99.5t-129 33.5q-162 0 -285 -45l29 178q168 53 274 54q340 0 340 -308q0 -49 -15.5 -151t-15.5 -160q0 -123 82 -123q70 0 139.5 99.5t69.5 279.5q0 162 -88 288t-213 186.5t-258 60.5q-287 0 -509 -218t-222 -513q0 -125 44 -226.5 t107.5 -158t142.5 -94.5t138.5 -51t102.5 -13q258 0 469 104l-23 -149q-184 -86 -444 -86q-106 0 -217 38t-216.5 113.5t-172 213t-66.5 315.5zM766 313q0 -71 139 -71q111 0 154 46t53 158q-27 2 -80 3q-266 0 -266 -136z" />
-<glyph unicode="A" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM612 588h277l-137 424z" />
-<glyph unicode="B" horiz-adv-x="1349" d="M178 0v1366h463q541 0 541 -383q0 -66 -21.5 -116t-59.5 -79.5t-64.5 -43t-61.5 -25.5q274 -78 274 -342q0 -377 -567 -377h-504zM530 289h201h7h4q67 0 114 35q51 38 51 114t-57 111t-125 35h-195v-295zM530 831h181q49 0 89 31t40 84q0 70 -46 101.5t-104 31.5h-160 v-248z" />
-<glyph unicode="C" horiz-adv-x="1314" d="M125 684q0 207 57.5 351.5t155.5 216t201.5 101t226.5 29.5q197 0 438 -96v-305q-205 98 -418 98q-141 0 -225 -86t-84 -309t84 -309t225 -86q213 0 418 98v-305q-242 -96 -438 -96q-123 0 -226.5 29.5t-201.5 101t-155.5 216t-57.5 351.5z" />
-<glyph unicode="D" horiz-adv-x="1443" d="M178 0v1366h492q317 0 483 -174t166 -510q0 -334 -166 -508t-483 -174h-492zM530 289h140q297 0 297 393q0 397 -297 397h-140v-790z" />
-<glyph unicode="E" horiz-adv-x="1247" d="M178 0v1366h952v-287h-600v-252h521v-288h-521v-250h600v-289h-952z" />
-<glyph unicode="F" horiz-adv-x="1187" d="M178 0v1366h928v-287h-576v-276h498v-289h-498v-514h-352z" />
-<glyph unicode="G" horiz-adv-x="1427" d="M125 682q0 207 57.5 351.5t155.5 216t201.5 101t226.5 29.5q244 0 471 -102v-283q-207 84 -418 84q-344 0 -344 -395q0 -223 90 -309t242 -86q119 0 188 30v226h-243v262h524v-690q-223 -131 -510 -131q-96 0 -180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293z" />
-<glyph unicode="H" horiz-adv-x="1503" d="M180 0v1366h352v-539h439v539h352v-1366h-352v539h-439v-539h-352z" />
-<glyph unicode="I" horiz-adv-x="708" d="M180 0v1366h352v-1366h-352z" />
-<glyph unicode="J" horiz-adv-x="1060" d="M59 92v326q141 -129 293 -129q72 0 117 41t45 125v911h352v-889q0 -123 -32.5 -215t-79.5 -143t-114 -83t-122 -41t-115 -9q-176 0 -344 106z" />
-<glyph unicode="K" horiz-adv-x="1505" d="M178 0v1366h352v-481l375 481h475l-536 -594l575 -772h-444l-348 532l-97 -108v-424h-352z" />
-<glyph unicode="L" horiz-adv-x="1179" d="M178 0v1366h352v-1049h607v-317h-959z" />
-<glyph unicode="M" horiz-adv-x="1695" d="M180 0v1366h346l322 -465l321 465h347v-1366h-353v846l-315 -453l-316 453v-846h-352z" />
-<glyph unicode="N" horiz-adv-x="1619" d="M178 0v1366h352l555 -819v819h353v-1366h-348l-560 813v-813h-352z" />
-<glyph unicode="O" horiz-adv-x="1538" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293t-39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16t-180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM477 684q0 -223 79 -309t210 -86 t210 86t79 309t-79 309t-210 86t-210 -86t-79 -309z" />
-<glyph unicode="P" horiz-adv-x="1337" d="M176 0v1366h553q248 0 385 -126t137 -353q0 -229 -137 -355t-385 -126h-201v-406h-352zM528 696h168q106 0 155.5 56.5t49.5 134.5q0 76 -49 134t-156 58h-168v-383z" />
-<glyph unicode="Q" horiz-adv-x="1529" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293q0 -604 -494 -684q8 -162 213 -162v-227q-500 0 -505 387q-496 80 -496 686zM477 684q0 -223 79 -309t210 -86t210 86t79 309t-79 309t-210 86t-210 -86 t-79 -309z" />
-<glyph unicode="R" horiz-adv-x="1396" d="M178 0v1366h539q250 0 400.5 -116.5t150.5 -327.5q0 -162 -75 -266.5t-200 -145.5l330 -510h-393l-254 457h-146v-457h-352zM530 721h209q72 0 124 50t52 128q0 80 -52 130t-124 50h-209v-358z" />
-<glyph unicode="S" horiz-adv-x="1232" d="M127 954q0 211 145.5 317.5t360.5 106.5q195 0 368 -94v-307q-166 98 -317 98q-80 0 -142.5 -26.5t-62.5 -87.5q0 -39 46 -65t117 -39t151.5 -43t151.5 -73t117 -133t46 -219q0 -180 -143.5 -292.5t-348.5 -112.5q-281 0 -473 133v329q189 -157 419 -157h3 q78 0 134.5 29.5t56.5 84.5q0 37 -35 63t-91 41l-122 31q-66 16 -133.5 45t-122.5 71t-90 119t-35 181z" />
-<glyph unicode="T" horiz-adv-x="1191" d="M25 1047v319h1142v-319h-397v-1047h-352v1047h-393z" />
-<glyph unicode="U" horiz-adv-x="1533" d="M178 588v778h352v-819q0 -131 56.5 -194.5t175.5 -63.5t175 63.5t56 194.5v819h353v-778q0 -287 -154 -444.5t-430.5 -157.5t-430 157.5t-153.5 444.5z" />
-<glyph unicode="V" horiz-adv-x="1413" d="M25 1366h376l306 -967l305 967h377l-521 -1366h-319z" />
-<glyph unicode="W" horiz-adv-x="2045" d="M27 1366h368l234 -862l243 862h304l243 -862l234 862h368l-440 -1366h-321l-236 782l-236 -782h-321z" />
-<glyph unicode="X" horiz-adv-x="1443" d="M25 0l485 692l-475 674h430l258 -369l260 369h403l-462 -653l503 -713h-430l-288 410l-289 -410h-395z" />
-<glyph unicode="Y" horiz-adv-x="1492" d="M25 1366h428l290 -520l291 520h428l-542 -791v-575h-353v575z" />
-<glyph unicode="Z" horiz-adv-x="1337" d="M133 0v291l617 762h-611v313h1047v-285l-598 -772h598v-309h-1053z" />
-<glyph unicode="[" horiz-adv-x="849" d="M244 -238v1663h436v-247h-209v-1168h209v-248h-436z" />
-<glyph unicode="\" horiz-adv-x="880" d="M-76 1548h256l752 -1749h-254z" />
-<glyph unicode="]" horiz-adv-x="849" d="M172 10h209v1168h-209v247h436v-1663h-436v248z" />
-<glyph unicode="^" horiz-adv-x="1210" d="M246 958l282 455h148l289 -455h-230l-131 244l-131 -244h-227z" />
-<glyph unicode="_" horiz-adv-x="1118" d="M0 -49h1118v-209h-1118v209z" />
-<glyph unicode="`" horiz-adv-x="571" d="M92 1393h240l127 -240h-176z" />
-<glyph unicode="a" horiz-adv-x="1142" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM459 362q0 -108 98 -108h2q49 0 76 39t27 82v86 q-49 4 -68 4q-135 0 -135 -103z" />
-<glyph unicode="b" horiz-adv-x="1230" d="M160 348v1065h350v-383q29 4 133 4q223 0 344 -133t121 -383q0 -534 -502 -534h-2q-137 0 -229 30.5t-136 87t-61.5 114.5t-17.5 132zM510 287q0 -29 26.5 -51.5t81.5 -22.5q72 0 105 90t33 227q0 133 -45 209t-133 76q-18 0 -68 -4v-524z" />
-<glyph unicode="c" horiz-adv-x="993" d="M119 510q0 254 126 390t361 136q178 0 322 -69v-262q-139 63 -258 63q-90 0 -144.5 -64.5t-54.5 -193.5t54.5 -193.5t144.5 -64.5q119 0 258 63v-262q-141 -69 -317 -69h-5q-236 0 -361.5 136t-125.5 390z" />
-<glyph unicode="d" horiz-adv-x="1222" d="M129 518q0 250 121 383t344 133q104 0 133 -4v383h350v-1065q0 -74 -17.5 -132t-61.5 -114.5t-136 -87t-229 -30.5q-504 0 -504 534zM481 530q0 -137 32.5 -227t104.5 -90q55 0 82 22.5t27 51.5v524q-49 4 -68 4q-88 0 -133 -75.5t-45 -209.5z" />
-<glyph unicode="e" horiz-adv-x="1169" d="M131 502q0 244 113.5 390t355.5 146q229 0 347 -144t118 -353q0 -61 -6 -111h-582q0 -72 67.5 -121t151.5 -49q188 0 322 80v-248q-166 -106 -402 -106q-68 0 -129 11t-128.5 46t-115.5 89t-80 149.5t-32 220.5zM485 641h230q-4 59 -35 116.5t-80 57.5t-80 -53t-35 -121z " />
-<glyph unicode="f" horiz-adv-x="776" d="M82 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-207v-774h-352v774h-141z" />
-<glyph unicode="g" horiz-adv-x="1153" d="M127 516q0 520 491 520q117 0 202 -30.5t128 -84t62.5 -112.5t19.5 -131v-637q0 -51 -10 -102.5t-42 -115t-82 -110.5t-139 -79.5t-204 -32.5q-156 0 -322 53v250q115 -66 252 -66q102 0 145.5 48t49.5 130q-53 -4 -98 -4q-211 0 -332 131t-121 373zM479 510 q0 -127 40 -209t110 -82q24 0 51 6v490q0 80 -80 80q-57 0 -89 -81t-32 -204z" />
-<glyph unicode="h" horiz-adv-x="1316" d="M207 0v1411h352v-387q74 12 174 12q213 0 333 -117.5t120 -316.5v-602h-352v657q0 113 -181 113q-55 0 -94 -14v-756h-352z" />
-<glyph unicode="i" horiz-adv-x="735" d="M193 1139v268h360v-268h-360zM197 0v1022h352v-1022h-352z" />
-<glyph unicode="j" horiz-adv-x="667" d="M6 -141q168 0 168 125v1038h352v-1008q0 -119 -40 -201.5t-114.5 -126.5t-162.5 -62.5t-203 -18.5v254zM170 1139v268h360v-268h-360z" />
-<glyph unicode="k" horiz-adv-x="1318" d="M178 0v1405h352v-631l215 248h451l-416 -451l461 -571h-426l-250 330l-35 -37v-293h-352z" />
-<glyph unicode="l" horiz-adv-x="761" d="M207 0v1411h352v-1411h-352z" />
-<glyph unicode="m" horiz-adv-x="1939" d="M203 0v930l14 6l42 16q26 10 60.5 20.5t78.5 22t92.5 20.5t105.5 15t113 6q147 0 249 -59q175 59 355 59q215 0 337 -110.5t122 -323.5v-602h-353v616q0 158 -141 158q-80 0 -141 -20q26 -77 26 -147v-5v-602h-352v621q0 149 -131 149h-2q-55 0 -123 -18v-752h-352z" />
-<glyph unicode="n" horiz-adv-x="1325" d="M178 0v913q246 123 508 123q217 0 344 -115.5t127 -318.5v-602h-352v657q0 31 -8.5 51.5t-48 41t-115.5 20.5q-55 0 -103 -12v-758h-352z" />
-<glyph unicode="o" d="M92 510q0 254 128 389t363.5 135t363.5 -135t128 -389t-128 -389t-363.5 -135t-363.5 135t-128 389zM444 510q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270z" />
-<glyph unicode="p" horiz-adv-x="1232" d="M170 -379v1051q0 74 17.5 132t61.5 114.5t136 87t229 30.5q504 0 504 -534q0 -250 -121 -383t-344 -133q-104 0 -133 4v-369h-350zM520 209q49 -4 68 -4q88 0 133 75.5t45 208.5q0 137 -32.5 227.5t-104.5 90.5q-55 0 -82 -22.5t-27 -51.5v-524z" />
-<glyph unicode="q" horiz-adv-x="1228" d="M115 502q0 534 501 534h2q137 0 229.5 -30.5t136.5 -87t61.5 -115t17.5 -131.5v-1051h-350v369q-29 -4 -133 -4q-223 0 -344 133t-121 383zM467 489q0 -133 45 -208.5t133 -75.5q18 0 68 4v524q0 29 -27 51.5t-82 22.5q-72 0 -104.5 -90t-32.5 -228z" />
-<glyph unicode="r" horiz-adv-x="813" d="M164 0v922q264 109 584 116v-295q-127 -2 -232 -18v-725h-352z" />
-<glyph unicode="s" horiz-adv-x="962" d="M90 694q0 166 115 254t293 88q156 0 293 -84v-233q-133 74 -236 74q-111 0 -111 -70q0 -20 45.5 -40.5t110 -46.5t128 -61.5t108.5 -105.5t45 -164q0 -141 -113 -230t-293 -89q-190 0 -346 100v272q160 -125 309 -125q92 0 92 56q0 27 -45 48t-109.5 45t-130 57.5 t-110.5 99t-45 155.5z" />
-<glyph unicode="t" horiz-adv-x="839" d="M49 786v236h139v303h353v-303h213v-236h-213v-495q0 -23 18.5 -39t44.5 -16q63 0 150 51v-252q-88 -49 -205 -49q-178 0 -269.5 94t-91.5 264v442h-139z" />
-<glyph unicode="u" horiz-adv-x="1280" d="M172 461v561h352v-623q0 -150 122 -150l1 1q123 0 123 149v623h352v-561q0 -475 -475 -475t-475 475z" />
-<glyph unicode="v" horiz-adv-x="1224" d="M66 1022h374l170 -629l172 629h377l-391 -1022h-311z" />
-<glyph unicode="w" horiz-adv-x="1763" d="M82 1022h373l133 -616l162 616h262l162 -616l133 616h372l-362 -1022h-309l-127 459l-127 -459h-310z" />
-<glyph unicode="x" horiz-adv-x="1249" d="M53 0l363 518l-353 504h414l148 -207l145 207h389l-350 -479l387 -543h-416l-172 252l-166 -252h-389z" />
-<glyph unicode="y" horiz-adv-x="1169" d="M63 1022h365l156 -588l153 588h363l-373 -1002q-90 -231 -177 -327t-212 -96q-133 0 -223 43v256q70 -25 135 -25q47 0 92 56.5t78 170.5z" />
-<glyph unicode="z" horiz-adv-x="1030" d="M94 0v289l424 461h-414v272h805v-285l-381 -444h381v-293h-815z" />
-<glyph unicode="{" horiz-adv-x="804" d="M186 485v238q4 0 11.5 2t25 10t30.5 19.5t24.5 33t11.5 48.5v270q0 145 81 235.5t242 90.5h62v-236h-45q-45 0 -68.5 -22.5t-27 -45t-3.5 -67.5v-238q0 -94 -56 -147t-138 -72q82 -18 138 -71.5t56 -147.5v-238q0 -45 3.5 -67.5t27 -45t68.5 -22.5h45v-235h-62 q-162 0 -242.5 90t-80.5 235v271q0 41 -25.5 69.5t-50.5 36.5z" />
-<glyph unicode="|" horiz-adv-x="1005" d="M379 -203v1569h270v-1569h-270z" />
-<glyph unicode="}" horiz-adv-x="804" d="M156 12h45q45 0 68.5 22.5t26.5 45t3 67.5v238q0 94 56.5 147.5t138.5 71.5q-82 18 -138.5 71.5t-56.5 147.5v238q0 45 -3 67.5t-26.5 45t-68.5 22.5h-45v236h61q162 0 243 -90.5t81 -235.5v-270q0 -41 25.5 -70t51.5 -37l25 -6v-238q-4 0 -11 -2t-24.5 -10t-31 -19.5 t-24.5 -33t-11 -47.5v-271q0 -145 -81 -235t-243 -90h-61v235z" />
-<glyph unicode="~" horiz-adv-x="1071" d="M201 457v186q57 68 129 74q11 1 23 1q59 0 116 -26l135 -60q57 -25 115 -25q12 0 23 1q71 6 128 74v-186q-57 -68 -128 -74q-11 -1 -23 -1q-58 0 -115 25l-135 60q-56 26 -115 26q-12 0 -24 -2q-72 -6 -129 -73z" />
-<glyph unicode="¡" horiz-adv-x="882" d="M270 -344l45 975h265l45 -975h-355zM289 760v262h317v-262h-317z" />
-<glyph unicode="¢" horiz-adv-x="1007" d="M117 682q0 254 126 390t361 136h29v158h131v-174q90 -16 162 -53v-262q-90 43 -162 55v-500q70 14 162 55v-262q-74 -35 -162 -53v-172h-131v156h-29q-236 0 -361.5 136t-125.5 390zM469 682q0 -115 45 -178.5t119 -75.5v510q-164 -29 -164 -256z" />
-<glyph unicode="£" horiz-adv-x="1153" d="M100 -31v307q80 55 158 74v203h-158v268h158v168q0 162 105.5 277.5t328.5 115.5q162 0 346 -106v-281q-141 119 -299 119q-90 0 -144 -35t-54 -104v-154h340v-268h-340v-211q117 -53 227 -53q141 0 270 86v-297q-82 -76 -181 -90q-35 -6 -70 -6q-62 0 -123 17l-187 51 q-60 17 -122 17q-36 0 -73 -5q-100 -15 -182 -93z" />
-<glyph unicode="¥" horiz-adv-x="1492" d="M37 1366h428l291 -520l291 520h428l-408 -594h137v-201h-272v-155h272v-201h-272v-215h-352v215h-273v201h273v155h-273v201h137z" />
-<glyph unicode="§" horiz-adv-x="1247" d="M127 707q0 98 91 145t192 49q-43 4 -95.5 60.5t-52.5 121.5q0 86 39 148.5t106.5 94.5t141.5 45t160 13q180 0 362 -104v-248q-160 92 -311 92q-170 0 -170 -80q0 -31 84 -63.5t184 -62t184 -100.5t84 -171q0 -61 -48 -100t-94 -50.5t-93 -13.5q49 -27 74.5 -87 t25.5 -122q0 -137 -119.5 -213.5t-328.5 -76.5q-213 0 -410 131v276q171 -153 361 -153h2q66 0 120 17t54 58q0 27 -56.5 49.5t-136.5 46t-158.5 56.5t-135 95.5t-56.5 146.5zM453 715q0 -18 58 -43t210 -76q78 14 78 45q0 18 -49.5 38.5t-123 43t-89.5 29.5 q-84 -10 -84 -37z" />
-<glyph unicode="¨" horiz-adv-x="899" d="M141 1139v223h246v-223h-246zM489 1139v223h246v-223h-246z" />
-<glyph unicode="©" horiz-adv-x="1593" d="M98 682q0 291 205 495.5t494 204.5q291 0 495.5 -204.5t204.5 -495.5q0 -289 -205 -493.5t-495 -204.5q-289 0 -494 204.5t-205 493.5zM227 682q0 -236 167 -402.5t402.5 -166.5t403.5 166.5t168 402.5q0 238 -167 404.5t-404 166.5q-236 0 -403 -166.5t-167 -404.5z M430 684q0 121 34 206t91 126t117.5 58.5t132.5 17.5q117 0 258 -56v-178q-123 57 -246 57q-180 0 -180 -231t180 -231q123 0 246 57v-178q-139 -57 -258 -58q-72 0 -132.5 17.5t-117.5 59.5t-91 127t-34 206z" />
-<glyph unicode="ª" horiz-adv-x="833" d="M154 970q0 24 8 48t31.5 55t79 50.5t137.5 19.5h51q0 86 -78 86q-80 0 -174 -35v147q102 41 188 41q125 0 195 -64.5t70 -182.5v-136q0 -106 -57.5 -155t-205.5 -49q-245 0 -245 175zM350 1006q0 -61 58 -62q27 0 42 22.5t15 45.5v49q-8 0 -20.5 1t-18.5 1q-76 0 -76 -57 z" />
-<glyph unicode="«" horiz-adv-x="1347" d="M70 522l346 389h338l-373 -389l373 -391h-338zM565 522l346 389h338l-372 -389l372 -391h-338z" />
-<glyph unicode="¬" horiz-adv-x="1300" d="M199 465v219h903v-389h-211v170h-692z" />
-<glyph unicode="­" horiz-adv-x="950" d="M162 514v240h624v-240h-624z" />
-<glyph unicode="®" horiz-adv-x="1593" d="M100 682q0 291 205 495.5t494 204.5q291 0 495.5 -204.5t204.5 -495.5q0 -289 -204.5 -493.5t-495.5 -204.5q-289 0 -494 204.5t-205 493.5zM229 682q0 -236 167 -402.5t402.5 -166.5t403.5 166.5t168 402.5q0 238 -167 404.5t-404 166.5q-236 0 -403 -166.5t-167 -404.5 zM498 285v796h315q145 0 233.5 -67.5t88.5 -190.5q0 -94 -44 -155t-118 -84l192 -299h-229l-148 266h-83v-266h-207zM705 707h120q41 0 72 28.5t31 73.5q0 47 -31 76.5t-72 29.5h-120v-208z" />
-<glyph unicode="¯" horiz-adv-x="825" d="M176 1139v180h479v-180h-479z" />
-<glyph unicode="°" horiz-adv-x="792" d="M168 1143q0 111 57.5 169t166 58t165.5 -58.5t57 -168.5q0 -109 -57 -167t-165.5 -58t-166 58t-57.5 167zM293 1145q0 -57 23.5 -83t74.5 -26t73.5 25.5t22.5 83t-22.5 82t-73.5 24.5q-98 0 -98 -106z" />
-<glyph unicode="±" horiz-adv-x="1198" d="M236 12v215h743v-215h-743zM236 608v230h258v258h229v-258h256v-230h-256v-256h-229v256h-258z" />
-<glyph unicode="²" horiz-adv-x="696" d="M102 948v150q311 231 312 346q0 74 -97 74q-98 0 -211 -91v193q121 74 230 74q137 0 201.5 -69t64.5 -167q0 -154 -211 -340h215v-170h-504z" />
-<glyph unicode="³" horiz-adv-x="686" d="M100 999v183q121 -74 215 -74q49 0 79 21.5t30 54.5q0 82 -111 82h-120v135h122q76 0 76 61q0 27 -29.5 41.5t-66.5 14.5q-98 0 -180 -39v159q100 53 200 54q106 0 186.5 -47.5t80.5 -155.5q0 -51 -36 -93t-79 -50q66 -18 105.5 -70.5t39.5 -122.5q0 -119 -86 -167 t-202 -48q-96 0 -224 61z" />
-<glyph unicode="´" horiz-adv-x="559" d="M121 1139l127 239h239l-190 -239h-176z" />
-<glyph unicode="µ" horiz-adv-x="1325" d="M203 -430v1452h352v-657q0 -31 8 -51.5t48 -41t116 -20.5q55 0 102 12v758h353v-913q-246 -123 -508 -123q-57 0 -119 10v-426h-352z" />
-<glyph unicode="¶" horiz-adv-x="1083" d="M80 870q0 240 143.5 368t413.5 128h354v-1366h-274v371h-80q-270 0 -413.5 129t-143.5 370z" />
-<glyph unicode="·" horiz-adv-x="741" d="M215 420v282h295v-282h-295z" />
-<glyph unicode="¸" horiz-adv-x="585" d="M106 -250l191 240h176l-127 -240h-240z" />
-<glyph unicode="¹" horiz-adv-x="512" d="M92 1401v186l154 96h155v-735h-190v525z" />
-<glyph unicode="º" horiz-adv-x="868" d="M152 1090q0 143 71.5 218.5t204.5 75.5q131 0 202.5 -75.5t71.5 -218.5q0 -141 -71.5 -217t-202.5 -76q-133 0 -204.5 75.5t-71.5 217.5zM348 1090q0 -150 80 -150q78 0 78 150q0 151 -77 151h-1q-80 0 -80 -151z" />
-<glyph unicode="»" horiz-adv-x="1374" d="M106 131l373 391l-373 389h338l347 -389l-347 -391h-338zM602 131l373 391l-373 389h338l346 -389l-346 -391h-338z" />
-<glyph unicode="¼" horiz-adv-x="1476" d="M92 1098v186l154 96h155v-735h-190v524zM170 -45l895 1448h192l-895 -1448h-192zM840 186v146l321 403h166v-387h88v-162h-88v-186h-188v186h-299zM1040 348h99v129z" />
-<glyph unicode="½" horiz-adv-x="1550" d="M92 1098v186l154 96h155v-735h-190v524zM170 -45l895 1448h192l-895 -1448h-192zM946 0v150q311 231 311 346q0 73 -93 73h-3q-98 0 -211 -90v193q119 73 227 73h3q137 0 201.5 -68.5t64.5 -166.5q0 -154 -211 -340h215v-170h-504z" />
-<glyph unicode="¾" horiz-adv-x="1757" d="M137 696v183q121 -74 215 -74q49 0 79 21.5t30 54.5q0 82 -111 82h-121v135h123q76 0 76 61q0 27 -29.5 41t-66.5 14q-98 0 -180 -38v159q100 53 200 54q106 0 186 -47.5t80 -155.5q0 -51 -35.5 -93.5t-78.5 -50.5q66 -18 105.5 -70t39.5 -122q0 -119 -86 -167t-203 -48 q-96 0 -223 61zM387 -45l895 1448h193l-895 -1448h-193zM1067 186v146l322 403h165v-387h88v-162h-88v-186h-188v186h-299zM1268 348h98v129z" />
-<glyph unicode="¿" horiz-adv-x="1075" d="M164 37q0 70 22.5 126t56 92t72.5 74l73 73q34 34 56.5 87.5t22.5 118.5v21h281v-27q0 -104 -31 -184t-75 -127t-89 -86t-76 -81t-31 -89q0 -45 15.5 -76t46.5 -43t58.5 -16t66.5 -4q180 0 307 102v-262q-59 -41 -155.5 -68.5t-196.5 -27.5q-115 0 -200 33.5t-132 92 t-69.5 126t-22.5 145.5zM449 760v262h317v-262h-317z" />
-<glyph unicode="À" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM477 1722h240l127 -239h-176zM612 588h277l-137 424z" />
-<glyph unicode="Á" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM612 588h277l-137 424zM659 1483l127 239h240l-190 -239h-177z" />
-<glyph unicode="Â" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM457 1483l245 250h99l246 -250h-209l-86 90l-86 -90h-209zM612 588h277l-137 424z" />
-<glyph unicode="Ã" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM440 1497v174q57 68 124 74q11 1 22 1q55 0 106 -26l121 -60q51 -25 105 -25q11 0 22 1q66 6 123 74v-174q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -105 25l-121 60q-51 26 -106 26q-11 0 -22 -1 q-66 -6 -124 -74zM612 588h277l-137 424z" />
-<glyph unicode="Ä" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM455 1483v223h245v-223h-245zM612 588h277l-137 424zM803 1483v223h246v-223h-246z" />
-<glyph unicode="Å" horiz-adv-x="1503" d="M43 0l543 1366h332l542 -1366h-379l-106 328h-447l-106 -328h-379zM573 1618q0 84 47.5 130t131.5 46q86 0 132 -46t46 -130t-46 -130t-132 -46q-84 0 -131.5 46t-47.5 130zM612 588h277l-137 424zM700 1618q0 -37 11.5 -50.5t40 -13.5t40 13.5t11.5 50.5q0 63 -51.5 63 t-51.5 -63z" />
-<glyph unicode="Æ" horiz-adv-x="2217" d="M51 0l961 1366h1065v-287h-600v-252h520v-288h-520v-250h600v-289h-953v246h-469l-168 -246h-436zM831 504h293v428z" />
-<glyph unicode="Ç" horiz-adv-x="1314" d="M125 684q0 207 57.5 351.5t155.5 216t201.5 101t226.5 29.5q197 0 438 -96v-305q-205 98 -418 98q-141 0 -225 -86t-84 -309t84 -309t225 -86q213 0 418 98v-305q-195 -78 -356 -92l-125 -240h-240l189 240q-109 10 -200 48t-173 112.5t-128 211t-46 322.5z" />
-<glyph unicode="È" horiz-adv-x="1247" d="M178 0v1366h952v-287h-600v-252h521v-288h-521v-250h600v-289h-952zM424 1722h240l127 -239h-177z" />
-<glyph unicode="É" horiz-adv-x="1247" d="M178 0v1366h952v-287h-600v-252h521v-288h-521v-250h600v-289h-952zM561 1483l127 239h240l-191 -239h-176z" />
-<glyph unicode="Ê" horiz-adv-x="1247" d="M178 0v1366h952v-287h-600v-252h521v-288h-521v-250h600v-289h-952zM375 1483l246 250h98l246 -250h-209l-86 90l-86 -90h-209z" />
-<glyph unicode="Ë" horiz-adv-x="1247" d="M178 0v1366h952v-287h-600v-252h521v-288h-521v-250h600v-289h-952zM383 1483v223h246v-223h-246zM731 1483v223h246v-223h-246z" />
-<glyph unicode="Ì" horiz-adv-x="708" d="M76 1722h239l127 -239h-176zM180 0h352v1366h-352v-1366z" />
-<glyph unicode="Í" horiz-adv-x="708" d="M180 0h352v1366h-352v-1366zM270 1483l127 239h240l-191 -239h-176z" />
-<glyph unicode="Î" horiz-adv-x="708" d="M61 1483l246 250h99l245 -250h-209l-86 90l-86 -90h-209zM180 0h352v1366h-352v-1366z" />
-<glyph unicode="Ï" horiz-adv-x="708" d="M59 1483v223h246v-223h-246zM180 0h352v1366h-352v-1366zM408 1483v223h245v-223h-245z" />
-<glyph unicode="Ð" horiz-adv-x="1527" d="M106 582v200h166v584h492q317 0 483 -174t166 -510q0 -334 -166 -508t-483 -174h-492v582h-166zM625 289h139q297 0 297 393q0 397 -297 397h-139v-297h217v-200h-217v-293z" />
-<glyph unicode="Ñ" horiz-adv-x="1619" d="M178 0v1366h352l555 -819v819h353v-1366h-348l-560 813v-813h-352zM510 1497v174q57 68 124 74q10 1 21 1q55 0 105 -26l122 -60q52 -25 106 -25q11 0 22 1q65 6 123 74v-174q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -106 25l-122 60q-50 26 -105 26q-11 0 -21 -1 q-67 -6 -124 -74z" />
-<glyph unicode="Ò" horiz-adv-x="1538" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293t-39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16t-180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM477 684q0 -223 79 -309t210 -86 t210 86t79 309t-79 309t-210 86t-210 -86t-79 -309zM500 1722h239l127 -239h-176z" />
-<glyph unicode="Ó" horiz-adv-x="1538" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293t-39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16t-180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM477 684q0 -223 79 -309t210 -86 t210 86t79 309t-79 309t-210 86t-210 -86t-79 -309zM666 1483l127 239h239l-190 -239h-176z" />
-<glyph unicode="Ô" horiz-adv-x="1538" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293t-39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16t-180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM471 1483l246 250h98l246 -250h-209 l-86 90l-86 -90h-209zM477 684q0 -223 79 -309t210 -86t210 86t79 309t-79 309t-210 86t-210 -86t-79 -309z" />
-<glyph unicode="Õ" horiz-adv-x="1538" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293t-39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16t-180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM459 1497v174q57 68 123 74q11 1 22 1 q55 0 106 -26l122 -60q51 -25 105 -25q11 0 21 1q66 6 123 74v-174q-57 -68 -123 -74q-10 -1 -21 -1q-54 0 -105 25l-122 60q-51 26 -106 26q-11 0 -22 -1q-66 -6 -123 -74zM477 684q0 -223 79 -309t210 -86t210 86t79 309t-79 309t-210 86t-210 -86t-79 -309z" />
-<glyph unicode="Ö" horiz-adv-x="1538" d="M125 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16t180 -16t171 -63.5t149.5 -122t101.5 -201.5t39 -293t-39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16t-180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM469 1483v223h246v-223h-246zM477 684 q0 -223 79 -309t210 -86t210 86t79 309t-79 309t-210 86t-210 -86t-79 -309zM817 1483v223h246v-223h-246z" />
-<glyph unicode="×" horiz-adv-x="1093" d="M147 154l287 370l-284 371h225l174 -223l176 223h223l-284 -371l284 -370h-223l-176 223l-174 -223h-228z" />
-<glyph unicode="Ø" horiz-adv-x="1529" d="M14 0l211 246q-100 168 -100 436q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16q215 0 360 -84l62 72h328l-211 -246q102 -168 102 -438q0 -166 -39 -293t-101.5 -201.5t-149.5 -122t-171 -63.5t-180 -16q-217 0 -363 86l-61 -72h-328zM477 682q0 -82 8 -133 l426 495q-57 33 -145 33q-131 0 -210 -86t-79 -309zM618 319q55 -32 143 -32h5q131 0 210 86t79 309q0 70 -11 135z" />
-<glyph unicode="Ù" horiz-adv-x="1533" d="M178 588v778h352v-819q0 -131 56.5 -194.5t175.5 -63.5t175 63.5t56 194.5v819h353v-778q0 -287 -154 -444.5t-430.5 -157.5t-430 157.5t-153.5 444.5zM506 1722h239l127 -239h-176z" />
-<glyph unicode="Ú" horiz-adv-x="1533" d="M178 588v778h352v-819q0 -131 56.5 -194.5t175.5 -63.5t175 63.5t56 194.5v819h353v-778q0 -287 -154 -444.5t-430.5 -157.5t-430 157.5t-153.5 444.5zM651 1483l127 239h240l-191 -239h-176z" />
-<glyph unicode="Û" horiz-adv-x="1533" d="M178 588v778h352v-819q0 -131 56.5 -194.5t175.5 -63.5t175 63.5t56 194.5v819h353v-778q0 -287 -154 -444.5t-430.5 -157.5t-430 157.5t-153.5 444.5zM467 1483l246 250h98l246 -250h-209l-86 90l-86 -90h-209z" />
-<glyph unicode="Ü" horiz-adv-x="1533" d="M178 588v778h352v-819q0 -131 56.5 -194.5t175.5 -63.5t175 63.5t56 194.5v819h353v-778q0 -287 -154 -444.5t-430.5 -157.5t-430 157.5t-153.5 444.5zM465 1483v223h246v-223h-246zM813 1483v223h246v-223h-246z" />
-<glyph unicode="Ý" horiz-adv-x="1492" d="M25 1366h428l290 -520l291 520h428l-542 -791v-575h-353v575zM635 1483l127 239h239l-190 -239h-176z" />
-<glyph unicode="Þ" horiz-adv-x="1337" d="M176 0v1366h352v-203h177q248 0 385 -126t137 -353q0 -229 -137.5 -355t-384.5 -126h-177v-203h-352zM528 494h144q106 0 155.5 56t49.5 134q0 76 -49.5 134.5t-155.5 58.5h-144v-383z" />
-<glyph unicode="ß" horiz-adv-x="1282" d="M180 0v846q0 160 41 271.5t110.5 166.5t143.5 77.5t162 22.5q236 0 357.5 -109.5t121.5 -266.5q0 -106 -60 -187.5t-130 -101.5q250 -74 250 -313q0 -221 -167 -318q-149 -87 -391 -87q-30 0 -61 1v256h12q124 0 217 38q97 40 97 134q0 51 -32 84t-91.5 47t-100.5 19.5 t-102 7.5v233q43 0 84 6.5t88 21.5t74.5 48t27.5 82q0 162 -167 162q-190 0 -191 -267v-874h-293z" />
-<glyph unicode="à" horiz-adv-x="1128" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM326 1378h239l127 -239h-176zM459 362 q0 -108 98 -108h2q49 0 76 39t27 82v86q-49 4 -68 4q-135 0 -135 -103z" />
-<glyph unicode="á" horiz-adv-x="1128" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM442 1139l127 239h240l-191 -239h-176zM459 362 q0 -108 98 -108h2q49 0 76 39t27 82v86q-49 4 -68 4q-135 0 -135 -103z" />
-<glyph unicode="â" horiz-adv-x="1128" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM268 1139l246 250h98l246 -250h-209l-86 90l-86 -90 h-209zM459 362q0 -108 98 -108h2q49 0 76 39t27 82v86q-49 4 -68 4q-135 0 -135 -103z" />
-<glyph unicode="ã" horiz-adv-x="1128" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM252 1153v174q57 68 124 74q10 1 21 1q55 0 105 -26 l122 -60q52 -25 106 -25q11 0 22 1q65 6 122 74v-174q-57 -68 -122 -74q-12 -1 -23 -1q-53 0 -105 25l-122 60q-50 26 -105 26q-11 0 -21 -1q-67 -6 -124 -74zM459 362q0 -108 98 -108h2q49 0 76 39t27 82v86q-49 4 -68 4q-135 0 -135 -103z" />
-<glyph unicode="ä" horiz-adv-x="1128" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM266 1139v223h246v-223h-246zM459 362 q0 -108 98 -108h2q49 0 76 39t27 82v86q-49 4 -68 4q-135 0 -135 -103zM614 1139v223h246v-223h-246z" />
-<glyph unicode="å" horiz-adv-x="1128" d="M106 301q0 31 7.5 63.5t35 78.5t72.5 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -311 -64v267q178 71 336 71q225 0 348 -114.5t123 -329.5v-242q0 -190 -102.5 -277t-366.5 -87q-438 0 -439 315zM385 1272q0 84 47 130t131 46q86 0 132 -46t46 -130 t-46 -130t-132 -46q-84 0 -131 46t-47 130zM459 362q0 -108 98 -108h2q49 0 76 39t27 82v86q-49 4 -68 4q-135 0 -135 -103zM512 1272q0 -37 11.5 -50.5t40 -13.5t39.5 13.5t11 50.5q0 63 -51 63t-51 -63z" />
-<glyph unicode="æ" horiz-adv-x="1792" d="M127 297q0 31 7 63.5t35 78.5t73 81t134 59.5t208 24.5q63 0 92 -2q0 84 -41 120t-96 36q-154 0 -312 -64v267q176 71 332 71h4q92 0 175 -44t124 -91q35 51 113 94t170 43q229 0 347 -144t118 -353q0 -61 -6 -111h-582q0 -72 67.5 -121t151.5 -49q188 0 322 80v-248 q-166 -106 -389 -106q-96 0 -175 48t-112 109q-89 -157 -319 -157h-3q-438 0 -438 315zM479 358q0 -108 99 -108h2q49 0 75.5 39t26.5 82v86q-49 4 -68 4q-135 0 -135 -103zM1030 637h230q-4 59 -35 116.5t-80 57.5t-80 -53t-35 -121z" />
-<glyph unicode="ç" horiz-adv-x="993" d="M119 510q0 254 126 390t361 136q178 0 322 -69v-262q-139 63 -258 63q-90 0 -144.5 -64.5t-54.5 -193.5t54.5 -193.5t144.5 -64.5q119 0 258 63v-262q-106 -51 -238 -63l-125 -240h-239l188 240q-193 25 -294 158t-101 362z" />
-<glyph unicode="è" d="M131 502q0 244 113.5 390t355.5 146q229 0 347 -144t118 -353q0 -61 -6 -111h-582q0 -72 67.5 -121t151.5 -49q188 0 322 80v-248q-166 -106 -402 -106q-68 0 -129 11t-128.5 46t-115.5 89t-80 149.5t-32 220.5zM338 1393h240l127 -240h-177zM485 641h230 q-4 59 -35 116.5t-80 57.5t-80 -53t-35 -121z" />
-<glyph unicode="é" d="M131 502q0 244 113.5 390t355.5 146q229 0 347 -144t118 -353q0 -61 -6 -111h-582q0 -72 67.5 -121t151.5 -49q188 0 322 80v-248q-166 -106 -402 -106q-68 0 -129 11t-128.5 46t-115.5 89t-80 149.5t-32 220.5zM475 1139l127 239h240l-191 -239h-176zM485 641h230 q-4 59 -35 116.5t-80 57.5t-80 -53t-35 -121z" />
-<glyph unicode="ê" d="M131 502q0 244 113.5 390t355.5 146q229 0 347 -144t118 -353q0 -61 -6 -111h-582q0 -72 67.5 -121t151.5 -49q188 0 322 80v-248q-166 -106 -402 -106q-68 0 -129 11t-128.5 46t-115.5 89t-80 149.5t-32 220.5zM301 1139l246 250h98l246 -250h-209l-86 90l-86 -90h-209z M485 641h230q-4 59 -35 116.5t-80 57.5t-80 -53t-35 -121z" />
-<glyph unicode="ë" d="M131 502q0 244 113.5 390t355.5 146q229 0 347 -144t118 -353q0 -61 -6 -111h-582q0 -72 67.5 -121t151.5 -49q188 0 322 80v-248q-166 -106 -402 -106q-68 0 -129 11t-128.5 46t-115.5 89t-80 149.5t-32 220.5zM299 1139v223h246v-223h-246zM485 641h230 q-4 59 -35 116.5t-80 57.5t-80 -53t-35 -121zM647 1139v223h246v-223h-246z" />
-<glyph unicode="ì" horiz-adv-x="735" d="M92 1393h240l127 -240h-176zM197 0h352v1022h-352v-1022z" />
-<glyph unicode="í" horiz-adv-x="735" d="M197 0h352v1022h-352v-1022zM287 1139l127 239h239l-190 -239h-176z" />
-<glyph unicode="î" horiz-adv-x="735" d="M78 1139l246 250h98l246 -250h-209l-86 90l-86 -90h-209zM197 0h352v1022h-352v-1022z" />
-<glyph unicode="ï" horiz-adv-x="735" d="M76 1139v223h246v-223h-246zM197 0h352v1022h-352v-1022zM424 1139v223h246v-223h-246z" />
-<glyph unicode="ð" horiz-adv-x="1271" d="M117 481q0 131 41 220.5t115.5 129t148.5 55t170 15.5q123 0 190 -27q-14 109 -123 164l-151 -118l-92 114l61 49h-16q-90 0 -199 -30v301q91 30 219 30h8q162 0 291 -59l107 84l92 -117l-68 -53q215 -193 215 -606q0 -649 -505 -649q-131 0 -232.5 45t-159 118.5 t-85 158.5t-27.5 175zM471 483q0 -31 7 -68.5t44.5 -82.5t98.5 -45q66 0 113 95q42 87 42 247v28q-53 23 -151 23q-78 0 -116 -59.5t-38 -137.5z" />
-<glyph unicode="ñ" horiz-adv-x="1308" d="M178 0v913q246 123 508 123q217 0 344 -115.5t127 -318.5v-602h-352v657q0 31 -8.5 51.5t-48 41t-115.5 20.5q-55 0 -103 -12v-758h-352zM342 1153v174q57 68 124 74q10 1 21 1q55 0 105 -26l122 -60q52 -25 106 -25q11 0 22 1q65 6 123 74v-174q-57 -68 -123 -74 q-11 -1 -22 -1q-54 0 -106 25l-122 60q-50 26 -105 26q-11 0 -21 -1q-67 -6 -124 -74z" />
-<glyph unicode="ò" d="M92 510q0 254 128 389t363.5 135t363.5 -135t128 -389t-128 -389t-363.5 -135t-363.5 135t-128 389zM332 1378h239l127 -239h-176zM444 510q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270z" />
-<glyph unicode="ó" d="M92 510q0 254 128 389t363.5 135t363.5 -135t128 -389t-128 -389t-363.5 -135t-363.5 135t-128 389zM444 510q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270zM469 1139l127 239h240l-191 -239h-176z" />
-<glyph unicode="ô" d="M92 510q0 254 128 389t363.5 135t363.5 -135t128 -389t-128 -389t-363.5 -135t-363.5 135t-128 389zM289 1139l246 250h98l246 -250h-209l-86 90l-86 -90h-209zM444 510q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270z" />
-<glyph unicode="õ" d="M92 510q0 254 128 389t363.5 135t363.5 -135t128 -389t-128 -389t-363.5 -135t-363.5 135t-128 389zM276 1153v174q57 68 124 74q11 1 22 1q55 0 106 -26l122 -60q51 -25 105 -25q11 0 21 1q66 6 123 74v-174q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60 q-51 26 -106 26q-11 0 -22 -1q-66 -6 -124 -74zM444 510q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270z" />
-<glyph unicode="ö" d="M92 510q0 254 128 389t363.5 135t363.5 -135t128 -389t-128 -389t-363.5 -135t-363.5 135t-128 389zM287 1139v223h245v-223h-245zM444 510q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270zM635 1139v223h246v-223h-246z" />
-<glyph unicode="÷" horiz-adv-x="1286" d="M188 418v231h916v-231h-916zM520 47v240h252v-240h-252zM520 782v240h252v-240h-252z" />
-<glyph unicode="ø" horiz-adv-x="1255" d="M23 0l178 209q-68 119 -68 303q0 254 128 389t364 135q141 0 243 -49l31 35h330l-178 -207q65 -119 65 -298v-5q0 -254 -128 -389t-363 -135q-139 0 -244 49l-31 -37h-327zM487 541l193 227q-28 14 -55 14q-131 0 -138 -241zM569 256q23 -14 56 -14q131 0 139 239z" />
-<glyph unicode="ù" horiz-adv-x="1269" d="M172 461v561h352v-623q0 -150 122 -150q1 0 1 1q123 0 123 149v623h352v-561q0 -475 -475 -475t-475 475zM391 1378h240l127 -239h-176z" />
-<glyph unicode="ú" horiz-adv-x="1269" d="M172 461v561h352v-623q0 -150 122 -150q1 0 1 1q123 0 123 149v623h352v-561q0 -475 -475 -475t-475 475zM537 1139l127 239h239l-190 -239h-176z" />
-<glyph unicode="û" horiz-adv-x="1269" d="M172 461v561h352v-623q0 -150 122 -150q1 0 1 1q123 0 123 149v623h352v-561q0 -475 -475 -475t-475 475zM352 1139l246 250h98l246 -250h-209l-86 90l-86 -90h-209z" />
-<glyph unicode="ü" horiz-adv-x="1269" d="M172 461v561h352v-623q0 -150 122 -150q1 0 1 1q123 0 123 149v623h352v-561q0 -475 -475 -475t-475 475zM350 1139v223h246v-223h-246zM698 1139v223h246v-223h-246z" />
-<glyph unicode="ý" horiz-adv-x="1169" d="M63 1022h373l148 -588l145 588h371l-373 -1002q-90 -231 -177 -327t-212 -96q-133 0 -223 43v256q70 -25 135 -25q47 0 92 56.5t78 170.5zM485 1139l127 239h240l-190 -239h-177z" />
-<glyph unicode="þ" horiz-adv-x="1218" d="M172 -375v1778h350v-379q43 6 133 6q223 0 344 -133t121 -383t-120.5 -383t-344.5 -133q-90 0 -133 6v-379h-350zM522 221q50 -4 68 -4q178 12 178 297t-178 297q-18 0 -68 -4v-586z" />
-<glyph unicode="ÿ" horiz-adv-x="1169" d="M63 1022h373l148 -588l145 588h371l-373 -1002q-90 -231 -177 -327t-212 -96q-133 0 -223 43v256q70 -25 135 -25q47 0 92 56.5t78 170.5zM287 1139v223h245v-223h-245zM635 1139v223h246v-223h-246z" />
-<glyph unicode="Œ" horiz-adv-x="2160" d="M123 684q0 166 39 293t101.5 201.5t149.5 122t171 63.5t180 16q168 0 293 -51v37h952v-287h-600v-252h520v-288h-520v-250h600v-289h-952v39q-125 -51 -293 -51q-96 0 -180 16t-171 63.5t-149.5 122t-101.5 201.5t-39 293zM475 684q0 -223 79 -309t210 -86t210 86t79 309 t-79 309t-210 86t-210 -86t-79 -309z" />
-<glyph unicode="œ" horiz-adv-x="1853" d="M125 512q0 254 128 389t363 135q190 0 308 -86q117 88 297 88q229 0 347 -144t118 -353q0 -61 -7 -111h-581q0 -72 67.5 -121t151.5 -49q188 0 321 80v-248q-166 -106 -401 -106q-193 0 -311 88q-119 -86 -310 -86q-236 0 -363.5 135t-127.5 389zM477 512 q0 -270 139.5 -270t139.5 270t-139.5 270t-139.5 -270zM1106 641h229q-4 59 -34.5 116.5t-80 57.5t-80 -53t-34.5 -121z" />
-<glyph unicode="Ÿ" horiz-adv-x="1492" d="M25 1366h428l290 -520l291 520h428l-542 -791v-575h-353v575zM449 1483v223h245v-223h-245zM797 1483v223h245v-223h-245z" />
-<glyph unicode="ˆ" horiz-adv-x="874" d="M137 1155l246 250h98l246 -250h-209l-86 90l-86 -90h-209z" />
-<glyph unicode="˜" horiz-adv-x="1058" d="M215 1153v174q57 68 124 74q11 1 22 1q55 0 105 -26l122 -60q51 -25 105 -25q11 0 21 1q66 6 124 74v-174q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -105 25l-122 60q-50 26 -105 26q-11 0 -22 -1q-67 -6 -124 -74z" />
-<glyph unicode=" " horiz-adv-x="897" />
-<glyph unicode=" " horiz-adv-x="1794" />
-<glyph unicode=" " horiz-adv-x="897" />
-<glyph unicode=" " horiz-adv-x="1794" />
-<glyph unicode=" " horiz-adv-x="598" />
-<glyph unicode=" " horiz-adv-x="448" />
-<glyph unicode=" " horiz-adv-x="299" />
-<glyph unicode=" " horiz-adv-x="299" />
-<glyph unicode=" " horiz-adv-x="224" />
-<glyph unicode=" " horiz-adv-x="358" />
-<glyph unicode=" " horiz-adv-x="99" />
-<glyph unicode="‐" horiz-adv-x="950" d="M162 514v240h624v-240h-624z" />
-<glyph unicode="‑" horiz-adv-x="950" d="M162 514v240h624v-240h-624z" />
-<glyph unicode="‒" horiz-adv-x="950" d="M162 514v240h624v-240h-624z" />
-<glyph unicode="–" horiz-adv-x="1146" d="M141 475v240h860v-240h-860z" />
-<glyph unicode="—" horiz-adv-x="1824" d="M162 475v240h1501v-240h-1501z" />
-<glyph unicode="‘" horiz-adv-x="616" d="M96 1384h285l151 -401h-239z" />
-<glyph unicode="’" horiz-adv-x="616" d="M96 983l152 401h284l-196 -401h-240z" />
-<glyph unicode="‚" horiz-adv-x="698" d="M147 -229l152 401h285l-197 -401h-240z" />
-<glyph unicode="“" horiz-adv-x="980" d="M98 1384h285l152 -401h-240zM455 1384h284l152 -401h-240z" />
-<glyph unicode="”" horiz-adv-x="980" d="M98 983l152 401h285l-197 -401h-240zM455 983l151 401h285l-197 -401h-239z" />
-<glyph unicode="„" horiz-adv-x="1056" d="M147 -229l152 401h285l-197 -401h-240zM504 -229l151 401h285l-197 -401h-239z" />
-<glyph unicode="•" horiz-adv-x="1085" d="M172 665.5q0 153.5 109.5 262t263 108.5t261 -107.5t107.5 -263t-107.5 -263t-261 -107.5t-263 108.5t-109.5 262z" />
-<glyph unicode="…" horiz-adv-x="1849" d="M219 0v283h295v-283h-295zM780 0v283h295v-283h-295zM1343 0v283h295v-283h-295z" />
-<glyph unicode=" " horiz-adv-x="358" />
-<glyph unicode="‹" horiz-adv-x="819" d="M63 522l347 389h338l-373 -389l373 -391h-338z" />
-<glyph unicode="›" horiz-adv-x="843" d="M80 131l373 391l-373 389h338l346 -389l-346 -391h-338z" />
-<glyph unicode=" " horiz-adv-x="448" />
-<glyph unicode="€" horiz-adv-x="1466" d="M98 408v190h166q-2 27 -2 86t2 88h-166v191h197q117 419 606 419h2q199 0 438 -96v-305q-204 98 -417 98q-174 0 -248 -116h397v-191h-455q-4 -53 -4 -88q0 -33 4 -86h455v-190h-397q74 -119 248 -119q213 0 417 98v-305q-240 -96 -438 -96q-492 0 -608 422h-197z" />
-<glyph unicode="™" horiz-adv-x="1581" d="M100 1206v160h572v-160h-199v-522h-176v522h-197zM772 684v682h174l160 -233l160 233h174v-682h-176v422l-158 -227l-158 227v-422h-176z" />
-<glyph unicode="" horiz-adv-x="1020" d="M0 0v1020h1020v-1020h-1020z" />
-<glyph unicode="fi" horiz-adv-x="1462" d="M84 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-206v-774h-353v774h-141zM981 1139h360v268h-360v-268zM985 0h352v1022h-352v-1022z" />
-<glyph unicode="fl" horiz-adv-x="1462" d="M82 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-207v-774h-352v774h-141zM983 0h352v1411h-352v-1411z" />
-<glyph unicode="ffi" horiz-adv-x="2287" d="M82 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-207v-774h-352v774h-141zM858 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-207v-774h-352v774h-141z M1745 1139v268h360v-268h-360zM1749 0v1022h352v-1022h-352z" />
-<glyph unicode="ffl" horiz-adv-x="2313" d="M82 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-207v-774h-352v774h-141zM858 774v248h145q1 424 404 424q84 0 151 -23v-211q-29 8 -55 9q-72 0 -109.5 -63.5t-37.5 -135.5h202v-248h-207v-774h-352v774h-141z M1759 0v1411h352v-1411h-352z" />
-</font>
-</defs></svg>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
-<svg xmlns="http://www.w3.org/2000/svg">
-<metadata></metadata>
-<defs>
-<font id="maven_probold" horiz-adv-x="1441" >
-<font-face units-per-em="2048" ascent="1638" descent="-410" />
-<missing-glyph horiz-adv-x="692" />
-<glyph horiz-adv-x="2048" />
-<glyph horiz-adv-x="2048" />
-<glyph unicode="
" horiz-adv-x="682" />
-<glyph unicode=" " horiz-adv-x="692" />
-<glyph unicode="	" horiz-adv-x="692" />
-<glyph unicode=" " horiz-adv-x="692" />
-<glyph unicode="!" horiz-adv-x="825" d="M283 1366h280l-45 -973h-190zM303 0v227h240v-227h-240z" />
-<glyph unicode=""" horiz-adv-x="825" d="M166 1384h205l-35 -411h-133zM461 1384h205l-35 -411h-133z" />
-<glyph unicode="#" horiz-adv-x="1216" d="M152 256v152h180l51 292h-174v152h201l36 217h162l-39 -217h232l39 217h159l-38 -217h133v-152h-158l-51 -292h153v-152h-178l-39 -217h-162l39 217h-231l-37 -217h-162l39 217h-155zM494 408h231l49 292h-229z" />
-<glyph unicode="$" horiz-adv-x="1181" d="M150 987q0 174 111.5 273.5t289.5 119.5v209h119v-207q168 -6 334 -92v-239q-160 102 -334 104v-340q74 -23 120 -39t109.5 -53t100 -81t63.5 -116t27 -164q0 -154 -121 -257t-299 -117v-209h-119v209q-219 14 -383 114v250q174 -129 383 -131v379q-76 23 -117 37 t-104.5 47t-96 70t-58 97t-25.5 136zM395 993q0 -53 39 -84.5t117 -58.5v293q-156 -33 -156 -150zM670 229q174 27 174 150q0 121 -174 184v-334z" />
-<glyph unicode="%" horiz-adv-x="1777" d="M145 983q0 377 289 377q291 0 291 -377t-291 -377q-289 0 -289 377zM305 983q0 -133 36 -179t93.5 -46t93 46t35.5 179q0 135 -35.5 180t-93 45t-93.5 -45t-36 -180zM381 0l858 1366h174l-858 -1366h-174zM1065 383q0 377 289 377q291 0 291 -377t-291 -377 q-289 0 -289 377zM1225 383q0 -133 35.5 -179t93 -46t93.5 46t36 179q0 135 -36 180t-93.5 45t-93 -45t-35.5 -180z" />
-<glyph unicode="&" horiz-adv-x="1310" d="M113 389q0 53 7 94t31.5 96.5t79 108.5t138.5 103q-125 131 -125 249q0 137 85 235.5t240 98.5q139 0 232.5 -92t93.5 -227q0 -104 -50 -174t-173 -152l229 -274q29 72 43 155h201q-29 -180 -109 -319l244 -291h-262l-105 129q-152 -145 -364 -145q-113 0 -201 41 t-137 103t-73.5 130t-24.5 131zM307 383q0 -39 17.5 -83t77 -87t151.5 -43q135 0 242 106l-301 361q-66 -35 -108 -72t-57.5 -72.5t-18.5 -56t-3 -53.5zM438 1059q0 -14 5.5 -31.5t10.5 -29t18 -32t19.5 -27.5t25 -29.5t22.5 -26.5q96 57 129.5 97t33.5 87q0 53 -37.5 90 t-93 37t-94.5 -39t-39 -96z" />
-<glyph unicode="'" horiz-adv-x="534" d="M166 1384h205l-35 -411h-133z" />
-<glyph unicode="(" horiz-adv-x="751" d="M190 627q0 440 211 856h213q-211 -416 -211 -856q0 -436 211 -858h-213q-211 422 -211 858z" />
-<glyph unicode=")" horiz-adv-x="751" d="M133 -231q211 412 211 858q0 449 -211 856h213q211 -416 211 -856q0 -436 -211 -858h-213z" />
-<glyph unicode="*" horiz-adv-x="845" d="M172 1133l166 57l-166 57l78 139l123 -104l-25 156h160l-25 -156l123 104l78 -139l-166 -57l166 -57l-78 -140l-123 105l25 -156h-160l25 156l-123 -105z" />
-<glyph unicode="+" horiz-adv-x="1128" d="M199 520v182h272v275h182v-275h273v-182h-273v-270h-182v270h-272z" />
-<glyph unicode="," horiz-adv-x="677" d="M152 -229l151 401h215l-182 -401h-184z" />
-<glyph unicode="-" horiz-adv-x="950" d="M162 514v193h624v-193h-624z" />
-<glyph unicode="." horiz-adv-x="759" d="M260 0v227h240v-227h-240z" />
-<glyph unicode="/" horiz-adv-x="897" d="M-29 -201l752 1749h205l-754 -1749h-203z" />
-<glyph unicode="0" horiz-adv-x="1345" d="M133 677.5q0 700.5 538.5 700.5t538.5 -700.5t-538.5 -700.5t-538.5 700.5zM379 678q0 -156 24.5 -260.5t71.5 -152.5t92 -64.5t104.5 -16.5t104.5 16.5t92 64.5t72 152.5t25 260.5q0 125 -17.5 218t-45.5 144.5t-68.5 82t-77.5 39.5t-84 9t-84 -9t-78 -39.5t-68.5 -82 t-45 -144.5t-17.5 -218z" />
-<glyph unicode="1" horiz-adv-x="993" d="M211 936v262l266 168h223v-1366h-245v1085z" />
-<glyph unicode="2" horiz-adv-x="1226" d="M158 0v201q94 70 192.5 154.5t205 193t174 222.5t67.5 204q0 70 -56.5 117t-185.5 47q-100 0 -205.5 -34t-179.5 -85v244q195 120 392 120h3q242 0 358.5 -120.5t116.5 -288.5q0 -182 -137 -373.5t-336 -357.5h494v-244h-903z" />
-<glyph unicode="3" horiz-adv-x="1228" d="M166 84v250q180 -106 373 -107q290 0 290 188q0 91 -68.5 149.5t-230.5 58.5h-174v202h181q115 0 171 47.5t56 116.5q0 82 -73 116t-169 34q-178 0 -327 -90v251q178 84 342 84q205 0 339 -92t134 -295q0 -168 -168 -260q104 -37 167.5 -121t63.5 -204q0 -96 -32.5 -172 t-85 -123t-123 -78t-138 -43t-139.5 -12q-203 0 -389 100z" />
-<glyph unicode="4" horiz-adv-x="1234" d="M78 385v203l622 778h234v-762h178v-219h-178v-385h-246v385h-610zM365 604h323v420z" />
-<glyph unicode="5" horiz-adv-x="1245" d="M170 709l74 657h751v-244h-542q-4 -51 -12.5 -126.5t-12.5 -123.5q96 18 184 18q2 1 4 1q86 0 158 -19q74 -20 146.5 -67t113.5 -141.5t41 -227.5q0 -135 -41 -229t-110.5 -140.5t-143 -65.5t-162.5 -19q-227 0 -415 96v248q190 -121 401 -121q227 0 227 211v20 q-10 219 -223 223h-9q-174 0 -292 -73z" />
-<glyph unicode="6" horiz-adv-x="1222" d="M131 629q0 195 49 340t117 222t162 123t162.5 58t140.5 12q100 0 235 -38v-218q-82 27 -221 27q-47 0 -98 -12.5t-113.5 -42t-112 -96t-65.5 -160.5q90 69 250 69q221 0 342 -120.5t121 -331.5q0 -86 -26.5 -167t-81 -152.5t-152 -114.5t-224.5 -43q-485 0 -485 645z M378 585q0 -103 20 -179q22 -91 64 -132t78.5 -57t75.5 -16q76 0 128.5 31.5t73 80.5t27.5 84t7 66q0 104 -61.5 166.5t-174.5 62.5q-133 0 -237 -69q-1 -19 -1 -38z" />
-<glyph unicode="7" horiz-adv-x="1202" d="M141 1120v246h965v-195l-563 -1171h-291l545 1120h-656z" />
-<glyph unicode="8" horiz-adv-x="1236" d="M129 408q0 74 25.5 134t61.5 94t73 57.5t63 31.5l25 8q-8 2 -21.5 7t-48.5 26.5t-61.5 49.5t-48 81t-21.5 119q0 172 119 270t325.5 98t325.5 -98t119 -270q0 -188 -158 -266l-41 -17q10 -2 26.5 -8t58.5 -30.5t75 -58.5t59.5 -94.5t26.5 -133.5q0 -199 -129 -311.5 t-362.5 -112.5t-362.5 112.5t-129 311.5zM375 416q0 -111 61.5 -175.5t184 -64.5t184 63.5t61.5 176.5q0 82 -63.5 141t-182 59t-182 -59t-63.5 -141zM422 1010q0 -98 59.5 -140.5t139 -42.5t139 42t59.5 141q0 74 -51 124t-147.5 50t-147.5 -50.5t-51 -123.5z" />
-<glyph unicode="9" horiz-adv-x="1234" d="M117 907q0 86 26.5 167t81 152.5t151.5 114.5t224 43q485 0 485 -645q0 -195 -49 -340t-116.5 -222t-161.5 -123t-163 -58t-140 -12q-100 0 -236 39v217q82 -27 221 -27q47 0 98.5 12.5t114 42t111.5 96t65 160.5q-89 -69 -249 -69q-221 0 -342 120.5t-121 331.5z M365 905q0 -104 61 -166.5t174 -62.5q133 0 238 69q1 19 1 38q0 103 -19 179q-23 91 -65 132t-79 57t-76 16q-76 0 -128 -31.5t-72.5 -80.5t-27.5 -84t-7 -66z" />
-<glyph unicode=":" horiz-adv-x="813" d="M293 78v223h235v-223h-235zM293 723v223h235v-223h-235z" />
-<glyph unicode=";" horiz-adv-x="858" d="M182 -229l152 401h215l-182 -401h-185zM322 723v223h235v-223h-235z" />
-<glyph unicode="<" horiz-adv-x="1351" d="M246 477v148l850 379v-211l-617 -242l617 -242v-211z" />
-<glyph unicode="=" horiz-adv-x="1306" d="M260 348v176h789v-176h-789zM260 698v176h789v-176h-789z" />
-<glyph unicode=">" horiz-adv-x="1329" d="M270 98v211l617 242l-617 242v211l850 -379v-148z" />
-<glyph unicode="?" horiz-adv-x="1042" d="M164 1059v227q150 96 321 96q115 0 200 -33.5t132 -92t69.5 -126t22.5 -145.5q0 -70 -22.5 -126t-56 -92t-72.5 -74l-73 -73q-34 -34 -56.5 -87.5t-22.5 -118.5v-21h-217v27q0 104 31 184t76 127t89 86t74.5 81t30.5 89q0 182 -221 182q-180 0 -305 -110zM379 0v227h239 v-227h-239z" />
-<glyph unicode="@" horiz-adv-x="1869" d="M170 371q0 184 79 349t205 276.5t282.5 176t310.5 64.5q84 0 174 -22.5t183 -75t165.5 -127t119 -191t46.5 -258.5q0 -207 -92.5 -362.5t-266.5 -155.5q-82 0 -127 54.5t-51 97.5q-27 -66 -101.5 -109t-185.5 -43q-154 0 -242.5 69.5t-88.5 190.5q0 53 17 99.5t62.5 94.5 t142.5 76.5t238 28.5q45 0 115 -4q0 76 -34 114t-140 38q-166 0 -279 -43q4 29 13.5 82t13.5 81q158 51 270 52q166 0 252 -77t86 -226q0 -51 -16 -156t-16 -162q0 -133 92 -133q74 0 147.5 103.5t73.5 289.5q0 127 -53.5 234.5t-137.5 175t-185 105.5t-202 38 q-295 0 -522 -224t-227 -527q0 -129 46 -233.5t111.5 -163t145.5 -97.5t141.5 -52.5t106.5 -13.5q262 0 465 99l-21 -131q-188 -84 -442 -84q-106 0 -217 37.5t-216.5 113.5t-173 214.5t-67.5 316.5zM762 309q-1 -80 149 -80q119 0 165 50.5t54 175.5q-27 2 -90 2 q-279 0 -278 -148z" />
-<glyph unicode="A" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM516 551h408l-203 536z" />
-<glyph unicode="B" horiz-adv-x="1323" d="M178 0v1366h492q94 0 174 -19.5t149.5 -61.5t109.5 -120t40 -184q0 -180 -162 -264q238 -86 238 -316q0 -55 -9.5 -103t-42 -105.5t-85 -97.5t-145.5 -67.5t-218 -27.5h-541zM424 215h283q266 0 266 199q0 178 -275 178h-274v-377zM424 809h260q213 0 213 164 q0 180 -231 180h-242v-344z" />
-<glyph unicode="C" horiz-adv-x="1286" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16q209 0 414 -81v-230q-195 90 -392 90q-92 0 -157.5 -16.5t-128 -63.5t-95 -147t-32.5 -252t32.5 -252t95 -147t128 -63.5t157.5 -16.5q197 0 392 90v-229q-205 -82 -414 -82q-96 0 -179 16t-170 63.5 t-148.5 123t-100.5 202.5t-39 295z" />
-<glyph unicode="D" horiz-adv-x="1419" d="M178 0v1366h471q317 0 481 -174t164 -510q0 -334 -163.5 -508t-481.5 -174h-471zM424 223h225q213 0 306.5 107.5t93.5 351.5q0 246 -93.5 353.5t-306.5 107.5h-225v-920z" />
-<glyph unicode="E" horiz-adv-x="1204" d="M178 0v1366h909v-221h-663v-346h575v-232h-575v-346h663v-221h-909z" />
-<glyph unicode="F" horiz-adv-x="1142" d="M178 0v1366h883v-221h-637v-346h551v-232h-551v-567h-246z" />
-<glyph unicode="G" horiz-adv-x="1406" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16q217 0 450 -92v-225q-221 98 -426 98q-72 0 -127 -9t-111 -39t-93 -81t-60.5 -140t-23.5 -210q0 -123 23.5 -212t60.5 -140t93 -81t111.5 -39t126.5 -9q113 0 242 35v327h-321v217h548v-682 q-238 -116 -489 -116h-4q-96 0 -179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294z" />
-<glyph unicode="H" horiz-adv-x="1429" d="M178 0v1366h246v-567h580v567h245v-1366h-245v567h-580v-567h-246z" />
-<glyph unicode="I" horiz-adv-x="600" d="M178 0v1366h246v-1366h-246z" />
-<glyph unicode="J" horiz-adv-x="1003" d="M59 72v243q125 -102 281 -102q115 0 169 48t54 188v917h246v-909q0 -145 -44 -245.5t-117 -147t-139.5 -63.5t-139.5 -17q-168 0 -310 88z" />
-<glyph unicode="K" horiz-adv-x="1415" d="M178 0v1366h246v-578l516 578h330l-551 -600l608 -766h-307l-459 592l-137 -150v-442h-246z" />
-<glyph unicode="L" horiz-adv-x="1124" d="M178 0v1366h246v-1133h657v-233h-903z" />
-<glyph unicode="M" horiz-adv-x="1695" d="M178 0v1366h273l395 -571l395 571h272v-1366h-245v1010l-422 -604l-422 604v-1010h-246z" />
-<glyph unicode="N" horiz-adv-x="1587" d="M178 0v1366h279l702 -1012v1012h246v-1366h-275l-706 1020v-1020h-246z" />
-<glyph unicode="O" horiz-adv-x="1538" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16t-179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM371 684q0 -154 30.5 -255 t90 -148.5t122 -64.5t148.5 -17q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 152 -30.5 253t-91 148.5t-122 63.5t-149.5 16q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212z" />
-<glyph unicode="P" horiz-adv-x="1277" d="M176 0v1366h510q242 0 374 -120t132 -339t-132 -338.5t-374 -119.5h-264v-449h-246zM422 672h246q162 0 221 59.5t59 175.5q0 115 -59.5 173.5t-220.5 58.5h-246v-467z" />
-<glyph unicode="Q" horiz-adv-x="1529" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t171 63.5t180 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -152 -32 -269.5t-84 -193.5t-126 -127t-148.5 -74.5t-160.5 -31.5q0 -94 69.5 -136.5t165.5 -42.5v-188q-438 0 -438 369q-106 14 -193 53 t-165 115.5t-122 211t-44 314.5zM371 682q0 -154 30.5 -254t91 -148.5t122 -64.5t149.5 -16q86 0 148.5 16t123 64.5t91 148.5t30.5 254t-30.5 254t-91 148.5t-123 64.5t-148.5 16q-88 0 -149.5 -16t-122 -64.5t-91 -148.5t-30.5 -254z" />
-<glyph unicode="R" horiz-adv-x="1337" d="M178 0v1366h524q240 0 373 -113.5t133 -324.5q0 -314 -299 -406l359 -522h-297l-326 481h-221v-481h-246zM424 707h272q125 0 196 45t71 170q0 127 -71 171t-196 44h-272v-430z" />
-<glyph unicode="S" horiz-adv-x="1189" d="M125 985q0 197 139 297t348 100q184 0 367 -94v-239q-158 104 -338 104q-113 0 -191.5 -39t-78.5 -123q0 -68 71.5 -106.5t173 -65t204 -66.5t174 -138.5t71.5 -254.5q0 -166 -137 -271t-334 -105q-260 0 -451 116v250q176 -131 394 -131q282 0 282 157q0 71 -51 118 t-129 69.5t-167 53t-167 66.5t-129 112.5t-51 189.5z" />
-<glyph unicode="T" horiz-adv-x="1169" d="M25 1120v246h1120v-246h-438v-1120h-246v1120h-436z" />
-<glyph unicode="U" horiz-adv-x="1511" d="M178 578v788h246v-809q0 -180 78 -264t248 -84t248.5 84t78.5 264v809h246v-788q0 -283 -150.5 -438.5t-423 -155.5t-422 155.5t-149.5 438.5z" />
-<glyph unicode="V" horiz-adv-x="1347" d="M25 1366h262l387 -1061l387 1061h262l-522 -1366h-252z" />
-<glyph unicode="W" horiz-adv-x="2045" d="M25 1366h258l307 -1010l325 1010h215l326 -1010l309 1010h256l-438 -1366h-254l-305 952l-307 -952h-254z" />
-<glyph unicode="X" horiz-adv-x="1329" d="M25 0l485 690l-473 676h299l326 -461l323 461h289l-469 -666l491 -700h-299l-344 487l-344 -487h-284z" />
-<glyph unicode="Y" horiz-adv-x="1368" d="M25 1366h297l362 -555l360 555h297l-534 -780v-586h-246v586z" />
-<glyph unicode="Z" horiz-adv-x="1271" d="M131 0v225l684 895h-676v246h981v-219l-688 -901h688v-246h-989z" />
-<glyph unicode="[" horiz-adv-x="808" d="M244 -238v1663h395v-190h-207v-1282h207v-191h-395z" />
-<glyph unicode="\" horiz-adv-x="829" d="M-76 1548h205l752 -1749h-203z" />
-<glyph unicode="]" horiz-adv-x="808" d="M172 -47h207v1282h-207v190h395v-1663h-395v191z" />
-<glyph unicode="^" horiz-adv-x="1146" d="M264 969l223 444h113l225 -444h-170l-110 260l-111 -260h-170z" />
-<glyph unicode="_" horiz-adv-x="1118" d="M0 -49h1118v-141h-1118v141z" />
-<glyph unicode="`" horiz-adv-x="522" d="M92 1395h201l127 -240h-137z" />
-<glyph unicode="a" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM358 365q0 -164 193 -164h2 q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133z" />
-<glyph unicode="b" horiz-adv-x="1181" d="M170 328v1085h246v-391q88 16 168 16q229 0 352 -133t123 -383q0 -137 -36 -241.5t-85 -158.5t-117.5 -88t-119 -42t-101.5 -8q-219 0 -324.5 101t-105.5 243zM416 305q0 -110 184 -110q117 0 164 79.5t47 260.5q0 172 -60.5 240.5t-175.5 68.5q-90 0 -159 -17v-522z" />
-<glyph unicode="c" horiz-adv-x="966" d="M119 510q0 256 125 392t358 136q150 0 295 -57v-205q-121 49 -254 49q-141 0 -209.5 -64.5t-68.5 -250.5q0 -184 68.5 -249.5t209.5 -65.5q127 0 254 51v-205q-145 -57 -295 -57q-233 0 -358 136t-125 390z" />
-<glyph unicode="d" horiz-adv-x="1167" d="M129 522q0 250 122 383t351 133q82 0 170 -16v391h246v-1085q0 -141 -106.5 -242.5t-325.5 -101.5q-51 0 -101.5 8t-118 42t-117.5 88t-85 158.5t-35 241.5zM375 535q0 -180 47 -260t164 -80q186 0 186 110v522q-72 16 -162 17q-115 0 -175 -68.5t-60 -240.5z" />
-<glyph unicode="e" horiz-adv-x="1144" d="M129 500q0 246 109.5 393t347.5 147q225 0 340.5 -143t115.5 -352q0 -86 -4 -127h-657q10 -133 79.5 -178t180.5 -45q180 0 348 79v-198q-170 -92 -385 -92q-68 0 -128 11t-125.5 46t-113 89t-78 149.5t-30.5 220.5zM381 588h412q-10 106 -62.5 179t-144.5 73 t-143.5 -59.5t-61.5 -192.5z" />
-<glyph unicode="f" horiz-adv-x="731" d="M82 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174z" />
-<glyph unicode="g" horiz-adv-x="1122" d="M123 514q0 135 35 235.5t83 153.5t113.5 86t114.5 41t98 8q215 0 316.5 -98t101.5 -238v-727q0 -49 -9 -93t-38 -98t-77 -92t-135 -64.5t-202 -26.5q-162 0 -295 41v219q123 -41 267 -41q86 0 140 13t73.5 41t24.5 47.5t5 53.5v56q-82 -16 -155 -17q-223 0 -342 129 t-119 371zM369 510q0 -172 55 -233.5t168 -61.5q75 0 147 14v476q0 114 -169 114h-3q-98 0 -148 -71.5t-50 -237.5z" />
-<glyph unicode="h" horiz-adv-x="1228" d="M193 0v1411h245v-407q88 34 205 34q213 0 333 -111.5t120 -312.5v-614h-244v635q0 41 -7 68.5t-30.5 57.5t-77 45t-135.5 15q-96 0 -164 -30v-791h-245z" />
-<glyph unicode="i" horiz-adv-x="638" d="M193 1161v225h251v-225h-251zM197 0v1022h245v-1022h-245z" />
-<glyph unicode="j" horiz-adv-x="593" d="M6 -205q70 4 112 17.5t57 42t18.5 49t3.5 65.5v1053h245v-1036q0 -66 -7 -113t-33.5 -99.5t-72.5 -85t-128 -56t-195 -27.5v190zM193 1161v225h251v-225h-251z" />
-<glyph unicode="k" horiz-adv-x="1226" d="M188 0v1405h246v-758l344 375h326l-445 -455l492 -567h-311l-338 403l-68 -69v-334h-246z" />
-<glyph unicode="l" horiz-adv-x="659" d="M207 0v1411h246v-1411h-246z" />
-<glyph unicode="m" horiz-adv-x="1908" d="M193 0v924q227 114 446 114h4q166 0 277 -75q190 75 368 75q207 0 322.5 -112.5t115.5 -311.5v-614h-245v631q0 98 -49.5 148t-184.5 50q-106 0 -209 -24q43 -49 43 -191v-614h-243v637q0 94 -48.5 141t-181.5 47q-88 0 -170 -16v-809h-245z" />
-<glyph unicode="n" horiz-adv-x="1273" d="M168 0v926q225 112 469 112q213 0 333 -111.5t120 -312.5v-614h-244v635q0 41 -7.5 68.5t-30 57.5t-73.5 45t-131 15q-90 0 -190 -18v-803h-246z" />
-<glyph unicode="o" horiz-adv-x="1159" d="M100 510q0 254 125 390t358.5 136t358.5 -136t125 -390t-125 -390t-358.5 -136t-358.5 136t-125 390zM346 509.5q0 -190.5 58.5 -257t179.5 -66.5t179 66.5t58 257t-58 257.5t-179 67t-179.5 -67t-58.5 -257.5z" />
-<glyph unicode="p" horiz-adv-x="1175" d="M168 -379v1071q0 141 105.5 242.5t324.5 101.5q51 0 101.5 -8t119 -42t117.5 -88t85 -158.5t36 -241.5q0 -250 -123 -384t-352 -134q-86 0 -168 18v-377h-246zM414 182q85 -12 151 -12q119 0 181.5 69.5t62.5 245.5q0 180 -47 260t-164 80q-184 0 -184 -110v-533z" />
-<glyph unicode="q" horiz-adv-x="1171" d="M115 498q0 137 34.5 241.5t85 158.5t118 88t117.5 42t101 8q219 0 326 -101t107 -243v-1071h-246v377q-82 -18 -170 -18q-229 0 -351 134t-122 384zM360 485q0 -176 62.5 -245.5t181.5 -69.5q68 0 154 12v533q0 110 -184 110h-3q-117 0 -164 -80t-47 -260z" />
-<glyph unicode="r" horiz-adv-x="741" d="M166 0v930q25 12 69 28.5t177 47t264 32.5v-213q-154 -2 -264 -34v-791h-246z" />
-<glyph unicode="s" horiz-adv-x="923" d="M100 719q0 154 108.5 236.5t270.5 82.5q152 0 279 -73v-187q-104 49 -236 49q-176 0 -176 -86q0 -43 50 -67.5t122 -43t143.5 -50t122 -106.5t50.5 -191q0 -129 -104.5 -214t-276.5 -85q-176 0 -324 84v219q135 -90 289 -90q106 0 138 24.5t32 73.5q0 45 -50.5 72.5 t-122 48t-143 48.5t-122 92.5t-50.5 162.5z" />
-<glyph unicode="t" horiz-adv-x="794" d="M49 811v211h172v303h246v-303h242v-211h-242v-504q0 -66 27.5 -89t97.5 -23q66 0 117 22v-203q-72 -30 -157 -30h-5q-152 0 -239 90t-87 248v489h-172z" />
-<glyph unicode="u" horiz-adv-x="1259" d="M168 463v559h248v-592q0 -123 50 -177t163 -54q115 0 165 54t50 177v592h248v-559q0 -225 -123 -352t-340 -127t-339 127t-122 352z" />
-<glyph unicode="v" horiz-adv-x="1169" d="M66 1022h262l256 -772l256 772h264l-391 -1022h-256z" />
-<glyph unicode="w" horiz-adv-x="1710" d="M78 1022h260l217 -737l195 737h211l194 -737l217 737h260l-362 -1022h-246l-168 588l-170 -588h-246z" />
-<glyph unicode="x" horiz-adv-x="1142" d="M57 0l363 516l-354 506h301l204 -291l205 291h273l-342 -483l378 -539h-301l-229 326l-231 -326h-267z" />
-<glyph unicode="y" horiz-adv-x="1107" d="M63 1022h261l229 -713l225 713h260l-389 -1047q-82 -223 -178 -300.5t-199 -77.5q-76 0 -155 28v205q59 -25 116 -25q106 0 197 248z" />
-<glyph unicode="z" horiz-adv-x="999" d="M92 0v229l471 570h-461v223h777v-227l-478 -572h478v-223h-787z" />
-<glyph unicode="{" horiz-adv-x="784" d="M186 506v194q12 2 28.5 7.5t45.5 34t29 71.5v293q0 145 81 235.5t242 90.5h41v-185h-24q-43 0 -70 -11t-37 -34.5t-13 -41t-3 -48.5v-289q0 -86 -52.5 -142t-117.5 -79q66 -20 118 -76.5t52 -142.5v-289q0 -31 3 -48t13 -40.5t37 -35t70 -11.5h24v-184h-41 q-162 0 -242.5 90t-80.5 235v293q0 41 -25.5 70t-50.5 37z" />
-<glyph unicode="|" horiz-adv-x="954" d="M379 -203v1569h219v-1569h-219z" />
-<glyph unicode="}" horiz-adv-x="784" d="M156 -41h22q43 0 69.5 11.5t37 35t13.5 40.5t3 48v289q0 86 52.5 142.5t117.5 76.5q-66 23 -118 79t-52 142v289q0 31 -3 48.5t-13.5 41t-37 34.5t-69.5 11h-22v185h39q164 0 244.5 -89.5t80.5 -236.5v-293q0 -43 25.5 -70.5t52.5 -33.5l25 -9v-194q-4 0 -11.5 -2 t-25 -10.5t-30.5 -19.5t-24.5 -32.5t-11.5 -48.5v-293q0 -147 -81 -236t-244 -89h-39v184z" />
-<glyph unicode="~" horiz-adv-x="1071" d="M201 457v168q57 68 129 74q11 1 23 1q59 0 116 -26l135 -60q57 -25 115 -25q12 0 23 1q71 6 128 74v-168q-57 -68 -128 -74q-11 -1 -23 -1q-58 0 -115 25l-135 60q-56 26 -115 26q-12 0 -24 -2q-72 -6 -129 -73z" />
-<glyph unicode="¡" horiz-adv-x="825" d="M283 -344l45 973h190l45 -973h-280zM303 795v227h240v-227h-240z" />
-<glyph unicode="¢" horiz-adv-x="976" d="M117 682q0 246 115.5 382t334.5 146v156h119v-160q98 -10 209 -53v-205q-98 41 -209 47v-626q104 6 209 49v-205q-111 -43 -209 -53v-160h-119v156q-219 10 -334.5 145t-115.5 381zM362 682q0 -158 51.5 -228.5t153.5 -82.5v622q-102 -12 -153.5 -81.5t-51.5 -229.5z" />
-<glyph unicode="£" horiz-adv-x="1153" d="M100 -31v209q51 39 174 68v319h-174v195h174v235q0 160 105.5 273.5t327.5 113.5q168 0 331 -94v-211q-149 113 -313 113q-117 0 -178.5 -56.5t-61.5 -152.5v-221h377v-195h-377v-325q6 -2 75 -20.5t85.5 -21.5t74.5 -14q49 -10 76 -10h8q27 2 74.5 4t85 15.5t74.5 35.5 v-215q-86 -44 -182 -44h-4q-98 1 -186 25.5t-178 46.5q-54 13 -112 13q-38 0 -78 -6q-101 -14 -198 -80z" />
-<glyph unicode="¥" horiz-adv-x="1396" d="M37 1366h297l362 -555l361 555h297l-410 -598h162v-184h-287v-160h287v-184h-287v-240h-246v240h-288v184h288v160h-288v184h161z" />
-<glyph unicode="§" horiz-adv-x="1247" d="M137 709q0 98 79 152t175 59q-47 20 -78.5 69t-31.5 103q0 106 67.5 174.5t158.5 93t204 24.5q184 0 344 -92v-211q-164 78 -314 78q-74 0 -135 -21.5t-61 -72.5q0 -27 42 -50.5t105.5 -41t136 -46t136 -61t105.5 -90t42 -131.5q0 -86 -65.5 -137t-161.5 -62 q84 -51 84 -167q0 -133 -117 -214t-315 -81q-211 0 -394 120v230q164 -133 369 -133q82 0 141.5 22.5t59.5 69.5q0 35 -59.5 60.5t-143.5 50t-169 56.5t-144.5 95t-59.5 154zM401 719q0 -43 68 -75t254 -93q61 6 93 33.5t32 54.5q0 37 -55.5 66.5t-147.5 57.5t-106 34 q-45 -4 -91.5 -24.5t-46.5 -53.5z" />
-<glyph unicode="¨" horiz-adv-x="856" d="M162 1155v195h207v-195h-207zM489 1155v195h207v-195h-207z" />
-<glyph unicode="©" horiz-adv-x="1593" d="M98 682q0 291 205 495.5t494 204.5q291 0 495.5 -204.5t204.5 -495.5q0 -289 -205 -493.5t-495 -204.5q-289 0 -494 204.5t-205 493.5zM203 682q0 -246 174 -420t420 -174q248 0 422 174t174 420q0 248 -174.5 422t-421.5 174q-246 0 -420 -174t-174 -422zM440 682 q0 121 33 206t90.5 127t116.5 59.5t131 17.5q123 0 242 -48v-135q-117 53 -229 53q-1 1 -2 1q-112 0 -174 -57q-64 -56 -64 -224t63 -223.5t176 -55.5q119 0 230 54v-135q-119 -47 -242 -48q-72 0 -131 17.5t-116.5 58.5t-90.5 126t-33 206z" />
-<glyph unicode="ª" horiz-adv-x="808" d="M154 989q0 25 7 48.5t28.5 57t74.5 54t133 20.5q39 0 101 -8q0 106 -121 107q-98 0 -166 -29v111q78 32 174 32q121 0 186.5 -62.5t65.5 -178.5v-154q0 -80 -58.5 -136t-181.5 -56q-125 0 -184 57t-59 137zM289 1008q0 -92 106 -93q105 1 105 89v71q-74 6 -82 6 q-129 0 -129 -73z" />
-<glyph unicode="«" horiz-adv-x="1247" d="M66 522l346 389h286l-372 -389l372 -391h-286zM522 522l346 389h287l-373 -389l373 -391h-287z" />
-<glyph unicode="¬" horiz-adv-x="1273" d="M199 492v192h876v-389h-176v197h-700z" />
-<glyph unicode="­" horiz-adv-x="950" d="M162 514v193h624v-193h-624z" />
-<glyph unicode="®" horiz-adv-x="1593" d="M100 682q0 291 205 495.5t494 204.5q291 0 495.5 -204.5t204.5 -495.5q0 -289 -204.5 -493.5t-495.5 -204.5q-289 0 -494 204.5t-205 493.5zM205 682q0 -246 174 -420t420 -174q248 0 422 174t174 420q0 248 -174 422t-422 174q-246 0 -420 -174t-174 -422zM528 285v796 h306q139 0 216.5 -66.5t77.5 -189.5q0 -184 -174 -235l209 -305h-174l-188 280h-129v-280h-144zM672 696h157q72 0 114 26t42 99.5t-42 100t-114 26.5h-157v-252z" />
-<glyph unicode="¯" horiz-adv-x="825" d="M176 1180v145h479v-145h-479z" />
-<glyph unicode="°" horiz-adv-x="792" d="M168 1143q0 111 57.5 169t166 58t165.5 -58.5t57 -168.5q0 -109 -57 -167t-165.5 -58t-166 58t-57.5 167zM276 1145q0 -68 28 -97.5t87.5 -29.5t86 29.5t26.5 97.5q0 66 -26.5 95.5t-86 29.5t-87.5 -30t-28 -95z" />
-<glyph unicode="±" horiz-adv-x="1198" d="M242 61v166h727v-166h-727zM242 604v182h272v275h182v-275h273v-182h-273v-270h-182v270h-272z" />
-<glyph unicode="²" horiz-adv-x="696" d="M109 948v109q125 92 234.5 209.5t109.5 208.5q0 88 -129 88q-119 0 -209 -66v133q106 63 213 64q131 0 193.5 -64.5t62.5 -154.5q0 -180 -254 -396h266v-131h-487z" />
-<glyph unicode="³" horiz-adv-x="686" d="M111 993v135q96 -57 200 -57q158 0 158 100q0 113 -162 113h-94v109h98q61 0 91 25.5t30 62.5q0 80 -129 80q-100 0 -176 -48v136q98 45 184 45q109 0 181.5 -49.5t72.5 -159.5q0 -90 -90 -139q125 -43 125 -177q0 -121 -85 -175t-196 -54q-109 0 -208 53z" />
-<glyph unicode="´" horiz-adv-x="528" d="M121 1155l127 240h201l-191 -240h-137z" />
-<glyph unicode="µ" horiz-adv-x="1323" d="M203 -430v1452h243v-635q0 -41 7.5 -68.5t30 -57.5t73.5 -45t131 -15q90 0 191 18v803h245v-926q-223 -112 -465 -112h-4q-127 0 -209 51v-465h-243z" />
-<glyph unicode="¶" horiz-adv-x="911" d="M78 946q0 203 121 311.5t350 108.5h299v-1366h-232v524h-67q-229 0 -350 108.5t-121 313.5z" />
-<glyph unicode="·" horiz-adv-x="663" d="M211 444v228h240v-228h-240z" />
-<glyph unicode="¸" horiz-adv-x="563" d="M119 -256l178 246h160l-127 -246h-211z" />
-<glyph unicode="¹" horiz-adv-x="512" d="M109 1452v141l143 90h121v-735h-133v586z" />
-<glyph unicode="º" horiz-adv-x="854" d="M152 1092q0 141 69.5 216.5t200.5 75.5t200.5 -75.5t69.5 -216.5q0 -143 -69.5 -219t-200.5 -76t-200.5 75.5t-69.5 219.5zM289 1092q0 -106 32.5 -144.5t100.5 -38.5q66 0 99.5 38t33.5 145q0 104 -34 142t-99 38q-68 0 -100.5 -38t-32.5 -142z" />
-<glyph unicode="»" horiz-adv-x="1277" d="M106 131l373 389l-373 391h287l346 -391l-346 -389h-287zM563 131l373 389l-373 391h287l346 -391l-346 -389h-287z" />
-<glyph unicode="¼" horiz-adv-x="1476" d="M109 1149v141l143 90h121v-735h-133v586zM168 -45l895 1448h164l-895 -1448h-164zM825 209v108l336 418h125v-409h96v-117h-96v-209h-131v209h-330zM981 326h174v227z" />
-<glyph unicode="½" horiz-adv-x="1527" d="M109 1149v141l143 90h121v-735h-133v586zM168 -45l895 1448h164l-895 -1448h-164zM936 0v109q125 92 234.5 209.5t109.5 207.5q0 88 -129 88q-119 0 -209 -65v133q106 63 213 63q131 0 193.5 -64.5t62.5 -154.5q0 -180 -254 -395h266v-131h-487z" />
-<glyph unicode="¾" horiz-adv-x="1691" d="M135 688v135q96 -57 201 -57q158 0 158 100q0 113 -162 113h-94v108h98q61 0 91 26t30 63q0 79 -126 79h-3q-100 0 -176 -47v135q98 45 184 46q109 0 181.5 -49.5t72.5 -159.5q0 -91 -90 -140q125 -43 125 -176q0 -121 -85 -175t-196 -54q-109 0 -209 53zM369 -45 l895 1448h163l-895 -1448h-163zM1032 209v108l336 418h125v-409h96v-117h-96v-209h-131v209h-330zM1188 326h174v227z" />
-<glyph unicode="¿" horiz-adv-x="1042" d="M156 37q0 70 22.5 126t56 92t72.5 74l73 73q34 34 56.5 87.5t22.5 118.5v21h217v-27q0 -104 -31 -184t-76 -127t-89 -86t-74.5 -81t-30.5 -89q0 -182 221 -182q180 0 305 110v-227q-150 -96 -321 -96q-115 0 -200 33.5t-132 92t-69.5 126t-22.5 145.5zM446 795v227h240 v-227h-240z" />
-<glyph unicode="À" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM459 1722h200l127 -239h-137zM516 551h408l-203 536z" />
-<glyph unicode="Á" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM516 551h408l-203 536zM655 1483l127 239h201l-190 -239h-138z" />
-<glyph unicode="Â" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM485 1483l187 250h98l188 -250h-163l-74 114l-74 -114h-162zM516 551h408l-203 536z" />
-<glyph unicode="Ã" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM410 1481v155q57 68 124 74q10 1 21 1q55 0 105 -25l122 -61q51 -25 104 -25q12 0 24 2q65 6 122 73v-155q-57 -68 -122 -74q-12 -1 -23 -1q-53 0 -105 25l-122 60q-50 26 -104 26q-11 0 -22 -2 q-67 -6 -124 -73zM516 551h408l-203 536z" />
-<glyph unicode="Ä" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM455 1483v194h207v-194h-207zM516 551h408l-203 536zM782 1483v194h207v-194h-207z" />
-<glyph unicode="Å" d="M43 0l543 1366h270l543 -1366h-264l-125 328h-578l-125 -328h-264zM516 551h408l-203 536zM543 1599.5q0 83.5 47 130t131 46.5q86 0 132 -46.5t46 -130t-46 -130t-132 -46.5q-84 0 -131 46.5t-47 130zM651 1599.5q0 -49.5 15.5 -69t54.5 -19.5t54.5 19.5t15.5 69 t-15.5 69t-54.5 19.5t-54.5 -19.5t-15.5 -69z" />
-<glyph unicode="Æ" horiz-adv-x="2039" d="M51 0l864 1366h1002v-221h-664v-346h576v-232h-576v-346h664v-221h-909v307h-478l-188 -307h-291zM655 512h353v578z" />
-<glyph unicode="Ç" horiz-adv-x="1286" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16q211 0 412 -81v-230q-191 90 -390 90q-92 0 -157.5 -16.5t-128 -63.5t-95 -147t-32.5 -252t32.5 -252t95 -147t128 -63.5t157.5 -16.5q199 0 390 90v-229q-170 -72 -349 -80l-123 -242h-213l177 244 q-109 12 -198 50t-171 113.5t-127 212t-45 320.5z" />
-<glyph unicode="È" horiz-adv-x="1204" d="M178 0v1366h909v-221h-663v-346h575v-232h-575v-346h663v-221h-909zM397 1722h201l127 -239h-137z" />
-<glyph unicode="É" horiz-adv-x="1204" d="M178 0v1366h909v-221h-663v-346h575v-232h-575v-346h663v-221h-909zM555 1483l127 239h201l-191 -239h-137z" />
-<glyph unicode="Ê" horiz-adv-x="1204" d="M178 0v1366h909v-221h-663v-346h575v-232h-575v-346h663v-221h-909zM414 1483l186 250h98l189 -250h-164l-74 114l-74 -114h-161z" />
-<glyph unicode="Ë" horiz-adv-x="1204" d="M178 0v1366h909v-221h-663v-346h575v-232h-575v-346h663v-221h-909zM389 1483v194h207v-194h-207zM717 1483v194h207v-194h-207z" />
-<glyph unicode="Ì" horiz-adv-x="600" d="M35 1722h201l126 -239h-137zM178 0h246v1366h-246v-1366z" />
-<glyph unicode="Í" horiz-adv-x="600" d="M178 0h246v1366h-246v-1366zM240 1483l127 239h200l-190 -239h-137z" />
-<glyph unicode="Î" horiz-adv-x="600" d="M63 1483l187 250h98l189 -250h-164l-74 114l-74 -114h-162zM178 0h246v1366h-246v-1366z" />
-<glyph unicode="Ï" horiz-adv-x="600" d="M33 1483v194h207v-194h-207zM178 0h246v1366h-246v-1366zM360 1483v194h207v-194h-207z" />
-<glyph unicode="Ð" horiz-adv-x="1509" d="M106 598v168h162v600h471q317 0 481 -174t164 -510q0 -334 -163.5 -508t-481.5 -174h-471v598h-162zM514 223h225q213 0 306.5 107.5t93.5 351.5q0 246 -93.5 353.5t-306.5 107.5h-225v-377h242v-168h-242v-375z" />
-<glyph unicode="Ñ" horiz-adv-x="1587" d="M178 0v1366h279l702 -1012v1012h246v-1366h-275l-706 1020v-1020h-246zM481 1481v155q57 68 124 74q11 1 22 1q55 0 105 -25l122 -61q50 -25 104 -25q11 0 23 2q66 6 123 73v-155q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -105 25l-122 60q-50 26 -104 26q-11 0 -22 -2 q-67 -6 -125 -73z" />
-<glyph unicode="Ò" horiz-adv-x="1538" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16t-179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM371 684q0 -154 30.5 -255 t90 -148.5t122 -64.5t148.5 -17q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 152 -30.5 253t-91 148.5t-122 63.5t-149.5 16q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212zM518 1722h201l127 -239h-137z" />
-<glyph unicode="Ó" horiz-adv-x="1538" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16t-179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM371 684q0 -154 30.5 -255 t90 -148.5t122 -64.5t148.5 -17q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 152 -30.5 253t-91 148.5t-122 63.5t-149.5 16q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212zM680 1483l127 239h201l-191 -239h-137z" />
-<glyph unicode="Ô" horiz-adv-x="1538" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16t-179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM371 684q0 -154 30.5 -255 t90 -148.5t122 -64.5t148.5 -17q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 152 -30.5 253t-91 148.5t-122 63.5t-149.5 16q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212zM526 1483l187 250h98l188 -250h-163l-74 114l-74 -114h-162z" />
-<glyph unicode="Õ" horiz-adv-x="1538" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16t-179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM371 684q0 -154 30.5 -255 t90 -148.5t122 -64.5t148.5 -17q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 152 -30.5 253t-91 148.5t-122 63.5t-149.5 16q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212zM451 1481v155q57 68 123 74q11 1 22 1q55 0 106 -25l122 -61q50 -25 103 -25 q12 0 23 2q66 6 123 73v-155q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60q-51 26 -105 26q-11 0 -23 -2q-66 -6 -123 -73z" />
-<glyph unicode="Ö" horiz-adv-x="1538" d="M125 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16t179 -16t170 -63.5t149.5 -123t101.5 -202.5t39 -295q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16t-179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM371 684q0 -154 30.5 -255 t90 -148.5t122 -64.5t148.5 -17q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 152 -30.5 253t-91 148.5t-122 63.5t-149.5 16q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212zM496 1483v194h206v-194h-206zM823 1483v194h207v-194h-207z" />
-<glyph unicode="×" horiz-adv-x="1062" d="M156 160l282 364l-282 365h184l190 -246l193 246h182l-280 -365l280 -364h-182l-193 243l-190 -243h-184z" />
-<glyph unicode="Ø" horiz-adv-x="1529" d="M68 0l178 211q-121 174 -121 471q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16q233 0 385 -102l74 86h237l-180 -213q123 -178 123 -471q0 -166 -39 -294t-101.5 -202.5t-149.5 -122t-170 -63.5t-179 -16q-233 0 -387 100l-70 -84h-237zM371 684q0 -168 39 -278 l594 702q-84 57 -242 57q-68 0 -119 -9t-105.5 -39t-88 -81t-56 -140t-22.5 -212zM520 256q84 -57 242 -57q70 0 121 9t105 38.5t88 82t56.5 142.5t22.5 213q0 162 -41 274z" />
-<glyph unicode="Ù" horiz-adv-x="1511" d="M178 578v788h246v-809q0 -180 78 -264t248 -84t248.5 84t78.5 264v809h246v-788q0 -283 -150.5 -438.5t-423 -155.5t-422 155.5t-149.5 438.5zM506 1722h201l127 -239h-138z" />
-<glyph unicode="Ú" horiz-adv-x="1511" d="M178 578v788h246v-809q0 -180 78 -264t248 -84t248.5 84t78.5 264v809h246v-788q0 -283 -150.5 -438.5t-423 -155.5t-422 155.5t-149.5 438.5zM668 1483l127 239h200l-190 -239h-137z" />
-<glyph unicode="Û" horiz-adv-x="1511" d="M178 578v788h246v-809q0 -180 78 -264t248 -84t248.5 84t78.5 264v809h246v-788q0 -283 -150.5 -438.5t-423 -155.5t-422 155.5t-149.5 438.5zM514 1483l186 250h99l188 -250h-164l-73 114l-74 -114h-162z" />
-<glyph unicode="Ü" horiz-adv-x="1511" d="M178 578v788h246v-809q0 -180 78 -264t248 -84t248.5 84t78.5 264v809h246v-788q0 -283 -150.5 -438.5t-423 -155.5t-422 155.5t-149.5 438.5zM483 1483v194h207v-194h-207zM811 1483v194h207v-194h-207z" />
-<glyph unicode="Ý" horiz-adv-x="1368" d="M25 1366h297l362 -555l360 555h297l-534 -780v-586h-246v586zM594 1483l127 239h201l-191 -239h-137z" />
-<glyph unicode="Þ" horiz-adv-x="1253" d="M176 0v1366h246v-172h209q242 0 374 -120t132 -339t-132.5 -339t-373.5 -120h-209v-276h-246zM422 500h190q162 0 221.5 59t59.5 176q0 115 -59.5 173.5t-221.5 58.5h-190v-467z" />
-<glyph unicode="ß" horiz-adv-x="1255" d="M180 0v856q0 156 40 265.5t108.5 162.5t140.5 75.5t156 22.5q223 0 344 -113t121 -265q0 -109 -63.5 -187t-133.5 -104q37 -10 70.5 -27.5t82 -51.5t77 -93.5t28.5 -134.5q0 -201 -160 -310q-144 -97 -364 -97q-24 0 -49 1v199h12q334 0 334 215q0 193 -346 204v181 q66 0 124 11t113 58t55 127q0 213 -219 213q-51 0 -89 -13t-75.5 -48t-58 -109.5t-20.5 -187.5v-850h-228z" />
-<glyph unicode="à" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM313 1395h201l127 -240h-137z M358 365q0 -164 193 -164h2q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133z" />
-<glyph unicode="á" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM358 365q0 -164 193 -164h2 q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133zM459 1155l127 240h200l-190 -240h-137z" />
-<glyph unicode="â" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM338 1155l186 250h99l188 -250h-164 l-74 115l-73 -115h-162zM358 365q0 -164 193 -164h2q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133z" />
-<glyph unicode="ã" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM258 1153v156q57 68 124 74 q11 1 22 1q55 0 105 -26l121 -60q52 -25 105 -25q11 0 23 1q65 6 123 74v-156q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -106 25l-121 60q-50 26 -105 26q-11 0 -22 -1q-67 -6 -124 -74zM358 365q0 -164 193 -164h2q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133z" />
-<glyph unicode="ä" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM287 1155v195h207v-195h-207z M358 365q0 -164 193 -164h2q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133zM614 1155v195h207v-195h-207z" />
-<glyph unicode="å" horiz-adv-x="1110" d="M115 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q219 0 335 -111.5t116 -322.5v-276q0 -141 -105.5 -242.5t-324.5 -101.5q-225 0 -331.5 102t-106.5 248zM358 365q0 -164 193 -164h2 q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133zM389 1272q0 84 47 130t131 46q86 0 132 -46t46 -130t-46 -130t-132 -46q-84 0 -131 46t-47 130zM498 1272q0 -49 15 -68.5t54 -19.5t54.5 19.5t15.5 68.5t-15.5 68.5t-54.5 19.5t-54 -19.5t-15 -68.5z" />
-<glyph unicode="æ" horiz-adv-x="1792" d="M109 334q0 31 6 63.5t31.5 81.5t69.5 86t130 63.5t203 26.5q84 0 180 -14q0 193 -217 193q-172 0 -299 -52v199q141 57 313 57q236 0 338 -143q35 57 121 101t199 44q225 0 340.5 -143t115.5 -352q0 -86 -4 -127h-657q10 -133 79.5 -178t180.5 -45q180 0 348 79v-198 q-170 -92 -385 -92q-104 0 -193 43t-135 102q-106 -145 -327 -145q-225 0 -331.5 102t-106.5 248zM352 365q0 -164 193 -164h2q184 0 184 155v129q-86 12 -147 13q-231 0 -232 -133zM979 588h412q-10 106 -62.5 179t-144.5 73t-143.5 -59.5t-61.5 -192.5z" />
-<glyph unicode="ç" horiz-adv-x="966" d="M119 510q0 256 125 392t358 136q150 0 295 -57v-205q-121 49 -254 49q-141 0 -209.5 -64.5t-68.5 -250.5q0 -184 68.5 -249.5t209.5 -65.5q127 0 254 51v-205q-106 -45 -227 -53l-123 -244h-213l176 246q-190 25 -290.5 158t-100.5 362z" />
-<glyph unicode="è" horiz-adv-x="1144" d="M129 500q0 246 109.5 393t347.5 147q225 0 340.5 -143t115.5 -352q0 -86 -4 -127h-657q10 -133 79.5 -178t180.5 -45q180 0 348 79v-198q-170 -92 -385 -92q-68 0 -128 11t-125.5 46t-113 89t-78 149.5t-30.5 220.5zM334 1395h201l127 -240h-138zM381 588h412 q-10 106 -62.5 179t-144.5 73t-143.5 -59.5t-61.5 -192.5z" />
-<glyph unicode="é" horiz-adv-x="1144" d="M129 500q0 246 109.5 393t347.5 147q225 0 340.5 -143t115.5 -352q0 -86 -4 -127h-657q10 -133 79.5 -178t180.5 -45q180 0 348 79v-198q-170 -92 -385 -92q-68 0 -128 11t-125.5 46t-113 89t-78 149.5t-30.5 220.5zM381 588h412q-10 106 -62.5 179t-144.5 73 t-143.5 -59.5t-61.5 -192.5zM496 1155l127 240h200l-190 -240h-137z" />
-<glyph unicode="ê" horiz-adv-x="1144" d="M129 500q0 246 109.5 393t347.5 147q225 0 340.5 -143t115.5 -352q0 -86 -4 -127h-657q10 -133 79.5 -178t180.5 -45q180 0 348 79v-198q-170 -92 -385 -92q-68 0 -128 11t-125.5 46t-113 89t-78 149.5t-30.5 220.5zM346 1155l186 250h99l188 -250h-164l-73 115l-74 -115 h-162zM381 588h412q-10 106 -62.5 179t-144.5 73t-143.5 -59.5t-61.5 -192.5z" />
-<glyph unicode="ë" horiz-adv-x="1144" d="M129 500q0 246 109.5 393t347.5 147q225 0 340.5 -143t115.5 -352q0 -86 -4 -127h-657q10 -133 79.5 -178t180.5 -45q180 0 348 79v-198q-170 -92 -385 -92q-68 0 -128 11t-125.5 46t-113 89t-78 149.5t-30.5 220.5zM309 1155v195h207v-195h-207zM381 588h412 q-10 106 -62.5 179t-144.5 73t-143.5 -59.5t-61.5 -192.5zM637 1155v195h207v-195h-207z" />
-<glyph unicode="ì" horiz-adv-x="638" d="M63 1395h201l127 -240h-137zM197 0h245v1022h-245v-1022z" />
-<glyph unicode="í" horiz-adv-x="638" d="M197 0h245v1022h-245v-1022zM248 1155l127 240h200l-190 -240h-137z" />
-<glyph unicode="î" horiz-adv-x="638" d="M84 1155l186 250h99l188 -250h-164l-74 115l-73 -115h-162zM197 0h245v1022h-245v-1022z" />
-<glyph unicode="ï" horiz-adv-x="638" d="M53 1155v195h207v-195h-207zM197 0h245v1022h-245v-1022zM381 1155v195h207v-195h-207z" />
-<glyph unicode="ð" horiz-adv-x="1232" d="M117 461q0 211 120.5 331.5t342.5 120.5q160 0 249 -69q-27 152 -145 237l-180 -123l-78 113l104 74q-41 10 -90 10q-82 0 -145 -10v219q92 20 160 20q178 0 315 -75l162 112l78 -114l-123 -84q199 -195 198 -594q0 -645 -485 -645q-127 0 -224 43t-151.5 114.5 t-81 152.5t-26.5 167zM365 463q0 -31 7 -66t27.5 -84t72.5 -80.5t128 -31.5q39 0 76 16t79 57t65 132q19 76 19 179q0 19 -1 38q-103 69 -234 69h-4q-113 0 -174 -62.5t-61 -166.5z" />
-<glyph unicode="ñ" horiz-adv-x="1273" d="M168 0v926q225 112 469 112q213 0 333 -111.5t120 -312.5v-614h-244v635q0 41 -7.5 68.5t-30 57.5t-73.5 45t-131 15q-90 0 -190 -18v-803h-246zM326 1153v156q57 68 124 74q10 1 21 1q55 0 105 -26l122 -60q52 -25 105 -25q11 0 23 1q65 6 122 74v-156q-57 -68 -122 -74 q-12 -1 -23 -1q-53 0 -105 25l-122 60q-50 26 -105 26q-11 0 -21 -1q-67 -6 -124 -74z" />
-<glyph unicode="ò" horiz-adv-x="1159" d="M100 510q0 254 125 390t358.5 136t358.5 -136t125 -390t-125 -390t-358.5 -136t-358.5 136t-125 390zM326 1395h200l127 -240h-137zM346 509.5q0 -190.5 58.5 -257t179.5 -66.5t179 66.5t58 257t-58 257.5t-179 67t-179.5 -67t-58.5 -257.5z" />
-<glyph unicode="ó" horiz-adv-x="1159" d="M100 510q0 254 125 390t358.5 136t358.5 -136t125 -390t-125 -390t-358.5 -136t-358.5 136t-125 390zM346 509.5q0 -190.5 58.5 -257t179.5 -66.5t179 66.5t58 257t-58 257.5t-179 67t-179.5 -67t-58.5 -257.5zM514 1155l127 240h201l-191 -240h-137z" />
-<glyph unicode="ô" horiz-adv-x="1159" d="M100 510q0 254 125 390t358.5 136t358.5 -136t125 -390t-125 -390t-358.5 -136t-358.5 136t-125 390zM346 509.5q0 -190.5 58.5 -257t179.5 -66.5t179 66.5t58 257t-58 257.5t-179 67t-179.5 -67t-58.5 -257.5zM348 1155l187 250h98l188 -250h-164l-73 115l-74 -115h-162 z" />
-<glyph unicode="õ" horiz-adv-x="1159" d="M100 510q0 254 125 390t358.5 136t358.5 -136t125 -390t-125 -390t-358.5 -136t-358.5 136t-125 390zM272 1153v156q57 68 124 74q11 1 22 1q55 0 106 -26l122 -60q51 -25 104 -25q11 0 22 1q66 6 123 74v-156q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60 q-51 26 -106 26q-11 0 -22 -1q-66 -6 -124 -74zM346 509.5q0 -190.5 58.5 -257t179.5 -66.5t179 66.5t58 257t-58 257.5t-179 67t-179.5 -67t-58.5 -257.5z" />
-<glyph unicode="ö" horiz-adv-x="1159" d="M100 510q0 254 125 390t358.5 136t358.5 -136t125 -390t-125 -390t-358.5 -136t-358.5 136t-125 390zM317 1155v195h207v-195h-207zM346 509.5q0 -190.5 58.5 -257t179.5 -66.5t179 66.5t58 257t-58 257.5t-179 67t-179.5 -67t-58.5 -257.5zM645 1155v195h207v-195h-207z " />
-<glyph unicode="÷" horiz-adv-x="1286" d="M211 446v164h866v-164h-866zM541 129v193h207v-193h-207zM541 737v193h207v-193h-207z" />
-<glyph unicode="ø" horiz-adv-x="1175" d="M39 0l147 176q-82 133 -82 334q0 254 125 390t359 136q164 0 270 -63l43 49h238l-150 -176q82 -127 82 -336q0 -254 -125 -390t-358 -136q-158 0 -271 65l-41 -49h-237zM350 510q0 -68 10 -129l361 426q-47 27 -133 27q-121 0 -179.5 -67t-58.5 -257zM457 215 q51 -29 131 -29q121 0 179 67t58 257q0 82 -8 131z" />
-<glyph unicode="ù" horiz-adv-x="1259" d="M168 463v559h248v-592q0 -123 50 -177t163 -54q115 0 165 54t50 177v592h248v-559q0 -225 -123 -352t-340 -127t-339 127t-122 352zM381 1395h201l127 -240h-138z" />
-<glyph unicode="ú" horiz-adv-x="1259" d="M168 463v559h248v-592q0 -123 50 -177t163 -54q115 0 165 54t50 177v592h248v-559q0 -225 -123 -352t-340 -127t-339 127t-122 352zM551 1155l127 240h201l-191 -240h-137z" />
-<glyph unicode="û" horiz-adv-x="1259" d="M168 463v559h248v-592q0 -123 50 -177t163 -54q115 0 165 54t50 177v592h248v-559q0 -225 -123 -352t-340 -127t-339 127t-122 352zM393 1155l187 250h98l188 -250h-164l-73 115l-74 -115h-162z" />
-<glyph unicode="ü" horiz-adv-x="1259" d="M168 463v559h248v-592q0 -123 50 -177t163 -54q115 0 165 54t50 177v592h248v-559q0 -225 -123 -352t-340 -127t-339 127t-122 352zM362 1155v195h207v-195h-207zM690 1155v195h207v-195h-207z" />
-<glyph unicode="ý" horiz-adv-x="1107" d="M63 1022h261l229 -713l225 713h260l-389 -1047q-82 -223 -178 -300.5t-199 -77.5q-76 0 -155 28v205q59 -25 116 -25q106 0 197 248zM473 1155l127 240h201l-191 -240h-137z" />
-<glyph unicode="þ" horiz-adv-x="1157" d="M170 -377v1778h246v-389q88 16 168 16q229 0 352 -133t123 -383t-123 -383t-352 -133q-80 0 -168 16v-389h-246zM416 207q69 -17 159 -17q115 0 175.5 71t60.5 251t-60.5 251t-175.5 71q-90 0 -159 -17v-610z" />
-<glyph unicode="ÿ" horiz-adv-x="1107" d="M63 1022h261l229 -713l225 713h260l-389 -1047q-82 -223 -178 -300.5t-199 -77.5q-76 0 -155 28v205q59 -25 116 -25q106 0 197 248zM285 1155v195h207v-195h-207zM612 1155v195h207v-195h-207z" />
-<glyph unicode="Œ" horiz-adv-x="2074" d="M123 682q0 168 39 295t100.5 202.5t148.5 123t170 63.5t179 16q147 0 270 -43v27h909v-221h-663v-346h575v-232h-575v-346h663v-221h-909v27q-115 -43 -270 -43q-96 0 -179 16t-170 63.5t-148.5 122t-100.5 202.5t-39 294zM369 684q0 -154 33.5 -255t98 -148.5 t131.5 -64.5t161 -17q145 0 237 37v884q-111 45 -237 45q-94 0 -161 -16t-131.5 -63.5t-98 -148.5t-33.5 -253z" />
-<glyph unicode="œ" horiz-adv-x="1853" d="M98 512q0 254 125 390t359 136q229 0 356 -133q119 135 338 135q225 0 341 -143t116 -352q0 -86 -4 -127h-658q10 -133 80 -178t180 -45q180 0 348 79v-198q-170 -92 -385 -92q-233 0 -356 135q-123 -133 -356.5 -133t-358.5 136t-125 390zM344 512q0 -190 58.5 -257 t179 -67t179 67t58.5 257t-58.5 257t-179 67t-179 -67t-58.5 -257zM1071 588h412q-10 106 -62.5 179t-144.5 73t-143.5 -59.5t-61.5 -192.5z" />
-<glyph unicode="Ÿ" horiz-adv-x="1368" d="M25 1366h297l362 -555l360 555h297l-534 -780v-586h-246v586zM418 1483v194h207v-194h-207zM745 1483v194h207v-194h-207z" />
-<glyph unicode="ˆ" horiz-adv-x="874" d="M201 1155l186 250h98l189 -250h-164l-74 115l-74 -115h-161z" />
-<glyph unicode="˜" horiz-adv-x="1058" d="M215 1153v156q57 68 124 74q11 1 22 1q55 0 105 -26l122 -60q51 -25 104 -25q11 0 22 1q66 6 124 74v-156q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -105 25l-122 60q-50 26 -105 26q-11 0 -22 -1q-67 -6 -124 -74z" />
-<glyph unicode=" " horiz-adv-x="888" />
-<glyph unicode=" " horiz-adv-x="1776" />
-<glyph unicode=" " horiz-adv-x="888" />
-<glyph unicode=" " horiz-adv-x="1776" />
-<glyph unicode=" " horiz-adv-x="592" />
-<glyph unicode=" " horiz-adv-x="444" />
-<glyph unicode=" " horiz-adv-x="296" />
-<glyph unicode=" " horiz-adv-x="296" />
-<glyph unicode=" " horiz-adv-x="222" />
-<glyph unicode=" " horiz-adv-x="355" />
-<glyph unicode=" " horiz-adv-x="98" />
-<glyph unicode="‐" horiz-adv-x="950" d="M162 514v193h624v-193h-624z" />
-<glyph unicode="‑" horiz-adv-x="950" d="M162 514v193h624v-193h-624z" />
-<glyph unicode="‒" horiz-adv-x="950" d="M162 514v193h624v-193h-624z" />
-<glyph unicode="–" horiz-adv-x="1146" d="M141 500v192h860v-192h-860z" />
-<glyph unicode="—" horiz-adv-x="1824" d="M162 500v192h1501v-192h-1501z" />
-<glyph unicode="‘" horiz-adv-x="552" d="M96 1384h215l152 -401h-184z" />
-<glyph unicode="’" horiz-adv-x="552" d="M96 983l152 401h215l-182 -401h-185z" />
-<glyph unicode="‚" horiz-adv-x="651" d="M147 -229l152 401h215l-182 -401h-185z" />
-<glyph unicode="“" horiz-adv-x="858" d="M96 1384h215l152 -401h-184zM408 1384h215l151 -401h-184z" />
-<glyph unicode="”" horiz-adv-x="858" d="M96 983l152 401h215l-182 -401h-185zM408 983l151 401h215l-182 -401h-184z" />
-<glyph unicode="„" horiz-adv-x="948" d="M147 -229l152 401h215l-182 -401h-185zM459 -229l151 401h215l-182 -401h-184z" />
-<glyph unicode="•" horiz-adv-x="890" d="M172 661.5q0 112.5 80 192.5t194 80q113 0 192 -80t79 -192.5t-79 -192.5t-192 -80q-115 0 -194.5 80t-79.5 192.5z" />
-<glyph unicode="…" horiz-adv-x="1726" d="M219 0v227h240v-227h-240zM743 0v227h240v-227h-240zM1266 0v227h239v-227h-239z" />
-<glyph unicode=" " horiz-adv-x="355" />
-<glyph unicode="‹" horiz-adv-x="770" d="M63 522l347 389h286l-372 -389l372 -391h-286z" />
-<glyph unicode="›" horiz-adv-x="815" d="M111 131l372 389l-372 391h286l346 -391l-346 -389h-286z" />
-<glyph unicode=" " horiz-adv-x="444" />
-<glyph unicode="€" d="M102 412v178h162q-4 57 -4 94t4 94h-162v178h191q115 428 604 428q209 0 414 -81v-230q-194 90 -391 90q-137 0 -231.5 -43t-141.5 -164h530v-178h-567q-4 -57 -4 -94t4 -94h567v-178h-530q47 -121 141 -164t232 -43q197 0 391 90v-229q-205 -82 -414 -82 q-492 0 -604 428h-191z" />
-<glyph unicode="™" horiz-adv-x="1572" d="M100 1245v121h559v-121h-219v-561h-121v561h-219zM762 684v682h137l197 -285l196 285h138v-682h-123v506l-211 -303l-211 303v-506h-123z" />
-<glyph unicode="" horiz-adv-x="1020" d="M0 0v1020h1020v-1020h-1020z" />
-<glyph unicode="fi" horiz-adv-x="1333" d="M82 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174zM950 1161h252v225h-252v-225zM954 0h246v1022h-246v-1022z" />
-<glyph unicode="fl" horiz-adv-x="1333" d="M82 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174zM958 0h246v1411h-246v-1411z" />
-<glyph unicode="ffi" horiz-adv-x="2100" d="M82 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174zM813 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174zM1655 1161v225 h251v-225h-251zM1659 0v1022h245v-1022h-245z" />
-<glyph unicode="ffl" horiz-adv-x="2121" d="M82 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174zM813 811v211h174q2 424 381 424q49 0 100 -10v-193q-37 4 -55 4q-96 0 -138 -46t-42 -179h235v-211h-235v-811h-246v811h-174zM1669 0v1411h246 v-1411h-246z" />
-</font>
-</defs></svg>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
-<svg xmlns="http://www.w3.org/2000/svg">
-<metadata></metadata>
-<defs>
-<font id="maven_promedium" horiz-adv-x="1529" >
-<font-face units-per-em="2048" ascent="1638" descent="-410" />
-<missing-glyph horiz-adv-x="692" />
-<glyph horiz-adv-x="2048" />
-<glyph horiz-adv-x="2048" />
-<glyph unicode="
" horiz-adv-x="682" />
-<glyph unicode=" " horiz-adv-x="692" />
-<glyph unicode="	" horiz-adv-x="692" />
-<glyph unicode=" " horiz-adv-x="692" />
-<glyph unicode="!" horiz-adv-x="800" d="M299 1366h195l-17 -1032h-162zM303 0v174h186v-174h-186z" />
-<glyph unicode=""" horiz-adv-x="821" d="M166 1384h190l-34 -411h-119zM461 1384h190l-37 -411h-118z" />
-<glyph unicode="#" horiz-adv-x="1210" d="M145 260v135h183l55 318h-180v135h203l38 221h144l-39 -221h256l39 221h143l-39 -221h137v-135h-159l-56 -318h158v-135h-180l-39 -221h-143l39 221h-259l-36 -221h-144l39 221h-160zM471 395h256l55 318h-256z" />
-<glyph unicode="$" horiz-adv-x="1198" d="M158 993q0 145 96 256t299 131v209h127v-207q160 -10 307 -86v-188q-141 86 -307 90v-418q86 -31 145.5 -60.5t122 -78.5t95 -120t32.5 -163q0 -156 -103.5 -252t-291.5 -116v-211h-127v207q-193 6 -377 98v197q180 -109 377 -115v467q-76 23 -118 38t-102.5 46t-93 64.5 t-57 89t-24.5 122.5zM348 993q0 -57 51.5 -94t153.5 -74v367q-205 -35 -205 -199zM680 174q205 35 205 184q0 86 -53.5 138.5t-151.5 91.5v-414z" />
-<glyph unicode="%" horiz-adv-x="1814" d="M145 981q0 178 75 278.5t222.5 100.5t222 -100.5t74.5 -278.5t-74.5 -277.5t-222 -99.5t-222.5 99.5t-75 277.5zM276 981q0 -133 38 -197.5t128 -64.5t128 64.5t38 197.5q0 135 -38 198.5t-128 63.5t-128 -63.5t-38 -198.5zM410 0l858 1366h151l-858 -1366h-151z M1087 385q0 176 75 276.5t222.5 100.5t222 -100.5t74.5 -276.5q0 -178 -74.5 -278.5t-222 -100.5t-222.5 100.5t-75 278.5zM1219 385q0 -135 37.5 -198.5t128 -63.5t128 63.5t37.5 198.5q0 133 -37.5 197.5t-128 64.5t-128 -64.5t-37.5 -197.5z" />
-<glyph unicode="&" horiz-adv-x="1325" d="M111 385q0 115 54 216t210 192q-127 158 -127 272q0 131 93 225t226 94q135 0 227.5 -90t92.5 -221q0 -109 -56.5 -183.5t-183.5 -148.5l266 -319q37 84 54 176h176q-27 -168 -115 -313l238 -285h-228l-116 141q-157 -157 -374 -157h-3q-113 0 -200 39.5t-136 103 t-73.5 129.5t-24.5 129zM281 379q0 -31 11 -65.5t38.5 -75.5t84 -68t134.5 -27q156 0 270 123l-327 396q-74 -39 -121 -79t-64.5 -81t-21.5 -64.5t-4 -58.5zM416 1077.5q0 -65.5 114 -203.5q119 68 155 111t36 100q0 63 -43 105.5t-109 42.5q-63 0 -108 -45t-45 -110.5z" />
-<glyph unicode="'" horiz-adv-x="524" d="M166 1384h190l-34 -411h-119z" />
-<glyph unicode="(" horiz-adv-x="722" d="M190 627q0 440 211 856h185q-211 -416 -211 -856q0 -436 211 -858h-185q-211 422 -211 858z" />
-<glyph unicode=")" horiz-adv-x="722" d="M133 -231q211 412 211 858q0 449 -211 856h184q211 -416 211 -856q0 -436 -211 -858h-184z" />
-<glyph unicode="*" horiz-adv-x="841" d="M172 1118l162 70l-162 72l64 108l139 -100l-19 168h125l-14 -168l139 100l64 -108l-160 -72l160 -70l-64 -112l-139 102l14 -166h-125l19 166l-139 -102z" />
-<glyph unicode="+" horiz-adv-x="1150" d="M199 545v141h297v297h141v-297h295v-141h-295v-295h-141v295h-297z" />
-<glyph unicode="," horiz-adv-x="667" d="M152 -229l151 401h193l-172 -401h-172z" />
-<glyph unicode="-" horiz-adv-x="950" d="M162 530v160h624v-160h-624z" />
-<glyph unicode="." horiz-adv-x="712" d="M260 0v182h195v-182h-195z" />
-<glyph unicode="/" horiz-adv-x="870" d="M-29 -201l752 1749h178l-754 -1749h-176z" />
-<glyph unicode="0" horiz-adv-x="1351" d="M133 684q0 166 34 294t86 202.5t126 122t145.5 63.5t153.5 16q104 0 191 -29.5t171 -101t132.5 -216t48.5 -351.5q0 -168 -34 -295t-86 -202.5t-126 -123t-144.5 -63.5t-152.5 -16t-153.5 16t-145.5 63.5t-126 123t-86 202.5t-34 295zM324 684q0 -135 21.5 -234.5 t54 -154.5t82.5 -88t94.5 -42t101.5 -9q55 0 100 9t94.5 42t82 88t54 154.5t21.5 234.5t-21.5 233.5t-54 153.5t-82 88t-94.5 42t-100 9q-57 0 -101.5 -9t-94.5 -42t-82.5 -88t-54 -153.5t-21.5 -233.5z" />
-<glyph unicode="1" horiz-adv-x="925" d="M211 1040v199l223 127h207v-1366h-190v1180z" />
-<glyph unicode="2" horiz-adv-x="1189" d="M158 0v152q111 84 214 177t210.5 208.5t172 234.5t64.5 215q0 219 -303 219q-184 0 -336 -102v180q170 98 361 98q238 0 351.5 -115.5t113.5 -277.5q0 -346 -519 -799h533v-190h-862z" />
-<glyph unicode="3" horiz-adv-x="1193" d="M166 63v187q160 -92 334 -92q338 0 338 239.5t-359 239.5h-119v168h115q295 0 295 199q0 200 -292 200h-3q-152 0 -278 -69v182q139 65 296 65h5q238 0 350.5 -110.5t112.5 -267.5q0 -177 -162 -273q229 -102 229 -332q0 -80 -27.5 -150.5t-86 -132t-161 -97 t-237.5 -35.5q-178 0 -350 79z" />
-<glyph unicode="4" horiz-adv-x="1210" d="M78 430v170l600 766h213v-766h194v-170h-194v-430h-191v430h-622zM303 600h397v494z" />
-<glyph unicode="5" horiz-adv-x="1228" d="M178 739l70 627h756v-176h-590l-33 -348q96 26 199 26h8q238 0 361.5 -112.5t123.5 -329.5t-124 -329.5t-361.5 -112.5t-401.5 102v205q133 -125 385 -125q180 0 246 65.5t66 194.5q0 131 -69 196.5t-263 65.5q-150 0 -277 -49z" />
-<glyph unicode="6" horiz-adv-x="1202" d="M133 627q0 141 25.5 256.5t65.5 191.5t96.5 134.5t111.5 90t119.5 51t112 25.5t94.5 6q106 0 211 -28v-172q-81 24 -175 24h-8q-180 0 -295.5 -100t-154.5 -287q111 72 285 72q240 0 348 -128t108 -310q0 -193 -120.5 -331t-350.5 -138q-473 0 -473 643zM324 616 q2 -131 24.5 -224t50 -138t72.5 -68.5t70.5 -26.5t64.5 -3q88 0 149.5 32.5t87 85t35 94.5t9.5 85q0 264 -281 264q-176 0 -282 -101z" />
-<glyph unicode="7" horiz-adv-x="1189" d="M143 1178v188h951v-180l-572 -1186h-211l580 1178h-748z" />
-<glyph unicode="8" horiz-adv-x="1196" d="M125 416q0 104 57.5 188t147.5 131q-154 98 -154 283q0 150 105.5 257t324.5 107t324.5 -107.5t105.5 -258.5q0 -186 -151 -279q200 -105 200 -319q0 -180 -122.5 -307t-356 -127t-357.5 128t-124 304zM315 414q0 -37 9.5 -74t37 -82t90 -72.5t154.5 -27.5t154.5 27.5 t90.5 72.5t37 82t9 74q0 225 -291 225t-291 -225zM365 1022q0 -31 8 -60.5t30.5 -67.5t74.5 -60.5t128 -22.5t128 22.5t73.5 60.5t30 67.5t8.5 60.5q0 186 -240 186q-241 0 -241 -186z" />
-<glyph unicode="9" horiz-adv-x="1212" d="M119 913q0 193 120.5 331t352.5 138q471 0 471 -643q0 -141 -25.5 -256.5t-65.5 -191.5t-96.5 -134.5t-111.5 -90t-119.5 -51t-112 -25.5t-94.5 -6q-106 0 -211 28v172q85 -24 176 -24h7q180 0 295.5 100t154.5 287q-111 -72 -285 -72q-240 0 -348 128t-108 310zM309 913 q0 -264 283 -264q176 0 282 101q-2 131 -24.5 224t-50 138t-72.5 68.5t-70.5 26.5t-64.5 3q-90 0 -151.5 -32.5t-87 -85t-35 -94.5t-9.5 -85z" />
-<glyph unicode=":" horiz-adv-x="813" d="M313 94v180h193v-180h-193zM313 741v181h193v-181h-193z" />
-<glyph unicode=";" horiz-adv-x="858" d="M184 -229l152 401h192l-172 -401h-172zM340 741v181h192v-181h-192z" />
-<glyph unicode="<" horiz-adv-x="1357" d="M246 477v148l850 379v-181l-643 -272l643 -272v-181z" />
-<glyph unicode="=" horiz-adv-x="1306" d="M260 365v159h789v-159h-789zM260 698v160h789v-160h-789z" />
-<glyph unicode=">" horiz-adv-x="1337" d="M270 98v181l641 272l-641 272v181l848 -379v-148z" />
-<glyph unicode="?" horiz-adv-x="1042" d="M164 1083v203q150 96 321 96q115 0 200 -33.5t132 -92t69.5 -126t22.5 -145.5q0 -82 -30.5 -146.5t-75.5 -106.5l-90 -83q-45 -41 -76 -100.5t-31 -134.5v-70h-180v76q0 104 30.5 184t76 127t89.5 86t74.5 81t30.5 89q0 219 -258 219q-166 0 -305 -123zM416 0v186h200 v-186h-200z" />
-<glyph unicode="@" horiz-adv-x="1869" d="M170 371q0 184 79 348t204 273.5t280 174t311 64.5q82 0 171.5 -21.5t180.5 -74t162.5 -126t117.5 -188t46 -254.5q0 -129 -33.5 -239.5t-113.5 -188.5t-193 -78q-137 0 -186 127q-90 -127 -293 -127q-143 0 -227 64.5t-84 175.5q0 51 16.5 96t59.5 95.5t138 80t232 29.5 q84 0 142 -8q0 4 1 15.5t1 17.5q0 35 -6.5 59.5t-26 50t-62.5 37.5t-110 12q-131 0 -252 -49l18 129q135 45 252 45q156 0 239 -72.5t83 -211.5q0 -53 -9 -129l-16 -125q-4 -37 -4 -74v-25q2 -49 26.5 -72.5t76.5 -23.5q88 0 151.5 97.5t63.5 287.5q0 143 -56.5 255 t-145.5 173.5t-183.5 92t-184.5 30.5q-184 0 -357 -98t-284.5 -273.5t-111.5 -377.5q0 -127 45 -229.5t109.5 -162t145.5 -99.5t144.5 -54.5t114.5 -14.5q250 0 442 103l-18 -133q-184 -82 -428 -82q-82 0 -169 22.5t-177.5 74.5t-161 128t-115.5 193.5t-45 259.5zM725 305 q0 -108 175 -108h3q141 0 188.5 51t61.5 153q4 49 8 74q-55 6 -123 6q-313 0 -313 -176z" />
-<glyph unicode="A" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM463 561h434l-217 567z" />
-<glyph unicode="B" horiz-adv-x="1280" d="M178 0v1366h473q139 0 234.5 -37t138.5 -98.5t59.5 -117.5t16.5 -120q0 -59 -16.5 -109t-40 -80t-48 -51.5t-41.5 -29.5l-16 -8l26 -11q16 -6 57.5 -32.5t73 -59t58.5 -92t27 -129.5q0 -68 -18.5 -127t-65.5 -123.5t-150.5 -102.5t-255.5 -38h-512zM369 168l309 2 q158 0 234.5 53t76.5 176q0 127 -84 173t-241 46h-295v-450zM369 788h280q127 0 193.5 42t66.5 157q0 117 -70.5 163t-207.5 46h-262v-408z" />
-<glyph unicode="C" horiz-adv-x="1286" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16q213 0 418 -81v-177q-193 86 -396 86q-242 0 -351 -124.5t-109 -403.5q0 -276 109 -402t351 -126q203 0 396 86v-176q-205 -82 -418 -82q-94 0 -177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295z" />
-<glyph unicode="D" horiz-adv-x="1380" d="M178 0v1366h436q315 0 478 -174t163 -510t-162.5 -509t-478.5 -173h-436zM369 168h215q250 0 366.5 124t116.5 390q0 268 -116.5 392t-366.5 124h-215v-1030z" />
-<glyph unicode="E" horiz-adv-x="1187" d="M178 0v1366h891v-178h-700v-416h616v-178h-616v-416h700v-178h-891z" />
-<glyph unicode="F" horiz-adv-x="1124" d="M178 0v1366h860v-178h-669v-416h589v-178h-589v-594h-191z" />
-<glyph unicode="G" horiz-adv-x="1386" d="M125 684q0 166 39 294t100.5 202.5t147.5 122t169 63.5t179 16q227 0 430 -79v-175q-199 82 -412 82q-74 0 -132 -9t-122.5 -42t-107.5 -88t-72 -153.5t-29 -233.5t29 -234.5t72 -154.5t108.5 -88t125 -42t132.5 -9q135 0 275 39v385h-279v178h463v-666 q-227 -108 -476 -108h-5q-96 0 -179 16t-169 63.5t-147.5 123t-100.5 202.5t-39 295z" />
-<glyph unicode="H" horiz-adv-x="1388" d="M178 0v1366h189v-594h655v594h188v-1366h-188v594h-655v-594h-189z" />
-<glyph unicode="I" horiz-adv-x="544" d="M178 0v1366h189v-1366h-189z" />
-<glyph unicode="J" horiz-adv-x="931" d="M61 66v198q119 -96 269 -96q117 0 174 49t57 186v963h189v-950q0 -133 -40 -225.5t-106.5 -134.5t-127 -58t-128.5 -16q-158 0 -287 84z" />
-<glyph unicode="K" horiz-adv-x="1380" d="M178 0v1366h189v-661l626 661h240l-580 -618l631 -748h-235l-521 614l-161 -172v-442h-189z" />
-<glyph unicode="L" horiz-adv-x="1089" d="M178 0v1366h189v-1188h680v-178h-869z" />
-<glyph unicode="M" horiz-adv-x="1665" d="M178 0v1366h205l448 -590l451 590h205v-1366h-189v1077l-467 -612l-464 612v-1077h-189z" />
-<glyph unicode="N" horiz-adv-x="1511" d="M178 0v1366h217l764 -1087v1087h191v-1366h-211l-770 1094v-1094h-191z" />
-<glyph unicode="O" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16t177 -16t170 -63.5t148.5 -123t100.5 -202.5t39 -295t-39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16t-177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5 t103.5 -88t117.5 -42t126.5 -9t127 9t118 42t103.5 88t68.5 154.5t27.5 234.5t-27.5 234.5t-68.5 154.5t-103.5 88t-118 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5z" />
-<glyph unicode="P" horiz-adv-x="1228" d="M176 0v1366h492q227 0 352 -112.5t125 -317.5t-125 -317.5t-352 -112.5h-301v-506h-191zM367 674h284q182 0 243.5 70.5t61.5 189.5t-61 189.5t-244 70.5h-284v-520z" />
-<glyph unicode="Q" d="M125 684q0 166 39 294t100.5 202.5t148.5 122t170 63.5t177 16q96 0 179 -16t169 -63.5t147.5 -122t100.5 -202.5t39 -294q0 -182 -45 -317.5t-124 -212t-167 -114.5t-195 -50q0 -82 54.5 -143.5t150.5 -61.5v-145q-170 0 -280.5 101t-110.5 247q-111 10 -202 46 t-174 111.5t-130 213t-47 325.5zM315 684q0 -135 28 -234.5t69 -154.5t103.5 -88t118.5 -42t125.5 -9t126 9t119 42t104.5 88t69.5 154.5t27.5 234.5t-27.5 233.5t-68.5 153.5t-104.5 88t-119 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -153.5t-28 -233.5z" />
-<glyph unicode="R" horiz-adv-x="1298" d="M178 0v1366h504q229 0 360.5 -107.5t131.5 -306.5q0 -168 -87.5 -267t-218.5 -136l363 -549h-225l-330 518h-307v-518h-191zM369 698h305q178 0 243.5 69t65.5 179q0 250 -317 250h-297v-498z" />
-<glyph unicode="S" horiz-adv-x="1155" d="M125 993q0 162 116.5 275.5t360.5 113.5q184 0 352 -86v-188q-150 90 -329 90q-310 0 -310 -205q0 -55 54.5 -96t134.5 -65.5t175 -63.5t175 -83t134 -129t54 -198q0 -176 -131 -275t-362 -99q-205 0 -406 100v197q190 -115 389 -115q319 0 320 192q0 82 -54 140t-134 84 l-176 59q-96 33 -175.5 67.5t-133.5 107.5t-54 177z" />
-<glyph unicode="T" horiz-adv-x="1163" d="M25 1188v178h1112v-178h-463v-1188h-189v1188h-460z" />
-<glyph unicode="U" horiz-adv-x="1474" d="M178 563v803h191v-819q0 -201 90 -297t278.5 -96t278.5 96t90 297v819h190v-803q0 -276 -146 -428.5t-412.5 -152.5t-413 152.5t-146.5 428.5z" />
-<glyph unicode="V" horiz-adv-x="1288" d="M25 1366h202l416 -1126l418 1126h203l-525 -1366h-190z" />
-<glyph unicode="W" horiz-adv-x="1961" d="M25 1366h198l338 -1090l336 1090h162l338 -1090l336 1090h198l-438 -1366h-195l-319 1026l-322 -1026h-194z" />
-<glyph unicode="X" horiz-adv-x="1253" d="M25 0l489 684l-477 682h231l359 -510l358 510h232l-484 -664l496 -702h-234l-368 526l-371 -526h-231z" />
-<glyph unicode="Y" horiz-adv-x="1275" d="M25 1366h229l383 -559l383 559h231l-520 -754v-612h-188v610z" />
-<glyph unicode="Z" horiz-adv-x="1247" d="M131 0v166l721 1010h-700v190h946v-170l-717 -1008h717v-188h-967z" />
-<glyph unicode="[" horiz-adv-x="784" d="M244 -238v1663h370v-151h-213v-1360h213v-152h-370z" />
-<glyph unicode="\" horiz-adv-x="802" d="M-76 1548h178l752 -1749h-176z" />
-<glyph unicode="]" horiz-adv-x="780" d="M172 -86h211v1360h-211v151h369v-1663h-369v152z" />
-<glyph unicode="^" horiz-adv-x="1146" d="M272 969l215 444h113l217 -444h-143l-129 303l-129 -303h-144z" />
-<glyph unicode="_" horiz-adv-x="1118" d="M0 -49h1118v-115h-1118v115z" />
-<glyph unicode="`" horiz-adv-x="491" d="M90 1395h184l127 -240h-116z" />
-<glyph unicode="a" horiz-adv-x="1093" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q145 55 309 55q213 0 329 -110.5t116 -317.5v-295q0 -33 -6.5 -65.5t-31 -83.5t-66.5 -89t-126 -65.5t-194 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M301 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88z" />
-<glyph unicode="b" horiz-adv-x="1167" d="M170 315v1096h186v-403q106 30 219 30q88 0 164 -21.5t149 -74.5t114.5 -159.5t41.5 -258.5q0 -135 -31.5 -236.5t-77.5 -157.5t-110.5 -91t-119 -45t-113.5 -10q-111 0 -194 27.5t-125 65.5t-66.5 89t-30.5 84t-6 65zM356 303q0 -153 236 -153q53 0 94 13t83 49 t64.5 115t22.5 197q0 111 -22.5 183.5t-64.5 106.5t-87 46t-107 12q-115 0 -219 -43v-526z" />
-<glyph unicode="c" horiz-adv-x="966" d="M119 512q0 256 124 391t357 135q156 0 301 -57v-178q-139 67 -281 67h-4q-141 0 -221 -72.5t-80 -285.5q0 -113 23.5 -187.5t70 -110.5t94 -48t113.5 -12q147 0 285 65v-178q-145 -57 -301 -57q-233 0 -357 136t-124 392z" />
-<glyph unicode="d" horiz-adv-x="1150" d="M129 524q0 152 42 258.5t114.5 159.5t148.5 74.5t164 21.5q111 0 219 -30v403h187v-1096q0 -33 -6.5 -65.5t-31 -83.5t-66.5 -89t-125 -65.5t-193 -27.5q-59 0 -113.5 10t-119 45t-110.5 91t-78 157.5t-32 236.5zM315 524q0 -119 22.5 -197.5t64.5 -114.5t83 -49t97 -13 q236 0 235 153v526q-104 43 -219 43q-61 0 -106.5 -12t-88.5 -46t-65.5 -106.5t-22.5 -183.5z" />
-<glyph unicode="e" horiz-adv-x="1136" d="M129 498q0 540 451 540h2q205 0 327.5 -134t122.5 -359q0 -74 -6 -142h-704q34 -260 288 -260q207 0 353 76v-170q-158 -67 -357 -67h-6q-90 0 -166 22.5t-148.5 74.5t-114.5 159.5t-42 259.5zM319 573h525q-4 51 -17.5 97.5t-41 94.5t-79.5 76.5t-124 28.5 q-129 0 -189.5 -66.5t-73.5 -230.5z" />
-<glyph unicode="f" horiz-adv-x="675" d="M82 858v164h143v49q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5t-14 -122v-49h264v-164h-264v-858h-187v858h-143z" />
-<glyph unicode="g" horiz-adv-x="1101" d="M123 518q0 238 107.5 380t330.5 142q186 0 298 -84t112 -239v-760q0 -68 -19.5 -126t-65.5 -113.5t-138.5 -87t-225.5 -31.5q-154 0 -303 43v178q145 -45 287 -45q98 0 159.5 17.5t85 50t28.5 55t5 59.5v90q-104 -29 -206 -29q-219 0 -337 129t-118 371zM311 518 q0 -199 71 -264.5t196 -65.5q110 0 206 39v502q0 23 -4 39t-22.5 45t-68.5 44t-128 15q-51 0 -89 -12t-78 -46t-61.5 -108.5t-21.5 -187.5z" />
-<glyph unicode="h" horiz-adv-x="1187" d="M193 0v1411h188v-424q131 51 270 51q190 0 295 -113.5t105 -322.5v-602h-189v608q0 150 -63.5 200t-165.5 50q-125 0 -252 -49v-809h-188z" />
-<glyph unicode="i" horiz-adv-x="589" d="M195 1180v198h200v-198h-200zM201 0v1022h188v-1022h-188z" />
-<glyph unicode="j" horiz-adv-x="526" d="M6 -242q109 0 142.5 47.5t33.5 135.5v1081h187v-1081q0 -342 -363 -342v159zM176 1180v198h199v-198h-199z" />
-<glyph unicode="k" horiz-adv-x="1204" d="M188 0v1411h187v-817l442 428h254l-444 -426l503 -596h-249l-379 475l-127 -123v-352h-187z" />
-<glyph unicode="l" horiz-adv-x="602" d="M207 0v1411h188v-1411h-188z" />
-<glyph unicode="m" horiz-adv-x="1853" d="M193 0v938q205 102 425 102q166 0 273 -77q193 77 375 77q193 0 301 -101t108 -282v-657h-186v670q0 88 -43 142t-168 54q-135 0 -274 -57q24 -65 24 -146v-6v-657h-188v668q0 90 -47.5 142t-188.5 52q-109 0 -223 -31v-831h-188z" />
-<glyph unicode="n" horiz-adv-x="1198" d="M168 0v934q213 106 436 106q193 0 302.5 -101t109.5 -280v-659h-189v672q0 90 -46 142t-185 52q-125 0 -240 -37v-829h-188z" />
-<glyph unicode="o" horiz-adv-x="1161" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170 22.5t170 -22.5t151.5 -76.5t117 -162.5t43 -264.5t-43 -264.5t-117 -162.5t-151.5 -76.5t-170 -22.5t-170 22.5t-151.5 76.5t-117 162.5t-43 264.5zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12 t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5z" />
-<glyph unicode="p" horiz-adv-x="1167" d="M168 -389v1098q0 33 6 65.5t30.5 82.5t66.5 88t126 65.5t195 27.5q57 0 111.5 -10t119 -45t111.5 -91t78.5 -157.5t31.5 -234.5q0 -152 -41.5 -259.5t-114.5 -159.5t-148.5 -74.5t-166.5 -22.5q-113 0 -217 30v-403h-188zM356 193q106 -41 217 -41q63 0 107.5 12t87.5 46 t65.5 106.5t22.5 183.5q0 119 -22.5 197.5t-64.5 114.5t-83 49t-94 13q-236 0 -236 -153v-528z" />
-<glyph unicode="q" horiz-adv-x="1157" d="M115 500q0 133 31.5 234.5t77.5 157.5t110.5 91t119 45t111.5 10q111 0 195 -27.5t126 -65.5t66.5 -88t30.5 -83t6 -65v-1098h-188v403q-104 -30 -217 -30q-90 0 -166 21.5t-147.5 74.5t-113.5 160.5t-42 259.5zM301 500q0 -111 22.5 -183.5t65.5 -106.5t87 -46t108 -12 q113 0 217 41v528q0 153 -236 153q-53 0 -94 -13t-83 -49t-64.5 -115t-22.5 -197z" />
-<glyph unicode="r" horiz-adv-x="702" d="M166 0v936q256 102 498 102v-172q-152 -2 -310 -43v-823h-188z" />
-<glyph unicode="s" horiz-adv-x="892" d="M102 733q0 127 90.5 216t276.5 89q139 0 274 -67v-162q-133 61 -251 61q-203 0 -203 -131q0 -43 54 -71.5t130 -50t152.5 -52t131 -102.5t54.5 -178q0 -123 -93 -212t-288 -89q-164 0 -295 71v170q133 -75 283 -75h4q98 0 149.5 35.5t51.5 90.5q0 63 -53.5 101.5 t-130.5 60t-153.5 48t-130 88t-53.5 159.5z" />
-<glyph unicode="t" horiz-adv-x="765" d="M49 864v158h178v305h187v-305h272v-158h-272v-594q0 -120 151 -120h2q70 0 119 30v-168q-70 -28 -140 -28h-5q-37 0 -79 9t-100.5 35.5t-96.5 92t-38 162.5v581h-178z" />
-<glyph unicode="u" horiz-adv-x="1208" d="M168 430v592h186v-606q0 -139 59.5 -204t186.5 -65t185.5 64.5t58.5 204.5v606h188v-592q0 -213 -113.5 -330.5t-318.5 -117.5t-318.5 117.5t-113.5 330.5z" />
-<glyph unicode="v" horiz-adv-x="1107" d="M66 1022h200l287 -797l289 797h200l-393 -1022h-192z" />
-<glyph unicode="w" horiz-adv-x="1656" d="M80 1022h207l229 -756l225 756h174l228 -756l227 756h207l-338 -1022h-186l-224 711l-225 -711h-186z" />
-<glyph unicode="x" horiz-adv-x="1073" d="M57 0l369 510l-360 512h229l242 -342l241 342h230l-361 -498l369 -524h-230l-249 354l-250 -354h-230z" />
-<glyph unicode="y" horiz-adv-x="1073" d="M63 1022h201l275 -766l270 766h201l-420 -1131q-109 -294 -317 -294h-1q-68 0 -143 16v168q61 -16 115 -17q127 0 205 236z" />
-<glyph unicode="z" horiz-adv-x="962" d="M92 0v172l520 676h-508v174h748v-172l-518 -674h518v-176h-760z" />
-<glyph unicode="{" horiz-adv-x="763" d="M186 535v137q109 31 109 127v313q0 156 82 238t237 82h17v-158q-84 0 -120 -32t-36 -124v-307q0 -123 -119 -207q119 -88 119 -209v-307q0 -92 36 -123t120 -33v-157h-17q-156 0 -237.5 83t-81.5 238v312q0 96 -109 127z" />
-<glyph unicode="|" horiz-adv-x="946" d="M379 -203v1569h188v-1569h-188z" />
-<glyph unicode="}" horiz-adv-x="759" d="M156 -68q84 2 119.5 33t35.5 123v307q0 121 119 209q-119 84 -119 207v307q0 94 -35.5 125t-119.5 31v158h18q154 0 237 -82t83 -238v-313q0 -94 106 -127v-137q-106 -33 -106 -127v-312q0 -156 -83 -238.5t-237 -82.5h-18v157z" />
-<glyph unicode="~" horiz-adv-x="1024" d="M201 473v135q57 68 123 74q11 1 22 1q55 0 106 -25l122 -61q50 -25 103 -25q12 0 23 2q66 6 123 73v-135q-57 -68 -123 -74q-10 -1 -21 -1q-54 0 -105 25l-122 60q-51 26 -106 26q-11 0 -22 -1q-66 -6 -123 -74z" />
-<glyph unicode="¡" horiz-adv-x="788" d="M299 -344l16 1030h162l17 -1030h-195zM303 848v174h186v-174h-186z" />
-<glyph unicode="¢" horiz-adv-x="976" d="M117 684q0 238 108.5 373t313.5 151v158h123v-158q131 -12 237 -55v-178q-123 57 -237 65v-712q121 8 237 63v-178q-117 -47 -237 -55v-158h-123v158q-205 16 -313.5 152t-108.5 374zM313 684q0 -182 59.5 -260t166.5 -94v708q-106 -16 -166 -94t-60 -260z" />
-<glyph unicode="£" horiz-adv-x="1155" d="M104 -53v182q82 66 181 82v360h-181v168h181v258q0 74 23.5 139.5t71.5 122t134 89t201 32.5q172 0 325 -90v-192q-143 108 -307 108q-264 0 -264 -209v-258h389v-168h-389v-366q45 -8 139 -33t150 -35q34 -6 75 -6q28 0 61 3q79 7 146 44v-182q-86 -47 -186 -47 t-191.5 25.5t-183.5 47.5q-56 14 -114 14q-38 0 -77 -6q-98 -14 -184 -83z" />
-<glyph unicode="¥" horiz-adv-x="1298" d="M37 1366h229l383 -559l383 559h232l-435 -629h215v-153h-301v-162h301v-154h-301v-268h-188v268h-301v154h301v162h-301v153h215z" />
-<glyph unicode="§" horiz-adv-x="1198" d="M131 725q0 160 225 225q-72 55 -71 140q0 125 100 209.5t313 84.5q166 0 322 -77v-181q-145 80 -291 80q-248 0 -248 -125q0 -31 45 -54t112 -39.5t144.5 -43t144 -60.5t111.5 -96.5t45 -144.5q0 -90 -64.5 -145.5t-160.5 -79.5q72 -53 72 -131q0 -125 -101.5 -214 t-314.5 -89q-190 0 -375 106v191q174 -117 348 -117q246 0 246 123q0 31 -45 54.5t-111.5 39.5t-144.5 43t-144.5 60.5t-111.5 96t-45 144.5zM328 723q0 -33 19.5 -59.5t61.5 -46t79.5 -34t101 -34t100.5 -33.5q90 8 143.5 40t53.5 81q0 33 -19.5 58.5t-61.5 46t-80 35 t-101.5 33.5t-100.5 34q-90 -8 -143 -41t-53 -80z" />
-<glyph unicode="¨" horiz-adv-x="831" d="M162 1155v174h186v-174h-186zM465 1155v174h186v-174h-186z" />
-<glyph unicode="©" horiz-adv-x="1593" d="M98 680q0 289 204 493.5t492.5 204.5t493.5 -204.5t205 -493.5t-205 -492.5t-493.5 -203.5t-492.5 203.5t-204 492.5zM188 680q0 -250 178.5 -428t428.5 -178q252 0 430 178t178 428q0 252 -178 430t-430 178q-250 0 -428.5 -178t-178.5 -430zM432 680q0 121 33 206 t90 127t117.5 59.5t130.5 17.5q127 0 244 -48v-104q-113 51 -232 51q-141 0 -204.5 -72.5t-63.5 -236.5q0 -162 63.5 -234.5t204.5 -72.5q115 0 232 49v-103q-117 -47 -244 -47q-55 0 -103.5 9.5t-99.5 37t-87 71.5t-58.5 118t-22.5 172z" />
-<glyph unicode="ª" horiz-adv-x="813" d="M156 985q0 84 71.5 132t196.5 48q53 0 102 -6q-4 72 -39.5 103.5t-117.5 31.5q-70 0 -156 -28v88q86 30 172 30q119 0 182.5 -61t63.5 -176v-164q0 -25 -7.5 -49.5t-28 -58t-72.5 -55t-128 -21.5q-63 0 -110 15t-71 36.5t-38 50.5t-17 47.5t-3 36.5zM260 987 q0 -96 135 -96q131 0 131 92v92q-35 8 -98 8q-33 0 -66.5 -6t-67.5 -29.5t-34 -60.5z" />
-<glyph unicode="«" horiz-adv-x="1171" d="M63 522l347 389h223l-365 -389l365 -391h-223zM498 522l346 389h223l-365 -389l365 -391h-223z" />
-<glyph unicode="¬" horiz-adv-x="1273" d="M199 528v156h876v-360h-151v204h-725z" />
-<glyph unicode="­" horiz-adv-x="950" d="M162 530v160h624v-160h-624z" />
-<glyph unicode="®" horiz-adv-x="1593" d="M98 684q0 289 205 494.5t496 205.5t495.5 -205.5t204.5 -494.5q0 -291 -204.5 -495.5t-495.5 -204.5t-496 204.5t-205 495.5zM188 684q0 -254 178.5 -432t432.5 -178q252 0 430 178t178 432q0 252 -178 430t-430 178q-254 0 -432.5 -178t-178.5 -430zM528 285v796h295 q133 0 210 -62.5t77 -178.5q0 -98 -51 -156t-127 -78l211 -321h-131l-193 303h-180v-303h-111zM639 692h178q180 0 180 145.5t-184 145.5h-174v-291z" />
-<glyph unicode="¯" horiz-adv-x="825" d="M176 1180v123h477v-123h-477z" />
-<glyph unicode="°" horiz-adv-x="792" d="M168 1143q0 111 57.5 169t166 58t165.5 -58.5t57 -168.5q0 -109 -57 -167t-165.5 -58t-166 58t-57.5 167zM260 1143q0 -76 31 -109t100.5 -33t99 33t29.5 109t-29.5 108.5t-99 32.5t-100.5 -32.5t-31 -108.5z" />
-<glyph unicode="±" horiz-adv-x="1214" d="M231 55v150h754v-150h-754zM231 612v150h304v301h149v-301h301v-150h-301v-303h-149v303h-304z" />
-<glyph unicode="²" horiz-adv-x="671" d="M109 948v82q137 102 246.5 226t109.5 223q0 118 -161 118h-3q-104 0 -180 -55v96q88 53 194 54q127 0 188.5 -62.5t61.5 -148.5q0 -184 -278 -430h274v-103h-452z" />
-<glyph unicode="³" horiz-adv-x="661" d="M109 983v100q86 -49 180 -49q182 0 182 127q0 129 -192 129h-62v92h59q158 0 158 106.5t-158 106.5q-86 0 -159 -41v99q74 39 172 39q127 0 187.5 -59.5t60.5 -143.5q0 -95 -86 -148q122 -53 122 -176q0 -94 -66.5 -159.5t-207.5 -65.5q-104 0 -190 43z" />
-<glyph unicode="´" horiz-adv-x="518" d="M123 1155l127 240h184l-194 -240h-117z" />
-<glyph unicode="µ" horiz-adv-x="1253" d="M203 -430v1454h188v-672q0 -90 46 -142t186 -52q125 0 239 37v829h189v-934q-213 -106 -437 -106q-129 0 -223 47v-461h-188z" />
-<glyph unicode="¶" horiz-adv-x="940" d="M78 936q0 207 123.5 318.5t359.5 111.5h238v-1366h-170v504h-68q-236 0 -359.5 111.5t-123.5 320.5z" />
-<glyph unicode="·" horiz-adv-x="663" d="M238 471v174h186v-174h-186z" />
-<glyph unicode="¸" horiz-adv-x="555" d="M115 -262l182 252h145l-141 -252h-186z" />
-<glyph unicode="¹" horiz-adv-x="491" d="M109 1509v107l118 67h113v-735h-102v635z" />
-<glyph unicode="º" horiz-adv-x="864" d="M156 1090q0 115 44 184t99 89.5t125 20.5q113 0 191.5 -64.5t78.5 -229.5q0 -164 -78.5 -229.5t-191.5 -65.5q-111 0 -189.5 65.5t-78.5 229.5zM260 1089.5q0 -120.5 43 -160.5t121 -40t121 40t43 160.5t-43 161.5t-121 41t-121 -41t-43 -161.5z" />
-<glyph unicode="»" horiz-adv-x="1212" d="M137 131l365 391l-365 389h223l347 -389l-347 -391h-223zM571 131l365 391l-365 389h224l346 -389l-346 -391h-224z" />
-<glyph unicode="¼" horiz-adv-x="1447" d="M106 1206v107l119 67h111v-735h-103v635zM145 -45l893 1448h131l-895 -1448h-129zM799 231v93l323 413h115v-413h104v-93h-104v-229h-102v229h-336zM920 324h215v266z" />
-<glyph unicode="½" horiz-adv-x="1482" d="M106 1206v107l119 67h111v-735h-103v635zM145 -45l893 1448h131l-895 -1448h-129zM895 2v82q135 102 244.5 226t109.5 222q0 119 -164 119q-92 0 -180 -55v96q88 53 195 53q127 0 188.5 -61t61.5 -149q0 -188 -279 -431h277v-102h-453z" />
-<glyph unicode="¾" horiz-adv-x="1662" d="M135 680v100q86 -49 180 -49q182 0 183 127q0 129 -193 129h-61v92h59q160 0 160 106.5t-160 106.5q-86 0 -160 -41v99q82 41 172 41q127 0 187.5 -60.5t60.5 -144.5q0 -95 -86 -148q123 -53 123 -178q0 -92 -66.5 -157.5t-207.5 -65.5q-100 0 -191 43zM369 -45l895 1448 h129l-893 -1448h-131zM1024 231v93l322 413h116v-413h105v-93h-105v-229h-102v229h-336zM1145 324h215v266z" />
-<glyph unicode="¿" horiz-adv-x="1052" d="M174 37q0 82 31 146.5t76 106.5l90 83q45 41 75.5 100t30.5 135v70h180v-76q0 -104 -30.5 -184t-75.5 -127t-89 -86t-75 -81t-31 -89q0 -219 258 -219q166 0 306 123v-203q-150 -96 -322 -96q-115 0 -200 33.5t-132 92t-69.5 126t-22.5 145.5zM467 836v186h201v-186h-201 z" />
-<glyph unicode="À" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM424 1722h182l129 -239h-117zM463 561h434l-217 567z" />
-<glyph unicode="Á" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM463 561h434l-217 567zM625 1483l129 239h182l-195 -239h-116z" />
-<glyph unicode="Â" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM451 1483l180 250h98l182 -250h-145l-86 133l-86 -133h-143zM463 561h434l-217 567z" />
-<glyph unicode="Ã" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM369 1481v135q57 68 123 74q11 1 22 1q55 0 106 -26l122 -61q51 -24 104 -24q11 0 22 1q66 6 123 74v-135q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60q-51 26 -105 26q-11 0 -23 -2 q-66 -6 -123 -73zM463 561h434l-217 567z" />
-<glyph unicode="Ä" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM434 1483v174h189v-174h-189zM463 561h434l-217 567zM737 1483v174h189v-174h-189z" />
-<glyph unicode="Å" horiz-adv-x="1359" d="M43 0l545 1366h186l543 -1366h-205l-147 385h-570l-147 -385h-205zM463 561h434l-217 567zM502 1599q0 82 47 128.5t131 46.5t131 -46.5t47 -128.5q0 -84 -47 -130t-131 -46t-131 46.5t-47 129.5zM600 1599.5q0 -51.5 17.5 -72t62.5 -20.5t62.5 20.5t17.5 72t-17.5 72 t-62.5 20.5t-62.5 -20.5t-17.5 -72z" />
-<glyph unicode="Æ" horiz-adv-x="2031" d="M51 0l858 1366h983v-178h-700v-416h616v-178h-616v-416h700v-178h-891v414h-481l-258 -414h-211zM633 592h368v588z" />
-<glyph unicode="Ç" horiz-adv-x="1286" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16q213 0 418 -81v-177q-193 86 -396 86q-242 0 -351 -124.5t-109 -403.5q0 -276 109.5 -402t350.5 -126q203 0 396 86v-176q-176 -72 -361 -80l-137 -248h-188l184 250q-111 10 -201 47t-173 112.5t-130 213 t-47 323.5z" />
-<glyph unicode="È" horiz-adv-x="1187" d="M178 0v1366h891v-178h-700v-416h616v-178h-616v-416h700v-178h-891zM412 1722h184l127 -239h-117z" />
-<glyph unicode="É" horiz-adv-x="1187" d="M178 0v1366h891v-178h-700v-416h616v-178h-616v-416h700v-178h-891zM582 1483l127 239h184l-195 -239h-116z" />
-<glyph unicode="Ê" horiz-adv-x="1187" d="M178 0v1366h891v-178h-700v-416h616v-178h-616v-416h700v-178h-891zM416 1483l180 250h98l183 -250h-146l-86 133l-86 -133h-143z" />
-<glyph unicode="Ë" horiz-adv-x="1187" d="M178 0v1366h891v-178h-700v-416h616v-178h-616v-416h700v-178h-891zM397 1483v174h187v-174h-187zM700 1483v174h187v-174h-187z" />
-<glyph unicode="Ì" horiz-adv-x="544" d="M14 1722h185l127 -239h-117zM178 0h189v1366h-189v-1366z" />
-<glyph unicode="Í" horiz-adv-x="544" d="M178 0v1366h189v-1366h-189zM217 1483l127 239h184l-194 -239h-117z" />
-<glyph unicode="Î" horiz-adv-x="544" d="M41 1483l180 250h98l183 -250h-146l-86 133l-86 -133h-143zM178 0h189v1366h-189v-1366z" />
-<glyph unicode="Ï" horiz-adv-x="544" d="M27 1483v174h186v-174h-186zM178 0h189v1366h-189v-1366zM330 1483v174h186v-174h-186z" />
-<glyph unicode="Ð" horiz-adv-x="1480" d="M109 606v154h163v606h437q315 0 479 -174t164 -510t-164 -509t-479 -173h-437v606h-163zM463 168h215q252 0 368.5 124t116.5 390q0 268 -116.5 392t-368.5 124h-215v-438h305v-154h-305v-438z" />
-<glyph unicode="Ñ" horiz-adv-x="1511" d="M178 0v1366h217l764 -1087v1087h191v-1366h-211l-770 1094v-1094h-191zM453 1481v135q57 68 123 74q11 1 22 1q55 0 106 -26l122 -61q51 -24 104 -24q11 0 22 1q66 6 123 74v-135q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60q-51 26 -105 26q-11 0 -23 -2 q-66 -6 -123 -73z" />
-<glyph unicode="Ò" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16t177 -16t170 -63.5t148.5 -123t100.5 -202.5t39 -295t-39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16t-177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5 t103.5 -88t117.5 -42t126.5 -9t127 9t118 42t103.5 88t68.5 154.5t27.5 234.5t-27.5 234.5t-68.5 154.5t-103.5 88t-118 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5zM524 1722h185l127 -239h-117z" />
-<glyph unicode="Ó" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16t177 -16t170 -63.5t148.5 -123t100.5 -202.5t39 -295t-39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16t-177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5 t103.5 -88t117.5 -42t126.5 -9t127 9t118 42t103.5 88t68.5 154.5t27.5 234.5t-27.5 234.5t-68.5 154.5t-103.5 88t-118 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5zM684 1483l127 239h184l-194 -239h-117z" />
-<glyph unicode="Ô" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16t177 -16t170 -63.5t148.5 -123t100.5 -202.5t39 -295t-39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16t-177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5 t103.5 -88t117.5 -42t126.5 -9t127 9t118 42t103.5 88t68.5 154.5t27.5 234.5t-27.5 234.5t-68.5 154.5t-103.5 88t-118 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5zM528 1483l181 250h98l182 -250h-145l-86 133l-86 -133h-144z" />
-<glyph unicode="Õ" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16t177 -16t170 -63.5t148.5 -123t100.5 -202.5t39 -295t-39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16t-177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5 t103.5 -88t117.5 -42t126.5 -9t127 9t118 42t103.5 88t68.5 154.5t27.5 234.5t-27.5 234.5t-68.5 154.5t-103.5 88t-118 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5zM449 1481v135q57 68 123 74q11 1 22 1q55 0 106 -26l122 -61q51 -24 104 -24 q11 0 22 1q66 6 123 74v-135q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60q-51 26 -105 26q-11 0 -23 -2q-66 -6 -123 -73z" />
-<glyph unicode="Ö" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16t177 -16t170 -63.5t148.5 -123t100.5 -202.5t39 -295t-39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16t-177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5 t103.5 -88t117.5 -42t126.5 -9t127 9t118 42t103.5 88t68.5 154.5t27.5 234.5t-27.5 234.5t-68.5 154.5t-103.5 88t-118 42t-127 9t-126.5 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5zM514 1483v174h186v-174h-186zM817 1483v174h187v-174h-187z" />
-<glyph unicode="×" horiz-adv-x="1050" d="M158 160l282 364l-282 365h164l200 -258l203 258h162l-281 -365l281 -364h-162l-203 258l-200 -258h-164z" />
-<glyph unicode="Ø" d="M88 0l168 201q-131 174 -131 483q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16q244 0 399 -112l82 94h191l-168 -199q131 -174 131 -483q0 -168 -39 -295t-100.5 -202.5t-148.5 -123t-170 -63.5t-177 -16q-244 0 -400 112l-81 -96h-191zM315 684q0 -209 64 -336 l665 789q-99 75 -279 75h-5q-72 0 -127 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5zM475 231q97 -75 280 -75h5q72 0 127 9t117.5 42t103.5 88t68.5 154.5t27.5 234.5q0 209 -63 336z" />
-<glyph unicode="Ù" horiz-adv-x="1476" d="M178 563v803h191v-819q0 -201 90 -297t278.5 -96t278.5 96t90 297v819h190v-803q0 -276 -146 -428.5t-412.5 -152.5t-413 152.5t-146.5 428.5zM498 1722h184l127 -239h-117z" />
-<glyph unicode="Ú" horiz-adv-x="1476" d="M178 563v803h191v-819q0 -201 90 -297t278.5 -96t278.5 96t90 297v819h190v-803q0 -276 -146 -428.5t-412.5 -152.5t-413 152.5t-146.5 428.5zM666 1483l127 239h184l-195 -239h-116z" />
-<glyph unicode="Û" horiz-adv-x="1476" d="M178 563v803h191v-819q0 -201 90 -297t278.5 -96t278.5 96t90 297v819h190v-803q0 -276 -146 -428.5t-412.5 -152.5t-413 152.5t-146.5 428.5zM506 1483l180 250h98l183 -250h-146l-86 133l-86 -133h-143z" />
-<glyph unicode="Ü" horiz-adv-x="1476" d="M178 563v803h191v-819q0 -201 90 -297t278.5 -96t278.5 96t90 297v819h190v-803q0 -276 -146 -428.5t-412.5 -152.5t-413 152.5t-146.5 428.5zM492 1483v174h186v-174h-186zM795 1483v174h186v-174h-186z" />
-<glyph unicode="Ý" horiz-adv-x="1275" d="M25 1366h229l383 -559l383 559h231l-520 -754v-612h-188v610zM561 1483l127 239h184l-194 -239h-117z" />
-<glyph unicode="Þ" horiz-adv-x="1187" d="M176 0v1366h191v-205h231q227 0 352 -112.5t125 -317.5t-125 -317.5t-352 -112.5h-231v-301h-191zM367 469h215q182 0 243.5 70.5t61.5 189.5v2v2q0 119 -61.5 189.5t-243.5 70.5h-215v-524z" />
-<glyph unicode="ß" horiz-adv-x="1255" d="M180 0v862q0 154 39 262.5t106.5 161.5t137 75.5t153.5 22.5q236 0 347.5 -114.5t111.5 -276.5q0 -188 -188 -280q254 -74 254 -316q0 -186 -150.5 -291.5t-400.5 -105.5v174q61 0 115.5 10.5t113 33t93 70.5t34.5 118q0 217 -356 231v143q293 4 293 219q0 219 -267 220 q-53 0 -92 -13.5t-75.5 -49.5t-56 -111.5t-19.5 -190.5v-854h-193z" />
-<glyph unicode="à" horiz-adv-x="1099" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q159 55 309 55q213 0 328 -110.5t115 -317.5v-295q0 -33 -5.5 -65.5t-31 -83.5t-67.5 -89t-125 -65.5t-193 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M301 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88zM315 1395h185l127 -240h-117z" />
-<glyph unicode="á" horiz-adv-x="1099" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q159 55 309 55q213 0 328 -110.5t115 -317.5v-295q0 -33 -5.5 -65.5t-31 -83.5t-67.5 -89t-125 -65.5t-193 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M301 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88zM461 1155l127 240h184l-194 -240h-117z" />
-<glyph unicode="â" horiz-adv-x="1099" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q159 55 309 55q213 0 328 -110.5t115 -317.5v-295q0 -33 -5.5 -65.5t-31 -83.5t-67.5 -89t-125 -65.5t-193 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M301 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88zM338 1155l180 250h98l183 -250h-146l-86 133l-86 -133h-143z" />
-<glyph unicode="ã" horiz-adv-x="1099" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q159 55 309 55q213 0 328 -110.5t115 -317.5v-295q0 -33 -5.5 -65.5t-31 -83.5t-67.5 -89t-125 -65.5t-193 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M250 1153v135q57 68 124 74q10 1 21 1q55 0 105 -25l122 -61q51 -25 104 -25q12 0 24 2q65 6 122 73v-135q-57 -68 -122 -74q-12 -1 -23 -1q-53 0 -105 25l-122 60q-50 26 -105 26q-11 0 -21 -1q-67 -6 -124 -74zM301 324q0 -174 246 -174h3q232 0 232 167v166 q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88z" />
-<glyph unicode="ä" horiz-adv-x="1099" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q159 55 309 55q213 0 328 -110.5t115 -317.5v-295q0 -33 -5.5 -65.5t-31 -83.5t-67.5 -89t-125 -65.5t-193 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M301 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88zM324 1155v174h186v-174h-186zM627 1155v174h186v-174h-186z" />
-<glyph unicode="å" horiz-adv-x="1099" d="M115 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q159 55 309 55q213 0 328 -110.5t115 -317.5v-295q0 -33 -5.5 -65.5t-31 -83.5t-67.5 -89t-125 -65.5t-193 -27.5q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67z M301 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88zM379 1272q0 84 47 130t131 46q86 0 132 -46t46 -130t-46 -130t-132 -46q-84 0 -131 46t-47 130zM477 1272q0 -51 17.5 -71.5t62.5 -20.5t62.5 20.5t17.5 71.5 t-17.5 71.5t-62.5 20.5t-62.5 -20.5t-17.5 -71.5z" />
-<glyph unicode="æ" horiz-adv-x="1802" d="M111 322q0 152 128 237.5t353 85.5q96 0 186 -12q-6 129 -70.5 185t-211.5 56q-133 0 -283 -51v160q160 55 309 55q244 0 357 -143q113 143 348 143q205 0 327.5 -134t122.5 -359q0 -74 -6 -142h-704q35 -260 288 -260q197 0 353 76v-170q-158 -67 -357 -67h-6 q-229 0 -350 131q-106 -129 -352 -129q-113 0 -198 28.5t-128 66.5t-68.5 90t-31.5 86t-6 67zM297 324q0 -174 246 -174h3q232 0 232 167v166q-74 12 -178 13q-41 0 -86 -7.5t-97 -23.5t-86 -53t-34 -88zM965 573h524q-27 297 -262 297q-129 0 -189.5 -66.5t-72.5 -230.5z " />
-<glyph unicode="ç" horiz-adv-x="966" d="M119 512q0 256 124 391t357 135q156 0 301 -57v-178q-142 67 -285 67q-141 0 -221 -72.5t-80 -285.5q0 -113 23.5 -187.5t70 -110.5t94.5 -48t113 -12q147 0 285 65v-178q-104 -41 -223 -53l-139 -254h-189l182 254q-201 18 -307 152t-106 372z" />
-<glyph unicode="è" horiz-adv-x="1136" d="M129 500q0 540 451 540h2q205 0 327.5 -134t122.5 -359q0 -74 -6 -141h-704q35 -260 288 -261q201 0 353 74v-168q-158 -67 -357 -67h-6q-90 0 -166 22.5t-148.5 74.5t-114.5 159.5t-42 259.5zM319 575h525q-4 51 -17.5 97.5t-41 94.5t-80 76.5t-123.5 28.5 q-129 0 -190 -66.5t-73 -230.5zM346 1395h184l127 -240h-116z" />
-<glyph unicode="é" horiz-adv-x="1136" d="M129 500q0 540 451 540h2q205 0 327.5 -134t122.5 -359q0 -74 -6 -141h-704q35 -260 288 -261q201 0 353 74v-168q-158 -67 -357 -67h-6q-90 0 -166 22.5t-148.5 74.5t-114.5 159.5t-42 259.5zM319 575h525q-4 51 -17.5 97.5t-41 94.5t-80 76.5t-123.5 28.5 q-129 0 -190 -66.5t-73 -230.5zM506 1155l127 240h184l-194 -240h-117z" />
-<glyph unicode="ê" horiz-adv-x="1136" d="M129 500q0 540 451 540h2q205 0 327.5 -134t122.5 -359q0 -74 -6 -141h-704q35 -260 288 -261q201 0 353 74v-168q-158 -67 -357 -67h-6q-90 0 -166 22.5t-148.5 74.5t-114.5 159.5t-42 259.5zM319 575h525q-4 51 -17.5 97.5t-41 94.5t-80 76.5t-123.5 28.5 q-129 0 -190 -66.5t-73 -230.5zM350 1155l180 250h99l182 -250h-145l-86 133l-86 -133h-144z" />
-<glyph unicode="ë" horiz-adv-x="1136" d="M129 500q0 540 451 540h2q205 0 327.5 -134t122.5 -359q0 -74 -6 -141h-704q35 -260 288 -261q201 0 353 74v-168q-158 -67 -357 -67h-6q-90 0 -166 22.5t-148.5 74.5t-114.5 159.5t-42 259.5zM319 575h525q-4 51 -17.5 97.5t-41 94.5t-80 76.5t-123.5 28.5 q-129 0 -190 -66.5t-73 -230.5zM336 1155v174h186v-174h-186zM639 1155v174h186v-174h-186z" />
-<glyph unicode="ì" horiz-adv-x="589" d="M41 1395h184l127 -240h-116zM201 0h188v1022h-188v-1022z" />
-<glyph unicode="í" horiz-adv-x="589" d="M201 0v1022h188v-1022h-188zM236 1155l126 240h185l-195 -240h-116z" />
-<glyph unicode="î" horiz-adv-x="589" d="M63 1155l181 250h98l182 -250h-145l-86 133l-86 -133h-144zM201 0h188v1022h-188v-1022z" />
-<glyph unicode="ï" horiz-adv-x="589" d="M49 1155v174h187v-174h-187zM201 0h188v1022h-188v-1022zM352 1155v174h187v-174h-187z" />
-<glyph unicode="ð" horiz-adv-x="1212" d="M119 453q0 182 108.5 310t347.5 128q170 0 285 -72q-41 193 -162 293l-217 -131l-67 113l151 90q-63 22 -149 22h-8q-74 0 -156 -18v172q81 22 178 22h8q184 0 322 -79l178 108l68 -113l-136 -81q193 -197 193 -590q0 -643 -473 -643q-229 0 -350 138t-121 331zM309 453 q0 -43 9.5 -85t35 -94.5t87 -85t149.5 -32.5q39 0 64.5 3t70.5 26.5t72.5 68.5t50 138t24.5 224q-106 100 -282 101q-281 0 -281 -264z" />
-<glyph unicode="ñ" horiz-adv-x="1198" d="M168 0v934q213 106 436 106q193 0 302.5 -101t109.5 -280v-659h-189v672q0 90 -46 142t-185 52q-125 0 -240 -37v-829h-188zM281 1153v135q57 68 123 74q11 1 22 1q55 0 106 -25l122 -61q50 -25 103 -25q12 0 23 2q66 6 123 73v-135q-57 -68 -123 -74q-11 -1 -22 -1 q-53 0 -104 25l-122 60q-51 26 -106 26q-11 0 -22 -1q-66 -6 -123 -74z" />
-<glyph unicode="ò" horiz-adv-x="1155" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170 22.5t170 -22.5t151.5 -76.5t117 -162.5t43 -264.5t-43 -264.5t-117 -162.5t-151.5 -76.5t-170 -22.5t-170 22.5t-151.5 76.5t-117 162.5t-43 264.5zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12 t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5zM319 1395h185l127 -240h-117z" />
-<glyph unicode="ó" horiz-adv-x="1155" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170 22.5t170 -22.5t151.5 -76.5t117 -162.5t43 -264.5t-43 -264.5t-117 -162.5t-151.5 -76.5t-170 -22.5t-170 22.5t-151.5 76.5t-117 162.5t-43 264.5zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12 t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5zM532 1155l127 240h185l-195 -240h-117z" />
-<glyph unicode="ô" horiz-adv-x="1155" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170 22.5t170 -22.5t151.5 -76.5t117 -162.5t43 -264.5t-43 -264.5t-117 -162.5t-151.5 -76.5t-170 -22.5t-170 22.5t-151.5 76.5t-117 162.5t-43 264.5zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12 t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5zM350 1155l180 250h99l182 -250h-145l-86 133l-86 -133h-144z" />
-<glyph unicode="õ" horiz-adv-x="1155" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170 22.5t170 -22.5t151.5 -76.5t117 -162.5t43 -264.5t-43 -264.5t-117 -162.5t-151.5 -76.5t-170 -22.5t-170 22.5t-151.5 76.5t-117 162.5t-43 264.5zM270 1153v135q57 68 124 74q11 1 22 1q55 0 106 -25l122 -61 q50 -25 103 -25q12 0 23 2q66 6 123 73v-135q-57 -68 -123 -74q-11 -1 -22 -1q-53 0 -104 25l-122 60q-51 26 -106 26q-11 0 -22 -1q-66 -6 -124 -74zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5 t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5z" />
-<glyph unicode="ö" horiz-adv-x="1155" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170 22.5t170 -22.5t151.5 -76.5t117 -162.5t43 -264.5t-43 -264.5t-117 -162.5t-151.5 -76.5t-170 -22.5t-170 22.5t-151.5 76.5t-117 162.5t-43 264.5zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12 t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5zM336 1155v174h186v-174h-186zM639 1155v174h186v-174h-186z" />
-<glyph unicode="÷" horiz-adv-x="1286" d="M227 453v151h832v-151h-832zM549 143v174h186v-174h-186zM549 739v174h186v-174h-186z" />
-<glyph unicode="ø" horiz-adv-x="1175" d="M55 0l135 160q-90 131 -90 350q0 156 43 264.5t117 162.5t151.5 76.5t170.5 22.5q170 0 284 -73l52 59h190l-135 -162q90 -131 90 -350q0 -156 -43 -264.5t-117 -162.5t-151.5 -76.5t-169.5 -22.5q-172 0 -287 73l-49 -57h-191zM289 510q0 -119 26 -199l441 520 q-61 39 -174 39q-66 0 -112 -12t-91 -48t-67.5 -110.5t-22.5 -189.5zM406 188q59 -38 170 -38h6q66 0 111.5 12t90.5 48t67.5 110.5t22.5 189.5q0 125 -28 201z" />
-<glyph unicode="ù" horiz-adv-x="1208" d="M168 430v592h188v-606q0 -139 58.5 -204t185.5 -65t186.5 64.5t59.5 204.5v606h186v-592q0 -213 -113.5 -330.5t-318.5 -117.5t-318.5 117.5t-113.5 330.5zM358 1395h185l127 -240h-117z" />
-<glyph unicode="ú" horiz-adv-x="1208" d="M168 430v592h186v-606q0 -139 59.5 -204t186.5 -65t185.5 64.5t58.5 204.5v606h188v-592q0 -213 -113.5 -330.5t-318.5 -117.5t-318.5 117.5t-113.5 330.5zM528 1155l127 240h185l-195 -240h-117z" />
-<glyph unicode="û" horiz-adv-x="1208" d="M168 430v592h186v-606q0 -139 59.5 -204t186.5 -65t185.5 64.5t58.5 204.5v606h188v-592q0 -213 -113.5 -330.5t-318.5 -117.5t-318.5 117.5t-113.5 330.5zM369 1155l180 250h98l182 -250h-145l-86 133l-86 -133h-143z" />
-<glyph unicode="ü" horiz-adv-x="1208" d="M168 430v592h186v-606q0 -139 59.5 -204t186.5 -65t185.5 64.5t58.5 204.5v606h188v-592q0 -213 -113.5 -330.5t-318.5 -117.5t-318.5 117.5t-113.5 330.5zM354 1155v174h187v-174h-187zM657 1155v174h187v-174h-187z" />
-<glyph unicode="ý" horiz-adv-x="1073" d="M63 1022h201l275 -766l270 766h201l-420 -1131q-109 -294 -317 -294h-1q-68 0 -143 16v168q61 -16 115 -17q127 0 205 236zM467 1155l127 240h184l-194 -240h-117z" />
-<glyph unicode="þ" horiz-adv-x="1142" d="M170 -377v1774h186v-404q106 31 219 31q88 0 164 -21.5t149 -75t114.5 -159.5t41.5 -258t-41.5 -258.5t-114.5 -159.5t-148.5 -74.5t-164.5 -21.5q-113 0 -219 31v-404h-186zM356 205q104 -43 219 -43q61 0 106.5 12t88.5 46t65.5 106.5t22.5 183.5t-22.5 183.5 t-65.5 106.5t-88 46t-107 12q-115 0 -219 -43v-610z" />
-<glyph unicode="ÿ" horiz-adv-x="1069" d="M63 1022h201l275 -766l270 766h201l-420 -1131q-109 -294 -317 -294h-1q-68 0 -143 16v168q61 -16 115 -17q127 0 205 236zM291 1155v174h186v-174h-186zM594 1155v174h186v-174h-186z" />
-<glyph unicode="Œ" horiz-adv-x="2056" d="M125 684q0 168 39 295t100.5 202.5t148.5 123t170 63.5t177 16q152 0 272 -45v27h891v-178h-700v-416h616v-178h-616v-416h700v-178h-891v29q-121 -45 -272 -45q-94 0 -177 16t-170 63.5t-148.5 123t-100.5 202.5t-39 295zM315 684q0 -135 28 -234.5t69 -154.5t103.5 -88 t117.5 -42t127 -9q174 0 272 67v922q-97 67 -267 67h-5q-72 0 -127 -9t-117.5 -42t-103.5 -88t-69 -154.5t-28 -234.5z" />
-<glyph unicode="œ" horiz-adv-x="1886" d="M100 510q0 156 43 264.5t117 162.5t151.5 76.5t170.5 22.5q256 0 381 -159q112 163 359 163h3q205 0 328 -134t123 -359q0 -74 -7 -141h-704q35 -260 289 -261q201 0 352 74v-168q-158 -67 -357 -67h-6q-260 0 -378 166q-121 -166 -383 -166q-92 0 -170 22.5t-152 76.5 t-117 162.5t-43 264.5zM289 510q0 -115 22.5 -189.5t67.5 -110.5t91 -48t111.5 -12t111.5 12t91 48t67.5 110.5t22.5 189.5t-22.5 189.5t-67.5 110.5t-91 48t-111.5 12t-111.5 -12t-91 -48t-67.5 -110.5t-22.5 -189.5zM1063 575h524q-27 297 -262 297q-129 0 -189.5 -66.5 t-72.5 -230.5z" />
-<glyph unicode="Ÿ" horiz-adv-x="1277" d="M25 1366h229l383 -559l383 559h231l-520 -754v-612h-188v610zM393 1483v174h187v-174h-187zM696 1483v174h187v-174h-187z" />
-<glyph unicode="ˆ" horiz-adv-x="882" d="M207 1155l180 250h98l183 -250h-146l-86 133l-86 -133h-143z" />
-<glyph unicode="˜" horiz-adv-x="1079" d="M215 1153v135q57 68 124 74q11 1 22 1q55 0 105 -25l122 -61q50 -25 103 -25q12 0 23 2q66 6 124 73v-135q-57 -68 -123 -74q-11 -1 -22 -1q-54 0 -105 25l-122 60q-50 26 -105 26q-11 0 -22 -1q-67 -6 -124 -74z" />
-<glyph unicode=" " horiz-adv-x="887" />
-<glyph unicode=" " horiz-adv-x="1774" />
-<glyph unicode=" " horiz-adv-x="887" />
-<glyph unicode=" " horiz-adv-x="1774" />
-<glyph unicode=" " horiz-adv-x="591" />
-<glyph unicode=" " horiz-adv-x="443" />
-<glyph unicode=" " horiz-adv-x="295" />
-<glyph unicode=" " horiz-adv-x="295" />
-<glyph unicode=" " horiz-adv-x="221" />
-<glyph unicode=" " horiz-adv-x="354" />
-<glyph unicode=" " horiz-adv-x="98" />
-<glyph unicode="‐" horiz-adv-x="950" d="M162 530v160h624v-160h-624z" />
-<glyph unicode="‑" horiz-adv-x="950" d="M162 530v160h624v-160h-624z" />
-<glyph unicode="‒" horiz-adv-x="950" d="M162 530v160h624v-160h-624z" />
-<glyph unicode="–" horiz-adv-x="1146" d="M141 516v160h860v-160h-860z" />
-<glyph unicode="—" horiz-adv-x="1824" d="M162 516v160h1501v-160h-1501z" />
-<glyph unicode="‘" horiz-adv-x="552" d="M96 1384h193l151 -401h-172z" />
-<glyph unicode="’" horiz-adv-x="552" d="M96 983l152 401h192l-172 -401h-172z" />
-<glyph unicode="‚" horiz-adv-x="643" d="M147 -229l152 401h193l-173 -401h-172z" />
-<glyph unicode="“" horiz-adv-x="843" d="M96 1384h193l151 -401h-172zM401 1384h193l151 -401h-172z" />
-<glyph unicode="”" horiz-adv-x="827" d="M96 983l152 401h192l-172 -401h-172zM401 983l152 401h192l-172 -401h-172z" />
-<glyph unicode="„" horiz-adv-x="940" d="M147 -229l152 401h193l-173 -401h-172zM453 -229l151 401h193l-172 -401h-172z" />
-<glyph unicode="•" horiz-adv-x="864" d="M170 684q0 104 73.5 177t178.5 73q102 0 176 -73t74 -177t-74 -178t-176 -74q-104 0 -178 74t-74 178z" />
-<glyph unicode="…" horiz-adv-x="1607" d="M219 0v174h187v-174h-187zM711 0v174h186v-174h-186zM1200 0v174h186v-174h-186z" />
-<glyph unicode=" " horiz-adv-x="354" />
-<glyph unicode="‹" horiz-adv-x="733" d="M63 522l347 389h223l-365 -389l365 -391h-223z" />
-<glyph unicode="›" horiz-adv-x="792" d="M137 131l365 391l-365 389h223l347 -389l-347 -391h-223z" />
-<glyph unicode=" " horiz-adv-x="443" />
-<glyph unicode="€" horiz-adv-x="1400" d="M102 451v141h142q-2 29 -2 92q0 33 4 94h-144v142h162q29 135 94.5 231t153.5 144t176 68.5t189 20.5q213 0 417 -81v-177q-192 86 -395 86q-184 0 -290.5 -70.5t-145.5 -221.5h614v-142h-635q-4 -61 -4 -94q0 -63 4 -92h635v-141h-614q39 -154 145.5 -224.5t290.5 -70.5 q203 0 395 86v-176q-205 -82 -417 -82q-512 0 -613 467h-162z" />
-<glyph unicode="™" horiz-adv-x="1568" d="M100 1278v88h555v-88h-231v-594h-94v594h-230zM762 684v682h102l223 -295l226 295h102v-682h-94v539l-234 -308l-231 308v-539h-94z" />
-<glyph unicode="" horiz-adv-x="1020" d="M0 0v1020h1020v-1020h-1020z" />
-<glyph unicode="fi" horiz-adv-x="1202" d="M82 858v166h143v47q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5t-14 -122v-47h264v-166h-264v-858h-187v858h-143zM874 1180v198h201v-198h-201zM881 0v1022h188v-1022h-188z" />
-<glyph unicode="fl" horiz-adv-x="1220" d="M82 858v166h143v47q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5t-14 -122v-47h264v-166h-264v-858h-187v858h-143zM881 0v1411h188v-1411h-188z" />
-<glyph unicode="ffi" horiz-adv-x="1939" d="M82 858v164h143v49q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5t-14 -122v-49h264v-164h-264v-858h-187v858h-143zM757 858v164h143v49q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5 t-14 -122v-49h264v-164h-264v-858h-187v858h-143zM1545 1180v198h200v-198h-200zM1551 0v1022h188v-1022h-188z" />
-<glyph unicode="ffl" horiz-adv-x="1952" d="M82 858v164h143v49q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5t-14 -122v-49h264v-164h-264v-858h-187v858h-143zM757 858v164h143v49q0 381 340 381q57 0 111 -14v-162q-41 8 -76 8q-47 0 -71.5 -4t-56.5 -22.5t-46 -64.5 t-14 -122v-49h264v-164h-264v-858h-187v858h-143zM1557 0v1411h188v-1411h-188z" />
-</font>
-</defs></svg>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
-<svg xmlns="http://www.w3.org/2000/svg">
-<metadata></metadata>
-<defs>
-<font id="maven_proregular" horiz-adv-x="1529" >
-<font-face units-per-em="2048" ascent="1638" descent="-410" />
-<missing-glyph horiz-adv-x="692" />
-<glyph horiz-adv-x="2048" />
-<glyph horiz-adv-x="2048" />
-<glyph unicode="
" horiz-adv-x="682" />
-<glyph unicode=" " horiz-adv-x="692" />
-<glyph unicode="	" horiz-adv-x="692" />
-<glyph unicode=" " horiz-adv-x="692" />
-<glyph unicode="!" horiz-adv-x="761" d="M303 0v164h164v-164h-164zM311 1366h148l-15 -1022h-118z" />
-<glyph unicode=""" horiz-adv-x="780" d="M166 1384h170l-35 -411h-98zM440 1384h170l-37 -411h-98z" />
-<glyph unicode="#" horiz-adv-x="1210" d="M145 258v123h189l55 326h-186v122h209l41 242h125l-41 -242h270l43 242h125l-43 -242h147v-122h-168l-57 -326h166v-123h-186l-37 -219h-125l37 219h-271l-39 -219h-125l39 219h-168zM459 381h270l57 326h-272z" />
-<glyph unicode="$" horiz-adv-x="1181" d="M154 1004q0 143 95 249.5t294 126.5v209h110v-205q166 -8 314 -81v-152q-150 84 -314 86v-469q88 -29 147.5 -55.5t124 -73.5t97.5 -120t33 -171q0 -156 -105.5 -250t-296.5 -110v-209h-110v205q-207 6 -377 96v160q158 -104 377 -109v520q-76 23 -117 37t-101.5 44 t-92 63.5t-55 87t-23.5 121.5zM301 1004q0 -78 61.5 -121t180.5 -80v428q-242 -34 -242 -227zM653 135q254 33 254 213q0 106 -65.5 165.5t-188.5 102.5v-481z" />
-<glyph unicode="%" horiz-adv-x="1777" d="M145 983q0 111 27 189.5t73 116.5t93 54.5t104 16.5q123 0 209 -83t86 -294q0 -113 -26.5 -190.5t-71.5 -116.5t-93 -55.5t-104 -16.5q-57 0 -104 16.5t-93 55.5t-73 116.5t-27 190.5zM258 983q0 -162 50 -217t134 -55t133.5 55t49.5 217t-49.5 216t-133.5 54 q-86 0 -135 -54t-49 -216zM395 0l858 1366h131l-858 -1366h-131zM1042 385q0 113 27 190.5t73 116.5t93 55.5t104 16.5q55 0 103.5 -16.5t93.5 -55.5t71.5 -116.5t26.5 -190.5t-26.5 -190.5t-71.5 -116.5t-93.5 -55.5t-103.5 -16.5q-57 0 -104 16.5t-93 55.5t-73 116.5 t-27 190.5zM1155 385q0 -162 50 -217t134 -55t133.5 55t49.5 217t-49.5 217t-133.5 55t-134 -55t-50 -217z" />
-<glyph unicode="&" horiz-adv-x="1310" d="M111 377q0 119 56 220t220 191q-135 168 -135 283q0 127 91 218t218 91q131 0 221 -87t90 -214q0 -109 -59 -183.5t-192 -152.5l297 -354q57 100 83 205h134q-33 -177 -121 -322l227 -272h-192l-125 152q-162 -168 -385 -168q-111 0 -198 38.5t-134 101t-71.5 127 t-24.5 126.5zM256 381q0 -33 12.5 -71t42 -82t90 -72.5t144.5 -28.5q172 0 291 129l-357 428q-78 -41 -128 -85t-68.5 -87t-22.5 -67.5t-4 -63.5zM395 1069q0 -72 129 -213q131 70 171 116t40 105q0 68 -49 112t-121 44q-70 0 -120 -48t-50 -116z" />
-<glyph unicode="'" horiz-adv-x="509" d="M166 1384h170l-35 -411h-98z" />
-<glyph unicode="(" horiz-adv-x="706" d="M190 625q1 450 228 858h155q-29 -47 -59.5 -112.5t-74.5 -180.5t-72.5 -264.5t-28.5 -300.5q0 -150 28.5 -300.5t72.5 -264t76 -180t58 -111.5h-155q-227 408 -228 856z" />
-<glyph unicode=")" horiz-adv-x="706" d="M133 -231q29 47 59.5 111.5t75.5 180t74 265t29 301.5q0 195 -53.5 396.5t-98.5 296.5t-86 163h158q227 -408 227 -856q0 -451 -227 -858h-158z" />
-<glyph unicode="*" horiz-adv-x="841" d="M172 1112l178 78l-178 80l55 94l156 -111l-21 187h111l-16 -187l153 111l56 -94l-177 -80l177 -78l-56 -98l-153 112l16 -184h-111l21 184l-156 -112z" />
-<glyph unicode="+" horiz-adv-x="1128" d="M199 551v123h303v303h123v-303h301v-123h-301v-301h-123v301h-303z" />
-<glyph unicode="," horiz-adv-x="641" d="M152 -229l151 401h168l-172 -401h-147z" />
-<glyph unicode="-" horiz-adv-x="950" d="M162 539v143h624v-143h-624z" />
-<glyph unicode="." horiz-adv-x="671" d="M260 0v164h164v-164h-164z" />
-<glyph unicode="/" horiz-adv-x="827" d="M-29 -201l752 1749h135l-754 -1749h-133z" />
-<glyph unicode="0" horiz-adv-x="1345" d="M133 677.5q0 700.5 538.5 700.5t538.5 -700.5t-538.5 -700.5t-538.5 700.5zM281 678q0 -145 23.5 -250.5t60 -165t92 -94.5t104.5 -45t110.5 -10t110.5 10t104.5 45t92.5 94.5t60.5 164.5t23.5 251q0 178 -34 298t-95.5 174t-121.5 73.5t-140 19.5t-140.5 -19.5 t-122 -73.5t-95 -174t-33.5 -298z" />
-<glyph unicode="1" horiz-adv-x="923" d="M211 1032v174l252 160h168v-1366h-148v1212z" />
-<glyph unicode="2" horiz-adv-x="1189" d="M158 0v129q102 76 208.5 169t223.5 215t189.5 252t72.5 234q0 238 -322 238q-190 0 -358 -119v162q172 104 371 104q233 0 344.5 -112.5t111.5 -272.5q0 -111 -56 -232.5t-151.5 -236t-190 -206t-200.5 -177.5h619v-147h-862z" />
-<glyph unicode="3" horiz-adv-x="1193" d="M166 70v151q160 -90 346 -90q182 0 278.5 67.5t96.5 194.5t-98.5 194.5t-284.5 67.5h-150v142h156q150 0 228.5 56t78.5 162.5t-83 164t-240 57.5q-152 0 -295 -72v150q145 69 306 69h5q233 0 344 -108.5t111 -259.5q0 -172 -148 -260l-29 -17q10 -4 26.5 -12t58.5 -39 t75 -67.5t59.5 -99t26.5 -132.5q0 -78 -27.5 -147.5t-84 -129t-157.5 -94t-235 -34.5q-197 0 -364 86z" />
-<glyph unicode="4" horiz-adv-x="1210" d="M78 410v157l645 799h166v-819h194v-137h-194v-410h-148v410h-663zM242 547h499v608z" />
-<glyph unicode="5" horiz-adv-x="1202" d="M178 735l70 631h702v-145h-575l-39 -406q121 37 266 37q84 0 153.5 -18.5t136 -63.5t105.5 -135t39 -217t-39 -217t-105.5 -135.5t-136 -63.5t-153.5 -18q-236 0 -391 84v153q150 -94 381 -94q80 0 138 19.5t89 46t47.5 69.5t20.5 77t4 79q0 92 -24.5 152.5t-71.5 88 t-93.5 37t-109.5 9.5q-96 0 -177 -16.5t-112 -33.5l-33 -14z" />
-<glyph unicode="6" horiz-adv-x="1193" d="M133 625q0 141 25.5 257.5t65.5 193.5t96.5 135.5t111.5 90t118.5 51t111 25.5t94.5 6q109 0 198 -26v-137q-98 24 -176 24h-6q-47 0 -92 -6t-113.5 -32.5t-122 -73t-97.5 -136.5t-58 -213q133 90 309 90q215 0 340 -115.5t125 -314.5q0 -84 -25.5 -162.5t-78 -147 t-145.5 -109.5t-216 -41q-465 0 -465 641zM281 621q2 -141 27.5 -241.5t57 -149t83 -75t79 -29.5t70.5 -3q102 0 172 38t98.5 98t37.5 103.5t9 83.5q0 139 -81.5 215t-235.5 76q-201 0 -317 -116z" />
-<glyph unicode="7" horiz-adv-x="1175" d="M143 1219v147h932v-137l-592 -1229h-164l596 1219h-772z" />
-<glyph unicode="8" horiz-adv-x="1191" d="M125 406q0 203 184 307q51 25 51 24l-20 11q-12 6 -44 30.5t-56.5 55t-44 84t-19.5 112.5q0 145 103.5 249.5t318.5 104.5t318.5 -103t103.5 -251q0 -184 -148 -274l-30 -17q10 -4 25.5 -11t54.5 -34.5t69.5 -62.5t55 -95.5t24.5 -129.5q0 -78 -25.5 -149t-79 -134.5 t-148.5 -101t-220 -37.5q-240 0 -356.5 127t-116.5 295zM272 406q0 -27 4.5 -55.5t23.5 -71.5t52 -76t96.5 -56.5t149.5 -23.5t149.5 23.5t96.5 56.5t52 76t23.5 71.5t4.5 55.5q0 123 -84 188t-242 65t-242 -65.5t-84 -187.5zM324 1030q0 -111 72.5 -173t201.5 -62 t201.5 62.5t72.5 172.5q0 209 -274 209t-274 -209z" />
-<glyph unicode="9" horiz-adv-x="1202" d="M119 924q0 84 25.5 162.5t77.5 147.5t145.5 109.5t216.5 40.5q465 0 465 -643q0 -195 -48.5 -340t-116 -223t-160.5 -124t-160.5 -58t-137.5 -12q-104 0 -199 24v139q81 -24 175 -24h8q47 0 92 6t113.5 32.5t120.5 73t97.5 136.5t57.5 213q-121 -90 -307 -90 q-215 0 -340 115.5t-125 314.5zM266 922q0 -139 82 -215t236 -76q207 0 317 117q-2 141 -27.5 241.5t-57.5 148.5t-83 74.5t-78.5 29.5t-70.5 3q-102 0 -172 -38t-98.5 -98t-38 -103t-9.5 -84z" />
-<glyph unicode=":" horiz-adv-x="800" d="M324 98v164h163v-164h-163zM324 754v164h163v-164h-163z" />
-<glyph unicode=";" horiz-adv-x="854" d="M199 -229l151 401h168l-172 -401h-147zM352 754v164h164v-164h-164z" />
-<glyph unicode="<" horiz-adv-x="1351" d="M246 481v140l844 383v-164l-672 -289l672 -291v-162z" />
-<glyph unicode="=" horiz-adv-x="1306" d="M260 381v143h789v-143h-789zM260 698v142h789v-142h-789z" />
-<glyph unicode=">" horiz-adv-x="1329" d="M270 98v162l670 291l-670 289v164l842 -383v-140z" />
-<glyph unicode="?" horiz-adv-x="993" d="M164 1145v160q113 77 285 77q61 0 120.5 -14t129 -50t112.5 -117t43 -197q0 -82 -31.5 -142.5t-76.5 -98.5l-91 -76q-45 -38 -76.5 -101.5t-31.5 -151.5v-90h-148v100q0 104 32 181t77 120t90 78t77 79t32 102q0 231 -269 231q-156 0 -274 -90zM391 0v164h164v-164h-164z " />
-<glyph unicode="@" horiz-adv-x="1869" d="M170 371q0 244 133 445.5t334 309t410 107.5q82 0 171 -21.5t180 -73t162.5 -126t116.5 -189t45 -254.5q0 -221 -91 -362t-242 -141q-137 0 -191 100q-96 -100 -295 -100q-143 0 -225 62t-82 171q0 49 16.5 94t59.5 94.5t138 79t230 29.5q74 0 152 -10q2 14 2 43 q0 35 -6 59.5t-23.5 50t-60.5 39t-109 13.5q-143 0 -256 -43l17 118q123 41 239 41q156 0 237 -70.5t81 -207.5q0 -59 -11.5 -142.5t-17.5 -137.5q-4 -40 -4 -81v-26q2 -54 30.5 -79.5t86.5 -25.5q90 0 169 114t79 312q0 145 -55.5 262t-144.5 186.5t-191.5 106.5t-206.5 37 q-311 0 -554 -230.5t-243 -553.5q0 -154 51 -270.5t123 -177t163 -98.5t153.5 -47t113.5 -9q254 0 430 100l-18 -92q-172 -82 -428 -82q-86 0 -174.5 22.5t-178.5 74.5t-158.5 127t-112.5 192.5t-44 259.5zM715 300q0 -116 188 -116q49 0 88 8.5t65.5 17.5t47 31.5t31 37 t18.5 46t11 46t6.5 51.5t5.5 47q-55 8 -136 8q-325 0 -325 -177z" />
-<glyph unicode="A" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM422 567h479l-239 625z" />
-<glyph unicode="B" horiz-adv-x="1271" d="M178 0v1366h453q454 0 454 -365q0 -57 -15 -106t-36.5 -78t-42 -50.5t-37.5 -29.5l-14 -10q10 -2 24.5 -8t54.5 -33t69.5 -61.5t54 -98t24.5 -139.5q0 -387 -495 -387h-494zM326 137h358q156 0 246 54.5t90 203.5q0 63 -21.5 108.5t-52.5 71t-80 39t-91 17.5t-97 4h-352 v-498zM326 774h338q125 0 199.5 43t74.5 172q0 133 -72.5 184.5t-220.5 51.5h-319v-451z" />
-<glyph unicode="C" horiz-adv-x="1286" d="M125 684q0 700 633 700q219 0 416 -79v-142q-195 82 -400 82q-260 0 -381 -135t-121 -426t121 -426t381 -135q209 0 400 80v-142q-196 -77 -411 -77h-5q-633 0 -633 700z" />
-<glyph unicode="D" horiz-adv-x="1372" d="M178 0v1366h428q641 0 641 -682q0 -684 -641 -684h-428zM326 139h270q262 0 383 131t121 414t-121 414t-383 131h-270v-1090z" />
-<glyph unicode="E" horiz-adv-x="1159" d="M178 0v1366h862v-139h-714v-471h628v-144h-628v-473h714v-139h-862z" />
-<glyph unicode="F" horiz-adv-x="1097" d="M178 0v1366h838v-139h-690v-471h606v-144h-606v-612h-148z" />
-<glyph unicode="G" horiz-adv-x="1386" d="M125 684q0 700 633 700q227 0 430 -86v-143q-199 88 -414 88q-260 0 -381 -135t-121 -424q0 -291 121 -425t381 -134q164 0 313 49v426h-329v143h477v-653q-221 -106 -477 -106q-633 0 -633 700z" />
-<glyph unicode="H" horiz-adv-x="1380" d="M178 0v1366h148v-612h727v612h147v-1366h-147v612h-727v-612h-148z" />
-<glyph unicode="I" horiz-adv-x="501" d="M178 0v1366h148v-1366h-148z" />
-<glyph unicode="J" horiz-adv-x="915" d="M59 55v154q117 -82 252 -82q139 0 200.5 68.5t61.5 224.5v946h148v-958q0 -125 -35 -213.5t-95.5 -131.5t-124 -61t-136.5 -18q-147 0 -271 71z" />
-<glyph unicode="K" horiz-adv-x="1294" d="M178 0v1366h148v-704l632 704h199l-586 -639l633 -727h-194l-535 623l-149 -164v-459h-148z" />
-<glyph unicode="L" horiz-adv-x="1067" d="M178 0v1366h148v-1223h698v-143h-846z" />
-<glyph unicode="M" horiz-adv-x="1619" d="M178 0v1366h160l469 -694l471 694h160v-1366h-148v1124l-483 -696l-481 696v-1124h-148z" />
-<glyph unicode="N" horiz-adv-x="1490" d="M178 0v1366h168l815 -1151v1151h148v-1366h-166l-817 1151v-1151h-148z" />
-<glyph unicode="O" d="M125 684q0 700 633 700q635 0 635 -700t-635 -700q-633 0 -633 700zM272 684q0 -291 117 -426t369 -135q254 0 370.5 135t116.5 426t-116.5 426t-370.5 135q-252 0 -369 -135t-117 -426z" />
-<glyph unicode="P" horiz-adv-x="1220" d="M176 0v1366h486q229 0 351 -108.5t122 -313.5t-122 -313.5t-351 -108.5h-338v-522h-148zM324 664h319q109 0 181.5 23.5t105.5 68.5t45 88t12 100t-12 100t-45 88.5t-105.5 69t-181.5 23.5h-319v-561z" />
-<glyph unicode="Q" d="M125 684q0 700 633 700q635 0 635 -700q0 -651 -553 -698q41 -170 268 -176v-134q-113 0 -196 30t-127 81t-65.5 98t-29.5 101q-565 37 -565 698zM272 684q0 -291 117 -426t369 -135q254 0 370.5 135t116.5 426t-116.5 426t-370.5 135q-252 0 -369 -135t-117 -426z" />
-<glyph unicode="R" horiz-adv-x="1271" d="M178 0v1366h502q223 0 343 -101.5t120 -295.5q0 -88 -28 -160t-63.5 -112t-84.5 -69.5t-80 -39t-58 -15.5l375 -573h-174l-356 551h-348v-551h-148zM326 690h346q102 0 170.5 23.5t99.5 65.5t42 83t11 96.5t-11 96.5t-42 83t-99.5 65.5t-170.5 23.5h-346v-537z" />
-<glyph unicode="S" horiz-adv-x="1150" d="M125 1004q0 158 114.5 269t356.5 111q174 0 342 -81v-152q-152 86 -328 86q-164 0 -251 -60t-87 -173q0 -70 56.5 -117t139.5 -69.5t181.5 -57.5t181 -76t139 -127t56.5 -209q0 -170 -128 -267t-357 -97q-223 0 -404 96v160q162 -109 393 -109q348 0 349 217 q0 92 -56.5 154.5t-139.5 91.5l-181 60q-98 32 -181 65t-139.5 105.5t-56.5 179.5z" />
-<glyph unicode="T" horiz-adv-x="1163" d="M25 1223v143h1112v-143h-482v-1223h-147v1223h-483z" />
-<glyph unicode="U" horiz-adv-x="1476" d="M178 561v805h148v-813q0 -434 407.5 -434t407.5 434v813h147v-805q0 -281 -143.5 -429t-411.5 -148t-411.5 148.5t-143.5 428.5z" />
-<glyph unicode="V" horiz-adv-x="1251" d="M25 1366h157l441 -1155l438 1155h158l-525 -1366h-145z" />
-<glyph unicode="W" horiz-adv-x="1947" d="M25 1366h155l359 -1143l360 1143h148l358 -1143l360 1143h156l-440 -1366h-150l-358 1114l-359 -1114h-151z" />
-<glyph unicode="X" horiz-adv-x="1206" d="M25 0l485 692l-473 674h176l387 -549l385 549h180l-475 -676l484 -690h-177l-397 563l-395 -563h-180z" />
-<glyph unicode="Y" horiz-adv-x="1224" d="M25 1366h178l405 -596l404 596h178l-508 -739v-627h-147v627z" />
-<glyph unicode="Z" horiz-adv-x="1247" d="M131 0v129l780 1090h-759v147h952v-125l-785 -1094h785v-147h-973z" />
-<glyph unicode="[" horiz-adv-x="784" d="M244 -238v1663h370v-139h-223v-1384h223v-140h-370z" />
-<glyph unicode="\" horiz-adv-x="753" d="M-76 1548h135l752 -1749h-133z" />
-<glyph unicode="]" horiz-adv-x="780" d="M172 -98h221v1384h-221v139h369v-1663h-369v140z" />
-<glyph unicode="^" horiz-adv-x="1146" d="M274 969l213 444h113l213 -444h-123l-145 325l-148 -325h-123z" />
-<glyph unicode="_" horiz-adv-x="1118" d="M0 -49h1118v-90h-1118v90z" />
-<glyph unicode="`" horiz-adv-x="448" d="M94 1391h150l116 -236h-96z" />
-<glyph unicode="a" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t94 59t101 36t104.5 17.5t86 7t60.5 1l164 -10q0 137 -75.5 195.5t-223.5 58.5q-166 0 -299 -49v143q151 53 307 53q215 0 328 -108.5t113 -317.5v-307q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 313q0 -192 278 -192h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -203z" />
-<glyph unicode="b" horiz-adv-x="1167" d="M170 307v1106h147v-414q123 41 252 41q70 0 131.5 -12t125 -47t109.5 -90t74.5 -149.5t28.5 -217.5q0 -135 -31.5 -236.5t-77.5 -157.5t-110.5 -91t-119 -45t-111.5 -10q-111 0 -193 27.5t-123 63.5t-66.5 86t-30.5 82.5t-5 63.5zM317 299q1 -180 271 -180q57 0 103 13 t95.5 51t77 125t27.5 216q0 199 -76 289t-246 90q-127 0 -252 -51v-553z" />
-<glyph unicode="c" horiz-adv-x="966" d="M119 512q0 156 43 265.5t116.5 163.5t150.5 76.5t169 22.5q154 0 303 -57v-141q-139 57 -287 57q-182 0 -265 -92t-83 -295q0 -205 83 -296t265 -91q154 0 287 57v-143q-143 -55 -303 -55q-72 0 -134.5 12t-128 48t-111.5 92.5t-75.5 152.5t-29.5 223z" />
-<glyph unicode="d" horiz-adv-x="1150" d="M129 524q0 152 41 258.5t113.5 159.5t147.5 74.5t165 21.5q135 0 254 -39v412h147v-1106q0 -41 -12 -84t-50 -102.5t-130 -97t-227 -37.5q-57 0 -111.5 10t-118 45t-109.5 91t-78 157.5t-32 236.5zM276 524q0 -129 28 -216t77 -125t94 -51t103 -13q272 0 272 180v553 q-125 51 -254 51q-168 0 -244 -90t-76 -289z" />
-<glyph unicode="e" horiz-adv-x="1136" d="M129 498v7v8q0 527 449 527q94 0 170.5 -29.5t121.5 -69.5t79 -104.5t48.5 -109.5t22.5 -109q8 -62 8 -78v-44l-4 -72h-745q14 -154 94 -223.5t237 -69.5q223 0 351 76v-152q-148 -71 -359 -71h-6q-90 0 -165 21.5t-147.5 74.5t-113.5 159.5t-41 258.5zM279 555h598 q0 35 -8.5 78t-34 110.5t-92 112.5t-164.5 45q-152 0 -220.5 -79t-78.5 -267z" />
-<glyph unicode="f" horiz-adv-x="675" d="M82 887v135h176v55q0 106 27.5 182t62.5 112t89.5 56.5t83 22.5t67.5 2q55 0 98 -12v-131q-41 8 -74 8q-49 0 -74.5 -4t-61.5 -24.5t-53 -73t-17 -136.5v-57h280v-135h-280v-887h-148v887h-176z" />
-<glyph unicode="g" horiz-adv-x="1101" d="M123 516q0 160 43 271.5t115.5 162.5t138 70.5t137.5 19.5q131 0 220 -36.5t125 -94t48.5 -99.5t12.5 -83v-770q0 -68 -19.5 -125t-64.5 -111.5t-137.5 -85t-223.5 -30.5q-143 0 -291 37v139q131 -37 275 -37q98 0 164.5 17.5t96.5 50t41 64.5t11 77v102 q-115 -37 -240 -37q-68 0 -127 11.5t-120.5 45.5t-105.5 88t-71.5 144t-27.5 209zM270 516q0 -115 24.5 -189.5t72 -110.5t95.5 -48t113 -12q123 0 240 47v530q0 172 -258 172q-55 0 -98 -13t-90 -50t-73 -120t-26 -206z" />
-<glyph unicode="h" horiz-adv-x="1181" d="M193 0v1411h147v-436q155 65 309 65q188 0 290.5 -113.5t102.5 -320.5v-606h-147v606q0 287 -262 287q-143 0 -293 -62v-831h-147z" />
-<glyph unicode="i" horiz-adv-x="540" d="M193 1204v162h163v-162h-163zM201 0h147v1022h-147v-1022z" />
-<glyph unicode="j" horiz-adv-x="514" d="M6 -258q127 2 168 50t41 157v1073h147v-1081q0 -336 -356 -336v137zM207 1204v162h164v-162h-164z" />
-<glyph unicode="k" horiz-adv-x="1124" d="M188 0v1411h148v-891l459 502h200l-436 -469l492 -553h-197l-391 449l-127 -138v-311h-148z" />
-<glyph unicode="l" horiz-adv-x="552" d="M207 0v1411h147v-1411h-147z" />
-<glyph unicode="m" horiz-adv-x="1824" d="M193 0v942q197 98 409 98q168 0 270 -84q197 84 379 84q186 0 288.5 -98t102.5 -276v-666h-147v666q0 43 -8 76.5t-32.5 71.5t-79 58.5t-134.5 20.5q-129 0 -283 -51q33 -74 33 -176v-666h-147v666q0 45 -8.5 78.5t-33 70.5t-80.5 57.5t-138 20.5q-125 0 -244 -37v-856 h-147z" />
-<glyph unicode="n" horiz-adv-x="1189" d="M168 0v940q201 100 432 100q193 0 299.5 -98t106.5 -276v-666h-148v666q0 43 -9 76.5t-35 71.5t-84 58.5t-146 20.5q-129 0 -269 -41v-852h-147z" />
-<glyph unicode="o" horiz-adv-x="1155" d="M100 512q0 156 43 265.5t117 163.5t150.5 76.5t169 22.5t170 -22.5t151.5 -76.5t117 -163.5t43 -265.5t-43 -265.5t-117 -163.5t-151.5 -76.5t-170 -22.5t-169 22.5t-150.5 76.5t-117 163.5t-43 265.5zM248 512q0 -205 79 -299t253 -94t253.5 93t79.5 300t-79.5 300 t-253.5 93t-253 -94t-79 -299z" />
-<glyph unicode="p" horiz-adv-x="1159" d="M168 -379v1096q0 41 12.5 84t50 103.5t130 98t227.5 37.5q57 0 111.5 -10t118 -45t109.5 -92t77.5 -158.5t31.5 -236.5q0 -152 -41 -258.5t-113.5 -159.5t-147.5 -74.5t-165 -21.5q-125 0 -254 39v-402h-147zM315 172q117 -51 254 -51q168 0 244 89t76 290 q0 129 -27.5 215t-77 125t-94.5 52t-102 13q-272 0 -273 -182v-551z" />
-<glyph unicode="q" horiz-adv-x="1150" d="M115 498q0 135 31.5 236.5t77.5 158.5t109.5 92t118 45t111.5 10q111 0 193 -27.5t123 -63.5t66.5 -86t30.5 -82.5t5 -63.5v-1096h-147v402q-117 -39 -252 -39q-90 0 -165 21.5t-147.5 74.5t-113.5 159.5t-41 258.5zM262 500q0 -201 76 -290t244 -89q135 0 252 51v551 q0 182 -271 182q-57 0 -102 -13t-94.5 -52t-77 -125t-27.5 -215z" />
-<glyph unicode="r" horiz-adv-x="702" d="M166 0v940q231 98 496 98v-141q-168 -4 -349 -53v-844h-147z" />
-<glyph unicode="s" horiz-adv-x="872" d="M100 743q0 123 89.5 210t273.5 87q131 0 262 -63v-148q-117 70 -246 70q-231 0 -231 -156q0 -53 56 -85.5t137 -54t162 -51.5t137.5 -101.5t56.5 -182.5q0 -133 -99.5 -208.5t-275.5 -75.5q-170 0 -309 77v156q147 -90 303 -90q233 0 233 141q0 76 -56 119t-137 64.5 t-162 47t-137.5 86t-56.5 158.5z" />
-<glyph unicode="t" horiz-adv-x="706" d="M49 887v135h178v289h148v-289h264v-135h-264v-623q0 -78 32.5 -109.5t114.5 -31.5q63 0 117 24v-139q-65 -24 -130 -24h-5q-41 0 -80 9t-88 35.5t-79 89t-30 154.5v615h-178z" />
-<glyph unicode="u" horiz-adv-x="1202" d="M168 430v592h147v-600q0 -156 68 -228.5t213 -72.5q143 0 212 72.5t69 228.5v600h147v-592q0 -213 -111.5 -329.5t-316.5 -116.5t-316.5 116.5t-111.5 329.5z" />
-<glyph unicode="v" horiz-adv-x="1062" d="M66 1022h157l309 -838l310 838h157l-393 -1022h-149z" />
-<glyph unicode="w" horiz-adv-x="1603" d="M78 1022h155l261 -791l235 791h141l238 -791l260 791h156l-340 -1022h-150l-233 764l-234 -764h-149z" />
-<glyph unicode="x" horiz-adv-x="1032" d="M57 0l365 518l-354 504h174l268 -379l268 379h180l-356 -504l-2 -4l363 -514h-177l-274 389l-274 -389h-181z" />
-<glyph unicode="y" horiz-adv-x="1028" d="M63 1022h158l291 -809l295 809h158l-418 -1122q-109 -295 -322 -295q-49 0 -96 18v135q41 -18 88 -18q39 0 77 28.5t62.5 68.5t45 81t28.5 70l10 28z" />
-<glyph unicode="z" horiz-adv-x="950" d="M92 0v133l557 748h-543v141h734v-129l-561 -752h561v-141h-748z" />
-<glyph unicode="{" horiz-adv-x="763" d="M186 551v100q129 33 129 148v319q0 150 79 229.5t229 79.5v-139q-82 -4 -121 -36.5t-39 -123.5v-329q0 -119 -119 -199q119 -80 119 -199v-329q0 -88 39 -122t121 -38v-137q-150 0 -229 78.5t-79 228.5v319q0 111 -129 150z" />
-<glyph unicode="|" horiz-adv-x="907" d="M379 -203v1565h147v-1565h-147z" />
-<glyph unicode="}" horiz-adv-x="753" d="M156 -88q82 4 120.5 38t38.5 122v329q0 117 121 199q-121 82 -121 199v329q0 90 -37.5 123t-121.5 37v139q150 0 228.5 -79.5t78.5 -229.5v-319q0 -115 129 -148v-100q-129 -39 -129 -150v-319q0 -150 -79 -228.5t-228 -78.5v137z" />
-<glyph unicode="~" horiz-adv-x="1024" d="M201 473v125q57 72 123 79q11 1 22 1q54 0 104 -27l124 -65q50 -27 103 -27q12 0 23 1q66 7 123 79v-123q-57 -72 -123 -79q-11 -1 -22 -1q-54 0 -104 27l-124 65q-50 25 -103 25q-12 0 -23 -1q-66 -7 -123 -79z" />
-<glyph unicode="¡" horiz-adv-x="741" d="M303 860v162h164v-162h-164zM311 -344l15 1022h118l15 -1022h-148z" />
-<glyph unicode="¢" horiz-adv-x="976" d="M117 682q0 270 116.5 390t294.5 136v158h103v-156q131 -2 266 -55v-141q-125 51 -266 55v-772q143 4 266 57v-143q-125 -49 -266 -55v-156h-103v158q-411 41 -411 524zM264 684q0 -178 63.5 -270t200.5 -111v762q-137 -20 -200.5 -111.5t-63.5 -269.5z" />
-<glyph unicode="£" horiz-adv-x="1136" d="M104 -31v150q80 39 177 45v411h-177v136h177v293q0 158 105.5 269t328.5 111q162 0 309 -79v-150q-139 88 -297 88q-299 0 -299 -233v-299h412v-136h-412v-415q51 -6 145.5 -25.5t154.5 -30.5q47 -8 107 -8q18 0 36 1q83 3 153 30v-150q-80 -31 -180 -31q-24 0 -49 2 q-129 9 -230.5 37.5t-229.5 31.5h-14q-120 0 -217 -48z" />
-<glyph unicode="¥" horiz-adv-x="1232" d="M37 1366h180l404 -588l403 588h180l-444 -645h256v-123h-322v-192h322v-123h-322v-283h-147v283h-322v123h322v192h-322v123h256z" />
-<glyph unicode="§" horiz-adv-x="1112" d="M119 727q0 164 186 223q-82 51 -82 144q0 119 100.5 204.5t309.5 85.5q156 0 297 -61v-145q-129 69 -281 69h-4q-274 0 -274 -153q0 -25 8 -40.5t68.5 -44t179.5 -55.5q84 -18 140 -38.5t116.5 -56.5t92.5 -94t32 -136q0 -76 -51.5 -130.5t-125.5 -78.5q72 -59 72 -158 q0 -131 -111.5 -204.5t-310.5 -73.5q-203 0 -350 73v152q135 -84 340 -84q285 0 285 137q0 61 -48.5 102.5t-218.5 79.5q-184 43 -277 101.5t-93 181.5zM266 727q0 -25 8.5 -40t70 -44t179.5 -55q135 -29 219 -74q117 35 117 115q0 61 -49 102t-217 80q-98 23 -176 49 q-152 -29 -152 -133z" />
-<glyph unicode="¨" horiz-adv-x="788" d="M162 1155v162h162v-162h-162zM465 1155v162h162v-162h-162z" />
-<glyph unicode="©" horiz-adv-x="1593" d="M98 682q0 291 206 495.5t495 204.5q291 0 495.5 -204.5t204.5 -495.5q0 -289 -204.5 -493.5t-495.5 -204.5q-289 0 -495 204.5t-206 493.5zM172 682q0 -258 184.5 -442.5t442.5 -184.5q260 0 443 184.5t183 442.5q0 260 -183 444.5t-443 184.5q-258 0 -442.5 -184.5 t-184.5 -444.5zM449 682q0 410 368 410q129 0 244 -45v-82q-109 45 -234 45q-152 0 -222 -79t-70 -249t70.5 -249t221.5 -79q121 0 234 47v-82q-115 -45 -244 -45q-369 0 -368 408z" />
-<glyph unicode="ª" horiz-adv-x="806" d="M156 979q0 186 297 186l92 -6q0 143 -168 144q-92 0 -164 -29v80q72 28 168 28q246 0 246 -235v-172q0 -72 -46 -115t-92.5 -53t-95.5 -10t-96 10t-94 54t-47 118zM238 982q0 -110 155 -110q152 1 152 105v108q-37 6 -101 7q-206 0 -206 -110z" />
-<glyph unicode="«" horiz-adv-x="1132" d="M63 522l347 389h198l-364 -389l364 -391h-198zM453 522l346 389h198l-364 -389l364 -391h-198z" />
-<glyph unicode="¬" horiz-adv-x="1273" d="M199 528v136h876v-340h-135v204h-741z" />
-<glyph unicode="­" horiz-adv-x="950" d="M162 539v143h624v-143h-624z" />
-<glyph unicode="®" horiz-adv-x="1593" d="M98 682q0 291 206 495.5t495 204.5q291 0 495.5 -204.5t204.5 -495.5q0 -289 -204.5 -493.5t-495.5 -204.5q-289 0 -495 204.5t-206 493.5zM172 682q0 -258 184.5 -442.5t442.5 -184.5q260 0 443 184.5t183 442.5q0 260 -183 444.5t-443 184.5q-258 0 -442.5 -184.5 t-184.5 -444.5zM545 283v798h293q131 0 201.5 -58t70.5 -173q0 -63 -22.5 -111.5t-57.5 -72t-58 -33t-46 -15.5l219 -335h-103l-206 323h-205v-323h-86zM631 688h203q113 0 150.5 43t37.5 112.5t-38 112.5t-150 43h-203v-311z" />
-<glyph unicode="¯" horiz-adv-x="825" d="M176 1180v108h479v-108h-479z" />
-<glyph unicode="°" horiz-adv-x="792" d="M168 1142q0 228 223 228q109 0 166 -58.5t57 -168.5q0 -227 -223 -227q0 -1 -1 -1q-222 0 -222 227zM242 1143q0 -82 34.5 -118t114.5 -36q78 0 114 36t36 118q0 80 -36 116.5t-114 36.5q-80 0 -114.5 -36.5t-34.5 -116.5z" />
-<glyph unicode="±" horiz-adv-x="1198" d="M231 68v122h727v-122h-727zM231 598v123h304v301h122v-301h301v-123h-301v-303h-122v303h-304z" />
-<glyph unicode="²" horiz-adv-x="671" d="M109 948v70q133 98 252.5 230t119.5 239q0 125 -172 125q-106 0 -192 -62v86q90 55 198 56q125 0 185.5 -60.5t60.5 -145.5q0 -197 -321 -458h331v-80h-462z" />
-<glyph unicode="³" horiz-adv-x="661" d="M109 985v82q90 -49 186 -49q203 0 203 141t-207 141h-80v76h84q164 0 164 119q0 117 -174 117q-88 0 -158 -37v80q82 37 168 37q125 0 184.5 -58.5t59.5 -138.5q0 -49 -23.5 -86t-48.5 -49l-23 -14q6 -2 14.5 -6.5t31 -20.5t40 -36.5t31.5 -54.5t14 -71 q0 -90 -66.5 -153.5t-203.5 -63.5q-102 0 -196 45z" />
-<glyph unicode="´" horiz-adv-x="483" d="M123 1155l117 236h151l-170 -236h-98z" />
-<glyph unicode="µ" horiz-adv-x="1247" d="M203 -430v1452h147v-664q0 -45 9.5 -78.5t35 -70.5t83 -57.5t145.5 -20.5q135 0 268 39v852h147v-938q-193 -100 -430 -100q-152 2 -258 82v-496h-147z" />
-<glyph unicode="¶" horiz-adv-x="911" d="M78 946q0 203 121 311.5t350 108.5h203v-1366h-136v524h-67q-229 0 -350 108.5t-121 313.5z" />
-<glyph unicode="·" horiz-adv-x="636" d="M238 475v164h161v-164h-161z" />
-<glyph unicode="¸" horiz-adv-x="518" d="M115 -246l168 236h116l-129 -236h-155z" />
-<glyph unicode="¹" horiz-adv-x="491" d="M109 1503v94l135 86h90v-735h-78v651z" />
-<glyph unicode="º" horiz-adv-x="854" d="M156 1090q0 86 23.5 147t64.5 92t84 43t94 12q266 0 266 -294q0 -293 -266 -293q-113 0 -189.5 64.5t-76.5 228.5zM238 1089.5q0 -114.5 44 -166t140 -51.5q98 0 141 51.5t43 166t-43 167t-141 52.5q-96 0 -140 -52.5t-44 -167z" />
-<glyph unicode="»" horiz-adv-x="1132" d="M137 131l363 391l-363 389h197l348 -389l-348 -391h-197zM526 131l363 391l-363 389h197l348 -389l-348 -391h-197z" />
-<glyph unicode="¼" horiz-adv-x="1433" d="M106 1200v94l134 86h92v-733h-80v651zM147 -45l895 1448h97l-895 -1448h-97zM788 221v84l347 428h90v-438h104v-74h-104v-221h-80v221h-357zM877 295h268v326z" />
-<glyph unicode="½" horiz-adv-x="1460" d="M106 1200v94l134 86h92v-733h-80v651zM147 -45l895 1448h97l-895 -1448h-97zM870 0v70q133 98 252 229t119 238q0 127 -172 127q-104 0 -192 -64v88q98 55 198 55q127 0 186.5 -60t59.5 -146q0 -195 -322 -457h332v-80h-461z" />
-<glyph unicode="¾" horiz-adv-x="1654" d="M135 684v80q94 -47 189 -47q200 0 200 140q0 69 -53 105.5t-154 36.5h-79v74h84q164 0 164 118q0 120 -175 120q-84 0 -157 -39v82q82 37 168 37q125 0 184 -58.5t59 -140.5q0 -49 -23.5 -86t-45.5 -49l-25 -15q6 -2 14.5 -6t31 -20.5t39.5 -35.5t31.5 -53t14.5 -71 q0 -90 -65.5 -154.5t-204.5 -64.5q-98 0 -197 47zM369 -45l895 1448h96l-895 -1448h-96zM1010 221v84l346 428h90v-438h104v-74h-104v-221h-80v221h-356zM1098 295h268v326z" />
-<glyph unicode="¿" horiz-adv-x="989" d="M147 20q0 82 32 141.5t77 97.5l90 76q45 38 77 101.5t32 151.5v92h147v-102q0 -104 -31.5 -181.5t-76.5 -119t-90.5 -77.5t-77 -80t-31.5 -100q0 -233 270 -233q141 0 275 90v-160q-115 -77 -287 -77q-47 0 -93 7t-105.5 31.5t-102.5 64.5t-74 111.5t-31 165.5zM446 860 v162h164v-162h-164z" />
-<glyph unicode="À" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM422 567h479l-239 625zM438 1718h152l117 -235h-99z" />
-<glyph unicode="Á" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM422 567h479l-239 625zM614 1483l117 235h152l-170 -235h-99z" />
-<glyph unicode="Â" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM422 567h479l-239 625zM455 1483l163 248h87l163 -248h-114l-92 145l-93 -145h-114z" />
-<glyph unicode="Ã" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM373 1485v117q53 66 115 72h19q51 0 97 -24l112 -60q46 -24 97 -24q10 0 21 1q61 6 114 71v-112q-53 -66 -114 -73q-12 -1 -24 -1q-49 0 -94 24l-112 59q-44 24 -92 24q-12 0 -24 -1q-62 -8 -115 -73z M422 567h479l-239 625z" />
-<glyph unicode="Ä" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM422 567h479l-239 625zM428 1483v164h164v-164h-164zM731 1483v164h164v-164h-164z" />
-<glyph unicode="Å" horiz-adv-x="1325" d="M43 0l545 1366h147l543 -1366h-158l-164 426h-589l-164 -426h-160zM422 567h479l-239 625zM496 1591q0 82 43 125t122.5 43t122.5 -43t43 -125t-43 -125t-122.5 -43t-122.5 43t-43 125zM569 1591.5q0 -53.5 21 -76t72 -22.5q49 0 70.5 22.5t21.5 76t-21.5 77t-70.5 23.5 q-51 0 -72 -23.5t-21 -77z" />
-<glyph unicode="Æ" horiz-adv-x="1939" d="M51 0l811 1366h955v-141h-691v-471h607v-144h-607v-469h691v-141h-838v428h-506l-252 -428h-170zM559 575h420v644h-45z" />
-<glyph unicode="Ç" horiz-adv-x="1286" d="M125 686q0 700 633 700q217 0 416 -77v-142q-187 80 -400 80q-260 0 -381 -134t-121 -425t121 -426t381 -135q209 0 400 80v-141q-172 -68 -357 -78l-125 -232h-155l163 232q-575 35 -575 698z" />
-<glyph unicode="È" horiz-adv-x="1159" d="M178 0v1366h862v-139h-714v-471h628v-144h-628v-473h714v-139h-862zM444 1718h152l117 -235h-99z" />
-<glyph unicode="É" horiz-adv-x="1159" d="M178 0v1366h862v-139h-714v-471h628v-144h-628v-473h714v-139h-862zM565 1483l115 235h151l-169 -235h-97z" />
-<glyph unicode="Ê" horiz-adv-x="1159" d="M178 0v1366h862v-139h-714v-471h628v-144h-628v-473h714v-139h-862zM412 1483l166 248h84l165 -248h-114l-92 145l-95 -145h-114z" />
-<glyph unicode="Ë" horiz-adv-x="1159" d="M178 0v1366h862v-139h-714v-471h628v-144h-628v-473h714v-139h-862zM395 1483v164h162v-164h-162zM698 1483v164h162v-164h-162z" />
-<glyph unicode="Ì" horiz-adv-x="501" d="M33 1718h151l117 -235h-98zM178 0v1366h148v-1366h-148z" />
-<glyph unicode="Í" horiz-adv-x="501" d="M178 0h148v1366h-148v-1366zM203 1483l116 235h152l-170 -235h-98z" />
-<glyph unicode="Î" horiz-adv-x="501" d="M45 1483l164 248h86l164 -248h-115l-92 145l-92 -145h-115zM178 0h148v1366h-148v-1366z" />
-<glyph unicode="Ï" horiz-adv-x="501" d="M18 1483v164h164v-164h-164zM178 0h148v1366h-148v-1366zM322 1483v164h163v-164h-163z" />
-<glyph unicode="Ð" horiz-adv-x="1468" d="M109 621v122h159v623h428q641 0 641 -682q0 -684 -641 -684h-428v621h-159zM416 139h270q262 0 383 131t121 414t-121 414t-383 131h-270v-486h350v-122h-350v-482z" />
-<glyph unicode="Ñ" horiz-adv-x="1490" d="M178 0v1366h168l815 -1151v1151h148v-1366h-166l-817 1151v-1151h-148zM455 1485v117q53 66 115 72h19q51 0 98 -24l113 -60q46 -24 97 -24q10 0 21 1q61 6 114 71v-112q-53 -66 -114 -73q-12 -1 -24 -1q-49 0 -94 24l-113 59q-45 24 -93 24q-12 0 -24 -1 q-62 -8 -115 -73z" />
-<glyph unicode="Ò" d="M125 684q0 700 633 700t633 -700t-633 -700t-633 700zM272 684q0 -291 117 -426t369 -135t368.5 135t116.5 426t-116.5 426t-368.5 135t-369 -135t-117 -426zM563 1718h150l116 -235h-96z" />
-<glyph unicode="Ó" d="M125 684q0 700 633 700t633 -700t-633 -700t-633 700zM272 684q0 -291 117 -426t369 -135t368.5 135t116.5 426t-116.5 426t-368.5 135t-369 -135t-117 -426zM686 1483l115 235h151l-170 -235h-96z" />
-<glyph unicode="Ô" d="M125 684q0 700 633 700t633 -700t-633 -700t-633 700zM272 684q0 -291 117 -426t369 -135t368.5 135t116.5 426t-116.5 426t-368.5 135t-369 -135t-117 -426zM551 1483l164 248h86l164 -248h-115l-92 145l-92 -145h-115z" />
-<glyph unicode="Õ" d="M125 684q0 700 633 700q635 0 635 -700t-635 -700q-633 0 -633 700zM272 684q0 -291 117 -426t369 -135q254 0 370.5 135t116.5 426t-116.5 426t-370.5 135q-252 0 -369 -135t-117 -426zM471 1485v117q53 66 113 72h19q51 0 99 -24l112 -60q47 -24 97 -24q10 0 21 1 q61 6 115 71v-112q-53 -66 -115 -73q-12 -1 -24 -1q-49 0 -94 24l-112 59q-46 24 -93 24q-12 0 -25 -1q-60 -8 -113 -73z" />
-<glyph unicode="Ö" d="M125 684q0 700 633 700t633 -700t-633 -700t-633 700zM272 684q0 -291 117 -426t369 -135t368.5 135t116.5 426t-116.5 426t-368.5 135t-369 -135t-117 -426zM524 1483v164h164v-164h-164zM827 1483v164h164v-164h-164z" />
-<glyph unicode="×" horiz-adv-x="985" d="M158 160l272 364l-272 365h131l207 -277l209 277h131l-273 -365l273 -364h-131l-209 276l-207 -276h-131z" />
-<glyph unicode="Ø" d="M102 0l158 188q-135 172 -135 496q0 700 633 700q256 0 411 -118l86 100h160l-160 -188q137 -174 138 -494q0 -702 -635 -702q-254 0 -410 118l-86 -100h-160zM272 684q0 -252 84 -383l721 856q-115 88 -319 88q-252 0 -369 -135t-117 -426zM440 209q115 -88 318 -88 q254 0 370.5 135t116.5 428q0 244 -84 381z" />
-<glyph unicode="Ù" horiz-adv-x="1476" d="M178 561v805h148v-813q0 -434 407.5 -434t407.5 434v813h147v-805q0 -281 -143.5 -429t-411.5 -148t-411.5 148.5t-143.5 428.5zM520 1718h152l116 -235h-96z" />
-<glyph unicode="Ú" horiz-adv-x="1476" d="M178 561v805h148v-813q0 -434 407.5 -434t407.5 434v813h147v-805q0 -281 -143.5 -429t-411.5 -148t-411.5 148.5t-143.5 428.5zM678 1483l117 235h151l-170 -235h-98z" />
-<glyph unicode="Û" horiz-adv-x="1476" d="M178 561v805h148v-813q0 -434 407.5 -434t407.5 434v813h147v-805q0 -281 -143.5 -429t-411.5 -148t-411.5 148.5t-143.5 428.5zM526 1483l164 248h86l164 -248h-115l-92 145l-92 -145h-115z" />
-<glyph unicode="Ü" horiz-adv-x="1476" d="M178 561v805h148v-813q0 -434 407.5 -434t407.5 434v813h147v-805q0 -281 -143.5 -429t-411.5 -148t-411.5 148.5t-143.5 428.5zM500 1483v164h164v-164h-164zM803 1483v164h164v-164h-164z" />
-<glyph unicode="Ý" horiz-adv-x="1224" d="M25 1366h178l405 -596l404 596h178l-508 -739v-627h-147v627zM539 1483l116 235h150l-170 -235h-96z" />
-<glyph unicode="Þ" horiz-adv-x="1167" d="M176 0v1366h148v-207h266q227 0 349 -109.5t122 -314.5t-122 -313.5t-349 -108.5h-266v-313h-148zM324 455h245q109 0 181.5 23.5t105.5 68.5t45 88t12 100.5t-12 100.5t-45 88t-105.5 68.5t-181.5 23.5h-245v-561z" />
-<glyph unicode="ß" horiz-adv-x="1255" d="M180 0v868q0 113 23.5 203t73 162t136.5 111.5t208 39.5q229 0 341.5 -112.5t112.5 -267.5q0 -51 -19.5 -100.5t-46 -84.5t-54 -61.5t-46.5 -40.5l-20 -12q10 -4 27.5 -12.5t61.5 -39t76.5 -66.5t60.5 -94.5t28 -123.5q0 -170 -150.5 -269.5t-384.5 -99.5v133 q162 0 274.5 61.5t112.5 188.5q0 129 -120.5 188.5t-266.5 59.5v129q145 0 232.5 62.5t87.5 181.5q0 123 -85 183t-222 60q-141 0 -217 -87t-76 -300v-860h-148z" />
-<glyph unicode="à" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t94 59t101 36t104.5 17.5t86 7t60.5 1l164 -10q0 137 -75.5 195.5t-223.5 58.5q-166 0 -299 -49v143q151 53 307 53q215 0 328 -108.5t113 -315.5v-309q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 313q0 -192 278 -192h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -203zM356 1391h150l117 -236h-97z" />
-<glyph unicode="á" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t94 59t101 36t104.5 17.5t86 7t60.5 1l164 -10q0 137 -75.5 195.5t-223.5 58.5q-166 0 -299 -49v143q151 53 307 53q215 0 328 -108.5t113 -315.5v-309q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 313q0 -192 278 -192h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -203zM467 1155l117 236h151l-170 -236h-98z" />
-<glyph unicode="â" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t94 59t101 36t104.5 17.5t86 7t60.5 1l164 -10q0 137 -75.5 195.5t-223.5 58.5q-166 0 -299 -49v143q151 53 307 53q215 0 328 -108.5t113 -315.5v-309q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 313q0 -192 278 -192h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -203zM362 1155l164 246h86l164 -246h-114l-93 143l-92 -143h-115z" />
-<glyph unicode="ã" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t93 59t101 36t105.5 17.5t85 7t59.5 1l166 -10q0 137 -75.5 195.5t-223.5 58.5q-150 0 -299 -47v141q141 53 307 53q215 0 328 -108.5t113 -315.5v-309q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 315q0 -194 278 -194h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -201zM268 1157v117q53 66 114 72q11 1 21 1q50 0 97 -25l114 -60q46 -24 97 -24q10 0 21 1q61 6 114 72v-113q-53 -66 -114 -73q-12 -1 -24 -1q-49 0 -94 24 l-114 60q-45 23 -94 23q-11 0 -23 -1q-61 -7 -115 -73z" />
-<glyph unicode="ä" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t94 59t101 36t104.5 17.5t86 7t60.5 1l164 -10q0 137 -75.5 195.5t-223.5 58.5q-166 0 -299 -49v143q151 53 307 53q215 0 328 -108.5t113 -315.5v-309q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 313q0 -192 278 -192h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -203zM336 1155v162h164v-162h-164zM639 1155v162h164v-162h-164z" />
-<glyph unicode="å" horiz-adv-x="1093" d="M115 313q0 70 25.5 125.5t62.5 90t94 59t101 36t104.5 17.5t86 7t60.5 1l164 -10q0 137 -75.5 195.5t-223.5 58.5q-166 0 -299 -49v143q151 53 307 53q215 0 328 -108.5t113 -315.5v-309q0 -98 -45.5 -167.5t-117 -100.5t-134 -43t-123.5 -12q-47 0 -96.5 6t-112 27.5 t-108.5 55t-78.5 96t-32.5 144.5zM262 313q0 -192 278 -192h3q272 0 272 186v197q-68 12 -182 12q-371 0 -371 -203zM393 1264q0 82 43 125t123 43t123 -43t43 -125t-43 -125t-123 -43t-123 43t-43 125zM467 1263.5q0 -53.5 20.5 -77t71.5 -23.5q49 0 70.5 23.5t21.5 77 t-21.5 76t-70.5 22.5q-51 0 -71.5 -22.5t-20.5 -76z" />
-<glyph unicode="æ" horiz-adv-x="1824" d="M111 313q0 70 25.5 125.5t62.5 90t93 59t101 36t105.5 17.5t85 7t59.5 1l166 -10q0 137 -76 195.5t-223 58.5q-166 0 -299 -49v143q152 53 305 53q268 0 377 -168q112 168 362 168q94 0 171 -29.5t122 -69.5t79 -104.5t48.5 -109.5t22.5 -108.5t8 -78.5v-44l-4 -72h-746 q14 -154 94 -223.5t240 -69.5q219 0 350 76v-152q-152 -71 -360 -71h-6q-248 0 -365 151q-103 -151 -367 -151h-3q-47 0 -96.5 6t-112 27.5t-108.5 55t-78.5 96t-32.5 144.5zM258 313q0 -192 278 -192h3q270 0 270 186v197q-66 12 -180 12q-371 0 -371 -203zM956 555h598 q0 35 -8 78t-33.5 110.5t-92 112.5t-165.5 45q-150 0 -219.5 -79t-79.5 -267z" />
-<glyph unicode="ç" horiz-adv-x="966" d="M119 512q0 156 43 265.5t116.5 163.5t150.5 76.5t169 22.5q154 0 303 -57v-141q-140 57 -287 57q-182 0 -265 -92t-83 -295q0 -205 83 -296t265 -91q154 0 287 57v-143q-115 -41 -225 -53l-127 -230h-156l164 228q-86 4 -157.5 30.5t-138 83t-104.5 162t-38 252.5z" />
-<glyph unicode="è" horiz-adv-x="1136" d="M129 498v7v8q0 527 449 527q94 0 170.5 -29.5t121.5 -69.5t79 -104.5t48.5 -109.5t22.5 -108.5t8 -78.5v-44l-4 -72h-745q14 -154 94 -223.5t237 -69.5q223 0 351 76v-152q-148 -71 -359 -71h-6q-90 0 -165 21.5t-147.5 74.5t-113.5 159.5t-41 258.5zM279 555h598 q0 35 -8.5 78t-34 110.5t-92 112.5t-164.5 45q-152 0 -220.5 -79t-78.5 -267zM395 1391h152l117 -236h-99z" />
-<glyph unicode="é" horiz-adv-x="1136" d="M129 498v7v8q0 527 449 527q94 0 170.5 -29.5t121.5 -69.5t79 -104.5t48.5 -109.5t22.5 -108.5t8 -78.5v-44l-4 -72h-745q14 -154 94 -223.5t237 -69.5q223 0 351 76v-152q-148 -71 -359 -71h-6q-90 0 -165 21.5t-147.5 74.5t-113.5 159.5t-41 258.5zM279 555h598 q0 35 -8.5 78t-34 110.5t-92 112.5t-164.5 45q-152 0 -220.5 -79t-78.5 -267zM506 1155l117 236h151l-170 -236h-98z" />
-<glyph unicode="ê" horiz-adv-x="1136" d="M129 498v7v8q0 527 449 527q94 0 170.5 -29.5t121.5 -69.5t79 -104.5t48.5 -109.5t22.5 -108.5t8 -78.5v-44l-4 -72h-745q14 -154 94 -223.5t237 -69.5q223 0 351 76v-152q-148 -71 -359 -71h-6q-90 0 -165 21.5t-147.5 74.5t-113.5 159.5t-41 258.5zM279 555h598 q0 35 -8.5 78t-34 110.5t-92 112.5t-164.5 45q-152 0 -220.5 -79t-78.5 -267zM371 1155l164 246h86l163 -246h-114l-92 143l-93 -143h-114z" />
-<glyph unicode="ë" horiz-adv-x="1136" d="M129 498v7v8q0 527 449 527q94 0 170.5 -29.5t121.5 -69.5t79 -104.5t48.5 -109.5t22.5 -108.5t8 -78.5v-44l-4 -72h-745q14 -154 94 -223.5t237 -69.5q223 0 351 76v-152q-148 -71 -359 -71h-6q-90 0 -165 21.5t-147.5 74.5t-113.5 159.5t-41 258.5zM279 555h598 q0 35 -8.5 78t-34 110.5t-92 112.5t-164.5 45q-152 0 -220.5 -79t-78.5 -267zM344 1155v162h164v-162h-164zM647 1155v162h164v-162h-164z" />
-<glyph unicode="ì" horiz-adv-x="552" d="M57 1391h152l117 -236h-99zM203 0v1022h147v-1022h-147z" />
-<glyph unicode="í" horiz-adv-x="552" d="M203 0h147v1022h-147v-1022zM227 1155l115 236h152l-170 -236h-97z" />
-<glyph unicode="î" horiz-adv-x="552" d="M68 1155l165 246h84l166 -246h-114l-93 143l-94 -143h-114zM203 0h147v1022h-147v-1022z" />
-<glyph unicode="ï" horiz-adv-x="552" d="M43 1155v162h162v-162h-162zM203 0h147v1022h-147v-1022zM346 1155v162h162v-162h-162z" />
-<glyph unicode="ð" horiz-adv-x="1193" d="M119 444q0 199 125 314.5t340 115.5q186 0 307 -90q-25 244 -174 365l-230 -129l-55 98l170 94q-86 33 -192 33q-41 0 -111 -8v137q66 10 127 10q184 0 328 -86l190 107l53 -98l-145 -84q197 -193 197 -596q0 -643 -465 -643q-123 0 -216.5 41t-145.5 109.5t-77.5 147 t-25.5 162.5zM266 446q0 -41 9.5 -84t38 -103t98.5 -98t172 -38q43 0 70.5 3t78.5 29.5t83 75t57.5 148.5t27.5 242q-110 116 -314 116h-3q-154 0 -236 -75.5t-82 -215.5z" />
-<glyph unicode="ñ" horiz-adv-x="1189" d="M168 0v940q201 100 432 100q193 0 299.5 -98t106.5 -276v-666h-148v666q0 43 -9 76.5t-35 71.5t-84 58.5t-146 20.5q-129 0 -269 -41v-852h-147zM299 1157v117q53 66 113 72q11 1 21 1q49 0 97 -25l112 -60q47 -24 97 -24q10 0 21 1q61 6 114 72v-113q-53 -66 -114 -73 q-12 -1 -24 -1q-49 0 -94 24l-112 60q-46 23 -94 23q-12 0 -24 -1q-60 -7 -113 -73z" />
-<glyph unicode="ò" horiz-adv-x="1155" d="M100 512q0 156 43 265.5t117 163.5t151.5 76.5t170 22.5t169 -22.5t150.5 -76.5t117 -163.5t43 -265.5t-43 -265.5t-117 -163.5t-150.5 -76.5t-169 -22.5t-170 22.5t-151.5 76.5t-117 163.5t-43 265.5zM248 512q0 -207 80 -300t254 -93t252.5 94t78.5 299t-78.5 299 t-252.5 94t-254 -93t-80 -300zM362 1391h152l117 -236h-99z" />
-<glyph unicode="ó" horiz-adv-x="1155" d="M100 512q0 156 43 265.5t117 163.5t151.5 76.5t170 22.5t169 -22.5t150.5 -76.5t117 -163.5t43 -265.5t-43 -265.5t-117 -163.5t-150.5 -76.5t-169 -22.5t-170 22.5t-151.5 76.5t-117 163.5t-43 265.5zM248 512q0 -207 80 -300t254 -93t252.5 94t78.5 299t-78.5 299 t-252.5 94t-254 -93t-80 -300zM530 1155l117 236h152l-170 -236h-99z" />
-<glyph unicode="ô" horiz-adv-x="1155" d="M100 512q0 156 43 265.5t117 163.5t151.5 76.5t170 22.5t169 -22.5t150.5 -76.5t117 -163.5t43 -265.5t-43 -265.5t-117 -163.5t-150.5 -76.5t-169 -22.5t-170 22.5t-151.5 76.5t-117 163.5t-43 265.5zM248 512q0 -207 80 -300t254 -93t252.5 94t78.5 299t-78.5 299 t-252.5 94t-254 -93t-80 -300zM373 1155l166 246h84l165 -246h-114l-92 143l-95 -143h-114z" />
-<glyph unicode="õ" horiz-adv-x="1155" d="M100 512q0 156 43 265.5t117 163.5t150.5 76.5t169 22.5t170 -22.5t151.5 -76.5t117 -163.5t43 -265.5t-43 -265.5t-117 -163.5t-151.5 -76.5t-170 -22.5t-169 22.5t-150.5 76.5t-117 163.5t-43 265.5zM248 512q0 -207 79 -300t253 -93t253.5 93t79.5 300t-79.5 300 t-253.5 93t-253 -93t-79 -300zM301 1159v115q53 66 113 73q12 1 23 1q49 0 95 -24l112 -60q46 -25 95 -25q11 0 23 1q61 7 115 73v-115q-53 -66 -115 -73q-12 -1 -23 -1q-50 0 -95 24l-112 60q-47 24 -97 24q-10 0 -21 -1q-60 -6 -113 -72z" />
-<glyph unicode="ö" horiz-adv-x="1155" d="M100 512q0 156 43 265.5t117 163.5t151.5 76.5t170 22.5t169 -22.5t150.5 -76.5t117 -163.5t43 -265.5t-43 -265.5t-117 -163.5t-150.5 -76.5t-169 -22.5t-170 22.5t-151.5 76.5t-117 163.5t-43 265.5zM248 512q0 -207 80 -300t254 -93t252.5 94t78.5 299t-78.5 299 t-252.5 94t-254 -93t-80 -300zM348 1155v162h162v-162h-162zM651 1155v162h162v-162h-162z" />
-<glyph unicode="÷" horiz-adv-x="1286" d="M227 465h832v123h-832v-123zM561 152v163h164v-163h-164zM561 735v164h164v-164h-164z" />
-<glyph unicode="ø" horiz-adv-x="1175" d="M76 0l127 152q-96 133 -97 360q0 156 43.5 265.5t117 163.5t151.5 76.5t170 22.5q180 0 297 -82l53 64h160l-125 -150q94 -131 94 -360q0 -156 -43 -265.5t-116.5 -163.5t-150.5 -76.5t-169 -22.5q-182 0 -297 79l-55 -63h-160zM254 512q0 -158 45 -246l496 586 q-78 53 -207 53q-174 0 -254 -93t-80 -300zM381 170q76 -51 207 -51q174 0 253 93t79 300q0 156 -46 246z" />
-<glyph unicode="ù" horiz-adv-x="1202" d="M168 430v592h147v-600q0 -156 68 -228.5t213 -72.5q143 0 212 72.5t69 228.5v600h147v-592q0 -213 -111.5 -329.5t-316.5 -116.5t-316.5 116.5t-111.5 329.5zM383 1391h152l116 -236h-98z" />
-<glyph unicode="ú" horiz-adv-x="1202" d="M168 430v592h147v-600q0 -156 68 -228.5t213 -72.5q143 0 212 72.5t69 228.5v600h147v-592q0 -213 -111.5 -329.5t-316.5 -116.5t-316.5 116.5t-111.5 329.5zM541 1155l116 236h150l-170 -236h-96z" />
-<glyph unicode="û" horiz-adv-x="1202" d="M168 430v592h147v-600q0 -156 68 -228.5t213 -72.5q143 0 212 72.5t69 228.5v600h147v-592q0 -213 -111.5 -329.5t-316.5 -116.5t-316.5 116.5t-111.5 329.5zM389 1155l164 246h84l166 -246h-115l-92 143l-92 -143h-115z" />
-<glyph unicode="ü" horiz-adv-x="1202" d="M168 430v592h147v-600q0 -156 68 -228.5t213 -72.5q143 0 212 72.5t69 228.5v600h147v-592q0 -213 -111.5 -329.5t-316.5 -116.5t-316.5 116.5t-111.5 329.5zM362 1155v162h164v-162h-164zM666 1155v162h163v-162h-163z" />
-<glyph unicode="ý" horiz-adv-x="1028" d="M63 1022h158l293 -809l293 809h158l-418 -1122q-109 -295 -320 -295q-51 0 -98 18v135q41 -18 88 -18q63 0 118.5 68.5t80.5 138.5l24 69zM449 1155l116 236h152l-170 -236h-98z" />
-<glyph unicode="þ" horiz-adv-x="1136" d="M170 -377v1776h147v-414q123 41 252 41q70 0 131.5 -12.5t125 -47t109.5 -90t74.5 -149.5t28.5 -217q0 -152 -42 -258.5t-114.5 -159.5t-148.5 -74.5t-164 -21.5q-129 0 -252 41v-414h-147zM317 184q117 -51 252 -51q170 0 246 89t76 288t-76 289t-246 90 q-135 0 -252 -51v-654z" />
-<glyph unicode="ÿ" horiz-adv-x="1028" d="M63 1022h158l293 -809l293 809h158l-418 -1122q-109 -295 -320 -295q-51 0 -98 18v135q41 -18 88 -18q63 0 118.5 68.5t80.5 138.5l24 69zM279 1155v162h161v-162h-161zM582 1155v162h161v-162h-161z" />
-<glyph unicode="Œ" horiz-adv-x="2029" d="M123 684q0 700 633 700q160 0 274 -45v27h864v-139h-716v-471h628v-144h-628v-473h716v-139h-864v27q-117 -43 -274 -43q-633 0 -633 700zM270 684q0 -291 117 -426t369 -135q170 0 274 59v1004q-109 59 -274 59q-252 0 -369 -135t-117 -426z" />
-<glyph unicode="œ" horiz-adv-x="1921" d="M102 512q0 156 43 265.5t117 163.5t150.5 76.5t169.5 22.5q279 0 397 -186q113 186 377 186q94 0 170 -29.5t122 -69.5t78.5 -104.5t47 -109.5t22.5 -108.5t8 -78.5v-44l-2 -72h-745q14 -154 94 -223.5t238 -69.5q219 0 350 76v-152q-148 -71 -359 -71h-6 q-276 0 -393 190q-119 -190 -399 -190q-92 0 -169 22.5t-151 76.5t-117 163.5t-43 265.5zM250 512q0 -207 79 -300t253 -93t253.5 93t79.5 300t-79.5 300t-253.5 93t-253 -93t-79 -300zM1055 555h600q0 33 -8.5 77t-35 111.5t-93 112.5t-162.5 45q-152 0 -221.5 -79 t-79.5 -267z" />
-<glyph unicode="Ÿ" horiz-adv-x="1224" d="M25 1366h178l405 -596l404 596h178l-508 -739v-627h-147v627zM375 1483v164h164v-164h-164zM678 1483v164h164v-164h-164z" />
-<glyph unicode="ˆ" horiz-adv-x="823" d="M207 1155l166 246h84l164 -246h-115l-92 143l-92 -143h-115z" />
-<glyph unicode="˜" horiz-adv-x="1038" d="M215 1157v117q53 66 113 72q11 1 21 1q49 0 97 -25l112 -60q47 -24 97 -24q10 0 21 1q61 6 115 72v-113q-53 -66 -115 -73q-12 -1 -23 -1q-50 0 -95 24l-112 60q-46 23 -94 23q-12 0 -24 -1q-60 -7 -113 -73z" />
-<glyph unicode=" " horiz-adv-x="879" />
-<glyph unicode=" " horiz-adv-x="1759" />
-<glyph unicode=" " horiz-adv-x="879" />
-<glyph unicode=" " horiz-adv-x="1759" />
-<glyph unicode=" " horiz-adv-x="586" />
-<glyph unicode=" " horiz-adv-x="439" />
-<glyph unicode=" " horiz-adv-x="293" />
-<glyph unicode=" " horiz-adv-x="293" />
-<glyph unicode=" " horiz-adv-x="219" />
-<glyph unicode=" " horiz-adv-x="351" />
-<glyph unicode=" " horiz-adv-x="97" />
-<glyph unicode="‐" horiz-adv-x="950" d="M162 539v143h624v-143h-624z" />
-<glyph unicode="‑" horiz-adv-x="950" d="M162 539v143h624v-143h-624z" />
-<glyph unicode="‒" horiz-adv-x="950" d="M162 539v143h624v-143h-624z" />
-<glyph unicode="–" horiz-adv-x="1146" d="M141 528v127h860v-127h-860z" />
-<glyph unicode="—" horiz-adv-x="1824" d="M162 528v136h1501v-136h-1501z" />
-<glyph unicode="‘" horiz-adv-x="522" d="M96 1384h168l152 -401h-148z" />
-<glyph unicode="’" horiz-adv-x="522" d="M96 983l152 401h168l-172 -401h-148z" />
-<glyph unicode="‚" horiz-adv-x="614" d="M147 -229l152 401h166l-170 -401h-148z" />
-<glyph unicode="“" horiz-adv-x="800" d="M96 1384h168l152 -401h-148zM375 1384h168l151 -401h-147z" />
-<glyph unicode="”" horiz-adv-x="792" d="M96 983l152 401h168l-172 -401h-148zM375 983l151 401h168l-172 -401h-147z" />
-<glyph unicode="„" horiz-adv-x="892" d="M147 -229l152 401h166l-170 -401h-148zM426 -229l152 401h167l-172 -401h-147z" />
-<glyph unicode="•" horiz-adv-x="849" d="M184 684q0 98 70 167t168 69q96 0 165.5 -69t69.5 -167t-69.5 -168t-165.5 -70q-98 0 -168 70t-70 168z" />
-<glyph unicode="…" horiz-adv-x="1576" d="M219 0v164h164v-164h-164zM711 0v164h161v-164h-161zM1200 0v164h162v-164h-162z" />
-<glyph unicode=" " horiz-adv-x="351" />
-<glyph unicode="‹" horiz-adv-x="710" d="M63 522l347 389h198l-364 -389l364 -391h-198z" />
-<glyph unicode="›" horiz-adv-x="776" d="M137 131l363 391l-363 389h197l348 -389l-348 -391h-197z" />
-<glyph unicode=" " horiz-adv-x="439" />
-<glyph unicode="€" horiz-adv-x="1390" d="M102 467v123h136q-4 61 -4 94q-1 3 -1 6q0 33 5 88h-136v123h150q90 483 614 483q215 0 416 -79v-142q-194 82 -399 82q-207 0 -325 -84t-157 -260h658v-123h-674q-4 -61 -4 -94q0 -37 4 -94h674v-123h-658q39 -176 157 -260t325 -84q209 0 399 80v-142 q-200 -77 -411 -77h-5q-524 0 -614 483h-150z" />
-<glyph unicode="™" horiz-adv-x="1550" d="M100 1294v72h557v-72h-241v-610h-74v610h-242zM762 684v682h80l235 -346l236 346h80v-682h-74v561l-242 -348l-241 348v-561h-74z" />
-<glyph unicode="" horiz-adv-x="1020" d="M0 0v1020h1020v-1020h-1020z" />
-<glyph unicode="fi" horiz-adv-x="1202" d="M84 887v137h176v53q0 106 28.5 182t63.5 112t89.5 56.5t82 22.5t66.5 2q53 0 100 -10v-133q-41 8 -74 8q-39 0 -59 -2t-54 -14.5t-52.5 -35t-33 -70.5t-14.5 -116v-55h287v-137h-287v-887h-143v887h-176zM885 1204v162h164v-162h-164zM893 0h147v1022h-147v-1022z" />
-<glyph unicode="fl" horiz-adv-x="1220" d="M84 887v137h176v53q0 106 28.5 182t63.5 112t89.5 56.5t82 22.5t66.5 2q53 0 100 -10v-133q-41 8 -74 8q-39 0 -59 -2t-54 -14.5t-52.5 -35t-33 -70.5t-14.5 -116v-55h287v-137h-287v-887h-143v887h-176zM893 0h147v1411h-147v-1411z" />
-<glyph unicode="ffi" horiz-adv-x="1890" d="M82 887v135h176v55q0 106 27.5 182t62.5 112t89.5 56.5t83 22.5t67.5 2q55 0 98 -12v-131q-41 8 -74 8q-49 0 -74.5 -4t-61.5 -24.5t-53 -73t-17 -136.5v-57h280v-135h-280v-887h-148v887h-176zM757 887v135h176v55q0 106 27.5 182t62.5 112t89.5 56.5t83 22.5t67.5 2 q55 0 98 -12v-131q-41 8 -74 8q-49 0 -74.5 -4t-61.5 -24.5t-53 -73t-17 -136.5v-57h280v-135h-280v-887h-148v887h-176zM1543 1204v162h163v-162h-163zM1551 0h147v1022h-147v-1022z" />
-<glyph unicode="ffl" horiz-adv-x="1902" d="M82 887v135h176v55q0 106 27.5 182t62.5 112t89.5 56.5t83 22.5t67.5 2q55 0 98 -12v-131q-41 8 -74 8q-49 0 -74.5 -4t-61.5 -24.5t-53 -73t-17 -136.5v-57h280v-135h-280v-887h-148v887h-176zM757 887v135h176v55q0 106 27.5 182t62.5 112t89.5 56.5t83 22.5t67.5 2 q55 0 98 -12v-131q-41 8 -74 8q-49 0 -74.5 -4t-61.5 -24.5t-53 -73t-17 -136.5v-57h280v-135h-280v-887h-148v887h-176zM1557 0v1411h147v-1411h-147z" />
-</font>
-</defs></svg>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="96"
- height="48"
- id="svg2"
- version="1.1"
- inkscape:version="0.48.4 r9939"
- sodipodi:docname="arrows.svg"
- inkscape:export-filename="/media/data/projects/motioneye/static/img/settings.png"
- inkscape:export-xdpi="90"
- inkscape:export-ydpi="90">
- <defs
- id="defs4" />
- <sodipodi:namedview
- id="base"
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0.0"
- inkscape:pageshadow="2"
- inkscape:zoom="5.0625"
- inkscape:cx="43.687152"
- inkscape:cy="14.670355"
- inkscape:document-units="px"
- inkscape:current-layer="layer1"
- showgrid="false"
- inkscape:window-width="1920"
- inkscape:window-height="1030"
- inkscape:window-x="0"
- inkscape:window-y="25"
- inkscape:window-maximized="1"
- inkscape:snap-bbox="true"
- inkscape:bbox-nodes="true"
- inkscape:bbox-paths="true"
- inkscape:snap-bbox-edge-midpoints="true"
- inkscape:snap-bbox-midpoints="true"
- inkscape:object-paths="true"
- inkscape:object-nodes="true"
- inkscape:snap-smooth-nodes="true"
- inkscape:snap-midpoints="true"
- inkscape:snap-object-midpoints="true"
- inkscape:snap-center="true"
- inkscape:snap-page="true"
- inkscape:snap-intersection-paths="true"
- showguides="true"
- inkscape:guide-bbox="true" />
- <metadata
- id="metadata7">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title />
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:label="Layer 1"
- inkscape:groupmode="layer"
- id="layer1"
- transform="translate(0,-1004.3622)">
- <path
- style="fill:none;stroke:#3498db;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
- d="m 30,1012.3622 -17,17 17,15"
- id="path3755"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="ccc" />
- <path
- sodipodi:nodetypes="ccc"
- inkscape:connector-curvature="0"
- id="path3757"
- d="m 66,1012.3622 17,17 -17,15"
- style="fill:none;stroke:#3498db;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
- </g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="20"
- height="16"
- id="svg2"
- version="1.1"
- inkscape:version="0.48.4 r9939"
- sodipodi:docname="combo-box-arrow.svg"
- inkscape:export-filename="/media/data/projects/motioneye/static/img/combo-box-arrow.png"
- inkscape:export-xdpi="90"
- inkscape:export-ydpi="90">
- <defs
- id="defs4" />
- <sodipodi:namedview
- id="base"
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0.0"
- inkscape:pageshadow="2"
- inkscape:zoom="1"
- inkscape:cx="7.1473113"
- inkscape:cy="7.817561"
- inkscape:document-units="px"
- inkscape:current-layer="layer1"
- showgrid="false"
- inkscape:window-width="1920"
- inkscape:window-height="1027"
- inkscape:window-x="0"
- inkscape:window-y="25"
- inkscape:window-maximized="1"
- inkscape:snap-bbox="true"
- inkscape:bbox-nodes="true"
- inkscape:bbox-paths="true"
- inkscape:snap-bbox-edge-midpoints="true"
- inkscape:snap-bbox-midpoints="true"
- inkscape:object-paths="true"
- inkscape:object-nodes="true"
- inkscape:snap-smooth-nodes="true"
- inkscape:snap-midpoints="true"
- inkscape:snap-object-midpoints="true"
- inkscape:snap-center="true"
- inkscape:snap-page="true"
- inkscape:snap-intersection-paths="true" />
- <metadata
- id="metadata7">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:label="Layer 1"
- inkscape:groupmode="layer"
- id="layer1"
- transform="translate(0,-1036.3622)">
- <path
- style="fill:#3498db;fill-opacity:1;stroke:#3498db;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
- d="m 1,1040.8622 14,0 -7,8 z"
- id="path3809"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cccc" />
- </g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- id="svg2"
- xml:space="preserve"
- viewBox="0 0 64 64"
- version="1.1"
- inkscape:version="0.48.5 r10040"
- width="100%"
- height="100%"
- sodipodi:docname="logout.svg"><defs
- id="defs8" /><sodipodi:namedview
- pagecolor="#666666"
- bordercolor="#666666"
- borderopacity="1"
- objecttolerance="10"
- gridtolerance="10"
- guidetolerance="10"
- inkscape:pageopacity="0"
- inkscape:pageshadow="2"
- inkscape:window-width="1077"
- inkscape:window-height="782"
- id="namedview6"
- showgrid="false"
- inkscape:zoom="5.1943359"
- inkscape:cx="24.357877"
- inkscape:cy="26.948648"
- inkscape:window-x="258"
- inkscape:window-y="33"
- inkscape:window-maximized="0"
- inkscape:current-layer="svg2" /><metadata
- id="metadata8"><rdf:RDF><cc:Work
- rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><path
- sodipodi:nodetypes="ssccccccsccccc"
- style="fill:#3498db;fill-opacity:1"
- id="path3"
- d="m 21.267338,42.732446 c 3.741665,3.741354 9.807034,3.741354 13.548699,0 2.776803,-2.77657 3.490747,-6.8354 2.144089,-10.273324 l 5.763494,-5.763661 c 1.49694,-1.496847 1.496489,-3.922567 0,-5.419481 -1.496034,-1.496525 -3.922182,-1.496525 -5.419118,0 l -5.763946,5.764034 c -3.437493,-1.346908 -7.495965,-0.633431 -10.27367,2.144832 -3.740312,3.740676 -3.740765,9.806754 4.52e-4,13.5476 z m 6.435428,-6.434662 c 0.898073,0.897093 0.898073,2.353493 0,3.2516 -0.897621,0.897939 -2.35394,0.897939 -3.251561,0 -0.89717,-0.898107 -0.89717,-2.354507 0,-3.2516 0.897621,-0.898107 2.353489,-0.898107 3.251561,0 z"
- inkscape:connector-curvature="0" /><path
- sodipodi:type="arc"
- style="fill:none;stroke:#3498db;stroke-width:1.80392128;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
- id="path4097"
- sodipodi:cx="16"
- sodipodi:cy="16.5"
- sodipodi:rx="14"
- sodipodi:ry="12.5"
- d="m 30,16.5 a 14,12.5 0 1 1 -28,0 14,12.5 0 1 1 28,0 z"
- transform="matrix(1.5714286,0,0,1.7600001,6.8571426,2.9599978)" /></svg>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- id="svg2"
- version="1.1"
- inkscape:version="0.48.4 r9939"
- width="64"
- height="64"
- xml:space="preserve"
- sodipodi:docname="motioneye-icon.svg"
- inkscape:export-filename="/home/ccrisan/projects/motioneye/static/img/motioneye-logo.png"
- inkscape:export-xdpi="960"
- inkscape:export-ydpi="960"><metadata
- id="metadata8"><rdf:RDF><cc:Work
- rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
- id="defs6" /><sodipodi:namedview
- pagecolor="#969696"
- bordercolor="#666666"
- borderopacity="1"
- objecttolerance="10"
- gridtolerance="10"
- guidetolerance="10"
- inkscape:pageopacity="0"
- inkscape:pageshadow="2"
- inkscape:window-width="1920"
- inkscape:window-height="1030"
- id="namedview4"
- showgrid="false"
- inkscape:zoom="3.2304687"
- inkscape:cx="-17.830274"
- inkscape:cy="37.141088"
- inkscape:window-x="0"
- inkscape:window-y="25"
- inkscape:window-maximized="1"
- inkscape:current-layer="g10"
- showguides="true"
- inkscape:guide-bbox="true" /><g
- id="g10"
- inkscape:groupmode="layer"
- inkscape:label="ink_ext_XXXXXX"
- transform="matrix(1.25,0,0,-1.25,0,64)"><path
- style="fill:#515151;fill-opacity:1;stroke:none"
- d="m 32,6 c -5.264933,0 -10.15584,1.684955 -14.25,4.375 4.426748,0.474546 8.794165,1.616509 13.75,4.0625 5.014819,-2.5549 10.152222,-3.621792 14.78125,-4.15625 C 42.179879,7.5768125 37.280255,6 32,6 z m 17,5.59375 c -5.21085,0.03453 -12.330126,1.666208 -17.5,4.03125 -5.435679,-1.964064 -11.012887,-4.147356 -17.3125,-4 -0.419974,0.0099 -0.823167,0.03114 -1.25,0.0625 L 8,12.09375 l 4.6875,1.5625 c 6.429941,2.095118 12.084993,4.395085 17.0625,13.03125 1.154473,0 3.425649,5e-4 4.5,5e-4 4.957862,-8.587269 10.904708,-10.983331 17.03125,-13.03175 l 4.625,-1.5 -4.8125,-0.46875 C 50.437916,11.625136 49.744407,11.588818 49,11.59375 z M 12.65625,14.6875 C 8.5126665,19.293305 6,25.316864 6,32 6,46.359404 17.640596,58 32,58 46.359404,58 58,46.359404 58,32 58,25.341351 55.491547,19.287285 51.375,14.6875 49.406504,15.410957 47.442694,16.253379 45.5,17.34375 49.260018,18.43176 52,21.91463 52,26 c 0,4.945036 -4.027245,9 -9,9 -4.217962,0 -7.765819,-2.914031 -8.75,-6.8125 -1.243768,0 -3.31109,0 -4.5,0 C 28.774204,32.087724 25.219217,35 21,35 c -4.972755,0 -9.03125,-4.054964 -9.03125,-9 0,-4.133444 2.851912,-7.644 6.6875,-8.6875 -1.945667,-1.058808 -3.952016,-1.881003 -6,-2.625 z M 21,23 c -1.656854,0 -3,1.343146 -3,3 0,1.656854 1.343146,3 3,3 1.656854,0 3,-1.343146 3,-3 0,-1.656854 -1.343146,-3 -3,-3 z m 22,0 c -1.656854,0 -3,1.343146 -3,3 0,1.656854 1.343146,3 3,3 1.656854,0 3,-1.343146 3,-3 0,-1.656854 -1.343146,-3 -3,-3 z m -11,8 c 0.618234,2.299881 1.278866,4.581981 4,6 l -4,6 -4,-6 c 2.383634,-1.6888 3.483037,-3.758112 4,-6 z"
- transform="matrix(0.8,0,0,-0.8,0,51.2)"
- id="path3938"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="scccssccccccccccscsssccssccssccssssssssssccccc" /></g></svg>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 64 64" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata id="metadata8"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><g id="g10" transform="matrix(1.25,0,0,-1.25,0,64)"><path id="path3938" d="m32,6c-5.2649,0-10.156,1.685-14.25,4.375,4.4267,0.47455,8.7942,1.6165,13.75,4.0625,5.0148-2.5549,10.152-3.6218,14.781-4.1562-4.101-2.7052-9.001-4.282-14.281-4.282zm17,5.5938c-5.2108,0.03453-12.33,1.6662-17.5,4.0312-5.4357-1.9641-11.013-4.1474-17.312-4-0.41997,0.0099-0.82317,0.03114-1.25,0.0625l-4.938,0.406,4.6875,1.5625c6.4299,2.0951,12.085,4.3951,17.062,13.031,1.1545,0,3.4256,0.0005,4.5,0.0005,4.9579-8.5873,10.905-10.983,17.031-13.032l4.625-1.5-4.8125-0.46875c-0.656-0.062-1.35-0.098-2.094-0.093zm-36.344,3.094c-4.1433,4.605-6.656,10.629-6.656,17.312,0,14.359,11.641,26,26,26s26-11.641,26-26c0-6.659-2.508-12.713-6.625-17.312-1.968,0.723-3.932,1.565-5.875,2.656,3.76,1.088,6.5,4.571,6.5,8.656,0,4.945-4.0272,9-9,9-4.218,0-7.7658-2.914-8.75-6.8125h-4.5c-0.976,3.9-4.531,6.812-8.75,6.812-4.9728,0-9.0312-4.055-9.0312-9,0-4.1334,2.8519-7.644,6.6875-8.6875-1.9457-1.0588-3.952-1.881-6-2.625zm8.344,8.312c-1.6569,0-3,1.3431-3,3s1.3431,3,3,3,3-1.3431,3-3-1.3431-3-3-3zm22,0c-1.6569,0-3,1.3431-3,3s1.3431,3,3,3,3-1.3431,3-3-1.3431-3-3-3zm-11,8c0.61823,2.2999,1.2789,4.582,4,6l-4,6-4-6c2.3836-1.6888,3.483-3.7581,4-6z" transform="matrix(0.8,0,0,-0.8,0,51.2)" fill="#FFF"/></g></svg>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- id="svg2"
- version="1.1"
- inkscape:version="0.48.4 r9939"
- width="160"
- height="160"
- xml:space="preserve"
- sodipodi:docname="no-camera.svg"><metadata
- id="metadata8"><rdf:RDF><cc:Work
- rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
- id="defs6" /><sodipodi:namedview
- pagecolor="#969696"
- bordercolor="#666666"
- borderopacity="1"
- objecttolerance="10"
- gridtolerance="10"
- guidetolerance="10"
- inkscape:pageopacity="0"
- inkscape:pageshadow="2"
- inkscape:window-width="1920"
- inkscape:window-height="1027"
- id="namedview4"
- showgrid="false"
- inkscape:zoom="1"
- inkscape:cx="194.39325"
- inkscape:cy="58.632885"
- inkscape:window-x="0"
- inkscape:window-y="25"
- inkscape:window-maximized="1"
- inkscape:current-layer="g10" /><g
- id="g10"
- inkscape:groupmode="layer"
- inkscape:label="ink_ext_XXXXXX"
- transform="matrix(1.25,0,0,-1.25,0,160)"><g
- id="g3916"
- transform="matrix(0.23474178,0,0,0.23474178,-6.596479,-6.504096)"><path
- id="path28"
- transform="matrix(0.8,0,0,-0.8,0,599)"
- d="m 370.46875,230.75 c -25.42532,0 -48.59369,9.59353 -66.125,25.34375 l 57.96875,57.96875 c 3.96533,-0.60105 8.02296,-0.9375 12.15625,-0.9375 44.43125,0 80.4375,36.00625 80.4375,80.4375 0,4.1334 -0.33645,8.19036 -0.9375,12.15625 L 554.5,506.25 c 14.98807,-4.91141 25.8125,-18.99541 25.8125,-35.625 l 0,-162.5 c 0,-20.71 -16.79,-37.5 -37.5,-37.5 l -20.875,0 0,-7.1875 c 0,-8.9975 -7.31375,-16.28125 -16.3125,-16.28125 -8.99625,0 -16.28125,7.28375 -16.28125,16.28125 l 0,7.1875 -39.4375,0 C 431.86011,246.414 402.99373,230.75 370.46875,230.75 z m -13.59375,13 27.5,0 c 8.63,0 15.625,6.99625 15.625,15.625 0,4.41759 -1.83365,8.40792 -4.78125,11.25 l -49.1875,0 c -2.94775,-2.84208 -4.78125,-6.83241 -4.78125,-11.25 0,-8.62875 6.99625,-15.625 15.625,-15.625 z"
- style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
- inkscape:connector-curvature="0" /><path
- id="path3901"
- d="m 164.25,382.5 c -16.569,0 -30,-13.432 -30,-30 l 0,-130 c 0,-16.568 13.431,-30 30,-30 l 203.075,0 -35.8,35.775 c -9.41466,-5.39918 -20.31889,-8.5 -31.95,-8.5 -35.543,0 -64.35,28.831 -64.35,64.375 0,11.62338 3.11098,22.51462 8.5,31.925 L 177.3,382.5 l -13.05,0 z"
- style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
- inkscape:connector-curvature="0" /><path
- id="path36"
- transform="matrix(0.8,0,0,-0.8,0,599)"
- d="m 374.0625,54.96875 c -176.1485,0 -319.5,143.28875 -319.5,319.4375 0,176.15678 143.35153,319.5 319.5,319.5 176.15077,0 319.5,-143.34335 319.5,-319.5 0,-176.14864 -143.3492,-319.4375 -319.5,-319.4375 z m 0,50 c 149.1292,0 269.5,120.31114 269.5,269.4375 0,64.41395 -22.47282,123.45239 -60,169.75 L 204.34375,164.9375 c 46.29727,-37.51599 105.31199,-59.96875 169.71875,-59.96875 z m -209.5,99.71875 379.21875,379.21875 c -46.2979,37.52569 -105.30826,60 -169.71875,60 -149.12653,0 -269.5,-120.36428 -269.5,-269.5 0,-64.4098 22.47246,-123.42872 60,-169.71875 z"
- style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
- inkscape:connector-curvature="0" /></g></g></svg>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- id="svg2"
- version="1.1"
- inkscape:version="0.48.4 r9939"
- width="64"
- height="64"
- xml:space="preserve"
- sodipodi:docname="no-preview.svg"
- inkscape:export-filename="/home/ccrisan/projects/motioneye/static/img/motioneye-logo.png"
- inkscape:export-xdpi="960"
- inkscape:export-ydpi="960"><metadata
- id="metadata8"><rdf:RDF><cc:Work
- rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
- id="defs6" /><sodipodi:namedview
- pagecolor="#969696"
- bordercolor="#666666"
- borderopacity="1"
- objecttolerance="10"
- gridtolerance="10"
- guidetolerance="10"
- inkscape:pageopacity="1"
- inkscape:pageshadow="2"
- inkscape:window-width="1920"
- inkscape:window-height="1030"
- id="namedview4"
- showgrid="false"
- inkscape:zoom="5.0625"
- inkscape:cx="53.237951"
- inkscape:cy="28.106834"
- inkscape:window-x="0"
- inkscape:window-y="25"
- inkscape:window-maximized="1"
- inkscape:current-layer="g10"
- showguides="true"
- inkscape:guide-bbox="true" /><g
- id="g10"
- inkscape:groupmode="layer"
- inkscape:label="ink_ext_XXXXXX"
- transform="matrix(1.25,0,0,-1.25,0,64)"><g
- id="g3757"
- style="opacity:0.7"><rect
- transform="scale(1,-1)"
- y="-42.400002"
- x="4"
- height="34.400002"
- width="43.200001"
- id="rect3751"
- style="fill:none;stroke:#ffffff;stroke-width:1.60000001999999997;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /><path
- sodipodi:nodetypes="cccccc"
- transform="matrix(0.8,0,0,-0.8,0,51.2)"
- inkscape:connector-curvature="0"
- id="path3753"
- d="m 11.5,46.5 11,-14 6,6 11,-14 13,22 z"
- style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /><path
- transform="matrix(1.0251562,0,0,-1.0251562,-4.199999,56.75)"
- d="m 20.484682,24.142662 a 2.3411067,2.3411067 0 1 1 -4.682213,0 2.3411067,2.3411067 0 1 1 4.682213,0 z"
- sodipodi:ry="2.3411067"
- sodipodi:rx="2.3411067"
- sodipodi:cy="24.142662"
- sodipodi:cx="18.143576"
- id="path3755"
- style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.56073773000000005;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
- sodipodi:type="arc" /></g></g></svg>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="96"
- height="48"
- id="svg2"
- version="1.1"
- inkscape:version="0.48.4 r9939"
- sodipodi:docname="settings.svg"
- inkscape:export-filename="/media/data/projects/motioneye/static/img/settings.png"
- inkscape:export-xdpi="90"
- inkscape:export-ydpi="90">
- <defs
- id="defs4" />
- <sodipodi:namedview
- id="base"
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0.0"
- inkscape:pageshadow="2"
- inkscape:zoom="17.085938"
- inkscape:cx="93.239037"
- inkscape:cy="25.695745"
- inkscape:document-units="px"
- inkscape:current-layer="layer1"
- showgrid="false"
- inkscape:window-width="1920"
- inkscape:window-height="1027"
- inkscape:window-x="0"
- inkscape:window-y="25"
- inkscape:window-maximized="1"
- inkscape:snap-bbox="true"
- inkscape:bbox-nodes="true"
- inkscape:bbox-paths="true"
- inkscape:snap-bbox-edge-midpoints="true"
- inkscape:snap-bbox-midpoints="true"
- inkscape:object-paths="true"
- inkscape:object-nodes="true"
- inkscape:snap-smooth-nodes="true"
- inkscape:snap-midpoints="true"
- inkscape:snap-object-midpoints="true"
- inkscape:snap-center="true"
- inkscape:snap-page="true"
- inkscape:snap-intersection-paths="true"
- showguides="true"
- inkscape:guide-bbox="true" />
- <metadata
- id="metadata7">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title />
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:label="Layer 1"
- inkscape:groupmode="layer"
- id="layer1"
- transform="translate(0,-1004.3622)">
- <path
- style="fill:#3498db;fill-opacity:1;stroke:none"
- d="m 23,30 c -2.045693,0 -3.788228,1.240478 -4.5625,3 L 5,33 c -0.554,0 -1,0.446 -1,1 l 0,2 c 0,0.554 0.446,1 1,1 l 13.4375,0 c 0.774272,1.759522 2.516807,3 4.5625,3 2.045693,0 3.788228,-1.240478 4.5625,-3 L 43,37 c 0.554,0 1,-0.446 1,-1 l 0,-2 c 0,-0.554 -0.446,-1 -1,-1 L 27.5625,33 C 26.788228,31.240478 25.045693,30 23,30 z m 0,3 c 1.10457,0 2,0.89543 2,2 0,1.10457 -0.89543,2 -2,2 -1.10457,0 -2,-0.89543 -2,-2 0,-1.10457 0.89543,-2 2,-2 z"
- transform="translate(0,1004.3622)"
- id="rect3755"
- inkscape:connector-curvature="0" />
- <path
- style="fill:#3498db;fill-opacity:1;stroke:none"
- d="m 36,20 c -2.045693,0 -3.788228,1.240478 -4.5625,3 L 5,23 c -0.554,0 -1,0.446 -1,1 l 0,2 c 0,0.554 0.446,1 1,1 l 26.4375,0 c 0.774272,1.759522 2.516807,3 4.5625,3 2.045693,0 3.788228,-1.240478 4.5625,-3 L 43,27 c 0.554,0 1,-0.446 1,-1 l 0,-2 c 0,-0.554 -0.446,-1 -1,-1 l -2.4375,0 C 39.788228,21.240478 38.045693,20 36,20 z m 0,3 c 1.10457,0 2,0.89543 2,2 0,1.10457 -0.89543,2 -2,2 -1.10457,0 -2,-0.89543 -2,-2 0,-1.10457 0.89543,-2 2,-2 z"
- transform="translate(0,1004.3622)"
- id="rect3757"
- inkscape:connector-curvature="0" />
- <path
- style="fill:#3498db;fill-opacity:1;stroke:none"
- d="m 15,10 c -2.045692,0 -3.788228,1.240478 -4.5625,3 L 5,13 c -0.554,0 -1,0.446 -1,1 l 0,2 c 0,0.554 0.446,1 1,1 l 5.4375,0 c 0.774272,1.759522 2.516808,3 4.5625,3 2.045692,0 3.788228,-1.240478 4.5625,-3 L 43,17 c 0.554,0 1,-0.446 1,-1 l 0,-2 c 0,-0.554 -0.446,-1 -1,-1 L 19.5625,13 C 18.788228,11.240478 17.045692,10 15,10 z m 0,3 c 1.10457,0 2,0.89543 2,2 0,1.10457 -0.89543,2 -2,2 -1.10457,0 -2,-0.89543 -2,-2 0,-1.10457 0.89543,-2 2,-2 z"
- transform="translate(0,1004.3622)"
- id="rect3759"
- inkscape:connector-curvature="0" />
- <path
- sodipodi:type="arc"
- style="fill:none;stroke:#3498db;stroke-width:2.6408689;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
- id="path3764"
- sodipodi:cx="67.901237"
- sodipodi:cy="6.4814816"
- sodipodi:rx="20.098766"
- sodipodi:ry="19.518518"
- d="m 88.000004,6.4814816 a 20.098766,19.518518 0 1 1 -40.197533,0 20.098766,19.518518 0 1 1 40.197533,0 z"
- transform="matrix(0.74631447,0,0,0.76850096,21.324322,1023.3813)" />
- <path
- inkscape:connector-curvature="0"
- id="path3766"
- d="m 82,1030.3622 c 0.554,0 1,-0.446 1,-1 l 0,-2 c 0,-0.554 -0.446,-1 -1,-1 -13.004,0 -8.7461,0 -20,0 -0.554,0 -1,0.446 -1,1 l 0,2 c 0,0.554 0.446,1 1,1 12.4816,0 8.4097,0 20,0 z"
- style="fill:#3498db;fill-opacity:1;stroke:none"
- sodipodi:nodetypes="cssccsscc" />
- </g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="11"
- height="18"
- id="svg2"
- version="1.1"
- inkscape:version="0.48.4 r9939"
- sodipodi:docname="slider-arrow.svg"
- inkscape:export-filename="/media/data/projects/motioneye/static/img/slider-arrow.png"
- inkscape:export-xdpi="90"
- inkscape:export-ydpi="90">
- <defs
- id="defs4" />
- <sodipodi:namedview
- id="base"
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0.0"
- inkscape:pageshadow="2"
- inkscape:zoom="1"
- inkscape:cx="20.539051"
- inkscape:cy="-1.1141645"
- inkscape:document-units="px"
- inkscape:current-layer="layer1"
- showgrid="false"
- inkscape:window-width="1920"
- inkscape:window-height="1027"
- inkscape:window-x="0"
- inkscape:window-y="25"
- inkscape:window-maximized="1"
- inkscape:snap-bbox="true"
- inkscape:bbox-nodes="true"
- inkscape:bbox-paths="true"
- inkscape:snap-bbox-edge-midpoints="true"
- inkscape:snap-bbox-midpoints="true"
- inkscape:object-paths="true"
- inkscape:object-nodes="true"
- inkscape:snap-smooth-nodes="true"
- inkscape:snap-midpoints="true"
- inkscape:snap-object-midpoints="true"
- inkscape:snap-center="true"
- inkscape:snap-page="true"
- inkscape:snap-intersection-paths="true" />
- <metadata
- id="metadata7">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:label="Layer 1"
- inkscape:groupmode="layer"
- id="layer1"
- transform="translate(0,-1034.3622)">
- <path
- style="fill:#3498db;fill-opacity:1;stroke:#3498db;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
- d="m 1,1040.3622 0,10 9,0 0,-10 -4.5,-4.5 z"
- id="path3809"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cccccc" />
- </g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="80"
- height="16"
- id="svg2"
- version="1.1"
- inkscape:version="0.48.4 r9939"
- sodipodi:docname="top-bar-buttons.svg"
- inkscape:export-filename="/media/data/projects/motioneye/static/img/top-bar-buttons.png"
- inkscape:export-xdpi="90"
- inkscape:export-ydpi="90">
- <defs
- id="defs4" />
- <sodipodi:namedview
- id="base"
- pagecolor="#b39a9a"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0.42745098"
- inkscape:pageshadow="2"
- inkscape:zoom="2.25"
- inkscape:cx="111.39282"
- inkscape:cy="5.668431"
- inkscape:document-units="px"
- inkscape:current-layer="layer1"
- showgrid="false"
- inkscape:window-width="1920"
- inkscape:window-height="1030"
- inkscape:window-x="0"
- inkscape:window-y="25"
- inkscape:window-maximized="1"
- inkscape:snap-bbox="true"
- inkscape:bbox-nodes="true"
- inkscape:bbox-paths="true"
- inkscape:snap-bbox-edge-midpoints="true"
- inkscape:snap-bbox-midpoints="true"
- inkscape:object-paths="true"
- inkscape:object-nodes="true"
- inkscape:snap-smooth-nodes="true"
- inkscape:snap-midpoints="true"
- inkscape:snap-object-midpoints="true"
- inkscape:snap-center="true"
- inkscape:snap-page="true"
- inkscape:snap-intersection-paths="true"
- showguides="true"
- inkscape:guide-bbox="true" />
- <metadata
- id="metadata7">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title />
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:label="Layer 1"
- inkscape:groupmode="layer"
- id="layer1"
- transform="translate(0,-1036.3622)">
- <path
- sodipodi:type="arc"
- style="fill:none;stroke:#3498db;stroke-width:0.84620845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
- id="path3752"
- sodipodi:cx="7.8036885"
- sodipodi:cy="8.2353296"
- sodipodi:rx="5.6186557"
- sodipodi:ry="5.3845448"
- d="m 13.422344,8.2353296 a 5.6186557,5.3845448 0 1 1 -11.2373112,0 5.6186557,5.3845448 0 1 1 11.2373112,0 z"
- transform="matrix(1.1568604,0,0,1.2071587,-1.0277779,1034.4208)" />
- <path
- style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
- d="m 5.5,5.5 5,5"
- id="path3756"
- inkscape:connector-curvature="0"
- transform="translate(0,1036.3622)"
- sodipodi:nodetypes="cc" />
- <path
- sodipodi:nodetypes="cc"
- inkscape:connector-curvature="0"
- id="path3758"
- d="m 10.5,1041.8622 -5,5"
- style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
- <path
- sodipodi:type="arc"
- style="fill:none;stroke:#3498db;stroke-width:0.84620845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
- id="path3792"
- sodipodi:cx="7.8036885"
- sodipodi:cy="8.2353296"
- sodipodi:rx="5.6186557"
- sodipodi:ry="5.3845448"
- d="m 13.422344,8.2353296 a 5.6186557,5.3845448 0 1 1 -11.2373112,0 5.6186557,5.3845448 0 1 1 11.2373112,0 z"
- transform="matrix(-1.1568604,0,0,1.2071587,33.027778,1034.4209)" />
- <path
- sodipodi:nodetypes="cc"
- inkscape:connector-curvature="0"
- id="path3794"
- d="m 21.5,1046.8622 3,-3"
- style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
- <path
- style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
- d="m 24.5,1043.8622 0,-2"
- id="path3796"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc" />
- <path
- sodipodi:nodetypes="cc"
- inkscape:connector-curvature="0"
- id="path3798"
- d="m 24.5,1043.8622 2,0"
- style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
- <path
- style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
- d="m 27.5,1042.8622 -1,1"
- id="path3800"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cc" />
- <path
- sodipodi:nodetypes="cc"
- inkscape:connector-curvature="0"
- id="path3802"
- d="m 25.5,1040.8622 -1,1"
- style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
- <path
- transform="matrix(-1.1568604,0,0,1.2071587,49.027778,1034.4209)"
- d="m 13.422344,8.2353296 a 5.6186557,5.3845448 0 1 1 -11.2373112,0 5.6186557,5.3845448 0 1 1 11.2373112,0 z"
- sodipodi:ry="5.3845448"
- sodipodi:rx="5.6186557"
- sodipodi:cy="8.2353296"
- sodipodi:cx="7.8036885"
- id="path3759"
- style="fill:none;stroke:#3498db;stroke-width:0.84620845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
- sodipodi:type="arc" />
- <path
- sodipodi:nodetypes="ccccc"
- inkscape:connector-curvature="0"
- id="path3761"
- d="m 36.5,1041.8622 7,0 0,5 -7,0 z"
- style="fill:none;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" />
- <path
- style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#3498db;fill-opacity:1;stroke:none;stroke-width:8.06000042;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
- d="m 56.025,1041.1703 c -0.717378,0 -1.358863,0.3074 -1.769063,0.7819 l -1.988437,0 c -0.436275,0 -0.7875,0.3512 -0.7875,0.7875 l 0,3.375 c 0,0.4363 0.351225,0.7875 0.7875,0.7875 l 7.425,0 c 0.436275,0 0.7875,-0.3512 0.7875,-0.7875 l 0,-3.375 c 0,-0.4363 -0.351225,-0.7875 -0.7875,-0.7875 l -0.2925,0 0,-0.1012 a 0.450045,0.450045 0 1 0 -0.9,0 l 0,0.1012 -0.705938,0 c -0.409047,-0.4745 -1.051682,-0.7819 -1.769062,-0.7819 z m -0.36,0.3319 0.72,0 c 0.12465,0 0.225,0.1004 0.225,0.225 l 0,0.09 c 0,0.1247 -0.10035,0.225 -0.225,0.225 l -0.72,0 c -0.12465,0 -0.225,-0.1003 -0.225,-0.225 l 0,-0.09 c 0,-0.1246 0.10035,-0.225 0.225,-0.225 z m 0.36,1.35 c 0.919555,0 1.665,0.7455 1.665,1.665 0,0.9196 -0.745445,1.665 -1.665,1.665 -0.919553,0 -1.665,-0.7454 -1.665,-1.665 0,-0.9195 0.745447,-1.665 1.665,-1.665 z"
- id="path3803"
- inkscape:connector-curvature="0" />
- <path
- sodipodi:type="arc"
- style="fill:none;stroke:#3498db;stroke-width:0.84620845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
- id="path3815"
- sodipodi:cx="7.8036885"
- sodipodi:cy="8.2353296"
- sodipodi:rx="5.6186557"
- sodipodi:ry="5.3845448"
- d="m 13.422344,8.2353296 a 5.6186557,5.3845448 0 1 1 -11.2373112,0 5.6186557,5.3845448 0 1 1 11.2373112,0 z"
- transform="matrix(-1.1568604,0,0,1.2071587,65.027778,1034.4209)" />
- <path
- transform="matrix(-1.1568604,0,0,1.2071587,81.027778,1034.4209)"
- d="m 13.422344,8.2353296 a 5.6186557,5.3845448 0 1 1 -11.2373112,0 5.6186557,5.3845448 0 1 1 11.2373112,0 z"
- sodipodi:ry="5.3845448"
- sodipodi:rx="5.6186557"
- sodipodi:cy="8.2353296"
- sodipodi:cx="7.8036885"
- id="path3817"
- style="fill:none;stroke:#3498db;stroke-width:0.84620845;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
- sodipodi:type="arc" />
- <path
- style="fill:#3498db;fill-opacity:1;stroke:#3498db;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
- d="m 69.5,1040.8622 6,3.5 -6,3.5 z"
- id="path3819"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="cccc" />
- </g>
-</svg>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="20"
- height="20"
- id="svg2"
- version="1.1"
- inkscape:version="0.48.4 r9939"
- sodipodi:docname="validation-error.svg"
- inkscape:export-filename="/media/data/projects/motioneye/static/img/validation-error.png"
- inkscape:export-xdpi="90"
- inkscape:export-ydpi="90">
- <defs
- id="defs4" />
- <sodipodi:namedview
- id="base"
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0.0"
- inkscape:pageshadow="2"
- inkscape:zoom="11.390625"
- inkscape:cx="-0.48299409"
- inkscape:cy="28.256052"
- inkscape:document-units="px"
- inkscape:current-layer="layer1"
- showgrid="false"
- inkscape:window-width="1920"
- inkscape:window-height="1027"
- inkscape:window-x="0"
- inkscape:window-y="25"
- inkscape:window-maximized="1"
- inkscape:snap-bbox="true"
- inkscape:bbox-nodes="true"
- inkscape:bbox-paths="true"
- inkscape:snap-bbox-edge-midpoints="true"
- inkscape:snap-bbox-midpoints="true"
- inkscape:object-paths="true"
- inkscape:object-nodes="true"
- inkscape:snap-smooth-nodes="true"
- inkscape:snap-midpoints="true"
- inkscape:snap-object-midpoints="true"
- inkscape:snap-center="true"
- inkscape:snap-page="true"
- inkscape:snap-intersection-paths="true"
- showguides="true"
- inkscape:guide-bbox="true" />
- <metadata
- id="metadata7">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:label="Layer 1"
- inkscape:groupmode="layer"
- id="layer1"
- transform="translate(0,-1032.3622)">
- <path
- style="fill:#c11c00;fill-opacity:1;stroke:none"
- d="M 7 3 C 3.1340068 3 0 6.1340067 0 10 C -5.9211895e-16 13.865993 3.1340068 17 7 17 C 10.865993 17 14 13.865993 14 10 C 14 6.1340067 10.865993 3 7 3 z M 6 5.25 L 8.125 5.25 L 8.125 8.625 L 7.8125 11.0625 L 6.3125 11.0625 L 6 8.625 L 6 5.25 z M 6 11.90625 L 8.125 11.90625 L 8.125 14 L 6 14 L 6 11.90625 z "
- transform="translate(0,1032.3622)"
- id="path3751" />
- <g
- style="font-size:13px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
- id="text3753" />
- </g>
-</svg>
+++ /dev/null
-/*
-CSS Browser Selector js v0.5.3 (July 2, 2013)
-
--- original --
-Rafael Lima (http://rafael.adm.br)
-http://rafael.adm.br/css_browser_selector
-License: http://creativecommons.org/licenses/by/2.5/
-Contributors: http://rafael.adm.br/css_browser_selector#contributors
--- /original --
-
-Fork project: http://code.google.com/p/css-browser-selector/
-Song Hyo-Jin (shj at xenosi.de)
-*/
-function css_browser_selector(n){var b=n.toLowerCase(),f=function(c){return b.indexOf(c)>-1},h="gecko",k="webkit",p="safari",j="chrome",d="opera",e="mobile",l=0,a=window.devicePixelRatio?(window.devicePixelRatio+"").replace(".","_"):"1";var i=[(!(/opera|webtv/.test(b))&&/msie\s(\d+)/.test(b)&&(l=RegExp.$1*1))?("ie ie"+l+((l==6||l==7)?" ie67 ie678 ie6789":(l==8)?" ie678 ie6789":(l==9)?" ie6789 ie9m":(l>9)?" ie9m":"")):(/firefox\/(\d+)\.(\d+)/.test(b)&&(re=RegExp))?h+" ff ff"+re.$1+" ff"+re.$1+"_"+re.$2:f("gecko/")?h:f(d)?d+(/version\/(\d+)/.test(b)?" "+d+RegExp.$1:(/opera(\s|\/)(\d+)/.test(b)?" "+d+RegExp.$2:"")):f("konqueror")?"konqueror":f("blackberry")?e+" blackberry":(f(j)||f("crios"))?k+" "+j:f("iron")?k+" iron":!f("cpu os")&&f("applewebkit/")?k+" "+p:f("mozilla/")?h:"",f("android")?e+" android":"",f("tablet")?"tablet":"",f("j2me")?e+" j2me":f("ipad; u; cpu os")?e+" chrome android tablet":f("ipad;u;cpu os")?e+" chromedef android tablet":f("iphone")?e+" ios iphone":f("ipod")?e+" ios ipod":f("ipad")?e+" ios ipad tablet":f("mac")?"mac":f("darwin")?"mac":f("webtv")?"webtv":f("win")?"win"+(f("windows nt 6.0")?" vista":""):f("freebsd")?"freebsd":(f("x11")||f("linux"))?"linux":"",(a!="1")?" retina ratio"+a:"","js portrait"].join(" ");if(window.jQuery&&!window.jQuery.browser){window.jQuery.browser=l?{msie:1,version:l}:{}}return i}(function(j,b){var c=css_browser_selector(navigator.userAgent);var g=j.documentElement;g.className+=" "+c;var a=c.replace(/^\s*|\s*$/g,"").split(/ +/);b.CSSBS=1;for(var f=0;f<a.length;f++){b["CSSBS_"+a[f]]=1}var e=function(d){return j.documentElement[d]||j.body[d]};if(b.jQuery){(function(q){var h="portrait",k="landscape";var i="smartnarrow",u="smartwide",x="tabletnarrow",r="tabletwide",w=i+" "+u+" "+x+" "+r+" pc";var v=q(g);var s=0,o=0;function d(){try{var l=e("clientWidth"),p=e("clientHeight");if(l>p){v.removeClass(h).addClass(k)}else{v.removeClass(k).addClass(h)}if(l==o){return}o=l;clearTimeout(s)}catch(m){}s=setTimeout(n,100)}function n(){try{v.removeClass(w);v.addClass((o<=360)?i:(o<=640)?u:(o<=768)?x:(o<=1024)?r:"pc")}catch(l){}}q(b).on("resize orientationchange",d).trigger("resize")})(b.jQuery)}})(document,window);
+++ /dev/null
-
-var refreshDisabled = false;
-
-
- /* camera frame */
-
-function setupCameraFrame() {
- var cameraFrameDiv = $('div.camera-frame')
- var cameraPlaceholder = cameraFrameDiv.find('div.camera-placeholder');
- var cameraProgress = cameraFrameDiv.find('div.camera-progress');
- var cameraImg = cameraFrameDiv.find('img.camera');
- var cameraId = cameraFrameDiv.attr('id').substring(6);
- var progressImg = cameraFrameDiv.find('img.camera-progress');
- var body = $('body');
-
- cameraFrameDiv[0].refreshDivider = 0;
- cameraFrameDiv[0].streamingFramerate = parseInt(cameraFrameDiv.attr('streaming_framerate')) || 1;
- cameraFrameDiv[0].streamingServerResize = cameraFrameDiv.attr('streaming_server_resize') == 'True';
- cameraFrameDiv[0].proto = cameraFrameDiv.attr('proto');
- cameraFrameDiv[0].url = cameraFrameDiv.attr('url');
- progressImg.attr('src', staticUrl + 'img/camera-progress.gif');
-
- cameraProgress.addClass('visible');
- cameraPlaceholder.css('opacity', '0');
-
- /* fade in */
- cameraFrameDiv.animate({'opacity': 1}, 100);
-
- /* error and load handlers */
- cameraImg.error(function () {
- this.error = true;
- this.loading = 0;
-
- cameraImg.addClass('error').removeClass('loading');
- cameraPlaceholder.css('opacity', 1);
- cameraProgress.removeClass('visible');
- cameraFrameDiv.removeClass('motion-detected');
- });
- cameraImg.load(function () {
- if (refreshDisabled) {
- return; /* refresh temporarily disabled for updating */
- }
-
- this.error = false;
- this.loading = 0;
-
- cameraImg.removeClass('error').removeClass('loading');
- cameraPlaceholder.css('opacity', 0);
- cameraProgress.removeClass('visible');
-
- /* there's no point in looking for a cookie update more often than once every second */
- var now = new Date().getTime();
- if ((!this.lastCookieTime || now - this.lastCookieTime > 1000) && (cameraFrameDiv[0].proto != 'mjpeg')) {
- if (getCookie('motion_detected_' + cameraId) == 'true') {
- cameraFrameDiv.addClass('motion-detected');
- }
- else {
- cameraFrameDiv.removeClass('motion-detected');
- }
-
- this.lastCookieTime = now;
- }
-
- if (this.naturalWidth / this.naturalHeight > body.width() / body.height()) {
- cameraImg.css('width', '100%');
- cameraImg.css('height', 'auto');
- }
- else {
- cameraImg.css('width', 'auto');
- cameraImg.css('height', '100%');
- }
- });
-
- cameraImg.addClass('loading');
-}
-
-function refreshCameraFrame() {
- var $cameraFrame = $('div.camera-frame');
- var cameraFrame = $cameraFrame[0];
- var img = $cameraFrame.find('img.camera')[0];
- var cameraId = cameraFrame.id.substring(6);
-
- if (cameraFrame.proto == 'mjpeg') {
- /* no manual refresh for simple mjpeg cameras */
- var url = cameraFrame.url.replace('127.0.0.1', window.location.host.split(':')[0]);
- url += (url.indexOf('?') > 0 ? '&' : '?') + '_=' + new Date().getTime();
- img.src = url;
- return;
- }
-
- /* at a refresh interval of 50ms, the refresh rate is limited to 20 fps */
- var count = 1000 / (refreshInterval * cameraFrame.streamingFramerate);
- if (count <= 2) {
- /* skipping frames (showing the same frame twice) at this rate won't be visible,
- * while the effective framerate will be as close as possible to the motion's one */
- count -= 1;
- }
-
- if (img.error) {
- /* in case of error, decrease the refresh rate to 1 fps */
- count = 1000 / refreshInterval;
- }
-
- if (cameraFrame.refreshDivider < count) {
- cameraFrame.refreshDivider++;
- }
- else {
- (function () {
- if (refreshDisabled) {
- /* camera refreshing disabled, retry later */
-
- return;
- }
-
- if (img.loading) {
- img.loading++; /* increases each time the camera would refresh but is still loading */
-
- if (img.loading > 2 * 1000 / refreshInterval) { /* limits the retry at one every two seconds */
- img.loading = 0;
- }
- else {
- return; /* wait for the previous frame to finish loading */
- }
- }
-
- var timestamp = new Date().getTime();
- var uri = baseUri + 'picture/' + cameraId + '/current/?_=' + timestamp;
- if (cameraFrame.serverSideResize) {
- uri += '&width=' + img.width;
- }
-
- uri = addAuthParams('GET', uri);
- img.src = uri;
- img.loading = 1;
-
- cameraFrame.refreshDivider = 0;
- })();
- }
-
- setTimeout(refreshCameraFrame, refreshInterval);
-}
-
-
- /* startup function */
-
-$(document).ready(function () {
- setupCameraFrame();
- refreshCameraFrame();
-});
-
+++ /dev/null
-/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license
-*/
-(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="<div class='a'></div><div class='a i'></div>",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav></:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="<div></div>",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t
-}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/<tbody/i,wt=/<|&#?\w+;/,Tt=/<(?:script|style|link)/i,Ct=/^(?:checkbox|radio)$/i,Nt=/checked\s*(?:[^=]|=\s*.checked.)/i,kt=/^$|\/(?:java|ecma)script/i,Et=/^true\/(.*)/,St=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,At={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle);
-u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(Pt[0].contentWindow||Pt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=un(e,t),Pt.detach()),Gt[e]=n),n}function un(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,n){x.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(x.css(e,"display"))?x.swap(e,Qt,function(){return sn(e,n,i)}):sn(e,n,i):t},set:function(e,t,r){var i=r&&Rt(e);return on(e,t,r?an(e,n,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x.support.opacity||(x.cssHooks.opacity={get:function(e,t){return It.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=x.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===x.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,n){return n?x.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,n){x.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?x(e).position()[n]+"px":r):t}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!x.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||x.css(e,"display"))},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+Zt[r]+t]=o[r]||o[r-2]||o[0];return i}},Ut.test(e)||(x.cssHooks[e+t].set=on)});var cn=/%20/g,pn=/\[\]$/,fn=/\r?\n/g,dn=/^(?:submit|button|image|reset|file)$/i,hn=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Ct.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),x.param=function(e,n){var r,i=[],o=function(e,t){t=x.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){o(this.name,this.value)});else for(r in e)gn(r,e[r],n,o);return i.join("&").replace(cn,"+")};function gn(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||pn.test(e)?r(e,i):gn(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}x.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var mn,yn,vn=x.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Cn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Nn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=x.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=o.href}catch(Ln){yn=a.createElement("a"),yn.href="",yn=yn.href}mn=En.exec(yn.toLowerCase())||[];function Hn(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(T)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function qn(e,n,r,i){var o={},a=e===jn;function s(l){var u;return o[l]=!0,x.each(e[l]||[],function(e,l){var c=l(n,r,i);return"string"!=typeof c||a||o[c]?a?!(u=c):t:(n.dataTypes.unshift(c),s(c),!1)}),u}return s(n.dataTypes[0])||!o["*"]&&s("*")}function _n(e,n){var r,i,o=x.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,l=e.indexOf(" ");return l>=0&&(i=e.slice(l,e.length),e=e.slice(0,l)),x.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&x.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?x("<div>").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window);
+++ /dev/null
-/*! Copyright (c) 2013 Brandon Aaron (http://brandonaaron.net)
- * Licensed under the MIT License (LICENSE.txt).
- *
- * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
- * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
- * Thanks to: Seamus Leahy for adding deltaX and deltaY
- *
- * Version: 3.1.3
- *
- * Requires: 1.2.2+
- */
-
-(function (factory) {
- if ( typeof define === 'function' && define.amd ) {
- // AMD. Register as an anonymous module.
- define(['jquery'], factory);
- } else if (typeof exports === 'object') {
- // Node/CommonJS style for Browserify
- module.exports = factory;
- } else {
- // Browser globals
- factory(jQuery);
- }
-}(function ($) {
-
- var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
- var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
- var lowestDelta, lowestDeltaXY;
-
- if ( $.event.fixHooks ) {
- for ( var i = toFix.length; i; ) {
- $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
- }
- }
-
- $.event.special.mousewheel = {
- setup: function() {
- if ( this.addEventListener ) {
- for ( var i = toBind.length; i; ) {
- this.addEventListener( toBind[--i], handler, false );
- }
- } else {
- this.onmousewheel = handler;
- }
- },
-
- teardown: function() {
- if ( this.removeEventListener ) {
- for ( var i = toBind.length; i; ) {
- this.removeEventListener( toBind[--i], handler, false );
- }
- } else {
- this.onmousewheel = null;
- }
- }
- };
-
- $.fn.extend({
- mousewheel: function(fn) {
- return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
- },
-
- unmousewheel: function(fn) {
- return this.unbind("mousewheel", fn);
- }
- });
-
-
- function handler(event) {
- var orgEvent = event || window.event,
- args = [].slice.call(arguments, 1),
- delta = 0,
- deltaX = 0,
- deltaY = 0,
- absDelta = 0,
- absDeltaXY = 0,
- fn;
- event = $.event.fix(orgEvent);
- event.type = "mousewheel";
-
- // Old school scrollwheel delta
- if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
- if ( orgEvent.detail ) { delta = orgEvent.detail * -1; }
-
- // New school wheel delta (wheel event)
- if ( orgEvent.deltaY ) {
- deltaY = orgEvent.deltaY * -1;
- delta = deltaY;
- }
- if ( orgEvent.deltaX ) {
- deltaX = orgEvent.deltaX;
- delta = deltaX * -1;
- }
-
- // Webkit
- if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
- if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX * -1; }
-
- // Look for lowest delta to normalize the delta values
- absDelta = Math.abs(delta);
- if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
- absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
- if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
-
- // Get a whole value for the deltas
- fn = delta > 0 ? 'floor' : 'ceil';
- delta = Math[fn](delta / lowestDelta);
- deltaX = Math[fn](deltaX / lowestDeltaXY);
- deltaY = Math[fn](deltaY / lowestDeltaXY);
-
- // Add event and delta to the front of the arguments
- args.unshift(event, delta, deltaX, deltaY);
-
- return ($.event.dispatch || $.event.handle).apply(this, args);
- }
-
-}));
+++ /dev/null
-!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){function b(a){if(a.minTime&&(a.minTime=s(a.minTime)),a.maxTime&&(a.maxTime=s(a.maxTime)),a.durationTime&&"function"!=typeof a.durationTime&&(a.durationTime=s(a.durationTime)),a.disableTimeRanges.length>0){for(var b in a.disableTimeRanges)a.disableTimeRanges[b]=[s(a.disableTimeRanges[b][0]),s(a.disableTimeRanges[b][1])];a.disableTimeRanges=a.disableTimeRanges.sort(function(a,b){return a[0]-b[0]})}return a}function c(b){var c=b.data("timepicker-settings"),d=b.data("timepicker-list");d&&d.length&&(d.remove(),b.data("timepicker-list",!1)),d=a("<ul />",{"class":"ui-timepicker-list"});var e=a("<div />",{"class":"ui-timepicker-wrapper",tabindex:-1});e.css({display:"none",position:"absolute"}).append(d),c.className&&e.addClass(c.className),null===c.minTime&&null===c.durationTime||!c.showDuration||e.addClass("ui-timepicker-with-duration");var f=c.minTime;"function"==typeof c.durationTime?f=s(c.durationTime()):null!==c.durationTime&&(f=c.durationTime);var h=null!==c.minTime?c.minTime:0,j=null!==c.maxTime?c.maxTime:h+v-1;h>=j&&(j+=v);for(var k=c.disableTimeRanges,l=0,m=k.length,n=h;j>=n;n+=60*c.step){var o=n%v,t=a("<li />");if(t.data("time",o),t.text(r(o,c.timeFormat)),(null!==c.minTime||null!==c.durationTime)&&c.showDuration){var u=a("<span />");u.addClass("ui-timepicker-duration"),u.text(" ("+q(n-f)+")"),t.append(u)}m>l&&(o>=k[l][1]&&(l+=1),k[l]&&o>=k[l][0]&&o<k[l][1]&&t.addClass("ui-timepicker-disabled")),d.append(t)}e.data("timepicker-input",b),b.data("timepicker-list",e);var w=c.appendTo;"string"==typeof w?w=a(w):"function"==typeof w&&(w=w(b)),w.append(e),i(b,d),d.on("click","li",function(){b.off("focus.timepicker"),b.on("focus.timepicker-ie-hack",function(){b.off("focus.timepicker-ie-hack"),b.on("focus.timepicker",y.show)}),g(b)||b[0].focus(),d.find("li").removeClass("ui-timepicker-selected"),a(this).addClass("ui-timepicker-selected"),p(b)&&(b.trigger("hideTimepicker"),e.hide())})}function d(){return new Date(1970,1,1,0,0,0)}function e(b){"ontouchstart"in document?a("body").on("touchstart.ui-timepicker",f):(a("body").on("mousedown.ui-timepicker",f),b.closeOnWindowScroll&&a(window).on("scroll.ui-timepicker",f))}function f(b){var c=a(b.target),d=c.closest(".ui-timepicker-input");0===d.length&&0===c.closest(".ui-timepicker-wrapper").length&&(y.hide(),a("body").unbind(".ui-timepicker"),a(window).unbind(".ui-timepicker"))}function g(a){var b=a.data("timepicker-settings");return(window.navigator.msMaxTouchPoints||"ontouchstart"in document)&&b.disableTouchKeyboard}function h(b,c,d){if(!d&&0!==d)return!1;var e=b.data("timepicker-settings"),f=!1,g=30*e.step;return c.find("li").each(function(b,c){var e=a(c),h=e.data("time")-d;return Math.abs(h)<g||h==g?(f=e,!1):void 0}),f}function i(a,b){b.find("li").removeClass("ui-timepicker-selected");var c=s(k(a));if(c){var d=h(a,b,c);if(d){var e=d.offset().top-b.offset().top;(e+d.outerHeight()>b.outerHeight()||0>e)&&b.scrollTop(b.scrollTop()+d.position().top-d.outerHeight()),d.addClass("ui-timepicker-selected")}}}function j(){if(""!==this.value){var b=a(this),c=b.data("timepicker-list");if(!c||!c.is(":visible")){var d=s(this.value);if(null===d)return b.trigger("timeFormatError"),void 0;var e=b.data("timepicker-settings"),f=!1;if(null!==e.minTime&&d<e.minTime?f=!0:null!==e.maxTime&&d>e.maxTime&&(f=!0),a.each(e.disableTimeRanges,function(){return d>=this[0]&&d<this[1]?(f=!0,!1):void 0}),e.forceRoundTime){var g=d%(60*e.step);g>=30*e.step?d+=60*e.step-g:d-=g}var h=r(d,e.timeFormat);f?l(b,h,"error")&&b.trigger("timeRangeError"):l(b,h)}}}function k(a){return a.is("input")?a.val():a.data("ui-timepicker-value")}function l(a,b,c){return a.data("ui-timepicker-value")!=b?(a.data("ui-timepicker-value",b),a.is("input")&&a.val(b),"select"==c?a.trigger("selectTime").trigger("changeTime").trigger("change"):"error"!=c&&a.trigger("changeTime"),!0):(a.trigger("selectTime"),!1)}function m(b){var c=a(this),d=c.data("timepicker-list");if(!d||!d.is(":visible")){if(40!=b.keyCode)return n(b,c);g(c)||c.focus()}switch(b.keyCode){case 13:return p(c)&&y.hide.apply(this),b.preventDefault(),!1;case 38:var e=d.find(".ui-timepicker-selected");return e.length?e.is(":first-child")||(e.removeClass("ui-timepicker-selected"),e.prev().addClass("ui-timepicker-selected"),e.prev().position().top<e.outerHeight()&&d.scrollTop(d.scrollTop()-e.outerHeight())):(d.find("li").each(function(b,c){return a(c).position().top>0?(e=a(c),!1):void 0}),e.addClass("ui-timepicker-selected")),!1;case 40:return e=d.find(".ui-timepicker-selected"),0===e.length?(d.find("li").each(function(b,c){return a(c).position().top>0?(e=a(c),!1):void 0}),e.addClass("ui-timepicker-selected")):e.is(":last-child")||(e.removeClass("ui-timepicker-selected"),e.next().addClass("ui-timepicker-selected"),e.next().position().top+2*e.outerHeight()>d.outerHeight()&&d.scrollTop(d.scrollTop()+e.outerHeight())),!1;case 27:d.find("li").removeClass("ui-timepicker-selected"),y.hide();break;case 9:y.hide();break;default:return n(b,c)}}function n(a,b){return!b.data("timepicker-settings").disableTextInput||a.ctrlKey||a.altKey||a.metaKey||2!=a.keyCode&&a.keyCode<46}function o(b){var c=a(this),d=c.data("timepicker-list");if(!d||!d.is(":visible"))return!0;switch(b.keyCode){case 96:case 97:case 98:case 99:case 100:case 101:case 102:case 103:case 104:case 105:case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:case 65:case 77:case 80:case 186:case 8:case 46:i(c,d);break;default:return}}function p(a){var b=a.data("timepicker-settings"),c=a.data("timepicker-list"),d=null,e=c.find(".ui-timepicker-selected");if(e.hasClass("ui-timepicker-disabled"))return!1;if(e.length?d=e.data("time"):k(a)&&(d=s(k(a)),i(a,c)),null!==d){var f=r(d,b.timeFormat);l(a,f,"select")}return!0}function q(a){var b,c=Math.round(a/60);if(Math.abs(c)<60)b=[c,x.mins];else if(60==c)b=["1",x.hr];else{var d=(c/60).toFixed(1);"."!=x.decimal&&(d=d.replace(".",x.decimal)),b=[d,x.hrs]}return b.join(" ")}function r(a,b){if(null!==a){for(var c,d,e=new Date(u.valueOf()+1e3*a),f="",g=0;g<b.length;g++)switch(d=b.charAt(g)){case"a":f+=e.getHours()>11?"pm":"am";break;case"A":f+=e.getHours()>11?"PM":"AM";break;case"g":c=e.getHours()%12,f+=0===c?"12":c;break;case"G":f+=e.getHours();break;case"h":c=e.getHours()%12,0!==c&&10>c&&(c="0"+c),f+=0===c?"12":c;break;case"H":c=e.getHours(),f+=c>9?c:"0"+c;break;case"i":var h=e.getMinutes();f+=h>9?h:"0"+h;break;case"s":a=e.getSeconds(),f+=a>9?a:"0"+a;break;default:f+=d}return f}}function s(a){if(""===a)return null;if(!a||a+0==a)return a;"object"==typeof a&&(a=a.getHours()+":"+t(a.getMinutes())+":"+t(a.getSeconds())),a=a.toLowerCase(),new Date(0);var b;if(-1===a.indexOf(":")?(b=a.match(/^([0-9]):?([0-5][0-9])?:?([0-5][0-9])?\s*([pa]?)m?$/),b||(b=a.match(/^([0-2][0-9]):?([0-5][0-9])?:?([0-5][0-9])?\s*([pa]?)m?$/))):b=a.match(/^(\d{1,2})(?::([0-5][0-9]))?(?::([0-5][0-9]))?\s*([pa]?)m?$/),!b)return null;var c,d=parseInt(1*b[1],10);c=b[4]?12==d?"p"==b[4]?12:0:d+("p"==b[4]?12:0):d;var e=1*b[2]||0,f=1*b[3]||0;return 3600*c+60*e+f}function t(a){return("0"+a).slice(-2)}var u=d(),v=86400,w={className:null,minTime:null,maxTime:null,durationTime:null,step:30,showDuration:!1,timeFormat:"g:ia",scrollDefaultNow:!1,scrollDefaultTime:!1,selectOnBlur:!1,disableTouchKeyboard:!0,forceRoundTime:!1,appendTo:"body",disableTimeRanges:[],closeOnWindowScroll:!1,disableTextInput:!1},x={decimal:".",mins:"mins",hr:"hr",hrs:"hrs"},y={init:function(c){return this.each(function(){var d=a(this);if("SELECT"==d[0].tagName){for(var e={type:"text",value:d.val()},f=d[0].attributes,g=0;g<f.length;g++)e[f[g].nodeName]=f[g].nodeValue;var h=a("<input />",e);d.replaceWith(h),d=h}var i=a.extend({},w);c&&(i=a.extend(i,c)),i.lang&&(x=a.extend(x,i.lang)),i=b(i),d.data("timepicker-settings",i),d.prop("autocomplete","off"),d.on("click.timepicker focus.timepicker",y.show),d.on("change.timepicker",j),d.on("keydown.timepicker",m),d.on("keyup.timepicker",o),d.addClass("ui-timepicker-input"),j.call(d.get(0))})},show:function(){var b=a(this),d=b.data("timepicker-settings");g(b)&&b.blur();var f=b.data("timepicker-list");if(!b.prop("readonly")&&(f&&0!==f.length&&"function"!=typeof d.durationTime||(c(b),f=b.data("timepicker-list")),!f.is(":visible"))){y.hide(),f.show(),b.offset().top+b.outerHeight(!0)+f.outerHeight()>a(window).height()+a(window).scrollTop()?f.offset({left:b.offset().left+parseInt(f.css("marginLeft").replace("px",""),10),top:b.offset().top-f.outerHeight()+parseInt(f.css("marginTop").replace("px",""),10)}):f.offset({left:b.offset().left+parseInt(f.css("marginLeft").replace("px",""),10),top:b.offset().top+b.outerHeight()+parseInt(f.css("marginTop").replace("px",""),10)});var i=f.find(".ui-timepicker-selected");if(i.length||(k(b)?i=h(b,f,s(k(b))):d.scrollDefaultNow?i=h(b,f,s(new Date)):d.scrollDefaultTime!==!1&&(i=h(b,f,s(d.scrollDefaultTime)))),i&&i.length){var j=f.scrollTop()+i.position().top-i.outerHeight();f.scrollTop(j)}else f.scrollTop(0);e(d),b.trigger("showTimepicker")}},hide:function(){a(".ui-timepicker-wrapper:visible").each(function(){var b=a(this),c=b.data("timepicker-input"),d=c.data("timepicker-settings");d&&d.selectOnBlur&&p(c),b.hide(),c.trigger("hideTimepicker")})},option:function(c,d){var e=this,f=e.data("timepicker-settings"),g=e.data("timepicker-list");if("object"==typeof c)f=a.extend(f,c);else if("string"==typeof c&&"undefined"!=typeof d)f[c]=d;else if("string"==typeof c)return f[c];return f=b(f),e.data("timepicker-settings",f),g&&(g.remove(),e.data("timepicker-list",!1)),e},getSecondsFromMidnight:function(){return s(k(this))},getTime:function(){var a=this,b=new Date;return b.setHours(0,0,0,0),new Date(b.valueOf()+1e3*s(k(a)))},setTime:function(a){var b=this,c=r(s(a),b.data("timepicker-settings").timeFormat);l(b,c)},remove:function(){var a=this;a.hasClass("ui-timepicker-input")&&(a.removeAttr("autocomplete","off"),a.removeClass("ui-timepicker-input"),a.removeData("timepicker-settings"),a.off(".timepicker"),a.data("timepicker-list")&&a.data("timepicker-list").remove(),a.removeData("timepicker-list"))}};a.fn.timepicker=function(b){return y[b]?y[b].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof b&&b?(a.error("Method "+b+" does not exist on jQuery.timepicker"),void 0):y.init.apply(this,arguments)}});
\ No newline at end of file
+++ /dev/null
-
-var pushConfigs = {};
-var pushConfigReboot = false;
-var refreshDisabled = {}; /* dictionary indexed by cameraId, tells if refresh is disabled for a given camera */
-var fullScreenCameraId = null;
-var inProgress = false;
-var refreshInterval = 50; /* milliseconds */
-var username = '';
-var password = '';
-var baseUri = null;
-var signatureRegExp = new RegExp('[^a-zA-Z0-9/?_.=&{}\\[\\]":, _-]', 'g');
-
-
- /* utils */
-
-var sha1 = (function () {
- function hash(msg) {
- var K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];
-
- msg += String.fromCharCode(0x80);
-
- var l = msg.length / 4 + 2;
- var N = Math.ceil(l / 16);
- var M = new Array(N);
-
- for (var i = 0; i < N; i++) {
- M[i] = new Array(16);
- for (var j = 0; j < 16; j++) {
- M[i][j] = (msg.charCodeAt(i * 64 + j * 4) << 24) | (msg.charCodeAt(i * 64 + j * 4 + 1) << 16) |
- (msg.charCodeAt(i * 64 + j * 4 + 2) << 8) | (msg.charCodeAt(i * 64 + j * 4 + 3));
- }
- }
- M[N-1][14] = ((msg.length-1) * 8) / Math.pow(2, 32);
- M[N-1][14] = Math.floor(M[N-1][14]);
- M[N-1][15] = ((msg.length-1) * 8) & 0xffffffff;
-
- var H0 = 0x67452301;
- var H1 = 0xefcdab89;
- var H2 = 0x98badcfe;
- var H3 = 0x10325476;
- var H4 = 0xc3d2e1f0;
-
- var W = new Array(80);
- var a, b, c, d, e;
- for (var i = 0; i < N; i++) {
- for (var t = 0; t < 16; t++) W[t] = M[i][t];
- for (var t = 16; t < 80; t++) W[t] = ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
-
- a = H0; b = H1; c = H2; d = H3; e = H4;
-
- for (var t = 0; t < 80; t++) {
- var s = Math.floor(t / 20);
- var T = (ROTL(a, 5) + f(s, b, c, d) + e + K[s] + W[t]) & 0xffffffff;
- e = d;
- d = c;
- c = ROTL(b, 30);
- b = a;
- a = T;
- }
-
- H0 = (H0 + a) & 0xffffffff;
- H1 = (H1 + b) & 0xffffffff;
- H2 = (H2 + c) & 0xffffffff;
- H3 = (H3 + d) & 0xffffffff;
- H4 = (H4 + e) & 0xffffffff;
- }
-
- return toHexStr(H0) + toHexStr(H1) + toHexStr(H2) + toHexStr(H3) + toHexStr(H4);
- }
-
- function f(s, x, y, z) {
- switch (s) {
- case 0: return (x & y) ^ (~x & z);
- case 1: return x ^ y ^ z;
- case 2: return (x & y) ^ (x & z) ^ (y & z);
- case 3: return x ^ y ^ z;
- }
- }
-
- function ROTL(x, n) {
- return (x << n) | (x >>> (32 - n));
- }
-
- function toHexStr(n) {
- var s = "", v;
- for (var i = 7; i >= 0; i--) {
- v = (n >>> (i * 4)) & 0xf;
- s += v.toString(16);
- }
- return s;
- }
-
- return hash;
-}());
-
-function splitUrl(url) {
- if (!url) {
- url = window.location.href;
- }
-
- var parts = url.split('?');
- if (parts.length < 2 || parts[1].length === 0) {
- return {baseUrl: parts[0], params: {}};
- }
-
- var baseUrl = parts[0];
- var paramStr = parts[1];
-
- parts = paramStr.split('&');
- var params = {};
-
- for (var i = 0; i < parts.length; i++) {
- var pair = parts[i].split('=');
- params[pair[0]] = pair[1];
- }
-
- return {baseUrl: baseUrl, params: params};
-}
-
-function qualifyUrl(url) {
- var a = document.createElement('a');
- a.href = url;
- return a.href;
-}
-
-function qualifyUri(uri) {
- var url = qualifyUrl(uri);
- var pos = url.indexOf('//');
- if (pos === -1) { /* not a full url */
- return url;
- }
-
- url = url.substring(pos + 2);
- pos = url.indexOf('/');
- if (pos === -1) { /* root with no trailing slash */
- return '';
- }
-
- return url.substring(pos);
-}
-
-function computeSignature(method, uri, body) {
- uri = qualifyUri(uri);
-
- var parts = splitUrl(uri);
- var query = parts.params;
- var baseUrl = parts.baseUrl;
-
- /* sort query arguments alphabetically */
- query = Object.keys(query).map(function (key) {return {key: key, value: decodeURIComponent(query[key])};});
- query = query.filter(function (q) {return q.key !== '_signature';});
- query.sortKey(function (q) {return q.key;});
- query = query.map(function (q) {return q.key + '=' + encodeURIComponent(q.value);}).join('&');
- uri = baseUrl + '?' + query;
- uri = uri.replace(signatureRegExp, '-');
- body = body && body.replace(signatureRegExp, '-');
- var password = window.password.replace(signatureRegExp, '-');
-
- return sha1(method + ':' + uri + ':' + (body || '') + ':' + password).toLowerCase();
-}
-
-function addAuthParams(method, url, body) {
- if (!window.username) {
- return url;
- }
-
- if (url.indexOf('?') < 0) {
- url += '?';
- }
- else {
- url += '&';
- }
-
- url += '_username=' + window.username;
- if (window._loginDialogSubmitted) {
- url += '&_login=true';
- delete _loginDialogSubmitted;
- }
- var signature = computeSignature(method, url, body);
- url += '&_signature=' + signature;
-
- return url;
-}
-
-function isAdmin() {
- return username === adminUsername;
-}
-
-function ajax(method, url, data, callback, error, timeout) {
- var origUrl = url;
- var origData = data;
-
- if (url.indexOf('?') < 0) {
- url += '?';
- }
- else {
- url += '&';
- }
-
- url += '_=' + new Date().getTime();
-
- var json = false;
- var processData = true;
- if (method == 'POST') {
- if (window.FormData && (data instanceof FormData)) {
- json = false;
- processData = false;
- }
- else if (typeof data == 'object') {
- data = JSON.stringify(data);
- json = true;
- }
- }
- else { /* assuming GET */
- if (data) {
- url += '&' + $.param(data);
- data = null;
- }
- }
-
- url = addAuthParams(method, url, processData ? data : null);
-
- var options = {
- type: method,
- url: url,
- data: data,
- timeout: timeout || 300 * 1000,
- success: function (data) {
- if (data && data.error == 'unauthorized') {
- if (data.prompt) {
- runLoginDialog(function () {
- ajax(method, origUrl, origData, callback, error);
- });
- }
-
- window._loginRetry = true;
- }
- else {
- delete window._loginRetry;
- if (callback) {
- $('body').toggleClass('admin', isAdmin());
- callback(data);
- }
- }
- },
- contentType: json ? 'application/json' : false,
- processData: processData,
- error: error || function (request, options, error) {
- showErrorMessage();
- if (callback) {
- callback();
- }
- }
- };
-
- $.ajax(options);
-}
-
-Object.keys = Object.keys || (function () {
- var hasOwnProperty = Object.prototype.hasOwnProperty;
- var hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString');
- var dontEnums = [
- 'toString',
- 'toLocaleString',
- 'valueOf',
- 'hasOwnProperty',
- 'isPrototypeOf',
- 'propertyIsEnumerable',
- 'constructor'
- ];
- var dontEnumsLength = dontEnums.length;
-
- return function (obj) {
- if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) {
- return [];
- }
-
- var result = [];
- for (var prop in obj) {
- if (hasOwnProperty.call(obj, prop)) {
- result.push(prop);
- }
- }
-
- if (hasDontEnumBug) {
- for (var i = 0; i < dontEnumsLength; i++) {
- if (hasOwnProperty.call(obj, dontEnums[i])) {
- result.push(dontEnums[i]);
- }
- }
- }
-
- return result;
- };
-})();
-
-Object.update = function (dest, source) {
- for (var key in source) {
- if (!source.hasOwnProperty(key)) {
- continue;
- }
-
- dest[key] = source[key];
- }
-};
-
-Array.prototype.indexOf = Array.prototype.indexOf || function (obj) {
- for (var i = 0; i < this.length; i++) {
- if (this[i] === obj) {
- return i;
- }
- }
-
- return -1;
-};
-
-Array.prototype.forEach = Array.prototype.forEach || function (callback, thisArg) {
- for (var i = 0; i < this.length; i++) {
- callback.call(thisArg, this[i], i, this);
- }
-};
-
-Array.prototype.every = Array.prototype.every || function (callback, thisArg) {
- for (var i = 0; i < this.length; i++) {
- if (!callback.call(thisArg, this[i], i, this)) {
- return false;
- }
- }
-
- return true;
-};
-
-Array.prototype.unique = function (callback, thisArg) {
- var uniqueElements = [];
- this.forEach(function (element) {
- if (uniqueElements.indexOf(element, Utils.equals) === -1) {
- uniqueElements.push(element);
- }
- });
-
- return uniqueElements;
-};
-
-Array.prototype.filter = function (func, thisArg) {
- var filtered = [];
- for (var i = 0; i < this.length; i++) {
- if (func.call(thisArg, this[i], i, this)) {
- filtered.push(this[i]);
- }
- }
-
- return filtered;
-};
-
-Array.prototype.map = function (func, thisArg) {
- var mapped = [];
- for (var i = 0; i < this.length; i++) {
- mapped.push(func.call(thisArg, this[i], i, this));
- }
-
- return mapped;
-};
-
-Array.prototype.sortKey = function (keyFunc, reverse) {
- this.sort(function (e1, e2) {
- var k1 = keyFunc(e1);
- var k2 = keyFunc(e2);
-
- if ((k1 < k2 && !reverse) || (k1 > k2 && reverse)) {
- return -1;
- }
- else if ((k1 > k2 && !reverse) || (k1 < k2 && reverse)) {
- return 1;
- }
- else {
- return 0;
- }
- });
-};
-
-String.prototype.startsWith = String.prototype.startsWith || function (str) {
- return (this.substr(0, str.length) === str);
-};
-
-String.prototype.endsWith = String.prototype.endsWith || function (str) {
- return (this.substr(this.length - str.length) === str);
-};
-
-String.prototype.trim = String.prototype.trim || function () {
- return this.replace(new RegExp('^\\s*'), '').replace(new RegExp('\\s*$'), '');
-};
-
-String.prototype.replaceAll = String.prototype.replaceAll || function (oldStr, newStr) {
- var p, s = this;
- while ((p = s.indexOf(oldStr)) >= 0) {
- s = s.substring(0, p) + newStr + s.substring(p + oldStr.length, s.length);
- }
-
- return s.toString();
-};
-
-function getCookie(name) {
- if (document.cookie.length <= 0) {
- return null;
- }
-
- var start = document.cookie.indexOf(name + '=');
- if (start == -1) {
- return null;
- }
-
- var start = start + name.length + 1;
- var end = document.cookie.indexOf(';', start);
- if (end == -1) {
- end = document.cookie.length;
- }
-
- return unescape(document.cookie.substring(start, end));
-}
-
-function setCookie(name, value, days) {
- var date, expires;
- if (days) {
- date = new Date();
- date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
- expires = 'expires=' + date.toGMTString();
- }
- else {
- expires = '';
- }
-
- document.cookie = name + '=' + value + '; ' + expires + '; path=/';
-}
-
-function remCookie(name) {
- document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
-}
-
-function showErrorMessage(message) {
- if (message == null || message == true) {
- message = 'An error occurred. Refreshing is recommended.';
- }
-
- showPopupMessage(message, 'error');
-}
-
-function doLogout() {
- setCookie('username', '_');
- window.location.reload(true);
-}
-
-
-/* UI initialization */
-
-function initUI() {
- /* checkboxes */
- makeCheckBox($('input[type=checkbox].styled'));
-
- /* sliders */
- $('input[type=text].range.styled').each(function () {
- var $this = $(this);
- var $tr = $this.parent().parent();
- var ticks = null;
- var ticksAttr = $tr.attr('ticks');
- if (ticksAttr) {
- ticks = ticksAttr.split('|').map(function (t) {
- var parts = t.split(',');
- if (parts.length < 2) {
- parts.push(parts[0]);
- }
- return {value: Number(parts[0]), label: parts[1]};
- });
- }
- makeSlider($this, Number($tr.attr('min')), Number($tr.attr('max')),
- Number($tr.attr('snap')), ticks, Number($tr.attr('ticksnum')), Number($tr.attr('decimals')), $tr.attr('unit'));
- });
-
- /* progress bars */
- makeProgressBar($('div.progress-bar'));
-
- /* text validators */
- makeTextValidator($('tr[required=true] input[type=text]'), true);
- makeTextValidator($('tr[required=true] input[type=password]'), true);
-
- /* number validators */
- $('input[type=text].number').each(function () {
- var $this = $(this);
- var $tr = $this.parent().parent();
- makeNumberValidator($this, Number($tr.attr('min')), Number($tr.attr('max')),
- Boolean($tr.attr('floating')), Boolean($tr.attr('sign')), Boolean($tr.attr('required')));
- });
-
- /* time validators */
- makeTimeValidator($('input[type=text].time'));
-
- /* custom validators */
- makeCustomValidator($('#deviceNameEntry'), function (value) {
- if (!value) {
- return 'this field is required';
- }
-
- if (!value.toLowerCase().match(new RegExp('^[a-z0-9\-\_\+\ ]*$'))) {
- return "special characters are not allowed in camera's name";
- }
-
- return true;
- }, '');
- makeCustomValidator($('#rootDirectoryEntry'), function (value) {
- if ($('#storageDeviceSelect').val() == 'custom-path' && String(value).trim() == '/') {
- return 'files cannot be created directly on the root of your system';
- }
-
- return true;
- }, '');
- makeCustomValidator($('#emailAddressesEntry'), function (value) {
- if (!value.toLowerCase().match(new RegExp('^[a-z0-9\-\_\+\.\@\^\~\, ]+$'))) {
- return 'enter a list of comma-separated valid email addresses';
- }
-
- return true;
- }, '');
- $('tr[validate] input[type=text]').each(function () {
- var $this = $(this);
- var $tr = $this.parent().parent();
- var required = $tr.attr('required');
- var validate = $tr.attr('validate');
- if (!validate) {
- return;
- }
-
- makeCustomValidator($this, function (value) {
- if (!value && required) {
- return 'this field is required';
- }
-
- if (!value.toLowerCase().match(new RegExp(validate))) {
- return 'enter a valid value';
- }
-
- return true;
- }, '');
- });
-
- /* input value processors */
- makeStrippedInput($('tr[strip=true] input[type=text]'));
- makeStrippedInput($('tr[strip=true] input[type=password]'));
-
- function checkMinimizeSection() {
- var $switch = $(this);
- var $sectionDiv = $switch.parents('div.settings-section-title:eq(0)');
-
- var $minimizeSpan = $switch.parent().find('span.minimize');
- if ($switch.is(':checked') && !$minimizeSpan.hasClass('open')) {
- $minimizeSpan.addClass('open');
- }
- else if (!$switch.is(':checked') && $minimizeSpan.hasClass('open') && !$sectionDiv.attr('minimize-switch-independent')) {
- $minimizeSpan.removeClass('open');
- }
- }
-
- /* ui elements that enable/disable other ui elements */
- $('#motionEyeSwitch').change(updateConfigUi);
- $('#showAdvancedSwitch').change(updateConfigUi);
- $('#storageDeviceSelect').change(updateConfigUi);
- $('#resolutionSelect').change(updateConfigUi);
- $('#leftTextSelect').change(updateConfigUi);
- $('#rightTextSelect').change(updateConfigUi);
- $('#captureModeSelect').change(updateConfigUi);
- $('#autoNoiseDetectSwitch').change(updateConfigUi);
- $('#videoDeviceSwitch').change(checkMinimizeSection).change(updateConfigUi);
- $('#textOverlaySwitch').change(checkMinimizeSection).change(updateConfigUi);
- $('#videoStreamingSwitch').change(checkMinimizeSection).change(updateConfigUi);
- $('#streamingServerResizeSwitch').change(updateConfigUi);
- $('#stillImagesSwitch').change(checkMinimizeSection).change(updateConfigUi);
- $('#preservePicturesSelect').change(updateConfigUi);
- $('#motionDetectionSwitch').change(checkMinimizeSection).change(updateConfigUi);
- $('#motionMoviesSwitch').change(checkMinimizeSection).change(updateConfigUi);
- $('#preserveMoviesSelect').change(updateConfigUi);
- $('#emailNotificationsSwitch').change(updateConfigUi);
- $('#webHookNotificationsSwitch').change(updateConfigUi);
- $('#commandNotificationsSwitch').change(updateConfigUi);
- $('#workingScheduleSwitch').change(checkMinimizeSection).change(updateConfigUi);
-
- $('#mondayEnabledSwitch').change(updateConfigUi);
- $('#tuesdayEnabledSwitch').change(updateConfigUi);
- $('#wednesdayEnabledSwitch').change(updateConfigUi);
- $('#thursdayEnabledSwitch').change(updateConfigUi);
- $('#fridayEnabledSwitch').change(updateConfigUi);
- $('#saturdayEnabledSwitch').change(updateConfigUi);
- $('#sundayEnabledSwitch').change(updateConfigUi);
-
- /* minimizable sections */
- $('span.minimize').click(function () {
- $(this).toggleClass('open');
-
- /* enable the section switch when unminimizing */
- if ($(this).hasClass('open')) {
- var sectionSwitch = $(this).parent().find('input[type=checkbox]');
- var sectionSwitchDiv = $(this).parent().find('div.check-box');
- var sectionDiv = $(this).parents('div.settings-section-title:eq(0)');
- if (sectionSwitch.length && !sectionSwitch.is(':checked') &&
- !sectionSwitchDiv[0]._hideNull && !sectionDiv.attr('minimize-switch-independent')) {
-
- sectionSwitch[0].checked = true;
- sectionSwitch.change();
- }
- }
-
- updateConfigUi();
- });
-
- $('a.settings-section-title').click(function () {
- $(this).parent().find('span.minimize').click();
- });
-
- /* additional configs */
- var seenDependNames = {};
- $('tr[depends]').each(function () {
- var $tr = $(this);
- var depends = $tr.attr('depends').split(' ');
- depends.forEach(function (depend) {
- depend = depend.split('=')[0];
- depend = depend.replace(new RegExp('[^a-zA-Z0-9_]', 'g'), '');
-
- if (depend in seenDependNames) {
- return;
- }
-
- seenDependNames[depend] = true;
-
- var control = $('#' + depend + 'Entry, #' + depend + 'Select, #' + depend + 'Slider, #' + depend + 'Switch');
- control.change(updateConfigUi);
- });
- });
-
- $('#storageDeviceSelect').change(function () {
- $('#rootDirectoryEntry').val('/');
- });
-
- $('#rootDirectoryEntry').change(function () {
- this.value = this.value.trim();
- });
-
- $('#rootDirectoryEntry').change(function () {
- if (this.value.charAt(0) !== '/') {
- this.value = '/' + this.value;
- }
- });
-
- /* fetch & push handlers */
- $('#cameraSelect').focus(function () {
- /* remember the previously selected index */
- this._prevSelectedIndex = this.selectedIndex;
-
- }).change(function () {
- if ($('#cameraSelect').val() === 'add') {
- runAddCameraDialog();
- this.selectedIndex = this._prevSelectedIndex;
- }
- else {
- this._prevSelectedIndex = this.selectedIndex;
- beginProgress([$(this).val()]);
- fetchCurrentCameraConfig(endProgress);
- }
- });
- $('input.main-config, select.main-config, textarea.main-config').change(function () {
- pushMainConfig($(this).parents('tr:eq(0)').attr('reboot') == 'true');
- });
- $('input.camera-config, select.camera-config, textarea.camera-config').change(function () {
- pushCameraConfig($(this).parents('tr:eq(0)').attr('reboot') == 'true');
- });
-
- /* streaming framerate must be >= device framerate */
- $('#framerateSlider').change(function (val) {
- var value = Number($('#framerateSlider').val());
- var streamingValue = Number($('#streamingFramerateSlider').val());
-
- if (streamingValue < value) {
- $('#streamingFramerateSlider').val(value).change();
- }
- });
-
- /* preview controls */
- $('#brightnessSlider').change(function () {pushPreview('brightness');});
- $('#contrastSlider').change(function () {pushPreview('contrast');});
- $('#saturationSlider').change(function () {pushPreview('saturation');});
- $('#hueSlider').change(function () {pushPreview('hue');});
-
- /* apply button */
- $('#applyButton').click(function () {
- if ($(this).hasClass('progress')) {
- return; /* in progress */
- }
-
- doApply();
- });
-
- /* shut down button */
- $('#shutDownButton').click(function () {
- doShutDown();
- });
-
- /* reboot button */
- $('#rebootButton').click(function () {
- doReboot();
- });
-
- /* whenever the window is resized,
- * if a modal dialog is visible, it should be repositioned */
- $(window).resize(updateModalDialogPosition);
-
- /* remove camera button */
- $('div.button.rem-camera-button').click(doRemCamera);
-
- /* logout button */
- $('div.button.logout-button').click(doLogout);
-
- /* autoselect urls in read-only entries */
- $('#streamingSnapshotUrlEntry:text, #streamingMjpgUrlEntry:text, #streamingEmbedUrlEntry:text').click(function () {
- this.select();
- });
-}
-
-
- /* settings */
-
-function openSettings(cameraId) {
- if (cameraId != null) {
- $('#cameraSelect').val(cameraId).change();
- }
-
- $('div.settings').addClass('open').removeClass('closed');
- $('div.page-container').addClass('stretched');
- $('div.settings-top-bar').addClass('open').removeClass('closed');
-
- updateConfigUi();
-}
-
-function closeSettings() {
- hideApply();
- pushConfigs = {};
- pushConfigReboot = false;
-
- $('div.settings').removeClass('open').addClass('closed');
- $('div.page-container').removeClass('stretched');
- $('div.settings-top-bar').removeClass('open').addClass('closed');
-}
-
-function isSettingsOpen() {
- return $('div.settings').hasClass('open');
-}
-
-function updateConfigUi() {
- var objs = $('tr.settings-item, div.advanced-setting, table.advanced-setting, div.settings-section-title, table.settings, ' +
- 'div.check-box.camera-config, div.check-box.main-config');
-
- function markHideLogic() {
- this._hideLogic = true;
- }
-
- function markHideAdvanced() {
- this._hideAdvanced = true;
- }
-
- function markHideMinimized() {
- this._hideMinimized = true;
- }
-
- function unmarkHide() {
- this._hideLogic = false;
- this._hideAdvanced = false;
- this._hideMinimized = false;
- }
-
- objs.each(unmarkHide);
-
- /* hide sliders that, for some reason, don't have a value */
- $('input.range').each(function () {
- if (this.value == '') {
- $(this).parents('tr:eq(0)').each(markHideLogic);
- }
- });
-
- /* minimizable sections */
- $('span.minimize').each(function () {
- var $this = $(this);
- if (!$this.hasClass('open')) {
- $this.parent().next('table.settings').find('tr').each(markHideMinimized);
- }
- });
-
- /* general enable switch */
- var motionEyeEnabled = $('#motionEyeSwitch').get(0).checked;
- if (!motionEyeEnabled) {
- objs.not($('#motionEyeSwitch').parents('div').get(0)).each(markHideLogic);
- }
-
- if ($('#cameraSelect').find('option').length < 2) { /* no camera configured */
- $('#videoDeviceSwitch').parent().each(markHideLogic);
- $('#videoDeviceSwitch').parent().nextAll('div.settings-section-title, table.settings').each(markHideLogic);
- }
-
- if ($('#videoDeviceSwitch')[0].error) { /* config error */
- $('#videoDeviceSwitch').parent().nextAll('div.settings-section-title, table.settings').each(markHideLogic);
- }
-
- /* advanced settings */
- var showAdvanced = $('#showAdvancedSwitch').get(0).checked;
- if (!showAdvanced) {
- $('tr.advanced-setting, div.advanced-setting, table.advanced-setting').each(markHideAdvanced);
- }
-
- /* hide resolution select if no resolution is selected (none matches) */
- if ($('#resolutionSelect')[0].selectedIndex == -1) {
- $('#resolutionSelect').parents('tr:eq(0)').each(markHideLogic);
- }
-
- /* storage device */
- if ($('#storageDeviceSelect').val() !== 'network-share') {
- $('#networkServerEntry').parents('tr:eq(0)').each(markHideLogic);
- $('#networkUsernameEntry').parents('tr:eq(0)').each(markHideLogic);
- $('#networkPasswordEntry').parents('tr:eq(0)').each(markHideLogic);
- $('#networkShareNameEntry').parents('tr:eq(0)').each(markHideLogic);
- }
-
- /* text */
- if ($('#leftTextSelect').val() !== 'custom-text') {
- $('#leftTextEntry').parents('tr:eq(0)').each(markHideLogic);
- }
- if ($('#rightTextSelect').val() !== 'custom-text') {
- $('#rightTextEntry').parents('tr:eq(0)').each(markHideLogic);
- }
-
- /* still images capture mode */
- if ($('#captureModeSelect').val() !== 'interval-snapshots') {
- $('#snapshotIntervalEntry').parents('tr:eq(0)').each(markHideLogic);
- }
-
- /* auto noise level */
- if ($('#autoNoiseDetectSwitch').get(0).checked) {
- $('#noiseLevelSlider').parents('tr:eq(0)').each(markHideLogic);
- }
-
- /* video device switch */
- if (!$('#videoDeviceSwitch').get(0).checked) {
- $('#videoDeviceSwitch').parent().nextAll('div.settings-section-title, table.settings').each(markHideLogic);
- }
-
- /* text overlay switch */
- if (!$('#textOverlaySwitch').get(0).checked) {
- $('#textOverlaySwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
- }
-
- /* video streaming */
- if (!$('#videoStreamingSwitch').get(0).checked) {
- $('#videoStreamingSwitch').parent().next('table.settings').find('tr.settings-item').not('.localhost-streaming').each(markHideLogic);
- }
- if (!$('#streamingServerResizeSwitch').get(0).checked) {
- $('#streamingResolutionSlider').parents('tr:eq(0)').each(markHideLogic);
- }
-
- /* still images switch */
- if (!$('#stillImagesSwitch').get(0).checked) {
- $('#stillImagesSwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
- }
-
- /* preserve pictures */
- if ($('#preservePicturesSelect').val() != '-1') {
- $('#picturesLifetimeEntry').parents('tr:eq(0)').each(markHideLogic);
- }
-
- /* motion detection switch */
- if (!$('#motionDetectionSwitch').get(0).checked) {
- $('#motionDetectionSwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
-
- /* hide the entire motion movies section */
- $('#motionMoviesSwitch').parent().each(markHideLogic);
- $('#motionMoviesSwitch').parent().next('table.settings').each(markHideLogic);
-
- /* hide the entire notifications section */
- $('#emailNotificationsSwitch').parents('table.settings').prev().each(markHideLogic);
- $('#emailNotificationsSwitch').parents('table.settings').each(markHideLogic);
-
- /* hide the entire working schedule section */
- $('#workingScheduleSwitch').parent().each(markHideLogic);
- $('#workingScheduleSwitch').parent().next('table.settings').each(markHideLogic);
- }
-
- /* motion movies switch */
- if (!$('#motionMoviesSwitch').get(0).checked) {
- $('#motionMoviesSwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
- }
-
- /* preserve movies */
- if ($('#preserveMoviesSelect').val() != '-1') {
- $('#moviesLifetimeEntry').parents('tr:eq(0)').each(markHideLogic);
- }
-
- /* event notifications */
- if (!$('#emailNotificationsSwitch').get(0).checked) {
- $('#emailAddressesEntry').parents('tr:eq(0)').each(markHideLogic);
- $('#smtpServerEntry').parents('tr:eq(0)').each(markHideLogic);
- $('#smtpPortEntry').parents('tr:eq(0)').each(markHideLogic);
- $('#smtpAccountEntry').parents('tr:eq(0)').each(markHideLogic);
- $('#smtpPasswordEntry').parents('tr:eq(0)').each(markHideLogic);
- $('#smtpTlsSwitch').parents('tr:eq(0)').each(markHideLogic);
- $('#emailPictureTimeSpanEntry').parents('tr:eq(0)').each(markHideLogic);
- }
-
- if (!$('#webHookNotificationsSwitch').get(0).checked) {
- $('#webHookUrlEntry').parents('tr:eq(0)').each(markHideLogic);
- $('#webHookHttpMethodSelect').parents('tr:eq(0)').each(markHideLogic);
- }
-
- if (!$('#commandNotificationsSwitch').get(0).checked) {
- $('#commandNotificationsEntry').parents('tr:eq(0)').each(markHideLogic);
- }
-
- /* working schedule */
- if (!$('#workingScheduleSwitch').get(0).checked) {
- $('#workingScheduleSwitch').parent().next('table.settings').find('tr.settings-item').each(markHideLogic);
- }
-
- /* additional configs */
- $('tr[depends]').each(function () {
- var $tr = $(this);
- var depends = $tr.attr('depends').split(' ');
- var conditionOk = true;
- depends.every(function (depend) {
- var neg = depend.indexOf('!') >= 0;
- var parts = depend.split('=');
- var boolCheck = parts.length == 1;
- depend = parts[0].replace(new RegExp('[^a-zA-Z0-9_$]', 'g'), '');
-
- var control = $('#' + depend + 'Entry, #' + depend + 'Select, #' + depend + 'Slider');
- var val = false;
- if (control.length) {
- val = control.val();
- }
- else { /* maybe it's a checkbox */
- control = $('#' + depend + 'Switch');
- if (control.length) {
- val = control.get(0).checked;
- }
- }
-
- if (boolCheck) {
- if (neg) {
- val = !val;
- }
-
- if (!val) {
- conditionOk = false;
- return false;
- }
- }
- else { /* comparison */
- var equal = parts[parts.length - 1] == val;
- if (equal == neg) {
- conditionOk = false;
- return false;
- }
- }
-
- return true;
- });
-
- if (!conditionOk) {
- $tr.each(markHideLogic);
- }
- });
-
- /* hide sections that have no visible configs and no switch */
- $('div.settings-section-title').each(function () {
- var $this = $(this);
- var $table = $this.next();
- var controls = $table.find('input, select');
-
- var switchButton = $this.children('div.check-box');
- if (switchButton.length && !switchButton[0]._hideNull) {
- return; /* has visible switch */
- }
-
- for (var i = 0; i < controls.length; i++) {
- var control = $(controls[i]);
- var tr = control.parents('tr:eq(0)')[0];
- if (!tr._hideLogic && !tr._hideAdvanced && !tr._hideNull) {
- return; /* has visible controls */
- }
- }
-
- $table.find('div.settings-item-separator').each(function () {
- $(this).parent().parent().each(markHideLogic);
- });
-
- $this.each(markHideLogic);
- $table.each(markHideLogic);
- });
-
- /* hide useless separators */
- $('div.settings-container table.settings').each(function () {
- var $table = $(this);
-
- /* filter visible rows */
- var visibleTrs = $table.find('tr').filter(function () {
- return !this._hideLogic && !this._hideAdvanced && !this._hideNull;
- }).map(function () {
- var $tr = $(this);
- $tr.isSeparator = $tr.find('div.settings-item-separator').length > 0;
-
- return $tr;
- }).get();
-
- for (var i = 1; i < visibleTrs.length; i++) {
- var $prevTr = visibleTrs[i - 1];
- var $tr = visibleTrs[i];
- if ($prevTr.isSeparator && $tr.isSeparator) {
- $tr.each(markHideLogic);
- }
- }
-
- /* filter visible rows again */
- visibleTrs = $table.find('tr').filter(function () {
- return !this._hideLogic && !this._hideAdvanced && !this._hideNull;
- }).map(function () {
- var $tr = $(this);
- $tr.isSeparator = $tr.find('div.settings-item-separator').length > 0;
-
- return $tr;
- }).get();
-
- if (visibleTrs.length) {
- /* test first row */
- if (visibleTrs[0].isSeparator) {
- visibleTrs[0].each(markHideLogic);
- }
-
- /* test last row */
- if (visibleTrs[visibleTrs.length - 1].isSeparator) {
- visibleTrs[visibleTrs.length - 1].each(markHideLogic);
- }
- }
- });
-
- var weekDays = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
- weekDays.forEach(function (weekDay) {
- var check = $('#' + weekDay + 'EnabledSwitch');
- if (check.get(0).checked) {
- check.parent().find('.time').show();
- }
- else {
- check.parent().find('.time').hide();
- }
- });
-
- objs.each(function () {
- if (this._hideLogic || this._hideAdvanced || this._hideMinimized || this._hideNull /* from dict2ui */) {
- $(this).hide(200);
- }
- else {
- $(this).show(200);
- }
- });
-
- /* re-validate all the validators */
- $('div.settings').find('.validator').each(function () {
- this.validate();
- });
-
- /* update all checkboxes and sliders */
- $('div.settings').find('input[type=checkbox], input.range').each(function () {
- this.update();
- });
-
- /* select the first option for the selects with no current selection */
- $('div.settings').find('select').not('#cameraSelect').each(function () {
- if (this.selectedIndex === -1) {
- this.selectedIndex = 0;
- }
- });
-}
-
-function configUiValid() {
- /* re-validate all the validators */
- $('div.settings').find('.validator').each(function () {
- this.validate();
- });
-
- var valid = true;
- $('div.settings input, select').each(function () {
- if (this.invalid) {
- valid = false;
- return false;
- }
- });
-
- return valid;
-}
-
-function mainUi2Dict() {
- var dict = {
- 'enabled': $('#motionEyeSwitch')[0].checked,
-
- 'show_advanced': $('#showAdvancedSwitch')[0].checked,
- 'admin_username': $('#adminUsernameEntry').val(),
- 'admin_password': $('#adminPasswordEntry').val(),
- 'normal_username': $('#normalUsernameEntry').val(),
- 'normal_password': $('#normalPasswordEntry').val()
- };
-
- /* additional sections */
- $('input[type=checkbox].additional-section.main-config').each(function () {
- dict['_' + this.id.substring(0, this.id.length - 6)] = this.checked;
- });
-
- /* additional configs */
- $('tr.additional-config').each(function () {
- var $this = $(this);
- var control = $this.find('input, select');
-
- if (!control.hasClass('main-config')) {
- return;
- }
-
- var id = control.attr('id');
- var name, value;
- if (id.endsWith('Entry')) {
- name = id.substring(0, id.length - 5);
- value = control.val();
- if (control.hasClass('number')) {
- value = Number(value);
- }
- }
- else if (id.endsWith('Select')) {
- name = id.substring(0, id.length - 6);
- value = control.val();
- }
- else if (id.endsWith('Slider')) {
- name = id.substring(0, id.length - 6);
- value = Number(control.val());
- }
- else if (id.endsWith('Switch')) {
- name = id.substring(0, id.length - 6);
- value = control[0].checked;
- }
-
- dict['_' + name] = value;
- });
-
- return dict;
-}
-
-function dict2MainUi(dict) {
- function markHideIfNull(field, elemId) {
- var elem = $('#' + elemId);
- var sectionDiv = elem.parents('div.settings-section-title:eq(0)');
- var hideNull = (field === true) || (typeof field == 'string' && dict[field] == null);
-
- if (sectionDiv.length) { /* element is a section */
- sectionDiv.find('div.check-box').each(function () {this._hideNull = hideNull;});
- if (hideNull) {
- sectionDiv.find('input[type=checkbox]').each(function () {this.checked = true;});
- }
- }
- else { /* element is a config option */
- elem.parents('tr:eq(0)').each(function () {this._hideNull = hideNull;});
- }
- }
-
- $('#motionEyeSwitch')[0].checked = dict['enabled'];
-
- $('#showAdvancedSwitch')[0].checked = dict['show_advanced']; markHideIfNull('show_advanced', 'showAdvancedSwitch');
- $('#adminUsernameEntry').val(dict['admin_username']); markHideIfNull('admin_username', 'adminUsernameEntry');
- $('#adminPasswordEntry').val(dict['admin_password']); markHideIfNull('admin_password', 'adminPasswordEntry');
- $('#normalUsernameEntry').val(dict['normal_username']); markHideIfNull('normal_username', 'normalUsernameEntry');
- $('#normalPasswordEntry').val(dict['normal_password']); markHideIfNull('normal_password', 'normalPasswordEntry');
-
- /* additional sections */
- $('input[type=checkbox].additional-section.main-config').each(function () {
- var name = this.id.substring(0, this.id.length - 6);
- this.checked = dict[name];
- markHideIfNull(name, this.id);
- });
-
- /* additional configs */
- $('tr.additional-config').each(function () {
- var $this = $(this);
- var control = $this.find('input, select, textarea, div.html');
-
- if (!control.hasClass('main-config')) {
- return;
- }
-
- var id = control.attr('id');
- var name;
- if (id.endsWith('Entry')) {
- name = id.substring(0, id.length - 5);
- control.val(dict['_' + name]);
- }
- else if (id.endsWith('Select')) {
- name = id.substring(0, id.length - 6);
- control.val(dict['_' + name]);
- }
- else if (id.endsWith('Slider')) {
- name = id.substring(0, id.length - 6);
- control.val(dict['_' + name]);
- }
- else if (id.endsWith('Switch')) {
- name = id.substring(0, id.length - 6);
- control[0].checked = dict['_' + name];
- }
- else if (id.endsWith('Html')) {
- name = id.substring(0, id.length - 4);
- control.html(dict['_' + name]);
- }
-
- markHideIfNull('_' + name, id);
- });
-
- updateConfigUi();
-}
-
-function cameraUi2Dict() {
- if ($('#videoDeviceSwitch')[0].error) { /* config error */
- return {
- 'enabled': $('#videoDeviceSwitch')[0].checked,
- };
- }
-
- var dict = {
- 'enabled': $('#videoDeviceSwitch')[0].checked,
- 'name': $('#deviceNameEntry').val(),
- 'proto': $('#deviceTypeEntry')[0].proto,
-
- /* video device */
- 'light_switch_detect': $('#lightSwitchDetectSwitch')[0].checked,
- 'auto_brightness': $('#autoBrightnessSwitch')[0].checked,
- 'rotation': $('#rotationSelect').val(),
- 'framerate': $('#framerateSlider').val(),
- 'extra_options': $('#extraOptionsEntry').val().split(new RegExp('(\n)|(\r\n)|(\n\r)')).map(function (o) {
- if (!o) {
- return null;
- }
-
- o = o.trim();
- if (!o.length) {
- return null;
- }
-
- var parts = o.replace(new RegExp('\\s+', 'g'), ' ').split(' ');
- if (parts.length < 2) {
- return [parts[0], ''];
- }
- else if (parts.length == 2) {
- return parts;
- }
- else {
- return [parts[0], parts.slice(1).join(' ')];
- }
- }).filter(function (e) {return e;}),
-
- /* file storage */
- 'storage_device': $('#storageDeviceSelect').val(),
- 'network_server': $('#networkServerEntry').val(),
- 'network_share_name': $('#networkShareNameEntry').val(),
- 'network_username': $('#networkUsernameEntry').val(),
- 'network_password': $('#networkPasswordEntry').val(),
- 'root_directory': $('#rootDirectoryEntry').val(),
-
- /* text overlay */
- 'text_overlay': $('#textOverlaySwitch')[0].checked,
- 'left_text': $('#leftTextSelect').val(),
- 'custom_left_text': $('#leftTextEntry').val(),
- 'right_text': $('#rightTextSelect').val(),
- 'custom_right_text': $('#rightTextEntry').val(),
-
- /* video streaming */
- 'video_streaming': $('#videoStreamingSwitch')[0].checked,
- 'streaming_framerate': $('#streamingFramerateSlider').val(),
- 'streaming_quality': $('#streamingQualitySlider').val(),
- 'streaming_resolution': $('#streamingResolutionSlider').val(),
- 'streaming_server_resize': $('#streamingServerResizeSwitch')[0].checked,
- 'streaming_port': $('#streamingPortEntry').val(),
- 'streaming_auth_mode': $('#streamingAuthModeSelect').val() || 'disabled', /* compatibility with old motion */
- 'streaming_motion': $('#streamingMotion')[0].checked,
-
- /* still images */
- 'still_images': $('#stillImagesSwitch')[0].checked,
- 'image_file_name': $('#imageFileNameEntry').val(),
- 'image_quality': $('#imageQualitySlider').val(),
- 'capture_mode': $('#captureModeSelect').val(),
- 'snapshot_interval': $('#snapshotIntervalEntry').val(),
- 'preserve_pictures': $('#preservePicturesSelect').val() >= 0 ? $('#preservePicturesSelect').val() : $('#picturesLifetimeEntry').val(),
-
- /* motion detection */
- 'motion_detection': $('#motionDetectionSwitch')[0].checked,
- 'show_frame_changes': $('#showFrameChangesSwitch')[0].checked,
- 'frame_change_threshold': $('#frameChangeThresholdSlider').val(),
- 'auto_noise_detect': $('#autoNoiseDetectSwitch')[0].checked,
- 'noise_level': $('#noiseLevelSlider').val(),
- 'event_gap': $('#eventGapEntry').val(),
- 'pre_capture': $('#preCaptureEntry').val(),
- 'post_capture': $('#postCaptureEntry').val(),
- 'minimum_motion_frames': $('#minimumMotionFramesEntry').val(),
-
- /* motion movies */
- 'motion_movies': $('#motionMoviesSwitch')[0].checked,
- 'movie_file_name': $('#movieFileNameEntry').val(),
- 'movie_quality': $('#movieQualitySlider').val(),
- 'max_movie_length': $('#maxMovieLengthEntry').val(),
- 'preserve_movies': $('#preserveMoviesSelect').val() >= 0 ? $('#preserveMoviesSelect').val() : $('#moviesLifetimeEntry').val(),
-
- /* motion notifications */
- 'email_notifications_enabled': $('#emailNotificationsSwitch')[0].checked,
- 'email_notifications_addresses': $('#emailAddressesEntry').val(),
- 'email_notifications_smtp_server': $('#smtpServerEntry').val(),
- 'email_notifications_smtp_port': $('#smtpPortEntry').val(),
- 'email_notifications_smtp_account': $('#smtpAccountEntry').val(),
- 'email_notifications_smtp_password': $('#smtpPasswordEntry').val(),
- 'email_notifications_smtp_tls': $('#smtpTlsSwitch')[0].checked,
- 'email_notifications_picture_time_span': $('#emailPictureTimeSpanEntry').val(),
- 'web_hook_notifications_enabled': $('#webHookNotificationsSwitch')[0].checked,
- 'web_hook_notifications_url': $('#webHookUrlEntry').val(),
- 'web_hook_notifications_http_method': $('#webHookHttpMethodSelect').val(),
- 'command_notifications_enabled': $('#commandNotificationsSwitch')[0].checked,
- 'command_notifications_exec': $('#commandNotificationsEntry').val(),
-
- /* working schedule */
- 'working_schedule': $('#workingScheduleSwitch')[0].checked,
- 'monday_from': $('#mondayEnabledSwitch')[0].checked ? $('#mondayFromEntry').val() : '',
- 'monday_to':$('#mondayEnabledSwitch')[0].checked ? $('#mondayToEntry').val() : '',
- 'tuesday_from': $('#tuesdayEnabledSwitch')[0].checked ? $('#tuesdayFromEntry').val() : '',
- 'tuesday_to': $('#tuesdayEnabledSwitch')[0].checked ? $('#tuesdayToEntry').val() : '',
- 'wednesday_from': $('#wednesdayEnabledSwitch')[0].checked ? $('#wednesdayFromEntry').val() : '',
- 'wednesday_to': $('#wednesdayEnabledSwitch')[0].checked ? $('#wednesdayToEntry').val() : '',
- 'thursday_from': $('#thursdayEnabledSwitch')[0].checked ? $('#thursdayFromEntry').val() : '',
- 'thursday_to': $('#thursdayEnabledSwitch')[0].checked ? $('#thursdayToEntry').val() : '',
- 'friday_from': $('#fridayEnabledSwitch')[0].checked ? $('#fridayFromEntry').val() : '',
- 'friday_to': $('#fridayEnabledSwitch')[0].checked ? $('#fridayToEntry').val() :'',
- 'saturday_from': $('#saturdayEnabledSwitch')[0].checked ? $('#saturdayFromEntry').val() : '',
- 'saturday_to': $('#saturdayEnabledSwitch')[0].checked ? $('#saturdayToEntry').val() : '',
- 'sunday_from': $('#sundayEnabledSwitch')[0].checked ? $('#sundayFromEntry').val() : '',
- 'sunday_to': $('#sundayEnabledSwitch')[0].checked ? $('#sundayToEntry').val() : '',
- 'working_schedule_type': $('#workingScheduleTypeSelect').val(),
- };
-
- if ($('#resolutionSelect')[0].selectedIndex != -1) {
- dict.resolution = $('#resolutionSelect').val();
- }
-
- if ($('#brightnessSlider').val() !== '') {
- dict.brightness = $('#brightnessSlider').val();
- }
-
- if ($('#contrastSlider').val() !== '') {
- dict.contrast = $('#contrastSlider').val();
- }
-
- if ($('#saturationSlider').val() !== '') {
- dict.saturation = $('#saturationSlider').val();
- }
-
- if ($('#hueSlider').val() !== '') {
- dict.hue = $('#hueSlider').val();
- }
-
- /* additional sections */
- $('input[type=checkbox].additional-section.camera-config').each(function () {
- dict['_' + this.id.substring(0, this.id.length - 6)] = this.checked;
- });
-
- /* additional configs */
- $('tr.additional-config').each(function () {
- var $this = $(this);
- var control = $this.find('input, select');
-
- if (!control.hasClass('camera-config')) {
- return;
- }
-
- var id = control.attr('id');
- var name, value;
- if (id.endsWith('Entry')) {
- name = id.substring(0, id.length - 5);
- value = control.val();
- if (control.hasClass('number')) {
- value = Number(value);
- }
- }
- else if (id.endsWith('Select')) {
- name = id.substring(0, id.length - 6);
- value = control.val();
- }
- else if (id.endsWith('Slider')) {
- name = id.substring(0, id.length - 6);
- value = Number(control.val());
- }
- else if (id.endsWith('Switch')) {
- name = id.substring(0, id.length - 6);
- value = control[0].checked;
- }
-
- dict['_' + name] = value;
- });
-
- return dict;
-}
-
-function dict2CameraUi(dict) {
- if (dict == null) {
- /* errors while getting the configuration */
-
- $('#videoDeviceSwitch')[0].error = true;
- $('#videoDeviceSwitch')[0].checked = true; /* so that the user can explicitly disable the camera */
- updateConfigUi();
-
- return;
- }
- else {
- $('#videoDeviceSwitch')[0].error = false;
- }
-
- function markHideIfNull(field, elemId) {
- var elem = $('#' + elemId);
- var sectionDiv = elem.parents('div.settings-section-title:eq(0)');
- var hideNull = (field === true) || (typeof field == 'string' && dict[field] == null);
-
- if (sectionDiv.length) { /* element is a section */
- sectionDiv.find('div.check-box').each(function () {this._hideNull = hideNull;});
- if (hideNull) {
- sectionDiv.find('input[type=checkbox]').each(function () {this.checked = true;});
- }
- }
- else { /* element is a config option */
- elem.parents('tr:eq(0)').each(function () {this._hideNull = hideNull;});
- }
- }
-
- /* video device */
- var prettyType = '';
- switch (dict['proto']) {
- case 'v4l2':
- prettyType = 'V4L2 Camera';
- break;
-
- case 'netcam':
- prettyType = 'Network Camera';
- break;
-
- case 'motioneye':
- prettyType = 'Remote motionEye Camera';
- break;
-
- case 'mjpeg':
- prettyType = 'Simple MJPEG Camera';
- break;
- }
-
- $('#videoDeviceSwitch')[0].checked = dict['enabled']; markHideIfNull('enabled', 'videoDeviceSwitch');
- $('#deviceNameEntry').val(dict['name']); markHideIfNull('name', 'deviceNameEntry');
- $('#deviceUriEntry').val(dict['device_url']); markHideIfNull('device_url', 'deviceUriEntry');
- $('#deviceTypeEntry').val(prettyType); markHideIfNull(!prettyType, 'deviceTypeEntry');
- $('#deviceTypeEntry')[0].proto = dict['proto'];
- $('#lightSwitchDetectSwitch')[0].checked = dict['light_switch_detect']; markHideIfNull('light_switch_detect', 'lightSwitchDetectSwitch');
- $('#autoBrightnessSwitch')[0].checked = dict['auto_brightness']; markHideIfNull('auto_brightness', 'autoBrightnessSwitch');
-
- $('#brightnessSlider').val(dict['brightness']); markHideIfNull('brightness', 'brightnessSlider');
- $('#contrastSlider').val(dict['contrast']); markHideIfNull('contrast', 'contrastSlider');
- $('#saturationSlider').val(dict['saturation']); markHideIfNull('saturation', 'saturationSlider');
- $('#hueSlider').val(dict['hue']); markHideIfNull('hue', 'hueSlider');
-
- $('#resolutionSelect').html('');
- if (dict['available_resolutions']) {
- dict['available_resolutions'].forEach(function (resolution) {
- $('#resolutionSelect').append('<option value="' + resolution + '">' + resolution + '</option>');
- });
- }
- $('#resolutionSelect').val(dict['resolution']); markHideIfNull('available_resolutions', 'resolutionSelect');
-
- $('#rotationSelect').val(dict['rotation']); markHideIfNull('rotation', 'rotationSelect');
- $('#framerateSlider').val(dict['framerate']); markHideIfNull('framerate', 'framerateSlider');
- $('#extraOptionsEntry').val(dict['extra_options'] ? (dict['extra_options'].map(function (o) {
- return o.join(' ');
- }).join('\r\n')) : ''); markHideIfNull('extra_options', 'extraOptionsEntry');
-
- /* file storage */
- $('#storageDeviceSelect').empty();
- dict['available_disks'] = dict['available_disks'] || [];
- var storageDeviceOptions = {'network-share': true};
- dict['available_disks'].forEach(function (disk) {
- disk.partitions.forEach(function (partition) {
- var target = partition.target.replaceAll('/', '-');
- var option = 'local-disk' + target;
- var label = partition.vendor;
- if (partition.model) {
- label += ' ' + partition.model;
- }
- if (disk.partitions.length > 1) {
- label += '/part' + partition.part_no;
- }
- label += ' (' + partition.target + ')';
-
- storageDeviceOptions[option] = true;
-
- $('#storageDeviceSelect').append('<option value="' + option + '">' + label + '</option>');
- });
- });
- $('#storageDeviceSelect').append('<option value="custom-path">Custom Path</option>');
- if (dict['smb_shares']) {
- $('#storageDeviceSelect').append('<option value="network-share">Network Share</option>');
- }
-
- if (storageDeviceOptions[dict['storage_device']]) {
- $('#storageDeviceSelect').val(dict['storage_device']);
- }
- else {
- $('#storageDeviceSelect').val('custom-path');
- }
- markHideIfNull('storage_device', 'storageDeviceSelect');
- $('#networkServerEntry').val(dict['network_server']); markHideIfNull('network_server', 'networkServerEntry');
- $('#networkShareNameEntry').val(dict['network_share_name']); markHideIfNull('network_share_name', 'networkShareNameEntry');
- $('#networkUsernameEntry').val(dict['network_username']); markHideIfNull('network_username', 'networkUsernameEntry');
- $('#networkPasswordEntry').val(dict['network_password']); markHideIfNull('network_password', 'networkPasswordEntry');
- $('#rootDirectoryEntry').val(dict['root_directory']); markHideIfNull('root_directory', 'rootDirectoryEntry');
- var percent = 0;
- if (dict['disk_total'] != 0) {
- percent = parseInt(dict['disk_used'] * 100 / dict['disk_total']);
- }
-
- $('#diskUsageProgressBar').each(function () {
- this.setProgress(percent);
- this.setText((dict['disk_used'] / 1073741824).toFixed(1) + '/' + (dict['disk_total'] / 1073741824).toFixed(1) + ' GB (' + percent + '%)');
- }); markHideIfNull('disk_used', 'diskUsageProgressBar');
-
- /* text overlay */
- $('#textOverlaySwitch')[0].checked = dict['text_overlay']; markHideIfNull('text_overlay', 'textOverlaySwitch');
- $('#leftTextSelect').val(dict['left_text']); markHideIfNull('left_text', 'leftTextSelect');
- $('#leftTextEntry').val(dict['custom_left_text']); markHideIfNull('custom_left_text', 'leftTextEntry');
- $('#rightTextSelect').val(dict['right_text']); markHideIfNull('right_text', 'rightTextSelect');
- $('#rightTextEntry').val(dict['custom_right_text']); markHideIfNull('custom_right_text', 'rightTextEntry');
-
- /* video streaming */
- $('#videoStreamingSwitch')[0].checked = dict['video_streaming']; markHideIfNull('video_streaming', 'videoStreamingSwitch');
- $('#streamingFramerateSlider').val(dict['streaming_framerate']); markHideIfNull('streaming_framerate', 'streamingFramerateSlider');
- $('#streamingQualitySlider').val(dict['streaming_quality']); markHideIfNull('streaming_quality', 'streamingQualitySlider');
- $('#streamingResolutionSlider').val(dict['streaming_resolution']); markHideIfNull('streaming_resolution', 'streamingResolutionSlider');
- $('#streamingServerResizeSwitch')[0].checked = dict['streaming_server_resize']; markHideIfNull('streaming_server_resize', 'streamingServerResizeSwitch');
- $('#streamingPortEntry').val(dict['streaming_port']); markHideIfNull('streaming_port', 'streamingPortEntry');
- $('#streamingAuthModeSelect').val(dict['streaming_auth_mode']); markHideIfNull('streaming_auth_mode', 'streamingAuthModeSelect');
- $('#streamingMotion')[0].checked = dict['streaming_motion']; markHideIfNull('streaming_motion', 'streamingMotion');
-
- var cameraUrl = location.protocol + '//' + location.host + '/picture/' + dict.id + '/';
-
- var snapshotUrl = null;
- var mjpgUrl = null;
- var embedUrl = null;
-
- if (dict['proto'] == 'mjpeg') {
- mjpgUrl = dict['url'];
- mjpgUrl = mjpgUrl.replace('127.0.0.1', window.location.host.split(':')[0]);
- embedUrl = cameraUrl + 'frame/';
- }
- else {
- snapshotUrl = cameraUrl + 'current/';
- mjpgUrl = location.protocol + '//' + location.host.split(':')[0] + ':' + dict.streaming_port;
- embedUrl = cameraUrl + 'frame/';
- }
-
- if (dict.proto == 'motioneye') {
- /* cannot tell the mjpg streaming url for a remote motionEye camera */
- mjpgUrl = '';
- }
-
- if ($('#normalPasswordEntry').val()) { /* anonymous access is disabled */
- if (snapshotUrl) {
- snapshotUrl = addAuthParams('GET', snapshotUrl);
- }
- }
-
- $('#streamingSnapshotUrlEntry').val(snapshotUrl); markHideIfNull(!snapshotUrl, 'streamingSnapshotUrlEntry');
- $('#streamingMjpgUrlEntry').val(mjpgUrl); markHideIfNull(!mjpgUrl, 'streamingMjpgUrlEntry');
- $('#streamingEmbedUrlEntry').val(embedUrl); markHideIfNull(!embedUrl, 'streamingEmbedUrlEntry');
-
- /* still images */
- $('#stillImagesSwitch')[0].checked = dict['still_images']; markHideIfNull('still_images', 'stillImagesSwitch');
- $('#imageFileNameEntry').val(dict['image_file_name']); markHideIfNull('image_file_name', 'imageFileNameEntry');
- $('#imageQualitySlider').val(dict['image_quality']); markHideIfNull('image_quality', 'imageQualitySlider');
- $('#captureModeSelect').val(dict['capture_mode']); markHideIfNull('capture_mode', 'captureModeSelect');
- $('#snapshotIntervalEntry').val(dict['snapshot_interval']); markHideIfNull('snapshot_interval', 'snapshotIntervalEntry');
- $('#preservePicturesSelect').val(dict['preserve_pictures']);
- if ($('#preservePicturesSelect').val() == null) {
- $('#preservePicturesSelect').val('-1');
- }
- markHideIfNull('preserve_pictures', 'preservePicturesSelect');
- $('#picturesLifetimeEntry').val(dict['preserve_pictures']); markHideIfNull('preserve_pictures', 'picturesLifetimeEntry');
-
- /* motion detection */
- $('#motionDetectionSwitch')[0].checked = dict['motion_detection']; markHideIfNull('motion_detection', 'motionDetectionSwitch');
- $('#showFrameChangesSwitch')[0].checked = dict['show_frame_changes']; markHideIfNull('show_frame_changes', 'showFrameChangesSwitch');
- $('#frameChangeThresholdSlider').val(dict['frame_change_threshold']); markHideIfNull('frame_change_threshold', 'frameChangeThresholdSlider');
- $('#autoNoiseDetectSwitch')[0].checked = dict['auto_noise_detect']; markHideIfNull('auto_noise_detect', 'autoNoiseDetectSwitch');
- $('#noiseLevelSlider').val(dict['noise_level']); markHideIfNull('noise_level', 'noiseLevelSlider');
- $('#eventGapEntry').val(dict['event_gap']); markHideIfNull('event_gap', 'eventGapEntry');
- $('#preCaptureEntry').val(dict['pre_capture']); markHideIfNull('pre_capture', 'preCaptureEntry');
- $('#postCaptureEntry').val(dict['post_capture']); markHideIfNull('post_capture', 'postCaptureEntry');
- $('#minimumMotionFramesEntry').val(dict['minimum_motion_frames']); markHideIfNull('minimum_motion_frames', 'minimumMotionFramesEntry');
-
- /* motion movies */
- $('#motionMoviesSwitch')[0].checked = dict['motion_movies']; markHideIfNull('motion_movies', 'motionMoviesSwitch');
- $('#movieFileNameEntry').val(dict['movie_file_name']); markHideIfNull('movie_file_name', 'movieFileNameEntry');
- $('#movieQualitySlider').val(dict['movie_quality']); markHideIfNull('movie_quality', 'movieQualitySlider');
- $('#maxMovieLengthEntry').val(dict['max_movie_length']); markHideIfNull('max_movie_length', 'maxMovieLengthEntry');
- $('#preserveMoviesSelect').val(dict['preserve_movies']);
- if ($('#preserveMoviesSelect').val() == null) {
- $('#preserveMoviesSelect').val('-1');
- }
- markHideIfNull('preserve_movies', 'preserveMoviesSelect');
- $('#moviesLifetimeEntry').val(dict['preserve_movies']); markHideIfNull('preserve_movies', 'moviesLifetimeEntry');
-
- /* motion notifications */
- $('#emailNotificationsSwitch')[0].checked = dict['email_notifications_enabled']; markHideIfNull('email_notifications_enabled', 'emailNotificationsSwitch');
- $('#emailAddressesEntry').val(dict['email_notifications_addresses']);
- $('#smtpServerEntry').val(dict['email_notifications_smtp_server']);
- $('#smtpPortEntry').val(dict['email_notifications_smtp_port']);
- $('#smtpAccountEntry').val(dict['email_notifications_smtp_account']);
- $('#smtpPasswordEntry').val(dict['email_notifications_smtp_password']);
- $('#smtpTlsSwitch')[0].checked = dict['email_notifications_smtp_tls'];
- $('#emailPictureTimeSpanEntry').val(dict['email_notifications_picture_time_span']);
- $('#webHookNotificationsSwitch')[0].checked = dict['web_hook_notifications_enabled']; markHideIfNull('web_hook_notifications_enabled', 'webHookNotificationsSwitch');
- $('#webHookUrlEntry').val(dict['web_hook_notifications_url']);
- $('#webHookHttpMethodSelect').val(dict['web_hook_notifications_http_method']);
- $('#commandNotificationsSwitch')[0].checked = dict['command_notifications_enabled']; markHideIfNull('command_notifications_enabled', 'commandNotificationsSwitch');
- $('#commandNotificationsEntry').val(dict['command_notifications_exec']);
-
- /* working schedule */
- $('#workingScheduleSwitch')[0].checked = dict['working_schedule']; markHideIfNull('working_schedule', 'workingScheduleSwitch');
- $('#mondayEnabledSwitch')[0].checked = Boolean(dict['monday_from'] && dict['monday_to']); markHideIfNull('monday_from', 'mondayEnabledSwitch');
- $('#mondayFromEntry').val(dict['monday_from']); markHideIfNull('monday_from', 'mondayFromEntry');
- $('#mondayToEntry').val(dict['monday_to']); markHideIfNull('monday_to', 'mondayToEntry');
-
- $('#tuesdayEnabledSwitch')[0].checked = Boolean(dict['tuesday_from'] && dict['tuesday_to']); markHideIfNull('tuesday_from', 'tuesdayEnabledSwitch');
- $('#tuesdayFromEntry').val(dict['tuesday_from']); markHideIfNull('tuesday_from', 'tuesdayFromEntry');
- $('#tuesdayToEntry').val(dict['tuesday_to']); markHideIfNull('tuesday_to', 'tuesdayToEntry');
-
- $('#wednesdayEnabledSwitch')[0].checked = Boolean(dict['wednesday_from'] && dict['wednesday_to']); markHideIfNull('wednesday_from', 'wednesdayEnabledSwitch');
- $('#wednesdayFromEntry').val(dict['wednesday_from']); markHideIfNull('wednesday_from', 'wednesdayFromEntry');
- $('#wednesdayToEntry').val(dict['wednesday_to']); markHideIfNull('wednesday_to', 'wednesdayToEntry');
-
- $('#thursdayEnabledSwitch')[0].checked = Boolean(dict['thursday_from'] && dict['thursday_to']); markHideIfNull('thursday_from', 'thursdayEnabledSwitch');
- $('#thursdayFromEntry').val(dict['thursday_from']); markHideIfNull('thursday_from', 'thursdayFromEntry');
- $('#thursdayToEntry').val(dict['thursday_to']); markHideIfNull('thursday_to', 'thursdayToEntry');
-
- $('#fridayEnabledSwitch')[0].checked = Boolean(dict['friday_from'] && dict['friday_to']); markHideIfNull('friday_from', 'fridayEnabledSwitch');
- $('#fridayFromEntry').val(dict['friday_from']); markHideIfNull('friday_from', 'fridayFromEntry');
- $('#fridayToEntry').val(dict['friday_to']); markHideIfNull('friday_to', 'fridayToEntry');
-
- $('#saturdayEnabledSwitch')[0].checked = Boolean(dict['saturday_from'] && dict['saturday_to']); markHideIfNull('saturday_from', 'saturdayEnabledSwitch');
- $('#saturdayFromEntry').val(dict['saturday_from']); markHideIfNull('saturday_from', 'saturdayFromEntry');
- $('#saturdayToEntry').val(dict['saturday_to']); markHideIfNull('saturday_to', 'saturdayToEntry');
-
- $('#sundayEnabledSwitch')[0].checked = Boolean(dict['sunday_from'] && dict['sunday_to']); markHideIfNull('sunday_from', 'sundayEnabledSwitch');
- $('#sundayFromEntry').val(dict['sunday_from']); markHideIfNull('sunday_from', 'sundayFromEntry');
- $('#sundayToEntry').val(dict['sunday_to']); markHideIfNull('sunday_to', 'sundayToEntry');
- $('#workingScheduleTypeSelect').val(dict['working_schedule_type']); markHideIfNull('working_schedule_type', 'workingScheduleTypeSelect');
-
- /* additional sections */
- $('input[type=checkbox].additional-section.main-config').each(function () {
- var name = this.id.substring(0, this.id.length - 6);
- this.checked = dict[name];
- markHideIfNull(name, this.id);
- });
-
- /* additional configs */
- $('tr.additional-config').each(function () {
- var $this = $(this);
- var control = $this.find('input, select, textarea, div.html');
-
- if (!control.hasClass('camera-config')) {
- return;
- }
-
- var id = control.attr('id');
- var name;
- if (id.endsWith('Entry')) {
- name = id.substring(0, id.length - 5);
- control.val(dict['_' + name]);
- }
- else if (id.endsWith('Select')) {
- name = id.substring(0, id.length - 6);
- control.val(dict['_' + name]);
- }
- else if (id.endsWith('Slider')) {
- name = id.substring(0, id.length - 6);
- control.val(dict['_' + name]);
- }
- else if (id.endsWith('Switch')) {
- name = id.substring(0, id.length - 6);
- control[0].checked = dict['_' + name];
- }
- else if (id.endsWith('Html')) {
- name = id.substring(0, id.length - 4);
- control.html(dict['_' + name]);
- }
-
- markHideIfNull('_' + name, id);
- });
-
- updateConfigUi();
-}
-
-
- /* progress */
-
-function beginProgress(cameraIds) {
- if (inProgress) {
- return; /* already in progress */
- }
-
- inProgress = true;
-
- /* replace the main page message with a progress indicator */
- $('div.add-camera-message').replaceWith('<img class="main-loading-progress" src="' + staticUrl + 'img/main-loading-progress.gif">');
-
- /* show the apply button progress indicator */
- $('#applyButton').html('<img class="apply-progress" src="' + staticUrl + 'img/apply-progress.gif">');
-
- /* show the camera progress indicators */
- if (cameraIds) {
- cameraIds.forEach(function (cameraId) {
- $('div.camera-frame#camera' + cameraId + ' div.camera-progress').addClass('visible');
- });
- }
- else {
- $('div.camera-progress').addClass('visible');
- }
-
- /* remove the settings progress lock */
- $('div.settings-progress').css('width', '100%').css('opacity', '0.9');
-}
-
-function endProgress() {
- if (!inProgress) {
- return; /* not in progress */
- }
-
- inProgress = false;
-
- /* deal with the apply button */
- if (Object.keys(pushConfigs).length === 0) {
- hideApply();
- }
- else {
- showApply();
- }
-
- /* hide the settings progress lock */
- $('div.settings-progress').css('opacity', '0');
-
- /* hide the camera progress indicator */
- $('div.camera-progress').removeClass('visible');
-
- setTimeout(function () {
- $('div.settings-progress').css('width', '0px');
- }, 500);
-}
-
-function downloadFile(uri) {
- uri = baseUri + uri;
-
- var url = window.location.href;
- var parts = url.split('/');
- url = parts.slice(0, 3).join('/') + uri;
- url = addAuthParams('GET', url);
-
- /* download the file by creating a temporary iframe */
- var frame = $('<iframe style="display: none;"></iframe>');
- frame.attr('src', url);
- $('body').append(frame);
-}
-
-function uploadFile(uri, input, callback) {
- if (!window.FormData) {
- showErrorMessage("Your browser doesn't implement this function!");s
- callback();
- }
-
- var formData = new FormData();
- var files = input[0].files;
- formData.append('files', files[0], files[0].name);
-
- ajax('POST', uri, formData, callback);
-}
-
-
- /* apply button */
-
-function showApply() {
- var applyButton = $('#applyButton');
-
- applyButton.html('Apply');
- applyButton.css('display', 'inline-block');
- applyButton.removeClass('progress');
- setTimeout(function () {
- applyButton.css('opacity', '1');
- }, 10);
-}
-
-function hideApply() {
- var applyButton = $('#applyButton');
-
- applyButton.css('opacity', '0');
- applyButton.removeClass('progress');
-
- setTimeout(function () {
- applyButton.css('display', 'none');
- }, 500);
-}
-
-function isApplyVisible() {
- var applyButton = $('#applyButton');
-
- return applyButton.is(':visible');
-}
-
-function doApply() {
- if (!configUiValid()) {
- runAlertDialog('Make sure all the configuration options are valid!');
- return;
- }
-
- function actualApply() {
- /* gather the affected motion instances */
- var affectedInstances = {};
- Object.keys(pushConfigs).forEach(function (key) {
- var config = pushConfigs[key];
- if (key === 'main') {
- return;
- }
-
- var instance;
- if (config.proto == 'netcam' || config.proto == 'v4l2') {
- instance = '';
- }
- else if (config.proto == 'motioneye') { /* motioneye */
- instance = config.host || '';
- if (config.port) {
- instance += ':' + config.port;
- }
- }
-
- affectedInstances[instance] = true;
- });
- affectedInstances = Object.keys(affectedInstances);
-
- /* compute the affected camera ids */
- var cameraIdsByInstance = getCameraIdsByInstance();
- var affectedCameraIds = [];
-
- affectedInstances.forEach(function (instance) {
- affectedCameraIds = affectedCameraIds.concat(cameraIdsByInstance[instance] || []);
- });
-
- beginProgress(affectedCameraIds);
- affectedCameraIds.forEach(function (cameraId) {
- refreshDisabled[cameraId] |= 0;
- refreshDisabled[cameraId]++;
- });
-
- ajax('POST', baseUri + 'config/0/set/', pushConfigs, function (data) {
- affectedCameraIds.forEach(function (cameraId) {
- refreshDisabled[cameraId]--;
- });
-
- if (data == null || data.error) {
- endProgress();
- showErrorMessage(data && data.error);
- return;
- }
-
- if (data.reboot) {
- var count = 0;
- function checkServerReboot() {
- ajax('GET', baseUri + 'config/0/get/', null,
- function () {
- window.location.reload(true);
- },
- function () {
- if (count < 25) {
- count += 1;
- setTimeout(checkServerReboot, 2000);
- }
- else {
- window.location.reload(true);
- }
- }
- );
- }
-
- setTimeout(checkServerReboot, 15000);
-
- return;
- }
-
- if (data.reload) {
- window.location.reload(true);
- return;
- }
-
- /* update the camera name in the device select
- * and frame title bar */
- Object.keys(pushConfigs).forEach(function (key) {
- var config = pushConfigs[key];
- if (config.key !== 'main') {
- $('#cameraSelect').find('option[value=' + key + ']').html(config.name);
- }
-
- $('#camera' + key).find('span.camera-name').html(config.name);
- });
-
- pushConfigs = {};
- pushConfigReboot = false;
- endProgress();
- recreateCameraFrames(); /* a camera could have been disabled */
- });
- }
-
- if (pushConfigReboot) {
- runConfirmDialog('This will reboot the system. Continue?', function () {
- actualApply();
- });
- }
- else {
- actualApply();
- }
-}
-
-function doShutDown() {
- runConfirmDialog('Really shut down?', function () {
- ajax('POST', baseUri + 'power/shutdown/');
- setTimeout(function () {
- refreshInterval = 1000000;
- showModalDialog('<div class="modal-progress"></div>');
-
- function checkServer() {
- ajax('GET', baseUri, null,
- function () {
- setTimeout(checkServer, 1000);
- },
- function () {
- showModalDialog('Powered Off');
- setTimeout(function () {
- $('div.modal-glass').animate({'opacity': '1', 'background-color': '#212121'}, 200);
- },100);
- },
- 10000 /* timeout = 10s */
- );
- }
-
- checkServer();
- }, 10);
- });
-}
-
-function doReboot() {
- runConfirmDialog('Really reboot?', function () {
- ajax('POST', baseUri + 'power/reboot/');
- setTimeout(function () {
- refreshInterval = 1000000;
- showModalDialog('<div class="modal-progress"></div>');
- var shutDown = false;
-
- function checkServer() {
- ajax('GET', baseUri, null,
- function () {
- if (!shutDown) {
- setTimeout(checkServer, 1000);
- }
- else {
- runAlertDialog('The system has been rebooted!', function () {
- window.location.reload(true);
- });
- }
- },
- function () {
- shutDown = true; /* the first error indicates the system was shut down */
- setTimeout(checkServer, 1000);
- },
- 5 * 1000 /* timeout = 5s */
- );
- }
-
- checkServer();
- }, 10);
- });
-}
-
-function doRemCamera() {
- if (Object.keys(pushConfigs).length) {
- return runAlertDialog('Please apply the modified settings first!');
- }
-
- var cameraId = $('#cameraSelect').val();
- if (cameraId == null || cameraId === 'add') {
- runAlertDialog('No camera to remove!');
- return;
- }
-
- var deviceName = $('#cameraSelect').find('option[value=' + cameraId + ']').text();
-
- runConfirmDialog('Remove camera ' + deviceName + '?', function () {
- /* disable further refreshing of this camera */
- var img = $('div.camera-frame#camera' + cameraId).find('img.camera');
- if (img.length) {
- img[0].loading = 1;
- }
-
- beginProgress();
- ajax('POST', baseUri + 'config/' + cameraId + '/rem/', null, function (data) {
- if (data == null || data.error) {
- endProgress();
- showErrorMessage(data && data.error);
- return;
- }
-
- fetchCurrentConfig(endProgress);
- });
- });
-}
-
-function doUpdate() {
- if (Object.keys(pushConfigs).length) {
- return runAlertDialog('Please apply the modified settings first!');
- }
-
- showModalDialog('<div class="modal-progress"></div>');
- ajax('GET', baseUri + 'update/', null, function (data) {
- if (data.update_version == null) {
- runAlertDialog('motionEye is up to date (current version: ' + data.current_version + ')');
- }
- else {
- runConfirmDialog('New version available: ' + data.update_version + '. Update?', function () {
- refreshInterval = 1000000;
- showModalDialog('<div style="text-align: center;"><span>Updating. This may take a few minutes.</span><div class="modal-progress"></div></div>');
- ajax('POST', baseUri + 'update/?version=' + data.update_version, null, function () {
- var count = 0;
- function checkServer() {
- ajax('GET', baseUri + 'config/0/get/', null,
- function () {
- runAlertDialog('motionEye was successfully updated!', function () {
- window.location.reload(true);
- });
- },
- function () {
- if (count < 60) {
- count += 1;
- setTimeout(checkServer, 5000);
- }
- else {
- runAlertDialog('Update failed!', function () {
- window.location.reload(true);
- });
- }
- }
- );
- }
-
- setTimeout(checkServer, 10000);
-
- }, function (e) { /* error */
- runAlertDialog('The update process has failed!', function () {
- window.location.reload(true);
- });
- });
-
- return false; /* prevents hiding the modal container */
- });
- }
- });
-}
-
-function doBackup() {
- downloadFile('config/backup/');
-}
-
-function doRestore() {
- var content =
- $('<table class="restore-dialog">' +
- '<tr>' +
- '<td class="dialog-item-label"><span class="dialog-item-label">Backup File</span></td>' +
- '<td class="dialog-item-value"><form><input type="file" class="styled" id="fileInput"></form></td>' +
- '<td><span class="help-mark" title="the backup file you have previously downloaded">?</span></td>' +
- '</tr>' +
- '</table>');
-
- /* collect ui widgets */
- var fileInput = content.find('#fileInput');
-
- /* make validators */
- makeFileValidator(fileInput, true);
-
- function uiValid() {
- /* re-validate all the validators */
- content.find('.validator').each(function () {
- this.validate();
- });
-
- var valid = true;
- var query = content.find('input, select');
- query.each(function () {
- if (this.invalid) {
- valid = false;
- return false;
- }
- });
-
- return valid;
- }
-
- runModalDialog({
- title: 'Restore Configuration',
- closeButton: true,
- buttons: 'okcancel',
- content: content,
- onOk: function () {
- if (!uiValid(true)) {
- return false;
- }
-
- refreshInterval = 1000000;
-
- setTimeout(function () {
- showModalDialog('<div style="text-align: center;"><span>Restoring configuration...</span><div class="modal-progress"></div></div>');
- uploadFile(baseUri + 'config/restore/', fileInput, function (data) {
- if (data && data.ok) {
- var count = 0;
- function checkServer() {
- ajax('GET', baseUri + 'config/0/get/', null,
- function () {
- runAlertDialog('The configuration has been restored!', function () {
- window.location.reload(true);
- });
- },
- function () {
- if (count < 25) {
- count += 1;
- setTimeout(checkServer, 2000);
- }
- else {
- runAlertDialog('Failed to restore the configuration!', function () {
- window.location.reload(true);
- });
- }
- }
- );
- }
-
- if (data.reboot) {
- setTimeout(checkServer, 10000);
- }
- else {
- setTimeout(function () {
- window.location.reload();
- }, 5000);
- }
- }
- else {
- hideModalDialog();
- showErrorMessage('Failed to restore the configuration!');
- }
- });
- }, 10);
- }
- });
-}
-
-function doDownloadZipped(cameraId, groupKey) {
- showModalDialog('<div class="modal-progress"></div>', null, null, true);
- ajax('GET', baseUri + 'picture/' + cameraId + '/zipped/' + groupKey + '/', null, function (data) {
- if (data.error) {
- hideModalDialog(); /* progress */
- showErrorMessage(data.error);
- }
- else {
- hideModalDialog(); /* progress */
- downloadFile('picture/' + cameraId + '/zipped/' + groupKey + '/?key=' + data.key);
- }
- });
-}
-
-function doDeleteFile(uri, callback) {
- var url = window.location.href;
- var parts = url.split('/');
- url = parts.slice(0, 3).join('/') + uri;
-
- runConfirmDialog('Really delete this file?', function () {
- showModalDialog('<div class="modal-progress"></div>', null, null, true);
- ajax('POST', url, null, function (data) {
- hideModalDialog(); /* progress */
- hideModalDialog(); /* confirm */
-
- if (data == null || data.error) {
- showErrorMessage(data && data.error);
- return;
- }
-
- if (callback) {
- callback();
- }
- });
-
- return false;
- }, {stack: true});
-}
-
-function doDeleteAllFiles(mediaType, cameraId, groupKey, callback) {
- runConfirmDialog('Really delete all ' + mediaType + 's in ' + groupKey + '?', function () {
- showModalDialog('<div class="modal-progress"></div>', null, null, true);
- ajax('POST', baseUri + mediaType + '/' + cameraId + '/delete_all/' + groupKey + '/', null, function (data) {
- hideModalDialog(); /* progress */
- hideModalDialog(); /* confirm */
-
- if (data == null || data.error) {
- showErrorMessage(data && data.error);
- return;
- }
-
- if (callback) {
- callback();
- }
- });
-
- return false;
- }, {stack: true});
-}
-
-
- /* fetch & push */
-
-function fetchCurrentConfig(onFetch) {
- function fetchCameraList() {
- /* fetch the camera list */
- ajax('GET', baseUri + 'config/list/', null, function (data) {
- if (data == null || data.error) {
- showErrorMessage(data && data.error);
- data = {cameras: []};
- if (onFetch) {
- onFetch(null);
- }
- }
-
- var i, cameras = data.cameras;
-
- if (isAdmin()) {
- var cameraSelect = $('#cameraSelect');
- cameraSelect.html('');
- for (i = 0; i < cameras.length; i++) {
- var camera = cameras[i];
- cameraSelect.append('<option value="' + camera['id'] + '">' + camera['name'] + '</option>');
- }
- cameraSelect.append('<option value="add">add camera...</option>');
-
- var enabledCameras = cameras.filter(function (camera) {return camera['enabled'];});
- if (enabledCameras.length > 0) { /* prefer the first enabled camera */
- cameraSelect[0].selectedIndex = cameras.indexOf(enabledCameras[0]);
- fetchCurrentCameraConfig(onFetch);
- }
- else if (cameras.length) { /* only disabled cameras */
- cameraSelect[0].selectedIndex = 0;
- fetchCurrentCameraConfig(onFetch);
- }
- else { /* no camera at all */
- cameraSelect[0].selectedIndex = -1;
-
- if (onFetch) {
- onFetch(data);
- }
- }
-
- updateConfigUi();
- }
- else { /* normal user */
- if (!cameras.length) {
- /* normal user with no cameras doesn't make too much sense - force login */
- doLogout();
- }
-
- if (onFetch) {
- onFetch(data);
- }
- }
-
- var mainLoadingProgressImg = $('img.main-loading-progress');
- if (mainLoadingProgressImg.length) {
- mainLoadingProgressImg.animate({'opacity': 0}, 200, function () {
- recreateCameraFrames(cameras);
- mainLoadingProgressImg.remove();
- });
- }
- else {
- recreateCameraFrames(cameras);
- }
- });
- }
-
- /* add a progress indicator */
- $('div.page-container').append('<img class="main-loading-progress" src="' + staticUrl + 'img/main-loading-progress.gif">');
-
- if (isAdmin()) {
- /* fetch the main configuration */
- ajax('GET', baseUri + 'config/main/get/', null, function (data) {
- if (data == null || data.error) {
- showErrorMessage(data && data.error);
- return;
- }
-
- dict2MainUi(data);
- fetchCameraList();
- });
- }
- else {
- fetchCameraList();
- }
-}
-
-function fetchCurrentCameraConfig(onFetch) {
- var cameraId = $('#cameraSelect').val();
- if (cameraId != null) {
- ajax('GET', baseUri + 'config/' + cameraId + '/get/?force=true', null, function (data) {
- if (data == null || data.error) {
- showErrorMessage(data && data.error);
- dict2CameraUi(null);
- if (onFetch) {
- onFetch(null);
- }
-
- return;
- }
-
- dict2CameraUi(data);
- if (onFetch) {
- onFetch(data);
- }
- });
- }
- else {
- dict2CameraUi({});
- if (onFetch) {
- onFetch({});
- }
- }
-}
-
-function pushMainConfig(reboot) {
- var mainConfig = mainUi2Dict();
-
- pushConfigReboot = pushConfigReboot || reboot;
- pushConfigs['main'] = mainConfig;
- if (!isApplyVisible()) {
- showApply();
- }
-}
-
-function pushCameraConfig(reboot) {
- var cameraConfig = cameraUi2Dict();
- var cameraId = $('#cameraSelect').val();
-
- if (!cameraId) {
- return; /* event triggered without a selected camera */
- }
-
- pushConfigReboot = pushConfigReboot || reboot;
- pushConfigs[cameraId] = cameraConfig;
- if (!isApplyVisible()) {
- showApply();
- }
-
- /* also update the config stored in the camera frame div */
- var cameraFrame = $('div.camera-frame#camera' + cameraId);
- if (cameraFrame.length) {
- Object.update(cameraFrame[0].config, cameraConfig);
- }
-}
-
-function pushPreview(control) {
- var cameraId = $('#cameraSelect').val();
-
- var brightness = $('#brightnessSlider').val();
- var contrast= $('#contrastSlider').val();
- var saturation = $('#saturationSlider').val();
- var hue = $('#hueSlider').val();
-
- var data = {};
-
- if (brightness !== '' && (!control || control == 'brightness')) {
- data.brightness = brightness;
- }
-
- if (contrast !== '' && (!control || control == 'contrast')) {
- data.contrast = contrast;
- }
-
- if (saturation !== '' && (!control || control == 'saturation')) {
- data.saturation = saturation;
- }
-
- if (hue !== '' && (!control || control == 'hue')) {
- data.hue = hue;
- }
-
- refreshDisabled[cameraId] |= 0;
- refreshDisabled[cameraId]++;
-
- ajax('POST', baseUri + 'config/' + cameraId + '/set_preview/', data, function (data) {
- refreshDisabled[cameraId]--;
-
- if (data == null || data.error) {
- showErrorMessage(data && data.error);
- return;
- }
- });
-}
-
-function getCameraIdsByInstance() {
- /* a motion instance is identified by the (host, port) pair;
- * the local instance has both the host and the port set to empty string */
-
- var cameraIdsByInstance = {};
- $('div.camera-frame').each(function () {
- var instance;
- if (this.config.proto == 'netcam' || this.config.proto == 'v4l2') {
- instance = '';
- }
- else if (this.config.proto == 'motioneye') {
- instance = this.config.host || '';
- if (this.config.port) {
- instance += ':' + this.config.port;
- }
- }
- else { /* assuming simple mjpeg camera */
- return;
- }
-
- (cameraIdsByInstance[instance] = cameraIdsByInstance[instance] || []).push(this.config.id);
- });
-
- return cameraIdsByInstance;
-}
-
-
- /* dialogs */
-
-function runAlertDialog(message, onOk, options) {
- var params = {
- title: message,
- buttons: 'ok',
- onOk: onOk
- };
-
- if (options) {
- Object.update(params, options);
- }
-
- runModalDialog(params);
-}
-
-function runConfirmDialog(message, onYes, options) {
- var params = {
- title: message,
- buttons: 'yesno',
- onYes: onYes
- };
-
- if (options) {
- Object.update(params, options);
- }
-
- runModalDialog(params);
-}
-
-function runLoginDialog(retry) {
- /* a workaround so that browsers will remember the credentials */
- var tempFrame = $('<iframe name="temp" id="temp" style="display: none;"></iframe>');
- $('body').append(tempFrame);
-
- var form =
- $('<form action="' + baseUri + 'login/" target="temp" method="POST"><table class="login-dialog">' +
- '<tr>' +
- '<td class="login-dialog-error" colspan="100"></td>' +
- '</tr>' +
- '<tr>' +
- '<td class="dialog-item-label"><span class="dialog-item-label">Username</span></td>' +
- '<td class="dialog-item-value"><input type="text" name="username" class="styled" id="usernameEntry"></td>' +
- '</tr>' +
- '<tr>' +
- '<td class="dialog-item-label"><span class="dialog-item-label">Password</span></td>' +
- '<td class="dialog-item-value"><input type="password" name="password" class="styled" id="passwordEntry"></td>' +
- '<input type="submit" style="display: none;" name="login" value="login">' +
- '</tr>' +
- '</table></form>');
-
- var usernameEntry = form.find('#usernameEntry');
- var passwordEntry = form.find('#passwordEntry');
- var errorTd = form.find('td.login-dialog-error');
-
- if (window._loginRetry) {
- errorTd.css('display', 'table-cell');
- errorTd.html('Invalid credentials.');
- }
-
- var params = {
- title: 'Login',
- content: form,
- buttons: [
- {caption: 'Cancel', isCancel: true, click: function () {
- tempFrame.remove();
- }},
- {caption: 'Login', isDefault: true, click: function () {
- window.username = usernameEntry.val();
- window.password = passwordEntry.val();
- window._loginDialogSubmitted = true;
-
- setCookie('username', window.username);
-
- form.submit();
- setTimeout(function () {
- tempFrame.remove();
- }, 5000);
-
- if (retry) {
- retry();
- }
- }}
- ],
- };
-
- runModalDialog(params);
-}
-
-function runPictureDialog(entries, pos, mediaType) {
- var content = $('<div class="picture-dialog-content"></div>');
-
- var img = $('<img class="picture-dialog-content">');
- content.append(img);
-
- var prevArrow = $('<div class="picture-dialog-prev-arrow button mouse-effect" title="previous picture"></div>');
- content.append(prevArrow);
-
- var nextArrow = $('<div class="picture-dialog-next-arrow button mouse-effect" title="next picture"></div>');
- content.append(nextArrow);
-
- var progressImg = $('<img class="picture-dialog-progress" src="' + staticUrl + 'img/modal-progress.gif">');
-
- function updatePicture() {
- var entry = entries[pos];
-
- var windowWidth = $(window).width();
- var windowHeight = $(window).height();
- var widthCoef = windowWidth < 1000 ? 0.8 : 0.5;
- var heightCoef = 0.75;
-
- var width = parseInt(windowWidth * widthCoef);
- var height = parseInt(windowHeight * heightCoef);
-
- prevArrow.css('display', 'none');
- nextArrow.css('display', 'none');
- img.parent().append(progressImg);
- updateModalDialogPosition();
- progressImg.css('left', (img.parent().width() - progressImg.width()) / 2);
- progressImg.css('top', (img.parent().height() - progressImg.height()) / 2);
-
- img.attr('src', addAuthParams('GET', baseUri + mediaType + '/' + entry.cameraId + '/preview' + entry.path));
- img.load(function () {
- var aspectRatio = this.naturalWidth / this.naturalHeight;
- var sizeWidth = width * width / aspectRatio;
- var sizeHeight = height * aspectRatio * height;
-
- if (sizeWidth < sizeHeight) {
- img.width(width);
- }
- else {
- img.height(height);
- }
- updateModalDialogPosition();
- prevArrow.css('display', pos > 0 ? '' : 'none');
- nextArrow.css('display', pos < entries.length - 1 ? '' : 'none');
- progressImg.remove();
- });
-
- $('div.modal-container').find('span.modal-title:last').html(entry.name);
- updateModalDialogPosition();
- }
-
- prevArrow.click(function () {
- if (pos > 0) {
- pos--;
- }
-
- updatePicture();
- });
-
- nextArrow.click(function () {
- if (pos < entries.length - 1) {
- pos++;
- }
-
- updatePicture();
- });
-
- function bodyKeyDown(e) {
- switch (e.which) {
- case 37:
- if (prevArrow.is(':visible')) {
- prevArrow.click();
- }
- break;
-
- case 39:
- if (nextArrow.is(':visible')) {
- nextArrow.click();
- }
- break;
- }
- }
-
- $('body').on('keydown', bodyKeyDown);
-
- img.load(updateModalDialogPosition);
-
- runModalDialog({
- title: ' ',
- closeButton: true,
- buttons: [
- {caption: 'Close'},
- {caption: 'Download', isDefault: true, click: function () {
- var entry = entries[pos];
- downloadFile(mediaType + '/' + entry.cameraId + '/download' + entry.path);
-
- return false;
- }}
- ],
- content: content,
- stack: true,
- onShow: updatePicture,
- onClose: function () {
- $('body').off('keydown', bodyKeyDown);
- }
- });
-}
-
-function runAddCameraDialog() {
- if (!$('#motionEyeSwitch')[0].checked) {
- return runAlertDialog('Please enable motionEye first!');
- }
-
- if (Object.keys(pushConfigs).length) {
- return runAlertDialog('Please apply the modified settings first!');
- }
-
- var content =
- $('<table class="add-camera-dialog">' +
- '<tr>' +
- '<td class="dialog-item-label"><span class="dialog-item-label">Camera Type</span></td>' +
- '<td class="dialog-item-value"><select class="styled" id="typeSelect">' +
- '<option value="v4l2">Local Camera</option>' +
- '<option value="netcam">Network Camera</option>' +
- '<option value="motioneye">Remote motionEye Camera</option>' +
- '<option value="mjpeg">Simple MJPEG Camera</option>' +
- '</select></td>' +
- '<td><span class="help-mark" title="the type of camera you wish to add">?</span></td>' +
- '</tr>' +
- '<tr class="motioneye netcam mjpeg">' +
- '<td class="dialog-item-label"><span class="dialog-item-label">URL</span></td>' +
- '<td class="dialog-item-value"><input type="text" class="styled" id="urlEntry" placeholder="http://example.com:8080/cams/..."></td>' +
- '<td><span class="help-mark" title="the camera URL (e.g. http://example.com:8080/cam/)">?</span></td>' +
- '</tr>' +
- '<tr class="motioneye netcam mjpeg">' +
- '<td class="dialog-item-label"><span class="dialog-item-label">Username</span></td>' +
- '<td class="dialog-item-value"><input type="text" class="styled" id="usernameEntry" placeholder="username..."></td>' +
- '<td><span class="help-mark" title="the username for the URL, if required (e.g. admin)">?</span></td>' +
- '</tr>' +
- '<tr class="motioneye netcam mjpeg">' +
- '<td class="dialog-item-label"><span class="dialog-item-label">Password</span></td>' +
- '<td class="dialog-item-value"><input type="password" class="styled" id="passwordEntry" placeholder="password..."></td>' +
- '<td><span class="help-mark" title="the password for the URL, if required">?</span></td>' +
- '</tr>' +
- '<tr class="v4l2 motioneye netcam mjpeg">' +
- '<td class="dialog-item-label"><span class="dialog-item-label">Camera</span></td>' +
- '<td class="dialog-item-value"><select class="styled" id="addCameraSelect"></select><span id="cameraMsgLabel"></span></td>' +
- '<td><span class="help-mark" title="the camera you wish to add">?</span></td>' +
- '</tr>' +
- '<tr class="v4l2 motioneye netcam mjpeg">' +
- '<td colspan="100"><div class="dialog-item-separator"></div></td>' +
- '</tr>' +
- '<tr class="v4l2 motioneye netcam mjpeg">' +
- '<td class="dialog-item-value" colspan="100"><div id="addCameraInfo"></div></td>' +
- '</tr>' +
- '</table>');
-
- /* collect ui widgets */
- var typeSelect = content.find('#typeSelect');
- var urlEntry = content.find('#urlEntry');
- var usernameEntry = content.find('#usernameEntry');
- var passwordEntry = content.find('#passwordEntry');
- var addCameraSelect = content.find('#addCameraSelect');
- var addCameraInfo = content.find('#addCameraInfo');
- var cameraMsgLabel = content.find('#cameraMsgLabel');
-
- /* make validators */
- makeUrlValidator(urlEntry, true);
- makeTextValidator(usernameEntry, false);
- makeTextValidator(typeSelect, false);
- makeComboValidator(addCameraSelect, true);
-
- /* ui interaction */
- function updateUi() {
- content.find('tr.v4l2, tr.motioneye, tr.netcam, tr.mjpeg').css('display', 'none');
-
- if (typeSelect.val() == 'motioneye') {
- content.find('tr.motioneye').css('display', 'table-row');
- usernameEntry.val('admin');
- usernameEntry.attr('readonly', 'readonly');
- addCameraInfo.html(
- 'Remote motionEye cameras are cameras installed behind another motionEye server. ' +
- 'Adding them here will allow you to view and manage them remotely.');
- }
- else if (typeSelect.val() == 'netcam') {
- usernameEntry.removeAttr('readonly');
-
- /* make sure there is one trailing slash so that
- * an URI can be detected */
- var url = urlEntry.val().trim();
- var m = url.match(new RegExp('/', 'g'));
- if (m && m.length < 3 && !url.endsWith('/')) {
- urlEntry.val(url + '/');
- }
-
- content.find('tr.netcam').css('display', 'table-row');
- addCameraInfo.html(
- 'Network cameras (or IP cameras) are devices that natively stream RTSP or MJPEG videos or plain JPEG images. ' +
- "Consult your device's manual to find out the correct RTSP, MJPEG or JPEG URL.");
- }
- else if (typeSelect.val() == 'mjpeg') {
- usernameEntry.removeAttr('readonly');
-
- /* make sure there is one trailing slash so that
- * an URI can be detected */
- var url = urlEntry.val().trim();
- var m = url.match(new RegExp('/', 'g'));
- if (m && m.length < 3 && !url.endsWith('/')) {
- urlEntry.val(url + '/');
- }
-
- content.find('tr.mjpeg').css('display', 'table-row');
- addCameraInfo.html(
- 'Adding your device as a simple MJPEG camera instead of as a network camera will improve the framerate, ' +
- 'but no motion detection, picture capturing or movie recording will be available for it. ' +
- 'The camera must be accessible to both your server and your browser. ' +
- 'This type of camera is not compatible with Internet Explorer.');
- }
- else { /* assuming v4l2 */
- content.find('tr.v4l2').css('display', 'table-row');
- addCameraInfo.html(
- 'Local cameras are camera devices that are connected directly to your motionEye system. ' +
- 'These are usually USB webcams or board-specific cameras.');
- }
-
- updateModalDialogPosition();
-
- /* re-validate all the validators */
- content.find('.validator').each(function () {
- this.validate();
- });
-
- if (uiValid()) {
- listCameras();
- }
- }
-
- function uiValid(includeCameraSelect) {
- var query = content.find('input, select');
- if (!includeCameraSelect) {
- query = query.not('#addCameraSelect');
- }
- else {
- if (cameraMsgLabel.html() || !addCameraSelect.val()) {
- return false;
- }
- }
-
- /* re-validate all the validators */
- content.find('.validator').each(function () {
- this.validate();
- });
-
- var valid = true;
- query.each(function () {
- if (this.invalid) {
- valid = false;
- return false;
- }
- });
-
- return valid;
- }
-
- function splitCameraUrl(url) {
- var parts = url.split('://');
- var scheme = parts[0];
- var index = parts[1].indexOf('/');
- var host = null;
- var uri = '';
- if (index >= 0) {
- host = parts[1].substring(0, index);
- uri = parts[1].substring(index);
- }
- else {
- host = parts[1];
- }
-
- var port = '';
- parts = host.split(':');
- if (parts.length >= 2) {
- host = parts[0];
- port = parts[1];
- }
-
- if (uri == '') {
- uri = '/';
- }
-
- return {
- scheme: scheme,
- host: host,
- port: port,
- uri: uri
- };
- }
-
- function listCameras() {
- var progress = $('<div style="text-align: center; margin: 2px;"><img src="' + staticUrl + 'img/small-progress.gif"></div>');
-
- addCameraSelect.html('');
- addCameraSelect.hide();
- addCameraSelect.parent().find('div').remove(); /* remove any previous progress div */
- addCameraSelect.before(progress);
-
- var data = {};
- if (urlEntry.is(':visible') && urlEntry.val()) {
- data = splitCameraUrl(urlEntry.val());
- }
- data.username = usernameEntry.val();
- data.password = passwordEntry.val();
- data.proto = typeSelect.val();
-
- cameraMsgLabel.html('');
-
- ajax('GET', baseUri + 'config/list/', data, function (data) {
- progress.remove();
-
- if (data == null || data.error) {
- cameraMsgLabel.html(data && data.error);
-
- return;
- }
-
- if (data.error || !data.cameras) {
- return;
- }
-
- data.cameras.forEach(function (info) {
- var option = $('<option value="' + info.id + '">' + info.name + '</option>');
- option[0]._extra_attrs = {};
- Object.keys(info).forEach(function (key) {
- if (key == 'id' || key == 'name') {
- return;
- }
-
- var value = info[key];
- option[0]._extra_attrs[key] = value;
- });
-
- addCameraSelect.append(option);
- });
-
- if (!data.cameras || !data.cameras.length) {
- addCameraSelect.append('<option value="">(no cameras)</option>');
- }
-
- addCameraSelect.show();
- addCameraSelect[0].validate();
- });
- }
-
- typeSelect.change(function () {
- addCameraSelect.html('');
- });
-
- typeSelect.change(updateUi);
- urlEntry.change(updateUi);
- usernameEntry.change(updateUi);
- passwordEntry.change(updateUi);
- updateUi();
-
- runModalDialog({
- title: 'Add Camera...',
- closeButton: true,
- buttons: 'okcancel',
- content: content,
- onOk: function () {
- if (!uiValid(true)) {
- return false;
- }
-
- var data = {};
-
- if (typeSelect.val() == 'motioneye') {
- data = splitCameraUrl(urlEntry.val());
- data.proto = 'motioneye';
- data.username = usernameEntry.val();
- data.password = passwordEntry.val();
- data.remote_camera_id = addCameraSelect.val();
- }
- else if (typeSelect.val() == 'netcam') {
- data = splitCameraUrl(urlEntry.val());
- data.username = usernameEntry.val();
- data.password = passwordEntry.val();
- data.proto = 'netcam';
- data.camera_index = addCameraSelect.val();
- }
- else if (typeSelect.val() == 'mjpeg') {
- data = splitCameraUrl(urlEntry.val());
- data.username = usernameEntry.val();
- data.password = passwordEntry.val();
- data.proto = 'mjpeg';
- }
- else { /* assuming v4l2 */
- data.proto = 'v4l2';
- data.uri = addCameraSelect.val();
- }
-
- /* add all extra attributes */
- var option = addCameraSelect.find('option:eq(' + addCameraSelect[0].selectedIndex + ')')[0];
- Object.keys(option._extra_attrs).forEach(function (key) {
- var value = option._extra_attrs[key];
- data[key] = value;
- });
-
- beginProgress();
- ajax('POST', baseUri + 'config/add/', data, function (data) {
- endProgress();
-
- if (data == null || data.error) {
- showErrorMessage(data && data.error);
- return;
- }
-
- var cameraOption = $('#cameraSelect').find('option[value=add]');
- cameraOption.before('<option value="' + data.id + '">' + data.name + '</option>');
- $('#cameraSelect').val(data.id).change();
- recreateCameraFrames();
- });
- }
- });
-}
-
-function runTimelapseDialog(cameraId, groupKey, group) {
- var content =
- $('<table class="timelapse-dialog">' +
- '<tr><td colspan="2" class="timelapse-warning"></td></tr>' +
- '<tr>' +
- '<td class="dialog-item-label"><span class="dialog-item-label">Group</span></td>' +
- '<td class="dialog-item-value">' + groupKey + '</td>' +
- '</tr>' +
- '<tr>' +
- '<td class="dialog-item-label"><span class="dialog-item-label">Include a picture taken every</span></td>' +
- '<td class="dialog-item-value">' +
- '<select class="styled timelapse" id="intervalSelect">' +
- '<option value="1">second</option>' +
- '<option value="5">5 seconds</option>' +
- '<option value="10">10 seconds</option>' +
- '<option value="30">30 seconds</option>' +
- '<option value="60">minute</option>' +
- '<option value="300">5 minutes</option>' +
- '<option value="600">10 minutes</option>' +
- '<option value="1800">30 minutes</option>' +
- '<option value="3600">hour</option>' +
- '</select>' +
- '</td>' +
- '<td><span class="help-mark" title="choose the interval of time between two selected pictures">?</span></td>' +
- '</tr>' +
- '<tr>' +
- '<td class="dialog-item-label"><span class="dialog-item-label">Movie framerate</span></td>' +
- '<td class="dialog-item-value"><input type="text" class="styled range" id="framerateSlider"></td>' +
- '<td><span class="help-mark" title="choose how fast you want the timelapse playback to be">?</span></td>' +
- '</tr>' +
- '</table>');
-
- var intervalSelect = content.find('#intervalSelect');
- var framerateSlider = content.find('#framerateSlider');
- var timelapseWarning = content.find('td.timelapse-warning');
-
- if (group.length > 1440) { /* one day worth of pictures, taken 1 minute apart */
- timelapseWarning.html('Given the large number of pictures, creating your timelapse might take a while!');
- timelapseWarning.css('display', 'table-cell');
- }
-
- makeSlider(framerateSlider, 1, 100, 0, [
- {value: 1, label: '1'},
- {value: 20, label: '20'},
- {value: 40, label: '40'},
- {value: 60, label: '60'},
- {value: 80, label: '80'},
- {value: 100, label: '100'}
- ], null, 0);
-
- intervalSelect.val(60);
- framerateSlider.val(20).each(function () {this.update()});
-
- runModalDialog({
- title: 'Create Timelapse Movie',
- closeButton: true,
- buttons: 'okcancel',
- content: content,
- onOk: function () {
- var progressBar = $('<div style=""></div>');
- makeProgressBar(progressBar);
-
- runModalDialog({
- title: 'Creating Timelapse Movie...',
- content: progressBar,
- stack: true,
- noKeys: true
- });
-
- var url = baseUri + 'picture/' + cameraId + '/timelapse/' + groupKey + '/';
- var data = {interval: intervalSelect.val(), framerate: framerateSlider.val()};
- var first = true;
-
- function checkTimelapse() {
- var actualUrl = url;
- if (!first) {
- actualUrl += '?check=true';
- }
-
- ajax('GET', actualUrl, data, function (data) {
- if (data == null || data.error) {
- hideModalDialog(); /* progress */
- hideModalDialog(); /* timelapse dialog */
- showErrorMessage(data && data.error);
- return;
- }
-
- if (data.progress != -1 && first) {
- showPopupMessage('A timelapse movie is already being created.');
- }
-
- if (data.progress == -1 && !first && !data.key) {
- hideModalDialog(); /* progress */
- hideModalDialog(); /* timelapse dialog */
- showErrorMessage('The timelapse movie could not be created.');
- return;
- }
-
- if (data.progress == -1) {
- data.progress = 0;
- }
-
- if (data.key) {
- progressBar[0].setProgress(100);
- progressBar[0].setText('100%');
-
- setTimeout(function () {
- hideModalDialog(); /* progress */
- hideModalDialog(); /* timelapse dialog */
- downloadFile('picture/' + cameraId + '/timelapse/' + groupKey + '/?key=' + data.key);
- }, 500);
- }
- else {
- progressBar[0].setProgress(data.progress * 100);
- progressBar[0].setText(parseInt(data.progress * 100) + '%');
- setTimeout(checkTimelapse, 1000);
- }
-
- first = false;
- });
- }
-
- checkTimelapse();
-
- return false;
- },
- stack: true
- });
-}
-
-function runMediaDialog(cameraId, mediaType) {
- var dialogDiv = $('<div class="media-dialog"></div>');
- var mediaListDiv = $('<div class="media-dialog-list"></div>');
- var groupsDiv = $('<div class="media-dialog-groups"></div>');
- var buttonsDiv = $('<div class="media-dialog-buttons"></div>');
-
- var groups = {};
- var groupKey = null;
-
- dialogDiv.append(groupsDiv);
- dialogDiv.append(mediaListDiv);
- dialogDiv.append(buttonsDiv);
-
- /* add a temporary div to compute 3em in px */
- var tempDiv = $('<div style="width: 3em; height: 3em;"></div>');
- $('div.modal-container').append(tempDiv);
- var height = tempDiv.height();
- tempDiv.remove();
-
- function showGroup(key) {
- groupKey = key;
-
- if (mediaListDiv.find('img.media-list-progress').length) {
- return; /* already in progress of loading */
- }
-
- /* (re)set the current state of the group buttons */
- groupsDiv.find('div.media-dialog-group-button').each(function () {
- var $this = $(this);
- if (this.key == key) {
- $this.addClass('current');
- }
- else {
- $this.removeClass('current');
- }
- });
-
- var mediaListByName = {};
- var entries = groups[key];
-
- /* cleanup the media list */
- mediaListDiv.children('div.media-list-entry').detach();
- mediaListDiv.html('');
-
- function addEntries() {
- /* add the entries to the media list */
- entries.forEach(function (entry, i) {
- var entryDiv = entry.div;
- var detailsDiv = null;
-
- if (!entryDiv) {
- entryDiv = $('<div class="media-list-entry"></div>');
-
- var previewImg = $('<img class="media-list-preview" src="' + staticUrl + 'img/modal-progress.gif"/>');
- entryDiv.append(previewImg);
- previewImg[0]._src = addAuthParams('GET', baseUri + mediaType + '/' + cameraId + '/preview' + entry.path + '?height=' + height);
-
- var downloadButton = $('<div class="media-list-download-button button">Download</div>');
- entryDiv.append(downloadButton);
-
- var deleteButton = $('<div class="media-list-delete-button button">Delete</div>');
- if (isAdmin()) {
- entryDiv.append(deleteButton);
- }
-
- var nameDiv = $('<div class="media-list-entry-name">' + entry.name + '</div>');
- entryDiv.append(nameDiv);
-
- detailsDiv = $('<div class="media-list-entry-details"></div>');
- entryDiv.append(detailsDiv);
-
- downloadButton.click(function () {
- downloadFile(mediaType + '/' + cameraId + '/download' + entry.path);
- return false;
- });
-
- deleteButton.click(function () {
- doDeleteFile(baseUri + mediaType + '/' + cameraId + '/delete' + entry.path, function () {
- entryDiv.remove();
- entries.splice(i, 1); /* remove entry from group */
-
- /* update text on group button */
- groupsDiv.find('div.media-dialog-group-button').each(function () {
- var $this = $(this);
- if (this.key == groupKey) {
- var text = this.innerHTML;
- text = text.substring(0, text.lastIndexOf(' '));
- text += ' (' + entries.length + ')';
- this.innerHTML = text;
- }
- });
- });
-
- return false;
- });
-
- entryDiv.click(function () {
- var pos = entries.indexOf(entry);
- runPictureDialog(entries, pos, mediaType);
- });
-
- entry.div = entryDiv;
- }
- else {
- detailsDiv = entry.div.find('div.media-list-entry-details');
- }
-
- var momentSpan = $('<span class="details-moment">' + entry.momentStr + ', </span>');
- var momentShortSpan = $('<span class="details-moment-short">' + entry.momentStrShort + '</span>');
- var sizeSpan = $('<span class="details-size">' + entry.sizeStr + '</span>');
- detailsDiv.empty();
- detailsDiv.append(momentSpan);
- detailsDiv.append(momentShortSpan);
- detailsDiv.append(sizeSpan);
- mediaListDiv.append(entryDiv);
- });
-
- /* trigger a scroll event */
- mediaListDiv.scroll();
- }
-
- /* if details are already fetched, simply add the entries and return */
- if (entries[0].timestamp) {
- return addEntries();
- }
-
- var previewImg = $('<img class="media-list-progress" src="' + staticUrl + 'img/modal-progress.gif"/>');
- mediaListDiv.append(previewImg);
-
- var url = baseUri + mediaType + '/' + cameraId + '/list/?prefix=' + (key || 'ungrouped');
- ajax('GET', url, null, function (data) {
- previewImg.remove();
-
- if (data == null || data.error) {
- hideModalDialog();
- showErrorMessage(data && data.error);
- return;
- }
-
- /* index the media list by name */
- data.mediaList.forEach(function (media) {
- var path = media.path;
- var parts = path.split('/');
- var name = parts[parts.length - 1];
-
- mediaListByName[name] = media;
- });
-
- /* assign details to entries */
- entries.forEach(function (entry) {
- var media = mediaListByName[entry.name];
- if (media) {
- entry.momentStr = media.momentStr;
- entry.momentStrShort = media.momentStrShort;
- entry.sizeStr = media.sizeStr;
- entry.timestamp = media.timestamp;
- }
- });
-
- /* sort the entries by timestamp */
- entries.sortKey(function (e) {return e.timestamp || e.name;}, true);
-
- addEntries();
- });
- }
-
- if (mediaType == 'picture') {
- var zippedButton = $('<div class="media-dialog-button">Zipped</div>');
- buttonsDiv.append(zippedButton);
-
- zippedButton.click(function () {
- if (groupKey != null) {
- doDownloadZipped(cameraId, groupKey);
- }
- });
-
- var timelapseButton = $('<div class="media-dialog-button">Timelapse</div>');
- buttonsDiv.append(timelapseButton);
-
- timelapseButton.click(function () {
- if (groupKey != null) {
- runTimelapseDialog(cameraId, groupKey, groups[groupKey]);
- }
- });
- }
-
- if (isAdmin()) {
- var deleteAllButton = $('<div class="media-dialog-button media-dialog-delete-all-button">Delete All</div>');
- buttonsDiv.append(deleteAllButton);
-
- deleteAllButton.click(function () {
- if (groupKey != null) {
- doDeleteAllFiles(mediaType, cameraId, groupKey, function () {
- /* delete th group button */
- groupsDiv.find('div.media-dialog-group-button').each(function () {
- var $this = $(this);
- if (this.key == groupKey) {
- $this.remove();
- }
- });
-
- /* delete the group itself */
- delete groups[groupKey];
-
- /* show the first existing group, if any */
- var keys = Object.keys(groups);
- if (keys.length) {
- showGroup(keys[0]);
- }
- else {
- hideModalDialog();
- }
- });
- }
- });
- }
-
- function updateDialogSize() {
- var windowWidth = $(window).width();
- var windowHeight = $(window).height();
-
- if (Object.keys(groups).length == 0) {
- groupsDiv.width('auto');
- groupsDiv.height('auto');
- groupsDiv.addClass('small-screen');
- mediaListDiv.width('auto');
- mediaListDiv.height('auto');
- buttonsDiv.hide();
-
- return;
- }
-
- buttonsDiv.show();
-
- if (windowWidth < 1000) {
- mediaListDiv.width(parseInt(windowWidth * 0.8));
- mediaListDiv.height(parseInt(windowHeight * 0.7));
- groupsDiv.width(parseInt(windowWidth * 0.8));
- groupsDiv.height('');
- groupsDiv.addClass('small-screen');
- }
- else {
- mediaListDiv.width(parseInt(windowWidth * 0.7));
- mediaListDiv.height(parseInt(windowHeight * 0.7));
- groupsDiv.height(parseInt(windowHeight * 0.7));
- groupsDiv.width('');
- groupsDiv.removeClass('small-screen');
- }
- }
-
- function onResize() {
- updateDialogSize();
- updateModalDialogPosition();
- }
-
- $(window).resize(onResize);
-
- updateDialogSize();
-
- showModalDialog('<div class="modal-progress"></div>');
-
- /* fetch the media list */
- ajax('GET', baseUri + mediaType + '/' + cameraId + '/list/', null, function (data) {
- if (data == null || data.error) {
- hideModalDialog();
- showErrorMessage(data && data.error);
- return;
- }
-
- /* group the media */
- data.mediaList.forEach(function (media) {
- var path = media.path;
- var parts = path.split('/');
- var keyParts = parts.splice(0, parts.length - 1);
- var key = keyParts.join('/');
-
- if (key.indexOf('/') === 0) {
- key = key.substring(1);
- }
-
- var list = (groups[key] = groups[key] || []);
-
- list.push({
- 'path': path,
- 'group': key,
- 'name': parts[parts.length - 1],
- 'cameraId': cameraId
- });
- });
-
- updateDialogSize();
-
- var keys = Object.keys(groups);
- keys.sort();
- keys.reverse();
-
- if (keys.length) {
- keys.forEach(function (key) {
- var groupButton = $('<div class="media-dialog-group-button"></div>');
- groupButton.text((key || '(ungrouped)') + ' (' + groups[key].length + ')');
- groupButton[0].key = key;
-
- groupButton.click(function () {
- showGroup(key);
- });
-
- groupsDiv.append(groupButton);
- });
-
- /* add tooltips to larger group buttons */
- setTimeout(function () {
- groupsDiv.find('div.media-dialog-group-button').each(function () {
- if (this.scrollWidth > this.offsetWidth) {
- this.title = this.innerHTML;
- }
- });
- }, 10);
- }
- else {
- groupsDiv.html('(no media files)');
- mediaListDiv.remove();
- }
-
- var title;
- if ($(window).width() < 1000) {
- title = data.cameraName;
- }
- else if (mediaType === 'picture') {
- title = 'Pictures taken by ' + data.cameraName;
- }
- else {
- title = 'Movies recorded by ' + data.cameraName;
- }
-
- runModalDialog({
- title: title,
- closeButton: true,
- buttons: '',
- content: dialogDiv,
- onShow: function () {
- //dialogDiv.scrollTop(dialogDiv.prop('scrollHeight'));
- if (keys.length) {
- showGroup(keys[0]);
- }
- },
- onClose: function () {
- $(window).unbind('resize', onResize);
- }
- });
- });
-
- /* install the media list scroll event handler */
- mediaListDiv.scroll(function () {
- var height = mediaListDiv.height();
-
- mediaListDiv.find('img.media-list-preview').each(function () {
- if (!this._src) {
- return;
- }
-
- var $this = $(this);
- var entryDiv = $this.parent();
-
- var top1 = entryDiv.position().top;
- var top2 = top1 + entryDiv.height();
-
- if ((top1 >= 0 && top1 <= height) ||
- (top2 >= 0 && top2 <= height)) {
-
- this.src = this._src;
- delete this._src;
- }
- });
- });
-}
-
-
- /* camera frames */
-
-function addCameraFrameUi(cameraConfig) {
- var pageContainer = $('div.page-container');
-
- if (cameraConfig == null) {
- var cameraFrameDivPlaceHolder = $('<div class="camera-frame-place-holder"></div>');
- pageContainer.append(cameraFrameDivPlaceHolder);
-
- return;
- }
-
- var cameraId = cameraConfig.id;
-
- var cameraFrameDiv = $(
- '<div class="camera-frame">' +
- '<div class="camera-top-bar">' +
- '<span class="camera-name"></span>' +
- '<div class="camera-buttons">' +
- '<div class="button camera-button mouse-effect full-screen" title="full-screen window"></div>' +
- '<div class="button camera-button mouse-effect media-pictures" title="pictures"></div>' +
- '<div class="button camera-button mouse-effect media-movies" title="movies"></div>' +
- '<div class="button camera-button mouse-effect configure" title="configure"></div>' +
- '</div>' +
- '</div>' +
- '<div class="camera-container">' +
- '<div class="camera-placeholder"><img class="no-camera" src="' + staticUrl + 'img/no-camera.svg"></div>' +
- '<img class="camera">' +
- '<div class="camera-progress"><img class="camera-progress"></div>' +
- '</div>' +
- '</div>');
-
- var nameSpan = cameraFrameDiv.find('span.camera-name');
- var configureButton = cameraFrameDiv.find('div.camera-button.configure');
- var picturesButton = cameraFrameDiv.find('div.camera-button.media-pictures');
- var moviesButton = cameraFrameDiv.find('div.camera-button.media-movies');
- var fullScreenButton = cameraFrameDiv.find('div.camera-button.full-screen');
- var cameraPlaceholder = cameraFrameDiv.find('div.camera-placeholder');
- var cameraProgress = cameraFrameDiv.find('div.camera-progress');
- var cameraImg = cameraFrameDiv.find('img.camera');
- var progressImg = cameraFrameDiv.find('img.camera-progress');
-
- /* no camera buttons if not admin */
- if (!isAdmin()) {
- configureButton.hide();
- }
-
- /* no media buttons for simple mjpeg cameras */
- if (cameraConfig['proto'] == 'mjpeg') {
- picturesButton.hide();
- moviesButton.hide();
- }
-
- cameraFrameDiv.attr('id', 'camera' + cameraId);
- cameraFrameDiv[0].refreshDivider = 0;
- cameraFrameDiv[0].config = cameraConfig;
- nameSpan.html(cameraConfig.name);
- progressImg.attr('src', staticUrl + 'img/camera-progress.gif');
-
- cameraProgress.click(function () {
- doFullScreenCamera(cameraId);
- });
-
- cameraProgress.addClass('visible');
- cameraPlaceholder.css('opacity', '0');
-
- /* insert the new camera frame at the right position,
- * with respect to the camera id */
- var cameraFrames = pageContainer.find('div.camera-frame');
- var cameraIds = cameraFrames.map(function () {return parseInt(this.id.substring(6));});
- cameraIds.sort();
-
- var index = 0; /* find the first position that is greater than the current camera id */
- while (index < cameraIds.length && cameraIds[index] < cameraId) {
- index++;
- }
-
- if (index < cameraIds.length) {
- var beforeCameraFrame = pageContainer.find('div.camera-frame#camera' + cameraIds[index]);
- cameraFrameDiv.insertAfter(beforeCameraFrame);
- }
- else {
- pageContainer.append(cameraFrameDiv);
- }
-
- /* fade in */
- cameraFrameDiv.animate({'opacity': 1}, 100);
-
- /* add the button handlers */
- configureButton.click(function () {
- doConfigureCamera(cameraId);
- });
-
- picturesButton.click(function (cameraId) {
- return function () {
- runMediaDialog(cameraId, 'picture');
- };
- }(cameraId));
-
- moviesButton.click(function (cameraId) {
- return function () {
- runMediaDialog(cameraId, 'movie');
- };
- }(cameraId));
-
- fullScreenButton.click(function (cameraId) {
- return function () {
- var url = baseUri + 'picture/' + cameraId + '/frame/';
- window.open(url, '_blank');
- };
- }(cameraId));
-
- /* error and load handlers */
- cameraImg.error(function () {
- this.error = true;
- this.loading = 0;
-
- cameraImg.addClass('error').removeClass('loading');
- cameraImg.height(Math.round(cameraImg.width() * 0.75));
- cameraPlaceholder.css('opacity', 1);
- cameraProgress.removeClass('visible');
- cameraFrameDiv.removeClass('motion-detected');
- });
- cameraImg.load(function () {
- if (refreshDisabled[cameraId]) {
- return; /* refresh temporarily disabled for updating */
- }
-
- this.error = false;
- this.loading = 0;
-
- cameraImg.removeClass('error').removeClass('loading');
- cameraImg.css('height', '');
- cameraPlaceholder.css('opacity', 0);
- cameraProgress.removeClass('visible');
-
- /* there's no point in looking for a cookie update more often than once every second */
- var now = new Date().getTime();
- if ((!this.lastCookieTime || now - this.lastCookieTime > 1000) && (cameraFrameDiv[0].config['proto'] != 'mjpeg')) {
- if (getCookie('motion_detected_' + cameraId) == 'true') {
- cameraFrameDiv.addClass('motion-detected');
- }
- else {
- cameraFrameDiv.removeClass('motion-detected');
- }
-
- this.lastCookieTime = now;
- }
-
- if (fullScreenCameraId) {
- /* update the modal dialog position when image is loaded */
- updateModalDialogPosition();
- }
- });
-
- cameraImg.addClass('loading');
- cameraImg.height(Math.round(cameraImg.width() * 0.75));
-}
-
-function remCameraFrameUi(cameraId) {
- var pageContainer = $('div.page-container');
- var cameraFrameDiv = pageContainer.find('div.camera-frame#camera' + cameraId);
- cameraFrameDiv.animate({'opacity': 0}, 100, function () {
- cameraFrameDiv.remove();
- });
-}
-
-function recreateCameraFrames(cameras) {
- var pageContainer = $('div.page-container');
-
- function updateCameras(cameras) {
- cameras = cameras.filter(function (camera) {return camera.enabled;});
- var i, camera;
-
- /* remove everything on the page */
- pageContainer.children().remove();
-
- /* add camera frames */
- for (i = 0; i < cameras.length; i++) {
- camera = cameras[i];
- addCameraFrameUi(camera);
- }
-
- if ($('#cameraSelect').find('option').length < 2 && isAdmin() && $('#motionEyeSwitch')[0].checked) {
- /* invite the user to add a camera */
- var addCameraLink = $('<div class="add-camera-message">' +
- '<a href="javascript:runAddCameraDialog()">You have not configured any camera yet. Click here to add one...</a></div>');
- pageContainer.append(addCameraLink);
- }
- }
-
- if (cameras != null) {
- updateCameras(cameras);
- }
- else {
- ajax('GET', baseUri + 'config/list/', null, function (data) {
- if (data == null || data.error) {
- showErrorMessage(data && data.error);
- return;
- }
-
- updateCameras(data.cameras);
- });
- }
-
- /* update settings panel */
- var cameraId = $('#cameraSelect').val();
- if (cameraId && cameraId != 'add') {
- openSettings(cameraId);
- }
-}
-
-
-function doConfigureCamera(cameraId) {
- if (inProgress) {
- return;
- }
-
- hideApply();
- pushConfigs = {};
- pushConfigReboot = false;
-
- openSettings(cameraId);
-}
-
-function doFullScreenCamera(cameraId) {
- if (inProgress || refreshDisabled[cameraId]) {
- return;
- }
-
- if (fullScreenCameraId != null) {
- return; /* a camera is already in full screen */
- }
-
- fullScreenCameraId = -1; /* avoids successive fast toggles of fullscreen */
-
- var cameraFrameDiv = $('#camera' + cameraId);
- var cameraName = cameraFrameDiv.find('span.camera-name').text();
- var frameImg = cameraFrameDiv.find('img.camera');
- var aspectRatio = frameImg.width() / frameImg.height();
- var windowWidth = $(window).width();
- var windowHeight = $(window).height();
- var windowAspectRatio = windowWidth / windowHeight;
- var frameIndex = cameraFrameDiv.index();
- var pageContainer = $('div.page-container');
-
- if (frameImg.hasClass('error')) {
- return; /* no full screen for erroneous cameras */
- }
-
- var width;
- if (windowAspectRatio > aspectRatio) {
- width = aspectRatio * Math.round(0.8 * windowHeight);
- }
- else {
- width = Math.round(0.9 * windowWidth);
- }
-
- cameraFrameDiv.find('div.camera-progress').addClass('visible');
-
- var cameraImg = cameraFrameDiv.find('img.camera');
- cameraImg.load(function showFullScreenCamera() {
- cameraFrameDiv.css('width', width);
- fullScreenCameraId = cameraId;
-
- runModalDialog({
- title: cameraName,
- closeButton: true,
- content: cameraFrameDiv,
- onShow: function () {
- cameraImg.unbind('load', showFullScreenCamera);
- },
- onClose: function () {
- fullScreenCameraId = null;
- cameraFrameDiv.css('width', '');
- var nextFrame = pageContainer.children('div:eq(' + frameIndex + ')');
- if (nextFrame.length) {
- nextFrame.before(cameraFrameDiv);
- }
- else {
- pageContainer.append(cameraFrameDiv);
- }
- }
- });
- });
-
- if (cameraFrameDiv[0].config['proto'] == 'mjpeg') {
- /* manually trigger the load event on simple mjpeg cameras */
- cameraImg.load();
- }
-}
-
-function refreshCameraFrames() {
- function refreshCameraFrame(cameraId, img, serverSideResize) {
- if (refreshDisabled[cameraId]) {
- /* camera refreshing disabled, retry later */
-
- return;
- }
-
- if (img.loading) {
- img.loading++; /* increases each time the camera would refresh but is still loading */
-
- if (img.loading > 2 * 1000 / refreshInterval) { /* limits the retries to one every two seconds */
- img.loading = 0;
- }
- else {
- return; /* wait for the previous frame to finish loading */
- }
- }
-
- var timestamp = new Date().getTime();
- var uri = baseUri + 'picture/' + cameraId + '/current/?_=' + timestamp;
- if (serverSideResize) {
- uri += '&width=' + img.width;
- }
-
- uri = addAuthParams('GET', uri);
-
- img.src = uri;
- img.loading = 1;
- }
-
- var cameraFrames;
- if (fullScreenCameraId != null && fullScreenCameraId >= 0) {
- cameraFrames = $('#camera' + fullScreenCameraId);
- }
- else {
- cameraFrames = $('div.page-container').find('div.camera-frame');
- }
-
- cameraFrames.each(function () {
- if (!this.img) {
- this.img = $(this).find('img.camera')[0];
- if (this.config['proto'] == 'mjpeg') {
- var url = this.config['url'].replace('127.0.0.1', window.location.host.split(':')[0]);
- url += (url.indexOf('?') > 0 ? '&' : '?') + '_=' + new Date().getTime();
- this.img.src = url;
- }
- }
-
- if (this.config['proto'] == 'mjpeg') {
- return; /* no manual refresh for simple mjpeg cameras */
- }
-
- /* at a refresh interval of 50ms, the refresh rate is limited to 20 fps */
- var count = 1000 / (refreshInterval * this.config['streaming_framerate']);
- var serverSideResize = this.config['streaming_server_resize'];
-
- if (count <= 2) {
- /* skipping frames (showing the same frame twice) at this rate won't be visible,
- * while the effective framerate will be as close as possible to the motion's one */
- count -= 1;
- }
-
- if (this.img.error) {
- /* in case of error, decrease the refresh rate to 1 fps */
- count = 1000 / refreshInterval;
- }
-
- if (this.refreshDivider < count) {
- this.refreshDivider++;
- }
- else {
- var cameraId = this.id.substring(6);
- refreshCameraFrame(cameraId, this.img, serverSideResize);
-
- this.refreshDivider = 0;
- }
- });
-
- setTimeout(refreshCameraFrames, refreshInterval);
-}
-
-function checkCameraErrors() {
- /* properly triggers the onerror event on the cameras whose imgs were not successfully loaded,
- * but the onerror event hasn't been triggered, for some reason (seems to happen in Chrome) */
- var cameraFrames = $('div.page-container').find('img.camera');
-
- cameraFrames.each(function () {
- if (this.complete === true && this.naturalWidth === 0 && !this.error && this.src) {
- $(this).error();
- }
- });
-
- setTimeout(checkCameraErrors, 500);
-}
-
-
- /* startup function */
-
-$(document).ready(function () {
- /* detect base uri */
- if (frame) {
- baseUri = qualifyUri('../../../');
-
- }
- else {
- baseUri = splitUrl(qualifyUri('')).baseUrl;
-
- /* restore the username from cookie */
- window.username = getCookie('username');
- }
-
- /* open/close settings */
- $('div.settings-button').click(function () {
- if (isSettingsOpen()) {
- closeSettings();
- }
- else {
- openSettings();
- }
- });
-
- /* software update button */
- $('div#updateButton').click(doUpdate);
-
- /* backup/restore */
- $('div#backupButton').click(doBackup);
- $('div#restoreButton').click(doRestore);
-
- /* prevent scroll events on settings div from propagating TODO this does not actually work */
- $('div.settings').mousewheel(function (e, d) {
- var t = $(this);
- if (d > 0 && t.scrollTop() === 0) {
- e.preventDefault();
- }
- else if (d < 0 && (t.scrollTop() === t.get(0).scrollHeight - t.innerHeight())) {
- e.preventDefault();
- }
- });
-
- initUI();
- beginProgress();
-
- ajax('GET', baseUri + 'login/', null, function () {
- if (!frame) {
- fetchCurrentConfig(endProgress);
- }
- });
-
- refreshCameraFrames();
- checkCameraErrors();
-});
-
+++ /dev/null
-
-var _modalDialogContexts = [];
-
-
- /* UI widgets */
-
-function makeCheckBox($input) {
- $input.each(function () {
- var $this = $(this);
-
- var mainDiv = $('<div class="check-box"></div>');
- var buttonDiv = $('<div class="check-box-button"></div>');
- var text = $('<span class="check-box-text"><span>');
-
- function setOn() {
- text.html('ON');
- mainDiv.addClass('on');
- }
-
- function setOff() {
- text.html('OFF');
- mainDiv.removeClass('on');
- }
-
- buttonDiv.append(text);
- mainDiv.append(buttonDiv);
-
- /* transfer the CSS classes */
- mainDiv[0].className += ' ' + $this[0].className;
-
- /* add the element */
- $this.after(mainDiv);
-
- function update() {
- if ($this[0].checked) {
- setOn();
- }
- else {
- setOff();
- }
- }
-
- /* add event handers */
- $this.change(update).change();
-
- mainDiv.click(function () {
- $this[0].checked = !$this[0].checked;
- $this.change();
- });
-
- /* make the element focusable */
- mainDiv[0].tabIndex = 0;
-
- /* handle the key events */
- mainDiv.keydown(function (e) {
- if (e.which === 13 || e.which === 32) {
- $this[0].checked = !$this[0].checked;
- $this.change();
-
- return false;
- }
- });
-
- this.update = update;
- });
-}
-
-function makeSlider($input, minVal, maxVal, snapMode, ticks, ticksNumber, decimals, unit) {
- unit = unit || '';
-
- $input.each(function () {
- var $this = $(this);
- var slider = $('<div class="slider"></div>');
-
- var labels = $('<div class="slider-labels"></div>');
- slider.append(labels);
-
- var bar = $('<div class="slider-bar"></div>');
- slider.append(bar);
-
- bar.append('<div class="slider-bar-inside"></div>');
-
- var cursor = $('<div class="slider-cursor"></div>');
- bar.append(cursor);
-
- var cursorLabel = $('<div class="slider-cursor-label"></div>');
- cursor.append(cursorLabel);
-
- function bestPos(pos) {
- if (pos < 0) {
- pos = 0;
- }
- if (pos > 100) {
- pos = 100;
- }
-
- if (snapMode > 0) {
- var minDif = Infinity;
- var bestPos = null;
- for (var i = 0; i < ticks.length; i++) {
- var tick = ticks[i];
- var p = valToPos(tick.value);
- var dif = Math.abs(p - pos);
- if ((dif < minDif) && (snapMode == 1 || dif < 5)) {
- minDif = dif;
- bestPos = p;
- }
- }
-
- if (bestPos != null) {
- pos = bestPos;
- }
- }
-
- return pos;
- }
-
- function getPos() {
- return parseInt(cursor.position().left * 100 / bar.width());
- }
-
- function valToPos(val) {
- return (val - minVal) * 100 / (maxVal - minVal);
- }
-
- function posToVal(pos) {
- return minVal + pos * (maxVal - minVal) / 100;
- }
-
- function sliderChange(val) {
- $this.val(val.toFixed(decimals));
- cursorLabel.html('' + val.toFixed(decimals) + unit);
- }
-
- function bodyMouseMove(e) {
- if (bar[0]._mouseDown) {
- var offset = bar.offset();
- var pos = e.pageX - offset.left - 5;
- pos = pos / slider.width() * 100;
- pos = bestPos(pos);
- var val = posToVal(pos);
-
- cursor.css('left', pos + '%');
- sliderChange(val);
- }
- }
-
- function bodyMouseUp(e) {
- bar[0]._mouseDown = false;
-
- $('body').unbind('mousemove', bodyMouseMove);
- $('body').unbind('mouseup', bodyMouseUp);
-
- cursorLabel.css('display', 'none');
-
- $this.change();
- }
-
- bar.mousedown(function (e) {
- if (e.which > 1) {
- return;
- }
-
- this._mouseDown = true;
- bodyMouseMove(e);
-
- $('body').mousemove(bodyMouseMove);
- $('body').mouseup(bodyMouseUp);
-
- slider.focus();
- cursorLabel.css('display', 'inline-block');
-
- return false;
- });
-
- /* ticks */
- var autoTicks = (ticks == null);
-
- function makeTicks() {
- if (ticksNumber == null) {
- ticksNumber = 11;
- }
-
- labels.html('');
-
- if (autoTicks) {
- ticks = [];
- var i;
- for (i = 0; i < ticksNumber; i++) {
- var val = minVal + i * (maxVal - minVal) / (ticksNumber - 1);
- var valStr;
- if (Math.round(val) == val) {
- valStr = '' + val;
- }
- else {
- valStr = val.toFixed(decimals);
- }
- ticks.push({value: val, label: valStr + unit});
- }
- }
-
- for (i = 0; i < ticks.length; i++) {
- var tick = ticks[i];
- var pos = valToPos(tick.value);
- var span = $('<span class="slider-label" style="left: -9999px;">' + tick.label + '</span>');
-
- labels.append(span);
- span.css('left', (pos - 10) + '%');
- }
-
- return ticks;
- }
-
- makeTicks();
-
- function input2slider() {
- var value = parseFloat($this.val());
- if (isNaN(value)) {
- value = minVal;
- }
-
- var pos = valToPos(value);
- pos = bestPos(pos);
- cursor.css('left', pos + '%');
- cursorLabel.html($this.val() + unit);
- }
-
- /* transfer the CSS classes */
- slider.addClass($this.attr('class'));
-
- /* handle input events */
- $this.change(input2slider).change();
-
- /* add the slider to the parent of the input */
- $this.after(slider);
-
- /* make the slider focusable */
- slider.attr('tabIndex', 0);
-
- /* handle key events */
- slider.keydown(function (e) {
- switch (e.which) {
- case 37: /* left */
- if (snapMode == 1) { /* strict snapping */
- // TODO implement me
- }
- else {
- var step = (maxVal - minVal) / 200;
- var val = Math.max(minVal, parseFloat($this.val()) - step);
- if (decimals == 0) {
- val = Math.floor(val);
- }
-
- var origSnapMode = snapMode;
- snapMode = 0;
- $this.val(val).change();
- snapMode = origSnapMode;
- }
-
- break;
-
- case 39: /* right */
- if (snapMode == 1) { /* strict snapping */
- // TODO implement me
- }
- else {
- var step = (maxVal - minVal) / 200;
- var val = Math.min(maxVal, parseFloat($this.val()) + step);
- if (decimals == 0) {
- val = Math.ceil(val);
- }
-
- var origSnapMode = snapMode;
- snapMode = 0;
- $this.val(val).change();
- snapMode = origSnapMode;
- }
-
- break;
- }
- });
-
- this.update = input2slider;
-
- slider[0].setMinVal = function (mv) {
- minVal = mv;
-
- makeTicks();
- };
-
- slider[0].setMaxVal = function (mv) {
- maxVal = mv;
-
- makeTicks();
-
- input2slider();
- };
- });
-}
-
-function makeProgressBar($div) {
- $div.each(function () {
- var $this = $(this);
-
- $this.addClass('progress-bar-container');
- var fillDiv = $('<div class="progress-bar-fill"></div>');
- var textSpan = $('<span class="progress-bar-text"></span>');
-
- $this.append(fillDiv);
- $this.append(textSpan);
-
- this.setProgress = function (progress) {
- $this.progress = progress;
- fillDiv.width(progress + '%');
- };
-
- this.setText = function (text) {
- textSpan.html(text);
- };
- });
-}
-
-
- /* validators */
-
-function makeTextValidator($input, required) {
- if (required == null) {
- required = true;
- }
-
- $input.each(function () {
- var $this = $(this);
-
- function isValid(strVal) {
- if (!$this.is(':visible')) {
- return true; /* an invisible element is considered always valid */
- }
-
- if (strVal.length === 0 && required) {
- return false;
- }
-
- return true;
- }
-
- var msg = 'this field is required';
-
- function validate() {
- var strVal = $this.val();
- if (isValid(strVal)) {
- $this.attr('title', '');
- $this.removeClass('error');
- $this[0].invalid = false;
- }
- else {
- $this.attr('title', msg);
- $this.addClass('error');
- $this[0].invalid = true;
- }
- }
-
- $this.keyup(validate);
- $this.blur(validate);
- $this.change(validate).change();
-
- $this.addClass('validator');
- $this.addClass('text-validator');
- $this.each(function () {
- var oldValidate = this.validate;
- this.validate = function () {
- if (oldValidate) {
- oldValidate.call(this);
- }
- validate();
- }
- });
- });
-}
-
-function makeComboValidator($select, required) {
- if (required == null) {
- required = true;
- }
-
- $select.each(function () {
- $this = $(this);
-
- function isValid(strVal) {
- if (!$this.is(':visible')) {
- return true; /* an invisible element is considered always valid */
- }
-
- if (strVal.length === 0 && required) {
- return false;
- }
-
- return true;
- }
-
- var msg = 'this field is required';
-
- function validate() {
- var strVal = $this.val() || '';
- if (isValid(strVal)) {
- $this.attr('title', '');
- $this.removeClass('error');
- $this[0].invalid = false;
- }
- else {
- $this.attr('title', msg);
- $this.addClass('error');
- $this[0].invalid = true;
- }
- }
-
- $this.keyup(validate);
- $this.blur(validate);
- $this.change(validate).change();
-
- $this.addClass('validator');
- $this.addClass('combo-validator');
- $this.each(function () {
- var oldValidate = this.validate;
- this.validate = function () {
- if (oldValidate) {
- oldValidate.call(this);
- }
- validate();
- }
- });
- });
-}
-
-function makeNumberValidator($input, minVal, maxVal, floating, sign, required) {
- if (minVal == null) {
- minVal = -Infinity;
- }
- if (maxVal == null) {
- maxVal = Infinity;
- }
- if (floating == null) {
- floating = false;
- }
- if (sign == null) {
- sign = false;
- }
- if (required == null) {
- required = true;
- }
-
- $input.each(function () {
- var $this = $(this);
-
- function isValid(strVal) {
- if (!$this.is(':visible')) {
- return true; /* an invisible element is considered always valid */
- }
-
- if (strVal.length === 0 && !required) {
- return true;
- }
-
- var numVal = parseInt(strVal);
- if ('' + numVal != strVal) {
- return false;
- }
-
- if (numVal < minVal || numVal > maxVal) {
- return false;
- }
-
- if (!sign && numVal < 0) {
- return false;
- }
-
- return true;
- }
-
- var msg = '';
- if (!sign) {
- msg = 'enter a positive';
- }
- else {
- msg = 'enter a';
- }
- if (floating) {
- msg += ' number';
- }
- else {
- msg += ' integer number';
- }
- if (isFinite(minVal)) {
- if (isFinite(maxVal)) {
- msg += ' between ' + minVal + ' and ' + maxVal;
- }
- else {
- msg += ' greater than ' + minVal;
- }
- }
- else {
- if (isFinite(maxVal)) {
- msg += ' smaller than ' + maxVal;
- }
- }
-
- function validate() {
- var strVal = $this.val();
- if (isValid(strVal)) {
- $this.attr('title', '');
- $this.removeClass('error');
- $this[0].invalid = false;
- }
- else {
- $this.attr('title', msg);
- $this.addClass('error');
- $this[0].invalid = true;
- }
- }
-
- $this.keyup(validate);
- $this.blur(validate);
- $this.change(validate).change();
-
- $this.addClass('validator');
- $this.addClass('number-validator');
- $this.each(function () {
- var oldValidate = this.validate;
- this.validate = function () {
- if (oldValidate) {
- oldValidate.call(this);
- }
- validate();
- }
- });
- });
-
- makeStrippedInput($input);
-}
-
-function makeTimeValidator($input) {
- $input.each(function () {
- var $this = $(this);
-
- function isValid(strVal) {
- if (!$this.is(':visible')) {
- return true; /* an invisible element is considered always valid */
- }
-
- return strVal.match(new RegExp('^[0-2][0-9]:[0-5][0-9]$')) != null;
- }
-
- var msg = 'enter a valid time in the following format: HH:MM';
-
- function validate() {
- var strVal = $this.val();
- if (isValid(strVal)) {
- $this.attr('title', '');
- $this.removeClass('error');
- $this[0].invalid = false;
- }
- else {
- $this.attr('title', msg);
- $this.addClass('error');
- $this[0].invalid = true;
- }
- }
-
- $this.keyup(validate);
- $this.blur(validate);
- $this.change(validate).change();
- $this.timepicker({
- closeOnWindowScroll: true,
- selectOnBlur: true,
- timeFormat: 'H:i',
- });
-
- $this.addClass('validator');
- $this.addClass('time-validator');
- $this.each(function () {
- var oldValidate = this.validate;
- this.validate = function () {
- if (oldValidate) {
- oldValidate.call(this);
- }
- validate();
- }
- });
- });
-
- makeStrippedInput($input);
-}
-
-function makeUrlValidator($input) {
- $input.each(function () {
- var $this = $(this);
-
- function isValid(strVal) {
- if (!$this.is(':visible')) {
- return true; /* an invisible element is considered always valid */
- }
-
- return strVal.match(new RegExp('^([a-zA-Z]+)://([\\w\-.]+)(:\\d+)?(/.*)?$')) != null;
- }
-
- var msg = 'enter a valid URL (e.g. http://example.com:8080/cams/)';
-
- function validate() {
- var strVal = $this.val();
- if (isValid(strVal)) {
- $this.attr('title', '');
- $this.removeClass('error');
- $this[0].invalid = false;
- }
- else {
- $this.attr('title', msg);
- $this.addClass('error');
- $this[0].invalid = true;
- }
- }
-
- $this.keyup(validate);
- $this.blur(validate);
- $this.change(validate).change();
-
- $this.addClass('validator');
- $this.addClass('url-validator');
- $this.each(function () {
- var oldValidate = this.validate;
- this.validate = function () {
- if (oldValidate) {
- oldValidate.call(this);
- }
- validate();
- }
- });
- });
-}
-
-function makeFileValidator($input, required) {
- if (required == null) {
- required = true;
- }
-
- $input.each(function () {
- var $this = $(this);
-
- function isValid(strVal) {
- if (!$this.is(':visible')) {
- return true; /* an invisible element is considered always valid */
- }
-
- if (strVal.length === 0 && required) {
- return false;
- }
-
- return true;
- }
-
- var msg = 'this field is required';
-
- function validate() {
- var strVal = $this.val();
- if (isValid(strVal)) {
- $this.attr('title', '');
- $this.removeClass('error');
- $this[0].invalid = false;
- }
- else {
- $this.attr('title', msg);
- $this.addClass('error');
- $this[0].invalid = true;
- }
- }
-
- $this.keyup(validate);
- $this.blur(validate);
- $this.change(validate).change();
-
- $this.addClass('validator');
- $this.addClass('file-validator');
- $this.each(function () {
- var oldValidate = this.validate;
- this.validate = function () {
- if (oldValidate) {
- oldValidate.call(this);
- }
- validate();
- }
- });
- });
-}
-function makeCustomValidator($input, isValidFunc) {
- $input.each(function () {
- var $this = $(this);
-
- function isValid(strVal) {
- if (!$this.is(':visible')) {
- return true; /* an invisible element is considered always valid */
- }
-
- return isValidFunc(strVal);
- }
-
- function validate() {
- var strVal = $this.val();
- var valid = isValid(strVal);
- if (valid == true) {
- $this.attr('title', '');
- $this.removeClass('error');
- $this[0].invalid = false;
- }
- else {
- $this.attr('title', valid || 'enter a valid value');
- $this.addClass('error');
- $this[0].invalid = true;
- }
- }
-
- $this.keyup(validate);
- $this.blur(validate);
- $this.change(validate).change();
-
- $this.addClass('validator');
- $this.addClass('custom-validator');
- $this.each(function () {
- var oldValidate = this.validate;
- this.validate = function () {
- if (oldValidate) {
- oldValidate.call(this);
- }
- validate();
- }
- });
- });
-}
-
-
- /* other input value processors */
-
-function makeStrippedInput($input) {
- $input.change(function () {
- this.value = $.trim(this.value);
- });
-}
-
-function makeCharReplacer($input, oldChars, newStr) {
- $input.change(function () {
- this.value = this.value.replace(new RegExp('[' + oldChars + ']', 'g'), newStr);
- });
-}
-
-
- /* modal dialog */
-
-function showModalDialog(content, onClose, onShow, stack) {
- var glass = $('div.modal-glass');
- var container = $('div.modal-container');
-
- if (container.is(':animated')) {
- return setTimeout(function () {
- showModalDialog(content, onClose, onShow, stack);
- }, 100);
- }
-
- if (container.is(':visible') && stack) {
- /* the modal dialog is already visible,
- * we just replace the content */
-
- var children = container.children(':visible');
- _modalDialogContexts.push({
- children: children,
- onClose: container[0]._onClose,
- });
-
- children.css('display', 'none');
- updateModalDialogPosition();
-
- container[0]._onClose = onClose; /* set the new onClose handler */
- container.append(content);
- updateModalDialogPosition();
-
- if (onShow) {
- onShow();
- }
-
- return;
- }
-
- glass.css('display', 'block');
- glass.animate({'opacity': '0.7'}, 200);
-
- container[0]._onClose = onClose; /* remember the onClose handler */
- container.html(content);
-
- container.css('display', 'block');
- updateModalDialogPosition();
- container.animate({'opacity': '1'}, 200);
-
- if (onShow) {
- onShow();
- }
-}
-
-function hideModalDialog() {
- var glass = $('div.modal-glass');
- var container = $('div.modal-container');
-
- if (container.is(':animated')) {
- return setTimeout(function () {
- hideModalDialog();
- }, 100);
- }
-
- if (_modalDialogContexts.length) {
- if (container[0]._onClose) {
- container[0]._onClose();
- }
-
- container.children(':visible').remove();
-
- var context = _modalDialogContexts.pop();
- context.children.css('display', '');
- container[0]._onClose = context.onClose;
- updateModalDialogPosition();
-
- return;
- }
-
- glass.animate({'opacity': '0'}, 200, function () {
- glass.css('display', 'none');
- });
-
- container.animate({'opacity': '0'}, 200, function () {
- container.css('display', 'none');
- container.html('');
- });
-
- /* run the onClose handler, if supplied */
- if (container[0]._onClose) {
- container[0]._onClose();
- }
-}
-
-function updateModalDialogPosition() {
- var container = $('div.modal-container');
- if (!container.is(':visible')) {
- return;
- }
-
- var windowWidth = $(window).width();
- var windowHeight = $(window).height();
- var modalWidth, modalHeight, i;
-
- /* repeat the operation multiple times, the size might change */
- for (i = 0; i < 3; i++) {
- modalWidth = container.outerWidth();
- modalHeight = container.outerHeight();
-
- container.css('left', Math.floor((windowWidth - modalWidth) / 2));
- container.css('top', Math.floor((windowHeight - modalHeight) / 2));
- }
-}
-
-function makeModalDialogButtons(buttonsInfo) {
- /* buttonsInfo is an array of:
- * * caption: String
- * * isDefault: Boolean
- * * click: Function
- */
-
- var buttonsContainer = $('<table class="modal-buttons-container"><tr></tr></table>');
- var tr = buttonsContainer.find('tr');
-
- buttonsInfo.forEach(function (info) {
- var buttonDiv = $('<div class="button dialog mouse-effect"></div>');
-
- buttonDiv.attr('tabIndex', '0'); /* make button focusable */
- buttonDiv.html(info.caption);
-
- if (info.isDefault) {
- buttonDiv.addClass('default');
- }
-
- if (info.click) {
- var oldClick = info.click;
- info.click = function () {
- if (oldClick() == false) {
- return false;
- }
-
- hideModalDialog();
-
- return false;
- };
- }
- else {
- info.click = hideModalDialog; /* every button closes the dialog */
- }
-
- buttonDiv.click(info.click);
-
- var td = $('<td></td>');
- td.append(buttonDiv);
- tr.append(td);
- });
-
- /* limit the size of the buttons container */
- buttonsContainer.css('max-width', (buttonsInfo.length * 10) + 'em');
-
- return buttonsContainer;
-}
-
-function makeModalDialogTitleBar(options) {
- /* available options:
- * * title: String
- * * closeButton: Boolean
- */
-
- var titleBar = $('<div class="modal-title-bar"></div>');
-
- var titleSpan = $('<span class="modal-title"></span>');
- titleSpan.html(options.title || '');
- if (options.closeButton) {
- titleSpan.css('margin', '0px 1.5em');
- }
-
- titleBar.append(titleSpan);
-
- if (options.closeButton) {
- var closeButton = $('<div class="button modal-close-button mouse-effect" title="close"></div>');
- closeButton.click(hideModalDialog);
- titleBar.append(closeButton);
- }
-
- return titleBar;
-}
-
-function runModalDialog(options) {
- /* available options:
- * * title: String
- * * closeButton: Boolean
- * * content: any
- * * buttons: 'ok'|'yesno'|'okcancel'|Array
- * * onYes: Function
- * * onNo: Function
- * * onOk: Function
- * * onCancel: Function
- * * onClose: Function
- * * onShow: Function
- * * stack: Boolean
- * * noKeys: Boolean
- */
-
- var content = $('<div></div>');
- var titleBar = null;
- var buttonsDiv = null;
- var defaultClick = null;
- var cancelClick = null;
-
- /* add title bar */
- if (options.title) {
- titleBar = makeModalDialogTitleBar({title: options.title, closeButton: options.closeButton});
- content.append(titleBar);
- }
-
- /* add supplied content */
- if (options.content) {
- var contentWrapper = $('<div style="padding: 10px;"></div>');
- contentWrapper.append(options.content);
- content.append(contentWrapper);
- }
-
- /* add buttons */
- if (options.buttons === 'yesno') {
- options.buttons = [
- {caption: 'No', click: options.onNo},
- {caption: 'Yes', isDefault: true, click: options.onYes}
- ];
- }
- if (options.buttons === 'yesnocancel') {
- options.buttons = [
- {caption: 'Cancel', isCancel: true, click: options.onCancel},
- {caption: 'No', click: options.onNo},
- {caption: 'Yes', isDefault: true, click: options.onYes}
- ];
- }
- else if (options.buttons === 'okcancel') {
- options.buttons = [
- {caption: 'Cancel', isCancel:true, click: options.onCancel},
- {caption: 'OK', isDefault: true, click: options.onOk}
- ];
- }
- else if (options.buttons === 'ok') {
- options.buttons = [
- {caption: 'OK', isDefault: true, click: options.onOk}
- ];
- }
-
- if (options.buttons) {
- buttonsDiv = makeModalDialogButtons(options.buttons);
- content.append(buttonsDiv);
-
- options.buttons.forEach(function (info) {
- if (info.isDefault) {
- defaultClick = info.click;
- }
- else if (info.isCancel) {
- cancelClick = info.click;
- }
- });
- }
-
- /* add some margins */
- if ((buttonsDiv || options.content) && titleBar) {
- titleBar.css('margin-bottom', '5px');
- }
-
- if (buttonsDiv && options.content) {
- buttonsDiv.css('margin-top', '5px');
- }
-
- var handleKeyUp = !options.noKeys && function (e) {
- if (!content.is(':visible')) {
- return;
- }
-
- switch (e.which) {
- case 13:
- if (defaultClick && defaultClick() == false) {
- return;
- }
-
- hideModalDialog();
-
- break;
-
- case 27:
- if (cancelClick && cancelClick() == false) {
- return;
- }
-
- hideModalDialog();
-
- break;
- }
- };
-
- var onClose = function () {
- if (options.onClose) {
- options.onClose();
- }
-
- /* unbind html handlers */
-
- $('html').unbind('keyup', handleKeyUp);
- };
-
- /* bind key handlers */
- $('html').bind('keyup', handleKeyUp);
-
- /* and finally, show the dialog */
-
- showModalDialog(content, onClose, options.onShow, options.stack);
-
- /* focus the default button if nothing else is focused */
- if (content.find('*:focus').length === 0) {
- content.find('div.button.default').focus();
- }
-}
-
-
- /* popup message */
-
-function showPopupMessage(message, type) {
- var container = $('div.popup-message-container');
- var content = $('<span class="popup-message"></span>');
-
- if (window._popupMessageTimeout) {
- clearTimeout(window._popupMessageTimeout);
- }
-
- content.html(message);
- content.addClass(type);
- container.html(content);
-
- var windowWidth = $(window).width();
- var messageWidth = container.width();
-
- container.css('display', 'block');
- container.css('left', (windowWidth - messageWidth) / 2);
-
- container.animate({'opacity': '1'}, 200);
-
- window._popupMessageTimeout = setTimeout(function () {
- window._popupMessageTimeout = null;
- container.animate({'opacity': '0'}, 200, function () {
- container.css('display', 'none');
- });
- }, 5000);
-}
+++ /dev/null
-
-$(window).load(function () {
- if (window.parent && window.parent.postMessage) {
- window.parent.postMessage({'hostname': hostname, 'version': version, 'url': window.location.href.replace('version/', '')}, '*');
- }
-});
+++ /dev/null
-<!DOCTYPE html>
-<html>
- <head>
- {% block meta %}
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <meta http-equiv="X-UA-Compatible" content="IE=Edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
- <meta name="apple-mobile-web-app-capable" content="yes">
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
- {% endblock %}
- <title>{% block title %}{% endblock %}</title>
- {% block style %}
- <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}css/jquery.timepicker.css" />
- <link rel="shortcut icon" href="/static/favicon.ico" />
- <link rel="apple-touch-icon" href="/static/favicon.ico" />
- {% endblock %}
- {% block script %}
- <script type="text/javascript" src="{{STATIC_URL}}js/css-browser-selector.js"></script>
- <script type="text/javascript" src="{{STATIC_URL}}js/jquery.min.js"></script>
- <script type="text/javascript" src="{{STATIC_URL}}js/jquery.timepicker.min.js"></script>
- <script type="text/javascript" src="{{STATIC_URL}}js/jquery.mousewheel.js"></script>
-
- <script type="text/javascript">
- var staticUrl = '{{STATIC_URL}}';
- </script>
- {% endblock %}
- </head>
-
- <body>
- {% block body %}{% endblock %}
- </body>
-</html>
+++ /dev/null
-{% extends "base.html" %}
-
-{% macro config_item(config) -%}
- <tr class="settings-item additional-config {% if config['advanced'] %}advanced-setting{% endif %}"
- {% if config.get('reboot') and enable_reboot %}reboot="true"{% endif %}
- {% if config.get('required') %}required="true"{% endif %}
- {% if config.get('strip') %}strip="true"{% endif %}
- {% if config.get('depends') %}depends="{{' '.join(config['depends'])}}"{% endif %}
- {% if config.get('min') is not none %}min="{{config['min']}}"{% endif %}
- {% if config.get('max') is not none %}max="{{config['max']}}"{% endif %}
- {% if config.get('floating') %}floating="true"{% endif %}
- {% if config.get('sign') %}sign="true"{% endif %}
- {% if config.get('snap') is not none %}snap="{{config['snap']}}"{% endif %}
- {% if config.get('ticks') %}ticks="{{config['ticks']}}"{% endif %}
- {% if config.get('ticksnum') is not none %}ticksnum="{{config['ticksnum']}}"{% endif %}
- {% if config.get('decimals') is not none %}decimals="{{config['decimals']}}"{% endif %}
- {% if config.get('unit') %}unit="{{config['unit']}}"{% endif %}
- {% if config.get('validate') %}validate="{{config['validate']}}"{% endif %}>
- {% if config['type'] == 'separator' %}
- <td colspan="100"><div class="settings-item-separator"></div></td>
- {% else %}
- <td class="settings-item-label"><span class="settings-item-label">{{config['label']}}</span></td>
- <td class="settings-item-value">
- {% if config['type'] == 'str' %}
- <input type="text" class="styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Entry">
- {% elif config['type'] == 'pwd' %}
- <input type="password" class="styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Entry">
- {% elif config['type'] == 'number' %}
- <input type="text" class="number styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Entry">
- {% elif config['type'] == 'range' %}
- <input type="text" class="range styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Slider">
- {% elif config['type'] == 'bool' %}
- <input type="checkbox" class="styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Switch">
- {% elif config['type'] == 'choices' %}
- <select class="styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Select">
- {% for choice in config['choices'] %}
- <option value="{{choice[0]}}">{{choice[1]}}</option>
- {% endfor %}
- </select>
- {% elif config['type'] == 'html' %}
- <div class="html styled {{config['section']}} {% if config.get('camera') %}camera{% else %}main{% endif %}-config" id="{{config['name']}}Html">
- {% endif %}
- </td>
- <td>{% if config.get('description') %}<span class="help-mark" title="{{config['description']}}">?</span>{% endif %}</td>
- {% endif %}
- </tr>
-{%- endmacro %}
-
-{% block title %}{% if title %}{{title}}{% else %}{{hostname}}{% endif %}{% endblock %}
-
-{% block style %}
- {{super()}}
- <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}css/ui.css" />
- <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}css/main.css" />
- {% if frame %}
- <link rel="stylesheet" type="text/css" href="{{STATIC_URL}}css/frame.css" />
- {% endif %}
-{% endblock %}
-
-{% block script %}
- {{super()}}
- <script type="text/javascript" src="{{STATIC_URL}}js/ui.js"></script>
- <script type="text/javascript" src="{{STATIC_URL}}js/main.js"></script>
- {% if frame %}
- <script type="text/javascript" src="{{STATIC_URL}}js/frame.js"></script>
- {% endif %}
- <script type="text/javascript">
- var adminUsername = '{{admin_username}}';
- var frame = {% if frame %}true{% else %}false{% endif %};
- var baseUri = null;
- </script>
-{% endblock %}
-
-{% block body %}
- {% if not frame %}
- <div class="header">
- <div class="header-container">
- <div class="settings-top-bar closed">
- <div class="button settings-button mouse-effect" title="settings"></div>
- <div class="button logout-button mouse-effect" title="switch user"></div>
- <select class="styled" id="cameraSelect" {% if not add_remove_cameras %}style="display: none;"{% endif %}></select>
- <div class="button rem-camera-button mouse-effect" id="remCameraButton" title="remove camera" {% if not add_remove_cameras %}style="display: none;"{% endif %}></div>
- <div class="button apply-button" id="applyButton">Apply</div>
- {% if hostname %}<div class="hostname">{{hostname}}</div>{% endif %}
- </div>
- <div class="button logout-button mouse-effect" title="switch user"></div>
- <div class="logo">
- <a href="/">
- <span class="logo">motionEye</span>
- <img class="logo" src="{{STATIC_URL}}img/motioneye-logo.svg">
- </a>
- </div>
- </div>
- </div>
- {% endif %}
- {% if not frame %}
- <div class="page">
- <div class="settings closed">
- <div class="settings-container">
- <div class="settings-section-title">
- <input type="checkbox" class="styled section general main-config" id="motionEyeSwitch">
- <span class="help-mark" title="general settings, not related to any camera">?</span>
- <a class="settings-section-title">General Settings</a>
- <span class="minimize open"></span>
- </div>
- <table class="settings">
- <tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Show Advanced Settings</span></td>
- <td class="settings-item-value"><input type="checkbox" class="styled general main-config" id="showAdvancedSwitch"></td>
- <td><span class="help-mark" title="enable this to be able to access all the advanced settings">?</span></td>
- </tr>
- <tr class="settings-item" required="true" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Administrator Username</span></td>
- <td class="settings-item-value"><input type="text" class="styled general main-config" id="adminUsernameEntry" readonly="readonly"></td>
- <td><span class="help-mark" title="the username to be used for configuring the system">?</span></td>
- </tr>
- <tr class="settings-item" strip="true" {% if enable_reboot %}reboot="true"{% endif %}>
- <td class="settings-item-label"><span class="settings-item-label">Administrator Password</span></td>
- <td class="settings-item-value"><input type="password" class="styled general main-config" id="adminPasswordEntry"></td>
- <td><span class="help-mark" title="administrator's password">?</span></td>
- </tr>
- <tr class="settings-item" required="true" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Surveillance Username</span></td>
- <td class="settings-item-value"><input type="text" class="styled general main-config" id="normalUsernameEntry"></td>
- <td><span class="help-mark" title="the username to be used for video surveillance">?</span></td>
- </tr>
- <tr class="settings-item" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Surveillance Password</span></td>
- <td class="settings-item-value"><input type="password" class="styled general main-config" id="normalPasswordEntry"></td>
- <td><span class="help-mark" title="the password for the surveillance user (leave empty for passwordless surveillance)">?</span></td>
- </tr>
- {% for config in main_sections.get('general', {}).get('configs', []) %}
- {{config_item(config)}}
- {% endfor %}
- <tr class="settings-item advanced-setting">
- <td colspan="100"><div class="settings-item-separator"></div></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Current Version</span></td>
- <td class="settings-item-value"><span class="settings-item-label">{{version}}</span></td>
- </tr>
- {% if enable_update %}
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Software Update</span></td>
- <td class="settings-item-value"><div class="button normal-button update-button" id="updateButton">Check</div></td>
- <td><span class="help-mark" title="checks for new versions and performs updates">?</span></td>
- </tr>
- {% endif %}
- <tr class="settings-item advanced-setting{% if not enable_reboot %} hidden{% endif %}">
- <td class="settings-item-label"><span class="settings-item-label">Power</span></td>
- <td class="settings-item-value"><div class="button normal-button shut-down-button" id="shutDownButton">Shut Down</div></td>
- <td><span class="help-mark" title="shuts down the system">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting{% if not enable_reboot %} hidden{% endif %}">
- <td class="settings-item-label"><span class="settings-item-label"></span></td>
- <td class="settings-item-value"><div class="button normal-button reboot-button" id="rebootButton">Reboot</div></td>
- <td><span class="help-mark" title="reboots the system">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td colspan="100"><div class="settings-item-separator"></div></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Configuration</span></td>
- <td class="settings-item-value"><div class="button normal-button backup-button" id="backupButton">Backup</div></td>
- <td><span class="help-mark" title="creates a file with the current configuration for you to save it locally">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label"></span></td>
- <td class="settings-item-value"><div class="button normal-button restore-button" id="restoreButton">Restore</div></td>
- <td><span class="help-mark" title="restores the configuration from a previously saved backup file">?</span></td>
- </tr>
- </table>
-
- {% for section in main_sections.values() %}
- {% if section.get('label') and section.get('configs') %}
- <div class="settings-section-title {% if section['advanced'] %}advanced-setting{% endif %}">
- {% if section.get('onoff') %}<input type="checkbox" class="styled section additional-section {{section['name']}} main-config" id="{{section['name']}}Switch">{% endif %}
- {% if section.get('description') %}<span class="help-mark" title="{{section['description']}}">?</span>{% endif %}
- <a class="settings-section-title">{{section['label']}}</a>
- <span class="minimize {% if section.get('open') %}open{% endif %}"></span>
- </div>
- <table class="settings {% if section.get('advanced') %}advanced-setting{% endif %}">
- {% for config in section['configs'] %}
- {{config_item(config)}}
- {% endfor %}
- </table>
- {% endif %}
- {% endfor %}
-
- <tr class="settings-item advanced-setting">
- <td colspan="100"><div class="settings-item-separator" style="margin: 0px 0px 1.5em 0px;"></div></td>
- </tr>
-
- <div class="settings-section-title">
- <input type="checkbox" class="styled section device camera-config" id="videoDeviceSwitch">
- <span class="help-mark" title="enable this if you want to use this camera device">?</span>
- <a class="settings-section-title">Video Device</a>
- <span class="minimize open"></span>
- </div>
- <table class="settings">
- <tr class="settings-item" required="true" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Camera Name</span></td>
- <td class="settings-item-value"><input type="text" class="styled device camera-config" id="deviceNameEntry" placeholder="camera name..."></td>
- <td><span class="help-mark" title="an alias for this camera device">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Camera Device</span></td>
- <td class="settings-item-value"><input type="text" class="styled device camera-config" id="deviceUriEntry" readonly="readonly"></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Camera Type</span></td>
- <td class="settings-item-value"><input type="text" class="styled device camera-config" id="deviceTypeEntry" readonly="readonly"></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td colspan="100"><div class="settings-item-separator"></div></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Light Switch Detection</span></td>
- <td class="settings-item-value"><input type="checkbox" class="styled device camera-config" id="lightSwitchDetectSwitch"></td>
- <td><span class="help-mark" title="enable this if you want sudden changes in light to not be treated as motion">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Automatic Brightness</span></td>
- <td class="settings-item-value"><input type="checkbox" class="styled device camera-config" id="autoBrightnessSwitch"></td>
- <td><span class="help-mark" title="enables software automatic brightness (only recommended for cameras without autobrightness)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
- <td class="settings-item-label"><span class="settings-item-label">Brightness</span></td>
- <td class="settings-item-value"><input type="text" class="range styled device camera-config" id="brightnessSlider"></td>
- <td><span class="help-mark" title="sets a desired brightness level for this camera">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
- <td class="settings-item-label"><span class="settings-item-label">Contrast</span></td>
- <td class="settings-item-value"><input type="text" class="range styled device camera-config" id="contrastSlider"></td>
- <td><span class="help-mark" title="sets a desired contrast level for this camera">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
- <td class="settings-item-label"><span class="settings-item-label">Saturation</span></td>
- <td class="settings-item-value"><input type="text" class="range styled device camera-config" id="saturationSlider"></td>
- <td><span class="help-mark" title="sets a desired saturation level for this camera">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
- <td class="settings-item-label"><span class="settings-item-label">Hue</span></td>
- <td class="settings-item-value"><input type="text" class="range styled device camera-config" id="hueSlider"></td>
- <td><span class="help-mark" title="sets a desired hue for this camera">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td colspan="100"><div class="settings-item-separator"></div></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Video Resolution</span></td>
- <td class="settings-item-value">
- <select class="video-resolution styled device camera-config" id="resolutionSelect">
- </select>
- </td>
- <td><span class="help-mark" title="the video resolution (larger values produce better quality but require more CPU power, larger storage space and bandwidth)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Video Rotation</span></td>
- <td class="settings-item-value">
- <select class="rotation styled device camera-config" id="rotationSelect">
- <option value="0">0°</option>
- <option value="90">90°</option>
- <option value="180">180°</option>
- <option value="270">270°</option>
- </select>
- </td>
- <td><span class="help-mark" title="use this to rotate the captured image, if your camera is not positioned correctly">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="2" max="30" snap="0" ticks="2|5|10|15|20|25|30" decimals="0">
- <td class="settings-item-label"><span class="settings-item-label">Frame Rate</span></td>
- <td class="settings-item-value"><input type="text" class="range styled device camera-config" id="framerateSlider"></td>
- <td><span class="help-mark" title="sets the number of frames captured by the camera every second (higher values produce smoother videos but require more CPU power, larger storage space and bandwidth)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Extra Motion Options</span></td>
- <td class="settings-item-value"><textarea class="styled device camera-config" id="extraOptionsEntry" rows="3"></textarea></td>
- <td><span class="help-mark" title="you can add here any extra options for the motion daemon (use the "name value" format, one option per line)">?</span></td>
- </tr>
- {% for config in camera_sections.get('device', {}).get('configs', []) %}
- {{config_item(config)}}
- {% endfor %}
- </table>
-
- <div class="settings-section-title advanced-setting">
- <span class="help-mark" title="choose where and how your media files are saved">?</span>
- <a class="settings-section-title">File Storage</a>
- <span class="minimize"></span>
- </div>
- <table class="settings advanced-setting">
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Storage Device</span></td>
- <td class="settings-item-value">
- <select class="styled storage camera-config" id="storageDeviceSelect">
- </select>
- </td>
- <td><span class="help-mark" title="indicates the storage device where the image and video files will be saved">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" required="true" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Network Server</span></td>
- <td class="settings-item-value"><input type="text" class="styled storage camera-config" id="networkServerEntry"></td>
- <td><span class="help-mark" title="the address of the network server (IP address or hostname)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" required="true" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Share Name</span></td>
- <td class="settings-item-value"><input type="text" class="styled storage camera-config" id="networkShareNameEntry"></td>
- <td><span class="help-mark" title="the name of the network share">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Share Username</span></td>
- <td class="settings-item-value"><input type="text" class="styled storage camera-config" id="networkUsernameEntry"></td>
- <td><span class="help-mark" title="the username to be supplied when accessing the network share (leave empty if no username is required)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Share Password</span></td>
- <td class="settings-item-value"><input type="password" class="styled storage camera-config" id="networkPasswordEntry"></td>
- <td><span class="help-mark" title="the password required by the network share (leave empty if no password is required)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" required="true" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Root Directory</span></td>
- <td class="settings-item-value"><input type="text" class="styled storage camera-config" id="rootDirectoryEntry"></td>
- <td><span class="help-mark" title="the root path (on the selected storage device) where the files will be saved">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td colspan="100"><div class="settings-item-separator"></div></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Disk Usage</span></td>
- <td class="settings-item-value">
- <div id="diskUsageProgressBar" class="progress-bar"></div>
- </td>
- <td><span class="help-mark" title="the used/total size of the disk where the root directory resides">?</span></td>
- </tr>
- {% for config in camera_sections.get('storage', {}).get('configs', []) %}
- {{config_item(config)}}
- {% endfor %}
- </table>
-
- <div class="settings-section-title advanced-setting">
- <input type="checkbox" class="styled section text-overlay camera-config" id="textOverlaySwitch">
- <span class="help-mark" title="choose what information is displayed on the captured frames">?</span>
- <a class="settings-section-title">Text Overlay</a>
- <span class="minimize"></span>
- </div>
- <table class="settings advanced-setting">
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Left Text</span></td>
- <td class="settings-item-value">
- <select class="styled text-overlay camera-config" id="leftTextSelect">
- <option value="camera-name">Camera Name</option>
- <option value="timestamp">Timestamp</option>
- <option value="custom-text">Custom Text</option>
- <option value="disabled">Disabled</option>
- </select>
- </td>
- <td><span class="help-mark" title="sets the text displayed on the movies and images, on the lower left corner">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" required="true" strip="true">
- <td class="settings-item-label"></td>
- <td class="settings-item-value"><input type="text" class="styled text-overlay camera-config" id="leftTextEntry" placeholder="custom text..."></td>
- <td><span class="help-mark" title="sets a custom left text; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %T = HH:MM:SS, %q = frame number, \n = new line">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Right Text</span></td>
- <td class="settings-item-value">
- <select class="styled text-overlay camera-config" id="rightTextSelect">
- <option value="camera-name">Camera Name</option>
- <option value="timestamp">Timestamp</option>
- <option value="custom-text">Custom Text</option>
- <option value="disabled">Disabled</option>
- </select>
- </td>
- <td><span class="help-mark" title="sets the text displayed on the movies and images, on the lower right corner">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" required="true" strip="true">
- <td class="settings-item-label"></td>
- <td class="settings-item-value"><input type="text" class="styled text-overlay camera-config" id="rightTextEntry" placeholder="custom text..."></td>
- <td><span class="help-mark" title="sets a custom right text; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %T = HH:MM:SS, %q = frame number, \n = new line">?</span></td>
- </tr>
- {% for config in camera_sections.get('text-overlay', {}).get('configs', []) %}
- {{config_item(config)}}
- {% endfor %}
- </table>
-
- <div class="settings-section-title" minimize-switch-independent="true">
- <input type="checkbox" class="styled section streaming camera-config" id="videoStreamingSwitch">
- <span class="help-mark" title="enable this if you want video streaming for this camera">?</span>
- <a class="settings-section-title">Video Streaming</a>
- <span class="minimize"></span>
- </div>
- <table class="settings">
- <tr class="settings-item advanced-setting localhost-streaming" min="1" max="30" snap="0" ticks="1|5|10|15|20|25|30" decimals="0">
- <td class="settings-item-label"><span class="settings-item-label">Streaming Frame Rate</span></td>
- <td class="settings-item-value"><input type="text" class="range styled streaming camera-config" id="streamingFramerateSlider"></td>
- <td><span class="help-mark" title="sets the number of frames transmitted every second on the live streaming">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting localhost-streaming" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
- <td class="settings-item-label"><span class="settings-item-label">Streaming Quality</span></td>
- <td class="settings-item-value"><input type="text" class="range styled streaming camera-config" id="streamingQualitySlider"></td>
- <td><span class="help-mark" title="sets the live streaming quality (higher values produce a better video quality but require more bandwidth)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting localhost-streaming">
- <td class="settings-item-label"><span class="settings-item-label">Streaming Image Resizing</span></td>
- <td class="settings-item-value"><input type="checkbox" class="styled streaming camera-config" id="streamingServerResizeSwitch"></td>
- <td><span class="help-mark" title="when this is enabled, the images are resized before they are sent to the browser (disable when running on a slow CPU)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting localhost-streaming" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
- <td class="settings-item-label"><span class="settings-item-label">Streaming Resolution</span></td>
- <td class="settings-item-value"><input type="text" class="range styled streaming camera-config" id="streamingResolutionSlider"></td>
- <td><span class="help-mark" title="the streaming resolution given as percent of the video device resolution (higher values produce better video quality but require more bandwidth)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="1024" max="65535" required="true">
- <td class="settings-item-label"><span class="settings-item-label">Streaming Port</span></td>
- <td class="settings-item-value"><input type="text" class="styled streaming camera-config" id="streamingPortEntry"></td>
- <td><span class="help-mark" title="sets the TCP port on which the webcam streaming server listens">?</span></td>
- </tr>
- {% if not old_motion %}
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Authentication Mode</span></td>
- <td class="settings-item-value">
- <select class="styled streaming camera-config" id="streamingAuthModeSelect">
- <option value="disabled">Disabled</option>
- <option value="basic">Basic</option>
- <option value="digest">Digest</option>
- </select>
- </td>
- <td><span class="help-mark" title="the authentication mode to use when accessing the stream (use Basic instead of Digest if you encounter issues with third party apps)">?</span></td>
- </tr>
- {% endif %}
- <tr class="settings-item advanced-setting localhost-streaming">
- <td class="settings-item-label"><span class="settings-item-label">Motion Optimization</span></td>
- <td class="settings-item-value"><input type="checkbox" class="styled streaming camera-config" id="streamingMotion"></td>
- <td><span class="help-mark" title="enable this if you want a lower frame rate for the live streaming when no motion is detected">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td colspan="100"><div class="settings-item-separator"></div></td>
- </tr>
- <tr class="settings-item advanced-setting localhost-streaming">
- <td class="settings-item-label"><span class="settings-item-label">Snapshot URL</span></td>
- <td class="settings-item-value"><input type="text" class="styled streaming camera-config" id="streamingSnapshotUrlEntry" readonly="readonly"></td>
- <td><span class="help-mark" title="a URL that provides a JPEG image with the most recent snapshot of the camera">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Streaming URL</span></td>
- <td class="settings-item-value"><input type="text" class="styled streaming camera-config" id="streamingMjpgUrlEntry" readonly="readonly"></td>
- <td><span class="help-mark" title="a URL that provides a MJPEG stream of the camera (there is no password protection for this URL!)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting localhost-streaming">
- <td class="settings-item-label"><span class="settings-item-label">Embed URL</span></td>
- <td class="settings-item-value"><input type="text" class="styled streaming camera-config" id="streamingEmbedUrlEntry" readonly="readonly"></td>
- <td><span class="help-mark" title="a URL that provides a minimal HTML document containing the camera frame, ready to be embedded">?</span></td>
- </tr>
- {% for config in camera_sections.get('streaming', {}).get('configs', []) %}
- {{config_item(config)}}
- {% endfor %}
- </table>
-
- <div class="settings-section-title">
- <input type="checkbox" class="styled section still-images camera-config" id="stillImagesSwitch">
- <span class="help-mark" title="enable this if you want to capture still images (pictures)">?</span>
- <a class="settings-section-title">Still Images</a>
- <span class="minimize"></span>
- </div>
- <table class="settings">
- <tr class="settings-item advanced-setting" required="true" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Image File Name</span></td>
- <td class="settings-item-value"><input type="text" class="styled still-images camera-config" id="imageFileNameEntry" placeholder="file name pattern..."></td>
- <td><span class="help-mark" title="sets the name pattern for the image (JPEG) files; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %q = frame number, %v = event number / = subfolder">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
- <td class="settings-item-label"><span class="settings-item-label">Image Quality</span></td>
- <td class="settings-item-value"><input type="text" class="range styled still-images camera-config" id="imageQualitySlider"></td>
- <td><span class="help-mark" title="sets the JPEG image quality (higher values produce a better image quality but require more storage space)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Capture Mode</span></td>
- <td class="settings-item-value">
- <select class="styled still-images camera-config" id="captureModeSelect">
- <option value="motion-triggered">Motion Triggered</option>
- <option value="interval-snapshots">Interval Snapshots</option>
- <option value="all-frames">All Frames</option>
- </select>
- </td>
- <td><span class="help-mark" title="sets the image capture mode: Motion Triggered = an image captured whenever motion is detected, Automated Snapshots = an image captured every x seconds, All Frames = saves each frame into an image file">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="1" max="86400" required="true">
- <td class="settings-item-label"><span class="settings-item-label">Snapshot Interval</span></td>
- <td class="settings-item-value"><input type="text" class="styled number still-images camera-config" id="snapshotIntervalEntry"><span class="settings-item-unit">seconds</span></td>
- <td><span class="help-mark" title="sets the interval (in seconds) for the automated snapshots">?</span></td>
- </tr>
- <tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Preserve Pictures</span></td>
- <td class="settings-item-value">
- <select class="styled still-images camera-config" id="preservePicturesSelect">
- <option value="1">For One Day</option>
- <option value="7">For One Week</option>
- <option value="30">For One Month</option>
- <option value="365">For One Year</option>
- <option value="0">Forever</option>
- <option value="-1">Custom</option>
- </select>
- </td>
- <td><span class="help-mark" title="images older than the specified duration are automatically deleted to free storage space">?</span></td>
- </tr>
- <tr class="settings-item" min="1" max="3650" required="true">
- <td class="settings-item-label"><span class="settings-item-label">Pictures Lifetime</span></td>
- <td class="settings-item-value"><input type="text" class="styled number still-images camera-config" id="picturesLifetimeEntry"><span class="settings-item-unit">days</span></td>
- <td><span class="help-mark" title="sets the number of days after which the pictures will be deleted automatically">?</span></td>
- </tr>
- {% for config in camera_sections.get('still-images', {}).get('configs', []) %}
- {{config_item(config)}}
- {% endfor %}
- </table>
-
- <div class="settings-section-title advanced-setting">
- <input type="checkbox" class="styled section motion-detection camera-config" id="motionDetectionSwitch">
- <span class="help-mark" title="enable this to use and configure the motion detection mechanism">?</span>
- <a class="settings-section-title">Motion Detection</a>
- <span class="minimize"></span>
- </div>
- <table class="settings advanced-setting">
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Show Frame Changes</span></td>
- <td class="settings-item-value"><input type="checkbox" class="styled motion-detection camera-config" id="showFrameChangesSwitch"></td>
- <td><span class="help-mark" title="if this is enabled, frame changes (number of pixels as well as the changed area) are shown on the video; temporarily enable this option to help adjust the motion detection parameters">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="0" max="20" snap="0" ticksnum="5" decimals="1" unit="%">
- <td class="settings-item-label"><span class="settings-item-label">Frame Change Threshold</span></td>
- <td class="settings-item-value"><input type="text" class="range styled motion-detection camera-config" id="frameChangeThresholdSlider"></td>
- <td><span class="help-mark" title="indicates the minimal percent of the image that must change between two successive frames in order for motion to be detected (smaller values give a more sensitive detection, but are prone to false positives)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Auto Noise Detection</span></td>
- <td class="settings-item-value"><input type="checkbox" class="styled motion-detection camera-config" id="autoNoiseDetectSwitch"></td>
- <td><span class="help-mark" title="enable this to automatically adjust the noise level">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="0" max="25" snap="0" ticksnum="6" decimals="0" unit="%">
- <td class="settings-item-label"><span class="settings-item-label">Noise Level</span></td>
- <td class="settings-item-value"><input type="text" class="range styled motion-detection camera-config" id="noiseLevelSlider"></td>
- <td><span class="help-mark" title="manually sets the noise level to a fixed value">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td colspan="100"><div class="settings-item-separator"></div></td>
- </tr>
- <tr class="settings-item advanced-setting" min="1" max="86400" required="true">
- <td class="settings-item-label"><span class="settings-item-label">Motion Gap</span></td>
- <td class="settings-item-value"><input type="text" class="number styled motion-detection camera-config" id="eventGapEntry"><span class="settings-item-unit">seconds</span></td>
- <td><span class="help-mark" title="sets the number of seconds of silence (i.e. no motion) that mark the end of a motion event">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="0" max="100" required="true">
- <td class="settings-item-label"><span class="settings-item-label">Captured Before</span></td>
- <td class="settings-item-value"><input type="text" class="number styled motion-detection camera-config" id="preCaptureEntry"><span class="settings-item-unit">frames</span></td>
- <td><span class="help-mark" title="sets the number of frames to be captured (and included in the movie) before a motion event is detected">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="0" max="100" required="true">
- <td class="settings-item-label"><span class="settings-item-label">Captured After</span></td>
- <td class="settings-item-value"><input type="text" class="number styled motion-detection camera-config" id="postCaptureEntry"><span class="settings-item-unit">frames</span></td>
- <td><span class="help-mark" title="sets the number of frames to be captured (and included in the movie) after a motion event is detected">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="1" max="1000" required="true">
- <td class="settings-item-label"><span class="settings-item-label">Minimum Motion Frames</span></td>
- <td class="settings-item-value"><input type="text" class="number styled motion-detection camera-config" id="minimumMotionFramesEntry"><span class="settings-item-unit">frames</span></td>
- <td><span class="help-mark" title="sets the minimum number of successive motion frames required to start a motion event">?</span></td>
- </tr>
- {% for config in camera_sections.get('motion-detection', {}).get('configs', []) %}
- {{config_item(config)}}
- {% endfor %}
- </table>
-
- <div class="settings-section-title">
- <input type="checkbox" class="styled section motion-movies camera-config" id="motionMoviesSwitch">
- <span class="help-mark" title="enable this if you want to record motion movies">?</span>
- <a class="settings-section-title">Motion Movies</a>
- <span class="minimize"></span>
- </div>
- <table class="settings">
- <tr class="settings-item advanced-setting" required="true" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Movie File Name</span></td>
- <td class="settings-item-value"><input type="text" class="styled motion-movies camera-config" id="movieFileNameEntry" placeholder="file name pattern..."></td>
- <td><span class="help-mark" title="sets the name pattern for the movie (MPEG) files; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %q = frame number, / = subfolder">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="0" max="100" snap="2" ticksnum="5" decimals="0" unit="%">
- <td class="settings-item-label"><span class="settings-item-label">Movie Quality</span></td>
- <td class="settings-item-value"><input type="text" class="range styled motion-movies camera-config" id="movieQualitySlider"></td>
- <td><span class="help-mark" title="sets the MPEG video quality (higher values produce a better video quality but require more storage space)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="0" max="86400" required="true">
- <td class="settings-item-label"><span class="settings-item-label">Maximum Movie Length</span></td>
- <td class="settings-item-value"><input type="text" class="number styled motion-movies camera-config" id="maxMovieLengthEntry"><span class="settings-item-unit">seconds</span></td>
- <td><span class="help-mark" title="sets the maximum length of motion movies, in seconds; if the motion event lasts longer, a new movie file is created; use 0 for infinite length">?</span></td>
- </tr>
- <tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Preserve Movies</span></td>
- <td class="settings-item-value">
- <select class="styled motion-movies camera-config" id="preserveMoviesSelect">
- <option value="1">For One Day</option>
- <option value="7">For One Week</option>
- <option value="30">For One Month</option>
- <option value="365">For One Year</option>
- <option value="0">Forever</option>
- <option value="-1">Custom</option>
- </select>
- </td>
- <td><span class="help-mark" title="movies older than the specified duration are automatically deleted to free storage space">?</span></td>
- </tr>
- <tr class="settings-item" min="1" max="3650" required="true">
- <td class="settings-item-label"><span class="settings-item-label">Movies Lifetime</span></td>
- <td class="settings-item-value"><input type="text" class="styled number motion-movies camera-config" id="moviesLifetimeEntry"><span class="settings-item-unit">days</span></td>
- <td><span class="help-mark" title="sets the number of days after which the movies will be deleted automatically">?</span></td>
- </tr>
- {% for config in camera_sections.get('motion-movies', {}).get('configs', []) %}
- {{config_item(config)}}
- {% endfor %}
- </table>
- <div class="settings-section-title advanced-setting">
- <span class="help-mark" title="enable this if you want to be notified when motion is detected">?</span>
- <a class="settings-section-title">Motion Notifications</a>
- <span class="minimize"></span>
- </div>
- <table class="settings">
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Email Notifications</span></td>
- <td class="settings-item-value"><input type="checkbox" class="styled notifications camera-config" id="emailNotificationsSwitch"></td>
- <td><span class="help-mark" title="enable this if you want to receive email notifications whenever a motion event is detected">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" required="true" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Email Addresses</span></td>
- <td class="settings-item-value"><input type="text" class="styled notifications camera-config" id="emailAddressesEntry" placeholder="email addresses..."></td>
- <td><span class="help-mark" title="email addresses (separated by comma) that are added here will receive notifications whenever a motion event is detected">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" required="true" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">SMTP Server</span></td>
- <td class="settings-item-value"><input type="text" class="styled notifications camera-config" id="smtpServerEntry" placeholder="e.g. smtp.gmail.com"></td>
- <td><span class="help-mark" title="enter the hostname or IP address of your SMTP server (for Gmail use smtp.gmail.com)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="1" max="65535" required="true">
- <td class="settings-item-label"><span class="settings-item-label">SMTP Port</span></td>
- <td class="settings-item-value"><input type="text" class="styled number notifications camera-config" id="smtpPortEntry" placeholder="e.g. 587"></td>
- <td><span class="help-mark" title="enter the port used by your SMTP server (usually 465 for non-TLS connections and 587 for TLS connections)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">SMTP Account</span></td>
- <td class="settings-item-value"><input type="text" class="styled notifications camera-config" id="smtpAccountEntry" placeholder="account@gmail.com..."></td>
- <td><span class="help-mark" title="enter your SMTP account (normally your email address)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">SMTP Password</span></td>
- <td class="settings-item-value"><input type="password" class="styled notifications camera-config" id="smtpPasswordEntry"></td>
- <td><span class="help-mark" title="enter your SMTP account password (for Gmail use your Google password or an app-specific generated password)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Use TLS</span></td>
- <td class="settings-item-value"><input type="checkbox" class="styled notifications camera-config" id="smtpTlsSwitch"></td>
- <td><span class="help-mark" title="enable this if your SMTP server requires TLS (Gmail needs this to be enabled)">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" min="0" max="60" required="true">
- <td class="settings-item-label"><span class="settings-item-label">Attached Pictures Time Span</span></td>
- <td class="settings-item-value"><input type="text" class="number styled notifications camera-config" id="emailPictureTimeSpanEntry"><span class="settings-item-unit">seconds</span></td>
- <td><span class="help-mark" title="defines the picture search time interval to use when creating email attachments (higher values generate emails with more pictures at the cost of an increased notification delay); set to 0 to disable picture attachments">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td colspan="100"><div class="settings-item-separator"></div></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Web Hook Notifications</span></td>
- <td class="settings-item-value"><input type="checkbox" class="styled notifications camera-config" id="webHookNotificationsSwitch"></td>
- <td><span class="help-mark" title="enable this if you want a URL to be requested whenever a motion event is detected">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" required="true" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Web Hook URL</span></td>
- <td class="settings-item-value"><input type="text" class="styled notifications camera-config" id="webHookUrlEntry" placeholder="e.g. http://example.com/notify/"></td>
- <td><span class="help-mark" title="a URL to be requested when motion is detected; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %q = frame number">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">HTTP Method</span></td>
- <td class="settings-item-value">
- <select class="styled notifications camera-config" id="webHookHttpMethodSelect">
- <option value="GET">GET</option>
- <option value="POST">POST</option>
- </select>
- </td>
- <td><span class="help-mark" title="the HTTP method to use when requesting the web hook URL">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td colspan="100"><div class="settings-item-separator"></div></td>
- </tr>
- <tr class="settings-item advanced-setting">
- <td class="settings-item-label"><span class="settings-item-label">Run A Command</span></td>
- <td class="settings-item-value"><input type="checkbox" class="styled notifications camera-config" id="commandNotificationsSwitch"></td>
- <td><span class="help-mark" title="enable this if you want to execute a command whenever a motion event is detected">?</span></td>
- </tr>
- <tr class="settings-item advanced-setting" required="true" strip="true">
- <td class="settings-item-label"><span class="settings-item-label">Command</span></td>
- <td class="settings-item-value"><input type="text" class="styled notifications camera-config" id="commandNotificationsEntry" placeholder="command..."></td>
- <td><span class="help-mark" title="a command to be executed when motion is detected; multiple commands can be separated by a semicolon; the following special tokens are accepted: %Y = year, %m = month, %d = date, %H = hour, %M = minute, %S = second, %q = frame number">?</span></td>
- </tr>
- {% for config in camera_sections.get('notifications', {}).get('configs', []) %}
- {{config_item(config)}}
- {% endfor %}
- </table>
-
- <div class="settings-section-title">
- <input type="checkbox" class="styled section working-schedule camera-config" id="workingScheduleSwitch">
- <span class="help-mark" title="enable this if you want to define a weekly working schedule for motion detection">?</span>
- <a class="settings-section-title">Working Schedule</a>
- <span class="minimize"></span>
- </div>
- <table class="settings">
- <tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Monday</span></td>
- <td class="settings-item-value">
- <input type="checkbox" class="styled working-schedule camera-config" id="mondayEnabledSwitch">
- <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="mondayFromEntry">
- <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="mondayToEntry">
- </td>
- <td><span class="help-mark" title="sets the working schedule time interval for Mondays">?</span></td>
- </tr>
- <tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Tuesday</span></td>
- <td class="settings-item-value">
- <input type="checkbox" class="styled working-schedule camera-config" id="tuesdayEnabledSwitch">
- <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="tuesdayFromEntry">
- <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="tuesdayToEntry">
- </td>
- <td><span class="help-mark" title="sets the working schedule time interval for Tuesdays">?</span></td>
- </tr>
- <tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Wednesday</span></td>
- <td class="settings-item-value">
- <input type="checkbox" class="styled working-schedule camera-config" id="wednesdayEnabledSwitch">
- <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="wednesdayFromEntry">
- <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="wednesdayToEntry">
- </td>
- <td><span class="help-mark" title="sets the working schedule time interval for Wednesdays">?</span></td>
- </tr>
- <tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Thursday</span></td>
- <td class="settings-item-value">
- <input type="checkbox" class="styled working-schedule camera-config" id="thursdayEnabledSwitch">
- <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="thursdayFromEntry">
- <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="thursdayToEntry">
- </td>
- <td><span class="help-mark" title="sets the working schedule time interval for Thursdays">?</span></td>
- </tr>
- <tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Friday</span></td>
- <td class="settings-item-value">
- <input type="checkbox" class="styled working-schedule camera-config" id="fridayEnabledSwitch">
- <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="fridayFromEntry">
- <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="fridayToEntry">
- </td>
- <td><span class="help-mark" title="sets the working schedule time interval for Friday">?</span></td>
- </tr>
- <tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Saturday</span></td>
- <td class="settings-item-value">
- <input type="checkbox" class="styled working-schedule camera-config" id="saturdayEnabledSwitch">
- <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="saturdayFromEntry">
- <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="saturdayToEntry">
- </td>
- <td><span class="help-mark" title="sets the working schedule time interval for Saturday">?</span></td>
- </tr>
- <tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Sunday</span></td>
- <td class="settings-item-value">
- <input type="checkbox" class="styled working-schedule camera-config" id="sundayEnabledSwitch">
- <span class="settings-item-unit time">from</span><input type="text" class="styled working-schedule time camera-config" id="sundayFromEntry">
- <span class="settings-item-unit time">to</span><input type="text" class="styled working-schedule time camera-config" id="sundayToEntry">
- </td>
- <td><span class="help-mark" title="sets the working schedule time interval for Sunday">?</span></td>
- </tr>
- <tr class="settings-item">
- <td class="settings-item-label"><span class="settings-item-label">Detect Motion</span></td>
- <td class="settings-item-value">
- <select class="styled working-schedule camera-config" id="workingScheduleTypeSelect">
- <option value="during">During Working Schedule</option>
- <option value="outside">Outside Working Schedule</option>
- </select>
- </td>
- <td><span class="help-mark" title="sets whether motion detection should be active during or outside the working schedule">?</span></td>
- </tr>
- {% for config in camera_sections.get('working-schedule', {}).get('configs', []) %}
- {{config_item(config)}}
- {% endfor %}
- </table>
-
- {% for section in camera_sections.values() %}
- {% if section.get('label') and section.get('configs') %}
- <div class="settings-section-title {% if section['advanced'] %}advanced-setting{% endif %}">
- {% if section.get('onoff') %}<input type="checkbox" class="styled section additional-section {{section['name']}} camera-config" id="{{section['name']}}Switch">{% endif %}
- {% if section.get('description') %}<span class="help-mark" title="{{section['description']}}">?</span>{% endif %}
- <a class="settings-section-title">{{section['label']}}</a>
- <span class="minimize {% if section.get('open') %}open{% endif %}"></span>
- </div>
- <table class="settings">
- {% for config in section['configs'] %}
- {{config_item(config)}}
- {% endfor %}
- </table>
- {% endif %}
- {% endfor %}
-
- <div class="settings-progress"></div>
- </div>
- </div>
- <img class="background-logo" src="{{STATIC_URL}}img/motioneye-logo.svg" onmousedown="return false;">
- <div class="page-container"></div>
- <div class="footer">
- <div class="copyright-note">copyright © Calin Crisan</div>
- </div>
- </div>
- {% else %}
- <div class="camera-frame" id="camera{{camera_id}}"
- streaming_framerate="{{camera_config['stream_maxrate']}}" streaming_server_resize="{{camera_config['@webcam_server_resize']}}"
- proto="{{camera_config['@proto']}}" url="{{camera_config['@url']}}">
-
- <div class="camera-container">
- <div class="camera-placeholder"><img class="no-camera" src="{{STATIC_URL}}img/no-camera.svg"></div><img
- class="camera"><div class="camera-progress"><img class="camera-progress"></div>
- </div>
- </div>
- {% endif %}
- <div class="modal-glass"></div>
- <div class="modal-container"></div>
- <div class="popup-message-container"></div>
-{% endblock %}
+++ /dev/null
-{% extends "base.html" %}
-
-{% block script %}
- {{super()}}
- <script type="text/javascript">
- var hostname = '{{hostname}}';
- var version = '{{version}}';
- </script>
- <script type="text/javascript" src="{{STATIC_URL}}js/version.js"></script>
-{% endblock %}
-
-{% block body %}
-hostname = "{{hostname}}"<br>
-version = "{{version}}"
-{% endblock %}
+++ /dev/null
-#!/usr/bin/env python
-
-# Copyright (c) 2013 Calin Crisan
-# This file is part of motionEye.
-#
-# motionEye is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import logging
-import sys
-import urllib2
-import urlparse
-
-import settings
-
-from motioneye import _configure_settings, _configure_logging
-
-
-_configure_settings()
-_configure_logging()
-
-
-def print_usage():
- print 'Usage: webhook.py <method> <url>'
-
-
-if __name__ == '__main__':
- if len(sys.argv) < 3:
- print_usage()
- sys.exit(-1)
-
- method = sys.argv[1]
- url = sys.argv[2]
-
- logging.debug('method = %s' % method)
- logging.debug('url = %s' % url)
-
- if method == 'POST':
- parts = urlparse.urlparse(url)
- data = parts.query
-
- else:
- data = None
-
- request = urllib2.Request(url, data)
- try:
- urllib2.urlopen(request, timeout=settings.REMOTE_REQUEST_TIMEOUT)
- logging.debug('webhook successfully called')
-
- except Exception as e:
- logging.error('failed to call webhook: %s' % e)