From 977f996d8e7f9bf5e680f4e9c981cdba60b08735 Mon Sep 17 00:00:00 2001 From: KuhnChris Date: Mon, 13 Feb 2023 07:46:16 +0100 Subject: [PATCH 1/8] Adding new "manual_skip" field; adapt UI --- .../migrations/0015_auto_20230213_0603.py | 23 +++++++++++++++++++ tubesync/sync/models.py | 8 ++++++- tubesync/sync/signals.py | 4 ++++ tubesync/sync/tasks.py | 5 ++++ tubesync/sync/templates/sync/media-item.html | 13 +++++++---- tubesync/sync/templates/sync/media.html | 6 +++-- tubesync/sync/views.py | 10 ++++---- 7 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 tubesync/sync/migrations/0015_auto_20230213_0603.py diff --git a/tubesync/sync/migrations/0015_auto_20230213_0603.py b/tubesync/sync/migrations/0015_auto_20230213_0603.py new file mode 100644 index 0000000..54592f9 --- /dev/null +++ b/tubesync/sync/migrations/0015_auto_20230213_0603.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.17 on 2023-02-13 06:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sync', '0014_alter_media_media_file'), + ] + + operations = [ + migrations.AddField( + model_name='media', + name='manual_skip', + field=models.BooleanField(db_index=True, default=False, help_text='Media marked as "skipped", won\' be downloaded', verbose_name='manual_skip'), + ), + migrations.AlterField( + model_name='media', + name='skip', + field=models.BooleanField(db_index=True, default=False, help_text='INTERNAL FLAG - Media will be skipped and not downloaded', verbose_name='skip'), + ), + ] diff --git a/tubesync/sync/models.py b/tubesync/sync/models.py index 16415c7..342a8a3 100644 --- a/tubesync/sync/models.py +++ b/tubesync/sync/models.py @@ -679,7 +679,13 @@ class Media(models.Model): _('skip'), db_index=True, default=False, - help_text=_('Media will be skipped and not downloaded') + help_text=_('INTERNAL FLAG - Media will be skipped and not downloaded') + ) + manual_skip = models.BooleanField( + _('manual_skip'), + db_index=True, + default=False, + help_text=_('Media marked as "skipped", won\' be downloaded') ) downloaded = models.BooleanField( _('downloaded'), diff --git a/tubesync/sync/signals.py b/tubesync/sync/signals.py index 9e417ff..6800a2f 100644 --- a/tubesync/sync/signals.py +++ b/tubesync/sync/signals.py @@ -93,6 +93,10 @@ def task_task_failed(sender, task_id, completed_task, **kwargs): @receiver(post_save, sender=Media) def media_post_save(sender, instance, created, **kwargs): + # If the media is skipped manually, bail. + if instance.manual_skip: + return + # Triggered after media is saved cap_changed = False can_download_changed = False diff --git a/tubesync/sync/tasks.py b/tubesync/sync/tasks.py index a597d11..8601915 100644 --- a/tubesync/sync/tasks.py +++ b/tubesync/sync/tasks.py @@ -223,6 +223,11 @@ def download_media_metadata(media_id): log.error(f'Task download_media_metadata(pk={media_id}) called but no ' f'media exists with ID: {media_id}') return + + if media.manual_skip: + log.info(f'Task for ID: {media_id} skipped, due to task being manually skipped.') + return + source = media.source metadata = media.index_metadata() media.metadata = json.dumps(metadata, default=json_serial) diff --git a/tubesync/sync/templates/sync/media-item.html b/tubesync/sync/templates/sync/media-item.html index c2e83dd..4f0e524 100644 --- a/tubesync/sync/templates/sync/media-item.html +++ b/tubesync/sync/templates/sync/media-item.html @@ -22,8 +22,11 @@ {% endif %} -{% if not media.can_download %}{% include 'errorbox.html' with message='Media cannot be downloaded because it has no formats which match the source requirements.' %}{% endif %} -{% if media.skip %}{% include 'errorbox.html' with message='Media is marked to be skipped and will not be downloaded.' %}{% endif %} +{% if media.manual_skip %}{% include 'errorbox.html' with message='Media is marked to be skipped and will not be downloaded.' %} +{% else %} + {% if not media.can_download %}{% include 'errorbox.html' with message='Media cannot be downloaded because it has no formats which match the source requirements.' %}{% endif %} + {% if media.skip %}{% include 'errorbox.html' with message='This media may be skipped due to error(s).' %}{% endif %} +{% endif %} {% include 'infobox.html' with message=message %}
@@ -167,10 +170,10 @@ {% else %}
- {% if media.skip %} - Enable (unskip) media + {% if media.manual_skip %} + Unskip media (manually) {% else %} - Skip media + Manually mark media to be skipped {% endif %}
diff --git a/tubesync/sync/templates/sync/media.html b/tubesync/sync/templates/sync/media.html index a210458..420b15b 100644 --- a/tubesync/sync/templates/sync/media.html +++ b/tubesync/sync/templates/sync/media.html @@ -36,8 +36,10 @@ {% if m.downloaded %} {{ m.download_date|date:'Y-m-d' }} {% else %} - {% if m.skip %} - Skipped + {% if m.manual_skip %} + Manually skipped + {% elif m.skip %} + Skipped by system {% elif not m.source.download_media %} Disabled at source {% elif not m.has_metadata %} diff --git a/tubesync/sync/views.py b/tubesync/sync/views.py index 912dd22..86717e9 100644 --- a/tubesync/sync/views.py +++ b/tubesync/sync/views.py @@ -486,16 +486,16 @@ class MediaView(ListView): if self.show_skipped: q = Media.objects.filter(source=self.filter_source) elif self.only_skipped: - q = Media.objects.filter(source=self.filter_source, skip=True) + q = Media.objects.filter(Q(source=self.filter_source) & (Q(skip=True) | Q(manual_skip=True))) else: - q = Media.objects.filter(source=self.filter_source, skip=False) + q = Media.objects.filter(Q(source=self.filter_source) & (Q(skip=False) & Q(manual_skip=False))) else: if self.show_skipped: q = Media.objects.all() elif self.only_skipped: - q = Media.objects.filter(skip=True) + q = Media.objects.filter(Q(skip=True)|Q(manual_skip=True)) else: - q = Media.objects.filter(skip=False) + q = Media.objects.filter(Q(skip=False)&Q(manual_skip=False)) return q.order_by('-published', '-created') def get_context_data(self, *args, **kwargs): @@ -667,6 +667,7 @@ class MediaSkipView(FormView, SingleObjectMixin): self.object.downloaded_filesize = None # Mark it to be skipped self.object.skip = True + self.object.manual_skip = True self.object.save() return super().form_valid(form) @@ -695,6 +696,7 @@ class MediaEnableView(FormView, SingleObjectMixin): def form_valid(self, form): # Mark it as not skipped self.object.skip = False + self.object.manual_skip = False self.object.save() return super().form_valid(form) From 931aa78815ab1be4886f977261d4c1c96ea9531c Mon Sep 17 00:00:00 2001 From: KuhnChris Date: Tue, 14 Feb 2023 22:06:15 +0100 Subject: [PATCH 2/8] align (i) better with text (+ checkbox less wide) --- .../styles/materializecss/components/forms/_checkboxes.scss | 2 +- tubesync/common/static/styles/tubesync.scss | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tubesync/common/static/styles/materializecss/components/forms/_checkboxes.scss b/tubesync/common/static/styles/materializecss/components/forms/_checkboxes.scss index ddc7d96..22a0e33 100644 --- a/tubesync/common/static/styles/materializecss/components/forms/_checkboxes.scss +++ b/tubesync/common/static/styles/materializecss/components/forms/_checkboxes.scss @@ -14,7 +14,7 @@ // Text Label Style + span:not(.lever) { position: relative; - padding-left: 35px; + padding-left: 27px; cursor: pointer; display: inline-block; height: 25px; diff --git a/tubesync/common/static/styles/tubesync.scss b/tubesync/common/static/styles/tubesync.scss index cdf89ec..30a41fd 100644 --- a/tubesync/common/static/styles/tubesync.scss +++ b/tubesync/common/static/styles/tubesync.scss @@ -26,4 +26,7 @@ html { .flex-grow { flex-grow: 1; } - \ No newline at end of file + +.help-text > i { + padding-right: 6px; +} \ No newline at end of file From 35678e3be95dd528fa2655efe985d48103db7a9a Mon Sep 17 00:00:00 2001 From: meeb Date: Sat, 18 Feb 2023 12:54:39 +1100 Subject: [PATCH 3/8] temporarily disable sponsorblock by default pending #338 --- tubesync/sync/youtube.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tubesync/sync/youtube.py b/tubesync/sync/youtube.py index 08b60fe..1a70217 100644 --- a/tubesync/sync/youtube.py +++ b/tubesync/sync/youtube.py @@ -100,7 +100,17 @@ def download_media(url, media_format, extension, output_file, info_json, sponsor else: log.warn(f'[youtube-dl] unknown event: {str(event)}') hook.download_progress = 0 - + postprocessors = [] + postprocessors.append({ + 'key': 'FFmpegMetadata', + 'add_chapters': True, + 'add_metadata': True + }) + # Pending configuration options from PR #338 + #postprocessors.append({ + # 'key': 'SponsorBlock', + # 'categories': [sponsor_categories] + #}) opts = get_yt_opts() opts.update({ 'format': media_format, @@ -109,14 +119,7 @@ def download_media(url, media_format, extension, output_file, info_json, sponsor 'quiet': True, 'progress_hooks': [hook], 'writeinfojson': info_json, - 'postprocessors': [{ - 'key': 'SponsorBlock', - 'categories': [sponsor_categories] - },{ - 'key': 'FFmpegMetadata', - 'add_chapters': True, - 'add_metadata': True - }] + 'postprocessors': postprocessors, }) with yt_dlp.YoutubeDL(opts) as y: try: From 8315efac03e96b4b35e91bfa1e279622a3201734 Mon Sep 17 00:00:00 2001 From: meeb Date: Sat, 18 Feb 2023 12:59:57 +1100 Subject: [PATCH 4/8] bump to 0.12.1, resolves #340 and #341 --- tubesync/tubesync/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tubesync/tubesync/settings.py b/tubesync/tubesync/settings.py index f56aeaa..de7a1bc 100644 --- a/tubesync/tubesync/settings.py +++ b/tubesync/tubesync/settings.py @@ -6,7 +6,7 @@ CONFIG_BASE_DIR = BASE_DIR DOWNLOADS_BASE_DIR = BASE_DIR -VERSION = '0.12.0' +VERSION = '0.12.1' SECRET_KEY = '' DEBUG = False ALLOWED_HOSTS = [] From d8a9572411044008765d340a98cd172e7b67a7f5 Mon Sep 17 00:00:00 2001 From: meeb Date: Sat, 18 Feb 2023 13:14:25 +1100 Subject: [PATCH 5/8] bump ffmpeg, fix container build --- Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8896c0a..22a8995 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,8 @@ FROM debian:bullseye-slim ARG TARGETPLATFORM ARG S6_VERSION="3.1.2.1" -ARG FFMPEG_DATE="autobuild-2023-01-03-12-55" -ARG FFMPEG_VERSION="109474-gc94988a781" +ARG FFMPEG_DATE="autobuild-2023-02-17-12-59" +ARG FFMPEG_VERSION="109874-gaeceefa622" ENV DEBIAN_FRONTEND="noninteractive" \ HOME="/root" \ @@ -27,8 +27,8 @@ RUN export ARCH=$(case ${TARGETPLATFORM:-linux/amd64} in \ "linux/arm64") echo "https://github.com/just-containers/s6-overlay/releases/download/v${S6_VERSION}/s6-overlay-aarch64.tar.xz" ;; \ *) echo "" ;; esac) && \ export FFMPEG_EXPECTED_SHA256=$(case ${TARGETPLATFORM:-linux/amd64} in \ - "linux/amd64") echo "ed9059668e4a6dac9bde122a775f52ad08cbb90df3658f8c1e328477c13c242e" ;; \ - "linux/arm64") echo "dd1375bd351d38ea1cc3efd68a998699366e28bd9b90df65d11af2b9121746b7" ;; \ + "linux/amd64") echo "f855e481b78aed625eb729e7a53bb7e84e07f76ab64c8bf48a30e631f88e2ff5" ;; \ + "linux/arm64") echo "9439235e4db6dcd99685d3c51223413e4f89f25e27f7aa7fe17a53c1a3d3ca9f" ;; \ *) echo "" ;; esac) && \ export FFMPEG_DOWNLOAD=$(case ${TARGETPLATFORM:-linux/amd64} in \ "linux/amd64") echo "https://github.com/yt-dlp/FFmpeg-Builds/releases/download/${FFMPEG_DATE}/ffmpeg-N-${FFMPEG_VERSION}-linux64-gpl.tar.xz" ;; \ From 1d5579aa312b8c58cf2847d5a07e5a58980c1309 Mon Sep 17 00:00:00 2001 From: KuhnChris Date: Tue, 14 Feb 2023 21:52:50 +0100 Subject: [PATCH 6/8] Phase 1 - extend model for new fields --- .gitignore | 3 +- Pipfile | 1 + .../migrations/0015_auto_20230214_2052.py | 34 +++++++ tubesync/sync/models.py | 95 +++++++++++++++++++ .../templates/widgets/checkbox_option.html | 7 ++ .../templates/widgets/checkbox_select.html | 5 + tubesync/sync/views.py | 4 +- tubesync/sync/youtube.py | 2 +- 8 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 tubesync/sync/migrations/0015_auto_20230214_2052.py create mode 100644 tubesync/sync/templates/widgets/checkbox_option.html create mode 100644 tubesync/sync/templates/widgets/checkbox_select.html diff --git a/.gitignore b/.gitignore index e6e892e..eec472f 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,5 @@ dmypy.json # Pyre type checker .pyre/ -Pipfile.lock \ No newline at end of file +Pipfile.lock +.vscode/launch.json diff --git a/Pipfile b/Pipfile index 243f0f3..21e4e49 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +autopep8 = "*" [packages] django = "~=3.2" diff --git a/tubesync/sync/migrations/0015_auto_20230214_2052.py b/tubesync/sync/migrations/0015_auto_20230214_2052.py new file mode 100644 index 0000000..aab006f --- /dev/null +++ b/tubesync/sync/migrations/0015_auto_20230214_2052.py @@ -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'))), + ), + ] diff --git a/tubesync/sync/models.py b/tubesync/sync/models.py index 342a8a3..17c2942 100644 --- a/tubesync/sync/models.py +++ b/tubesync/sync/models.py @@ -7,6 +7,7 @@ from datetime import datetime, timedelta from pathlib import Path from django.conf import settings from django.db import models +from django.forms import MultipleChoiceField, CheckboxSelectMultiple from django.core.files.storage import FileSystemStorage from django.utils.text import slugify 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/') +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): ''' @@ -106,6 +164,43 @@ class Source(models.Model): EXTENSION_MKV = '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 ICONS = { SOURCE_TYPE_YOUTUBE_CHANNEL: '', diff --git a/tubesync/sync/templates/widgets/checkbox_option.html b/tubesync/sync/templates/widgets/checkbox_option.html new file mode 100644 index 0000000..8578ecc --- /dev/null +++ b/tubesync/sync/templates/widgets/checkbox_option.html @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/tubesync/sync/templates/widgets/checkbox_select.html b/tubesync/sync/templates/widgets/checkbox_select.html new file mode 100644 index 0000000..f621908 --- /dev/null +++ b/tubesync/sync/templates/widgets/checkbox_select.html @@ -0,0 +1,5 @@ +{% for group, options, index in widget.optgroups %} + {% for option in options %} + {% include option.template_name with option=option %} + {% endfor%} +{% endfor %} \ No newline at end of file diff --git a/tubesync/sync/views.py b/tubesync/sync/views.py index 86717e9..58bb465 100644 --- a/tubesync/sync/views.py +++ b/tubesync/sync/views.py @@ -297,7 +297,9 @@ class EditSourceMixin: fields = ('source_type', 'key', 'name', 'directory', 'media_format', 'index_schedule', 'download_media', 'download_cap', 'delete_old_media', '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 = { 'invalid_media_format': _('Invalid media format, the media format contains ' 'errors or is empty. Check the table at the end of ' diff --git a/tubesync/sync/youtube.py b/tubesync/sync/youtube.py index 1a70217..bf01f2d 100644 --- a/tubesync/sync/youtube.py +++ b/tubesync/sync/youtube.py @@ -64,7 +64,7 @@ def get_media_info(url): 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. ''' From c927f32aa6b0e443b273091a8321a7bd2bad584f Mon Sep 17 00:00:00 2001 From: KuhnChris Date: Wed, 15 Feb 2023 00:01:44 +0100 Subject: [PATCH 7/8] ffmpeg embed thumbnails, configuration --- tubesync/sync/models.py | 101 +++++++++++++----- .../templates/widgets/checkbox_option.html | 2 +- .../templates/widgets/checkbox_select.html | 10 +- tubesync/sync/youtube.py | 36 +++++-- 4 files changed, 105 insertions(+), 44 deletions(-) diff --git a/tubesync/sync/models.py b/tubesync/sync/models.py index 17c2942..a438b5c 100644 --- a/tubesync/sync/models.py +++ b/tubesync/sync/models.py @@ -1,4 +1,5 @@ import os +from typing import Any, Optional, Dict import uuid import json from xml.etree import ElementTree @@ -7,7 +8,7 @@ from datetime import datetime, timedelta from pathlib import Path from django.conf import settings 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.utils.text import slugify 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/') -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): + 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" - def __init__(self, separator=",", possible_choices=(("","")), *args, **kwargs): - print(">",separator, possible_choices, args, kwargs) + def __init__(self, separator=",", possible_choices=(("","")), all_choice="", all_label="All", allow_all=False, *args, **kwargs): + self.separator = separator 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): 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 @@ -63,25 +71,54 @@ class CommaSepChoiceField(CommaSepField): def db_type(self, _connection): return 'char(1024)' - def get_choices(self): + def get_my_choices(self): choiceArray = [] if self.possible_choices is None: return choiceArray + if self.allow_all: + choiceArray.append((self.all_choice, _(self.all_label))) + 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} + 'choices': self.get_my_choices, + 'widget': CustomCheckboxSelectMultiple, + 'label': '', + 'required': False} defaults.update(kwargs) #del defaults.required 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): ''' 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 SPONSORBLOCK_CATEGORIES_CHOICES = ( - ('all', 'All'), ('sponsor', 'Sponsor'), ('intro', 'Intermission/Intro Animation'), ('outro', 'Endcards/Credits'), @@ -179,8 +215,13 @@ class Source(models.Model): ) sponsorblock_categories = CommaSepChoiceField( + _(''), 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( @@ -1386,7 +1427,9 @@ class Media(models.Model): f'no valid format available') # Download the media with youtube-dl 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 format_str, self.source.extension diff --git a/tubesync/sync/templates/widgets/checkbox_option.html b/tubesync/sync/templates/widgets/checkbox_option.html index 8578ecc..06a6723 100644 --- a/tubesync/sync/templates/widgets/checkbox_option.html +++ b/tubesync/sync/templates/widgets/checkbox_option.html @@ -2,6 +2,6 @@ --> \ No newline at end of file diff --git a/tubesync/sync/templates/widgets/checkbox_select.html b/tubesync/sync/templates/widgets/checkbox_select.html index f621908..ded69d0 100644 --- a/tubesync/sync/templates/widgets/checkbox_select.html +++ b/tubesync/sync/templates/widgets/checkbox_select.html @@ -1,5 +1,5 @@ -{% for group, options, index in widget.optgroups %} - {% for option in options %} - {% include option.template_name with option=option %} - {% endfor%} -{% endfor %} \ No newline at end of file + +{% for option in widget.multipleChoiceProperties %} + {% include option.template_name with option=option %} +{% endfor %} +