3 # Allow direct execution
8 sys
.path
.insert(0, os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
))))
14 from test
.helper
import FakeYDL
, assertRegexpMatches
15 from yt_dlp
import YoutubeDL
16 from yt_dlp
.compat
import compat_os_name
17 from yt_dlp
.extractor
import YoutubeIE
18 from yt_dlp
.extractor
.common
import InfoExtractor
19 from yt_dlp
.postprocessor
.common
import PostProcessor
20 from yt_dlp
.utils
import (
28 TEST_URL
= 'http://localhost/sample.mp4'
32 def __init__(self
, *args
, **kwargs
):
33 super().__init
__(*args
, **kwargs
)
34 self
.downloaded_info_dicts
= []
37 def process_info(self
, info_dict
):
38 self
.downloaded_info_dicts
.append(info_dict
.copy())
40 def to_screen(self
, msg
, *args
, **kwargs
):
43 def dl(self
, *args
, **kwargs
):
44 assert False, 'Downloader must not be invoked for test_YoutubeDL'
47 def _make_result(formats
, **kwargs
):
51 'title': 'testttitle',
52 'extractor': 'testex',
53 'extractor_key': 'TestEx',
54 'webpage_url': 'http://example.com/watch?v=shenanigans',
60 class TestFormatSelection(unittest
.TestCase
):
61 def test_prefer_free_formats(self
):
62 # Same resolution => download webm
64 ydl
.params
['prefer_free_formats'] = True
66 {'ext': 'webm', 'height': 460, 'url': TEST_URL}
,
67 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}
,
69 info_dict
= _make_result(formats
)
70 ydl
.sort_formats(info_dict
)
71 ydl
.process_ie_result(info_dict
)
72 downloaded
= ydl
.downloaded_info_dicts
[0]
73 self
.assertEqual(downloaded
['ext'], 'webm')
75 # Different resolution => download best quality (mp4)
77 ydl
.params
['prefer_free_formats'] = True
79 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
80 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}
,
82 info_dict
['formats'] = formats
83 ydl
.sort_formats(info_dict
)
84 ydl
.process_ie_result(info_dict
)
85 downloaded
= ydl
.downloaded_info_dicts
[0]
86 self
.assertEqual(downloaded
['ext'], 'mp4')
88 # No prefer_free_formats => prefer mp4 and webm
90 ydl
.params
['prefer_free_formats'] = False
92 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
93 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}
,
94 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
96 info_dict
['formats'] = formats
97 ydl
.sort_formats(info_dict
)
98 ydl
.process_ie_result(info_dict
)
99 downloaded
= ydl
.downloaded_info_dicts
[0]
100 self
.assertEqual(downloaded
['ext'], 'mp4')
103 ydl
.params
['prefer_free_formats'] = False
105 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
106 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
108 info_dict
['formats'] = formats
109 ydl
.sort_formats(info_dict
)
110 ydl
.process_ie_result(info_dict
)
111 downloaded
= ydl
.downloaded_info_dicts
[0]
112 self
.assertEqual(downloaded
['ext'], 'webm')
114 def test_format_selection(self
):
116 {'format_id': '35', 'ext': 'mp4', 'preference': 0, 'url': TEST_URL}
,
117 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL}
,
118 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL}
,
119 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL}
,
120 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL}
,
122 info_dict
= _make_result(formats
)
124 def test(inp
, *expected
, multi
=False):
127 'allow_multiple_video_streams': multi
,
128 'allow_multiple_audio_streams': multi
,
130 ydl
.process_ie_result(info_dict
.copy())
131 downloaded
= map(lambda x
: x
['format_id'], ydl
.downloaded_info_dicts
)
132 self
.assertEqual(list(downloaded
), list(expected
))
135 test('20/71/worst', '35')
137 test('webm/mp4', '47')
138 test('3gp/40/mp4', '35')
139 test('example-with-dashes', 'example-with-dashes')
140 test('all', '2', '47', '45', 'example-with-dashes', '35')
141 test('mergeall', '2+47+45+example-with-dashes+35', multi
=True)
143 def test_format_selection_audio(self
):
145 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
146 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
147 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}
,
148 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL}
,
150 info_dict
= _make_result(formats
)
152 ydl
= YDL({'format': 'bestaudio'}
)
153 ydl
.process_ie_result(info_dict
.copy())
154 downloaded
= ydl
.downloaded_info_dicts
[0]
155 self
.assertEqual(downloaded
['format_id'], 'audio-high')
157 ydl
= YDL({'format': 'worstaudio'}
)
158 ydl
.process_ie_result(info_dict
.copy())
159 downloaded
= ydl
.downloaded_info_dicts
[0]
160 self
.assertEqual(downloaded
['format_id'], 'audio-low')
163 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
164 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL}
,
166 info_dict
= _make_result(formats
)
168 ydl
= YDL({'format': 'bestaudio/worstaudio/best'}
)
169 ydl
.process_ie_result(info_dict
.copy())
170 downloaded
= ydl
.downloaded_info_dicts
[0]
171 self
.assertEqual(downloaded
['format_id'], 'vid-high')
173 def test_format_selection_audio_exts(self
):
175 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
176 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
177 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
178 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
179 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
182 info_dict
= _make_result(formats
)
183 ydl
= YDL({'format': 'best'}
)
184 ydl
.sort_formats(info_dict
)
185 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
186 downloaded
= ydl
.downloaded_info_dicts
[0]
187 self
.assertEqual(downloaded
['format_id'], 'aac-64')
189 ydl
= YDL({'format': 'mp3'}
)
190 ydl
.sort_formats(info_dict
)
191 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
192 downloaded
= ydl
.downloaded_info_dicts
[0]
193 self
.assertEqual(downloaded
['format_id'], 'mp3-64')
195 ydl
= YDL({'prefer_free_formats': True}
)
196 ydl
.sort_formats(info_dict
)
197 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
198 downloaded
= ydl
.downloaded_info_dicts
[0]
199 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
201 def test_format_selection_video(self
):
203 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
204 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
205 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
207 info_dict
= _make_result(formats
)
209 ydl
= YDL({'format': 'bestvideo'}
)
210 ydl
.process_ie_result(info_dict
.copy())
211 downloaded
= ydl
.downloaded_info_dicts
[0]
212 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
214 ydl
= YDL({'format': 'worstvideo'}
)
215 ydl
.process_ie_result(info_dict
.copy())
216 downloaded
= ydl
.downloaded_info_dicts
[0]
217 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
219 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
220 ydl
.process_ie_result(info_dict
.copy())
221 downloaded
= ydl
.downloaded_info_dicts
[0]
222 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
225 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
227 info_dict
= _make_result(formats
)
229 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
230 ydl
.process_ie_result(info_dict
.copy())
231 downloaded
= ydl
.downloaded_info_dicts
[0]
232 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
234 def test_format_selection_string_ops(self
):
236 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
237 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
239 info_dict
= _make_result(formats
)
242 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
243 ydl
.process_ie_result(info_dict
.copy())
244 downloaded
= ydl
.downloaded_info_dicts
[0]
245 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
247 # does not equal (!=)
248 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
249 ydl
.process_ie_result(info_dict
.copy())
250 downloaded
= ydl
.downloaded_info_dicts
[0]
251 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
253 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
254 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
257 ydl
= YDL({'format': '[format_id^=abc]'}
)
258 ydl
.process_ie_result(info_dict
.copy())
259 downloaded
= ydl
.downloaded_info_dicts
[0]
260 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
262 # does not start with (!^=)
263 ydl
= YDL({'format': '[format_id!^=abc]'}
)
264 ydl
.process_ie_result(info_dict
.copy())
265 downloaded
= ydl
.downloaded_info_dicts
[0]
266 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
268 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
269 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
272 ydl
= YDL({'format': '[format_id$=cba]'}
)
273 ydl
.process_ie_result(info_dict
.copy())
274 downloaded
= ydl
.downloaded_info_dicts
[0]
275 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
277 # does not end with (!$=)
278 ydl
= YDL({'format': '[format_id!$=cba]'}
)
279 ydl
.process_ie_result(info_dict
.copy())
280 downloaded
= ydl
.downloaded_info_dicts
[0]
281 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
283 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
284 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
287 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
288 ydl
.process_ie_result(info_dict
.copy())
289 downloaded
= ydl
.downloaded_info_dicts
[0]
290 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
292 # does not contain (!*=)
293 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
294 ydl
.process_ie_result(info_dict
.copy())
295 downloaded
= ydl
.downloaded_info_dicts
[0]
296 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
298 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
299 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
301 ydl
= YDL({'format': '[format_id!*=-]'}
)
302 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
304 def test_youtube_format_selection(self
):
305 # FIXME: Rewrite in accordance with the new format sorting options
309 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
310 # Apple HTTP Live Streaming
311 '96', '95', '94', '93', '92', '132', '151',
313 '85', '84', '102', '83', '101', '82', '100',
315 '137', '248', '136', '247', '135', '246',
316 '245', '244', '134', '243', '133', '242', '160',
318 '141', '172', '140', '171', '139',
321 def format_info(f_id
):
322 info
= YoutubeIE
._formats
[f_id
].copy()
324 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
325 # and 'vcodec', while in tests such information is incomplete since
326 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
327 # test_YoutubeDL.test_youtube_format_selection is broken without
329 if 'acodec' in info
and 'vcodec' not in info
:
330 info
['vcodec'] = 'none'
331 elif 'vcodec' in info
and 'acodec' not in info
:
332 info
['acodec'] = 'none'
334 info
['format_id'] = f_id
335 info
['url'] = 'url:' + f_id
337 formats_order
= [format_info(f_id
) for f_id
in order
]
339 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
340 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
341 ydl
.sort_formats(info_dict
)
342 ydl
.process_ie_result(info_dict
)
343 downloaded
= ydl
.downloaded_info_dicts
[0]
344 self
.assertEqual(downloaded
['format_id'], '248+172')
345 self
.assertEqual(downloaded
['ext'], 'mp4')
347 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
348 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
349 ydl
.sort_formats(info_dict
)
350 ydl
.process_ie_result(info_dict
)
351 downloaded
= ydl
.downloaded_info_dicts
[0]
352 self
.assertEqual(downloaded
['format_id'], '38')
354 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
355 ydl
= YDL({'format': 'bestvideo/best,bestaudio'}
)
356 ydl
.sort_formats(info_dict
)
357 ydl
.process_ie_result(info_dict
)
358 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
359 self
.assertEqual(downloaded_ids
, ['137', '141'])
361 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
362 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'}
)
363 ydl
.sort_formats(info_dict
)
364 ydl
.process_ie_result(info_dict
)
365 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
366 self
.assertEqual(downloaded_ids
, ['137+141', '248+141'])
368 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
369 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'}
)
370 ydl
.sort_formats(info_dict
)
371 ydl
.process_ie_result(info_dict
)
372 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
373 self
.assertEqual(downloaded_ids
, ['136+141', '247+141'])
375 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
376 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}
)
377 ydl
.sort_formats(info_dict
)
378 ydl
.process_ie_result(info_dict
)
379 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
380 self
.assertEqual(downloaded_ids
, ['248+141'])
382 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
383 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
384 ydl
= YDL({'format': 'best/bestvideo'}
)
385 ydl
.sort_formats(info_dict
)
386 ydl
.process_ie_result(info_dict
)
387 downloaded
= ydl
.downloaded_info_dicts
[0]
388 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
390 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
391 ydl
= YDL({'format': 'best/bestvideo'}
)
392 ydl
.sort_formats(info_dict
)
393 ydl
.process_ie_result(info_dict
)
394 downloaded
= ydl
.downloaded_info_dicts
[0]
395 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
397 def test_audio_only_extractor_format_selection(self
):
398 # For extractors with incomplete formats (all formats are audio-only or
399 # video-only) best and worst should fallback to corresponding best/worst
400 # video-only or audio-only formats (as per
401 # https://github.com/ytdl-org/youtube-dl/pull/5556)
403 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
404 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
406 info_dict
= _make_result(formats
)
408 ydl
= YDL({'format': 'best'}
)
409 ydl
.process_ie_result(info_dict
.copy())
410 downloaded
= ydl
.downloaded_info_dicts
[0]
411 self
.assertEqual(downloaded
['format_id'], 'high')
413 ydl
= YDL({'format': 'worst'}
)
414 ydl
.process_ie_result(info_dict
.copy())
415 downloaded
= ydl
.downloaded_info_dicts
[0]
416 self
.assertEqual(downloaded
['format_id'], 'low')
418 def test_format_not_available(self
):
420 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
421 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
423 info_dict
= _make_result(formats
)
425 # This must fail since complete video-audio format does not match filter
426 # and extractor does not provide incomplete only formats (i.e. only
427 # video-only or audio-only).
428 ydl
= YDL({'format': 'best[height>360]'}
)
429 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
431 def test_format_selection_issue_10083(self
):
432 # See https://github.com/ytdl-org/youtube-dl/issues/10083
434 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
435 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
436 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
438 info_dict
= _make_result(formats
)
440 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
441 ydl
.process_ie_result(info_dict
.copy())
442 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
444 def test_invalid_format_specs(self
):
445 def assert_syntax_error(format_spec
):
446 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec}
)
448 assert_syntax_error('bestvideo,,best')
449 assert_syntax_error('+bestaudio')
450 assert_syntax_error('bestvideo+')
451 assert_syntax_error('/')
452 assert_syntax_error('[720<height]')
454 def test_format_filtering(self
):
456 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
457 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
458 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
459 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
460 {'format_id': 'E', 'filesize': 3000}
,
462 {'format_id': 'G', 'filesize': 1000000}
,
465 f
['url'] = 'http://_/'
467 info_dict
= _make_result(formats
, _format_sort_fields
=('id', ))
469 ydl
= YDL({'format': 'best[filesize<3000]'}
)
470 ydl
.process_ie_result(info_dict
)
471 downloaded
= ydl
.downloaded_info_dicts
[0]
472 self
.assertEqual(downloaded
['format_id'], 'D')
474 ydl
= YDL({'format': 'best[filesize<=3000]'}
)
475 ydl
.process_ie_result(info_dict
)
476 downloaded
= ydl
.downloaded_info_dicts
[0]
477 self
.assertEqual(downloaded
['format_id'], 'E')
479 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
480 ydl
.process_ie_result(info_dict
)
481 downloaded
= ydl
.downloaded_info_dicts
[0]
482 self
.assertEqual(downloaded
['format_id'], 'F')
484 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'}
)
485 ydl
.process_ie_result(info_dict
)
486 downloaded
= ydl
.downloaded_info_dicts
[0]
487 self
.assertEqual(downloaded
['format_id'], 'B')
489 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
490 ydl
.process_ie_result(info_dict
)
491 downloaded
= ydl
.downloaded_info_dicts
[0]
492 self
.assertEqual(downloaded
['format_id'], 'C')
494 ydl
= YDL({'format': '[filesize>?1]'}
)
495 ydl
.process_ie_result(info_dict
)
496 downloaded
= ydl
.downloaded_info_dicts
[0]
497 self
.assertEqual(downloaded
['format_id'], 'G')
499 ydl
= YDL({'format': '[filesize<1M]'}
)
500 ydl
.process_ie_result(info_dict
)
501 downloaded
= ydl
.downloaded_info_dicts
[0]
502 self
.assertEqual(downloaded
['format_id'], 'E')
504 ydl
= YDL({'format': '[filesize<1MiB]'}
)
505 ydl
.process_ie_result(info_dict
)
506 downloaded
= ydl
.downloaded_info_dicts
[0]
507 self
.assertEqual(downloaded
['format_id'], 'G')
509 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
510 ydl
.process_ie_result(info_dict
)
511 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
512 self
.assertEqual(downloaded_ids
, ['D', 'C', 'B'])
514 ydl
= YDL({'format': 'best[height<40]'}
)
516 ydl
.process_ie_result(info_dict
)
517 except ExtractorError
:
519 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
521 def test_default_format_spec(self
):
522 ydl
= YDL({'simulate': True}
)
523 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
526 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
528 ydl
= YDL({'simulate': True}
)
529 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
531 ydl
= YDL({'outtmpl': '-'}
)
532 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
535 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
536 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
539 class TestYoutubeDL(unittest
.TestCase
):
540 def test_subtitles(self
):
541 def s_formats(lang
, autocaption
=False):
544 'url': f
'http://localhost/video.{lang}.{ext}',
545 '_auto': autocaption
,
546 } for ext
in ['vtt', 'srt', 'ass']]
547 subtitles
= {l: s_formats(l) for l in ['en', 'fr', 'es']}
548 auto_captions
= {l: s_formats(l, True) for l in ['it', 'pt', 'es']}
552 'url': 'http://localhost/video.mp4',
553 'subtitles': subtitles
,
554 'automatic_captions': auto_captions
,
556 'webpage_url': 'http://example.com/watch?v=shenanigans',
559 def get_info(params
={}):
560 params
.setdefault('simulate', True)
562 ydl
.report_warning
= lambda *args
, **kargs
: None
563 return ydl
.process_video_result(info_dict
, download
=False)
566 self
.assertFalse(result
.get('requested_subtitles'))
567 self
.assertEqual(result
['subtitles'], subtitles
)
568 self
.assertEqual(result
['automatic_captions'], auto_captions
)
570 result
= get_info({'writesubtitles': True}
)
571 subs
= result
['requested_subtitles']
572 self
.assertTrue(subs
)
573 self
.assertEqual(set(subs
.keys()), {'en'}
)
574 self
.assertTrue(subs
['en'].get('data') is None)
575 self
.assertEqual(subs
['en']['ext'], 'ass')
577 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
578 subs
= result
['requested_subtitles']
579 self
.assertEqual(subs
['en']['ext'], 'srt')
581 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
582 subs
= result
['requested_subtitles']
583 self
.assertTrue(subs
)
584 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
586 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
587 subs
= result
['requested_subtitles']
588 self
.assertTrue(subs
)
589 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
591 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
592 subs
= result
['requested_subtitles']
593 self
.assertTrue(subs
)
594 self
.assertEqual(set(subs
.keys()), {'fr'}
)
596 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
597 subs
= result
['requested_subtitles']
598 self
.assertTrue(subs
)
599 self
.assertEqual(set(subs
.keys()), {'en'}
)
601 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
602 subs
= result
['requested_subtitles']
603 self
.assertTrue(subs
)
604 self
.assertEqual(set(subs
.keys()), {'es', 'en'}
)
606 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
607 subs
= result
['requested_subtitles']
608 self
.assertTrue(subs
)
609 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
610 self
.assertFalse(subs
['es']['_auto'])
611 self
.assertTrue(subs
['pt']['_auto'])
613 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
614 subs
= result
['requested_subtitles']
615 self
.assertTrue(subs
)
616 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
617 self
.assertTrue(subs
['es']['_auto'])
618 self
.assertTrue(subs
['pt']['_auto'])
620 def test_add_extra_info(self
):
626 'playlist': 'funny videos',
628 YDL
.add_extra_info(test_dict
, extra_info
)
629 self
.assertEqual(test_dict
['extractor'], 'Foo')
630 self
.assertEqual(test_dict
['playlist'], 'funny videos')
640 'title3': 'foo/bar\\test',
641 'title4': 'foo "bar" test',
643 'timestamp': 1618488000,
646 'playlist_autonumber': 2,
647 '__last_playlist_index': 100,
650 {'id': 'id 1', 'height': 1080, 'width': 1920}
,
651 {'id': 'id 2', 'height': 720}
,
656 def test_prepare_outtmpl_and_filename(self
):
657 def test(tmpl
, expected
, *, info
=None, **params
):
658 params
['outtmpl'] = tmpl
659 ydl
= FakeYDL(params
)
660 ydl
._num
_downloads
= 1
661 self
.assertEqual(ydl
.validate_outtmpl(tmpl
), None)
663 out
= ydl
.evaluate_outtmpl(tmpl
, info
or self
.outtmpl_info
)
664 fname
= ydl
.prepare_filename(info
or self
.outtmpl_info
)
666 if not isinstance(expected
, (list, tuple)):
667 expected
= (expected
, expected
)
668 for (name
, got
), expect
in zip((('outtmpl', out
), ('filename', fname
)), expected
):
670 self
.assertTrue(expect(got
), f
'Wrong {name} from {tmpl}')
671 elif expect
is not None:
672 self
.assertEqual(got
, expect
, f
'Wrong {name} from {tmpl}')
675 original_infodict
= dict(self
.outtmpl_info
)
676 test('foo.bar', 'foo.bar')
677 original_infodict
['epoch'] = self
.outtmpl_info
.get('epoch')
678 self
.assertTrue(isinstance(original_infodict
['epoch'], int))
679 test('%(epoch)d', int_or_none
)
680 self
.assertEqual(original_infodict
, self
.outtmpl_info
)
682 # Auto-generated fields
683 test('%(id)s.%(ext)s', '1234.mp4')
684 test('%(duration_string)s', ('27:46:40', '27-46-40'))
685 test('%(resolution)s', '1080p')
686 test('%(playlist_index)s', '001')
687 test('%(playlist_autonumber)s', '02')
688 test('%(autonumber)s', '00001')
689 test('%(autonumber+2)03d', '005', autonumber_start
=3)
690 test('%(autonumber)s', '001', autonumber_size
=3)
699 test('%abc%', '%abc%')
700 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
701 test('%%%(height)s', '%1080')
702 test('%(width)06d.%(ext)s', 'NA.mp4')
703 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
704 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
707 test('%(id)s', '_abcd', info
={'id': '_abcd'}
)
708 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'}
)
709 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}
]})
710 test('%(id)s', '-abcd', info
={'id': '-abcd'}
)
711 test('%(id)s', '.abcd', info
={'id': '.abcd'}
)
712 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'}
)
713 test('%(id)s', ('ab:cd', 'ab:cd'), info
={'id': 'ab:cd'}
)
714 test('%(id.0)s', '-', info
={'id': '--'}
)
717 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
718 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
720 test('%(formats.{id)s', 'NA')
723 def expect_same_infodict(out
):
724 got_dict
= json
.loads(out
)
725 for info_field
, expected
in self
.outtmpl_info
.items():
726 self
.assertEqual(got_dict
.get(info_field
), expected
, info_field
)
729 test('%()j', (expect_same_infodict
, str))
732 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
733 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
734 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
735 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
736 test('%(non_existent.0)s', 'NA')
739 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
740 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
741 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
742 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
743 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
744 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
745 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
746 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
747 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
748 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
749 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
752 test('%(id)d', '1234')
753 test('%(height)c', '1')
755 test('%(id)d %(id)r', "1234 '1234'")
756 test('%(id)r %(height)r', "'1234' 1080")
757 test('%(ext)s-%(ext|def)d', 'mp4-def')
758 test('%(width|0)04d', '0')
759 test('a%(width|b)d', 'ab', outtmpl_na_placeholder
='none')
761 FORMATS
= self
.outtmpl_info
['formats']
763 # Custom type casting
764 test('%(formats.:.id)l', 'id 1, id 2, id 3')
765 test('%(formats.:.id)#l', ('id 1\nid 2\nid 3', 'id 1 id 2 id 3'))
766 test('%(ext)l', 'mp4')
767 test('%(formats.:.id) 18l', ' id 1, id 2, id 3')
768 test('%(formats)j', (json
.dumps(FORMATS
), None))
769 test('%(formats)#j', (
770 json
.dumps(FORMATS
, indent
=4),
771 json
.dumps(FORMATS
, indent
=4).replace(':', ':').replace('"', """).replace('\n', ' ')
773 test('%(title5).3B', 'á')
774 test('%(title5)U', 'áéí 𝐀')
775 test('%(title5)#U', 'a\u0301e\u0301i\u0301 𝐀')
776 test('%(title5)+U', 'áéí A')
777 test('%(title5)+#U', 'a\u0301e\u0301i\u0301 A')
778 test('%(height)D', '1k')
779 test('%(filesize)#D', '1Ki')
780 test('%(height)5.2D', ' 1.08k')
781 test('%(title4)#S', 'foo_bar_test')
782 test('%(title4).10S', ('foo "bar" ', 'foo "bar"' + ('#' if compat_os_name
== 'nt' else ' ')))
783 if compat_os_name
== 'nt':
784 test('%(title4)q', ('"foo \\"bar\\" test"', ""foo ⧹"bar⧹" test""))
785 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', '"id 1" "id 2" "id 3"'))
786 test('%(formats.0.id)#q', ('"id 1"', '"id 1"'))
788 test('%(title4)q', ('\'foo "bar" test\'', '\'foo "bar" test\''))
789 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'")
790 test('%(formats.0.id)#q', "'id 1'")
792 # Internal formatting
793 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
794 test('%(title|%)s %(title|%%)s', '% %%')
795 test('%(id+1-height+3)05d', '00158')
796 test('%(width+100)05d', 'NA')
797 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], None))
798 test('%(formats.0)r', (repr(FORMATS
[0]), None))
799 test('%(height.0)03d', '001')
800 test('%(-height.0)04d', '-001')
801 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
802 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
803 test('%(formats.3)s', 'NA')
804 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
805 test('%(formats.0.id.-1+id)f', '1235.000000')
806 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
807 out
= json
.dumps([{'id': f['id'], 'height.:2': str(f['height'])[:2]}
808 if 'height' in f
else {'id': f['id']}
810 test('%(formats.:.{id,height.:2})j', (out
, None))
811 test('%(formats.:.{id,height}.id)l', ', '.join(f
['id'] for f
in FORMATS
))
812 test('%(.{id,title})j', ('{"id": "1234"}', '{"id": "1234"}'))
815 test('%(title,id)s', '1234')
816 test('%(width-100,height+20|def)d', '1100')
817 test('%(width-100,height+width|def)s', 'def')
818 test('%(timestamp-x>%H\\,%M\\,%S,timestamp>%H\\,%M\\,%S)s', '12,00,00')
821 test('%(id&foo)s.bar', 'foo.bar')
822 test('%(title&foo)s.bar', 'NA.bar')
823 test('%(title&foo|baz)s.bar', 'baz.bar')
824 test('%(x,id&foo|baz)s.bar', 'foo.bar')
825 test('%(x,title&foo|baz)s.bar', 'baz.bar')
826 test('%(id&a\nb|)s', ('a\nb', 'a b'))
827 test('%(id&hi {:>10} {}|)s', 'hi 1234 1234')
828 test(R
'%(id&{0} {}|)s', 'NA')
829 test(R
'%(id&{0.1}|)s', 'NA')
834 raise self
.assertTrue(False, 'LazyList should not be evaluated till here')
835 test('%(key.4)s', '4', info
={'key': LazyList(gen())}
)
838 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
839 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
840 # test('%(foo|)s', ('', '_')) # fixme
842 # Environment variable expansion for prepare_filename
843 os
.environ
['__yt_dlp_var'] = 'expanded'
844 envvar
= '%__yt_dlp_var%' if compat_os_name
== 'nt' else '$__yt_dlp_var'
845 test(envvar
, (envvar
, 'expanded'))
846 if compat_os_name
== 'nt':
847 test('%s%', ('%s%', '%s%'))
848 os
.environ
['s'] = 'expanded'
849 test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
850 os
.environ
['(test)s'] = 'expanded'
851 test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
853 # Path expansion and escaping
854 test('Hello %(title1)s', 'Hello $PATH')
855 test('Hello %(title2)s', 'Hello %PATH%')
856 test('%(title3)s', ('foo/bar\\test', 'foo⧸bar⧹test'))
857 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo⧸bar⧹test' % os
.path
.sep
))
859 def test_format_note(self
):
861 self
.assertEqual(ydl
._format
_note
({}), '')
862 assertRegexpMatches(self
, ydl
._format
_note
({
865 assertRegexpMatches(self
, ydl
._format
_note
({
869 def test_postprocessors(self
):
870 filename
= 'post-processor-testfile.mp4'
871 audiofile
= filename
+ '.mp3'
873 class SimplePP(PostProcessor
):
875 with open(audiofile
, 'w') as f
:
877 return [info
['filepath']], info
879 def run_pp(params
, PP
):
880 with open(filename
, 'w') as f
:
882 ydl
= YoutubeDL(params
)
883 ydl
.add_post_processor(PP())
884 ydl
.post_process(filename
, {'filepath': filename}
)
886 run_pp({'keepvideo': True}
, SimplePP
)
887 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
888 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
892 run_pp({'keepvideo': False}
, SimplePP
)
893 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
894 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
897 class ModifierPP(PostProcessor
):
899 with open(info
['filepath'], 'w') as f
:
903 run_pp({'keepvideo': False}
, ModifierPP
)
904 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
907 def test_match_filter(self
):
914 'filesize': 10 * 1024,
916 'uploader': "變態妍字幕版 太妍 тест",
917 'creator': "тест ' 123 ' тест--",
918 'webpage_url': 'http://example.com/watch?v=shenanigans',
926 'description': 'foo',
927 'filesize': 5 * 1024,
929 'uploader': "тест 123",
930 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
932 videos
= [first
, second
]
934 def get_videos(filter_
=None):
935 ydl
= YDL({'match_filter': filter_, 'simulate': True}
)
937 ydl
.process_ie_result(v
, download
=True)
938 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
941 self
.assertEqual(res
, ['1', '2'])
943 def f(v
, incomplete
):
947 return 'Video id is not 1'
949 self
.assertEqual(res
, ['1'])
951 f
= match_filter_func('duration < 30')
953 self
.assertEqual(res
, ['2'])
955 f
= match_filter_func('description = foo')
957 self
.assertEqual(res
, ['2'])
959 f
= match_filter_func('description =? foo')
961 self
.assertEqual(res
, ['1', '2'])
963 f
= match_filter_func('filesize > 5KiB')
965 self
.assertEqual(res
, ['1'])
967 f
= match_filter_func('playlist_id = 42')
969 self
.assertEqual(res
, ['1'])
971 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
973 self
.assertEqual(res
, ['1'])
975 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
977 self
.assertEqual(res
, ['2'])
979 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
981 self
.assertEqual(res
, ['1'])
983 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
985 self
.assertEqual(res
, ['1'])
987 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
989 self
.assertEqual(res
, [])
991 def test_playlist_items_selection(self
):
992 INDICES
, PAGE_SIZE
= list(range(1, 11)), 3
994 def entry(i
, evaluated
):
1002 def pagedlist_entries(evaluated
):
1004 start
= PAGE_SIZE
* n
1005 for i
in INDICES
[start
: start
+ PAGE_SIZE
]:
1006 yield entry(i
, evaluated
)
1007 return OnDemandPagedList(page_func
, PAGE_SIZE
)
1010 return (i
+ PAGE_SIZE
- 1) // PAGE_SIZE
1012 def generator_entries(evaluated
):
1014 yield entry(i
, evaluated
)
1016 def list_entries(evaluated
):
1017 return list(generator_entries(evaluated
))
1019 def lazylist_entries(evaluated
):
1020 return LazyList(generator_entries(evaluated
))
1022 def get_downloaded_info_dicts(params
, entries
):
1024 ydl
.process_ie_result({
1025 '_type': 'playlist',
1027 'extractor': 'test:playlist',
1028 'extractor_key': 'test:playlist',
1029 'webpage_url': 'http://example.com',
1032 return ydl
.downloaded_info_dicts
1034 def test_selection(params
, expected_ids
, evaluate_all
=False):
1035 expected_ids
= list(expected_ids
)
1037 generator_eval
= pagedlist_eval
= INDICES
1038 elif not expected_ids
:
1039 generator_eval
= pagedlist_eval
= []
1041 generator_eval
= INDICES
[0: max(expected_ids
)]
1042 pagedlist_eval
= INDICES
[PAGE_SIZE
* page_num(min(expected_ids
)) - PAGE_SIZE
:
1043 PAGE_SIZE
* page_num(max(expected_ids
))]
1045 for name
, func
, expected_eval
in (
1046 ('list', list_entries
, INDICES
),
1047 ('Generator', generator_entries
, generator_eval
),
1048 # ('LazyList', lazylist_entries, generator_eval), # Generator and LazyList follow the exact same code path
1049 ('PagedList', pagedlist_entries
, pagedlist_eval
),
1052 entries
= func(evaluated
)
1053 results
= [(v
['playlist_autonumber'] - 1, (int(v
['id']), v
['playlist_index']))
1054 for v
in get_downloaded_info_dicts(params
, entries
)]
1055 self
.assertEqual(results
, list(enumerate(zip(expected_ids
, expected_ids
))), f
'Entries of {name} for {params}')
1056 self
.assertEqual(sorted(evaluated
), expected_eval
, f
'Evaluation of {name} for {params}')
1058 test_selection({}, INDICES
)
1059 test_selection({'playlistend': 20}
, INDICES
, True)
1060 test_selection({'playlistend': 2}
, INDICES
[:2])
1061 test_selection({'playliststart': 11}
, [], True)
1062 test_selection({'playliststart': 2}
, INDICES
[1:])
1063 test_selection({'playlist_items': '2-4'}
, INDICES
[1:4])
1064 test_selection({'playlist_items': '2,4'}
, [2, 4])
1065 test_selection({'playlist_items': '20'}
, [], True)
1066 test_selection({'playlist_items': '0'}
, [])
1068 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
1069 test_selection({'playlist_items': '2-4,3-4,3'}
, [2, 3, 4])
1070 test_selection({'playlist_items': '4,2'}
, [4, 2])
1072 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720
1073 # https://github.com/yt-dlp/yt-dlp/issues/302
1074 test_selection({'playlistreverse': True}
, INDICES
[::-1])
1075 test_selection({'playliststart': 2, 'playlistreverse': True}
, INDICES
[:0:-1])
1076 test_selection({'playlist_items': '2,4', 'playlistreverse': True}
, [4, 2])
1077 test_selection({'playlist_items': '4,2'}
, [4, 2])
1079 # Tests for --playlist-items start:end:step
1080 test_selection({'playlist_items': ':'}
, INDICES
, True)
1081 test_selection({'playlist_items': '::1'}
, INDICES
, True)
1082 test_selection({'playlist_items': '::-1'}
, INDICES
[::-1], True)
1083 test_selection({'playlist_items': ':6'}
, INDICES
[:6])
1084 test_selection({'playlist_items': ':-6'}
, INDICES
[:-5], True)
1085 test_selection({'playlist_items': '-1:6:-2'}
, INDICES
[:4:-2], True)
1086 test_selection({'playlist_items': '9:-6:-2'}
, INDICES
[8:3:-2], True)
1088 test_selection({'playlist_items': '1:inf:2'}
, INDICES
[::2], True)
1089 test_selection({'playlist_items': '-2:inf'}
, INDICES
[-2:], True)
1090 test_selection({'playlist_items': ':inf:-1'}
, [], True)
1091 test_selection({'playlist_items': '0-2:2'}
, [2])
1092 test_selection({'playlist_items': '1-:2'}
, INDICES
[::2], True)
1093 test_selection({'playlist_items': '0--2:2'}
, INDICES
[1:-1:2], True)
1095 test_selection({'playlist_items': '10::3'}
, [10], True)
1096 test_selection({'playlist_items': '-1::3'}
, [10], True)
1097 test_selection({'playlist_items': '11::3'}
, [], True)
1098 test_selection({'playlist_items': '-15::2'}
, INDICES
[1::2], True)
1099 test_selection({'playlist_items': '-15::15'}
, [], True)
1101 def test_do_not_override_ie_key_in_url_transparent(self
):
1104 class Foo1IE(InfoExtractor
):
1105 _VALID_URL
= r
'foo1:'
1107 def _real_extract(self
, url
):
1109 '_type': 'url_transparent',
1112 'title': 'foo1 title',
1116 class Foo2IE(InfoExtractor
):
1117 _VALID_URL
= r
'foo2:'
1119 def _real_extract(self
, url
):
1126 class Foo3IE(InfoExtractor
):
1127 _VALID_URL
= r
'foo3:'
1129 def _real_extract(self
, url
):
1130 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1132 ydl
.add_info_extractor(Foo1IE(ydl
))
1133 ydl
.add_info_extractor(Foo2IE(ydl
))
1134 ydl
.add_info_extractor(Foo3IE(ydl
))
1135 ydl
.extract_info('foo1:')
1136 downloaded
= ydl
.downloaded_info_dicts
[0]
1137 self
.assertEqual(downloaded
['url'], TEST_URL
)
1138 self
.assertEqual(downloaded
['title'], 'foo1 title')
1139 self
.assertEqual(downloaded
['id'], 'testid')
1140 self
.assertEqual(downloaded
['extractor'], 'testex')
1141 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1143 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1144 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1147 def __init__(self
, *args
, **kwargs
):
1148 super().__init
__(*args
, **kwargs
)
1150 def trouble(self
, s
, tb
=None):
1155 'ignoreerrors': True,
1158 class VideoIE(InfoExtractor
):
1159 _VALID_URL
= r
'video:(?P<id>\d+)'
1161 def _real_extract(self
, url
):
1162 video_id
= self
._match
_id
(url
)
1164 'format_id': 'default',
1168 raise ExtractorError('foo')
1171 'format_id': 'extra',
1176 'title': 'Video %s' % video_id
,
1180 class PlaylistIE(InfoExtractor
):
1181 _VALID_URL
= r
'playlist:'
1187 '_type': 'url_transparent',
1188 'ie_key': VideoIE
.ie_key(),
1190 'url': 'video:%s' % video_id
,
1191 'title': 'Video Transparent %s' % video_id
,
1194 def _real_extract(self
, url
):
1195 return self
.playlist_result(self
._entries
())
1197 ydl
.add_info_extractor(VideoIE(ydl
))
1198 ydl
.add_info_extractor(PlaylistIE(ydl
))
1199 info
= ydl
.extract_info('playlist:')
1200 entries
= info
['entries']
1201 self
.assertEqual(len(entries
), 3)
1202 self
.assertTrue(entries
[0] is None)
1203 self
.assertTrue(entries
[1] is None)
1204 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1205 downloaded
= ydl
.downloaded_info_dicts
[0]
1206 entries
[2].pop('requested_downloads', None)
1207 self
.assertEqual(entries
[2], downloaded
)
1208 self
.assertEqual(downloaded
['url'], TEST_URL
)
1209 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1210 self
.assertEqual(downloaded
['id'], '2')
1211 self
.assertEqual(downloaded
['extractor'], 'Video')
1212 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1215 if __name__
== '__main__':