diff options
Diffstat (limited to 'yt_dlp')
-rw-r--r-- | yt_dlp/YoutubeDL.py | 84 | ||||
-rw-r--r-- | yt_dlp/postprocessor/execafterdownload.py | 2 | ||||
-rw-r--r-- | yt_dlp/utils.py | 8 |
3 files changed, 66 insertions, 28 deletions
diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 0af036458..79ba3ef93 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -843,29 +843,67 @@ class YoutubeDL(object): if sanitize is None: sanitize = lambda k, v: v - # Internal Formatting = name.key1.key2+number>strf - INTERNAL_FORMAT_RE = FORMAT_RE.format( - r'''(?P<final_key> - (?P<fields>\w+(?:\.[-\w]+)*) - (?:\+(?P<add>-?\d+(?:\.\d+)?))? - (?:>(?P<strf_format>.+?))? - )''') - for mobj in re.finditer(INTERNAL_FORMAT_RE, outtmpl): - mobj = mobj.groupdict() - # Object traversal - fields = mobj['fields'].split('.') - final_key = mobj['final_key'] - value = traverse_dict(template_dict, fields) - # Offset the value - if mobj['add']: - value = float_or_none(value) - if value is not None: - value = value + float(mobj['add']) - # Datetime formatting - if mobj['strf_format']: - value = strftime_or_none(value, mobj['strf_format']) - if mobj['type'] in 'crs' and value is not None: # string - value = sanitize('%{}'.format(mobj['type']) % fields[-1], value) + EXTERNAL_FORMAT_RE = FORMAT_RE.format('(?P<key>[^)]*)') + # 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<negate>-)? + (?P<fields>{0}) + (?P<maths>(?:[-+]-?(?:\d+(?:\.\d+)?|{0}))*) + (?:>(?P<strf_format>.+?))? + (?:\|(?P<default>.*?))? + $'''.format(FIELD_RE)) + MATH_OPERATORS_RE = re.compile(r'(?<![-+])([-+])') + MATH_FUNCTIONS = { + '+': float.__add__, + '-': float.__sub__, + } + for outer_mobj in re.finditer(EXTERNAL_FORMAT_RE, outtmpl): + final_key = outer_mobj.group('key') + str_type = outer_mobj.group('type') + value = None + mobj = re.match(INTERNAL_FORMAT_RE, final_key) + if mobj is not None: + mobj = mobj.groupdict() + # Object traversal + fields = mobj['fields'].split('.') + value = traverse_dict(template_dict, fields) + # Negative + if mobj['negate']: + value = float_or_none(value) + if value is not None: + value *= -1 + # Do maths + if mobj['maths']: + value = float_or_none(value) + operator = None + for item in MATH_OPERATORS_RE.split(mobj['maths'])[1:]: + if item == '': + value = None + if value is None: + break + 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(traverse_dict(template_dict, item.split('.'))) + try: + value = operator(value, multiplier * offset) + except (TypeError, ZeroDivisionError): + value = None + operator = None + else: + operator = MATH_FUNCTIONS[item] + # Datetime formatting + if mobj['strf_format']: + value = strftime_or_none(value, mobj['strf_format']) + # Set default + if value is None and mobj['default'] is not None: + value = mobj['default'] + # Sanitize + if str_type in 'crs' and value is not None: # string + value = sanitize('%{}'.format(str_type) % fields[-1], value) else: # numeric numeric_fields.append(final_key) value = float_or_none(value) diff --git a/yt_dlp/postprocessor/execafterdownload.py b/yt_dlp/postprocessor/execafterdownload.py index 4a0649680..9d68583e7 100644 --- a/yt_dlp/postprocessor/execafterdownload.py +++ b/yt_dlp/postprocessor/execafterdownload.py @@ -24,7 +24,7 @@ class ExecAfterDownloadPP(PostProcessor): def parse_cmd(self, cmd, info): # If no %(key)s is found, replace {} for backard compatibility - if not re.search(FORMAT_RE.format(r'[-\w>.+]+'), cmd): + if not re.search(FORMAT_RE.format(r'[^)]*'), cmd): if '{}' not in cmd: cmd += ' {}' return cmd.replace('{}', compat_shlex_quote(info['filepath'])) diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py index 08e2d19d2..baa2a415e 100644 --- a/yt_dlp/utils.py +++ b/yt_dlp/utils.py @@ -6112,11 +6112,11 @@ def traverse_dict(dictn, keys, casesense=True): key = key.lower() dictn = dictn.get(key) elif isinstance(dictn, (list, tuple, compat_str)): - key, n = int_or_none(key), len(dictn) - if key is not None and -n <= key < n: - dictn = dictn[key] + if ':' in key: + key = slice(*map(int_or_none, key.split(':'))) else: - dictn = None + key = int_or_none(key) + dictn = try_get(dictn, lambda x: x[key]) else: return None return dictn |