tweak media thumb downloading, more work on media interface
This commit is contained in:
parent
6562611a78
commit
60a7305fb7
|
@ -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;
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
if instance.thumb:
|
||||||
delete_file(instance.thumb.path)
|
delete_file(instance.thumb.path)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue