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
, float_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 ydl
= YDL({'format': format_spec}
)
465 info_dict
= _make_result([{'format_id': 'foo', 'url': TEST_URL}
])
466 self
.assertRaises(SyntaxError, ydl
.process_ie_result
, info_dict
)
468 assert_syntax_error('bestvideo,,best')
469 assert_syntax_error('+bestaudio')
470 assert_syntax_error('bestvideo+')
471 assert_syntax_error('/')
473 def test_format_filtering(self
):
475 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
476 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
477 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
478 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
479 {'format_id': 'E', 'filesize': 3000}
,
481 {'format_id': 'G', 'filesize': 1000000}
,
484 f
['url'] = 'http://_/'
486 info_dict
= _make_result(formats
)
488 ydl
= YDL({'format': 'best[filesize<3000]'}
)
489 ydl
.process_ie_result(info_dict
)
490 downloaded
= ydl
.downloaded_info_dicts
[0]
491 self
.assertEqual(downloaded
['format_id'], 'D')
493 ydl
= YDL({'format': 'best[filesize<=3000]'}
)
494 ydl
.process_ie_result(info_dict
)
495 downloaded
= ydl
.downloaded_info_dicts
[0]
496 self
.assertEqual(downloaded
['format_id'], 'E')
498 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
499 ydl
.process_ie_result(info_dict
)
500 downloaded
= ydl
.downloaded_info_dicts
[0]
501 self
.assertEqual(downloaded
['format_id'], 'F')
503 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'}
)
504 ydl
.process_ie_result(info_dict
)
505 downloaded
= ydl
.downloaded_info_dicts
[0]
506 self
.assertEqual(downloaded
['format_id'], 'B')
508 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
509 ydl
.process_ie_result(info_dict
)
510 downloaded
= ydl
.downloaded_info_dicts
[0]
511 self
.assertEqual(downloaded
['format_id'], 'C')
513 ydl
= YDL({'format': '[filesize>?1]'}
)
514 ydl
.process_ie_result(info_dict
)
515 downloaded
= ydl
.downloaded_info_dicts
[0]
516 self
.assertEqual(downloaded
['format_id'], 'G')
518 ydl
= YDL({'format': '[filesize<1M]'}
)
519 ydl
.process_ie_result(info_dict
)
520 downloaded
= ydl
.downloaded_info_dicts
[0]
521 self
.assertEqual(downloaded
['format_id'], 'E')
523 ydl
= YDL({'format': '[filesize<1MiB]'}
)
524 ydl
.process_ie_result(info_dict
)
525 downloaded
= ydl
.downloaded_info_dicts
[0]
526 self
.assertEqual(downloaded
['format_id'], 'G')
528 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
529 ydl
.process_ie_result(info_dict
)
530 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
531 self
.assertEqual(downloaded_ids
, ['B', 'C', 'D'])
533 ydl
= YDL({'format': 'best[height<40]'}
)
535 ydl
.process_ie_result(info_dict
)
536 except ExtractorError
:
538 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
540 def test_default_format_spec(self
):
541 ydl
= YDL({'simulate': True}
)
542 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
545 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
547 ydl
= YDL({'simulate': True}
)
548 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
550 ydl
= YDL({'outtmpl': '-'}
)
551 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
554 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
555 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
558 class TestYoutubeDL(unittest
.TestCase
):
559 def test_subtitles(self
):
560 def s_formats(lang
, autocaption
=False):
563 'url': 'http://localhost/video.%s.%s' % (lang
, ext
),
564 '_auto': autocaption
,
565 } for ext
in ['vtt', 'srt', 'ass']]
566 subtitles
= dict((l
, s_formats(l
)) for l
in ['en', 'fr', 'es'])
567 auto_captions
= dict((l
, s_formats(l
, True)) for l
in ['it', 'pt', 'es'])
571 'url': 'http://localhost/video.mp4',
572 'subtitles': subtitles
,
573 'automatic_captions': auto_captions
,
575 'webpage_url': 'http://example.com/watch?v=shenanigans',
578 def get_info(params
={}):
579 params
.setdefault('simulate', True)
581 ydl
.report_warning
= lambda *args
, **kargs
: None
582 return ydl
.process_video_result(info_dict
, download
=False)
585 self
.assertFalse(result
.get('requested_subtitles'))
586 self
.assertEqual(result
['subtitles'], subtitles
)
587 self
.assertEqual(result
['automatic_captions'], auto_captions
)
589 result
= get_info({'writesubtitles': True}
)
590 subs
= result
['requested_subtitles']
591 self
.assertTrue(subs
)
592 self
.assertEqual(set(subs
.keys()), set(['en']))
593 self
.assertTrue(subs
['en'].get('data') is None)
594 self
.assertEqual(subs
['en']['ext'], 'ass')
596 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
597 subs
= result
['requested_subtitles']
598 self
.assertEqual(subs
['en']['ext'], 'srt')
600 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
601 subs
= result
['requested_subtitles']
602 self
.assertTrue(subs
)
603 self
.assertEqual(set(subs
.keys()), set(['es', 'fr']))
605 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
606 subs
= result
['requested_subtitles']
607 self
.assertTrue(subs
)
608 self
.assertEqual(set(subs
.keys()), set(['es', 'fr']))
610 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
611 subs
= result
['requested_subtitles']
612 self
.assertTrue(subs
)
613 self
.assertEqual(set(subs
.keys()), set(['fr']))
615 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
616 subs
= result
['requested_subtitles']
617 self
.assertTrue(subs
)
618 self
.assertEqual(set(subs
.keys()), set(['en']))
620 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
621 subs
= result
['requested_subtitles']
622 self
.assertTrue(subs
)
623 self
.assertEqual(set(subs
.keys()), set(['es', 'en']))
625 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
626 subs
= result
['requested_subtitles']
627 self
.assertTrue(subs
)
628 self
.assertEqual(set(subs
.keys()), set(['es', 'pt']))
629 self
.assertFalse(subs
['es']['_auto'])
630 self
.assertTrue(subs
['pt']['_auto'])
632 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
633 subs
= result
['requested_subtitles']
634 self
.assertTrue(subs
)
635 self
.assertEqual(set(subs
.keys()), set(['es', 'pt']))
636 self
.assertTrue(subs
['es']['_auto'])
637 self
.assertTrue(subs
['pt']['_auto'])
639 def test_add_extra_info(self
):
645 'playlist': 'funny videos',
647 YDL
.add_extra_info(test_dict
, extra_info
)
648 self
.assertEqual(test_dict
['extractor'], 'Foo')
649 self
.assertEqual(test_dict
['playlist'], 'funny videos')
658 'title3': 'foo/bar\\test',
659 'timestamp': 1618488000,
662 '_last_playlist_index': 100,
664 'formats': [{'id': 'id1'}
, {'id': 'id2'}
, {'id': 'id3'}
]
667 def test_prepare_outtmpl(self
):
668 def out(tmpl
, **params
):
669 params
['outtmpl'] = tmpl
670 ydl
= YoutubeDL(params
)
671 ydl
._num
_downloads
= 1
672 err
= ydl
.validate_outtmpl(tmpl
)
675 outtmpl
, tmpl_dict
= ydl
.prepare_outtmpl(tmpl
, self
.outtmpl_info
)
676 return outtmpl
% tmpl_dict
678 self
.assertEqual(out('%(id)s.%(ext)s'), '1234.mp4')
679 self
.assertEqual(out('%(duration_string)s'), '27:46:40')
680 self
.assertTrue(float_or_none(out('%(epoch)d')))
681 self
.assertEqual(out('%(resolution)s'), '1080p')
682 self
.assertEqual(out('%(playlist_index)s'), '001')
683 self
.assertEqual(out('%(autonumber)s'), '00001')
684 self
.assertEqual(out('%(autonumber+2)03d', autonumber_start
=3), '005')
685 self
.assertEqual(out('%(autonumber)s', autonumber_size
=3), '001')
687 self
.assertEqual(out('%%'), '%')
688 self
.assertEqual(out('%%%%'), '%%')
689 self
.assertEqual(out('%(invalid@tmpl|def)s', outtmpl_na_placeholder
='none'), 'none')
690 self
.assertEqual(out('%()s'), 'NA')
691 self
.assertEqual(out('%s'), '%s')
692 self
.assertEqual(out('%d'), '%d')
693 self
.assertRaises(ValueError, out
, '%')
694 self
.assertRaises(ValueError, out
, '%(title)')
696 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
697 self
.assertEqual(out(NA_TEST_OUTTMPL
), 'NA-NA-def-1234.mp4')
698 self
.assertEqual(out(NA_TEST_OUTTMPL
, outtmpl_na_placeholder
='none'), 'none-none-def-1234.mp4')
699 self
.assertEqual(out(NA_TEST_OUTTMPL
, outtmpl_na_placeholder
=''), '--def-1234.mp4')
701 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
702 self
.assertEqual(out(FMT_TEST_OUTTMPL
% 's'), '1080.mp4')
703 self
.assertEqual(out(FMT_TEST_OUTTMPL
% 'd'), '1080.mp4')
704 self
.assertEqual(out(FMT_TEST_OUTTMPL
% '6d'), ' 1080.mp4')
705 self
.assertEqual(out(FMT_TEST_OUTTMPL
% '-6d'), '1080 .mp4')
706 self
.assertEqual(out(FMT_TEST_OUTTMPL
% '06d'), '001080.mp4')
707 self
.assertEqual(out(FMT_TEST_OUTTMPL
% ' 06d'), ' 01080.mp4')
708 self
.assertEqual(out(FMT_TEST_OUTTMPL
% ' 06d'), ' 01080.mp4')
709 self
.assertEqual(out(FMT_TEST_OUTTMPL
% '0 6d'), ' 01080.mp4')
710 self
.assertEqual(out(FMT_TEST_OUTTMPL
% '0 6d'), ' 01080.mp4')
711 self
.assertEqual(out(FMT_TEST_OUTTMPL
% ' 0 6d'), ' 01080.mp4')
713 self
.assertEqual(out('%(id)d'), '1234')
714 self
.assertEqual(out('%(height)c'), '1')
715 self
.assertEqual(out('%(ext)c'), 'm')
716 self
.assertEqual(out('%(id)d %(id)r'), "1234 '1234'")
717 self
.assertEqual(out('%(ext)s-%(ext|def)d'), 'mp4-def')
718 self
.assertEqual(out('%(width|0)04d'), '0000')
719 self
.assertEqual(out('%(width|)d', outtmpl_na_placeholder
='none'), '')
721 FORMATS
= self
.outtmpl_info
['formats']
722 self
.assertEqual(out('%(timestamp+-1000>%H-%M-%S)s'), '11-43-20')
723 self
.assertEqual(out('%(id+1-height+3)05d'), '00158')
724 self
.assertEqual(out('%(width+100)05d'), 'NA')
725 self
.assertEqual(out('%(formats.0)s'), str(FORMATS
[0]))
726 self
.assertEqual(out('%(height.0)03d'), '001')
727 self
.assertEqual(out('%(formats.-1.id)s'), str(FORMATS
[-1]['id']))
728 self
.assertEqual(out('%(formats.3)s'), 'NA')
729 self
.assertEqual(out('%(formats.:2:-1)r'), repr(FORMATS
[:2:-1]))
730 self
.assertEqual(out('%(formats.0.id.-1+id)f'), '1235.000000')
732 def test_prepare_filename(self
):
734 params
= {'outtmpl': templ}
735 ydl
= YoutubeDL(params
)
736 return ydl
.prepare_filename(self
.outtmpl_info
)
738 self
.assertEqual(fname('%(height)06d.%(ext)s'), '001080.mp4')
739 self
.assertEqual(fname('%(foo|)s-%(bar|)s.%(ext)s'), '-.mp4')
740 # self.assertEqual(fname('%(foo|)s.%(ext)s'), '_.mp4') # fixme
741 # self.assertEqual(fname('%(foo|)s'), '_') # fixme
743 self
.assertEqual(fname('%%'), '%')
744 self
.assertEqual(fname('%%%%'), '%%')
745 self
.assertEqual(fname('%%(width)06d.%(ext)s'), '%(width)06d.mp4')
746 self
.assertEqual(fname('%(width)06d.%(ext)s'), 'NA.mp4')
747 self
.assertEqual(fname('%(width)06d.%%(ext)s'), 'NA.%(ext)s')
748 self
.assertEqual(fname('%%(width)06d.%(ext)s'), '%(width)06d.mp4')
750 self
.assertEqual(fname('Hello %(title1)s'), 'Hello $PATH')
751 self
.assertEqual(fname('Hello %(title2)s'), 'Hello %PATH%')
753 self
.assertEqual(fname('%(title3)s'), 'foo_bar_test')
754 self
.assertEqual(fname('%(formats.0) 15s'), " {'id' - 'id1'}")
756 self
.assertEqual(fname('%(id)r %(height)r'), "'1234' 1080")
757 self
.assertEqual(fname('%(formats.0)r'), "{'id' - 'id1'}")
759 def test_format_note(self
):
761 self
.assertEqual(ydl
._format
_note
({}), '')
762 assertRegexpMatches(self
, ydl
._format
_note
({
765 assertRegexpMatches(self
, ydl
._format
_note
({
769 def test_postprocessors(self
):
770 filename
= 'post-processor-testfile.mp4'
771 audiofile
= filename
+ '.mp3'
773 class SimplePP(PostProcessor
):
775 with open(audiofile
, 'wt') as f
:
777 return [info
['filepath']], info
779 def run_pp(params
, PP
):
780 with open(filename
, 'wt') as f
:
782 ydl
= YoutubeDL(params
)
783 ydl
.add_post_processor(PP())
784 ydl
.post_process(filename
, {'filepath': filename}
)
786 run_pp({'keepvideo': True}
, SimplePP
)
787 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
788 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
792 run_pp({'keepvideo': False}
, SimplePP
)
793 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
794 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
797 class ModifierPP(PostProcessor
):
799 with open(info
['filepath'], 'wt') as f
:
803 run_pp({'keepvideo': False}
, ModifierPP
)
804 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
807 def test_match_filter(self
):
808 class FilterYDL(YDL
):
809 def __init__(self
, *args
, **kwargs
):
810 super(FilterYDL
, self
).__init
__(*args
, **kwargs
)
811 self
.params
['simulate'] = True
813 def process_info(self
, info_dict
):
814 super(YDL
, self
).process_info(info_dict
)
816 def _match_entry(self
, info_dict
, incomplete
=False):
817 res
= super(FilterYDL
, self
)._match
_entry
(info_dict
, incomplete
)
819 self
.downloaded_info_dicts
.append(info_dict
)
828 'filesize': 10 * 1024,
830 'uploader': "變態妍字幕版 太妍 тест",
831 'creator': "тест ' 123 ' тест--",
832 'webpage_url': 'http://example.com/watch?v=shenanigans',
840 'description': 'foo',
841 'filesize': 5 * 1024,
843 'uploader': "тест 123",
844 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
846 videos
= [first
, second
]
848 def get_videos(filter_
=None):
849 ydl
= FilterYDL({'match_filter': filter_}
)
851 ydl
.process_ie_result(v
, download
=True)
852 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
855 self
.assertEqual(res
, ['1', '2'])
861 return 'Video id is not 1'
863 self
.assertEqual(res
, ['1'])
865 f
= match_filter_func('duration < 30')
867 self
.assertEqual(res
, ['2'])
869 f
= match_filter_func('description = foo')
871 self
.assertEqual(res
, ['2'])
873 f
= match_filter_func('description =? foo')
875 self
.assertEqual(res
, ['1', '2'])
877 f
= match_filter_func('filesize > 5KiB')
879 self
.assertEqual(res
, ['1'])
881 f
= match_filter_func('playlist_id = 42')
883 self
.assertEqual(res
, ['1'])
885 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
887 self
.assertEqual(res
, ['1'])
889 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
891 self
.assertEqual(res
, ['2'])
893 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
895 self
.assertEqual(res
, ['1'])
897 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
899 self
.assertEqual(res
, ['1'])
901 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
903 self
.assertEqual(res
, [])
905 def test_playlist_items_selection(self
):
908 'title': compat_str(i
),
910 } for i
in range(1, 5)]
915 'extractor': 'test:playlist',
916 'extractor_key': 'test:playlist',
917 'webpage_url': 'http://example.com',
920 def get_downloaded_info_dicts(params
):
922 # make a deep copy because the dictionary and nested entries
924 ydl
.process_ie_result(copy
.deepcopy(playlist
))
925 return ydl
.downloaded_info_dicts
928 return [int(v
['id']) for v
in get_downloaded_info_dicts(params
)]
931 self
.assertEqual(result
, [1, 2, 3, 4])
933 result
= get_ids({'playlistend': 10}
)
934 self
.assertEqual(result
, [1, 2, 3, 4])
936 result
= get_ids({'playlistend': 2}
)
937 self
.assertEqual(result
, [1, 2])
939 result
= get_ids({'playliststart': 10}
)
940 self
.assertEqual(result
, [])
942 result
= get_ids({'playliststart': 2}
)
943 self
.assertEqual(result
, [2, 3, 4])
945 result
= get_ids({'playlist_items': '2-4'}
)
946 self
.assertEqual(result
, [2, 3, 4])
948 result
= get_ids({'playlist_items': '2,4'}
)
949 self
.assertEqual(result
, [2, 4])
951 result
= get_ids({'playlist_items': '10'}
)
952 self
.assertEqual(result
, [])
954 result
= get_ids({'playlist_items': '3-10'}
)
955 self
.assertEqual(result
, [3, 4])
957 result
= get_ids({'playlist_items': '2-4,3-4,3'}
)
958 self
.assertEqual(result
, [2, 3, 4])
960 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
962 result
= get_downloaded_info_dicts({'playlist_items': '2-4,3-4,3'}
)
963 self
.assertEqual(result
[0]['playlist_index'], 2)
964 self
.assertEqual(result
[1]['playlist_index'], 3)
966 result
= get_downloaded_info_dicts({'playlist_items': '2-4,3-4,3'}
)
967 self
.assertEqual(result
[0]['playlist_index'], 2)
968 self
.assertEqual(result
[1]['playlist_index'], 3)
969 self
.assertEqual(result
[2]['playlist_index'], 4)
971 result
= get_downloaded_info_dicts({'playlist_items': '4,2'}
)
972 self
.assertEqual(result
[0]['playlist_index'], 4)
973 self
.assertEqual(result
[1]['playlist_index'], 2)
976 def test_urlopen_no_file_protocol(self
):
977 # see https://github.com/ytdl-org/youtube-dl/issues/8227
979 self
.assertRaises(compat_urllib_error
.URLError
, ydl
.urlopen
, 'file:///etc/passwd')
981 def test_do_not_override_ie_key_in_url_transparent(self
):
984 class Foo1IE(InfoExtractor
):
985 _VALID_URL
= r
'foo1:'
987 def _real_extract(self
, url
):
989 '_type': 'url_transparent',
992 'title': 'foo1 title',
996 class Foo2IE(InfoExtractor
):
997 _VALID_URL
= r
'foo2:'
999 def _real_extract(self
, url
):
1006 class Foo3IE(InfoExtractor
):
1007 _VALID_URL
= r
'foo3:'
1009 def _real_extract(self
, url
):
1010 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1012 ydl
.add_info_extractor(Foo1IE(ydl
))
1013 ydl
.add_info_extractor(Foo2IE(ydl
))
1014 ydl
.add_info_extractor(Foo3IE(ydl
))
1015 ydl
.extract_info('foo1:')
1016 downloaded
= ydl
.downloaded_info_dicts
[0]
1017 self
.assertEqual(downloaded
['url'], TEST_URL
)
1018 self
.assertEqual(downloaded
['title'], 'foo1 title')
1019 self
.assertEqual(downloaded
['id'], 'testid')
1020 self
.assertEqual(downloaded
['extractor'], 'testex')
1021 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1023 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1024 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1027 def __init__(self
, *args
, **kwargs
):
1028 super(_YDL
, self
).__init
__(*args
, **kwargs
)
1030 def trouble(self
, s
, tb
=None):
1035 'ignoreerrors': True,
1038 class VideoIE(InfoExtractor
):
1039 _VALID_URL
= r
'video:(?P<id>\d+)'
1041 def _real_extract(self
, url
):
1042 video_id
= self
._match
_id
(url
)
1044 'format_id': 'default',
1048 raise ExtractorError('foo')
1051 'format_id': 'extra',
1056 'title': 'Video %s' % video_id
,
1060 class PlaylistIE(InfoExtractor
):
1061 _VALID_URL
= r
'playlist:'
1065 video_id
= compat_str(n
)
1067 '_type': 'url_transparent',
1068 'ie_key': VideoIE
.ie_key(),
1070 'url': 'video:%s' % video_id
,
1071 'title': 'Video Transparent %s' % video_id
,
1074 def _real_extract(self
, url
):
1075 return self
.playlist_result(self
._entries
())
1077 ydl
.add_info_extractor(VideoIE(ydl
))
1078 ydl
.add_info_extractor(PlaylistIE(ydl
))
1079 info
= ydl
.extract_info('playlist:')
1080 entries
= info
['entries']
1081 self
.assertEqual(len(entries
), 3)
1082 self
.assertTrue(entries
[0] is None)
1083 self
.assertTrue(entries
[1] is None)
1084 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1085 downloaded
= ydl
.downloaded_info_dicts
[0]
1086 self
.assertEqual(entries
[2], downloaded
)
1087 self
.assertEqual(downloaded
['url'], TEST_URL
)
1088 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1089 self
.assertEqual(downloaded
['id'], '2')
1090 self
.assertEqual(downloaded
['extractor'], 'Video')
1091 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1094 if __name__
== '__main__':