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 (
31 TEST_URL
= 'http://localhost/sample.mp4'
35 def __init__(self
, *args
, **kwargs
):
36 super().__init
__(*args
, **kwargs
)
37 self
.downloaded_info_dicts
= []
40 def process_info(self
, info_dict
):
41 self
.downloaded_info_dicts
.append(info_dict
.copy())
43 def to_screen(self
, msg
):
46 def dl(self
, *args
, **kwargs
):
47 assert False, 'Downloader must not be invoked for test_YoutubeDL'
50 def _make_result(formats
, **kwargs
):
54 'title': 'testttitle',
55 'extractor': 'testex',
56 'extractor_key': 'TestEx',
57 'webpage_url': 'http://example.com/watch?v=shenanigans',
63 class TestFormatSelection(unittest
.TestCase
):
64 def test_prefer_free_formats(self
):
65 # Same resolution => download webm
67 ydl
.params
['prefer_free_formats'] = True
69 {'ext': 'webm', 'height': 460, 'url': TEST_URL}
,
70 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}
,
72 info_dict
= _make_result(formats
)
74 yie
._sort
_formats
(info_dict
['formats'])
75 ydl
.process_ie_result(info_dict
)
76 downloaded
= ydl
.downloaded_info_dicts
[0]
77 self
.assertEqual(downloaded
['ext'], 'webm')
79 # Different resolution => download best quality (mp4)
81 ydl
.params
['prefer_free_formats'] = True
83 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
84 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}
,
86 info_dict
['formats'] = formats
88 yie
._sort
_formats
(info_dict
['formats'])
89 ydl
.process_ie_result(info_dict
)
90 downloaded
= ydl
.downloaded_info_dicts
[0]
91 self
.assertEqual(downloaded
['ext'], 'mp4')
93 # No prefer_free_formats => prefer mp4 and webm
95 ydl
.params
['prefer_free_formats'] = False
97 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
98 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}
,
99 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
101 info_dict
['formats'] = formats
103 yie
._sort
_formats
(info_dict
['formats'])
104 ydl
.process_ie_result(info_dict
)
105 downloaded
= ydl
.downloaded_info_dicts
[0]
106 self
.assertEqual(downloaded
['ext'], 'mp4')
109 ydl
.params
['prefer_free_formats'] = False
111 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
112 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
114 info_dict
['formats'] = formats
116 yie
._sort
_formats
(info_dict
['formats'])
117 ydl
.process_ie_result(info_dict
)
118 downloaded
= ydl
.downloaded_info_dicts
[0]
119 self
.assertEqual(downloaded
['ext'], 'webm')
121 def test_format_selection(self
):
123 {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
124 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL}
,
125 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL}
,
126 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL}
,
127 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL}
,
129 info_dict
= _make_result(formats
)
131 def test(inp
, *expected
, multi
=False):
134 'allow_multiple_video_streams': multi
,
135 'allow_multiple_audio_streams': multi
,
137 ydl
.process_ie_result(info_dict
.copy())
138 downloaded
= map(lambda x
: x
['format_id'], ydl
.downloaded_info_dicts
)
139 self
.assertEqual(list(downloaded
), list(expected
))
142 test('20/71/worst', '35')
144 test('webm/mp4', '47')
145 test('3gp/40/mp4', '35')
146 test('example-with-dashes', 'example-with-dashes')
147 test('all', '2', '47', '45', 'example-with-dashes', '35')
148 test('mergeall', '2+47+45+example-with-dashes+35', multi
=True)
150 def test_format_selection_audio(self
):
152 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
153 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
154 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}
,
155 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL}
,
157 info_dict
= _make_result(formats
)
159 ydl
= YDL({'format': 'bestaudio'}
)
160 ydl
.process_ie_result(info_dict
.copy())
161 downloaded
= ydl
.downloaded_info_dicts
[0]
162 self
.assertEqual(downloaded
['format_id'], 'audio-high')
164 ydl
= YDL({'format': 'worstaudio'}
)
165 ydl
.process_ie_result(info_dict
.copy())
166 downloaded
= ydl
.downloaded_info_dicts
[0]
167 self
.assertEqual(downloaded
['format_id'], 'audio-low')
170 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
171 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL}
,
173 info_dict
= _make_result(formats
)
175 ydl
= YDL({'format': 'bestaudio/worstaudio/best'}
)
176 ydl
.process_ie_result(info_dict
.copy())
177 downloaded
= ydl
.downloaded_info_dicts
[0]
178 self
.assertEqual(downloaded
['format_id'], 'vid-high')
180 def test_format_selection_audio_exts(self
):
182 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
183 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
184 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
185 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
186 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
189 info_dict
= _make_result(formats
)
190 ydl
= YDL({'format': 'best'}
)
192 ie
._sort
_formats
(info_dict
['formats'])
193 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
194 downloaded
= ydl
.downloaded_info_dicts
[0]
195 self
.assertEqual(downloaded
['format_id'], 'aac-64')
197 ydl
= YDL({'format': 'mp3'}
)
199 ie
._sort
_formats
(info_dict
['formats'])
200 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
201 downloaded
= ydl
.downloaded_info_dicts
[0]
202 self
.assertEqual(downloaded
['format_id'], 'mp3-64')
204 ydl
= YDL({'prefer_free_formats': True}
)
206 ie
._sort
_formats
(info_dict
['formats'])
207 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
208 downloaded
= ydl
.downloaded_info_dicts
[0]
209 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
211 def test_format_selection_video(self
):
213 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
214 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
215 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
217 info_dict
= _make_result(formats
)
219 ydl
= YDL({'format': 'bestvideo'}
)
220 ydl
.process_ie_result(info_dict
.copy())
221 downloaded
= ydl
.downloaded_info_dicts
[0]
222 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
224 ydl
= YDL({'format': 'worstvideo'}
)
225 ydl
.process_ie_result(info_dict
.copy())
226 downloaded
= ydl
.downloaded_info_dicts
[0]
227 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
229 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
230 ydl
.process_ie_result(info_dict
.copy())
231 downloaded
= ydl
.downloaded_info_dicts
[0]
232 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
235 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
237 info_dict
= _make_result(formats
)
239 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
240 ydl
.process_ie_result(info_dict
.copy())
241 downloaded
= ydl
.downloaded_info_dicts
[0]
242 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
244 def test_format_selection_string_ops(self
):
246 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
247 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
249 info_dict
= _make_result(formats
)
252 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
253 ydl
.process_ie_result(info_dict
.copy())
254 downloaded
= ydl
.downloaded_info_dicts
[0]
255 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
257 # does not equal (!=)
258 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
259 ydl
.process_ie_result(info_dict
.copy())
260 downloaded
= ydl
.downloaded_info_dicts
[0]
261 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
263 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
264 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
267 ydl
= YDL({'format': '[format_id^=abc]'}
)
268 ydl
.process_ie_result(info_dict
.copy())
269 downloaded
= ydl
.downloaded_info_dicts
[0]
270 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
272 # does not start with (!^=)
273 ydl
= YDL({'format': '[format_id!^=abc]'}
)
274 ydl
.process_ie_result(info_dict
.copy())
275 downloaded
= ydl
.downloaded_info_dicts
[0]
276 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
278 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
279 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
282 ydl
= YDL({'format': '[format_id$=cba]'}
)
283 ydl
.process_ie_result(info_dict
.copy())
284 downloaded
= ydl
.downloaded_info_dicts
[0]
285 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
287 # does not end with (!$=)
288 ydl
= YDL({'format': '[format_id!$=cba]'}
)
289 ydl
.process_ie_result(info_dict
.copy())
290 downloaded
= ydl
.downloaded_info_dicts
[0]
291 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
293 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
294 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
297 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
298 ydl
.process_ie_result(info_dict
.copy())
299 downloaded
= ydl
.downloaded_info_dicts
[0]
300 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
302 # does not contain (!*=)
303 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
304 ydl
.process_ie_result(info_dict
.copy())
305 downloaded
= ydl
.downloaded_info_dicts
[0]
306 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
308 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
309 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
311 ydl
= YDL({'format': '[format_id!*=-]'}
)
312 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
314 def test_youtube_format_selection(self
):
315 # FIXME: Rewrite in accordance with the new format sorting options
319 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
320 # Apple HTTP Live Streaming
321 '96', '95', '94', '93', '92', '132', '151',
323 '85', '84', '102', '83', '101', '82', '100',
325 '137', '248', '136', '247', '135', '246',
326 '245', '244', '134', '243', '133', '242', '160',
328 '141', '172', '140', '171', '139',
331 def format_info(f_id
):
332 info
= YoutubeIE
._formats
[f_id
].copy()
334 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
335 # and 'vcodec', while in tests such information is incomplete since
336 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
337 # test_YoutubeDL.test_youtube_format_selection is broken without
339 if 'acodec' in info
and 'vcodec' not in info
:
340 info
['vcodec'] = 'none'
341 elif 'vcodec' in info
and 'acodec' not in info
:
342 info
['acodec'] = 'none'
344 info
['format_id'] = f_id
345 info
['url'] = 'url:' + f_id
347 formats_order
= [format_info(f_id
) for f_id
in order
]
349 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
350 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
352 yie
._sort
_formats
(info_dict
['formats'])
353 ydl
.process_ie_result(info_dict
)
354 downloaded
= ydl
.downloaded_info_dicts
[0]
355 self
.assertEqual(downloaded
['format_id'], '248+172')
356 self
.assertEqual(downloaded
['ext'], 'mp4')
358 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
359 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
361 yie
._sort
_formats
(info_dict
['formats'])
362 ydl
.process_ie_result(info_dict
)
363 downloaded
= ydl
.downloaded_info_dicts
[0]
364 self
.assertEqual(downloaded
['format_id'], '38')
366 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
367 ydl
= YDL({'format': 'bestvideo/best,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'])
374 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
375 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+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
, ['137+141', '248+141'])
382 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
383 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+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
, ['136+141', '247+141'])
390 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
391 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}
)
393 yie
._sort
_formats
(info_dict
['formats'])
394 ydl
.process_ie_result(info_dict
)
395 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
396 self
.assertEqual(downloaded_ids
, ['248+141'])
398 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
399 info_dict
= _make_result([f1
, f2
], 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 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
408 ydl
= YDL({'format': 'best/bestvideo'}
)
410 yie
._sort
_formats
(info_dict
['formats'])
411 ydl
.process_ie_result(info_dict
)
412 downloaded
= ydl
.downloaded_info_dicts
[0]
413 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
415 def test_audio_only_extractor_format_selection(self
):
416 # For extractors with incomplete formats (all formats are audio-only or
417 # video-only) best and worst should fallback to corresponding best/worst
418 # video-only or audio-only formats (as per
419 # https://github.com/ytdl-org/youtube-dl/pull/5556)
421 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
422 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
424 info_dict
= _make_result(formats
)
426 ydl
= YDL({'format': 'best'}
)
427 ydl
.process_ie_result(info_dict
.copy())
428 downloaded
= ydl
.downloaded_info_dicts
[0]
429 self
.assertEqual(downloaded
['format_id'], 'high')
431 ydl
= YDL({'format': 'worst'}
)
432 ydl
.process_ie_result(info_dict
.copy())
433 downloaded
= ydl
.downloaded_info_dicts
[0]
434 self
.assertEqual(downloaded
['format_id'], 'low')
436 def test_format_not_available(self
):
438 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
439 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
441 info_dict
= _make_result(formats
)
443 # This must fail since complete video-audio format does not match filter
444 # and extractor does not provide incomplete only formats (i.e. only
445 # video-only or audio-only).
446 ydl
= YDL({'format': 'best[height>360]'}
)
447 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
449 def test_format_selection_issue_10083(self
):
450 # See https://github.com/ytdl-org/youtube-dl/issues/10083
452 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
453 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
454 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
456 info_dict
= _make_result(formats
)
458 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
459 ydl
.process_ie_result(info_dict
.copy())
460 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
462 def test_invalid_format_specs(self
):
463 def assert_syntax_error(format_spec
):
464 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec}
)
466 assert_syntax_error('bestvideo,,best')
467 assert_syntax_error('+bestaudio')
468 assert_syntax_error('bestvideo+')
469 assert_syntax_error('/')
470 assert_syntax_error('[720<height]')
472 def test_format_filtering(self
):
474 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
475 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
476 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
477 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
478 {'format_id': 'E', 'filesize': 3000}
,
480 {'format_id': 'G', 'filesize': 1000000}
,
483 f
['url'] = 'http://_/'
485 info_dict
= _make_result(formats
)
487 ydl
= YDL({'format': 'best[filesize<3000]'}
)
488 ydl
.process_ie_result(info_dict
)
489 downloaded
= ydl
.downloaded_info_dicts
[0]
490 self
.assertEqual(downloaded
['format_id'], 'D')
492 ydl
= YDL({'format': 'best[filesize<=3000]'}
)
493 ydl
.process_ie_result(info_dict
)
494 downloaded
= ydl
.downloaded_info_dicts
[0]
495 self
.assertEqual(downloaded
['format_id'], 'E')
497 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
498 ydl
.process_ie_result(info_dict
)
499 downloaded
= ydl
.downloaded_info_dicts
[0]
500 self
.assertEqual(downloaded
['format_id'], 'F')
502 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'}
)
503 ydl
.process_ie_result(info_dict
)
504 downloaded
= ydl
.downloaded_info_dicts
[0]
505 self
.assertEqual(downloaded
['format_id'], 'B')
507 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
508 ydl
.process_ie_result(info_dict
)
509 downloaded
= ydl
.downloaded_info_dicts
[0]
510 self
.assertEqual(downloaded
['format_id'], 'C')
512 ydl
= YDL({'format': '[filesize>?1]'}
)
513 ydl
.process_ie_result(info_dict
)
514 downloaded
= ydl
.downloaded_info_dicts
[0]
515 self
.assertEqual(downloaded
['format_id'], 'G')
517 ydl
= YDL({'format': '[filesize<1M]'}
)
518 ydl
.process_ie_result(info_dict
)
519 downloaded
= ydl
.downloaded_info_dicts
[0]
520 self
.assertEqual(downloaded
['format_id'], 'E')
522 ydl
= YDL({'format': '[filesize<1MiB]'}
)
523 ydl
.process_ie_result(info_dict
)
524 downloaded
= ydl
.downloaded_info_dicts
[0]
525 self
.assertEqual(downloaded
['format_id'], 'G')
527 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
528 ydl
.process_ie_result(info_dict
)
529 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
530 self
.assertEqual(downloaded_ids
, ['D', 'C', 'B'])
532 ydl
= YDL({'format': 'best[height<40]'}
)
534 ydl
.process_ie_result(info_dict
)
535 except ExtractorError
:
537 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
539 def test_default_format_spec(self
):
540 ydl
= YDL({'simulate': True}
)
541 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
544 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
546 ydl
= YDL({'simulate': True}
)
547 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
549 ydl
= YDL({'outtmpl': '-'}
)
550 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
553 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
554 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
557 class TestYoutubeDL(unittest
.TestCase
):
558 def test_subtitles(self
):
559 def s_formats(lang
, autocaption
=False):
562 'url': f
'http://localhost/video.{lang}.{ext}',
563 '_auto': autocaption
,
564 } for ext
in ['vtt', 'srt', 'ass']]
565 subtitles
= {l: s_formats(l) for l in ['en', 'fr', 'es']}
566 auto_captions
= {l: s_formats(l, True) for l in ['it', 'pt', 'es']}
570 'url': 'http://localhost/video.mp4',
571 'subtitles': subtitles
,
572 'automatic_captions': auto_captions
,
574 'webpage_url': 'http://example.com/watch?v=shenanigans',
577 def get_info(params
={}):
578 params
.setdefault('simulate', True)
580 ydl
.report_warning
= lambda *args
, **kargs
: None
581 return ydl
.process_video_result(info_dict
, download
=False)
584 self
.assertFalse(result
.get('requested_subtitles'))
585 self
.assertEqual(result
['subtitles'], subtitles
)
586 self
.assertEqual(result
['automatic_captions'], auto_captions
)
588 result
= get_info({'writesubtitles': True}
)
589 subs
= result
['requested_subtitles']
590 self
.assertTrue(subs
)
591 self
.assertEqual(set(subs
.keys()), {'en'}
)
592 self
.assertTrue(subs
['en'].get('data') is None)
593 self
.assertEqual(subs
['en']['ext'], 'ass')
595 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
596 subs
= result
['requested_subtitles']
597 self
.assertEqual(subs
['en']['ext'], 'srt')
599 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
600 subs
= result
['requested_subtitles']
601 self
.assertTrue(subs
)
602 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
604 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
605 subs
= result
['requested_subtitles']
606 self
.assertTrue(subs
)
607 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
609 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
610 subs
= result
['requested_subtitles']
611 self
.assertTrue(subs
)
612 self
.assertEqual(set(subs
.keys()), {'fr'}
)
614 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
615 subs
= result
['requested_subtitles']
616 self
.assertTrue(subs
)
617 self
.assertEqual(set(subs
.keys()), {'en'}
)
619 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
620 subs
= result
['requested_subtitles']
621 self
.assertTrue(subs
)
622 self
.assertEqual(set(subs
.keys()), {'es', 'en'}
)
624 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
625 subs
= result
['requested_subtitles']
626 self
.assertTrue(subs
)
627 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
628 self
.assertFalse(subs
['es']['_auto'])
629 self
.assertTrue(subs
['pt']['_auto'])
631 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
632 subs
= result
['requested_subtitles']
633 self
.assertTrue(subs
)
634 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
635 self
.assertTrue(subs
['es']['_auto'])
636 self
.assertTrue(subs
['pt']['_auto'])
638 def test_add_extra_info(self
):
644 'playlist': 'funny videos',
646 YDL
.add_extra_info(test_dict
, extra_info
)
647 self
.assertEqual(test_dict
['extractor'], 'Foo')
648 self
.assertEqual(test_dict
['playlist'], 'funny videos')
658 'title3': 'foo/bar\\test',
659 'title4': 'foo "bar" test',
661 'timestamp': 1618488000,
664 'playlist_autonumber': 2,
665 '__last_playlist_index': 100,
667 'formats': [{'id': 'id 1'}
, {'id': 'id 2'}
, {'id': 'id 3'}
]
670 def test_prepare_outtmpl_and_filename(self
):
671 def test(tmpl
, expected
, *, info
=None, **params
):
672 params
['outtmpl'] = tmpl
673 ydl
= YoutubeDL(params
)
674 ydl
._num
_downloads
= 1
675 self
.assertEqual(ydl
.validate_outtmpl(tmpl
), None)
677 out
= ydl
.evaluate_outtmpl(tmpl
, info
or self
.outtmpl_info
)
678 fname
= ydl
.prepare_filename(info
or self
.outtmpl_info
)
680 if not isinstance(expected
, (list, tuple)):
681 expected
= (expected
, expected
)
682 for (name
, got
), expect
in zip((('outtmpl', out
), ('filename', fname
)), expected
):
684 self
.assertTrue(expect(got
), f
'Wrong {name} from {tmpl}')
686 self
.assertEqual(got
, expect
, f
'Wrong {name} from {tmpl}')
689 original_infodict
= dict(self
.outtmpl_info
)
690 test('foo.bar', 'foo.bar')
691 original_infodict
['epoch'] = self
.outtmpl_info
.get('epoch')
692 self
.assertTrue(isinstance(original_infodict
['epoch'], int))
693 test('%(epoch)d', int_or_none
)
694 self
.assertEqual(original_infodict
, self
.outtmpl_info
)
696 # Auto-generated fields
697 test('%(id)s.%(ext)s', '1234.mp4')
698 test('%(duration_string)s', ('27:46:40', '27-46-40'))
699 test('%(resolution)s', '1080p')
700 test('%(playlist_index)s', '001')
701 test('%(playlist_autonumber)s', '02')
702 test('%(autonumber)s', '00001')
703 test('%(autonumber+2)03d', '005', autonumber_start
=3)
704 test('%(autonumber)s', '001', autonumber_size
=3)
713 test('%abc%', '%abc%')
714 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
715 test('%%%(height)s', '%1080')
716 test('%(width)06d.%(ext)s', 'NA.mp4')
717 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
718 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
721 test('%(id)s', '_abcd', info
={'id': '_abcd'}
)
722 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'}
)
723 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}
]})
724 test('%(id)s', '-abcd', info
={'id': '-abcd'}
)
725 test('%(id)s', '.abcd', info
={'id': '.abcd'}
)
726 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'}
)
727 test('%(id)s', ('ab:cd', 'ab -cd'), info
={'id': 'ab:cd'}
)
728 test('%(id.0)s', '-', info
={'id': '--'}
)
731 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
732 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
736 def expect_same_infodict(out
):
737 got_dict
= json
.loads(out
)
738 for info_field
, expected
in self
.outtmpl_info
.items():
739 self
.assertEqual(got_dict
.get(info_field
), expected
, info_field
)
742 test('%()j', (expect_same_infodict
, str))
745 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
746 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
747 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
748 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
749 test('%(non_existent.0)s', 'NA')
752 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
753 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
754 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
755 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
756 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
757 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
758 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
759 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
760 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
761 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
762 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
765 test('%(id)d', '1234')
766 test('%(height)c', '1')
768 test('%(id)d %(id)r', "1234 '1234'")
769 test('%(id)r %(height)r', "'1234' 1080")
770 test('%(ext)s-%(ext|def)d', 'mp4-def')
771 test('%(width|0)04d', '0000')
772 test('a%(width|)d', 'a', outtmpl_na_placeholder
='none')
774 FORMATS
= self
.outtmpl_info
['formats']
775 sanitize
= lambda x
: x
.replace(':', ' -').replace('"', "'").replace('\n', ' ')
777 # Custom type casting
778 test('%(formats.:.id)l', 'id 1, id 2, id 3')
779 test('%(formats.:.id)#l', ('id 1\nid 2\nid 3', 'id 1 id 2 id 3'))
780 test('%(ext)l', 'mp4')
781 test('%(formats.:.id) 18l', ' id 1, id 2, id 3')
782 test('%(formats)j', (json
.dumps(FORMATS
), sanitize(json
.dumps(FORMATS
))))
783 test('%(formats)#j', (json
.dumps(FORMATS
, indent
=4), sanitize(json
.dumps(FORMATS
, indent
=4))))
784 test('%(title5).3B', 'á')
785 test('%(title5)U', 'áéí 𝐀')
786 test('%(title5)#U', 'a\u0301e\u0301i\u0301 𝐀')
787 test('%(title5)+U', 'áéí A')
788 test('%(title5)+#U', 'a\u0301e\u0301i\u0301 A')
789 test('%(height)D', '1k')
790 test('%(filesize)#D', '1Ki')
791 test('%(height)5.2D', ' 1.08k')
792 test('%(title4)#S', 'foo_bar_test')
793 test('%(title4).10S', ('foo \'bar\' ', 'foo \'bar\'' + ('#' if compat_os_name
== 'nt' else ' ')))
794 if compat_os_name
== 'nt':
795 test('%(title4)q', ('"foo \\"bar\\" test"', "'foo _'bar_' test'"))
796 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', "'id 1' 'id 2' 'id 3'"))
797 test('%(formats.0.id)#q', ('"id 1"', "'id 1'"))
799 test('%(title4)q', ('\'foo "bar" test\'', "'foo 'bar' test'"))
800 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'")
801 test('%(formats.0.id)#q', "'id 1'")
803 # Internal formatting
804 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
805 test('%(title|%)s %(title|%%)s', '% %%')
806 test('%(id+1-height+3)05d', '00158')
807 test('%(width+100)05d', 'NA')
808 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], '% 15s' % sanitize(str(FORMATS
[0]))))
809 test('%(formats.0)r', (repr(FORMATS
[0]), sanitize(repr(FORMATS
[0]))))
810 test('%(height.0)03d', '001')
811 test('%(-height.0)04d', '-001')
812 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
813 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
814 test('%(formats.3)s', 'NA')
815 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
816 test('%(formats.0.id.-1+id)f', '1235.000000')
817 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
820 test('%(title,id)s', '1234')
821 test('%(width-100,height+20|def)d', '1100')
822 test('%(width-100,height+width|def)s', 'def')
823 test('%(timestamp-x>%H\\,%M\\,%S,timestamp>%H\\,%M\\,%S)s', '12,00,00')
826 test('%(id&foo)s.bar', 'foo.bar')
827 test('%(title&foo)s.bar', 'NA.bar')
828 test('%(title&foo|baz)s.bar', 'baz.bar')
829 test('%(x,id&foo|baz)s.bar', 'foo.bar')
830 test('%(x,title&foo|baz)s.bar', 'baz.bar')
835 raise self
.assertTrue(False, 'LazyList should not be evaluated till here')
836 test('%(key.4)s', '4', info
={'key': LazyList(gen())}
)
839 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
840 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
841 # test('%(foo|)s', ('', '_')) # fixme
843 # Environment variable expansion for prepare_filename
844 compat_setenv('__yt_dlp_var', 'expanded')
845 envvar
= '%__yt_dlp_var%' if compat_os_name
== 'nt' else '$__yt_dlp_var'
846 test(envvar
, (envvar
, 'expanded'))
847 if compat_os_name
== 'nt':
848 test('%s%', ('%s%', '%s%'))
849 compat_setenv('s', 'expanded')
850 test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
851 compat_setenv('(test)s', 'expanded')
852 test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
854 # Path expansion and escaping
855 test('Hello %(title1)s', 'Hello $PATH')
856 test('Hello %(title2)s', 'Hello %PATH%')
857 test('%(title3)s', ('foo/bar\\test', 'foo_bar_test'))
858 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo_bar_test' % os
.path
.sep
))
860 def test_format_note(self
):
862 self
.assertEqual(ydl
._format
_note
({}), '')
863 assertRegexpMatches(self
, ydl
._format
_note
({
866 assertRegexpMatches(self
, ydl
._format
_note
({
870 def test_postprocessors(self
):
871 filename
= 'post-processor-testfile.mp4'
872 audiofile
= filename
+ '.mp3'
874 class SimplePP(PostProcessor
):
876 with open(audiofile
, 'wt') as f
:
878 return [info
['filepath']], info
880 def run_pp(params
, PP
):
881 with open(filename
, 'wt') as f
:
883 ydl
= YoutubeDL(params
)
884 ydl
.add_post_processor(PP())
885 ydl
.post_process(filename
, {'filepath': filename}
)
887 run_pp({'keepvideo': True}
, SimplePP
)
888 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
889 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
893 run_pp({'keepvideo': False}
, SimplePP
)
894 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
895 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
898 class ModifierPP(PostProcessor
):
900 with open(info
['filepath'], 'wt') as f
:
904 run_pp({'keepvideo': False}
, ModifierPP
)
905 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
908 def test_match_filter(self
):
915 'filesize': 10 * 1024,
917 'uploader': "變態妍字幕版 太妍 тест",
918 'creator': "тест ' 123 ' тест--",
919 'webpage_url': 'http://example.com/watch?v=shenanigans',
927 'description': 'foo',
928 'filesize': 5 * 1024,
930 'uploader': "тест 123",
931 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
933 videos
= [first
, second
]
935 def get_videos(filter_
=None):
936 ydl
= YDL({'match_filter': filter_, 'simulate': True}
)
938 ydl
.process_ie_result(v
, download
=True)
939 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
942 self
.assertEqual(res
, ['1', '2'])
944 def f(v
, incomplete
):
948 return 'Video id is not 1'
950 self
.assertEqual(res
, ['1'])
952 f
= match_filter_func('duration < 30')
954 self
.assertEqual(res
, ['2'])
956 f
= match_filter_func('description = foo')
958 self
.assertEqual(res
, ['2'])
960 f
= match_filter_func('description =? foo')
962 self
.assertEqual(res
, ['1', '2'])
964 f
= match_filter_func('filesize > 5KiB')
966 self
.assertEqual(res
, ['1'])
968 f
= match_filter_func('playlist_id = 42')
970 self
.assertEqual(res
, ['1'])
972 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
974 self
.assertEqual(res
, ['1'])
976 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
978 self
.assertEqual(res
, ['2'])
980 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
982 self
.assertEqual(res
, ['1'])
984 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
986 self
.assertEqual(res
, ['1'])
988 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
990 self
.assertEqual(res
, [])
992 def test_playlist_items_selection(self
):
993 INDICES
, PAGE_SIZE
= list(range(1, 11)), 3
995 def entry(i
, evaluated
):
1003 def pagedlist_entries(evaluated
):
1005 start
= PAGE_SIZE
* n
1006 for i
in INDICES
[start
: start
+ PAGE_SIZE
]:
1007 yield entry(i
, evaluated
)
1008 return OnDemandPagedList(page_func
, PAGE_SIZE
)
1011 return (i
+ PAGE_SIZE
- 1) // PAGE_SIZE
1013 def generator_entries(evaluated
):
1015 yield entry(i
, evaluated
)
1017 def list_entries(evaluated
):
1018 return list(generator_entries(evaluated
))
1020 def lazylist_entries(evaluated
):
1021 return LazyList(generator_entries(evaluated
))
1023 def get_downloaded_info_dicts(params
, entries
):
1025 ydl
.process_ie_result({
1026 '_type': 'playlist',
1028 'extractor': 'test:playlist',
1029 'extractor_key': 'test:playlist',
1030 'webpage_url': 'http://example.com',
1033 return ydl
.downloaded_info_dicts
1035 def test_selection(params
, expected_ids
, evaluate_all
=False):
1036 expected_ids
= list(expected_ids
)
1038 generator_eval
= pagedlist_eval
= INDICES
1039 elif not expected_ids
:
1040 generator_eval
= pagedlist_eval
= []
1042 generator_eval
= INDICES
[0: max(expected_ids
)]
1043 pagedlist_eval
= INDICES
[PAGE_SIZE
* page_num(min(expected_ids
)) - PAGE_SIZE
:
1044 PAGE_SIZE
* page_num(max(expected_ids
))]
1046 for name
, func
, expected_eval
in (
1047 ('list', list_entries
, INDICES
),
1048 ('Generator', generator_entries
, generator_eval
),
1049 ('LazyList', lazylist_entries
, generator_eval
),
1050 ('PagedList', pagedlist_entries
, pagedlist_eval
),
1053 entries
= func(evaluated
)
1054 results
= [(v
['playlist_autonumber'] - 1, (int(v
['id']), v
['playlist_index']))
1055 for v
in get_downloaded_info_dicts(params
, entries
)]
1056 self
.assertEqual(results
, list(enumerate(zip(expected_ids
, expected_ids
))), f
'Entries of {name} for {params}')
1057 self
.assertEqual(sorted(evaluated
), expected_eval
, f
'Evaluation of {name} for {params}')
1058 test_selection({}, INDICES
)
1059 test_selection({'playlistend': 20}
, INDICES
, True)
1060 test_selection({'playlistend': 2}
, INDICES
[:2])
1061 test_selection({'playliststart': 11}
, [], True)
1062 test_selection({'playliststart': 2}
, INDICES
[1:])
1063 test_selection({'playlist_items': '2-4'}
, INDICES
[1:4])
1064 test_selection({'playlist_items': '2,4'}
, [2, 4])
1065 test_selection({'playlist_items': '20'}
, [], True)
1066 test_selection({'playlist_items': '0'}
, [])
1068 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
1069 test_selection({'playlist_items': '2-4,3-4,3'}
, [2, 3, 4])
1070 test_selection({'playlist_items': '4,2'}
, [4, 2])
1072 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720
1073 # https://github.com/yt-dlp/yt-dlp/issues/302
1074 test_selection({'playlistreverse': True}
, INDICES
[::-1])
1075 test_selection({'playliststart': 2, 'playlistreverse': True}
, INDICES
[:0:-1])
1076 test_selection({'playlist_items': '2,4', 'playlistreverse': True}
, [4, 2])
1077 test_selection({'playlist_items': '4,2'}
, [4, 2])
1079 # Tests for --playlist-items start:end:step
1080 test_selection({'playlist_items': ':'}
, INDICES
, True)
1081 test_selection({'playlist_items': '::1'}
, INDICES
, True)
1082 test_selection({'playlist_items': '::-1'}
, INDICES
[::-1], True)
1083 test_selection({'playlist_items': ':6'}
, INDICES
[:6])
1084 test_selection({'playlist_items': ':-6'}
, INDICES
[:-5], True)
1085 test_selection({'playlist_items': '-1:6:-2'}
, INDICES
[:4:-2], True)
1086 test_selection({'playlist_items': '9:-6:-2'}
, INDICES
[8:3:-2], True)
1088 test_selection({'playlist_items': '1:inf:2'}
, INDICES
[::2], True)
1089 test_selection({'playlist_items': '-2:inf'}
, INDICES
[-2:], True)
1090 test_selection({'playlist_items': ':inf:-1'}
, [], True)
1091 test_selection({'playlist_items': '0-2:2'}
, [2])
1092 test_selection({'playlist_items': '1-:2'}
, INDICES
[::2], True)
1093 test_selection({'playlist_items': '0--2:2'}
, INDICES
[1:-1:2], True)
1095 test_selection({'playlist_items': '10::3'}
, [10], True)
1096 test_selection({'playlist_items': '-1::3'}
, [10], True)
1097 test_selection({'playlist_items': '11::3'}
, [], True)
1098 test_selection({'playlist_items': '-15::2'}
, INDICES
[1::2], True)
1099 test_selection({'playlist_items': '-15::15'}
, [], True)
1101 def test_urlopen_no_file_protocol(self
):
1102 # see https://github.com/ytdl-org/youtube-dl/issues/8227
1104 self
.assertRaises(compat_urllib_error
.URLError
, ydl
.urlopen
, 'file:///etc/passwd')
1106 def test_do_not_override_ie_key_in_url_transparent(self
):
1109 class Foo1IE(InfoExtractor
):
1110 _VALID_URL
= r
'foo1:'
1112 def _real_extract(self
, url
):
1114 '_type': 'url_transparent',
1117 'title': 'foo1 title',
1121 class Foo2IE(InfoExtractor
):
1122 _VALID_URL
= r
'foo2:'
1124 def _real_extract(self
, url
):
1131 class Foo3IE(InfoExtractor
):
1132 _VALID_URL
= r
'foo3:'
1134 def _real_extract(self
, url
):
1135 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1137 ydl
.add_info_extractor(Foo1IE(ydl
))
1138 ydl
.add_info_extractor(Foo2IE(ydl
))
1139 ydl
.add_info_extractor(Foo3IE(ydl
))
1140 ydl
.extract_info('foo1:')
1141 downloaded
= ydl
.downloaded_info_dicts
[0]
1142 self
.assertEqual(downloaded
['url'], TEST_URL
)
1143 self
.assertEqual(downloaded
['title'], 'foo1 title')
1144 self
.assertEqual(downloaded
['id'], 'testid')
1145 self
.assertEqual(downloaded
['extractor'], 'testex')
1146 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1148 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1149 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1152 def __init__(self
, *args
, **kwargs
):
1153 super().__init
__(*args
, **kwargs
)
1155 def trouble(self
, s
, tb
=None):
1160 'ignoreerrors': True,
1163 class VideoIE(InfoExtractor
):
1164 _VALID_URL
= r
'video:(?P<id>\d+)'
1166 def _real_extract(self
, url
):
1167 video_id
= self
._match
_id
(url
)
1169 'format_id': 'default',
1173 raise ExtractorError('foo')
1176 'format_id': 'extra',
1181 'title': 'Video %s' % video_id
,
1185 class PlaylistIE(InfoExtractor
):
1186 _VALID_URL
= r
'playlist:'
1190 video_id
= compat_str(n
)
1192 '_type': 'url_transparent',
1193 'ie_key': VideoIE
.ie_key(),
1195 'url': 'video:%s' % video_id
,
1196 'title': 'Video Transparent %s' % video_id
,
1199 def _real_extract(self
, url
):
1200 return self
.playlist_result(self
._entries
())
1202 ydl
.add_info_extractor(VideoIE(ydl
))
1203 ydl
.add_info_extractor(PlaylistIE(ydl
))
1204 info
= ydl
.extract_info('playlist:')
1205 entries
= info
['entries']
1206 self
.assertEqual(len(entries
), 3)
1207 self
.assertTrue(entries
[0] is None)
1208 self
.assertTrue(entries
[1] is None)
1209 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1210 downloaded
= ydl
.downloaded_info_dicts
[0]
1211 entries
[2].pop('requested_downloads', None)
1212 self
.assertEqual(entries
[2], downloaded
)
1213 self
.assertEqual(downloaded
['url'], TEST_URL
)
1214 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1215 self
.assertEqual(downloaded
['id'], '2')
1216 self
.assertEqual(downloaded
['extractor'], 'Video')
1217 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1220 if __name__
== '__main__':