|
|
@ -28,6 +28,7 @@ import traceback
|
|
|
|
import random
|
|
|
|
import random
|
|
|
|
import unicodedata
|
|
|
|
import unicodedata
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from enum import Enum
|
|
|
|
from string import ascii_letters
|
|
|
|
from string import ascii_letters
|
|
|
|
|
|
|
|
|
|
|
|
from .compat import (
|
|
|
|
from .compat import (
|
|
|
@ -81,6 +82,7 @@ from .utils import (
|
|
|
|
make_HTTPS_handler,
|
|
|
|
make_HTTPS_handler,
|
|
|
|
MaxDownloadsReached,
|
|
|
|
MaxDownloadsReached,
|
|
|
|
network_exceptions,
|
|
|
|
network_exceptions,
|
|
|
|
|
|
|
|
number_of_digits,
|
|
|
|
orderedSet,
|
|
|
|
orderedSet,
|
|
|
|
OUTTMPL_TYPES,
|
|
|
|
OUTTMPL_TYPES,
|
|
|
|
PagedList,
|
|
|
|
PagedList,
|
|
|
@ -107,7 +109,6 @@ from .utils import (
|
|
|
|
strftime_or_none,
|
|
|
|
strftime_or_none,
|
|
|
|
subtitles_filename,
|
|
|
|
subtitles_filename,
|
|
|
|
supports_terminal_sequences,
|
|
|
|
supports_terminal_sequences,
|
|
|
|
TERMINAL_SEQUENCES,
|
|
|
|
|
|
|
|
ThrottledDownload,
|
|
|
|
ThrottledDownload,
|
|
|
|
to_high_limit_path,
|
|
|
|
to_high_limit_path,
|
|
|
|
traverse_obj,
|
|
|
|
traverse_obj,
|
|
|
@ -123,6 +124,7 @@ from .utils import (
|
|
|
|
YoutubeDLRedirectHandler,
|
|
|
|
YoutubeDLRedirectHandler,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
from .cache import Cache
|
|
|
|
from .cache import Cache
|
|
|
|
|
|
|
|
from .minicurses import format_text
|
|
|
|
from .extractor import (
|
|
|
|
from .extractor import (
|
|
|
|
gen_extractor_classes,
|
|
|
|
gen_extractor_classes,
|
|
|
|
get_info_extractor,
|
|
|
|
get_info_extractor,
|
|
|
@ -524,7 +526,10 @@ class YoutubeDL(object):
|
|
|
|
|
|
|
|
|
|
|
|
windows_enable_vt_mode()
|
|
|
|
windows_enable_vt_mode()
|
|
|
|
# FIXME: This will break if we ever print color to stdout
|
|
|
|
# FIXME: This will break if we ever print color to stdout
|
|
|
|
self.params['no_color'] = self.params.get('no_color') or not supports_terminal_sequences(self._err_file)
|
|
|
|
self._allow_colors = {
|
|
|
|
|
|
|
|
'screen': not self.params.get('no_color') and supports_terminal_sequences(self._screen_file),
|
|
|
|
|
|
|
|
'err': not self.params.get('no_color') and supports_terminal_sequences(self._err_file),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if sys.version_info < (3, 6):
|
|
|
|
if sys.version_info < (3, 6):
|
|
|
|
self.report_warning(
|
|
|
|
self.report_warning(
|
|
|
@ -532,10 +537,10 @@ class YoutubeDL(object):
|
|
|
|
|
|
|
|
|
|
|
|
if self.params.get('allow_unplayable_formats'):
|
|
|
|
if self.params.get('allow_unplayable_formats'):
|
|
|
|
self.report_warning(
|
|
|
|
self.report_warning(
|
|
|
|
f'You have asked for {self._color_text("unplayable formats", "blue")} to be listed/downloaded. '
|
|
|
|
f'You have asked for {self._format_err("UNPLAYABLE", self.Styles.EMPHASIS)} formats to be listed/downloaded. '
|
|
|
|
'This is a developer option intended for debugging. \n'
|
|
|
|
'This is a developer option intended for debugging. \n'
|
|
|
|
' If you experience any issues while using this option, '
|
|
|
|
' If you experience any issues while using this option, '
|
|
|
|
f'{self._color_text("DO NOT", "red")} open a bug report')
|
|
|
|
f'{self._format_err("DO NOT", self.Styles.ERROR)} open a bug report')
|
|
|
|
|
|
|
|
|
|
|
|
def check_deprecated(param, option, suggestion):
|
|
|
|
def check_deprecated(param, option, suggestion):
|
|
|
|
if self.params.get(param) is not None:
|
|
|
|
if self.params.get(param) is not None:
|
|
|
@ -554,6 +559,9 @@ class YoutubeDL(object):
|
|
|
|
for msg in self.params.get('_warnings', []):
|
|
|
|
for msg in self.params.get('_warnings', []):
|
|
|
|
self.report_warning(msg)
|
|
|
|
self.report_warning(msg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if 'list-formats' in self.params.get('compat_opts', []):
|
|
|
|
|
|
|
|
self.params['listformats_table'] = False
|
|
|
|
|
|
|
|
|
|
|
|
if 'overwrites' not in self.params and self.params.get('nooverwrites') is not None:
|
|
|
|
if 'overwrites' not in self.params and self.params.get('nooverwrites') is not None:
|
|
|
|
# nooverwrites was unnecessarily changed to overwrites
|
|
|
|
# nooverwrites was unnecessarily changed to overwrites
|
|
|
|
# in 0c3d0f51778b153f65c21906031c2e091fcfb641
|
|
|
|
# in 0c3d0f51778b153f65c21906031c2e091fcfb641
|
|
|
@ -826,10 +834,32 @@ class YoutubeDL(object):
|
|
|
|
self.to_stdout(
|
|
|
|
self.to_stdout(
|
|
|
|
message, skip_eol, quiet=self.params.get('quiet', False))
|
|
|
|
message, skip_eol, quiet=self.params.get('quiet', False))
|
|
|
|
|
|
|
|
|
|
|
|
def _color_text(self, text, color):
|
|
|
|
class Styles(Enum):
|
|
|
|
if self.params.get('no_color'):
|
|
|
|
HEADERS = 'yellow'
|
|
|
|
return text
|
|
|
|
EMPHASIS = 'blue'
|
|
|
|
return f'{TERMINAL_SEQUENCES[color.upper()]}{text}{TERMINAL_SEQUENCES["RESET_STYLE"]}'
|
|
|
|
ID = 'green'
|
|
|
|
|
|
|
|
DELIM = 'blue'
|
|
|
|
|
|
|
|
ERROR = 'red'
|
|
|
|
|
|
|
|
WARNING = 'yellow'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __format_text(self, out, text, f, fallback=None, *, test_encoding=False):
|
|
|
|
|
|
|
|
assert out in ('screen', 'err')
|
|
|
|
|
|
|
|
if test_encoding:
|
|
|
|
|
|
|
|
original_text = text
|
|
|
|
|
|
|
|
handle = self._screen_file if out == 'screen' else self._err_file
|
|
|
|
|
|
|
|
encoding = self.params.get('encoding') or getattr(handle, 'encoding', 'ascii')
|
|
|
|
|
|
|
|
text = text.encode(encoding, 'ignore').decode(encoding)
|
|
|
|
|
|
|
|
if fallback is not None and text != original_text:
|
|
|
|
|
|
|
|
text = fallback
|
|
|
|
|
|
|
|
if isinstance(f, self.Styles):
|
|
|
|
|
|
|
|
f = f._value_
|
|
|
|
|
|
|
|
return format_text(text, f) if self._allow_colors[out] else text if fallback is None else fallback
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _format_screen(self, *args, **kwargs):
|
|
|
|
|
|
|
|
return self.__format_text('screen', *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _format_err(self, *args, **kwargs):
|
|
|
|
|
|
|
|
return self.__format_text('err', *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
def report_warning(self, message, only_once=False):
|
|
|
|
def report_warning(self, message, only_once=False):
|
|
|
|
'''
|
|
|
|
'''
|
|
|
@ -841,14 +871,14 @@ class YoutubeDL(object):
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
if self.params.get('no_warnings'):
|
|
|
|
if self.params.get('no_warnings'):
|
|
|
|
return
|
|
|
|
return
|
|
|
|
self.to_stderr(f'{self._color_text("WARNING:", "yellow")} {message}', only_once)
|
|
|
|
self.to_stderr(f'{self._format_err("WARNING:", self.Styles.WARNING)} {message}', only_once)
|
|
|
|
|
|
|
|
|
|
|
|
def report_error(self, message, tb=None):
|
|
|
|
def report_error(self, message, tb=None):
|
|
|
|
'''
|
|
|
|
'''
|
|
|
|
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._color_text("ERROR:", "red")} {message}', tb)
|
|
|
|
self.trouble(f'{self._format_err("ERROR:", self.Styles.ERROR)} {message}', tb)
|
|
|
|
|
|
|
|
|
|
|
|
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'''
|
|
|
@ -977,8 +1007,8 @@ class YoutubeDL(object):
|
|
|
|
# For fields playlist_index, playlist_autonumber and autonumber convert all occurrences
|
|
|
|
# For fields playlist_index, playlist_autonumber and autonumber convert all occurrences
|
|
|
|
# of %(field)s to %(field)0Nd for backward compatibility
|
|
|
|
# of %(field)s to %(field)0Nd for backward compatibility
|
|
|
|
field_size_compat_map = {
|
|
|
|
field_size_compat_map = {
|
|
|
|
'playlist_index': len(str(info_dict.get('_last_playlist_index') or '')),
|
|
|
|
'playlist_index': number_of_digits(info_dict.get('_last_playlist_index') or 0),
|
|
|
|
'playlist_autonumber': len(str(info_dict.get('n_entries') or '')),
|
|
|
|
'playlist_autonumber': number_of_digits(info_dict.get('n_entries') or 0),
|
|
|
|
'autonumber': self.params.get('autonumber_size') or 5,
|
|
|
|
'autonumber': self.params.get('autonumber_size') or 5,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -3167,38 +3197,46 @@ class YoutubeDL(object):
|
|
|
|
res += '~' + format_bytes(fdict['filesize_approx'])
|
|
|
|
res += '~' + format_bytes(fdict['filesize_approx'])
|
|
|
|
return res
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _list_format_headers(self, *headers):
|
|
|
|
|
|
|
|
if self.params.get('listformats_table', True) is not False:
|
|
|
|
|
|
|
|
return [self._format_screen(header, self.Styles.HEADERS) for header in headers]
|
|
|
|
|
|
|
|
return headers
|
|
|
|
|
|
|
|
|
|
|
|
def list_formats(self, info_dict):
|
|
|
|
def list_formats(self, info_dict):
|
|
|
|
formats = info_dict.get('formats', [info_dict])
|
|
|
|
formats = info_dict.get('formats', [info_dict])
|
|
|
|
new_format = (
|
|
|
|
new_format = self.params.get('listformats_table', True) is not False
|
|
|
|
'list-formats' not in self.params.get('compat_opts', [])
|
|
|
|
|
|
|
|
and self.params.get('listformats_table', True) is not False)
|
|
|
|
|
|
|
|
if new_format:
|
|
|
|
if new_format:
|
|
|
|
|
|
|
|
tbr_digits = number_of_digits(max(f.get('tbr') or 0 for f in formats))
|
|
|
|
|
|
|
|
vbr_digits = number_of_digits(max(f.get('vbr') or 0 for f in formats))
|
|
|
|
|
|
|
|
abr_digits = number_of_digits(max(f.get('abr') or 0 for f in formats))
|
|
|
|
|
|
|
|
delim = self._format_screen('\u2502', self.Styles.DELIM, '|', test_encoding=True)
|
|
|
|
table = [
|
|
|
|
table = [
|
|
|
|
[
|
|
|
|
[
|
|
|
|
format_field(f, 'format_id'),
|
|
|
|
self._format_screen(format_field(f, 'format_id'), self.Styles.ID),
|
|
|
|
format_field(f, 'ext'),
|
|
|
|
format_field(f, 'ext'),
|
|
|
|
self.format_resolution(f),
|
|
|
|
self.format_resolution(f),
|
|
|
|
format_field(f, 'fps', '%d'),
|
|
|
|
format_field(f, 'fps', '%d'),
|
|
|
|
format_field(f, 'dynamic_range', '%s', ignore=(None, 'SDR')).replace('HDR', ''),
|
|
|
|
format_field(f, 'dynamic_range', '%s', ignore=(None, 'SDR')).replace('HDR', ''),
|
|
|
|
'|',
|
|
|
|
delim,
|
|
|
|
format_field(f, 'filesize', ' %s', func=format_bytes) + format_field(f, 'filesize_approx', '~%s', func=format_bytes),
|
|
|
|
format_field(f, 'filesize', ' %s', func=format_bytes) + format_field(f, 'filesize_approx', '~%s', func=format_bytes),
|
|
|
|
format_field(f, 'tbr', '%4dk'),
|
|
|
|
format_field(f, 'tbr', f'%{tbr_digits}dk'),
|
|
|
|
shorten_protocol_name(f.get('protocol', '').replace("native", "n")),
|
|
|
|
shorten_protocol_name(f.get('protocol', '').replace("native", "n")),
|
|
|
|
'|',
|
|
|
|
delim,
|
|
|
|
format_field(f, 'vcodec', default='unknown').replace('none', ''),
|
|
|
|
format_field(f, 'vcodec', default='unknown').replace('none', ''),
|
|
|
|
format_field(f, 'vbr', '%4dk'),
|
|
|
|
format_field(f, 'vbr', f'%{vbr_digits}dk'),
|
|
|
|
format_field(f, 'acodec', default='unknown').replace('none', ''),
|
|
|
|
format_field(f, 'acodec', default='unknown').replace('none', ''),
|
|
|
|
format_field(f, 'abr', '%3dk'),
|
|
|
|
format_field(f, 'abr', f'%{abr_digits}dk'),
|
|
|
|
format_field(f, 'asr', '%5dHz'),
|
|
|
|
format_field(f, 'asr', '%5dHz'),
|
|
|
|
', '.join(filter(None, (
|
|
|
|
', '.join(filter(None, (
|
|
|
|
'UNSUPPORTED' if f.get('ext') in ('f4f', 'f4m') else '',
|
|
|
|
self._format_screen('UNSUPPORTED', 'light red') if f.get('ext') in ('f4f', 'f4m') else '',
|
|
|
|
format_field(f, 'language', '[%s]'),
|
|
|
|
format_field(f, 'language', '[%s]'),
|
|
|
|
format_field(f, 'format_note'),
|
|
|
|
format_field(f, 'format_note'),
|
|
|
|
format_field(f, 'container', ignore=(None, f.get('ext'))),
|
|
|
|
format_field(f, 'container', ignore=(None, f.get('ext'))),
|
|
|
|
))),
|
|
|
|
))),
|
|
|
|
] for f in formats if f.get('preference') is None or f['preference'] >= -1000]
|
|
|
|
] for f in formats if f.get('preference') is None or f['preference'] >= -1000]
|
|
|
|
header_line = ['ID', 'EXT', 'RESOLUTION', 'FPS', 'HDR', '|', ' FILESIZE', ' TBR', 'PROTO',
|
|
|
|
header_line = self._list_format_headers(
|
|
|
|
'|', 'VCODEC', ' VBR', 'ACODEC', ' ABR', ' ASR', 'MORE INFO']
|
|
|
|
'ID', 'EXT', 'RESOLUTION', 'FPS', 'HDR', delim, ' FILESIZE', ' TBR', 'PROTO',
|
|
|
|
|
|
|
|
delim, 'VCODEC', ' VBR', 'ACODEC', ' ABR', ' ASR', 'MORE INFO')
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
table = [
|
|
|
|
table = [
|
|
|
|
[
|
|
|
|
[
|
|
|
@ -3213,7 +3251,10 @@ class YoutubeDL(object):
|
|
|
|
self.to_screen(
|
|
|
|
self.to_screen(
|
|
|
|
'[info] Available formats for %s:' % info_dict['id'])
|
|
|
|
'[info] Available formats for %s:' % info_dict['id'])
|
|
|
|
self.to_stdout(render_table(
|
|
|
|
self.to_stdout(render_table(
|
|
|
|
header_line, table, delim=new_format, extraGap=(0 if new_format else 1), hideEmpty=new_format))
|
|
|
|
header_line, table,
|
|
|
|
|
|
|
|
extraGap=(0 if new_format else 1),
|
|
|
|
|
|
|
|
hideEmpty=new_format,
|
|
|
|
|
|
|
|
delim=new_format and self._format_screen('\u2500', self.Styles.DELIM, '-', test_encoding=True)))
|
|
|
|
|
|
|
|
|
|
|
|
def list_thumbnails(self, info_dict):
|
|
|
|
def list_thumbnails(self, info_dict):
|
|
|
|
thumbnails = list(info_dict.get('thumbnails'))
|
|
|
|
thumbnails = list(info_dict.get('thumbnails'))
|
|
|
@ -3224,7 +3265,7 @@ class YoutubeDL(object):
|
|
|
|
self.to_screen(
|
|
|
|
self.to_screen(
|
|
|
|
'[info] Thumbnails for %s:' % info_dict['id'])
|
|
|
|
'[info] Thumbnails for %s:' % info_dict['id'])
|
|
|
|
self.to_stdout(render_table(
|
|
|
|
self.to_stdout(render_table(
|
|
|
|
['ID', 'width', 'height', 'URL'],
|
|
|
|
self._list_format_headers('ID', 'Width', 'Height', 'URL'),
|
|
|
|
[[t['id'], t.get('width', 'unknown'), t.get('height', 'unknown'), t['url']] for t in thumbnails]))
|
|
|
|
[[t['id'], t.get('width', 'unknown'), t.get('height', 'unknown'), t['url']] for t in thumbnails]))
|
|
|
|
|
|
|
|
|
|
|
|
def list_subtitles(self, video_id, subtitles, name='subtitles'):
|
|
|
|
def list_subtitles(self, video_id, subtitles, name='subtitles'):
|
|
|
@ -3241,7 +3282,7 @@ class YoutubeDL(object):
|
|
|
|
return [lang, ', '.join(names), ', '.join(exts)]
|
|
|
|
return [lang, ', '.join(names), ', '.join(exts)]
|
|
|
|
|
|
|
|
|
|
|
|
self.to_stdout(render_table(
|
|
|
|
self.to_stdout(render_table(
|
|
|
|
['Language', 'Name', 'Formats'],
|
|
|
|
self._list_format_headers('Language', 'Name', 'Formats'),
|
|
|
|
[_row(lang, formats) for lang, formats in subtitles.items()],
|
|
|
|
[_row(lang, formats) for lang, formats in subtitles.items()],
|
|
|
|
hideEmpty=True))
|
|
|
|
hideEmpty=True))
|
|
|
|
|
|
|
|
|
|
|
|