diff --git a/pandora/archive/extract.py b/pandora/archive/extract.py index 3d6c74fe..7b9f24ea 100644 --- a/pandora/archive/extract.py +++ b/pandora/archive/extract.py @@ -19,10 +19,13 @@ import ox.image from ox.utils import json from django.conf import settings from PIL import Image, ImageOps +import pillow_avif +from pillow_heif import register_heif_opener from .chop import Chop, make_keyframe_index +register_heif_opener() logger = logging.getLogger('pandora.' + __name__) img_extension = 'jpg' diff --git a/pandora/document/fulltext.py b/pandora/document/fulltext.py index c4c9aaff..5f2a3e50 100644 --- a/pandora/document/fulltext.py +++ b/pandora/document/fulltext.py @@ -8,6 +8,7 @@ from django.conf import settings logger = logging.getLogger('pandora.' + __name__) +IMAGE_EXTENSIONS = ('png', 'jpg', 'webp', 'heic', 'heif') def extract_text(pdf, page=None): if page is not None: @@ -53,7 +54,7 @@ class FulltextMixin: if self.file: if self.extension == 'pdf': return extract_text(self.file.path) - elif self.extension in ('png', 'jpg'): + elif self.extension in IMAGE_EXTENSIONS: return ocr_image(self.file.path) elif self.extension == 'html': return self.data.get('text', '') @@ -180,7 +181,7 @@ class FulltextPageMixin(FulltextMixin): if self.document.file: if self.document.extension == 'pdf': return extract_text(self.document.file.path, self.page) - elif self.extension in ('png', 'jpg'): + elif self.extension in IMAGE_EXTENSIONS: return ocr_image(self.document.file.path) elif self.extension == 'html': # FIXME: is there a nice way to split that into pages diff --git a/pandora/document/models.py b/pandora/document/models.py index 68819f39..3b3a0262 100644 --- a/pandora/document/models.py +++ b/pandora/document/models.py @@ -521,14 +521,17 @@ class Document(models.Model, FulltextMixin): return save_chunk(self, self.file, chunk, offset, name, done_cb) return False, 0 - def thumbnail(self, size=None, page=None): + def thumbnail(self, size=None, page=None, accept=None): if not self.file: return os.path.join(settings.STATIC_ROOT, 'png/document.png') src = self.file.path folder = os.path.dirname(src) if size: size = int(size) - path = os.path.join(folder, '%d.jpg' % size) + ext = 'jpg' + if accept and 'image/avif' in accept and size > 512: + ext = 'avif' + path = os.path.join(folder, '%d.%s' % (size, ext)) else: path = src if self.extension == 'pdf': @@ -561,7 +564,7 @@ class Document(models.Model, FulltextMixin): path = os.path.join(folder, '%dp%d,%s.jpg' % (size, page, ','.join(map(str, crop)))) if not os.path.exists(path): resize_image(src, path, size=size) - elif self.extension in ('jpg', 'png', 'gif'): + elif self.extension in ('jpg', 'png', 'gif', 'webp', 'heic', 'heif'): if os.path.exists(src): if size and page: crop = list(map(int, page.split(','))) @@ -574,7 +577,7 @@ class Document(models.Model, FulltextMixin): img = open_image_rgb(path) src = path if size < max(img.size): - path = os.path.join(folder, '%sp%s.jpg' % (size, ','.join(map(str, crop)))) + path = os.path.join(folder, '%sp%s.%s' % (size, ','.join(map(str, crop)), ext)) if not os.path.exists(path): resize_image(src, path, size=size) if os.path.exists(src) and not os.path.exists(path): diff --git a/pandora/document/views.py b/pandora/document/views.py index c2d9f631..6657f92c 100644 --- a/pandora/document/views.py +++ b/pandora/document/views.py @@ -378,15 +378,22 @@ actions.register(sortDocuments, cache=False) def file(request, id, name=None): document = get_document_or_404_json(request, id) + accept = request.headers.get("Accept") + if accept and 'image/' in accept and document.extension in ( + 'webp', 'heif', 'heic', 'avif' + ) and document.extension not in accept: + image_size = max(document.width, document.height) + return HttpFileResponse(document.thumbnail(image_size)) return HttpFileResponse(document.file.path) def thumbnail(request, id, size=256, page=None): size = int(size) document = get_document_or_404_json(request, id) + accept = request.headers.get("Accept") if "q" in request.GET and page: img = document.highlight_page(page, request.GET["q"], size) return HttpResponse(img, content_type="image/jpeg") - return HttpFileResponse(document.thumbnail(size, page=page)) + return HttpFileResponse(document.thumbnail(size, page=page, accept=accept)) @login_required_json diff --git a/pandora/urls.py b/pandora/urls.py index 993dda65..f6b66ae2 100644 --- a/pandora/urls.py +++ b/pandora/urls.py @@ -53,7 +53,7 @@ urlpatterns += [ re_path(r'^resetUI$', user.views.reset_ui), re_path(r'^collection/(?P.*?)/icon(?P\d*).jpg$', documentcollection.views.icon), re_path(r'^documents/(?P[A-Z0-9]+)/(?P\d*)p(?P[\d,]*).jpg$', document.views.thumbnail), - re_path(r'^documents/(?P[A-Z0-9]+)/(?P.*?\.[^\d]{3})$', document.views.file), + re_path(r'^documents/(?P[A-Z0-9]+)/(?P.*?\.[^\d]{3,4})$', document.views.file), re_path(r'^documents/(?P.*?)$', document.views.document), re_path(r'^edit/(?P.*?)/icon(?P\d*).jpg$', edit.views.icon), re_path(r'^list/(?P.*?)/icon(?P\d*).jpg$', itemlist.views.icon), diff --git a/requirements.txt b/requirements.txt index 3eabf484..f4085fe9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,4 +20,6 @@ future pytz pypdfium2 Pillow>=10 +pillow-heif +pillow-avif-plugin mozilla-django-oidc==4.0.1 diff --git a/static/js/editor.js b/static/js/editor.js index 0138f239..a4b066d1 100644 --- a/static/js/editor.js +++ b/static/js/editor.js @@ -454,6 +454,17 @@ pandora.ui.editor = function(data) { $dialog.close(); pandora.api.transcribeAudio({ item: pandora.user.ui.item + }, function(result) { + if (result.data.taskId) { + pandora.wait(result.data.taskId, function(result) { + Ox.Request.clearCache(); + if (ui.item == data.id && ui.itemView == 'editor') { + pandora.$ui.contentPanel.replaceElement( + 1, pandora.$ui.item = pandora.ui.item() + ); + } + }) + } }) pandora.ui.tasksDialog().open(); } diff --git a/static/js/uploadDocumentDialog.js b/static/js/uploadDocumentDialog.js index 5a9193ca..50eaeb0a 100644 --- a/static/js/uploadDocumentDialog.js +++ b/static/js/uploadDocumentDialog.js @@ -8,7 +8,7 @@ pandora.ui.uploadDocumentDialog = function(options, callback) { existingFiles = [], uploadFiles = [], - supportedExtensions = ['gif', 'jpg', 'jpeg', 'pdf', 'png'], + supportedExtensions = ['gif', 'jpg', 'jpeg', 'pdf', 'png', 'webp', 'heic', 'heif', 'avif'], filename, diff --git a/static/js/utils.js b/static/js/utils.js index 6c0bb43b..29e0d75c 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -421,7 +421,7 @@ pandora.createLinks = function($element) { }; pandora.uploadDroppedFiles = function(files) { - var documentExtensions = ['pdf', /* 'epub', 'txt', */ 'png', 'gif', 'jpg', 'jpeg']; + var documentExtensions = ['pdf', /* 'epub', 'txt', */ 'png', 'gif', 'jpg', 'jpeg', 'webp', 'heic', 'heif', 'avif']; files = Ox.map(files, function(file) { return file }); if (files.every(function(file) { @@ -2132,7 +2132,9 @@ pandora.getSpan = function(state, val, callback) { } else { state.span = val; } - } else if (Ox.contains(['gif', 'jpg', 'png'], extension)) { + } else if (Ox.contains([ + 'gif', 'gif', 'jpg', 'png', 'webp', 'heic', 'heif', 'avif' + ], extension)) { values = val.split(','); if (values.length == 4) { state.span = values.map(function(number, index) { diff --git a/static/mobile/js/VideoElement.js b/static/mobile/js/VideoElement.js index 8238d95d..9abe27d9 100644 --- a/static/mobile/js/VideoElement.js +++ b/static/mobile/js/VideoElement.js @@ -8,6 +8,7 @@ VideoElement VideoElement Object loop loop playback playbackRate playback rate position start position + in start offset self Shared private variable ([options[, self]]) -> VideoElement Object loadedmetadata loadedmetadata @@ -38,6 +39,7 @@ window.VideoElement = function(options) { muted: false, playbackRate: 1, position: 0, + "in": 0, volume: 1 } Object.assign(self.options, options); @@ -166,9 +168,10 @@ window.VideoElement = function(options) { function getCurrentTime() { var item = self.items[self.currentItem]; - return self.seeking || self.loading + var currentTime = self.seeking || self.loading ? self.currentTime - : item ? item.position + self.video.currentTime - item['in'] : 0; + : item ? item.position + self.video.currentTime - item['in'] - self.options["in"] : 0; + return currentTime } function getset(key, value) { @@ -508,6 +511,7 @@ window.VideoElement = function(options) { } function setCurrentItemTime(currentTime) { + currentTime += self.options["in"] debug('Video', 'sCIT', currentTime, self.video.currentTime, 'delta', currentTime - self.video.currentTime); isReady(self.video, function(video) { diff --git a/static/mobile/js/VideoPlayer.js b/static/mobile/js/VideoPlayer.js index 95720e2d..b6e6ca57 100644 --- a/static/mobile/js/VideoPlayer.js +++ b/static/mobile/js/VideoPlayer.js @@ -10,6 +10,7 @@ window.VideoPlayer = function(options) { loop: false, muted: false, playbackRate: 1, + "in": 0, position: 0, volume: 1 } diff --git a/static/mobile/js/edits.js b/static/mobile/js/edits.js index 5494fcb1..f691d618 100644 --- a/static/mobile/js/edits.js +++ b/static/mobile/js/edits.js @@ -91,7 +91,7 @@ async function loadEdit(id, args) { } } data.edit = response['data'] - if (data.edit.status !== 'public') { + if (['public', 'featured'].indexOf(data.edit.status) == -1) { return { site: data.site, error: { diff --git a/static/mobile/js/main.js b/static/mobile/js/main.js index c84b5260..421eacc2 100644 --- a/static/mobile/js/main.js +++ b/static/mobile/js/main.js @@ -67,6 +67,7 @@ function parseURL() { id = id.replace('/editor/', '/').replace('/player/', '/') type = "item" } + //console.log(type, id, args) return [type, id, args] } diff --git a/static/mobile/js/render.js b/static/mobile/js/render.js index 307c5d2f..40422e55 100644 --- a/static/mobile/js/render.js +++ b/static/mobile/js/render.js @@ -75,7 +75,8 @@ function renderItem(data) { var video = window.video = VideoPlayer({ items: data.videos, poster: data.poster, - position: data["in"] || 0, + "in": data["in"] || 0, + position: 0, duration: data.duration, aspectratio: data.aspectratio }) @@ -85,16 +86,10 @@ function renderItem(data) { video.addEventListener("loadedmetadata", event => { // }) - video.addEventListener("timeupdate", event => { - var currentTime = video.currentTime() - if (currentTime >= data['out']) { - if (!video.paused) { - video.pause() - } - video.currentTime(data['in']) - } + + function updateAnnotations(currentTime) { div.querySelectorAll('.annotation').forEach(annot => { - var now = currentTime + var now = currentTime + (data["in"] || 0) var start = parseFloat(annot.dataset.in) var end = parseFloat(annot.dataset.out) if (now >= start && now <= end) { @@ -107,8 +102,18 @@ function renderItem(data) { } } }) - + } + video.addEventListener("timeupdate", event => { + var currentTime = video.currentTime() + if ((currentTime + (data["in"] || 0)) >= data['out']) { + if (!video.paused) { + video.pause() + } + video.currentTime(0) + } + updateAnnotations(currentTime) }) + updateAnnotations(data["position"] || 0) if (item.next || item.previous) { var nav = document.createElement('nav') nav.classList.add('items') diff --git a/update.py b/update.py index d414b97b..c5e7603e 100755 --- a/update.py +++ b/update.py @@ -304,6 +304,8 @@ if __name__ == "__main__": run('./bin/pip', 'install', '-r', 'requirements.txt') if old <= 6659: run('./bin/pip', 'install', '-r', 'requirements.txt') + if old <= 6688: + run('./bin/pip', 'install', '-r', 'requirements.txt') else: if len(sys.argv) == 1: branch = get_branch()