]> www.vanbest.org Git - motioneye-debian/commitdiff
major internal camera type/proto refactoring
authorCalin Crisan <ccrisan@gmail.com>
Sun, 3 May 2015 15:01:57 +0000 (18:01 +0300)
committerCalin Crisan <ccrisan@gmail.com>
Sun, 3 May 2015 15:01:57 +0000 (18:01 +0300)
12 files changed:
src/config.py
src/handlers.py
src/mediafiles.py
src/mjpgclient.py
src/motionctl.py
src/remote.py
src/utils.py
src/wsswitch.py
static/css/main.css
static/css/ui.css
static/js/main.js
templates/main.html

index 231726ada59e9a362e792114dfed94f79786a1a4..e044294bd2eebba1557c2d3500513a2557be40db 100644 (file)
@@ -22,6 +22,7 @@ import os.path
 import re
 import shlex
 import subprocess
+import urlparse
 
 from tornado.ioloop import IOLoop
 
@@ -208,7 +209,7 @@ def has_local_enabled_cameras():
     
     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.get('@enabled') and utils.local_camera(c)])
+    return bool([c for c in cameras if c.get('@enabled') and utils.local_motion_camera(c)])
 
 
 def get_network_shares():
@@ -270,7 +271,7 @@ def get_camera(camera_id, as_lines=False):
             no_convert=['@name', '@network_share_name', '@network_server',
                         '@network_username', '@network_password', '@storage_device'])
     
-    if utils.local_camera(camera_config):
+    if utils.local_motion_camera(camera_config):
         # determine the enabled status
         main_config = get_main()
         threads = main_config.get('thread', [])
@@ -330,7 +331,7 @@ def set_camera(camera_id, camera_config):
     _camera_config_cache[camera_id] = camera_config
     camera_config = dict(camera_config)
     
-    if utils.local_camera(camera_config):
+    if utils.local_motion_camera(camera_config):
         old_motion = is_old_motion()
         
         # adapt directives to old configuration, if needed
@@ -422,6 +423,21 @@ 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()
 
@@ -433,14 +449,20 @@ def add_camera(device_details):
     
     # prepare a default camera config
     camera_config = {'@enabled': True}
-    if device_details['proto'] == 'v4l2':
+    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)
-        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':
+    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']
@@ -448,12 +470,15 @@ def add_camera(device_details):
         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
+    elif proto == 'netcam':
+        camera_config['netcam_url'] = device_details['url']
+        if device_details['username']:
+            camera_config['netcam_userpass'] = device_details['username'] + ':' + device_details['password']
         _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)
+
+    else: # assuming mjpeg
+        camera_config['@proto'] = 'mjpeg'
+        camera_config['@url'] = device_details['url']
 
     # write the configuration to file
     set_camera(camera_id, camera_config)
@@ -539,7 +564,8 @@ def main_dict_to_ui(data):
     return ui
 
 
-def camera_ui_to_dict(ui):
+def camera_ui_to_dict(ui, old_config=None):
+    old_config = dict(old_config or {})
     main_config = get_main() # needed for surveillance password
 
     data = {
@@ -607,9 +633,14 @@ def camera_ui_to_dict(ui):
         'on_event_end': ''
     }
     
-    if ui['proto'] == 'v4l2':
-        # device
-        data['videodevice'] = ui['uri']
+    if utils.v4l2_camera(old_config):
+        proto = 'v4l2'
+        
+    else:
+        proto = 'netcam'
+    
+    if proto == 'v4l2':
+        # leave videodevice unchanged
         
         # resolution
         if not ui['resolution']:
@@ -650,17 +681,9 @@ def camera_ui_to_dict(ui):
             else:
                 data['hue'] = max(1, int(round(int(ui['hue']) * 2.55)))
     
-    else: # assuming http/netcam
-        # device
-        data['netcam_url'] = ui['proto'] + '://' + ui['host']
-        if ui['port']:
-            data['netcam_url'] += ':' + str(ui['port'])
-        
-        data['netcam_url'] += ui['uri']
-        
-        if ui['username'] or ui['password']:
-            data['netcam_userpass'] = (ui['username'] or '') + ':' + (ui['password'] or '')
-
+    else: # assuming netcam
+        # leave netcam_url unchanged
+        # leave netcam_userpass unchanged
         data['netcam_keepalive'] = True
         data['netcam_tolerant_check'] = True
 
@@ -714,7 +737,7 @@ def camera_ui_to_dict(ui):
         else:
             data['text_right'] = ui['custom_right_text']
         
-        if ui['proto'] != 'v4l2' or data['width'] > 320:
+        if proto == 'netcam' or data['width'] > 320:
             data['text_double'] = True
     
     if ui['still_images']:
@@ -735,10 +758,10 @@ def camera_ui_to_dict(ui):
         data['quality'] = max(1, int(ui['image_quality']))
     
     if ui['motion_movies']:
-        if ui['proto'] == 'v4l2':
+        if proto == 'v4l2':
             max_val = data['width'] * data['height'] * data['framerate'] / 3
         
-        else:
+        else: # always assume netcam image size of (640x480) - we have no means to test it
             max_val = 640 * 480 * data['framerate'] / 3
             
         max_val = min(max_val, 9999999)
@@ -811,7 +834,9 @@ def camera_ui_to_dict(ui):
     for name, value in extra_options:
         data[name] = value or ''
 
-    return data
+    old_config.update(data)
+
+    return old_config
 
 
 def camera_dict_to_ui(data):
@@ -893,44 +918,16 @@ def camera_dict_to_ui(data):
     }
     
     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]
-            uri = '/' + uri
-        
-        else:
-            host_port, uri = rest, ''
-        
-        parts = host_port.split(':')
-        if len(parts) > 1:
-            host, port = parts[:2]
-        
-        else:
-            host, port = host_port, ''
+        ui['device_url'] = data['netcam_url']
+        ui['proto'] = 'netcam'
 
-        ui['proto'] = proto
-        ui['host'] = host
-        ui['port'] = port
-        ui['uri'] = uri
-        
-        userpass = data.get('netcam_userpass')
-        if userpass:
-            ui['username'], ui['password'] = userpass.split(':', 1)
-        
-        else:
-            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['device_url'] = data['videodevice']
         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'])
