camera_ids = get_camera_ids()
cameras = [get_camera(camera_id) for camera_id in camera_ids]
- return bool([c for c in cameras if c['@enabled'] and c['@proto'] == 'v4l2'])
+ return bool([c for c in cameras if c.get('@enabled') and utils.local_camera(c)])
def get_network_shares():
camera_config = _conf_to_dict(lines)
- camera_config.setdefault('@proto', 'v4l2')
-
- # determine the enabled status
- if camera_config['@proto'] == 'v4l2':
+ if utils.local_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
if 'gap' in camera_config:
camera_config['event_gap'] = camera_config.pop('gap')
- _set_default_motion_camera(camera_id, camera_config, False)
+ _set_default_motion_camera(camera_id, camera_config)
_camera_config_cache[camera_id] = dict(camera_config)
def set_camera(camera_id, camera_config):
global _camera_config_cache
+ camera_config = dict(camera_config)
camera_config['@id'] = camera_id
- _camera_config_cache[camera_id] = dict(camera_config)
+ _camera_config_cache[camera_id] = camera_config
- if camera_config['@proto'] == 'v4l2':
+ if utils.local_camera(camera_config):
old_motion = _is_old_motion()
# adapt directives to old configuration, if needed
camera_config['webcam_localhost'] = camera_config.pop('stream_localhost')
if 'event_gap' in camera_config:
camera_config['gap'] = camera_config.pop('event_gap')
+
+ camera_config['netcam_tolerant_check'] = True
_set_default_motion_camera(camera_id, camera_config, old_motion)
logging.info('adding new camera with id %(id)s...' % {'id': camera_id})
- # add the default camera config
- proto = device_details['proto']
-
- data = OrderedDict()
- data['@proto'] = proto
- data['@enabled'] = device_details.get('enabled', True)
-
- if proto == 'v4l2':
- data['@name'] = 'Camera' + str(camera_id)
- data['videodevice'] = device_details['device_uri']
- if 'width' in device_details:
- data['width'] = device_details['width']
- data['height'] = device_details['height']
- data['ffmpeg_bps'] = device_details['ffmpeg_bps']
-
- if ('root_directory' in device_details) and ('storage_device' in device_details):
- if ((device_details['storage_device'] == 'network-share') and settings.SMB_SHARES and
- ('network_server' in device_details) and ('network_share_name' in device_details) and
- ('network_username' in device_details)):
-
- mount_point = smbctl.make_mount_point(
- device_details['network_server'], device_details['network_share_name'], device_details['network_username'])
-
- if device_details['root_directory'].startswith('/'):
- device_details['root_directory'] = device_details['root_directory'][1:]
- data['target_dir'] = os.path.normpath(os.path.join(mount_point, device_details['root_directory']))
- data['@storage_device'] = 'network-share'
-
- elif device_details['storage_device'].startswith('local-disk'):
- target_dev = device_details['storage_device'][10:].replace('-', '/')
- mounted_partitions = diskctl.list_mounted_partitions()
- partition = mounted_partitions[target_dev]
- mount_point = partition['mount_point']
-
- if device_details['root_directory'].startswith('/'):
- device_details['root_directory'] = device_details['root_directory'][1:]
- data['target_dir'] = os.path.normpath(os.path.join(mount_point, device_details['root_directory']))
- data['@storage_device'] = device_details['storage_device']
-
- else:
- data['target_dir'] = device_details['root_directory']
- data['@storage_device'] = 'custom-path'
-
- else: # remote
- data['@host'] = device_details['host']
- data['@port'] = device_details['port']
- data['@username'] = device_details['username']
- data['@password'] = device_details['password']
- data['@remote_camera_id'] = device_details['remote_camera_id']
- data['@enabled'] = device_details.get('enabled', True)
+ # prepare a default camera config
+ camera_config = {'@enabled': True}
+ if device_details['proto'] == 'v4l2':
+ _set_default_motion_camera(camera_id, camera_config)
+ camera_config_ui = camera_dict_to_ui(camera_config)
+ camera_config_ui.update(device_details)
+ camera_config = camera_ui_to_dict(camera_config_ui)
+
+ elif device_details['proto'] == 'motioneye':
+ camera_config['@proto'] = 'motioneye'
+ 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']
+
+ else: # assuming netcam
+ camera_config['netcam_url'] = 'http://dummy' # used only to identify it as a netcam
+ _set_default_motion_camera(camera_id, camera_config)
+ camera_config_ui = camera_dict_to_ui(camera_config)
+ camera_config_ui.update(device_details)
+ camera_config = camera_ui_to_dict(camera_config_ui)
# write the configuration to file
- set_camera(camera_id, data)
+ set_camera(camera_id, camera_config)
_camera_ids_cache = None
_camera_config_cache = {}
- data = get_camera(camera_id)
+ camera_config = get_camera(camera_id)
- return camera_id, data
+ return camera_id, camera_config
def rem_camera(camera_id):
def camera_ui_to_dict(ui):
- if not ui['resolution']: # avoid errors for empty resolution setting
- ui['resolution'] = '352x288'
-
- width = int(ui['resolution'].split('x')[0])
- height = int(ui['resolution'].split('x')[1])
- threshold = int(float(ui['frame_change_threshold']) * width * height / 100)
-
data = {
# device
'@name': ui['name'],
'@enabled': ui['enabled'],
- '@proto': ui['proto'],
- 'videodevice': ui['device_uri'],
'lightswitch': int(ui['light_switch_detect']) * 50,
- 'auto_brightness': ui['auto_brightness'],
- 'width': width,
- 'height': height,
'framerate': int(ui['framerate']),
'rotate': int(ui['rotation']),
# motion detection
'text_changes': ui['show_frame_changes'],
'locate_motion_mode': ui['show_frame_changes'],
- 'threshold': threshold,
'noise_tune': ui['auto_noise_detect'],
'noise_level': max(1, int(round(int(ui['noise_level']) * 2.55))),
'event_gap': int(ui['event_gap']),
'on_event_start': ''
}
- on_event_start = []
-
- 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
+ if ui['proto'] == 'v4l2':
+ # device
+ data['videodevice'] = ui['uri']
+
+ # 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)))
- else:
- data['contrast'] = max(1, int(round(int(ui['contrast']) * 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)))
- if 'saturation' in ui:
- if int(ui['saturation']) == 50:
- data['saturation'] = 0
-
- else:
- data['saturation'] = max(1, int(round(int(ui['saturation']) * 2.55)))
+ else: # assuming http/netcam
+ # device
+ data['netcam_url'] = ui['proto'] + '://' + ui['host']
+ if ui['port']:
+ data['netcam_url'] += ':' + ui['port']
- if 'hue' in ui:
- if int(ui['hue']) == 50:
- data['hue'] = 0
-
- else:
- data['hue'] = max(1, int(round(int(ui['hue']) * 2.55)))
+ data['netcam_url'] += ui['uri']
+
+ if ui['username'] or ui['password']:
+ data['necam_userpass'] = (ui['username'] or '') + ':' + (ui['password'] or '')
+
+ data['netcam_http'] = '1.1'
+
+ 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'])
else:
data['text_right'] = ui['custom_right_text']
- if data['width'] > 320:
+ if ui['proto'] != 'v4l2' or data['width'] > 320:
data['text_double'] = True
if ui['still_images']:
data['quality'] = max(1, int(ui['image_quality']))
if ui['motion_movies']:
- max_val = data['width'] * data['height'] * data['framerate'] / 3
+ if ui['proto'] == 'v4l2':
+ max_val = data['width'] * data['height'] * data['framerate'] / 3
+
+ else:
+ 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'])
+
+ # event start notifications
+ on_event_start = []
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)
'password': ui['email_notifications_smtp_password'],
'tls': ui['email_notifications_smtp_tls'],
'to': emails})
-
+
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)
commands = ui['command_notifications_exec'].split(';')
on_event_start += [c.strip() for c in commands]
- 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'])
-
if on_event_start:
data['on_event_start'] = '; '.join(on_event_start)
def camera_dict_to_ui(data):
- usage = utils.get_disk_usage(data['target_dir'])
- if usage:
- disk_used, disk_total = usage
-
- else:
- disk_used, disk_total = 0, 0
-
- resolutions = v4l2ctl.list_resolutions(data['videodevice'])
- resolutions = [(str(w) + 'x' + str(h)) for (w, h) in resolutions]
-
- threshold = data['threshold'] * 100.0 / (data['width'] * data['height'])
-
ui = {
# device
'name': data['@name'],
'enabled': data['@enabled'],
'id': data['@id'],
- 'proto': data['@proto'],
- 'host': data.get('@host', ''),
- 'port': data.get('@port', ''),
- 'device_uri': data['videodevice'],
'light_switch_detect': data['lightswitch'] > 0,
- 'auto_brightness': data['auto_brightness'],
- 'resolution': str(data['width']) + 'x' + str(data['height']),
- 'available_resolutions': resolutions,
'framerate': int(data['framerate']),
'rotation': int(data['rotate']),
'network_share_name': data['@network_share_name'],
'network_username': data['@network_username'],
'network_password': data['@network_password'],
- 'disk_used': disk_used,
- 'disk_total': disk_total,
+ 'disk_used': 0,
+ 'disk_total': 0,
'available_disks': diskctl.list_mounted_disks(),
# text overlay
# motion detection
'show_frame_changes': data['text_changes'] or data['locate_motion_mode'],
- 'frame_change_threshold': threshold,
'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']),
+ # motion notifications
+ 'email_notifications_enabled': False,
+ 'web_hook_notifications_enabled': False,
+ 'command_notifications_enabled': False,
+
# working schedule
'working_schedule': False,
'monday_from': '09:00', 'monday_to': '17:00',
'saturday_from': '09:00', 'saturday_to': '17:00',
'sunday_from': '09:00', 'sunday_to': '17:00'
}
-
- 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(';')]
-
- # 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(ui['device_uri'])
- if brightness is not None: # has brightness control
- if data.get('brightness', 0) != 0:
- ui['brightness'] = brightness
-
+
+ if utils.net_camera(data):
+ netcam_url = data.get('netcam_url')
+ proto, rest = netcam_url.split('://')
+ parts = rest.split('/', 1)
+ if len(parts) > 1:
+ host_port, uri = parts[:2]
+
else:
- ui['brightness'] = 50
+ host_port, uri = rest, ''
- contrast = v4l2ctl.get_contrast(ui['device_uri'])
- if contrast is not None: # has contrast control
- if data.get('contrast', 0) != 0:
- ui['contrast'] = contrast
+ parts = host_port.split(':')
+ if len(parts) > 1:
+ host, port = parts[:2]
else:
- ui['contrast'] = 50
+ host, port = host_port, ''
+
+ ui['proto'] = proto
+ ui['host'] = host
+ ui['port'] = port
+ ui['uri'] = uri
- saturation = v4l2ctl.get_saturation(ui['device_uri'])
- if saturation is not None: # has saturation control
- if data.get('saturation', 0) != 0:
- ui['saturation'] = saturation
+ userpass = data.get('netcam_userpass')
+ if userpass:
+ ui['username'], ui['password'] = userpass.split(':', 1)
else:
- ui['saturation'] = 50
+ ui['username'], ui['password'] = '', ''
+
+ # width & height are not available for netcams,
+ # we have no other choice but use something like 640x480 as reference
+ threshold = data['threshold'] * 100.0 / (640 * 480)
+
+ else: # assuming v4l2
+ ui['proto'] = 'v4l2'
+ ui['host'], ui['port'] = None, None
+ ui['uri'] = data['videodevice']
+ ui['username'], ui['password'] = None, None
+
+ # 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(ui['uri'])
+ 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(ui['uri'])
+ 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(ui['uri'])
+ 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(ui['uri'])
+ if hue is not None: # has hue control
+ if data.get('hue', 0) != 0:
+ ui['hue'] = hue
+
+ else:
+ ui['hue'] = 50
- hue = v4l2ctl.get_hue(ui['device_uri'])
- if hue is not None: # has hue control
- if data.get('hue', 0) != 0:
- ui['hue'] = hue
+ threshold = data['threshold'] * 100.0 / (data['width'] * data['height'])
- else:
- ui['hue'] = 50
+ 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'])
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['image_quality'] = data['quality']
ffmpeg_bps = data['ffmpeg_bps']
- if ffmpeg_bps is not None:
- max_val = data['width'] * data['height'] * data['framerate'] / 3
+ if ffmpeg_bps is not None:
+ if utils.v4l2_camera(data):
+ max_val = data['width'] * data['height'] * data['framerate'] / 3
+
+ else:
+ 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['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'] = True
+ # event start notifications
+ 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(';')]
+
command_notifications = []
for e in on_event_start:
if e.count('sendmail.py') and e.count('motion_start'):
ui['command_notifications_enabled'] = True
ui['command_notifications_exec'] = '; '.join(command_notifications)
- working_schedule = data['@working_schedule']
- if working_schedule:
- days = working_schedule.split('|')
- 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'] = True
-
return ui
data.setdefault('@wifi_key', '')
-def _set_default_motion_camera(camera_id, data, old_motion):
+def _set_default_motion_camera(camera_id, data, old_motion=False):
data.setdefault('@name', 'Camera' + str(camera_id))
data.setdefault('@enabled', False)
- data.setdefault('@proto', 'v4l2')
- data.setdefault('videodevice', '/dev/video0')
+ 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('brightness', 0)
- data.setdefault('contrast', 0)
- data.setdefault('saturation', 0)
- data.setdefault('hue', 0)
- data.setdefault('width', 352)
- data.setdefault('height', 288)
data.setdefault('framerate', 2)
data.setdefault('rotate', 0)
data.setdefault('stream_maxrate', 5)
data.setdefault('stream_quality', 85)
data.setdefault('stream_motion', False)
+
data.setdefault('@webcam_resolution', 100)
data.setdefault('@webcam_server_resize', False)
raise HTTPError(404, 'no such camera')
local_config = config.get_camera(camera_id)
- if local_config['@proto'] != 'v4l2':
- def on_response(remote_ui_config):
- if remote_ui_config is None:
- return self.finish_json({'error': 'Failed to get remote camera configuration for %(url)s.' % {
- 'url': utils.make_camera_url(local_config)}})
+ if utils.local_camera(local_config):
+ ui_config = config.camera_dict_to_ui(local_config)
+
+ self.finish_json(ui_config)
+
+ else:
+ 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.make_camera_url(local_config)}, 'msg': error})
for key, value in local_config.items():
remote_ui_config[key.replace('@', '')] = value
remote.get_config(local_config, on_response)
- else:
- ui_config = config.camera_dict_to_ui(local_config)
-
- self.finish_json(ui_config)
-
else:
logging.debug('getting main config')
raise HTTPError(404, 'no such camera')
local_config = config.get_camera(camera_id)
- if local_config['@proto'] == 'v4l2':
- # overwrite some fields whose values should not be changed this way
- ui_config['device_uri'] = local_config['videodevice']
- ui_config['proto'] = 'v4l2'
- ui_config['host'] = ''
- ui_config['port'] = ''
- ui_config.setdefault('enabled', True)
-
+ if utils.local_camera(local_config):
local_config = config.camera_ui_to_dict(ui_config)
config.set_camera(camera_id, local_config)
on_finish(None, True) # (no error, motion needs restart)
- else:
+ else: # remote camera
# update the camera locally
local_config['@enabled'] = ui_config['enabled']
config.set_camera(camera_id, local_config)
# when the local_config supplied has only the enabled state,
# the camera was probably disabled due to errors
- if ui_config.has_key('device_uri'):
- # delete some fields that should not get to the remote side as they are
- del ui_config['device_uri']
- del ui_config['proto']
- del ui_config['host']
- del ui_config['port']
+ if ui_config.has_key('name'):
+ # never disable a remote camera on the remote host itself
del ui_config['enabled']
- def on_finish_wrapper(error):
+ def on_finish_wrapper(error=None):
return on_finish(error, False)
remote.set_config(local_config, ui_config, on_finish_wrapper)
raise
camera_config = config.get_camera(camera_id)
- if camera_config['@proto'] == 'v4l2':
+ if utils.v4l2_camera(camera_config):
device = camera_config['videodevice']
if 'brightness' in controls:
self.finish_json({})
- else:
- def on_response(response):
- if response is None:
- self.finish_json({'error': True})
+ 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:
+ self.finish_json({'error': True})
@BaseHandler.auth()
def list_cameras(self):
logging.debug('listing cameras')
-
- if 'host' in self.get_data(): # remote listing
- def on_response(cameras):
- if cameras is None:
- self.finish_json({'error': 'Failed to list remote cameras.'})
+
+ type = self.get_data().get('type')
+ if type == '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_cameras(self.get_data(), on_response)
+
+ elif type == 'netcam':
+ def on_response(cameras=None, error=None):
+ if error:
+ self.finish_json({'error': error})
+
+ else:
+ self.finish_json({'cameras': cameras})
+
+ utils.test_netcam_url(self.get_data(), on_response)
- else: # local listing
+ else: # assuming local v4l2 listing
cameras = []
camera_ids = config.get_camera_ids()
if not config.get_main().get('@enabled'):
self.finish_json({'cameras': cameras})
def on_response_builder(camera_id, local_config):
- def on_response(remote_ui_config):
- if remote_ui_config is None:
+ def on_response(remote_ui_config=None, error=None):
+ if error:
cameras.append({
'id': camera_id,
- 'name': '<' + utils.make_camera_url(local_config) + '>',
+ 'name': '<' + remote.make_camera_url(local_config) + '>',
'enabled': False,
'streaming_framerate': 1,
'framerate': 1
for camera_id in camera_ids:
local_config = config.get_camera(camera_id)
- if local_config['@proto'] == 'v4l2':
+ if utils.local_camera(local_config):
ui_config = config.camera_dict_to_ui(local_config)
cameras.append(ui_config)
check_finished()
else: # remote camera
- if local_config['@enabled']:
+ if local_config.get('@enabled'):
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
configured_devices = {}
for camera_id in config.get_camera_ids():
data = config.get_camera(camera_id)
- if data['@proto'] == 'v4l2':
+ if utils.v4l2_camera(data):
configured_devices[data['videodevice']] = True
- devices = [{'device_uri': d[0], 'name': d[1], 'configured': d[0] in configured_devices}
+ devices = [{'uri': d[0], 'name': d[1], 'configured': d[0] in configured_devices}
for d in v4l2ctl.list_devices()]
self.finish_json({'devices': devices})
proto = device_details['proto']
if proto == 'v4l2':
# find a suitable resolution
- for (w, h) in v4l2ctl.list_resolutions(device_details['device_uri']):
+ for (w, h) in v4l2ctl.list_resolutions(device_details['uri']):
if w > 300:
- device_details['width'] = w
- device_details['height'] = h
- # compute the ffmpeg bps
-
- max_val = w * h * 2 / 3
- max_val = min(max_val, 9999999)
- val = max_val * 75 / 100
- device_details['ffmpeg_bps'] = val
-
+ device_details.setdefault('resolution', str(w) + 'x' + str(h))
break
+
+ else:
+ # adjust uri format
+ if device_details['uri'] and device_details['uri'].startswith('/'):
+ device_details['uri'] = '/' + device_details['uri']
+ while device_details['uri'] and device_details['uri'].endswith('/'):
+ device_details['uri'] = device_details['uri'][:-1]
camera_id, camera_config = config.add_camera(device_details)
camera_config['@id'] = camera_id
- if proto == 'v4l2':
+ if utils.local_camera(camera_config):
motionctl.stop()
if settings.SMB_SHARES:
self.finish_json(ui_config)
- else:
- def on_response(remote_ui_config):
- if remote_ui_config is None:
- self.finish_json({'error': True})
+ else: # remote camera
+ def on_response(remote_ui_config=None, error=None):
+ if error:
+ self.finish_json({'error': error})
for key, value in camera_config.items():
remote_ui_config[key.replace('@', '')] = value
def rem_camera(self, camera_id):
logging.debug('removing camera %(id)s' % {'id': camera_id})
- local = config.get_camera(camera_id).get('@proto') == 'v4l2'
+ local = utils.local_camera(config.get_camera(camera_id))
config.rem_camera(camera_id)
if local:
return self.try_finish(picture)
camera_config = config.get_camera(camera_id)
- if camera_config['@proto'] == 'v4l2':
+ if utils.local_camera(camera_config):
picture = mediafiles.get_current_picture(camera_config,
width=width,
height=height)
self.try_finish(picture)
- else:
- def on_response(picture):
+ else: # remote camera
+ def on_response(picture=None, error=None):
if sequence and picture:
mediafiles.set_picture_cache(camera_id, sequence, width, picture)
raise HTTPError(404, 'no such camera')
camera_config = config.get_camera(camera_id)
- if camera_config['@proto'] != 'v4l2':
- def on_response(remote_list):
- if remote_list is None:
- return self.finish_json({'error': 'Failed to get picture list for %(url)s.' % {
- 'url': utils.make_camera_url(camera_config)}})
-
- self.finish_json(remote_list)
-
- remote.list_media(camera_config, on_response, media_type='picture', prefix=self.get_argument('prefix', None))
-
- else:
+ if utils.local_camera(camera_config):
def on_media_list(media_list):
if media_list is None:
return self.finish_json({'error': 'Failed to get pictures list.'})
mediafiles.list_media(camera_config, media_type='picture',
callback=on_media_list, prefix=self.get_argument('prefix', None))
+ else: # remote camera
+ 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.make_camera_url(camera_config)}, 'msg': error})
+
+ self.finish_json(remote_list)
+
+ remote.list_media(camera_config, on_response, media_type='picture', prefix=self.get_argument('prefix', None))
+
@BaseHandler.auth()
def frame(self, camera_id):
camera_config = config.get_camera(camera_id)
raise HTTPError(404, 'no such camera')
camera_config = config.get_camera(camera_id)
- if camera_config['@proto'] != 'v4l2':
- def on_response(response):
- if response is None:
- return self.finish_json({'error': 'Failed to download picture from %(url)s.' % {
- 'url': utils.make_camera_url(camera_config)}})
-
- 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, on_response, filename=filename, media_type='picture')
-
- else:
+ if utils.local_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-Disposition', 'attachment; filename=' + pretty_filename + ';')
self.finish(content)
+
+ else: # remote camera
+ 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.make_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, on_response, filename=filename, media_type='picture')
@BaseHandler.auth()
def preview(self, camera_id, filename):
raise HTTPError(404, 'no such camera')
camera_config = config.get_camera(camera_id)
- if camera_config['@proto'] != 'v4l2':
- def on_response(content):
- 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, on_response, filename=filename, media_type='picture',
- width=self.get_argument('width', None),
- height=self.get_argument('height', None))
-
- else:
+ if utils.local_camera(camera_config):
content = mediafiles.get_media_preview(camera_config, filename, 'picture',
width=self.get_argument('width', None),
height=self.get_argument('height', None))
content = open(os.path.join(settings.STATIC_PATH, 'img', 'no-preview.svg')).read()
self.finish(content)
+
+ else:
+ 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, on_response, filename=filename, media_type='picture',
+ width=self.get_argument('width', None),
+ height=self.get_argument('height', None))
def try_finish(self, content):
try:
raise HTTPError(404, 'no such camera')
camera_config = config.get_camera(camera_id)
- if camera_config['@proto'] != 'v4l2':
- def on_response(remote_list):
- if remote_list is None:
- return self.finish_json({'error': 'Failed to get movie list for %(url)s.' % {
- 'url': utils.make_camera_url(camera_config)}})
-
- self.finish_json(remote_list)
-
- remote.list_media(camera_config, on_response, media_type='movie', prefix=self.get_argument('prefix', None))
-
- else:
+ if utils.local_camera(camera_config):
def on_media_list(media_list):
if media_list is None:
return self.finish_json({'error': 'Failed to get movies list.'})
mediafiles.list_media(camera_config, media_type='movie',
callback=on_media_list, prefix=self.get_argument('prefix', None))
+
+ else:
+ 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.make_camera_url(camera_config)}, 'msg': error})
+
+ self.finish_json(remote_list)
+ remote.list_media(camera_config, on_response, media_type='movie', prefix=self.get_argument('prefix', None))
+
@BaseHandler.auth()
def download(self, camera_id, filename):
logging.debug('downloading movie %(filename)s of camera %(id)s' % {
raise HTTPError(404, 'no such camera')
camera_config = config.get_camera(camera_id)
- if camera_config['@proto'] != 'v4l2':
- def on_response(response):
- if response is None:
- return self.finish_json({'error': 'Failed to download movie from %(url)s.' % {
- 'url': utils.make_camera_url(camera_config)}})
+ if utils.local_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)
+
+ else:
+ 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.make_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.finish(response)
remote.get_media_content(camera_config, on_response, filename=filename, media_type='movie')
-
- else:
- 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)
@BaseHandler.auth()
def preview(self, camera_id, filename):
raise HTTPError(404, 'no such camera')
camera_config = config.get_camera(camera_id)
- if camera_config['@proto'] != 'v4l2':
- def on_response(content):
- 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, on_response, filename=filename, media_type='movie',
- width=self.get_argument('width', None),
- height=self.get_argument('height', None))
-
- else:
+ if utils.local_camera(camera_config):
content = mediafiles.get_media_preview(camera_config, filename, 'movie',
width=self.get_argument('width', None),
height=self.get_argument('height', None))
content = open(os.path.join(settings.STATIC_PATH, 'img', 'no-preview.svg')).read()
self.finish(content)
+
+ else:
+ 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, on_response, filename=filename, media_type='movie',
+ width=self.get_argument('width', None),
+ height=self.get_argument('height', None))
class UpdateHandler(BaseHandler):
for camera_id in config.get_camera_ids():
camera_config = config.get_camera(camera_id)
- if camera_config.get('@proto') != 'v4l2':
+ if not utils.local_camera(camera_config):
continue
preserve_media = camera_config.get('@preserve_%(media_type)ss' % {'media_type': media_type}, 0)
count = 0
for camera_id in config.get_camera_ids():
camera_config = config.get_camera(camera_id)
- if camera_config.get('@proto') != 'v4l2':
+ if not utils.local_camera(camera_config):
continue
target_dir = camera_config['target_dir']
import config
import motionctl
import settings
+import utils
class MjpgClient(iostream.IOStream):
'camera_id': camera_id})
camera_config = config.get_camera(camera_id)
- if not camera_config['@enabled'] or camera_config['@proto'] != 'v4l2':
+ if not camera_config['@enabled'] or not utils.local_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})
raise Exception('could not terminate the motion process')
except OSError as e:
- if e.errno != errno.ECHILD:
+ if e.errno not in (errno.ESRCH, errno.ECHILD):
raise
def _make_request(host, port, username, password, uri, method='GET', data=None, query=None, timeout=None):
- url = '%(scheme)s://%(host)s:%(port)s%(uri)s' % {
+ url = '%(scheme)s://%(host)s%(port)s%(uri)s' % {
'scheme': 'http',
'host': host,
- 'port': port,
- 'uri': uri}
+ 'port': ':' + str(port) if port else '',
+ 'uri': uri or ''}
if query:
url += '?' + '&'.join([(n + '=' + v) for (n, v) in query.iteritems()])
return request
-def make_camera_uri(camera_id):
- return '/config/%(camera_id)s' % {'camera_id': camera_id}
+def make_camera_url(local_config, camera=True):
+ 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'))
+ uri = local_config.get('@uri', local_config.get('uri')) or ''
+
+ url = 'motioneye://' + username + '@' + host
+ if port:
+ url += ':' + str(port)
+
+ if uri:
+ url += uri
+
+ 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 list_cameras(local_config, callback):
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 ''
- logging.debug('listing remote cameras on %(host)s:%(port)s' % {
- 'host': host,
- 'port': port})
+ logging.debug('listing remote cameras on %(url)s' % {
+ 'url': make_camera_url(local_config, camera=False)})
- request = _make_request(host, port, username, password, '/config/list/')
+ request = _make_request(host, port, username, password, uri + '/config/list/')
def on_response(response):
if response.error:
- logging.error('failed to list remote cameras on %(host)s:%(port)s: %(msg)s' % {
- 'host': host,
- 'port': port,
+ logging.error('failed to list remote cameras on %(url)s: %(msg)s' % {
+ 'url': make_camera_url(local_config, camera=False),
'msg': unicode(response.error)})
- return callback(None)
+ return callback(error=unicode(response.error))
try:
response = json.loads(response.body)
except Exception as e:
- logging.error('failed to decode json answer from %(host)s:%(port)s: %(msg)s' % {
- 'host': host,
- 'port': port,
+ logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
+ 'url': make_camera_url(local_config, camera=False),
'msg': unicode(e)})
- return callback(None)
+ return callback(error=unicode(e))
return callback(response['cameras'])
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('getting config for remote camera %(id)s on %(host)s:%(port)s' % {
+ logging.debug('getting config for remote camera %(id)s on %(url)s' % {
'id': camera_id,
- 'host': host,
- 'port': port})
+ 'url': make_camera_url(local_config)})
- request = _make_request(host, port, username, password, '/config/%(id)s/get/' % {'id': camera_id})
+ request = _make_request(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 %(host)s:%(port)s: %(msg)s' % {
+ logging.error('failed to get config for remote camera %(id)s on %(url)s: %(msg)s' % {
'id': camera_id,
- 'host': host,
- 'port': port,
+ 'url': make_camera_url(local_config),
'msg': unicode(response.error)})
- return callback(None)
+ return callback(error=unicode(response.error))
try:
response = json.loads(response.body)
except Exception as e:
- logging.error('failed to decode json answer from %(host)s:%(port)s: %(msg)s' % {
- 'host': host,
- 'port': port,
+ logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
+ 'url': make_camera_url(local_config),
'msg': unicode(e)})
- return callback(None)
+ return callback(error=unicode(e))
response['host'] = host
response['port'] = port
- response['device_uri'] = make_camera_uri(camera_id)
callback(response)
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 %(host)s:%(port)s' % {
+ logging.debug('setting config for remote camera %(id)s on %(url)s' % {
'id': camera_id,
- 'host': host,
- 'port': port})
+ 'url': make_camera_url(local_config)})
ui_config = json.dumps(ui_config)
- request = _make_request(host, port, username, password, '/config/%(id)s/set/' % {'id': camera_id}, method='POST', data=ui_config)
+ request = _make_request(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 %(host)s:%(port)s: %(msg)s' % {
+ logging.error('failed to set config for remote camera %(id)s on %(url)s: %(msg)s' % {
'id': camera_id,
- 'host': host,
- 'port': port,
+ 'url': make_camera_url(local_config),
'msg': unicode(response.error)})
- return callback(response.error)
+ return callback(error=unicode(response.error))
- callback(None)
+ callback()
http_client = AsyncHTTPClient()
http_client.fetch(request, on_response)
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 preview for remote camera %(id)s on %(host)s:%(port)s' % {
+ logging.debug('setting preview for remote camera %(id)s on %(url)s' % {
'id': camera_id,
- 'host': host,
- 'port': port})
+ 'url': make_camera_url(local_config)})
data = json.dumps(controls)
- request = _make_request(host, port, username, password, '/config/%(id)s/set_preview/' % {'id': camera_id}, method='POST', data=data)
+ request = _make_request(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 %(host)s:%(port)s: %(msg)s' % {
+ logging.error('failed to set preview for remote camera %(id)s on %(url)s: %(msg)s' % {
'id': camera_id,
- 'host': host,
- 'port': port,
+ 'url': make_camera_url(local_config),
'msg': unicode(response.error)})
- return callback(None)
+ return callback(error=unicode(response.error))
- callback('')
+ callback()
http_client = AsyncHTTPClient()
http_client.fetch(request, on_response)
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('getting current picture for remote camera %(id)s on %(host)s:%(port)s' % {
+ logging.debug('getting current picture for remote camera %(id)s on %(url)s' % {
'id': camera_id,
- 'host': host,
- 'port': port})
+ 'url': make_camera_url(local_config)})
query = {}
if height:
query['height'] = str(height)
- request = _make_request(host, port, username, password, '/picture/%(id)s/current/' % {'id': camera_id}, query=query)
+ request = _make_request(host, port, username, password, uri + '/picture/%(id)s/current/' % {'id': camera_id}, query=query)
def on_response(response):
if response.error:
- logging.error('failed to get current picture for remote camera %(id)s on %(host)s:%(port)s: %(msg)s' % {
+ logging.error('failed to get current picture for remote camera %(id)s on %(url)s: %(msg)s' % {
'id': camera_id,
- 'host': host,
- 'port': port,
+ 'url': make_camera_url(local_config),
'msg': unicode(response.error)})
- return callback(None)
+ return callback(error=unicode(response.error))
callback(response.body)
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('getting media list for remote camera %(id)s on %(host)s:%(port)s' % {
+ logging.debug('getting media list for remote camera %(id)s on %(url)s' % {
'id': camera_id,
- 'host': host,
- 'port': port})
+ 'url': make_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(host, port, username, password, '/%(media_type)s/%(id)s/list/' % {
+ request = _make_request(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 %(host)s:%(port)s: %(msg)s' % {
+ logging.error('failed to get media list for remote camera %(id)s on %(url)s: %(msg)s' % {
'id': camera_id,
- 'host': host,
- 'port': port,
+ 'url': make_camera_url(local_config),
'msg': unicode(response.error)})
- return callback(None)
+ return callback(error=unicode(response.error))
try:
response = json.loads(response.body)
except Exception as e:
- logging.error('failed to decode json answer from %(host)s:%(port)s: %(msg)s' % {
- 'host': host,
- 'port': port,
+ logging.error('failed to decode json answer from %(url)s: %(msg)s' % {
+ 'url': make_camera_url(local_config),
'msg': unicode(e)})
- return callback(None)
+ return callback(error=unicode(e))
return callback(response)
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('downloading file %(filename)s of remote camera %(id)s on %(host)s:%(port)s' % {
+ logging.debug('downloading file %(filename)s of remote camera %(id)s on %(url)s' % {
'filename': filename,
'id': camera_id,
- 'host': host,
- 'port': port})
+ 'url': make_camera_url(local_config)})
- uri = '/%(media_type)s/%(id)s/download/%(filename)s' % {
+ uri += '/%(media_type)s/%(id)s/download/%(filename)s' % {
'media_type': media_type,
'id': camera_id,
'filename': filename}
def on_response(response):
if response.error:
- logging.error('failed to download file %(filename)s of remote camera %(id)s on %(host)s:%(port)s: %(msg)s' % {
+ logging.error('failed to download file %(filename)s of remote camera %(id)s on %(url)s: %(msg)s' % {
'filename': filename,
'id': camera_id,
- 'host': host,
- 'port': port,
+ 'url': make_camera_url(local_config),
'msg': unicode(response.error)})
- return callback(None)
+ return callback(error=unicode(response.error))
return callback(response.body)
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('getting file preview for %(filename)s of remote camera %(id)s on %(host)s:%(port)s' % {
+ logging.debug('getting file preview for %(filename)s of remote camera %(id)s on %(url)s' % {
'filename': filename,
'id': camera_id,
- 'host': host,
- 'port': port})
+ 'url': make_camera_url(local_config)})
- uri = '/%(media_type)s/%(id)s/preview/%(filename)s' % {
+ uri += '/%(media_type)s/%(id)s/preview/%(filename)s' % {
'media_type': media_type,
'id': camera_id,
'filename': filename}
def on_response(response):
if response.error:
- logging.error('failed to get file preview for %(filename)s of remote camera %(id)s on %(host)s:%(port)s: %(msg)s' % {
+ 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,
- 'host': host,
- 'port': port,
+ 'url': make_camera_url(local_config),
'msg': unicode(response.error)})
- return callback(None)
+ return callback(error=unicode(response.error))
return callback(response.body)
import logging
import os
-import remote
-
def pretty_date_time(date_time, tzinfo=None):
if date_time is None:
return (used_size, total_size)
-def make_camera_url(config):
- proto = config.get('proto', config.get('@proto', ''))
- host = config.get('host', config.get('@host', ''))
- port = config.get('port', config.get('@port', ''))
- device_uri = config.get('device_uri', config.get('videodevice', remote.make_camera_uri(config.get('@remote_camera_id'))))
+def local_camera(config):
+ return bool(config.get('videodevice') or config.get('netcam_url'))
+
+
+def remote_camera(config):
+ return config.get('@proto') == 'motioneye'
+
+
+def v4l2_camera(config):
+ return bool(config.get('videodevice'))
+
+
+def net_camera(config):
+ return bool(config.get('netcam_url'))
+
+
+def test_netcam_url(data, callback):
+ url = '%(proto)s://%(host)s%(port)s%(uri)s' % {
+ 'proto': data['proto'],
+ 'host': data['host'],
+ 'port': ':' + str(data['port']) if data['port'] else '',
+ 'uri': data['uri'] or ''}
- return proto + '://' + host + (':' + str(port) if port else '') + device_uri
+ logging.debug('testing netcam at %s' % url)
+
+ import time
+ time.sleep(1)
+
+ username = data['username']
+ password = data['password']
+
+ # TODO implement me
+ #callback(error='General failure')
+ callback([{'id': 1, 'name': 'Network Camera'}])
width: 17em;
}
+span#cameraMsgLabel {
+ color: red;
+ font-size: 0.7em;
+}
+
div.media-dialog {
}
return s.toString();
};
+function makeDeviceUrl(dict) {
+ switch (dict.proto) {
+ case 'v4l2':
+ return dict.proto + '://' + dict.uri;
+
+ case 'motioneye':
+ return dict.proto + '://' + dict.host + (dict.port ? ':' + dict.port : '') + dict.uri + '/config/' + dict.remote_camera_id;
+
+ default: /* assuming netcam */
+ return dict.proto + '://' + dict.host + (dict.port ? ':' + dict.port : '') + dict.uri;
+ }
+}
+
/* UI initialization */
$('#showAdvancedSwitch').change(updateConfigUi);
$('#wifiSwitch').change(updateConfigUi);
$('#storageDeviceSelect').change(updateConfigUi);
- $('#autoBrightnessSwitch').change(updateConfigUi);
$('#resolutionSelect').change(updateConfigUi);
$('#leftTextSelect').change(updateConfigUi);
$('#rightTextSelect').change(updateConfigUi);
$('tr.advanced-setting, div.advanced-setting, table.advanced-setting').each(markHide);
}
+ /* video device */
+ if ($('#brightnessSlider').val() == '') {
+ $('#brightnessSlider').parents('tr:eq(0)').each(markHide);
+ }
+ if ($('#contrastSlider').val() == '') {
+ $('#contrastSlider').parents('tr:eq(0)').each(markHide);
+ }
+ if ($('#saturationSlider').val() == '') {
+ $('#saturationSlider').parents('tr:eq(0)').each(markHide);
+ }
+ if ($('#hueSlider').val() == '') {
+ $('#hueSlider').parents('tr:eq(0)').each(markHide);
+ }
+ if ($('#contrastSlider').val() == '') {
+ $('#contrastSlider').parents('tr:eq(0)').each(markHide);
+ }
+ if ($('#resolutionSelect')[0].selectedIndex == -1) {
+ $('#resolutionSelect').parents('tr:eq(0)').each(markHide);
+ }
+
/* storage device */
if ($('#storageDeviceSelect').val() !== 'network-share') {
$('#networkServerEntry').parents('tr:eq(0)').each(markHide);
$('#networkShareNameEntry').parents('tr:eq(0)').each(markHide);
}
- /* auto brightness */
- if ($('#autoBrightnessSwitch').get(0).checked) {
- $('#brightnessSlider').parents('tr:eq(0)').each(markHide);
- }
-
/* text */
if ($('#leftTextSelect').val() !== 'custom-text') {
$('#leftTextEntry').parents('tr:eq(0)').each(markHide);
};
}
- var deviceUrl = $('#deviceEntry').val();
- var proto = '';
- var hostPort = '';
- var deviceUri = '';
- var host = '';
- var port = '';
-
- var parts;
- if (deviceUrl) {
- parts = deviceUrl.split('://');
- proto = parts[0];
- if (parts.length > 1) {
- parts = parts[1].split('/');
- hostPort = parts[0];
- deviceUri = '/' + parts.slice(1).join('/');
- parts = hostPort.split(':');
- host = parts[0];
- if (parts.length > 1) {
- port = parts[1] || '';
- }
- }
- }
-
var dict = {
/* video device */
'enabled': $('#videoDeviceSwitch')[0].checked,
'name': $('#deviceNameEntry').val(),
- 'proto': proto,
- 'host': host,
- 'port': port,
- 'device_uri': deviceUri,
'light_switch_detect': $('#lightSwitchDetectSwitch')[0].checked,
- 'auto_brightness': $('#autoBrightnessSwitch')[0].checked,
- 'resolution': $('#resolutionSelect').val(),
'rotation': $('#rotationSelect').val(),
'framerate': $('#framerateSlider').val(),
+ 'proto': $('#deviceEntry')[0].proto,
+ 'host': $('#deviceEntry')[0].host,
+ 'port': $('#deviceEntry')[0].port,
+ 'uri': $('#deviceEntry')[0].uri,
+ 'username': $('#deviceEntry')[0].username,
+ 'password': $('#deviceEntry')[0].password,
/* file storage */
'storage_device': $('#storageDeviceSelect').val(),
'sunday_to': $('#sundayTo').val(),
};
+ if ($('#resolutionSelect')[0].selectedIndex != -1) {
+ dict.resolution = $('#resolutionSelect').val();
+ }
+
if ($('#brightnessSlider').val() !== '') {
dict.brightness = $('#brightnessSlider').val();
}
/* video device */
$('#videoDeviceSwitch')[0].checked = dict['enabled'];
$('#deviceNameEntry').val(dict['name']);
- $('#deviceEntry').val(dict['proto'] + '://' + dict['host'] + (dict['port'] ? ':' + dict['port'] : '') + dict['device_uri']);
+ $('#deviceEntry').val(makeDeviceUrl(dict));
+ $('#deviceEntry')[0].proto = dict['proto'];
+ $('#deviceEntry')[0].host = dict['host'];
+ $('#deviceEntry')[0].port = dict['port'];
+ $('#deviceEntry')[0].uri= dict['uri'];
+ $('#deviceEntry')[0].username = dict['username'];
+ $('#deviceEntry')[0].password = dict['password'];
$('#lightSwitchDetectSwitch')[0].checked = dict['light_switch_detect'];
- $('#autoBrightnessSwitch')[0].checked = dict['auto_brightness'];
- $('#brightnessSlider').val(dict['brightness']);
- $('#contrastSlider').val(dict['contrast']);
- $('#saturationSlider').val(dict['saturation']);
- $('#hueSlider').val(dict['hue']);
+ if (dict['brightness'] != null) {
+ $('#brightnessSlider').val(dict['brightness']);
+ }
+ else {
+ $('#brightnessSlider').val('');
+ }
+
+ if (dict['contrast'] != null) {
+ $('#contrastSlider').val(dict['contrast']);
+ }
+ else {
+ $('#contrastSlider').val('');
+ }
+
+ if (dict['saturation'] != null) {
+ $('#saturationSlider').val(dict['saturation']);
+ }
+ else {
+ $('#saturationSlider').val('');
+ }
+
+ if (dict['hue'] != null) {
+ $('#hueSlider').val(dict['hue']);
+ }
+ else {
+ $('#hueSlider').val('');
+ }
$('#resolutionSelect').html('');
if (dict['available_resolutions']) {
'<td class="dialog-item-value"><select class="styled" id="deviceSelect"></select></td>' +
'<td><span class="help-mark" title="the device you wish to add to motionEye">?</span></td>' +
'</tr>' +
- '<tr class="remote">' +
- '<td class="dialog-item-label"><span class="dialog-item-label">Host</span></td>' +
- '<td class="dialog-item-value"><input type="text" class="styled" id="hostEntry" placeholder="e.g. 192.168.1.2"></td>' +
- '<td><span class="help-mark" title="the remote motionEye host (e.g. 192.168.1.2)">?</span></td>' +
+ '<tr class="motioneye netcam">' +
+ '<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/cams/)">?</span></td>' +
'</tr>' +
- '<tr class="remote">' +
- '<td class="dialog-item-label"><span class="dialog-item-label">Port</span></td>' +
- '<td class="dialog-item-value"><input type="text" class="styled" id="portEntry" placeholder="e.g. 80"></td>' +
- '<td><span class="help-mark" title="the remote motionEye port (e.g. 80)">?</span></td>' +
- '</tr>' +
- '<tr class="remote">' +
+ '<tr class="motioneye netcam">' +
'<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 remote administrator\'s username">?</span></td>' +
+ '<td><span class="help-mark" title="the username for the URL, if required (e.g. admin)">?</span></td>' +
'</tr>' +
- '<tr class="remote">' +
+ '<tr class="motioneye netcam">' +
'<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 remote administrator\'s password">?</span></td>' +
+ '<td><span class="help-mark" title="the password for the URL, if required">?</span></td>' +
'</tr>' +
- '<tr class="remote">' +
+ '<tr class="motioneye netcam">' +
'<td class="dialog-item-label"><span class="dialog-item-label">Camera</span></td>' +
- '<td class="dialog-item-value"><select class="styled" id="cameraSelect"></select></td>' +
- '<td><span class="help-mark" title="the remote camera you wish to add to motionEye">?</span></td>' +
+ '<td class="dialog-item-value"><select class="styled" id="cameraSelect"></select><span id="cameraMsgLabel"></span></td>' +
+ '<td><span class="help-mark" title="the camera you wish to add to motionEye">?</span></td>' +
'</tr>' +
'</table>');
/* collect ui widgets */
var deviceSelect = content.find('#deviceSelect');
- var hostEntry = content.find('#hostEntry');
- var portEntry = content.find('#portEntry');
+ var urlEntry = content.find('#urlEntry');
var usernameEntry = content.find('#usernameEntry');
var passwordEntry = content.find('#passwordEntry');
var cameraSelect = content.find('#cameraSelect');
+ var cameraMsgLabel = content.find('#cameraMsgLabel');
/* make validators */
- makeTextValidator(hostEntry, true);
- makeNumberValidator(portEntry, 1, 65535, false, false, true);
- makeTextValidator(usernameEntry, true);
- makeTextValidator(deviceSelect, true);
+ makeUrlValidator(urlEntry, true);
+ makeTextValidator(usernameEntry, false);
+ makeTextValidator(deviceSelect, false);
makeComboValidator(cameraSelect, true);
/* ui interaction */
- content.find('tr.remote').css('display', 'none');
+ content.find('tr.motioneye, tr.netcam').css('display', 'none');
function updateUi() {
- if (deviceSelect.val() === 'remote') {
- content.find('tr.remote').css('display', 'table-row');
+ content.find('tr.motioneye, tr.netcam').css('display', 'none');
+ if (deviceSelect.val() == 'motioneye') {
+ content.find('tr.motioneye').css('display', 'table-row');
+ cameraSelect.hide();
}
- else {
- content.find('tr.remote').css('display', 'none');
+ else if (deviceSelect.val() == 'netcam') {
+ content.find('tr.netcam').css('display', 'table-row');
+ cameraSelect.hide();
}
updateModalDialogPosition();
cameraSelect.html('');
-
+
/* re-validate all the validators */
content.find('.validator').each(function () {
this.validate();
});
- if (content.is(':visible') && uiValid() && deviceSelect.val() == 'remote') {
+ if (content.is(':visible') && uiValid() && (deviceSelect.val() == 'motioneye' || deviceSelect.val() == 'netcam')) {
fetchRemoteCameras();
}
}
return valid;
}
+ function splitUrl(url) {
+ var parts = url.split('://');
+ var proto = 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 {
+ proto: proto,
+ host: host,
+ port: port,
+ uri: uri
+ };
+ }
+
function fetchRemoteCameras() {
var progress = $('<div style="text-align: center; margin: 2px;"><img src="' + staticUrl + 'img/small-progress.gif"></div>');
cameraSelect.hide();
- cameraSelect.before(progress);
cameraSelect.parent().find('div').remove(); /* remove any previous progress div */
+ cameraSelect.before(progress);
- var data = {
- host: hostEntry.val(),
- port: portEntry.val(),
- username: usernameEntry.val(),
- password: passwordEntry.val()
- };
+ var data = splitUrl(urlEntry.val());
+ data.username = usernameEntry.val();
+ data.password = passwordEntry.val();
+ data.type = deviceSelect.val();
+
+ cameraMsgLabel.html('');
ajax('GET', '/config/list/', data, function (data) {
+ progress.remove();
+
if (data == null || data.error) {
- progress.remove();
- if (passwordEntry.val()) { /* only show an error message when a password is supplied */
- showErrorMessage(data && data.error);
- }
+ //showErrorMessage(data && data.error);
+ cameraMsgLabel.html(data && data.error);
return;
}
cameraSelect.html('');
- progress.remove();
if (data.error || !data.cameras) {
return;
}
deviceSelect.change(updateUi);
- hostEntry.change(updateUi);
- portEntry.change(updateUi);
+ urlEntry.change(updateUi);
usernameEntry.change(updateUi);
passwordEntry.change(updateUi);
updateUi();
/* add available devices */
data.devices.forEach(function (device) {
if (!device.configured) {
- deviceSelect.append('<option value="' + device.device_uri + '">' + device.name + '</option>');
+ deviceSelect.append('<option value="' + device.uri + '">' + device.name + '</option>');
}
});
- deviceSelect.append('<option value="remote">Remote motionEye camera...</option>');
+ deviceSelect.append('<option value="netcam">Network camera...</option>');
+ deviceSelect.append('<option value="motioneye">Remote motionEye camera...</option>');
updateUi();
if (!uiValid(true)) {
return false;
}
-
+
var data = {};
- if (deviceSelect.val() == 'remote') {
- data.proto = 'http';
- data.host = hostEntry.val();
- data.port = portEntry.val();
+
+ if (deviceSelect.val() == 'motioneye') {
+ data = splitUrl(urlEntry.val());
+ data.proto = 'motioneye';
data.username = usernameEntry.val();
data.password = passwordEntry.val();
data.remote_camera_id = cameraSelect.val();
}
- else {
+ else if (deviceSelect.val() == 'netcam') {
+ data = splitUrl(urlEntry.val());
+ data.username = usernameEntry.val();
+ data.password = passwordEntry.val();
+ }
+ else { /* assuming v4l2 */
data.proto = 'v4l2';
- data.device_uri = deviceSelect.val();
+ data.uri = deviceSelect.val();
}
beginProgress();
function makeTimeValidator($input) {
function isValid(strVal) {
- return strVal.match('^[0-2][0-9]:[0-5][0-9]$') != null;
+ 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 makeUrlValidator($input) {
+ function isValid(strVal) {
+ 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() {
+ if (!$input.parents('tr:eq(0)').is(':visible')) {
+ return true; /* an invisible element is considered always valid */
+ }
+
+ var strVal = $input.val();
+ if (isValid(strVal)) {
+ $input.attr('title', '');
+ $input.removeClass('error');
+ $input[0].invalid = false;
+ }
+ else {
+ $input.attr('title', msg);
+ $input.addClass('error');
+ $input[0].invalid = true;
+ }
+ }
+
+ $input.keyup(validate);
+ $input.blur(validate);
+ $input.change(validate).change();
+
+ $input.addClass('validator');
+ $input.addClass('url-validator');
+ $input.each(function () {
+ this.validate = validate;
+ });
+}
+
function makeRegexValidator($input, regex, required) {
if (required == null) {
required = true;
<td class="settings-item-value"><input type="checkbox" class="styled device" 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" id="autoBrightnessSwitch"></td>
- <td><span class="help-mark" title="enables software automatic brightness (not needed for most cameras)">?</span></td>
- </tr>
<tr class="settings-item advanced-setting">
<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" id="brightnessSlider"></td>
</tr>
<tr class="settings-item advanced-setting">
<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" id="webHookUrlEntry" placeholder="e.g. http://www.example.com/notify/"></td>
+ <td class="settings-item-value"><input type="text" class="styled notifications" 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">