tweak media thumb downloading, more work on media interface

This commit is contained in:
meeb 2020-12-06 18:33:48 +11:00
parent 6562611a78
commit 60a7305fb7
10 changed files with 61 additions and 37 deletions

View File

@ -41,12 +41,15 @@ $collection-text-colour: $colour-near-black;
$collection-background-hover-colour: $colour-orange; $collection-background-hover-colour: $colour-orange;
$collection-text-hover-colour: $colour-near-white; $collection-text-hover-colour: $colour-near-white;
$mediacard-title-background-colour: $colour-white; $mediacard-title-background-colour: $colour-black;
$mediacard-title-text-colour: $colour-black; $mediacard-title-text-colour: $colour-white;
$box-error-background-colour: $colour-red; $box-error-background-colour: $colour-red;
$box-error-text-colour: $colour-near-white; $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-background-colour: $colour-near-white;
$pagination-text-colour: $colour-near-black; $pagination-text-colour: $colour-near-black;
$pagination-border-colour: $colour-light-blue; $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-background-colour: $colour-orange;
$pagination-current-text-colour: $colour-near-white; $pagination-current-text-colour: $colour-near-white;
$pagination-current-border-colour: $colour-orange; $pagination-current-border-colour: $colour-orange;
$infobox-background-colour: $colour-near-white;
$infobox-text-colour: $colour-near-black;

View File

@ -3,7 +3,7 @@
<div class="col s12"> <div class="col s12">
<div class="pagination"> <div class="pagination">
{% for i in paginator.page_range %} {% for i in paginator.page_range %}
<a class="pagenum{% if i == page_obj.number %} currentpage{% endif %}" href="?page={{ i }}">{{ i }}</a> <a class="pagenum{% if i == page_obj.number %} currentpage{% endif %}" href="?{% if filter %}filter={{ filter }}&{% endif %}page={{ i }}">{{ i }}</a>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>

View File

@ -188,7 +188,7 @@ class Source(models.Model):
max_length=1, max_length=1,
db_index=True, db_index=True,
choices=FALLBACK_CHOICES, 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') 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): if not callable(indexer):
raise Exception(f'Source type f"{self.source_type}" has no indexer') raise Exception(f'Source type f"{self.source_type}" has no indexer')
response = indexer(self.url) 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): def get_media_thumb_path(instance, filename):
@ -426,21 +441,28 @@ class Media(models.Model):
def title(self): def title(self):
return self.loaded_metadata.get('title', '').strip() return self.loaded_metadata.get('title', '').strip()
@property
def name(self):
title = self.title
return title if title else self.key
@property @property
def upload_date(self): def upload_date(self):
upload_date_str = self.loaded_metadata.get('upload_date', '').strip() upload_date_str = self.loaded_metadata.get('upload_date', '').strip()
try: try:
return datetime.strptime(upload_date_str, '%Y%m%d') return datetime.strptime(upload_date_str, '%Y%m%d')
except ValueError as e: except (AttributeError, ValueError) as e:
return None return None
@property @property
def filename(self): 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) source_name = slugify(self.source.name)
title = slugify(self.title.replace('&', 'and').replace('+', 'and')) title = slugify(self.title.replace('&', 'and').replace('+', 'and'))
ext = self.source.extension ext = self.source.extension
fn = f'{upload_date}_{source_name}_{title}'[:100] fn = f'{datestr}_{source_name}_{title}'[:100]
return f'{fn}.{ext}' return f'{fn}.{ext}'
@property @property

View File

@ -43,4 +43,5 @@ def media_post_save(sender, instance, created, **kwargs):
@receiver(post_delete, sender=Media) @receiver(post_delete, sender=Media)
def media_post_delete(sender, instance, **kwargs): def media_post_delete(sender, instance, **kwargs):
# Triggered when media is deleted, delete media thumbnail # Triggered when media is deleted, delete media thumbnail
delete_file(instance.thumb.path) if instance.thumb:
delete_file(instance.thumb.path)

View File

@ -5,9 +5,12 @@
import json import json
import math
from io import BytesIO from io import BytesIO
from PIL import Image
from django.conf import settings from django.conf import settings
from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils import timezone
from background_task import background from background_task import background
from background_task.models import Task from background_task.models import Task
from common.logger import log from common.logger import log
@ -52,6 +55,12 @@ def index_source_task(source_id):
media = Media(key=key) media = Media(key=key)
media.source = source media.source = source
media.metadata = json.dumps(video) 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() media.save()
log.info(f'Indexed media: {source} / {media}') 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 # Task triggered but the media no longer exists, ignore task
return return
i = get_remote_image(url) i = get_remote_image(url)
max_width, max_height = getattr(settings, 'MAX_MEDIA_THUMBNAIL_SIZE', (512, 512)) ratio = i.width / i.height
if i.width > max_width or i.height > max_height: new_height = getattr(settings, 'MEDIA_THUMBNAIL_HEIGHT', 240)
# Image is larger than we want to save, resize it new_width = math.ceil(new_height * ratio)
log.info(f'Resizing thumbnail ({i.width}x{i.height}): {url}') log.info(f'Resizing {i.width}x{i.height} thumbnail to '
i.thumbnail(size=(max_width, max_height)) f'{new_width}x{new_height}: {url}')
i = i.resize((new_width, new_height), Image.ANTIALIAS)
image_file = BytesIO() image_file = BytesIO()
i.save(image_file, 'JPEG', quality=80, optimize=True, progressive=True) i.save(image_file, 'JPEG', quality=80, optimize=True, progressive=True)
image_file.seek(0) image_file.seek(0)

