set_default_setting('ENABLE_REBOOT', False)
set_default_setting('SMTP_TIMEOUT', 60)
set_default_setting('NOTIFY_MEDIA_TIMESPAN', 5)
+ set_default_setting('ZIP_TIMEOUT', 500)
+ set_default_setting('TIMELAPSE_TIMEOUT', 500)
length = len(sys.argv) - 1
for i in xrange(length):
# the interval in seconds to consider around the moment of the event when attaching media files to notifications
NOTIFY_MEDIA_TIMESPAN = 5
+
+# the time to wait for zip file creation
+ZIP_TIMEOUT = 500
+
+# the time to wait for timelapse movie file creation
+TIMELAPSE_TIMEOUT = 500
import json
import logging
import os
+import re
import socket
from tornado.web import RequestHandler, HTTPError, asynchronous
class PictureHandler(BaseHandler):
@asynchronous
- def get(self, camera_id, op, filename=None):
+ def get(self, camera_id, op, filename=None, group=None):
if camera_id is not None:
camera_id = int(camera_id)
if camera_id not in config.get_camera_ids():
elif op == 'preview':
self.preview(camera_id, filename)
+ elif op == 'zipped':
+ self.zipped(camera_id, group)
+
+ elif op == 'timelapse':
+ self.timelapse(camera_id, group)
+
else:
raise HTTPError(400, 'unknown operation')
width=self.get_argument('width', None),
height=self.get_argument('height', None))
+ @BaseHandler.auth()
+ def zipped(self, camera_id, group):
+ if camera_id not in config.get_camera_ids():
+ raise HTTPError(404, 'no such camera')
+
+ key = self.get_argument('key', None)
+ if key:
+ logging.debug('serving zip file for group %(group)s of camera %(id)s with key %(key)s' % {
+ 'group': group, 'id': camera_id, 'key': key})
+
+ data = mediafiles.get_prepared_cache(key)
+ if not data:
+ logging.error('prepared cache data for key "%s" does not exist' % key)
+
+ raise HTTPError(404, 'no such key')
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_camera(camera_config):
+ pretty_filename = camera_config['@name'] + '_' + group
+ pretty_filename = re.sub('[^a-zA-Z0-9]', '_', pretty_filename)
+
+ else: # remote camera
+ pretty_filename = re.sub('[^a-zA-Z0-9]', '_', group)
+
+ self.set_header('Content-Type', 'application/zip')
+ self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + '.zip;')
+ self.finish(data)
+
+ else:
+ logging.debug('preparing zip file for group %(group)s of camera %(id)s' % {
+ 'group': group, 'id': camera_id})
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_camera(camera_config):
+ def on_zip(data):
+ if data is None:
+ return self.finish_json({'error': 'Failed to create zip file.'})
+
+ key = mediafiles.set_prepared_cache(data)
+ logging.debug('prepared zip file for group %(group)s of camera %(id)s with key %(key)s' % {
+ 'group': group, 'id': camera_id, 'key': key})
+ self.finish_json({'key': key})
+
+ mediafiles.get_zipped_content(camera_config, media_type='picture', callback=on_zip, prefix=group)
+
+ else: # remote camera
+ 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})
+
+ key = mediafiles.set_prepared_cache(response)
+ logging.debug('prepared zip file for group %(group)s of camera %(id)s with key %(key)s' % {
+ 'group': group, 'id': camera_id, 'key': key})
+ self.finish_json({'key': key})
+
+ remote.get_zipped_content(camera_config, media_type='picture', callback=on_response, group=group)
+
+ @BaseHandler.auth()
+ def timelapse(self, camera_id, group):
+ if camera_id not in config.get_camera_ids():
+ raise HTTPError(404, 'no such camera')
+
+ key = self.get_argument('key', None)
+ if key:
+ logging.debug('serving timelapse movie for group %(group)s of camera %(id)s with key %(key)s' % {
+ 'group': group, 'id': camera_id, 'key': key})
+
+ data = mediafiles.get_prepared_cache(key)
+ if not data:
+ logging.error('prepared cache data for key "%s" does not exist' % key)
+
+ raise HTTPError(404, 'no such key')
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_camera(camera_config):
+ pretty_filename = camera_config['@name'] + '_' + group
+ pretty_filename = re.sub('[^a-zA-Z0-9]', '_', pretty_filename)
+
+ else: # remote camera
+ pretty_filename = re.sub('[^a-zA-Z0-9]', '_', group)
+
+ self.set_header('Content-Type', 'video/x-msvideo')
+ self.set_header('Content-Disposition', 'attachment; filename=' + pretty_filename + '.avi;')
+ self.finish(data)
+
+ else:
+ interval = int(self.get_argument('interval'))
+
+ logging.debug('preparing timelapse movie for group %(group)s of camera %(id)s with interval %(int)s' % {
+ 'group': group, 'id': camera_id, 'int': interval})
+
+ camera_config = config.get_camera(camera_id)
+ if utils.local_camera(camera_config):
+ def on_timelapse(data):
+ if data is None:
+ return self.finish_json({'error': 'Failed to create timelapse movie file.'})
+
+ key = mediafiles.set_prepared_cache(data)
+ logging.debug('prepared timelapse movie for group %(group)s of camera %(id)s with key %(key)s' % {
+ 'group': group, 'id': camera_id, 'key': key})
+ self.finish_json({'key': key})
+
+ mediafiles.get_timelapse_movie(camera_config, interval, callback=on_timelapse, group=group)
+
+ else: # remote camera
+ 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})
+
+ key = mediafiles.set_prepared_cache(response)
+ logging.debug('prepared timelapse movie for group %(group)s of camera %(id)s with key %(key)s' % {
+ 'group': group, 'id': camera_id, 'key': key})
+ self.finish_json({'key': key})
+
+ remote.get_timelapse_movie(camera_config, interval, callback=on_response, group=group)
+
def try_finish(self, content):
try:
self.finish(content)
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
+import hashlib
import logging
import multiprocessing
import os.path
import stat
import StringIO
import subprocess
+import time
import tornado
+import zipfile
from PIL import Image
+from tornado import ioloop
import config
import settings
# a cache list of paths to movies without preview
_previewless_movie_files = []
+# a cache of prepared files (whose preparing time is significant)
+_prepared_files = {}
+
def _list_media_files(dir, exts, prefix=None):
media_files = []
return None
+def get_zipped_content(camera_config, media_type, callback, prefix):
+ target_dir = camera_config.get('target_dir')
+
+ if media_type == 'picture':
+ exts = _PICTURE_EXTS
+
+ elif media_type == 'movie':
+ exts = _MOVIE_EXTS
+
+ working = multiprocessing.Value('b')
+ working.value = True
+
+ # create a subprocess to add files to zip
+ def do_zip(pipe):
+ mf = _list_media_files(target_dir, exts=exts, prefix=prefix)
+ paths = []
+ for (p, st) in mf: # @UnusedVariable
+ path = p[len(target_dir):]
+ if path.startswith('/'):
+ path = path[1:]
+
+ paths.append(path)
+
+ zip_filename = os.path.join(settings.MEDIA_PATH, '.zip-%s' % int(time.time()))
+ logging.debug('adding %d files to zip file "%s"' % (len(paths), zip_filename))
+
+ try:
+ with zipfile.ZipFile(zip_filename, mode='w') as f:
+ for path in paths:
+ full_path = os.path.join(target_dir, path)
+ f.write(full_path, path)
+
+ except Exception as e:
+ logging.error('failed to create zip file "%s": %s' % (zip_filename, e))
+
+ working.value = False
+ pipe.close()
+ return
+
+ logging.debug('reading zip file "%s" into memory' % zip_filename)
+
+ try:
+ with open(zip_filename, mode='r') as f:
+ data = f.read()
+
+ working.value = False
+ pipe.send(data)
+ logging.debug('zip data ready')
+
+ except Exception as e:
+ logging.error('failed to read zip file "%s": %s' % (zip_filename, e))
+ working.value = False
+
+ finally:
+ os.remove(zip_filename)
+ pipe.close()
+
+ logging.debug('starting zip process...')
+
+ (parent_pipe, child_pipe) = multiprocessing.Pipe(duplex=False)
+ process = multiprocessing.Process(target=do_zip, args=(child_pipe, ))
+ process.start()
+
+ # poll the subprocess to see when it has finished
+ started = datetime.datetime.now()
+
+ def poll_process():
+ ioloop = tornado.ioloop.IOLoop.instance()
+ if working.value:
+ now = datetime.datetime.now()
+ delta = now - started
+ if delta.seconds < settings.ZIP_TIMEOUT:
+ ioloop.add_timeout(datetime.timedelta(seconds=0.5), poll_process)
+
+ else: # process did not finish within 2 minutes
+ logging.error('timeout waiting for the zip process to finish')
+
+ callback(None)
+
+ else: # finished
+ try:
+ data = parent_pipe.recv()
+ logging.debug('zip process has returned %d bytes' % len(data))
+
+ except:
+ data = None
+
+ callback(data)
+
+ poll_process()
+
+
+def get_timelapse_movie(camera_config, interval, callback, prefix):
+ target_dir = camera_config.get('target_dir')
+
+# working = multiprocessing.Value('b')
+# working.value = True
+
+# # create a subprocess to create the files
+# def do_zip(pipe):
+# mf = _list_media_files(target_dir, exts=exts, prefix=prefix)
+# paths = []
+# for (p, st) in mf: # @UnusedVariable
+# path = p[len(target_dir):]
+# if path.startswith('/'):
+# path = path[1:]
+#
+# paths.append(path)
+#
+# zip_filename = os.path.join(settings.MEDIA_PATH, '.zip-%s' % int(time.time()))
+# logging.debug('adding %d files to zip file "%s"' % (len(paths), zip_filename))
+#
+# try:
+# with zipfile.ZipFile(zip_filename, mode='w') as f:
+# for path in paths:
+# full_path = os.path.join(target_dir, path)
+# f.write(full_path, path)
+#
+# except Exception as e:
+# logging.error('failed to create zip file "%s": %s' % (zip_filename, e))
+#
+# working.value = False
+# pipe.close()
+# return
+#
+# logging.debug('reading zip file "%s" into memory' % zip_filename)
+#
+# try:
+# with open(zip_filename, mode='r') as f:
+# data = f.read()
+#
+# working.value = False
+# pipe.send(data)
+# logging.debug('zip data ready')
+#
+# except Exception as e:
+# logging.error('failed to read zip file "%s": %s' % (zip_filename, e))
+# working.value = False
+#
+# finally:
+# os.remove(zip_filename)
+# pipe.close()
+#
+# logging.debug('starting zip process...')
+#
+# (parent_pipe, child_pipe) = multiprocessing.Pipe(duplex=False)
+# process = multiprocessing.Process(target=do_zip, args=(child_pipe, ))
+# process.start()
+#
+# # poll the subprocess to see when it has finished
+# started = datetime.datetime.now()
+#
+# def poll_process():
+# ioloop = tornado.ioloop.IOLoop.instance()
+# if working.value:
+# now = datetime.datetime.now()
+# delta = now - started
+# if delta.seconds < settings.ZIP_TIMEOUT:
+# ioloop.add_timeout(datetime.timedelta(seconds=0.5), poll_process)
+#
+# else: # process did not finish within 2 minutes
+# logging.error('timeout waiting for the zip process to finish')
+#
+# callback(None)
+#
+# else: # finished
+# try:
+# data = parent_pipe.recv()
+# logging.debug('zip process has returned %d bytes' % len(data))
+#
+# except:
+# data = None
+#
+# callback(data)
+#
+# poll_process()
+
+
def get_media_preview(camera_config, path, media_type, width, height):
target_dir = camera_config.get('target_dir')
full_path = os.path.join(target_dir, path)
def set_picture_cache(camera_id, sequence, width, content):
- global _current_pictures_cache
-
if not content:
return
def get_picture_cache(camera_id, sequence, width):
- global _current_pictures_cache
-
cache = _current_pictures_cache.setdefault(camera_id, [])
now = datetime.datetime.utcnow()
return content
return None
+
+
+def get_prepared_cache(key):
+ return _prepared_files.get(key)
+
+
+def set_prepared_cache(data):
+ key = hashlib.sha1(str(time.time())).hexdigest()
+
+ if key in _prepared_files:
+ logging.warn('key "%s" already present in prepared cache' % key)
+
+ _prepared_files[key] = data
+
+ def clear():
+ if _prepared_files.pop(key, None) is not None:
+ logging.warn('key "%s" was still present in the prepared cache, removed' % key)
+
+ timeout = max(settings.ZIP_TIMEOUT, settings.TIMELAPSE_TIMEOUT)
+ ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=timeout), clear)
+
+ return key
http_client.fetch(request, on_response)
+def get_zipped_content(local_config, callback, group, media_type):
+ 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('downloading 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)})
+
+ prepare_uri = uri + '/%(media_type)s/%(id)s/zipped/%(group)s/' % {
+ 'media_type': media_type,
+ 'id': camera_id,
+ '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)
+
+ def on_prepare(response):
+ if response.error:
+ logging.error('failed to download 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': unicode(response.error)})
+
+ return callback(error=unicode(response.error))
+
+ 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),
+ 'msg': unicode(e)})
+
+ return callback(error=unicode(e))
+
+ download_uri = uri + '/%(media_type)s/%(id)s/zipped/%(group)s/?key=%(key)s' % {
+ 'media_type': media_type,
+ 'id': camera_id,
+ 'group': group,
+ 'key': key}
+
+ request = _make_request(host, port, username, password, download_uri, timeout=100 * settings.REMOTE_REQUEST_TIMEOUT)
+
+ def on_download(response):
+ if response.error:
+ logging.error('failed to download 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': unicode(response.error)})
+
+ return callback(error=unicode(response.error))
+
+ callback(response.body)
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, on_download)
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, on_prepare)
+
+
+def get_timelapse_movie(local_config, interval, callback, group):
+ 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('downloading timelapse movie for group %(group)s of remote camera %(id)s with interval %(int)s on %(url)s' % {
+ 'group': group,
+ 'id': camera_id,
+ 'int': interval,
+ 'url': make_camera_url(local_config)})
+
+ prepare_uri = uri + '/picture/%(id)s/timelapse/%(group)s/?interval=%(int)s' % {
+ 'id': camera_id,
+ 'int': interval,
+ '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)
+
+ def on_prepare(response):
+ if response.error:
+ logging.error('failed to download timelapse movie for group %(group)s of remote camera %(id)s with interval %(int)s on %(url)s: %(msg)s' % {
+ 'group': group,
+ 'id': camera_id,
+ 'url': make_camera_url(local_config),
+ 'int': interval,
+ 'msg': unicode(response.error)})
+
+ return callback(error=unicode(response.error))
+
+ 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),
+ 'msg': unicode(e)})
+
+ return callback(error=unicode(e))
+
+ download_uri = uri + '/picture/%(id)s/timelapse/%(group)s/?key=%(key)s' % {
+ 'id': camera_id,
+ 'group': group,
+ 'int': interval,
+ 'key': key}
+
+ request = _make_request(host, port, username, password, download_uri, timeout=100 * settings.REMOTE_REQUEST_TIMEOUT)
+
+ def on_download(response):
+ if response.error:
+ logging.error('failed to download timelapse movie for group %(group)s of remote camera %(id)s with interval %(int)s on %(url)s: %(msg)s' % {
+ 'group': group,
+ 'id': camera_id,
+ 'url': make_camera_url(local_config),
+ 'int': interval,
+ 'msg': unicode(response.error)})
+
+ return callback(error=unicode(response.error))
+
+ callback(response.body)
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, on_download)
+
+ http_client = AsyncHTTPClient()
+ http_client.fetch(request, on_prepare)
+
+
def get_media_preview(local_config, callback, filename, media_type, width, height):
host = local_config.get('@host', local_config.get('host'))
port = local_config.get('@port', local_config.get('port'))
(r'^/config/(?P<camera_id>\d+)/(?P<op>get|set|rem|set_preview)/?$', handlers.ConfigHandler),
(r'^/config/(?P<op>add|list|list_devices)/?$', handlers.ConfigHandler),
(r'^/picture/(?P<camera_id>\d+)/(?P<op>current|list|frame)/?$', handlers.PictureHandler),
- (r'^/picture/(?P<camera_id>\d+)/(?P<op>download|preview)/(?P<filename>.+)/?$', handlers.PictureHandler),
+ (r'^/picture/(?P<camera_id>\d+)/(?P<op>download|preview)/(?P<filename>.+?)/?$', handlers.PictureHandler),
+ (r'^/picture/(?P<camera_id>\d+)/(?P<op>zipped|timelapse)/(?P<group>.+?)/?$', handlers.PictureHandler),
(r'^/movie/(?P<camera_id>\d+)/(?P<op>list)/?$', handlers.MovieHandler),
- (r'^/movie/(?P<camera_id>\d+)/(?P<op>download|preview)/(?P<filename>.+)/?$', handlers.MovieHandler),
+ (r'^/movie/(?P<camera_id>\d+)/(?P<op>download|preview)/(?P<filename>.+?)/?$', handlers.MovieHandler),
(r'^/update/?$', handlers.UpdateHandler),
(r'^/power/(?P<op>shutdown)/?$', handlers.PowerHandler),
(r'^/version/?$', handlers.VersionHandler),
background-color: #317CAD;
}
+div.media-dialog-buttons {
+ margin: 0.5em 0px 0px 0px;
+ text-align: center;
+}
+
+div.media-dialog-button {
+ cursor: pointer;
+ display: inline-block;
+ height: 1.5em;
+ line-height: 1.5em;
+ text-align: center;
+ padding: 0px 0.5em;
+ margin: 0px 5px 0px 0px;
+ color: white;
+ background-color: #317CAD;
+ border-radius: 3px;
+ transition: all 0.1s linear;
+}
+
+div.media-dialog-button:HOVER {
+ background-color: #3498db;
+}
+
+div.media-dialog-button:ACTIVE {
+ background-color: #317CAD;
+}
+
div.picture-dialog-content {
position: relative;
text-align: center;
opacity: 0.7;
}
+table.timelapse-dialog select#intervalSelect {
+ width: 10em;
+}
+
/* camera frames */
}, 500);
}
-function downloadMediaFile(uri) {
+function downloadFile(uri) {
var url = window.location.href;
var parts = url.split('/');
url = parts.slice(0, 3).join('/') + uri;
});
}
+function doDownloadZipped(cameraId, groupKey) {
+ showModalDialog('<div class="modal-progress"></div>', null, null, true);
+ ajax('GET', '/picture/' + cameraId + '/zipped/' + groupKey + '/', null, function (data) {
+ hideModalDialog(); /* progress */
+ downloadFile('/picture/' + cameraId + '/zipped/' + groupKey + '/?key=' + data.key);
+ });
+}
+
/* fetch & push */
{caption: 'Close'},
{caption: 'Download', isDefault: true, click: function () {
var entry = entries[pos];
- downloadMediaFile('/' + mediaType + '/' + entry.cameraId + '/download' + entry.path);
+ downloadFile('/' + mediaType + '/' + entry.cameraId + '/download' + entry.path);
return false;
}}
});
}
+function runTimelapseDialog(cameraId, groupKey, group) {
+ var content =
+ $('<table class="timelapse-dialog">' +
+ '<tr>' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">Group</span></td>' +
+ '<td class="dialog-item-value">' + groupKey + '</td>' +
+ '</tr>' +
+ '<tr>' +
+ '<td class="dialog-item-label"><span class="dialog-item-label">Include a picture every</span></td>' +
+ '<td class="dialog-item-value">' +
+ '<select class="styled timelapse" id="intervalSelect">' +
+ '<option value="1">second</option>' +
+ '<option value="10">5 seconds</option>' +
+ '<option value="10">10 seconds</option>' +
+ '<option value="30">30 seconds</option>' +
+ '<option value="60">minute</option>' +
+ '<option value="300">5 minutes</option>' +
+ '<option value="600">10 minutes</option>' +
+ '<option value="1800">30 minutes</option>' +
+ '<option value="3600">hour</option>' +
+ '</select>' +
+ '</td>' +
+ '<td><span class="help-mark" title="select the interval of time between each two successive pictures included in the movie">?</span></td>' +
+ '</tr>' +
+ '</table>');
+
+ var intervalSelect = content.find('#intervalSelect');
+
+ runModalDialog({
+ title: 'Create Timelapse Movie',
+ closeButton: true,
+ buttons: 'okcancel',
+ content: content,
+ onOk: function () {
+ showModalDialog('<div class="modal-progress"></div>', null, null, true);
+ ajax('GET', '/picture/' + cameraId + '/timelapse/' + groupKey + '/', {interval: intervalSelect.val()}, function (data) {
+ hideModalDialog(); /* progress */
+ hideModalDialog(); /* timelapse dialog */
+ downloadFile('/picture/' + cameraId + '/timelapse/' + groupKey + '/key=' + data.key);
+ });
+
+ return false;
+ },
+ stack: true
+ });
+}
function runMediaDialog(cameraId, mediaType) {
var dialogDiv = $('<div class="media-dialog"></div>');
var mediaListDiv = $('<div class="media-dialog-list"></div>');
var groupsDiv = $('<div class="media-dialog-groups"></div>');
+ var groups = {};
+ var groupKey = null;
+
dialogDiv.append(groupsDiv);
dialogDiv.append(mediaListDiv);
+ if (mediaType == 'picture') {
+ var buttonsDiv = $('<div class="media-dialog-buttons"></div>');
+ dialogDiv.append(buttonsDiv);
+
+ var zippedButton = $('<div class="media-dialog-button">Zipped Pictures</div>');
+ buttonsDiv.append(zippedButton);
+
+ zippedButton.click(function () {
+ if (groupKey) {
+ doDownloadZipped(cameraId, groupKey);
+ }
+ });
+
+ var timelapseButton = $('<div class="media-dialog-button">Timelapse Movie</div>');
+ buttonsDiv.append(timelapseButton);
+
+ timelapseButton.click(function () {
+ if (groupKey) {
+ runTimelapseDialog(cameraId, groupKey, groups[groupKey]);
+ }
+ });
+ }
+
function setDialogSize() {
var windowWidth = $(window).width();
var windowHeight = $(window).height();
}
/* group the media */
- var groups = {};
data.mediaList.forEach(function (media) {
var path = media.path;
var parts = path.split('/');
tempDiv.remove();
function showGroup(key) {
+ groupKey = key;
+
if (mediaListDiv.find('img.media-list-progress').length) {
return; /* already in progress of loading */
}
entryDiv.append(previewImg);
previewImg[0]._src = '/' + mediaType + '/' + cameraId + '/preview' + entry.path + '?height=' + height;
- var downloadButton = $('<div class="media-list-download-button button">download</div>');
+ var downloadButton = $('<div class="media-list-download-button button">Download</div>');
entryDiv.append(downloadButton);
var nameDiv = $('<div class="media-list-entry-name">' + entry.name + '</div>');
entryDiv.append(detailsDiv);
downloadButton.click(function () {
- downloadMediaFile('/picture/' + cameraId + '/download' + entry.path);
+ downloadFile('/picture/' + cameraId + '/download' + entry.path);
return false;
});