]> www.vanbest.org Git - motioneye-debian/commitdiff
media files details are now fetched upon display, asynchronously
authorCalin Crisan <ccrisan@gmail.com>
Sun, 17 Nov 2013 10:51:49 +0000 (12:51 +0200)
committerCalin Crisan <ccrisan@gmail.com>
Sun, 17 Nov 2013 11:13:32 +0000 (13:13 +0200)
README.md
doc/todo.txt
src/config.py
src/handlers.py
src/mediafiles.py
src/remote.py
static/css/main.css
static/js/main.js
static/js/ui.js

index 597e4218a47fc32b0440dcc12cf8127634b21050..bb7717cf3d312381883b9afa54e5626b155ffd55 100644 (file)
--- 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 ##
 
index d81fb15c502c72f2480047ce3cf576ca323ea291..6032486f09f770a462fdaf8f3657f206759ad9ca 100644 (file)
@@ -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
 
index d56c5160f5bb36c1c5ef59990a16600eaea6b28a..246afdba6003476483807910b78eb65a8f6b478c 100644 (file)
@@ -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)
index d8fd132d1292a6b42f9bec57c2bd226bdf9a197c..e41be6a31b932a171b7133f561427f9946adc7e7 100644 (file)
@@ -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,
index c262f68e8223c8e3906fa522bb86969ba5da8bd5..b8d0dfbf1edc2f070858e50cf8de23289b1ce523 100644 (file)
@@ -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
index 6a976a0e4aa730fcccfedcc76df9a72356f45ca4..ec0dde469a4b04ef7544e28d61f865ec665dac0a 100644 (file)
@@ -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:
index e97d29fdb75af1b7d65c4f2893b165ef648d529e..9b9000d5c0f0237619bc1106c99c504f584f8e40 100644 (file)
@@ -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;
index b0c68d5373c6302aa7ce46a629e95c58ef6411bf..b9b5e4d1d0879f4da84514f2c2b33caacfbb3c60 100644 (file)
@@ -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 = $('<div class="media-list-entry-name">' + entry.name + '</div>');
                 entryDiv.append(nameDiv);
                 
-                if (entry.momentStr && entry.sizeStr) {
-                    var detailsDiv = $('<div class="media-list-entry-details"></div>');
-                    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 = $('<img class="media-list-progress" src="' + staticUrl + 'img/modal-progress.gif"/>');
+            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 = $('<div class="media-list-entry-details"></div>');
+                            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) {
index 2b188b41e630e07db8a21b8f8ba4c8091d69beda..68b64097695432c26eeb65177f736257e49c84c2 100644 (file)
@@ -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);