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
= info_dict
.copy()
34 info_dict
.pop('__original_infodict', None)
35 self
.downloaded_info_dicts
.append(info_dict
)
37 def to_screen(self
, msg
):
40 def dl(self
, *args
, **kwargs
):
41 assert False, 'Downloader must not be invoked for test_YoutubeDL'
44 def _make_result(formats
, **kwargs
):
48 'title': 'testttitle',
49 'extractor': 'testex',
50 'extractor_key': 'TestEx',
51 'webpage_url': 'http://example.com/watch?v=shenanigans',
57 class TestFormatSelection(unittest
.TestCase
):
58 def test_prefer_free_formats(self
):
59 # Same resolution => download webm
61 ydl
.params
['prefer_free_formats'] = True
63 {'ext': 'webm', 'height': 460, 'url': TEST_URL}
,
64 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}
,
66 info_dict
= _make_result(formats
)
68 yie
._sort
_formats
(info_dict
['formats'])
69 ydl
.process_ie_result(info_dict
)
70 downloaded
= ydl
.downloaded_info_dicts
[0]
71 self
.assertEqual(downloaded
['ext'], 'webm')
73 # Different resolution => download best quality (mp4)
75 ydl
.params
['prefer_free_formats'] = True
77 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
78 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}
,
80 info_dict
['formats'] = formats
82 yie
._sort
_formats
(info_dict
['formats'])
83 ydl
.process_ie_result(info_dict
)
84 downloaded
= ydl
.downloaded_info_dicts
[0]
85 self
.assertEqual(downloaded
['ext'], 'mp4')
87 # No prefer_free_formats => prefer mp4 and webm
89 ydl
.params
['prefer_free_formats'] = False
91 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
92 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}
,
93 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
95 info_dict
['formats'] = formats
97 yie
._sort
_formats
(info_dict
['formats'])
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
110 yie
._sort
_formats
(info_dict
['formats'])
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': 1, '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'}
)
186 ie
._sort
_formats
(info_dict
['formats'])
187 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
188 downloaded
= ydl
.downloaded_info_dicts
[0]
189 self
.assertEqual(downloaded
['format_id'], 'aac-64')
191 ydl
= YDL({'format': 'mp3'}
)
193 ie
._sort
_formats
(info_dict
['formats'])
194 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
195 downloaded
= ydl
.downloaded_info_dicts
[0]
196 self
.assertEqual(downloaded
['format_id'], 'mp3-64')
198 ydl
= YDL({'prefer_free_formats': True}
)
200 ie
._sort
_formats
(info_dict
['formats'])
201 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
202 downloaded
= ydl
.downloaded_info_dicts
[0]
203 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
205 def test_format_selection_video(self
):
207 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
208 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
209 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
211 info_dict
= _make_result(formats
)
213 ydl
= YDL({'format': 'bestvideo'}
)
214 ydl
.process_ie_result(info_dict
.copy())
215 downloaded
= ydl
.downloaded_info_dicts
[0]
216 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
218 ydl
= YDL({'format': 'worstvideo'}
)
219 ydl
.process_ie_result(info_dict
.copy())
220 downloaded
= ydl
.downloaded_info_dicts
[0]
221 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
223 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
224 ydl
.process_ie_result(info_dict
.copy())
225 downloaded
= ydl
.downloaded_info_dicts
[0]
226 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
229 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
231 info_dict
= _make_result(formats
)
233 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
234 ydl
.process_ie_result(info_dict
.copy())
235 downloaded
= ydl
.downloaded_info_dicts
[0]
236 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
238 def test_format_selection_string_ops(self
):
240 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
241 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
243 info_dict
= _make_result(formats
)
246 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
247 ydl
.process_ie_result(info_dict
.copy())
248 downloaded
= ydl
.downloaded_info_dicts
[0]
249 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
251 # does not equal (!=)
252 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
253 ydl
.process_ie_result(info_dict
.copy())
254 downloaded
= ydl
.downloaded_info_dicts
[0]
255 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
257 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
258 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
261 ydl
= YDL({'format': '[format_id^=abc]'}
)
262 ydl
.process_ie_result(info_dict
.copy())
263 downloaded
= ydl
.downloaded_info_dicts
[0]
264 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
266 # does not start with (!^=)
267 ydl
= YDL({'format': '[format_id!^=abc]'}
)
268 ydl
.process_ie_result(info_dict
.copy())
269 downloaded
= ydl
.downloaded_info_dicts
[0]
270 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
272 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
273 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
276 ydl
= YDL({'format': '[format_id$=cba]'}
)
277 ydl
.process_ie_result(info_dict
.copy())
278 downloaded
= ydl
.downloaded_info_dicts
[0]
279 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
281 # does not end with (!$=)
282 ydl
= YDL({'format': '[format_id!$=cba]'}
)
283 ydl
.process_ie_result(info_dict
.copy())
284 downloaded
= ydl
.downloaded_info_dicts
[0]
285 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
287 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
288 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
291 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
292 ydl
.process_ie_result(info_dict
.copy())
293 downloaded
= ydl
.downloaded_info_dicts
[0]
294 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
296 # does not contain (!*=)
297 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
298 ydl
.process_ie_result(info_dict
.copy())
299 downloaded
= ydl
.downloaded_info_dicts
[0]
300 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
302 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
303 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
305 ydl
= YDL({'format': '[format_id!*=-]'}
)
306 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
308 def test_youtube_format_selection(self
):
309 # FIXME: Rewrite in accordance with the new format sorting options
313 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
314 # Apple HTTP Live Streaming
315 '96', '95', '94', '93', '92', '132', '151',
317 '85', '84', '102', '83', '101', '82', '100',
319 '137', '248', '136', '247', '135', '246',
320 '245', '244', '134', '243', '133', '242', '160',
322 '141', '172', '140', '171', '139',
325 def format_info(f_id
):
326 info
= YoutubeIE
._formats
[f_id
].copy()
328 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
329 # and 'vcodec', while in tests such information is incomplete since
330 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
331 # test_YoutubeDL.test_youtube_format_selection is broken without
333 if 'acodec' in info
and 'vcodec' not in info
:
334 info
['vcodec'] = 'none'
335 elif 'vcodec' in info
and 'acodec' not in info
:
336 info
['acodec'] = 'none'
338 info
['format_id'] = f_id
339 info
['url'] = 'url:' + f_id
341 formats_order
= [format_info(f_id
) for f_id
in order
]
343 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
344 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
346 yie
._sort
_formats
(info_dict
['formats'])
347 ydl
.process_ie_result(info_dict
)
348 downloaded
= ydl
.downloaded_info_dicts
[0]
349 self
.assertEqual(downloaded
['format_id'], '248+172')
350 self
.assertEqual(downloaded
['ext'], 'mp4')
352 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
353 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
355 yie
._sort
_formats
(info_dict
['formats'])
356 ydl
.process_ie_result(info_dict
)
357 downloaded
= ydl
.downloaded_info_dicts
[0]
358 self
.assertEqual(downloaded
['format_id'], '38')
360 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
361 ydl
= YDL({'format': 'bestvideo/best,bestaudio'}
)
363 yie
._sort
_formats
(info_dict
['formats'])
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'])
368 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
369 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'}
)
371 yie
._sort
_formats
(info_dict
['formats'])
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
, ['137+141', '248+141'])
376 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
377 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'}
)
379 yie
._sort
_formats
(info_dict
['formats'])
380 ydl
.process_ie_result(info_dict
)
381 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
382 self
.assertEqual(downloaded_ids
, ['136+141', '247+141'])
384 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
385 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}
)
387 yie
._sort
_formats
(info_dict
['formats'])
388 ydl
.process_ie_result(info_dict
)
389 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
390 self
.assertEqual(downloaded_ids
, ['248+141'])
392 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
393 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
394 ydl
= YDL({'format': 'best/bestvideo'}
)
396 yie
._sort
_formats
(info_dict
['formats'])
397 ydl
.process_ie_result(info_dict
)
398 downloaded
= ydl
.downloaded_info_dicts
[0]
399 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
401 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
402 ydl
= YDL({'format': 'best/bestvideo'}
)
404 yie
._sort
_formats
(info_dict
['formats'])
405 ydl
.process_ie_result(info_dict
)
406 downloaded
= ydl
.downloaded_info_dicts
[0]
407 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
409 def test_audio_only_extractor_format_selection(self
):
410 # For extractors with incomplete formats (all formats are audio-only or
411 # video-only) best and worst should fallback to corresponding best/worst
412 # video-only or audio-only formats (as per
413 # https://github.com/ytdl-org/youtube-dl/pull/5556)
415 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
416 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
418 info_dict
= _make_result(formats
)
420 ydl
= YDL({'format': 'best'}
)
421 ydl
.process_ie_result(info_dict
.copy())
422 downloaded
= ydl
.downloaded_info_dicts
[0]
423 self
.assertEqual(downloaded
['format_id'], 'high')
425 ydl
= YDL({'format': 'worst'}
)
426 ydl
.process_ie_result(info_dict
.copy())
427 downloaded
= ydl
.downloaded_info_dicts
[0]
428 self
.assertEqual(downloaded
['format_id'], 'low')
430 def test_format_not_available(self
):
432 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
433 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
435 info_dict
= _make_result(formats
)
437 # This must fail since complete video-audio format does not match filter
438 # and extractor does not provide incomplete only formats (i.e. only
439 # video-only or audio-only).
440 ydl
= YDL({'format': 'best[height>360]'}
)
441 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
443 def test_format_selection_issue_10083(self
):
444 # See https://github.com/ytdl-org/youtube-dl/issues/10083
446 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
447 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
448 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
450 info_dict
= _make_result(formats
)
452 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
453 ydl
.process_ie_result(info_dict
.copy())
454 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
456 def test_invalid_format_specs(self
):
457 def assert_syntax_error(format_spec
):
458 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec}
)
460 assert_syntax_error('bestvideo,,best')
461 assert_syntax_error('+bestaudio')
462 assert_syntax_error('bestvideo+')
463 assert_syntax_error('/')
464 assert_syntax_error('[720<height]')
466 def test_format_filtering(self
):
468 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
469 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
470 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
471 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
472 {'format_id': 'E', 'filesize': 3000}
,
474 {'format_id': 'G', 'filesize': 1000000}
,
477 f
['url'] = 'http://_/'
479 info_dict
= _make_result(formats
)
481 ydl
= YDL({'format': 'best[filesize<3000]'}
)
482 ydl
.process_ie_result(info_dict
)
483 downloaded
= ydl
.downloaded_info_dicts
[0]
484 self
.assertEqual(downloaded
['format_id'], 'D')
486 ydl
= YDL({'format': 'best[filesize<=3000]'}
)
487 ydl
.process_ie_result(info_dict
)
488 downloaded
= ydl
.downloaded_info_dicts
[0]
489 self
.assertEqual(downloaded
['format_id'], 'E')
491 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
492 ydl
.process_ie_result(info_dict
)
493 downloaded
= ydl
.downloaded_info_dicts
[0]
494 self
.assertEqual(downloaded
['format_id'], 'F')
496 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'}
)
497 ydl
.process_ie_result(info_dict
)
498 downloaded
= ydl
.downloaded_info_dicts
[0]
499 self
.assertEqual(downloaded
['format_id'], 'B')
501 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
502 ydl
.process_ie_result(info_dict
)
503 downloaded
= ydl
.downloaded_info_dicts
[0]
504 self
.assertEqual(downloaded
['format_id'], 'C')
506 ydl
= YDL({'format': '[filesize>?1]'}
)
507 ydl
.process_ie_result(info_dict
)
508 downloaded
= ydl
.downloaded_info_dicts
[0]
509 self
.assertEqual(downloaded
['format_id'], 'G')
511 ydl
= YDL({'format': '[filesize<1M]'}
)
512 ydl
.process_ie_result(info_dict
)
513 downloaded
= ydl
.downloaded_info_dicts
[0]
514 self
.assertEqual(downloaded
['format_id'], 'E')
516 ydl
= YDL({'format': '[filesize<1MiB]'}
)
517 ydl
.process_ie_result(info_dict
)
518 downloaded
= ydl
.downloaded_info_dicts
[0]
519 self
.assertEqual(downloaded
['format_id'], 'G')
521 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
522 ydl
.process_ie_result(info_dict
)
523 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
524 self
.assertEqual(downloaded_ids
, ['D', 'C', 'B'])
526 ydl
= YDL({'format': 'best[height<40]'}
)
528 ydl
.process_ie_result(info_dict
)
529 except ExtractorError
:
531 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
533 def test_default_format_spec(self
):
534 ydl
= YDL({'simulate': True}
)
535 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
538 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
540 ydl
= YDL({'simulate': True}
)
541 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
543 ydl
= YDL({'outtmpl': '-'}
)
544 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
547 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
548 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
551 class TestYoutubeDL(unittest
.TestCase
):
552 def test_subtitles(self
):
553 def s_formats(lang
, autocaption
=False):
556 'url': 'http://localhost/video.%s.%s' % (lang
, ext
),
557 '_auto': autocaption
,
558 } for ext
in ['vtt', 'srt', 'ass']]
559 subtitles
= dict((l
, s_formats(l
)) for l
in ['en', 'fr', 'es'])
560 auto_captions
= dict((l
, s_formats(l
, True)) for l
in ['it', 'pt', 'es'])
564 'url': 'http://localhost/video.mp4',
565 'subtitles': subtitles
,
566 'automatic_captions': auto_captions
,
568 'webpage_url': 'http://example.com/watch?v=shenanigans',
571 def get_info(params
={}):
572 params
.setdefault('simulate', True)
574 ydl
.report_warning
= lambda *args
, **kargs
: None
575 return ydl
.process_video_result(info_dict
, download
=False)
578 self
.assertFalse(result
.get('requested_subtitles'))
579 self
.assertEqual(result
['subtitles'], subtitles
)
580 self
.assertEqual(result
['automatic_captions'], auto_captions
)
582 result
= get_info({'writesubtitles': True}
)
583 subs
= result
['requested_subtitles']
584 self
.assertTrue(subs
)
585 self
.assertEqual(set(subs
.keys()), set(['en']))
586 self
.assertTrue(subs
['en'].get('data') is None)
587 self
.assertEqual(subs
['en']['ext'], 'ass')
589 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
590 subs
= result
['requested_subtitles']
591 self
.assertEqual(subs
['en']['ext'], 'srt')
593 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
594 subs
= result
['requested_subtitles']
595 self
.assertTrue(subs
)
596 self
.assertEqual(set(subs
.keys()), set(['es', 'fr']))
598 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
599 subs
= result
['requested_subtitles']
600 self
.assertTrue(subs
)
601 self
.assertEqual(set(subs
.keys()), set(['es', 'fr']))
603 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
604 subs
= result
['requested_subtitles']
605 self
.assertTrue(subs
)
606 self
.assertEqual(set(subs
.keys()), set(['fr']))
608 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
609 subs
= result
['requested_subtitles']
610 self
.assertTrue(subs
)
611 self
.assertEqual(set(subs
.keys()), set(['en']))
613 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
614 subs
= result
['requested_subtitles']
615 self
.assertTrue(subs
)
616 self
.assertEqual(set(subs
.keys()), set(['es', 'en']))
618 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
619 subs
= result
['requested_subtitles']
620 self
.assertTrue(subs
)
621 self
.assertEqual(set(subs
.keys()), set(['es', 'pt']))
622 self
.assertFalse(subs
['es']['_auto'])
623 self
.assertTrue(subs
['pt']['_auto'])
625 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
626 subs
= result
['requested_subtitles']
627 self
.assertTrue(subs
)
628 self
.assertEqual(set(subs
.keys()), set(['es', 'pt']))
629 self
.assertTrue(subs
['es']['_auto'])
630 self
.assertTrue(subs
['pt']['_auto'])
632 def test_add_extra_info(self
):
638 'playlist': 'funny videos',
640 YDL
.add_extra_info(test_dict
, extra_info
)
641 self
.assertEqual(test_dict
['extractor'], 'Foo')
642 self
.assertEqual(test_dict
['playlist'], 'funny videos')
652 'title3': 'foo/bar\\test',
653 'title4': 'foo "bar" test',
655 'timestamp': 1618488000,
658 'playlist_autonumber': 2,
659 '_last_playlist_index': 100,
661 'formats': [{'id': 'id 1'}
, {'id': 'id 2'}
, {'id': 'id 3'}
]
664 def test_prepare_outtmpl_and_filename(self
):
665 def test(tmpl
, expected
, *, info
=None, **params
):
666 params
['outtmpl'] = tmpl
667 ydl
= YoutubeDL(params
)
668 ydl
._num
_downloads
= 1
669 self
.assertEqual(ydl
.validate_outtmpl(tmpl
), None)
671 out
= ydl
.evaluate_outtmpl(tmpl
, info
or self
.outtmpl_info
)
672 fname
= ydl
.prepare_filename(info
or self
.outtmpl_info
)
674 if not isinstance(expected
, (list, tuple)):
675 expected
= (expected
, expected
)
676 for (name
, got
), expect
in zip((('outtmpl', out
), ('filename', fname
)), expected
):
678 self
.assertTrue(expect(got
), f
'Wrong {name} from {tmpl}')
680 self
.assertEqual(got
, expect
, f
'Wrong {name} from {tmpl}')
683 original_infodict
= dict(self
.outtmpl_info
)
684 test('foo.bar', 'foo.bar')
685 original_infodict
['epoch'] = self
.outtmpl_info
.get('epoch')
686 self
.assertTrue(isinstance(original_infodict
['epoch'], int))
687 test('%(epoch)d', int_or_none
)
688 self
.assertEqual(original_infodict
, self
.outtmpl_info
)
690 # Auto-generated fields
691 test('%(id)s.%(ext)s', '1234.mp4')
692 test('%(duration_string)s', ('27:46:40', '27-46-40'))
693 test('%(resolution)s', '1080p')
694 test('%(playlist_index)s', '001')
695 test('%(playlist_autonumber)s', '02')
696 test('%(autonumber)s', '00001')
697 test('%(autonumber+2)03d', '005', autonumber_start
=3)
698 test('%(autonumber)s', '001', autonumber_size
=3)
707 test('%abc%', '%abc%')
708 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
709 test('%%%(height)s', '%1080')
710 test('%(width)06d.%(ext)s', 'NA.mp4')
711 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
712 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
715 test('%(id)s', '_abcd', info
={'id': '_abcd'}
)
716 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'}
)
717 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}
]})
718 test('%(id)s', '-abcd', info
={'id': '-abcd'}
)
719 test('%(id)s', '.abcd', info
={'id': '.abcd'}
)
720 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'}
)
721 test('%(id)s', ('ab:cd', 'ab -cd'), info
={'id': 'ab:cd'}
)
722 test('%(id.0)s', '-', info
={'id': '--'}
)
725 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
726 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
730 def expect_same_infodict(out
):
731 got_dict
= json
.loads(out
)
732 for info_field
, expected
in self
.outtmpl_info
.items():
733 self
.assertEqual(got_dict
.get(info_field
), expected
, info_field
)
736 test('%()j', (expect_same_infodict
, str))
739 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
740 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
741 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
742 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
743 test('%(non_existent.0)s', 'NA')
746 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
747 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
748 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
749 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
750 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
751 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
752 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
753 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
754 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
755 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
756 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
759 test('%(id)d', '1234')
760 test('%(height)c', '1')
762 test('%(id)d %(id)r', "1234 '1234'")
763 test('%(id)r %(height)r', "'1234' 1080")
764 test('%(ext)s-%(ext|def)d', 'mp4-def')
765 test('%(width|0)04d', '0000')
766 test('a%(width|)d', 'a', outtmpl_na_placeholder
='none')
768 FORMATS
= self
.outtmpl_info
['formats']
769 sanitize
= lambda x
: x
.replace(':', ' -').replace('"', "'").replace('\n', ' ')
771 # Custom type casting
772 test('%(formats.:.id)l', 'id 1, id 2, id 3')
773 test('%(formats.:.id)#l', ('id 1\nid 2\nid 3', 'id 1 id 2 id 3'))
774 test('%(ext)l', 'mp4')
775 test('%(formats.:.id) 18l', ' id 1, id 2, id 3')
776 test('%(formats)j', (json
.dumps(FORMATS
), sanitize(json
.dumps(FORMATS
))))
777 test('%(formats)#j', (json
.dumps(FORMATS
, indent
=4), sanitize(json
.dumps(FORMATS
, indent
=4))))
778 test('%(title5).3B', 'á')
779 test('%(title5)U', 'áéí 𝐀')
780 test('%(title5)#U', 'a\u0301e\u0301i\u0301 𝐀')
781 test('%(title5)+U', 'áéí A')
782 test('%(title5)+#U', 'a\u0301e\u0301i\u0301 A')
783 test('%(height)D', '1k')
784 test('%(filesize)#D', '1Ki')
785 test('%(height)5.2D', ' 1.08k')
786 test('%(title4)#S', 'foo_bar_test')
787 test('%(title4).10S', ('foo \'bar\' ', 'foo \'bar\'' + ('#' if compat_os_name
== 'nt' else ' ')))
788 if compat_os_name
== 'nt':
789 test('%(title4)q', ('"foo \\"bar\\" test"', "'foo _'bar_' test'"))
790 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', "'id 1' 'id 2' 'id 3'"))
791 test('%(formats.0.id)#q', ('"id 1"', "'id 1'"))
793 test('%(title4)q', ('\'foo "bar" test\'', "'foo 'bar' test'"))
794 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'")
795 test('%(formats.0.id)#q', "'id 1'")
797 # Internal formatting
798 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
799 test('%(title|%)s %(title|%%)s', '% %%')
800 test('%(id+1-height+3)05d', '00158')
801 test('%(width+100)05d', 'NA')
802 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], '% 15s' % sanitize(str(FORMATS
[0]))))
803 test('%(formats.0)r', (repr(FORMATS
[0]), sanitize(repr(FORMATS
[0]))))
804 test('%(height.0)03d', '001')
805 test('%(-height.0)04d', '-001')
806 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
807 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
808 test('%(formats.3)s', 'NA')
809 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
810 test('%(formats.0.id.-1+id)f', '1235.000000')
811 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
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')
827 raise self
.assertTrue(False, 'LazyList should not be evaluated till here')
828 test('%(key.4)s', '4', info
={'key': LazyList(gen())}
)
831 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
832 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
833 # test('%(foo|)s', ('', '_')) # fixme
835 # Environment variable expansion for prepare_filename
836 compat_setenv('__yt_dlp_var', 'expanded')
837 envvar
= '%__yt_dlp_var%' if compat_os_name
== 'nt' else '$__yt_dlp_var'
838 test(envvar
, (envvar
, 'expanded'))
839 if compat_os_name
== 'nt':
840 test('%s%', ('%s%', '%s%'))
841 compat_setenv('s', 'expanded')
842 test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
843 compat_setenv('(test)s', 'expanded')
844 test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
846 # Path expansion and escaping
847 test('Hello %(title1)s', 'Hello $PATH')
848 test('Hello %(title2)s', 'Hello %PATH%')
849 test('%(title3)s', ('foo/bar\\test', 'foo_bar_test'))
850 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo_bar_test' % os
.path
.sep
))
852 def test_format_note(self
):
854 self
.assertEqual(ydl
._format
_note
({}), '')
855 assertRegexpMatches(self
, ydl
._format
_note
({
858 assertRegexpMatches(self
, ydl
._format
_note
({
862 def test_postprocessors(self
):
863 filename
= 'post-processor-testfile.mp4'
864 audiofile
= filename
+ '.mp3'
866 class SimplePP(PostProcessor
):
868 with open(audiofile
, 'wt') as f
:
870 return [info
['filepath']], info
872 def run_pp(params
, PP
):
873 with open(filename
, 'wt') as f
:
875 ydl
= YoutubeDL(params
)
876 ydl
.add_post_processor(PP())
877 ydl
.post_process(filename
, {'filepath': filename}
)
879 run_pp({'keepvideo': True}
, SimplePP
)
880 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
881 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
885 run_pp({'keepvideo': False}
, SimplePP
)
886 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
887 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
890 class ModifierPP(PostProcessor
):
892 with open(info
['filepath'], 'wt') as f
:
896 run_pp({'keepvideo': False}
, ModifierPP
)
897 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
900 def test_match_filter(self
):
901 class FilterYDL(YDL
):
902 def __init__(self
, *args
, **kwargs
):
903 super(FilterYDL
, self
).__init
__(*args
, **kwargs
)
904 self
.params
['simulate'] = True
906 def process_info(self
, info_dict
):
907 super(YDL
, self
).process_info(info_dict
)
909 def _match_entry(self
, info_dict
, incomplete
=False):
910 res
= super(FilterYDL
, self
)._match
_entry
(info_dict
, incomplete
)
912 self
.downloaded_info_dicts
.append(info_dict
.copy())
921 'filesize': 10 * 1024,
923 'uploader': "變態妍字幕版 太妍 тест",
924 'creator': "тест ' 123 ' тест--",
925 'webpage_url': 'http://example.com/watch?v=shenanigans',
933 'description': 'foo',
934 'filesize': 5 * 1024,
936 'uploader': "тест 123",
937 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
939 videos
= [first
, second
]
941 def get_videos(filter_
=None):
942 ydl
= FilterYDL({'match_filter': filter_}
)
944 ydl
.process_ie_result(v
, download
=True)
945 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
948 self
.assertEqual(res
, ['1', '2'])
954 return 'Video id is not 1'
956 self
.assertEqual(res
, ['1'])
958 f
= match_filter_func('duration < 30')
960 self
.assertEqual(res
, ['2'])
962 f
= match_filter_func('description = foo')
964 self
.assertEqual(res
, ['2'])
966 f
= match_filter_func('description =? foo')
968 self
.assertEqual(res
, ['1', '2'])
970 f
= match_filter_func('filesize > 5KiB')
972 self
.assertEqual(res
, ['1'])
974 f
= match_filter_func('playlist_id = 42')
976 self
.assertEqual(res
, ['1'])
978 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
980 self
.assertEqual(res
, ['1'])
982 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
984 self
.assertEqual(res
, ['2'])
986 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
988 self
.assertEqual(res
, ['1'])
990 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
992 self
.assertEqual(res
, ['1'])
994 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
996 self
.assertEqual(res
, [])
998 def test_playlist_items_selection(self
):
1000 'id': compat_str(i
),
1001 'title': compat_str(i
),
1003 } for i
in range(1, 5)]
1005 '_type': 'playlist',
1008 'extractor': 'test:playlist',
1009 'extractor_key': 'test:playlist',
1010 'webpage_url': 'http://example.com',
1013 def get_downloaded_info_dicts(params
):
1015 # make a deep copy because the dictionary and nested entries
1017 ydl
.process_ie_result(copy
.deepcopy(playlist
))
1018 return ydl
.downloaded_info_dicts
1020 def test_selection(params
, expected_ids
):
1022 (v
['playlist_autonumber'] - 1, (int(v
['id']), v
['playlist_index']))
1023 for v
in get_downloaded_info_dicts(params
)]
1024 self
.assertEqual(results
, list(enumerate(zip(expected_ids
, expected_ids
))))
1026 test_selection({}, [1, 2, 3, 4])
1027 test_selection({'playlistend': 10}
, [1, 2, 3, 4])
1028 test_selection({'playlistend': 2}
, [1, 2])
1029 test_selection({'playliststart': 10}
, [])
1030 test_selection({'playliststart': 2}
, [2, 3, 4])
1031 test_selection({'playlist_items': '2-4'}
, [2, 3, 4])
1032 test_selection({'playlist_items': '2,4'}
, [2, 4])
1033 test_selection({'playlist_items': '10'}
, [])
1034 test_selection({'playlist_items': '0'}
, [])
1036 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
1037 test_selection({'playlist_items': '2-4,3-4,3'}
, [2, 3, 4])
1038 test_selection({'playlist_items': '4,2'}
, [4, 2])
1040 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720
1041 # https://github.com/yt-dlp/yt-dlp/issues/302
1042 test_selection({'playlistreverse': True}
, [4, 3, 2, 1])
1043 test_selection({'playliststart': 2, 'playlistreverse': True}
, [4, 3, 2])
1044 test_selection({'playlist_items': '2,4', 'playlistreverse': True}
, [4, 2])
1045 test_selection({'playlist_items': '4,2'}
, [4, 2])
1047 def test_urlopen_no_file_protocol(self
):
1048 # see https://github.com/ytdl-org/youtube-dl/issues/8227
1050 self
.assertRaises(compat_urllib_error
.URLError
, ydl
.urlopen
, 'file:///etc/passwd')
1052 def test_do_not_override_ie_key_in_url_transparent(self
):
1055 class Foo1IE(InfoExtractor
):
1056 _VALID_URL
= r
'foo1:'
1058 def _real_extract(self
, url
):
1060 '_type': 'url_transparent',
1063 'title': 'foo1 title',
1067 class Foo2IE(InfoExtractor
):
1068 _VALID_URL
= r
'foo2:'
1070 def _real_extract(self
, url
):
1077 class Foo3IE(InfoExtractor
):
1078 _VALID_URL
= r
'foo3:'
1080 def _real_extract(self
, url
):
1081 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1083 ydl
.add_info_extractor(Foo1IE(ydl
))
1084 ydl
.add_info_extractor(Foo2IE(ydl
))
1085 ydl
.add_info_extractor(Foo3IE(ydl
))
1086 ydl
.extract_info('foo1:')
1087 downloaded
= ydl
.downloaded_info_dicts
[0]
1088 self
.assertEqual(downloaded
['url'], TEST_URL
)
1089 self
.assertEqual(downloaded
['title'], 'foo1 title')
1090 self
.assertEqual(downloaded
['id'], 'testid')
1091 self
.assertEqual(downloaded
['extractor'], 'testex')
1092 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1094 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1095 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1098 def __init__(self
, *args
, **kwargs
):
1099 super(_YDL
, self
).__init
__(*args
, **kwargs
)
1101 def trouble(self
, s
, tb
=None):
1106 'ignoreerrors': True,
1109 class VideoIE(InfoExtractor
):
1110 _VALID_URL
= r
'video:(?P<id>\d+)'
1112 def _real_extract(self
, url
):
1113 video_id
= self
._match
_id
(url
)
1115 'format_id': 'default',
1119 raise ExtractorError('foo')
1122 'format_id': 'extra',
1127 'title': 'Video %s' % video_id
,
1131 class PlaylistIE(InfoExtractor
):
1132 _VALID_URL
= r
'playlist:'
1136 video_id
= compat_str(n
)
1138 '_type': 'url_transparent',
1139 'ie_key': VideoIE
.ie_key(),
1141 'url': 'video:%s' % video_id
,
1142 'title': 'Video Transparent %s' % video_id
,
1145 def _real_extract(self
, url
):
1146 return self
.playlist_result(self
._entries
())
1148 ydl
.add_info_extractor(VideoIE(ydl
))
1149 ydl
.add_info_extractor(PlaylistIE(ydl
))
1150 info
= ydl
.extract_info('playlist:')
1151 entries
= info
['entries']
1152 self
.assertEqual(len(entries
), 3)
1153 self
.assertTrue(entries
[0] is None)
1154 self
.assertTrue(entries
[1] is None)
1155 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1156 downloaded
= ydl
.downloaded_info_dicts
[0]
1157 self
.assertEqual(entries
[2], downloaded
)
1158 self
.assertEqual(downloaded
['url'], TEST_URL
)
1159 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1160 self
.assertEqual(downloaded
['id'], '2')
1161 self
.assertEqual(downloaded
['extractor'], 'Video')
1162 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1165 if __name__
== '__main__':