@@ -940,7 +937,7 @@ def camera_dict_to_ui(data):
         # 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'])
+        brightness = v4l2ctl.get_brightness(data['videodevice'])
         if brightness is not None: # has brightness control
             if data.get('brightness', 0) != 0:
                 ui['brightness'] = brightness
@@ -948,7 +945,7 @@ def camera_dict_to_ui(data):
             else:
                 ui['brightness'] = 50
 
-        contrast = v4l2ctl.get_contrast(ui['uri'])
+        contrast = v4l2ctl.get_contrast(data['videodevice'])
         if contrast is not None: # has contrast control
             if data.get('contrast', 0) != 0:
                 ui['contrast'] = contrast
@@ -956,7 +953,7 @@ def camera_dict_to_ui(data):
             else:
                 ui['contrast'] = 50
             
-        saturation = v4l2ctl.get_saturation(ui['uri'])
+        saturation = v4l2ctl.get_saturation(data['videodevice'])
         if saturation is not None: # has saturation control
             if data.get('saturation', 0) != 0:
                 ui['saturation'] = saturation
@@ -964,7 +961,7 @@ def camera_dict_to_ui(data):
             else:
                 ui['saturation'] = 50
             
-        hue = v4l2ctl.get_hue(ui['uri'])
+        hue = v4l2ctl.get_hue(data['videodevice'])
         if hue is not None: # has hue control
             if data.get('hue', 0) != 0:
                 ui['hue'] = hue
@@ -1062,7 +1059,7 @@ def camera_dict_to_ui(data):
         if utils.v4l2_camera(data):
             max_val = data['width'] * data['height'] * data['framerate'] / 3
         
-        else:
+        else: # net camera
             max_val = 640 * 480 * data['framerate'] / 3
             
         max_val = min(max_val, 9999999)
index 8f6182356a7ea33cf6a8565c9e5a93d88c4daccd..162cee2b7ffefc411d085e2ebb6d16bd0cde3054 100644 (file)
@@ -215,7 +215,7 @@ class ConfigHandler(BaseHandler):
                 raise HTTPError(404, 'no such camera')
             
             local_config = config.get_camera(camera_id)
-            if utils.local_camera(local_config):
+            if utils.local_motion_camera(local_config):
                 ui_config = config.camera_dict_to_ui(local_config)
                     
                 self.finish_json(ui_config)
@@ -224,11 +224,13 @@ class ConfigHandler(BaseHandler):
                 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}})
+                                '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)
@@ -258,19 +260,9 @@ class ConfigHandler(BaseHandler):
                 raise HTTPError(404, 'no such camera')
             
             local_config = config.get_camera(camera_id)
-            if utils.local_camera(local_config):
-                # certain parameters cannot be changed via ui_config;
-                # we must preserve existing values for those params
-                local_ui_config = config.camera_dict_to_ui(local_config)
-                ui_config.setdefault('enabled', local_ui_config['enabled'])
-                ui_config['proto'] = local_ui_config['proto']
-                ui_config['host'] = local_ui_config['host']
-                ui_config['port'] = local_ui_config['port']
-                ui_config['uri'] = local_ui_config['uri']
-                ui_config['username'] = local_ui_config['username']
-                ui_config['password'] = local_ui_config['password']
-                
-                local_config = config.camera_ui_to_dict(ui_config)
+            if utils.local_motion_camera(local_config):
+                local_config = config.camera_ui_to_dict(ui_config, local_config)
+
                 config.set_camera(camera_id, local_config)
             
                 on_finish(None, True) # (no error, motion needs restart)
@@ -280,16 +272,17 @@ class ConfigHandler(BaseHandler):
                 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('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)
 
         def set_main_config(ui_config):
@@ -322,14 +315,15 @@ class ConfigHandler(BaseHandler):
             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_camera(local_config):
+                    if not utils.local_motion_camera(local_config):
                         continue
                     
-                    # this will update the stream authentication options
                     ui_config = config.camera_dict_to_ui(local_config)
-                    local_config = config.camera_ui_to_dict(ui_config)
+                    local_config = config.camera_ui_to_dict(ui_config, local_config)
+
                     config.set_camera(camera_id, local_config)
                     
                     restart = True
@@ -475,15 +469,15 @@ class ConfigHandler(BaseHandler):
             
             remote.set_preview(camera_config, controls, on_response)
         
-        else:
+        else: # not supported
             self.finish_json({'error': True})
 
     @BaseHandler.auth()
     def list_cameras(self):
         logging.debug('listing cameras')
 
-        type = self.get_data().get('type')        
-        if type == 'motioneye':  # remote listing
+        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})
@@ -494,7 +488,17 @@ class ConfigHandler(BaseHandler):
 
             remote.list_cameras(self.get_data(), on_response)
         
-        elif type == 'netcam':
+        elif proto == 'netcam':
+            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'], allow_jpeg=True, callback=on_response)
+                
+        elif proto == 'mjpeg':
             def on_response(cameras=None, error=None):
                 if error:
                     self.finish_json({'error': error})
@@ -502,9 +506,9 @@ class ConfigHandler(BaseHandler):
                 else:
                     self.finish_json({'cameras': cameras})
             
-            utils.test_netcam_url(self.get_data(), on_response)
+            utils.test_mjpeg_url(self.get_data(), auth_modes=['basic', 'digest'], allow_jpeg=False, callback=on_response)
                 
-        else:  # assuming local listing
+        else:  # assuming local motionEye camera listing
             cameras = []
             camera_ids = config.get_camera_ids()
             if not config.get_main().get('@enabled'):
