[core] Implement --color flag (#6904)

Authored by: Grub4K
This commit is contained in:
Simon Sawicki 2023-05-24 20:35:07 +02:00 committed by GitHub
parent 7aeda6cc9e
commit 8417f26b8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 66 additions and 12 deletions

View File

@ -425,8 +425,12 @@ ## General Options:
--no-wait-for-video Do not wait for scheduled streams (default)
--mark-watched Mark videos watched (even with --simulate)
--no-mark-watched Do not mark videos watched (default)
--no-colors Do not emit color codes in output (Alias:
--no-colours)
--color [STREAM:]POLICY Whether to emit color codes in output,
optionally prefixed by the STREAM (stdout or
stderr) to apply the setting to. Can be one
of "always", "auto" (default), "never", or
"no_color" (use non color terminal
sequences). Can be used multiple times
--compat-options OPTS Options that can help keep compatibility
with youtube-dl or youtube-dlc
configurations by reverting some of the
@ -2148,6 +2152,7 @@ #### Redundant options
--playlist-end NUMBER -I :NUMBER
--playlist-reverse -I ::-1
--no-playlist-reverse Default
--no-colors --color no_color
#### Not recommended

View File

@ -415,7 +415,12 @@ class YoutubeDL:
- Raise utils.DownloadCancelled(msg) to abort remaining
downloads when a video is rejected.
match_filter_func in utils.py is one example for this.
no_color: Do not emit color codes in output.
color: A Dictionary with output stream names as keys
and their respective color policy as values.
Can also just be a single color policy,
in which case it applies to all outputs.
Valid stream names are 'stdout' and 'stderr'.
Valid color policies are one of 'always', 'auto', 'no_color' or 'never'.
geo_bypass: Bypass geographic restriction via faking X-Forwarded-For
HTTP header
geo_bypass_country:
@ -537,6 +542,7 @@ class YoutubeDL:
data will be downloaded and processed by extractor.
You can reduce network I/O by disabling it if you don't
care about HLS. (only for youtube)
no_color: Same as `color='no_color'`
"""
_NUMERIC_FIELDS = {
@ -603,9 +609,24 @@ def __init__(self, params=None, auto_init=True):
except Exception as e:
self.write_debug(f'Failed to enable VT mode: {e}')
if self.params.get('no_color'):
if self.params.get('color') is not None:
self.report_warning('Overwriting params from "color" with "no_color"')
self.params['color'] = 'no_color'
term_allow_color = os.environ.get('TERM', '').lower() != 'dumb'
def process_color_policy(stream):
stream_name = {sys.stdout: 'stdout', sys.stderr: 'stderr'}[stream]
policy = traverse_obj(self.params, ('color', (stream_name, None), {str}), get_all=False)
if policy in ('auto', None):
return term_allow_color and supports_terminal_sequences(stream)
assert policy in ('always', 'never', 'no_color')
return {'always': True, 'never': False}.get(policy, policy)
self._allow_colors = Namespace(**{
type_: not self.params.get('no_color') and supports_terminal_sequences(stream)
for type_, stream in self._out_files.items_ if type_ != 'console'
name: process_color_policy(stream)
for name, stream in self._out_files.items_ if name != 'console'
})
# The code is left like this to be reused for future deprecations
@ -974,7 +995,7 @@ def _format_text(self, handle, allow_colors, text, f, fallback=None, *, test_enc
text = text.encode(encoding, 'ignore').decode(encoding)
if fallback is not None and text != original_text:
text = fallback
return format_text(text, f) if allow_colors else text if fallback is None else fallback
return format_text(text, f) if allow_colors is True else text if fallback is None else fallback
def _format_out(self, *args, **kwargs):
return self._format_text(self._out_files.out, self._allow_colors.out, *args, **kwargs)
@ -3769,9 +3790,14 @@ def print_debug_header(self):
def get_encoding(stream):
ret = str(getattr(stream, 'encoding', 'missing (%s)' % type(stream).__name__))
additional_info = []
if os.environ.get('TERM', '').lower() == 'dumb':
additional_info.append('dumb')
if not supports_terminal_sequences(stream):
from .utils import WINDOWS_VT_MODE # Must be imported locally
ret += ' (No VT)' if WINDOWS_VT_MODE is False else ' (No ANSI)'
additional_info.append('No VT' if WINDOWS_VT_MODE is False else 'No ANSI')
if additional_info:
ret = f'{ret} ({",".join(additional_info)})'
return ret
encoding_str = 'Encodings: locale %s, fs %s, pref %s, %s' % (

View File

@ -436,6 +436,10 @@ def metadataparser_actions(f):
elif ed and proto == 'default':
default_downloader = ed.get_basename()
for policy in opts.color.values():
if policy not in ('always', 'auto', 'no_color', 'never'):
raise ValueError(f'"{policy}" is not a valid color policy')
warnings, deprecation_warnings = [], []
# Common mistake: -f best
@ -894,7 +898,7 @@ def parse_options(argv=None):
'playlist_items': opts.playlist_items,
'xattr_set_filesize': opts.xattr_set_filesize,
'match_filter': opts.match_filter,
'no_color': opts.no_color,
'color': opts.color,
'ffmpeg_location': opts.ffmpeg_location,
'hls_prefer_native': opts.hls_prefer_native,
'hls_use_mpegts': opts.hls_use_mpegts,

View File

@ -296,7 +296,8 @@ def _prepare_multiline_status(self, lines=1):
self._multiline = BreaklineStatusPrinter(self.ydl._out_files.out, lines)
else:
self._multiline = MultilinePrinter(self.ydl._out_files.out, lines, not self.params.get('quiet'))
self._multiline.allow_colors = self._multiline._HAVE_FULLCAP and not self.params.get('no_color')
self._multiline.allow_colors = self.ydl._allow_colors.out and self.ydl._allow_colors.out != 'no_color'
self._multiline._HAVE_FULLCAP = self.ydl._allow_colors.out
def _finish_multiline_status(self):
self._multiline.end()

View File

@ -34,6 +34,7 @@
join_nonempty,
orderedSet_from_options,
remove_end,
variadic,
write_string,
)
from .version import CHANNEL, __version__
@ -250,7 +251,7 @@ def _dict_from_options_callback(
if multiple_args:
val = [val, *value[1:]]
elif default_key is not None:
keys, val = [default_key], value
keys, val = variadic(default_key), value
else:
raise optparse.OptionValueError(
f'wrong {opt_str} formatting; it should be {option.metavar}, not "{value}"')
@ -440,8 +441,25 @@ def _alias_callback(option, opt_str, value, parser, opts, nargs):
help='Do not mark videos watched (default)')
general.add_option(
'--no-colors', '--no-colours',
action='store_true', dest='no_color', default=False,
help='Do not emit color codes in output (Alias: --no-colours)')
action='store_const', dest='color', const={
'stdout': 'no_color',
'stderr': 'no_color',
},
help=optparse.SUPPRESS_HELP)
general.add_option(
'--color',
dest='color', metavar='[STREAM:]POLICY', default={}, type='str',
action='callback', callback=_dict_from_options_callback,
callback_kwargs={
'allowed_keys': 'stdout|stderr',
'default_key': ['stdout', 'stderr'],
'process': str.strip,
}, help=(
'Whether to emit color codes in output, optionally prefixed by '
'the STREAM (stdout or stderr) to apply the setting to. '
'Can be one of "always", "auto" (default), "never", or '
'"no_color" (use non color terminal sequences). '
'Can be used multiple times'))
general.add_option(
'--compat-options',
metavar='OPTS', dest='compat_opts', default=set(), type='str',