media format detection
This commit is contained in:
parent
d958f426d7
commit
d7345c92c3
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.1.4 on 2020-12-08 05:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sync', '0015_auto_20201207_0744'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='source',
|
||||||
|
name='fallback',
|
||||||
|
field=models.CharField(choices=[('f', 'Fail, do not download any media'), ('n', 'Get next best resolution or codec instead'), ('h', 'Get next best resolution but at least HD')], db_index=True, default='h', help_text='What do do when media in your source resolution and codecs is not available', max_length=1, verbose_name='fallback'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -72,13 +72,13 @@ class Source(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
FALLBACK_FAIL = 'f'
|
FALLBACK_FAIL = 'f'
|
||||||
FALLBACK_NEXT_SD = 's'
|
FALLBACK_NEXT_BEST = 'n'
|
||||||
FALLBACK_NEXT_HD = 'h'
|
FALLBACK_NEXT_BEST_HD = 'h'
|
||||||
FALLBACKS = (FALLBACK_FAIL, FALLBACK_NEXT_SD, FALLBACK_NEXT_HD)
|
FALLBACKS = (FALLBACK_FAIL, FALLBACK_NEXT_BEST, FALLBACK_NEXT_BEST_HD)
|
||||||
FALLBACK_CHOICES = (
|
FALLBACK_CHOICES = (
|
||||||
(FALLBACK_FAIL, _('Fail, do not download any media')),
|
(FALLBACK_FAIL, _('Fail, do not download any media')),
|
||||||
(FALLBACK_NEXT_SD, _('Get next best SD media or codec instead')),
|
(FALLBACK_NEXT_BEST, _('Get next best resolution or codec instead')),
|
||||||
(FALLBACK_NEXT_HD, _('Get next best HD media or codec instead')),
|
(FALLBACK_NEXT_BEST_HD, _('Get next best resolution but at least HD'))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fontawesome icons used for the source on the front end
|
# Fontawesome icons used for the source on the front end
|
||||||
|
@ -218,7 +218,7 @@ class Source(models.Model):
|
||||||
max_length=1,
|
max_length=1,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
choices=FALLBACK_CHOICES,
|
choices=FALLBACK_CHOICES,
|
||||||
default=FALLBACK_NEXT_HD,
|
default=FALLBACK_NEXT_BEST_HD,
|
||||||
help_text=_('What do do when media in your source resolution and codecs is not available')
|
help_text=_('What do do when media in your source resolution and codecs is not available')
|
||||||
)
|
)
|
||||||
has_failed = models.BooleanField(
|
has_failed = models.BooleanField(
|
||||||
|
@ -300,6 +300,10 @@ class Source(models.Model):
|
||||||
def source_resolution_height(self):
|
def source_resolution_height(self):
|
||||||
return self.RESOLUTION_MAP.get(self.source_resolution, 0)
|
return self.RESOLUTION_MAP.get(self.source_resolution, 0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_fallback(self):
|
||||||
|
return self.fallback != self.FALLBACK_FAIL
|
||||||
|
|
||||||
def index_media(self):
|
def index_media(self):
|
||||||
'''
|
'''
|
||||||
Index the media source returning a list of media metadata as dicts.
|
Index the media source returning a list of media metadata as dicts.
|
||||||
|
@ -494,94 +498,237 @@ class Media(models.Model):
|
||||||
fields = self.METADATA_FIELDS.get(field, {})
|
fields = self.METADATA_FIELDS.get(field, {})
|
||||||
return fields.get(self.source.source_type, '')
|
return fields.get(self.source.source_type, '')
|
||||||
|
|
||||||
|
def iter_formats(self):
|
||||||
|
for fmt in self.formats:
|
||||||
|
yield parse_media_format(fmt)
|
||||||
|
|
||||||
def get_best_combined_format(self):
|
def get_best_combined_format(self):
|
||||||
'''
|
'''
|
||||||
Attempts to see if there is a single, combined audio and video format that
|
Attempts to see if there is a single, combined audio and video format that
|
||||||
exactly matches the source requirements. This is used over separate audio
|
exactly matches the source requirements. This is used over separate audio
|
||||||
and video formats if possible.
|
and video formats if possible. Combined formats are the easiest to check
|
||||||
|
for as they must exactly match the source profile be be valid.
|
||||||
single format structure = {
|
|
||||||
'format_id': '22',
|
|
||||||
'url': '... long url ...',
|
|
||||||
'player_url': None,
|
|
||||||
'ext': 'mp4',
|
|
||||||
'width': 1280,
|
|
||||||
'height': 720,
|
|
||||||
'acodec': 'mp4a.40.2',
|
|
||||||
'abr': 192,
|
|
||||||
'vcodec': 'avc1.64001F',
|
|
||||||
'asr': 44100,
|
|
||||||
'filesize': None,
|
|
||||||
'format_note': '720p',
|
|
||||||
'fps': 30,
|
|
||||||
'tbr': 1571.695,
|
|
||||||
'format': '22 - 1280x720 (720p)',
|
|
||||||
'protocol': 'https',
|
|
||||||
'http_headers': {... dict of headers ...}
|
|
||||||
}
|
|
||||||
|
|
||||||
or for hdr = {
|
|
||||||
'format_id': '336',
|
|
||||||
'url': '... long url ...',
|
|
||||||
'player_url': None,
|
|
||||||
'asr': None,
|
|
||||||
'filesize': 312014985,
|
|
||||||
'format_note': '1440p60 HDR',
|
|
||||||
'fps': 60,
|
|
||||||
'height': 1440,
|
|
||||||
'tbr': 16900.587,
|
|
||||||
'width': 2560,
|
|
||||||
'ext': 'webm',
|
|
||||||
'vcodec': 'vp9.2',
|
|
||||||
'acodec': 'none',
|
|
||||||
'downloader_options': {'http_chunk_size': 10485760},
|
|
||||||
'format': '336 - 2560x1440 (1440p60 HDR)',
|
|
||||||
'protocol': 'https',
|
|
||||||
'http_headers': {... dict of headers ...}
|
|
||||||
}
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
candidates = []
|
for fmt in self.iter_formats():
|
||||||
for fmt in self.formats:
|
|
||||||
parsed_fmt = parse_media_format(fmt)
|
|
||||||
# Check height matches
|
# Check height matches
|
||||||
print(self.source.source_resolution_height, parsed_fmt['height'])
|
if self.source.source_resolution.strip().upper() != fmt['format']:
|
||||||
if self.source.source_resolution_height != parsed_fmt['height']:
|
|
||||||
print()
|
|
||||||
continue
|
continue
|
||||||
print('height OK')
|
|
||||||
# Check the video codec matches
|
# Check the video codec matches
|
||||||
print(self.source.source_vcodec, parsed_fmt['vcodec'])
|
if self.source.source_vcodec != fmt['vcodec']:
|
||||||
if self.source.source_vcodec != parsed_fmt['vcodec']:
|
|
||||||
print()
|
|
||||||
continue
|
continue
|
||||||
print('vcodec OK')
|
|
||||||
# Check the audio codec matches
|
# Check the audio codec matches
|
||||||
print(self.source.source_acodec, parsed_fmt['acodec'])
|
if self.source.source_acodec != fmt['acodec']:
|
||||||
if self.source.source_acodec != parsed_fmt['acodec']:
|
|
||||||
print()
|
|
||||||
continue
|
continue
|
||||||
print('acodec OK')
|
# if the source prefers 60fps, check for it
|
||||||
# All OK so far...
|
if self.source.prefer_60fps:
|
||||||
candidates.append(parsed_fmt)
|
if not fmt['is_60fps']:
|
||||||
print()
|
continue
|
||||||
for c in candidates:
|
# If the source prefers HDR, check for it
|
||||||
print(c)
|
if self.source.prefer_hdr:
|
||||||
return 'combined'
|
if not fmt['is_hdr']:
|
||||||
|
continue
|
||||||
|
# If we reach here, we have a combined match!
|
||||||
|
return True, fmt['id']
|
||||||
|
return False, False
|
||||||
|
|
||||||
def get_best_audio_format(self):
|
def get_best_audio_format(self):
|
||||||
'''
|
'''
|
||||||
Finds the best match for the source required audio format. If the source
|
Finds the best match for the source required audio format. If the source
|
||||||
has a 'fallback' of fail this can return no match.
|
has a 'fallback' of fail this can return no match.
|
||||||
'''
|
'''
|
||||||
return 'audio'
|
# Order all audio-only formats by bitrate
|
||||||
|
audio_formats = []
|
||||||
|
for fmt in self.iter_formats():
|
||||||
|
# If the format has a video stream, skip it
|
||||||
|
if fmt['vcodec']:
|
||||||
|
continue
|
||||||
|
audio_formats.append(fmt)
|
||||||
|
audio_formats = list(reversed(sorted(audio_formats, key=lambda k: k['abr'])))
|
||||||
|
if not audio_formats:
|
||||||
|
# Media has no audio formats at all
|
||||||
|
return False, False
|
||||||
|
# Find the highest bitrate audio format with a matching codec
|
||||||
|
for fmt in audio_formats:
|
||||||
|
if self.source.source_acodec == fmt['acodec']:
|
||||||
|
# Matched!
|
||||||
|
return True, fmt['id']
|
||||||
|
# No codecs matched
|
||||||
|
if self.source.can_fallback:
|
||||||
|
# Can fallback, find the next highest bitrate non-matching codec
|
||||||
|
return False, audio_formats[0]
|
||||||
|
else:
|
||||||
|
# Can't fallback
|
||||||
|
return False, False
|
||||||
|
|
||||||
|
|
||||||
def get_best_video_format(self):
|
def get_best_video_format(self):
|
||||||
'''
|
'''
|
||||||
Finds the best match for the source required video format. If the source
|
Finds the best match for the source required video format. If the source
|
||||||
has a 'fallback' of fail this can return no match.
|
has a 'fallback' of fail this can return no match. Resolution is treated
|
||||||
|
as the most important factor to match.
|
||||||
'''
|
'''
|
||||||
return 'video'
|
min_height = getattr(settings, 'VIDEO_HEIGHT_CUTOFF', 360)
|
||||||
|
fallback_hd_cutoff = getattr(settings, 'VIDEO_HEIGHT_IS_HD', 500)
|
||||||
|
# Filter video-only formats by resolution that matches the source
|
||||||
|
video_formats = []
|
||||||
|
for fmt in self.iter_formats():
|
||||||
|
# If the format has an audio stream, skip it
|
||||||
|
if fmt['acodec']:
|
||||||
|
continue
|
||||||
|
if self.source.source_resolution.strip().upper() == fmt['format']:
|
||||||
|
video_formats.append(fmt)
|
||||||
|
# Check we matched some streams
|
||||||
|
if not video_formats:
|
||||||
|
# No streams match the requested resolution, see if we can fallback
|
||||||
|
if self.source.can_fallback:
|
||||||
|
# Find the next-best format matches by height
|
||||||
|
for fmt in self.iter_formats():
|
||||||
|
# If the format has an audio stream, skip it
|
||||||
|
if fmt['acodec']:
|
||||||
|
continue
|
||||||
|
if (fmt['height'] <= self.source.source_resolution_height and
|
||||||
|
fmt['height'] >= min_height):
|
||||||
|
video_formats.append(fmt)
|
||||||
|
else:
|
||||||
|
# Can't fallback
|
||||||
|
return False, False
|
||||||
|
video_formats = list(reversed(sorted(video_formats, key=lambda k: k['height'])))
|
||||||
|
if not video_formats:
|
||||||
|
# Still no matches
|
||||||
|
return False, False
|
||||||
|
exact_match, best_match = None, None
|
||||||
|
# Of our filtered video formats, check for resolution + codec + hdr + fps match
|
||||||
|
if self.source.prefer_60fps and self.source.prefer_hdr:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for an exact match
|
||||||
|
if (self.source.source_resolution.strip().upper() == fmt['format'] and
|
||||||
|
self.source.source_vcodec == fmt['vcodec'] and
|
||||||
|
fmt['is_hdr'] and
|
||||||
|
fmt['is_60fps']):
|
||||||
|
# Exact match
|
||||||
|
exact_match, best_match = True, fmt
|
||||||
|
break
|
||||||
|
if self.source.can_fallback:
|
||||||
|
if not best_match:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for a codec, hdr and fps match but drop the resolution
|
||||||
|
if (self.source.source_vcodec == fmt['vcodec'] and
|
||||||
|
fmt['is_hdr'] and fmt['is_60fps']):
|
||||||
|
# Close match
|
||||||
|
exact_match, best_match = False, fmt
|
||||||
|
break
|
||||||
|
if not best_match:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for hdr and fps match but drop the resolution and codec
|
||||||
|
if fmt['is_hdr'] and fmt['is_60fps']:
|
||||||
|
exact_match, best_match = False, fmt
|
||||||
|
break
|
||||||
|
if not best_match:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for fps match but drop the resolution and codec and hdr
|
||||||
|
if fmt['is_hdr'] and fmt['is_60fps']:
|
||||||
|
exact_match, best_match = False, fmt
|
||||||
|
break
|
||||||
|
if not best_match:
|
||||||
|
# Match the highest resolution
|
||||||
|
exact_match, best_match = False, video_formats[0]
|
||||||
|
# Check for resolution + codec + fps match
|
||||||
|
if self.source.prefer_60fps and not self.source.prefer_hdr:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for an exact match
|
||||||
|
if (self.source.source_resolution.strip().upper() == fmt['format'] and
|
||||||
|
self.source.source_vcodec == fmt['vcodec'] and
|
||||||
|
fmt['is_60fps']):
|
||||||
|
# Exact match
|
||||||
|
exact_match, best_match = True, fmt
|
||||||
|
break
|
||||||
|
if self.source.can_fallback:
|
||||||
|
if not best_match:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for a codec and fps match but drop the resolution
|
||||||
|
if (self.source.source_vcodec == fmt['vcodec'] and
|
||||||
|
fmt['is_60fps']):
|
||||||
|
exact_match, best_match = False, fmt
|
||||||
|
break
|
||||||
|
if not best_match:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for an fps match but drop the resolution and codec
|
||||||
|
if fmt['is_60fps']:
|
||||||
|
exact_match, best_match = False, fmt
|
||||||
|
break
|
||||||
|
if not best_match:
|
||||||
|
# Match the highest resolution
|
||||||
|
exact_match, best_match = False, video_formats[0]
|
||||||
|
# Check for resolution + codec + hdr
|
||||||
|
if self.source.prefer_hdr and not self.source.prefer_60fps:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for an exact match
|
||||||
|
if (self.source.source_resolution.strip().upper() == fmt['format'] and
|
||||||
|
self.source.source_vcodec == fmt['vcodec'] and
|
||||||
|
fmt['is_hdr']):
|
||||||
|
# Exact match
|
||||||
|
exact_match, best_match = True, fmt
|
||||||
|
break
|
||||||
|
if self.source.can_fallback:
|
||||||
|
if not best_match:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for a codec and hdr match but drop the resolution
|
||||||
|
if (self.source.source_vcodec == fmt['vcodec'] and
|
||||||
|
fmt['is_hdr']):
|
||||||
|
exact_match, best_match = True, fmt
|
||||||
|
break
|
||||||
|
if not best_match:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for an hdr match but drop the resolution and codec
|
||||||
|
if fmt['is_hdr']:
|
||||||
|
exact_match, best_match = False, fmt
|
||||||
|
break
|
||||||
|
if not best_match:
|
||||||
|
# Match the highest resolution
|
||||||
|
exact_match, best_match = False, video_formats[0]
|
||||||
|
# check for resolution + codec
|
||||||
|
if not self.source.prefer_hdr and not self.source.prefer_60fps:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for an exact match
|
||||||
|
if (self.source.source_resolution.strip().upper() == fmt['format'] and
|
||||||
|
self.source.source_vcodec == fmt['vcodec'] and
|
||||||
|
not fmt['is_60fps']):
|
||||||
|
# Exact match
|
||||||
|
exact_match, best_match = True, fmt
|
||||||
|
break
|
||||||
|
if self.source.can_fallback:
|
||||||
|
if not best_match:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for a codec match without 60fps and drop the resolution
|
||||||
|
if (self.source.source_vcodec == fmt['vcodec'] and
|
||||||
|
not fmt['is_60fps']):
|
||||||
|
exact_match, best_match = False, fmt
|
||||||
|
break
|
||||||
|
if not best_match:
|
||||||
|
for fmt in video_formats:
|
||||||
|
# Check for a codec match but drop the resolution
|
||||||
|
if self.source.source_vcodec == fmt['vcodec']:
|
||||||
|
# Close match
|
||||||
|
exact_match, best_match = False, fmt
|
||||||
|
break
|
||||||
|
if not best_match:
|
||||||
|
# Match the highest resolution
|
||||||
|
exact_match, best_match = False, video_formats[0]
|
||||||
|
# See if we found a match
|
||||||
|
if best_match:
|
||||||
|
# Final check to see if the match we found was good enough
|
||||||
|
if exact_match:
|
||||||
|
return True, best_match['id']
|
||||||
|
elif self.source.can_fallback:
|
||||||
|
# Allow the fallback if it meets requirements
|
||||||
|
if (self.source.fallback == self.source.FALLBACK_NEXT_BEST_HD and
|
||||||
|
best_match['height'] >= fallback_hd_cutoff):
|
||||||
|
return False, best_match['id']
|
||||||
|
elif self.source.fallback == self.source.FALLBACK_NEXT_BEST:
|
||||||
|
return False, best_match['id']
|
||||||
|
# Nope, failed to find match
|
||||||
|
return False, False
|
||||||
|
|
||||||
|
|
||||||
def get_format_str(self):
|
def get_format_str(self):
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -33,6 +33,10 @@
|
||||||
<td class="hide-on-small-only">Desired format</td>
|
<td class="hide-on-small-only">Desired format</td>
|
||||||
<td><span class="hide-on-med-and-up">Desired format<br></span><strong>{{ media.source.format_summary }}</strong></td>
|
<td><span class="hide-on-med-and-up">Desired format<br></span><strong>{{ media.source.format_summary }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr title="Fallback setting on the source">
|
||||||
|
<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>
|
||||||
<tr title="Has the media been downloaded">
|
<tr title="Has the media been downloaded">
|
||||||
<td class="hide-on-small-only">Downloaded</td>
|
<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>
|
<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>
|
||||||
|
@ -48,11 +52,11 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr title="Best available format for source requirements">
|
<tr title="Best available format for source requirements">
|
||||||
<td class="hide-on-small-only">Best match</td>
|
<td class="hide-on-small-only">Matched formats</td>
|
||||||
<td><span class="hide-on-med-and-up">Best match<br></span><strong>
|
<td><span class="hide-on-med-and-up">Matched formats<br></span>
|
||||||
audio: {{ media.get_best_audio_format }}<br>
|
Combined: <strong>{% if combined_format %}{{ combined_format }} {% if combined_exact %}(exact match){% else %}(fallback){% endif %}{% else %}No match{% endif %}</strong><br>
|
||||||
video: {{ media.get_best_video_format }}<br>
|
Audio: <strong>{% if audio_format %}{{ audio_format }} {% if audio_exact %}(exact match){% else %}(fallback){% endif %}{% else %}No match{% endif %}</strong><br>
|
||||||
combo: {{ media.get_best_combined_format }}
|
Video: <strong>{% if video_format %}{{ video_format }} {% if video_exact %}(exact match){% else %}(fallback){% endif %}{% else %}No match{% endif %}
|
||||||
</strong></td>
|
</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
<a href="{% url 'sync:tasks-completed' %}?filter={{ source.pk }}" class="btn">View tasks<span class="hide-on-small-only"> linked to this source</span> <i class="far fa-fw fa-clock"></i></a>
|
<a href="{% url 'sync:tasks-completed' %}?filter={{ source.pk }}" class="btn">View tasks<span class="hide-on-small-only"> linked to this source</span> <i class="far fa-fw fa-clock"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% include 'infobox.html' with message=message %}
|
||||||
{% if source.has_failed %}{% include 'errorbox.html' with message='This source has encountered permanent failures listed at the bottom of this page, check its settings' %}{% endif %}
|
{% if source.has_failed %}{% include 'errorbox.html' with message='This source has encountered permanent failures listed at the bottom of this page, check its settings' %}{% endif %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
|
|
|
@ -142,11 +142,21 @@ def parse_media_format(format_dict):
|
||||||
acodec = None
|
acodec = None
|
||||||
if acodec == 'NONE':
|
if acodec == 'NONE':
|
||||||
acodec = None
|
acodec = None
|
||||||
|
try:
|
||||||
|
fps = int(format_dict.get('fps', 0))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
fps = 0
|
||||||
|
format_full = format_dict.get('format_note', '').strip().upper()
|
||||||
|
format_str = format_full[:-2] if format_full.endswith('60') else format_full
|
||||||
return {
|
return {
|
||||||
'id': format_dict.get('format_id', ''),
|
'id': format_dict.get('format_id', ''),
|
||||||
|
'format': format_str,
|
||||||
|
'format_verbose': format_dict.get('format', ''),
|
||||||
'height': format_dict.get('height', 0),
|
'height': format_dict.get('height', 0),
|
||||||
'is_60fps': format_dict.get('fps', 0) == 60,
|
|
||||||
'is_hdr': 'HDR' in format_dict.get('format', '').upper(),
|
|
||||||
'vcodec': vcodec,
|
'vcodec': vcodec,
|
||||||
|
'vbr': format_dict.get('tbr', 0),
|
||||||
'acodec': acodec,
|
'acodec': acodec,
|
||||||
|
'abr': format_dict.get('abr', 0),
|
||||||
|
'is_60fps': fps > 50,
|
||||||
|
'is_hdr': 'HDR' in format_dict.get('format', '').upper(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,9 +41,7 @@ class SourcesView(ListView):
|
||||||
context_object_name = 'sources'
|
context_object_name = 'sources'
|
||||||
paginate_by = settings.SOURCES_PER_PAGE
|
paginate_by = settings.SOURCES_PER_PAGE
|
||||||
messages = {
|
messages = {
|
||||||
'source-created': _('Your new source has been added'),
|
|
||||||
'source-deleted': _('Your selected source has been deleted.'),
|
'source-deleted': _('Your selected source has been deleted.'),
|
||||||
'source-updated': _('Your selected source has been updated.'),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -235,7 +233,7 @@ class AddSourceView(CreateView):
|
||||||
return initial
|
return initial
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
url = reverse_lazy('sync:sources')
|
url = reverse_lazy('sync:source', kwargs={'pk': self.object.pk})
|
||||||
return append_uri_params(url, {'message': 'source-created'})
|
return append_uri_params(url, {'message': 'source-created'})
|
||||||
|
|
||||||
|
|
||||||
|
@ -243,9 +241,23 @@ class SourceView(DetailView):
|
||||||
|
|
||||||
template_name = 'sync/source.html'
|
template_name = 'sync/source.html'
|
||||||
model = Source
|
model = Source
|
||||||
|
messages = {
|
||||||
|
'source-created': _('Your new source has been created'),
|
||||||
|
'source-updated': _('Your source has been updated.'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.message = None
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
message_key = request.GET.get('message', '')
|
||||||
|
self.message = self.messages.get(message_key, '')
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_context_data(self, *args, **kwargs):
|
||||||
data = super().get_context_data(*args, **kwargs)
|
data = super().get_context_data(*args, **kwargs)
|
||||||
|
data['message'] = self.message
|
||||||
data['errors'] = []
|
data['errors'] = []
|
||||||
for error in get_source_completed_tasks(self.object.pk, only_errors=True):
|
for error in get_source_completed_tasks(self.object.pk, only_errors=True):
|
||||||
error_message = get_error_message(error)
|
error_message = get_error_message(error)
|
||||||
|
@ -264,7 +276,7 @@ class UpdateSourceView(UpdateView):
|
||||||
'source_acodec', 'prefer_60fps', 'prefer_hdr', 'fallback')
|
'source_acodec', 'prefer_60fps', 'prefer_hdr', 'fallback')
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
url = reverse_lazy('sync:sources')
|
url = reverse_lazy('sync:source', kwargs={'pk': self.object.pk})
|
||||||
return append_uri_params(url, {'message': 'source-updated'})
|
return append_uri_params(url, {'message': 'source-updated'})
|
||||||
|
|
||||||
|
|
||||||
|
@ -366,6 +378,19 @@ class MediaItemView(DetailView):
|
||||||
template_name = 'sync/media-item.html'
|
template_name = 'sync/media-item.html'
|
||||||
model = Media
|
model = Media
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
data = super().get_context_data(*args, **kwargs)
|
||||||
|
combined_exact, combined_format = self.object.get_best_combined_format()
|
||||||
|
audio_exact, audio_format = self.object.get_best_audio_format()
|
||||||
|
video_exact, video_format = self.object.get_best_video_format()
|
||||||
|
data['combined_exact'] = combined_exact
|
||||||
|
data['combined_format'] = combined_format
|
||||||
|
data['audio_exact'] = audio_exact
|
||||||
|
data['audio_format'] = audio_format
|
||||||
|
data['video_exact'] = video_exact
|
||||||
|
data['video_format'] = video_format
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class TasksView(ListView):
|
class TasksView(ListView):
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -130,6 +130,10 @@ MEDIA_THUMBNAIL_WIDTH = 430 # Width in pixels to resize thumbnai
|
||||||
MEDIA_THUMBNAIL_HEIGHT = 240 # Height in pixels to resize thumbnails to
|
MEDIA_THUMBNAIL_HEIGHT = 240 # Height in pixels to resize thumbnails to
|
||||||
|
|
||||||
|
|
||||||
|
VIDEO_HEIGHT_CUTOFF = 360 # Smallest resolution in pixels permitted to download
|
||||||
|
VIDEO_HEIGHT_IS_HD = 500 # Height in pixels to count as 'HD'
|
||||||
|
|
||||||
|
|
||||||
YOUTUBE_DEFAULTS = {
|
YOUTUBE_DEFAULTS = {
|
||||||
'no_color': True, # Do not use colours in output
|
'no_color': True, # Do not use colours in output
|
||||||
'age_limit': 99, # 'Age in years' to spoof
|
'age_limit': 99, # 'Age in years' to spoof
|
||||||
|
|
Loading…
Reference in New Issue