@@ -521,7 +525,7 @@ class ConfigHandler(BaseHandler):
                     if error:
                         cameras.append({
                             'id': camera_id,
-                            'name': '&lt;' + remote.make_camera_url(local_config) + '&gt;',
+                            'name': '&lt;' + remote.pretty_camera_url(local_config) + '&gt;',
                             'enabled': False,
                             'streaming_framerate': 1,
                             'framerate': 1
@@ -553,7 +557,7 @@ class ConfigHandler(BaseHandler):
                 if local_config is None:
                     continue
                 
-                if utils.local_camera(local_config):
+                if utils.local_motion_camera(local_config):
                     ui_config = config.camera_dict_to_ui(local_config)
                     cameras.append(ui_config)
                     check_finished()
@@ -595,23 +599,10 @@ class ConfigHandler(BaseHandler):
             
             raise
 
-        proto = device_details['proto']
-        if proto == 'v4l2':
-            # find a suitable resolution
-            for (w, h) in v4l2ctl.list_resolutions(device_details['uri']):
-                if w > 300:
-                    device_details.setdefault('resolution', str(w) + 'x' + str(h))
-                    break
-                
-        else:
-            # adjust uri format
-            if device_details['uri'] and not device_details['uri'].startswith('/'):
-                device_details['uri'] = '/' + device_details['uri']
-
         camera_id, camera_config = config.add_camera(device_details)
         camera_config['@id'] = camera_id
 
-        if utils.local_camera(camera_config):
+        if utils.local_motion_camera(camera_config):
             motionctl.stop()
             
             if settings.SMB_SHARES:
@@ -637,13 +628,13 @@ class ConfigHandler(BaseHandler):
                 
                 self.finish_json(remote_ui_config)
                 
-            remote.get_config(device_details, on_response)
+            remote.get_config(camera_config, on_response)
     
     @BaseHandler.auth(admin=True)
     def rem_camera(self, camera_id):
         logging.debug('removing camera %(id)s' % {'id': camera_id})
         
-        local = utils.local_camera(config.get_camera(camera_id))
+        local = utils.local_motion_camera(config.get_camera(camera_id))
         config.rem_camera(camera_id)
         
         if local:
@@ -689,7 +680,7 @@ class ConfigHandler(BaseHandler):
             logging.warn('ignoring event for remote camera with id %s (probably removed)' % camera_id)
             return self.finish_json()
 
-        if not utils.local_camera(camera_config):
+        if not utils.local_motion_camera(camera_config):
             logging.warn('ignoring event for remote camera with id %s' % camera_id)
             return self.finish_json()
         
@@ -774,7 +765,7 @@ class PictureHandler(BaseHandler):
             return self.try_finish(picture)
 
         camera_config = config.get_camera(camera_id)
-        if utils.local_camera(camera_config):
+        if utils.local_motion_camera(camera_config):
             picture = mediafiles.get_current_picture(camera_config,
                     width=width,
                     height=height)
@@ -800,7 +791,7 @@ class PictureHandler(BaseHandler):
         logging.debug('listing pictures for camera %(id)s' % {'id': camera_id})
         
         camera_config = config.get_camera(camera_id)
-        if utils.local_camera(camera_config):
+        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.'})
@@ -817,7 +808,7 @@ class PictureHandler(BaseHandler):
             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}})
+                            'url': remote.pretty_camera_url(camera_config), 'msg': error}})
 
                 self.finish_json(remote_list)
             
@@ -826,7 +817,7 @@ class PictureHandler(BaseHandler):
     def frame(self, camera_id):
         camera_config = config.get_camera(camera_id)
         
-        if utils.local_camera(camera_config) or self.get_argument('title', None) is not None:
+        if utils.local_motion_camera(camera_config) or self.get_argument('title', None) is not None:
             self.render('main.html',
                     frame=True,
                     camera_id=camera_id,
@@ -843,7 +834,10 @@ class PictureHandler(BaseHandler):
                             camera_config=camera_config,
                             title=self.get_argument('title', ''))
 
+                # issue a fake camera_ui_to_dict() call to transform
+                # the remote UI values into motion config directives
                 remote_config = config.camera_ui_to_dict(remote_ui_config)
+                
                 self.render('main.html',
                         frame=True,
                         camera_id=camera_id,
@@ -860,7 +854,7 @@ class PictureHandler(BaseHandler):
                 'filename': filename, 'id': camera_id})
         
         camera_config = config.get_camera(camera_id)
-        if utils.local_camera(camera_config):
+        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)
@@ -873,7 +867,7 @@ class PictureHandler(BaseHandler):
             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}})
+                            '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')
@@ -889,7 +883,7 @@ class PictureHandler(BaseHandler):
                 'filename': filename, 'id': camera_id})
         
         camera_config = config.get_camera(camera_id)
-        if utils.local_camera(camera_config):
+        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))
@@ -925,7 +919,7 @@ class PictureHandler(BaseHandler):
                 'filename': filename, 'id': camera_id})
         
         camera_config = config.get_camera(camera_id)
-        if utils.local_camera(camera_config):
+        if utils.local_motion_camera(camera_config):
             try:
                 mediafiles.del_media_content(camera_config, filename, 'picture')
                 self.finish_json()
@@ -937,7 +931,7 @@ class PictureHandler(BaseHandler):
             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.make_camera_url(camera_config), 'msg': error}})
+                            'url': remote.pretty_camera_url(camera_config), 'msg': error}})
 
                 self.finish_json()
 
@@ -952,7 +946,7 @@ class PictureHandler(BaseHandler):
             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_camera(camera_config):
+            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)
@@ -960,7 +954,7 @@ class PictureHandler(BaseHandler):
                     raise HTTPError(404, 'no such key')
     
                 camera_config = config.get_camera(camera_id)
-                if utils.local_camera(camera_config):
+                if utils.local_motion_camera(camera_config):
                     pretty_filename = camera_config['@name'] + '_' + group
                     pretty_filename = re.sub('[^a-zA-Z0-9]', '_', pretty_filename)
          
@@ -975,7 +969,7 @@ class PictureHandler(BaseHandler):
                 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.make_camera_url(camera_config), 'msg': error}})
+                                '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'])
@@ -987,7 +981,7 @@ class PictureHandler(BaseHandler):
             logging.debug('preparing zip file for group %(group)s of camera %(id)s' % {
                     'group': group, 'id': camera_id})
 
-            if utils.local_camera(camera_config):
+            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.'})
@@ -1003,7 +997,7 @@ class PictureHandler(BaseHandler):
                 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.make_camera_url(camera_config), 'msg': error}})
+                                'url': remote.pretty_camera_url(camera_config), 'msg': error}})
 
                     self.finish_json({'key': response['key']})
 
@@ -1019,7 +1013,7 @@ class PictureHandler(BaseHandler):
             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_camera(camera_config):
+            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)
@@ -1027,7 +1021,7 @@ class PictureHandler(BaseHandler):
                     raise HTTPError(404, 'no such key')
 
                 camera_config = config.get_camera(camera_id)
