Add interactive format selection with `-f -`

Closes #2065
pull/2077/head
pukkandan 2 years ago
parent 1cefca9e44
commit fa9f30b802
No known key found for this signature in database
GPG Key ID: 0F00D95A001F4698

@ -1290,6 +1290,8 @@ The simplest case is requesting a specific format, for example with `-f 22` you
You can also use a file extension (currently `3gp`, `aac`, `flv`, `m4a`, `mp3`, `mp4`, `ogg`, `wav`, `webm` are supported) to download the best quality format of a particular file extension served as a single file, e.g. `-f webm` will download the best quality format with the `webm` extension served as a single file. You can also use a file extension (currently `3gp`, `aac`, `flv`, `m4a`, `mp3`, `mp4`, `ogg`, `wav`, `webm` are supported) to download the best quality format of a particular file extension served as a single file, e.g. `-f webm` will download the best quality format with the `webm` extension served as a single file.
You can use `-f -` to interactively provide the format selector *for each video*
You can also use special names to select particular edge case formats: You can also use special names to select particular edge case formats:
- `all`: Select **all formats** separately - `all`: Select **all formats** separately

@ -624,7 +624,7 @@ class YoutubeDL(object):
# Creating format selector here allows us to catch syntax errors before the extraction # Creating format selector here allows us to catch syntax errors before the extraction
self.format_selector = ( self.format_selector = (
None if self.params.get('format') is None self.params.get('format') if self.params.get('format') in (None, '-')
else self.params['format'] if callable(self.params['format']) else self.params['format'] if callable(self.params['format'])
else self.build_format_selector(self.params['format'])) else self.build_format_selector(self.params['format']))
@ -818,14 +818,15 @@ class YoutubeDL(object):
if self.params.get('cookiefile') is not None: if self.params.get('cookiefile') is not None:
self.cookiejar.save(ignore_discard=True, ignore_expires=True) self.cookiejar.save(ignore_discard=True, ignore_expires=True)
def trouble(self, message=None, tb=None): def trouble(self, message=None, tb=None, is_error=True):
"""Determine action to take when a download problem appears. """Determine action to take when a download problem appears.
Depending on if the downloader has been configured to ignore Depending on if the downloader has been configured to ignore
download errors or not, this method may throw an exception or download errors or not, this method may throw an exception or
not when errors are found, after printing the message. not when errors are found, after printing the message.
tb, if given, is additional traceback information. @param tb If given, is additional traceback information
@param is_error Whether to raise error according to ignorerrors
""" """
if message is not None: if message is not None:
self.to_stderr(message) self.to_stderr(message)
@ -841,6 +842,8 @@ class YoutubeDL(object):
tb = ''.join(tb_data) tb = ''.join(tb_data)
if tb: if tb:
self.to_stderr(tb) self.to_stderr(tb)
if not is_error:
return
if not self.params.get('ignoreerrors'): if not self.params.get('ignoreerrors'):
if sys.exc_info()[0] and hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]: if sys.exc_info()[0] and hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
exc_info = sys.exc_info()[1].exc_info exc_info = sys.exc_info()[1].exc_info
@ -900,12 +903,12 @@ class YoutubeDL(object):
else: else:
self.to_stderr(f'{self._format_err("DeprecationWarning:", self.Styles.ERROR)} {message}', True) self.to_stderr(f'{self._format_err("DeprecationWarning:", self.Styles.ERROR)} {message}', True)
def report_error(self, message, tb=None): def report_error(self, message, *args, **kwargs):
''' '''
Do the same as trouble, but prefixes the message with 'ERROR:', colored Do the same as trouble, but prefixes the message with 'ERROR:', colored
in red if stderr is a tty file. in red if stderr is a tty file.
''' '''
self.trouble(f'{self._format_err("ERROR:", self.Styles.ERROR)} {message}', tb) self.trouble(f'{self._format_err("ERROR:", self.Styles.ERROR)} {message}', *args, **kwargs)
def write_debug(self, message, only_once=False): def write_debug(self, message, only_once=False):
'''Log debug message or Print message to stderr''' '''Log debug message or Print message to stderr'''
@ -2448,20 +2451,21 @@ class YoutubeDL(object):
# The pre-processors may have modified the formats # The pre-processors may have modified the formats
formats = info_dict.get('formats', [info_dict]) formats = info_dict.get('formats', [info_dict])
list_only = self.params.get('simulate') is None and (
self.params.get('list_thumbnails') or self.params.get('listformats') or self.params.get('listsubtitles'))
interactive_format_selection = not list_only and self.format_selector == '-'
if self.params.get('list_thumbnails'): if self.params.get('list_thumbnails'):
self.list_thumbnails(info_dict) self.list_thumbnails(info_dict)
if self.params.get('listformats'):
if not info_dict.get('formats') and not info_dict.get('url'):
self.to_screen('%s has no formats' % info_dict['id'])
else:
self.list_formats(info_dict)
if self.params.get('listsubtitles'): if self.params.get('listsubtitles'):
if 'automatic_captions' in info_dict: if 'automatic_captions' in info_dict:
self.list_subtitles( self.list_subtitles(
info_dict['id'], automatic_captions, 'automatic captions') info_dict['id'], automatic_captions, 'automatic captions')
self.list_subtitles(info_dict['id'], subtitles, 'subtitles') self.list_subtitles(info_dict['id'], subtitles, 'subtitles')
list_only = self.params.get('simulate') is None and ( if self.params.get('listformats') or interactive_format_selection:
self.params.get('list_thumbnails') or self.params.get('listformats') or self.params.get('listsubtitles')) if not info_dict.get('formats') and not info_dict.get('url'):
self.to_screen('%s has no formats' % info_dict['id'])
else:
self.list_formats(info_dict)
if list_only: if list_only:
# Without this printing, -F --print-json will not work # Without this printing, -F --print-json will not work
self.__forced_printings(info_dict, self.prepare_filename(info_dict), incomplete=True) self.__forced_printings(info_dict, self.prepare_filename(info_dict), incomplete=True)
@ -2473,6 +2477,16 @@ class YoutubeDL(object):
self.write_debug('Default format spec: %s' % req_format) self.write_debug('Default format spec: %s' % req_format)
format_selector = self.build_format_selector(req_format) format_selector = self.build_format_selector(req_format)
while True:
if interactive_format_selection:
req_format = input(
self._format_screen('\nEnter format selector: ', self.Styles.EMPHASIS))
try:
format_selector = self.build_format_selector(req_format)
except SyntaxError as err:
self.report_error(err, tb=False, is_error=False)
continue
# While in format selection we may need to have an access to the original # While in format selection we may need to have an access to the original
# format set in order to calculate some metrics or do some processing. # format set in order to calculate some metrics or do some processing.
# For now we need to be able to guess whether original formats provided # For now we need to be able to guess whether original formats provided
@ -2500,6 +2514,11 @@ class YoutubeDL(object):
} }
formats_to_download = list(format_selector(ctx)) formats_to_download = list(format_selector(ctx))
if interactive_format_selection and not formats_to_download:
self.report_error('Requested format is not available', tb=False, is_error=False)
continue
break
if not formats_to_download: if not formats_to_download:
if not self.params.get('ignore_no_formats_error'): if not self.params.get('ignore_no_formats_error'):
raise ExtractorError('Requested format is not available', expected=True, raise ExtractorError('Requested format is not available', expected=True,

Loading…
Cancel
Save