4 from __future__
import unicode_literals
6 # Allow direct execution
10 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_str
, compat_urllib_error
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 ExtractorError
, int_or_none
, match_filter_func
22 TEST_URL
= 'http://localhost/sample.mp4'
26 def __init__(self
, *args
, **kwargs
):
27 super(YDL
, self
).__init
__(*args
, **kwargs
)
28 self
.downloaded_info_dicts
= []
31 def process_info(self
, info_dict
):
32 info_dict
.pop('__original_infodict', None)
33 self
.downloaded_info_dicts
.append(info_dict
)
35 def to_screen(self
, msg
):
39 def _make_result(formats
, **kwargs
):
43 'title': 'testttitle',
44 'extractor': 'testex',
45 'extractor_key': 'TestEx',
46 'webpage_url': 'http://example.com/watch?v=shenanigans',
52 class TestFormatSelection(unittest
.TestCase
):
53 def test_prefer_free_formats(self
):
54 # Same resolution => download webm
56 ydl
.params
['prefer_free_formats'] = True
58 {'ext': 'webm', 'height': 460, 'url': TEST_URL}
,
59 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}
,
61 info_dict
= _make_result(formats
)
63 yie
._sort
_formats
(info_dict
['formats'])
64 ydl
.process_ie_result(info_dict
)
65 downloaded
= ydl
.downloaded_info_dicts
[0]
66 self
.assertEqual(downloaded
['ext'], 'webm')
68 # Different resolution => download best quality (mp4)
70 ydl
.params
['prefer_free_formats'] = True
72 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
73 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}
,
75 info_dict
['formats'] = formats
77 yie
._sort
_formats
(info_dict
['formats'])
78 ydl
.process_ie_result(info_dict
)
79 downloaded
= ydl
.downloaded_info_dicts
[0]
80 self
.assertEqual(downloaded
['ext'], 'mp4')
82 # No prefer_free_formats => prefer mp4 and webm
84 ydl
.params
['prefer_free_formats'] = False
86 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
87 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}
,
88 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
90 info_dict
['formats'] = formats
92 yie
._sort
_formats
(info_dict
['formats'])
93 ydl
.process_ie_result(info_dict
)
94 downloaded
= ydl
.downloaded_info_dicts
[0]
95 self
.assertEqual(downloaded
['ext'], 'mp4')
98 ydl
.params
['prefer_free_formats'] = False
100 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
101 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
103 info_dict
['formats'] = formats
105 yie
._sort
_formats
(info_dict
['formats'])
106 ydl
.process_ie_result(info_dict
)
107 downloaded
= ydl
.downloaded_info_dicts
[0]
108 self
.assertEqual(downloaded
['ext'], 'webm')
110 def test_format_selection(self
):
112 {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
113 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL}
,
114 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL}
,
115 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL}
,
116 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL}
,
118 info_dict
= _make_result(formats
)
120 ydl
= YDL({'format': '20/47'}
)
121 ydl
.process_ie_result(info_dict
.copy())
122 downloaded
= ydl
.downloaded_info_dicts
[0]
123 self
.assertEqual(downloaded
['format_id'], '47')
125 ydl
= YDL({'format': '20/71/worst'}
)
126 ydl
.process_ie_result(info_dict
.copy())
127 downloaded
= ydl
.downloaded_info_dicts
[0]
128 self
.assertEqual(downloaded
['format_id'], '35')
131 ydl
.process_ie_result(info_dict
.copy())
132 downloaded
= ydl
.downloaded_info_dicts
[0]
133 self
.assertEqual(downloaded
['format_id'], '2')
135 ydl
= YDL({'format': 'webm/mp4'}
)
136 ydl
.process_ie_result(info_dict
.copy())
137 downloaded
= ydl
.downloaded_info_dicts
[0]
138 self
.assertEqual(downloaded
['format_id'], '47')
140 ydl
= YDL({'format': '3gp/40/mp4'}
)
141 ydl
.process_ie_result(info_dict
.copy())
142 downloaded
= ydl
.downloaded_info_dicts
[0]
143 self
.assertEqual(downloaded
['format_id'], '35')
145 ydl
= YDL({'format': 'example-with-dashes'}
)
146 ydl
.process_ie_result(info_dict
.copy())
147 downloaded
= ydl
.downloaded_info_dicts
[0]
148 self
.assertEqual(downloaded
['format_id'], 'example-with-dashes')
150 def test_format_selection_audio(self
):
152 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
153 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
154 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}
,
155 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL}
,
157 info_dict
= _make_result(formats
)
159 ydl
= YDL({'format': 'bestaudio'}
)
160 ydl
.process_ie_result(info_dict
.copy())
161 downloaded
= ydl
.downloaded_info_dicts
[0]
162 self
.assertEqual(downloaded
['format_id'], 'audio-high')
164 ydl
= YDL({'format': 'worstaudio'}
)
165 ydl
.process_ie_result(info_dict
.copy())
166 downloaded
= ydl
.downloaded_info_dicts
[0]
167 self
.assertEqual(downloaded
['format_id'], 'audio-low')
170 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
171 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL}
,
173 info_dict
= _make_result(formats
)
175 ydl
= YDL({'format': 'bestaudio/worstaudio/best'}
)
176 ydl
.process_ie_result(info_dict
.copy())
177 downloaded
= ydl
.downloaded_info_dicts
[0]
178 self
.assertEqual(downloaded
['format_id'], 'vid-high')
180 def test_format_selection_audio_exts(self
):
182 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
183 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
184 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
185 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
186 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
189 info_dict
= _make_result(formats
)
190 ydl
= YDL({'format': 'best'}
)
192 ie
._sort
_formats
(info_dict
['formats'])
193 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
194 downloaded
= ydl
.downloaded_info_dicts
[0]
195 self
.assertEqual(downloaded
['format_id'], 'aac-64')
197 ydl
= YDL({'format': 'mp3'}
)
199 ie
._sort
_formats
(info_dict
['formats'])
200 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
201 downloaded
= ydl
.downloaded_info_dicts
[0]
202 self
.assertEqual(downloaded
['format_id'], 'mp3-64')
204 ydl
= YDL({'prefer_free_formats': True}
)
206 ie
._sort
_formats
(info_dict
['formats'])
207 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
208 downloaded
= ydl
.downloaded_info_dicts
[0]
209 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
211 def test_format_selection_video(self
):
213 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
214 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
215 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
217 info_dict
= _make_result(formats
)
219 ydl
= YDL({'format': 'bestvideo'}
)
220 ydl
.process_ie_result(info_dict
.copy())
221 downloaded
= ydl
.downloaded_info_dicts
[0]
222 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
224 ydl
= YDL({'format': 'worstvideo'}
)
225 ydl
.process_ie_result(info_dict
.copy())
226 downloaded
= ydl
.downloaded_info_dicts
[0]
227 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
229 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
230 ydl
.process_ie_result(info_dict
.copy())
231 downloaded
= ydl
.downloaded_info_dicts
[0]
232 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
235 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
237 info_dict
= _make_result(formats
)
239 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
240 ydl
.process_ie_result(info_dict
.copy())
241 downloaded
= ydl
.downloaded_info_dicts
[0]
242 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
244 def test_format_selection_string_ops(self
):
246 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
247 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
249 info_dict
= _make_result(formats
)
252 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
253 ydl
.process_ie_result(info_dict
.copy())
254 downloaded
= ydl
.downloaded_info_dicts
[0]
255 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
257 # does not equal (!=)
258 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
259 ydl
.process_ie_result(info_dict
.copy())
260 downloaded
= ydl
.downloaded_info_dicts
[0]
261 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
263 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
264 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
267 ydl
= YDL({'format': '[format_id^=abc]'}
)
268 ydl
.process_ie_result(info_dict
.copy())
269 downloaded
= ydl
.downloaded_info_dicts
[0]
270 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
272 # does not start with (!^=)
273 ydl
= YDL({'format': '[format_id!^=abc]'}
)
274 ydl
.process_ie_result(info_dict
.copy())
275 downloaded
= ydl
.downloaded_info_dicts
[0]
276 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
278 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
279 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
282 ydl
= YDL({'format': '[format_id$=cba]'}
)
283 ydl
.process_ie_result(info_dict
.copy())
284 downloaded
= ydl
.downloaded_info_dicts
[0]
285 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
287 # does not end with (!$=)
288 ydl
= YDL({'format': '[format_id!$=cba]'}
)
289 ydl
.process_ie_result(info_dict
.copy())
290 downloaded
= ydl
.downloaded_info_dicts
[0]
291 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
293 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
294 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
297 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
298 ydl
.process_ie_result(info_dict
.copy())
299 downloaded
= ydl
.downloaded_info_dicts
[0]
300 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
302 # does not contain (!*=)
303 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
304 ydl
.process_ie_result(info_dict
.copy())
305 downloaded
= ydl
.downloaded_info_dicts
[0]
306 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
308 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
309 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
311 ydl
= YDL({'format': '[format_id!*=-]'}
)
312 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
314 def test_youtube_format_selection(self
):
315 # FIXME: Rewrite in accordance with the new format sorting options
319 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
320 # Apple HTTP Live Streaming
321 '96', '95', '94', '93', '92', '132', '151',
323 '85', '84', '102', '83', '101', '82', '100',
325 '137', '248', '136', '247', '135', '246',
326 '245', '244', '134', '243', '133', '242', '160',
328 '141', '172', '140', '171', '139',
331 def format_info(f_id
):
332 info
= YoutubeIE
._formats
[f_id
].copy()
334 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
335 # and 'vcodec', while in tests such information is incomplete since
336 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
337 # test_YoutubeDL.test_youtube_format_selection is broken without
339 if 'acodec' in info
and 'vcodec' not in info
:
340 info
['vcodec'] = 'none'
341 elif 'vcodec' in info
and 'acodec' not in info
:
342 info
['acodec'] = 'none'
344 info
['format_id'] = f_id
345 info
['url'] = 'url:' + f_id
347 formats_order
= [format_info(f_id
) for f_id
in order
]
349 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
350 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
352 yie
._sort
_formats
(info_dict
['formats'])
353 ydl
.process_ie_result(info_dict
)
354 downloaded
= ydl
.downloaded_info_dicts
[0]
355 self
.assertEqual(downloaded
['format_id'], '248+172')
356 self
.assertEqual(downloaded
['ext'], 'mp4')
358 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
359 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
361 yie
._sort
_formats
(info_dict
['formats'])
362 ydl
.process_ie_result(info_dict
)
363 downloaded
= ydl
.downloaded_info_dicts
[0]
364 self
.assertEqual(downloaded
['format_id'], '38')
366 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
367 ydl
= YDL({'format': 'bestvideo/best,bestaudio'}
)
369 yie
._sort
_formats
(info_dict
['formats'])
370 ydl
.process_ie_result(info_dict
)
371 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
372 self
.assertEqual(downloaded_ids
, ['137', '141'])
374 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
375 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'}
)
377 yie
._sort
_formats
(info_dict
['formats'])
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
, ['137+141', '248+141'])
382 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
383 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'}
)
385 yie
._sort
_formats
(info_dict
['formats'])
386 ydl
.process_ie_result(info_dict
)
387 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
388 self
.assertEqual(downloaded_ids
, ['136+141', '247+141'])
390 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
391 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}
)
393 yie
._sort
_formats
(info_dict
['formats'])
394 ydl
.process_ie_result(info_dict
)
395 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
396 self
.assertEqual(downloaded_ids
, ['248+141'])
398 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
399 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
400 ydl
= YDL({'format': 'best/bestvideo'}
)
402 yie
._sort
_formats
(info_dict
['formats'])
403 ydl
.process_ie_result(info_dict
)
404 downloaded
= ydl
.downloaded_info_dicts
[0]
405 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
407 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
408 ydl
= YDL({'format': 'best/bestvideo'}
)
410 yie
._sort
_formats
(info_dict
['formats'])
411 ydl
.process_ie_result(info_dict
)
412 downloaded
= ydl
.downloaded_info_dicts
[0]
413 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
415 def test_audio_only_extractor_format_selection(self
):
416 # For extractors with incomplete formats (all formats are audio-only or
417 # video-only) best and worst should fallback to corresponding best/worst
418 # video-only or audio-only formats (as per
419 # https://github.com/ytdl-org/youtube-dl/pull/5556)
421 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
422 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
424 info_dict
= _make_result(formats
)
426 ydl
= YDL({'format': 'best'}
)
427 ydl
.process_ie_result(info_dict
.copy())
428 downloaded
= ydl
.downloaded_info_dicts
[0]
429 self
.assertEqual(downloaded
['format_id'], 'high')
431 ydl
= YDL({'format': 'worst'}
)
432 ydl
.process_ie_result(info_dict
.copy())
433 downloaded
= ydl
.downloaded_info_dicts
[0]
434 self
.assertEqual(downloaded
['format_id'], 'low')
436 def test_format_not_available(self
):
438 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
439 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
441 info_dict
= _make_result(formats
)
443 # This must fail since complete video-audio format does not match filter
444 # and extractor does not provide incomplete only formats (i.e. only
445 # video-only or audio-only).
446 ydl
= YDL({'format': 'best[height>360]'}
)
447 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
449 def test_format_selection_issue_10083(self
):
450 # See https://github.com/ytdl-org/youtube-dl/issues/10083
452 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
453 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
454 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
456 info_dict
= _make_result(formats
)
458 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
459 ydl
.process_ie_result(info_dict
.copy())
460 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
462 def test_invalid_format_specs(self
):
463 def assert_syntax_error(format_spec
):
464 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec}
)
466 assert_syntax_error('bestvideo,,best')
467 assert_syntax_error('+bestaudio')
468 assert_syntax_error('bestvideo+')
469 assert_syntax_error('/')
470 assert_syntax_error('[720<height]')
472 def test_format_filtering(self
):
474 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
475 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
476 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
477 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
478 {'format_id': 'E', 'filesize': 3000}
,
480 {'format_id': 'G', 'filesize': 1000000}
,
483 f
['url'] = 'http://_/'
485 info_dict
= _make_result(formats
)
487 ydl
= YDL({'format': 'best[filesize<3000]'}
)
488 ydl
.process_ie_result(info_dict
)
489 downloaded
= ydl
.downloaded_info_dicts
[0]
490 self
.assertEqual(downloaded
['format_id'], 'D')
492 ydl
= YDL({'format': 'best[filesize<=3000]'}
)
493 ydl
.process_ie_result(info_dict
)
494 downloaded
= ydl
.downloaded_info_dicts
[0]
495 self
.assertEqual(downloaded
['format_id'], 'E')
497 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
498 ydl
.process_ie_result(info_dict
)
499 downloaded
= ydl
.downloaded_info_dicts
[0]
500 self
.assertEqual(downloaded
['format_id'], 'F')
502 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'}
)
503 ydl
.process_ie_result(info_dict
)
504 downloaded
= ydl
.downloaded_info_dicts
[0]
505 self
.assertEqual(downloaded
['format_id'], 'B')
507 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
508 ydl
.process_ie_result(info_dict
)
509 downloaded
= ydl
.downloaded_info_dicts
[0]
510 self
.assertEqual(downloaded
['format_id'], 'C')
512 ydl
= YDL({'format': '[filesize>?1]'}
)
513 ydl
.process_ie_result(info_dict
)
514 downloaded
= ydl
.downloaded_info_dicts
[0]
515 self
.assertEqual(downloaded
['format_id'], 'G')
517 ydl
= YDL({'format': '[filesize<1M]'}
)
518 ydl
.process_ie_result(info_dict
)
519 downloaded
= ydl
.downloaded_info_dicts
[0]
520 self
.assertEqual(downloaded
['format_id'], 'E')
522 ydl
= YDL({'format': '[filesize<1MiB]'}
)
523 ydl
.process_ie_result(info_dict
)
524 downloaded
= ydl
.downloaded_info_dicts
[0]
525 self
.assertEqual(downloaded
['format_id'], 'G')
527 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
528 ydl
.process_ie_result(info_dict
)
529 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
530 self
.assertEqual(downloaded_ids
, ['B', 'C', 'D'])
532 ydl
= YDL({'format': 'best[height<40]'}
)
534 ydl
.process_ie_result(info_dict
)
535 except ExtractorError
:
537 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
539 def test_default_format_spec(self
):
540 ydl
= YDL({'simulate': True}
)
541 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
544 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
546 ydl
= YDL({'simulate': True}
)
547 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
549 ydl
= YDL({'outtmpl': '-'}
)
550 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
553 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
554 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
557 class TestYoutubeDL(unittest
.TestCase
):
558 def test_subtitles(self
):
559 def s_formats(lang
, autocaption
=False):
562 'url': 'http://localhost/video.%s.%s' % (lang
, ext
),
563 '_auto': autocaption
,
564 } for ext
in ['vtt', 'srt', 'ass']]
565 subtitles
= dict((l
, s_formats(l
)) for l
in ['en', 'fr', 'es'])
566 auto_captions
= dict((l
, s_formats(l
, True)) for l
in ['it', 'pt', 'es'])
570 'url': 'http://localhost/video.mp4',
571 'subtitles': subtitles
,
572 'automatic_captions': auto_captions
,
574 'webpage_url': 'http://example.com/watch?v=shenanigans',
577 def get_info(params
={}):
578 params
.setdefault('simulate', True)
580 ydl
.report_warning
= lambda *args
, **kargs
: None
581 return ydl
.process_video_result(info_dict
, download
=False)
584 self
.assertFalse(result
.get('requested_subtitles'))
585 self
.assertEqual(result
['subtitles'], subtitles
)
586 self
.assertEqual(result
['automatic_captions'], auto_captions
)
588 result
= get_info({'writesubtitles': True}
)
589 subs
= result
['requested_subtitles']
590 self
.assertTrue(subs
)
591 self
.assertEqual(set(subs
.keys()), set(['en']))
592 self
.assertTrue(subs
['en'].get('data') is None)
593 self
.assertEqual(subs
['en']['ext'], 'ass')
595 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
596 subs
= result
['requested_subtitles']
597 self
.assertEqual(subs
['en']['ext'], 'srt')
599 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
600 subs
= result
['requested_subtitles']
601 self
.assertTrue(subs
)
602 self
.assertEqual(set(subs
.keys()), set(['es', 'fr']))
604 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
605 subs
= result
['requested_subtitles']
606 self
.assertTrue(subs
)
607 self
.assertEqual(set(subs
.keys()), set(['es', 'fr']))
609 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
610 subs
= result
['requested_subtitles']
611 self
.assertTrue(subs
)
612 self
.assertEqual(set(subs
.keys()), set(['fr']))
614 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
615 subs
= result
['requested_subtitles']
616 self
.assertTrue(subs
)
617 self
.assertEqual(set(subs
.keys()), set(['en']))
619 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
620 subs
= result
['requested_subtitles']
621 self
.assertTrue(subs
)
622 self
.assertEqual(set(subs
.keys()), set(['es', 'en']))
624 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
625 subs
= result
['requested_subtitles']
626 self
.assertTrue(subs
)
627 self
.assertEqual(set(subs
.keys()), set(['es', 'pt']))
628 self
.assertFalse(subs
['es']['_auto'])
629 self
.assertTrue(subs
['pt']['_auto'])
631 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
632 subs
= result
['requested_subtitles']
633 self
.assertTrue(subs
)
634 self
.assertEqual(set(subs
.keys()), set(['es', 'pt']))
635 self
.assertTrue(subs
['es']['_auto'])
636 self
.assertTrue(subs
['pt']['_auto'])
638 def test_add_extra_info(self
):
644 'playlist': 'funny videos',
646 YDL
.add_extra_info(test_dict
, extra_info
)
647 self
.assertEqual(test_dict
['extractor'], 'Foo')
648 self
.assertEqual(test_dict
['playlist'], 'funny videos')
657 'title3': 'foo/bar\\test',
658 'timestamp': 1618488000,
661 '_last_playlist_index': 100,
663 'formats': [{'id': 'id1'}
, {'id': 'id2'}
, {'id': 'id3'}
]
666 def test_prepare_outtmpl_and_filename(self
):
667 def test(tmpl
, expected
, *, info
=None, **params
):
668 params
['outtmpl'] = tmpl
669 ydl
= YoutubeDL(params
)
670 ydl
._num
_downloads
= 1
671 self
.assertEqual(ydl
.validate_outtmpl(tmpl
), None)
673 outtmpl
, tmpl_dict
= ydl
.prepare_outtmpl(tmpl
, info
or self
.outtmpl_info
)
674 out
= outtmpl
% tmpl_dict
675 fname
= ydl
.prepare_filename(info
or self
.outtmpl_info
)
677 if callable(expected
):
678 self
.assertTrue(expected(out
))
679 self
.assertTrue(expected(fname
))
680 elif isinstance(expected
, compat_str
):
681 self
.assertEqual((out
, fname
), (expected
, expected
))
683 self
.assertEqual((out
, fname
), expected
)
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('%(epoch)d', int_or_none
)
689 test('%(resolution)s', '1080p')
690 test('%(playlist_index)s', '001')
691 test('%(autonumber)s', '00001')
692 test('%(autonumber+2)03d', '005', autonumber_start
=3)
693 test('%(autonumber)s', '001', autonumber_size
=3)
698 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
699 test('%(width)06d.%(ext)s', 'NA.mp4')
700 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
701 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
704 test('%(id)s', '_abcd', info
={'id': '_abcd'}
)
705 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'}
)
706 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}
]})
707 test('%(id)s', '-abcd', info
={'id': '-abcd'}
)
708 test('%(id)s', '.abcd', info
={'id': '.abcd'}
)
709 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'}
)
710 test('%(id)s', ('ab:cd', 'ab -cd'), info
={'id': 'ab:cd'}
)
713 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%'), ValueError))
714 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
715 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
721 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
722 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
723 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
724 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
727 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
728 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
729 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
730 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
731 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
732 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
733 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
734 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
735 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
736 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
737 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
740 test('%(id)d', '1234')
741 test('%(height)c', '1')
743 test('%(id)d %(id)r', "1234 '1234'")
744 test('%(id)r %(height)r', "'1234' 1080")
745 test('%(ext)s-%(ext|def)d', 'mp4-def')
746 test('%(width|0)04d', '0000')
747 test('a%(width|)d', 'a', outtmpl_na_placeholder
='none')
749 # Internal formatting
750 FORMATS
= self
.outtmpl_info
['formats']
751 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
752 test('%(id+1-height+3)05d', '00158')
753 test('%(width+100)05d', 'NA')
754 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], '% 15s' % str(FORMATS
[0]).replace(':', ' -')))
755 test('%(formats.0)r', (repr(FORMATS
[0]), repr(FORMATS
[0]).replace(':', ' -')))
756 test('%(height.0)03d', '001')
757 test('%(-height.0)04d', '-001')
758 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
759 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
760 test('%(formats.3)s', 'NA')
761 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
762 test('%(formats.0.id.-1+id)f', '1235.000000')
763 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
766 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
767 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
768 # test('%(foo|)s', ('', '_')) # fixme
770 # Path expansion and escaping
771 test('Hello %(title1)s', 'Hello $PATH')
772 test('Hello %(title2)s', 'Hello %PATH%')
773 test('%(title3)s', ('foo/bar\\test', 'foo_bar_test'))
774 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo_bar_test' % os
.path
.sep
))
776 def test_format_note(self
):
778 self
.assertEqual(ydl
._format
_note
({}), '')
779 assertRegexpMatches(self
, ydl
._format
_note
({
782 assertRegexpMatches(self
, ydl
._format
_note
({
786 def test_postprocessors(self
):
787 filename
= 'post-processor-testfile.mp4'
788 audiofile
= filename
+ '.mp3'
790 class SimplePP(PostProcessor
):
792 with open(audiofile
, 'wt') as f
:
794 return [info
['filepath']], info
796 def run_pp(params
, PP
):
797 with open(filename
, 'wt') as f
:
799 ydl
= YoutubeDL(params
)
800 ydl
.add_post_processor(PP())
801 ydl
.post_process(filename
, {'filepath': filename}
)
803 run_pp({'keepvideo': True}
, SimplePP
)
804 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
805 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
809 run_pp({'keepvideo': False}
, SimplePP
)
810 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
811 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
814 class ModifierPP(PostProcessor
):
816 with open(info
['filepath'], 'wt') as f
:
820 run_pp({'keepvideo': False}
, ModifierPP
)
821 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
824 def test_match_filter(self
):
825 class FilterYDL(YDL
):
826 def __init__(self
, *args
, **kwargs
):
827 super(FilterYDL
, self
).__init
__(*args
, **kwargs
)
828 self
.params
['simulate'] = True
830 def process_info(self
, info_dict
):
831 super(YDL
, self
).process_info(info_dict
)
833 def _match_entry(self
, info_dict
, incomplete
=False):
834 res
= super(FilterYDL
, self
)._match
_entry
(info_dict
, incomplete
)
836 self
.downloaded_info_dicts
.append(info_dict
)
845 'filesize': 10 * 1024,
847 'uploader': "變態妍字幕版 太妍 тест",
848 'creator': "тест ' 123 ' тест--",
849 'webpage_url': 'http://example.com/watch?v=shenanigans',
857 'description': 'foo',
858 'filesize': 5 * 1024,
860 'uploader': "тест 123",
861 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
863 videos
= [first
, second
]
865 def get_videos(filter_
=None):
866 ydl
= FilterYDL({'match_filter': filter_}
)
868 ydl
.process_ie_result(v
, download
=True)
869 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
872 self
.assertEqual(res
, ['1', '2'])
878 return 'Video id is not 1'
880 self
.assertEqual(res
, ['1'])
882 f
= match_filter_func('duration < 30')
884 self
.assertEqual(res
, ['2'])
886 f
= match_filter_func('description = foo')
888 self
.assertEqual(res
, ['2'])
890 f
= match_filter_func('description =? foo')
892 self
.assertEqual(res
, ['1', '2'])
894 f
= match_filter_func('filesize > 5KiB')
896 self
.assertEqual(res
, ['1'])
898 f
= match_filter_func('playlist_id = 42')
900 self
.assertEqual(res
, ['1'])
902 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
904 self
.assertEqual(res
, ['1'])
906 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
908 self
.assertEqual(res
, ['2'])
910 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
912 self
.assertEqual(res
, ['1'])
914 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
916 self
.assertEqual(res
, ['1'])
918 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
920 self
.assertEqual(res
, [])
922 def test_playlist_items_selection(self
):
925 'title': compat_str(i
),
927 } for i
in range(1, 5)]
932 'extractor': 'test:playlist',
933 'extractor_key': 'test:playlist',
934 'webpage_url': 'http://example.com',
937 def get_downloaded_info_dicts(params
):
939 # make a deep copy because the dictionary and nested entries
941 ydl
.process_ie_result(copy
.deepcopy(playlist
))
942 return ydl
.downloaded_info_dicts
945 return [int(v
['id']) for v
in get_downloaded_info_dicts(params
)]
948 self
.assertEqual(result
, [1, 2, 3, 4])
950 result
= get_ids({'playlistend': 10}
)
951 self
.assertEqual(result
, [1, 2, 3, 4])
953 result
= get_ids({'playlistend': 2}
)
954 self
.assertEqual(result
, [1, 2])
956 result
= get_ids({'playliststart': 10}
)
957 self
.assertEqual(result
, [])
959 result
= get_ids({'playliststart': 2}
)
960 self
.assertEqual(result
, [2, 3, 4])
962 result
= get_ids({'playlist_items': '2-4'}
)
963 self
.assertEqual(result
, [2, 3, 4])
965 result
= get_ids({'playlist_items': '2,4'}
)
966 self
.assertEqual(result
, [2, 4])
968 result
= get_ids({'playlist_items': '10'}
)
969 self
.assertEqual(result
, [])
971 result
= get_ids({'playlist_items': '3-10'}
)
972 self
.assertEqual(result
, [3, 4])
974 result
= get_ids({'playlist_items': '2-4,3-4,3'}
)
975 self
.assertEqual(result
, [2, 3, 4])
977 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
979 result
= get_downloaded_info_dicts({'playlist_items': '2-4,3-4,3'}
)
980 self
.assertEqual(result
[0]['playlist_index'], 2)
981 self
.assertEqual(result
[1]['playlist_index'], 3)
983 result
= get_downloaded_info_dicts({'playlist_items': '2-4,3-4,3'}
)
984 self
.assertEqual(result
[0]['playlist_index'], 2)
985 self
.assertEqual(result
[1]['playlist_index'], 3)
986 self
.assertEqual(result
[2]['playlist_index'], 4)
988 result
= get_downloaded_info_dicts({'playlist_items': '4,2'}
)
989 self
.assertEqual(result
[0]['playlist_index'], 4)
990 self
.assertEqual(result
[1]['playlist_index'], 2)
993 def test_urlopen_no_file_protocol(self
):
994 # see https://github.com/ytdl-org/youtube-dl/issues/8227
996 self
.assertRaises(compat_urllib_error
.URLError
, ydl
.urlopen
, 'file:///etc/passwd')
998 def test_do_not_override_ie_key_in_url_transparent(self
):
1001 class Foo1IE(InfoExtractor
):
1002 _VALID_URL
= r
'foo1:'
1004 def _real_extract(self
, url
):
1006 '_type': 'url_transparent',
1009 'title': 'foo1 title',
1013 class Foo2IE(InfoExtractor
):
1014 _VALID_URL
= r
'foo2:'
1016 def _real_extract(self
, url
):
1023 class Foo3IE(InfoExtractor
):
1024 _VALID_URL
= r
'foo3:'
1026 def _real_extract(self
, url
):
1027 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1029 ydl
.add_info_extractor(Foo1IE(ydl
))
1030 ydl
.add_info_extractor(Foo2IE(ydl
))
1031 ydl
.add_info_extractor(Foo3IE(ydl
))
1032 ydl
.extract_info('foo1:')
1033 downloaded
= ydl
.downloaded_info_dicts
[0]
1034 self
.assertEqual(downloaded
['url'], TEST_URL
)
1035 self
.assertEqual(downloaded
['title'], 'foo1 title')
1036 self
.assertEqual(downloaded
['id'], 'testid')
1037 self
.assertEqual(downloaded
['extractor'], 'testex')
1038 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1040 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1041 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1044 def __init__(self
, *args
, **kwargs
):
1045 super(_YDL
, self
).__init
__(*args
, **kwargs
)
1047 def trouble(self
, s
, tb
=None):
1052 'ignoreerrors': True,
1055 class VideoIE(InfoExtractor
):
1056 _VALID_URL
= r
'video:(?P<id>\d+)'
1058 def _real_extract(self
, url
):
1059 video_id
= self
._match
_id
(url
)
1061 'format_id': 'default',
1065 raise ExtractorError('foo')
1068 'format_id': 'extra',
1073 'title': 'Video %s' % video_id
,
1077 class PlaylistIE(InfoExtractor
):
1078 _VALID_URL
= r
'playlist:'
1082 video_id
= compat_str(n
)
1084 '_type': 'url_transparent',
1085 'ie_key': VideoIE
.ie_key(),
1087 'url': 'video:%s' % video_id
,
1088 'title': 'Video Transparent %s' % video_id
,
1091 def _real_extract(self
, url
):
1092 return self
.playlist_result(self
._entries
())
1094 ydl
.add_info_extractor(VideoIE(ydl
))
1095 ydl
.add_info_extractor(PlaylistIE(ydl
))
1096 info
= ydl
.extract_info('playlist:')
1097 entries
= info
['entries']
1098 self
.assertEqual(len(entries
), 3)
1099 self
.assertTrue(entries
[0] is None)
1100 self
.assertTrue(entries
[1] is None)
1101 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1102 downloaded
= ydl
.downloaded_info_dicts
[0]
1103 self
.assertEqual(entries
[2], downloaded
)
1104 self
.assertEqual(downloaded
['url'], TEST_URL
)
1105 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1106 self
.assertEqual(downloaded
['id'], '2')
1107 self
.assertEqual(downloaded
['extractor'], 'Video')
1108 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1111 if __name__
== '__main__':