-                if utils.local_camera(camera_config):
+                if utils.local_motion_camera(camera_config):
                     pretty_filename = camera_config['@name'] + '_' + group
                     pretty_filename = re.sub('[^a-zA-Z0-9]', '_', pretty_filename)
     
@@ -1042,7 +1036,7 @@ class PictureHandler(BaseHandler):
                 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.make_camera_url(camera_config), 'msg': error}})
+                                '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'])
@@ -1054,7 +1048,7 @@ class PictureHandler(BaseHandler):
             logging.debug('checking timelapse movie status for group %(group)s of camera %(id)s' % {
                     'group': group, 'id': camera_id})
 
-            if utils.local_camera(camera_config):
+            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'])
@@ -1069,7 +1063,7 @@ class PictureHandler(BaseHandler):
                 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.make_camera_url(camera_config), 'msg': error}})
+                                '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})
@@ -1086,7 +1080,7 @@ class PictureHandler(BaseHandler):
             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_camera(camera_config):
+            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
@@ -1099,7 +1093,7 @@ class PictureHandler(BaseHandler):
                 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.make_camera_url(camera_config), 'msg': error}})
+                                'url': remote.pretty_camera_url(camera_config), 'msg': error}})
                     
                     if response['progress'] != -1:
                         return self.finish_json({'progress': response['progress']}) # timelapse already active
@@ -1107,7 +1101,7 @@ class PictureHandler(BaseHandler):
                     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.make_camera_url(camera_config), 'msg': error}})
+                                    'url': remote.pretty_camera_url(camera_config), 'msg': error}})
     
                         self.finish_json({'progress': -1})
                     
@@ -1122,7 +1116,7 @@ class PictureHandler(BaseHandler):
                 'group': group, 'id': camera_id})
 
         camera_config = config.get_camera(camera_id)
-        if utils.local_camera(camera_config):
+        if utils.local_motion_camera(camera_config):
             try:
                 mediafiles.del_media_group(camera_config, group, 'picture')
                 self.finish_json()
@@ -1134,7 +1128,7 @@ class PictureHandler(BaseHandler):
             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.make_camera_url(camera_config), 'msg': error}})
+                            'url': remote.pretty_camera_url(camera_config), 'msg': error}})
 
                 self.finish_json()
 
@@ -1189,7 +1183,7 @@ class MovieHandler(BaseHandler):
         logging.debug('listing movies for camera %(id)s' % {'id': camera_id})
         
         camera_config = config.get_camera(camera_id)
-        if utils.local_camera(camera_config):
+        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.'})
@@ -1206,7 +1200,7 @@ class MovieHandler(BaseHandler):
             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}})
+                            'url': remote.pretty_camera_url(camera_config), 'msg': error}})
 
                 self.finish_json(remote_list)
             
@@ -1218,7 +1212,7 @@ class MovieHandler(BaseHandler):
                 'filename': filename, 'id': camera_id})
         
         camera_config = config.get_camera(camera_id)
-        if utils.local_camera(camera_config):
+        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)
@@ -1231,7 +1225,7 @@ class MovieHandler(BaseHandler):
             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}})
+                            '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')
@@ -1247,7 +1241,7 @@ class MovieHandler(BaseHandler):
                 'filename': filename, 'id': camera_id})
         
         camera_config = config.get_camera(camera_id)
-        if utils.local_camera(camera_config):
+        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))
@@ -1283,7 +1277,7 @@ class MovieHandler(BaseHandler):
                 'filename': filename, 'id': camera_id})
         
         camera_config = config.get_camera(camera_id)
-        if utils.local_camera(camera_config):
+        if utils.local_motion_camera(camera_config):
             try:
                 mediafiles.del_media_content(camera_config, filename, 'movie')
                 self.finish_json()
@@ -1295,7 +1289,7 @@ class MovieHandler(BaseHandler):
             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.make_camera_url(camera_config), 'msg': error}})
+                            'url': remote.pretty_camera_url(camera_config), 'msg': error}})
 
                 self.finish_json()
 
@@ -1307,7 +1301,7 @@ class MovieHandler(BaseHandler):
                 'group': group, 'id': camera_id})
 
         camera_config = config.get_camera(camera_id)
-        if utils.local_camera(camera_config):
+        if utils.local_motion_camera(camera_config):
             try:
                 mediafiles.del_media_group(camera_config, group, 'movie')
                 self.finish_json()
@@ -1319,7 +1313,7 @@ class MovieHandler(BaseHandler):
             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.make_camera_url(camera_config), 'msg': error}})
+                            'url': remote.pretty_camera_url(camera_config), 'msg': error}})
 
                 self.finish_json()
 
index 978c3306109dd1e890e908606ac929746ab17080..e6bc5a83d90079e09ddd179eb95b24ce69c45595 100644 (file)
@@ -153,7 +153,7 @@ def cleanup_media(media_type):
         
     for camera_id in config.get_camera_ids():
         camera_config = config.get_camera(camera_id)
-        if not utils.local_camera(camera_config):
+        if not utils.local_motion_camera(camera_config):
             continue
         
         preserve_media = camera_config.get('@preserve_%(media_type)ss' % {'media_type': media_type}, 0)
@@ -239,7 +239,7 @@ def make_next_movie_preview():
         count = 0
         for camera_id in config.get_camera_ids():
             camera_config = config.get_camera(camera_id)
-            if not utils.local_camera(camera_config):
+            if not utils.local_motion_camera(camera_config):
                 continue
             
             target_dir = camera_config['target_dir']
index 7e01b9b7ec765e87522aea422ea7a763243b6d3d..b103bd36ed424d92968152abfb87bc938f4afdff 100644 (file)
@@ -267,7 +267,7 @@ def get_jpg(camera_id):
                 'camera_id': camera_id})
         
         camera_config = config.get_camera(camera_id)
-        if not camera_config['@enabled'] or not utils.local_camera(camera_config):
+        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})
             
index b656dc0650d25a3d9a17b18dce290911c3a3741f..c6c4fd9d5cf2e7219d77624bd0966970858a9fcf 100644 (file)
@@ -72,7 +72,7 @@ def find_motion():
 def _disable_initial_motion_detection():
     for camera_id in config.get_camera_ids():
         camera_config = config.get_camera(camera_id)
