2 # Allow direct execution
7 sys
.path
.insert(0, os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
))))
11 from test
.helper
import FakeYDL
, assertRegexpMatches
13 from yt_dlp
import YoutubeDL
14 from yt_dlp
.compat
import (
20 from yt_dlp
.extractor
import YoutubeIE
21 from yt_dlp
.extractor
.common
import InfoExtractor
22 from yt_dlp
.postprocessor
.common
import PostProcessor
23 from yt_dlp
.utils
import (
30 TEST_URL
= 'http://localhost/sample.mp4'
34 def __init__(self
, *args
, **kwargs
):
35 super().__init
__(*args
, **kwargs
)
36 self
.downloaded_info_dicts
= []
39 def process_info(self
, info_dict
):
40 self
.downloaded_info_dicts
.append(info_dict
.copy())
42 def to_screen(self
, msg
):
45 def dl(self
, *args
, **kwargs
):
46 assert False, 'Downloader must not be invoked for test_YoutubeDL'
49 def _make_result(formats
, **kwargs
):
53 'title': 'testttitle',
54 'extractor': 'testex',
55 'extractor_key': 'TestEx',
56 'webpage_url': 'http://example.com/watch?v=shenanigans',
62 class TestFormatSelection(unittest
.TestCase
):
63 def test_prefer_free_formats(self
):
64 # Same resolution => download webm
66 ydl
.params
['prefer_free_formats'] = True
68 {'ext': 'webm', 'height': 460, 'url': TEST_URL}
,
69 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}
,
71 info_dict
= _make_result(formats
)
73 yie
._sort
_formats
(info_dict
['formats'])
74 ydl
.process_ie_result(info_dict
)
75 downloaded
= ydl
.downloaded_info_dicts
[0]
76 self
.assertEqual(downloaded
['ext'], 'webm')
78 # Different resolution => download best quality (mp4)
80 ydl
.params
['prefer_free_formats'] = True
82 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
83 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}
,
85 info_dict
['formats'] = formats
87 yie
._sort
_formats
(info_dict
['formats'])
88 ydl
.process_ie_result(info_dict
)
89 downloaded
= ydl
.downloaded_info_dicts
[0]
90 self
.assertEqual(downloaded
['ext'], 'mp4')
92 # No prefer_free_formats => prefer mp4 and webm
94 ydl
.params
['prefer_free_formats'] = False
96 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
97 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}
,
98 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
100 info_dict
['formats'] = formats
102 yie
._sort
_formats
(info_dict
['formats'])
103 ydl
.process_ie_result(info_dict
)
104 downloaded
= ydl
.downloaded_info_dicts
[0]
105 self
.assertEqual(downloaded
['ext'], 'mp4')
108 ydl
.params
['prefer_free_formats'] = False
110 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
111 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
113 info_dict
['formats'] = formats
115 yie
._sort
_formats
(info_dict
['formats'])
116 ydl
.process_ie_result(info_dict
)
117 downloaded
= ydl
.downloaded_info_dicts
[0]
118 self
.assertEqual(downloaded
['ext'], 'webm')
120 def test_format_selection(self
):
122 {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
123 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL}
,
124 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL}
,
125 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL}
,
126 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL}
,
128 info_dict
= _make_result(formats
)
130 def test(inp
, *expected
, multi
=False):
133 'allow_multiple_video_streams': multi
,
134 'allow_multiple_audio_streams': multi
,
136 ydl
.process_ie_result(info_dict
.copy())
137 downloaded
= map(lambda x
: x
['format_id'], ydl
.downloaded_info_dicts
)
138 self
.assertEqual(list(downloaded
), list(expected
))
141 test('20/71/worst', '35')
143 test('webm/mp4', '47')
144 test('3gp/40/mp4', '35')
145 test('example-with-dashes', 'example-with-dashes')
146 test('all', '2', '47', '45', 'example-with-dashes', '35')
147 test('mergeall', '2+47+45+example-with-dashes+35', multi
=True)
149 def test_format_selection_audio(self
):
151 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
152 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
153 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}
,
154 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL}
,
156 info_dict
= _make_result(formats
)
158 ydl
= YDL({'format': 'bestaudio'}
)
159 ydl
.process_ie_result(info_dict
.copy())
160 downloaded
= ydl
.downloaded_info_dicts
[0]
161 self
.assertEqual(downloaded
['format_id'], 'audio-high')
163 ydl
= YDL({'format': 'worstaudio'}
)
164 ydl
.process_ie_result(info_dict
.copy())
165 downloaded
= ydl
.downloaded_info_dicts
[0]
166 self
.assertEqual(downloaded
['format_id'], 'audio-low')
169 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
170 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL}
,
172 info_dict
= _make_result(formats
)
174 ydl
= YDL({'format': 'bestaudio/worstaudio/best'}
)
175 ydl
.process_ie_result(info_dict
.copy())
176 downloaded
= ydl
.downloaded_info_dicts
[0]
177 self
.assertEqual(downloaded
['format_id'], 'vid-high')
179 def test_format_selection_audio_exts(self
):
181 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
182 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
183 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
184 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
185 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
188 info_dict
= _make_result(formats
)
189 ydl
= YDL({'format': 'best'}
)
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'], 'aac-64')
196 ydl
= YDL({'format': 'mp3'}
)
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'], 'mp3-64')
203 ydl
= YDL({'prefer_free_formats': True}
)
205 ie
._sort
_formats
(info_dict
['formats'])
206 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
207 downloaded
= ydl
.downloaded_info_dicts
[0]
208 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
210 def test_format_selection_video(self
):
212 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
213 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
214 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
216 info_dict
= _make_result(formats
)
218 ydl
= YDL({'format': 'bestvideo'}
)
219 ydl
.process_ie_result(info_dict
.copy())
220 downloaded
= ydl
.downloaded_info_dicts
[0]
221 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
223 ydl
= YDL({'format': 'worstvideo'}
)
224 ydl
.process_ie_result(info_dict
.copy())
225 downloaded
= ydl
.downloaded_info_dicts
[0]
226 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
228 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
229 ydl
.process_ie_result(info_dict
.copy())
230 downloaded
= ydl
.downloaded_info_dicts
[0]
231 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
234 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
236 info_dict
= _make_result(formats
)
238 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
239 ydl
.process_ie_result(info_dict
.copy())
240 downloaded
= ydl
.downloaded_info_dicts
[0]
241 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
243 def test_format_selection_string_ops(self
):
245 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
246 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
248 info_dict
= _make_result(formats
)
251 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
252 ydl
.process_ie_result(info_dict
.copy())
253 downloaded
= ydl
.downloaded_info_dicts
[0]
254 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
256 # does not equal (!=)
257 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
258 ydl
.process_ie_result(info_dict
.copy())
259 downloaded
= ydl
.downloaded_info_dicts
[0]
260 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
262 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
263 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
266 ydl
= YDL({'format': '[format_id^=abc]'}
)
267 ydl
.process_ie_result(info_dict
.copy())
268 downloaded
= ydl
.downloaded_info_dicts
[0]
269 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
271 # does not start with (!^=)
272 ydl
= YDL({'format': '[format_id!^=abc]'}
)
273 ydl
.process_ie_result(info_dict
.copy())
274 downloaded
= ydl
.downloaded_info_dicts
[0]
275 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
277 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
278 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
281 ydl
= YDL({'format': '[format_id$=cba]'}
)
282 ydl
.process_ie_result(info_dict
.copy())
283 downloaded
= ydl
.downloaded_info_dicts
[0]
284 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
286 # does not end with (!$=)
287 ydl
= YDL({'format': '[format_id!$=cba]'}
)
288 ydl
.process_ie_result(info_dict
.copy())
289 downloaded
= ydl
.downloaded_info_dicts
[0]
290 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
292 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
293 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
296 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
297 ydl
.process_ie_result(info_dict
.copy())
298 downloaded
= ydl
.downloaded_info_dicts
[0]
299 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
301 # does not contain (!*=)
302 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
303 ydl
.process_ie_result(info_dict
.copy())
304 downloaded
= ydl
.downloaded_info_dicts
[0]
305 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
307 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
308 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
310 ydl
= YDL({'format': '[format_id!*=-]'}
)
311 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
313 def test_youtube_format_selection(self
):
314 # FIXME: Rewrite in accordance with the new format sorting options
318 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
319 # Apple HTTP Live Streaming
320 '96', '95', '94', '93', '92', '132', '151',
322 '85', '84', '102', '83', '101', '82', '100',
324 '137', '248', '136', '247', '135', '246',
325 '245', '244', '134', '243', '133', '242', '160',
327 '141', '172', '140', '171', '139',
330 def format_info(f_id
):
331 info
= YoutubeIE
._formats
[f_id
].copy()
333 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
334 # and 'vcodec', while in tests such information is incomplete since
335 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
336 # test_YoutubeDL.test_youtube_format_selection is broken without
338 if 'acodec' in info
and 'vcodec' not in info
:
339 info
['vcodec'] = 'none'
340 elif 'vcodec' in info
and 'acodec' not in info
:
341 info
['acodec'] = 'none'
343 info
['format_id'] = f_id
344 info
['url'] = 'url:' + f_id
346 formats_order
= [format_info(f_id
) for f_id
in order
]
348 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
349 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
351 yie
._sort
_formats
(info_dict
['formats'])
352 ydl
.process_ie_result(info_dict
)
353 downloaded
= ydl
.downloaded_info_dicts
[0]
354 self
.assertEqual(downloaded
['format_id'], '248+172')
355 self
.assertEqual(downloaded
['ext'], 'mp4')
357 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
358 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
360 yie
._sort
_formats
(info_dict
['formats'])
361 ydl
.process_ie_result(info_dict
)
362 downloaded
= ydl
.downloaded_info_dicts
[0]
363 self
.assertEqual(downloaded
['format_id'], '38')
365 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
366 ydl
= YDL({'format': 'bestvideo/best,bestaudio'}
)
368 yie
._sort
_formats
(info_dict
['formats'])
369 ydl
.process_ie_result(info_dict
)
370 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
371 self
.assertEqual(downloaded_ids
, ['137', '141'])
373 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
374 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'}
)
376 yie
._sort
_formats
(info_dict
['formats'])
377 ydl
.process_ie_result(info_dict
)
378 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
379 self
.assertEqual(downloaded_ids
, ['137+141', '248+141'])
381 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
382 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'}
)
384 yie
._sort
_formats
(info_dict
['formats'])
385 ydl
.process_ie_result(info_dict
)
386 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
387 self
.assertEqual(downloaded_ids
, ['136+141', '247+141'])
389 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
390 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}
)
392 yie
._sort
_formats
(info_dict
['formats'])
393 ydl
.process_ie_result(info_dict
)
394 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
395 self
.assertEqual(downloaded_ids
, ['248+141'])
397 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
398 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
399 ydl
= YDL({'format': 'best/bestvideo'}
)
401 yie
._sort
_formats
(info_dict
['formats'])
402 ydl
.process_ie_result(info_dict
)
403 downloaded
= ydl
.downloaded_info_dicts
[0]
404 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
406 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
407 ydl
= YDL({'format': 'best/bestvideo'}
)
409 yie
._sort
_formats
(info_dict
['formats'])
410 ydl
.process_ie_result(info_dict
)
411 downloaded
= ydl
.downloaded_info_dicts
[0]
412 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
414 def test_audio_only_extractor_format_selection(self
):
415 # For extractors with incomplete formats (all formats are audio-only or
416 # video-only) best and worst should fallback to corresponding best/worst
417 # video-only or audio-only formats (as per
418 # https://github.com/ytdl-org/youtube-dl/pull/5556)
420 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
421 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
423 info_dict
= _make_result(formats
)
425 ydl
= YDL({'format': 'best'}
)
426 ydl
.process_ie_result(info_dict
.copy())
427 downloaded
= ydl
.downloaded_info_dicts
[0]
428 self
.assertEqual(downloaded
['format_id'], 'high')
430 ydl
= YDL({'format': 'worst'}
)
431 ydl
.process_ie_result(info_dict
.copy())
432 downloaded
= ydl
.downloaded_info_dicts
[0]
433 self
.assertEqual(downloaded
['format_id'], 'low')
435 def test_format_not_available(self
):
437 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
438 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
440 info_dict
= _make_result(formats
)
442 # This must fail since complete video-audio format does not match filter
443 # and extractor does not provide incomplete only formats (i.e. only
444 # video-only or audio-only).
445 ydl
= YDL({'format': 'best[height>360]'}
)
446 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
448 def test_format_selection_issue_10083(self
):
449 # See https://github.com/ytdl-org/youtube-dl/issues/10083
451 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
452 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
453 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
455 info_dict
= _make_result(formats
)
457 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
458 ydl
.process_ie_result(info_dict
.copy())
459 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
461 def test_invalid_format_specs(self
):
462 def assert_syntax_error(format_spec
):
463 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec}
)
465 assert_syntax_error('bestvideo,,best')
466 assert_syntax_error('+bestaudio')
467 assert_syntax_error('bestvideo+')
468 assert_syntax_error('/')
469 assert_syntax_error('[720<height]')
471 def test_format_filtering(self
):
473 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
474 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
475 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
476 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
477 {'format_id': 'E', 'filesize': 3000}
,
479 {'format_id': 'G', 'filesize': 1000000}
,
482 f
['url'] = 'http://_/'
484 info_dict
= _make_result(formats
)
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'], 'D')
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'], 'E')
496 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
497 ydl
.process_ie_result(info_dict
)
498 downloaded
= ydl
.downloaded_info_dicts
[0]
499 self
.assertEqual(downloaded
['format_id'], 'F')
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'], 'B')
506 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
507 ydl
.process_ie_result(info_dict
)
508 downloaded
= ydl
.downloaded_info_dicts
[0]
509 self
.assertEqual(downloaded
['format_id'], 'C')
511 ydl
= YDL({'format': '[filesize>?1]'}
)
512 ydl
.process_ie_result(info_dict
)
513 downloaded
= ydl
.downloaded_info_dicts
[0]
514 self
.assertEqual(downloaded
['format_id'], 'G')
516 ydl
= YDL({'format': '[filesize<1M]'}
)
517 ydl
.process_ie_result(info_dict
)
518 downloaded
= ydl
.downloaded_info_dicts
[0]
519 self
.assertEqual(downloaded
['format_id'], 'E')
521 ydl
= YDL({'format': '[filesize<1MiB]'}
)
522 ydl
.process_ie_result(info_dict
)
523 downloaded
= ydl
.downloaded_info_dicts
[0]
524 self
.assertEqual(downloaded
['format_id'], 'G')
526 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
527 ydl
.process_ie_result(info_dict
)
528 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
529 self
.assertEqual(downloaded_ids
, ['D', 'C', 'B'])
531 ydl
= YDL({'format': 'best[height<40]'}
)
533 ydl
.process_ie_result(info_dict
)
534 except ExtractorError
:
536 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
538 def test_default_format_spec(self
):
539 ydl
= YDL({'simulate': True}
)
540 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
543 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
545 ydl
= YDL({'simulate': True}
)
546 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
548 ydl
= YDL({'outtmpl': '-'}
)
549 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
552 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
553 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
556 class TestYoutubeDL(unittest
.TestCase
):
557 def test_subtitles(self
):
558 def s_formats(lang
, autocaption
=False):
561 'url': f
'http://localhost/video.{lang}.{ext}',
562 '_auto': autocaption
,
563 } for ext
in ['vtt', 'srt', 'ass']]
564 subtitles
= {l: s_formats(l) for l in ['en', 'fr', 'es']}
565 auto_captions
= {l: s_formats(l, True) for l in ['it', 'pt', 'es']}
569 'url': 'http://localhost/video.mp4',
570 'subtitles': subtitles
,
571 'automatic_captions': auto_captions
,
573 'webpage_url': 'http://example.com/watch?v=shenanigans',
576 def get_info(params
={}):
577 params
.setdefault('simulate', True)
579 ydl
.report_warning
= lambda *args
, **kargs
: None
580 return ydl
.process_video_result(info_dict
, download
=False)
583 self
.assertFalse(result
.get('requested_subtitles'))
584 self
.assertEqual(result
['subtitles'], subtitles
)
585 self
.assertEqual(result
['automatic_captions'], auto_captions
)
587 result
= get_info({'writesubtitles': True}
)
588 subs
= result
['requested_subtitles']
589 self
.assertTrue(subs
)
590 self
.assertEqual(set(subs
.keys()), {'en'}
)
591 self
.assertTrue(subs
['en'].get('data') is None)
592 self
.assertEqual(subs
['en']['ext'], 'ass')
594 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
595 subs
= result
['requested_subtitles']
596 self
.assertEqual(subs
['en']['ext'], 'srt')
598 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
599 subs
= result
['requested_subtitles']
600 self
.assertTrue(subs
)
601 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
603 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
604 subs
= result
['requested_subtitles']
605 self
.assertTrue(subs
)
606 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
608 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
609 subs
= result
['requested_subtitles']
610 self
.assertTrue(subs
)
611 self
.assertEqual(set(subs
.keys()), {'fr'}
)
613 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
614 subs
= result
['requested_subtitles']
615 self
.assertTrue(subs
)
616 self
.assertEqual(set(subs
.keys()), {'en'}
)
618 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
619 subs
= result
['requested_subtitles']
620 self
.assertTrue(subs
)
621 self
.assertEqual(set(subs
.keys()), {'es', 'en'}
)
623 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
624 subs
= result
['requested_subtitles']
625 self
.assertTrue(subs
)
626 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
627 self
.assertFalse(subs
['es']['_auto'])
628 self
.assertTrue(subs
['pt']['_auto'])
630 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
631 subs
= result
['requested_subtitles']
632 self
.assertTrue(subs
)
633 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
634 self
.assertTrue(subs
['es']['_auto'])
635 self
.assertTrue(subs
['pt']['_auto'])
637 def test_add_extra_info(self
):
643 'playlist': 'funny videos',
645 YDL
.add_extra_info(test_dict
, extra_info
)
646 self
.assertEqual(test_dict
['extractor'], 'Foo')
647 self
.assertEqual(test_dict
['playlist'], 'funny videos')
657 'title3': 'foo/bar\\test',
658 'title4': 'foo "bar" test',
660 'timestamp': 1618488000,
663 'playlist_autonumber': 2,
664 '_last_playlist_index': 100,
666 'formats': [{'id': 'id 1'}
, {'id': 'id 2'}
, {'id': 'id 3'}
]
669 def test_prepare_outtmpl_and_filename(self
):
670 def test(tmpl
, expected
, *, info
=None, **params
):
671 params
['outtmpl'] = tmpl
672 ydl
= YoutubeDL(params
)
673 ydl
._num
_downloads
= 1
674 self
.assertEqual(ydl
.validate_outtmpl(tmpl
), None)
676 out
= ydl
.evaluate_outtmpl(tmpl
, info
or self
.outtmpl_info
)
677 fname
= ydl
.prepare_filename(info
or self
.outtmpl_info
)
679 if not isinstance(expected
, (list, tuple)):
680 expected
= (expected
, expected
)
681 for (name
, got
), expect
in zip((('outtmpl', out
), ('filename', fname
)), expected
):
683 self
.assertTrue(expect(got
), f
'Wrong {name} from {tmpl}')
685 self
.assertEqual(got
, expect
, f
'Wrong {name} from {tmpl}')
688 original_infodict
= dict(self
.outtmpl_info
)
689 test('foo.bar', 'foo.bar')
690 original_infodict
['epoch'] = self
.outtmpl_info
.get('epoch')
691 self
.assertTrue(isinstance(original_infodict
['epoch'], int))
692 test('%(epoch)d', int_or_none
)
693 self
.assertEqual(original_infodict
, self
.outtmpl_info
)
695 # Auto-generated fields
696 test('%(id)s.%(ext)s', '1234.mp4')
697 test('%(duration_string)s', ('27:46:40', '27-46-40'))
698 test('%(resolution)s', '1080p')
699 test('%(playlist_index)s', '001')
700 test('%(playlist_autonumber)s', '02')
701 test('%(autonumber)s', '00001')
702 test('%(autonumber+2)03d', '005', autonumber_start
=3)
703 test('%(autonumber)s', '001', autonumber_size
=3)
712 test('%abc%', '%abc%')
713 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
714 test('%%%(height)s', '%1080')
715 test('%(width)06d.%(ext)s', 'NA.mp4')
716 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
717 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
720 test('%(id)s', '_abcd', info
={'id': '_abcd'}
)
721 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'}
)
722 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}
]})
723 test('%(id)s', '-abcd', info
={'id': '-abcd'}
)
724 test('%(id)s', '.abcd', info
={'id': '.abcd'}
)
725 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'}
)
726 test('%(id)s', ('ab:cd', 'ab -cd'), info
={'id': 'ab:cd'}
)
727 test('%(id.0)s', '-', info
={'id': '--'}
)
730 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
731 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
735 def expect_same_infodict(out
):
736 got_dict
= json
.loads(out
)
737 for info_field
, expected
in self
.outtmpl_info
.items():
738 self
.assertEqual(got_dict
.get(info_field
), expected
, info_field
)
741 test('%()j', (expect_same_infodict
, str))
744 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
745 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
746 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
747 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
748 test('%(non_existent.0)s', 'NA')
751 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
752 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
753 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
754 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
755 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
756 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
757 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
758 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
759 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
760 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
761 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
764 test('%(id)d', '1234')
765 test('%(height)c', '1')
767 test('%(id)d %(id)r', "1234 '1234'")
768 test('%(id)r %(height)r', "'1234' 1080")
769 test('%(ext)s-%(ext|def)d', 'mp4-def')
770 test('%(width|0)04d', '0000')
771 test('a%(width|)d', 'a', outtmpl_na_placeholder
='none')
773 FORMATS
= self
.outtmpl_info
['formats']
774 sanitize
= lambda x
: x
.replace(':', ' -').replace('"', "'").replace('\n', ' ')
776 # Custom type casting
777 test('%(formats.:.id)l', 'id 1, id 2, id 3')
778 test('%(formats.:.id)#l', ('id 1\nid 2\nid 3', 'id 1 id 2 id 3'))
779 test('%(ext)l', 'mp4')
780 test('%(formats.:.id) 18l', ' id 1, id 2, id 3')
781 test('%(formats)j', (json
.dumps(FORMATS
), sanitize(json
.dumps(FORMATS
))))
782 test('%(formats)#j', (json
.dumps(FORMATS
, indent
=4), sanitize(json
.dumps(FORMATS
, indent
=4))))
783 test('%(title5).3B', 'á')
784 test('%(title5)U', 'áéí 𝐀')
785 test('%(title5)#U', 'a\u0301e\u0301i\u0301 𝐀')
786 test('%(title5)+U', 'áéí A')
787 test('%(title5)+#U', 'a\u0301e\u0301i\u0301 A')
788 test('%(height)D', '1k')
789 test('%(filesize)#D', '1Ki')
790 test('%(height)5.2D', ' 1.08k')
791 test('%(title4)#S', 'foo_bar_test')
792 test('%(title4).10S', ('foo \'bar\' ', 'foo \'bar\'' + ('#' if compat_os_name
== 'nt' else ' ')))
793 if compat_os_name
== 'nt':
794 test('%(title4)q', ('"foo \\"bar\\" test"', "'foo _'bar_' test'"))
795 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', "'id 1' 'id 2' 'id 3'"))
796 test('%(formats.0.id)#q', ('"id 1"', "'id 1'"))
798 test('%(title4)q', ('\'foo "bar" test\'', "'foo 'bar' test'"))
799 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'")
800 test('%(formats.0.id)#q', "'id 1'")
802 # Internal formatting
803 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
804 test('%(title|%)s %(title|%%)s', '% %%')
805 test('%(id+1-height+3)05d', '00158')
806 test('%(width+100)05d', 'NA')
807 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], '% 15s' % sanitize(str(FORMATS
[0]))))
808 test('%(formats.0)r', (repr(FORMATS
[0]), sanitize(repr(FORMATS
[0]))))
809 test('%(height.0)03d', '001')
810 test('%(-height.0)04d', '-001')
811 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
812 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
813 test('%(formats.3)s', 'NA')
814 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
815 test('%(formats.0.id.-1+id)f', '1235.000000')
816 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
819 test('%(title,id)s', '1234')
820 test('%(width-100,height+20|def)d', '1100')
821 test('%(width-100,height+width|def)s', 'def')
822 test('%(timestamp-x>%H\\,%M\\,%S,timestamp>%H\\,%M\\,%S)s', '12,00,00')
825 test('%(id&foo)s.bar', 'foo.bar')
826 test('%(title&foo)s.bar', 'NA.bar')
827 test('%(title&foo|baz)s.bar', 'baz.bar')
828 test('%(x,id&foo|baz)s.bar', 'foo.bar')
829 test('%(x,title&foo|baz)s.bar', 'baz.bar')
834 raise self
.assertTrue(False, 'LazyList should not be evaluated till here')
835 test('%(key.4)s', '4', info
={'key': LazyList(gen())}
)
838 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
839 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
840 # test('%(foo|)s', ('', '_')) # fixme
842 # Environment variable expansion for prepare_filename
843 compat_setenv('__yt_dlp_var', 'expanded')
844 envvar
= '%__yt_dlp_var%' if compat_os_name
== 'nt' else '$__yt_dlp_var'
845 test(envvar
, (envvar
, 'expanded'))
846 if compat_os_name
== 'nt':
847 test('%s%', ('%s%', '%s%'))
848 compat_setenv('s', 'expanded')
849 test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
850 compat_setenv('(test)s', 'expanded')
851 test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
853 # Path expansion and escaping
854 test('Hello %(title1)s', 'Hello $PATH')
855 test('Hello %(title2)s', 'Hello %PATH%')
856 test('%(title3)s', ('foo/bar\\test', 'foo_bar_test'))
857 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo_bar_test' % os
.path
.sep
))
859 def test_format_note(self
):
861 self
.assertEqual(ydl
._format
_note
({}), '')
862 assertRegexpMatches(self
, ydl
._format
_note
({
865 assertRegexpMatches(self
, ydl
._format
_note
({
869 def test_postprocessors(self
):
870 filename
= 'post-processor-testfile.mp4'
871 audiofile
= filename
+ '.mp3'
873 class SimplePP(PostProcessor
):
875 with open(audiofile
, 'wt') as f
:
877 return [info
['filepath']], info
879 def run_pp(params
, PP
):
880 with open(filename
, 'wt') as f
:
882 ydl
= YoutubeDL(params
)
883 ydl
.add_post_processor(PP())
884 ydl
.post_process(filename
, {'filepath': filename}
)
886 run_pp({'keepvideo': True}
, SimplePP
)
887 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
888 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
892 run_pp({'keepvideo': False}
, SimplePP
)
893 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
894 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
897 class ModifierPP(PostProcessor
):
899 with open(info
['filepath'], 'wt') as f
:
903 run_pp({'keepvideo': False}
, ModifierPP
)
904 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
907 def test_match_filter(self
):
914 'filesize': 10 * 1024,
916 'uploader': "變態妍字幕版 太妍 тест",
917 'creator': "тест ' 123 ' тест--",
918 'webpage_url': 'http://example.com/watch?v=shenanigans',
926 'description': 'foo',
927 'filesize': 5 * 1024,
929 'uploader': "тест 123",
930 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
932 videos
= [first
, second
]
934 def get_videos(filter_
=None):
935 ydl
= YDL({'match_filter': filter_, 'simulate': True}
)
937 ydl
.process_ie_result(v
, download
=True)
938 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
941 self
.assertEqual(res
, ['1', '2'])
943 def f(v
, incomplete
):
947 return 'Video id is not 1'
949 self
.assertEqual(res
, ['1'])
951 f
= match_filter_func('duration < 30')
953 self
.assertEqual(res
, ['2'])
955 f
= match_filter_func('description = foo')
957 self
.assertEqual(res
, ['2'])
959 f
= match_filter_func('description =? foo')
961 self
.assertEqual(res
, ['1', '2'])
963 f
= match_filter_func('filesize > 5KiB')
965 self
.assertEqual(res
, ['1'])
967 f
= match_filter_func('playlist_id = 42')
969 self
.assertEqual(res
, ['1'])
971 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
973 self
.assertEqual(res
, ['1'])
975 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
977 self
.assertEqual(res
, ['2'])
979 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
981 self
.assertEqual(res
, ['1'])
983 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
985 self
.assertEqual(res
, ['1'])
987 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
989 self
.assertEqual(res
, [])
991 def test_playlist_items_selection(self
):
994 'title': compat_str(i
),
996 } for i
in range(1, 5)]
1001 'extractor': 'test:playlist',
1002 'extractor_key': 'test:playlist',
1003 'webpage_url': 'http://example.com',
1006 def get_downloaded_info_dicts(params
):
1008 # make a deep copy because the dictionary and nested entries
1010 ydl
.process_ie_result(copy
.deepcopy(playlist
))
1011 return ydl
.downloaded_info_dicts
1013 def test_selection(params
, expected_ids
):
1015 (v
['playlist_autonumber'] - 1, (int(v
['id']), v
['playlist_index']))
1016 for v
in get_downloaded_info_dicts(params
)]
1017 self
.assertEqual(results
, list(enumerate(zip(expected_ids
, expected_ids
))))
1019 test_selection({}, [1, 2, 3, 4])
1020 test_selection({'playlistend': 10}
, [1, 2, 3, 4])
1021 test_selection({'playlistend': 2}
, [1, 2])
1022 test_selection({'playliststart': 10}
, [])
1023 test_selection({'playliststart': 2}
, [2, 3, 4])
1024 test_selection({'playlist_items': '2-4'}
, [2, 3, 4])
1025 test_selection({'playlist_items': '2,4'}
, [2, 4])
1026 test_selection({'playlist_items': '10'}
, [])
1027 test_selection({'playlist_items': '0'}
, [])
1029 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
1030 test_selection({'playlist_items': '2-4,3-4,3'}
, [2, 3, 4])
1031 test_selection({'playlist_items': '4,2'}
, [4, 2])
1033 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720
1034 # https://github.com/yt-dlp/yt-dlp/issues/302
1035 test_selection({'playlistreverse': True}
, [4, 3, 2, 1])
1036 test_selection({'playliststart': 2, 'playlistreverse': True}
, [4, 3, 2])
1037 test_selection({'playlist_items': '2,4', 'playlistreverse': True}
, [4, 2])
1038 test_selection({'playlist_items': '4,2'}
, [4, 2])
1040 def test_urlopen_no_file_protocol(self
):
1041 # see https://github.com/ytdl-org/youtube-dl/issues/8227
1043 self
.assertRaises(compat_urllib_error
.URLError
, ydl
.urlopen
, 'file:///etc/passwd')
1045 def test_do_not_override_ie_key_in_url_transparent(self
):
1048 class Foo1IE(InfoExtractor
):
1049 _VALID_URL
= r
'foo1:'
1051 def _real_extract(self
, url
):
1053 '_type': 'url_transparent',
1056 'title': 'foo1 title',
1060 class Foo2IE(InfoExtractor
):
1061 _VALID_URL
= r
'foo2:'
1063 def _real_extract(self
, url
):
1070 class Foo3IE(InfoExtractor
):
1071 _VALID_URL
= r
'foo3:'
1073 def _real_extract(self
, url
):
1074 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1076 ydl
.add_info_extractor(Foo1IE(ydl
))
1077 ydl
.add_info_extractor(Foo2IE(ydl
))
1078 ydl
.add_info_extractor(Foo3IE(ydl
))
1079 ydl
.extract_info('foo1:')
1080 downloaded
= ydl
.downloaded_info_dicts
[0]
1081 self
.assertEqual(downloaded
['url'], TEST_URL
)
1082 self
.assertEqual(downloaded
['title'], 'foo1 title')
1083 self
.assertEqual(downloaded
['id'], 'testid')
1084 self
.assertEqual(downloaded
['extractor'], 'testex')
1085 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1087 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1088 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1091 def __init__(self
, *args
, **kwargs
):
1092 super().__init
__(*args
, **kwargs
)
1094 def trouble(self
, s
, tb
=None):
1099 'ignoreerrors': True,
1102 class VideoIE(InfoExtractor
):
1103 _VALID_URL
= r
'video:(?P<id>\d+)'
1105 def _real_extract(self
, url
):
1106 video_id
= self
._match
_id
(url
)
1108 'format_id': 'default',
1112 raise ExtractorError('foo')
1115 'format_id': 'extra',
1120 'title': 'Video %s' % video_id
,
1124 class PlaylistIE(InfoExtractor
):
1125 _VALID_URL
= r
'playlist:'
1129 video_id
= compat_str(n
)
1131 '_type': 'url_transparent',
1132 'ie_key': VideoIE
.ie_key(),
1134 'url': 'video:%s' % video_id
,
1135 'title': 'Video Transparent %s' % video_id
,
1138 def _real_extract(self
, url
):
1139 return self
.playlist_result(self
._entries
())
1141 ydl
.add_info_extractor(VideoIE(ydl
))
1142 ydl
.add_info_extractor(PlaylistIE(ydl
))
1143 info
= ydl
.extract_info('playlist:')
1144 entries
= info
['entries']
1145 self
.assertEqual(len(entries
), 3)
1146 self
.assertTrue(entries
[0] is None)
1147 self
.assertTrue(entries
[1] is None)
1148 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1149 downloaded
= ydl
.downloaded_info_dicts
[0]
1150 entries
[2].pop('requested_downloads', None)
1151 self
.assertEqual(entries
[2], downloaded
)
1152 self
.assertEqual(downloaded
['url'], TEST_URL
)
1153 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1154 self
.assertEqual(downloaded
['id'], '2')
1155 self
.assertEqual(downloaded
['extractor'], 'Video')
1156 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1159 if __name__
== '__main__':