Allow entire infodict to be printed using %()s
Makes `--dump-json` redundant
This commit is contained in:
parent
b7b04c782e
commit
2b8a2973bd
3 changed files with 30 additions and 18 deletions
|
@ -919,7 +919,7 @@ The simplest usage of `-o` is not to set any template arguments when downloading
|
||||||
It may however also contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to [python string formatting operations](https://docs.python.org/2/library/stdtypes.html#string-formatting). For example, `%(NAME)s` or `%(NAME)05d`. To clarify, that is a percent symbol followed by a name in parentheses, followed by formatting operations.
|
It may however also contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to [python string formatting operations](https://docs.python.org/2/library/stdtypes.html#string-formatting). For example, `%(NAME)s` or `%(NAME)05d`. To clarify, that is a percent symbol followed by a name in parentheses, followed by formatting operations.
|
||||||
|
|
||||||
The field names themselves (the part inside the parenthesis) can also have some special formatting:
|
The field names themselves (the part inside the parenthesis) can also have some special formatting:
|
||||||
1. **Object traversal**: The dictionaries and lists available in metadata can be traversed by using a `.` (dot) separator. You can also do python slicing using `:`. Eg: `%(tags.0)s`, `%(subtitles.en.-1.ext)`, `%(id.3:7:-1)s`, `%(formats.:.format_id)s`. Note that all the fields that become available using this method are not listed below. Use `-j` to see such fields
|
1. **Object traversal**: The dictionaries and lists available in metadata can be traversed by using a `.` (dot) separator. You can also do python slicing using `:`. Eg: `%(tags.0)s`, `%(subtitles.en.-1.ext)s`, `%(id.3:7:-1)s`, `%(formats.:.format_id)s`. `%()s` refers to the entire infodict. Note that all the fields that become available using this method are not listed below. Use `-j` to see such fields
|
||||||
1. **Addition**: Addition and subtraction of numeric fields can be done using `+` and `-` respectively. Eg: `%(playlist_index+10)03d`, `%(n_entries+1-playlist_index)d`
|
1. **Addition**: Addition and subtraction of numeric fields can be done using `+` and `-` respectively. Eg: `%(playlist_index+10)03d`, `%(n_entries+1-playlist_index)d`
|
||||||
1. **Date/time Formatting**: Date/time fields can be formatted according to [strftime formatting](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) by specifying it separated from the field name using a `>`. Eg: `%(duration>%H-%M-%S)s`, `%(upload_date>%Y-%m-%d)s`, `%(epoch-3600>%H-%M-%S)s`
|
1. **Date/time Formatting**: Date/time fields can be formatted according to [strftime formatting](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) by specifying it separated from the field name using a `>`. Eg: `%(duration>%H-%M-%S)s`, `%(upload_date>%Y-%m-%d)s`, `%(epoch-3600>%H-%M-%S)s`
|
||||||
1. **Default**: A 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 default value can be specified for when the field is empty using a `|` seperator. This overrides `--output-na-template`. Eg: `%(uploader|Unknown)s`
|
||||||
|
@ -1417,6 +1417,7 @@ While these options are redundant, they are still expected to be used due to the
|
||||||
--get-thumbnail --print thumbnail
|
--get-thumbnail --print thumbnail
|
||||||
-e, --get-title --print title
|
-e, --get-title --print title
|
||||||
-g, --get-url --print urls
|
-g, --get-url --print urls
|
||||||
|
-j, --dump-json --print "%()j"
|
||||||
|
|
||||||
|
|
||||||
#### Not recommended
|
#### Not recommended
|
||||||
|
|
|
@ -668,15 +668,13 @@ class TestYoutubeDL(unittest.TestCase):
|
||||||
out = ydl.escape_outtmpl(outtmpl) % tmpl_dict
|
out = ydl.escape_outtmpl(outtmpl) % tmpl_dict
|
||||||
fname = ydl.prepare_filename(info or self.outtmpl_info)
|
fname = ydl.prepare_filename(info or self.outtmpl_info)
|
||||||
|
|
||||||
if callable(expected):
|
if not isinstance(expected, (list, tuple)):
|
||||||
self.assertTrue(expected(out))
|
expected = (expected, expected)
|
||||||
self.assertTrue(expected(fname))
|
for (name, got), expect in zip((('outtmpl', out), ('filename', fname)), expected):
|
||||||
elif isinstance(expected, str):
|
if callable(expect):
|
||||||
self.assertEqual(out, expected)
|
self.assertTrue(expect(got), f'Wrong {name} from {tmpl}')
|
||||||
self.assertEqual(fname, expected)
|
|
||||||
else:
|
else:
|
||||||
self.assertEqual(out, expected[0])
|
self.assertEqual(got, expect, f'Wrong {name} from {tmpl}')
|
||||||
self.assertEqual(fname, expected[1])
|
|
||||||
|
|
||||||
# Side-effects
|
# Side-effects
|
||||||
original_infodict = dict(self.outtmpl_info)
|
original_infodict = dict(self.outtmpl_info)
|
||||||
|
@ -721,7 +719,16 @@ class TestYoutubeDL(unittest.TestCase):
|
||||||
# Invalid templates
|
# Invalid templates
|
||||||
self.assertTrue(isinstance(YoutubeDL.validate_outtmpl('%(title)'), ValueError))
|
self.assertTrue(isinstance(YoutubeDL.validate_outtmpl('%(title)'), ValueError))
|
||||||
test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder='none')
|
test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder='none')
|
||||||
test('%()s', 'NA')
|
test('%(..)s', 'NA')
|
||||||
|
|
||||||
|
# Entire info_dict
|
||||||
|
def expect_same_infodict(out):
|
||||||
|
got_dict = json.loads(out)
|
||||||
|
for info_field, expected in self.outtmpl_info.items():
|
||||||
|
self.assertEqual(got_dict.get(info_field), expected, info_field)
|
||||||
|
return True
|
||||||
|
|
||||||
|
test('%()j', (expect_same_infodict, str))
|
||||||
|
|
||||||
# NA placeholder
|
# NA placeholder
|
||||||
NA_TEST_OUTTMPL = '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
|
NA_TEST_OUTTMPL = '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
|
||||||
|
|
|
@ -917,7 +917,7 @@ class YoutubeDL(object):
|
||||||
}
|
}
|
||||||
# Field is of the form key1.key2...
|
# Field is of the form key1.key2...
|
||||||
# where keys (except first) can be string, int or slice
|
# where keys (except first) can be string, int or slice
|
||||||
FIELD_RE = r'\w+(?:\.(?:\w+|{num}|{num}?(?::{num}?){{1,2}}))*'.format(num=r'(?:-?\d+)')
|
FIELD_RE = r'\w*(?:\.(?:\w+|{num}|{num}?(?::{num}?){{1,2}}))*'.format(num=r'(?:-?\d+)')
|
||||||
MATH_FIELD_RE = r'''{field}|{num}'''.format(field=FIELD_RE, num=r'-?\d+(?:.\d+)?')
|
MATH_FIELD_RE = r'''{field}|{num}'''.format(field=FIELD_RE, num=r'-?\d+(?:.\d+)?')
|
||||||
MATH_OPERATORS_RE = r'(?:%s)' % '|'.join(map(re.escape, MATH_FUNCTIONS.keys()))
|
MATH_OPERATORS_RE = r'(?:%s)' % '|'.join(map(re.escape, MATH_FUNCTIONS.keys()))
|
||||||
INTERNAL_FORMAT_RE = re.compile(r'''(?x)
|
INTERNAL_FORMAT_RE = re.compile(r'''(?x)
|
||||||
|
@ -928,12 +928,15 @@ class YoutubeDL(object):
|
||||||
(?:\|(?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))
|
||||||
|
|
||||||
get_key = lambda k: traverse_obj(
|
def _traverse_infodict(k):
|
||||||
info_dict, k.split('.'), is_user_input=True, traverse_string=True)
|
k = k.split('.')
|
||||||
|
if k[0] == '':
|
||||||
|
k.pop(0)
|
||||||
|
return traverse_obj(info_dict, k, is_user_input=True, traverse_string=True)
|
||||||
|
|
||||||
def get_value(mdict):
|
def get_value(mdict):
|
||||||
# Object traversal
|
# Object traversal
|
||||||
value = get_key(mdict['fields'])
|
value = _traverse_infodict(mdict['fields'])
|
||||||
# Negative
|
# Negative
|
||||||
if mdict['negate']:
|
if mdict['negate']:
|
||||||
value = float_or_none(value)
|
value = float_or_none(value)
|
||||||
|
@ -955,7 +958,7 @@ class YoutubeDL(object):
|
||||||
item, multiplier = (item[1:], -1) if item[0] == '-' else (item, 1)
|
item, multiplier = (item[1:], -1) if item[0] == '-' else (item, 1)
|
||||||
offset = float_or_none(item)
|
offset = float_or_none(item)
|
||||||
if offset is None:
|
if offset is None:
|
||||||
offset = float_or_none(get_key(item))
|
offset = float_or_none(_traverse_infodict(item))
|
||||||
try:
|
try:
|
||||||
value = operator(value, multiplier * offset)
|
value = operator(value, multiplier * offset)
|
||||||
except (TypeError, ZeroDivisionError):
|
except (TypeError, ZeroDivisionError):
|
||||||
|
@ -2378,6 +2381,8 @@ class YoutubeDL(object):
|
||||||
elif 'url' in info_dict:
|
elif 'url' in info_dict:
|
||||||
info_dict['urls'] = info_dict['url'] + info_dict.get('play_path', '')
|
info_dict['urls'] = info_dict['url'] + info_dict.get('play_path', '')
|
||||||
|
|
||||||
|
if self.params.get('forceprint') or self.params.get('forcejson'):
|
||||||
|
self.post_extract(info_dict)
|
||||||
for tmpl in self.params.get('forceprint', []):
|
for tmpl in self.params.get('forceprint', []):
|
||||||
if re.match(r'\w+$', tmpl):
|
if re.match(r'\w+$', tmpl):
|
||||||
tmpl = '%({})s'.format(tmpl)
|
tmpl = '%({})s'.format(tmpl)
|
||||||
|
@ -2394,8 +2399,7 @@ class YoutubeDL(object):
|
||||||
self.to_stdout(formatSeconds(info_dict['duration']))
|
self.to_stdout(formatSeconds(info_dict['duration']))
|
||||||
print_mandatory('format')
|
print_mandatory('format')
|
||||||
|
|
||||||
if self.params.get('forcejson', False):
|
if self.params.get('forcejson'):
|
||||||
self.post_extract(info_dict)
|
|
||||||
self.to_stdout(json.dumps(self.sanitize_info(info_dict)))
|
self.to_stdout(json.dumps(self.sanitize_info(info_dict)))
|
||||||
|
|
||||||
def dl(self, name, info, subtitle=False, test=False):
|
def dl(self, name, info, subtitle=False, test=False):
|
||||||
|
|
Loading…
Reference in a new issue