-        if not utils.local_camera(camera_config):
+        if not utils.local_motion_camera(camera_config):
             continue
 
         if not camera_config['@motion_detection']:
@@ -253,7 +253,7 @@ def set_motion_detection(camera_id, enabled):
             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.error)})
+                    'msg': utils.pretty_http_error(response)})
         
         else:
             logging.debug('successfully %(what)s motion detection for camera with id %(id)s' % {
@@ -276,7 +276,7 @@ def _get_thread_id(camera_id):
     thread_id = 0
     for cid in camera_ids:
         camera_config = config.get_camera(cid)
-        if utils.local_camera(camera_config):
+        if utils.local_motion_camera(camera_config):
             thread_id += 1
         
         if cid == camera_id:
index 6dc4feb053535c366b3e4a0d92ee1be8c9dd0ab7..0bab9525ca0ae8be09137d06dafe4becbc7f40fa 100644 (file)
@@ -28,10 +28,10 @@ import utils
 _DOUBLE_SLASH_REGEX = re.compile('//+')
 
 
-def _make_request(host, port, username, password, uri, method='GET', data=None, query=None, timeout=None):
+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': 'http',
+            'scheme': scheme,
             'host': host,
             'port': ':' + str(port) if port else '',
             'uri': uri or ''}
@@ -74,19 +74,22 @@ def _callback_wrapper(callback):
     return wrapper
 
 
-def make_camera_url(local_config, camera=True):
+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'))
-    username = local_config.get('@username', local_config.get('username')) or ''
     uri = local_config.get('@uri', local_config.get('uri')) or ''
 
-    url = 'motioneye://' + username + '@' + host
-    if port:
+    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')))
@@ -97,71 +100,78 @@ def make_camera_url(local_config, camera=True):
     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_cameras(local_config, callback):
-    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 ''
+    scheme, host, port, username, password, uri, _ = _remote_params(local_config)
     
     logging.debug('listing remote cameras on %(url)s' % {
-            'url': make_camera_url(local_config, camera=False)})
+            'url': pretty_camera_url(local_config, camera=False)})
     
-    request = _make_request(host, port, username, password, uri + '/config/list/')
+    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': make_camera_url(local_config, camera=False),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config, camera=False),
+                    'msg': utils.pretty_http_error(response)})
             
-            return callback(error=utils.pretty_http_error(response.error))
+            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': make_camera_url(local_config, camera=False),
+                    'url': pretty_camera_url(local_config, camera=False),
                     'msg': unicode(e)})
             
             return callback(error=unicode(e))
         
-        callback(response['cameras'])
+        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):
-    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'))
+    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': make_camera_url(local_config)})
+            'url': pretty_camera_url(local_config)})
     
-    request = _make_request(host, port, username, password, uri + '/config/%(id)s/get/' % {'id': camera_id})
+    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': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
             
-            return callback(error=utils.pretty_http_error(response.error))
+            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': make_camera_url(local_config),
+                    'url': pretty_camera_url(local_config),
                     'msg': unicode(e)})
             
             return callback(error=unicode(e))
@@ -176,38 +186,30 @@ def get_config(local_config, callback):
     
 
 def set_config(local_config, ui_config, callback):
-    host = local_config.get('@host') 
-    port = local_config.get('@port')
-    username = local_config.get('@username')
-    password = local_config.get('@password')
-    uri = local_config.get('@uri') or ''
-    camera_id = local_config.get('@remote_camera_id')
-    
-    # make sure these values never get to the remote instance
-    local_config.pop('enabled', None)
-    local_config.pop('proto', None)
-    local_config.pop('host', None)
-    local_config.pop('port', None)
-    local_config.pop('uri', None)
-    local_config.pop('username', None)
-    local_config.pop('password', None)
-    
+    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': make_camera_url(local_config)})
+            'url': pretty_camera_url(local_config)})
     
     ui_config = json.dumps(ui_config)
     
-    request = _make_request(host, port, username, password, uri + '/config/%(id)s/set/' % {'id': camera_id}, method='POST', data=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': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
             
-            return callback(error=utils.pretty_http_error(response.error))
+            return callback(error=utils.pretty_http_error(response))
     
         callback()
 
@@ -216,29 +218,24 @@ def set_config(local_config, ui_config, callback):
 
 
 def set_preview(local_config, controls, callback):
-    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'))
+    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': make_camera_url(local_config)})
+            'url': pretty_camera_url(local_config)})
     
     data = json.dumps(controls)
     
-    request = _make_request(host, port, username, password, uri + '/config/%(id)s/set_preview/' % {'id': camera_id}, method='POST', data=data)
+    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': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
         
-            return callback(error=utils.pretty_http_error(response.error))
+            return callback(error=utils.pretty_http_error(response))
         
         callback()
 
@@ -247,16 +244,11 @@ def set_preview(local_config, controls, callback):
 
 
 def get_current_picture(local_config, width, height, callback):
-    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'))
+    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': make_camera_url(local_config)})
+            'url': pretty_camera_url(local_config)})
     
     query = {}
     
@@ -266,7 +258,7 @@ def get_current_picture(local_config, width, height, callback):
     if height:
         query['height'] = str(height)
     
-    request = _make_request(host, port, username, password, uri + '/picture/%(id)s/current/' % {'id': camera_id}, query=query)
+    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
@@ -281,10 +273,10 @@ def get_current_picture(local_config, width, height, callback):
         if response.error:
             logging.error('failed to get current picture for remote camera %(id)s on %(url)s: %(msg)s' % {
                     'id': camera_id,
-                    'url': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
             
-            return callback(error=utils.pretty_http_error(response.error))
+            return callback(error=utils.pretty_http_error(response))
 
         callback(motion_detected, response.body)
     
@@ -293,40 +285,35 @@ def get_current_picture(local_config, width, height, callback):
 
 
 def list_media(local_config, media_type, prefix, callback):
-    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'))
+    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': make_camera_url(local_config)})
+            '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(host, port, username, password, uri + '/%(media_type)s/%(id)s/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': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
             
-            return callback(error=utils.pretty_http_error(response.error))
+            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': make_camera_url(local_config),
+                    'url': pretty_camera_url(local_config),
                     'msg': unicode(e)})
             
             return callback(error=unicode(e))
