4 from __future__
import unicode_literals
6 # Allow direct execution
10 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
, compat_setenv
, compat_str
, compat_urllib_error
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 ExtractorError
, int_or_none
, match_filter_func
, LazyList
23 TEST_URL
= 'http://localhost/sample.mp4'
27 def __init__(self
, *args
, **kwargs
):
28 super(YDL
, self
).__init
__(*args
, **kwargs
)
29 self
.downloaded_info_dicts
= []
32 def process_info(self
, info_dict
):
33 info_dict
.pop('__original_infodict', None)
34 self
.downloaded_info_dicts
.append(info_dict
)
36 def to_screen(self
, msg
):
39 def dl(self
, *args
, **kwargs
):
40 assert False, 'Downloader must not be invoked for test_YoutubeDL'
43 def _make_result(formats
, **kwargs
):
47 'title': 'testttitle',
48 'extractor': 'testex',
49 'extractor_key': 'TestEx',
50 'webpage_url': 'http://example.com/watch?v=shenanigans',
56 class TestFormatSelection(unittest
.TestCase
):
57 def test_prefer_free_formats(self
):
58 # Same resolution => download webm
60 ydl
.params
['prefer_free_formats'] = True
62 {'ext': 'webm', 'height': 460, 'url': TEST_URL}
,
63 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}
,
65 info_dict
= _make_result(formats
)
67 yie
._sort
_formats
(info_dict
['formats'])
68 ydl
.process_ie_result(info_dict
)
69 downloaded
= ydl
.downloaded_info_dicts
[0]
70 self
.assertEqual(downloaded
['ext'], 'webm')
72 # Different resolution => download best quality (mp4)
74 ydl
.params
['prefer_free_formats'] = True
76 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
77 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}
,
79 info_dict
['formats'] = formats
81 yie
._sort
_formats
(info_dict
['formats'])
82 ydl
.process_ie_result(info_dict
)
83 downloaded
= ydl
.downloaded_info_dicts
[0]
84 self
.assertEqual(downloaded
['ext'], 'mp4')
86 # No prefer_free_formats => prefer mp4 and webm
88 ydl
.params
['prefer_free_formats'] = False
90 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
91 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}
,
92 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
94 info_dict
['formats'] = formats
96 yie
._sort
_formats
(info_dict
['formats'])
97 ydl
.process_ie_result(info_dict
)
98 downloaded
= ydl
.downloaded_info_dicts
[0]
99 self
.assertEqual(downloaded
['ext'], 'mp4')
102 ydl
.params
['prefer_free_formats'] = False
104 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
105 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
107 info_dict
['formats'] = formats
109 yie
._sort
_formats
(info_dict
['formats'])
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': 1, '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'}
)
185 ie
._sort
_formats
(info_dict
['formats'])
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'}
)
192 ie
._sort
_formats
(info_dict
['formats'])
193 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
194 downloaded
= ydl
.downloaded_info_dicts
[0]
195 self
.assertEqual(downloaded
['format_id'], 'mp3-64')
197 ydl
= YDL({'prefer_free_formats': True}
)
199 ie
._sort
_formats
(info_dict
['formats'])
200 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
201 downloaded
= ydl
.downloaded_info_dicts
[0]
202 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
204 def test_format_selection_video(self
):
206 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
207 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
208 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
210 info_dict
= _make_result(formats
)
212 ydl
= YDL({'format': 'bestvideo'}
)
213 ydl
.process_ie_result(info_dict
.copy())
214 downloaded
= ydl
.downloaded_info_dicts
[0]
215 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
217 ydl
= YDL({'format': 'worstvideo'}
)
218 ydl
.process_ie_result(info_dict
.copy())
219 downloaded
= ydl
.downloaded_info_dicts
[0]
220 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
222 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
223 ydl
.process_ie_result(info_dict
.copy())
224 downloaded
= ydl
.downloaded_info_dicts
[0]
225 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
228 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
230 info_dict
= _make_result(formats
)
232 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
233 ydl
.process_ie_result(info_dict
.copy())
234 downloaded
= ydl
.downloaded_info_dicts
[0]
235 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
237 def test_format_selection_string_ops(self
):
239 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
240 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
242 info_dict
= _make_result(formats
)
245 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
246 ydl
.process_ie_result(info_dict
.copy())
247 downloaded
= ydl
.downloaded_info_dicts
[0]
248 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
250 # does not equal (!=)
251 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
252 ydl
.process_ie_result(info_dict
.copy())
253 downloaded
= ydl
.downloaded_info_dicts
[0]
254 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
256 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
257 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
260 ydl
= YDL({'format': '[format_id^=abc]'}
)
261 ydl
.process_ie_result(info_dict
.copy())
262 downloaded
= ydl
.downloaded_info_dicts
[0]
263 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
265 # does not start with (!^=)
266 ydl
= YDL({'format': '[format_id!^=abc]'}
)
267 ydl
.process_ie_result(info_dict
.copy())
268 downloaded
= ydl
.downloaded_info_dicts
[0]
269 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
271 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
272 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
275 ydl
= YDL({'format': '[format_id$=cba]'}
)
276 ydl
.process_ie_result(info_dict
.copy())
277 downloaded
= ydl
.downloaded_info_dicts
[0]
278 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
280 # does not end with (!$=)
281 ydl
= YDL({'format': '[format_id!$=cba]'}
)
282 ydl
.process_ie_result(info_dict
.copy())
283 downloaded
= ydl
.downloaded_info_dicts
[0]
284 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
286 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
287 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
290 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
291 ydl
.process_ie_result(info_dict
.copy())
292 downloaded
= ydl
.downloaded_info_dicts
[0]
293 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
295 # does not contain (!*=)
296 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
297 ydl
.process_ie_result(info_dict
.copy())
298 downloaded
= ydl
.downloaded_info_dicts
[0]
299 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
301 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
302 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
304 ydl
= YDL({'format': '[format_id!*=-]'}
)
305 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
307 def test_youtube_format_selection(self
):
308 # FIXME: Rewrite in accordance with the new format sorting options
312 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
313 # Apple HTTP Live Streaming
314 '96', '95', '94', '93', '92', '132', '151',
316 '85', '84', '102', '83', '101', '82', '100',
318 '137', '248', '136', '247', '135', '246',
319 '245', '244', '134', '243', '133', '242', '160',
321 '141', '172', '140', '171', '139',
324 def format_info(f_id
):
325 info
= YoutubeIE
._formats
[f_id
].copy()
327 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
328 # and 'vcodec', while in tests such information is incomplete since
329 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
330 # test_YoutubeDL.test_youtube_format_selection is broken without
332 if 'acodec' in info
and 'vcodec' not in info
:
333 info
['vcodec'] = 'none'
334 elif 'vcodec' in info
and 'acodec' not in info
:
335 info
['acodec'] = 'none'
337 info
['format_id'] = f_id
338 info
['url'] = 'url:' + f_id
340 formats_order
= [format_info(f_id
) for f_id
in order
]
342 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
343 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
345 yie
._sort
_formats
(info_dict
['formats'])
346 ydl
.process_ie_result(info_dict
)
347 downloaded
= ydl
.downloaded_info_dicts
[0]
348 self
.assertEqual(downloaded
['format_id'], '248+172')
349 self
.assertEqual(downloaded
['ext'], 'mp4')
351 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
352 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
354 yie
._sort
_formats
(info_dict
['formats'])
355 ydl
.process_ie_result(info_dict
)
356 downloaded
= ydl
.downloaded_info_dicts
[0]
357 self
.assertEqual(downloaded
['format_id'], '38')
359 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
360 ydl
= YDL({'format': 'bestvideo/best,bestaudio'}
)
362 yie
._sort
_formats
(info_dict
['formats'])
363 ydl
.process_ie_result(info_dict
)
364 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
365 self
.assertEqual(downloaded_ids
, ['137', '141'])
367 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
368 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'}
)
370 yie
._sort
_formats
(info_dict
['formats'])
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
, ['137+141', '248+141'])
375 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
376 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'}
)
378 yie
._sort
_formats
(info_dict
['formats'])
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
, ['136+141', '247+141'])
383 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
384 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}
)
386 yie
._sort
_formats
(info_dict
['formats'])
387 ydl
.process_ie_result(info_dict
)
388 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
389 self
.assertEqual(downloaded_ids
, ['248+141'])
391 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
392 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
393 ydl
= YDL({'format': 'best/bestvideo'}
)
395 yie
._sort
_formats
(info_dict
['formats'])
396 ydl
.process_ie_result(info_dict
)
397 downloaded
= ydl
.downloaded_info_dicts
[0]
398 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
400 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
401 ydl
= YDL({'format': 'best/bestvideo'}
)
403 yie
._sort
_formats
(info_dict
['formats'])
404 ydl
.process_ie_result(info_dict
)
405 downloaded
= ydl
.downloaded_info_dicts
[0]
406 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
408 def test_audio_only_extractor_format_selection(self
):
409 # For extractors with incomplete formats (all formats are audio-only or
410 # video-only) best and worst should fallback to corresponding best/worst
411 # video-only or audio-only formats (as per
412 # https://github.com/ytdl-org/youtube-dl/pull/5556)
414 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
415 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
417 info_dict
= _make_result(formats
)
419 ydl
= YDL({'format': 'best'}
)
420 ydl
.process_ie_result(info_dict
.copy())
421 downloaded
= ydl
.downloaded_info_dicts
[0]
422 self
.assertEqual(downloaded
['format_id'], 'high')
424 ydl
= YDL({'format': 'worst'}
)
425 ydl
.process_ie_result(info_dict
.copy())
426 downloaded
= ydl
.downloaded_info_dicts
[0]
427 self
.assertEqual(downloaded
['format_id'], 'low')
429 def test_format_not_available(self
):
431 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
432 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
434 info_dict
= _make_result(formats
)
436 # This must fail since complete video-audio format does not match filter
437 # and extractor does not provide incomplete only formats (i.e. only
438 # video-only or audio-only).
439 ydl
= YDL({'format': 'best[height>360]'}
)
440 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
442 def test_format_selection_issue_10083(self
):
443 # See https://github.com/ytdl-org/youtube-dl/issues/10083
445 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
446 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
447 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
449 info_dict
= _make_result(formats
)
451 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
452 ydl
.process_ie_result(info_dict
.copy())
453 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
455 def test_invalid_format_specs(self
):
456 def assert_syntax_error(format_spec
):
457 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec}
)
459 assert_syntax_error('bestvideo,,best')
460 assert_syntax_error('+bestaudio')
461 assert_syntax_error('bestvideo+')
462 assert_syntax_error('/')
463 assert_syntax_error('[720<height]')
465 def test_format_filtering(self
):
467 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
468 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
469 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
470 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
471 {'format_id': 'E', 'filesize': 3000}
,
473 {'format_id': 'G', 'filesize': 1000000}
,
476 f
['url'] = 'http://_/'
478 info_dict
= _make_result(formats
)
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'], 'D')
485 ydl
= YDL({'format': 'best[filesize<=3000]'}
)
486 ydl
.process_ie_result(info_dict
)
487 downloaded
= ydl
.downloaded_info_dicts
[0]
488 self
.assertEqual(downloaded
['format_id'], 'E')
490 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
491 ydl
.process_ie_result(info_dict
)
492 downloaded
= ydl
.downloaded_info_dicts
[0]
493 self
.assertEqual(downloaded
['format_id'], 'F')
495 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'}
)
496 ydl
.process_ie_result(info_dict
)
497 downloaded
= ydl
.downloaded_info_dicts
[0]
498 self
.assertEqual(downloaded
['format_id'], 'B')
500 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
501 ydl
.process_ie_result(info_dict
)
502 downloaded
= ydl
.downloaded_info_dicts
[0]
503 self
.assertEqual(downloaded
['format_id'], 'C')
505 ydl
= YDL({'format': '[filesize>?1]'}
)
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': '[filesize<1M]'}
)
511 ydl
.process_ie_result(info_dict
)
512 downloaded
= ydl
.downloaded_info_dicts
[0]
513 self
.assertEqual(downloaded
['format_id'], 'E')
515 ydl
= YDL({'format': '[filesize<1MiB]'}
)
516 ydl
.process_ie_result(info_dict
)
517 downloaded
= ydl
.downloaded_info_dicts
[0]
518 self
.assertEqual(downloaded
['format_id'], 'G')
520 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
521 ydl
.process_ie_result(info_dict
)
522 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
523 self
.assertEqual(downloaded_ids
, ['D', 'C', 'B'])
525 ydl
= YDL({'format': 'best[height<40]'}
)
527 ydl
.process_ie_result(info_dict
)
528 except ExtractorError
:
530 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
532 def test_default_format_spec(self
):
533 ydl
= YDL({'simulate': True}
)
534 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
537 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
539 ydl
= YDL({'simulate': True}
)
540 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
542 ydl
= YDL({'outtmpl': '-'}
)
543 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
546 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
547 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
550 class TestYoutubeDL(unittest
.TestCase
):
551 def test_subtitles(self
):
552 def s_formats(lang
, autocaption
=False):
555 'url': 'http://localhost/video.%s.%s' % (lang
, ext
),
556 '_auto': autocaption
,
557 } for ext
in ['vtt', 'srt', 'ass']]
558 subtitles
= dict((l
, s_formats(l
)) for l
in ['en', 'fr', 'es'])
559 auto_captions
= dict((l
, s_formats(l
, True)) for l
in ['it', 'pt', 'es'])
563 'url': 'http://localhost/video.mp4',
564 'subtitles': subtitles
,
565 'automatic_captions': auto_captions
,
567 'webpage_url': 'http://example.com/watch?v=shenanigans',
570 def get_info(params
={}):
571 params
.setdefault('simulate', True)
573 ydl
.report_warning
= lambda *args
, **kargs
: None
574 return ydl
.process_video_result(info_dict
, download
=False)
577 self
.assertFalse(result
.get('requested_subtitles'))
578 self
.assertEqual(result
['subtitles'], subtitles
)
579 self
.assertEqual(result
['automatic_captions'], auto_captions
)
581 result
= get_info({'writesubtitles': True}
)
582 subs
= result
['requested_subtitles']
583 self
.assertTrue(subs
)
584 self
.assertEqual(set(subs
.keys()), set(['en']))
585 self
.assertTrue(subs
['en'].get('data') is None)
586 self
.assertEqual(subs
['en']['ext'], 'ass')
588 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
589 subs
= result
['requested_subtitles']
590 self
.assertEqual(subs
['en']['ext'], 'srt')
592 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
593 subs
= result
['requested_subtitles']
594 self
.assertTrue(subs
)
595 self
.assertEqual(set(subs
.keys()), set(['es', 'fr']))
597 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
598 subs
= result
['requested_subtitles']
599 self
.assertTrue(subs
)
600 self
.assertEqual(set(subs
.keys()), set(['es', 'fr']))
602 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
603 subs
= result
['requested_subtitles']
604 self
.assertTrue(subs
)
605 self
.assertEqual(set(subs
.keys()), set(['fr']))
607 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
608 subs
= result
['requested_subtitles']
609 self
.assertTrue(subs
)
610 self
.assertEqual(set(subs
.keys()), set(['en']))
612 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
613 subs
= result
['requested_subtitles']
614 self
.assertTrue(subs
)
615 self
.assertEqual(set(subs
.keys()), set(['es', 'en']))
617 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
618 subs
= result
['requested_subtitles']
619 self
.assertTrue(subs
)
620 self
.assertEqual(set(subs
.keys()), set(['es', 'pt']))
621 self
.assertFalse(subs
['es']['_auto'])
622 self
.assertTrue(subs
['pt']['_auto'])
624 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
625 subs
= result
['requested_subtitles']
626 self
.assertTrue(subs
)
627 self
.assertEqual(set(subs
.keys()), set(['es', 'pt']))
628 self
.assertTrue(subs
['es']['_auto'])
629 self
.assertTrue(subs
['pt']['_auto'])
631 def test_add_extra_info(self
):
637 'playlist': 'funny videos',
639 YDL
.add_extra_info(test_dict
, extra_info
)
640 self
.assertEqual(test_dict
['extractor'], 'Foo')
641 self
.assertEqual(test_dict
['playlist'], 'funny videos')
650 'title3': 'foo/bar\\test',
651 'title4': 'foo "bar" test',
653 'timestamp': 1618488000,
656 'playlist_autonumber': 2,
657 '_last_playlist_index': 100,
659 'formats': [{'id': 'id 1'}
, {'id': 'id 2'}
, {'id': 'id 3'}
]
662 def test_prepare_outtmpl_and_filename(self
):
663 def test(tmpl
, expected
, *, info
=None, **params
):
664 params
['outtmpl'] = tmpl
665 ydl
= YoutubeDL(params
)
666 ydl
._num
_downloads
= 1
667 self
.assertEqual(ydl
.validate_outtmpl(tmpl
), None)
669 out
= ydl
.evaluate_outtmpl(tmpl
, info
or self
.outtmpl_info
)
670 fname
= ydl
.prepare_filename(info
or self
.outtmpl_info
)
672 if not isinstance(expected
, (list, tuple)):
673 expected
= (expected
, expected
)
674 for (name
, got
), expect
in zip((('outtmpl', out
), ('filename', fname
)), expected
):
676 self
.assertTrue(expect(got
), f
'Wrong {name} from {tmpl}')
678 self
.assertEqual(got
, expect
, f
'Wrong {name} from {tmpl}')
681 original_infodict
= dict(self
.outtmpl_info
)
682 test('foo.bar', 'foo.bar')
683 original_infodict
['epoch'] = self
.outtmpl_info
.get('epoch')
684 self
.assertTrue(isinstance(original_infodict
['epoch'], int))
685 test('%(epoch)d', int_or_none
)
686 self
.assertEqual(original_infodict
, self
.outtmpl_info
)
688 # Auto-generated fields
689 test('%(id)s.%(ext)s', '1234.mp4')
690 test('%(duration_string)s', ('27:46:40', '27-46-40'))
691 test('%(resolution)s', '1080p')
692 test('%(playlist_index)s', '001')
693 test('%(playlist_autonumber)s', '02')
694 test('%(autonumber)s', '00001')
695 test('%(autonumber+2)03d', '005', autonumber_start
=3)
696 test('%(autonumber)s', '001', autonumber_size
=3)
705 test('%abc%', '%abc%')
706 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
707 test('%%%(height)s', '%1080')
708 test('%(width)06d.%(ext)s', 'NA.mp4')
709 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
710 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
713 test('%(id)s', '_abcd', info
={'id': '_abcd'}
)
714 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'}
)
715 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}
]})
716 test('%(id)s', '-abcd', info
={'id': '-abcd'}
)
717 test('%(id)s', '.abcd', info
={'id': '.abcd'}
)
718 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'}
)
719 test('%(id)s', ('ab:cd', 'ab -cd'), info
={'id': 'ab:cd'}
)
720 test('%(id.0)s', '-', info
={'id': '--'}
)
723 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
724 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
728 def expect_same_infodict(out
):
729 got_dict
= json
.loads(out
)
730 for info_field
, expected
in self
.outtmpl_info
.items():
731 self
.assertEqual(got_dict
.get(info_field
), expected
, info_field
)
734 test('%()j', (expect_same_infodict
, str))
737 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
738 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
739 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
740 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
741 test('%(non_existent.0)s', 'NA')
744 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
745 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
746 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
747 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
748 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
749 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
750 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
751 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
752 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
753 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
754 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
757 test('%(id)d', '1234')
758 test('%(height)c', '1')
760 test('%(id)d %(id)r', "1234 '1234'")
761 test('%(id)r %(height)r', "'1234' 1080")
762 test('%(ext)s-%(ext|def)d', 'mp4-def')
763 test('%(width|0)04d', '0000')
764 test('a%(width|)d', 'a', outtmpl_na_placeholder
='none')
766 FORMATS
= self
.outtmpl_info
['formats']
767 sanitize
= lambda x
: x
.replace(':', ' -').replace('"', "'").replace('\n', ' ')
769 # Custom type casting
770 test('%(formats.:.id)l', 'id 1, id 2, id 3')
771 test('%(formats.:.id)#l', ('id 1\nid 2\nid 3', 'id 1 id 2 id 3'))
772 test('%(ext)l', 'mp4')
773 test('%(formats.:.id) 18l', ' id 1, id 2, id 3')
774 test('%(formats)j', (json
.dumps(FORMATS
), sanitize(json
.dumps(FORMATS
))))
775 test('%(formats)#j', (json
.dumps(FORMATS
, indent
=4), sanitize(json
.dumps(FORMATS
, indent
=4))))
776 test('%(title5).3B', 'á')
777 test('%(title5)U', 'áéí 𝐀')
778 test('%(title5)#U', 'a\u0301e\u0301i\u0301 𝐀')
779 test('%(title5)+U', 'áéí A')
780 test('%(title5)+#U', 'a\u0301e\u0301i\u0301 A')
781 test('%(height)D', '1K')
782 test('%(height)5.2D', ' 1.08K')
783 test('%(title4)#S', 'foo_bar_test')
784 test('%(title4).10S', ('foo \'bar\' ', 'foo \'bar\'' + ('#' if compat_os_name
== 'nt' else ' ')))
785 if compat_os_name
== 'nt':
786 test('%(title4)q', ('"foo \\"bar\\" test"', "'foo _'bar_' test'"))
787 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', "'id 1' 'id 2' 'id 3'"))
788 test('%(formats.0.id)#q', ('"id 1"', "'id 1'"))
790 test('%(title4)q', ('\'foo "bar" test\'', "'foo 'bar' test'"))
791 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'")
792 test('%(formats.0.id)#q', "'id 1'")
794 # Internal formatting
795 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
796 test('%(title|%)s %(title|%%)s', '% %%')
797 test('%(id+1-height+3)05d', '00158')
798 test('%(width+100)05d', 'NA')
799 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], '% 15s' % sanitize(str(FORMATS
[0]))))
800 test('%(formats.0)r', (repr(FORMATS
[0]), sanitize(repr(FORMATS
[0]))))
801 test('%(height.0)03d', '001')
802 test('%(-height.0)04d', '-001')
803 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
804 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
805 test('%(formats.3)s', 'NA')
806 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
807 test('%(formats.0.id.-1+id)f', '1235.000000')
808 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
811 test('%(title,id)s', '1234')
812 test('%(width-100,height+20|def)d', '1100')
813 test('%(width-100,height+width|def)s', 'def')
814 test('%(timestamp-x>%H\\,%M\\,%S,timestamp>%H\\,%M\\,%S)s', '12,00,00')
817 test('%(id&foo)s.bar', 'foo.bar')
818 test('%(title&foo)s.bar', 'NA.bar')
819 test('%(title&foo|baz)s.bar', 'baz.bar')
824 raise self
.assertTrue(False, 'LazyList should not be evaluated till here')
825 test('%(key.4)s', '4', info
={'key': LazyList(gen())}
)
828 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
829 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
830 # test('%(foo|)s', ('', '_')) # fixme
832 # Environment variable expansion for prepare_filename
833 compat_setenv('__yt_dlp_var', 'expanded')
834 envvar
= '%__yt_dlp_var%' if compat_os_name
== 'nt' else '$__yt_dlp_var'
835 test(envvar
, (envvar
, 'expanded'))
836 if compat_os_name
== 'nt':
837 test('%s%', ('%s%', '%s%'))
838 compat_setenv('s', 'expanded')
839 test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
840 compat_setenv('(test)s', 'expanded')
841 test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
843 # Path expansion and escaping
844 test('Hello %(title1)s', 'Hello $PATH')
845 test('Hello %(title2)s', 'Hello %PATH%')
846 test('%(title3)s', ('foo/bar\\test', 'foo_bar_test'))
847 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo_bar_test' % os
.path
.sep
))
849 def test_format_note(self
):
851 self
.assertEqual(ydl
._format
_note
({}), '')
852 assertRegexpMatches(self
, ydl
._format
_note
({
855 assertRegexpMatches(self
, ydl
._format
_note
({
859 def test_postprocessors(self
):
860 filename
= 'post-processor-testfile.mp4'
861 audiofile
= filename
+ '.mp3'
863 class SimplePP(PostProcessor
):
865 with open(audiofile
, 'wt') as f
:
867 return [info
['filepath']], info
869 def run_pp(params
, PP
):
870 with open(filename
, 'wt') as f
:
872 ydl
= YoutubeDL(params
)
873 ydl
.add_post_processor(PP())
874 ydl
.post_process(filename
, {'filepath': filename}
)
876 run_pp({'keepvideo': True}
, SimplePP
)
877 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
878 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
882 run_pp({'keepvideo': False}
, SimplePP
)
883 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
884 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
887 class ModifierPP(PostProcessor
):
889 with open(info
['filepath'], 'wt') as f
:
893 run_pp({'keepvideo': False}
, ModifierPP
)
894 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
897 def test_match_filter(self
):
898 class FilterYDL(YDL
):
899 def __init__(self
, *args
, **kwargs
):
900 super(FilterYDL
, self
).__init
__(*args
, **kwargs
)
901 self
.params
['simulate'] = True
903 def process_info(self
, info_dict
):
904 super(YDL
, self
).process_info(info_dict
)
906 def _match_entry(self
, info_dict
, incomplete
=False):
907 res
= super(FilterYDL
, self
)._match
_entry
(info_dict
, incomplete
)
909 self
.downloaded_info_dicts
.append(info_dict
)
918 'filesize': 10 * 1024,
920 'uploader': "變態妍字幕版 太妍 тест",
921 'creator': "тест ' 123 ' тест--",
922 'webpage_url': 'http://example.com/watch?v=shenanigans',
930 'description': 'foo',
931 'filesize': 5 * 1024,
933 'uploader': "тест 123",
934 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
936 videos
= [first
, second
]
938 def get_videos(filter_
=None):
939 ydl
= FilterYDL({'match_filter': filter_}
)
941 ydl
.process_ie_result(v
, download
=True)
942 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
945 self
.assertEqual(res
, ['1', '2'])
951 return 'Video id is not 1'
953 self
.assertEqual(res
, ['1'])
955 f
= match_filter_func('duration < 30')
957 self
.assertEqual(res
, ['2'])
959 f
= match_filter_func('description = foo')
961 self
.assertEqual(res
, ['2'])
963 f
= match_filter_func('description =? foo')
965 self
.assertEqual(res
, ['1', '2'])
967 f
= match_filter_func('filesize > 5KiB')
969 self
.assertEqual(res
, ['1'])
971 f
= match_filter_func('playlist_id = 42')
973 self
.assertEqual(res
, ['1'])
975 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
977 self
.assertEqual(res
, ['1'])
979 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
981 self
.assertEqual(res
, ['2'])
983 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
985 self
.assertEqual(res
, ['1'])
987 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
989 self
.assertEqual(res
, ['1'])
991 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
993 self
.assertEqual(res
, [])
995 def test_playlist_items_selection(self
):
998 'title': compat_str(i
),
1000 } for i
in range(1, 5)]
1002 '_type': 'playlist',
1005 'extractor': 'test:playlist',
1006 'extractor_key': 'test:playlist',
1007 'webpage_url': 'http://example.com',
1010 def get_downloaded_info_dicts(params
):
1012 # make a deep copy because the dictionary and nested entries
1014 ydl
.process_ie_result(copy
.deepcopy(playlist
))
1015 return ydl
.downloaded_info_dicts
1017 def test_selection(params
, expected_ids
):
1019 (v
['playlist_autonumber'] - 1, (int(v
['id']), v
['playlist_index']))
1020 for v
in get_downloaded_info_dicts(params
)]
1021 self
.assertEqual(results
, list(enumerate(zip(expected_ids
, expected_ids
))))
1023 test_selection({}, [1, 2, 3, 4])
1024 test_selection({'playlistend': 10}
, [1, 2, 3, 4])
1025 test_selection({'playlistend': 2}
, [1, 2])
1026 test_selection({'playliststart': 10}
, [])
1027 test_selection({'playliststart': 2}
, [2, 3, 4])
1028 test_selection({'playlist_items': '2-4'}
, [2, 3, 4])
1029 test_selection({'playlist_items': '2,4'}
, [2, 4])
1030 test_selection({'playlist_items': '10'}
, [])
1031 test_selection({'playlist_items': '0'}
, [])
1033 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
1034 test_selection({'playlist_items': '2-4,3-4,3'}
, [2, 3, 4])
1035 test_selection({'playlist_items': '4,2'}
, [4, 2])
1037 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720
1038 # https://github.com/yt-dlp/yt-dlp/issues/302
1039 test_selection({'playlistreverse': True}
, [4, 3, 2, 1])
1040 test_selection({'playliststart': 2, 'playlistreverse': True}
, [4, 3, 2])
1041 test_selection({'playlist_items': '2,4', 'playlistreverse': True}
, [4, 2])
1042 test_selection({'playlist_items': '4,2'}
, [4, 2])
1044 def test_urlopen_no_file_protocol(self
):
1045 # see https://github.com/ytdl-org/youtube-dl/issues/8227
1047 self
.assertRaises(compat_urllib_error
.URLError
, ydl
.urlopen
, 'file:///etc/passwd')
1049 def test_do_not_override_ie_key_in_url_transparent(self
):
1052 class Foo1IE(InfoExtractor
):
1053 _VALID_URL
= r
'foo1:'
1055 def _real_extract(self
, url
):
1057 '_type': 'url_transparent',
1060 'title': 'foo1 title',
1064 class Foo2IE(InfoExtractor
):
1065 _VALID_URL
= r
'foo2:'
1067 def _real_extract(self
, url
):
1074 class Foo3IE(InfoExtractor
):
1075 _VALID_URL
= r
'foo3:'
1077 def _real_extract(self
, url
):
1078 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1080 ydl
.add_info_extractor(Foo1IE(ydl
))
1081 ydl
.add_info_extractor(Foo2IE(ydl
))
1082 ydl
.add_info_extractor(Foo3IE(ydl
))
1083 ydl
.extract_info('foo1:')
1084 downloaded
= ydl
.downloaded_info_dicts
[0]
1085 self
.assertEqual(downloaded
['url'], TEST_URL
)
1086 self
.assertEqual(downloaded
['title'], 'foo1 title')
1087 self
.assertEqual(downloaded
['id'], 'testid')
1088 self
.assertEqual(downloaded
['extractor'], 'testex')
1089 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1091 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1092 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1095 def __init__(self
, *args
, **kwargs
):
1096 super(_YDL
, self
).__init
__(*args
, **kwargs
)
1098 def trouble(self
, s
, tb
=None):
1103 'ignoreerrors': True,
1106 class VideoIE(InfoExtractor
):
1107 _VALID_URL
= r
'video:(?P<id>\d+)'
1109 def _real_extract(self
, url
):
1110 video_id
= self
._match
_id
(url
)
1112 'format_id': 'default',
1116 raise ExtractorError('foo')
1119 'format_id': 'extra',
1124 'title': 'Video %s' % video_id
,
1128 class PlaylistIE(InfoExtractor
):
1129 _VALID_URL
= r
'playlist:'
1133 video_id
= compat_str(n
)
1135 '_type': 'url_transparent',
1136 'ie_key': VideoIE
.ie_key(),
1138 'url': 'video:%s' % video_id
,
1139 'title': 'Video Transparent %s' % video_id
,
1142 def _real_extract(self
, url
):
1143 return self
.playlist_result(self
._entries
())
1145 ydl
.add_info_extractor(VideoIE(ydl
))
1146 ydl
.add_info_extractor(PlaylistIE(ydl
))
1147 info
= ydl
.extract_info('playlist:')
1148 entries
= info
['entries']
1149 self
.assertEqual(len(entries
), 3)
1150 self
.assertTrue(entries
[0] is None)
1151 self
.assertTrue(entries
[1] is None)
1152 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1153 downloaded
= ydl
.downloaded_info_dicts
[0]
1154 self
.assertEqual(entries
[2], downloaded
)
1155 self
.assertEqual(downloaded
['url'], TEST_URL
)
1156 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1157 self
.assertEqual(downloaded
['id'], '2')
1158 self
.assertEqual(downloaded
['extractor'], 'Video')
1159 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1162 if __name__
== '__main__':