Add option to export channel thumbnails for Jellyfin
This commit is contained in:
parent
3a87b5779e
commit
e9d0599569
1
Pipfile
1
Pipfile
|
@ -23,3 +23,4 @@ yt-dlp = "*"
|
|||
redis = "*"
|
||||
hiredis = "*"
|
||||
requests = {extras = ["socks"], version = "*"}
|
||||
bs4 = "*"
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by nothing. Done manually by InterN0te on 2023-12-10 16:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sync', '0020_auto_20231024_1825'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='source',
|
||||
name='copy_channel_thumbnails',
|
||||
field=models.BooleanField(default=False, help_text='Copy channel thumbnails in poster.jpg and season-poster.jpg, these may be detected and used by some media servers', verbose_name='copy channel thumbnails'),
|
||||
),
|
||||
]
|
|
@ -2,6 +2,8 @@ import os
|
|||
import uuid
|
||||
import json
|
||||
import re
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from xml.etree import ElementTree
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime, timedelta
|
||||
|
@ -342,6 +344,11 @@ class Source(models.Model):
|
|||
default=FALLBACK_NEXT_BEST_HD,
|
||||
help_text=_('What do do when media in your source resolution and codecs is not available')
|
||||
)
|
||||
copy_channel_thumbnails = models.BooleanField(
|
||||
_('copy channel thumbnails'),
|
||||
default=False,
|
||||
help_text=_('Copy channel thumbnails in poster.jpg and season-poster.jpg, these may be detected and used by some media servers')
|
||||
)
|
||||
copy_thumbnails = models.BooleanField(
|
||||
_('copy thumbnails'),
|
||||
default=False,
|
||||
|
@ -482,6 +489,15 @@ class Source(models.Model):
|
|||
def make_directory(self):
|
||||
return os.makedirs(self.directory_path, exist_ok=True)
|
||||
|
||||
@property
|
||||
def get_thumbnail_url(self):
|
||||
if self.source_type==Source.SOURCE_TYPE_YOUTUBE_PLAYLIST:
|
||||
raise Exception('This source is a playlist so it doesn\'t have thumbnail.')
|
||||
soup = BeautifulSoup(requests.get(self.url, cookies={'CONSENT': 'YES+1'}).text, "html.parser")
|
||||
data = re.search(r"var ytInitialData = ({.*});", str(soup.prettify())).group(1)
|
||||
json_data = json.loads(data)
|
||||
return json_data["header"]["c4TabbedHeaderRenderer"]["avatar"]["thumbnails"][2]["url"]
|
||||
|
||||
def directory_exists(self):
|
||||
return (os.path.isdir(self.directory_path) and
|
||||
os.access(self.directory_path, os.W_OK))
|
||||
|
|
|
@ -10,7 +10,7 @@ from .models import Source, Media, MediaServer
|
|||
from .tasks import (delete_task_by_source, delete_task_by_media, index_source_task,
|
||||
download_media_thumbnail, download_media_metadata,
|
||||
map_task_to_instance, check_source_directory_exists,
|
||||
download_media, rescan_media_server)
|
||||
download_media, rescan_media_server, download_source_thumbnail)
|
||||
from .utils import delete_file
|
||||
|
||||
|
||||
|
@ -47,6 +47,12 @@ def source_post_save(sender, instance, created, **kwargs):
|
|||
priority=0,
|
||||
verbose_name=verbose_name.format(instance.name)
|
||||
)
|
||||
if instance.source_type != Source.SOURCE_TYPE_YOUTUBE_PLAYLIST and instance.copy_channel_thumbnails:
|
||||
download_source_thumbnail(
|
||||
str(instance.pk),
|
||||
priority=0,
|
||||
verbose_name=verbose_name.format(instance.name)
|
||||
)
|
||||
if instance.index_schedule > 0:
|
||||
delete_task_by_source('sync.tasks.index_source_task', instance.pk)
|
||||
log.info(f'Scheduling media indexing for source: {instance.name}')
|
||||
|
|
|
@ -14,6 +14,7 @@ from datetime import timedelta, datetime
|
|||
from shutil import copyfile
|
||||
from PIL import Image
|
||||
from django.conf import settings
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.utils import timezone
|
||||
from django.db.utils import IntegrityError
|
||||
|
@ -219,6 +220,42 @@ def check_source_directory_exists(source_id):
|
|||
source.make_directory()
|
||||
|
||||
|
||||
@background(schedule=0)
|
||||
def download_source_thumbnail(source_id):
|
||||
'''
|
||||
Downloads an image and save it as a local thumbnail attached to a
|
||||
Source instance.
|
||||
'''
|
||||
try:
|
||||
source = Source.objects.get(pk=source_id)
|
||||
except Source.DoesNotExist:
|
||||
# Task triggered but the source no longer exists, do nothing
|
||||
log.error(f'Task download_source_thumbnail(pk={source_id}) called but no '
|
||||
f'source exists with ID: {source_id}')
|
||||
return
|
||||
|
||||
url = source.get_thumbnail_url
|
||||
width = 400
|
||||
height = 400
|
||||
i = get_remote_image(url)
|
||||
log.info(f'Resizing {i.width}x{i.height} thumbnail to '
|
||||
f'{width}x{height}: {url}')
|
||||
i = resize_image_to_height(i, width, height)
|
||||
image_file = BytesIO()
|
||||
i.save(image_file, 'JPEG', quality=85, optimize=True, progressive=True)
|
||||
|
||||
for file_name in ["poster.jpg", "season-poster.jpg"]:
|
||||
# Reset file pointer to the beginning for the next save
|
||||
image_file.seek(0)
|
||||
# Create a Django ContentFile from BytesIO stream
|
||||
django_file = ContentFile(image_file.read())
|
||||
file_path = source.directory_path / file_name
|
||||
with open(file_path, 'wb') as f:
|
||||
f.write(django_file.read())
|
||||
|
||||
log.info(f'Thumbnail downloaded from {url} for source with ID: {source_id}')
|
||||
|
||||
|
||||
@background(schedule=0)
|
||||
def download_media_metadata(media_id):
|
||||
'''
|
||||
|
|
|
@ -297,8 +297,8 @@ class EditSourceMixin:
|
|||
fields = ('source_type', 'key', 'name', 'directory', 'filter_text', 'media_format',
|
||||
'index_schedule', 'download_media', 'download_cap', 'delete_old_media',
|
||||
'delete_removed_media', 'days_to_keep', 'source_resolution', 'source_vcodec',
|
||||
'source_acodec', 'prefer_60fps', 'prefer_hdr', 'fallback', 'copy_thumbnails',
|
||||
'write_nfo', 'write_json', 'embed_metadata', 'embed_thumbnail',
|
||||
'source_acodec', 'prefer_60fps', 'prefer_hdr', 'fallback', 'copy_channel_thumbnails',
|
||||
'copy_thumbnails', 'write_nfo', 'write_json', 'embed_metadata', 'embed_thumbnail',
|
||||
'enable_sponsorblock', 'sponsorblock_categories', 'write_subtitles',
|
||||
'auto_subtitles', 'sub_langs')
|
||||
errors = {
|
||||
|
|
Loading…
Reference in New Issue