@@ -338,17 +325,12 @@ def list_media(local_config, media_type, prefix, callback):
 
 
 def get_media_content(local_config, filename, media_type, callback):
-    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'))
+    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': make_camera_url(local_config)})
+            'url': pretty_camera_url(local_config)})
     
     uri += '/%(media_type)s/%(id)s/download/%(filename)s' % {
             'media_type': media_type,
@@ -356,17 +338,17 @@ def get_media_content(local_config, filename, media_type, callback):
             'filename': filename}
     
     # 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, uri, timeout=10 * settings.REMOTE_REQUEST_TIMEOUT)
+    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': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
             
-            return callback(error=utils.pretty_http_error(response.error))
+            return callback(error=utils.pretty_http_error(response))
         
         return callback(response.body)
 
@@ -375,17 +357,12 @@ def get_media_content(local_config, filename, media_type, callback):
 
 
 def make_zipped_content(local_config, media_type, group, callback):
-    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'))
+    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': make_camera_url(local_config)})
+            'url': pretty_camera_url(local_config)})
 
     prepare_uri = uri + '/%(media_type)s/%(id)s/zipped/%(group)s/' % {
             'media_type': media_type,
@@ -393,24 +370,24 @@ def make_zipped_content(local_config, media_type, group, callback):
             'group': group}
  
     # timeout here is 100 times larger than usual - we expect a big delay
-    request = _make_request(host, port, username, password, prepare_uri, timeout=100 * settings.REMOTE_REQUEST_TIMEOUT)
+    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': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
 
-            return callback(error=utils.pretty_http_error(response.error))
+            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': make_camera_url(local_config),
+                    'url': pretty_camera_url(local_config),
                     'msg': unicode(e)})
 
             return callback(error=unicode(e))
@@ -422,18 +399,13 @@ def make_zipped_content(local_config, media_type, group, callback):
 
 
 def get_zipped_content(local_config, media_type, key, group, callback):
-    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'))
+    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': make_camera_url(local_config)})
+            'url': pretty_camera_url(local_config)})
     
-    request = _make_request(host, port, username, password, uri + '/%(media_type)s/%(id)s/zipped/%(group)s/?key=%(key)s' % {
+    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,
@@ -444,10 +416,10 @@ def get_zipped_content(local_config, media_type, key, group, callback):
         if response.error:
             logging.error('failed to download zip file for remote camera %(id)s on %(url)s: %(msg)s' % {
                     'id': camera_id,
-                    'url': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
 
-            return callback(error=utils.pretty_http_error(response.error))
+            return callback(error=utils.pretty_http_error(response))
 
         callback({
             'data': response.body,
@@ -460,19 +432,14 @@ def get_zipped_content(local_config, media_type, key, group, callback):
 
 
 def make_timelapse_movie(local_config, framerate, interval, group, callback):
-    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'))
+    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': make_camera_url(local_config)})
+            'url': pretty_camera_url(local_config)})
 
     uri += '/picture/%(id)s/timelapse/%(group)s/?interval=%(int)s&framerate=%(framerate)s' % {
             'id': camera_id,
@@ -480,26 +447,26 @@ def make_timelapse_movie(local_config, framerate, interval, group, callback):
             'framerate': framerate,
             'group': group}
     
-    request = _make_request(host, port, username, password, uri, timeout=100 * settings.REMOTE_REQUEST_TIMEOUT)
+    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': make_camera_url(local_config),
+                    'url': pretty_camera_url(local_config),
                     'int': interval,
                     'framerate': framerate,
-                    'msg': utils.pretty_http_error(response.error)})
+                    'msg': utils.pretty_http_error(response)})
 
-            return callback(error=utils.pretty_http_error(response.error))
+            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': make_camera_url(local_config),
+                    'url': pretty_camera_url(local_config),
                     'msg': unicode(e)})
 
             return callback(error=unicode(e))
@@ -511,18 +478,13 @@ def make_timelapse_movie(local_config, framerate, interval, group, callback):
 
 
 def check_timelapse_movie(local_config, group, callback):
-    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'))
+    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': make_camera_url(local_config)})
+            'url': pretty_camera_url(local_config)})
     
-    request = _make_request(host, port, username, password, uri + '/picture/%(id)s/timelapse/%(group)s/?check=true' % {
+    request = _make_request(scheme, host, port, username, password, uri + '/picture/%(id)s/timelapse/%(group)s/?check=true' % {
             'id': camera_id,
             'group': group})
     
@@ -530,17 +492,17 @@ def check_timelapse_movie(local_config, group, callback):
         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': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
 
-            return callback(error=utils.pretty_http_error(response.error))
+            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': make_camera_url(local_config),
+                    'url': pretty_camera_url(local_config),
                     'msg': unicode(e)})
 
             return callback(error=unicode(e))
@@ -552,18 +514,13 @@ def check_timelapse_movie(local_config, group, callback):
 
 
 def get_timelapse_movie(local_config, key, group, callback):
-    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'))
+    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': make_camera_url(local_config)})
+            'url': pretty_camera_url(local_config)})
     
