mirror of
				https://github.com/ytdl-org/youtube-dl.git
				synced 2025-10-29 09:26:20 -07:00 
			
		
		
		
	[YoutubeDL] format spec: allow grouping specifiers with parentheses
This commit is contained in:
		| @@ -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'}) | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user