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)
143 # See: https://github.com/yt-dlp/yt-dlp/pulls/8797
144 test('7_a/worst', '35')
146 def test_format_selection_audio(self
):
148 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
149 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
150 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}
,
151 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL}
,
153 info_dict
= _make_result(formats
)
155 ydl
= YDL({'format': 'bestaudio'}
)
156 ydl
.process_ie_result(info_dict
.copy())
157 downloaded
= ydl
.downloaded_info_dicts
[0]
158 self
.assertEqual(downloaded
['format_id'], 'audio-high')
160 ydl
= YDL({'format': 'worstaudio'}
)
161 ydl
.process_ie_result(info_dict
.copy())
162 downloaded
= ydl
.downloaded_info_dicts
[0]
163 self
.assertEqual(downloaded
['format_id'], 'audio-low')
166 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
167 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL}
,
169 info_dict
= _make_result(formats
)
171 ydl
= YDL({'format': 'bestaudio/worstaudio/best'}
)
172 ydl
.process_ie_result(info_dict
.copy())
173 downloaded
= ydl
.downloaded_info_dicts
[0]
174 self
.assertEqual(downloaded
['format_id'], 'vid-high')
176 def test_format_selection_audio_exts(self
):
178 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
179 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
180 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
181 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
182 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
185 info_dict
= _make_result(formats
)
186 ydl
= YDL({'format': 'best'}
)
187 ydl
.sort_formats(info_dict
)
188 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
189 downloaded
= ydl
.downloaded_info_dicts
[0]
190 self
.assertEqual(downloaded
['format_id'], 'aac-64')
192 ydl
= YDL({'format': 'mp3'}
)
193 ydl
.sort_formats(info_dict
)
194 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
195 downloaded
= ydl
.downloaded_info_dicts
[0]
196 self
.assertEqual(downloaded
['format_id'], 'mp3-64')
198 ydl
= YDL({'prefer_free_formats': True}
)
199 ydl
.sort_formats(info_dict
)
200 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
201 downloaded
= ydl
.downloaded_info_dicts
[0]
202 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
204 def test_format_selection_video(self
):
206 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
207 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
208 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
210 info_dict
= _make_result(formats
)
212 ydl
= YDL({'format': 'bestvideo'}
)
213 ydl
.process_ie_result(info_dict
.copy())
214 downloaded
= ydl
.downloaded_info_dicts
[0]
215 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
217 ydl
= YDL({'format': 'worstvideo'}
)
218 ydl
.process_ie_result(info_dict
.copy())
219 downloaded
= ydl
.downloaded_info_dicts
[0]
220 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
222 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
223 ydl
.process_ie_result(info_dict
.copy())
224 downloaded
= ydl
.downloaded_info_dicts
[0]
225 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
228 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
230 info_dict
= _make_result(formats
)
232 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
233 ydl
.process_ie_result(info_dict
.copy())
234 downloaded
= ydl
.downloaded_info_dicts
[0]
235 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
237 def test_format_selection_string_ops(self
):
239 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
240 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
242 info_dict
= _make_result(formats
)
245 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
246 ydl
.process_ie_result(info_dict
.copy())
247 downloaded
= ydl
.downloaded_info_dicts
[0]
248 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
250 # does not equal (!=)
251 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
252 ydl
.process_ie_result(info_dict
.copy())
253 downloaded
= ydl
.downloaded_info_dicts
[0]
254 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
256 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
257 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
260 ydl
= YDL({'format': '[format_id^=abc]'}
)
261 ydl
.process_ie_result(info_dict
.copy())
262 downloaded
= ydl
.downloaded_info_dicts
[0]
263 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
265 # does not start with (!^=)
266 ydl
= YDL({'format': '[format_id!^=abc]'}
)
267 ydl
.process_ie_result(info_dict
.copy())
268 downloaded
= ydl
.downloaded_info_dicts
[0]
269 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
271 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
272 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
275 ydl
= YDL({'format': '[format_id$=cba]'}
)
276 ydl
.process_ie_result(info_dict
.copy())
277 downloaded
= ydl
.downloaded_info_dicts
[0]
278 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
280 # does not end with (!$=)
281 ydl
= YDL({'format': '[format_id!$=cba]'}
)
282 ydl
.process_ie_result(info_dict
.copy())
283 downloaded
= ydl
.downloaded_info_dicts
[0]
284 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
286 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
287 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
290 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
291 ydl
.process_ie_result(info_dict
.copy())
292 downloaded
= ydl
.downloaded_info_dicts
[0]
293 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
295 # does not contain (!*=)
296 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
297 ydl
.process_ie_result(info_dict
.copy())
298 downloaded
= ydl
.downloaded_info_dicts
[0]
299 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
301 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
302 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
304 ydl
= YDL({'format': '[format_id!*=-]'}
)
305 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
307 def test_youtube_format_selection(self
):
308 # FIXME: Rewrite in accordance with the new format sorting options
312 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
313 # Apple HTTP Live Streaming
314 '96', '95', '94', '93', '92', '132', '151',
316 '85', '84', '102', '83', '101', '82', '100',
318 '137', '248', '136', '247', '135', '246',
319 '245', '244', '134', '243', '133', '242', '160',
321 '141', '172', '140', '171', '139',
324 def format_info(f_id
):
325 info
= YoutubeIE
._formats
[f_id
].copy()
327 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
328 # and 'vcodec', while in tests such information is incomplete since
329 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
330 # test_YoutubeDL.test_youtube_format_selection is broken without
332 if 'acodec' in info
and 'vcodec' not in info
:
333 info
['vcodec'] = 'none'
334 elif 'vcodec' in info
and 'acodec' not in info
:
335 info
['acodec'] = 'none'
337 info
['format_id'] = f_id
338 info
['url'] = 'url:' + f_id
340 formats_order
= [format_info(f_id
) for f_id
in order
]
342 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
343 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
344 ydl
.sort_formats(info_dict
)
345 ydl
.process_ie_result(info_dict
)
346 downloaded
= ydl
.downloaded_info_dicts
[0]
347 self
.assertEqual(downloaded
['format_id'], '248+172')
348 self
.assertEqual(downloaded
['ext'], 'mp4')
350 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
351 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
352 ydl
.sort_formats(info_dict
)
353 ydl
.process_ie_result(info_dict
)
354 downloaded
= ydl
.downloaded_info_dicts
[0]
355 self
.assertEqual(downloaded
['format_id'], '38')
357 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
358 ydl
= YDL({'format': 'bestvideo/best,bestaudio'}
)
359 ydl
.sort_formats(info_dict
)
360 ydl
.process_ie_result(info_dict
)
361 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
362 self
.assertEqual(downloaded_ids
, ['137', '141'])
364 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
365 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'}
)
366 ydl
.sort_formats(info_dict
)
367 ydl
.process_ie_result(info_dict
)
368 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
369 self
.assertEqual(downloaded_ids
, ['137+141', '248+141'])
371 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
372 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'}
)
373 ydl
.sort_formats(info_dict
)
374 ydl
.process_ie_result(info_dict
)
375 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
376 self
.assertEqual(downloaded_ids
, ['136+141', '247+141'])
378 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
379 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}
)
380 ydl
.sort_formats(info_dict
)
381 ydl
.process_ie_result(info_dict
)
382 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
383 self
.assertEqual(downloaded_ids
, ['248+141'])
385 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
386 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
387 ydl
= YDL({'format': 'best/bestvideo'}
)
388 ydl
.sort_formats(info_dict
)
389 ydl
.process_ie_result(info_dict
)
390 downloaded
= ydl
.downloaded_info_dicts
[0]
391 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
393 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
394 ydl
= YDL({'format': 'best/bestvideo'}
)
395 ydl
.sort_formats(info_dict
)
396 ydl
.process_ie_result(info_dict
)
397 downloaded
= ydl
.downloaded_info_dicts
[0]
398 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
400 def test_audio_only_extractor_format_selection(self
):
401 # For extractors with incomplete formats (all formats are audio-only or
402 # video-only) best and worst should fallback to corresponding best/worst
403 # video-only or audio-only formats (as per
404 # https://github.com/ytdl-org/youtube-dl/pull/5556)
406 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
407 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
409 info_dict
= _make_result(formats
)
411 ydl
= YDL({'format': 'best'}
)
412 ydl
.process_ie_result(info_dict
.copy())
413 downloaded
= ydl
.downloaded_info_dicts
[0]
414 self
.assertEqual(downloaded
['format_id'], 'high')
416 ydl
= YDL({'format': 'worst'}
)
417 ydl
.process_ie_result(info_dict
.copy())
418 downloaded
= ydl
.downloaded_info_dicts
[0]
419 self
.assertEqual(downloaded
['format_id'], 'low')
421 def test_format_not_available(self
):
423 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
424 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
426 info_dict
= _make_result(formats
)
428 # This must fail since complete video-audio format does not match filter
429 # and extractor does not provide incomplete only formats (i.e. only
430 # video-only or audio-only).
431 ydl
= YDL({'format': 'best[height>360]'}
)
432 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
434 def test_format_selection_issue_10083(self
):
435 # See https://github.com/ytdl-org/youtube-dl/issues/10083
437 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
438 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
439 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
441 info_dict
= _make_result(formats
)
443 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
444 ydl
.process_ie_result(info_dict
.copy())
445 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
447 def test_invalid_format_specs(self
):
448 def assert_syntax_error(format_spec
):
449 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec}
)
451 assert_syntax_error('bestvideo,,best')
452 assert_syntax_error('+bestaudio')
453 assert_syntax_error('bestvideo+')
454 assert_syntax_error('/')
455 assert_syntax_error('[720<height]')
457 def test_format_filtering(self
):
459 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
460 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
461 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
462 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
463 {'format_id': 'E', 'filesize': 3000}
,
465 {'format_id': 'G', 'filesize': 1000000}
,
468 f
['url'] = 'http://_/'
470 info_dict
= _make_result(formats
, _format_sort_fields
=('id', ))
472 ydl
= YDL({'format': 'best[filesize<3000]'}
)
473 ydl
.process_ie_result(info_dict
)
474 downloaded
= ydl
.downloaded_info_dicts
[0]
475 self
.assertEqual(downloaded
['format_id'], 'D')
477 ydl
= YDL({'format': 'best[filesize<=3000]'}
)
478 ydl
.process_ie_result(info_dict
)
479 downloaded
= ydl
.downloaded_info_dicts
[0]
480 self
.assertEqual(downloaded
['format_id'], 'E')
482 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
483 ydl
.process_ie_result(info_dict
)
484 downloaded
= ydl
.downloaded_info_dicts
[0]
485 self
.assertEqual(downloaded
['format_id'], 'F')
487 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'}
)
488 ydl
.process_ie_result(info_dict
)
489 downloaded
= ydl
.downloaded_info_dicts
[0]
490 self
.assertEqual(downloaded
['format_id'], 'B')
492 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
493 ydl
.process_ie_result(info_dict
)
494 downloaded
= ydl
.downloaded_info_dicts
[0]
495 self
.assertEqual(downloaded
['format_id'], 'C')
497 ydl
= YDL({'format': '[filesize>?1]'}
)
498 ydl
.process_ie_result(info_dict
)
499 downloaded
= ydl
.downloaded_info_dicts
[0]
500 self
.assertEqual(downloaded
['format_id'], 'G')
502 ydl
= YDL({'format': '[filesize<1M]'}
)
503 ydl
.process_ie_result(info_dict
)
504 downloaded
= ydl
.downloaded_info_dicts
[0]
505 self
.assertEqual(downloaded
['format_id'], 'E')
507 ydl
= YDL({'format': '[filesize<1MiB]'}
)
508 ydl
.process_ie_result(info_dict
)
509 downloaded
= ydl
.downloaded_info_dicts
[0]
510 self
.assertEqual(downloaded
['format_id'], 'G')
512 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
513 ydl
.process_ie_result(info_dict
)
514 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
515 self
.assertEqual(downloaded_ids
, ['D', 'C', 'B'])
517 ydl
= YDL({'format': 'best[height<40]'}
)
519 ydl
.process_ie_result(info_dict
)
520 except ExtractorError
:
522 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
524 def test_default_format_spec(self
):
525 ydl
= YDL({'simulate': True}
)
526 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
529 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
531 ydl
= YDL({'simulate': True}
)
532 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
534 ydl
= YDL({'outtmpl': '-'}
)
535 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
538 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
539 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
542 class TestYoutubeDL(unittest
.TestCase
):
543 def test_subtitles(self
):
544 def s_formats(lang
, autocaption
=False):
547 'url': f
'http://localhost/video.{lang}.{ext}',
548 '_auto': autocaption
,
549 } for ext
in ['vtt', 'srt', 'ass']]
550 subtitles
= {l: s_formats(l) for l in ['en', 'fr', 'es']}
551 auto_captions
= {l: s_formats(l, True) for l in ['it', 'pt', 'es']}
555 'url': 'http://localhost/video.mp4',
556 'subtitles': subtitles
,
557 'automatic_captions': auto_captions
,
559 'webpage_url': 'http://example.com/watch?v=shenanigans',
562 def get_info(params
={}):
563 params
.setdefault('simulate', True)
565 ydl
.report_warning
= lambda *args
, **kargs
: None
566 return ydl
.process_video_result(info_dict
, download
=False)
569 self
.assertFalse(result
.get('requested_subtitles'))
570 self
.assertEqual(result
['subtitles'], subtitles
)
571 self
.assertEqual(result
['automatic_captions'], auto_captions
)
573 result
= get_info({'writesubtitles': True}
)
574 subs
= result
['requested_subtitles']
575 self
.assertTrue(subs
)
576 self
.assertEqual(set(subs
.keys()), {'en'}
)
577 self
.assertTrue(subs
['en'].get('data') is None)
578 self
.assertEqual(subs
['en']['ext'], 'ass')
580 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
581 subs
= result
['requested_subtitles']
582 self
.assertEqual(subs
['en']['ext'], 'srt')
584 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
585 subs
= result
['requested_subtitles']
586 self
.assertTrue(subs
)
587 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
589 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
590 subs
= result
['requested_subtitles']
591 self
.assertTrue(subs
)
592 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
594 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
595 subs
= result
['requested_subtitles']
596 self
.assertTrue(subs
)
597 self
.assertEqual(set(subs
.keys()), {'fr'}
)
599 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
600 subs
= result
['requested_subtitles']
601 self
.assertTrue(subs
)
602 self
.assertEqual(set(subs
.keys()), {'en'}
)
604 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
605 subs
= result
['requested_subtitles']
606 self
.assertTrue(subs
)
607 self
.assertEqual(set(subs
.keys()), {'es', 'en'}
)
609 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
610 subs
= result
['requested_subtitles']
611 self
.assertTrue(subs
)
612 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
613 self
.assertFalse(subs
['es']['_auto'])
614 self
.assertTrue(subs
['pt']['_auto'])
616 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
617 subs
= result
['requested_subtitles']
618 self
.assertTrue(subs
)
619 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
620 self
.assertTrue(subs
['es']['_auto'])
621 self
.assertTrue(subs
['pt']['_auto'])
623 def test_add_extra_info(self
):
629 'playlist': 'funny videos',
631 YDL
.add_extra_info(test_dict
, extra_info
)
632 self
.assertEqual(test_dict
['extractor'], 'Foo')
633 self
.assertEqual(test_dict
['playlist'], 'funny videos')
643 'title3': 'foo/bar\\test',
644 'title4': 'foo "bar" test',
646 'timestamp': 1618488000,
649 'playlist_autonumber': 2,
650 '__last_playlist_index': 100,
653 {'id': 'id 1', 'height': 1080, 'width': 1920}
,
654 {'id': 'id 2', 'height': 720}
,
659 def test_prepare_outtmpl_and_filename(self
):
660 def test(tmpl
, expected
, *, info
=None, **params
):
661 params
['outtmpl'] = tmpl
662 ydl
= FakeYDL(params
)
663 ydl
._num
_downloads
= 1
664 self
.assertEqual(ydl
.validate_outtmpl(tmpl
), None)
666 out
= ydl
.evaluate_outtmpl(tmpl
, info
or self
.outtmpl_info
)
667 fname
= ydl
.prepare_filename(info
or self
.outtmpl_info
)
669 if not isinstance(expected
, (list, tuple)):
670 expected
= (expected
, expected
)
671 for (name
, got
), expect
in zip((('outtmpl', out
), ('filename', fname
)), expected
):
673 self
.assertTrue(expect(got
), f
'Wrong {name} from {tmpl}')
674 elif expect
is not None:
675 self
.assertEqual(got
, expect
, f
'Wrong {name} from {tmpl}')
678 original_infodict
= dict(self
.outtmpl_info
)
679 test('foo.bar', 'foo.bar')
680 original_infodict
['epoch'] = self
.outtmpl_info
.get('epoch')
681 self
.assertTrue(isinstance(original_infodict
['epoch'], int))
682 test('%(epoch)d', int_or_none
)
683 self
.assertEqual(original_infodict
, self
.outtmpl_info
)
685 # Auto-generated fields
686 test('%(id)s.%(ext)s', '1234.mp4')
687 test('%(duration_string)s', ('27:46:40', '27-46-40'))
688 test('%(resolution)s', '1080p')
689 test('%(playlist_index|)s', '001')
690 test('%(playlist_index&{}!)s', '1!')
691 test('%(playlist_autonumber)s', '02')
692 test('%(autonumber)s', '00001')
693 test('%(autonumber+2)03d', '005', autonumber_start
=3)
694 test('%(autonumber)s', '001', autonumber_size
=3)
703 test('%abc%', '%abc%')
704 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
705 test('%%%(height)s', '%1080')
706 test('%(width)06d.%(ext)s', 'NA.mp4')
707 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
708 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
711 test('%(id)s', '_abcd', info
={'id': '_abcd'}
)
712 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'}
)
713 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}
]})
714 test('%(id)s', '-abcd', info
={'id': '-abcd'}
)
715 test('%(id)s', '.abcd', info
={'id': '.abcd'}
)
716 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'}
)
717 test('%(id)s', ('ab:cd', 'ab:cd'), info
={'id': 'ab:cd'}
)
718 test('%(id.0)s', '-', info
={'id': '--'}
)
721 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
722 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
724 test('%(formats.{id)s', 'NA')
727 def expect_same_infodict(out
):
728 got_dict
= json
.loads(out
)
729 for info_field
, expected
in self
.outtmpl_info
.items():
730 self
.assertEqual(got_dict
.get(info_field
), expected
, info_field
)
733 test('%()j', (expect_same_infodict
, None))
736 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
737 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
738 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
739 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
740 test('%(non_existent.0)s', 'NA')
743 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
744 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
745 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
746 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
747 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
748 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
749 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
750 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
751 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
752 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
753 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
756 test('%(id)d', '1234')
757 test('%(height)c', '1')
759 test('%(id)d %(id)r', "1234 '1234'")
760 test('%(id)r %(height)r', "'1234' 1080")
761 test('%(title5)a %(height)a', (R
"'\xe1\xe9\xed \U0001d400' 1080", None))
762 test('%(ext)s-%(ext|def)d', 'mp4-def')
763 test('%(width|0)04d', '0')
764 test('a%(width|b)d', 'ab', outtmpl_na_placeholder
='none')
766 FORMATS
= self
.outtmpl_info
['formats']
768 # Custom type casting
769 test('%(formats.:.id)l', 'id 1, id 2, id 3')
770 test('%(formats.:.id)#l', ('id 1\nid 2\nid 3', 'id 1 id 2 id 3'))
771 test('%(ext)l', 'mp4')
772 test('%(formats.:.id) 18l', ' id 1, id 2, id 3')
773 test('%(formats)j', (json
.dumps(FORMATS
), None))
774 test('%(formats)#j', (
775 json
.dumps(FORMATS
, indent
=4),
776 json
.dumps(FORMATS
, indent
=4).replace(':', ':').replace('"', """).replace('\n', ' ')
778 test('%(title5).3B', 'á')
779 test('%(title5)U', 'áéí 𝐀')
780 test('%(title5)#U', 'a\u0301e\u0301i\u0301 𝐀')
781 test('%(title5)+U', 'áéí A')
782 test('%(title5)+#U', 'a\u0301e\u0301i\u0301 A')
783 test('%(height)D', '1k')
784 test('%(filesize)#D', '1Ki')
785 test('%(height)5.2D', ' 1.08k')
786 test('%(title4)#S', 'foo_bar_test')
787 test('%(title4).10S', ('foo "bar" ', 'foo "bar"' + ('#' if compat_os_name
== 'nt' else ' ')))
788 if compat_os_name
== 'nt':
789 test('%(title4)q', ('"foo ""bar"" test"', None))
790 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', None))
791 test('%(formats.0.id)#q', ('"id 1"', None))
793 test('%(title4)q', ('\'foo "bar" test\'', '\'foo "bar" test\''))
794 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'")
795 test('%(formats.0.id)#q', "'id 1'")
797 # Internal formatting
798 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
799 test('%(title|%)s %(title|%%)s', '% %%')
800 test('%(id+1-height+3)05d', '00158')
801 test('%(width+100)05d', 'NA')
802 test('%(filesize*8)d', '8192')
803 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], None))
804 test('%(formats.0)r', (repr(FORMATS
[0]), None))
805 test('%(height.0)03d', '001')
806 test('%(-height.0)04d', '-001')
807 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
808 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
809 test('%(formats.3)s', 'NA')
810 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
811 test('%(formats.0.id.-1+id)f', '1235.000000')
812 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
813 out
= json
.dumps([{'id': f['id'], 'height.:2': str(f['height'])[:2]}
814 if 'height' in f
else {'id': f['id']}
816 test('%(formats.:.{id,height.:2})j', (out
, None))
817 test('%(formats.:.{id,height}.id)l', ', '.join(f
['id'] for f
in FORMATS
))
818 test('%(.{id,title})j', ('{"id": "1234"}', '{"id": "1234"}'))
821 test('%(title,id)s', '1234')
822 test('%(width-100,height+20|def)d', '1100')
823 test('%(width-100,height+width|def)s', 'def')
824 test('%(timestamp-x>%H\\,%M\\,%S,timestamp>%H\\,%M\\,%S)s', '12,00,00')
827 test('%(id&foo)s.bar', 'foo.bar')
828 test('%(title&foo)s.bar', 'NA.bar')
829 test('%(title&foo|baz)s.bar', 'baz.bar')
830 test('%(x,id&foo|baz)s.bar', 'foo.bar')
831 test('%(x,title&foo|baz)s.bar', 'baz.bar')
832 test('%(id&a\nb|)s', ('a\nb', 'a b'))
833 test('%(id&hi {:>10} {}|)s', 'hi 1234 1234')
834 test(R
'%(id&{0} {}|)s', 'NA')
835 test(R
'%(id&{0.1}|)s', 'NA')
836 test('%(height&{:,d})S', '1,080')
841 raise self
.assertTrue(False, 'LazyList should not be evaluated till here')
842 test('%(key.4)s', '4', info
={'key': LazyList(gen())}
)
845 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
846 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
847 # test('%(foo|)s', ('', '_')) # fixme
849 # Environment variable expansion for prepare_filename
850 os
.environ
['__yt_dlp_var'] = 'expanded'
851 envvar
= '%__yt_dlp_var%' if compat_os_name
== 'nt' else '$__yt_dlp_var'
852 test(envvar
, (envvar
, 'expanded'))
853 if compat_os_name
== 'nt':
854 test('%s%', ('%s%', '%s%'))
855 os
.environ
['s'] = 'expanded'
856 test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
857 os
.environ
['(test)s'] = 'expanded'
858 test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
860 # Path expansion and escaping
861 test('Hello %(title1)s', 'Hello $PATH')
862 test('Hello %(title2)s', 'Hello %PATH%')
863 test('%(title3)s', ('foo/bar\\test', 'foo⧸bar⧹test'))
864 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo⧸bar⧹test' % os
.path
.sep
))
866 def test_format_note(self
):
868 self
.assertEqual(ydl
._format
_note
({}), '')
869 assertRegexpMatches(self
, ydl
._format
_note
({
872 assertRegexpMatches(self
, ydl
._format
_note
({
876 def test_postprocessors(self
):
877 filename
= 'post-processor-testfile.mp4'
878 audiofile
= filename
+ '.mp3'
880 class SimplePP(PostProcessor
):
882 with open(audiofile
, 'w') as f
:
884 return [info
['filepath']], info
886 def run_pp(params
, PP
):
887 with open(filename
, 'w') as f
:
889 ydl
= YoutubeDL(params
)
890 ydl
.add_post_processor(PP())
891 ydl
.post_process(filename
, {'filepath': filename}
)
893 run_pp({'keepvideo': True}
, SimplePP
)
894 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
895 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
899 run_pp({'keepvideo': False}
, SimplePP
)
900 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
901 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
904 class ModifierPP(PostProcessor
):
906 with open(info
['filepath'], 'w') as f
:
910 run_pp({'keepvideo': False}
, ModifierPP
)
911 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
914 def test_match_filter(self
):
921 'filesize': 10 * 1024,
923 'uploader': "變態妍字幕版 太妍 тест",
924 'creator': "тест ' 123 ' тест--",
925 'webpage_url': 'http://example.com/watch?v=shenanigans',
933 'description': 'foo',
934 'filesize': 5 * 1024,
936 'uploader': "тест 123",
937 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
939 videos
= [first
, second
]
941 def get_videos(filter_
=None):
942 ydl
= YDL({'match_filter': filter_, 'simulate': True}
)
944 ydl
.process_ie_result(v
.copy(), download
=True)
945 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
948 self
.assertEqual(res
, ['1', '2'])
950 def f(v
, incomplete
):
954 return 'Video id is not 1'
956 self
.assertEqual(res
, ['1'])
958 f
= match_filter_func('duration < 30')
960 self
.assertEqual(res
, ['2'])
962 f
= match_filter_func('description = foo')
964 self
.assertEqual(res
, ['2'])
966 f
= match_filter_func('description =? foo')
968 self
.assertEqual(res
, ['1', '2'])
970 f
= match_filter_func('filesize > 5KiB')
972 self
.assertEqual(res
, ['1'])
974 f
= match_filter_func('playlist_id = 42')
976 self
.assertEqual(res
, ['1'])
978 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
980 self
.assertEqual(res
, ['1'])
982 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
984 self
.assertEqual(res
, ['2'])
986 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
988 self
.assertEqual(res
, ['1'])
990 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
992 self
.assertEqual(res
, ['1'])
994 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
996 self
.assertEqual(res
, [])
998 def test_playlist_items_selection(self
):
999 INDICES
, PAGE_SIZE
= list(range(1, 11)), 3
1001 def entry(i
, evaluated
):
1009 def pagedlist_entries(evaluated
):
1011 start
= PAGE_SIZE
* n
1012 for i
in INDICES
[start
: start
+ PAGE_SIZE
]:
1013 yield entry(i
, evaluated
)
1014 return OnDemandPagedList(page_func
, PAGE_SIZE
)
1017 return (i
+ PAGE_SIZE
- 1) // PAGE_SIZE
1019 def generator_entries(evaluated
):
1021 yield entry(i
, evaluated
)
1023 def list_entries(evaluated
):
1024 return list(generator_entries(evaluated
))
1026 def lazylist_entries(evaluated
):
1027 return LazyList(generator_entries(evaluated
))
1029 def get_downloaded_info_dicts(params
, entries
):
1031 ydl
.process_ie_result({
1032 '_type': 'playlist',
1034 'extractor': 'test:playlist',
1035 'extractor_key': 'test:playlist',
1036 'webpage_url': 'http://example.com',
1039 return ydl
.downloaded_info_dicts
1041 def test_selection(params
, expected_ids
, evaluate_all
=False):
1042 expected_ids
= list(expected_ids
)
1044 generator_eval
= pagedlist_eval
= INDICES
1045 elif not expected_ids
:
1046 generator_eval
= pagedlist_eval
= []
1048 generator_eval
= INDICES
[0: max(expected_ids
)]
1049 pagedlist_eval
= INDICES
[PAGE_SIZE
* page_num(min(expected_ids
)) - PAGE_SIZE
:
1050 PAGE_SIZE
* page_num(max(expected_ids
))]
1052 for name
, func
, expected_eval
in (
1053 ('list', list_entries
, INDICES
),
1054 ('Generator', generator_entries
, generator_eval
),
1055 # ('LazyList', lazylist_entries, generator_eval), # Generator and LazyList follow the exact same code path
1056 ('PagedList', pagedlist_entries
, pagedlist_eval
),
1059 entries
= func(evaluated
)
1060 results
= [(v
['playlist_autonumber'] - 1, (int(v
['id']), v
['playlist_index']))
1061 for v
in get_downloaded_info_dicts(params
, entries
)]
1062 self
.assertEqual(results
, list(enumerate(zip(expected_ids
, expected_ids
))), f
'Entries of {name} for {params}')
1063 self
.assertEqual(sorted(evaluated
), expected_eval
, f
'Evaluation of {name} for {params}')
1065 test_selection({}, INDICES
)
1066 test_selection({'playlistend': 20}
, INDICES
, True)
1067 test_selection({'playlistend': 2}
, INDICES
[:2])
1068 test_selection({'playliststart': 11}
, [], True)
1069 test_selection({'playliststart': 2}
, INDICES
[1:])
1070 test_selection({'playlist_items': '2-4'}
, INDICES
[1:4])
1071 test_selection({'playlist_items': '2,4'}
, [2, 4])
1072 test_selection({'playlist_items': '20'}
, [], True)
1073 test_selection({'playlist_items': '0'}
, [])
1075 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
1076 test_selection({'playlist_items': '2-4,3-4,3'}
, [2, 3, 4])
1077 test_selection({'playlist_items': '4,2'}
, [4, 2])
1079 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720
1080 # https://github.com/yt-dlp/yt-dlp/issues/302
1081 test_selection({'playlistreverse': True}
, INDICES
[::-1])
1082 test_selection({'playliststart': 2, 'playlistreverse': True}
, INDICES
[:0:-1])
1083 test_selection({'playlist_items': '2,4', 'playlistreverse': True}
, [4, 2])
1084 test_selection({'playlist_items': '4,2'}
, [4, 2])
1086 # Tests for --playlist-items start:end:step
1087 test_selection({'playlist_items': ':'}
, INDICES
, True)
1088 test_selection({'playlist_items': '::1'}
, INDICES
, True)
1089 test_selection({'playlist_items': '::-1'}
, INDICES
[::-1], True)
1090 test_selection({'playlist_items': ':6'}
, INDICES
[:6])
1091 test_selection({'playlist_items': ':-6'}
, INDICES
[:-5], True)
1092 test_selection({'playlist_items': '-1:6:-2'}
, INDICES
[:4:-2], True)
1093 test_selection({'playlist_items': '9:-6:-2'}
, INDICES
[8:3:-2], True)
1095 test_selection({'playlist_items': '1:inf:2'}
, INDICES
[::2], True)
1096 test_selection({'playlist_items': '-2:inf'}
, INDICES
[-2:], True)
1097 test_selection({'playlist_items': ':inf:-1'}
, [], True)
1098 test_selection({'playlist_items': '0-2:2'}
, [2])
1099 test_selection({'playlist_items': '1-:2'}
, INDICES
[::2], True)
1100 test_selection({'playlist_items': '0--2:2'}
, INDICES
[1:-1:2], True)
1102 test_selection({'playlist_items': '10::3'}
, [10], True)
1103 test_selection({'playlist_items': '-1::3'}
, [10], True)
1104 test_selection({'playlist_items': '11::3'}
, [], True)
1105 test_selection({'playlist_items': '-15::2'}
, INDICES
[1::2], True)
1106 test_selection({'playlist_items': '-15::15'}
, [], True)
1108 def test_do_not_override_ie_key_in_url_transparent(self
):
1111 class Foo1IE(InfoExtractor
):
1112 _VALID_URL
= r
'foo1:'
1114 def _real_extract(self
, url
):
1116 '_type': 'url_transparent',
1119 'title': 'foo1 title',
1123 class Foo2IE(InfoExtractor
):
1124 _VALID_URL
= r
'foo2:'
1126 def _real_extract(self
, url
):
1133 class Foo3IE(InfoExtractor
):
1134 _VALID_URL
= r
'foo3:'
1136 def _real_extract(self
, url
):
1137 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1139 ydl
.add_info_extractor(Foo1IE(ydl
))
1140 ydl
.add_info_extractor(Foo2IE(ydl
))
1141 ydl
.add_info_extractor(Foo3IE(ydl
))
1142 ydl
.extract_info('foo1:')
1143 downloaded
= ydl
.downloaded_info_dicts
[0]
1144 self
.assertEqual(downloaded
['url'], TEST_URL
)
1145 self
.assertEqual(downloaded
['title'], 'foo1 title')
1146 self
.assertEqual(downloaded
['id'], 'testid')
1147 self
.assertEqual(downloaded
['extractor'], 'testex')
1148 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1150 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1151 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1154 def __init__(self
, *args
, **kwargs
):
1155 super().__init
__(*args
, **kwargs
)
1157 def trouble(self
, s
, tb
=None):
1162 'ignoreerrors': True,
1165 class VideoIE(InfoExtractor
):
1166 _VALID_URL
= r
'video:(?P<id>\d+)'
1168 def _real_extract(self
, url
):
1169 video_id
= self
._match
_id
(url
)
1171 'format_id': 'default',
1175 raise ExtractorError('foo')
1178 'format_id': 'extra',
1183 'title': 'Video %s' % video_id
,
1187 class PlaylistIE(InfoExtractor
):
1188 _VALID_URL
= r
'playlist:'
1194 '_type': 'url_transparent',
1195 'ie_key': VideoIE
.ie_key(),
1197 'url': 'video:%s' % video_id
,
1198 'title': 'Video Transparent %s' % video_id
,
1201 def _real_extract(self
, url
):
1202 return self
.playlist_result(self
._entries
())
1204 ydl
.add_info_extractor(VideoIE(ydl
))
1205 ydl
.add_info_extractor(PlaylistIE(ydl
))
1206 info
= ydl
.extract_info('playlist:')
1207 entries
= info
['entries']
1208 self
.assertEqual(len(entries
), 3)
1209 self
.assertTrue(entries
[0] is None)
1210 self
.assertTrue(entries
[1] is None)
1211 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1212 downloaded
= ydl
.downloaded_info_dicts
[0]
1213 entries
[2].pop('requested_downloads', None)
1214 self
.assertEqual(entries
[2], downloaded
)
1215 self
.assertEqual(downloaded
['url'], TEST_URL
)
1216 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1217 self
.assertEqual(downloaded
['id'], '2')
1218 self
.assertEqual(downloaded
['extractor'], 'Video')
1219 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1221 def test_header_cookies(self
):
1222 from http
.cookiejar
import Cookie
1225 ydl
.report_warning
= lambda *_
, **__
: None
1227 def cookie(name
, value
, version
=None, domain
='', path
='', secure
=False, expires
=None):
1229 version
or 0, name
, value
, None, False,
1230 domain
, bool(domain
), bool(domain
), path
, bool(path
),
1231 secure
, expires
, False, None, None, rest
={})
1233 _test_url
= 'https://yt.dlp/test'
1235 def test(encoded_cookies
, cookies
, *, headers
=False, round_trip
=None, error_re
=None):
1237 ydl
.cookiejar
.clear()
1238 ydl
._load
_cookies
(encoded_cookies
, autoscope
=headers
)
1240 ydl
._apply
_header
_cookies
(_test_url
)
1241 data
= {'url': _test_url}
1242 ydl
._calc
_headers
(data
)
1243 self
.assertCountEqual(
1244 map(vars, ydl
.cookiejar
), map(vars, cookies
),
1245 'Extracted cookiejar.Cookie is not the same')
1248 data
.get('cookies'), round_trip
or encoded_cookies
,
1249 'Cookie is not the same as round trip')
1250 ydl
.__dict
__['_YoutubeDL__header_cookies'] = []
1252 with self
.subTest(msg
=encoded_cookies
):
1256 with self
.assertRaisesRegex(Exception, error_re
):
1259 test('test=value; Domain=.yt.dlp', [cookie('test', 'value', domain
='.yt.dlp')])
1260 test('test=value', [cookie('test', 'value')], error_re
=r
'Unscoped cookies are not allowed')
1261 test('cookie1=value1; Domain=.yt.dlp; Path=/test; cookie2=value2; Domain=.yt.dlp; Path=/', [
1262 cookie('cookie1', 'value1', domain
='.yt.dlp', path
='/test'),
1263 cookie('cookie2', 'value2', domain
='.yt.dlp', path
='/')])
1264 test('test=value; Domain=.yt.dlp; Path=/test; Secure; Expires=9999999999', [
1265 cookie('test', 'value', domain
='.yt.dlp', path
='/test', secure
=True, expires
=9999999999)])
1266 test('test="value; "; path=/test; domain=.yt.dlp', [
1267 cookie('test', 'value; ', domain
='.yt.dlp', path
='/test')],
1268 round_trip
='test="value\\073 "; Domain=.yt.dlp; Path=/test')
1269 test('name=; Domain=.yt.dlp', [cookie('name', '', domain
='.yt.dlp')],
1270 round_trip
='name=""; Domain=.yt.dlp')
1272 test('test=value', [cookie('test', 'value', domain
='.yt.dlp')], headers
=True)
1273 test('cookie1=value; Domain=.yt.dlp; cookie2=value', [], headers
=True, error_re
=r
'Invalid syntax')
1274 ydl
.deprecated_feature
= ydl
.report_error
1275 test('test=value', [], headers
=True, error_re
=r
'Passing cookies as a header is a potential security risk')
1277 def test_infojson_cookies(self
):
1278 TEST_FILE
= 'test_infojson_cookies.info.json'
1279 TEST_URL
= 'https://example.com/example.mp4'
1280 COOKIES
= 'a=b; Domain=.example.com; c=d; Domain=.example.com'
1281 COOKIE_HEADER
= {'Cookie': 'a=b; c=d'}
1284 ydl
.process_info
= lambda x
: ydl
._write
_info
_json
('test', x
, TEST_FILE
)
1286 def make_info(info_header_cookies
=False, fmts_header_cookies
=False, cookies_field
=False):
1287 fmt
= {'url': TEST_URL}
1288 if fmts_header_cookies
:
1289 fmt
['http_headers'] = COOKIE_HEADER
1291 fmt
['cookies'] = COOKIES
1292 return _make_result([fmt
], http_headers
=COOKIE_HEADER
if info_header_cookies
else None)
1294 def test(initial_info
, note
):
1296 result
['processed'] = ydl
.process_ie_result(initial_info
)
1297 self
.assertTrue(ydl
.cookiejar
.get_cookies_for_url(TEST_URL
),
1298 msg
=f
'No cookies set in cookiejar after initial process when {note}')
1299 ydl
.cookiejar
.clear()
1300 with open(TEST_FILE
) as infojson
:
1301 result
['loaded'] = ydl
.sanitize_info(json
.load(infojson
), True)
1302 result
['final'] = ydl
.process_ie_result(result
['loaded'].copy(), download
=False)
1303 self
.assertTrue(ydl
.cookiejar
.get_cookies_for_url(TEST_URL
),
1304 msg
=f
'No cookies set in cookiejar after final process when {note}')
1305 ydl
.cookiejar
.clear()
1306 for key
in ('processed', 'loaded', 'final'):
1309 traverse_obj(info
, ((None, ('formats', 0)), 'http_headers', 'Cookie'), casesense
=False, get_all
=False),
1310 msg
=f
'Cookie header not removed in {key} result when {note}')
1312 traverse_obj(info
, ((None, ('formats', 0)), 'cookies'), get_all
=False), COOKIES
,
1313 msg
=f
'No cookies field found in {key} result when {note}')
1315 test({'url': TEST_URL, 'http_headers': COOKIE_HEADER, 'id': '1', 'title': 'x'}
, 'no formats field')
1316 test(make_info(info_header_cookies
=True), 'info_dict header cokies')
1317 test(make_info(fmts_header_cookies
=True), 'format header cookies')
1318 test(make_info(info_header_cookies
=True, fmts_header_cookies
=True), 'info_dict and format header cookies')
1319 test(make_info(info_header_cookies
=True, fmts_header_cookies
=True, cookies_field
=True), 'all cookies fields')
1320 test(make_info(cookies_field
=True), 'cookies format field')
1321 test({'url': TEST_URL, 'cookies': COOKIES, 'id': '1', 'title': 'x'}
, 'info_dict cookies field only')
1325 def test_add_headers_cookie(self
):
1326 def check_for_cookie_header(result
):
1327 return traverse_obj(result
, ((None, ('formats', 0)), 'http_headers', 'Cookie'), casesense
=False, get_all
=False)
1329 ydl
= FakeYDL({'http_headers': {'Cookie': 'a=b'}
})
1330 ydl
._apply
_header
_cookies
(_make_result([])['webpage_url']) # Scope to input webpage URL: .example.com
1332 fmt
= {'url': 'https://example.com/video.mp4'}
1333 result
= ydl
.process_ie_result(_make_result([fmt
]), download
=False)
1334 self
.assertIsNone(check_for_cookie_header(result
), msg
='http_headers cookies in result info_dict')
1335 self
.assertEqual(result
.get('cookies'), 'a=b; Domain=.example.com', msg
='No cookies were set in cookies field')
1336 self
.assertIn('a=b', ydl
.cookiejar
.get_cookie_header(fmt
['url']), msg
='No cookies were set in cookiejar')
1338 fmt
= {'url': 'https://wrong.com/video.mp4'}
1339 result
= ydl
.process_ie_result(_make_result([fmt
]), download
=False)
1340 self
.assertIsNone(check_for_cookie_header(result
), msg
='http_headers cookies for wrong domain')
1341 self
.assertFalse(result
.get('cookies'), msg
='Cookies set in cookies field for wrong domain')
1342 self
.assertFalse(ydl
.cookiejar
.get_cookie_header(fmt
['url']), msg
='Cookies set in cookiejar for wrong domain')
1345 if __name__
== '__main__':