-    request = _make_request(host, port, username, password, uri + '/picture/%(id)s/timelapse/%(group)s/?key=%(key)s' % {
+    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},
@@ -573,10 +530,10 @@ def get_timelapse_movie(local_config, key, group, callback):
         if response.error:
             logging.error('failed to download timelapse movie for remote camera %(id)s on %(url)s: %(msg)s' % {
                     'id': camera_id,
-                    'url': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
 
-            return callback(error=utils.pretty_http_error(response.error))
+            return callback(error=utils.pretty_http_error(response))
 
         callback({
             'data': response.body,
@@ -589,17 +546,12 @@ def get_timelapse_movie(local_config, key, group, callback):
 
 
 def get_media_preview(local_config, filename, media_type, width, height, callback):
-    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'))
+    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': make_camera_url(local_config)})
+            'url': pretty_camera_url(local_config)})
     
     uri += '/%(media_type)s/%(id)s/preview/%(filename)s' % {
             'media_type': media_type,
@@ -614,17 +566,17 @@ def get_media_preview(local_config, filename, media_type, width, height, callbac
     if height:
         query['height'] = str(height)
     
-    request = _make_request(host, port, username, password, uri, query=query)
+    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': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
             
-            return callback(error=utils.pretty_http_error(response.error))
+            return callback(error=utils.pretty_http_error(response))
         
         callback(response.body)
 
@@ -633,34 +585,29 @@ def get_media_preview(local_config, filename, media_type, width, height, callbac
 
 
 def del_media_content(local_config, filename, media_type, callback):
-    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'))
+    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': make_camera_url(local_config)})
+            '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(host, port, username, password, uri, method='POST', data='{}', timeout=settings.REMOTE_REQUEST_TIMEOUT)
+    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': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
             
-            return callback(error=utils.pretty_http_error(response.error))
+            return callback(error=utils.pretty_http_error(response))
         
         callback()
 
@@ -669,34 +616,29 @@ def del_media_content(local_config, filename, media_type, callback):
 
 
 def del_media_group(local_config, group, media_type, callback):
-    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'))
+    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': make_camera_url(local_config)})
+            '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(host, port, username, password, uri, method='POST', data='{}', timeout=settings.REMOTE_REQUEST_TIMEOUT)
+    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': make_camera_url(local_config),
-                    'msg': utils.pretty_http_error(response.error)})
+                    'url': pretty_camera_url(local_config),
+                    'msg': utils.pretty_http_error(response)})
             
-            return callback(error=utils.pretty_http_error(response.error))
+            return callback(error=utils.pretty_http_error(response))
         
         callback()
 
index 101a88127e0e6c22222f8412edfaa864e82fd0e2..b7aaf87dfeac6ebf349a2c76b14ffbe77e9d7535 100644 (file)
@@ -210,13 +210,22 @@ def pretty_size(size):
     return '%.1f %s' % (size, unit)
 
 
-def pretty_http_error(http_error):
-    msg = unicode(http_error)
+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
 
@@ -244,40 +253,48 @@ def get_disk_usage(path):
     return (used_size, total_size)
 
 
-def local_camera(config):
+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 test_netcam_url(data, callback):
+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('proto', 'http')
+    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 = '%(proto)s://%(host)s%(port)s%(uri)s' % {
-            'proto': data['proto'],
+    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]
-    auth_modes = ['basic'] # 'digest' netcams are not supported by motion yet
 
     def on_header(header):
         header = header.lower()
@@ -285,14 +302,14 @@ def test_netcam_url(data, callback):
             content_type = header.split(':')[1].strip()
             called[0] = True
 
-            if content_type in ['image/jpg', 'image/jpeg', 'image/pjpg']:
+            if content_type in ['image/jpg', 'image/jpeg', 'image/pjpg'] and allow_jpeg:
                 callback([{'id': 1, 'name': 'JPEG Network Camera'}])
             
             elif content_type.startswith('multipart/x-mixed-replace'):
                 callback([{'id': 1, 'name': 'MJPEG Network Camera'}])
             
             else:
-                callback(error='not a network camera')
+                callback(error='not a supported network camera')
 
         else:
             # check for the status header
@@ -324,7 +341,7 @@ def test_netcam_url(data, callback):
                 
             else:
                 called[0] = True
-                callback(error=pretty_http_error(response.error) if response.error else 'not a network camera')
+                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
index 66cd08324e42cb32a93ea5ced2a13c2bfba351da..be65c2749ce8b6598156c7e08c13a831dc8d7b4a 100644 (file)
@@ -80,7 +80,7 @@ def _check_ws():
     now = datetime.datetime.now()
     for camera_id in config.get_camera_ids():
         camera_config = config.get_camera(camera_id)
-        if not utils.local_camera(camera_config):
+        if not utils.local_motion_camera(camera_config):
             continue
         
         working_schedule = camera_config.get('@working_schedule')
index 95c44bfe43003fe96ef099343a4e487d5a33c8e4..11b3a35ce2f975772e11ad1eec4356c1e4d406d0 100644 (file)
@@ -521,6 +521,10 @@ span#cameraMsgLabel {
     font-size: 0.7em;
 }
 
+div#addCameraInfo {
+    font-size: 0.7em;
+    max-width: 33em;
+}
 
 div.media-dialog {
 }
index 15a33b3bf96c1fdd3a641c91114457c7463028ee..ad9acc21c527b16fef094cc3827f2a831c8e7471 100644 (file)
@@ -176,6 +176,9 @@ input[type=text].time {
     width: 3.5em;
 }
 
+input[disabled] {
+    border: 1px solid #555 !important;
+}
 
     /* combo box */
 
@@ -410,6 +413,12 @@ 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 */
 
