[outtmpl] Add operator & for replacement text (#2012)

Authored by: PilzAdam
This commit is contained in:
PilzAdam 2021-12-17 21:35:48 +01:00 committed by GitHub
parent ec2e44fc57
commit e978789f0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 16 additions and 4 deletions

View file

@ -1076,6 +1076,8 @@ # OUTPUT TEMPLATE
1. **Alternatives**: Alternate fields can be specified seperated with a `,`. Eg: `%(release_date>%Y,upload_date>%Y|Unknown)s` 1. **Alternatives**: Alternate fields can be specified seperated with a `,`. Eg: `%(release_date>%Y,upload_date>%Y|Unknown)s`
1. **Replacement**: A replacement value can specified using a `&` separator. If the field is *not* empty, this replacement value will be used instead of the actual field content. This is done after alternate fields are considered; thus the replacement is used if *any* of the alternative fields is *not* empty.
1. **Default**: A literal default value can be specified for when the field is empty using a `|` seperator. This overrides `--output-na-template`. Eg: `%(uploader|Unknown)s` 1. **Default**: A literal default value can be specified for when the field is empty using a `|` seperator. This overrides `--output-na-template`. Eg: `%(uploader|Unknown)s`
1. **More Conversions**: In addition to the normal format types `diouxXeEfFgGcrs`, `B`, `j`, `l`, `q` can be used for converting to **B**ytes, **j**son (flag `#` for pretty-printing), a comma seperated **l**ist (flag `#` for `\n` newline-seperated) and a string **q**uoted for the terminal (flag `#` to split a list into different arguments), respectively 1. **More Conversions**: In addition to the normal format types `diouxXeEfFgGcrs`, `B`, `j`, `l`, `q` can be used for converting to **B**ytes, **j**son (flag `#` for pretty-printing), a comma seperated **l**ist (flag `#` for `\n` newline-seperated) and a string **q**uoted for the terminal (flag `#` to split a list into different arguments), respectively
@ -1084,7 +1086,7 @@ # OUTPUT TEMPLATE
To summarize, the general syntax for a field is: To summarize, the general syntax for a field is:
``` ```
%(name[.keys][addition][>strf][,alternate][|default])[flags][width][.precision][length]type %(name[.keys][addition][>strf][,alternate][&replacement][|default])[flags][width][.precision][length]type
``` ```
Additionally, you can set different output templates for the various metadata files separately from the general output template by specifying the type of file followed by the template separated by a colon `:`. The different file types supported are `subtitle`, `thumbnail`, `description`, `annotation` (deprecated), `infojson`, `link`, `pl_thumbnail`, `pl_description`, `pl_infojson`, `chapter`. For example, `-o '%(title)s.%(ext)s' -o 'thumbnail:%(title)s\%(title)s.%(ext)s'` will put the thumbnails in a folder with the same name as the video. If any of the templates (except default) is empty, that type of file will not be written. Eg: `--write-thumbnail -o "thumbnail:"` will write thumbnails only for playlists and not for video. Additionally, you can set different output templates for the various metadata files separately from the general output template by specifying the type of file followed by the template separated by a colon `:`. The different file types supported are `subtitle`, `thumbnail`, `description`, `annotation` (deprecated), `infojson`, `link`, `pl_thumbnail`, `pl_description`, `pl_infojson`, `chapter`. For example, `-o '%(title)s.%(ext)s' -o 'thumbnail:%(title)s\%(title)s.%(ext)s'` will put the thumbnails in a folder with the same name as the video. If any of the templates (except default) is empty, that type of file will not be written. Eg: `--write-thumbnail -o "thumbnail:"` will write thumbnails only for playlists and not for video.
@ -1252,6 +1254,9 @@ # Download YouTube playlist videos in separate directory indexed by video order
# Download YouTube playlist videos in separate directories according to their uploaded year # Download YouTube playlist videos in separate directories according to their uploaded year
$ yt-dlp -o '%(upload_date>%Y)s/%(title)s.%(ext)s' https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re $ yt-dlp -o '%(upload_date>%Y)s/%(title)s.%(ext)s' https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re
# Prefix playlist index with " - " separator, but only if it is available
$ yt-dlp -o '%(playlist_index|)s%(playlist_index& - |)s%(title)s.%(ext)s' BaW_jenozKc https://www.youtube.com/user/TheLinuxFoundation/playlists
# Download all playlists of YouTube channel/user keeping each playlist in separate directory: # Download all playlists of YouTube channel/user keeping each playlist in separate directory:
$ yt-dlp -o '%(uploader)s/%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s' https://www.youtube.com/user/TheLinuxFoundation/playlists $ yt-dlp -o '%(uploader)s/%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s' https://www.youtube.com/user/TheLinuxFoundation/playlists

View file

@ -836,6 +836,11 @@ def gen():
test('%(title3)s', ('foo/bar\\test', 'foo_bar_test')) test('%(title3)s', ('foo/bar\\test', 'foo_bar_test'))
test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo_bar_test' % os.path.sep)) test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo_bar_test' % os.path.sep))
# Replacement
test('%(id&foo)s.bar', 'foo.bar')
test('%(title&foo)s.bar', 'NA.bar')
test('%(title&foo|baz)s.bar', 'baz.bar')
def test_format_note(self): def test_format_note(self):
ydl = YoutubeDL() ydl = YoutubeDL()
self.assertEqual(ydl._format_note({}), '') self.assertEqual(ydl._format_note({}), '')

