diff --git a/pandora/clip/models.py b/pandora/clip/models.py index 456b5c8de..c37407e40 100644 --- a/pandora/clip/models.py +++ b/pandora/clip/models.py @@ -78,7 +78,7 @@ class MetaClip: for a in annotations] for key in keys: if key not in self.clip_keys and key not in j: - value = self.item.get(key) + value = self.item.get(key) or self.item.json.get(key) if not value and hasattr(self.item.sort, key): value = getattr(self.item.sort, key) j[key] = value diff --git a/pandora/config.0xdb.jsonc b/pandora/config.0xdb.jsonc index 8b39c6a9f..d75b2000c 100644 --- a/pandora/config.0xdb.jsonc +++ b/pandora/config.0xdb.jsonc @@ -678,7 +678,7 @@ //{"id": "maps", "title": "with Maps"}, //{"id": "calendars", "title": "with Calendars"}, {"id": "clip", "title": "as Clips"}, - //{"id": "video", "title": "as Video"}, + {"id": "video", "title": "as Video"}, {"id": "map", "title": "on Map"}, {"id": "calendar", "title": "on Calendar"} ], diff --git a/pandora/config.indiancinema.jsonc b/pandora/config.indiancinema.jsonc index 519993dfb..0026480a2 100644 --- a/pandora/config.indiancinema.jsonc +++ b/pandora/config.indiancinema.jsonc @@ -691,6 +691,7 @@ {"id": "timelines", "title": "with Timelines"}, {"id": "clips", "title": "with Clips"}, {"id": "clip", "title": "as Clips"}, + {"id": "video", "title": "as Videos"}, {"id": "map", "title": "on Map"}, {"id": "calendar", "title": "on Calendar"} ], diff --git a/pandora/config.padma.jsonc b/pandora/config.padma.jsonc index 1a29fb0c6..207fafcbd 100644 --- a/pandora/config.padma.jsonc +++ b/pandora/config.padma.jsonc @@ -575,6 +575,7 @@ {"id": "timelines", "title": "with Timelines"}, {"id": "clips", "title": "with Clips"}, {"id": "clip", "title": "as Clips"}, + {"id": "video", "title": "as Video"}, {"id": "map", "title": "on Map"}, {"id": "calendar", "title": "on Calendar"} ], diff --git a/pandora/config.pandora.jsonc b/pandora/config.pandora.jsonc index efdc4ac8f..ab8f275ab 100644 --- a/pandora/config.pandora.jsonc +++ b/pandora/config.pandora.jsonc @@ -494,6 +494,7 @@ {"id": "timelines", "title": "with Timelines"}, {"id": "clips", "title": "with Clips"}, {"id": "clip", "title": "as Clips"}, + {"id": "video", "title": "as Video"}, {"id": "map", "title": "on Map"}, {"id": "calendar", "title": "on Calendar"} ], diff --git a/pandora/edit/models.py b/pandora/edit/models.py index fe91f263c..bc8ed80f2 100644 --- a/pandora/edit/models.py +++ b/pandora/edit/models.py @@ -297,10 +297,14 @@ class Clip(models.Model): data['annotation'] = self.annotation.public_id data['in'] = self.annotation.start data['out'] = self.annotation.end + data['parts'] = self.annotation.item.json['parts'] + data['durations'] = self.annotation.item.json['durations'] else: data['item'] = self.item.itemId data['in'] = self.start data['out'] = self.end + data['parts'] = self.item.json['parts'] + data['durations'] = self.item.json['durations'] data['duration'] = data['out'] - data['in'] return data diff --git a/static/js/pandora/browser.js b/static/js/pandora/browser.js index a9a3b25f6..ffba9a318 100644 --- a/static/js/pandora/browser.js +++ b/static/js/pandora/browser.js @@ -31,6 +31,8 @@ pandora.ui.browser = function() { pandora.$ui.map.resizeMap(); } else if (pandora.user.ui.listView == 'calendar') { pandora.$ui.calendar.resizeCalendar(); + } else if (pandora.user.ui.listView == 'video') { + pandora.$ui.list.size(); } }, resizeend: function(data) { @@ -59,6 +61,8 @@ pandora.ui.browser = function() { pandora.$ui.map.resizeMap(); } else if (pandora.user.ui.listView == 'calendar') { pandora.$ui.calendar.resizeCalendar(); + } else if (pandora.user.ui.listView == 'video') { + pandora.$ui.list.size(); } } }); diff --git a/static/js/pandora/clipList.js b/static/js/pandora/clipList.js index 0543592d9..da2bf496d 100644 --- a/static/js/pandora/clipList.js +++ b/static/js/pandora/clipList.js @@ -175,25 +175,23 @@ pandora.ui.clipList = function(videoRatio) { } pandora.api.get({id: item, keys: ['durations', 'rightslevel']}, function(result) { var points = [that.value(id, 'in'), that.value(id, 'out')], - partsAndPoints = pandora.getVideoPartsAndPoints( - result.data.durations, points - ), $player = Ox.VideoPlayer({ censored: pandora.site.capabilities.canPlayClips[pandora.user.level] < result.data.rightslevel - ? [{'in': partsAndPoints.points[0], out: partsAndPoints.points[1]}] + ? [{'in': 0, out: points[1] - points[0]}] : [], censoredIcon: pandora.site.cantPlay.icon, censoredTooltip: pandora.site.cantPlay.text, height: height, - 'in': partsAndPoints.points[0], - out: partsAndPoints.points[1], paused: true, - playInToOut: true, poster: '/' + item + '/' + height + 'p' + points[0] + '.jpg', rewind: true, - video: partsAndPoints.parts.map(function(i) { - return pandora.getVideoURL(item, Ox.min(pandora.site.video.resolutions), i + 1); - }), + video: pandora.getClipVideos({ + item: item, + parts: result.data.durations.length, + durations: result.data.durations, + 'in': points[0], + out: points[1] + }, Ox.min(pandora.site.video.resolutions)), width: width }) .addClass('OxTarget') diff --git a/static/js/pandora/clipPlayer.js b/static/js/pandora/clipPlayer.js deleted file mode 100644 index 72390e0ff..000000000 --- a/static/js/pandora/clipPlayer.js +++ /dev/null @@ -1,61 +0,0 @@ -// vim: et:ts=4:sw=4:sts=4:ft=javascript -'use strict'; -pandora.ui.clipPlayer = function() { - // FIXME: is clipPlayer the best name for this? - var that = Ox.VideoPlayer({ - controlsBottom: ['play', 'previous', 'next', 'volume'], - controlsTop: ['fullscreen', 'scale'], - enableMouse: true, - height: 384, - paused: true, - position: 0, - video: function(range, callback) { - var callback = arguments[arguments.length - 1], - range = arguments.length == 2 ? arguments[0] : null, - itemsQuery = pandora.user.ui.find, - query = {conditions:[]}; - //fixme: can this be in pandora.Query? dont just check for subtitles - itemsQuery.conditions.forEach(function(q) { - if (q.key == 'subtitles') { - query.conditions.push({key: 'subtitles', value: q.value, operator: q.operator}); - } - }); - pandora.api.findClips(Ox.extend({ - query: query, - itemsQuery: itemsQuery - }, range ? { - keys: ['id', 'in', 'out', 'subtitles'], - range: range, - sort: pandora.user.ui.listSort - } : {}), function(result) { - //Ox.Log('', 'API findClips range', range, 'result', result.data); - if (!range) { - callback(result.data.items); - } else { - var counter = 0, - length = range[1] - range[0], - data = []; - result.data.items.forEach(function(item, i) { - var id = item.id.split('/')[0]; - pandora.api.get({id: id, keys: ['durations']}, function(result) { - //Ox.Log('', 'API get item', id, 'result', result.data); - var points = [item['in'], item.out], - partsAndPoints = pandora.getVideoPartsAndPoints(result.data.durations, points); - data[i] = { - parts: partsAndPoints.parts.map(function(i) { - return pandora.getVideoURL(item, Ox.min(pandora.site.video.resolutions), i + 1); - }), - points: partsAndPoints.points - }; - if (++counter == length) { - callback(data); - } - }); - }); - } - }); - }, - width: 512 - }); - return that; -}; diff --git a/static/js/pandora/editPanel.js b/static/js/pandora/editPanel.js index a4a03bbc3..1054b60eb 100644 --- a/static/js/pandora/editPanel.js +++ b/static/js/pandora/editPanel.js @@ -22,6 +22,25 @@ pandora.ui.editPanel = function() { $editMenu, + $viewSelect = Ox.Select({ + items: [ + {'id': 'list', 'title': Ox._('View as List')}, + {'id': 'player', 'title': Ox._('View as Player')}, + ], + value: 'list', + width: 128 + }) + .css({ + float: 'left', + margin: '4px 0 0 4px' + }) + .bindEvent({ + change: function(data) { + $panel.replaceElement(0, pandora.$ui.edit = pandora.ui[ + data.value == 'player' ? 'editPlayer' : 'editList' + ](edit)); + }, + }).appendTo($toolbar), $statusbar = Ox.Bar({size: 16}), @@ -145,7 +164,7 @@ pandora.ui.editList = function(edit) { edit: pandora.user.ui.edit }, function(result) { Ox.Request.clearCache(); - pandora.$ui.rightPanel.reload() + pandora.$ui.rightPanel.reload(); }); } }, @@ -198,3 +217,35 @@ pandora.ui.editList = function(edit) { return that; }; +pandora.ui.editPlayer = function(edit) { + var that = Ox.Element() + .css({ + 'overflow-y': 'auto' + }); + + + self.$player = Ox.VideoPlayer({ + controlsBottom: ['play', 'previous', 'next', 'volume', 'position'], + controlsTop: ['fullscreen', 'scale'], + enableMouse: true, + height: getHeight(), + paused: true, + position: 0, + video: Ox.flatten(edit.clips.map(function(clip) { + return pandora.getClipVideos(clip); + })), + width: getWidth() + }).appendTo(that); + + function getHeight() { + // 24 menu + 24 toolbar + 16 statusbar + 32 title + 32 margins + // + 1px to ge trid of scrollbar + return window.innerHeight - 128 -1; + } + + function getWidth() { + return window.innerWidth + - pandora.user.ui.showSidebar * pandora.user.ui.sidebarSize - 1; + } + return that; +}; diff --git a/static/js/pandora/embedPlayer.js b/static/js/pandora/embedPlayer.js index 88feafd2e..53fc6227b 100644 --- a/static/js/pandora/embedPlayer.js +++ b/static/js/pandora/embedPlayer.js @@ -27,7 +27,7 @@ pandora.ui.embedPlayer = function() { $title, $player, $controls, $timeline, $annotations; pandora.api.get({id: ui.item, keys: [ - 'duration', 'layers', 'parts', 'posterFrame', + 'duration', 'durations', 'layers', 'parts', 'posterFrame', 'rightslevel', 'size', 'title', 'videoRatio' ]}, function(result) { diff --git a/static/js/pandora/item.js b/static/js/pandora/item.js index 2883f8a4d..ad710de6a 100644 --- a/static/js/pandora/item.js +++ b/static/js/pandora/item.js @@ -13,11 +13,11 @@ pandora.ui.item = function() { pandora.api.get({ id: pandora.user.ui.item, keys: isVideoView ? [ - 'cuts', 'director', 'duration', 'editable', 'layers', + 'cuts', 'director', 'duration', 'durations', 'editable', 'layers', 'modified', 'parts', 'posterFrame', 'rendered', 'rightslevel', 'size', 'title', 'videoRatio', 'year' ] : pandora.user.ui.itemView == 'documents' ? [ - 'director', 'documents', 'duration', 'editable', + 'director', 'documents', 'duration', 'durations', 'editable', 'rightslevel', 'size', 'title', 'videoRatio', 'year' ] : [] }, pandora.user.ui.itemView == 'info' && pandora.site.capabilities.canEditMetadata[pandora.user.level] ? 0 : -1, function(result) { diff --git a/static/js/pandora/list.js b/static/js/pandora/list.js index 5418a2030..97ad50d97 100644 --- a/static/js/pandora/list.js +++ b/static/js/pandora/list.js @@ -377,7 +377,7 @@ pandora.ui.list = function() { } else if (view == 'clip') { that = pandora.$ui.clipList = pandora.ui.clipList(); } else if (view == 'video') { - that = pandora.ui.clipPlayer(); + that = pandora.ui.videoView(); } else if (['map', 'calendar'].indexOf(view) > -1) { that = pandora.ui.navigationView(view); } diff --git a/static/js/pandora/rightPanel.js b/static/js/pandora/rightPanel.js index e7511a1e0..a11448294 100644 --- a/static/js/pandora/rightPanel.js +++ b/static/js/pandora/rightPanel.js @@ -38,6 +38,8 @@ pandora.ui.rightPanel = function() { pandora.$ui.map.resizeMap(); } else if (pandora.user.ui.listView == 'calendar') { pandora.$ui.calendar.resizeCalendar(); + } else if (pandora.user.ui.listView == 'video') { + pandora.$ui.list.resize(); } } else { pandora.$ui.browser.scrollToSelection(); @@ -53,6 +55,8 @@ pandora.ui.rightPanel = function() { pandora.$ui.map.resizeMap(); } else if (pandora.user.ui.listView == 'calendar') { pandora.$ui.calendar.resizeCalendar(); + } else if (pandora.user.ui.listView == 'video') { + pandora.$ui.list.size(); } } } diff --git a/static/js/pandora/utils.js b/static/js/pandora/utils.js index d06a411b8..dfd020032 100644 --- a/static/js/pandora/utils.js +++ b/static/js/pandora/utils.js @@ -584,6 +584,45 @@ pandora.getClipsQuery = function() { return clipsQuery; }; +pandora.getClipVideos = function(clip, resolution) { + var currentTime = 0, + start = clip['in'] || 0, + end = clip.out; + resolution = resolution || pandora.user.ui.videoResolution; + + return Ox.flatten(Ox.range(clip.parts).map(function(i) { + var item = { + src: pandora.getVideoURL(clip.item, resolution, i + 1) + }; + if(currentTime + clip.durations[i] < start || currentTime > end) { + item = null; + } else { + if(currentTime <= start && currentTime + clip.durations[i] > start) { + item['in'] = start - currentTime; + } + if (currentTime + clip.durations[i] >= end) { + item.out = end - currentTime; + } + if (item['in'] && item.out) { + item.duration = item.out - item['in'] + } else if (item.out) { + item.duration = item.out; + } else if (!Ox.isUndefined(item['in'])) { + item.duration = clip.durations[i] - item['in']; + item.out = clip.durations[i]; + } else { + item.duration = clip.durations[i]; + item['in'] = 0; + item.out = item.duration; + } + } + currentTime += clip.durations[i]; + return item; + }).filter(function(c) { + return !!c; + })); +}; + (function() { var itemTitles = {}; pandora.getDocumentTitle = function(itemTitle) { @@ -1065,7 +1104,7 @@ pandora.getStatusText = function(data) { var ui = pandora.user.ui, canSeeMedia = pandora.site.capabilities.canSeeMedia[pandora.user.level], canSeeSize = pandora.site.capabilities.canSeeSize[pandora.user.level], - itemName = ui.listView == 'clip' + itemName = ['clip', 'video'].indexOf(ui.listView) > -1 ? (data.items == 1 ? Ox._('Clip') : Ox._('Clips')) : (pandora.site.itemName[data.items == 1 ? 'singular' : 'plural']), parts = []; @@ -1139,7 +1178,10 @@ pandora.getVideoOptions = function(data) { options.video = {}; pandora.site.video.resolutions.forEach(function(resolution) { options.video[resolution] = Ox.range(data.parts).map(function(i) { - return pandora.getVideoURL(data.item || pandora.user.ui.item, resolution, i + 1); + return { + duration: data.durations[i], + src: pandora.getVideoURL(data.item || pandora.user.ui.item, resolution, i + 1) + }; }); }); options.annotations = []; diff --git a/static/js/pandora/videoView.js b/static/js/pandora/videoView.js new file mode 100644 index 000000000..465c005b4 --- /dev/null +++ b/static/js/pandora/videoView.js @@ -0,0 +1,93 @@ +// vim: et:ts=4:sw=4:sts=4:ft=javascript +'use strict'; + +pandora.ui.videoView = function() { + var ui = pandora.user.ui, + itemsQuery, + query, + that = Ox.Element().css({ + width: '100%', + hegiht: '100%', + }), + range = [0, 500], + clips = [], + player; + + if (!ui.item) { + itemsQuery = ui.find; + query = {conditions: [], operator: '&'}; + // if the item query contains a layer condition, + // then this condition is added to the clip query + itemsQuery.conditions.forEach(function(condition) { + if ( + condition.key == 'annotations' + || Ox.getIndexById(pandora.site.layers, condition.key) > -1 + ) { + query.conditions.push(condition); + } + }); + } else { + itemsQuery = { + conditions:[{key: 'id', value: ui.item, operator: '=='}], + operator: '&' + }; + query = { + conditions: ui.itemFind === '' ? [] : [{ + key: 'annotations', + value: ui.itemFind, + operator: '=' + }], + operator: '&' + }; + } + loadPlayer(); + function loadPlayer() { + pandora.api.findClips({ + query: query, + itemsQuery: itemsQuery, + keys: ['id', 'in', 'out', 'durations', 'parts'], + range: range, + sort: pandora.user.ui.listSort + }, function(result) { + pandora.$ui.statusbar.set('total', { + items: result.data.items.length + }); + player && player.remove(); + player = Ox.VideoPlayer({ + controlsBottom: ['play', 'previous', 'next', 'volume'], + controlsTop: ['fullscreen', 'scale'], + enableMouse: true, + height: getHeight(), + paused: true, + position: 0, + video: Ox.flatten(result.data.items.map(function(clip) { + clip.item = clip.id.split('/')[0]; + var r = pandora.getClipVideos(clip); + console.log(clip, r); + return r; + })), + width: getWidth() + }).appendTo(that); + }); + } + + function getHeight() { + return that.height(); + } + + function getWidth() { + return that.width(); + } + + that.reloadList = function() { + loadPlayer(); + }; + that.size = function() { + player && player.options({ + height: getHeight(), + width: getWidth(), + }) + } + + return that; +};