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
30 TEST_URL
= 'http://localhost/sample.mp4'
34 def __init__(self
, *args
, **kwargs
):
35 super().__init
__(*args
, **kwargs
)
36 self
.downloaded_info_dicts
= []
39 def process_info(self
, info_dict
):
40 self
.downloaded_info_dicts
.append(info_dict
.copy())
42 def to_screen(self
, msg
, *args
, **kwargs
):
45 def dl(self
, *args
, **kwargs
):
46 assert False, 'Downloader must not be invoked for test_YoutubeDL'
49 def _make_result(formats
, **kwargs
):
53 'title': 'testttitle',
54 'extractor': 'testex',
55 'extractor_key': 'TestEx',
56 'webpage_url': 'http://example.com/watch?v=shenanigans',
62 class TestFormatSelection(unittest
.TestCase
):
63 def test_prefer_free_formats(self
):
64 # Same resolution => download webm
66 ydl
.params
['prefer_free_formats'] = True
68 {'ext': 'webm', 'height': 460, 'url': TEST_URL}
,
69 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}
,
71 info_dict
= _make_result(formats
)
72 ydl
.sort_formats(info_dict
)
73 ydl
.process_ie_result(info_dict
)
74 downloaded
= ydl
.downloaded_info_dicts
[0]
75 self
.assertEqual(downloaded
['ext'], 'webm')
77 # Different resolution => download best quality (mp4)
79 ydl
.params
['prefer_free_formats'] = True
81 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
82 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}
,
84 info_dict
['formats'] = formats
85 ydl
.sort_formats(info_dict
)
86 ydl
.process_ie_result(info_dict
)
87 downloaded
= ydl
.downloaded_info_dicts
[0]
88 self
.assertEqual(downloaded
['ext'], 'mp4')
90 # No prefer_free_formats => prefer mp4 and webm
92 ydl
.params
['prefer_free_formats'] = False
94 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
95 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}
,
96 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
98 info_dict
['formats'] = formats
99 ydl
.sort_formats(info_dict
)
100 ydl
.process_ie_result(info_dict
)
101 downloaded
= ydl
.downloaded_info_dicts
[0]
102 self
.assertEqual(downloaded
['ext'], 'mp4')
105 ydl
.params
['prefer_free_formats'] = False
107 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
108 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
110 info_dict
['formats'] = formats
111 ydl
.sort_formats(info_dict
)
112 ydl
.process_ie_result(info_dict
)
113 downloaded
= ydl
.downloaded_info_dicts
[0]
114 self
.assertEqual(downloaded
['ext'], 'webm')
116 def test_format_selection(self
):
118 {'format_id': '35', 'ext': 'mp4', 'preference': 0, 'url': TEST_URL}
,
119 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL}
,
120 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL}
,
121 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL}
,
122 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL}
,
124 info_dict
= _make_result(formats
)
126 def test(inp
, *expected
, multi
=False):
129 'allow_multiple_video_streams': multi
,
130 'allow_multiple_audio_streams': multi
,
132 ydl
.process_ie_result(info_dict
.copy())
133 downloaded
= map(lambda x
: x
['format_id'], ydl
.downloaded_info_dicts
)
134 self
.assertEqual(list(downloaded
), list(expected
))
137 test('20/71/worst', '35')
139 test('webm/mp4', '47')
140 test('3gp/40/mp4', '35')
141 test('example-with-dashes', 'example-with-dashes')
142 test('all', '2', '47', '45', 'example-with-dashes', '35')
143 test('mergeall', '2+47+45+example-with-dashes+35', multi
=True)
145 def test_format_selection_audio(self
):
147 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
148 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
149 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}
,
150 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL}
,
152 info_dict
= _make_result(formats
)
154 ydl
= YDL({'format': 'bestaudio'}
)
155 ydl
.process_ie_result(info_dict
.copy())
156 downloaded
= ydl
.downloaded_info_dicts
[0]
157 self
.assertEqual(downloaded
['format_id'], 'audio-high')
159 ydl
= YDL({'format': 'worstaudio'}
)
160 ydl
.process_ie_result(info_dict
.copy())
161 downloaded
= ydl
.downloaded_info_dicts
[0]
162 self
.assertEqual(downloaded
['format_id'], 'audio-low')
165 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
166 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL}
,
168 info_dict
= _make_result(formats
)
170 ydl
= YDL({'format': 'bestaudio/worstaudio/best'}
)
171 ydl
.process_ie_result(info_dict
.copy())
172 downloaded
= ydl
.downloaded_info_dicts
[0]
173 self
.assertEqual(downloaded
['format_id'], 'vid-high')
175 def test_format_selection_audio_exts(self
):
177 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
178 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
179 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
180 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
181 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
184 info_dict
= _make_result(formats
)
185 ydl
= YDL({'format': 'best'}
)
186 ydl
.sort_formats(info_dict
)
187 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
188 downloaded
= ydl
.downloaded_info_dicts
[0]
189 self
.assertEqual(downloaded
['format_id'], 'aac-64')
191 ydl
= YDL({'format': 'mp3'}
)
192 ydl
.sort_formats(info_dict
)
193 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
194 downloaded
= ydl
.downloaded_info_dicts
[0]
195 self
.assertEqual(downloaded
['format_id'], 'mp3-64')
197 ydl
= YDL({'prefer_free_formats': True}
)
198 ydl
.sort_formats(info_dict
)
199 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
200 downloaded
= ydl
.downloaded_info_dicts
[0]
201 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
203 def test_format_selection_video(self
):
205 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
206 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
207 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
209 info_dict
= _make_result(formats
)
211 ydl
= YDL({'format': 'bestvideo'}
)
212 ydl
.process_ie_result(info_dict
.copy())
213 downloaded
= ydl
.downloaded_info_dicts
[0]
214 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
216 ydl
= YDL({'format': 'worstvideo'}
)
217 ydl
.process_ie_result(info_dict
.copy())
218 downloaded
= ydl
.downloaded_info_dicts
[0]
219 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
221 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
222 ydl
.process_ie_result(info_dict
.copy())
223 downloaded
= ydl
.downloaded_info_dicts
[0]
224 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
227 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
229 info_dict
= _make_result(formats
)
231 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
232 ydl
.process_ie_result(info_dict
.copy())
233 downloaded
= ydl
.downloaded_info_dicts
[0]
234 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
236 def test_format_selection_string_ops(self
):
238 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
239 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
241 info_dict
= _make_result(formats
)
244 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
245 ydl
.process_ie_result(info_dict
.copy())
246 downloaded
= ydl
.downloaded_info_dicts
[0]
247 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
249 # does not equal (!=)
250 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
251 ydl
.process_ie_result(info_dict
.copy())
252 downloaded
= ydl
.downloaded_info_dicts
[0]
253 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
255 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
256 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
259 ydl
= YDL({'format': '[format_id^=abc]'}
)
260 ydl
.process_ie_result(info_dict
.copy())
261 downloaded
= ydl
.downloaded_info_dicts
[0]
262 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
264 # does not start with (!^=)
265 ydl
= YDL({'format': '[format_id!^=abc]'}
)
266 ydl
.process_ie_result(info_dict
.copy())
267 downloaded
= ydl
.downloaded_info_dicts
[0]
268 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
270 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
271 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
274 ydl
= YDL({'format': '[format_id$=cba]'}
)
275 ydl
.process_ie_result(info_dict
.copy())
276 downloaded
= ydl
.downloaded_info_dicts
[0]
277 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
279 # does not end with (!$=)
280 ydl
= YDL({'format': '[format_id!$=cba]'}
)
281 ydl
.process_ie_result(info_dict
.copy())
282 downloaded
= ydl
.downloaded_info_dicts
[0]
283 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
285 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
286 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
289 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
290 ydl
.process_ie_result(info_dict
.copy())
291 downloaded
= ydl
.downloaded_info_dicts
[0]
292 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
294 # does not contain (!*=)
295 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
296 ydl
.process_ie_result(info_dict
.copy())
297 downloaded
= ydl
.downloaded_info_dicts
[0]
298 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
300 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
301 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
303 ydl
= YDL({'format': '[format_id!*=-]'}
)
304 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
306 def test_youtube_format_selection(self
):
307 # FIXME: Rewrite in accordance with the new format sorting options
311 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
312 # Apple HTTP Live Streaming
313 '96', '95', '94', '93', '92', '132', '151',
315 '85', '84', '102', '83', '101', '82', '100',
317 '137', '248', '136', '247', '135', '246',
318 '245', '244', '134', '243', '133', '242', '160',
320 '141', '172', '140', '171', '139',
323 def format_info(f_id
):
324 info
= YoutubeIE
._formats
[f_id
].copy()
326 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
327 # and 'vcodec', while in tests such information is incomplete since
328 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
329 # test_YoutubeDL.test_youtube_format_selection is broken without
331 if 'acodec' in info
and 'vcodec' not in info
:
332 info
['vcodec'] = 'none'
333 elif 'vcodec' in info
and 'acodec' not in info
:
334 info
['acodec'] = 'none'
336 info
['format_id'] = f_id
337 info
['url'] = 'url:' + f_id
339 formats_order
= [format_info(f_id
) for f_id
in order
]
341 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
342 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
343 ydl
.sort_formats(info_dict
)
344 ydl
.process_ie_result(info_dict
)
345 downloaded
= ydl
.downloaded_info_dicts
[0]
346 self
.assertEqual(downloaded
['format_id'], '248+172')
347 self
.assertEqual(downloaded
['ext'], 'mp4')
349 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
350 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
351 ydl
.sort_formats(info_dict
)
352 ydl
.process_ie_result(info_dict
)
353 downloaded
= ydl
.downloaded_info_dicts
[0]
354 self
.assertEqual(downloaded
['format_id'], '38')
356 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
357 ydl
= YDL({'format': 'bestvideo/best,bestaudio'}
)
358 ydl
.sort_formats(info_dict
)
359 ydl
.process_ie_result(info_dict
)
360 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
361 self
.assertEqual(downloaded_ids
, ['137', '141'])
363 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
364 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'}
)
365 ydl
.sort_formats(info_dict
)
366 ydl
.process_ie_result(info_dict
)
367 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
368 self
.assertEqual(downloaded_ids
, ['137+141', '248+141'])
370 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
371 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'}
)
372 ydl
.sort_formats(info_dict
)
373 ydl
.process_ie_result(info_dict
)
374 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
375 self
.assertEqual(downloaded_ids
, ['136+141', '247+141'])
377 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
378 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}
)
379 ydl
.sort_formats(info_dict
)
380 ydl
.process_ie_result(info_dict
)
381 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
382 self
.assertEqual(downloaded_ids
, ['248+141'])
384 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
385 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
386 ydl
= YDL({'format': 'best/bestvideo'}
)
387 ydl
.sort_formats(info_dict
)
388 ydl
.process_ie_result(info_dict
)
389 downloaded
= ydl
.downloaded_info_dicts
[0]
390 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
392 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
393 ydl
= YDL({'format': 'best/bestvideo'}
)
394 ydl
.sort_formats(info_dict
)
395 ydl
.process_ie_result(info_dict
)
396 downloaded
= ydl
.downloaded_info_dicts
[0]
397 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
399 def test_audio_only_extractor_format_selection(self
):
400 # For extractors with incomplete formats (all formats are audio-only or
401 # video-only) best and worst should fallback to corresponding best/worst
402 # video-only or audio-only formats (as per
403 # https://github.com/ytdl-org/youtube-dl/pull/5556)
405 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
406 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
408 info_dict
= _make_result(formats
)
410 ydl
= YDL({'format': 'best'}
)
411 ydl
.process_ie_result(info_dict
.copy())
412 downloaded
= ydl
.downloaded_info_dicts
[0]
413 self
.assertEqual(downloaded
['format_id'], 'high')
415 ydl
= YDL({'format': 'worst'}
)
416 ydl
.process_ie_result(info_dict
.copy())
417 downloaded
= ydl
.downloaded_info_dicts
[0]
418 self
.assertEqual(downloaded
['format_id'], 'low')
420 def test_format_not_available(self
):
422 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
423 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
425 info_dict
= _make_result(formats
)
427 # This must fail since complete video-audio format does not match filter
428 # and extractor does not provide incomplete only formats (i.e. only
429 # video-only or audio-only).
430 ydl
= YDL({'format': 'best[height>360]'}
)
431 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
433 def test_format_selection_issue_10083(self
):
434 # See https://github.com/ytdl-org/youtube-dl/issues/10083
436 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
437 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
438 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
440 info_dict
= _make_result(formats
)
442 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
443 ydl
.process_ie_result(info_dict
.copy())
444 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
446 def test_invalid_format_specs(self
):
447 def assert_syntax_error(format_spec
):
448 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec}
)
450 assert_syntax_error('bestvideo,,best')
451 assert_syntax_error('+bestaudio')
452 assert_syntax_error('bestvideo+')
453 assert_syntax_error('/')
454 assert_syntax_error('[720<height]')
456 def test_format_filtering(self
):
458 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
459 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
460 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
461 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
462 {'format_id': 'E', 'filesize': 3000}
,
464 {'format_id': 'G', 'filesize': 1000000}
,
467 f
['url'] = 'http://_/'
469 info_dict
= _make_result(formats
, _format_sort_fields
=('id', ))
471 ydl
= YDL({'format': 'best[filesize<3000]'}
)
472 ydl
.process_ie_result(info_dict
)
473 downloaded
= ydl
.downloaded_info_dicts
[0]
474 self
.assertEqual(downloaded
['format_id'], 'D')
476 ydl
= YDL({'format': 'best[filesize<=3000]'}
)
477 ydl
.process_ie_result(info_dict
)
478 downloaded
= ydl
.downloaded_info_dicts
[0]
479 self
.assertEqual(downloaded
['format_id'], 'E')
481 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
482 ydl
.process_ie_result(info_dict
)
483 downloaded
= ydl
.downloaded_info_dicts
[0]
484 self
.assertEqual(downloaded
['format_id'], 'F')
486 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'}
)
487 ydl
.process_ie_result(info_dict
)
488 downloaded
= ydl
.downloaded_info_dicts
[0]
489 self
.assertEqual(downloaded
['format_id'], 'B')
491 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
492 ydl
.process_ie_result(info_dict
)
493 downloaded
= ydl
.downloaded_info_dicts
[0]
494 self
.assertEqual(downloaded
['format_id'], 'C')
496 ydl
= YDL({'format': '[filesize>?1]'}
)
497 ydl
.process_ie_result(info_dict
)
498 downloaded
= ydl
.downloaded_info_dicts
[0]
499 self
.assertEqual(downloaded
['format_id'], 'G')
501 ydl
= YDL({'format': '[filesize<1M]'}
)
502 ydl
.process_ie_result(info_dict
)
503 downloaded
= ydl
.downloaded_info_dicts
[0]
504 self
.assertEqual(downloaded
['format_id'], 'E')
506 ydl
= YDL({'format': '[filesize<1MiB]'}
)
507 ydl
.process_ie_result(info_dict
)
508 downloaded
= ydl
.downloaded_info_dicts
[0]
509 self
.assertEqual(downloaded
['format_id'], 'G')
511 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
512 ydl
.process_ie_result(info_dict
)
513 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
514 self
.assertEqual(downloaded_ids
, ['D', 'C', 'B'])
516 ydl
= YDL({'format': 'best[height<40]'}
)
518 ydl
.process_ie_result(info_dict
)
519 except ExtractorError
:
521 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
523 def test_default_format_spec(self
):
524 ydl
= YDL({'simulate': True}
)
525 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
528 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
530 ydl
= YDL({'simulate': True}
)
531 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
533 ydl
= YDL({'outtmpl': '-'}
)
534 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
537 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
538 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
541 class TestYoutubeDL(unittest
.TestCase
):
542 def test_subtitles(self
):
543 def s_formats(lang
, autocaption
=False):
546 'url': f
'http://localhost/video.{lang}.{ext}',
547 '_auto': autocaption
,
548 } for ext
in ['vtt', 'srt', 'ass']]
549 subtitles
= {l: s_formats(l) for l in ['en', 'fr', 'es']}
550 auto_captions
= {l: s_formats(l, True) for l in ['it', 'pt', 'es']}
554 'url': 'http://localhost/video.mp4',
555 'subtitles': subtitles
,
556 'automatic_captions': auto_captions
,
558 'webpage_url': 'http://example.com/watch?v=shenanigans',
561 def get_info(params
={}):
562 params
.setdefault('simulate', True)
564 ydl
.report_warning
= lambda *args
, **kargs
: None
565 return ydl
.process_video_result(info_dict
, download
=False)
568 self
.assertFalse(result
.get('requested_subtitles'))
569 self
.assertEqual(result
['subtitles'], subtitles
)
570 self
.assertEqual(result
['automatic_captions'], auto_captions
)
572 result
= get_info({'writesubtitles': True}
)
573 subs
= result
['requested_subtitles']
574 self
.assertTrue(subs
)
575 self
.assertEqual(set(subs
.keys()), {'en'}
)
576 self
.assertTrue(subs
['en'].get('data') is None)
577 self
.assertEqual(subs
['en']['ext'], 'ass')
579 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
580 subs
= result
['requested_subtitles']
581 self
.assertEqual(subs
['en']['ext'], 'srt')
583 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
584 subs
= result
['requested_subtitles']
585 self
.assertTrue(subs
)
586 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
588 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
589 subs
= result
['requested_subtitles']
590 self
.assertTrue(subs
)
591 self
.assertEqual(set(subs
.keys()), {'es', 'fr'}
)
593 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
594 subs
= result
['requested_subtitles']
595 self
.assertTrue(subs
)
596 self
.assertEqual(set(subs
.keys()), {'fr'}
)
598 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
599 subs
= result
['requested_subtitles']
600 self
.assertTrue(subs
)
601 self
.assertEqual(set(subs
.keys()), {'en'}
)
603 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
604 subs
= result
['requested_subtitles']
605 self
.assertTrue(subs
)
606 self
.assertEqual(set(subs
.keys()), {'es', 'en'}
)
608 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
609 subs
= result
['requested_subtitles']
610 self
.assertTrue(subs
)
611 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
612 self
.assertFalse(subs
['es']['_auto'])
613 self
.assertTrue(subs
['pt']['_auto'])
615 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
616 subs
= result
['requested_subtitles']
617 self
.assertTrue(subs
)
618 self
.assertEqual(set(subs
.keys()), {'es', 'pt'}
)
619 self
.assertTrue(subs
['es']['_auto'])
620 self
.assertTrue(subs
['pt']['_auto'])
622 def test_add_extra_info(self
):
628 'playlist': 'funny videos',
630 YDL
.add_extra_info(test_dict
, extra_info
)
631 self
.assertEqual(test_dict
['extractor'], 'Foo')
632 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_autonumber)s', '02')
691 test('%(autonumber)s', '00001')
692 test('%(autonumber+2)03d', '005', autonumber_start
=3)
693 test('%(autonumber)s', '001', autonumber_size
=3)
702 test('%abc%', '%abc%')
703 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
704 test('%%%(height)s', '%1080')
705 test('%(width)06d.%(ext)s', 'NA.mp4')
706 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
707 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
710 test('%(id)s', '_abcd', info
={'id': '_abcd'}
)
711 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'}
)
712 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}
]})
713 test('%(id)s', '-abcd', info
={'id': '-abcd'}
)
714 test('%(id)s', '.abcd', info
={'id': '.abcd'}
)
715 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'}
)
716 test('%(id)s', ('ab:cd', 'ab:cd'), info
={'id': 'ab:cd'}
)
717 test('%(id.0)s', '-', info
={'id': '--'}
)
720 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
721 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
723 test('%(formats.{id)s', 'NA')
726 def expect_same_infodict(out
):
727 got_dict
= json
.loads(out
)
728 for info_field
, expected
in self
.outtmpl_info
.items():
729 self
.assertEqual(got_dict
.get(info_field
), expected
, info_field
)
732 test('%()j', (expect_same_infodict
, str))
735 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
736 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
737 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
738 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
739 test('%(non_existent.0)s', 'NA')
742 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
743 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
744 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
745 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
746 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
747 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
748 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
749 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
750 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
751 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
752 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
755 test('%(id)d', '1234')
756 test('%(height)c', '1')
758 test('%(id)d %(id)r', "1234 '1234'")
759 test('%(id)r %(height)r', "'1234' 1080")
760 test('%(title5)a %(height)a', (R
"'\xe1\xe9\xed \U0001d400' 1080", None))
761 test('%(ext)s-%(ext|def)d', 'mp4-def')
762 test('%(width|0)04d', '0')
763 test('a%(width|b)d', 'ab', outtmpl_na_placeholder
='none')
765 FORMATS
= self
.outtmpl_info
['formats']
767 # Custom type casting
768 test('%(formats.:.id)l', 'id 1, id 2, id 3')
769 test('%(formats.:.id)#l', ('id 1\nid 2\nid 3', 'id 1 id 2 id 3'))
770 test('%(ext)l', 'mp4')
771 test('%(formats.:.id) 18l', ' id 1, id 2, id 3')
772 test('%(formats)j', (json
.dumps(FORMATS
), None))
773 test('%(formats)#j', (
774 json
.dumps(FORMATS
, indent
=4),
775 json
.dumps(FORMATS
, indent
=4).replace(':', ':').replace('"', """).replace('\n', ' ')
777 test('%(title5).3B', 'á')
778 test('%(title5)U', 'áéí 𝐀')
779 test('%(title5)#U', 'a\u0301e\u0301i\u0301 𝐀')
780 test('%(title5)+U', 'áéí A')
781 test('%(title5)+#U', 'a\u0301e\u0301i\u0301 A')
782 test('%(height)D', '1k')
783 test('%(filesize)#D', '1Ki')
784 test('%(height)5.2D', ' 1.08k')
785 test('%(title4)#S', 'foo_bar_test')
786 test('%(title4).10S', ('foo "bar" ', 'foo "bar"' + ('#' if compat_os_name
== 'nt' else ' ')))
787 if compat_os_name
== 'nt':
788 test('%(title4)q', ('"foo \\"bar\\" test"', ""foo ⧹"bar⧹" test""))
789 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', '"id 1" "id 2" "id 3"'))
790 test('%(formats.0.id)#q', ('"id 1"', '"id 1"'))
792 test('%(title4)q', ('\'foo "bar" test\'', '\'foo "bar" test\''))
793 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'")
794 test('%(formats.0.id)#q', "'id 1'")
796 # Internal formatting
797 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
798 test('%(title|%)s %(title|%%)s', '% %%')
799 test('%(id+1-height+3)05d', '00158')
800 test('%(width+100)05d', 'NA')
801 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], None))
802 test('%(formats.0)r', (repr(FORMATS
[0]), None))
803 test('%(height.0)03d', '001')
804 test('%(-height.0)04d', '-001')
805 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
806 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
807 test('%(formats.3)s', 'NA')
808 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
809 test('%(formats.0.id.-1+id)f', '1235.000000')
810 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
811 out
= json
.dumps([{'id': f['id'], 'height.:2': str(f['height'])[:2]}
812 if 'height' in f
else {'id': f['id']}
814 test('%(formats.:.{id,height.:2})j', (out
, None))
815 test('%(formats.:.{id,height}.id)l', ', '.join(f
['id'] for f
in FORMATS
))
816 test('%(.{id,title})j', ('{"id": "1234"}', '{"id": "1234"}'))
819 test('%(title,id)s', '1234')
820 test('%(width-100,height+20|def)d', '1100')
821 test('%(width-100,height+width|def)s', 'def')
822 test('%(timestamp-x>%H\\,%M\\,%S,timestamp>%H\\,%M\\,%S)s', '12,00,00')
825 test('%(id&foo)s.bar', 'foo.bar')
826 test('%(title&foo)s.bar', 'NA.bar')
827 test('%(title&foo|baz)s.bar', 'baz.bar')
828 test('%(x,id&foo|baz)s.bar', 'foo.bar')
829 test('%(x,title&foo|baz)s.bar', 'baz.bar')
830 test('%(id&a\nb|)s', ('a\nb', 'a b'))
831 test('%(id&hi {:>10} {}|)s', 'hi 1234 1234')
832 test(R
'%(id&{0} {}|)s', 'NA')
833 test(R
'%(id&{0.1}|)s', 'NA')
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__':