ffmpeg embed thumbnails, configuration

This commit is contained in:
KuhnChris 2023-02-15 00:01:44 +01:00
parent 1d5579aa31
commit c927f32aa6
4 changed files with 105 additions and 44 deletions

View File

@ -1,4 +1,5 @@
import os import os
from typing import Any, Optional, Dict
import uuid import uuid
import json import json
from xml.etree import ElementTree from xml.etree import ElementTree
@ -7,7 +8,7 @@ from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
from django.conf import settings from django.conf import settings
from django.db import models from django.db import models
from django.forms import MultipleChoiceField, CheckboxSelectMultiple from django.forms import MultipleChoiceField, CheckboxSelectMultiple, Field, TypedMultipleChoiceField
from django.core.files.storage import FileSystemStorage from django.core.files.storage import FileSystemStorage
from django.utils.text import slugify from django.utils.text import slugify
from django.utils import timezone from django.utils import timezone
@ -24,37 +25,44 @@ from .mediaservers import PlexMediaServer
media_file_storage = FileSystemStorage(location=str(settings.DOWNLOAD_ROOT), base_url='/media-data/') media_file_storage = FileSystemStorage(location=str(settings.DOWNLOAD_ROOT), base_url='/media-data/')
class CommaSepField(models.Field):
"Implements comma-separated storage of lists"
def __init__(self, separator=",", *args, **kwargs):
self.separator = separator
super().__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
# Only include kwarg if it's not the default
if self.separator != ",":
kwargs['separator'] = self.separator
return name, path, args, kwargs
class CustomCheckboxSelectMultiple(CheckboxSelectMultiple): class CustomCheckboxSelectMultiple(CheckboxSelectMultiple):
template_name = 'widgets/checkbox_select.html' template_name = 'widgets/checkbox_select.html'
option_template_name = 'widgets/checkbox_option.html' option_template_name = 'widgets/checkbox_option.html'
class CommaSepChoiceField(CommaSepField): def get_context(self, name: str, value: Any, attrs) -> Dict[str, Any]:
ctx = super().get_context(name, value, attrs)['widget']
ctx["multipleChoiceProperties"] = []
for _group, options, _index in ctx["optgroups"]:
for option in options:
if not isinstance(value,list) and ( option["value"] in value.selected_choices or ( value.allow_all and value.all_choice in value.selected_choices ) ):
checked = True
else:
checked = False
ctx["multipleChoiceProperties"].append({
"template_name": option["template_name"],
"type": option["type"],
"value": option["value"],
"label": option["label"],
"name": option["name"],
"checked": checked})
return { 'widget': ctx }
class CommaSepChoiceField(models.Field):
"Implements comma-separated storage of lists" "Implements comma-separated storage of lists"
def __init__(self, separator=",", possible_choices=(("","")), *args, **kwargs): def __init__(self, separator=",", possible_choices=(("","")), all_choice="", all_label="All", allow_all=False, *args, **kwargs):
print(">",separator, possible_choices, args, kwargs) self.separator = separator
self.possible_choices = possible_choices self.possible_choices = possible_choices
super().__init__(separator=separator, *args, **kwargs) self.selected_choices = []
self.allow_all = allow_all
self.all_label = all_label
self.all_choice = all_choice
super().__init__(*args, **kwargs)
def deconstruct(self): def deconstruct(self):
name, path, args, kwargs = super().deconstruct() name, path, args, kwargs = super().deconstruct()
print("<",name,path,args,kwargs)
# Only include kwarg if it's not the default
if self.separator != ",": if self.separator != ",":
kwargs['separator'] = self.separator kwargs['separator'] = self.separator
kwargs['possible_choices'] = self.possible_choices kwargs['possible_choices'] = self.possible_choices
@ -63,25 +71,54 @@ class CommaSepChoiceField(CommaSepField):
def db_type(self, _connection): def db_type(self, _connection):
return 'char(1024)' return 'char(1024)'
def get_choices(self): def get_my_choices(self):
choiceArray = [] choiceArray = []
if self.possible_choices is None: if self.possible_choices is None:
return choiceArray return choiceArray
if self.allow_all:
choiceArray.append((self.all_choice, _(self.all_label)))
for t in self.possible_choices: for t in self.possible_choices:
choiceArray.append(t) choiceArray.append(t)
return choiceArray return choiceArray
def formfield(self, **kwargs): def formfield(self, **kwargs):
# This is a fairly standard way to set up some defaults # This is a fairly standard way to set up some defaults
# while letting the caller override them. # while letting the caller override them.
print(self.choices)
defaults = {'form_class': MultipleChoiceField, defaults = {'form_class': MultipleChoiceField,
'choices': self.get_choices, 'choices': self.get_my_choices,
'widget': CustomCheckboxSelectMultiple} 'widget': CustomCheckboxSelectMultiple,
'label': '',
'required': False}
defaults.update(kwargs) defaults.update(kwargs)
#del defaults.required #del defaults.required
return super().formfield(**defaults) return super().formfield(**defaults)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
# Only include kwarg if it's not the default
if self.separator != ",":
kwargs['separator'] = self.separator
return name, path, args, kwargs
def from_db_value(self, value, expr, conn):
if value is None:
self.selected_choices = []
else:
self.selected_choices = value.split(",")
return self
def get_prep_value(self, value):
if value is None:
return ""
if not isinstance(value,list):
print("?! CommaSepChoiceField -> ",value)
return ""
return ",".join(value)
class Source(models.Model): class Source(models.Model):
''' '''
A Source is a source of media. Currently, this is either a YouTube channel A Source is a source of media. Currently, this is either a YouTube channel
@ -167,7 +204,6 @@ class Source(models.Model):
# as stolen from: https://wiki.sponsor.ajay.app/w/Types / https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/postprocessor/sponsorblock.py # as stolen from: https://wiki.sponsor.ajay.app/w/Types / https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/postprocessor/sponsorblock.py
SPONSORBLOCK_CATEGORIES_CHOICES = ( SPONSORBLOCK_CATEGORIES_CHOICES = (
('all', 'All'),
('sponsor', 'Sponsor'), ('sponsor', 'Sponsor'),
('intro', 'Intermission/Intro Animation'), ('intro', 'Intermission/Intro Animation'),
('outro', 'Endcards/Credits'), ('outro', 'Endcards/Credits'),
@ -179,8 +215,13 @@ class Source(models.Model):
) )
sponsorblock_categories = CommaSepChoiceField( sponsorblock_categories = CommaSepChoiceField(
_(''),
possible_choices=SPONSORBLOCK_CATEGORIES_CHOICES, possible_choices=SPONSORBLOCK_CATEGORIES_CHOICES,
default="all" all_choice="all",
allow_all=True,
all_label="(all options)",
default="all",
help_text=_("Select the sponsorblocks you want to enforce")
) )
embed_metadata = models.BooleanField( embed_metadata = models.BooleanField(
@ -1386,7 +1427,9 @@ class Media(models.Model):
f'no valid format available') f'no valid format available')
# Download the media with youtube-dl # Download the media with youtube-dl
download_youtube_media(self.url, format_str, self.source.extension, download_youtube_media(self.url, format_str, self.source.extension,
str(self.filepath), self.source.write_json) str(self.filepath), self.source.write_json,
self.source.sponsorblock_categories, self.source.embed_thumbnail,
self.source.embed_metadata, self.source.enable_sponsorblock)
# Return the download paramaters # Return the download paramaters
return format_str, self.source.extension return format_str, self.source.extension

