[youtube] Fix format sorting when using alternate clients

This commit is contained in:
pukkandan 2021-07-26 03:33:42 +05:30
parent 5a1fc62b41
commit 2a9c6dcd22
No known key found for this signature in database
GPG key ID: 0F00D95A001F4698

View file

@ -2499,11 +2499,12 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
def _extract_formats(self, streaming_data, video_id, player_url, is_live): def _extract_formats(self, streaming_data, video_id, player_url, is_live):
itags, stream_ids = [], [] itags, stream_ids = [], []
itag_qualities = {} itag_qualities, res_qualities = {}, {}
q = qualities([ q = qualities([
# "tiny" is the smallest video-only format. But some audio-only formats # Normally tiny is the smallest video-only formats. But
# was also labeled "tiny". It is not clear if such formats still exist # audio-only formats with unknown quality may get tagged as tiny
'tiny', 'audio_quality_low', 'audio_quality_medium', 'audio_quality_high', # Audio only formats 'tiny',
'audio_quality_ultralow', 'audio_quality_low', 'audio_quality_medium', 'audio_quality_high', # Audio only formats
'small', 'medium', 'large', 'hd720', 'hd1080', 'hd1440', 'hd2160', 'hd2880', 'highres' 'small', 'medium', 'large', 'hd720', 'hd1080', 'hd1440', 'hd2160', 'hd2880', 'highres'
]) ])
streaming_formats = traverse_obj(streaming_data, (..., ('formats', 'adaptiveFormats'), ...), default=[]) streaming_formats = traverse_obj(streaming_data, (..., ('formats', 'adaptiveFormats'), ...), default=[])
@ -2519,10 +2520,18 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
continue continue
quality = fmt.get('quality') quality = fmt.get('quality')
height = int_or_none(fmt.get('height'))
if quality == 'tiny' or not quality: if quality == 'tiny' or not quality:
quality = fmt.get('audioQuality', '').lower() or quality quality = fmt.get('audioQuality', '').lower() or quality
if itag and quality: # The 3gp format (17) in android client has a quality of "small",
itag_qualities[itag] = quality # but is actually worse than other formats
if itag == '17':
quality = 'tiny'
if quality:
if itag:
itag_qualities[itag] = quality
if height:
res_qualities[height] = quality
# FORMAT_STREAM_TYPE_OTF(otf=1) requires downloading the init fragment # FORMAT_STREAM_TYPE_OTF(otf=1) requires downloading the init fragment
# (adding `&sq=0` to the URL) and parsing emsg box to determine the # (adding `&sq=0` to the URL) and parsing emsg box to determine the
# number of fragment that would subsequently requested with (`&sq=N`) # number of fragment that would subsequently requested with (`&sq=N`)
@ -2553,13 +2562,14 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'filesize': int_or_none(fmt.get('contentLength')), 'filesize': int_or_none(fmt.get('contentLength')),
'format_id': itag, 'format_id': itag,
'format_note': ', '.join(filter(None, ( 'format_note': ', '.join(filter(None, (
audio_track.get('displayName'), fmt.get('qualityLabel') or quality))), audio_track.get('displayName'),
fmt.get('qualityLabel') or quality.replace('audio_quality_', '')))),
'fps': int_or_none(fmt.get('fps')), 'fps': int_or_none(fmt.get('fps')),
'height': int_or_none(fmt.get('height')), 'height': height,
'quality': q(quality), 'quality': q(quality),
'tbr': tbr, 'tbr': tbr,
'url': fmt_url, 'url': fmt_url,
'width': fmt.get('width'), 'width': int_or_none(fmt.get('width')),
'language': audio_track.get('id', '').split('.')[0], 'language': audio_track.get('id', '').split('.')[0],
} }
mime_mobj = re.match( mime_mobj = re.match(
@ -2567,11 +2577,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
if mime_mobj: if mime_mobj:
dct['ext'] = mimetype2ext(mime_mobj.group(1)) dct['ext'] = mimetype2ext(mime_mobj.group(1))
dct.update(parse_codecs(mime_mobj.group(2))) dct.update(parse_codecs(mime_mobj.group(2)))
# The 3gp format in android client has a quality of "small",
# but is actually worse than all other formats
if dct['ext'] == '3gp':
dct['quality'] = q('tiny')
dct['preference'] = -10
no_audio = dct.get('acodec') == 'none' no_audio = dct.get('acodec') == 'none'
no_video = dct.get('vcodec') == 'none' no_video = dct.get('vcodec') == 'none'
if no_audio: if no_audio:
@ -2591,11 +2596,16 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
get_dash = not is_live and 'dash' not in skip_manifests and self.get_param('youtube_include_dash_manifest', True) get_dash = not is_live and 'dash' not in skip_manifests and self.get_param('youtube_include_dash_manifest', True)
get_hls = 'hls' not in skip_manifests and self.get_param('youtube_include_hls_manifest', True) get_hls = 'hls' not in skip_manifests and self.get_param('youtube_include_hls_manifest', True)
def guess_quality(f):
for val, qdict in ((f.get('format_id'), itag_qualities), (f.get('height'), res_qualities)):
if val in qdict:
return q(qdict[val])
return -1
for sd in streaming_data: for sd in streaming_data:
hls_manifest_url = get_hls and sd.get('hlsManifestUrl') hls_manifest_url = get_hls and sd.get('hlsManifestUrl')
if hls_manifest_url: if hls_manifest_url:
for f in self._extract_m3u8_formats( for f in self._extract_m3u8_formats(hls_manifest_url, video_id, 'mp4', fatal=False):
hls_manifest_url, video_id, 'mp4', fatal=False):
itag = self._search_regex( itag = self._search_regex(
r'/itag/(\d+)', f['url'], 'itag', default=None) r'/itag/(\d+)', f['url'], 'itag', default=None)
if itag in itags: if itag in itags:
@ -2603,19 +2613,18 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
if itag: if itag:
f['format_id'] = itag f['format_id'] = itag
itags.append(itag) itags.append(itag)
f['quality'] = guess_quality(f)
yield f yield f
dash_manifest_url = get_dash and sd.get('dashManifestUrl') dash_manifest_url = get_dash and sd.get('dashManifestUrl')
if dash_manifest_url: if dash_manifest_url:
for f in self._extract_mpd_formats( for f in self._extract_mpd_formats(dash_manifest_url, video_id, fatal=False):
dash_manifest_url, video_id, fatal=False):
itag = f['format_id'] itag = f['format_id']
if itag in itags: if itag in itags:
continue continue
if itag: if itag:
itags.append(itag) itags.append(itag)
if itag in itag_qualities: f['quality'] = guess_quality(f)
f['quality'] = q(itag_qualities[itag])
filesize = int_or_none(self._search_regex( filesize = int_or_none(self._search_regex(
r'/clen/(\d+)', f.get('fragment_base_url') r'/clen/(\d+)', f.get('fragment_base_url')
or f['url'], 'file size', default=None)) or f['url'], 'file size', default=None))
@ -2740,13 +2749,14 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
self.raise_no_formats(reason, expected=True) self.raise_no_formats(reason, expected=True)
for f in formats: for f in formats:
# TODO: detect if throttled if '&c=WEB&' in f['url'] and '&ratebypass=yes&' not in f['url']: # throttled
if '&n=' in f['url']: # possibly throttled
f['source_preference'] = -10 f['source_preference'] = -10
# note = f.get('format_note') note = f.get('format_note')
# f['format_note'] = f'{note} (throttled)' if note else '(throttled)' f['format_note'] = f'{note} (throttled)' if note else '(throttled)'
self._sort_formats(formats) # Source is given priority since formats that throttle are given lower source_preference
# When throttling issue is fully fixed, remove this
self._sort_formats(formats, ('quality', 'height', 'fps', 'source'))
keywords = get_first(video_details, 'keywords', expected_type=list) or [] keywords = get_first(video_details, 'keywords', expected_type=list) or []
if not keywords and webpage: if not keywords and webpage: