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')
641 'title3': 'foo/bar\\test',
642 'title4': 'foo "bar" test',
644 'timestamp': 1618488000,
647 'playlist_autonumber': 2,
648 '__last_playlist_index': 100,
651 {'id': 'id 1', 'height': 1080, 'width': 1920}
,
652 {'id': 'id 2', 'height': 720}
,
657 def test_prepare_outtmpl_and_filename(self
):
658 def test(tmpl
, expected
, *, info
=None, **params
):
659 params
['outtmpl'] = tmpl
660 ydl
= FakeYDL(params
)
661 ydl
._num
_downloads
= 1
662 self
.assertEqual(ydl
.validate_outtmpl(tmpl
), None)
664 out
= ydl
.evaluate_outtmpl(tmpl
, info
or self
.outtmpl_info
)
665 fname
= ydl
.prepare_filename(info
or self
.outtmpl_info
)
667 if not isinstance(expected
, (list, tuple)):
668 expected
= (expected
, expected
)
669 for (name
, got
), expect
in zip((('outtmpl', out
), ('filename', fname
)), expected
):
671 self
.assertTrue(expect(got
), f
'Wrong {name} from {tmpl}')
672 elif expect
is not None:
673 self
.assertEqual(got
, expect
, f
'Wrong {name} from {tmpl}')
676 original_infodict
= dict(self
.outtmpl_info
)
677 test('foo.bar', 'foo.bar')
678 original_infodict
['epoch'] = self
.outtmpl_info
.get('epoch')
679 self
.assertTrue(isinstance(original_infodict
['epoch'], int))
680 test('%(epoch)d', int_or_none
)
681 self
.assertEqual(original_infodict
, self
.outtmpl_info
)
683 # Auto-generated fields
684 test('%(id)s.%(ext)s', '1234.mp4')
685 test('%(duration_string)s', ('27:46:40', '27-46-40'))
686 test('%(resolution)s', '1080p')
687 test('%(playlist_index|)s', '001')
688 test('%(playlist_index&{}!)s', '1!')
689 test('%(playlist_autonumber)s', '02')
690 test('%(autonumber)s', '00001')
691 test('%(autonumber+2)03d', '005', autonumber_start
=3)
692 test('%(autonumber)s', '001', autonumber_size
=3)
701 test('%abc%', '%abc%')
702 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
703 test('%%%(height)s', '%1080')
704 test('%(width)06d.%(ext)s', 'NA.mp4')
705 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
706 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
709 test('%(id)s', '_abcd', info
={'id': '_abcd'}
)
710 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'}
)
711 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}
]})
712 test('%(id)s', '-abcd', info
={'id': '-abcd'}
)
713 test('%(id)s', '.abcd', info
={'id': '.abcd'}
)
714 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'}
)
715 test('%(id)s', ('ab:cd', 'ab:cd'), info
={'id': 'ab:cd'}
)
716 test('%(id.0)s', '-', info
={'id': '--'}
)
719 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
720 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
722 test('%(formats.{id)s', 'NA')
725 def expect_same_infodict(out
):
726 got_dict
= json
.loads(out
)
727 for info_field
, expected
in self
.outtmpl_info
.items():
728 self
.assertEqual(got_dict
.get(info_field
), expected
, info_field
)
731 test('%()j', (expect_same_infodict
, str))
734 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
735 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
736 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
737 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
738 test('%(non_existent.0)s', 'NA')
741 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
742 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
743 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
744 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
745 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
746 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
747 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
748 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
749 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
750 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
751 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
754 test('%(id)d', '1234')
755 test('%(height)c', '1')
757 test('%(id)d %(id)r', "1234 '1234'")
758 test('%(id)r %(height)r', "'1234' 1080")
759 test('%(title5)a %(height)a', (R
"'\xe1\xe9\xed \U0001d400' 1080", None))
760 test('%(ext)s-%(ext|def)d', 'mp4-def')
761 test('%(width|0)04d', '0')
762 test('a%(width|b)d', 'ab', outtmpl_na_placeholder
='none')
764 FORMATS
= self
.outtmpl_info
['formats']
766 # Custom type casting
767 test('%(formats.:.id)l', 'id 1, id 2, id 3')
768 test('%(formats.:.id)#l', ('id 1\nid 2\nid 3', 'id 1 id 2 id 3'))
769 test('%(ext)l', 'mp4')
770 test('%(formats.:.id) 18l', ' id 1, id 2, id 3')
771 test('%(formats)j', (json
.dumps(FORMATS
), None))
772 test('%(formats)#j', (
773 json
.dumps(FORMATS
, indent
=4),
774 json
.dumps(FORMATS
, indent
=4).replace(':', ':').replace('"', """).replace('\n', ' ')
776 test('%(title5).3B', 'á')
777 test('%(title5)U', 'áéí 𝐀')
778 test('%(title5)#U', 'a\u0301e\u0301i\u0301 𝐀')
779 test('%(title5)+U', 'áéí A')
780 test('%(title5)+#U', 'a\u0301e\u0301i\u0301 A')
781 test('%(height)D', '1k')
782 test('%(filesize)#D', '1Ki')
783 test('%(height)5.2D', ' 1.08k')
784 test('%(title4)#S', 'foo_bar_test')
785 test('%(title4).10S', ('foo "bar" ', 'foo "bar"' + ('#' if compat_os_name
== 'nt' else ' ')))
786 if compat_os_name
== 'nt':
787 test('%(title4)q', ('"foo ""bar"" test"', None))
788 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', None))
789 test('%(formats.0.id)#q', ('"id 1"', None))
791 test('%(title4)q', ('\'foo "bar" test\'', '\'foo "bar" test\''))
792 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'")
793 test('%(formats.0.id)#q', "'id 1'")
795 # Internal formatting
796 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
797 test('%(title|%)s %(title|%%)s', '% %%')
798 test('%(id+1-height+3)05d', '00158')
799 test('%(width+100)05d', 'NA')
800 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], None))
801 test('%(formats.0)r', (repr(FORMATS
[0]), None))
802 test('%(height.0)03d', '001')
803 test('%(-height.0)04d', '-001')
804 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
805 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
806 test('%(formats.3)s', 'NA')
807 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
808 test('%(formats.0.id.-1+id)f', '1235.000000')
809 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
810 out
= json
.dumps([{'id': f['id'], 'height.:2': str(f['height'])[:2]}
811 if 'height' in f
else {'id': f['id']}
813 test('%(formats.:.{id,height.:2})j', (out
, None))
814 test('%(formats.:.{id,height}.id)l', ', '.join(f
['id'] for f
in FORMATS
))
815 test('%(.{id,title})j', ('{"id": "1234"}', '{"id": "1234"}'))
818 test('%(title,id)s', '1234')
819 test('%(width-100,height+20|def)d', '1100')
820 test('%(width-100,height+width|def)s', 'def')
821 test('%(timestamp-x>%H\\,%M\\,%S,timestamp>%H\\,%M\\,%S)s', '12,00,00')
824 test('%(id&foo)s.bar', 'foo.bar')
825 test('%(title&foo)s.bar', 'NA.bar')
826 test('%(title&foo|baz)s.bar', 'baz.bar')
827 test('%(x,id&foo|baz)s.bar', 'foo.bar')
828 test('%(x,title&foo|baz)s.bar', 'baz.bar')
829 test('%(id&a\nb|)s', ('a\nb', 'a b'))
830 test('%(id&hi {:>10} {}|)s', 'hi 1234 1234')
831 test(R
'%(id&{0} {}|)s', 'NA')
832 test(R
'%(id&{0.1}|)s', 'NA')
833 test('%(height&{:,d})S', '1,080')
838 raise self
.assertTrue(False, 'LazyList should not be evaluated till here')
839 test('%(key.4)s', '4', info
={'key': LazyList(gen())}
)
842 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
843 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
844 # test('%(foo|)s', ('', '_')) # fixme
846 # Environment variable expansion for prepare_filename
847 os
.environ
['__yt_dlp_var'] = 'expanded'
848 envvar
= '%__yt_dlp_var%' if compat_os_name
== 'nt' else '$__yt_dlp_var'
849 test(envvar
, (envvar
, 'expanded'))
850 if compat_os_name
== 'nt':
851 test('%s%', ('%s%', '%s%'))
852 os
.environ
['s'] = 'expanded'
853 test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
854 os
.environ
['(test)s'] = 'expanded'
855 test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
857 # Path expansion and escaping
858 test('Hello %(title1)s', 'Hello $PATH')
859 test('Hello %(title2)s', 'Hello %PATH%')
860 test('%(title3)s', ('foo/bar\\test', 'foo⧸bar⧹test'))
861 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo⧸bar⧹test' % os
.path
.sep
))
863 def test_format_note(self
):
865 self
.assertEqual(ydl
._format
_note
({}), '')
866 assertRegexpMatches(self
, ydl
._format
_note
({
869 assertRegexpMatches(self
, ydl
._format
_note
({
873 def test_postprocessors(self
):
874 filename
= 'post-processor-testfile.mp4'
875 audiofile
= filename
+ '.mp3'
877 class SimplePP(PostProcessor
):
879 with open(audiofile
, 'w') as f
:
881 return [info
['filepath']], info
883 def run_pp(params
, PP
):
884 with open(filename
, 'w') as f
:
886 ydl
= YoutubeDL(params
)
887 ydl
.add_post_processor(PP())
888 ydl
.post_process(filename
, {'filepath': filename}
)
890 run_pp({'keepvideo': True}
, SimplePP
)
891 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
892 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
896 run_pp({'keepvideo': False}
, SimplePP
)
897 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
898 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
901 class ModifierPP(PostProcessor
):
903 with open(info
['filepath'], 'w') as f
:
907 run_pp({'keepvideo': False}
, ModifierPP
)
908 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
911 def test_match_filter(self
):
918 'filesize': 10 * 1024,
920 'uploader': "變態妍字幕版 太妍 тест",
921 'creator': "тест ' 123 ' тест--",
922 'webpage_url': 'http://example.com/watch?v=shenanigans',
930 'description': 'foo',
931 'filesize': 5 * 1024,
933 'uploader': "тест 123",
934 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
936 videos
= [first
, second
]
938 def get_videos(filter_
=None):
939 ydl
= YDL({'match_filter': filter_, 'simulate': True}
)
941 ydl
.process_ie_result(v
, download
=True)
942 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
945 self
.assertEqual(res
, ['1', '2'])
947 def f(v
, incomplete
):
951 return 'Video id is not 1'
953 self
.assertEqual(res
, ['1'])
955 f
= match_filter_func('duration < 30')
957 self
.assertEqual(res
, ['2'])
959 f
= match_filter_func('description = foo')
961 self
.assertEqual(res
, ['2'])
963 f
= match_filter_func('description =? foo')
965 self
.assertEqual(res
, ['1', '2'])
967 f
= match_filter_func('filesize > 5KiB')
969 self
.assertEqual(res
, ['1'])
971 f
= match_filter_func('playlist_id = 42')
973 self
.assertEqual(res
, ['1'])
975 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
977 self
.assertEqual(res
, ['1'])
979 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
981 self
.assertEqual(res
, ['2'])
983 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
985 self
.assertEqual(res
, ['1'])
987 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
989 self
.assertEqual(res
, ['1'])
991 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
993 self
.assertEqual(res
, [])
995 def test_playlist_items_selection(self
):
996 INDICES
, PAGE_SIZE
= list(range(1, 11)), 3
998 def entry(i
, evaluated
):
1006 def pagedlist_entries(evaluated
):
1008 start
= PAGE_SIZE
* n
1009 for i
in INDICES
[start
: start
+ PAGE_SIZE
]:
1010 yield entry(i
, evaluated
)
1011 return OnDemandPagedList(page_func
, PAGE_SIZE
)
1014 return (i
+ PAGE_SIZE
- 1) // PAGE_SIZE
1016 def generator_entries(evaluated
):
1018 yield entry(i
, evaluated
)
1020 def list_entries(evaluated
):
1021 return list(generator_entries(evaluated
))
1023 def lazylist_entries(evaluated
):
1024 return LazyList(generator_entries(evaluated
))
1026 def get_downloaded_info_dicts(params
, entries
):
1028 ydl
.process_ie_result({
1029 '_type': 'playlist',
1031 'extractor': 'test:playlist',
1032 'extractor_key': 'test:playlist',
1033 'webpage_url': 'http://example.com',
1036 return ydl
.downloaded_info_dicts
1038 def test_selection(params
, expected_ids
, evaluate_all
=False):
1039 expected_ids
= list(expected_ids
)
1041 generator_eval
= pagedlist_eval
= INDICES
1042 elif not expected_ids
:
1043 generator_eval
= pagedlist_eval
= []
1045 generator_eval
= INDICES
[0: max(expected_ids
)]
1046 pagedlist_eval
= INDICES
[PAGE_SIZE
* page_num(min(expected_ids
)) - PAGE_SIZE
:
1047 PAGE_SIZE
* page_num(max(expected_ids
))]
1049 for name
, func
, expected_eval
in (
1050 ('list', list_entries
, INDICES
),
1051 ('Generator', generator_entries
, generator_eval
),
1052 # ('LazyList', lazylist_entries, generator_eval), # Generator and LazyList follow the exact same code path
1053 ('PagedList', pagedlist_entries
, pagedlist_eval
),
1056 entries
= func(evaluated
)
1057 results
= [(v
['playlist_autonumber'] - 1, (int(v
['id']), v
['playlist_index']))
1058 for v
in get_downloaded_info_dicts(params
, entries
)]
1059 self
.assertEqual(results
, list(enumerate(zip(expected_ids
, expected_ids
))), f
'Entries of {name} for {params}')
1060 self
.assertEqual(sorted(evaluated
), expected_eval
, f
'Evaluation of {name} for {params}')
1062 test_selection({}, INDICES
)
1063 test_selection({'playlistend': 20}
, INDICES
, True)
1064 test_selection({'playlistend': 2}
, INDICES
[:2])
1065 test_selection({'playliststart': 11}
, [], True)
1066 test_selection({'playliststart': 2}
, INDICES
[1:])
1067 test_selection({'playlist_items': '2-4'}
, INDICES
[1:4])
1068 test_selection({'playlist_items': '2,4'}
, [2, 4])
1069 test_selection({'playlist_items': '20'}
, [], True)
1070 test_selection({'playlist_items': '0'}
, [])
1072 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
1073 test_selection({'playlist_items': '2-4,3-4,3'}
, [2, 3, 4])
1074 test_selection({'playlist_items': '4,2'}
, [4, 2])
1076 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720
1077 # https://github.com/yt-dlp/yt-dlp/issues/302
1078 test_selection({'playlistreverse': True}
, INDICES
[::-1])
1079 test_selection({'playliststart': 2, 'playlistreverse': True}
, INDICES
[:0:-1])
1080 test_selection({'playlist_items': '2,4', 'playlistreverse': True}
, [4, 2])
1081 test_selection({'playlist_items': '4,2'}
, [4, 2])
1083 # Tests for --playlist-items start:end:step
1084 test_selection({'playlist_items': ':'}
, INDICES
, True)
1085 test_selection({'playlist_items': '::1'}
, INDICES
, True)
1086 test_selection({'playlist_items': '::-1'}
, INDICES
[::-1], True)
1087 test_selection({'playlist_items': ':6'}
, INDICES
[:6])
1088 test_selection({'playlist_items': ':-6'}
, INDICES
[:-5], True)
1089 test_selection({'playlist_items': '-1:6:-2'}
, INDICES
[:4:-2], True)
1090 test_selection({'playlist_items': '9:-6:-2'}
, INDICES
[8:3:-2], True)
1092 test_selection({'playlist_items': '1:inf:2'}
, INDICES
[::2], True)
1093 test_selection({'playlist_items': '-2:inf'}
, INDICES
[-2:], True)
1094 test_selection({'playlist_items': ':inf:-1'}
, [], True)
1095 test_selection({'playlist_items': '0-2:2'}
, [2])
1096 test_selection({'playlist_items': '1-:2'}
, INDICES
[::2], True)
1097 test_selection({'playlist_items': '0--2:2'}
, INDICES
[1:-1:2], True)
1099 test_selection({'playlist_items': '10::3'}
, [10], True)
1100 test_selection({'playlist_items': '-1::3'}
, [10], True)
1101 test_selection({'playlist_items': '11::3'}
, [], True)
1102 test_selection({'playlist_items': '-15::2'}
, INDICES
[1::2], True)
1103 test_selection({'playlist_items': '-15::15'}
, [], True)
1105 def test_do_not_override_ie_key_in_url_transparent(self
):
1108 class Foo1IE(InfoExtractor
):
1109 _VALID_URL
= r
'foo1:'
1111 def _real_extract(self
, url
):
1113 '_type': 'url_transparent',
1116 'title': 'foo1 title',
1120 class Foo2IE(InfoExtractor
):
1121 _VALID_URL
= r
'foo2:'
1123 def _real_extract(self
, url
):
1130 class Foo3IE(InfoExtractor
):
1131 _VALID_URL
= r
'foo3:'
1133 def _real_extract(self
, url
):
1134 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1136 ydl
.add_info_extractor(Foo1IE(ydl
))
1137 ydl
.add_info_extractor(Foo2IE(ydl
))
1138 ydl
.add_info_extractor(Foo3IE(ydl
))
1139 ydl
.extract_info('foo1:')
1140 downloaded
= ydl
.downloaded_info_dicts
[0]
1141 self
.assertEqual(downloaded
['url'], TEST_URL
)
1142 self
.assertEqual(downloaded
['title'], 'foo1 title')
1143 self
.assertEqual(downloaded
['id'], 'testid')
1144 self
.assertEqual(downloaded
['extractor'], 'testex')
1145 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1147 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1148 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1151 def __init__(self
, *args
, **kwargs
):
1152 super().__init
__(*args
, **kwargs
)
1154 def trouble(self
, s
, tb
=None):
1159 'ignoreerrors': True,
1162 class VideoIE(InfoExtractor
):
1163 _VALID_URL
= r
'video:(?P<id>\d+)'
1165 def _real_extract(self
, url
):
1166 video_id
= self
._match
_id
(url
)
1168 'format_id': 'default',
1172 raise ExtractorError('foo')
1175 'format_id': 'extra',
1180 'title': 'Video %s' % video_id
,
1184 class PlaylistIE(InfoExtractor
):
1185 _VALID_URL
= r
'playlist:'
1191 '_type': 'url_transparent',
1192 'ie_key': VideoIE
.ie_key(),
1194 'url': 'video:%s' % video_id
,
1195 'title': 'Video Transparent %s' % video_id
,
1198 def _real_extract(self
, url
):
1199 return self
.playlist_result(self
._entries
())
1201 ydl
.add_info_extractor(VideoIE(ydl
))
1202 ydl
.add_info_extractor(PlaylistIE(ydl
))
1203 info
= ydl
.extract_info('playlist:')
1204 entries
= info
['entries']
1205 self
.assertEqual(len(entries
), 3)
1206 self
.assertTrue(entries
[0] is None)
1207 self
.assertTrue(entries
[1] is None)
1208 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1209 downloaded
= ydl
.downloaded_info_dicts
[0]
1210 entries
[2].pop('requested_downloads', None)
1211 self
.assertEqual(entries
[2], downloaded
)
1212 self
.assertEqual(downloaded
['url'], TEST_URL
)
1213 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1214 self
.assertEqual(downloaded
['id'], '2')
1215 self
.assertEqual(downloaded
['extractor'], 'Video')
1216 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1218 def test_header_cookies(self
):
1219 from http
.cookiejar
import Cookie
1222 ydl
.report_warning
= lambda *_
, **__
: None
1224 def cookie(name
, value
, version
=None, domain
='', path
='', secure
=False, expires
=None):
1226 version
or 0, name
, value
, None, False,
1227 domain
, bool(domain
), bool(domain
), path
, bool(path
),
1228 secure
, expires
, False, None, None, rest
={})
1230 _test_url
= 'https://yt.dlp/test'
1232 def test(encoded_cookies
, cookies
, *, headers
=False, round_trip
=None, error_re
=None):
1234 ydl
.cookiejar
.clear()
1235 ydl
._load
_cookies
(encoded_cookies
, autoscope
=headers
)
1237 ydl
._apply
_header
_cookies
(_test_url
)
1238 data
= {'url': _test_url}
1239 ydl
._calc
_headers
(data
)
1240 self
.assertCountEqual(
1241 map(vars, ydl
.cookiejar
), map(vars, cookies
),
1242 'Extracted cookiejar.Cookie is not the same')
1245 data
.get('cookies'), round_trip
or encoded_cookies
,
1246 'Cookie is not the same as round trip')
1247 ydl
.__dict
__['_YoutubeDL__header_cookies'] = []
1249 with self
.subTest(msg
=encoded_cookies
):
1253 with self
.assertRaisesRegex(Exception, error_re
):
1256 test('test=value; Domain=.yt.dlp', [cookie('test', 'value', domain
='.yt.dlp')])
1257 test('test=value', [cookie('test', 'value')], error_re
=r
'Unscoped cookies are not allowed')
1258 test('cookie1=value1; Domain=.yt.dlp; Path=/test; cookie2=value2; Domain=.yt.dlp; Path=/', [
1259 cookie('cookie1', 'value1', domain
='.yt.dlp', path
='/test'),
1260 cookie('cookie2', 'value2', domain
='.yt.dlp', path
='/')])
1261 test('test=value; Domain=.yt.dlp; Path=/test; Secure; Expires=9999999999', [
1262 cookie('test', 'value', domain
='.yt.dlp', path
='/test', secure
=True, expires
=9999999999)])
1263 test('test="value; "; path=/test; domain=.yt.dlp', [
1264 cookie('test', 'value; ', domain
='.yt.dlp', path
='/test')],
1265 round_trip
='test="value\\073 "; Domain=.yt.dlp; Path=/test')
1266 test('name=; Domain=.yt.dlp', [cookie('name', '', domain
='.yt.dlp')],
1267 round_trip
='name=""; Domain=.yt.dlp')
1269 test('test=value', [cookie('test', 'value', domain
='.yt.dlp')], headers
=True)
1270 test('cookie1=value; Domain=.yt.dlp; cookie2=value', [], headers
=True, error_re
=r
'Invalid syntax')
1271 ydl
.deprecated_feature
= ydl
.report_error
1272 test('test=value', [], headers
=True, error_re
=r
'Passing cookies as a header is a potential security risk')
1274 def test_infojson_cookies(self
):
1275 TEST_FILE
= 'test_infojson_cookies.info.json'
1276 TEST_URL
= 'https://example.com/example.mp4'
1277 COOKIES
= 'a=b; Domain=.example.com; c=d; Domain=.example.com'
1278 COOKIE_HEADER
= {'Cookie': 'a=b; c=d'}
1281 ydl
.process_info
= lambda x
: ydl
._write
_info
_json
('test', x
, TEST_FILE
)
1283 def make_info(info_header_cookies
=False, fmts_header_cookies
=False, cookies_field
=False):
1284 fmt
= {'url': TEST_URL}
1285 if fmts_header_cookies
:
1286 fmt
['http_headers'] = COOKIE_HEADER
1288 fmt
['cookies'] = COOKIES
1289 return _make_result([fmt
], http_headers
=COOKIE_HEADER
if info_header_cookies
else None)
1291 def test(initial_info
, note
):
1293 result
['processed'] = ydl
.process_ie_result(initial_info
)
1294 self
.assertTrue(ydl
.cookiejar
.get_cookies_for_url(TEST_URL
),
1295 msg
=f
'No cookies set in cookiejar after initial process when {note}')
1296 ydl
.cookiejar
.clear()
1297 with open(TEST_FILE
) as infojson
:
1298 result
['loaded'] = ydl
.sanitize_info(json
.load(infojson
), True)
1299 result
['final'] = ydl
.process_ie_result(result
['loaded'].copy(), download
=False)
1300 self
.assertTrue(ydl
.cookiejar
.get_cookies_for_url(TEST_URL
),
1301 msg
=f
'No cookies set in cookiejar after final process when {note}')
1302 ydl
.cookiejar
.clear()
1303 for key
in ('processed', 'loaded', 'final'):
1306 traverse_obj(info
, ((None, ('formats', 0)), 'http_headers', 'Cookie'), casesense
=False, get_all
=False),
1307 msg
=f
'Cookie header not removed in {key} result when {note}')
1309 traverse_obj(info
, ((None, ('formats', 0)), 'cookies'), get_all
=False), COOKIES
,
1310 msg
=f
'No cookies field found in {key} result when {note}')
1312 test({'url': TEST_URL, 'http_headers': COOKIE_HEADER, 'id': '1', 'title': 'x'}
, 'no formats field')
1313 test(make_info(info_header_cookies
=True), 'info_dict header cokies')
1314 test(make_info(fmts_header_cookies
=True), 'format header cookies')
1315 test(make_info(info_header_cookies
=True, fmts_header_cookies
=True), 'info_dict and format header cookies')
1316 test(make_info(info_header_cookies
=True, fmts_header_cookies
=True, cookies_field
=True), 'all cookies fields')
1317 test(make_info(cookies_field
=True), 'cookies format field')
1318 test({'url': TEST_URL, 'cookies': COOKIES, 'id': '1', 'title': 'x'}
, 'info_dict cookies field only')
1322 def test_add_headers_cookie(self
):
1323 def check_for_cookie_header(result
):
1324 return traverse_obj(result
, ((None, ('formats', 0)), 'http_headers', 'Cookie'), casesense
=False, get_all
=False)
1326 ydl
= FakeYDL({'http_headers': {'Cookie': 'a=b'}
})
1327 ydl
._apply
_header
_cookies
(_make_result([])['webpage_url']) # Scope to input webpage URL: .example.com
1329 fmt
= {'url': 'https://example.com/video.mp4'}
1330 result
= ydl
.process_ie_result(_make_result([fmt
]), download
=False)
1331 self
.assertIsNone(check_for_cookie_header(result
), msg
='http_headers cookies in result info_dict')
1332 self
.assertEqual(result
.get('cookies'), 'a=b; Domain=.example.com', msg
='No cookies were set in cookies field')
1333 self
.assertIn('a=b', ydl
.cookiejar
.get_cookie_header(fmt
['url']), msg
='No cookies were set in cookiejar')
1335 fmt
= {'url': 'https://wrong.com/video.mp4'}
1336 result
= ydl
.process_ie_result(_make_result([fmt
]), download
=False)
1337 self
.assertIsNone(check_for_cookie_header(result
), msg
='http_headers cookies for wrong domain')
1338 self
.assertFalse(result
.get('cookies'), msg
='Cookies set in cookies field for wrong domain')
1339 self
.assertFalse(ydl
.cookiejar
.get_cookie_header(fmt
['url']), msg
='Cookies set in cookiejar for wrong domain')
1342 if __name__
== '__main__':