From: Calin Crisan Date: Sun, 17 Nov 2013 10:51:49 +0000 (+0200) Subject: media files details are now fetched upon display, asynchronously X-Git-Url: http://www.vanbest.org/gitweb/?a=commitdiff_plain;h=c1810c7d126c31b9bb7b12681ac2d12271b67aeb;p=motioneye-debian media files details are now fetched upon display, asynchronously --- diff --git a/README.md b/README.md index 597e421..bb7717c 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,14 @@ * python 2.6+ * tornado * jinja2 + * PIL * motion * ffmpeg * v4l2-utils On a debian-based system you could run: - apt-get install python-tornado python-jinja2 motion v4l2-utils + apt-get install python-tornado python-jinja2 python-imaging motion v4l2-utils ## Browser Compatibility ## diff --git a/doc/todo.txt b/doc/todo.txt index d81fb15..6032486 100644 --- a/doc/todo.txt +++ b/doc/todo.txt @@ -1,4 +1,7 @@ +-> test IE -> layout seems to be too wide for a modern mobile phone +-> add a mgmt command for generating thumbnails +-> implement media files paging - find a solution for the many files -> make camera frames positions configurable -> add a view log functionality diff --git a/src/config.py b/src/config.py index d56c516..246afdb 100644 --- a/src/config.py +++ b/src/config.py @@ -416,7 +416,7 @@ def camera_ui_to_dict(ui): '@enabled': ui.get('enabled', False), '@proto': ui.get('proto', 'v4l2'), 'videodevice': ui.get('device', ''), - 'lightswitch': int(ui.get('light_switch_detect', False)) * 5, + 'lightswitch': int(ui.get('light_switch_detect', True)) * 5, 'auto_brightness': ui.get('auto_brightness', False), 'width': int(ui['resolution'].split('x')[0]), 'height': int(ui['resolution'].split('x')[1]), @@ -951,7 +951,7 @@ def _set_default_motion_camera(data): data.setdefault('@enabled', False) data.setdefault('@proto', 'v4l2') data.setdefault('videodevice', '/dev/video0') - data.setdefault('lightswitch', 0) + data.setdefault('lightswitch', 5) data.setdefault('auto_brightness', False) data.setdefault('brightness', 0) data.setdefault('contrast', 0) diff --git a/src/handlers.py b/src/handlers.py index d8fd132..e41be6a 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -594,10 +594,14 @@ class PictureHandler(BaseHandler): camera_config.get('@username'), camera_config.get('@password'), camera_config.get('@remote_camera_id'), on_response, - media_type='picture') + media_type='picture', + prefix=self.get_argument('prefix', None), + stat=self.get_argument('stat', None)) else: - pictures = mediafiles.list_media(camera_config, media_type='picture') + pictures = mediafiles.list_media(camera_config, media_type='picture', + prefix=self.get_argument('prefix', None), + stat=self.get_argument('stat', None)) self.finish_json({ 'mediaList': pictures, @@ -747,10 +751,14 @@ class MovieHandler(BaseHandler): camera_config.get('@username'), camera_config.get('@password'), camera_config.get('@remote_camera_id'), on_response, - media_type='movie') + media_type='movie', + prefix=self.get_argument('prefix', None), + stat=self.get_argument('stat', None)) else: - movies = mediafiles.list_media(camera_config, media_type='movie') + movies = mediafiles.list_media(camera_config, media_type='movie', + prefix=self.get_argument('prefix', None), + stat=self.get_argument('stat', None)) self.finish_json({ 'mediaList': movies, diff --git a/src/mediafiles.py b/src/mediafiles.py index c262f68..b8d0dfb 100644 --- a/src/mediafiles.py +++ b/src/mediafiles.py @@ -24,16 +24,22 @@ import subprocess from PIL import Image import config +import utils _PICTURE_EXTS = ['.jpg'] _MOVIE_EXTS = ['.avi', '.mp4'] -def _list_media_files(dir, exts): +def _list_media_files(dir, exts, prefix=None): full_paths = [] - for root, dirs, files in os.walk(dir): # @UnusedVariable - for name in files: + + if prefix is not None: + if prefix == 'ungrouped': + prefix = '' + + root = os.path.join(dir, prefix) + for name in os.listdir(root): full_path = os.path.join(root, name) if not os.path.isfile(full_path): continue @@ -44,6 +50,19 @@ def _list_media_files(dir, exts): full_paths.append(full_path) + else: + for root, dirs, files in os.walk(dir): # @UnusedVariable + for name in files: + full_path = os.path.join(root, name) + if not os.path.isfile(full_path): + continue + + full_path_lower = full_path.lower() + if not [e for e in exts if full_path_lower.endswith(e)]: + continue + + full_paths.append(full_path) + return full_paths @@ -134,7 +153,7 @@ def make_next_movie_preview(): logging.debug('all movies have preview') -def list_media(camera_config, media_type): +def list_media(camera_config, media_type, prefix=None, stat=False): target_dir = camera_config.get('target_dir') if media_type == 'picture': @@ -143,31 +162,35 @@ def list_media(camera_config, media_type): elif media_type == 'movie': exts = _MOVIE_EXTS - full_paths = _list_media_files(target_dir, exts=exts) + full_paths = _list_media_files(target_dir, exts=exts, prefix=prefix) media_files = [] for p in full_paths: path = p[len(target_dir):] if not path.startswith('/'): path = '/' + path - -# try: -# stat = os.stat(p) -# -# except Exception as e: -# logging.error('stat call failed for file %(path)s: %(msg)s' % { -# 'path': path, 'msg': unicode(e)}) -# -# continue -# -# timestamp = stat.st_mtime -# size = stat.st_size + + timestamp = None + size = None + + if stat: + try: + stat = os.stat(p) + + except Exception as e: + logging.error('stat call failed for file %(path)s: %(msg)s' % { + 'path': path, 'msg': unicode(e)}) + + continue + + timestamp = stat.st_mtime + size = stat.st_size media_files.append({ 'path': path, - #'momentStr': utils.pretty_date_time(datetime.datetime.fromtimestamp(timestamp)), - #'sizeStr': utils.pretty_size(size), - #'timestamp': timestamp + 'momentStr': timestamp and utils.pretty_date_time(datetime.datetime.fromtimestamp(timestamp)), + 'sizeStr': size and utils.pretty_size(size), + 'timestamp': timestamp }) # TODO files listed here may not belong to the given camera diff --git a/src/remote.py b/src/remote.py index 6a976a0..ec0dde4 100644 --- a/src/remote.py +++ b/src/remote.py @@ -208,14 +208,21 @@ def current_picture(host, port, username, password, camera_id, callback): http_client.fetch(request, on_response) -def list_media(host, port, username, password, camera_id, callback, media_type): +def list_media(host, port, username, password, camera_id, callback, media_type, prefix=None, stat=False): logging.debug('getting media list for remote camera %(id)s on %(host)s:%(port)s' % { 'id': camera_id, 'host': host, 'port': port}) + query = {} + if prefix is not None: + query['prefix'] = prefix + + if stat: + query['stat'] = 'true' + request = _make_request(host, port, username, password, '/%(media_type)s/%(id)s/list/' % { - 'id': camera_id, 'media_type': media_type}) + 'id': camera_id, 'media_type': media_type}, query=query) def on_response(response): if response.error: @@ -251,7 +258,7 @@ def get_media_content(host, port, username, password, camera_id, callback, filen 'host': host, 'port': port}) - uri = '/%(media_type)s/%(id)s/download/%(filename)s?' % { + uri = '/%(media_type)s/%(id)s/download/%(filename)s' % { 'media_type': media_type, 'id': camera_id, 'filename': filename} @@ -282,18 +289,20 @@ def get_media_preview(host, port, username, password, camera_id, callback, filen 'host': host, 'port': port}) - uri = '/%(media_type)s/%(id)s/preview/%(filename)s?' % { + uri = '/%(media_type)s/%(id)s/preview/%(filename)s' % { 'media_type': media_type, 'id': camera_id, 'filename': filename} + query = {} + if width: - uri += 'width=' + str(width) + query['width'] = str(width) if height: - uri += 'height=' + str(height) + query['height'] = str(height) - request = _make_request(host, port, username, password, uri) + request = _make_request(host, port, username, password, uri, query=query) def on_response(response): if response.error: diff --git a/static/css/main.css b/static/css/main.css index e97d29f..9b9000d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -415,6 +415,13 @@ div.media-list-group-title { padding: 1em 0px 0.2em 0px; } +img.media-list-progress { + position: relative; + top: 35%; + display: block; + margin: auto; +} + div.media-list-entry { height: 4em; background-color: #414141; diff --git a/static/js/main.js b/static/js/main.js index b0c68d5..b9b5e4d 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -1361,10 +1361,7 @@ function runMediaDialog(cameraId, mediaType) { 'path': path, 'group': key, 'name': parts[parts.length - 1], - 'cameraId': cameraId, - 'momentStr': media.momentStr, - 'sizeStr': media.sizeStr, - 'timestamp': media.timestamp + 'cameraId': cameraId }); }); @@ -1395,15 +1392,6 @@ function runMediaDialog(cameraId, mediaType) { var nameDiv = $('
' + entry.name + '
'); entryDiv.append(nameDiv); - if (entry.momentStr && entry.sizeStr) { - var detailsDiv = $('
'); - detailsDiv.html(entry.momentStr + ' | ' + entry.sizeStr); - entryDiv.append(detailsDiv); - } - else { - nameDiv.css('line-height', '2.3em'); - } - downloadButton[0]._onClick = function () { window.location.href = '/picture/' + cameraId + '/download' + entry.path; @@ -1419,36 +1407,82 @@ function runMediaDialog(cameraId, mediaType) { }); function showGroup(key) { - groupsDiv.find('div.media-dialog-group-button').each(function () { - var $this = $(this); - if (this.key == key) { - $this.addClass('current'); - } - else { - $this.removeClass('current'); - } - }); + if (mediaListDiv.find('img.media-list-progress').length) { + return; /* already in progress of loading */ + } - mediaListDiv.html(''); - - var entries = groups[key]; - entries.forEach(function (entry) { - mediaListDiv.append(entry.div); - entry.div.click(entry.div[0]._onClick); - - var downloadButton = entry.div.find('div.media-list-download-button'); - downloadButton.click(downloadButton[0]._onClick); - }); + var previewImg = $(''); + mediaListDiv.append(previewImg); - setTimeout(function () { - mediaListDiv.find('img.media-list-preview').each(function () { - if (this._src) { - this.src = this._src; + var url = '/' + mediaType + '/' + cameraId + '/list/?prefix=' + (key || 'ungrouped')+ '&stat=true'; + ajax('GET', url, null, function (data) { + if (data == null || data.error) { + hideModalDialog(); + showErrorMessage(data && data.error); + return; + } + + var entries = groups[key]; + + /* index the media list by name */ + var mediaListByName = {}; + data.mediaList.forEach(function (media) { + var path = media.path; + var parts = path.split('/'); + var name = parts[parts.length - 1]; + + mediaListByName[name] = media; + }); + + /* (re)set the current state of the group buttons */ + groupsDiv.find('div.media-dialog-group-button').each(function () { + var $this = $(this); + if (this.key == key) { + $this.addClass('current'); + } + else { + $this.removeClass('current'); } + }); + + /* add the entries to the media list */ + mediaListDiv.children('div.media-list-entry').detach(); + mediaListDiv.html(''); + entries.forEach(function (entry) { + var entryDiv = entry.div; + var nameDiv = entryDiv.find('div.media-list-entry-name'); + var detailsDiv = entryDiv.find('div.media-list-entry-details'); + var downloadButton = entryDiv.find('div.media-list-download-button'); - delete this._src; + var media = mediaListByName[entry.name]; + if (media) { /* if details are available, show them */ + if (detailsDiv.length === 0) { + detailsDiv = $('
'); + entryDiv.append(detailsDiv); + } + + detailsDiv.html(media.momentStr + ' | ' + media.sizeStr); + } + else { + nameDiv.css('line-height', '2.3em'); + } + + entryDiv.click(entryDiv[0]._onClick); + downloadButton.click(downloadButton[0]._onClick); + + mediaListDiv.append(entryDiv); }); - }, 1000); + + setTimeout(function () { + mediaListDiv.find('img.media-list-preview').each(function () { + if (this._src) { + this.src = this._src; + } + + delete this._src; + }); + }, 1000); + }); } if (keys.length) { diff --git a/static/js/ui.js b/static/js/ui.js index 2b188b4..68b6409 100644 --- a/static/js/ui.js +++ b/static/js/ui.js @@ -548,6 +548,7 @@ function showModalDialog(content, onClose, onShow, stack) { }); children.css('display', 'none'); + updateModalDialogPosition(); container[0]._onClose = onClose; /* set the new onClose handler */ container.append(content);