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
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 (
28 TEST_URL
= 'http://localhost/sample.mp4'
32 def __init__(self
, *args
, **kwargs
):
33 super().__init
__(*args
, **kwargs
)
34 self
.downloaded_info_dicts
= []
37 def process_info(self
, info_dict
):
38 self
.downloaded_info_dicts
.append(info_dict
.copy())
40 def to_screen(self
, msg
, *args
, **kwargs
):
43 def dl(self
, *args
, **kwargs
):
44 assert False, 'Downloader must not be invoked for test_YoutubeDL'
47 def _make_result(formats
, **kwargs
):
51 'title': 'testttitle',
52 'extractor': 'testex',
53 'extractor_key': 'TestEx',
54 'webpage_url': 'http://example.com/watch?v=shenanigans',
60 class TestFormatSelection(unittest
.TestCase
):
61 def test_prefer_free_formats(self
):
62 # Same resolution => download webm
64 ydl
.params
['prefer_free_formats'] = True
66 {'ext': 'webm', 'height': 460, 'url': TEST_URL
},
67 {'ext': 'mp4', 'height': 460, 'url': TEST_URL
},
69 info_dict
= _make_result(formats
)
70 ydl
.sort_formats(info_dict
)
71 ydl
.process_ie_result(info_dict
)
72 downloaded
= ydl
.downloaded_info_dicts
[0]
73 self
.assertEqual(downloaded
['ext'], 'webm')
75 # Different resolution => download best quality (mp4)
77 ydl
.params
['prefer_free_formats'] = True
79 {'ext': 'webm', 'height': 720, 'url': TEST_URL
},
80 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL
},
82 info_dict
['formats'] = formats
83 ydl
.sort_formats(info_dict
)
84 ydl
.process_ie_result(info_dict
)
85 downloaded
= ydl
.downloaded_info_dicts
[0]
86 self
.assertEqual(downloaded
['ext'], 'mp4')
88 # No prefer_free_formats => prefer mp4 and webm
90 ydl
.params
['prefer_free_formats'] = False
92 {'ext': 'webm', 'height': 720, 'url': TEST_URL
},
93 {'ext': 'mp4', 'height': 720, 'url': TEST_URL
},
94 {'ext': 'flv', 'height': 720, 'url': TEST_URL
},
96 info_dict
['formats'] = formats
97 ydl
.sort_formats(info_dict
)
98 ydl
.process_ie_result(info_dict
)
99 downloaded
= ydl
.downloaded_info_dicts
[0]
100 self
.assertEqual(downloaded
['ext'], 'mp4')
103 ydl
.params
['prefer_free_formats'] = False
105 {'ext': 'flv', 'height': 720, 'url': TEST_URL
},
106 {'ext': 'webm', 'height': 720, 'url': TEST_URL
},
108 info_dict
['formats'] = formats
109 ydl
.sort_formats(info_dict
)
110 ydl
.process_ie_result(info_dict
)
111 downloaded
= ydl
.downloaded_info_dicts
[0]
112 self
.assertEqual(downloaded
['ext'], 'webm')
114 def test_format_selection(self
):
116 {'format_id': '35', 'ext': 'mp4', 'preference': 0, 'url': TEST_URL
},
117 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL
},
118 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL
},
119 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL
},
120 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL
},
122 info_dict
= _make_result(formats
)
124 def test(inp
, *expected
, multi
=False):
127 'allow_multiple_video_streams': multi
,
128 'allow_multiple_audio_streams': multi
,
130 ydl
.process_ie_result(info_dict
.copy())
131 downloaded
= map(lambda x
: x
['format_id'], ydl
.downloaded_info_dicts
)
132 self
.assertEqual(list(downloaded
), list(expected
))
135 test('20/71/worst', '35')
137 test('webm/mp4', '47')
138 test('3gp/40/mp4', '35')
139 test('example-with-dashes', 'example-with-dashes')
140 test('all', '2', '47', '45', 'example-with-dashes', '35')
141 test('mergeall', '2+47+45+example-with-dashes+35', multi
=True)
143 def test_format_selection_audio(self
):
145 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL
},
146 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL
},
147 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL
},
148 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL
},
150 info_dict
= _make_result(formats
)
152 ydl
= YDL({'format': 'bestaudio'})
153 ydl
.process_ie_result(info_dict
.copy())
154 downloaded
= ydl
.downloaded_info_dicts
[0]
155 self
.assertEqual(downloaded
['format_id'], 'audio-high')
157 ydl
= YDL({'format': 'worstaudio'})
158 ydl
.process_ie_result(info_dict
.copy())
159 downloaded
= ydl
.downloaded_info_dicts
[0]
160 self
.assertEqual(downloaded
['format_id'], 'audio-low')
163 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL
},
164 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL
},
166 info_dict
= _make_result(formats
)
168 ydl
= YDL({'format': 'bestaudio/worstaudio/best'})
169 ydl
.process_ie_result(info_dict
.copy())
170 downloaded
= ydl
.downloaded_info_dicts
[0]
171 self
.assertEqual(downloaded
['format_id'], 'vid-high')
173 def test_format_selection_audio_exts(self
):
175 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
176 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
177 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
178 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
179 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
182 info_dict
= _make_result(formats
)
183 ydl
= YDL({'format': 'best'})
184 ydl
.sort_formats(info_dict
)
185 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
186 downloaded
= ydl
.downloaded_info_dicts
[0]
187 self
.assertEqual(downloaded
['format_id'], 'aac-64')
189 ydl
= YDL({'format': 'mp3'})
190 ydl
.sort_formats(info_dict
)
191 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
192 downloaded
= ydl
.downloaded_info_dicts
[0]
193 self
.assertEqual(downloaded
['format_id'], 'mp3-64')
195 ydl
= YDL({'prefer_free_formats': True})
196 ydl
.sort_formats(info_dict
)
197 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
198 downloaded
= ydl
.downloaded_info_dicts
[0]
199 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
201 def test_format_selection_video(self
):
203 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL
},
204 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL
},
205 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL
},
207 info_dict
= _make_result(formats
)
209 ydl
= YDL({'format': 'bestvideo'})
210 ydl
.process_ie_result(info_dict
.copy())
211 downloaded
= ydl
.downloaded_info_dicts
[0]
212 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
214 ydl
= YDL({'format': 'worstvideo'})
215 ydl
.process_ie_result(info_dict
.copy())
216 downloaded
= ydl
.downloaded_info_dicts
[0]
217 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
219 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'})
220 ydl
.process_ie_result(info_dict
.copy())
221 downloaded
= ydl
.downloaded_info_dicts
[0]
222 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
225 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL
},
227 info_dict
= _make_result(formats
)
229 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'})
230 ydl
.process_ie_result(info_dict
.copy())
231 downloaded
= ydl
.downloaded_info_dicts
[0]
232 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
234 def test_format_selection_string_ops(self
):
236 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL
},
237 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL
},
239 info_dict
= _make_result(formats
)
242 ydl
= YDL({'format': '[format_id=abc-cba]'})
243 ydl
.process_ie_result(info_dict
.copy())
244 downloaded
= ydl
.downloaded_info_dicts
[0]
245 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
247 # does not equal (!=)
248 ydl
= YDL({'format': '[format_id!=abc-cba]'})
249 ydl
.process_ie_result(info_dict
.copy())
250 downloaded
= ydl
.downloaded_info_dicts
[0]
251 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
253 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'})
254 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
257 ydl
= YDL({'format': '[format_id^=abc]'})
258 ydl
.process_ie_result(info_dict
.copy())
259 downloaded
= ydl
.downloaded_info_dicts
[0]
260 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
262 # does not start with (!^=)
263 ydl
= YDL({'format': '[format_id!^=abc]'})
264 ydl
.process_ie_result(info_dict
.copy())
265 downloaded
= ydl
.downloaded_info_dicts
[0]
266 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
268 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'})
269 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
272 ydl
= YDL({'format': '[format_id$=cba]'})
273 ydl
.process_ie_result(info_dict
.copy())
274 downloaded
= ydl
.downloaded_info_dicts
[0]
275 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
277 # does not end with (!$=)
278 ydl
= YDL({'format': '[format_id!$=cba]'})
279 ydl
.process_ie_result(info_dict
.copy())
280 downloaded
= ydl
.downloaded_info_dicts
[0]
281 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
283 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'})
284 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
287 ydl
= YDL({'format': '[format_id*=bc-cb]'})
288 ydl
.process_ie_result(info_dict
.copy())
289 downloaded
= ydl
.downloaded_info_dicts
[0]
290 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
292 # does not contain (!*=)
293 ydl
= YDL({'format': '[format_id!*=bc-cb]'})
294 ydl
.process_ie_result(info_dict
.copy())
295 downloaded
= ydl
.downloaded_info_dicts
[0]
296 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
298 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'})
299 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
301 ydl
= YDL({'format': '[format_id!*=-]'})
302 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
304 def test_youtube_format_selection(self
):
305 # FIXME: Rewrite in accordance with the new format sorting options
309 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
310 # Apple HTTP Live Streaming
311 '96', '95', '94', '93', '92', '132', '151',
313 '85', '84', '102', '83', '101', '82', '100',
315 '137', '248', '136', '247', '135', '246',
316 '245', '244', '134', '243', '133', '242', '160',
318 '141', '172', '140', '171', '139',
321 def format_info(f_id
):
322 info
= YoutubeIE
._formats
[f_id
].copy()
324 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
325 # and 'vcodec', while in tests such information is incomplete since
326 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
327 # test_YoutubeDL.test_youtube_format_selection is broken without
329 if 'acodec' in info
and 'vcodec' not in info
:
330 info
['vcodec'] = 'none'
331 elif 'vcodec' in info
and 'acodec' not in info
:
332 info
['acodec'] = 'none'
334 info
['format_id'] = f_id
335 info
['url'] = 'url:' + f_id
337 formats_order
= [format_info(f_id
) for f_id
in order
]
339 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
340 ydl
= YDL({'format': 'bestvideo+bestaudio'})
341 ydl
.sort_formats(info_dict
)
342 ydl
.process_ie_result(info_dict
)
343 downloaded
= ydl
.downloaded_info_dicts
[0]
344 self
.assertEqual(downloaded
['format_id'], '248+172')
345 self
.assertEqual(downloaded
['ext'], 'mp4')
347 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
348 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'})
349 ydl
.sort_formats(info_dict
)
350 ydl
.process_ie_result(info_dict
)
351 downloaded
= ydl
.downloaded_info_dicts
[0]
352 self
.assertEqual(downloaded
['format_id'], '38')
354 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
355 ydl
= YDL({'format': 'bestvideo/best,bestaudio'})
356 ydl
.sort_formats(info_dict
)
357 ydl
.process_ie_result(info_dict
)
358 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
359 self
.assertEqual(downloaded_ids
, ['137', '141'])
361 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
362 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'})
363 ydl
.sort_formats(info_dict
)
364 ydl
.process_ie_result(info_dict
)
365 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
366 self
.assertEqual(downloaded_ids
, ['137+141', '248+141'])
368 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
369 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'})
370 ydl
.sort_formats(info_dict
)
371 ydl
.process_ie_result(info_dict
)
372 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
373 self
.assertEqual(downloaded_ids
, ['136+141', '247+141'])
375 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
376 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'})
377 ydl
.sort_formats(info_dict
)
378 ydl
.process_ie_result(info_dict
)
379 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
380 self
.assertEqual(downloaded_ids
, ['248+141'])
382 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
383 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
384 ydl
= YDL({'format': 'best/bestvideo'})
385 ydl
.sort_formats(info_dict
)
386 ydl
.process_ie_result(info_dict
)
387 downloaded
= ydl
.downloaded_info_dicts
[0]
388 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
390 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
391 ydl
= YDL({'format': 'best/bestvideo'})
392 ydl
.sort_formats(info_dict
)
393 ydl
.process_ie_result(info_dict
)
394 downloaded
= ydl
.downloaded_info_dicts
[0]
395 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
397 def test_audio_only_extractor_format_selection(self
):
398 # For extractors with incomplete formats (all formats are audio-only or
399 # video-only) best and worst should fallback to corresponding best/worst
400 # video-only or audio-only formats (as per
401 # https://github.com/ytdl-org/youtube-dl/pull/5556)
403 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL
},
404 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL
},
406 info_dict
= _make_result(formats
)
408 ydl
= YDL({'format': 'best'})
409 ydl
.process_ie_result(info_dict
.copy())
410 downloaded
= ydl
.downloaded_info_dicts
[0]
411 self
.assertEqual(downloaded
['format_id'], 'high')
413 ydl
= YDL({'format': 'worst'})
414 ydl
.process_ie_result(info_dict
.copy())
415 downloaded
= ydl
.downloaded_info_dicts
[0]
416 self
.assertEqual(downloaded
['format_id'], 'low')
418 def test_format_not_available(self
):
420 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL
},
421 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL
},
423 info_dict
= _make_result(formats
)
425 # This must fail since complete video-audio format does not match filter
426 # and extractor does not provide incomplete only formats (i.e. only
427 # video-only or audio-only).
428 ydl
= YDL({'format': 'best[height>360]'})
429 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
431 def test_format_selection_issue_10083(self
):
432 # See https://github.com/ytdl-org/youtube-dl/issues/10083
434 {'format_id': 'regular', 'height': 360, 'url': TEST_URL
},
435 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL
},
436 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL
},
438 info_dict
= _make_result(formats
)
440 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'})
441 ydl
.process_ie_result(info_dict
.copy())
442 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
444 def test_invalid_format_specs(self
):
445 def assert_syntax_error(format_spec
):
446 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec
})
448 assert_syntax_error('bestvideo,,best')
449 assert_syntax_error('+bestaudio')
450 assert_syntax_error('bestvideo+')
451 assert_syntax_error('/')
452 assert_syntax_error('[720<height]')
454 def test_format_filtering(self
):
456 {'format_id': 'A', 'filesize': 500, 'width': 1000},
457 {'format_id': 'B', 'filesize': 1000, 'width': 500},
458 {'format_id': 'C', 'filesize': 1000, 'width': 400},
459 {'format_id': 'D', 'filesize': 2000, 'width': 600},
460 {'format_id': 'E', 'filesize': 3000},
462 {'format_id': 'G', 'filesize': 1000000},
465 f
['url'] = 'http://_/'
467 info_dict
= _make_result(formats
, _format_sort_fields
=('id', ))
469 ydl
= YDL({'format': 'best[filesize<3000]'})
470 ydl
.process_ie_result(info_dict
)
471 downloaded
= ydl
.downloaded_info_dicts
[0]
472 self
.assertEqual(downloaded
['format_id'], 'D')
474 ydl
= YDL({'format': 'best[filesize<=3000]'})
475 ydl
.process_ie_result(info_dict
)
476 downloaded
= ydl
.downloaded_info_dicts
[0]
477 self
.assertEqual(downloaded
['format_id'], 'E')
479 ydl
= YDL({'format': 'best[filesize <= ? 3000]'})
480 ydl
.process_ie_result(info_dict
)
481 downloaded
= ydl
.downloaded_info_dicts
[0]
482 self
.assertEqual(downloaded
['format_id'], 'F')
484 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'})
485 ydl
.process_ie_result(info_dict
)
486 downloaded
= ydl
.downloaded_info_dicts
[0]
487 self
.assertEqual(downloaded
['format_id'], 'B')
489 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'})
490 ydl
.process_ie_result(info_dict
)
491 downloaded
= ydl
.downloaded_info_dicts
[0]
492 self
.assertEqual(downloaded
['format_id'], 'C')
494 ydl
= YDL({'format': '[filesize>?1]'})
495 ydl
.process_ie_result(info_dict
)
496 downloaded
= ydl
.downloaded_info_dicts
[0]
497 self
.assertEqual(downloaded
['format_id'], 'G')
499 ydl
= YDL({'format': '[filesize<1M]'})
500 ydl
.process_ie_result(info_dict
)
501 downloaded
= ydl
.downloaded_info_dicts
[0]
502 self
.assertEqual(downloaded
['format_id'], 'E')
504 ydl
= YDL({'format': '[filesize<1MiB]'})
505 ydl
.process_ie_result(info_dict
)
506 downloaded
= ydl
.downloaded_info_dicts
[0]
507 self
.assertEqual(downloaded
['format_id'], 'G')
509 ydl
= YDL({'format': 'all[width>=400][width<=600]'})
510 ydl
.process_ie_result(info_dict
)
511 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
512 self
.assertEqual(downloaded_ids
, ['D', 'C', 'B'])
514 ydl
= YDL({'format': 'best[height<40]'})
516 ydl
.process_ie_result(info_dict
)
517 except ExtractorError
:
519 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
521 def test_default_format_spec(self
):
522 ydl
= YDL({'simulate': True})
523 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
526 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}), 'best/bestvideo+bestaudio')
528 ydl
= YDL({'simulate': True})
529 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}), 'bestvideo*+bestaudio/best')
531 ydl
= YDL({'outtmpl': '-'})
532 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
535 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
536 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}), 'best/bestvideo+bestaudio')
539 class TestYoutubeDL(unittest
.TestCase
):
540 def test_subtitles(self
):
541 def s_formats(lang
, autocaption
=False):
544 'url': f
'http://localhost/video.{lang}.{ext}',
545 '_auto': autocaption
,
546 } for ext
in ['vtt', 'srt', 'ass']]
547 subtitles
= {l
: s_formats(l
) for l
in ['en', 'fr', 'es']}
548 auto_captions
= {l
: s_formats(l
, True) for l
in ['it', 'pt', 'es']}
552 'url': 'http://localhost/video.mp4',
553 'subtitles': subtitles
,
554 'automatic_captions': auto_captions
,
556 'webpage_url': 'http://example.com/watch?v=shenanigans',
559 def get_info(params
={}):
560 params
.setdefault('simulate', True)
562 ydl
.report_warning
= lambda *args
, **kargs
: None
563 return ydl
.process_video_result(info_dict
, download
=False)
566 self
.assertFalse(result
.get('requested_subtitles'))
567 self
.assertEqual(result
['subtitles'], subtitles
)
568 self
.assertEqual(result
['automatic_captions'], auto_captions
)
570 result
= get_info({'writesubtitles': True})
571 subs
= result
['requested_subtitles']
572 self
.assertTrue(subs
)
573 self
.assertEqual(set(subs
.keys()), {'en'})
574 self
.assertTrue(subs
['en'].get('data') is None)
575 self
.assertEqual(subs
['en']['ext'], 'ass')
577 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'})
578 subs
= result
['requested_subtitles']
579 self
.assertEqual(subs
['en']['ext'], 'srt')
581 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']})
582 subs
= result
['requested_subtitles']
583 self
.assertTrue(subs
)
584 self
.assertEqual(set(subs
.keys()), {'es', 'fr'})
586 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']})
587 subs
= result
['requested_subtitles']
588 self
.assertTrue(subs
)
589 self
.assertEqual(set(subs
.keys()), {'es', 'fr'})
591 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']})
592 subs
= result
['requested_subtitles']
593 self
.assertTrue(subs
)
594 self
.assertEqual(set(subs
.keys()), {'fr'})
596 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']})
597 subs
= result
['requested_subtitles']
598 self
.assertTrue(subs
)
599 self
.assertEqual(set(subs
.keys()), {'en'})
601 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']})
602 subs
= result
['requested_subtitles']
603 self
.assertTrue(subs
)
604 self
.assertEqual(set(subs
.keys()), {'es', 'en'})
606 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
607 subs
= result
['requested_subtitles']
608 self
.assertTrue(subs
)
609 self
.assertEqual(set(subs
.keys()), {'es', 'pt'})
610 self
.assertFalse(subs
['es']['_auto'])
611 self
.assertTrue(subs
['pt']['_auto'])
613 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
614 subs
= result
['requested_subtitles']
615 self
.assertTrue(subs
)
616 self
.assertEqual(set(subs
.keys()), {'es', 'pt'})
617 self
.assertTrue(subs
['es']['_auto'])
618 self
.assertTrue(subs
['pt']['_auto'])
620 def test_add_extra_info(self
):
626 'playlist': 'funny videos',
628 YDL
.add_extra_info(test_dict
, extra_info
)
629 self
.assertEqual(test_dict
['extractor'], 'Foo')
630 self
.assertEqual(test_dict
['playlist'], 'funny videos')
641 'title3': 'foo/bar\\test',
642 'title4': 'foo "bar" test',
644 'timestamp': 1618488000,
647 'playlist_autonumber': 2,
648 '__last_playlist_index': 100,
651 {'id': 'id 1', 'height': 1080, 'width': 1920},
652 {'id': 'id 2', 'height': 720},
657 def test_prepare_outtmpl_and_filename(self
):
658 def test(tmpl
, expected
, *, info
=None, **params
):
659 params
['outtmpl'] = tmpl
660 ydl
= FakeYDL(params
)
661 ydl
._num
_downloads
= 1
662 self
.assertEqual(ydl
.validate_outtmpl(tmpl
), None)
664 out
= ydl
.evaluate_outtmpl(tmpl
, info
or self
.outtmpl_info
)
665 fname
= ydl
.prepare_filename(info
or self
.outtmpl_info
)
667 if not isinstance(expected
, (list, tuple)):
668 expected
= (expected
, expected
)
669 for (name
, got
), expect
in zip((('outtmpl', out
), ('filename', fname
)), expected
):
671 self
.assertTrue(expect(got
), f
'Wrong {name} from {tmpl}')
672 elif expect
is not None:
673 self
.assertEqual(got
, expect
, f
'Wrong {name} from {tmpl}')
676 original_infodict
= dict(self
.outtmpl_info
)
677 test('foo.bar', 'foo.bar')
678 original_infodict
['epoch'] = self
.outtmpl_info
.get('epoch')
679 self
.assertTrue(isinstance(original_infodict
['epoch'], int))
680 test('%(epoch)d', int_or_none
)
681 self
.assertEqual(original_infodict
, self
.outtmpl_info
)
683 # Auto-generated fields
684 test('%(id)s.%(ext)s', '1234.mp4')
685 test('%(duration_string)s', ('27:46:40', '27-46-40'))
686 test('%(resolution)s', '1080p')
687 test('%(playlist_index|)s', '001')
688 test('%(playlist_autonumber)s', '02')
689 test('%(autonumber)s', '00001')
690 test('%(autonumber+2)03d', '005', autonumber_start
=3)
691 test('%(autonumber)s', '001', autonumber_size
=3)
700 test('%abc%', '%abc%')
701 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
702 test('%%%(height)s', '%1080')
703 test('%(width)06d.%(ext)s', 'NA.mp4')
704 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
705 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
708 test('%(id)s', '_abcd', info
={'id': '_abcd'})
709 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'})
710 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}]})
711 test('%(id)s', '-abcd', info
={'id': '-abcd'})
712 test('%(id)s', '.abcd', info
={'id': '.abcd'})
713 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'})
714 test('%(id)s', ('ab:cd', 'ab:cd'), info
={'id': 'ab:cd'})
715 test('%(id.0)s', '-', info
={'id': '--'})
718 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
719 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
721 test('%(formats.{id)s', 'NA')
724 def expect_same_infodict(out
):
725 got_dict
= json
.loads(out
)
726 for info_field
, expected
in self
.outtmpl_info
.items():
727 self
.assertEqual(got_dict
.get(info_field
), expected
, info_field
)
730 test('%()j', (expect_same_infodict
, str))
733 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
734 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
735 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
736 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
737 test('%(non_existent.0)s', 'NA')
740 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
741 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
742 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
743 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
744 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
745 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
746 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
747 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
748 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
749 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
750 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
753 test('%(id)d', '1234')
754 test('%(height)c', '1')
756 test('%(id)d %(id)r', "1234 '1234'")
757 test('%(id)r %(height)r', "'1234' 1080")
758 test('%(title5)a %(height)a', (R
"'\xe1\xe9\xed \U0001d400' 1080", None))
759 test('%(ext)s-%(ext|def)d', 'mp4-def')
760 test('%(width|0)04d', '0')
761 test('a%(width|b)d', 'ab', outtmpl_na_placeholder
='none')
763 FORMATS
= self
.outtmpl_info
['formats']
765 # Custom type casting
766 test('%(formats.:.id)l', 'id 1, id 2, id 3')
767 test('%(formats.:.id)#l', ('id 1\nid 2\nid 3', 'id 1 id 2 id 3'))
768 test('%(ext)l', 'mp4')
769 test('%(formats.:.id) 18l', ' id 1, id 2, id 3')
770 test('%(formats)j', (json
.dumps(FORMATS
), None))
771 test('%(formats)#j', (
772 json
.dumps(FORMATS
, indent
=4),
773 json
.dumps(FORMATS
, indent
=4).replace(':', ':').replace('"', """).replace('\n', ' ')
775 test('%(title5).3B', 'á')
776 test('%(title5)U', 'áéí 𝐀')
777 test('%(title5)#U', 'a\u0301e\u0301i\u0301 𝐀')
778 test('%(title5)+U', 'áéí A')
779 test('%(title5)+#U', 'a\u0301e\u0301i\u0301 A')
780 test('%(height)D', '1k')
781 test('%(filesize)#D', '1Ki')
782 test('%(height)5.2D', ' 1.08k')
783 test('%(title4)#S', 'foo_bar_test')
784 test('%(title4).10S', ('foo "bar" ', 'foo "bar"' + ('#' if compat_os_name
== 'nt' else ' ')))
785 if compat_os_name
== 'nt':
786 test('%(title4)q', ('"foo \\"bar\\" test"', ""foo ⧹"bar⧹" test""))
787 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', '"id 1" "id 2" "id 3"'))
788 test('%(formats.0.id)#q', ('"id 1"', '"id 1"'))
790 test('%(title4)q', ('\'foo "bar" test\'', '\'foo "bar" test\''))
791 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'")
792 test('%(formats.0.id)#q', "'id 1'")
794 # Internal formatting
795 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
796 test('%(title|%)s %(title|%%)s', '% %%')
797 test('%(id+1-height+3)05d', '00158')
798 test('%(width+100)05d', 'NA')
799 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], None))
800 test('%(formats.0)r', (repr(FORMATS
[0]), None))
801 test('%(height.0)03d', '001')
802 test('%(-height.0)04d', '-001')
803 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
804 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
805 test('%(formats.3)s', 'NA')
806 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
807 test('%(formats.0.id.-1+id)f', '1235.000000')
808 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
809 out
= json
.dumps([{'id': f
['id'], 'height.:2': str(f
['height'])[:2]}
810 if 'height' in f
else {'id': f
['id']}
812 test('%(formats.:.{id,height.:2})j', (out
, None))
813 test('%(formats.:.{id,height}.id)l', ', '.join(f
['id'] for f
in FORMATS
))
814 test('%(.{id,title})j', ('{"id": "1234"}', '{"id": "1234"}'))
817 test('%(title,id)s', '1234')
818 test('%(width-100,height+20|def)d', '1100')
819 test('%(width-100,height+width|def)s', 'def')
820 test('%(timestamp-x>%H\\,%M\\,%S,timestamp>%H\\,%M\\,%S)s', '12,00,00')
823 test('%(id&foo)s.bar', 'foo.bar')
824 test('%(title&foo)s.bar', 'NA.bar')
825 test('%(title&foo|baz)s.bar', 'baz.bar')
826 test('%(x,id&foo|baz)s.bar', 'foo.bar')
827 test('%(x,title&foo|baz)s.bar', 'baz.bar')
828 test('%(id&a\nb|)s', ('a\nb', 'a b'))
829 test('%(id&hi {:>10} {}|)s', 'hi 1234 1234')
830 test(R
'%(id&{0} {}|)s', 'NA')
831 test(R
'%(id&{0.1}|)s', 'NA')
836 raise self
.assertTrue(False, 'LazyList should not be evaluated till here')
837 test('%(key.4)s', '4', info
={'key': LazyList(gen())})
840 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
841 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
842 # test('%(foo|)s', ('', '_')) # fixme
844 # Environment variable expansion for prepare_filename
845 os
.environ
['__yt_dlp_var'] = 'expanded'
846 envvar
= '%__yt_dlp_var%' if compat_os_name
== 'nt' else '$__yt_dlp_var'
847 test(envvar
, (envvar
, 'expanded'))
848 if compat_os_name
== 'nt':
849 test('%s%', ('%s%', '%s%'))
850 os
.environ
['s'] = 'expanded'
851 test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s
852 os
.environ
['(test)s'] = 'expanded'
853 test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template
855 # Path expansion and escaping
856 test('Hello %(title1)s', 'Hello $PATH')
857 test('Hello %(title2)s', 'Hello %PATH%')
858 test('%(title3)s', ('foo/bar\\test', 'foo⧸bar⧹test'))
859 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo⧸bar⧹test' % os
.path
.sep
))
861 def test_format_note(self
):
863 self
.assertEqual(ydl
._format
_note
({}), '')
864 assertRegexpMatches(self
, ydl
._format
_note
({
867 assertRegexpMatches(self
, ydl
._format
_note
({
871 def test_postprocessors(self
):
872 filename
= 'post-processor-testfile.mp4'
873 audiofile
= filename
+ '.mp3'
875 class SimplePP(PostProcessor
):
877 with
open(audiofile
, 'w') as f
:
879 return [info
['filepath']], info
881 def run_pp(params
, PP
):
882 with
open(filename
, 'w') as f
:
884 ydl
= YoutubeDL(params
)
885 ydl
.add_post_processor(PP())
886 ydl
.post_process(filename
, {'filepath': filename
})
888 run_pp({'keepvideo': True}, SimplePP
)
889 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
890 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
894 run_pp({'keepvideo': False}, SimplePP
)
895 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
896 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
899 class ModifierPP(PostProcessor
):
901 with
open(info
['filepath'], 'w') as f
:
905 run_pp({'keepvideo': False}, ModifierPP
)
906 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
909 def test_match_filter(self
):
916 'filesize': 10 * 1024,
918 'uploader': "變態妍字幕版 太妍 тест",
919 'creator': "тест ' 123 ' тест--",
920 'webpage_url': 'http://example.com/watch?v=shenanigans',
928 'description': 'foo',
929 'filesize': 5 * 1024,
931 'uploader': "тест 123",
932 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
934 videos
= [first
, second
]
936 def get_videos(filter_
=None):
937 ydl
= YDL({'match_filter': filter_
, 'simulate': True})
939 ydl
.process_ie_result(v
, download
=True)
940 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
943 self
.assertEqual(res
, ['1', '2'])
945 def f(v
, incomplete
):
949 return 'Video id is not 1'
951 self
.assertEqual(res
, ['1'])
953 f
= match_filter_func('duration < 30')
955 self
.assertEqual(res
, ['2'])
957 f
= match_filter_func('description = foo')
959 self
.assertEqual(res
, ['2'])
961 f
= match_filter_func('description =? foo')
963 self
.assertEqual(res
, ['1', '2'])
965 f
= match_filter_func('filesize > 5KiB')
967 self
.assertEqual(res
, ['1'])
969 f
= match_filter_func('playlist_id = 42')
971 self
.assertEqual(res
, ['1'])
973 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
975 self
.assertEqual(res
, ['1'])
977 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
979 self
.assertEqual(res
, ['2'])
981 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
983 self
.assertEqual(res
, ['1'])
985 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
987 self
.assertEqual(res
, ['1'])
989 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
991 self
.assertEqual(res
, [])
993 def test_playlist_items_selection(self
):
994 INDICES
, PAGE_SIZE
= list(range(1, 11)), 3
996 def entry(i
, evaluated
):
1004 def pagedlist_entries(evaluated
):
1006 start
= PAGE_SIZE
* n
1007 for i
in INDICES
[start
: start
+ PAGE_SIZE
]:
1008 yield entry(i
, evaluated
)
1009 return OnDemandPagedList(page_func
, PAGE_SIZE
)
1012 return (i
+ PAGE_SIZE
- 1) // PAGE_SIZE
1014 def generator_entries(evaluated
):
1016 yield entry(i
, evaluated
)
1018 def list_entries(evaluated
):
1019 return list(generator_entries(evaluated
))
1021 def lazylist_entries(evaluated
):
1022 return LazyList(generator_entries(evaluated
))
1024 def get_downloaded_info_dicts(params
, entries
):
1026 ydl
.process_ie_result({
1027 '_type': 'playlist',
1029 'extractor': 'test:playlist',
1030 'extractor_key': 'test:playlist',
1031 'webpage_url': 'http://example.com',
1034 return ydl
.downloaded_info_dicts
1036 def test_selection(params
, expected_ids
, evaluate_all
=False):
1037 expected_ids
= list(expected_ids
)
1039 generator_eval
= pagedlist_eval
= INDICES
1040 elif not expected_ids
:
1041 generator_eval
= pagedlist_eval
= []
1043 generator_eval
= INDICES
[0: max(expected_ids
)]
1044 pagedlist_eval
= INDICES
[PAGE_SIZE
* page_num(min(expected_ids
)) - PAGE_SIZE
:
1045 PAGE_SIZE
* page_num(max(expected_ids
))]
1047 for name
, func
, expected_eval
in (
1048 ('list', list_entries
, INDICES
),
1049 ('Generator', generator_entries
, generator_eval
),
1050 # ('LazyList', lazylist_entries, generator_eval), # Generator and LazyList follow the exact same code path
1051 ('PagedList', pagedlist_entries
, pagedlist_eval
),
1054 entries
= func(evaluated
)
1055 results
= [(v
['playlist_autonumber'] - 1, (int(v
['id']), v
['playlist_index']))
1056 for v
in get_downloaded_info_dicts(params
, entries
)]
1057 self
.assertEqual(results
, list(enumerate(zip(expected_ids
, expected_ids
))), f
'Entries of {name} for {params}')
1058 self
.assertEqual(sorted(evaluated
), expected_eval
, f
'Evaluation of {name} for {params}')
1060 test_selection({}, INDICES
)
1061 test_selection({'playlistend': 20}, INDICES
, True)
1062 test_selection({'playlistend': 2}, INDICES
[:2])
1063 test_selection({'playliststart': 11}, [], True)
1064 test_selection({'playliststart': 2}, INDICES
[1:])
1065 test_selection({'playlist_items': '2-4'}, INDICES
[1:4])
1066 test_selection({'playlist_items': '2,4'}, [2, 4])
1067 test_selection({'playlist_items': '20'}, [], True)
1068 test_selection({'playlist_items': '0'}, [])
1070 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
1071 test_selection({'playlist_items': '2-4,3-4,3'}, [2, 3, 4])
1072 test_selection({'playlist_items': '4,2'}, [4, 2])
1074 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720
1075 # https://github.com/yt-dlp/yt-dlp/issues/302
1076 test_selection({'playlistreverse': True}, INDICES
[::-1])
1077 test_selection({'playliststart': 2, 'playlistreverse': True}, INDICES
[:0:-1])
1078 test_selection({'playlist_items': '2,4', 'playlistreverse': True}, [4, 2])
1079 test_selection({'playlist_items': '4,2'}, [4, 2])
1081 # Tests for --playlist-items start:end:step
1082 test_selection({'playlist_items': ':'}, INDICES
, True)
1083 test_selection({'playlist_items': '::1'}, INDICES
, True)
1084 test_selection({'playlist_items': '::-1'}, INDICES
[::-1], True)
1085 test_selection({'playlist_items': ':6'}, INDICES
[:6])
1086 test_selection({'playlist_items': ':-6'}, INDICES
[:-5], True)
1087 test_selection({'playlist_items': '-1:6:-2'}, INDICES
[:4:-2], True)
1088 test_selection({'playlist_items': '9:-6:-2'}, INDICES
[8:3:-2], True)
1090 test_selection({'playlist_items': '1:inf:2'}, INDICES
[::2], True)
1091 test_selection({'playlist_items': '-2:inf'}, INDICES
[-2:], True)
1092 test_selection({'playlist_items': ':inf:-1'}, [], True)
1093 test_selection({'playlist_items': '0-2:2'}, [2])
1094 test_selection({'playlist_items': '1-:2'}, INDICES
[::2], True)
1095 test_selection({'playlist_items': '0--2:2'}, INDICES
[1:-1:2], True)
1097 test_selection({'playlist_items': '10::3'}, [10], True)
1098 test_selection({'playlist_items': '-1::3'}, [10], True)
1099 test_selection({'playlist_items': '11::3'}, [], True)
1100 test_selection({'playlist_items': '-15::2'}, INDICES
[1::2], True)
1101 test_selection({'playlist_items': '-15::15'}, [], True)
1103 def test_do_not_override_ie_key_in_url_transparent(self
):
1106 class Foo1IE(InfoExtractor
):
1107 _VALID_URL
= r
'foo1:'
1109 def _real_extract(self
, url
):
1111 '_type': 'url_transparent',
1114 'title': 'foo1 title',
1118 class Foo2IE(InfoExtractor
):
1119 _VALID_URL
= r
'foo2:'
1121 def _real_extract(self
, url
):
1128 class Foo3IE(InfoExtractor
):
1129 _VALID_URL
= r
'foo3:'
1131 def _real_extract(self
, url
):
1132 return _make_result([{'url': TEST_URL
}], title
='foo3 title')
1134 ydl
.add_info_extractor(Foo1IE(ydl
))
1135 ydl
.add_info_extractor(Foo2IE(ydl
))
1136 ydl
.add_info_extractor(Foo3IE(ydl
))
1137 ydl
.extract_info('foo1:')
1138 downloaded
= ydl
.downloaded_info_dicts
[0]
1139 self
.assertEqual(downloaded
['url'], TEST_URL
)
1140 self
.assertEqual(downloaded
['title'], 'foo1 title')
1141 self
.assertEqual(downloaded
['id'], 'testid')
1142 self
.assertEqual(downloaded
['extractor'], 'testex')
1143 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1145 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1146 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1149 def __init__(self
, *args
, **kwargs
):
1150 super().__init
__(*args
, **kwargs
)
1152 def trouble(self
, s
, tb
=None):
1157 'ignoreerrors': True,
1160 class VideoIE(InfoExtractor
):
1161 _VALID_URL
= r
'video:(?P<id>\d+)'
1163 def _real_extract(self
, url
):
1164 video_id
= self
._match
_id
(url
)
1166 'format_id': 'default',
1170 raise ExtractorError('foo')
1173 'format_id': 'extra',
1178 'title': 'Video %s' % video_id
,
1182 class PlaylistIE(InfoExtractor
):
1183 _VALID_URL
= r
'playlist:'
1189 '_type': 'url_transparent',
1190 'ie_key': VideoIE
.ie_key(),
1192 'url': 'video:%s' % video_id
,
1193 'title': 'Video Transparent %s' % video_id
,
1196 def _real_extract(self
, url
):
1197 return self
.playlist_result(self
._entries
())
1199 ydl
.add_info_extractor(VideoIE(ydl
))
1200 ydl
.add_info_extractor(PlaylistIE(ydl
))
1201 info
= ydl
.extract_info('playlist:')
1202 entries
= info
['entries']
1203 self
.assertEqual(len(entries
), 3)
1204 self
.assertTrue(entries
[0] is None)
1205 self
.assertTrue(entries
[1] is None)
1206 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1207 downloaded
= ydl
.downloaded_info_dicts
[0]
1208 entries
[2].pop('requested_downloads', None)
1209 self
.assertEqual(entries
[2], downloaded
)
1210 self
.assertEqual(downloaded
['url'], TEST_URL
)
1211 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1212 self
.assertEqual(downloaded
['id'], '2')
1213 self
.assertEqual(downloaded
['extractor'], 'Video')
1214 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1216 def test_header_cookies(self
):
1217 from http
.cookiejar
import Cookie
1220 ydl
.report_warning
= lambda *_
, **__
: None
1222 def cookie(name
, value
, version
=None, domain
='', path
='', secure
=False, expires
=None):
1224 version
or 0, name
, value
, None, False,
1225 domain
, bool(domain
), bool(domain
), path
, bool(path
),
1226 secure
, expires
, False, None, None, rest
={})
1228 _test_url
= 'https://yt.dlp/test'
1230 def test(encoded_cookies
, cookies
, headers
=False, round_trip
=None, error
=None):
1232 ydl
.cookiejar
.clear()
1233 ydl
._load
_cookies
(encoded_cookies
, from_headers
=headers
)
1235 ydl
._apply
_header
_cookies
(_test_url
)
1236 data
= {'url': _test_url
}
1237 ydl
._calc
_headers
(data
)
1238 self
.assertCountEqual(
1239 map(vars, ydl
.cookiejar
), map(vars, cookies
),
1240 'Extracted cookiejar.Cookie is not the same')
1243 data
.get('cookies'), round_trip
or encoded_cookies
,
1244 'Cookie is not the same as round trip')
1245 ydl
.__dict
__['_YoutubeDL__header_cookies'] = []
1247 with self
.subTest(msg
=encoded_cookies
):
1251 with self
.assertRaisesRegex(Exception, error
):
1254 test('test=value; Domain=.yt.dlp', [cookie('test', 'value', domain
='.yt.dlp')])
1255 test('test=value', [cookie('test', 'value')], error
='Unscoped cookies are not allowed')
1256 test('cookie1=value1; Domain=.yt.dlp; Path=/test; cookie2=value2; Domain=.yt.dlp; Path=/', [
1257 cookie('cookie1', 'value1', domain
='.yt.dlp', path
='/test'),
1258 cookie('cookie2', 'value2', domain
='.yt.dlp', path
='/')])
1259 test('test=value; Domain=.yt.dlp; Path=/test; Secure; Expires=9999999999', [
1260 cookie('test', 'value', domain
='.yt.dlp', path
='/test', secure
=True, expires
=9999999999)])
1261 test('test="value; "; path=/test; domain=.yt.dlp', [
1262 cookie('test', 'value; ', domain
='.yt.dlp', path
='/test')],
1263 round_trip
='test="value\\073 "; Domain=.yt.dlp; Path=/test')
1264 test('name=; Domain=.yt.dlp', [cookie('name', '', domain
='.yt.dlp')],
1265 round_trip
='name=""; Domain=.yt.dlp')
1267 test('test=value', [cookie('test', 'value', domain
='.yt.dlp')], headers
=True)
1268 test('cookie1=value; Domain=.yt.dlp; cookie2=value', [], headers
=True, error
='Invalid syntax')
1269 ydl
.deprecated_feature
= ydl
.report_error
1270 test('test=value', [], headers
=True, error
='Passing cookies as a header is a potential security risk')
1273 if __name__
== '__main__':