|
|
|
@ -65,7 +65,8 @@ from .utils import (
|
|
|
|
|
float_or_none,
|
|
|
|
|
format_bytes,
|
|
|
|
|
format_field,
|
|
|
|
|
STR_FORMAT_RE,
|
|
|
|
|
STR_FORMAT_RE_TMPL,
|
|
|
|
|
STR_FORMAT_TYPES,
|
|
|
|
|
formatSeconds,
|
|
|
|
|
GeoRestrictedError,
|
|
|
|
|
HEADRequest,
|
|
|
|
@ -845,20 +846,40 @@ class YoutubeDL(object):
|
|
|
|
|
return sanitize_path(path, force=self.params.get('windowsfilenames'))
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def validate_outtmpl(tmpl):
|
|
|
|
|
def _outtmpl_expandpath(outtmpl):
|
|
|
|
|
# expand_path translates '%%' into '%' and '$$' into '$'
|
|
|
|
|
# correspondingly that is not what we want since we need to keep
|
|
|
|
|
# '%%' intact for template dict substitution step. Working around
|
|
|
|
|
# with boundary-alike separator hack.
|
|
|
|
|
sep = ''.join([random.choice(ascii_letters) for _ in range(32)])
|
|
|
|
|
outtmpl = outtmpl.replace('%%', '%{0}%'.format(sep)).replace('$$', '${0}$'.format(sep))
|
|
|
|
|
|
|
|
|
|
# outtmpl should be expand_path'ed before template dict substitution
|
|
|
|
|
# because meta fields may contain env variables we don't want to
|
|
|
|
|
# be expanded. For example, for outtmpl "%(title)s.%(ext)s" and
|
|
|
|
|
# title "Hello $PATH", we don't want `$PATH` to be expanded.
|
|
|
|
|
return expand_path(outtmpl).replace(sep, '')
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def escape_outtmpl(outtmpl):
|
|
|
|
|
''' Escape any remaining strings like %s, %abc% etc. '''
|
|
|
|
|
return re.sub(
|
|
|
|
|
STR_FORMAT_RE_TMPL.format('', '(?![%(\0])'),
|
|
|
|
|
lambda mobj: ('' if mobj.group('has_key') else '%') + mobj.group(0),
|
|
|
|
|
outtmpl)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def validate_outtmpl(cls, outtmpl):
|
|
|
|
|
''' @return None or Exception object '''
|
|
|
|
|
outtmpl = cls.escape_outtmpl(cls._outtmpl_expandpath(outtmpl))
|
|
|
|
|
try:
|
|
|
|
|
re.sub(
|
|
|
|
|
STR_FORMAT_RE.format(''),
|
|
|
|
|
lambda mobj: ('%' if not mobj.group('has_key') else '') + mobj.group(0),
|
|
|
|
|
tmpl
|
|
|
|
|
) % collections.defaultdict(int)
|
|
|
|
|
outtmpl % collections.defaultdict(int)
|
|
|
|
|
return None
|
|
|
|
|
except ValueError as err:
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
|
|
def prepare_outtmpl(self, outtmpl, info_dict, sanitize=None):
|
|
|
|
|
""" Make the template and info_dict suitable for substitution (outtmpl % info_dict)"""
|
|
|
|
|
""" Make the template and info_dict suitable for substitution : ydl.outtmpl_escape(outtmpl) % info_dict """
|
|
|
|
|
info_dict = dict(info_dict)
|
|
|
|
|
na = self.params.get('outtmpl_na_placeholder', 'NA')
|
|
|
|
|
|
|
|
|
@ -879,7 +900,7 @@ class YoutubeDL(object):
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TMPL_DICT = {}
|
|
|
|
|
EXTERNAL_FORMAT_RE = re.compile(STR_FORMAT_RE.format('[^)]*'))
|
|
|
|
|
EXTERNAL_FORMAT_RE = re.compile(STR_FORMAT_RE_TMPL.format('[^)]*', f'[{STR_FORMAT_TYPES}]'))
|
|
|
|
|
MATH_FUNCTIONS = {
|
|
|
|
|
'+': float.__add__,
|
|
|
|
|
'-': float.__sub__,
|
|
|
|
@ -938,10 +959,11 @@ class YoutubeDL(object):
|
|
|
|
|
|
|
|
|
|
def create_key(outer_mobj):
|
|
|
|
|
if not outer_mobj.group('has_key'):
|
|
|
|
|
return '%{}'.format(outer_mobj.group(0))
|
|
|
|
|
return f'%{outer_mobj.group(0)}'
|
|
|
|
|
|
|
|
|
|
prefix = outer_mobj.group('prefix')
|
|
|
|
|
key = outer_mobj.group('key')
|
|
|
|
|
fmt = outer_mobj.group('format')
|
|
|
|
|
original_fmt = fmt = outer_mobj.group('format')
|
|
|
|
|
mobj = re.match(INTERNAL_FORMAT_RE, key)
|
|
|
|
|
if mobj is None:
|
|
|
|
|
value, default, mobj = None, na, {'fields': ''}
|
|
|
|
@ -965,6 +987,7 @@ class YoutubeDL(object):
|
|
|
|
|
value = float_or_none(value)
|
|
|
|
|
if value is None:
|
|
|
|
|
value, fmt = default, 's'
|
|
|
|
|
|
|
|
|
|
if sanitize:
|
|
|
|
|
if fmt[-1] == 'r':
|
|
|
|
|
# If value is an object, sanitize might convert it to a string
|
|
|
|
@ -972,9 +995,10 @@ class YoutubeDL(object):
|
|
|
|
|
value, fmt = repr(value), '%ss' % fmt[:-1]
|
|
|
|
|
if fmt[-1] in 'csr':
|
|
|
|
|
value = sanitize(mobj['fields'].split('.')[-1], value)
|
|
|
|
|
key += '\0%s' % fmt
|
|
|
|
|
|
|
|
|
|
key = '%s\0%s' % (key.replace('%', '%\0'), original_fmt)
|
|
|
|
|
TMPL_DICT[key] = value
|
|
|
|
|
return '%({key}){fmt}'.format(key=key, fmt=fmt)
|
|
|
|
|
return f'{prefix}%({key}){fmt}'
|
|
|
|
|
|
|
|
|
|
return EXTERNAL_FORMAT_RE.sub(create_key, outtmpl), TMPL_DICT
|
|
|
|
|
|
|
|
|
@ -986,19 +1010,8 @@ class YoutubeDL(object):
|
|
|
|
|
is_id=(k == 'id' or k.endswith('_id')))
|
|
|
|
|
outtmpl = self.outtmpl_dict.get(tmpl_type, self.outtmpl_dict['default'])
|
|
|
|
|
outtmpl, template_dict = self.prepare_outtmpl(outtmpl, info_dict, sanitize)
|
|
|
|
|
|
|
|
|
|
# expand_path translates '%%' into '%' and '$$' into '$'
|
|
|
|
|
# correspondingly that is not what we want since we need to keep
|
|
|
|
|
# '%%' intact for template dict substitution step. Working around
|
|
|
|
|
# with boundary-alike separator hack.
|
|
|
|
|
sep = ''.join([random.choice(ascii_letters) for _ in range(32)])
|
|
|
|
|
outtmpl = outtmpl.replace('%%', '%{0}%'.format(sep)).replace('$$', '${0}$'.format(sep))
|
|
|
|
|
|
|
|
|
|
# outtmpl should be expand_path'ed before template dict substitution
|
|
|
|
|
# because meta fields may contain env variables we don't want to
|
|
|
|
|
# be expanded. For example, for outtmpl "%(title)s.%(ext)s" and
|
|
|
|
|
# title "Hello $PATH", we don't want `$PATH` to be expanded.
|
|
|
|
|
filename = expand_path(outtmpl).replace(sep, '') % template_dict
|
|
|
|
|
outtmpl = self.escape_outtmpl(self._outtmpl_expandpath(outtmpl))
|
|
|
|
|
filename = outtmpl % template_dict
|
|
|
|
|
|
|
|
|
|
force_ext = OUTTMPL_TYPES.get(tmpl_type)
|
|
|
|
|
if force_ext is not None:
|
|
|
|
@ -2344,7 +2357,7 @@ class YoutubeDL(object):
|
|
|
|
|
if re.match(r'\w+$', tmpl):
|
|
|
|
|
tmpl = '%({})s'.format(tmpl)
|
|
|
|
|
tmpl, info_copy = self.prepare_outtmpl(tmpl, info_dict)
|
|
|
|
|
self.to_stdout(tmpl % info_copy)
|
|
|
|
|
self.to_stdout(self.escape_outtmpl(tmpl) % info_copy)
|
|
|
|
|
|
|
|
|
|
print_mandatory('title')
|
|
|
|
|
print_mandatory('id')
|
|
|
|
|