diff --git a/app/common/static/styles/_colours.scss b/app/common/static/styles/_colours.scss index 9033b60..3b42365 100644 --- a/app/common/static/styles/_colours.scss +++ b/app/common/static/styles/_colours.scss @@ -41,12 +41,15 @@ $collection-text-colour: $colour-near-black; $collection-background-hover-colour: $colour-orange; $collection-text-hover-colour: $colour-near-white; -$mediacard-title-background-colour: $colour-white; -$mediacard-title-text-colour: $colour-black; +$mediacard-title-background-colour: $colour-black; +$mediacard-title-text-colour: $colour-white; $box-error-background-colour: $colour-red; $box-error-text-colour: $colour-near-white; +$infobox-background-colour: $colour-near-black; +$infobox-text-colour: $colour-near-white; + $pagination-background-colour: $colour-near-white; $pagination-text-colour: $colour-near-black; $pagination-border-colour: $colour-light-blue; @@ -56,6 +59,3 @@ $pagination-border-hover-colour: $colour-light-blue; $pagination-current-background-colour: $colour-orange; $pagination-current-text-colour: $colour-near-white; $pagination-current-border-colour: $colour-orange; - -$infobox-background-colour: $colour-near-white; -$infobox-text-colour: $colour-near-black; diff --git a/app/common/templates/pagination.html b/app/common/templates/pagination.html index 938d629..ae8f138 100644 --- a/app/common/templates/pagination.html +++ b/app/common/templates/pagination.html @@ -3,7 +3,7 @@
diff --git a/app/sync/models.py b/app/sync/models.py index 5b46aed..7fedb7f 100644 --- a/app/sync/models.py +++ b/app/sync/models.py @@ -188,7 +188,7 @@ class Source(models.Model): max_length=1, db_index=True, choices=FALLBACK_CHOICES, - default=FALLBACK_FAIL, + default=FALLBACK_NEXT_HD, help_text=_('What do do when media in your source resolution and codecs is not available') ) @@ -269,7 +269,22 @@ class Source(models.Model): if not callable(indexer): raise Exception(f'Source type f"{self.source_type}" has no indexer') response = indexer(self.url) - return response.get('entries', []) + + # Account for nested playlists, such as a channel of playlists of playlists + def _recurse_playlists(playlist): + videos = [] + entries = playlist.get('entries', []) + for entry in entries: + if not entry: + continue + subentries = entry.get('entries', []) + if subentries: + videos = videos + _recurse_playlists(entry) + else: + videos.append(entry) + return videos + + return _recurse_playlists(response) def get_media_thumb_path(instance, filename): @@ -426,21 +441,28 @@ class Media(models.Model): def title(self): return self.loaded_metadata.get('title', '').strip() + @property + def name(self): + title = self.title + return title if title else self.key + @property def upload_date(self): upload_date_str = self.loaded_metadata.get('upload_date', '').strip() try: return datetime.strptime(upload_date_str, '%Y%m%d') - except ValueError as e: + except (AttributeError, ValueError) as e: return None @property def filename(self): - upload_date = self.upload_date.strftime('%Y-%m-%d') + upload_date = self.upload_date + dateobj = upload_date if upload_date else self.created + datestr = dateobj.strftime('%Y-%m-%d') source_name = slugify(self.source.name) title = slugify(self.title.replace('&', 'and').replace('+', 'and')) ext = self.source.extension - fn = f'{upload_date}_{source_name}_{title}'[:100] + fn = f'{datestr}_{source_name}_{title}'[:100] return f'{fn}.{ext}' @property diff --git a/app/sync/signals.py b/app/sync/signals.py index 3dfa5c4..196f050 100644 --- a/app/sync/signals.py +++ b/app/sync/signals.py @@ -43,4 +43,5 @@ def media_post_save(sender, instance, created, **kwargs): @receiver(post_delete, sender=Media) def media_post_delete(sender, instance, **kwargs): # Triggered when media is deleted, delete media thumbnail - delete_file(instance.thumb.path) + if instance.thumb: + delete_file(instance.thumb.path) diff --git a/app/sync/tasks.py b/app/sync/tasks.py index 544043a..fd19244 100644 --- a/app/sync/tasks.py +++ b/app/sync/tasks.py @@ -5,9 +5,12 @@ import json +import math from io import BytesIO +from PIL import Image from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile +from django.utils import timezone from background_task import background from background_task.models import Task from common.logger import log @@ -52,6 +55,12 @@ def index_source_task(source_id): media = Media(key=key) media.source = source media.metadata = json.dumps(video) + upload_date = media.upload_date + if upload_date: + if timezone.is_aware(upload_date): + media.published = upload_date + else: + media.published = timezone.make_aware(upload_date) media.save() log.info(f'Indexed media: {source} / {media}') @@ -68,11 +77,12 @@ def download_media_thumbnail(media_id, url): # Task triggered but the media no longer exists, ignore task return i = get_remote_image(url) - max_width, max_height = getattr(settings, 'MAX_MEDIA_THUMBNAIL_SIZE', (512, 512)) - if i.width > max_width or i.height > max_height: - # Image is larger than we want to save, resize it - log.info(f'Resizing thumbnail ({i.width}x{i.height}): {url}') - i.thumbnail(size=(max_width, max_height)) + ratio = i.width / i.height + new_height = getattr(settings, 'MEDIA_THUMBNAIL_HEIGHT', 240) + new_width = math.ceil(new_height * ratio) + log.info(f'Resizing {i.width}x{i.height} thumbnail to ' + f'{new_width}x{new_height}: {url}') + i = i.resize((new_width, new_height), Image.ANTIALIAS) image_file = BytesIO() i.save(image_file, 'JPEG', quality=80, optimize=True, progressive=True) image_file.seek(0) diff --git a/app/sync/templates/sync/media.html b/app/sync/templates/sync/media.html index d779797..3a97bda 100644 --- a/app/sync/templates/sync/media.html +++ b/app/sync/templates/sync/media.html @@ -12,7 +12,8 @@