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
, **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
, self
.outtmpl_info
)
674 out
= outtmpl
% tmpl_dict
675 fname
= ydl
.prepare_filename(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 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%'), ValueError))
705 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
706 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
712 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
713 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
714 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
715 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
718 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
719 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
720 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
721 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
722 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
723 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
724 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
725 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
726 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
727 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
728 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
731 test('%(id)d', '1234')
732 test('%(height)c', '1')
734 test('%(id)d %(id)r', "1234 '1234'")
735 test('%(id)r %(height)r', "'1234' 1080")
736 test('%(ext)s-%(ext|def)d', 'mp4-def')
737 test('%(width|0)04d', '0000')
738 test('a%(width|)d', 'a', outtmpl_na_placeholder
='none')
740 # Internal formatting
741 FORMATS
= self
.outtmpl_info
['formats']
742 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
743 test('%(id+1-height+3)05d', '00158')
744 test('%(width+100)05d', 'NA')
745 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], '% 15s' % str(FORMATS
[0]).replace(':', ' -')))
746 test('%(formats.0)r', (repr(FORMATS
[0]), repr(FORMATS
[0]).replace(':', ' -')))
747 test('%(height.0)03d', '001')
748 test('%(-height.0)04d', '-001')
749 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
750 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
751 test('%(formats.3)s', 'NA')
752 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
753 test('%(formats.0.id.-1+id)f', '1235.000000')
754 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
757 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
758 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
759 # test('%(foo|)s', ('', '_')) # fixme
761 # Path expansion and escaping
762 test('Hello %(title1)s', 'Hello $PATH')
763 test('Hello %(title2)s', 'Hello %PATH%')
764 test('%(title3)s', ('foo/bar\\test', 'foo_bar_test'))
765 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo_bar_test' % os
.path
.sep
))
767 def test_format_note(self
):
769 self
.assertEqual(ydl
._format
_note
({}), '')
770 assertRegexpMatches(self
, ydl
._format
_note
({
773 assertRegexpMatches(self
, ydl
._format
_note
({
777 def test_postprocessors(self
):
778 filename
= 'post-processor-testfile.mp4'
779 audiofile
= filename
+ '.mp3'
781 class SimplePP(PostProcessor
):
783 with open(audiofile
, 'wt') as f
:
785 return [info
['filepath']], info
787 def run_pp(params
, PP
):
788 with open(filename
, 'wt') as f
:
790 ydl
= YoutubeDL(params
)
791 ydl
.add_post_processor(PP())
792 ydl
.post_process(filename
, {'filepath': filename}
)
794 run_pp({'keepvideo': True}
, SimplePP
)
795 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
796 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
800 run_pp({'keepvideo': False}
, SimplePP
)
801 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
802 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
805 class ModifierPP(PostProcessor
):
807 with open(info
['filepath'], 'wt') as f
:
811 run_pp({'keepvideo': False}
, ModifierPP
)
812 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
815 def test_match_filter(self
):
816 class FilterYDL(YDL
):
817 def __init__(self
, *args
, **kwargs
):
818 super(FilterYDL
, self
).__init
__(*args
, **kwargs
)
819 self
.params
['simulate'] = True
821 def process_info(self
, info_dict
):
822 super(YDL
, self
).process_info(info_dict
)
824 def _match_entry(self
, info_dict
, incomplete
=False):
825 res
= super(FilterYDL
, self
)._match
_entry
(info_dict
, incomplete
)
827 self
.downloaded_info_dicts
.append(info_dict
)
836 'filesize': 10 * 1024,
838 'uploader': "變態妍字幕版 太妍 тест",
839 'creator': "тест ' 123 ' тест--",
840 'webpage_url': 'http://example.com/watch?v=shenanigans',
848 'description': 'foo',
849 'filesize': 5 * 1024,
851 'uploader': "тест 123",
852 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
854 videos
= [first
, second
]
856 def get_videos(filter_
=None):
857 ydl
= FilterYDL({'match_filter': filter_}
)
859 ydl
.process_ie_result(v
, download
=True)
860 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
863 self
.assertEqual(res
, ['1', '2'])
869 return 'Video id is not 1'
871 self
.assertEqual(res
, ['1'])
873 f
= match_filter_func('duration < 30')
875 self
.assertEqual(res
, ['2'])
877 f
= match_filter_func('description = foo')
879 self
.assertEqual(res
, ['2'])
881 f
= match_filter_func('description =? foo')
883 self
.assertEqual(res
, ['1', '2'])
885 f
= match_filter_func('filesize > 5KiB')
887 self
.assertEqual(res
, ['1'])
889 f
= match_filter_func('playlist_id = 42')
891 self
.assertEqual(res
, ['1'])
893 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
895 self
.assertEqual(res
, ['1'])
897 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
899 self
.assertEqual(res
, ['2'])
901 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
903 self
.assertEqual(res
, ['1'])
905 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
907 self
.assertEqual(res
, ['1'])
909 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
911 self
.assertEqual(res
, [])
913 def test_playlist_items_selection(self
):
916 'title': compat_str(i
),
918 } for i
in range(1, 5)]
923 'extractor': 'test:playlist',
924 'extractor_key': 'test:playlist',
925 'webpage_url': 'http://example.com',
928 def get_downloaded_info_dicts(params
):
930 # make a deep copy because the dictionary and nested entries
932 ydl
.process_ie_result(copy
.deepcopy(playlist
))
933 return ydl
.downloaded_info_dicts
936 return [int(v
['id']) for v
in get_downloaded_info_dicts(params
)]
939 self
.assertEqual(result
, [1, 2, 3, 4])
941 result
= get_ids({'playlistend': 10}
)
942 self
.assertEqual(result
, [1, 2, 3, 4])
944 result
= get_ids({'playlistend': 2}
)
945 self
.assertEqual(result
, [1, 2])
947 result
= get_ids({'playliststart': 10}
)
948 self
.assertEqual(result
, [])
950 result
= get_ids({'playliststart': 2}
)
951 self
.assertEqual(result
, [2, 3, 4])
953 result
= get_ids({'playlist_items': '2-4'}
)
954 self
.assertEqual(result
, [2, 3, 4])
956 result
= get_ids({'playlist_items': '2,4'}
)
957 self
.assertEqual(result
, [2, 4])
959 result
= get_ids({'playlist_items': '10'}
)
960 self
.assertEqual(result
, [])
962 result
= get_ids({'playlist_items': '3-10'}
)
963 self
.assertEqual(result
, [3, 4])
965 result
= get_ids({'playlist_items': '2-4,3-4,3'}
)
966 self
.assertEqual(result
, [2, 3, 4])
968 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
970 result
= get_downloaded_info_dicts({'playlist_items': '2-4,3-4,3'}
)
971 self
.assertEqual(result
[0]['playlist_index'], 2)
972 self
.assertEqual(result
[1]['playlist_index'], 3)
974 result
= get_downloaded_info_dicts({'playlist_items': '2-4,3-4,3'}
)
975 self
.assertEqual(result
[0]['playlist_index'], 2)
976 self
.assertEqual(result
[1]['playlist_index'], 3)
977 self
.assertEqual(result
[2]['playlist_index'], 4)
979 result
= get_downloaded_info_dicts({'playlist_items': '4,2'}
)
980 self
.assertEqual(result
[0]['playlist_index'], 4)
981 self
.assertEqual(result
[1]['playlist_index'], 2)
984 def test_urlopen_no_file_protocol(self
):
985 # see https://github.com/ytdl-org/youtube-dl/issues/8227
987 self
.assertRaises(compat_urllib_error
.URLError
, ydl
.urlopen
, 'file:///etc/passwd')
989 def test_do_not_override_ie_key_in_url_transparent(self
):
992 class Foo1IE(InfoExtractor
):
993 _VALID_URL
= r
'foo1:'
995 def _real_extract(self
, url
):
997 '_type': 'url_transparent',
1000 'title': 'foo1 title',
1004 class Foo2IE(InfoExtractor
):
1005 _VALID_URL
= r
'foo2:'
1007 def _real_extract(self
, url
):
1014 class Foo3IE(InfoExtractor
):
1015 _VALID_URL
= r
'foo3:'
1017 def _real_extract(self
, url
):
1018 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1020 ydl
.add_info_extractor(Foo1IE(ydl
))
1021 ydl
.add_info_extractor(Foo2IE(ydl
))
1022 ydl
.add_info_extractor(Foo3IE(ydl
))
1023 ydl
.extract_info('foo1:')
1024 downloaded
= ydl
.downloaded_info_dicts
[0]
1025 self
.assertEqual(downloaded
['url'], TEST_URL
)
1026 self
.assertEqual(downloaded
['title'], 'foo1 title')
1027 self
.assertEqual(downloaded
['id'], 'testid')
1028 self
.assertEqual(downloaded
['extractor'], 'testex')
1029 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1031 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1032 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1035 def __init__(self
, *args
, **kwargs
):
1036 super(_YDL
, self
).__init
__(*args
, **kwargs
)
1038 def trouble(self
, s
, tb
=None):
1043 'ignoreerrors': True,
1046 class VideoIE(InfoExtractor
):
1047 _VALID_URL
= r
'video:(?P<id>\d+)'
1049 def _real_extract(self
, url
):
1050 video_id
= self
._match
_id
(url
)
1052 'format_id': 'default',
1056 raise ExtractorError('foo')
1059 'format_id': 'extra',
1064 'title': 'Video %s' % video_id
,
1068 class PlaylistIE(InfoExtractor
):
1069 _VALID_URL
= r
'playlist:'
1073 video_id
= compat_str(n
)
1075 '_type': 'url_transparent',
1076 'ie_key': VideoIE
.ie_key(),
1078 'url': 'video:%s' % video_id
,
1079 'title': 'Video Transparent %s' % video_id
,
1082 def _real_extract(self
, url
):
1083 return self
.playlist_result(self
._entries
())
1085 ydl
.add_info_extractor(VideoIE(ydl
))
1086 ydl
.add_info_extractor(PlaylistIE(ydl
))
1087 info
= ydl
.extract_info('playlist:')
1088 entries
= info
['entries']
1089 self
.assertEqual(len(entries
), 3)
1090 self
.assertTrue(entries
[0] is None)
1091 self
.assertTrue(entries
[1] is None)
1092 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1093 downloaded
= ydl
.downloaded_info_dicts
[0]
1094 self
.assertEqual(entries
[2], downloaded
)
1095 self
.assertEqual(downloaded
['url'], TEST_URL
)
1096 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1097 self
.assertEqual(downloaded
['id'], '2')
1098 self
.assertEqual(downloaded
['extractor'], 'Video')
1099 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1102 if __name__
== '__main__':