diff --git a/youtube_dl/__init__.py b/youtube_dl/__init__.py index 9a659fc65..179e34ed8 100644 --- a/youtube_dl/__init__.py +++ b/youtube_dl/__init__.py @@ -338,6 +338,8 @@ def _real_main(argv=None): 'simulate': opts.simulate or any_getting, 'skip_download': opts.skip_download, 'format': opts.format, + 'format_sort': opts.format_sort, + 'format_sort_force': opts.format_sort_force, 'listformats': opts.listformats, 'outtmpl': outtmpl, 'autonumber_size': opts.autonumber_size, diff --git a/youtube_dl/extractor/common.py b/youtube_dl/extractor/common.py index 021945a89..29cb3a7f0 100644 --- a/youtube_dl/extractor/common.py +++ b/youtube_dl/extractor/common.py @@ -1358,6 +1358,39 @@ class InfoExtractor(object): if not formats: raise ExtractorError('No video formats found') + def _get_sort_list(): + sort = self._downloader.params.get('format_sort', '') + sort = sort.split(',') if isinstance(sort, str) else [] + if self._downloader.params.get('verbose', False): + self._downloader.to_screen('[debug] Sort order given by user: %s' % sort) + if field_preference: + self._downloader.to_screen('[debug] Sort order given by extractor: %s' % ','.join(field_preference)) + + return (tuple() + if self._downloader.params.get('format_sort_force') + else ('preference', 'language_preference')) + \ + tuple(sort) + \ + (tuple(field_preference) + if isinstance(field_preference, (list, tuple)) + else tuple()) + \ + ('preference', 'language_preference', 'quality', # default order + 'tbr', 'filesize', 'vbr', 'height', 'width', + 'proto_preference', 'ext_preference', 'codec_preference', + 'abr', 'audio_ext_preference', 'audio_codec_preference', + 'fps', 'filesize_approx', 'source_preference', 'format_id') + + sort = {} + for item in _get_sort_list(): + item = item.split(":", 1) + lim = float(item[1]) if len(item)>1 else None + if item[0][:1] == "+": + sort.setdefault(item[0][1:], (-1, lim)) # item[key] = (Reverse for best, reverse again below this) + else: + sort.setdefault(item[0], (1, lim)) + if self._downloader.params.get('verbose', False): + self._downloader.to_screen('[debug] Formats sorted by: %s' + % ["".join(( ("+" if sort[i][0]<0 else ""), i, ":"+str(sort[i][1]) )) for i in sort] ) + for f in formats: # Automatically determine tbr when missing based on abr and vbr (improves # formats sorting in some cases) @@ -1370,13 +1403,6 @@ class InfoExtractor(object): if not f.get('ext') and 'url' in f: f['ext'] = determine_ext(f['url']) - if isinstance(field_preference, (list, tuple)): - return tuple( - f.get(field) - if f.get(field) is not None - else ('' if field == 'format_id' else -1) - for field in field_preference) - preference = f.get('preference') if preference is None: preference = 0 @@ -1410,24 +1436,69 @@ class InfoExtractor(object): ext_preference = -1 audio_ext_preference = 0 - return ( - preference, - f.get('language_preference') if f.get('language_preference') is not None else -1, - f.get('quality') if f.get('quality') is not None else -1, - f.get('tbr') if f.get('tbr') is not None else -1, - f.get('filesize') if f.get('filesize') is not None else -1, - f.get('vbr') if f.get('vbr') is not None else -1, - f.get('height') if f.get('height') is not None else -1, - f.get('width') if f.get('width') is not None else -1, - proto_preference, - ext_preference, - f.get('abr') if f.get('abr') is not None else -1, - audio_ext_preference, - f.get('fps') if f.get('fps') is not None else -1, - f.get('filesize_approx') if f.get('filesize_approx') is not None else -1, - f.get('source_preference') if f.get('source_preference') is not None else -1, - f.get('format_id') if f.get('format_id') is not None else '', - ) + if f.get('vcodec') == 'none': + codec_preference = -1 + elif not f.get('vcodec'): + codec_preference = 0 + else: + codec_preference = 10 + ORDER = ['av01', 'vp9', '(h265|he?vc?)', '(h264|avc)', 'vp8', '(mp4v|h263)', 'theora'] + for i in ORDER: + if re.match(i, f.get('vcodec')): + break + else: + codec_preference -= 1 + + if f.get('acodec') == 'none': + audio_codec_preference = -1 + elif not f.get('acodec'): + audio_codec_preference = 0 + else: + audio_codec_preference = 10 + ORDER = ['opus', 'vorbis', 'aac', 'mp4a', 'mp3', 'e?a?c-?3', 'dts'] + for i in ORDER: + if re.match(i, f.get('acodec')): + break + else: + audio_codec_preference -= 1 + + prefVars = {'preference': preference, + 'proto_preference': proto_preference, + 'ext_preference': ext_preference, + 'audio_ext_preference': audio_ext_preference, + 'codec_preference': codec_preference, + 'audio_codec_preference': audio_codec_preference } + + def format_get_val(field): + return f.get(field) if prefVars.get(field) is None else prefVars.get(field) + + def format_get_preference(field): + val = format_get_val(field) + return ( + (0, f.get(field, '')) + if field == 'format_id' + else (-10, 0) + if val is None + else (0, sort[field][0]*val) + if sort[field][1] is None + else (0, -val) + if val==sort[field][1] and sort[field][0]<0 + else (0, -val) + if val>sort[field][1] + else (-1, val) + if sort[field][0]<0 + else (0, val) ) + + ''' # For DEBUGGING + if self._downloader.params.get('verbose', False): + for field in sort: + self._downloader.to_screen('[debug] %s[%s] = %s | Pref = %s' + %( field, format_get_val('format_id'), format_get_val(field), str(format_get_preference(field)) ) ) + self._downloader.to_screen('') + ''' #''' + + return tuple(format_get_preference(field) for field in sort ) + formats.sort(key=_formats_key) def _check_formats(self, formats, video_id): diff --git a/youtube_dl/options.py b/youtube_dl/options.py index 6d5ac62b3..83e31112e 100644 --- a/youtube_dl/options.py +++ b/youtube_dl/options.py @@ -394,6 +394,32 @@ def parseOpts(overrideArguments=None): '-f', '--format', action='store', dest='format', metavar='FORMAT', default=None, help='Video format code, see the "FORMAT SELECTION" for all the info') + video_format.add_option( + '--format-sort', + action='store', dest='format_sort', metavar='FORMAT', default=None, + help=( + 'Sort the formats by the fields given. ' + 'Default order: preference, language_preference, quality, ' + 'tbr, filesize, vbr, height, width, ' + 'proto_preference, ext_preference, codec_preference, ' + 'abr, audio_ext_preference, audio_codec_preference, ' + 'fps, filesize_approx, source_preference, format_id. ' + 'Prefix the field (except format_id) by a + to sort it in reverse. ' + 'Suffix the field with :value to give highest preference to "value". ' + 'preference and language_preference will always have the highest priority ' + 'unless --format-sort-force is given. ' + 'Examples: 1) "-f bestvideo --format-sort +height:720,fps,+filesize" gets the video with ' + 'the smallest filesize with largest fps with ' + 'the smallest height>=720 (or largest height available if there is no such format). ' + '2) "-f bestvideo --format-sort height:720,proto_preference,tbr" gets the video with ' + 'largest bitrate with best protocol with ' + 'the largest height<=720 (or smallest height available if there is no such format)')) + video_format.add_option( + '--format-sort-force', + action='store_true', dest='format_sort_force', metavar='FORMAT', default=False, + help=( + 'User specified sort order takes priority even over ' + 'preference and language_preference')) video_format.add_option( '--all-formats', action='store_const', dest='format', const='all',