Compare commits
4 commits
ef680080cf
...
5591739531
| Author | SHA1 | Date | |
|---|---|---|---|
| 5591739531 | |||
| 7fe51fe7ac | |||
| 406237b837 | |||
| 0748265fe6 |
15 changed files with 67 additions and 25 deletions
|
|
@ -19,10 +19,13 @@ import ox.image
|
||||||
from ox.utils import json
|
from ox.utils import json
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
|
import pillow_avif
|
||||||
|
from pillow_heif import register_heif_opener
|
||||||
|
|
||||||
from .chop import Chop, make_keyframe_index
|
from .chop import Chop, make_keyframe_index
|
||||||
|
|
||||||
|
|
||||||
|
register_heif_opener()
|
||||||
logger = logging.getLogger('pandora.' + __name__)
|
logger = logging.getLogger('pandora.' + __name__)
|
||||||
|
|
||||||
img_extension = 'jpg'
|
img_extension = 'jpg'
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ from django.conf import settings
|
||||||
|
|
||||||
logger = logging.getLogger('pandora.' + __name__)
|
logger = logging.getLogger('pandora.' + __name__)
|
||||||
|
|
||||||
|
IMAGE_EXTENSIONS = ('png', 'jpg', 'webp', 'heic', 'heif')
|
||||||
|
|
||||||
def extract_text(pdf, page=None):
|
def extract_text(pdf, page=None):
|
||||||
if page is not None:
|
if page is not None:
|
||||||
|
|
@ -53,7 +54,7 @@ class FulltextMixin:
|
||||||
if self.file:
|
if self.file:
|
||||||
if self.extension == 'pdf':
|
if self.extension == 'pdf':
|
||||||
return extract_text(self.file.path)
|
return extract_text(self.file.path)
|
||||||
elif self.extension in ('png', 'jpg'):
|
elif self.extension in IMAGE_EXTENSIONS:
|
||||||
return ocr_image(self.file.path)
|
return ocr_image(self.file.path)
|
||||||
elif self.extension == 'html':
|
elif self.extension == 'html':
|
||||||
return self.data.get('text', '')
|
return self.data.get('text', '')
|
||||||
|
|
@ -180,7 +181,7 @@ class FulltextPageMixin(FulltextMixin):
|
||||||
if self.document.file:
|
if self.document.file:
|
||||||
if self.document.extension == 'pdf':
|
if self.document.extension == 'pdf':
|
||||||
return extract_text(self.document.file.path, self.page)
|
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)
|
return ocr_image(self.document.file.path)
|
||||||
elif self.extension == 'html':
|
elif self.extension == 'html':
|
||||||
# FIXME: is there a nice way to split that into pages
|
# FIXME: is there a nice way to split that into pages
|
||||||
|
|
|
||||||
|
|
@ -521,14 +521,17 @@ class Document(models.Model, FulltextMixin):
|
||||||
return save_chunk(self, self.file, chunk, offset, name, done_cb)
|
return save_chunk(self, self.file, chunk, offset, name, done_cb)
|
||||||
return False, 0
|
return False, 0
|
||||||
|
|
||||||
def thumbnail(self, size=None, page=None):
|
def thumbnail(self, size=None, page=None, accept=None):
|
||||||
if not self.file:
|
if not self.file:
|
||||||
return os.path.join(settings.STATIC_ROOT, 'png/document.png')
|
return os.path.join(settings.STATIC_ROOT, 'png/document.png')
|
||||||
src = self.file.path
|
src = self.file.path
|
||||||
folder = os.path.dirname(src)
|
folder = os.path.dirname(src)
|
||||||
if size:
|
if size:
|
||||||
size = int(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:
|
else:
|
||||||
path = src
|
path = src
|
||||||
if self.extension == 'pdf':
|
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))))
|
path = os.path.join(folder, '%dp%d,%s.jpg' % (size, page, ','.join(map(str, crop))))
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
resize_image(src, path, size=size)
|
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 os.path.exists(src):
|
||||||
if size and page:
|
if size and page:
|
||||||
crop = list(map(int, page.split(',')))
|
crop = list(map(int, page.split(',')))
|
||||||
|
|
@ -574,7 +577,7 @@ class Document(models.Model, FulltextMixin):
|
||||||
img = open_image_rgb(path)
|
img = open_image_rgb(path)
|
||||||
src = path
|
src = path
|
||||||
if size < max(img.size):
|
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):
|
if not os.path.exists(path):
|
||||||
resize_image(src, path, size=size)
|
resize_image(src, path, size=size)
|
||||||
if os.path.exists(src) and not os.path.exists(path):
|
if os.path.exists(src) and not os.path.exists(path):
|
||||||
|
|
|
||||||
|
|
@ -378,15 +378,22 @@ actions.register(sortDocuments, cache=False)
|
||||||
|
|
||||||
def file(request, id, name=None):
|
def file(request, id, name=None):
|
||||||
document = get_document_or_404_json(request, id)
|
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)
|
return HttpFileResponse(document.file.path)
|
||||||
|
|
||||||
def thumbnail(request, id, size=256, page=None):
|
def thumbnail(request, id, size=256, page=None):
|
||||||
size = int(size)
|
size = int(size)
|
||||||
document = get_document_or_404_json(request, id)
|
document = get_document_or_404_json(request, id)
|
||||||
|
accept = request.headers.get("Accept")
|
||||||
if "q" in request.GET and page:
|
if "q" in request.GET and page:
|
||||||
img = document.highlight_page(page, request.GET["q"], size)
|
img = document.highlight_page(page, request.GET["q"], size)
|
||||||
return HttpResponse(img, content_type="image/jpeg")
|
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
|
@login_required_json
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ urlpatterns += [
|
||||||
re_path(r'^resetUI$', user.views.reset_ui),
|
re_path(r'^resetUI$', user.views.reset_ui),
|
||||||
re_path(r'^collection/(?P<id>.*?)/icon(?P<size>\d*).jpg$', documentcollection.views.icon),
|
re_path(r'^collection/(?P<id>.*?)/icon(?P<size>\d*).jpg$', documentcollection.views.icon),
|
||||||
re_path(r'^documents/(?P<id>[A-Z0-9]+)/(?P<size>\d*)p(?P<page>[\d,]*).jpg$', document.views.thumbnail),
|
re_path(r'^documents/(?P<id>[A-Z0-9]+)/(?P<size>\d*)p(?P<page>[\d,]*).jpg$', document.views.thumbnail),
|
||||||
re_path(r'^documents/(?P<id>[A-Z0-9]+)/(?P<name>.*?\.[^\d]{3})$', document.views.file),
|
re_path(r'^documents/(?P<id>[A-Z0-9]+)/(?P<name>.*?\.[^\d]{3,4})$', document.views.file),
|
||||||
re_path(r'^documents/(?P<fragment>.*?)$', document.views.document),
|
re_path(r'^documents/(?P<fragment>.*?)$', document.views.document),
|
||||||
re_path(r'^edit/(?P<id>.*?)/icon(?P<size>\d*).jpg$', edit.views.icon),
|
re_path(r'^edit/(?P<id>.*?)/icon(?P<size>\d*).jpg$', edit.views.icon),
|
||||||
re_path(r'^list/(?P<id>.*?)/icon(?P<size>\d*).jpg$', itemlist.views.icon),
|
re_path(r'^list/(?P<id>.*?)/icon(?P<size>\d*).jpg$', itemlist.views.icon),
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,6 @@ future
|
||||||
pytz
|
pytz
|
||||||
pypdfium2
|
pypdfium2
|
||||||
Pillow>=10
|
Pillow>=10
|
||||||
|
pillow-heif
|
||||||
|
pillow-avif-plugin
|
||||||
mozilla-django-oidc==4.0.1
|
mozilla-django-oidc==4.0.1
|
||||||
|
|
|
||||||
|
|
@ -454,6 +454,17 @@ pandora.ui.editor = function(data) {
|
||||||
$dialog.close();
|
$dialog.close();
|
||||||
pandora.api.transcribeAudio({
|
pandora.api.transcribeAudio({
|
||||||
item: pandora.user.ui.item
|
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();
|
pandora.ui.tasksDialog().open();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ pandora.ui.uploadDocumentDialog = function(options, callback) {
|
||||||
existingFiles = [],
|
existingFiles = [],
|
||||||
uploadFiles = [],
|
uploadFiles = [],
|
||||||
|
|
||||||
supportedExtensions = ['gif', 'jpg', 'jpeg', 'pdf', 'png'],
|
supportedExtensions = ['gif', 'jpg', 'jpeg', 'pdf', 'png', 'webp', 'heic', 'heif', 'avif'],
|
||||||
|
|
||||||
filename,
|
filename,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -421,7 +421,7 @@ pandora.createLinks = function($element) {
|
||||||
};
|
};
|
||||||
|
|
||||||
pandora.uploadDroppedFiles = function(files) {
|
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 });
|
files = Ox.map(files, function(file) { return file });
|
||||||
|
|
||||||
if (files.every(function(file) {
|
if (files.every(function(file) {
|
||||||
|
|
@ -2132,7 +2132,9 @@ pandora.getSpan = function(state, val, callback) {
|
||||||
} else {
|
} else {
|
||||||
state.span = val;
|
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(',');
|
values = val.split(',');
|
||||||
if (values.length == 4) {
|
if (values.length == 4) {
|
||||||
state.span = values.map(function(number, index) {
|
state.span = values.map(function(number, index) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ VideoElement <f> VideoElement Object
|
||||||
loop <b|false> loop playback
|
loop <b|false> loop playback
|
||||||
playbackRate <n|1> playback rate
|
playbackRate <n|1> playback rate
|
||||||
position <n|0> start position
|
position <n|0> start position
|
||||||
|
in <n|0> start offset
|
||||||
self <o> Shared private variable
|
self <o> Shared private variable
|
||||||
([options[, self]]) -> <o:Element> VideoElement Object
|
([options[, self]]) -> <o:Element> VideoElement Object
|
||||||
loadedmetadata <!> loadedmetadata
|
loadedmetadata <!> loadedmetadata
|
||||||
|
|
@ -38,6 +39,7 @@ window.VideoElement = function(options) {
|
||||||
muted: false,
|
muted: false,
|
||||||
playbackRate: 1,
|
playbackRate: 1,
|
||||||
position: 0,
|
position: 0,
|
||||||
|
"in": 0,
|
||||||
volume: 1
|
volume: 1
|
||||||
}
|
}
|
||||||
Object.assign(self.options, options);
|
Object.assign(self.options, options);
|
||||||
|
|
@ -166,9 +168,10 @@ window.VideoElement = function(options) {
|
||||||
|
|
||||||
function getCurrentTime() {
|
function getCurrentTime() {
|
||||||
var item = self.items[self.currentItem];
|
var item = self.items[self.currentItem];
|
||||||
return self.seeking || self.loading
|
var currentTime = self.seeking || self.loading
|
||||||
? self.currentTime
|
? 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) {
|
function getset(key, value) {
|
||||||
|
|
@ -508,6 +511,7 @@ window.VideoElement = function(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCurrentItemTime(currentTime) {
|
function setCurrentItemTime(currentTime) {
|
||||||
|
currentTime += self.options["in"]
|
||||||
debug('Video', 'sCIT', currentTime, self.video.currentTime,
|
debug('Video', 'sCIT', currentTime, self.video.currentTime,
|
||||||
'delta', currentTime - self.video.currentTime);
|
'delta', currentTime - self.video.currentTime);
|
||||||
isReady(self.video, function(video) {
|
isReady(self.video, function(video) {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ window.VideoPlayer = function(options) {
|
||||||
loop: false,
|
loop: false,
|
||||||
muted: false,
|
muted: false,
|
||||||
playbackRate: 1,
|
playbackRate: 1,
|
||||||
|
"in": 0,
|
||||||
position: 0,
|
position: 0,
|
||||||
volume: 1
|
volume: 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ async function loadEdit(id, args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data.edit = response['data']
|
data.edit = response['data']
|
||||||
if (data.edit.status !== 'public') {
|
if (['public', 'featured'].indexOf(data.edit.status) == -1) {
|
||||||
return {
|
return {
|
||||||
site: data.site,
|
site: data.site,
|
||||||
error: {
|
error: {
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ function parseURL() {
|
||||||
id = id.replace('/editor/', '/').replace('/player/', '/')
|
id = id.replace('/editor/', '/').replace('/player/', '/')
|
||||||
type = "item"
|
type = "item"
|
||||||
}
|
}
|
||||||
|
//console.log(type, id, args)
|
||||||
return [type, id, args]
|
return [type, id, args]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,8 @@ function renderItem(data) {
|
||||||
var video = window.video = VideoPlayer({
|
var video = window.video = VideoPlayer({
|
||||||
items: data.videos,
|
items: data.videos,
|
||||||
poster: data.poster,
|
poster: data.poster,
|
||||||
position: data["in"] || 0,
|
"in": data["in"] || 0,
|
||||||
|
position: 0,
|
||||||
duration: data.duration,
|
duration: data.duration,
|
||||||
aspectratio: data.aspectratio
|
aspectratio: data.aspectratio
|
||||||
})
|
})
|
||||||
|
|
@ -85,16 +86,10 @@ function renderItem(data) {
|
||||||
video.addEventListener("loadedmetadata", event => {
|
video.addEventListener("loadedmetadata", event => {
|
||||||
//
|
//
|
||||||
})
|
})
|
||||||
video.addEventListener("timeupdate", event => {
|
|
||||||
var currentTime = video.currentTime()
|
function updateAnnotations(currentTime) {
|
||||||
if (currentTime >= data['out']) {
|
|
||||||
if (!video.paused) {
|
|
||||||
video.pause()
|
|
||||||
}
|
|
||||||
video.currentTime(data['in'])
|
|
||||||
}
|
|
||||||
div.querySelectorAll('.annotation').forEach(annot => {
|
div.querySelectorAll('.annotation').forEach(annot => {
|
||||||
var now = currentTime
|
var now = currentTime + (data["in"] || 0)
|
||||||
var start = parseFloat(annot.dataset.in)
|
var start = parseFloat(annot.dataset.in)
|
||||||
var end = parseFloat(annot.dataset.out)
|
var end = parseFloat(annot.dataset.out)
|
||||||
if (now >= start && now <= end) {
|
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) {
|
if (item.next || item.previous) {
|
||||||
var nav = document.createElement('nav')
|
var nav = document.createElement('nav')
|
||||||
nav.classList.add('items')
|
nav.classList.add('items')
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,8 @@ if __name__ == "__main__":
|
||||||
run('./bin/pip', 'install', '-r', 'requirements.txt')
|
run('./bin/pip', 'install', '-r', 'requirements.txt')
|
||||||
if old <= 6659:
|
if old <= 6659:
|
||||||
run('./bin/pip', 'install', '-r', 'requirements.txt')
|
run('./bin/pip', 'install', '-r', 'requirements.txt')
|
||||||
|
if old <= 6688:
|
||||||
|
run('./bin/pip', 'install', '-r', 'requirements.txt')
|
||||||
else:
|
else:
|
||||||
if len(sys.argv) == 1:
|
if len(sys.argv) == 1:
|
||||||
branch = get_branch()
|
branch = get_branch()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue