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
):
38 def dl(self
, *args
, **kwargs
):
39 assert False, 'Downloader must not be invoked for test_YoutubeDL'
42 def _make_result(formats
, **kwargs
):
46 'title': 'testttitle',
47 'extractor': 'testex',
48 'extractor_key': 'TestEx',
49 'webpage_url': 'http://example.com/watch?v=shenanigans',
55 class TestFormatSelection(unittest
.TestCase
):
56 def test_prefer_free_formats(self
):
57 # Same resolution => download webm
59 ydl
.params
['prefer_free_formats'] = True
61 {'ext': 'webm', 'height': 460, 'url': TEST_URL}
,
62 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}
,
64 info_dict
= _make_result(formats
)
66 yie
._sort
_formats
(info_dict
['formats'])
67 ydl
.process_ie_result(info_dict
)
68 downloaded
= ydl
.downloaded_info_dicts
[0]
69 self
.assertEqual(downloaded
['ext'], 'webm')
71 # Different resolution => download best quality (mp4)
73 ydl
.params
['prefer_free_formats'] = True
75 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
76 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}
,
78 info_dict
['formats'] = formats
80 yie
._sort
_formats
(info_dict
['formats'])
81 ydl
.process_ie_result(info_dict
)
82 downloaded
= ydl
.downloaded_info_dicts
[0]
83 self
.assertEqual(downloaded
['ext'], 'mp4')
85 # No prefer_free_formats => prefer mp4 and webm
87 ydl
.params
['prefer_free_formats'] = False
89 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
90 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}
,
91 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
93 info_dict
['formats'] = formats
95 yie
._sort
_formats
(info_dict
['formats'])
96 ydl
.process_ie_result(info_dict
)
97 downloaded
= ydl
.downloaded_info_dicts
[0]
98 self
.assertEqual(downloaded
['ext'], 'mp4')
101 ydl
.params
['prefer_free_formats'] = False
103 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
104 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
106 info_dict
['formats'] = formats
108 yie
._sort
_formats
(info_dict
['formats'])
109 ydl
.process_ie_result(info_dict
)
110 downloaded
= ydl
.downloaded_info_dicts
[0]
111 self
.assertEqual(downloaded
['ext'], 'webm')
113 def test_format_selection(self
):
115 {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
116 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL}
,
117 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL}
,
118 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL}
,
119 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL}
,
121 info_dict
= _make_result(formats
)
123 def test(inp
, *expected
, multi
=False):
126 'allow_multiple_video_streams': multi
,
127 'allow_multiple_audio_streams': multi
,
129 ydl
.process_ie_result(info_dict
.copy())
130 downloaded
= map(lambda x
: x
['format_id'], ydl
.downloaded_info_dicts
)
131 self
.assertEqual(list(downloaded
), list(expected
))
134 test('20/71/worst', '35')
136 test('webm/mp4', '47')
137 test('3gp/40/mp4', '35')
138 test('example-with-dashes', 'example-with-dashes')
139 test('all', '35', 'example-with-dashes', '45', '47', '2') # Order doesn't actually matter for this
140 test('mergeall', '2+47+45+example-with-dashes+35', multi
=True)
142 def test_format_selection_audio(self
):
144 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
145 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
146 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}
,
147 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL}
,
149 info_dict
= _make_result(formats
)
151 ydl
= YDL({'format': 'bestaudio'}
)
152 ydl
.process_ie_result(info_dict
.copy())
153 downloaded
= ydl
.downloaded_info_dicts
[0]
154 self
.assertEqual(downloaded
['format_id'], 'audio-high')
156 ydl
= YDL({'format': 'worstaudio'}
)
157 ydl
.process_ie_result(info_dict
.copy())
158 downloaded
= ydl
.downloaded_info_dicts
[0]
159 self
.assertEqual(downloaded
['format_id'], 'audio-low')
162 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
163 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL}
,
165 info_dict
= _make_result(formats
)
167 ydl
= YDL({'format': 'bestaudio/worstaudio/best'}
)
168 ydl
.process_ie_result(info_dict
.copy())
169 downloaded
= ydl
.downloaded_info_dicts
[0]
170 self
.assertEqual(downloaded
['format_id'], 'vid-high')
172 def test_format_selection_audio_exts(self
):
174 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
175 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
176 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
177 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
178 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
181 info_dict
= _make_result(formats
)
182 ydl
= YDL({'format': 'best'}
)
184 ie
._sort
_formats
(info_dict
['formats'])
185 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
186 downloaded
= ydl
.downloaded_info_dicts
[0]
187 self
.assertEqual(downloaded
['format_id'], 'aac-64')
189 ydl
= YDL({'format': 'mp3'}
)
191 ie
._sort
_formats
(info_dict
['formats'])
192 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
193 downloaded
= ydl
.downloaded_info_dicts
[0]
194 self
.assertEqual(downloaded
['format_id'], 'mp3-64')
196 ydl
= YDL({'prefer_free_formats': True}
)
198 ie
._sort
_formats
(info_dict
['formats'])
199 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
200 downloaded
= ydl
.downloaded_info_dicts
[0]
201 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
203 def test_format_selection_video(self
):
205 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
206 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
207 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
209 info_dict
= _make_result(formats
)
211 ydl
= YDL({'format': 'bestvideo'}
)
212 ydl
.process_ie_result(info_dict
.copy())
213 downloaded
= ydl
.downloaded_info_dicts
[0]
214 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
216 ydl
= YDL({'format': 'worstvideo'}
)
217 ydl
.process_ie_result(info_dict
.copy())
218 downloaded
= ydl
.downloaded_info_dicts
[0]
219 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
221 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
222 ydl
.process_ie_result(info_dict
.copy())
223 downloaded
= ydl
.downloaded_info_dicts
[0]
224 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
227 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
229 info_dict
= _make_result(formats
)
231 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
232 ydl
.process_ie_result(info_dict
.copy())
233 downloaded
= ydl
.downloaded_info_dicts
[0]
234 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
236 def test_format_selection_string_ops(self
):
238 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
239 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
241 info_dict
= _make_result(formats
)
244 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
245 ydl
.process_ie_result(info_dict
.copy())
246 downloaded
= ydl
.downloaded_info_dicts
[0]
247 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
249 # does not equal (!=)
250 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
251 ydl
.process_ie_result(info_dict
.copy())
252 downloaded
= ydl
.downloaded_info_dicts
[0]
253 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
255 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
256 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
259 ydl
= YDL({'format': '[format_id^=abc]'}
)
260 ydl
.process_ie_result(info_dict
.copy())
261 downloaded
= ydl
.downloaded_info_dicts
[0]
262 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
264 # does not start with (!^=)
265 ydl
= YDL({'format': '[format_id!^=abc]'}
)
266 ydl
.process_ie_result(info_dict
.copy())
267 downloaded
= ydl
.downloaded_info_dicts
[0]
268 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
270 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
271 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
274 ydl
= YDL({'format': '[format_id$=cba]'}
)
275 ydl
.process_ie_result(info_dict
.copy())
276 downloaded
= ydl
.downloaded_info_dicts
[0]
277 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
279 # does not end with (!$=)
280 ydl
= YDL({'format': '[format_id!$=cba]'}
)
281 ydl
.process_ie_result(info_dict
.copy())
282 downloaded
= ydl
.downloaded_info_dicts
[0]
283 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
285 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
286 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
289 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
290 ydl
.process_ie_result(info_dict
.copy())
291 downloaded
= ydl
.downloaded_info_dicts
[0]
292 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
294 # does not contain (!*=)
295 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
296 ydl
.process_ie_result(info_dict
.copy())
297 downloaded
= ydl
.downloaded_info_dicts
[0]
298 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
300 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
301 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
303 ydl
= YDL({'format': '[format_id!*=-]'}
)
304 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
306 def test_youtube_format_selection(self
):
307 # FIXME: Rewrite in accordance with the new format sorting options
311 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
312 # Apple HTTP Live Streaming
313 '96', '95', '94', '93', '92', '132', '151',
315 '85', '84', '102', '83', '101', '82', '100',
317 '137', '248', '136', '247', '135', '246',
318 '245', '244', '134', '243', '133', '242', '160',
320 '141', '172', '140', '171', '139',
323 def format_info(f_id
):
324 info
= YoutubeIE
._formats
[f_id
].copy()
326 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
327 # and 'vcodec', while in tests such information is incomplete since
328 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
329 # test_YoutubeDL.test_youtube_format_selection is broken without
331 if 'acodec' in info
and 'vcodec' not in info
:
332 info
['vcodec'] = 'none'
333 elif 'vcodec' in info
and 'acodec' not in info
:
334 info
['acodec'] = 'none'
336 info
['format_id'] = f_id
337 info
['url'] = 'url:' + f_id
339 formats_order
= [format_info(f_id
) for f_id
in order
]
341 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
342 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
344 yie
._sort
_formats
(info_dict
['formats'])
345 ydl
.process_ie_result(info_dict
)
346 downloaded
= ydl
.downloaded_info_dicts
[0]
347 self
.assertEqual(downloaded
['format_id'], '248+172')
348 self
.assertEqual(downloaded
['ext'], 'mp4')
350 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
351 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
353 yie
._sort
_formats
(info_dict
['formats'])
354 ydl
.process_ie_result(info_dict
)
355 downloaded
= ydl
.downloaded_info_dicts
[0]
356 self
.assertEqual(downloaded
['format_id'], '38')
358 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
359 ydl
= YDL({'format': 'bestvideo/best,bestaudio'}
)
361 yie
._sort
_formats
(info_dict
['formats'])
362 ydl
.process_ie_result(info_dict
)
363 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
364 self
.assertEqual(downloaded_ids
, ['137', '141'])
366 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
367 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+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', '248+141'])
374 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
375 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+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
, ['136+141', '247+141'])
382 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
383 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+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
, ['248+141'])
390 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
391 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
392 ydl
= YDL({'format': 'best/bestvideo'}
)
394 yie
._sort
_formats
(info_dict
['formats'])
395 ydl
.process_ie_result(info_dict
)
396 downloaded
= ydl
.downloaded_info_dicts
[0]
397 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
399 info_dict
= _make_result([f2
, f1
], 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 def test_audio_only_extractor_format_selection(self
):
408 # For extractors with incomplete formats (all formats are audio-only or
409 # video-only) best and worst should fallback to corresponding best/worst
410 # video-only or audio-only formats (as per
411 # https://github.com/ytdl-org/youtube-dl/pull/5556)
413 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
414 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
416 info_dict
= _make_result(formats
)
418 ydl
= YDL({'format': 'best'}
)
419 ydl
.process_ie_result(info_dict
.copy())
420 downloaded
= ydl
.downloaded_info_dicts
[0]
421 self
.assertEqual(downloaded
['format_id'], 'high')
423 ydl
= YDL({'format': 'worst'}
)
424 ydl
.process_ie_result(info_dict
.copy())
425 downloaded
= ydl
.downloaded_info_dicts
[0]
426 self
.assertEqual(downloaded
['format_id'], 'low')
428 def test_format_not_available(self
):
430 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
431 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
433 info_dict
= _make_result(formats
)
435 # This must fail since complete video-audio format does not match filter
436 # and extractor does not provide incomplete only formats (i.e. only
437 # video-only or audio-only).
438 ydl
= YDL({'format': 'best[height>360]'}
)
439 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
441 def test_format_selection_issue_10083(self
):
442 # See https://github.com/ytdl-org/youtube-dl/issues/10083
444 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
445 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
446 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
448 info_dict
= _make_result(formats
)
450 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
451 ydl
.process_ie_result(info_dict
.copy())
452 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
454 def test_invalid_format_specs(self
):
455 def assert_syntax_error(format_spec
):
456 self
.assertRaises(SyntaxError, YDL
, {'format': format_spec}
)
458 assert_syntax_error('bestvideo,,best')
459 assert_syntax_error('+bestaudio')
460 assert_syntax_error('bestvideo+')
461 assert_syntax_error('/')
462 assert_syntax_error('[720<height]')
464 def test_format_filtering(self
):
466 {'format_id': 'A', 'filesize': 500, 'width': 1000}
,
467 {'format_id': 'B', 'filesize': 1000, 'width': 500}
,
468 {'format_id': 'C', 'filesize': 1000, 'width': 400}
,
469 {'format_id': 'D', 'filesize': 2000, 'width': 600}
,
470 {'format_id': 'E', 'filesize': 3000}
,
472 {'format_id': 'G', 'filesize': 1000000}
,
475 f
['url'] = 'http://_/'
477 info_dict
= _make_result(formats
)
479 ydl
= YDL({'format': 'best[filesize<3000]'}
)
480 ydl
.process_ie_result(info_dict
)
481 downloaded
= ydl
.downloaded_info_dicts
[0]
482 self
.assertEqual(downloaded
['format_id'], 'D')
484 ydl
= YDL({'format': 'best[filesize<=3000]'}
)
485 ydl
.process_ie_result(info_dict
)
486 downloaded
= ydl
.downloaded_info_dicts
[0]
487 self
.assertEqual(downloaded
['format_id'], 'E')
489 ydl
= YDL({'format': 'best[filesize <= ? 3000]'}
)
490 ydl
.process_ie_result(info_dict
)
491 downloaded
= ydl
.downloaded_info_dicts
[0]
492 self
.assertEqual(downloaded
['format_id'], 'F')
494 ydl
= YDL({'format': 'best [filesize = 1000] [width>450]'}
)
495 ydl
.process_ie_result(info_dict
)
496 downloaded
= ydl
.downloaded_info_dicts
[0]
497 self
.assertEqual(downloaded
['format_id'], 'B')
499 ydl
= YDL({'format': 'best [filesize = 1000] [width!=450]'}
)
500 ydl
.process_ie_result(info_dict
)
501 downloaded
= ydl
.downloaded_info_dicts
[0]
502 self
.assertEqual(downloaded
['format_id'], 'C')
504 ydl
= YDL({'format': '[filesize>?1]'}
)
505 ydl
.process_ie_result(info_dict
)
506 downloaded
= ydl
.downloaded_info_dicts
[0]
507 self
.assertEqual(downloaded
['format_id'], 'G')
509 ydl
= YDL({'format': '[filesize<1M]'}
)
510 ydl
.process_ie_result(info_dict
)
511 downloaded
= ydl
.downloaded_info_dicts
[0]
512 self
.assertEqual(downloaded
['format_id'], 'E')
514 ydl
= YDL({'format': '[filesize<1MiB]'}
)
515 ydl
.process_ie_result(info_dict
)
516 downloaded
= ydl
.downloaded_info_dicts
[0]
517 self
.assertEqual(downloaded
['format_id'], 'G')
519 ydl
= YDL({'format': 'all[width>=400][width<=600]'}
)
520 ydl
.process_ie_result(info_dict
)
521 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
522 self
.assertEqual(downloaded_ids
, ['B', 'C', 'D'])
524 ydl
= YDL({'format': 'best[height<40]'}
)
526 ydl
.process_ie_result(info_dict
)
527 except ExtractorError
:
529 self
.assertEqual(ydl
.downloaded_info_dicts
, [])
531 def test_default_format_spec(self
):
532 ydl
= YDL({'simulate': True}
)
533 self
.assertEqual(ydl
._default
_format
_spec
({}), 'bestvideo*+bestaudio/best')
536 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
538 ydl
= YDL({'simulate': True}
)
539 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'bestvideo*+bestaudio/best')
541 ydl
= YDL({'outtmpl': '-'}
)
542 self
.assertEqual(ydl
._default
_format
_spec
({}), 'best/bestvideo+bestaudio')
545 self
.assertEqual(ydl
._default
_format
_spec
({}, download
=False), 'bestvideo*+bestaudio/best')
546 self
.assertEqual(ydl
._default
_format
_spec
({'is_live': True}
), 'best/bestvideo+bestaudio')
549 class TestYoutubeDL(unittest
.TestCase
):
550 def test_subtitles(self
):
551 def s_formats(lang
, autocaption
=False):
554 'url': 'http://localhost/video.%s.%s' % (lang
, ext
),
555 '_auto': autocaption
,
556 } for ext
in ['vtt', 'srt', 'ass']]
557 subtitles
= dict((l
, s_formats(l
)) for l
in ['en', 'fr', 'es'])
558 auto_captions
= dict((l
, s_formats(l
, True)) for l
in ['it', 'pt', 'es'])
562 'url': 'http://localhost/video.mp4',
563 'subtitles': subtitles
,
564 'automatic_captions': auto_captions
,
566 'webpage_url': 'http://example.com/watch?v=shenanigans',
569 def get_info(params
={}):
570 params
.setdefault('simulate', True)
572 ydl
.report_warning
= lambda *args
, **kargs
: None
573 return ydl
.process_video_result(info_dict
, download
=False)
576 self
.assertFalse(result
.get('requested_subtitles'))
577 self
.assertEqual(result
['subtitles'], subtitles
)
578 self
.assertEqual(result
['automatic_captions'], auto_captions
)
580 result
= get_info({'writesubtitles': True}
)
581 subs
= result
['requested_subtitles']
582 self
.assertTrue(subs
)
583 self
.assertEqual(set(subs
.keys()), set(['en']))
584 self
.assertTrue(subs
['en'].get('data') is None)
585 self
.assertEqual(subs
['en']['ext'], 'ass')
587 result
= get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}
)
588 subs
= result
['requested_subtitles']
589 self
.assertEqual(subs
['en']['ext'], 'srt')
591 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}
)
592 subs
= result
['requested_subtitles']
593 self
.assertTrue(subs
)
594 self
.assertEqual(set(subs
.keys()), set(['es', 'fr']))
596 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}
)
597 subs
= result
['requested_subtitles']
598 self
.assertTrue(subs
)
599 self
.assertEqual(set(subs
.keys()), set(['es', 'fr']))
601 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}
)
602 subs
= result
['requested_subtitles']
603 self
.assertTrue(subs
)
604 self
.assertEqual(set(subs
.keys()), set(['fr']))
606 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}
)
607 subs
= result
['requested_subtitles']
608 self
.assertTrue(subs
)
609 self
.assertEqual(set(subs
.keys()), set(['en']))
611 result
= get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}
)
612 subs
= result
['requested_subtitles']
613 self
.assertTrue(subs
)
614 self
.assertEqual(set(subs
.keys()), set(['es', 'en']))
616 result
= get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
617 subs
= result
['requested_subtitles']
618 self
.assertTrue(subs
)
619 self
.assertEqual(set(subs
.keys()), set(['es', 'pt']))
620 self
.assertFalse(subs
['es']['_auto'])
621 self
.assertTrue(subs
['pt']['_auto'])
623 result
= get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}
)
624 subs
= result
['requested_subtitles']
625 self
.assertTrue(subs
)
626 self
.assertEqual(set(subs
.keys()), set(['es', 'pt']))
627 self
.assertTrue(subs
['es']['_auto'])
628 self
.assertTrue(subs
['pt']['_auto'])
630 def test_add_extra_info(self
):
636 'playlist': 'funny videos',
638 YDL
.add_extra_info(test_dict
, extra_info
)
639 self
.assertEqual(test_dict
['extractor'], 'Foo')
640 self
.assertEqual(test_dict
['playlist'], 'funny videos')
649 'title3': 'foo/bar\\test',
650 'timestamp': 1618488000,
653 '_last_playlist_index': 100,
655 'formats': [{'id': 'id1'}
, {'id': 'id2'}
, {'id': 'id3'}
]
658 def test_prepare_outtmpl_and_filename(self
):
659 def test(tmpl
, expected
, *, info
=None, **params
):
660 params
['outtmpl'] = tmpl
661 ydl
= YoutubeDL(params
)
662 ydl
._num
_downloads
= 1
663 self
.assertEqual(ydl
.validate_outtmpl(tmpl
), None)
665 outtmpl
, tmpl_dict
= ydl
.prepare_outtmpl(tmpl
, info
or self
.outtmpl_info
)
666 out
= outtmpl
% tmpl_dict
667 fname
= ydl
.prepare_filename(info
or self
.outtmpl_info
)
669 if callable(expected
):
670 self
.assertTrue(expected(out
))
671 self
.assertTrue(expected(fname
))
672 elif isinstance(expected
, compat_str
):
673 self
.assertEqual((out
, fname
), (expected
, expected
))
675 self
.assertEqual((out
, fname
), expected
)
677 # Auto-generated fields
678 test('%(id)s.%(ext)s', '1234.mp4')
679 test('%(duration_string)s', ('27:46:40', '27-46-40'))
680 test('%(epoch)d', int_or_none
)
681 test('%(resolution)s', '1080p')
682 test('%(playlist_index)s', '001')
683 test('%(autonumber)s', '00001')
684 test('%(autonumber+2)03d', '005', autonumber_start
=3)
685 test('%(autonumber)s', '001', autonumber_size
=3)
690 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
691 test('%(width)06d.%(ext)s', 'NA.mp4')
692 test('%(width)06d.%%(ext)s', 'NA.%(ext)s')
693 test('%%(width)06d.%(ext)s', '%(width)06d.mp4')
696 test('%(id)s', '_abcd', info
={'id': '_abcd'}
)
697 test('%(some_id)s', '_abcd', info
={'some_id': '_abcd'}
)
698 test('%(formats.0.id)s', '_abcd', info
={'formats': [{'id': '_abcd'}
]})
699 test('%(id)s', '-abcd', info
={'id': '-abcd'}
)
700 test('%(id)s', '.abcd', info
={'id': '.abcd'}
)
701 test('%(id)s', 'ab__cd', info
={'id': 'ab__cd'}
)
702 test('%(id)s', ('ab:cd', 'ab -cd'), info
={'id': 'ab:cd'}
)
705 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%'), ValueError))
706 self
.assertTrue(isinstance(YoutubeDL
.validate_outtmpl('%(title)'), ValueError))
707 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder
='none')
713 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s'
714 test(NA_TEST_OUTTMPL
, 'NA-NA-def-1234.mp4')
715 test(NA_TEST_OUTTMPL
, 'none-none-def-1234.mp4', outtmpl_na_placeholder
='none')
716 test(NA_TEST_OUTTMPL
, '--def-1234.mp4', outtmpl_na_placeholder
='')
719 FMT_TEST_OUTTMPL
= '%%(height)%s.%%(ext)s'
720 test(FMT_TEST_OUTTMPL
% 's', '1080.mp4')
721 test(FMT_TEST_OUTTMPL
% 'd', '1080.mp4')
722 test(FMT_TEST_OUTTMPL
% '6d', ' 1080.mp4')
723 test(FMT_TEST_OUTTMPL
% '-6d', '1080 .mp4')
724 test(FMT_TEST_OUTTMPL
% '06d', '001080.mp4')
725 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
726 test(FMT_TEST_OUTTMPL
% ' 06d', ' 01080.mp4')
727 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
728 test(FMT_TEST_OUTTMPL
% '0 6d', ' 01080.mp4')
729 test(FMT_TEST_OUTTMPL
% ' 0 6d', ' 01080.mp4')
732 test('%(id)d', '1234')
733 test('%(height)c', '1')
735 test('%(id)d %(id)r', "1234 '1234'")
736 test('%(id)r %(height)r', "'1234' 1080")
737 test('%(ext)s-%(ext|def)d', 'mp4-def')
738 test('%(width|0)04d', '0000')
739 test('a%(width|)d', 'a', outtmpl_na_placeholder
='none')
741 # Internal formatting
742 FORMATS
= self
.outtmpl_info
['formats']
743 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20')
744 test('%(id+1-height+3)05d', '00158')
745 test('%(width+100)05d', 'NA')
746 test('%(formats.0) 15s', ('% 15s' % FORMATS
[0], '% 15s' % str(FORMATS
[0]).replace(':', ' -')))
747 test('%(formats.0)r', (repr(FORMATS
[0]), repr(FORMATS
[0]).replace(':', ' -')))
748 test('%(height.0)03d', '001')
749 test('%(-height.0)04d', '-001')
750 test('%(formats.-1.id)s', FORMATS
[-1]['id'])
751 test('%(formats.0.id.-1)d', FORMATS
[0]['id'][-1])
752 test('%(formats.3)s', 'NA')
753 test('%(formats.:2:-1)r', repr(FORMATS
[:2:-1]))
754 test('%(formats.0.id.-1+id)f', '1235.000000')
755 test('%(formats.0.id.-1+formats.1.id.-1)d', '3')
758 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4')
759 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme
760 # test('%(foo|)s', ('', '_')) # fixme
762 # Path expansion and escaping
763 test('Hello %(title1)s', 'Hello $PATH')
764 test('Hello %(title2)s', 'Hello %PATH%')
765 test('%(title3)s', ('foo/bar\\test', 'foo_bar_test'))
766 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo_bar_test' % os
.path
.sep
))
768 def test_format_note(self
):
770 self
.assertEqual(ydl
._format
_note
({}), '')
771 assertRegexpMatches(self
, ydl
._format
_note
({
774 assertRegexpMatches(self
, ydl
._format
_note
({
778 def test_postprocessors(self
):
779 filename
= 'post-processor-testfile.mp4'
780 audiofile
= filename
+ '.mp3'
782 class SimplePP(PostProcessor
):
784 with open(audiofile
, 'wt') as f
:
786 return [info
['filepath']], info
788 def run_pp(params
, PP
):
789 with open(filename
, 'wt') as f
:
791 ydl
= YoutubeDL(params
)
792 ydl
.add_post_processor(PP())
793 ydl
.post_process(filename
, {'filepath': filename}
)
795 run_pp({'keepvideo': True}
, SimplePP
)
796 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
797 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
801 run_pp({'keepvideo': False}
, SimplePP
)
802 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
803 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
806 class ModifierPP(PostProcessor
):
808 with open(info
['filepath'], 'wt') as f
:
812 run_pp({'keepvideo': False}
, ModifierPP
)
813 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
816 def test_match_filter(self
):
817 class FilterYDL(YDL
):
818 def __init__(self
, *args
, **kwargs
):
819 super(FilterYDL
, self
).__init
__(*args
, **kwargs
)
820 self
.params
['simulate'] = True
822 def process_info(self
, info_dict
):
823 super(YDL
, self
).process_info(info_dict
)
825 def _match_entry(self
, info_dict
, incomplete
=False):
826 res
= super(FilterYDL
, self
)._match
_entry
(info_dict
, incomplete
)
828 self
.downloaded_info_dicts
.append(info_dict
)
837 'filesize': 10 * 1024,
839 'uploader': "變態妍字幕版 太妍 тест",
840 'creator': "тест ' 123 ' тест--",
841 'webpage_url': 'http://example.com/watch?v=shenanigans',
849 'description': 'foo',
850 'filesize': 5 * 1024,
852 'uploader': "тест 123",
853 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
855 videos
= [first
, second
]
857 def get_videos(filter_
=None):
858 ydl
= FilterYDL({'match_filter': filter_}
)
860 ydl
.process_ie_result(v
, download
=True)
861 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
864 self
.assertEqual(res
, ['1', '2'])
870 return 'Video id is not 1'
872 self
.assertEqual(res
, ['1'])
874 f
= match_filter_func('duration < 30')
876 self
.assertEqual(res
, ['2'])
878 f
= match_filter_func('description = foo')
880 self
.assertEqual(res
, ['2'])
882 f
= match_filter_func('description =? foo')
884 self
.assertEqual(res
, ['1', '2'])
886 f
= match_filter_func('filesize > 5KiB')
888 self
.assertEqual(res
, ['1'])
890 f
= match_filter_func('playlist_id = 42')
892 self
.assertEqual(res
, ['1'])
894 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
896 self
.assertEqual(res
, ['1'])
898 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
900 self
.assertEqual(res
, ['2'])
902 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
904 self
.assertEqual(res
, ['1'])
906 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
908 self
.assertEqual(res
, ['1'])
910 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
912 self
.assertEqual(res
, [])
914 def test_playlist_items_selection(self
):
917 'title': compat_str(i
),
919 } for i
in range(1, 5)]
924 'extractor': 'test:playlist',
925 'extractor_key': 'test:playlist',
926 'webpage_url': 'http://example.com',
929 def get_downloaded_info_dicts(params
):
931 # make a deep copy because the dictionary and nested entries
933 ydl
.process_ie_result(copy
.deepcopy(playlist
))
934 return ydl
.downloaded_info_dicts
937 return [int(v
['id']) for v
in get_downloaded_info_dicts(params
)]
940 self
.assertEqual(result
, [1, 2, 3, 4])
942 result
= get_ids({'playlistend': 10}
)
943 self
.assertEqual(result
, [1, 2, 3, 4])
945 result
= get_ids({'playlistend': 2}
)
946 self
.assertEqual(result
, [1, 2])
948 result
= get_ids({'playliststart': 10}
)
949 self
.assertEqual(result
, [])
951 result
= get_ids({'playliststart': 2}
)
952 self
.assertEqual(result
, [2, 3, 4])
954 result
= get_ids({'playlist_items': '2-4'}
)
955 self
.assertEqual(result
, [2, 3, 4])
957 result
= get_ids({'playlist_items': '2,4'}
)
958 self
.assertEqual(result
, [2, 4])
960 result
= get_ids({'playlist_items': '10'}
)
961 self
.assertEqual(result
, [])
963 result
= get_ids({'playlist_items': '3-10'}
)
964 self
.assertEqual(result
, [3, 4])
966 result
= get_ids({'playlist_items': '2-4,3-4,3'}
)
967 self
.assertEqual(result
, [2, 3, 4])
969 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
971 result
= get_downloaded_info_dicts({'playlist_items': '2-4,3-4,3'}
)
972 self
.assertEqual(result
[0]['playlist_index'], 2)
973 self
.assertEqual(result
[1]['playlist_index'], 3)
975 result
= get_downloaded_info_dicts({'playlist_items': '2-4,3-4,3'}
)
976 self
.assertEqual(result
[0]['playlist_index'], 2)
977 self
.assertEqual(result
[1]['playlist_index'], 3)
978 self
.assertEqual(result
[2]['playlist_index'], 4)
980 result
= get_downloaded_info_dicts({'playlist_items': '4,2'}
)
981 self
.assertEqual(result
[0]['playlist_index'], 4)
982 self
.assertEqual(result
[1]['playlist_index'], 2)
985 def test_urlopen_no_file_protocol(self
):
986 # see https://github.com/ytdl-org/youtube-dl/issues/8227
988 self
.assertRaises(compat_urllib_error
.URLError
, ydl
.urlopen
, 'file:///etc/passwd')
990 def test_do_not_override_ie_key_in_url_transparent(self
):
993 class Foo1IE(InfoExtractor
):
994 _VALID_URL
= r
'foo1:'
996 def _real_extract(self
, url
):
998 '_type': 'url_transparent',
1001 'title': 'foo1 title',
1005 class Foo2IE(InfoExtractor
):
1006 _VALID_URL
= r
'foo2:'
1008 def _real_extract(self
, url
):
1015 class Foo3IE(InfoExtractor
):
1016 _VALID_URL
= r
'foo3:'
1018 def _real_extract(self
, url
):
1019 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
1021 ydl
.add_info_extractor(Foo1IE(ydl
))
1022 ydl
.add_info_extractor(Foo2IE(ydl
))
1023 ydl
.add_info_extractor(Foo3IE(ydl
))
1024 ydl
.extract_info('foo1:')
1025 downloaded
= ydl
.downloaded_info_dicts
[0]
1026 self
.assertEqual(downloaded
['url'], TEST_URL
)
1027 self
.assertEqual(downloaded
['title'], 'foo1 title')
1028 self
.assertEqual(downloaded
['id'], 'testid')
1029 self
.assertEqual(downloaded
['extractor'], 'testex')
1030 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
1032 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
1033 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
1036 def __init__(self
, *args
, **kwargs
):
1037 super(_YDL
, self
).__init
__(*args
, **kwargs
)
1039 def trouble(self
, s
, tb
=None):
1044 'ignoreerrors': True,
1047 class VideoIE(InfoExtractor
):
1048 _VALID_URL
= r
'video:(?P<id>\d+)'
1050 def _real_extract(self
, url
):
1051 video_id
= self
._match
_id
(url
)
1053 'format_id': 'default',
1057 raise ExtractorError('foo')
1060 'format_id': 'extra',
1065 'title': 'Video %s' % video_id
,
1069 class PlaylistIE(InfoExtractor
):
1070 _VALID_URL
= r
'playlist:'
1074 video_id
= compat_str(n
)
1076 '_type': 'url_transparent',
1077 'ie_key': VideoIE
.ie_key(),
1079 'url': 'video:%s' % video_id
,
1080 'title': 'Video Transparent %s' % video_id
,
1083 def _real_extract(self
, url
):
1084 return self
.playlist_result(self
._entries
())
1086 ydl
.add_info_extractor(VideoIE(ydl
))
1087 ydl
.add_info_extractor(PlaylistIE(ydl
))
1088 info
= ydl
.extract_info('playlist:')
1089 entries
= info
['entries']
1090 self
.assertEqual(len(entries
), 3)
1091 self
.assertTrue(entries
[0] is None)
1092 self
.assertTrue(entries
[1] is None)
1093 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1094 downloaded
= ydl
.downloaded_info_dicts
[0]
1095 self
.assertEqual(entries
[2], downloaded
)
1096 self
.assertEqual(downloaded
['url'], TEST_URL
)
1097 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1098 self
.assertEqual(downloaded
['id'], '2')
1099 self
.assertEqual(downloaded
['extractor'], 'Video')
1100 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1103 if __name__
== '__main__':