View File

@ -2,6 +2,6 @@
<label for="{{ option.value }}">{{option.label}}</label>--> <label for="{{ option.value }}">{{option.label}}</label>-->
<label> <label>
<input type="{{ option.type }}" name="{{ option.name }}" value="{{ option.value }}" id="{{ option.value }}"> <input type="{{ option.type }}" name="{{ option.name }}" value="{{ option.value }}" id="{{ option.value }}" {% if option.checked %}checked{% endif %}>
<span>{{option.label}}</span> <span>{{option.label}}</span>
</label> </label>

View File

@ -1,5 +1,5 @@
{% for group, options, index in widget.optgroups %} </label>
{% for option in options %} {% for option in widget.multipleChoiceProperties %}
{% include option.template_name with option=option %} {% include option.template_name with option=option %}
{% endfor%} {% endfor %}
{% endfor %} <label>

View File

@ -64,7 +64,9 @@ def get_media_info(url):
return response return response
def download_media(url, media_format, extension, output_file, info_json, sponsor_categories="all", embed_thumbnail=False, embed_metadata=False, skip_sponsors=True): def download_media(url, media_format, extension, output_file, info_json,
sponsor_categories="all",
embed_thumbnail=False, embed_metadata=False, skip_sponsors=True):
''' '''
Downloads a YouTube URL to a file on disk. Downloads a YouTube URL to a file on disk.
''' '''
@ -100,12 +102,6 @@ def download_media(url, media_format, extension, output_file, info_json, sponsor
else: else:
log.warn(f'[youtube-dl] unknown event: {str(event)}') log.warn(f'[youtube-dl] unknown event: {str(event)}')
hook.download_progress = 0 hook.download_progress = 0
postprocessors = []
postprocessors.append({
'key': 'FFmpegMetadata',
'add_chapters': True,
'add_metadata': True
})
# Pending configuration options from PR #338 # Pending configuration options from PR #338
#postprocessors.append({ #postprocessors.append({
# 'key': 'SponsorBlock', # 'key': 'SponsorBlock',
@ -119,8 +115,30 @@ def download_media(url, media_format, extension, output_file, info_json, sponsor
'quiet': True, 'quiet': True,
'progress_hooks': [hook], 'progress_hooks': [hook],
'writeinfojson': info_json, 'writeinfojson': info_json,
'postprocessors': postprocessors, 'postprocessors': []
}) }
sbopt = {
'key': 'SponsorBlock',
'categories': [sponsor_categories]
}
ffmdopt = {
'key': 'FFmpegMetadata',
'add_chapters': True,
'add_metadata': True
}
opts = get_yt_opts()
if embed_thumbnail:
ytopts['postprocessors'].append({'key': 'EmbedThumbnail'})
if embed_metadata:
ffmdopt["add_metadata"] = True
if skip_sponsors:
ytopts['postprocessors'].append(sbopt)
ytopts['postprocessors'].append(ffmdopt)
opts.update(ytopts)
with yt_dlp.YoutubeDL(opts) as y: with yt_dlp.YoutubeDL(opts) as y:
try: try:
return y.download([url]) return y.download([url])