View File

@ -12,7 +12,8 @@
<div class="card-image"> <div class="card-image">
<img src="{% if m.thumb %}{% url 'sync:media-thumb' pk=m.pk %}{% else %}{% static 'images/nothumb.jpg' %} {% endif %}"> <img src="{% if m.thumb %}{% url 'sync:media-thumb' pk=m.pk %}{% else %}{% static 'images/nothumb.jpg' %} {% endif %}">
<span class="card-title truncate">{{ m.source }}<br> <span class="card-title truncate">{{ m.source }}<br>
<span>{{ m }}</span> <span>{{ m.name }}</span><br>
<span>{{ m.published|date:'Y-m-d' }}</span>
</span> </span>
</div> </div>
</div> </div>
@ -26,5 +27,5 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% include 'pagination.html' with pagination=sources.paginator %} {% include 'pagination.html' with pagination=sources.paginator filter=source.pk %}
{% endblock %} {% endblock %}

View File

@ -17,7 +17,7 @@
<div class="collection"> <div class="collection">
{% for source in sources %} {% for source in sources %}
<a href="{% url 'sync:source' pk=source.pk %}" class="collection-item"> <a href="{% url 'sync:source' pk=source.pk %}" class="collection-item">
{{ source.icon|safe }} <strong>{{ source.name }}</strong>, {{ source.get_source_type_display }}<br> {{ source.icon|safe }} <strong>{{ source.name }}</strong> ({{ source.get_source_type_display }})<br>
{{ source.format_summary }}<br> {{ source.format_summary }}<br>
<strong>{{ source.media_count }}</strong> media items{% if source.delete_old_media and source.days_to_keep > 0 %}, keep {{ source.days_to_keep }} days of media{% endif %} <strong>{{ source.media_count }}</strong> media items{% if source.delete_old_media and source.days_to_keep > 0 %}, keep {{ source.days_to_keep }} days of media{% endif %}
</a> </a>

View File

@ -59,16 +59,6 @@ def get_remote_image(url):
return Image.open(r.raw) return Image.open(r.raw)
def path_is_parent(parent_path, child_path):
# Smooth out relative path names, note: if you are concerned about symbolic links, you should use os.path.realpath too
parent_path = os.path.abspath(parent_path)
child_path = os.path.abspath(child_path)
# Compare the common path of the parent and child path with the common path of just the parent path. Using the commonpath method on just the parent path will regularise the path name in the same way as the comparison that deals with both paths, removing any trailing path separator
return os.path.commonpath([parent_path]) == os.path.commonpath([parent_path, child_path])
def file_is_editable(filepath): def file_is_editable(filepath):
''' '''
Checks that a file exists and the file is in an allowed predefined tuple of Checks that a file exists and the file is in an allowed predefined tuple of

View File

@ -304,9 +304,10 @@ class MediaView(ListView):
def get_queryset(self): def get_queryset(self):
if self.filter_source: if self.filter_source:
return Media.objects.filter(source=self.filter_source).order_by('-created') q = Media.objects.filter(source=self.filter_source)
else: else:
return Media.objects.all().order_by('-created') q = Media.objects.all()
return q.order_by('-published', '-created')
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
data = super().get_context_data(*args, **kwargs) data = super().get_context_data(*args, **kwargs)
@ -314,10 +315,8 @@ class MediaView(ListView):
data['source'] = None data['source'] = None
if self.filter_source: if self.filter_source:
message = str(self.messages.get('filter', '')) message = str(self.messages.get('filter', ''))
print(message)
data['message'] = message.format(name=self.filter_source.name) data['message'] = message.format(name=self.filter_source.name)
data['source'] = self.filter_source data['source'] = self.filter_source
print(data)
return data return data

View File

@ -120,19 +120,20 @@ BACKGROUND_TASK_ASYNC_THREADS = 2 # Number of async tasks to run at on
BACKGROUND_TASK_PRIORITY_ORDERING = 'DESC' # Process high priority tasks first BACKGROUND_TASK_PRIORITY_ORDERING = 'DESC' # Process high priority tasks first
SOURCES_PER_PAGE = 25 SOURCES_PER_PAGE = 36
MEDIA_PER_PAGE = 25 MEDIA_PER_PAGE = 36
INDEX_SOURCE_EVERY = 21600 # Seconds between indexing sources, 21600 = every 6 hours INDEX_SOURCE_EVERY = 21600 # Seconds between indexing sources, 21600 = every 6 hours
MAX_MEDIA_THUMBNAIL_SIZE = (320, 240) # Max size in pixels for media thumbnails MEDIA_THUMBNAIL_HEIGHT = 240 # Height in pixels to resize thumbnails to
YOUTUBE_DEFAULTS = { YOUTUBE_DEFAULTS = {
'no_color': True, # Do not use colours in output 'no_color': True, # Do not use colours in output
'age_limit': 99, # 'Age in years' to spoof 'age_limit': 99, # 'Age in years' to spoof
'ignoreerrors': True, # Skip on errors (such as unavailable videos in playlists)
} }