diff --git a/ctl b/ctl index 6e16fb87..816b843d 100755 --- a/ctl +++ b/ctl @@ -30,6 +30,9 @@ if [ "$action" = "init" ]; then $SUDO git clone -b $branch https://code.0x2620.org/0x2620/oxjs.git static/oxjs fi $SUDO mkdir -p src + if [ ! -d src/oxtimelines ]; then + $SUDO git clone -b $branch https://code.0x2620.org/0x2620/oxtimelines.git src/oxtimelines + fi for package in oxtimelines python-ox; do cd ${BASE} if [ ! -d src/${package} ]; then diff --git a/pandora/app/oidc.py b/pandora/app/oidc.py deleted file mode 100644 index be397374..00000000 --- a/pandora/app/oidc.py +++ /dev/null @@ -1,36 +0,0 @@ -import unicodedata - -from django.contrib.auth import get_user_model - -import mozilla_django_oidc.auth - -from user.utils import prepare_user - -User = get_user_model() - - -class OIDCAuthenticationBackend(mozilla_django_oidc.auth.OIDCAuthenticationBackend): - def create_user(self, claims): - user = super(OIDCAuthenticationBackend, self).create_user(claims) - username = None - for key in ('preferred_username', 'name'): - if claims.get(key): - username = claims[key] - break - n = 1 - if username and username != user.username: - uname = username - while User.objects.filter(username=uname).exclude(id=user.id).exists(): - n += 1 - uname = '%s (%s)' % (username, n) - user.username = uname - user.save() - prepare_user(user) - return user - - def update_user(self, user, claims): - return user - - -def generate_username(email): - return unicodedata.normalize('NFKC', email)[:150] diff --git a/pandora/app/views.py b/pandora/app/views.py index 734a5305..2abd1f99 100644 --- a/pandora/app/views.py +++ b/pandora/app/views.py @@ -184,7 +184,6 @@ def init(request, data): except: pass - config['site']['oidc'] = bool(getattr(settings, 'OIDC_RP_CLIENT_ID', False)) response['data']['site'] = config response['data']['user'] = init_user(request.user, request) request.session['last_init'] = str(datetime.now()) diff --git a/pandora/archive/external.py b/pandora/archive/external.py index 54c55246..5f4ac3fd 100644 --- a/pandora/archive/external.py +++ b/pandora/archive/external.py @@ -40,12 +40,8 @@ info_key_map = { 'display_id': 'id', } -YT_DLP = ['yt-dlp'] -if settings.YT_DLP_EXTRA: - YT_DLP += settings.YT_DLP_EXTRA - def get_info(url, referer=None): - cmd = YT_DLP + ['-j', '--all-subs', url] + cmd = ['yt-dlp', '-j', '--all-subs', url] if referer: cmd += ['--referer', referer] p = subprocess.Popen(cmd, @@ -97,7 +93,7 @@ def add_subtitles(item, media, tmp): sub.save() def load_formats(url): - cmd = YT_DLP + ['-q', url, '-j', '-F'] + cmd = ['yt-dlp', '-q', url, '-j', '-F'] p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) @@ -116,7 +112,7 @@ def download(item_id, url, referer=None): if isinstance(tmp, bytes): tmp = tmp.decode('utf-8') os.chdir(tmp) - cmd = YT_DLP + ['-q', media['url']] + cmd = ['yt-dlp', '-q', media['url']] if referer: cmd += ['--referer', referer] elif 'referer' in media: diff --git a/pandora/archive/extract.py b/pandora/archive/extract.py index 7b9f24ea..3d6c74fe 100644 --- a/pandora/archive/extract.py +++ b/pandora/archive/extract.py @@ -19,13 +19,10 @@ 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/archive/migrations/0008_file_filename_file_folder.py b/pandora/archive/migrations/0008_file_filename_file_folder.py deleted file mode 100644 index 5c8b34da..00000000 --- a/pandora/archive/migrations/0008_file_filename_file_folder.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 4.2.7 on 2025-01-23 10:48 - -from django.db import migrations, models - -def update_path(apps, schema_editor): - File = apps.get_model("archive", "File") - for file in File.objects.all(): - if file.path: - parts = file.path.split('/') - file.filename = parts.pop() - file.folder = '/'.join(parts) - file.save() - -class Migration(migrations.Migration): - - dependencies = [ - ('archive', '0007_stream_archive_str_file_id_69a542_idx'), - ] - - operations = [ - migrations.AddField( - model_name='file', - name='filename', - field=models.CharField(default='', max_length=2048), - ), - migrations.AddField( - model_name='file', - name='folder', - field=models.CharField(default='', max_length=2048), - ), - migrations.RunPython(update_path), - ] diff --git a/pandora/archive/models.py b/pandora/archive/models.py index 4bbd5da1..a5800d3d 100644 --- a/pandora/archive/models.py +++ b/pandora/archive/models.py @@ -53,9 +53,6 @@ class File(models.Model): path = models.CharField(max_length=2048, default="") # canoncial path/file sort_path = models.CharField(max_length=2048, default="") # sort name - folder = models.CharField(max_length=2048, default="") - filename = models.CharField(max_length=2048, default="") - type = models.CharField(default="", max_length=255) # editable @@ -197,13 +194,6 @@ class File(models.Model): data['part'] = str(data['part']) return data - def update_path(self): - path = self.normalize_path() - parts = path.split('/') - self.filename = parts.pop() - self.folder = '/'.join(parts) - return path - def normalize_path(self): # FIXME: always use format_path if settings.CONFIG['site']['folderdepth'] == 4: @@ -267,7 +257,7 @@ class File(models.Model): update_path = False if self.info: if self.id: - self.path = self.update_path() + self.path = self.normalize_path() else: update_path = True if self.item: @@ -300,7 +290,7 @@ class File(models.Model): self.streams.filter(source=None, available=True).count() super(File, self).save(*args, **kwargs) if update_path: - self.path = self.update_path() + self.path = self.normalize_path() super(File, self).save(*args, **kwargs) def get_path(self, name): @@ -484,9 +474,6 @@ class File(models.Model): 'videoCodec': self.video_codec, 'wanted': self.wanted, } - for key in ('folder', 'filename'): - if keys and key in keys: - data[key] = getattr(self, key) if error: data['error'] = error for key in self.PATH_INFO: @@ -553,7 +540,7 @@ class File(models.Model): def process_stream(self): ''' - extract derivatives from stream upload + extract derivatives from webm upload ''' from . import tasks return tasks.process_stream.delay(self.id) diff --git a/pandora/config.0xdb.jsonc b/pandora/config.0xdb.jsonc index 0d4b1aae..61dfd6e2 100644 --- a/pandora/config.0xdb.jsonc +++ b/pandora/config.0xdb.jsonc @@ -56,7 +56,6 @@ "canExportAnnotations": {"friend": true, "staff": true, "admin": true}, "canImportAnnotations": {"staff": true, "admin": true}, "canImportItems": {}, - "canTranscribeAudio": {}, "canManageDocuments": {"staff": true, "admin": true}, "canManageEntities": {"staff": true, "admin": true}, "canManageHome": {}, @@ -1402,7 +1401,7 @@ 240, 288, 360, 432, 480, 720 and 1080. */ "video": { - "formats": ["mp4"], + "formats": ["webm", "mp4"], // fixme: this should be named "ratio" or "defaultRatio", // as it also applies to clip lists (on load) "previewRatio": 1.7777777778, diff --git a/pandora/config.indiancinema.jsonc b/pandora/config.indiancinema.jsonc index 9c22a02f..11e7eb03 100644 --- a/pandora/config.indiancinema.jsonc +++ b/pandora/config.indiancinema.jsonc @@ -58,7 +58,6 @@ "canImportAnnotations": {"researcher": true, "staff": true, "admin": true}, // import needs to handle itemRequiresVideo=false first "canImportItems": {}, - "canTranscribeAudio": {}, "canManageDocuments": {"member": true, "researcher": true, "staff": true, "admin": true}, "canManageEntities": {"member": true, "researcher": true, "staff": true, "admin": true}, "canManageHome": {"staff": true, "admin": true}, @@ -1888,7 +1887,7 @@ 240, 288, 360, 432, 480, 720 and 1080. */ "video": { - "formats": ["mp4"], + "formats": ["webm", "mp4"], "previewRatio": 1.375, "resolutions": [240, 480] } diff --git a/pandora/config.padma.jsonc b/pandora/config.padma.jsonc index a3e41988..3bd3b748 100644 --- a/pandora/config.padma.jsonc +++ b/pandora/config.padma.jsonc @@ -56,7 +56,6 @@ "canExportAnnotations": {"member": true, "staff": true, "admin": true}, "canImportAnnotations": {"member": true, "staff": true, "admin": true}, "canImportItems": {"member": true, "staff": true, "admin": true}, - "canTranscribeAudio": {"staff": true, "admin": true}, "canManageDocuments": {"member": true, "staff": true, "admin": true}, "canManageEntities": {"member": true, "staff": true, "admin": true}, "canManageHome": {"staff": true, "admin": true}, @@ -1392,7 +1391,7 @@ 240, 288, 360, 432, 480, 720 and 1080. */ "video": { - "formats": ["mp4"], + "formats": ["webm", "mp4"], "previewRatio": 1.3333333333, //supported resolutions are //1080, 720, 480, 432, 360, 288, 240, 144, 96 diff --git a/pandora/config.pandora.jsonc b/pandora/config.pandora.jsonc index b0a64ab4..51aaee24 100644 --- a/pandora/config.pandora.jsonc +++ b/pandora/config.pandora.jsonc @@ -63,7 +63,6 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution. "canExportAnnotations": {"member": true, "staff": true, "admin": true}, "canImportAnnotations": {"member": true, "staff": true, "admin": true}, "canImportItems": {"member": true, "staff": true, "admin": true}, - "canTranscribeAudio": {}, "canManageDocuments": {"member": true, "staff": true, "admin": true}, "canManageEntities": {"member": true, "staff": true, "admin": true}, "canManageHome": {"staff": true, "admin": true}, @@ -1281,8 +1280,8 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution. 240, 288, 360, 432, 480, 720 and 1080. */ "video": { - "downloadFormat": "mp4", - "formats": ["mp4"], + "downloadFormat": "webm", + "formats": ["webm", "mp4"], "previewRatio": 1.3333333333, "resolutions": [240, 480] } diff --git a/pandora/document/fulltext.py b/pandora/document/fulltext.py index 5f2a3e50..c4c9aaff 100644 --- a/pandora/document/fulltext.py +++ b/pandora/document/fulltext.py @@ -8,7 +8,6 @@ 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: @@ -54,7 +53,7 @@ class FulltextMixin: if self.file: if self.extension == 'pdf': return extract_text(self.file.path) - elif self.extension in IMAGE_EXTENSIONS: + elif self.extension in ('png', 'jpg'): return ocr_image(self.file.path) elif self.extension == 'html': return self.data.get('text', '') @@ -181,7 +180,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 IMAGE_EXTENSIONS: + elif self.extension in ('png', 'jpg'): 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 3b3a0262..68819f39 100644 --- a/pandora/document/models.py +++ b/pandora/document/models.py @@ -521,17 +521,14 @@ 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, accept=None): + def thumbnail(self, size=None, page=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) - ext = 'jpg' - if accept and 'image/avif' in accept and size > 512: - ext = 'avif' - path = os.path.join(folder, '%d.%s' % (size, ext)) + path = os.path.join(folder, '%d.jpg' % size) else: path = src if self.extension == 'pdf': @@ -564,7 +561,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', 'webp', 'heic', 'heif'): + elif self.extension in ('jpg', 'png', 'gif'): if os.path.exists(src): if size and page: crop = list(map(int, page.split(','))) @@ -577,7 +574,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.%s' % (size, ','.join(map(str, crop)), ext)) + path = os.path.join(folder, '%sp%s.jpg' % (size, ','.join(map(str, crop)))) 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 8ce0f35b..c2d9f631 100644 --- a/pandora/document/views.py +++ b/pandora/document/views.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -from glob import glob -import mimetypes import os import re +from glob import glob import unicodedata import ox @@ -379,24 +378,15 @@ actions.register(sortDocuments, cache=False) def file(request, id, name=None): document = get_document_or_404_json(request, id) - accept = request.headers.get("Accept") - mime_type = mimetypes.guess_type(document.file.path)[0] - mime_type = 'image/%s' % document.extension - if accept and 'image/' in accept and document.extension in ( - 'webp', 'heif', 'heic', 'avif', 'tiff' - ) and mime_type not in accept: - image_size = max(document.width, document.height) - return HttpFileResponse(document.thumbnail(image_size, accept=accept)) 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, accept=accept)) + return HttpFileResponse(document.thumbnail(size, page=page)) @login_required_json diff --git a/pandora/edit/models.py b/pandora/edit/models.py index 94ad7a82..5008ac45 100644 --- a/pandora/edit/models.py +++ b/pandora/edit/models.py @@ -495,9 +495,6 @@ class Clip(models.Model): 'id': self.get_id(), 'index': self.index, 'volume': self.volume, - 'hue': self.hue, - 'saturation': self.saturation, - 'lightness': self.lightness, } if self.annotation: data['annotation'] = self.annotation.public_id diff --git a/pandora/item/models.py b/pandora/item/models.py index 138b4fcd..be7f3303 100644 --- a/pandora/item/models.py +++ b/pandora/item/models.py @@ -1375,7 +1375,7 @@ class Item(models.Model): self.poster_height = self.poster.height self.poster_width = self.poster.width self.clear_poster_cache(self.poster.path) - if self.poster_width and self.cache.get('posterRatio') != self.poster_width / self.poster_height: + if self.cache.get('posterRatio') != self.poster_width / self.poster_height: self.update_cache(poster_width=self.poster_width, poster_height=self.poster_height) diff --git a/pandora/item/views.py b/pandora/item/views.py index 1670fade..8ad2db2e 100644 --- a/pandora/item/views.py +++ b/pandora/item/views.py @@ -1004,9 +1004,7 @@ def download_source(request, id, part=None): response['Content-Disposition'] = "attachment; filename*=UTF-8''%s" % quote(filename.encode('utf-8')) return response -def download(request, id, resolution=None, format=None, part=None): - if format is None: - format = settings.CONFIG['video']['formats'][0] +def download(request, id, resolution=None, format='webm', part=None): item = get_object_or_404(models.Item, public_id=id) if not resolution or int(resolution) not in settings.CONFIG['video']['resolutions']: resolution = max(settings.CONFIG['video']['resolutions']) diff --git a/pandora/oxdjango/api/views.py b/pandora/oxdjango/api/views.py index ce7df892..3a4a2e89 100644 --- a/pandora/oxdjango/api/views.py +++ b/pandora/oxdjango/api/views.py @@ -34,15 +34,8 @@ def api(request): return response if request.META.get('CONTENT_TYPE') == 'application/json': r = json.loads(request.body.decode('utf-8')) - if 'action' not in r: - logger.error("invalid api request: %s", r) - response = render_to_json_response(json_response(status=400, - text='Invalid request')) - response['Access-Control-Allow-Origin'] = '*' - return response - else: - action = r['action'] - data = r.get('data', {}) + action = r['action'] + data = r.get('data', {}) else: action = request.POST['action'] data = json.loads(request.POST.get('data', '{}')) diff --git a/pandora/settings.py b/pandora/settings.py index 5045c6c9..7268c31c 100644 --- a/pandora/settings.py +++ b/pandora/settings.py @@ -111,7 +111,6 @@ ROOT_URLCONF = 'urls' INSTALLED_APPS = ( 'django.contrib.auth', - 'mozilla_django_oidc', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', @@ -159,27 +158,6 @@ INSTALLED_APPS = ( ) AUTH_USER_MODEL = 'system.User' -AUTH_PROFILE_MODULE = 'user.UserProfile' -AUTH_CHECK_USERNAME = True - -AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', -) - -# OpenID Connect login support -LOGIN_REDIRECT_URL = "/grid" -LOGOUT_REDIRECT_URL = "/grid" -OIDC_USERNAME_ALGO = "app.oidc.generate_username" -OIDC_RP_CLIENT_ID = None - -# define those in local_settings to enable OCID based login -#OIDC_RP_CLIENT_ID = '' -#OIDC_RP_CLIENT_SECRET = '' -#OIDC_RP_SIGN_ALGO = "RS256" -#OIDC_OP_JWKS_ENDPOINT = "" -#OIDC_OP_AUTHORIZATION_ENDPOINT = "" -#OIDC_OP_TOKEN_ENDPOINT = "" -#OIDC_OP_USER_ENDPOINT = "" # Log errors into db LOGGING = { @@ -215,6 +193,8 @@ CACHES = { DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" +AUTH_PROFILE_MODULE = 'user.UserProfile' +AUTH_CHECK_USERNAME = True FFMPEG = 'ffmpeg' FFPROBE = 'ffprobe' USE_VP9 = True @@ -311,8 +291,6 @@ DATA_UPLOAD_MAX_MEMORY_SIZE = 32 * 1024 * 1024 EMPTY_CLIPS = True -YT_DLP_EXTRA = [] - #you can ignore things below this line #========================================================================= LOCAL_APPS = [] @@ -343,7 +321,3 @@ except NameError: INSTALLED_APPS = tuple(list(INSTALLED_APPS) + LOCAL_APPS) -if OIDC_RP_CLIENT_ID: - AUTHENTICATION_BACKENDS = list(AUTHENTICATION_BACKENDS) + [ - 'app.oidc.OIDCAuthenticationBackend' - ] diff --git a/pandora/taskqueue/models.py b/pandora/taskqueue/models.py index 0d03f920..1d114a2c 100644 --- a/pandora/taskqueue/models.py +++ b/pandora/taskqueue/models.py @@ -88,9 +88,6 @@ class Task(models.Model): except Item.DoesNotExist: return False - if self.status == 'transcribing': - return False - if self.item.files.filter(wanted=True, available=False).count(): status = 'pending' elif self.item.files.filter(uploading=True).count(): diff --git a/pandora/templates/mobile/index.html b/pandora/templates/mobile/index.html index e274b941..5cda3d82 100644 --- a/pandora/templates/mobile/index.html +++ b/pandora/templates/mobile/index.html @@ -22,8 +22,8 @@ {% endif %} {% compress css file m %} - - + + {% endcompress %} diff --git a/pandora/urls.py b/pandora/urls.py index f6b66ae2..36af1aa0 100644 --- a/pandora/urls.py +++ b/pandora/urls.py @@ -2,7 +2,7 @@ import os import importlib -from django.urls import path, re_path, include +from django.urls import path, re_path from oxdjango.http import HttpFileResponse from django.conf import settings @@ -33,15 +33,9 @@ import urlalias.views def serve_static_file(path, location, content_type): return HttpFileResponse(location, content_type=content_type) - urlpatterns = [ #path('admin/', admin.site.urls), -] -if settings.OIDC_RP_CLIENT_ID: - urlpatterns += [ - path('oidc/', include('mozilla_django_oidc.urls')), - ] -urlpatterns += [ + re_path(r'^api/locale.(?P.*).json$', translation.views.locale_json), re_path(r'^api/upload/text/?$', text.views.upload), re_path(r'^api/upload/document/?$', document.views.upload), @@ -53,7 +47,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,4})$', document.views.file), + re_path(r'^documents/(?P[A-Z0-9]+)/(?P.*?\.[^\d]{3})$', 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/pandora/user/models.py b/pandora/user/models.py index 4234ab45..7f0a51a8 100644 --- a/pandora/user/models.py +++ b/pandora/user/models.py @@ -436,7 +436,8 @@ def has_capability(user, capability): level = 'guest' else: level = user.profile.get_level() - return settings.CONFIG['capabilities'].get(capability, {}).get(level) + return level in settings.CONFIG['capabilities'][capability] \ + and settings.CONFIG['capabilities'][capability][level] def merge_users(old, new): old.annotations.all().update(user=new) diff --git a/pandora/user/utils.py b/pandora/user/utils.py index 9abecaaa..db557dfe 100644 --- a/pandora/user/utils.py +++ b/pandora/user/utils.py @@ -3,38 +3,6 @@ from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception import ox -def prepare_user(user): - from django.contrib.auth import get_user_model - from django.conf import settings - from itemlist.models import List, Position - from django.db.models import Max - - User = get_user_model() - - first_user_qs = User.objects.all() - if user.id: - first_user_qs = first_user_qs.exclude(id=user.id) - if first_user_qs.count() == 0: - user.is_superuser = True - user.is_staff = True - user.save() - - for l in settings.CONFIG['personalLists']: - list = List(name=l['title'], user=user) - for key in ('query', 'public', 'featured'): - if key in l: - setattr(list, key, l[key]) - if key == 'query': - for c in list.query['conditions']: - if c['key'] == 'user': - c['value'] = c['value'].format(username=user.username) - list.save() - pos = Position(list=list, section='personal', user=user) - qs = Position.objects.filter(user=user, section='personal') - pos.position = (qs.aggregate(Max('position'))['position__max'] or 0) + 1 - pos.save() - - def get_ip(request): if 'HTTP_X_FORWARDED_FOR' in request.META: ip = request.META['HTTP_X_FORWARDED_FOR'].split(',')[0] diff --git a/pandora/user/views.py b/pandora/user/views.py index 86e31537..a2a678f1 100644 --- a/pandora/user/views.py +++ b/pandora/user/views.py @@ -28,7 +28,7 @@ from user.models import Group from . import models from .decorators import capability_required_json -from .utils import rename_user, prepare_user +from .utils import rename_user User = get_user_model() @@ -177,10 +177,28 @@ def signup(request, data): } }) else: + first_user = User.objects.count() == 0 user = User(username=data['username'], email=data['email']) user.set_password(data['password']) + #make first user admin + user.is_superuser = first_user + user.is_staff = first_user user.save() - prepare_user(user) + #create default user lists: + for l in settings.CONFIG['personalLists']: + list = List(name=l['title'], user=user) + for key in ('query', 'public', 'featured'): + if key in l: + setattr(list, key, l[key]) + if key == 'query': + for c in list.query['conditions']: + if c['key'] == 'user': + c['value'] = c['value'].format(username=user.username) + list.save() + pos = Position(list=list, section='personal', user=user) + qs = Position.objects.filter(user=user, section='personal') + pos.position = (qs.aggregate(Max('position'))['position__max'] or 0) + 1 + pos.save() if request.session.session_key: models.SessionData.objects.filter(session_key=request.session.session_key).update(user=user) ui = json.loads(request.session.get('ui', 'null')) diff --git a/requirements.txt b/requirements.txt index f4085fe9..b1ef09f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,3 @@ future pytz pypdfium2 Pillow>=10 -pillow-heif -pillow-avif-plugin -mozilla-django-oidc==4.0.1 diff --git a/static/js/addFilesDialog.js b/static/js/addFilesDialog.js index a8f6ecf0..4bfa0a71 100644 --- a/static/js/addFilesDialog.js +++ b/static/js/addFilesDialog.js @@ -106,13 +106,6 @@ pandora.ui.addFilesDialog = function(options) { }); var selectItems = []; - selectItems.push({ - id: 'one', - title: Ox._( - options.items.length > 1 ? 'Create new {0} with multiple parts' : 'Create new {0}', - [pandora.site.itemName.singular.toLowerCase()] - ) - }); if (pandora.user.ui.item && options.editable) { selectItems.push({ id: 'add', @@ -131,6 +124,13 @@ pandora.ui.addFilesDialog = function(options) { ) }); } + selectItems.push({ + id: 'one', + title: Ox._( + options.items.length > 1 ? 'Create new {0} with multiple parts' : 'Create new {0}', + [pandora.site.itemName.singular.toLowerCase()] + ) + }); var $select = Ox.Select({ items: selectItems, width: 256 diff --git a/static/js/appPanel.js b/static/js/appPanel.js index a2442d43..5ca52e00 100644 --- a/static/js/appPanel.js +++ b/static/js/appPanel.js @@ -100,10 +100,6 @@ pandora.ui.appPanel = function() { pandora.$ui.siteDialog = pandora.ui.siteDialog(page).open(); } } else if (['signup', 'signin'].indexOf(page) > -1) { - if (pandora.site.site.oidc) { - pandora.oidcLogin() - return - } if (pandora.user.level == 'guest') { if (pandora.$ui.accountDialog && pandora.$ui.accountDialog.is(':visible')) { pandora.$ui.accountDialog.options(pandora.ui.accountDialogOptions(page)); diff --git a/static/js/editor.js b/static/js/editor.js index 24f9e557..90558d63 100644 --- a/static/js/editor.js +++ b/static/js/editor.js @@ -21,13 +21,11 @@ pandora.ui.editor = function(data) { censoredIcon: pandora.site.cantPlay.icon, censoredTooltip: Ox._(pandora.site.cantPlay.text), clickLink: pandora.clickLink, - confirmDeleteDialog: confirmDeleteDialog, cuts: data.cuts || [], duration: data.duration, enableDownload: pandora.hasCapability('canDownloadVideo') >= data.rightslevel || data.editable, enableExport: pandora.hasCapability('canExportAnnotations') || data.editable, enableImport: pandora.hasCapability('canImportAnnotations') || data.editable, - enableTranscribe: pandora.hasCapability('canTranscribeAudio') && data.editable, enableSetPosterFrame: !pandora.site.media.importFrames && data.editable, enableSubtitles: ui.videoSubtitles, find: ui.itemFind, @@ -222,9 +220,7 @@ pandora.ui.editor = function(data) { }); } that.updateAnnotation(data.id, result.data); - if (result.data.id) { - pandora.UI.set('videoPoints.' + ui.item + '.annotation', result.data.id.split('/')[1] || ''); - } + pandora.UI.set('videoPoints.' + ui.item + '.annotation', result.data.id.split('/')[1] || ''); Ox.Request.clearCache(); }; var edit = { @@ -395,21 +391,7 @@ pandora.ui.editor = function(data) { pandora.UI.set({videoResolution: data.resolution}); }, select: function(data) { - if (Ox.isArray(data.id)) { - var range = data.id.map(id => { - return getAnnotationById(id) - }) - data['in'] = Ox.min(range.map(annotation => { return annotation["in"]; })) - data['out'] = Ox.max(range.map(annotation => { return annotation["out"]; })) - pandora.UI.set('videoPoints.' + ui.item, { - annotation: '', - 'in': data['in'], - out: data.out, - position: ui.videoPoints[ui.item].position - }) - } else { - pandora.UI.set('videoPoints.' + ui.item + '.annotation', data.id.split('/')[1] || ''); - } + pandora.UI.set('videoPoints.' + ui.item + '.annotation', data.id.split('/')[1] || ''); }, showentityinfo: function(data) { pandora.URL.push('/entities/' + data.id) @@ -435,54 +417,6 @@ pandora.ui.editor = function(data) { togglesize: function(data) { pandora.UI.set({videoSize: data.size}); }, - transcribeaudio: function() { - const $dialog = pandora.ui.iconDialog({ - buttons: [ - Ox.Button({ - id: 'cancel', - title: Ox._('Cancel') - }) - .bindEvent({ - click: function() { - $dialog.close(); - } - }), - Ox.Button({ - id: 'transcribe', - title: Ox._('Transcribe Audio') - }) - .bindEvent({ - click: function() { - $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(); - } - }) - ], - content: Ox._( - 'Are you sure you want to add automated transcription to "{0}"', - [data.title] - ), - keys: {enter: 'transcribe', escape: 'cancel'}, - title: Ox._( - 'Transcribe {0} {1}', [data.title] - ) - }); - $dialog.open() - }, pandora_showannotations: function(data) { that.options({showAnnotations: data.value}); }, @@ -494,60 +428,6 @@ pandora.ui.editor = function(data) { pandora._dontSelectResult = false; - function confirmDeleteDialog(options, callback) { - const title = Ox.getObjectById( - pandora.site.layers, - getAnnotationById(options.items[0]).layer - ).item - const subject = options.items.length == 1 ? title : title + 's' - const $dialog = pandora.ui.iconDialog({ - buttons: [ - Ox.Button({ - id: 'cancel', - title: Ox._('Keep {0}', [Ox._(subject)]) - }) - .bindEvent({ - click: function() { - $dialog.close(); - } - }), - Ox.Button({ - id: 'delete', - title: Ox._('Delete {0}', [Ox._(subject)]) - }) - .bindEvent({ - click: function() { - $dialog.close(); - callback() - } - }) - ], - content: Ox._( - 'Are you sure you want delete {0} {1}?

