3 # Allow direct execution
8 sys
.path
.insert(0, os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
))))
15 from test
.helper
import FakeYDL
, assertRegexpMatches
16 from yt_dlp
import YoutubeDL
17 from yt_dlp
.compat
import compat_os_name
18 from yt_dlp
.extractor
import YoutubeIE
19 from yt_dlp
.extractor
.common
import InfoExtractor
20 from yt_dlp
.postprocessor
.common
import PostProcessor
21 from yt_dlp
.utils
import (
29 TEST_URL
= 'http://localhost/sample.mp4'
33 def __init__(self
, *args
, **kwargs
):
34 super().__init
__(*args
, **kwargs
)
35 self
.downloaded_info_dicts
= []
38 def process_info(self
, info_dict
):
39 self
.downloaded_info_dicts
.append(info_dict
.copy())
41 def to_screen(self
, msg
, *args
, **kwargs
):
44 def dl(self
, *args
, **kwargs
):
45 assert False, 'Downloader must not be invoked for test_YoutubeDL'
48 def _make_result(formats
, **kwargs
):
52 'title': 'testttitle',
53 'extractor': 'testex',
54 'extractor_key': 'TestEx',
55 'webpage_url': 'http://example.com/watch?v=shenanigans',
61 class TestFormatSelection(unittest
.TestCase
):
62 def test_prefer_free_formats(self
):
63 # Same resolution => download webm
65 ydl
.params
['prefer_free_formats'] = True
67 {'ext': 'webm', 'height': 460, 'url': TEST_URL}
,
68 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}
,
70 info_dict
= _make_result(formats
)
71 ydl
.sort_formats(info_dict
)
72 ydl
.process_ie_result(info_dict
)
73 downloaded
= ydl
.downloaded_info_dicts
[0]
74 self
.assertEqual(downloaded
['ext'], 'webm')
76 # Different resolution => download best quality (mp4)
78 ydl
.params
['prefer_free_formats'] = True
80 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
81 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}
,
83 info_dict
['formats'] = formats
84 ydl
.sort_formats(info_dict
)
85 ydl
.process_ie_result(info_dict
)
86 downloaded
= ydl
.downloaded_info_dicts
[0]
87 self
.assertEqual(downloaded
['ext'], 'mp4')
89 # No prefer_free_formats => prefer mp4 and webm
91 ydl
.params
['prefer_free_formats'] = False
93 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
94 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}
,
95 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
97 info_dict
['formats'] = formats
98 ydl
.sort_formats(info_dict
)
99 ydl
.process_ie_result(info_dict
)
100 downloaded
= ydl
.downloaded_info_dicts
[0]
101 self
.assertEqual(downloaded
['ext'], 'mp4')
104 ydl
.params
['prefer_free_formats'] = False
106 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
107 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
109 info_dict
['formats'] = formats
110 ydl
.sort_formats(info_dict
)
111 ydl
.process_ie_result(info_dict
)
112 downloaded
= ydl
.downloaded_info_dicts
[0]
113 self
.assertEqual(downloaded
['ext'], 'webm')
115 def test_format_selection(self
):
117 {'format_id': '35', 'ext': 'mp4', 'preference': 0, 'url': TEST_URL}
,
118 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL}
,
119 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL}
,
120 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL}
,
121 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL}
,
123 info_dict
= _make_result(formats
)
125 def test(inp
, *expected
, multi
=False):
128 'allow_multiple_video_streams': multi
,
129 'allow_multiple_audio_streams': multi
,
131 ydl
.process_ie_result(info_dict
.copy())
132 downloaded
= map(lambda x
: x
['format_id'], ydl
.downloaded_info_dicts
)
133 self
.assertEqual(list(downloaded
), list(expected
))
136 test('20/71/worst', '35')
138 test('webm/mp4', '47')
139 test('3gp/40/mp4', '35')
140 test('example-with-dashes', 'example-with-dashes')
141 test('all', '2', '47', '45', 'example-with-dashes', '35')
142 test('mergeall', '2+47+45+example-with-dashes+35', multi
=True)
144 def test_format_selection_audio(self
):
146 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
147 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
148 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}
,
149 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL}
,
151 info_dict
= _make_result(formats
)
153 ydl
= YDL({'format': 'bestaudio'}
)
154 ydl
.process_ie_result(info_dict
.copy())
155 downloaded
= ydl
.downloaded_info_dicts
[0]
156 self
.assertEqual(downloaded
['format_id'], 'audio-high')
158 ydl
= YDL({'format': 'worstaudio'}
)
159 ydl
.process_ie_result(info_dict
.copy())
160 downloaded
= ydl
.downloaded_info_dicts
[0]
161 self
.assertEqual(downloaded
['format_id'], 'audio-low')
164 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
165 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL}
,
167 info_dict
= _make_result(formats
)
169 ydl
= YDL({'format': 'bestaudio/worstaudio/best'}
)
170 ydl
.process_ie_result(info_dict
.copy())
171 downloaded
= ydl
.downloaded_info_dicts
[0]
172 self
.assertEqual(downloaded
['format_id'], 'vid-high')
174 def test_format_selection_audio_exts(self
):
176 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
177 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
178 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
179 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
180 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
183 info_dict
= _make_result(formats
)
184 ydl
= YDL({'format': 'best'}
)
185 ydl
.sort_formats(info_dict
)
186 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
187 downloaded
= ydl
.downloaded_info_dicts
[0]
188 self
.assertEqual(downloaded
['format_id'], 'aac-64')
190 ydl
= YDL({'format': 'mp3'}
)
191 ydl
.sort_formats(info_dict
)
192 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
193 downloaded
= ydl
.downloaded_info_dicts
[0]
194 self
.assertEqual(downloaded
['format_id'], 'mp3-64')
196 ydl
= YDL({'prefer_free_formats': True}
)
197 ydl
.sort_formats(info_dict
)
198 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
199 downloaded
= ydl
.downloaded_info_dicts
[0]
200 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
202 def test_format_selection_video(self
):
204 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
205 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
206 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
208 info_dict
= _make_result(formats
)
210 ydl
= YDL({'format': 'bestvideo'}
)
211 ydl
.process_ie_result(info_dict
.copy())
212 downloaded
= ydl
.downloaded_info_dicts
[0]
213 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
215 ydl
= YDL({'format': 'worstvideo'}
)
216 ydl
.process_ie_result(info_dict
.copy())
217 downloaded
= ydl
.downloaded_info_dicts
[0]
218 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
220 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
221 ydl
.process_ie_result(info_dict
.copy())
222 downloaded
= ydl
.downloaded_info_dicts
[0]
223 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
226 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
228 info_dict
= _make_result(formats
)
230 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
231 ydl
.process_ie_result(info_dict
.copy())
232 downloaded
= ydl
.downloaded_info_dicts
[0]
233 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
235 def test_format_selection_string_ops(self
):
237 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
238 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
240 info_dict
= _make_result(formats
)
243 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
244 ydl
.process_ie_result(info_dict
.copy())
245 downloaded
= ydl
.downloaded_info_dicts
[0]
246 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
248 # does not equal (!=)
249 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
250 ydl
.process_ie_result(info_dict
.copy())
251 downloaded
= ydl
.downloaded_info_dicts
[0]
252 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
254 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
255 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
258 ydl
= YDL({'format': '[format_id^=abc]'}
)
259 ydl
.process_ie_result(info_dict
.copy())
260 downloaded
= ydl
.downloaded_info_dicts
[0]
261 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
263 # does not start with (!^=)
264 ydl
= YDL({'format': '[format_id!^=abc]'}
)
265 ydl
.process_ie_result(info_dict
.copy())
266 downloaded
= ydl
.downloaded_info_dicts
[0]
267 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
269 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
270 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
273 ydl
= YDL({'format': '[format_id$=cba]'}
)
274 ydl
.process_ie_result(info_dict
.copy())
275 downloaded
= ydl
.downloaded_info_dicts
[0]
276 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
278 # does not end with (!$=)
279 ydl
= YDL({'format': '[format_id!$=cba]'}
)
280 ydl
.process_ie_result(info_dict
.copy())
281 downloaded
= ydl
.downloaded_info_dicts
[0]
282 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
284 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
285 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
288 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
289 ydl
.process_ie_result(info_dict
.copy())
290 downloaded
= ydl
.downloaded_info_dicts
[0]
291 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
293 # does not contain (!*=)
294 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
295 ydl
.process_ie_result(info_dict
.copy())
296 downloaded
= ydl
.downloaded_info_dicts
[0]
297 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
299 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
300 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
302 ydl
= YDL({'format': '[format_id!*=-]'}
)
303 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
305 def test_youtube_format_selection(self
):
306 # FIXME: Rewrite in accordance with the new format sorting options
310 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
311 # Apple HTTP Live Streaming
312 '96', '95', '94', '93', '92', '132', '151',
314 '85', '84', '102', '83', '101', '82', '100',
316 '137', '248', '136', '247', '135', '246',
317 '245', '244', '134', '243', '133', '242', '160',
319 '141', '172', '140', '171', '139',
322 def format_info(f_id
):
323 info
= YoutubeIE
._formats
[f_id
].copy()
325 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
326 # and 'vcodec', while in tests such information is incomplete since
327 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
328 # test_YoutubeDL.test_youtube_format_selection is broken without
330 if 'acodec' in info
and 'vcodec' not in info
:
331 info
['vcodec'] = 'none'
332 elif 'vcodec' in info
and 'acodec' not in info
:
333 info
['acodec'] = 'none'
335 info
['format_id'] = f_id
336 info
['url'] = 'url:' + f_id
338 formats_order
= [format_info(f_id
) for f_id
in order
]
340 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
341 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
342 ydl
.sort_formats(info_dict
)
343 ydl
.process_ie_result(info_dict
)
344 downloaded
= ydl
.downloaded_info_dicts
[0]
345 self
.assertEqual(downloaded
['format_id'], '248+172')
346 self
.assertEqual(downloaded
['ext'], 'mp4')
348 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
349 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
350 ydl
.sort_formats(info_dict
)
351 ydl
.process_ie_result(info_dict
)
352 downloaded
= ydl
.downloaded_info_dicts
[0]
353 self
.assertEqual(downloaded
['format_id'], '38')
355 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
356 ydl
= YDL({'format': 'bestvideo/best,bestaudio'}
)
357 ydl
.sort_formats(info_dict
)
358 ydl
.process_ie_result(info_dict
)
359 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
360 self
.assertEqual(downloaded_ids
, ['137', '141'])
362 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
363 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'}
)
364 ydl
.sort_formats(info_dict
)
365 ydl
.process_ie_result(info_dict
)
366 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
367 self
.assertEqual(downloaded_ids
, ['137+141', '248+141'])
369 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
370 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'}
)
371 ydl
.sort_formats(info_dict
)
372 ydl
.process_ie_result(info_dict
)
373 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
374 self
.assertEqual(downloaded_ids
, ['136+141', '247+141'])
376 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
377 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}
)
378 ydl
.sort_formats(info_dict
)
379 ydl
.process_ie_result(info_dict
)
380 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
381 self
.assertEqual(downloaded_ids
, ['248+141'])
383 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
384 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
385 ydl
= YDL({'format': 'best/bestvideo'}
)
386 ydl
.sort_formats(info_dict
)
387 ydl
.process_ie_result(info_dict
)
388 downloaded
= ydl
.downloaded_info_dicts
[0]
389 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
391 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
392 ydl
= YDL({'format': 'best/bestvideo'}
)
393 ydl
.sort_formats(info_dict
)
394 ydl
.process_ie_result(info_dict
)
395 downloaded
= ydl
.downloaded_info_dicts
[0]
396 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
398 def test_audio_only_extractor_format_selection(self
):
399 # For extractors with incomplete formats (all formats are audio-only or
400 # video-only) best and worst should fallback to corresponding best/worst
401 # video-only or audio-only formats (as per
402 # https://github.com/ytdl-org/youtube-dl/pull/5556)
404 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
405 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
407 info_dict
= _make_result(formats
)
409 ydl
= YDL({'format': 'best'}
)
410 ydl
.process_ie_result(info_dict
.copy())
411 downloaded
= ydl
.downloaded_info_dicts
[0]
412 self
.assertEqual(downloaded
['format_id'], 'high')
414 ydl
= YDL({'format': 'worst'}
)
415 ydl
.process_ie_result(info_dict
.copy())
416 downloaded
= ydl
.downloaded_info_dicts
[0]
417 self
.assertEqual(downloaded
['format_id'], 'low')
419 def test_format_not_available(self
):
421 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
422 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
424 info_dict
= _make_result(formats
)
426 # This must fail since complete video-audio format does not match filter
427 # and extractor does not provide incomplete only formats (i.e. only
428 # video-only or audio-only).
429 ydl
= YDL({'format': 'best[height>360]'}
)
430 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
432 def test_format_selection_issue_10083(self
):
433 # See https://github.com/ytdl-org/youtube-dl/issues/10083
435 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
436 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
437 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
439 info_dict
= _make_result(formats
)
441 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
442 ydl
.process_ie_result(info_dict
.copy())
443 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
445 def test_invalid_format_specs(self
):
446 def assert_syntax_error(format_spec
):
447 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec}
)
449 assert_syntax_error('bestvideo,,best')
450 assert_syntax_error('+bestaudio')
451 assert_syntax_error('bestvideo+')
452 assert_syntax_error('/')
453 assert_syntax_error('[720<height]')
455 def test_format_filtering(self
):
457 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
458 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
459 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
460 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
461 {'format_id': 'E', 'filesize': 3000}
,
463 {'format_id': 'G', 'filesize': 1000000}
,
466 f
['url'] = 'http://_/'
468 info_dict
= _make_result(formats
, _format_sort_fields
=('id', ))
470 ydl
= YDL({'format': 'best[filesize<3000]'}
)
471 ydl
.process_ie_result(info_dict
)
472 downloaded
= ydl
.downloaded_info_dicts
[0]
473 self
.assertEqual(downloaded
['format_id'], 'D')
475 ydl
= YDL({'format': 'best[filesize<=3000]'}
)
476 ydl
.process_ie_result(info_dict
)
477 downloaded
= ydl
.downloaded_info_dicts
[0]
478 self
.assertEqual(downloaded
['format_id'], 'E')
480 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
481 ydl
.process_ie_result(info_dict
)
482 downloaded
= ydl
.downloaded_info_dicts
[0]
483 self
.assertEqual(downloaded
['format_id'], 'F')
485 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'}
)
486 ydl
.process_ie_result(info_dict
)
487 downloaded
= ydl
.downloaded_info_dicts
[0]
488 self
.assertEqual(downloaded
['format_id'], 'B')
490 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
491 ydl
.process_ie_result(info_dict
)
492 downloaded
= ydl
.downloaded_info_dicts
[0]
493 self
.assertEqual(downloaded
['format_id'], 'C')
495 ydl
= YDL({'format': '[filesize>?1]'}
)
496 ydl
.process_ie_result(info_dict
)
497 downloaded
= ydl
.downloaded_info_dicts
[0]
498 self
.assertEqual(downloaded
['format_id'], 'G')
500 ydl
= YDL({'format': '[filesize<1M]'}
)
501 ydl
.process_ie_result(info_dict
)
502 downloaded
= ydl
.downloaded_info_dicts
[0]
503 self
.assertEqual(downloaded
['format_id'], 'E')
505 ydl
= YDL({'format': '[filesize<1MiB]'}
)
506 ydl
.process_ie_result(info_dict
)
507 downloaded
= ydl
.downloaded_info_dicts
[0]
508 self
.assertEqual(downloaded
['format_id'], 'G')
510 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
511 ydl
.process_ie_result(info_dict
)
512 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
513 self
.assertEqual(downloaded_ids
, ['D', 'C', 'B'])
515 ydl
= YDL({'format': 'best[height<40]'}
)
517 ydl
.process_ie_result(info_dict
)
518 except ExtractorError
:
520 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
522 def test_default_format_spec(self
):
523 ydl
= YDL({'simulate': True}
)
524 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
527 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
529 ydl
= YDL({'simulate': True}
)
530 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
532 ydl
= YDL({'outtmpl': '-'}
)
533 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
536 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
537 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
540 class TestYoutubeDL(unittest
.TestCase
):
541 def test_subtitles(self
):
542 def s_formats(lang
, autocaption
=False):
545 'url': f
'http://localhost/video.{lang}.{ext}',
546 '_auto': autocaption
,
547 } for ext
in ['vtt', 'srt', 'ass']]
548 subtitles
= {l: s_formats(l) for l in ['en', 'fr', 'es']}
549 auto_captions
= {l: s_formats(l, True) for l in ['it', 'pt', 'es']}
553 'url': 'http://localhost/video.mp4',
554 'subtitles': subtitles
,
555 'automatic_captions': auto_captions
,
557 'webpage_url': 'http://example.com/watch?v=shenanigans',
560 def get_info(params
={}):
561 params
.setdefault('simulate', True)
563 ydl
.report_warning
= lambda *args
, **kargs
: None
564 return ydl
.process_video_result(info_dict
, download
=False)
567 self
.assertFalse(result
.get('requested_subtitles'))
568 self
.assertEqual(result
['subtitles'], subtitles
)
569 self
.assertEqual(result
['automatic_captions'], auto_captions
)
571 result
= get_info({'writesubtitles': True}
)
572 subs
= result
['requested_subtitles']
573 self
.assertTrue(subs
)
574 self
.assertEqual(set(subs
.keys()), {'en'}
)
575 self
.assertTrue(subs
['en'].get('data') is None)
576 self
.assertEqual(subs
['en']['ext'], 'ass')
578 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
579 subs
= result
['requested_subtitles']
580 self
.assertEqual(subs
['en']['ext'], 'srt')
582 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
583 subs
= result
['requested_subtitles']
584 self
.assertTrue(subs
)
585 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
587 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
588 subs
= result
['requested_subtitles']
589 self
.assertTrue(subs
)
590 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
592 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
593 subs
= result
['requested_subtitles']
594 self
.assertTrue(subs
)
595 self
.assertEqual(set(subs
.keys()), {'fr'}
)
597 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
598 subs
= result
['requested_subtitles']
599 self
.assertTrue(subs
)
600 self
.assertEqual(set(subs
.keys()), {'en'}
)
602 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
603 subs
= result
['requested_subtitles']
604 self
.assertTrue(subs
)
605 self
.assertEqual(set(subs
.keys()), {'es', 'en'}
)
607 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
608 subs
= result
['requested_subtitles']
609 self
.assertTrue(subs
)
610 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
611 self
.assertFalse(subs
['es']['_auto'])
612 self
.assertTrue(subs
['pt']['_auto'])
614 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
615 subs
= result
['requested_subtitles']
616 self
.assertTrue(subs
)
617 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
618 self
.assertTrue(subs
['es']['_auto'])
619 self
.assertTrue(subs
['pt']['_auto'])
621 def test_add_extra_info(self
):
627 'playlist': 'funny videos',
629 YDL
.add_extra_info(test_dict
, extra_info
)
630 self
.assertEqual(test_dict
['extractor'], 'Foo')
631 self
.assertEqual(test_dict
['playlist'], 'funny videos')
641 'title3': 'foo/bar\\test',
642 'title4': 'foo "bar" test',
644 'timestamp': 1618488000,
647 'playlist_autonumber': 2,
648 '__last_playlist_index': 100,
651 {'id': 'id 1', 'height': 1080, 'width': 1920}
,
652 {'id': 'id 2', 'height': 720}
,
657 def test_prepare_outtmpl_and_filename(self
):
658 def test(tmpl
, expected
, *, info
=None, **params
):
659 params
['outtmpl'] = tmpl
660 ydl
= FakeYDL(params
)
661 ydl
._num
_downloads
= 1
662 self
.assertEqual(ydl
.validate_outtmpl(tmpl
), None)
664 out
= ydl
.evaluate_outtmpl(tmpl
, info
or self
.outtmpl_info
)
665 fname
= ydl
.prepare_filename(info
or self
.outtmpl_info
)
667 if not isinstance(expected
, (list, tuple)):
668 expected
= (expected
, expected
)
669 for (name
, got
), expect
in zip((('outtmpl', out
), ('filename', fname
)), expected
):
671 self
.assertTrue(expect(got
), f
'Wrong {name} from {tmpl}')
673 self
.assertEqual(got
, expect
, f
'Wrong {name} from {tmpl}')
676 original_infodict
= dict(self
.outtmpl_info
)
677 test('foo.bar', 'foo.bar')
678 original_infodict
['epoch'] = self
.outtmpl_info
.get('epoch')
679 self
.assertTrue(isinstance(original_infodict
['epoch'], int))
680 test('%(epoch)d', int_or_none
)
681 self
.assertEqual(original_infodict
, self
.outtmpl_info
)
683 # Auto-generated fields
684 test('%(id)s.%(ext)s', '1234.mp4')
685 test('%(duration_string)s', ('27:46:40', '27-46-40'))
686 test('%(resolution)s', '1080p')
687 test('%(playlist_index)s', '001')
688 test('%(playlist_autonumber)s', '02')
689 test('%(autonumber)s', '00001')
690 test('%(autonumber+2)03d', '005', autonumber_start
=3)
691 test('%(autonumber)s', '001', autonumber_size
=3)
700 test('%abc%', '%abc%')
701 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
702 test('%%%(height)s', '%1080')
703 test('%(width)06d.%(ext)s', 'NA.mp4')
704 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
705 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
708 test('%(id)s', '_abcd', info
={'id': '_abcd'}
)
709 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'}
)
710 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}
]})
711 test('%(id)s', '-abcd', info
={'id': '-abcd'}
)
712 test('%(id)s', '.abcd', info
={'id': '.abcd'}
)
713 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'}
)
714 test('%(id)s', ('ab:cd', 'ab:cd'), info
={'id': 'ab:cd'}
)
715 test('%(id.0)s', '-', info
={'id': '--'}
)
718 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
719 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
721 test('%(formats.{id)s', 'NA')
724 def expect_same_infodict(out
):
725 got_dict
= json
.loads(out
)
726 for info_field
, expected
in self
.outtmpl_info
.items():
727 self
.assertEqual(got_dict
.get(info_field
), expected
, info_field
)
730 test('%()j', (expect_same_infodict
, str))
733 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
734 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
735 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
736 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
737 test('%(non_existent.0)s', 'NA')
740 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
741 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
742 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
743 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
744 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
745 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
746 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
747 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
748 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
749 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
750 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
753 test('%(id)d', '1234')
754 test('%(height)c', '1')
756 test('%(id)d %(id)r', "1234 '1234'")
757 test('%(id)r %(height)r', "'1234' 1080")
758 test('%(ext)s-%(ext|def)d', 'mp4-def')
759 test('%(width|0)04d', '0000')
760 test('a%(width|)d', 'a', outtmpl_na_placeholder
='none')
762 FORMATS
= self
.outtmpl_info
['formats']
763 sanitize
= lambda x
: x
.replace(':', ':').replace('"', """).replace('\n', ' ')
765 # Custom type casting
766 test('%(formats.:.id)l', 'id 1, id 2, id 3')
767 test('%(formats.:.id)#l', ('id 1\nid 2\nid 3', 'id 1 id 2 id 3'))
768 test('%(ext)l', 'mp4')
769 test('%(formats.:.id) 18l', ' id 1, id 2, id 3')
770 test('%(formats)j', (json
.dumps(FORMATS
), sanitize(json
.dumps(FORMATS
))))
771 test('%(formats)#j', (json
.dumps(FORMATS
, indent
=4), sanitize(json
.dumps(FORMATS
, indent
=4))))
772 test('%(title5).3B', 'á')
773 test('%(title5)U', 'áéí 𝐀')
774 test('%(title5)#U', 'a\u0301e\u0301i\u0301 𝐀')
775 test('%(title5)+U', 'áéí A')
776 test('%(title5)+#U', 'a\u0301e\u0301i\u0301 A')
777 test('%(height)D', '1k')
778 test('%(filesize)#D', '1Ki')
779 test('%(height)5.2D', ' 1.08k')
780 test('%(title4)#S', 'foo_bar_test')
781 test('%(title4).10S', ('foo "bar" ', 'foo "bar"' + ('#' if compat_os_name
== 'nt' else ' ')))
782 if compat_os_name
== 'nt':
783 test('%(title4)q', ('"foo \\"bar\\" test"', ""foo ⧹"bar⧹" test""))
784 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', '"id 1" "id 2" "id 3"'))
785 test('%(formats.0.id)#q', ('"id 1"', '"id 1"'))
787 test('%(title4)q', ('\'foo "bar" test\'', '\'foo "bar" test\''))
788 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'")
789 test('%(formats.0.id)#q', "'id 1'")
791 # Internal formatting
792 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
793 test('%(title|%)s %(title|%%)s', '% %%')
794 test('%(id+1-height+3)05d', '00158')
795 test('%(width+100)05d', 'NA')
796 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], '% 15s' % sanitize(str(FORMATS
[0]))))
797 test('%(formats.0)r', (repr(FORMATS
[0]), sanitize(repr(FORMATS
[0]))))
798 test('%(height.0)03d', '001')
799 test('%(-height.0)04d', '-001')
800 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
801 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
802 test('%(formats.3)s', 'NA')
803 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
804 test('%(formats.0.id.-1+id)f', '1235.000000')
805 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
806 out
= json
.dumps([{'id': f['id'], 'height.:2': str(f['height'])[:2]}
807 if 'height' in f
else {'id': f['id']}
809 test('%(formats.:.{id,height.:2})j', (out
, sanitize(out
)))
810 test('%(formats.:.{id,height}.id)l', ', '.join(f
['id'] for f
in FORMATS
))
811 test('%(.{id,title})j', ('{"id": "1234"}', '{"id": "1234"}'))
814 test('%(title,id)s', '1234')
815 test('%(width-100,height+20|def)d', '1100')
816 test('%(width-100,height+width|def)s', 'def')
817 test('%(timestamp-x>%H\\,%M\\,%S,timestamp>%H\\,%M\\,%S)s', '12,00,00')
820 test('%(id&foo)s.bar', 'foo.bar')
821 test('%(title&foo)s.bar', 'NA.bar')
822 test('%(title&foo|baz)s.bar', 'baz.bar')
823 test('%(x,id&foo|baz)s.bar', 'foo.bar')
824 test('%(x,title&foo|baz)s.bar', 'baz.bar')
829 raise self
.assertTrue(False, 'LazyList should not be evaluated till here')
830 test('%(key.4)s', '4', info
={'key': LazyList(gen())}
)
833 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
834 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
835 # test('%(foo|)s', ('', '_')) # fixme
837 # Environment variable expansion for prepare_filename
838 os
.environ
['__yt_dlp_var'] = 'expanded'
839 envvar
= '%__yt_dlp_var%' if compat_os_name
== 'nt' else '$__yt_dlp_var'
840 test(envvar
, (envvar
, 'expanded'))
841 if compat_os_name
== 'nt':
842 test('%s%', ('%s%', '%s%'))
843 os
.environ
['s'] = 'expanded'
844 test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
845 os
.environ
['(test)s'] = 'expanded'
846 test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
848 # Path expansion and escaping
849 test('Hello %(title1)s', 'Hello $PATH')
850 test('Hello %(title2)s', 'Hello %PATH%')
851 test('%(title3)s', ('foo/bar\\test', 'foo⧸bar⧹test'))
852 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo⧸bar⧹test' % os
.path
.sep
))
854 def test_format_note(self
):
856 self
.assertEqual(ydl
._format
_note
({}), '')
857 assertRegexpMatches(self
, ydl
._format
_note
({
860 assertRegexpMatches(self
, ydl
._format
_note
({
864 def test_postprocessors(self
):
865 filename
= 'post-processor-testfile.mp4'
866 audiofile
= filename
+ '.mp3'
868 class SimplePP(PostProcessor
):
870 with open(audiofile
, 'wt') as f
:
872 return [info
['filepath']], info
874 def run_pp(params
, PP
):
875 with open(filename
, 'wt') as f
:
877 ydl
= YoutubeDL(params
)
878 ydl
.add_post_processor(PP())
879 ydl
.post_process(filename
, {'filepath': filename}
)
881 run_pp({'keepvideo': True}
, SimplePP
)
882 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
883 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
887 run_pp({'keepvideo': False}
, SimplePP
)
888 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
889 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
892 class ModifierPP(PostProcessor
):
894 with open(info
['filepath'], 'wt') as f
:
898 run_pp({'keepvideo': False}
, ModifierPP
)
899 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
902 def test_match_filter(self
):
909 'filesize': 10 * 1024,
911 'uploader': "變態妍字幕版 太妍 тест",
912 'creator': "тест ' 123 ' тест--",
913 'webpage_url': 'http://example.com/watch?v=shenanigans',
921 'description': 'foo',
922 'filesize': 5 * 1024,
924 'uploader': "тест 123",
925 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
927 videos
= [first
, second
]
929 def get_videos(filter_
=None):
930 ydl
= YDL({'match_filter': filter_, 'simulate': True}
)
932 ydl
.process_ie_result(v
, download
=True)
933 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
936 self
.assertEqual(res
, ['1', '2'])
938 def f(v
, incomplete
):
942 return 'Video id is not 1'
944 self
.assertEqual(res
, ['1'])
946 f
= match_filter_func('duration < 30')
948 self
.assertEqual(res
, ['2'])
950 f
= match_filter_func('description = foo')
952 self
.assertEqual(res
, ['2'])
954 f
= match_filter_func('description =? foo')
956 self
.assertEqual(res
, ['1', '2'])
958 f
= match_filter_func('filesize > 5KiB')
960 self
.assertEqual(res
, ['1'])
962 f
= match_filter_func('playlist_id = 42')
964 self
.assertEqual(res
, ['1'])
966 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
968 self
.assertEqual(res
, ['1'])
970 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
972 self
.assertEqual(res
, ['2'])
974 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
976 self
.assertEqual(res
, ['1'])
978 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
980 self
.assertEqual(res
, ['1'])
982 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
984 self
.assertEqual(res
, [])
986 def test_playlist_items_selection(self
):
987 INDICES
, PAGE_SIZE
= list(range(1, 11)), 3
989 def entry(i
, evaluated
):
997 def pagedlist_entries(evaluated
):
999 start
= PAGE_SIZE
* n
1000 for i
in INDICES
[start
: start
+ PAGE_SIZE
]:
1001 yield entry(i
, evaluated
)
1002 return OnDemandPagedList(page_func
, PAGE_SIZE
)
1005 return (i
+ PAGE_SIZE
- 1) // PAGE_SIZE
1007 def generator_entries(evaluated
):
1009 yield entry(i
, evaluated
)
1011 def list_entries(evaluated
):
1012 return list(generator_entries(evaluated
))
1014 def lazylist_entries(evaluated
):
1015 return LazyList(generator_entries(evaluated
))
1017 def get_downloaded_info_dicts(params
, entries
):
1019 ydl
.process_ie_result({
1020 '_type': 'playlist',
1022 'extractor': 'test:playlist',
1023 'extractor_key': 'test:playlist',
1024 'webpage_url': 'http://example.com',
1027 return ydl
.downloaded_info_dicts
1029 def test_selection(params
, expected_ids
, evaluate_all
=False):
1030 expected_ids
= list(expected_ids
)
1032 generator_eval
= pagedlist_eval
= INDICES
1033 elif not expected_ids
:
1034 generator_eval
= pagedlist_eval
= []
1036 generator_eval
= INDICES
[0: max(expected_ids
)]
1037 pagedlist_eval
= INDICES
[PAGE_SIZE
* page_num(min(expected_ids
)) - PAGE_SIZE
:
1038 PAGE_SIZE
* page_num(max(expected_ids
))]
1040 for name
, func
, expected_eval
in (
1041 ('list', list_entries
, INDICES
),
1042 ('Generator', generator_entries
, generator_eval
),
1043 # ('LazyList', lazylist_entries, generator_eval), # Generator and LazyList follow the exact same code path
1044 ('PagedList', pagedlist_entries
, pagedlist_eval
),
1047 entries
= func(evaluated
)
1048 results
= [(v
['playlist_autonumber'] - 1, (int(v
['id']), v
['playlist_index']))
1049 for v
in get_downloaded_info_dicts(params
, entries
)]
1050 self
.assertEqual(results
, list(enumerate(zip(expected_ids
, expected_ids
))), f
'Entries of {name} for {params}')
1051 self
.assertEqual(sorted(evaluated
), expected_eval
, f
'Evaluation of {name} for {params}')
1053 test_selection({}, INDICES
)
1054 test_selection({'playlistend': 20}
, INDICES
, True)
1055 test_selection({'playlistend': 2}
, INDICES
[:2])
1056 test_selection({'playliststart': 11}
, [], True)
1057 test_selection({'playliststart': 2}
, INDICES
[1:])
1058 test_selection({'playlist_items': '2-4'}
, INDICES
[1:4])
1059 test_selection({'playlist_items': '2,4'}
, [2, 4])
1060 test_selection({'playlist_items': '20'}
, [], True)
1061 test_selection({'playlist_items': '0'}
, [])
1063 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
1064 test_selection({'playlist_items': '2-4,3-4,3'}
, [2, 3, 4])
1065 test_selection({'playlist_items': '4,2'}
, [4, 2])
1067 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720
1068 # https://github.com/yt-dlp/yt-dlp/issues/302
1069 test_selection({'playlistreverse': True}
, INDICES
[::-1])
1070 test_selection({'playliststart': 2, 'playlistreverse': True}
, INDICES
[:0:-1])
1071 test_selection({'playlist_items': '2,4', 'playlistreverse': True}
, [4, 2])
1072 test_selection({'playlist_items': '4,2'}
, [4, 2])
1074 # Tests for --playlist-items start:end:step
1075 test_selection({'playlist_items': ':'}
, INDICES
, True)
1076 test_selection({'playlist_items': '::1'}
, INDICES
, True)
1077 test_selection({'playlist_items': '::-1'}
, INDICES
[::-1], True)
1078 test_selection({'playlist_items': ':6'}
, INDICES
[:6])
1079 test_selection({'playlist_items': ':-6'}
, INDICES
[:-5], True)
1080 test_selection({'playlist_items': '-1:6:-2'}
, INDICES
[:4:-2], True)
1081 test_selection({'playlist_items': '9:-6:-2'}
, INDICES
[8:3:-2], True)
1083 test_selection({'playlist_items': '1:inf:2'}
, INDICES
[::2], True)
1084 test_selection({'playlist_items': '-2:inf'}
, INDICES
[-2:], True)
1085 test_selection({'playlist_items': ':inf:-1'}
, [], True)
1086 test_selection({'playlist_items': '0-2:2'}
, [2])
1087 test_selection({'playlist_items': '1-:2'}
, INDICES
[::2], True)
1088 test_selection({'playlist_items': '0--2:2'}
, INDICES
[1:-1:2], True)
1090 test_selection({'playlist_items': '10::3'}
, [10], True)
1091 test_selection({'playlist_items': '-1::3'}
, [10], True)
1092 test_selection({'playlist_items': '11::3'}
, [], True)
1093 test_selection({'playlist_items': '-15::2'}
, INDICES
[1::2], True)
1094 test_selection({'playlist_items': '-15::15'}
, [], True)
1096 def test_urlopen_no_file_protocol(self
):
1097 # see https://github.com/ytdl-org/youtube-dl/issues/8227
1099 self
.assertRaises(urllib
.error
.URLError
, ydl
.urlopen
, 'file:///etc/passwd')
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__':