index 9359c694c2c4b901b7932b918485da69b5715221..819f5521d3b904f4c9337b6f6627706ce146705a 100644 (file)
@@ -394,19 +394,6 @@ String.prototype.replaceAll = String.prototype.replaceAll || function (oldStr, n
     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;
-    }
-}
-
 function getCookie(name) {
     if (document.cookie.length <= 0) {
         return null;
@@ -1155,12 +1142,6 @@ function cameraUi2Dict() {
         'auto_brightness': $('#autoBrightnessSwitch')[0].checked,
         '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,
         'extra_options': $('#extraOptionsEntry').html().split(new RegExp('(<br[^>]*>)|(<div>)|(<p>)')).map(function (o) {
             if (!o) {
                 return null;
@@ -1346,15 +1327,29 @@ function dict2CameraUi(dict) {
     }
     
     /* 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'];
     $('#deviceNameEntry').val(dict['name']);
-    $('#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'];
+    $('#deviceUriEntry').val(dict['device_url']);
+    $('#deviceTypeEntry').val(prettyType);
     $('#lightSwitchDetectSwitch')[0].checked = dict['light_switch_detect'];
     $('#autoBrightnessSwitch')[0].checked = dict['auto_brightness'];
     
@@ -1470,7 +1465,7 @@ function dict2CameraUi(dict) {
     var mjpgUrl = location.protocol + '//' + location.host.split(':')[0] + ':' + dict.streaming_port;
     var embedUrl = cameraUrl + 'frame/';
 
-    if (dict.proto == 'motioneye') {
+    if (dict.proto == 'motioneye') { // TODO what about other protocols
         /* cannot tell the mjpg streaming url for a remote motionEye camera */
         mjpgUrl = '';
     }
@@ -1730,7 +1725,7 @@ function doApply() {
             }
             
             var instance;
-            if (config.proto == 'http' || config.proto == 'v4l2') {
+            if (config.proto == 'netcam' || config.proto == 'v4l2') {
                 instance = '';
             }
             else { /* motioneye */
@@ -2313,7 +2308,7 @@ function getCameraIdsByInstance() {
     var cameraIdsByInstance = {};
     $('div.camera-frame').each(function () {
         var instance;
-        if (this.config.proto == 'http' || this.config.proto == 'v4l2') {
+        if (this.config.proto == 'netcam' || this.config.proto == 'v4l2') {
             instance = '';
         }
         else { /* motioneye */
@@ -2544,27 +2539,33 @@ function runAddCameraDialog() {
                 '<tr>' +
                     '<td class="dialog-item-label"><span class="dialog-item-label">Device</span></td>' +
                     '<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>' +
+                    '<td><span class="help-mark" title="the device type you wish to add">?</span></td>' +
                 '</tr>' +
-                '<tr class="motioneye netcam">' +
+                '<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/cams/)">?</span></td>' +
+                    '<td><span class="help-mark" title="the camera URL (e.g. http://example.com:8080/cam/)">?</span></td>' +
                 '</tr>' +
-                '<tr class="motioneye netcam">' +
+                '<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">' +
+                '<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="motioneye netcam">' +
+                '<tr class="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 to motionEye">?</span></td>' +
+                    '<td><span class="help-mark" title="the camera you wish to add">?</span></td>' +
+                '</tr>' +
+                '<tr class="motioneye netcam mjpeg">' +
+                    '<td colspan="100"><div class="dialog-item-separator"></div></td>' +
+                '</tr>' +
+                '<tr class="motioneye netcam mjpeg">' +
+                    '<td class="dialog-item-value" colspan="100"><div id="addCameraInfo">blah blah sdfsdf ana are mere multe verzi si vesele si mari</div></td>' +
                 '</tr>' +
             '</table>');
     
@@ -2574,6 +2575,7 @@ function runAddCameraDialog() {
     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 */
@@ -2583,16 +2585,17 @@ function runAddCameraDialog() {
     makeComboValidator(addCameraSelect, true);
     
     /* ui interaction */
-    content.find('tr.motioneye, tr.netcam').css('display', 'none');
-    
     function updateUi() {
-        content.find('tr.motioneye, tr.netcam').css('display', 'none');
+        content.find('tr.motioneye, tr.netcam, tr.mjpeg').css('display', 'none');
 
         if (deviceSelect.val() == 'motioneye') {
             content.find('tr.motioneye').css('display', 'table-row');
             addCameraSelect.hide();
             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 (deviceSelect.val() == 'netcam') {
             usernameEntry.removeAttr('readonly');
@@ -2607,6 +2610,28 @@ function runAddCameraDialog() {
 
             content.find('tr.netcam').css('display', 'table-row');
             addCameraSelect.hide();
+            addCameraInfo.html(
+                    'Network cameras (or IP cameras) are devices that natively stream MJPEG videos or plain JPEG images. ' +
+                    "Consult your device's manual to find out the correct MJPEG (or JPEG) URL.");
+        }
+        else if (deviceSelect.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');
+            addCameraSelect.hide();
+            addCameraInfo.html(
+                    'Adding your device as a simple MJPEG camera instead of as a network camera will improve the framerate, ' +
+                    'but no motion detection or other advanced features 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.');
         }
         
         updateModalDialogPosition();
@@ -2617,7 +2642,7 @@ function runAddCameraDialog() {
             this.validate();
         });
         
-        if (content.is(':visible') && uiValid() && (deviceSelect.val() == 'motioneye' || deviceSelect.val() == 'netcam')) {
+        if (content.is(':visible') && uiValid() && (deviceSelect.val() in {'motioneye': 1, 'netcam': 1, 'mjpeg': 1})) {
             fetchRemoteCameras();
         }
     }
@@ -2645,7 +2670,7 @@ function runAddCameraDialog() {
     
     function splitCameraUrl(url) {
         var parts = url.split('://');
-        var proto = parts[0];
+        var scheme = parts[0];
         var index = parts[1].indexOf('/');
         var host = null;
         var uri = '';
@@ -2669,7 +2694,7 @@ function runAddCameraDialog() {
         }
         
         return {
-            proto: proto,
+            scheme: scheme,
             host: host,
             port: port,
             uri: uri
@@ -2686,7 +2711,7 @@ function runAddCameraDialog() {
         var data = splitCameraUrl(urlEntry.val());
         data.username = usernameEntry.val();
         data.password = passwordEntry.val();
-        data.type = deviceSelect.val();
+        data.proto = deviceSelect.val();
         
         cameraMsgLabel.html('');
         
@@ -2738,6 +2763,7 @@ function runAddCameraDialog() {
         
         deviceSelect.append('<option value="netcam">Network camera...</option>');
         deviceSelect.append('<option value="motioneye">Remote motionEye camera...</option>');
+        //deviceSelect.append('<option value="mjpeg">Simple MJPEG camera...</option>');
         
         updateUi();
         
@@ -2764,6 +2790,13 @@ function runAddCameraDialog() {
                     data = splitCameraUrl(urlEntry.val());
                     data.username = usernameEntry.val();
                     data.password = passwordEntry.val();
+                    data.proto = 'netcam';
+                }
+                else if (deviceSelect.val() == 'mjpeg') {
+                    data = splitCameraUrl(urlEntry.val());
+                    data.username = usernameEntry.val();
+                    data.password = passwordEntry.val();
+                    data.proto = 'mjpeg';
                 }
                 else { /* assuming v4l2 */
                     data.proto = 'v4l2';
index cbc501646efd52c3865fda5327104ebcb278bf44..92df18ef19741be372b816207e01e80cb931fa9e 100644 (file)
                     </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="deviceEntry" disabled="disabled"></td>
+                        <td class="settings-item-value"><input type="text" class="styled device camera-config" id="deviceUriEntry" disabled="disabled"></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" disabled="disabled"></td>
                     </tr>
                     <tr class="settings-item advanced-setting">
                         <td colspan="100"><div class="settings-item-separator"></div></td>