3 # Allow direct execution
8 sys
.path
.insert(0, os
.path
.dirname(os
.path
.dirname(os
.path
.abspath(__file__
))))
14 from test
.helper
import FakeYDL
, assertRegexpMatches
, try_rm
15 from yt_dlp
import YoutubeDL
16 from yt_dlp
.compat
import compat_os_name
17 from yt_dlp
.extractor
import YoutubeIE
18 from yt_dlp
.extractor
.common
import InfoExtractor
19 from yt_dlp
.postprocessor
.common
import PostProcessor
20 from yt_dlp
.utils
import (
27 from yt_dlp
.utils
.traversal
import traverse_obj
29 TEST_URL
= 'http://localhost/sample.mp4'
33 def __init__(self
, *args
, **kwargs
):
34 super().__init
__(*args
, **kwargs
)
35 self
.downloaded_info_dicts
= []
38 def process_info(self
, info_dict
):
39 self
.downloaded_info_dicts
.append(info_dict
.copy())
41 def to_screen(self
, msg
, *args
, **kwargs
):
44 def dl(self
, *args
, **kwargs
):
45 assert False, 'Downloader must not be invoked for test_YoutubeDL'
48 def _make_result(formats
, **kwargs
):
52 'title': 'testttitle',
53 'extractor': 'testex',
54 'extractor_key': 'TestEx',
55 'webpage_url': 'http://example.com/watch?v=shenanigans',
61 class TestFormatSelection(unittest
.TestCase
):
62 def test_prefer_free_formats(self
):
63 # Same resolution => download webm
65 ydl
.params
['prefer_free_formats'] = True
67 {'ext': 'webm', 'height': 460, 'url': TEST_URL}
,
68 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}
,
70 info_dict
= _make_result(formats
)
71 ydl
.sort_formats(info_dict
)
72 ydl
.process_ie_result(info_dict
)
73 downloaded
= ydl
.downloaded_info_dicts
[0]
74 self
.assertEqual(downloaded
['ext'], 'webm')
76 # Different resolution => download best quality (mp4)
78 ydl
.params
['prefer_free_formats'] = True
80 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
81 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}
,
83 info_dict
['formats'] = formats
84 ydl
.sort_formats(info_dict
)
85 ydl
.process_ie_result(info_dict
)
86 downloaded
= ydl
.downloaded_info_dicts
[0]
87 self
.assertEqual(downloaded
['ext'], 'mp4')
89 # No prefer_free_formats => prefer mp4 and webm
91 ydl
.params
['prefer_free_formats'] = False
93 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
94 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}
,
95 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
97 info_dict
['formats'] = formats
98 ydl
.sort_formats(info_dict
)
99 ydl
.process_ie_result(info_dict
)
100 downloaded
= ydl
.downloaded_info_dicts
[0]
101 self
.assertEqual(downloaded
['ext'], 'mp4')
104 ydl
.params
['prefer_free_formats'] = False
106 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
107 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
109 info_dict
['formats'] = formats
110 ydl
.sort_formats(info_dict
)
111 ydl
.process_ie_result(info_dict
)
112 downloaded
= ydl
.downloaded_info_dicts
[0]
113 self
.assertEqual(downloaded
['ext'], 'webm')
115 def test_format_selection(self
):
117 {'format_id': '35', 'ext': 'mp4', 'preference': 0, 'url': TEST_URL}
,
118 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL}
,
119 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL}
,
120 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL}
,
121 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL}
,
123 info_dict
= _make_result(formats
)
125 def test(inp
, *expected
, multi
=False):
128 'allow_multiple_video_streams': multi
,
129 'allow_multiple_audio_streams': multi
,
131 ydl
.process_ie_result(info_dict
.copy())
132 downloaded
= map(lambda x
: x
['format_id'], ydl
.downloaded_info_dicts
)
133 self
.assertEqual(list(downloaded
), list(expected
))
136 test('20/71/worst', '35')
138 test('webm/mp4', '47')
139 test('3gp/40/mp4', '35')
140 test('example-with-dashes', 'example-with-dashes')
141 test('all', '2', '47', '45', 'example-with-dashes', '35')
142 test('mergeall', '2+47+45+example-with-dashes+35', multi
=True)
144 def test_format_selection_audio(self
):
146 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
147 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
148 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}
,
149 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL}
,
151 info_dict
= _make_result(formats
)
153 ydl
= YDL({'format': 'bestaudio'}
)
154 ydl
.process_ie_result(info_dict
.copy())
155 downloaded
= ydl
.downloaded_info_dicts
[0]
156 self
.assertEqual(downloaded
['format_id'], 'audio-high')
158 ydl
= YDL({'format': 'worstaudio'}
)
159 ydl
.process_ie_result(info_dict
.copy())
160 downloaded
= ydl
.downloaded_info_dicts
[0]
161 self
.assertEqual(downloaded
['format_id'], 'audio-low')
164 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
165 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL}
,
167 info_dict
= _make_result(formats
)
169 ydl
= YDL({'format': 'bestaudio/worstaudio/best'}
)
170 ydl
.process_ie_result(info_dict
.copy())
171 downloaded
= ydl
.downloaded_info_dicts
[0]
172 self
.assertEqual(downloaded
['format_id'], 'vid-high')
174 def test_format_selection_audio_exts(self
):
176 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
177 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
178 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
179 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
180 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
183 info_dict
= _make_result(formats
)
184 ydl
= YDL({'format': 'best'}
)
185 ydl
.sort_formats(info_dict
)
186 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
187 downloaded
= ydl
.downloaded_info_dicts
[0]
188 self
.assertEqual(downloaded
['format_id'], 'aac-64')
190 ydl
= YDL({'format': 'mp3'}
)
191 ydl
.sort_formats(info_dict
)
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}
)
197 ydl
.sort_formats(info_dict
)
198 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
199 downloaded
= ydl
.downloaded_info_dicts
[0]
200 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
202 def test_format_selection_video(self
):
204 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
205 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
206 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
208 info_dict
= _make_result(formats
)
210 ydl
= YDL({'format': 'bestvideo'}
)
211 ydl
.process_ie_result(info_dict
.copy())
212 downloaded
= ydl
.downloaded_info_dicts
[0]
213 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
215 ydl
= YDL({'format': 'worstvideo'}
)
216 ydl
.process_ie_result(info_dict
.copy())
217 downloaded
= ydl
.downloaded_info_dicts
[0]
218 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
220 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
221 ydl
.process_ie_result(info_dict
.copy())
222 downloaded
= ydl
.downloaded_info_dicts
[0]
223 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
226 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
228 info_dict
= _make_result(formats
)
230 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
231 ydl
.process_ie_result(info_dict
.copy())
232 downloaded
= ydl
.downloaded_info_dicts
[0]
233 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
235 def test_format_selection_string_ops(self
):
237 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
238 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
240 info_dict
= _make_result(formats
)
243 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
244 ydl
.process_ie_result(info_dict
.copy())
245 downloaded
= ydl
.downloaded_info_dicts
[0]
246 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
248 # does not equal (!=)
249 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
250 ydl
.process_ie_result(info_dict
.copy())
251 downloaded
= ydl
.downloaded_info_dicts
[0]
252 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
254 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
255 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
258 ydl
= YDL({'format': '[format_id^=abc]'}
)
259 ydl
.process_ie_result(info_dict
.copy())
260 downloaded
= ydl
.downloaded_info_dicts
[0]
261 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
263 # does not start with (!^=)
264 ydl
= YDL({'format': '[format_id!^=abc]'}
)
265 ydl
.process_ie_result(info_dict
.copy())
266 downloaded
= ydl
.downloaded_info_dicts
[0]
267 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
269 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
270 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
273 ydl
= YDL({'format': '[format_id$=cba]'}
)
274 ydl
.process_ie_result(info_dict
.copy())
275 downloaded
= ydl
.downloaded_info_dicts
[0]
276 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
278 # does not end with (!$=)
279 ydl
= YDL({'format': '[format_id!$=cba]'}
)
280 ydl
.process_ie_result(info_dict
.copy())
281 downloaded
= ydl
.downloaded_info_dicts
[0]
282 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
284 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
285 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
288 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
289 ydl
.process_ie_result(info_dict
.copy())
290 downloaded
= ydl
.downloaded_info_dicts
[0]
291 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
293 # does not contain (!*=)
294 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
295 ydl
.process_ie_result(info_dict
.copy())
296 downloaded
= ydl
.downloaded_info_dicts
[0]
297 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
299 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
300 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
302 ydl
= YDL({'format': '[format_id!*=-]'}
)
303 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
305 def test_youtube_format_selection(self
):
306 # FIXME: Rewrite in accordance with the new format sorting options
310 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
311 # Apple HTTP Live Streaming
312 '96', '95', '94', '93', '92', '132', '151',
314 '85', '84', '102', '83', '101', '82', '100',
316 '137', '248', '136', '247', '135', '246',
317 '245', '244', '134', '243', '133', '242', '160',
319 '141', '172', '140', '171', '139',
322 def format_info(f_id
):
323 info
= YoutubeIE
._formats
[f_id
].copy()
325 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
326 # and 'vcodec', while in tests such information is incomplete since
327 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
328 # test_YoutubeDL.test_youtube_format_selection is broken without
330 if 'acodec' in info
and 'vcodec' not in info
:
331 info
['vcodec'] = 'none'
332 elif 'vcodec' in info
and 'acodec' not in info
:
333 info
['acodec'] = 'none'
335 info
['format_id'] = f_id
336 info
['url'] = 'url:' + f_id
338 formats_order
= [format_info(f_id
) for f_id
in order
]
340 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
341 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
342 ydl
.sort_formats(info_dict
)
343 ydl
.process_ie_result(info_dict
)
344 downloaded
= ydl
.downloaded_info_dicts
[0]
345 self
.assertEqual(downloaded
['format_id'], '248+172')
346 self
.assertEqual(downloaded
['ext'], 'mp4')
348 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
349 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
350 ydl
.sort_formats(info_dict
)
351 ydl
.process_ie_result(info_dict
)
352 downloaded
= ydl
.downloaded_info_dicts
[0]
353 self
.assertEqual(downloaded
['format_id'], '38')
355 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
356 ydl
= YDL({'format': 'bestvideo/best,bestaudio'}
)
357 ydl
.sort_formats(info_dict
)
358 ydl
.process_ie_result(info_dict
)
359 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
360 self
.assertEqual(downloaded_ids
, ['137', '141'])
362 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
363 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'}
)
364 ydl
.sort_formats(info_dict
)
365 ydl
.process_ie_result(info_dict
)
366 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
367 self
.assertEqual(downloaded_ids
, ['137+141', '248+141'])
369 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
370 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'}
)
371 ydl
.sort_formats(info_dict
)
372 ydl
.process_ie_result(info_dict
)
373 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
374 self
.assertEqual(downloaded_ids
, ['136+141', '247+141'])
376 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
377 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}
)
378 ydl
.sort_formats(info_dict
)
379 ydl
.process_ie_result(info_dict
)
380 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
381 self
.assertEqual(downloaded_ids
, ['248+141'])
383 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
384 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
385 ydl
= YDL({'format': 'best/bestvideo'}
)
386 ydl
.sort_formats(info_dict
)
387 ydl
.process_ie_result(info_dict
)
388 downloaded
= ydl
.downloaded_info_dicts
[0]
389 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
391 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
392 ydl
= YDL({'format': 'best/bestvideo'}
)
393 ydl
.sort_formats(info_dict
)
394 ydl
.process_ie_result(info_dict
)
395 downloaded
= ydl
.downloaded_info_dicts
[0]
396 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
398 def test_audio_only_extractor_format_selection(self
):
399 # For extractors with incomplete formats (all formats are audio-only or
400 # video-only) best and worst should fallback to corresponding best/worst
401 # video-only or audio-only formats (as per
402 # https://github.com/ytdl-org/youtube-dl/pull/5556)
404 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
405 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
407 info_dict
= _make_result(formats
)
409 ydl
= YDL({'format': 'best'}
)
410 ydl
.process_ie_result(info_dict
.copy())
411 downloaded
= ydl
.downloaded_info_dicts
[0]
412 self
.assertEqual(downloaded
['format_id'], 'high')
414 ydl
= YDL({'format': 'worst'}
)
415 ydl
.process_ie_result(info_dict
.copy())
416 downloaded
= ydl
.downloaded_info_dicts
[0]
417 self
.assertEqual(downloaded
['format_id'], 'low')
419 def test_format_not_available(self
):
421 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
422 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
424 info_dict
= _make_result(formats
)
426 # This must fail since complete video-audio format does not match filter
427 # and extractor does not provide incomplete only formats (i.e. only
428 # video-only or audio-only).
429 ydl
= YDL({'format': 'best[height>360]'}
)
430 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
432 def test_format_selection_issue_10083(self
):
433 # See https://github.com/ytdl-org/youtube-dl/issues/10083
435 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
436 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
437 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
439 info_dict
= _make_result(formats
)
441 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
442 ydl
.process_ie_result(info_dict
.copy())
443 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
445 def test_invalid_format_specs(self
):
446 def assert_syntax_error(format_spec
):
447 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec}
)
449 assert_syntax_error('bestvideo,,best')
450 assert_syntax_error('+bestaudio')
451 assert_syntax_error('bestvideo+')
452 assert_syntax_error('/')
453 assert_syntax_error('[720<height]')
455 def test_format_filtering(self
):
457 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
458 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
459 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
460 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
461 {'format_id': 'E', 'filesize': 3000}
,
463 {'format_id': 'G', 'filesize': 1000000}
,
466 f
['url'] = 'http://_/'
468 info_dict
= _make_result(formats
, _format_sort_fields
=('id', ))
470 ydl
= YDL({'format': 'best[filesize<3000]'}
)
471 ydl
.process_ie_result(info_dict
)
472 downloaded
= ydl
.downloaded_info_dicts
[0]
473 self
.assertEqual(downloaded
['format_id'], 'D')
475 ydl
= YDL({'format': 'best[filesize<=3000]'}
)
476 ydl
.process_ie_result(info_dict
)
477 downloaded
= ydl
.downloaded_info_dicts
[0]
478 self
.assertEqual(downloaded
['format_id'], 'E')
480 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
481 ydl
.process_ie_result(info_dict
)
482 downloaded
= ydl
.downloaded_info_dicts
[0]
483 self
.assertEqual(downloaded
['format_id'], 'F')
485 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'}
)
486 ydl
.process_ie_result(info_dict
)
487 downloaded
= ydl
.downloaded_info_dicts
[0]
488 self
.assertEqual(downloaded
['format_id'], 'B')
490 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
491 ydl
.process_ie_result(info_dict
)
492 downloaded
= ydl
.downloaded_info_dicts
[0]
493 self
.assertEqual(downloaded
['format_id'], 'C')
495 ydl
= YDL({'format': '[filesize>?1]'}
)
496 ydl
.process_ie_result(info_dict
)
497 downloaded
= ydl
.downloaded_info_dicts
[0]
498 self
.assertEqual(downloaded
['format_id'], 'G')
500 ydl
= YDL({'format': '[filesize<1M]'}
)
501 ydl
.process_ie_result(info_dict
)
502 downloaded
= ydl
.downloaded_info_dicts
[0]
503 self
.assertEqual(downloaded
['format_id'], 'E')
505 ydl
= YDL({'format': '[filesize<1MiB]'}
)
506 ydl
.process_ie_result(info_dict
)
507 downloaded
= ydl
.downloaded_info_dicts
[0]
508 self
.assertEqual(downloaded
['format_id'], 'G')
510 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
511 ydl
.process_ie_result(info_dict
)
512 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
513 self
.assertEqual(downloaded_ids
, ['D', 'C', 'B'])
515 ydl
= YDL({'format': 'best[height<40]'}
)
517 ydl
.process_ie_result(info_dict
)
518 except ExtractorError
:
520 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
522 def test_default_format_spec(self
):
523 ydl
= YDL({'simulate': True}
)
524 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
527 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
529 ydl
= YDL({'simulate': True}
)
530 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
532 ydl
= YDL({'outtmpl': '-'}
)
533 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
536 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
537 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
540 class TestYoutubeDL(unittest
.TestCase
):
541 def test_subtitles(self
):
542 def s_formats(lang
, autocaption
=False):
545 'url': f
'http://localhost/video.{lang}.{ext}',
546 '_auto': autocaption
,
547 } for ext
in ['vtt', 'srt', 'ass']]
548 subtitles
= {l: s_formats(l) for l in ['en', 'fr', 'es']}
549 auto_captions
= {l: s_formats(l, True) for l in ['it', 'pt', 'es']}
553 'url': 'http://localhost/video.mp4',
554 'subtitles': subtitles
,
555 'automatic_captions': auto_captions
,
557 'webpage_url': 'http://example.com/watch?v=shenanigans',
560 def get_info(params
={}):
561 params
.setdefault('simulate', True)
563 ydl
.report_warning
= lambda *args
, **kargs
: None
564 return ydl
.process_video_result(info_dict
, download
=False)
567 self
.assertFalse(result
.get('requested_subtitles'))
568 self
.assertEqual(result
['subtitles'], subtitles
)
569 self
.assertEqual(result
['automatic_captions'], auto_captions
)
571 result
= get_info({'writesubtitles': True}
)
572 subs
= result
['requested_subtitles']
573 self
.assertTrue(subs
)
574 self
.assertEqual(set(subs
.keys()), {'en'}
)
575 self
.assertTrue(subs
['en'].get('data') is None)
576 self
.assertEqual(subs
['en']['ext'], 'ass')
578 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
579 subs
= result
['requested_subtitles']
580 self
.assertEqual(subs
['en']['ext'], 'srt')
582 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
583 subs
= result
['requested_subtitles']
584 self
.assertTrue(subs
)
585 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
587 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
588 subs
= result
['requested_subtitles']
589 self
.assertTrue(subs
)
590 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
592 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
593 subs
= result
['requested_subtitles']
594 self
.assertTrue(subs
)
595 self
.assertEqual(set(subs
.keys()), {'fr'}
)
597 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
598 subs
= result
['requested_subtitles']
599 self
.assertTrue(subs
)
600 self
.assertEqual(set(subs
.keys()), {'en'}
)
602 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
603 subs
= result
['requested_subtitles']
604 self
.assertTrue(subs
)
605 self
.assertEqual(set(subs
.keys()), {'es', 'en'}
)
607 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
608 subs
= result
['requested_subtitles']
609 self
.assertTrue(subs
)
610 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
611 self
.assertFalse(subs
['es']['_auto'])
612 self
.assertTrue(subs
['pt']['_auto'])
614 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
615 subs
= result
['requested_subtitles']
616 self
.assertTrue(subs
)
617 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
618 self
.assertTrue(subs
['es']['_auto'])
619 self
.assertTrue(subs
['pt']['_auto'])
621 def test_add_extra_info(self
):
627 'playlist': 'funny videos',
629 YDL
.add_extra_info(test_dict
, extra_info
)
630 self
.assertEqual(test_dict
['extractor'], 'Foo')
631 self
.assertEqual(test_dict
['playlist'], 'funny videos')
642 'title3': 'foo/bar\\test',
643 'title4': 'foo "bar" test',
645 'timestamp': 1618488000,
648 'playlist_autonumber': 2,
649 '__last_playlist_index': 100,
652 {'id': 'id 1', 'height': 1080, 'width': 1920}
,
653 {'id': 'id 2', 'height': 720}
,
658 def test_prepare_outtmpl_and_filename(self
):
659 def test(tmpl
, expected
, *, info
=None, **params
):
660 params
['outtmpl'] = tmpl
661 ydl
= FakeYDL(params
)
662 ydl
._num
_downloads
= 1
663 self
.assertEqual(ydl
.validate_outtmpl(tmpl
), None)
665 out
= ydl
.evaluate_outtmpl(tmpl
, info
or self
.outtmpl_info
)
666 fname
= ydl
.prepare_filename(info
or self
.outtmpl_info
)
668 if not isinstance(expected
, (list, tuple)):
669 expected
= (expected
, expected
)
670 for (name
, got
), expect
in zip((('outtmpl', out
), ('filename', fname
)), expected
):
672 self
.assertTrue(expect(got
), f
'Wrong {name} from {tmpl}')
673 elif expect
is not None:
674 self
.assertEqual(got
, expect
, f
'Wrong {name} from {tmpl}')
677 original_infodict
= dict(self
.outtmpl_info
)
678 test('foo.bar', 'foo.bar')
679 original_infodict
['epoch'] = self
.outtmpl_info
.get('epoch')
680 self
.assertTrue(isinstance(original_infodict
['epoch'], int))
681 test('%(epoch)d', int_or_none
)
682 self
.assertEqual(original_infodict
, self
.outtmpl_info
)
684 # Auto-generated fields
685 test('%(id)s.%(ext)s', '1234.mp4')
686 test('%(duration_string)s', ('27:46:40', '27-46-40'))
687 test('%(resolution)s', '1080p')
688 test('%(playlist_index|)s', '001')
689 test('%(playlist_index&{}!)s', '1!')
690 test('%(playlist_autonumber)s', '02')
691 test('%(autonumber)s', '00001')
692 test('%(autonumber+2)03d', '005', autonumber_start
=3)
693 test('%(autonumber)s', '001', autonumber_size
=3)
702 test('%abc%', '%abc%')
703 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
704 test('%%%(height)s', '%1080')
705 test('%(width)06d.%(ext)s', 'NA.mp4')
706 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
707 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
710 test('%(id)s', '_abcd', info
={'id': '_abcd'}
)
711 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'}
)
712 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}
]})
713 test('%(id)s', '-abcd', info
={'id': '-abcd'}
)
714 test('%(id)s', '.abcd', info
={'id': '.abcd'}
)
715 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'}
)
716 test('%(id)s', ('ab:cd', 'ab:cd'), info
={'id': 'ab:cd'}
)
717 test('%(id.0)s', '-', info
={'id': '--'}
)
720 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
721 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
723 test('%(formats.{id)s', 'NA')
726 def expect_same_infodict(out
):
727 got_dict
= json
.loads(out
)
728 for info_field
, expected
in self
.outtmpl_info
.items():
729 self
.assertEqual(got_dict
.get(info_field
), expected
, info_field
)
732 test('%()j', (expect_same_infodict
, str))
735 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
736 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
737 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
738 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
739 test('%(non_existent.0)s', 'NA')
742 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
743 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
744 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
745 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
746 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
747 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
748 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
749 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
750 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
751 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
752 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
755 test('%(id)d', '1234')
756 test('%(height)c', '1')
758 test('%(id)d %(id)r', "1234 '1234'")
759 test('%(id)r %(height)r', "'1234' 1080")
760 test('%(title5)a %(height)a', (R
"'\xe1\xe9\xed \U0001d400' 1080", None))
761 test('%(ext)s-%(ext|def)d', 'mp4-def')
762 test('%(width|0)04d', '0')
763 test('a%(width|b)d', 'ab', outtmpl_na_placeholder
='none')
765 FORMATS
= self
.outtmpl_info
['formats']
767 # Custom type casting
768 test('%(formats.:.id)l', 'id 1, id 2, id 3')
769 test('%(formats.:.id)#l', ('id 1\nid 2\nid 3', 'id 1 id 2 id 3'))
770 test('%(ext)l', 'mp4')
771 test('%(formats.:.id) 18l', ' id 1, id 2, id 3')
772 test('%(formats)j', (json
.dumps(FORMATS
), None))
773 test('%(formats)#j', (
774 json
.dumps(FORMATS
, indent
=4),
775 json
.dumps(FORMATS
, indent
=4).replace(':', ':').replace('"', """).replace('\n', ' ')
777 test('%(title5).3B', 'á')
778 test('%(title5)U', 'áéí 𝐀')
779 test('%(title5)#U', 'a\u0301e\u0301i\u0301 𝐀')
780 test('%(title5)+U', 'áéí A')
781 test('%(title5)+#U', 'a\u0301e\u0301i\u0301 A')
782 test('%(height)D', '1k')
783 test('%(filesize)#D', '1Ki')
784 test('%(height)5.2D', ' 1.08k')
785 test('%(title4)#S', 'foo_bar_test')
786 test('%(title4).10S', ('foo "bar" ', 'foo "bar"' + ('#' if compat_os_name
== 'nt' else ' ')))
787 if compat_os_name
== 'nt':
788 test('%(title4)q', ('"foo \\"bar\\" test"', ""foo ⧹"bar⧹" test""))
789 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', '"id 1" "id 2" "id 3"'))
790 test('%(formats.0.id)#q', ('"id 1"', '"id 1"'))
792 test('%(title4)q', ('\'foo "bar" test\'', '\'foo "bar" test\''))
793 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'")
794 test('%(formats.0.id)#q', "'id 1'")
796 # Internal formatting
797 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
798 test('%(title|%)s %(title|%%)s', '% %%')
799 test('%(id+1-height+3)05d', '00158')
800 test('%(width+100)05d', 'NA')
801 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], None))
802 test('%(formats.0)r', (repr(FORMATS
[0]), None))
803 test('%(height.0)03d', '001')
804 test('%(-height.0)04d', '-001')
805 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
806 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
807 test('%(formats.3)s', 'NA')
808 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
809 test('%(formats.0.id.-1+id)f', '1235.000000')
810 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
811 out
= json
.dumps([{'id': f['id'], 'height.:2': str(f['height'])[:2]}
812 if 'height' in f
else {'id': f['id']}
814 test('%(formats.:.{id,height.:2})j', (out
, None))
815 test('%(formats.:.{id,height}.id)l', ', '.join(f
['id'] for f
in FORMATS
))
816 test('%(.{id,title})j', ('{"id": "1234"}', '{"id": "1234"}'))
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')
830 test('%(id&a\nb|)s', ('a\nb', 'a b'))
831 test('%(id&hi {:>10} {}|)s', 'hi 1234 1234')
832 test(R
'%(id&{0} {}|)s', 'NA')
833 test(R
'%(id&{0.1}|)s', 'NA')
834 test('%(height&{:,d})S', '1,080')
839 raise self
.assertTrue(False, 'LazyList should not be evaluated till here')
840 test('%(key.4)s', '4', info
={'key': LazyList(gen())}
)
843 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
844 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
845 # test('%(foo|)s', ('', '_')) # fixme
847 # Environment variable expansion for prepare_filename
848 os
.environ
['__yt_dlp_var'] = 'expanded'
849 envvar
= '%__yt_dlp_var%' if compat_os_name
== 'nt' else '$__yt_dlp_var'
850 test(envvar
, (envvar
, 'expanded'))
851 if compat_os_name
== 'nt':
852 test('%s%', ('%s%', '%s%'))
853 os
.environ
['s'] = 'expanded'
854 test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
855 os
.environ
['(test)s'] = 'expanded'
856 test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
858 # Path expansion and escaping
859 test('Hello %(title1)s', 'Hello $PATH')
860 test('Hello %(title2)s', 'Hello %PATH%')
861 test('%(title3)s', ('foo/bar\\test', 'foo⧸bar⧹test'))
862 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo⧸bar⧹test' % os
.path
.sep
))
864 def test_format_note(self
):
866 self
.assertEqual(ydl
._format
_note
({}), '')
867 assertRegexpMatches(self
, ydl
._format
_note
({
870 assertRegexpMatches(self
, ydl
._format
_note
({
874 def test_postprocessors(self
):
875 filename
= 'post-processor-testfile.mp4'
876 audiofile
= filename
+ '.mp3'
878 class SimplePP(PostProcessor
):
880 with open(audiofile
, 'w') as f
:
882 return [info
['filepath']], info
884 def run_pp(params
, PP
):
885 with open(filename
, 'w') as f
:
887 ydl
= YoutubeDL(params
)
888 ydl
.add_post_processor(PP())
889 ydl
.post_process(filename
, {'filepath': filename}
)
891 run_pp({'keepvideo': True}
, SimplePP
)
892 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
893 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
897 run_pp({'keepvideo': False}
, SimplePP
)
898 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
899 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
902 class ModifierPP(PostProcessor
):
904 with open(info
['filepath'], 'w') as f
:
908 run_pp({'keepvideo': False}
, ModifierPP
)
909 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
912 def test_match_filter(self
):
919 'filesize': 10 * 1024,
921 'uploader': "變態妍字幕版 太妍 тест",
922 'creator': "тест ' 123 ' тест--",
923 'webpage_url': 'http://example.com/watch?v=shenanigans',
931 'description': 'foo',
932 'filesize': 5 * 1024,
934 'uploader': "тест 123",
935 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
937 videos
= [first
, second
]
939 def get_videos(filter_
=None):
940 ydl
= YDL({'match_filter': filter_, 'simulate': True}
)
942 ydl
.process_ie_result(v
, download
=True)
943 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
946 self
.assertEqual(res
, ['1', '2'])
948 def f(v
, incomplete
):
952 return 'Video id is not 1'
954 self
.assertEqual(res
, ['1'])
956 f
= match_filter_func('duration < 30')
958 self
.assertEqual(res
, ['2'])
960 f
= match_filter_func('description = foo')
962 self
.assertEqual(res
, ['2'])
964 f
= match_filter_func('description =? foo')
966 self
.assertEqual(res
, ['1', '2'])
968 f
= match_filter_func('filesize > 5KiB')
970 self
.assertEqual(res
, ['1'])
972 f
= match_filter_func('playlist_id = 42')
974 self
.assertEqual(res
, ['1'])
976 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
978 self
.assertEqual(res
, ['1'])
980 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
982 self
.assertEqual(res
, ['2'])
984 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
986 self
.assertEqual(res
, ['1'])
988 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
990 self
.assertEqual(res
, ['1'])
992 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
994 self
.assertEqual(res
, [])
996 def test_playlist_items_selection(self
):
997 INDICES
, PAGE_SIZE
= list(range(1, 11)), 3
999 def entry(i
, evaluated
):
1007 def pagedlist_entries(evaluated
):
1009 start
= PAGE_SIZE
* n
1010 for i
in INDICES
[start
: start
+ PAGE_SIZE
]:
1011 yield entry(i
, evaluated
)
1012 return OnDemandPagedList(page_func
, PAGE_SIZE
)
1015 return (i
+ PAGE_SIZE
- 1) // PAGE_SIZE
1017 def generator_entries(evaluated
):
1019 yield entry(i
, evaluated
)
1021 def list_entries(evaluated
):
1022 return list(generator_entries(evaluated
))
1024 def lazylist_entries(evaluated
):
1025 return LazyList(generator_entries(evaluated
))
1027 def get_downloaded_info_dicts(params
, entries
):
1029 ydl
.process_ie_result({
1030 '_type': 'playlist',
1032 'extractor': 'test:playlist',
1033 'extractor_key': 'test:playlist',
1034 'webpage_url': 'http://example.com',
1037 return ydl
.downloaded_info_dicts
1039 def test_selection(params
, expected_ids
, evaluate_all
=False):
1040 expected_ids
= list(expected_ids
)
1042 generator_eval
= pagedlist_eval
= INDICES
1043 elif not expected_ids
:
1044 generator_eval
= pagedlist_eval
= []
1046 generator_eval
= INDICES
[0: max(expected_ids
)]
1047 pagedlist_eval
= INDICES
[PAGE_SIZE
* page_num(min(expected_ids
)) - PAGE_SIZE
:
1048 PAGE_SIZE
* page_num(max(expected_ids
))]
1050 for name
, func
, expected_eval
in (
1051 ('list', list_entries
, INDICES
),
1052 ('Generator', generator_entries
, generator_eval
),
1053 # ('LazyList', lazylist_entries, generator_eval), # Generator and LazyList follow the exact same code path
1054 ('PagedList', pagedlist_entries
, pagedlist_eval
),
1057 entries
= func(evaluated
)
1058 results
= [(v
['playlist_autonumber'] - 1, (int(v
['id']), v
['playlist_index']))
1059 for v
in get_downloaded_info_dicts(params
, entries
)]
1060 self
.assertEqual(results
, list(enumerate(zip(expected_ids
, expected_ids
))), f
'Entries of {name} for {params}')
1061 self
.assertEqual(sorted(evaluated
), expected_eval
, f
'Evaluation of {name} for {params}')
1063 test_selection({}, INDICES
)
1064 test_selection({'playlistend': 20}
, INDICES
, True)
1065 test_selection({'playlistend': 2}
, INDICES
[:2])
1066 test_selection({'playliststart': 11}
, [], True)
1067 test_selection({'playliststart': 2}
, INDICES
[1:])
1068 test_selection({'playlist_items': '2-4'}
, INDICES
[1:4])
1069 test_selection({'playlist_items': '2,4'}
, [2, 4])
1070 test_selection({'playlist_items': '20'}
, [], True)
1071 test_selection({'playlist_items': '0'}
, [])
1073 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
1074 test_selection({'playlist_items': '2-4,3-4,3'}
, [2, 3, 4])
1075 test_selection({'playlist_items': '4,2'}
, [4, 2])
1077 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720
1078 # https://github.com/yt-dlp/yt-dlp/issues/302
1079 test_selection({'playlistreverse': True}
, INDICES
[::-1])
1080 test_selection({'playliststart': 2, 'playlistreverse': True}
, INDICES
[:0:-1])
1081 test_selection({'playlist_items': '2,4', 'playlistreverse': True}
, [4, 2])
1082 test_selection({'playlist_items': '4,2'}
, [4, 2])
1084 # Tests for --playlist-items start:end:step
1085 test_selection({'playlist_items': ':'}
, INDICES
, True)
1086 test_selection({'playlist_items': '::1'}
, INDICES
, True)
1087 test_selection({'playlist_items': '::-1'}
, INDICES
[::-1], True)
1088 test_selection({'playlist_items': ':6'}
, INDICES
[:6])
1089 test_selection({'playlist_items': ':-6'}
, INDICES
[:-5], True)
1090 test_selection({'playlist_items': '-1:6:-2'}
, INDICES
[:4:-2], True)
1091 test_selection({'playlist_items': '9:-6:-2'}
, INDICES
[8:3:-2], True)
1093 test_selection({'playlist_items': '1:inf:2'}
, INDICES
[::2], True)
1094 test_selection({'playlist_items': '-2:inf'}
, INDICES
[-2:], True)
1095 test_selection({'playlist_items': ':inf:-1'}
, [], True)
1096 test_selection({'playlist_items': '0-2:2'}
, [2])
1097 test_selection({'playlist_items': '1-:2'}
, INDICES
[::2], True)
1098 test_selection({'playlist_items': '0--2:2'}
, INDICES
[1:-1:2], True)
1100 test_selection({'playlist_items': '10::3'}
, [10], True)
1101 test_selection({'playlist_items': '-1::3'}
, [10], True)
1102 test_selection({'playlist_items': '11::3'}
, [], True)
1103 test_selection({'playlist_items': '-15::2'}
, INDICES
[1::2], True)
1104 test_selection({'playlist_items': '-15::15'}
, [], True)
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:'
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')
1219 def test_header_cookies(self
):
1220 from http
.cookiejar
import Cookie
1223 ydl
.report_warning
= lambda *_
, **__
: None
1225 def cookie(name
, value
, version
=None, domain
='', path
='', secure
=False, expires
=None):
1227 version
or 0, name
, value
, None, False,
1228 domain
, bool(domain
), bool(domain
), path
, bool(path
),
1229 secure
, expires
, False, None, None, rest
={})
1231 _test_url
= 'https://yt.dlp/test'
1233 def test(encoded_cookies
, cookies
, *, headers
=False, round_trip
=None, error_re
=None):
1235 ydl
.cookiejar
.clear()
1236 ydl
._load
_cookies
(encoded_cookies
, autoscope
=headers
)
1238 ydl
._apply
_header
_cookies
(_test_url
)
1239 data
= {'url': _test_url}
1240 ydl
._calc
_headers
(data
)
1241 self
.assertCountEqual(
1242 map(vars, ydl
.cookiejar
), map(vars, cookies
),
1243 'Extracted cookiejar.Cookie is not the same')
1246 data
.get('cookies'), round_trip
or encoded_cookies
,
1247 'Cookie is not the same as round trip')
1248 ydl
.__dict
__['_YoutubeDL__header_cookies'] = []
1250 with self
.subTest(msg
=encoded_cookies
):
1254 with self
.assertRaisesRegex(Exception, error_re
):
1257 test('test=value; Domain=.yt.dlp', [cookie('test', 'value', domain
='.yt.dlp')])
1258 test('test=value', [cookie('test', 'value')], error_re
=r
'Unscoped cookies are not allowed')
1259 test('cookie1=value1; Domain=.yt.dlp; Path=/test; cookie2=value2; Domain=.yt.dlp; Path=/', [
1260 cookie('cookie1', 'value1', domain
='.yt.dlp', path
='/test'),
1261 cookie('cookie2', 'value2', domain
='.yt.dlp', path
='/')])
1262 test('test=value; Domain=.yt.dlp; Path=/test; Secure; Expires=9999999999', [
1263 cookie('test', 'value', domain
='.yt.dlp', path
='/test', secure
=True, expires
=9999999999)])
1264 test('test="value; "; path=/test; domain=.yt.dlp', [
1265 cookie('test', 'value; ', domain
='.yt.dlp', path
='/test')],
1266 round_trip
='test="value\\073 "; Domain=.yt.dlp; Path=/test')
1267 test('name=; Domain=.yt.dlp', [cookie('name', '', domain
='.yt.dlp')],
1268 round_trip
='name=""; Domain=.yt.dlp')
1270 test('test=value', [cookie('test', 'value', domain
='.yt.dlp')], headers
=True)
1271 test('cookie1=value; Domain=.yt.dlp; cookie2=value', [], headers
=True, error_re
=r
'Invalid syntax')
1272 ydl
.deprecated_feature
= ydl
.report_error
1273 test('test=value', [], headers
=True, error_re
=r
'Passing cookies as a header is a potential security risk')
1275 def test_infojson_cookies(self
):
1276 TEST_FILE
= 'test_infojson_cookies.info.json'
1277 TEST_URL
= 'https://example.com/example.mp4'
1278 COOKIES
= 'a=b; Domain=.example.com; c=d; Domain=.example.com'
1279 COOKIE_HEADER
= {'Cookie': 'a=b; c=d'}
1282 ydl
.process_info
= lambda x
: ydl
._write
_info
_json
('test', x
, TEST_FILE
)
1284 def make_info(info_header_cookies
=False, fmts_header_cookies
=False, cookies_field
=False):
1285 fmt
= {'url': TEST_URL}
1286 if fmts_header_cookies
:
1287 fmt
['http_headers'] = COOKIE_HEADER
1289 fmt
['cookies'] = COOKIES
1290 return _make_result([fmt
], http_headers
=COOKIE_HEADER
if info_header_cookies
else None)
1292 def test(initial_info
, note
):
1294 result
['processed'] = ydl
.process_ie_result(initial_info
)
1295 self
.assertTrue(ydl
.cookiejar
.get_cookies_for_url(TEST_URL
),
1296 msg
=f
'No cookies set in cookiejar after initial process when {note}')
1297 ydl
.cookiejar
.clear()
1298 with open(TEST_FILE
) as infojson
:
1299 result
['loaded'] = ydl
.sanitize_info(json
.load(infojson
), True)
1300 result
['final'] = ydl
.process_ie_result(result
['loaded'].copy(), download
=False)
1301 self
.assertTrue(ydl
.cookiejar
.get_cookies_for_url(TEST_URL
),
1302 msg
=f
'No cookies set in cookiejar after final process when {note}')
1303 ydl
.cookiejar
.clear()
1304 for key
in ('processed', 'loaded', 'final'):
1307 traverse_obj(info
, ((None, ('formats', 0)), 'http_headers', 'Cookie'), casesense
=False, get_all
=False),
1308 msg
=f
'Cookie header not removed in {key} result when {note}')
1310 traverse_obj(info
, ((None, ('formats', 0)), 'cookies'), get_all
=False), COOKIES
,
1311 msg
=f
'No cookies field found in {key} result when {note}')
1313 test({'url': TEST_URL, 'http_headers': COOKIE_HEADER, 'id': '1', 'title': 'x'}
, 'no formats field')
1314 test(make_info(info_header_cookies
=True), 'info_dict header cokies')
1315 test(make_info(fmts_header_cookies
=True), 'format header cookies')
1316 test(make_info(info_header_cookies
=True, fmts_header_cookies
=True), 'info_dict and format header cookies')
1317 test(make_info(info_header_cookies
=True, fmts_header_cookies
=True, cookies_field
=True), 'all cookies fields')
1318 test(make_info(cookies_field
=True), 'cookies format field')
1319 test({'url': TEST_URL, 'cookies': COOKIES, 'id': '1', 'title': 'x'}
, 'info_dict cookies field only')
1323 def test_add_headers_cookie(self
):
1324 def check_for_cookie_header(result
):
1325 return traverse_obj(result
, ((None, ('formats', 0)), 'http_headers', 'Cookie'), casesense
=False, get_all
=False)
1327 ydl
= FakeYDL({'http_headers': {'Cookie': 'a=b'}
})
1328 ydl
._apply
_header
_cookies
(_make_result([])['webpage_url']) # Scope to input webpage URL: .example.com
1330 fmt
= {'url': 'https://example.com/video.mp4'}
1331 result
= ydl
.process_ie_result(_make_result([fmt
]), download
=False)
1332 self
.assertIsNone(check_for_cookie_header(result
), msg
='http_headers cookies in result info_dict')
1333 self
.assertEqual(result
.get('cookies'), 'a=b; Domain=.example.com', msg
='No cookies were set in cookies field')
1334 self
.assertIn('a=b', ydl
.cookiejar
.get_cookie_header(fmt
['url']), msg
='No cookies were set in cookiejar')
1336 fmt
= {'url': 'https://wrong.com/video.mp4'}
1337 result
= ydl
.process_ie_result(_make_result([fmt
]), download
=False)
1338 self
.assertIsNone(check_for_cookie_header(result
), msg
='http_headers cookies for wrong domain')
1339 self
.assertFalse(result
.get('cookies'), msg
='Cookies set in cookies field for wrong domain')
1340 self
.assertFalse(ydl
.cookiejar
.get_cookie_header(fmt
['url']), msg
='Cookies set in cookiejar for wrong domain')
1343 if __name__
== '__main__':