aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--test/test_YoutubeDL.py24
-rwxr-xr-xyoutube_dl/YoutubeDL.py39
2 files changed, 61 insertions, 2 deletions
diff --git a/test/test_YoutubeDL.py b/test/test_YoutubeDL.py
index 709e3100f..6f374d7ea 100644
--- a/test/test_YoutubeDL.py
+++ b/test/test_YoutubeDL.py
@@ -245,6 +245,30 @@ class TestFormatSelection(unittest.TestCase):
self.assertEqual(downloaded['format_id'], '137+141')
self.assertEqual(downloaded['ext'], 'mp4')
+ info_dict = _make_result(list(formats_order), extractor='youtube')
+ ydl = YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'})
+ yie = YoutubeIE(ydl)
+ yie._sort_formats(info_dict['formats'])
+ ydl.process_ie_result(info_dict)
+ downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
+ self.assertEqual(downloaded_ids, ['137+141', '248+141'])
+
+ info_dict = _make_result(list(formats_order), extractor='youtube')
+ ydl = YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'})
+ yie = YoutubeIE(ydl)
+ yie._sort_formats(info_dict['formats'])
+ ydl.process_ie_result(info_dict)
+ downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
+ self.assertEqual(downloaded_ids, ['136+141', '247+141'])
+
+ info_dict = _make_result(list(formats_order), extractor='youtube')
+ ydl = YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'})
+ yie = YoutubeIE(ydl)
+ yie._sort_formats(info_dict['formats'])
+ ydl.process_ie_result(info_dict)
+ downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
+ self.assertEqual(downloaded_ids, ['248+141'])
+
for f1, f2 in zip(formats_order, formats_order[1:]):
info_dict = _make_result([f1, f2], extractor='youtube')
ydl = YDL({'format': 'best/bestvideo'})
diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py
index 258e612af..e5b46f87e 100755
--- a/youtube_dl/YoutubeDL.py
+++ b/youtube_dl/YoutubeDL.py
@@ -920,6 +920,7 @@ class YoutubeDL(object):
PICKFIRST = 'PICKFIRST'
MERGE = 'MERGE'
SINGLE = 'SINGLE'
+ GROUP = 'GROUP'
FormatSelector = collections.namedtuple('FormatSelector', ['type', 'selector', 'filters'])
def _parse_filter(tokens):
@@ -942,6 +943,10 @@ class YoutubeDL(object):
elif type == tokenize.OP:
if string in endwith:
break
+ elif string == ')':
+ # ')' will be handled by the parentheses group
+ tokens.restore_last_token()
+ break
if string == ',':
selectors.append(current_selector)
current_selector = None
@@ -955,6 +960,10 @@ class YoutubeDL(object):
current_selector = FormatSelector(SINGLE, 'best', [])
format_filter = _parse_filter(tokens)
current_selector.filters.append(format_filter)
+ elif string == '(':
+ if current_selector:
+ raise syntax_error('Unexpected "("', start)
+ current_selector = FormatSelector(GROUP, _parse_format_selection(tokens, [')']), [])
elif string == '+':
video_selector = current_selector
audio_selector = _parse_format_selection(tokens, [','])
@@ -977,6 +986,8 @@ class YoutubeDL(object):
for format in f(formats):
yield format
return selector_function
+ elif selector.type == GROUP:
+ selector_function = _build_selector_function(selector.selector)
elif selector.type == PICKFIRST:
fs = [_build_selector_function(s) for s in selector.selector]
@@ -1084,8 +1095,32 @@ class YoutubeDL(object):
return final_selector
stream = io.BytesIO(format_spec.encode('utf-8'))
- tokens = compat_tokenize_tokenize(stream.readline)
- parsed_selector = _parse_format_selection(tokens)
+ try:
+ tokens = list(compat_tokenize_tokenize(stream.readline))
+ except tokenize.TokenError:
+ raise syntax_error('Missing closing/opening brackets or parenthesis', (0, len(format_spec)))
+
+ class TokenIterator(object):
+ def __init__(self, tokens):
+ self.tokens = tokens
+ self.counter = 0
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ if self.counter >= len(self.tokens):
+ raise StopIteration()
+ value = self.tokens[self.counter]
+ self.counter += 1
+ return value
+
+ next = __next__
+
+ def restore_last_token(self):
+ self.counter -= 1
+
+ parsed_selector = _parse_format_selection(iter(TokenIterator(tokens)))
return _build_selector_function(parsed_selector)
def _calc_headers(self, info_dict):