From 6922b7f0b5767902ba2d2160c4d6c514d3fd8173 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Sat, 16 May 2015 16:35:37 +0300 Subject: [PATCH] an intial working version of simple mjpeg camera --- src/config.py | 37 ++++++++++++++++++++++++++++++++++-- src/handlers.py | 39 +++++++++++++++++++++----------------- static/js/frame.js | 8 ++++++++ static/js/main.js | 46 ++++++++++++++++++++++++++++++++++----------- templates/main.html | 3 ++- 5 files changed, 102 insertions(+), 31 deletions(-) diff --git a/src/config.py b/src/config.py index 0a63012..e3a0986 100644 --- a/src/config.py +++ b/src/config.py @@ -489,6 +489,7 @@ def add_camera(device_details): else: # assuming mjpeg camera_config['@proto'] = 'mjpeg' camera_config['@url'] = device_details['url'] + _set_default_simple_mjpeg_camera(camera_id, camera_config) # write the configuration to file set_camera(camera_id, camera_config) @@ -574,7 +575,7 @@ def main_dict_to_ui(data): return ui -def camera_ui_to_dict(ui, old_config=None): +def motion_camera_ui_to_dict(ui, old_config=None): old_config = dict(old_config or {}) main_config = get_main() # needed for surveillance password @@ -849,7 +850,7 @@ def camera_ui_to_dict(ui, old_config=None): return old_config -def camera_dict_to_ui(data): +def motion_camera_dict_to_ui(data): ui = { # device 'name': data['@name'], @@ -1162,6 +1163,32 @@ def camera_dict_to_ui(data): return ui +def simple_mjpeg_camera_ui_to_dict(ui, old_config=None): + old_config = dict(old_config or {}) + + data = { + # device + '@name': ui['name'], + '@enabled': ui['enabled'], + } + + old_config.update(data) + + return old_config + + +def simple_mjpeg_camera_dict_to_ui(data): + ui = { + 'name': data['@name'], + 'enabled': data['@enabled'], + 'id': data['@id'], + 'proto': 'mjpeg', + 'url': data['@url'] + } + + return ui + + def backup(): logging.debug('generating config backup file') @@ -1545,6 +1572,12 @@ def _set_default_motion_camera(camera_id, data, old_motion=False): data.setdefault('on_event_end', '') +def _set_default_simple_mjpeg_camera(camera_id, data): + data.setdefault('@name', 'Camera' + str(camera_id)) + data.setdefault('@enabled', False) + data.setdefault('@id', camera_id) + + def get_additional_structure(camera, separators=False): if _additional_structure_cache.get((camera, separators)) is None: logging.debug('loading additional config structure for %s' % ('camera' if camera else 'main')) diff --git a/src/handlers.py b/src/handlers.py index 0a7aee1..d05dcba 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -216,7 +216,7 @@ class ConfigHandler(BaseHandler): local_config = config.get_camera(camera_id) if utils.local_motion_camera(local_config): - ui_config = config.camera_dict_to_ui(local_config) + ui_config = config.motion_camera_dict_to_ui(local_config) self.finish_json(ui_config) @@ -236,7 +236,9 @@ class ConfigHandler(BaseHandler): remote.get_config(local_config, on_response) else: # assuming simple mjpeg camera - pass # TODO implement me + ui_config = config.simple_mjpeg_camera_dict_to_ui(local_config) + + self.finish_json(ui_config) else: logging.debug('getting main config') @@ -264,7 +266,7 @@ class ConfigHandler(BaseHandler): local_config = config.get_camera(camera_id) if utils.local_motion_camera(local_config): - local_config = config.camera_ui_to_dict(ui_config, local_config) + local_config = config.motion_camera_ui_to_dict(ui_config, local_config) config.set_camera(camera_id, local_config) @@ -289,7 +291,11 @@ class ConfigHandler(BaseHandler): on_finish(None, False) else: # assuming simple mjpeg camera - pass # TODO implement me + local_config = config.simple_mjpeg_camera_ui_to_dict(ui_config, local_config) + + config.set_camera(camera_id, local_config) + + on_finish(None, False) # (no error, motion doesn't need restart) def set_main_config(ui_config): logging.debug('setting main config...') @@ -327,8 +333,8 @@ class ConfigHandler(BaseHandler): if not utils.local_motion_camera(local_config): continue - ui_config = config.camera_dict_to_ui(local_config) - local_config = config.camera_ui_to_dict(ui_config, local_config) + ui_config = config.motion_camera_dict_to_ui(local_config) + local_config = config.motion_camera_ui_to_dict(ui_config, local_config) config.set_camera(camera_id, local_config) @@ -564,7 +570,7 @@ class ConfigHandler(BaseHandler): continue if utils.local_motion_camera(local_config): - ui_config = config.camera_dict_to_ui(local_config) + ui_config = config.motion_camera_dict_to_ui(local_config) cameras.append(ui_config) check_finished() @@ -576,7 +582,9 @@ class ConfigHandler(BaseHandler): on_response_builder(camera_id, local_config)(error=True) else: # assuming simple mjpeg camera - pass # TODO implement me + ui_config = config.simple_mjpeg_camera_dict_to_ui(local_config) + cameras.append(ui_config) + check_finished() if length[0] == 0: self.finish_json({'cameras': []}) @@ -622,7 +630,7 @@ class ConfigHandler(BaseHandler): else: motionctl.start() - ui_config = config.camera_dict_to_ui(camera_config) + ui_config = config.motion_camera_dict_to_ui(camera_config) self.finish_json(ui_config) @@ -639,8 +647,8 @@ class ConfigHandler(BaseHandler): remote.get_config(camera_config, on_response) else: # assuming simple mjpeg camera - #ui_config = config.camera_dict_to_ui(camera_config) - # TODO use a special mjpeg function to generate ui_config + ui_config = config.simple_mjpeg_camera_dict_to_ui(camera_config) + self.finish_json(ui_config) @BaseHandler.auth(admin=True) @@ -837,7 +845,7 @@ class PictureHandler(BaseHandler): def frame(self, camera_id): camera_config = config.get_camera(camera_id) - if utils.local_motion_camera(camera_config) or self.get_argument('title', None) is not None: + if utils.local_motion_camera(camera_config) or utils.simple_mjpeg_camera(camera_config) or self.get_argument('title', None) is not None: self.render('main.html', frame=True, camera_id=camera_id, @@ -854,9 +862,9 @@ class PictureHandler(BaseHandler): camera_config=camera_config, title=self.get_argument('title', '')) - # issue a fake camera_ui_to_dict() call to transform + # issue a fake motion_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) + remote_config = config.motion_camera_ui_to_dict(remote_ui_config) self.render('main.html', frame=True, @@ -867,9 +875,6 @@ class PictureHandler(BaseHandler): remote.get_config(camera_config, on_response) - else: # assuming simple mjpeg camera - pass # TODO implement me - @BaseHandler.auth() def download(self, camera_id, filename): logging.debug('downloading picture %(filename)s of camera %(id)s' % { diff --git a/static/js/frame.js b/static/js/frame.js index 3d77c51..8620922 100644 --- a/static/js/frame.js +++ b/static/js/frame.js @@ -16,6 +16,8 @@ function setupCameraFrame() { cameraFrameDiv[0].refreshDivider = 0; cameraFrameDiv[0].streamingFramerate = parseInt(cameraFrameDiv.attr('streaming_framerate')) || 1; cameraFrameDiv[0].streamingServerResize = cameraFrameDiv.attr('streaming_server_resize') == 'True'; + cameraFrameDiv[0].proto = cameraFrameDiv.attr('proto'); + cameraFrameDiv[0].url = cameraFrameDiv.attr('url'); progressImg.attr('src', staticUrl + 'img/camera-progress.gif'); cameraProgress.addClass('visible'); @@ -78,6 +80,12 @@ function refreshCameraFrame() { var img = $cameraFrame.find('img.camera')[0]; var cameraId = cameraFrame.id.substring(6); + if (cameraFrame.proto == 'mjpeg') { + /* no manual refresh for simple mjpeg cameras */ + img.src = cameraFrame.url; + return; + } + /* at a refresh interval of 50ms, the refresh rate is limited to 20 fps */ var count = 1000 / (refreshInterval * cameraFrame.streamingFramerate); if (count <= 2) { diff --git a/static/js/main.js b/static/js/main.js index d3f6a30..619be98 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -1163,9 +1163,11 @@ function cameraUi2Dict() { } var dict = { - /* video device */ 'enabled': $('#videoDeviceSwitch')[0].checked, 'name': $('#deviceNameEntry').val(), + 'proto': $('#deviceTypeEntry')[0].proto, + + /* video device */ 'light_switch_detect': $('#lightSwitchDetectSwitch')[0].checked, 'auto_brightness': $('#autoBrightnessSwitch')[0].checked, 'rotation': $('#rotationSelect').val(), @@ -1355,15 +1357,15 @@ function dict2CameraUi(dict) { } function markHideIfNull(field, elemId) { - if (field == null || dict[field] == null) { - var elem = $('#' + elemId); - var sectionDiv = elem.parents('tr:eq(0), div.settings-section-title:eq(0)'); - if (sectionDiv.length) { /* element is a section */ - sectionDiv.add(sectionDiv.next()).each(function () {this._hideNull = true;}); - } - else { /* element is a config option */ - elem.parents('tr:eq(0)').each(function () {this._hideNull = true;}); - } + var elem = $('#' + elemId); + var sectionDiv = elem.parents('tr:eq(0), div.settings-section-title:eq(0)'); + var hideNull = field == null || dict[field] == null; + + if (sectionDiv.length) { /* element is a section */ + sectionDiv.add(sectionDiv.next()).each(function () {this._hideNull = hideNull;}); + } + else { /* element is a config option */ + elem.parents('tr:eq(0)').each(function () {this._hideNull = hideNull;}); } } @@ -1391,6 +1393,7 @@ function dict2CameraUi(dict) { $('#deviceNameEntry').val(dict['name']); markHideIfNull('name', 'deviceNameEntry'); $('#deviceUriEntry').val(dict['device_url']); markHideIfNull('device_url', 'deviceUriEntry'); $('#deviceTypeEntry').val(prettyType); markHideIfNull(prettyType, 'deviceTypeEntry'); + $('#deviceTypeEntry')[0].proto = dict['proto']; $('#lightSwitchDetectSwitch')[0].checked = dict['light_switch_detect']; markHideIfNull('light_switch_detect', 'lightSwitchDetectSwitch'); $('#autoBrightnessSwitch')[0].checked = dict['auto_brightness']; markHideIfNull('auto_brightness', 'autoBrightnessSwitch'); @@ -2342,12 +2345,15 @@ function getCameraIdsByInstance() { if (this.config.proto == 'netcam' || this.config.proto == 'v4l2') { instance = ''; } - else { /* motioneye */ + else if (this.config.proto == 'motioneye') { instance = this.config.host || ''; if (this.config.port) { instance += ':' + this.config.port; } } + else { /* assuming simple mjpeg camera */ + return; + } (cameraIdsByInstance[instance] = cameraIdsByInstance[instance] || []).push(this.config.id); }); @@ -3407,6 +3413,12 @@ function addCameraFrameUi(cameraConfig) { configureButton.hide(); } + /* no media buttons for simple mjpeg cameras */ + if (cameraConfig['proto'] == 'mjpeg') { + picturesButton.hide(); + moviesButton.hide(); + } + cameraFrameDiv.attr('id', 'camera' + cameraId); cameraFrameDiv[0].refreshDivider = 0; cameraFrameDiv[0].config = cameraConfig; @@ -3633,6 +3645,11 @@ function doFullScreenCamera(cameraId) { } }); }); + + if (cameraFrameDiv[0].config['proto'] == 'mjpeg') { + /* manually trigger the load event on simple mjpeg cameras */ + cameraImg.load(); + } } function refreshCameraFrames() { @@ -3678,6 +3695,13 @@ function refreshCameraFrames() { cameraFrames.each(function () { if (!this.img) { this.img = $(this).find('img.camera')[0]; + if (this.config['proto'] == 'mjpeg') { + this.img.src = this.config['url']; + } + } + + if (this.config['proto'] == 'mjpeg') { + return; /* no manual refresh for simple mjpeg cameras */ } /* at a refresh interval of 50ms, the refresh rate is limited to 20 fps */ diff --git a/templates/main.html b/templates/main.html index d7133e6..184bd96 100644 --- a/templates/main.html +++ b/templates/main.html @@ -806,7 +806,8 @@ {% else %}
+ streaming_framerate="{{camera_config['stream_maxrate']}}" streaming_server_resize="{{camera_config['@webcam_server_resize']}}" + proto="{{camera_config['@proto']}}" url="{{camera_config['@url']}}">