refactor media format codes, media skipping and re-enabling feature
This commit is contained in:
parent
6b138fb02e
commit
096c48ce1b
|
@ -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;
|
||||
|
|
|
@ -18,6 +18,10 @@ strong {
|
|||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: $error-text-colour;
|
||||
}
|
||||
|
||||
.errors {
|
||||
background-color: $box-error-background-colour;
|
||||
border-radius: 2px;
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -2,4 +2,5 @@ from django.apps import AppConfig
|
|||
|
||||
|
||||
class SyncConfig(AppConfig):
|
||||
|
||||
name = 'sync'
|
||||
|
|
|
@ -27,3 +27,13 @@ class ConfirmDeleteSourceForm(forms.Form):
|
|||
class RedownloadMediaForm(forms.Form):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class SkipMediaForm(forms.Form):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EnableMediaForm(forms.Form):
|
||||
|
||||
pass
|
||||
|
|
|
@ -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')},
|
||||
),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block headtitle %}Enable (unskip) media - {{ media }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row no-margin-bottom">
|
||||
<div class="col s12">
|
||||
<h1>Enable (unskip) <strong>{{ media }}</strong></h1>
|
||||
<p>
|
||||
You can enable your previously skipped media <strong>{{ media }}</strong>. This
|
||||
will re-enable the media to be downloaded.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<form method="post" action="{% url 'sync:enable-media' pk=media.pk %}" class="col s12 simpleform">
|
||||
{% csrf_token %}
|
||||
{% include 'simpleform.html' with form=form %}
|
||||
<div class="row no-margin-bottom padding-top">
|
||||
<div class="col s12">
|
||||
<button class="btn" type="submit" name="action">Enable and download media <i class="fas fa-cloud-download-alt"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -12,6 +12,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{% 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 %}
|
||||
<div class="row">
|
||||
<div class="col s12 m7">
|
||||
|
@ -63,10 +64,17 @@
|
|||
<td class="hide-on-small-only">Fallback</td>
|
||||
<td><span class="hide-on-med-and-up">Fallback<br></span><strong>{{ media.source.get_fallback_display }}</strong></td>
|
||||
</tr>
|
||||
{% if media.skip %}
|
||||
<tr title="Has the media been downloaded?">
|
||||
<td class="hide-on-small-only">Skipping?</td>
|
||||
<td><span class="hide-on-med-and-up">Skipping?<br></span><strong>{% if media.skip %}<i class="fas fa-check"></i>{% else %}<i class="fas fa-times"></i>{% endif %}</strong></td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr title="Has the media been downloaded?">
|
||||
<td class="hide-on-small-only">Downloaded?</td>
|
||||
<td><span class="hide-on-med-and-up">Downloaded?<br></span><strong>{% if media.downloaded %}<i class="fas fa-check"></i>{% else %}<i class="fas fa-times"></i>{% endif %}</strong></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if media.downloaded %}
|
||||
<tr title="The filename the media will be downloaded as">
|
||||
<td class="hide-on-small-only">Filename</td>
|
||||
|
@ -106,8 +114,8 @@
|
|||
{% for format in media.formats %}
|
||||
<div>
|
||||
ID: <strong>{{ format.format_id }}</strong>
|
||||
{% if format.vcodec|lower != 'none' %}, <strong>{{ format.format_note }} ({{ format.width }}x{{ format.height }})</strong>, fps:<strong>{{ format.fps|lower }}</strong>, video:<strong>{{ format.vcodec }} @{{ format.tbr }}k</strong>{% endif %}
|
||||
{% if format.acodec|lower != 'none' %}, audio:<strong>{{ format.acodec }} @{{ format.abr }}k / {{ format.asr }}Hz</strong>{% 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 %}<strong>(matched)</strong>{% endif %}
|
||||
</div>
|
||||
{% empty %}
|
||||
|
@ -127,9 +135,18 @@
|
|||
</div>
|
||||
</div>
|
||||
{% if media.downloaded %}
|
||||
<div class="row">
|
||||
<div class="col s12 l6">
|
||||
<a href="{% url 'sync:redownload-media' pk=media.pk %}" class="btn">Redownload media <i class="fas fa-cloud-download-alt"></i></a>
|
||||
</div>
|
||||
<div class="col s12 l6">
|
||||
<a href="{% url 'sync:skip-media' pk=media.pk %}" class="btn delete-button">Delete and skip media <i class="fas fa-times-circle"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
{% elif media.skip %}
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<a href="{% url 'sync:redownload-media' pk=media.pk %}" class="btn">Delete and redownload media <i class="fas fa-cloud-download-alt"></i></a>
|
||||
<a href="{% url 'sync:enable-media' pk=media.pk %}" class="btn">Enable (unskip) media <i class="fas fa-cloud-download-alt"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block headtitle %}Skip media - {{ media }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row no-margin-bottom">
|
||||
<div class="col s12">
|
||||
<h1>Delete and skip media <strong>{{ media }}</strong></h1>
|
||||
<p>
|
||||
You can delete the downloaded file for your media <strong>{{ media }}</strong> 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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<form method="post" action="{% url 'sync:skip-media' pk=media.pk %}" class="col s12 simpleform">
|
||||
{% csrf_token %}
|
||||
{% include 'simpleform.html' with form=form %}
|
||||
<div class="row no-margin-bottom padding-top">
|
||||
<div class="col s12">
|
||||
<button class="btn" type="submit" name="action">Really delete and skip media <i class="fas fa-trash-alt"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -18,7 +18,19 @@
|
|||
<img src="{% if m.thumb %}{% url 'sync:media-thumb' pk=m.pk %}{% else %}{% static 'images/nothumb.png' %}{% endif %}">
|
||||
<span class="card-title truncate">{{ m.source }}<br>
|
||||
<span>{{ m.name }}</span><br>
|
||||
<span>{% if m.can_download %}{% if m.downloaded %}<i class="fas fa-check-circle" title="Downloaded"></i>{% else %}<i class="far fa-clock" title="Waiting to download or downloading"></i>{% endif %} {{ m.published|date:'Y-m-d' }}{% else %}<i class="fas fa-exclamation-triangle"></i> No matching formats{% endif %}</span>
|
||||
<span>
|
||||
{% if m.downloaded %}
|
||||
<i class="fas fa-check-circle" title="Downloaded"></i> {{ m.download_date|date:'Y-m-d' }}
|
||||
{% else %}
|
||||
{% if m.skip %}
|
||||
<span class="error-text"><i class="fas fa-times" title="Skipping media"></i> Skipped</span>
|
||||
{% elif m.can_download %}
|
||||
<i class="far fa-clock" title="Waiting to download or downloading"></i> {{ m.published|date:'Y-m-d' }}
|
||||
{% else %}
|
||||
<span class="error-text"><i class="fas fa-exclamation-triangle" title="No matching formats to download"></i> No matching formats</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
|
|
|
@ -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/<uuid:pk>',
|
||||
MediaSkipView.as_view(),
|
||||
name='skip-media'),
|
||||
|
||||
path('media-enable/<uuid:pk>',
|
||||
MediaEnableView.as_view(),
|
||||
name='enable-media'),
|
||||
|
||||
# Task URLs
|
||||
|
||||
path('tasks',
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue