diff --git a/test/test_YoutubeDL.py b/test/test_YoutubeDL.py index e6508b889..0ffcaed91 100644 --- a/test/test_YoutubeDL.py +++ b/test/test_YoutubeDL.py @@ -752,6 +752,7 @@ def test(tmpl, expected, **params): test('%(formats.3)s', 'NA') test('%(formats.:2:-1)r', repr(FORMATS[:2:-1])) test('%(formats.0.id.-1+id)f', '1235.000000') + test('%(formats.0.id.-1+formats.1.id.-1)d', '3') # Empty filename test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4') diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 72fc9ad52..0edbb4119 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -847,23 +847,24 @@ def prepare_outtmpl(self, outtmpl, info_dict, sanitize=None): 'autonumber': self.params.get('autonumber_size') or 5, } - EXTERNAL_FORMAT_RE = STR_FORMAT_RE.format('[^)]*') - # Field is of the form key1.key2... - # where keys (except first) can be string, int or slice - FIELD_RE = r'\w+(?:\.(?:\w+|[-\d]*(?::[-\d]*){0,2}))*' - INTERNAL_FORMAT_RE = re.compile(r'''(?x) - (?P-)? - (?P{0}) - (?P(?:[-+]-?(?:\d+(?:\.\d+)?|{0}))*) - (?:>(?P.+?))? - (?:\|(?P.*?))? - $'''.format(FIELD_RE)) - MATH_OPERATORS_RE = re.compile(r'(?-)? + (?P{field}) + (?P(?:{math_op}{math_field})*) + (?:>(?P.+?))? + (?:\|(?P.*?))? + $'''.format(field=FIELD_RE, math_op=MATH_OPERATORS_RE, math_field=MATH_FIELD_RE)) get_key = lambda k: traverse_obj( info_dict, k.split('.'), is_user_input=True, traverse_string=True) @@ -877,24 +878,27 @@ def get_value(mdict): if value is not None: value *= -1 # Do maths - if mdict['maths']: + offset_key = mdict['maths'] + if offset_key: value = float_or_none(value) operator = None - for item in MATH_OPERATORS_RE.split(mdict['maths'])[1:]: - if item == '' or value is None: - return None - if operator: - item, multiplier = (item[1:], -1) if item[0] == '-' else (item, 1) - offset = float_or_none(item) - if offset is None: - offset = float_or_none(get_key(item)) - try: - value = operator(value, multiplier * offset) - except (TypeError, ZeroDivisionError): - return None - operator = None - else: + while offset_key: + item = re.match( + MATH_FIELD_RE if operator else MATH_OPERATORS_RE, + offset_key).group(0) + offset_key = offset_key[len(item):] + if operator is None: operator = MATH_FUNCTIONS[item] + continue + item, multiplier = (item[1:], -1) if item[0] == '-' else (item, 1) + offset = float_or_none(item) + if offset is None: + offset = float_or_none(get_key(item)) + try: + value = operator(value, multiplier * offset) + except (TypeError, ZeroDivisionError): + return None + operator = None # Datetime formatting if mdict['strf_format']: value = strftime_or_none(value, mdict['strf_format']) @@ -938,10 +942,10 @@ def create_key(outer_mobj): value, fmt = repr(value), '%ss' % fmt[:-1] if fmt[-1] in 'csr': value = sanitize(key, value) - tmpl_dict[key] = value + TMPL_DICT[key] = value return '%({key}){fmt}'.format(key=key, fmt=fmt) - return re.sub(EXTERNAL_FORMAT_RE, create_key, outtmpl), tmpl_dict + return EXTERNAL_FORMAT_RE.sub(create_key, outtmpl), TMPL_DICT def _prepare_filename(self, info_dict, tmpl_type='default'): try: