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 self
.downloaded_info_dicts
.append(info_dict
.copy())
35 def to_screen(self
, msg
):
38 def dl(self
, *args
, **kwargs
):
39 assert False, 'Downloader must not be invoked for test_YoutubeDL'
42 def _make_result(formats
, **kwargs
):
46 'title': 'testttitle',
47 'extractor': 'testex',
48 'extractor_key': 'TestEx',
49 'webpage_url': 'http://example.com/watch?v=shenanigans',
55 class TestFormatSelection(unittest
.TestCase
):
56 def test_prefer_free_formats(self
):
57 # Same resolution => download webm
59 ydl
.params
['prefer_free_formats'] = True
61 {'ext': 'webm', 'height': 460, 'url': TEST_URL}
,
62 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}
,
64 info_dict
= _make_result(formats
)
66 yie
._sort
_formats
(info_dict
['formats'])
67 ydl
.process_ie_result(info_dict
)
68 downloaded
= ydl
.downloaded_info_dicts
[0]
69 self
.assertEqual(downloaded
['ext'], 'webm')
71 # Different resolution => download best quality (mp4)
73 ydl
.params
['prefer_free_formats'] = True
75 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
76 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}
,
78 info_dict
['formats'] = formats
80 yie
._sort
_formats
(info_dict
['formats'])
81 ydl
.process_ie_result(info_dict
)
82 downloaded
= ydl
.downloaded_info_dicts
[0]
83 self
.assertEqual(downloaded
['ext'], 'mp4')
85 # No prefer_free_formats => prefer mp4 and webm
87 ydl
.params
['prefer_free_formats'] = False
89 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
90 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}
,
91 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
93 info_dict
['formats'] = formats
95 yie
._sort
_formats
(info_dict
['formats'])
96 ydl
.process_ie_result(info_dict
)
97 downloaded
= ydl
.downloaded_info_dicts
[0]
98 self
.assertEqual(downloaded
['ext'], 'mp4')
101 ydl
.params
['prefer_free_formats'] = False
103 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
104 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
106 info_dict
['formats'] = formats
108 yie
._sort
_formats
(info_dict
['formats'])
109 ydl
.process_ie_result(info_dict
)
110 downloaded
= ydl
.downloaded_info_dicts
[0]
111 self
.assertEqual(downloaded
['ext'], 'webm')
113 def test_format_selection(self
):
115 {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
116 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL}
,
117 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL}
,
118 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL}
,
119 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL}
,
121 info_dict
= _make_result(formats
)
123 def test(inp
, *expected
, multi
=False):
126 'allow_multiple_video_streams': multi
,
127 'allow_multiple_audio_streams': multi
,
129 ydl
.process_ie_result(info_dict
.copy())
130 downloaded
= map(lambda x
: x
['format_id'], ydl
.downloaded_info_dicts
)
131 self
.assertEqual(list(downloaded
), list(expected
))
134 test('20/71/worst', '35')
136 test('webm/mp4', '47')
137 test('3gp/40/mp4', '35')
138 test('example-with-dashes', 'example-with-dashes')
139 test('all', '2', '47', '45', 'example-with-dashes', '35')
140 test('mergeall', '2+47+45+example-with-dashes+35', multi
=True)
142 def test_format_selection_audio(self
):
144 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
145 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
146 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}
,
147 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL}
,
149 info_dict
= _make_result(formats
)
151 ydl
= YDL({'format': 'bestaudio'}
)
152 ydl
.process_ie_result(info_dict
.copy())
153 downloaded
= ydl
.downloaded_info_dicts
[0]
154 self
.assertEqual(downloaded
['format_id'], 'audio-high')
156 ydl
= YDL({'format': 'worstaudio'}
)
157 ydl
.process_ie_result(info_dict
.copy())
158 downloaded
= ydl
.downloaded_info_dicts
[0]
159 self
.assertEqual(downloaded
['format_id'], 'audio-low')
162 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
163 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL}
,
165 info_dict
= _make_result(formats
)
167 ydl
= YDL({'format': 'bestaudio/worstaudio/best'}
)
168 ydl
.process_ie_result(info_dict
.copy())
169 downloaded
= ydl
.downloaded_info_dicts
[0]
170 self
.assertEqual(downloaded
['format_id'], 'vid-high')
172 def test_format_selection_audio_exts(self
):
174 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
175 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
176 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
177 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
178 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
181 info_dict
= _make_result(formats
)
182 ydl
= YDL({'format': 'best'}
)
184 ie
._sort
_formats
(info_dict
['formats'])
185 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
186 downloaded
= ydl
.downloaded_info_dicts
[0]
187 self
.assertEqual(downloaded
['format_id'], 'aac-64')
189 ydl
= YDL({'format': 'mp3'}
)
191 ie
._sort
_formats
(info_dict
['formats'])
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}
)
198 ie
._sort
_formats
(info_dict
['formats'])
199 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
200 downloaded
= ydl
.downloaded_info_dicts
[0]
201 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
203 def test_format_selection_video(self
):
205 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
206 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
207 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
209 info_dict
= _make_result(formats
)
211 ydl
= YDL({'format': 'bestvideo'}
)
212 ydl
.process_ie_result(info_dict
.copy())
213 downloaded
= ydl
.downloaded_info_dicts
[0]
214 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
216 ydl
= YDL({'format': 'worstvideo'}
)
217 ydl
.process_ie_result(info_dict
.copy())
218 downloaded
= ydl
.downloaded_info_dicts
[0]
219 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
221 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
222 ydl
.process_ie_result(info_dict
.copy())
223 downloaded
= ydl
.downloaded_info_dicts
[0]
224 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
227 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
229 info_dict
= _make_result(formats
)
231 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
232 ydl
.process_ie_result(info_dict
.copy())
233 downloaded
= ydl
.downloaded_info_dicts
[0]
234 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
236 def test_format_selection_string_ops(self
):
238 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
239 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
241 info_dict
= _make_result(formats
)
244 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
245 ydl
.process_ie_result(info_dict
.copy())
246 downloaded
= ydl
.downloaded_info_dicts
[0]
247 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
249 # does not equal (!=)
250 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
251 ydl
.process_ie_result(info_dict
.copy())
252 downloaded
= ydl
.downloaded_info_dicts
[0]
253 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
255 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
256 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
259 ydl
= YDL({'format': '[format_id^=abc]'}
)
260 ydl
.process_ie_result(info_dict
.copy())
261 downloaded
= ydl
.downloaded_info_dicts
[0]
262 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
264 # does not start with (!^=)
265 ydl
= YDL({'format': '[format_id!^=abc]'}
)
266 ydl
.process_ie_result(info_dict
.copy())
267 downloaded
= ydl
.downloaded_info_dicts
[0]
268 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
270 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
271 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
274 ydl
= YDL({'format': '[format_id$=cba]'}
)
275 ydl
.process_ie_result(info_dict
.copy())
276 downloaded
= ydl
.downloaded_info_dicts
[0]
277 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
279 # does not end with (!$=)
280 ydl
= YDL({'format': '[format_id!$=cba]'}
)
281 ydl
.process_ie_result(info_dict
.copy())
282 downloaded
= ydl
.downloaded_info_dicts
[0]
283 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
285 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
286 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
289 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
290 ydl
.process_ie_result(info_dict
.copy())
291 downloaded
= ydl
.downloaded_info_dicts
[0]
292 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
294 # does not contain (!*=)
295 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
296 ydl
.process_ie_result(info_dict
.copy())
297 downloaded
= ydl
.downloaded_info_dicts
[0]
298 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
300 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
301 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
303 ydl
= YDL({'format': '[format_id!*=-]'}
)
304 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
306 def test_youtube_format_selection(self
):
307 # FIXME: Rewrite in accordance with the new format sorting options
311 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
312 # Apple HTTP Live Streaming
313 '96', '95', '94', '93', '92', '132', '151',
315 '85', '84', '102', '83', '101', '82', '100',
317 '137', '248', '136', '247', '135', '246',
318 '245', '244', '134', '243', '133', '242', '160',
320 '141', '172', '140', '171', '139',
323 def format_info(f_id
):
324 info
= YoutubeIE
._formats
[f_id
].copy()
326 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
327 # and 'vcodec', while in tests such information is incomplete since
328 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
329 # test_YoutubeDL.test_youtube_format_selection is broken without
331 if 'acodec' in info
and 'vcodec' not in info
:
332 info
['vcodec'] = 'none'
333 elif 'vcodec' in info
and 'acodec' not in info
:
334 info
['acodec'] = 'none'
336 info
['format_id'] = f_id
337 info
['url'] = 'url:' + f_id
339 formats_order
= [format_info(f_id
) for f_id
in order
]
341 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
342 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
344 yie
._sort
_formats
(info_dict
['formats'])
345 ydl
.process_ie_result(info_dict
)
346 downloaded
= ydl
.downloaded_info_dicts
[0]
347 self
.assertEqual(downloaded
['format_id'], '248+172')
348 self
.assertEqual(downloaded
['ext'], 'mp4')
350 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
351 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
353 yie
._sort
_formats
(info_dict
['formats'])
354 ydl
.process_ie_result(info_dict
)
355 downloaded
= ydl
.downloaded_info_dicts
[0]
356 self
.assertEqual(downloaded
['format_id'], '38')
358 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
359 ydl
= YDL({'format': 'bestvideo/best,bestaudio'}
)
361 yie
._sort
_formats
(info_dict
['formats'])
362 ydl
.process_ie_result(info_dict
)
363 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
364 self
.assertEqual(downloaded_ids
, ['137', '141'])
366 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
367 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'}
)
369 yie
._sort
_formats
(info_dict
['formats'])
370 ydl
.process_ie_result(info_dict
)
371 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
372 self
.assertEqual(downloaded_ids
, ['137+141', '248+141'])
374 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
375 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'}
)
377 yie
._sort
_formats
(info_dict
['formats'])
378 ydl
.process_ie_result(info_dict
)
379 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
380 self
.assertEqual(downloaded_ids
, ['136+141', '247+141'])
382 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
383 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}
)
385 yie
._sort
_formats
(info_dict
['formats'])
386 ydl
.process_ie_result(info_dict
)
387 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
388 self
.assertEqual(downloaded_ids
, ['248+141'])
390 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
391 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
392 ydl
= YDL({'format': 'best/bestvideo'}
)
394 yie
._sort
_formats
(info_dict
['formats'])
395 ydl
.process_ie_result(info_dict
)
396 downloaded
= ydl
.downloaded_info_dicts
[0]
397 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
399 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
400 ydl
= YDL({'format': 'best/bestvideo'}
)
402 yie
._sort
_formats
(info_dict
['formats'])
403 ydl
.process_ie_result(info_dict
)
404 downloaded
= ydl
.downloaded_info_dicts
[0]
405 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
407 def test_audio_only_extractor_format_selection(self
):
408 # For extractors with incomplete formats (all formats are audio-only or
409 # video-only) best and worst should fallback to corresponding best/worst
410 # video-only or audio-only formats (as per
411 # https://github.com/ytdl-org/youtube-dl/pull/5556)
413 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
414 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
416 info_dict
= _make_result(formats
)
418 ydl
= YDL({'format': 'best'}
)
419 ydl
.process_ie_result(info_dict
.copy())
420 downloaded
= ydl
.downloaded_info_dicts
[0]
421 self
.assertEqual(downloaded
['format_id'], 'high')
423 ydl
= YDL({'format': 'worst'}
)
424 ydl
.process_ie_result(info_dict
.copy())
425 downloaded
= ydl
.downloaded_info_dicts
[0]
426 self
.assertEqual(downloaded
['format_id'], 'low')
428 def test_format_not_available(self
):
430 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
431 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
433 info_dict
= _make_result(formats
)
435 # This must fail since complete video-audio format does not match filter
436 # and extractor does not provide incomplete only formats (i.e. only
437 # video-only or audio-only).
438 ydl
= YDL({'format': 'best[height>360]'}
)
439 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
441 def test_format_selection_issue_10083(self
):
442 # See https://github.com/ytdl-org/youtube-dl/issues/10083
444 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
445 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
446 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
448 info_dict
= _make_result(formats
)
450 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
451 ydl
.process_ie_result(info_dict
.copy())
452 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
454 def test_invalid_format_specs(self
):
455 def assert_syntax_error(format_spec
):
456 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec}
)
458 assert_syntax_error('bestvideo,,best')
459 assert_syntax_error('+bestaudio')
460 assert_syntax_error('bestvideo+')
461 assert_syntax_error('/')
462 assert_syntax_error('[720<height]')
464 def test_format_filtering(self
):
466 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
467 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
468 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
469 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
470 {'format_id': 'E', 'filesize': 3000}
,
472 {'format_id': 'G', 'filesize': 1000000}
,
475 f
['url'] = 'http://_/'
477 info_dict
= _make_result(formats
)
479 ydl
= YDL({'format': 'best[filesize<3000]'}
)
480 ydl
.process_ie_result(info_dict
)
481 downloaded
= ydl
.downloaded_info_dicts
[0]
482 self
.assertEqual(downloaded
['format_id'], 'D')
484 ydl
= YDL({'format': 'best[filesize<=3000]'}
)
485 ydl
.process_ie_result(info_dict
)
486 downloaded
= ydl
.downloaded_info_dicts
[0]
487 self
.assertEqual(downloaded
['format_id'], 'E')
489 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
490 ydl
.process_ie_result(info_dict
)
491 downloaded
= ydl
.downloaded_info_dicts
[0]
492 self
.assertEqual(downloaded
['format_id'], 'F')
494 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'}
)
495 ydl
.process_ie_result(info_dict
)
496 downloaded
= ydl
.downloaded_info_dicts
[0]
497 self
.assertEqual(downloaded
['format_id'], 'B')
499 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
500 ydl
.process_ie_result(info_dict
)
501 downloaded
= ydl
.downloaded_info_dicts
[0]
502 self
.assertEqual(downloaded
['format_id'], 'C')
504 ydl
= YDL({'format': '[filesize>?1]'}
)
505 ydl
.process_ie_result(info_dict
)
506 downloaded
= ydl
.downloaded_info_dicts
[0]
507 self
.assertEqual(downloaded
['format_id'], 'G')
509 ydl
= YDL({'format': '[filesize<1M]'}
)
510 ydl
.process_ie_result(info_dict
)
511 downloaded
= ydl
.downloaded_info_dicts
[0]
512 self
.assertEqual(downloaded
['format_id'], 'E')
514 ydl
= YDL({'format': '[filesize<1MiB]'}
)
515 ydl
.process_ie_result(info_dict
)
516 downloaded
= ydl
.downloaded_info_dicts
[0]
517 self
.assertEqual(downloaded
['format_id'], 'G')
519 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
520 ydl
.process_ie_result(info_dict
)
521 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
522 self
.assertEqual(downloaded_ids
, ['D', 'C', 'B'])
524 ydl
= YDL({'format': 'best[height<40]'}
)
526 ydl
.process_ie_result(info_dict
)
527 except ExtractorError
:
529 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
531 def test_default_format_spec(self
):
532 ydl
= YDL({'simulate': True}
)
533 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
536 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
538 ydl
= YDL({'simulate': True}
)
539 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
541 ydl
= YDL({'outtmpl': '-'}
)
542 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
545 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
546 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
549 class TestYoutubeDL(unittest
.TestCase
):
550 def test_subtitles(self
):
551 def s_formats(lang
, autocaption
=False):
554 'url': 'http://localhost/video.%s.%s' % (lang
, ext
),
555 '_auto': autocaption
,
556 } for ext
in ['vtt', 'srt', 'ass']]
557 subtitles
= dict((l
, s_formats(l
)) for l
in ['en', 'fr', 'es'])
558 auto_captions
= dict((l
, s_formats(l
, True)) for l
in ['it', 'pt', 'es'])
562 'url': 'http://localhost/video.mp4',
563 'subtitles': subtitles
,
564 'automatic_captions': auto_captions
,
566 'webpage_url': 'http://example.com/watch?v=shenanigans',
569 def get_info(params
={}):
570 params
.setdefault('simulate', True)
572 ydl
.report_warning
= lambda *args
, **kargs
: None
573 return ydl
.process_video_result(info_dict
, download
=False)
576 self
.assertFalse(result
.get('requested_subtitles'))
577 self
.assertEqual(result
['subtitles'], subtitles
)
578 self
.assertEqual(result
['automatic_captions'], auto_captions
)
580 result
= get_info({'writesubtitles': True}
)
581 subs
= result
['requested_subtitles']
582 self
.assertTrue(subs
)
583 self
.assertEqual(set(subs
.keys()), set(['en']))
584 self
.assertTrue(subs
['en'].get('data') is None)
585 self
.assertEqual(subs
['en']['ext'], 'ass')
587 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
588 subs
= result
['requested_subtitles']
589 self
.assertEqual(subs
['en']['ext'], 'srt')
591 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
592 subs
= result
['requested_subtitles']
593 self
.assertTrue(subs
)
594 self
.assertEqual(set(subs
.keys()), set(['es', 'fr']))
596 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
597 subs
= result
['requested_subtitles']
598 self
.assertTrue(subs
)
599 self
.assertEqual(set(subs
.keys()), set(['es', 'fr']))
601 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
602 subs
= result
['requested_subtitles']
603 self
.assertTrue(subs
)
604 self
.assertEqual(set(subs
.keys()), set(['fr']))
606 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
607 subs
= result
['requested_subtitles']
608 self
.assertTrue(subs
)
609 self
.assertEqual(set(subs
.keys()), set(['en']))
611 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
612 subs
= result
['requested_subtitles']
613 self
.assertTrue(subs
)
614 self
.assertEqual(set(subs
.keys()), set(['es', 'en']))
616 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
617 subs
= result
['requested_subtitles']
618 self
.assertTrue(subs
)
619 self
.assertEqual(set(subs
.keys()), set(['es', 'pt']))
620 self
.assertFalse(subs
['es']['_auto'])
621 self
.assertTrue(subs
['pt']['_auto'])
623 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
624 subs
= result
['requested_subtitles']
625 self
.assertTrue(subs
)
626 self
.assertEqual(set(subs
.keys()), set(['es', 'pt']))
627 self
.assertTrue(subs
['es']['_auto'])
628 self
.assertTrue(subs
['pt']['_auto'])
630 def test_add_extra_info(self
):
636 'playlist': 'funny videos',
638 YDL
.add_extra_info(test_dict
, extra_info
)
639 self
.assertEqual(test_dict
['extractor'], 'Foo')
640 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('%(filesize)#D', '1Ki')
783 test('%(height)5.2D', ' 1.08k')
784 test('%(title4)#S', 'foo_bar_test')
785 test('%(title4).10S', ('foo \'bar\' ', 'foo \'bar\'' + ('#' if compat_os_name
== 'nt' else ' ')))
786 if compat_os_name
== 'nt':
787 test('%(title4)q', ('"foo \\"bar\\" test"', "'foo _'bar_' test'"))
788 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', "'id 1' 'id 2' 'id 3'"))
789 test('%(formats.0.id)#q', ('"id 1"', "'id 1'"))
791 test('%(title4)q', ('\'foo "bar" test\'', "'foo 'bar' test'"))
792 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'")
793 test('%(formats.0.id)#q', "'id 1'")
795 # Internal formatting
796 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
797 test('%(title|%)s %(title|%%)s', '% %%')
798 test('%(id+1-height+3)05d', '00158')
799 test('%(width+100)05d', 'NA')
800 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], '% 15s' % sanitize(str(FORMATS
[0]))))
801 test('%(formats.0)r', (repr(FORMATS
[0]), sanitize(repr(FORMATS
[0]))))
802 test('%(height.0)03d', '001')
803 test('%(-height.0)04d', '-001')
804 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
805 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
806 test('%(formats.3)s', 'NA')
807 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
808 test('%(formats.0.id.-1+id)f', '1235.000000')
809 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
812 test('%(title,id)s', '1234')
813 test('%(width-100,height+20|def)d', '1100')
814 test('%(width-100,height+width|def)s', 'def')
815 test('%(timestamp-x>%H\\,%M\\,%S,timestamp>%H\\,%M\\,%S)s', '12,00,00')
818 test('%(id&foo)s.bar', 'foo.bar')
819 test('%(title&foo)s.bar', 'NA.bar')
820 test('%(title&foo|baz)s.bar', 'baz.bar')
821 test('%(x,id&foo|baz)s.bar', 'foo.bar')
822 test('%(x,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
):
907 'filesize': 10 * 1024,
909 'uploader': "變態妍字幕版 太妍 тест",
910 'creator': "тест ' 123 ' тест--",
911 'webpage_url': 'http://example.com/watch?v=shenanigans',
919 'description': 'foo',
920 'filesize': 5 * 1024,
922 'uploader': "тест 123",
923 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
925 videos
= [first
, second
]
927 def get_videos(filter_
=None):
928 ydl
= YDL({'match_filter': filter_, 'simulate': True}
)
930 ydl
.process_ie_result(v
, download
=True)
931 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
934 self
.assertEqual(res
, ['1', '2'])
936 def f(v
, incomplete
):
940 return 'Video id is not 1'
942 self
.assertEqual(res
, ['1'])
944 f
= match_filter_func('duration < 30')
946 self
.assertEqual(res
, ['2'])
948 f
= match_filter_func('description = foo')
950 self
.assertEqual(res
, ['2'])
952 f
= match_filter_func('description =? foo')
954 self
.assertEqual(res
, ['1', '2'])
956 f
= match_filter_func('filesize > 5KiB')
958 self
.assertEqual(res
, ['1'])
960 f
= match_filter_func('playlist_id = 42')
962 self
.assertEqual(res
, ['1'])
964 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
966 self
.assertEqual(res
, ['1'])
968 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
970 self
.assertEqual(res
, ['2'])
972 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
974 self
.assertEqual(res
, ['1'])
976 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
978 self
.assertEqual(res
, ['1'])
980 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
982 self
.assertEqual(res
, [])
984 def test_playlist_items_selection(self
):
987 'title': compat_str(i
),
989 } for i
in range(1, 5)]
994 'extractor': 'test:playlist',
995 'extractor_key': 'test:playlist',
996 'webpage_url': 'http://example.com',
999 def get_downloaded_info_dicts(params
):
1001 # make a deep copy because the dictionary and nested entries
1003 ydl
.process_ie_result(copy
.deepcopy(playlist
))
1004 return ydl
.downloaded_info_dicts
1006 def test_selection(params
, expected_ids
):
1008 (v
['playlist_autonumber'] - 1, (int(v
['id']), v
['playlist_index']))
1009 for v
in get_downloaded_info_dicts(params
)]
1010 self
.assertEqual(results
, list(enumerate(zip(expected_ids
, expected_ids
))))
1012 test_selection({}, [1, 2, 3, 4])
1013 test_selection({'playlistend': 10}
, [1, 2, 3, 4])
1014 test_selection({'playlistend': 2}
, [1, 2])
1015 test_selection({'playliststart': 10}
, [])
1016 test_selection({'playliststart': 2}
, [2, 3, 4])
1017 test_selection({'playlist_items': '2-4'}
, [2, 3, 4])
1018 test_selection({'playlist_items': '2,4'}
, [2, 4])
1019 test_selection({'playlist_items': '10'}
, [])
1020 test_selection({'playlist_items': '0'}
, [])
1022 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
1023 test_selection({'playlist_items': '2-4,3-4,3'}
, [2, 3, 4])
1024 test_selection({'playlist_items': '4,2'}
, [4, 2])
1026 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720
1027 # https://github.com/yt-dlp/yt-dlp/issues/302
1028 test_selection({'playlistreverse': True}
, [4, 3, 2, 1])
1029 test_selection({'playliststart': 2, 'playlistreverse': True}
, [4, 3, 2])
1030 test_selection({'playlist_items': '2,4', 'playlistreverse': True}
, [4, 2])
1031 test_selection({'playlist_items': '4,2'}
, [4, 2])
1033 def test_urlopen_no_file_protocol(self
):
1034 # see https://github.com/ytdl-org/youtube-dl/issues/8227
1036 self
.assertRaises(compat_urllib_error
.URLError
, ydl
.urlopen
, 'file:///etc/passwd')
1038 def test_do_not_override_ie_key_in_url_transparent(self
):
1041 class Foo1IE(InfoExtractor
):
1042 _VALID_URL
= r
'foo1:'
1044 def _real_extract(self
, url
):
1046 '_type': 'url_transparent',
1049 'title': 'foo1 title',
1053 class Foo2IE(InfoExtractor
):
1054 _VALID_URL
= r
'foo2:'
1056 def _real_extract(self
, url
):
1063 class Foo3IE(InfoExtractor
):
1064 _VALID_URL
= r
'foo3:'
1066 def _real_extract(self
, url
):
1067 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1069 ydl
.add_info_extractor(Foo1IE(ydl
))
1070 ydl
.add_info_extractor(Foo2IE(ydl
))
1071 ydl
.add_info_extractor(Foo3IE(ydl
))
1072 ydl
.extract_info('foo1:')
1073 downloaded
= ydl
.downloaded_info_dicts
[0]
1074 self
.assertEqual(downloaded
['url'], TEST_URL
)
1075 self
.assertEqual(downloaded
['title'], 'foo1 title')
1076 self
.assertEqual(downloaded
['id'], 'testid')
1077 self
.assertEqual(downloaded
['extractor'], 'testex')
1078 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1080 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1081 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1084 def __init__(self
, *args
, **kwargs
):
1085 super(_YDL
, self
).__init
__(*args
, **kwargs
)
1087 def trouble(self
, s
, tb
=None):
1092 'ignoreerrors': True,
1095 class VideoIE(InfoExtractor
):
1096 _VALID_URL
= r
'video:(?P<id>\d+)'
1098 def _real_extract(self
, url
):
1099 video_id
= self
._match
_id
(url
)
1101 'format_id': 'default',
1105 raise ExtractorError('foo')
1108 'format_id': 'extra',
1113 'title': 'Video %s' % video_id
,
1117 class PlaylistIE(InfoExtractor
):
1118 _VALID_URL
= r
'playlist:'
1122 video_id
= compat_str(n
)
1124 '_type': 'url_transparent',
1125 'ie_key': VideoIE
.ie_key(),
1127 'url': 'video:%s' % video_id
,
1128 'title': 'Video Transparent %s' % video_id
,
1131 def _real_extract(self
, url
):
1132 return self
.playlist_result(self
._entries
())
1134 ydl
.add_info_extractor(VideoIE(ydl
))
1135 ydl
.add_info_extractor(PlaylistIE(ydl
))
1136 info
= ydl
.extract_info('playlist:')
1137 entries
= info
['entries']
1138 self
.assertEqual(len(entries
), 3)
1139 self
.assertTrue(entries
[0] is None)
1140 self
.assertTrue(entries
[1] is None)
1141 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1142 downloaded
= ydl
.downloaded_info_dicts
[0]
1143 entries
[2].pop('requested_downloads', None)
1144 self
.assertEqual(entries
[2], downloaded
)
1145 self
.assertEqual(downloaded
['url'], TEST_URL
)
1146 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1147 self
.assertEqual(downloaded
['id'], '2')
1148 self
.assertEqual(downloaded
['extractor'], 'Video')
1149 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1152 if __name__
== '__main__':