From 096c48ce1b6a4ab9f2925e9fb6ae2e6900408d8f Mon Sep 17 00:00:00 2001 From: meeb Date: Fri, 11 Dec 2020 16:11:31 +1100 Subject: [PATCH] refactor media format codes, media skipping and re-enabling feature --- tubesync/common/static/styles/_colours.scss | 1 + tubesync/common/static/styles/_helpers.scss | 4 + tubesync/sync/admin.py | 5 +- tubesync/sync/apps.py | 1 + tubesync/sync/forms.py | 10 ++ .../migrations/0020_auto_20201211_0306.py | 29 +++++ .../migrations/0021_auto_20201211_0351.py | 23 ++++ .../migrations/0022_auto_20201211_0354.py | 38 +++++++ tubesync/sync/migrations/0023_media_skip.py | 18 +++ tubesync/sync/models.py | 106 ++++++++++++++---- tubesync/sync/signals.py | 13 ++- tubesync/sync/tasks.py | 24 +++- .../sync/templates/sync/media-enable.html | 26 +++++ tubesync/sync/templates/sync/media-item.html | 23 +++- tubesync/sync/templates/sync/media-skip.html | 27 +++++ tubesync/sync/templates/sync/media.html | 14 ++- tubesync/sync/urls.py | 12 +- tubesync/sync/utils.py | 1 + tubesync/sync/views.py | 69 +++++++++++- tubesync/sync/youtube.py | 4 +- 20 files changed, 409 insertions(+), 39 deletions(-) create mode 100644 tubesync/sync/migrations/0020_auto_20201211_0306.py create mode 100644 tubesync/sync/migrations/0021_auto_20201211_0351.py create mode 100644 tubesync/sync/migrations/0022_auto_20201211_0354.py create mode 100644 tubesync/sync/migrations/0023_media_skip.py create mode 100644 tubesync/sync/templates/sync/media-enable.html create mode 100644 tubesync/sync/templates/sync/media-skip.html diff --git a/tubesync/common/static/styles/_colours.scss b/tubesync/common/static/styles/_colours.scss index 305a639e..82ee0f5b 100644 --- a/tubesync/common/static/styles/_colours.scss +++ b/tubesync/common/static/styles/_colours.scss @@ -54,6 +54,7 @@ $infobox-text-colour: $colour-near-white; $errorbox-background-colour: $colour-red; $errorbox-text-colour: $colour-near-white; +$error-text-colour: $colour-red; $pagination-background-colour: $colour-near-white; $pagination-text-colour: $colour-near-black; diff --git a/tubesync/common/static/styles/_helpers.scss b/tubesync/common/static/styles/_helpers.scss index d1ef7b67..a7b1ff4d 100644 --- a/tubesync/common/static/styles/_helpers.scss +++ b/tubesync/common/static/styles/_helpers.scss @@ -18,6 +18,10 @@ strong { padding-top: 20px; } +.error-text { + color: $error-text-colour; +} + .errors { background-color: $box-error-background-colour; border-radius: 2px; diff --git a/tubesync/sync/admin.py b/tubesync/sync/admin.py index 9a109ba5..7cf64c0a 100644 --- a/tubesync/sync/admin.py +++ b/tubesync/sync/admin.py @@ -6,7 +6,8 @@ from .models import Source, Media class SourceAdmin(admin.ModelAdmin): ordering = ('-created',) - list_display = ('name', 'get_source_type_display', 'last_crawl', 'has_failed') + list_display = ('uuid', 'name', 'source_type', 'last_crawl', + 'has_failed') readonly_fields = ('uuid', 'created') search_fields = ('uuid', 'key', 'name') @@ -15,6 +16,6 @@ class SourceAdmin(admin.ModelAdmin): class MediaAdmin(admin.ModelAdmin): ordering = ('-created',) - list_display = ('key', 'source', 'can_download', 'downloaded') + list_display = ('uuid', 'key', 'source', 'can_download', 'skip', 'downloaded') readonly_fields = ('uuid', 'created') search_fields = ('uuid', 'source__key', 'key') diff --git a/tubesync/sync/apps.py b/tubesync/sync/apps.py index 0d95b485..40c902ed 100644 --- a/tubesync/sync/apps.py +++ b/tubesync/sync/apps.py @@ -2,4 +2,5 @@ from django.apps import AppConfig class SyncConfig(AppConfig): + name = 'sync' diff --git a/tubesync/sync/forms.py b/tubesync/sync/forms.py index adfe944a..553308d1 100644 --- a/tubesync/sync/forms.py +++ b/tubesync/sync/forms.py @@ -27,3 +27,13 @@ class ConfirmDeleteSourceForm(forms.Form): class RedownloadMediaForm(forms.Form): pass + + +class SkipMediaForm(forms.Form): + + pass + + +class EnableMediaForm(forms.Form): + + pass diff --git a/tubesync/sync/migrations/0020_auto_20201211_0306.py b/tubesync/sync/migrations/0020_auto_20201211_0306.py new file mode 100644 index 00000000..1e735e15 --- /dev/null +++ b/tubesync/sync/migrations/0020_auto_20201211_0306.py @@ -0,0 +1,29 @@ +# Generated by Django 3.1.4 on 2020-12-11 03:06 + +import django.core.files.storage +from django.db import migrations, models +import sync.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sync', '0019_auto_20201209_0857'), + ] + + operations = [ + migrations.AddField( + model_name='media', + name='download_date', + field=models.DateTimeField(blank=True, db_index=True, help_text='Date and time the download completed', null=True, verbose_name='download date'), + ), + migrations.AlterField( + model_name='media', + name='media_file', + field=models.FileField(blank=True, help_text='Media file', max_length=200, null=True, storage=django.core.files.storage.FileSystemStorage(location='/home/meeb/Repos/github.com/meeb/tubesync/tubesync/downloads'), upload_to=sync.models.get_media_file_path, verbose_name='media file'), + ), + migrations.AlterUniqueTogether( + name='media', + unique_together={('source', 'key')}, + ), + ] diff --git a/tubesync/sync/migrations/0021_auto_20201211_0351.py b/tubesync/sync/migrations/0021_auto_20201211_0351.py new file mode 100644 index 00000000..adf57540 --- /dev/null +++ b/tubesync/sync/migrations/0021_auto_20201211_0351.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.4 on 2020-12-11 03:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sync', '0020_auto_20201211_0306'), + ] + + operations = [ + migrations.AddField( + model_name='media', + name='downloaded_height', + field=models.PositiveIntegerField(blank=True, help_text='Height in pixels of the downloaded media', null=True, verbose_name='downloaded height'), + ), + migrations.AddField( + model_name='media', + name='downloaded_width', + field=models.PositiveIntegerField(blank=True, help_text='Width in pixels of the downloaded media', null=True, verbose_name='downloaded width'), + ), + ] diff --git a/tubesync/sync/migrations/0022_auto_20201211_0354.py b/tubesync/sync/migrations/0022_auto_20201211_0354.py new file mode 100644 index 00000000..28875985 --- /dev/null +++ b/tubesync/sync/migrations/0022_auto_20201211_0354.py @@ -0,0 +1,38 @@ +# Generated by Django 3.1.4 on 2020-12-11 03:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sync', '0021_auto_20201211_0351'), + ] + + operations = [ + migrations.AddField( + model_name='media', + name='downloaded_format', + field=models.CharField(blank=True, help_text='Audio codec of the downloaded media', max_length=30, null=True, verbose_name='downloaded format'), + ), + migrations.AlterField( + model_name='media', + name='downloaded_audio_codec', + field=models.CharField(blank=True, help_text='Audio codec of the downloaded media', max_length=30, null=True, verbose_name='downloaded audio codec'), + ), + migrations.AlterField( + model_name='media', + name='downloaded_container', + field=models.CharField(blank=True, help_text='Container format of the downloaded media', max_length=30, null=True, verbose_name='downloaded container format'), + ), + migrations.AlterField( + model_name='media', + name='downloaded_fps', + field=models.PositiveSmallIntegerField(blank=True, help_text='FPS of the downloaded media', null=True, verbose_name='downloaded fps'), + ), + migrations.AlterField( + model_name='media', + name='downloaded_video_codec', + field=models.CharField(blank=True, help_text='Video codec of the downloaded media', max_length=30, null=True, verbose_name='downloaded video codec'), + ), + ] diff --git a/tubesync/sync/migrations/0023_media_skip.py b/tubesync/sync/migrations/0023_media_skip.py new file mode 100644 index 00000000..7c0f8c96 --- /dev/null +++ b/tubesync/sync/migrations/0023_media_skip.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.4 on 2020-12-11 04:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sync', '0022_auto_20201211_0354'), + ] + + operations = [ + migrations.AddField( + model_name='media', + name='skip', + field=models.BooleanField(db_index=True, default=False, help_text='Media will be skipped and not downloaded', verbose_name='skip'), + ), + ] diff --git a/tubesync/sync/models.py b/tubesync/sync/models.py index bf9a7f08..b2721f59 100644 --- a/tubesync/sync/models.py +++ b/tubesync/sync/models.py @@ -16,7 +16,7 @@ from .matching import (get_best_combined_format, get_best_audio_format, get_best_video_format) -media_file_storage = FileSystemStorage(location=settings.DOWNLOAD_ROOT) +media_file_storage = FileSystemStorage(location=str(settings.DOWNLOAD_ROOT)) class Source(models.Model): @@ -491,16 +491,47 @@ class Media(models.Model): storage=media_file_storage, help_text=_('Media file') ) + skip = models.BooleanField( + _('skip'), + db_index=True, + default=False, + help_text=_('Media will be skipped and not downloaded') + ) downloaded = models.BooleanField( _('downloaded'), db_index=True, default=False, help_text=_('Media has been downloaded') ) + download_date = models.DateTimeField( + _('download date'), + db_index=True, + blank=True, + null=True, + help_text=_('Date and time the download completed') + ) + downloaded_format = models.CharField( + _('downloaded format'), + max_length=30, + blank=True, + null=True, + help_text=_('Audio codec of the downloaded media') + ) + downloaded_height = models.PositiveIntegerField( + _('downloaded height'), + blank=True, + null=True, + help_text=_('Height in pixels of the downloaded media') + ) + downloaded_width = models.PositiveIntegerField( + _('downloaded width'), + blank=True, + null=True, + help_text=_('Width in pixels of the downloaded media') + ) downloaded_audio_codec = models.CharField( _('downloaded audio codec'), max_length=30, - db_index=True, blank=True, null=True, help_text=_('Audio codec of the downloaded media') @@ -508,7 +539,6 @@ class Media(models.Model): downloaded_video_codec = models.CharField( _('downloaded video codec'), max_length=30, - db_index=True, blank=True, null=True, help_text=_('Video codec of the downloaded media') @@ -516,14 +546,12 @@ class Media(models.Model): downloaded_container = models.CharField( _('downloaded container format'), max_length=30, - db_index=True, blank=True, null=True, help_text=_('Container format of the downloaded media') ) downloaded_fps = models.PositiveSmallIntegerField( _('downloaded fps'), - db_index=True, blank=True, null=True, help_text=_('FPS of the downloaded media') @@ -567,7 +595,7 @@ class Media(models.Model): def get_best_video_format(self): return get_best_video_format(self) - + def get_format_str(self): ''' Returns a youtube-dl compatible format string for the best matches @@ -593,6 +621,55 @@ class Media(models.Model): return False return False + def get_display_format(self, format_str): + ''' + Returns a tuple used in the format component of the output filename. This + is the format(s) found by matching. Examples: + # Audio and video streams + ('1080p', 'vp9', 'opus') + # Audio only stream + ('opus',) + # Audio and video streams with additional flags + ('720p', 'avc1', 'mp4a', '60fps', 'hdr') + ''' + fmt = [] + # If the download has completed use existing values + if self.downloaded: + if self.downloaded_format != 'audio': + fmt.append(self.downloaded_format.lower()) + fmt.append(self.downloaded_video_codec.lower()) + fmt.append(self.downloaded_audio_codec.lower()) + if self.downloaded_format != 'audio': + fmt.append(str(self.downloaded_fps)) + if self.downloaded_hdr: + fmt.append('hdr') + return fmt + # Otherwise, calculate from matched format codes + vformat = None + aformat = None + if '+' in format_str: + # Seperate audio and video streams + vformat_code, aformat_code = format_str.split('+') + vformat = self.get_format_by_code(vformat_code) + aformat = self.get_format_by_code(aformat_code) + else: + # Combined stream or audio only + cformat = self.get_format_by_code(format_str) + aformat = cformat + if cformat['vcodec']: + # Combined + vformat = cformat + if vformat: + fmt.append(vformat['format'].lower()) + fmt.append(vformat['vcodec'].lower()) + fmt.append(aformat['acodec'].lower()) + if vformat: + if vformat['is_60fps']: + fmt.append('60fps') + if vformat['is_hdr']: + fmt.append('hdr') + return tuple(fmt) + def get_format_by_code(self, format_code): ''' Matches a format code, such as '22', to a processed format dict. @@ -671,18 +748,9 @@ class Media(models.Model): name = slugify(self.name.replace('&', 'and').replace('+', 'and')) name = name.replace('_', '-')[:80] key = self.key.strip().replace('_', '-')[:20] - fmt = [] - if self.source.is_audio: - fmt.append(self.source.source_acodec.lower()) - else: - fmt.append(self.source.source_resolution.lower()) - fmt.append(self.source.source_vcodec.lower()) - fmt.append(self.source.source_acodec.lower()) - if self.source.prefer_60fps: - fmt.append('60fps') - if self.source.prefer_hdr: - fmt.append('hdr') - fmt = '-'.join(fmt) + format_str = self.get_format_str() + format_tuple = self.get_display_format(format_str) + fmt = '-'.join(format_tuple) ext = self.source.extension return f'{datestr}_{source_name}_{name}_{key}_{fmt}.{ext}' @@ -721,7 +789,7 @@ class Media(models.Model): def download_media(self): format_str = self.get_format_str() if not format_str: - raise NoFormatException(f'Cannot download, media "{self.pk}" ({media}) has ' + raise NoFormatException(f'Cannot download, media "{self.pk}" ({self}) has ' f'no valid format available') # Download the media with youtube-dl download_youtube_media(self.url, format_str, self.source.extension, diff --git a/tubesync/sync/signals.py b/tubesync/sync/signals.py index 1b7e4e7d..a8cdc978 100644 --- a/tubesync/sync/signals.py +++ b/tubesync/sync/signals.py @@ -37,7 +37,12 @@ def source_pre_save(sender, instance, **kwargs): @receiver(post_save, sender=Source) def source_post_save(sender, instance, created, **kwargs): # Triggered after a source is saved, Create a new task to check the directory exists - check_source_directory_exists(str(instance.pk)) + verbose_name = _('Check download directory exists for source "{}"') + check_source_directory_exists( + str(instance.pk), + priority=0, + verbose_name=verbose_name.format(instance.name) + ) if created: # Create a new indexing task for newly created sources delete_task_by_source('sync.tasks.index_source_task', instance.pk) @@ -86,14 +91,16 @@ def task_task_failed(sender, task_id, completed_task, **kwargs): def media_post_save(sender, instance, created, **kwargs): # Triggered after media is saved, Recalculate the "can_download" flag, this may # need to change if the source specifications have been changed + post_save.disconnect(media_post_save, sender=Media) if instance.get_format_str(): if not instance.can_download: instance.can_download = True instance.save() else: if instance.can_download: - instance.can_download = True + instance.can_download = False instance.save() + post_save.connect(media_post_save, sender=Media) # If the media is missing a thumbnail schedule it to be downloaded if not instance.thumb: thumbnail_url = instance.thumbnail @@ -109,7 +116,7 @@ def media_post_save(sender, instance, created, **kwargs): verbose_name=verbose_name.format(instance.name) ) # If the media has not yet been downloaded schedule it to be downloaded - if not instance.downloaded: + if not instance.downloaded and instance.can_download and not instance.skip: delete_task_by_media('sync.tasks.download_media', (str(instance.pk),)) verbose_name = _('Downloading media for "{}"') download_media( diff --git a/tubesync/sync/tasks.py b/tubesync/sync/tasks.py index e851a110..80d8a41e 100644 --- a/tubesync/sync/tasks.py +++ b/tubesync/sync/tasks.py @@ -249,25 +249,37 @@ def download_media(media_id): # Link the media file to the object and update info about the download media.media_file.name = str(media.filepath) media.downloaded = True + media.download_date = timezone.now() + media.downloaded_filesize = os.path.getsize(media.filepath) + media.downloaded_container = container if '+' in format_str: + # Seperate audio and video streams vformat_code, aformat_code = format_str.split('+') aformat = media.get_format_by_code(aformat_code) vformat = media.get_format_by_code(vformat_code) + media.downloaded_format = vformat['format'] + media.downloaded_height = vformat['height'] + media.downloaded_width = vformat['width'] media.downloaded_audio_codec = aformat['acodec'] media.downloaded_video_codec = vformat['vcodec'] media.downloaded_container = container media.downloaded_fps = vformat['fps'] media.downloaded_hdr = vformat['is_hdr'] - media.downloaded_filesize = os.path.getsize(media.filepath) else: + # Combined stream or audio-only stream cformat_code = format_str cformat = media.get_format_by_code(cformat_code) media.downloaded_audio_codec = cformat['acodec'] - media.downloaded_video_codec = cformat['vcodec'] - media.downloaded_container = container - media.downloaded_fps = cformat['fps'] - media.downloaded_hdr = cformat['is_hdr'] - media.downloaded_filesize = os.path.getsize(media.filepath) + if cformat['vcodec']: + # Combined + media.downloaded_format = vformat['format'] + media.downloaded_height = cformat['height'] + media.downloaded_width = cformat['width'] + media.downloaded_video_codec = cformat['vcodec'] + media.downloaded_fps = cformat['fps'] + media.downloaded_hdr = cformat['is_hdr'] + else: + media.downloaded_format = 'audio' media.save() else: # Expected file doesn't exist on disk diff --git a/tubesync/sync/templates/sync/media-enable.html b/tubesync/sync/templates/sync/media-enable.html new file mode 100644 index 00000000..6c6b01e1 --- /dev/null +++ b/tubesync/sync/templates/sync/media-enable.html @@ -0,0 +1,26 @@ +{% extends 'base.html' %} + +{% block headtitle %}Enable (unskip) media - {{ media }}{% endblock %} + +{% block content %} +
+
+

Enable (unskip) {{ media }}

+

+ You can enable your previously skipped media {{ media }}. This + will re-enable the media to be downloaded. +

+
+
+
+
+ {% csrf_token %} + {% include 'simpleform.html' with form=form %} +
+
+ +
+
+
+
+{% endblock %} diff --git a/tubesync/sync/templates/sync/media-item.html b/tubesync/sync/templates/sync/media-item.html index 48c04d05..d96c6d5f 100644 --- a/tubesync/sync/templates/sync/media-item.html +++ b/tubesync/sync/templates/sync/media-item.html @@ -12,6 +12,7 @@ {% 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 %} {% include 'infobox.html' with message=message %}
@@ -63,10 +64,17 @@ Fallback Fallback
{{ media.source.get_fallback_display }} + {% if media.skip %} + + Skipping? + Skipping?
{% if media.skip %}{% else %}{% endif %} + + {% else %} Downloaded? Downloaded?
{% if media.downloaded %}{% else %}{% endif %} + {% endif %} {% if media.downloaded %} Filename @@ -106,8 +114,8 @@ {% for format in media.formats %}
ID: {{ format.format_id }} - {% if format.vcodec|lower != 'none' %}, {{ format.format_note }} ({{ format.width }}x{{ format.height }}), fps:{{ format.fps|lower }}, video:{{ format.vcodec }} @{{ format.tbr }}k{% endif %} - {% if format.acodec|lower != 'none' %}, audio:{{ format.acodec }} @{{ format.abr }}k / {{ format.asr }}Hz{% endif %} + {% if format.vcodec|lower != 'none' %}, {{ format.format_note }} ({{ format.width }}x{{ format.height }}), fps:{{ format.fps|lower }}, video:{{ format.vcodec }} @{{ format.tbr }}k{% endif %} + {% if format.acodec|lower != 'none' %}, audio:{{ format.acodec }} @{{ format.abr }}k / {{ format.asr }}Hz{% endif %} {% if format.format_id == combined_format or format.format_id == audio_format or format.format_id == video_format %}(matched){% endif %}
{% empty %} @@ -127,9 +135,18 @@
{% if media.downloaded %} +
+ + +
+{% elif media.skip %}
{% endif %} diff --git a/tubesync/sync/templates/sync/media-skip.html b/tubesync/sync/templates/sync/media-skip.html new file mode 100644 index 00000000..5c03e931 --- /dev/null +++ b/tubesync/sync/templates/sync/media-skip.html @@ -0,0 +1,27 @@ +{% extends 'base.html' %} + +{% block headtitle %}Skip media - {{ media }}{% endblock %} + +{% block content %} +
+
+

Delete and skip media {{ media }}

+

+ You can delete the downloaded file for your media {{ media }} and + mark it to never be downloaded. You might want to do this if you don't want a local + copy of some media or want to skip a single video from a source. +

+
+
+
+
+ {% csrf_token %} + {% include 'simpleform.html' with form=form %} +
+
+ +
+
+
+
+{% endblock %} diff --git a/tubesync/sync/templates/sync/media.html b/tubesync/sync/templates/sync/media.html index 2df760c4..0461c937 100644 --- a/tubesync/sync/templates/sync/media.html +++ b/tubesync/sync/templates/sync/media.html @@ -18,7 +18,19 @@ {{ m.source }}
{{ m.name }}
- {% if m.can_download %}{% if m.downloaded %}{% else %}{% endif %} {{ m.published|date:'Y-m-d' }}{% else %} No matching formats{% endif %} + + {% if m.downloaded %} + {{ m.download_date|date:'Y-m-d' }} + {% else %} + {% if m.skip %} + Skipped + {% elif m.can_download %} + {{ m.published|date:'Y-m-d' }} + {% else %} + No matching formats + {% endif %} + {% endif %} +
diff --git a/tubesync/sync/urls.py b/tubesync/sync/urls.py index 120f94b4..fc4ca9b1 100644 --- a/tubesync/sync/urls.py +++ b/tubesync/sync/urls.py @@ -1,8 +1,8 @@ from django.urls import path from .views import (DashboardView, SourcesView, ValidateSourceView, AddSourceView, SourceView, UpdateSourceView, DeleteSourceView, MediaView, - MediaThumbView, MediaItemView, MediaRedownloadView, TasksView, - CompletedTasksView) + MediaThumbView, MediaItemView, MediaRedownloadView, MediaSkipView, + MediaEnableView, TasksView, CompletedTasksView) app_name = 'sync' @@ -60,6 +60,14 @@ urlpatterns = [ MediaRedownloadView.as_view(), name='redownload-media'), + path('media-skip/', + MediaSkipView.as_view(), + name='skip-media'), + + path('media-enable/', + MediaEnableView.as_view(), + name='enable-media'), + # Task URLs path('tasks', diff --git a/tubesync/sync/utils.py b/tubesync/sync/utils.py index 63b5a9c6..8f64c00c 100644 --- a/tubesync/sync/utils.py +++ b/tubesync/sync/utils.py @@ -161,6 +161,7 @@ def parse_media_format(format_dict): 'format': format_str, 'format_verbose': format_dict.get('format', ''), 'height': format_dict.get('height', 0), + 'width': format_dict.get('width', 0), 'vcodec': vcodec, 'fps': format_dict.get('fps', 0), 'vbr': format_dict.get('tbr', 0), diff --git a/tubesync/sync/views.py b/tubesync/sync/views.py index 2b227bdc..ea9082f3 100644 --- a/tubesync/sync/views.py +++ b/tubesync/sync/views.py @@ -15,7 +15,8 @@ from django.utils.translation import gettext_lazy as _ from common.utils import append_uri_params from background_task.models import Task, CompletedTask from .models import Source, Media -from .forms import ValidateSourceForm, ConfirmDeleteSourceForm, RedownloadMediaForm +from .forms import (ValidateSourceForm, ConfirmDeleteSourceForm, RedownloadMediaForm, + SkipMediaForm, EnableMediaForm) from .utils import validate_url, delete_file from .tasks import (map_task_to_instance, get_error_message, get_source_completed_tasks, get_media_download_task, @@ -384,6 +385,8 @@ class MediaItemView(DetailView): model = Media messages = { 'redownloading': _('Media file has been deleted and scheduled to redownload'), + 'skipped': _('Media file has been deleted and marked to never download'), + 'enabled': _('Media has been re-enabled and will be downloaded'), } def __init__(self, *args, **kwargs): @@ -457,6 +460,70 @@ class MediaRedownloadView(FormView, SingleObjectMixin): return append_uri_params(url, {'message': 'redownloading'}) +class MediaSkipView(FormView, SingleObjectMixin): + + template_name = 'sync/media-skip.html' + form_class = SkipMediaForm + model = Media + + def __init__(self, *args, **kwargs): + self.object = None + super().__init__(*args, **kwargs) + + def dispatch(self, request, *args, **kwargs): + self.object = self.get_object() + return super().dispatch(request, *args, **kwargs) + + def form_valid(self, form): + # Delete any active download tasks for the media + delete_task_by_media('sync.tasks.download_media', (str(self.object.pk),)) + # If the media file exists on disk, delete it + if self.object.media_file_exists: + delete_file(self.object.media_file.path) + self.object.media_file = None + # Reset all download data + self.object.downloaded = False + self.object.downloaded_audio_codec = None + self.object.downloaded_video_codec = None + self.object.downloaded_container = None + self.object.downloaded_fps = None + self.object.downloaded_hdr = False + self.object.downloaded_filesize = None + # Mark it to be skipped + self.object.skip = True + self.object.save() + return super().form_valid(form) + + def get_success_url(self): + url = reverse_lazy('sync:media-item', kwargs={'pk': self.object.pk}) + return append_uri_params(url, {'message': 'skipped'}) + + +class MediaEnableView(FormView, SingleObjectMixin): + + template_name = 'sync/media-enable.html' + form_class = EnableMediaForm + model = Media + + def __init__(self, *args, **kwargs): + self.object = None + super().__init__(*args, **kwargs) + + def dispatch(self, request, *args, **kwargs): + self.object = self.get_object() + return super().dispatch(request, *args, **kwargs) + + def form_valid(self, form): + # Mark it as not skipped + self.object.skip = False + self.object.save() + return super().form_valid(form) + + def get_success_url(self): + url = reverse_lazy('sync:media-item', kwargs={'pk': self.object.pk}) + return append_uri_params(url, {'message': 'enabled'}) + + class TasksView(ListView): ''' A list of tasks queued to be completed. This is, for example, scraping for new diff --git a/tubesync/sync/youtube.py b/tubesync/sync/youtube.py index 637df6ae..3fc76cc4 100644 --- a/tubesync/sync/youtube.py +++ b/tubesync/sync/youtube.py @@ -53,8 +53,8 @@ def download_media(url, media_format, extension, output_file): if event['status'] == 'error': log.error(f'[youtube-dl] error occured downloading: {filename}') elif event['status'] == 'downloading': - p = round((event['downloaded_bytes'] / event['total_bytes']) * 100, -1) - if p > hook.download_progress: + p = round((event['downloaded_bytes'] / event['total_bytes']) * 100) + if (p % 5 == 0) and p > hook.download_progress: hook.download_progress = p eta = event.get('_eta_str', '?').strip() percent_done = event.get('_percent_str', '?').strip()