Phase 1 - extend model for new fields
This commit is contained in:
parent
f14d2dd29e
commit
24a49d2f14
|
@ -131,4 +131,5 @@ dmypy.json
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
Pipfile.lock
|
Pipfile.lock
|
||||||
|
.vscode/launch.json
|
||||||
|
|
1
Pipfile
1
Pipfile
|
@ -4,6 +4,7 @@ url = "https://pypi.org/simple"
|
||||||
verify_ssl = true
|
verify_ssl = true
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
autopep8 = "*"
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
django = "~=3.2"
|
django = "~=3.2"
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 3.2.18 on 2023-02-14 20:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import sync.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sync', '0014_alter_media_media_file'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='source',
|
||||||
|
name='embed_metadata',
|
||||||
|
field=models.BooleanField(default=False, help_text='Embed metadata from source into file', verbose_name='embed metadata'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='source',
|
||||||
|
name='embed_thumbnail',
|
||||||
|
field=models.BooleanField(default=False, help_text='Embed thumbnail into the file', verbose_name='embed thumbnail'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='source',
|
||||||
|
name='enable_sponsorblock',
|
||||||
|
field=models.BooleanField(default=True, help_text='Use SponsorBlock?', verbose_name='enable sponsorblock'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='source',
|
||||||
|
name='sponsorblock_categories',
|
||||||
|
field=sync.models.CommaSepChoiceField(default='all', possible_choices=(('all', 'All'), ('sponsor', 'Sponsor'), ('intro', 'Intermission/Intro Animation'), ('outro', 'Endcards/Credits'), ('selfpromo', 'Unpaid/Self Promotion'), ('preview', 'Preview/Recap'), ('filler', 'Filler Tangent'), ('interaction', 'Interaction Reminder'), ('music_offtopic', 'Non-Music Section'))),
|
||||||
|
),
|
||||||
|
]
|
|
@ -7,6 +7,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.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
|
||||||
|
@ -23,6 +24,63 @@ 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):
|
||||||
|
template_name = 'widgets/checkbox_select.html'
|
||||||
|
option_template_name = 'widgets/checkbox_option.html'
|
||||||
|
|
||||||
|
class CommaSepChoiceField(CommaSepField):
|
||||||
|
"Implements comma-separated storage of lists"
|
||||||
|
|
||||||
|
def __init__(self, separator=",", possible_choices=(("","")), *args, **kwargs):
|
||||||
|
print(">",separator, possible_choices, args, kwargs)
|
||||||
|
self.possible_choices = possible_choices
|
||||||
|
super().__init__(separator=separator, *args, **kwargs)
|
||||||
|
|
||||||
|
def deconstruct(self):
|
||||||
|
name, path, args, kwargs = super().deconstruct()
|
||||||
|
print("<",name,path,args,kwargs)
|
||||||
|
# Only include kwarg if it's not the default
|
||||||
|
if self.separator != ",":
|
||||||
|
kwargs['separator'] = self.separator
|
||||||
|
kwargs['possible_choices'] = self.possible_choices
|
||||||
|
return name, path, args, kwargs
|
||||||
|
|
||||||
|
def db_type(self, _connection):
|
||||||
|
return 'char(1024)'
|
||||||
|
|
||||||
|
def get_choices(self):
|
||||||
|
choiceArray = []
|
||||||
|
if self.possible_choices is None:
|
||||||
|
return choiceArray
|
||||||
|
for t in self.possible_choices:
|
||||||
|
choiceArray.append(t)
|
||||||
|
return choiceArray
|
||||||
|
|
||||||
|
def formfield(self, **kwargs):
|
||||||
|
# This is a fairly standard way to set up some defaults
|
||||||
|
# while letting the caller override them.
|
||||||
|
print(self.choices)
|
||||||
|
defaults = {'form_class': MultipleChoiceField,
|
||||||
|
'choices': self.get_choices,
|
||||||
|
'widget': CustomCheckboxSelectMultiple}
|
||||||
|
defaults.update(kwargs)
|
||||||
|
#del defaults.required
|
||||||
|
return super().formfield(**defaults)
|
||||||
|
|
||||||
class Source(models.Model):
|
class Source(models.Model):
|
||||||
'''
|
'''
|
||||||
|
@ -106,6 +164,43 @@ class Source(models.Model):
|
||||||
EXTENSION_MKV = 'mkv'
|
EXTENSION_MKV = 'mkv'
|
||||||
EXTENSIONS = (EXTENSION_M4A, EXTENSION_OGG, EXTENSION_MKV)
|
EXTENSIONS = (EXTENSION_M4A, EXTENSION_OGG, EXTENSION_MKV)
|
||||||
|
|
||||||
|
|
||||||
|
# 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 = (
|
||||||
|
('all', 'All'),
|
||||||
|
('sponsor', 'Sponsor'),
|
||||||
|
('intro', 'Intermission/Intro Animation'),
|
||||||
|
('outro', 'Endcards/Credits'),
|
||||||
|
('selfpromo', 'Unpaid/Self Promotion'),
|
||||||
|
('preview', 'Preview/Recap'),
|
||||||
|
('filler', 'Filler Tangent'),
|
||||||
|
('interaction', 'Interaction Reminder'),
|
||||||
|
('music_offtopic', 'Non-Music Section'),
|
||||||
|
)
|
||||||
|
|
||||||
|
sponsorblock_categories = CommaSepChoiceField(
|
||||||
|
possible_choices=SPONSORBLOCK_CATEGORIES_CHOICES,
|
||||||
|
default="all"
|
||||||
|
)
|
||||||
|
|
||||||
|
embed_metadata = models.BooleanField(
|
||||||
|
_('embed metadata'),
|
||||||
|
default=False,
|
||||||
|
help_text=_('Embed metadata from source into file')
|
||||||
|
)
|
||||||
|
embed_thumbnail = models.BooleanField(
|
||||||
|
_('embed thumbnail'),
|
||||||
|
default=False,
|
||||||
|
help_text=_('Embed thumbnail into the file')
|
||||||
|
)
|
||||||
|
|
||||||
|
enable_sponsorblock = models.BooleanField(
|
||||||
|
_('enable sponsorblock'),
|
||||||
|
default=True,
|
||||||
|
help_text=_('Use SponsorBlock?')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Fontawesome icons used for the source on the front end
|
# Fontawesome icons used for the source on the front end
|
||||||
ICONS = {
|
ICONS = {
|
||||||
SOURCE_TYPE_YOUTUBE_CHANNEL: '<i class="fab fa-youtube"></i>',
|
SOURCE_TYPE_YOUTUBE_CHANNEL: '<i class="fab fa-youtube"></i>',
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!--<input type="{{ option.type }}" name="{{ option.name }}" value="{{ option.value }}" id="{{ option.value }}"><BR>
|
||||||
|
<label for="{{ option.value }}">{{option.label}}</label>-->
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<input type="{{ option.type }}" name="{{ option.name }}" value="{{ option.value }}" id="{{ option.value }}">
|
||||||
|
<span>{{option.label}}</span>
|
||||||
|
</label>
|
|
@ -0,0 +1,5 @@
|
||||||
|
{% for group, options, index in widget.optgroups %}
|
||||||
|
{% for option in options %}
|
||||||
|
{% include option.template_name with option=option %}
|
||||||
|
{% endfor%}
|
||||||
|
{% endfor %}
|
|
@ -297,7 +297,9 @@ class EditSourceMixin:
|
||||||
fields = ('source_type', 'key', 'name', 'directory', 'media_format',
|
fields = ('source_type', 'key', 'name', 'directory', 'media_format',
|
||||||
'index_schedule', 'download_media', 'download_cap', 'delete_old_media',
|
'index_schedule', 'download_media', 'download_cap', 'delete_old_media',
|
||||||
'days_to_keep', 'source_resolution', 'source_vcodec', 'source_acodec',
|
'days_to_keep', 'source_resolution', 'source_vcodec', 'source_acodec',
|
||||||
'prefer_60fps', 'prefer_hdr', 'fallback', 'copy_thumbnails', 'write_nfo', 'write_json')
|
'prefer_60fps', 'prefer_hdr', 'fallback', 'copy_thumbnails', 'write_nfo',
|
||||||
|
'write_json', 'embed_metadata', 'embed_thumbnail', 'enable_sponsorblock',
|
||||||
|
'sponsorblock_categories')
|
||||||
errors = {
|
errors = {
|
||||||
'invalid_media_format': _('Invalid media format, the media format contains '
|
'invalid_media_format': _('Invalid media format, the media format contains '
|
||||||
'errors or is empty. Check the table at the end of '
|
'errors or is empty. Check the table at the end of '
|
||||||
|
|
|
@ -64,7 +64,7 @@ def get_media_info(url):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def download_media(url, media_format, extension, output_file, info_json, sponsor_categories="all"):
|
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.
|
||||||
'''
|
'''
|
||||||
|
@ -101,23 +101,37 @@ def download_media(url, media_format, extension, output_file, info_json, sponsor
|
||||||
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
|
||||||
|
|
||||||
opts = get_yt_opts()
|
ytopts = {
|
||||||
opts.update({
|
|
||||||
'format': media_format,
|
'format': media_format,
|
||||||
'merge_output_format': extension,
|
'merge_output_format': extension,
|
||||||
'outtmpl': output_file,
|
'outtmpl': output_file,
|
||||||
'quiet': True,
|
'quiet': True,
|
||||||
'progress_hooks': [hook],
|
'progress_hooks': [hook],
|
||||||
'writeinfojson': info_json,
|
'writeinfojson': info_json,
|
||||||
'postprocessors': [{
|
'postprocessors': []
|
||||||
|
}
|
||||||
|
sbopt = {
|
||||||
'key': 'SponsorBlock',
|
'key': 'SponsorBlock',
|
||||||
'categories': [sponsor_categories]
|
'categories': [sponsor_categories]
|
||||||
},{
|
}
|
||||||
'key': 'FFmpegMetadata',
|
ffmdopt = {
|
||||||
|
'key': 'FFmpegMetadata',
|
||||||
'add_chapters': True,
|
'add_chapters': True,
|
||||||
'add_metadata': True
|
'add_metadata': True
|
||||||
}]
|
}
|
||||||
})
|
|
||||||
|
opts = get_yt_opts()
|
||||||
|
if embed_thumbnail:
|
||||||
|
ytopts['postprocessors'].push({'key': 'EmbedThumbnail'})
|
||||||
|
if embed_metadata:
|
||||||
|
ffmdopt["embed-metadata"] = True
|
||||||
|
if skip_sponsors:
|
||||||
|
ytopts['postprocessors'].push(sbopt)
|
||||||
|
|
||||||
|
ytopts['postprocessors'].push(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])
|
||||||
|
|
Loading…
Reference in New Issue