refactor media format codes, media skipping and re-enabling feature

This commit is contained in:
meeb 2020-12-11 16:11:31 +11:00
parent 6b138fb02e
commit 096c48ce1b
20 changed files with 409 additions and 39 deletions

View File

@ -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;

View File

@ -18,6 +18,10 @@ strong {
padding-top: 20px;
}
.error-text {
color: $error-text-colour;
}
.errors {
background-color: $box-error-background-colour;
border-radius: 2px;

View File

@ -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')

View File

@ -2,4 +2,5 @@ from django.apps import AppConfig
class SyncConfig(AppConfig):
name = 'sync'

View File

@ -27,3 +27,13 @@ class ConfirmDeleteSourceForm(forms.Form):
class RedownloadMediaForm(forms.Form):
pass
class SkipMediaForm(forms.Form):
pass
class EnableMediaForm(forms.Form):
pass

View File

@ -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')},
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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,

View File

@ -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(

View File

@ -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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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>

View File

@ -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',

View File

@ -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),

View File

@ -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

View File

@ -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()