View file

@ -1055,7 +1055,8 @@ def prepare_outtmpl(self, outtmpl, info_dict, sanitize=None):
(?P<fields>{field}) (?P<fields>{field})
(?P<maths>(?:{math_op}{math_field})*) (?P<maths>(?:{math_op}{math_field})*)
(?:>(?P<strf_format>.+?))? (?:>(?P<strf_format>.+?))?
(?P<alternate>(?<!\\),[^|)]+)? (?P<alternate>(?<!\\),[^|&)]+)?
(?:&(?P<replacement>.*?))?
(?:\|(?P<default>.*?))? (?:\|(?P<default>.*?))?
$'''.format(field=FIELD_RE, math_op=MATH_OPERATORS_RE, math_field=MATH_FIELD_RE)) $'''.format(field=FIELD_RE, math_op=MATH_OPERATORS_RE, math_field=MATH_FIELD_RE))
@ -1114,11 +1115,12 @@ def create_key(outer_mobj):
key = outer_mobj.group('key') key = outer_mobj.group('key')
mobj = re.match(INTERNAL_FORMAT_RE, key) mobj = re.match(INTERNAL_FORMAT_RE, key)
initial_field = mobj.group('fields').split('.')[-1] if mobj else '' initial_field = mobj.group('fields').split('.')[-1] if mobj else ''
value, default = None, na value, replacement, default = None, None, na
while mobj: while mobj:
mobj = mobj.groupdict() mobj = mobj.groupdict()
default = mobj['default'] if mobj['default'] is not None else default default = mobj['default'] if mobj['default'] is not None else default
value = get_value(mobj) value = get_value(mobj)
replacement = mobj['replacement']
if value is None and mobj['alternate']: if value is None and mobj['alternate']:
mobj = re.match(INTERNAL_FORMAT_RE, mobj['alternate'][1:]) mobj = re.match(INTERNAL_FORMAT_RE, mobj['alternate'][1:])
else: else:
@ -1128,7 +1130,7 @@ def create_key(outer_mobj):
if fmt == 's' and value is not None and key in field_size_compat_map.keys(): if fmt == 's' and value is not None and key in field_size_compat_map.keys():
fmt = '0{:d}d'.format(field_size_compat_map[key]) fmt = '0{:d}d'.format(field_size_compat_map[key])
value = default if value is None else value value = default if value is None else value if replacement is None else replacement
flags = outer_mobj.group('conversion') or '' flags = outer_mobj.group('conversion') or ''
str_fmt = f'{fmt[:-1]}s' str_fmt = f'{fmt[:-1]}s'