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')
825 raise self
.assertTrue(False, 'LazyList should not be evaluated till here')
826 test('%(key.4)s', '4', info
={'key': LazyList(gen())}
)
829 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
830 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
831 # test('%(foo|)s', ('', '_')) # fixme
833 # Environment variable expansion for prepare_filename
834 compat_setenv('__yt_dlp_var', 'expanded')
835 envvar
= '%__yt_dlp_var%' if compat_os_name
== 'nt' else '$__yt_dlp_var'
836 test(envvar
, (envvar
, 'expanded'))
837 if compat_os_name
== 'nt':
838 test('%s%', ('%s%', '%s%'))
839 compat_setenv('s', 'expanded')
840 test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
841 compat_setenv('(test)s', 'expanded')
842 test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
844 # Path expansion and escaping
845 test('Hello %(title1)s', 'Hello $PATH')
846 test('Hello %(title2)s', 'Hello %PATH%')
847 test('%(title3)s', ('foo/bar\\test', 'foo_bar_test'))
848 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo_bar_test' % os
.path
.sep
))
850 def test_format_note(self
):
852 self
.assertEqual(ydl
._format
_note
({}), '')
853 assertRegexpMatches(self
, ydl
._format
_note
({
856 assertRegexpMatches(self
, ydl
._format
_note
({
860 def test_postprocessors(self
):
861 filename
= 'post-processor-testfile.mp4'
862 audiofile
= filename
+ '.mp3'
864 class SimplePP(PostProcessor
):
866 with open(audiofile
, 'wt') as f
:
868 return [info
['filepath']], info
870 def run_pp(params
, PP
):
871 with open(filename
, 'wt') as f
:
873 ydl
= YoutubeDL(params
)
874 ydl
.add_post_processor(PP())
875 ydl
.post_process(filename
, {'filepath': filename}
)
877 run_pp({'keepvideo': True}
, SimplePP
)
878 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
879 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
883 run_pp({'keepvideo': False}
, SimplePP
)
884 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
885 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
888 class ModifierPP(PostProcessor
):
890 with open(info
['filepath'], 'wt') as f
:
894 run_pp({'keepvideo': False}
, ModifierPP
)
895 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
898 def test_match_filter(self
):
905 'filesize': 10 * 1024,
907 'uploader': "變態妍字幕版 太妍 тест",
908 'creator': "тест ' 123 ' тест--",
909 'webpage_url': 'http://example.com/watch?v=shenanigans',
917 'description': 'foo',
918 'filesize': 5 * 1024,
920 'uploader': "тест 123",
921 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
923 videos
= [first
, second
]
925 def get_videos(filter_
=None):
926 ydl
= YDL({'match_filter': filter_, 'simulate': True}
)
928 ydl
.process_ie_result(v
, download
=True)
929 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
932 self
.assertEqual(res
, ['1', '2'])
938 return 'Video id is not 1'
940 self
.assertEqual(res
, ['1'])
942 f
= match_filter_func('duration < 30')
944 self
.assertEqual(res
, ['2'])
946 f
= match_filter_func('description = foo')
948 self
.assertEqual(res
, ['2'])
950 f
= match_filter_func('description =? foo')
952 self
.assertEqual(res
, ['1', '2'])
954 f
= match_filter_func('filesize > 5KiB')
956 self
.assertEqual(res
, ['1'])
958 f
= match_filter_func('playlist_id = 42')
960 self
.assertEqual(res
, ['1'])
962 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
964 self
.assertEqual(res
, ['1'])
966 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
968 self
.assertEqual(res
, ['2'])
970 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
972 self
.assertEqual(res
, ['1'])
974 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
976 self
.assertEqual(res
, ['1'])
978 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
980 self
.assertEqual(res
, [])
982 def test_playlist_items_selection(self
):
985 'title': compat_str(i
),
987 } for i
in range(1, 5)]
992 'extractor': 'test:playlist',
993 'extractor_key': 'test:playlist',
994 'webpage_url': 'http://example.com',
997 def get_downloaded_info_dicts(params
):
999 # make a deep copy because the dictionary and nested entries
1001 ydl
.process_ie_result(copy
.deepcopy(playlist
))
1002 return ydl
.downloaded_info_dicts
1004 def test_selection(params
, expected_ids
):
1006 (v
['playlist_autonumber'] - 1, (int(v
['id']), v
['playlist_index']))
1007 for v
in get_downloaded_info_dicts(params
)]
1008 self
.assertEqual(results
, list(enumerate(zip(expected_ids
, expected_ids
))))
1010 test_selection({}, [1, 2, 3, 4])
1011 test_selection({'playlistend': 10}
, [1, 2, 3, 4])
1012 test_selection({'playlistend': 2}
, [1, 2])
1013 test_selection({'playliststart': 10}
, [])
1014 test_selection({'playliststart': 2}
, [2, 3, 4])
1015 test_selection({'playlist_items': '2-4'}
, [2, 3, 4])
1016 test_selection({'playlist_items': '2,4'}
, [2, 4])
1017 test_selection({'playlist_items': '10'}
, [])
1018 test_selection({'playlist_items': '0'}
, [])
1020 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
1021 test_selection({'playlist_items': '2-4,3-4,3'}
, [2, 3, 4])
1022 test_selection({'playlist_items': '4,2'}
, [4, 2])
1024 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720
1025 # https://github.com/yt-dlp/yt-dlp/issues/302
1026 test_selection({'playlistreverse': True}
, [4, 3, 2, 1])
1027 test_selection({'playliststart': 2, 'playlistreverse': True}
, [4, 3, 2])
1028 test_selection({'playlist_items': '2,4', 'playlistreverse': True}
, [4, 2])
1029 test_selection({'playlist_items': '4,2'}
, [4, 2])
1031 def test_urlopen_no_file_protocol(self
):
1032 # see https://github.com/ytdl-org/youtube-dl/issues/8227
1034 self
.assertRaises(compat_urllib_error
.URLError
, ydl
.urlopen
, 'file:///etc/passwd')
1036 def test_do_not_override_ie_key_in_url_transparent(self
):
1039 class Foo1IE(InfoExtractor
):
1040 _VALID_URL
= r
'foo1:'
1042 def _real_extract(self
, url
):
1044 '_type': 'url_transparent',
1047 'title': 'foo1 title',
1051 class Foo2IE(InfoExtractor
):
1052 _VALID_URL
= r
'foo2:'
1054 def _real_extract(self
, url
):
1061 class Foo3IE(InfoExtractor
):
1062 _VALID_URL
= r
'foo3:'
1064 def _real_extract(self
, url
):
1065 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1067 ydl
.add_info_extractor(Foo1IE(ydl
))
1068 ydl
.add_info_extractor(Foo2IE(ydl
))
1069 ydl
.add_info_extractor(Foo3IE(ydl
))
1070 ydl
.extract_info('foo1:')
1071 downloaded
= ydl
.downloaded_info_dicts
[0]
1072 self
.assertEqual(downloaded
['url'], TEST_URL
)
1073 self
.assertEqual(downloaded
['title'], 'foo1 title')
1074 self
.assertEqual(downloaded
['id'], 'testid')
1075 self
.assertEqual(downloaded
['extractor'], 'testex')
1076 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1078 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1079 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1082 def __init__(self
, *args
, **kwargs
):
1083 super(_YDL
, self
).__init
__(*args
, **kwargs
)
1085 def trouble(self
, s
, tb
=None):
1090 'ignoreerrors': True,
1093 class VideoIE(InfoExtractor
):
1094 _VALID_URL
= r
'video:(?P<id>\d+)'
1096 def _real_extract(self
, url
):
1097 video_id
= self
._match
_id
(url
)
1099 'format_id': 'default',
1103 raise ExtractorError('foo')
1106 'format_id': 'extra',
1111 'title': 'Video %s' % video_id
,
1115 class PlaylistIE(InfoExtractor
):
1116 _VALID_URL
= r
'playlist:'
1120 video_id
= compat_str(n
)
1122 '_type': 'url_transparent',
1123 'ie_key': VideoIE
.ie_key(),
1125 'url': 'video:%s' % video_id
,
1126 'title': 'Video Transparent %s' % video_id
,
1129 def _real_extract(self
, url
):
1130 return self
.playlist_result(self
._entries
())
1132 ydl
.add_info_extractor(VideoIE(ydl
))
1133 ydl
.add_info_extractor(PlaylistIE(ydl
))
1134 info
= ydl
.extract_info('playlist:')
1135 entries
= info
['entries']
1136 self
.assertEqual(len(entries
), 3)
1137 self
.assertTrue(entries
[0] is None)
1138 self
.assertTrue(entries
[1] is None)
1139 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1140 downloaded
= ydl
.downloaded_info_dicts
[0]
1141 entries
[2].pop('requested_downloads', None)
1142 self
.assertEqual(entries
[2], downloaded
)
1143 self
.assertEqual(downloaded
['url'], TEST_URL
)
1144 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1145 self
.assertEqual(downloaded
['id'], '2')
1146 self
.assertEqual(downloaded
['extractor'], 'Video')
1147 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1150 if __name__
== '__main__':