All data will be removed.', - [options.items.length, Ox._(subject.toLowerCase())] - ), - height: 96, - keys: {enter: 'delete', escape: 'cancel'}, - title: Ox._( - 'Delete {0}', [Ox._(subject)] - ) - }); - $dialog.open() - return $dialog - } - - function getAnnotationById(id) { - var annotation - data.annotations.forEach(layer => { - layer.items.forEach(a => { - if (a.id == id) { - annotation = a - } - }) - }) - return annotation; - } - function updateBrowser() { pandora.$ui.browser.find('img[src*="/' + ui.item + '/"]').each(function() { $(this).attr({ diff --git a/static/js/exportAnnotationsDialog.js b/static/js/exportAnnotationsDialog.js index 79f0142d..4db7cf84 100644 --- a/static/js/exportAnnotationsDialog.js +++ b/static/js/exportAnnotationsDialog.js @@ -102,13 +102,6 @@ pandora.ui.exportAnnotationsDialog = function(options) { $button.wrap($('')); // On wrap, a reference to the link would *not* be the link in the DOM $link = $($button.parent()); - $link.on({ - click: function() { - setTimeout(() => { - that.close() - }, 10) - } - }) updateLink(); } diff --git a/static/js/folders.js b/static/js/folders.js index 3d54aee9..c23feedf 100644 --- a/static/js/folders.js +++ b/static/js/folders.js @@ -424,35 +424,26 @@ pandora.ui.folders = function(section) { }).bindEvent({ click: function() { var $dialog = pandora.ui.iconDialog({ - buttons: title != Ox._('Featured ' + folderItems) ? [].concat( - pandora.site.site.oidc ? [] - : [ - Ox.Button({title: Ox._('Sign Up...')}).bindEvent({ - click: function() { - $dialog.close(); - pandora.$ui.accountDialog = pandora.ui.accountDialog('signup').open(); - } - }) - ], - [ - Ox.Button({title: Ox._('Sign In...')}).bindEvent({ - click: function() { - $dialog.close(); - if (pandora.site.site.oidc) { - pandora.oidcLogin() - } else { - pandora.$ui.accountDialog = pandora.ui.accountDialog('signin').open(); - } - } - }), - {}, - Ox.Button({title: Ox._('Not Now')}).bindEvent({ - click: function() { - $dialog.close(); - } - }) - ] - ): [ + buttons: title != Ox._('Featured ' + folderItems) ? [ + Ox.Button({title: Ox._('Sign Up...')}).bindEvent({ + click: function() { + $dialog.close(); + pandora.$ui.accountDialog = pandora.ui.accountDialog('signup').open(); + } + }), + Ox.Button({title: Ox._('Sign In...')}).bindEvent({ + click: function() { + $dialog.close(); + pandora.$ui.accountDialog = pandora.ui.accountDialog('signin').open(); + } + }), + {}, + Ox.Button({title: Ox._('Not Now')}).bindEvent({ + click: function() { + $dialog.close(); + } + }) + ] : [ Ox.Button({title: Ox._('Close')}).bindEvent({ click: function() { $dialog.close(); diff --git a/static/js/getItemTitle.js b/static/js/getItemTitle.js index a13c8b83..286036c6 100644 --- a/static/js/getItemTitle.js +++ b/static/js/getItemTitle.js @@ -1,10 +1,6 @@ 'use strict'; pandora.getItemTitle = function(itemData, includeYear) { - var director = itemData.director - if (!Ox.isArray(director)) { - director = [director] - } return (itemData.title || Ox._('Untitled')) + ( Ox.len(itemData.director) || (includeYear && itemData.year) ? ' (' + ( diff --git a/static/js/helpDialog.js b/static/js/helpDialog.js index 97bc51bb..53b2497f 100644 --- a/static/js/helpDialog.js +++ b/static/js/helpDialog.js @@ -137,26 +137,24 @@ pandora.ui.helpDialog = function() { that.select = function(id) { var img, $img; - if ($text) { - $text.html(text[id || 'help']).scrollTop(0); - img = $text.find('img'); - if (img) { - $img = $(img); - $img.replaceWith( - $image = Ox.ImageElement( - Ox.extend(getImageSize(), {src: $img.attr('src')}) - ) - .css({borderRadius: '8px'}) - ); - } - $text.find('td:first-child') - .css({ - height: '16px', - paddingRight: '8px', - textAlign: 'right', - whiteSpace: 'nowrap' - }); + $text.html(text[id || 'help']).scrollTop(0); + img = $text.find('img'); + if (img) { + $img = $(img); + $img.replaceWith( + $image = Ox.ImageElement( + Ox.extend(getImageSize(), {src: $img.attr('src')}) + ) + .css({borderRadius: '8px'}) + ); } + $text.find('td:first-child') + .css({ + height: '16px', + paddingRight: '8px', + textAlign: 'right', + whiteSpace: 'nowrap' + }); return that; } diff --git a/static/js/home.js b/static/js/home.js index 56649cf9..76162e7a 100644 --- a/static/js/home.js +++ b/static/js/home.js @@ -171,13 +171,13 @@ pandora.ui.home = function() { }), $signinButton = Ox.Button({ title: Ox._('Sign In'), - width: pandora.site.site.oidc ? 156 : 74 + width: 74 }) .css({ position: 'absolute', left: 0, top: '112px', - right: pandora.site.site.oidc ? '164px' : '82px', + right: '82px', bottom: 0, margin: 'auto', opacity: 0 @@ -248,13 +248,7 @@ pandora.ui.home = function() { adjustRatio(); if (pandora.user.level == 'guest') { - if (pandora.site.site.oidc) { - $signinButton.options({ - width: 156 - }) - } else { - $signupButton.appendTo(that); - } + $signupButton.appendTo(that); $signinButton.appendTo(that); } else { $preferencesButton.appendTo(that); diff --git a/static/js/importAnnotationsDialog.js b/static/js/importAnnotationsDialog.js index 2c11160b..13e83118 100644 --- a/static/js/importAnnotationsDialog.js +++ b/static/js/importAnnotationsDialog.js @@ -201,11 +201,10 @@ pandora.ui.importAnnotationsDialog = function(options) { pandora.$ui.contentPanel.replaceElement( 1, pandora.$ui.item = pandora.ui.item() ); - that.close(); } else { $status.html(Ox._('Import failed.')); - enableButtons(); } + enableButtons(); }); } else { $status.html(Ox._('Import failed.')); diff --git a/static/js/item.js b/static/js/item.js index b158be02..2837fedc 100644 --- a/static/js/item.js +++ b/static/js/item.js @@ -39,7 +39,7 @@ pandora.ui.item = function() { } }) if (!Ox.isEmpty(set)) { - pandora.UI.set('videoPoints.' + item, Ox.extend(videoPoints, set)) + pandora.UI.set('videoPoints.' + item, Ox.extend(videoPoints[point], set[point])) } } @@ -63,9 +63,9 @@ pandora.ui.item = function() { var tasks = result_.data.items.filter(function(task) { return task.item == item}) if (tasks.length > 0) { html = Ox._( - '{0} is currently being processed. ' + '{0} is currently processed. ' + '{1} view will be available in a moment.', - [result.data.title, Ox.toTitleCase(Ox._(pandora.user.ui.itemView))] + [result.data.title, Ox._(pandora.user.ui.itemView)] ) } note.html(html) @@ -202,10 +202,6 @@ pandora.ui.item = function() { } }); } - // avoid loop while selecting multiple annotations - if (options.selected == '' && Ox.isArray(pandora.$ui[pandora.user.ui.itemView].options('selected'))) { - delete options.selected - } pandora.$ui[pandora.user.ui.itemView].options(options); } } diff --git a/static/js/list.js b/static/js/list.js index 03fe29d9..bc680f85 100644 --- a/static/js/list.js +++ b/static/js/list.js @@ -270,7 +270,7 @@ pandora.ui.list = function() { item: function(data, sort, size) { size = 128; var clipsQuery = pandora.getClipsQuery(), - isClipsQuery = clipsQuery.conditions.length > 1, + isClipsQuery = !!clipsQuery.conditions.length, ratio = ui.icons == 'posters' ? (ui.showSitePosters ? pandora.site.posters.ratio : data.posterRatio) : 1, url = pandora.getMediaURL('/' + data.id + '/' + ( @@ -352,7 +352,7 @@ pandora.ui.list = function() { }, items: function(data, callback) { var clipsQuery = pandora.getClipsQuery(), - isClipsQuery = clipsQuery.conditions.length > 1; + isClipsQuery = !!clipsQuery.conditions.length; pandora.api.find(Ox.extend(data, Ox.extend({ query: ui.find }, isClipsQuery ? {clips: { diff --git a/static/js/mainMenu.js b/static/js/mainMenu.js index d3fdf493..2efc217f 100644 --- a/static/js/mainMenu.js +++ b/static/js/mainMenu.js @@ -46,9 +46,7 @@ pandora.ui.mainMenu = function() { { id: 'tasks', title: Ox._('Tasks...'), disabled: isGuest }, { id: 'archives', title: Ox._('Archives...'), disabled: /*isGuest*/ true }, {}, - !pandora.site.site.oidc - ? { id: 'signup', title: Ox._('Sign Up...'), disabled: !isGuest } - : [], + { id: 'signup', title: Ox._('Sign Up...'), disabled: !isGuest }, isGuest ? { id: 'signin', title: Ox._('Sign In...')} : { id: 'signout', title: Ox._('Sign Out...')} ] }, diff --git a/static/js/mediaView.js b/static/js/mediaView.js index a05aa981..f7773148 100644 --- a/static/js/mediaView.js +++ b/static/js/mediaView.js @@ -142,22 +142,6 @@ pandora.ui.mediaView = function(options) { visible: true, width: 360 }, - { - align: 'left', - id: 'folder', - operator: '+', - title: Ox._('Folder'), - visible: false, - width: 360 - }, - { - align: 'left', - id: 'filename', - operator: '+', - title: Ox._('Filename'), - visible: false, - width: 360 - }, { editable: true, id: 'version', @@ -457,8 +441,6 @@ pandora.ui.mediaView = function(options) { }); } } - }).on({ - keyup: updateForm }); }); @@ -681,9 +663,7 @@ pandora.ui.mediaView = function(options) { }); } self.$moveButton.options({ - disabled: self.selected.length == 0 || ( - self.$idInput.value().length + self.$titleInput.value().length - ) == 0 + disabled: self.selected.length == 0 }); self.$menu[ self.selected.length == 0 ? 'disableItem' : 'enableItem' diff --git a/static/js/metadataDialog.js b/static/js/metadataDialog.js index 015ec833..3c0a718f 100644 --- a/static/js/metadataDialog.js +++ b/static/js/metadataDialog.js @@ -67,7 +67,7 @@ pandora.ui.metadataDialog = function(data) { 'To update the metadata for this {0}, please enter its IMDb ID.', [pandora.site.itemName.singular.toLowerCase()] ), - keys: {enter: 'update', escape: 'close'}, + keyboard: {enter: 'update', escape: 'close'}, title: Ox._('Update Metadata') }); } diff --git a/static/js/pandora.js b/static/js/pandora.js index dcf6ba1a..caeef67a 100644 --- a/static/js/pandora.js +++ b/static/js/pandora.js @@ -354,10 +354,6 @@ appPanel if (pandora.user.ui.itemView == 'video') { pandora.user.ui.itemView = 'player'; } - // item find accross relodas breaks links to in/out - if (pandora.user.ui.itemFind) { - pandora.user.ui.itemFind = ""; - } Ox.extend(pandora.site, { calendar: data.site.layers.some(function(layer) { diff --git a/static/js/tasksDialog.js b/static/js/tasksDialog.js index e2582cc6..dc5ec8fd 100644 --- a/static/js/tasksDialog.js +++ b/static/js/tasksDialog.js @@ -79,8 +79,7 @@ pandora.ui.tasksDialog = function(options) { 'processing': 'Processing', 'canceled': 'Canceled', 'failed': 'Failed', - 'finished': 'Finished', - 'transcribing': 'Transcribing' + 'finished': 'Finished' }[value] || value; }, id: 'status', diff --git a/static/js/uploadDocumentDialog.js b/static/js/uploadDocumentDialog.js index 7b80a241..5a9193ca 100644 --- a/static/js/uploadDocumentDialog.js +++ b/static/js/uploadDocumentDialog.js @@ -8,6 +8,8 @@ pandora.ui.uploadDocumentDialog = function(options, callback) { existingFiles = [], uploadFiles = [], + supportedExtensions = ['gif', 'jpg', 'jpeg', 'pdf', 'png'], + filename, ids = [], @@ -70,7 +72,7 @@ pandora.ui.uploadDocumentDialog = function(options, callback) { }); if (!Ox.every(extensions, function(extension) { - return Ox.contains(pandora.documentExtensions, extension) + return Ox.contains(supportedExtensions, extension) })) { return errorDialog( Ox._('Supported file types are GIF, JPG, PNG and PDF.') diff --git a/static/js/utils.js b/static/js/utils.js index ea28e18f..7bf3fa5e 100644 --- a/static/js/utils.js +++ b/static/js/utils.js @@ -420,28 +420,13 @@ pandora.createLinks = function($element) { }); }; -pandora.imageExtensions = [ - 'avif', - 'gif', - 'heic', - 'heif', - 'jpeg', - 'jpg', - 'png', - 'tiff', - 'webp' -]; - -pandora.documentExtensions = [ - 'pdf', /* 'epub', 'txt', */ -].concat(pandora.imageExtensions); - pandora.uploadDroppedFiles = function(files) { + var documentExtensions = ['pdf', /* 'epub', 'txt', */ 'png', 'gif', 'jpg', 'jpeg']; files = Ox.map(files, function(file) { return file }); if (files.every(function(file) { var extension = file.name.split('.').pop().toLowerCase() - return Ox.contains(pandora.documentExtensions, extension) + return Ox.contains(documentExtensions, extension) })) { pandora.ui.uploadDocumentDialog({ files: files @@ -2147,7 +2132,7 @@ pandora.getSpan = function(state, val, callback) { } else { state.span = val; } - } else if (Ox.contains(pandora.imageExtensions, extension)) { + } else if (Ox.contains(['gif', 'jpg', 'png'], extension)) { values = val.split(','); if (values.length == 4) { state.span = values.map(function(number, index) { @@ -2665,11 +2650,6 @@ pandora.logEvent = function(data, event, element) { } }; -pandora.oidcLogin = function() { - Ox.LoadingScreen().css({zIndex: 100}).addClass('OxScreen').appendTo(document.body).show().start() - document.location.href = '/oidc/authenticate/'; -}; - pandora.openLicenseDialog = function() { if (!Ox.Focus.focusedElementIsInput() && !pandora.hasDialogOrScreen()) { pandora.ui.licenseDialog().open().bindEvent({ diff --git a/static/mobile/css/style.css b/static/mobile/css/style.css index aaf5a6ee..2dd8e28c 100644 --- a/static/mobile/css/style.css +++ b/static/mobile/css/style.css @@ -201,75 +201,3 @@ ol li { width: 64px; height: 64px; } - - -.seekbar { - padding: 12px 22px; - position: relative; - width: 100%; -} -.fullscreen .seekbar { - padding: 28px 22px; -} - -.seekbar-progress { - height: 10px; - border: solid 1px #B1B1B1; -} - -.seekbar-progress [role="progressbar"] { - height: 100%; - position: relative; - background-color: #B1B1B180; -} - -.seekbar-progress [role="progressbar"]:after { - content: " "; - display: block; - width: 20px; - height: 20px; - position: absolute; - top: -6px; - right: -10px; - background-color: #B1B1B1; - border-radius: 20px; -} - -.seekbar input[type="range"] { - -webkit-appearance: none; - width: 100%; - height: 100%; - margin: 0; - position: absolute; - top: 0; - left: 0; - z-index: 2; - background: transparent; - outline: 0; - border: 0; -} - -.seekbar input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - display: block; - width: 48px; - height: 48px; - background-color: transparent; -} - -.seekbar input[type="range"]::-moz-range-thumb { - display: block; - width: 48px; - height: 48px; - background: transparent; - border: 0; -} - -.seekbar input[type="range"]::-moz-range-track { - background: transparent; - border: 0; -} - -.seekbar input[type="range"]::-moz-focus-outer { - border: 0; -} diff --git a/static/mobile/js/VideoElement.js b/static/mobile/js/VideoElement.js index 31fbcb8e..8238d95d 100644 --- a/static/mobile/js/VideoElement.js +++ b/static/mobile/js/VideoElement.js @@ -8,7 +8,6 @@ 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 @@ -39,7 +38,6 @@ window.VideoElement = function(options) { muted: false, playbackRate: 1, position: 0, - "in": 0, volume: 1 } Object.assign(self.options, options); @@ -168,10 +166,9 @@ window.VideoElement = function(options) { function getCurrentTime() { var item = self.items[self.currentItem]; - var currentTime = self.seeking || self.loading + return self.seeking || self.loading ? self.currentTime - : item ? item.position + self.video.currentTime - item['in'] - self.options["in"] : 0; - return currentTime + : item ? item.position + self.video.currentTime - item['in'] : 0; } function getset(key, value) { @@ -531,10 +528,6 @@ window.VideoElement = function(options) { function setCurrentTime(time) { debug('Video', 'sCT', time); - if (self.options["in"]) { - debug('Video', 'sCT shift time by', self.options["in"], 'to', time); - time += self.options["in"] - } var currentTime, currentItem; self.items.forEach(function(item, i) { if (time >= item.position diff --git a/static/mobile/js/VideoPlayer.js b/static/mobile/js/VideoPlayer.js index b6e6ca57..af219c54 100644 --- a/static/mobile/js/VideoPlayer.js +++ b/static/mobile/js/VideoPlayer.js @@ -10,7 +10,6 @@ window.VideoPlayer = function(options) { loop: false, muted: false, playbackRate: 1, - "in": 0, position: 0, volume: 1 } @@ -154,11 +153,9 @@ window.VideoPlayer = function(options) { ${icon.mute}
-
- -
-
-
+
+
+
@@ -226,31 +223,15 @@ window.VideoPlayer = function(options) { } } var showControls - function hideControlsLater() { - if (showControls) { - clearTimeout(showControls) - } - showControls = setTimeout(() => { - if (touching) { - hideControlsLater() - } else { - self.controls.style.opacity = that.paused ? '1' : '0' - showControls = null - } - }, 3000) - } var toggleControls = event => { - if (event.target.tagName == "INPUT") { - if (showControls) { - clearTimeout(showControls) - } - return - } if (self.controls.style.opacity == '0') { event.preventDefault() event.stopPropagation() self.controls.style.opacity = '1' - hideControlsLater() + showControls = setTimeout(() => { + self.controls.style.opacity = that.paused ? '1' : '0' + showControls = null + }, 3000) } else { self.controls.style.opacity = '0' } @@ -260,7 +241,10 @@ window.VideoPlayer = function(options) { clearTimeout(showControls) } self.controls.style.opacity = '1' - hideControlsLater() + showControls = setTimeout(() => { + self.controls.style.opacity = that.paused ? '1' : '0' + showControls = null + }, 3000) }) self.controls.addEventListener("mouseleave", event => { if (showControls) { @@ -269,13 +253,7 @@ window.VideoPlayer = function(options) { self.controls.style.opacity = that.paused ? '1' : '0' showControls = null }) - self.controls.addEventListener("touchstart", event => { - touching = true - }) self.controls.addEventListener("touchstart", toggleControls) - self.controls.addEventListener("touchend", event => { - touching = false - }) self.controls.querySelector('.toggle').addEventListener("click", toggleVideo) self.controls.querySelector('.volume').addEventListener("click", toggleSound) self.controls.querySelector('.fullscreen-btn').addEventListener("click", toggleFullscreen) @@ -332,7 +310,6 @@ window.VideoPlayer = function(options) { that.append(unblock) }) var loading = true - var touching = false that.brightness(0) that.addEventListener("loadedmetadata", event => { // @@ -354,40 +331,42 @@ window.VideoPlayer = function(options) { } }) - var time = that.querySelector('.controls .time div'); - const progressbar = that.querySelector('.seekbar div[role="progressbar"]'); - function setProgressPosition(value) { - progressbar.style.width = value + '%'; - progressbar.setAttribute('aria-valuenow', value); - - } - that.querySelector('.controls .position input').addEventListener('input', event => { - event.preventDefault() - event.stopPropagation() - setProgressPosition(event.target.value) - var position = event.target.value/100 * self.options.duration - displayTime(position) - that.currentTime(position) - hideControlsLater() - }) - function displayTime(currentTime) { - duration = formatDuration(self.options.duration) - currentTime = formatDuration(currentTime) - while (duration && duration.startsWith('00:')) { - duration = duration.slice(3) + var time = that.querySelector('.controls .time div'), + progress = that.querySelector('.controls .position .progress') + that.querySelector('.controls .position').addEventListener("click", event => { + var bar = event.target + while (bar && !bar.classList.contains('bar')) { + bar = bar.parentElement } - currentTime = currentTime.slice(currentTime.length - duration.length) - time.innerText = `${currentTime} / ${duration}` - } - + if (bar && bar.classList.contains('bar')) { + event.preventDefault() + event.stopPropagation() + var rect = bar.getBoundingClientRect() + var x = event.clientX - rect.x + var percent = x / rect.width + var position = percent * self.options.duration + if (self.options.position) { + position += self.options.position + } + progress.style.width = (100 * percent) + '%' + that.currentTime(position) + } + }) that.addEventListener("timeupdate", event => { var currentTime = that.currentTime(), duration = self.options.duration if (self.options.position) { currentTime -= self.options.position } - setProgressPosition(100 * currentTime / duration) - displayTime(currentTime) + progress.style.width = (100 * currentTime / duration) + '%' + duration = formatDuration(duration) + currentTime = formatDuration(currentTime) + while (duration && duration.startsWith('00:')) { + duration = duration.slice(3) + } + currentTime = currentTime.slice(currentTime.length - duration.length) + time.innerText = `${currentTime} / ${duration}` + }) that.addEventListener("play", event => { diff --git a/static/mobile/js/edits.js b/static/mobile/js/edits.js index f691d618..be9101ee 100644 --- a/static/mobile/js/edits.js +++ b/static/mobile/js/edits.js @@ -1,4 +1,35 @@ +const getSortValue = function(value) { + var sortValue = value; + function trim(value) { + return value.replace(/^\W+(?=\w)/, ''); + } + if ( + isEmpty(value) + || isNull(value) + || isUndefined(value) + ) { + sortValue = null; + } else if (isString(value)) { + // make lowercase and remove leading non-word characters + sortValue = trim(value.toLowerCase()); + // move leading articles to the end + // and remove leading non-word characters + ['a', 'an', 'the'].forEach(function(article) { + if (new RegExp('^' + article + ' ').test(sortValue)) { + sortValue = trim(sortValue.slice(article.length + 1)) + + ', ' + sortValue.slice(0, article.length); + return false; // break + } + }); + // remove thousand separators and pad numbers + sortValue = sortValue.replace(/(\d),(?=(\d{3}))/g, '$1') + .replace(/\d+/g, function(match) { + return match.padStart(64, '0') + }); + } + return sortValue; +}; const sortByKey = function(array, by) { return array.sort(function(a, b) { @@ -91,7 +122,7 @@ async function loadEdit(id, args) { } } data.edit = response['data'] - if (['public', 'featured'].indexOf(data.edit.status) == -1) { + if (data.edit.status !== 'public') { return { site: data.site, error: { diff --git a/static/mobile/js/item.js b/static/mobile/js/item.js index 42c4d2cd..a9942536 100644 --- a/static/mobile/js/item.js +++ b/static/mobile/js/item.js @@ -129,10 +129,6 @@ async function loadData(id, args) { ${icon.down} ${layerData.title} `) - data.layers[layer] = sortBy(data.layers[layer], [ - {key: "in", operator: "+"}, - {key: "created", operator: "+"} - ]) data.layers[layer].forEach(annotation => { if (pandora.url) { annotation.value = annotation.value.replace( diff --git a/static/mobile/js/main.js b/static/mobile/js/main.js index 421eacc2..c84b5260 100644 --- a/static/mobile/js/main.js +++ b/static/mobile/js/main.js @@ -67,7 +67,6 @@ 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 40422e55..307c5d2f 100644 --- a/static/mobile/js/render.js +++ b/static/mobile/js/render.js @@ -75,8 +75,7 @@ function renderItem(data) { var video = window.video = VideoPlayer({ items: data.videos, poster: data.poster, - "in": data["in"] || 0, - position: 0, + position: data["in"] || 0, duration: data.duration, aspectratio: data.aspectratio }) @@ -86,10 +85,16 @@ function renderItem(data) { video.addEventListener("loadedmetadata", event => { // }) - - function updateAnnotations(currentTime) { + video.addEventListener("timeupdate", event => { + var currentTime = video.currentTime() + if (currentTime >= data['out']) { + if (!video.paused) { + video.pause() + } + video.currentTime(data['in']) + } div.querySelectorAll('.annotation').forEach(annot => { - var now = currentTime + (data["in"] || 0) + var now = currentTime var start = parseFloat(annot.dataset.in) var end = parseFloat(annot.dataset.out) if (now >= start && now <= end) { @@ -102,18 +107,8 @@ 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/static/mobile/js/utils.js b/static/mobile/js/utils.js index 92d6c806..cbaec6a6 100644 --- a/static/mobile/js/utils.js +++ b/static/mobile/js/utils.js @@ -125,10 +125,7 @@ const clickLink = function(event) { } document.location.hash = '#' + link.slice(1) } else { - if (link.includes('/download/')) { - document.location.href = link - return - } else if (!link.startsWith('/m')) { + if (!link.startsWith('/m')) { link = '/m' + link } history.pushState({}, '', link); @@ -164,59 +161,3 @@ const getVideoURL = function(id, resolution, part, track, streamId) { return prefix + '/' + getVideoURLName(id, resolution, part, track, streamId); }; -const getSortValue = function(value) { - var sortValue = value; - function trim(value) { - return value.replace(/^\W+(?=\w)/, ''); - } - if ( - isEmpty(value) - || isNull(value) - || isUndefined(value) - ) { - sortValue = null; - } else if (isString(value)) { - // make lowercase and remove leading non-word characters - sortValue = trim(value.toLowerCase()); - // move leading articles to the end - // and remove leading non-word characters - ['a', 'an', 'the'].forEach(function(article) { - if (new RegExp('^' + article + ' ').test(sortValue)) { - sortValue = trim(sortValue.slice(article.length + 1)) - + ', ' + sortValue.slice(0, article.length); - return false; // break - } - }); - // remove thousand separators and pad numbers - sortValue = sortValue.replace(/(\d),(?=(\d{3}))/g, '$1') - .replace(/\d+/g, function(match) { - return match.padStart(64, '0') - }); - } - return sortValue; -}; - -function sortBy(array, by, map) { - return array.sort(function(a, b) { - var aValue, bValue, index = 0, key, ret = 0; - while (ret == 0 && index < by.length) { - key = by[index].key; - aValue = getSortValue( - map && map[key] ? map[key](a[key], a) : a[key] - ); - bValue = getSortValue( - map && map[key] ? map[key](b[key], b) : b[key] - ); - if ((aValue === null) != (bValue === null)) { - ret = aValue === null ? 1 : -1; - } else if (aValue < bValue) { - ret = by[index].operator == '+' ? -1 : 1; - } else if (aValue > bValue) { - ret = by[index].operator == '+' ? 1 : -1; - } else { - index++; - } - } - return ret; - }); -} diff --git a/update.py b/update.py index c5e7603e..04aaaa30 100755 --- a/update.py +++ b/update.py @@ -302,10 +302,6 @@ if __name__ == "__main__": if old <= 6581: run('./bin/pip', 'install', '-U', 'pip') 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() @@ -326,7 +322,6 @@ if __name__ == "__main__": current_branch = get_branch(path) revno = get_version(path) if repo == 'pandora': - print("Pandora Version pre update: ", revno) pandora_old_revno = revno current += revno if current_branch != branch: @@ -350,7 +345,6 @@ if __name__ == "__main__": new += '+' os.chdir(join(base, 'pandora')) if pandora_old_revno != pandora_new_revno: - print("Pandora Version post update: ", pandora_new_revno) os.chdir(base) run('./update.py', 'postupdate', pandora_old_revno, pandora_new_revno) os.chdir(join(base, 'pandora')) @@ -367,7 +361,7 @@ if __name__ == "__main__": and row not in ['BEGIN;', 'COMMIT;'] ] if diff: - print('Database has changed, please make a backup and run: sudo pandoractl update db') + print('Database has changed, please make a backup and run %s db' % sys.argv[0]) elif branch != 'master': print('pan.do/ra is at the latest release,\nyou can run "%s switch master" to switch to the development version' % sys.argv[0]) elif current != new: diff --git a/vm/pandora_install.sh b/vm/pandora_install.sh index 38d528f9..69d52adf 100755 --- a/vm/pandora_install.sh +++ b/vm/pandora_install.sh @@ -25,20 +25,53 @@ LXC=`grep -q lxc /proc/1/environ && echo 'yes' || echo 'no'` if [ -e /etc/os-release ]; then . /etc/os-release fi +if [ -z "$UBUNTU_CODENAME" ]; then + UBUNTU_CODENAME=bionic +fi +if [ "$VERSION_CODENAME" = "bullseye" ]; then + UBUNTU_CODENAME=focal +fi +if [ "$VERSION_CODENAME" = "bookworm" ]; then + UBUNTU_CODENAME=lunar +fi export DEBIAN_FRONTEND=noninteractive +echo "deb http://ppa.launchpad.net/j/pandora/ubuntu ${UBUNTU_CODENAME} main" > /etc/apt/sources.list.d/j-pandora.list -apt-get install -y gnupg curl +apt-get install -y gnupg -distribution=bookworm -for version in bookworm trixie bionic focal jammy noble; do - if [ "$VERSION_CODENAME" = $version ]; then - distribution=$VERSION_CODENAME - fi -done +if [ -e /etc/apt/trusted.gpg.d ]; then +gpg --dearmor > /etc/apt/trusted.gpg.d/j-pandora.gpg < /etc/apt/sources.list.d/pandora.list +mI0ESXYhEgEEALl9jDTdmgpApPbjN+7b85dC92HisPUp56ifEkKJOBj0X5HhRqxs +Wjx/zlP4/XJGrHnxJyrdPxjSwAXz7bNdeggkN4JWdusTkr5GOXvggQnng0X7f/rX +oJwoEGtYOCODLPs6PC0qjh5yPzJVeiRsKUOZ7YVNnwNwdfS4D8RZvtCrABEBAAG0 +FExhdW5jaHBhZCBQUEEgZm9yIGpeiLYEEwECACAFAkl2IRICGwMGCwkIBwMCBBUC +CAMEFgIDAQIeAQIXgAAKCRAohRM8AZde82FfA/9OB/64/YLaCpizHZ8f6DK3rGgF +e6mX3rFK8yOKGGL06316VhDzfzMiZSauUZ0t+lKHR/KZYeSaFwEoUoblTG/s4IIo +9aBMHWhVXJW6eifKUmTGqEn2/0UxoWQq2C3F6njMkCaP+ALOD5uzaSYGdjqAUAwS +pAAGSEQ4uz6bYSeM4Q== +=SM2a +-----END PGP PUBLIC KEY BLOCK----- +EOF +else +apt-key add - < /etc/apt/apt.conf.d/99languages apt-get update -qq