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
, 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 self
.downloaded_info_dicts
.append(info_dict
)
34 def to_screen(self
, msg
):
38 def _make_result(formats
, **kwargs
):
42 'title': 'testttitle',
43 'extractor': 'testex',
44 'extractor_key': 'TestEx',
45 'webpage_url': 'http://example.com/watch?v=shenanigans',
51 class TestFormatSelection(unittest
.TestCase
):
52 def test_prefer_free_formats(self
):
53 # Same resolution => download webm
55 ydl
.params
['prefer_free_formats'] = True
57 {'ext': 'webm', 'height': 460, 'url': TEST_URL}
,
58 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}
,
60 info_dict
= _make_result(formats
)
62 yie
._sort
_formats
(info_dict
['formats'])
63 ydl
.process_ie_result(info_dict
)
64 downloaded
= ydl
.downloaded_info_dicts
[0]
65 self
.assertEqual(downloaded
['ext'], 'webm')
67 # Different resolution => download best quality (mp4)
69 ydl
.params
['prefer_free_formats'] = True
71 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
72 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}
,
74 info_dict
['formats'] = formats
76 yie
._sort
_formats
(info_dict
['formats'])
77 ydl
.process_ie_result(info_dict
)
78 downloaded
= ydl
.downloaded_info_dicts
[0]
79 self
.assertEqual(downloaded
['ext'], 'mp4')
81 # No prefer_free_formats => prefer mp4 and webm
83 ydl
.params
['prefer_free_formats'] = False
85 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
86 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}
,
87 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
89 info_dict
['formats'] = formats
91 yie
._sort
_formats
(info_dict
['formats'])
92 ydl
.process_ie_result(info_dict
)
93 downloaded
= ydl
.downloaded_info_dicts
[0]
94 self
.assertEqual(downloaded
['ext'], 'mp4')
97 ydl
.params
['prefer_free_formats'] = False
99 {'ext': 'flv', 'height': 720, 'url': TEST_URL}
,
100 {'ext': 'webm', 'height': 720, 'url': TEST_URL}
,
102 info_dict
['formats'] = formats
104 yie
._sort
_formats
(info_dict
['formats'])
105 ydl
.process_ie_result(info_dict
)
106 downloaded
= ydl
.downloaded_info_dicts
[0]
107 self
.assertEqual(downloaded
['ext'], 'webm')
109 def test_format_selection(self
):
111 {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
112 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL}
,
113 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL}
,
114 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL}
,
115 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL}
,
117 info_dict
= _make_result(formats
)
119 ydl
= YDL({'format': '20/47'}
)
120 ydl
.process_ie_result(info_dict
.copy())
121 downloaded
= ydl
.downloaded_info_dicts
[0]
122 self
.assertEqual(downloaded
['format_id'], '47')
124 ydl
= YDL({'format': '20/71/worst'}
)
125 ydl
.process_ie_result(info_dict
.copy())
126 downloaded
= ydl
.downloaded_info_dicts
[0]
127 self
.assertEqual(downloaded
['format_id'], '35')
130 ydl
.process_ie_result(info_dict
.copy())
131 downloaded
= ydl
.downloaded_info_dicts
[0]
132 self
.assertEqual(downloaded
['format_id'], '2')
134 ydl
= YDL({'format': 'webm/mp4'}
)
135 ydl
.process_ie_result(info_dict
.copy())
136 downloaded
= ydl
.downloaded_info_dicts
[0]
137 self
.assertEqual(downloaded
['format_id'], '47')
139 ydl
= YDL({'format': '3gp/40/mp4'}
)
140 ydl
.process_ie_result(info_dict
.copy())
141 downloaded
= ydl
.downloaded_info_dicts
[0]
142 self
.assertEqual(downloaded
['format_id'], '35')
144 ydl
= YDL({'format': 'example-with-dashes'}
)
145 ydl
.process_ie_result(info_dict
.copy())
146 downloaded
= ydl
.downloaded_info_dicts
[0]
147 self
.assertEqual(downloaded
['format_id'], 'example-with-dashes')
149 def test_format_selection_audio(self
):
151 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
152 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
153 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}
,
154 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL}
,
156 info_dict
= _make_result(formats
)
158 ydl
= YDL({'format': 'bestaudio'}
)
159 ydl
.process_ie_result(info_dict
.copy())
160 downloaded
= ydl
.downloaded_info_dicts
[0]
161 self
.assertEqual(downloaded
['format_id'], 'audio-high')
163 ydl
= YDL({'format': 'worstaudio'}
)
164 ydl
.process_ie_result(info_dict
.copy())
165 downloaded
= ydl
.downloaded_info_dicts
[0]
166 self
.assertEqual(downloaded
['format_id'], 'audio-low')
169 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}
,
170 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL}
,
172 info_dict
= _make_result(formats
)
174 ydl
= YDL({'format': 'bestaudio/worstaudio/best'}
)
175 ydl
.process_ie_result(info_dict
.copy())
176 downloaded
= ydl
.downloaded_info_dicts
[0]
177 self
.assertEqual(downloaded
['format_id'], 'vid-high')
179 def test_format_selection_audio_exts(self
):
181 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
182 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
183 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}
,
184 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
185 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}
,
188 info_dict
= _make_result(formats
)
189 ydl
= YDL({'format': 'best'}
)
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'], 'aac-64')
196 ydl
= YDL({'format': 'mp3'}
)
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'], 'mp3-64')
203 ydl
= YDL({'prefer_free_formats': True}
)
205 ie
._sort
_formats
(info_dict
['formats'])
206 ydl
.process_ie_result(copy
.deepcopy(info_dict
))
207 downloaded
= ydl
.downloaded_info_dicts
[0]
208 self
.assertEqual(downloaded
['format_id'], 'ogg-64')
210 def test_format_selection_video(self
):
212 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}
,
213 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}
,
214 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}
,
216 info_dict
= _make_result(formats
)
218 ydl
= YDL({'format': 'bestvideo'}
)
219 ydl
.process_ie_result(info_dict
.copy())
220 downloaded
= ydl
.downloaded_info_dicts
[0]
221 self
.assertEqual(downloaded
['format_id'], 'dash-video-high')
223 ydl
= YDL({'format': 'worstvideo'}
)
224 ydl
.process_ie_result(info_dict
.copy())
225 downloaded
= ydl
.downloaded_info_dicts
[0]
226 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
228 ydl
= YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}
)
229 ydl
.process_ie_result(info_dict
.copy())
230 downloaded
= ydl
.downloaded_info_dicts
[0]
231 self
.assertEqual(downloaded
['format_id'], 'dash-video-low')
234 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}
,
236 info_dict
= _make_result(formats
)
238 ydl
= YDL({'format': 'bestvideo[vcodec=avc1.123456]'}
)
239 ydl
.process_ie_result(info_dict
.copy())
240 downloaded
= ydl
.downloaded_info_dicts
[0]
241 self
.assertEqual(downloaded
['format_id'], 'vid-vcodec-dot')
243 def test_format_selection_string_ops(self
):
245 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}
,
246 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}
,
248 info_dict
= _make_result(formats
)
251 ydl
= YDL({'format': '[format_id=abc-cba]'}
)
252 ydl
.process_ie_result(info_dict
.copy())
253 downloaded
= ydl
.downloaded_info_dicts
[0]
254 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
256 # does not equal (!=)
257 ydl
= YDL({'format': '[format_id!=abc-cba]'}
)
258 ydl
.process_ie_result(info_dict
.copy())
259 downloaded
= ydl
.downloaded_info_dicts
[0]
260 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
262 ydl
= YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}
)
263 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
266 ydl
= YDL({'format': '[format_id^=abc]'}
)
267 ydl
.process_ie_result(info_dict
.copy())
268 downloaded
= ydl
.downloaded_info_dicts
[0]
269 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
271 # does not start with (!^=)
272 ydl
= YDL({'format': '[format_id!^=abc]'}
)
273 ydl
.process_ie_result(info_dict
.copy())
274 downloaded
= ydl
.downloaded_info_dicts
[0]
275 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
277 ydl
= YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}
)
278 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
281 ydl
= YDL({'format': '[format_id$=cba]'}
)
282 ydl
.process_ie_result(info_dict
.copy())
283 downloaded
= ydl
.downloaded_info_dicts
[0]
284 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
286 # does not end with (!$=)
287 ydl
= YDL({'format': '[format_id!$=cba]'}
)
288 ydl
.process_ie_result(info_dict
.copy())
289 downloaded
= ydl
.downloaded_info_dicts
[0]
290 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
292 ydl
= YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}
)
293 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
296 ydl
= YDL({'format': '[format_id*=bc-cb]'}
)
297 ydl
.process_ie_result(info_dict
.copy())
298 downloaded
= ydl
.downloaded_info_dicts
[0]
299 self
.assertEqual(downloaded
['format_id'], 'abc-cba')
301 # does not contain (!*=)
302 ydl
= YDL({'format': '[format_id!*=bc-cb]'}
)
303 ydl
.process_ie_result(info_dict
.copy())
304 downloaded
= ydl
.downloaded_info_dicts
[0]
305 self
.assertEqual(downloaded
['format_id'], 'zxc-cxz')
307 ydl
= YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}
)
308 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
310 ydl
= YDL({'format': '[format_id!*=-]'}
)
311 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
313 def test_youtube_format_selection(self
):
314 # FIXME: Rewrite in accordance with the new format sorting options
318 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
319 # Apple HTTP Live Streaming
320 '96', '95', '94', '93', '92', '132', '151',
322 '85', '84', '102', '83', '101', '82', '100',
324 '137', '248', '136', '247', '135', '246',
325 '245', '244', '134', '243', '133', '242', '160',
327 '141', '172', '140', '171', '139',
330 def format_info(f_id
):
331 info
= YoutubeIE
._formats
[f_id
].copy()
333 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
334 # and 'vcodec', while in tests such information is incomplete since
335 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
336 # test_YoutubeDL.test_youtube_format_selection is broken without
338 if 'acodec' in info
and 'vcodec' not in info
:
339 info
['vcodec'] = 'none'
340 elif 'vcodec' in info
and 'acodec' not in info
:
341 info
['acodec'] = 'none'
343 info
['format_id'] = f_id
344 info
['url'] = 'url:' + f_id
346 formats_order
= [format_info(f_id
) for f_id
in order
]
348 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
349 ydl
= YDL({'format': 'bestvideo+bestaudio'}
)
351 yie
._sort
_formats
(info_dict
['formats'])
352 ydl
.process_ie_result(info_dict
)
353 downloaded
= ydl
.downloaded_info_dicts
[0]
354 self
.assertEqual(downloaded
['format_id'], '248+172')
355 self
.assertEqual(downloaded
['ext'], 'mp4')
357 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
358 ydl
= YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}
)
360 yie
._sort
_formats
(info_dict
['formats'])
361 ydl
.process_ie_result(info_dict
)
362 downloaded
= ydl
.downloaded_info_dicts
[0]
363 self
.assertEqual(downloaded
['format_id'], '38')
365 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
366 ydl
= YDL({'format': 'bestvideo/best,bestaudio'}
)
368 yie
._sort
_formats
(info_dict
['formats'])
369 ydl
.process_ie_result(info_dict
)
370 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
371 self
.assertEqual(downloaded_ids
, ['137', '141'])
373 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
374 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'}
)
376 yie
._sort
_formats
(info_dict
['formats'])
377 ydl
.process_ie_result(info_dict
)
378 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
379 self
.assertEqual(downloaded_ids
, ['137+141', '248+141'])
381 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
382 ydl
= YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'}
)
384 yie
._sort
_formats
(info_dict
['formats'])
385 ydl
.process_ie_result(info_dict
)
386 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
387 self
.assertEqual(downloaded_ids
, ['136+141', '247+141'])
389 info_dict
= _make_result(list(formats_order
), extractor
='youtube')
390 ydl
= YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}
)
392 yie
._sort
_formats
(info_dict
['formats'])
393 ydl
.process_ie_result(info_dict
)
394 downloaded_ids
= [info
['format_id'] for info
in ydl
.downloaded_info_dicts
]
395 self
.assertEqual(downloaded_ids
, ['248+141'])
397 for f1
, f2
in zip(formats_order
, formats_order
[1:]):
398 info_dict
= _make_result([f1
, f2
], extractor
='youtube')
399 ydl
= YDL({'format': 'best/bestvideo'}
)
401 yie
._sort
_formats
(info_dict
['formats'])
402 ydl
.process_ie_result(info_dict
)
403 downloaded
= ydl
.downloaded_info_dicts
[0]
404 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
406 info_dict
= _make_result([f2
, f1
], extractor
='youtube')
407 ydl
= YDL({'format': 'best/bestvideo'}
)
409 yie
._sort
_formats
(info_dict
['formats'])
410 ydl
.process_ie_result(info_dict
)
411 downloaded
= ydl
.downloaded_info_dicts
[0]
412 self
.assertEqual(downloaded
['format_id'], f1
['format_id'])
414 def test_audio_only_extractor_format_selection(self
):
415 # For extractors with incomplete formats (all formats are audio-only or
416 # video-only) best and worst should fallback to corresponding best/worst
417 # video-only or audio-only formats (as per
418 # https://github.com/ytdl-org/youtube-dl/pull/5556)
420 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}
,
421 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}
,
423 info_dict
= _make_result(formats
)
425 ydl
= YDL({'format': 'best'}
)
426 ydl
.process_ie_result(info_dict
.copy())
427 downloaded
= ydl
.downloaded_info_dicts
[0]
428 self
.assertEqual(downloaded
['format_id'], 'high')
430 ydl
= YDL({'format': 'worst'}
)
431 ydl
.process_ie_result(info_dict
.copy())
432 downloaded
= ydl
.downloaded_info_dicts
[0]
433 self
.assertEqual(downloaded
['format_id'], 'low')
435 def test_format_not_available(self
):
437 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}
,
438 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
440 info_dict
= _make_result(formats
)
442 # This must fail since complete video-audio format does not match filter
443 # and extractor does not provide incomplete only formats (i.e. only
444 # video-only or audio-only).
445 ydl
= YDL({'format': 'best[height>360]'}
)
446 self
.assertRaises(ExtractorError
, ydl
.process_ie_result
, info_dict
.copy())
448 def test_format_selection_issue_10083(self
):
449 # See https://github.com/ytdl-org/youtube-dl/issues/10083
451 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}
,
452 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}
,
453 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}
,
455 info_dict
= _make_result(formats
)
457 ydl
= YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}
)
458 ydl
.process_ie_result(info_dict
.copy())
459 self
.assertEqual(ydl
.downloaded_info_dicts
[0]['format_id'], 'video+audio')
461 def test_invalid_format_specs(self
):
462 def assert_syntax_error(format_spec
):
463 ydl
= YDL({'format': format_spec}
)
464 info_dict
= _make_result([{'format_id': 'foo', 'url': TEST_URL}
])
465 self
.assertRaises(SyntaxError, ydl
.process_ie_result
, info_dict
)
467 assert_syntax_error('bestvideo,,best')
468 assert_syntax_error('+bestaudio')
469 assert_syntax_error('bestvideo+')
470 assert_syntax_error('/')
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')
650 def test_prepare_filename(self
):
660 def fname(templ
, na_placeholder
='NA'):
661 params
= {'outtmpl': templ}
662 if na_placeholder
!= 'NA':
663 params
['outtmpl_na_placeholder'] = na_placeholder
664 ydl
= YoutubeDL(params
)
665 return ydl
.prepare_filename(info
)
666 self
.assertEqual(fname('%(id)s.%(ext)s'), '1234.mp4')
667 self
.assertEqual(fname('%(id)s-%(width)s.%(ext)s'), '1234-NA.mp4')
668 NA_TEST_OUTTMPL
= '%(uploader_date)s-%(width)d-%(id)s.%(ext)s'
669 # Replace missing fields with 'NA' by default
670 self
.assertEqual(fname(NA_TEST_OUTTMPL
), 'NA-NA-1234.mp4')
671 # Or by provided placeholder
672 self
.assertEqual(fname(NA_TEST_OUTTMPL
, na_placeholder
='none'), 'none-none-1234.mp4')
673 self
.assertEqual(fname(NA_TEST_OUTTMPL
, na_placeholder
=''), '--1234.mp4')
674 self
.assertEqual(fname('%(height)d.%(ext)s'), '1080.mp4')
675 self
.assertEqual(fname('%(height)6d.%(ext)s'), ' 1080.mp4')
676 self
.assertEqual(fname('%(height)-6d.%(ext)s'), '1080 .mp4')
677 self
.assertEqual(fname('%(height)06d.%(ext)s'), '001080.mp4')
678 self
.assertEqual(fname('%(height) 06d.%(ext)s'), ' 01080.mp4')
679 self
.assertEqual(fname('%(height) 06d.%(ext)s'), ' 01080.mp4')
680 self
.assertEqual(fname('%(height)0 6d.%(ext)s'), ' 01080.mp4')
681 self
.assertEqual(fname('%(height)0 6d.%(ext)s'), ' 01080.mp4')
682 self
.assertEqual(fname('%(height) 0 6d.%(ext)s'), ' 01080.mp4')
683 self
.assertEqual(fname('%%'), '%')
684 self
.assertEqual(fname('%%%%'), '%%')
685 self
.assertEqual(fname('%%(height)06d.%(ext)s'), '%(height)06d.mp4')
686 self
.assertEqual(fname('%(width)06d.%(ext)s'), 'NA.mp4')
687 self
.assertEqual(fname('%(width)06d.%%(ext)s'), 'NA.%(ext)s')
688 self
.assertEqual(fname('%%(width)06d.%(ext)s'), '%(width)06d.mp4')
689 self
.assertEqual(fname('Hello %(title1)s'), 'Hello $PATH')
690 self
.assertEqual(fname('Hello %(title2)s'), 'Hello %PATH%')
692 def test_format_note(self
):
694 self
.assertEqual(ydl
._format
_note
({}), '')
695 assertRegexpMatches(self
, ydl
._format
_note
({
698 assertRegexpMatches(self
, ydl
._format
_note
({
702 def test_postprocessors(self
):
703 filename
= 'post-processor-testfile.mp4'
704 audiofile
= filename
+ '.mp3'
706 class SimplePP(PostProcessor
):
708 with open(audiofile
, 'wt') as f
:
710 return [info
['filepath']], info
712 def run_pp(params
, PP
):
713 with open(filename
, 'wt') as f
:
715 ydl
= YoutubeDL(params
)
716 ydl
.add_post_processor(PP())
717 ydl
.post_process(filename
, {'filepath': filename}
)
719 run_pp({'keepvideo': True}
, SimplePP
)
720 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
721 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
725 run_pp({'keepvideo': False}
, SimplePP
)
726 self
.assertFalse(os
.path
.exists(filename
), '%s exists' % filename
)
727 self
.assertTrue(os
.path
.exists(audiofile
), '%s doesn\'t exist' % audiofile
)
730 class ModifierPP(PostProcessor
):
732 with open(info
['filepath'], 'wt') as f
:
736 run_pp({'keepvideo': False}
, ModifierPP
)
737 self
.assertTrue(os
.path
.exists(filename
), '%s doesn\'t exist' % filename
)
740 def test_match_filter(self
):
741 class FilterYDL(YDL
):
742 def __init__(self
, *args
, **kwargs
):
743 super(FilterYDL
, self
).__init
__(*args
, **kwargs
)
744 self
.params
['simulate'] = True
746 def process_info(self
, info_dict
):
747 super(YDL
, self
).process_info(info_dict
)
749 def _match_entry(self
, info_dict
, incomplete
):
750 res
= super(FilterYDL
, self
)._match
_entry
(info_dict
, incomplete
)
752 self
.downloaded_info_dicts
.append(info_dict
)
761 'filesize': 10 * 1024,
763 'uploader': "變態妍字幕版 太妍 тест",
764 'creator': "тест ' 123 ' тест--",
765 'webpage_url': 'http://example.com/watch?v=shenanigans',
773 'description': 'foo',
774 'filesize': 5 * 1024,
776 'uploader': "тест 123",
777 'webpage_url': 'http://example.com/watch?v=SHENANIGANS',
779 videos
= [first
, second
]
781 def get_videos(filter_
=None):
782 ydl
= FilterYDL({'match_filter': filter_}
)
784 ydl
.process_ie_result(v
, download
=True)
785 return [v
['id'] for v
in ydl
.downloaded_info_dicts
]
788 self
.assertEqual(res
, ['1', '2'])
794 return 'Video id is not 1'
796 self
.assertEqual(res
, ['1'])
798 f
= match_filter_func('duration < 30')
800 self
.assertEqual(res
, ['2'])
802 f
= match_filter_func('description = foo')
804 self
.assertEqual(res
, ['2'])
806 f
= match_filter_func('description =? foo')
808 self
.assertEqual(res
, ['1', '2'])
810 f
= match_filter_func('filesize > 5KiB')
812 self
.assertEqual(res
, ['1'])
814 f
= match_filter_func('playlist_id = 42')
816 self
.assertEqual(res
, ['1'])
818 f
= match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
820 self
.assertEqual(res
, ['1'])
822 f
= match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
824 self
.assertEqual(res
, ['2'])
826 f
= match_filter_func('creator = "тест \' 123 \' тест--"')
828 self
.assertEqual(res
, ['1'])
830 f
= match_filter_func("creator = 'тест \\' 123 \\' тест--'")
832 self
.assertEqual(res
, ['1'])
834 f
= match_filter_func(r
"creator = 'тест \' 123 \' тест--' & duration > 30")
836 self
.assertEqual(res
, [])
838 def test_playlist_items_selection(self
):
841 'title': compat_str(i
),
843 } for i
in range(1, 5)]
848 'extractor': 'test:playlist',
849 'extractor_key': 'test:playlist',
850 'webpage_url': 'http://example.com',
853 def get_downloaded_info_dicts(params
):
855 # make a deep copy because the dictionary and nested entries
857 ydl
.process_ie_result(copy
.deepcopy(playlist
))
858 return ydl
.downloaded_info_dicts
861 return [int(v
['id']) for v
in get_downloaded_info_dicts(params
)]
864 self
.assertEqual(result
, [1, 2, 3, 4])
866 result
= get_ids({'playlistend': 10}
)
867 self
.assertEqual(result
, [1, 2, 3, 4])
869 result
= get_ids({'playlistend': 2}
)
870 self
.assertEqual(result
, [1, 2])
872 result
= get_ids({'playliststart': 10}
)
873 self
.assertEqual(result
, [])
875 result
= get_ids({'playliststart': 2}
)
876 self
.assertEqual(result
, [2, 3, 4])
878 result
= get_ids({'playlist_items': '2-4'}
)
879 self
.assertEqual(result
, [2, 3, 4])
881 result
= get_ids({'playlist_items': '2,4'}
)
882 self
.assertEqual(result
, [2, 4])
884 result
= get_ids({'playlist_items': '10'}
)
885 self
.assertEqual(result
, [])
887 result
= get_ids({'playlist_items': '3-10'}
)
888 self
.assertEqual(result
, [3, 4])
890 result
= get_ids({'playlist_items': '2-4,3-4,3'}
)
891 self
.assertEqual(result
, [2, 3, 4])
893 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591
895 result
= get_downloaded_info_dicts({'playlist_items': '2-4,3-4,3'}
)
896 self
.assertEqual(result
[0]['playlist_index'], 2)
897 self
.assertEqual(result
[1]['playlist_index'], 3)
899 result
= get_downloaded_info_dicts({'playlist_items': '2-4,3-4,3'}
)
900 self
.assertEqual(result
[0]['playlist_index'], 2)
901 self
.assertEqual(result
[1]['playlist_index'], 3)
902 self
.assertEqual(result
[2]['playlist_index'], 4)
904 result
= get_downloaded_info_dicts({'playlist_items': '4,2'}
)
905 self
.assertEqual(result
[0]['playlist_index'], 4)
906 self
.assertEqual(result
[1]['playlist_index'], 2)
909 def test_urlopen_no_file_protocol(self
):
910 # see https://github.com/ytdl-org/youtube-dl/issues/8227
912 self
.assertRaises(compat_urllib_error
.URLError
, ydl
.urlopen
, 'file:///etc/passwd')
914 def test_do_not_override_ie_key_in_url_transparent(self
):
917 class Foo1IE(InfoExtractor
):
918 _VALID_URL
= r
'foo1:'
920 def _real_extract(self
, url
):
922 '_type': 'url_transparent',
925 'title': 'foo1 title',
929 class Foo2IE(InfoExtractor
):
930 _VALID_URL
= r
'foo2:'
932 def _real_extract(self
, url
):
939 class Foo3IE(InfoExtractor
):
940 _VALID_URL
= r
'foo3:'
942 def _real_extract(self
, url
):
943 return _make_result([{'url': TEST_URL}
], title
='foo3 title')
945 ydl
.add_info_extractor(Foo1IE(ydl
))
946 ydl
.add_info_extractor(Foo2IE(ydl
))
947 ydl
.add_info_extractor(Foo3IE(ydl
))
948 ydl
.extract_info('foo1:')
949 downloaded
= ydl
.downloaded_info_dicts
[0]
950 self
.assertEqual(downloaded
['url'], TEST_URL
)
951 self
.assertEqual(downloaded
['title'], 'foo1 title')
952 self
.assertEqual(downloaded
['id'], 'testid')
953 self
.assertEqual(downloaded
['extractor'], 'testex')
954 self
.assertEqual(downloaded
['extractor_key'], 'TestEx')
956 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064
957 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self
):
960 def __init__(self
, *args
, **kwargs
):
961 super(_YDL
, self
).__init
__(*args
, **kwargs
)
963 def trouble(self
, s
, tb
=None):
968 'ignoreerrors': True,
971 class VideoIE(InfoExtractor
):
972 _VALID_URL
= r
'video:(?P<id>\d+)'
974 def _real_extract(self
, url
):
975 video_id
= self
._match
_id
(url
)
977 'format_id': 'default',
981 raise ExtractorError('foo')
984 'format_id': 'extra',
989 'title': 'Video %s' % video_id
,
993 class PlaylistIE(InfoExtractor
):
994 _VALID_URL
= r
'playlist:'
998 video_id
= compat_str(n
)
1000 '_type': 'url_transparent',
1001 'ie_key': VideoIE
.ie_key(),
1003 'url': 'video:%s' % video_id
,
1004 'title': 'Video Transparent %s' % video_id
,
1007 def _real_extract(self
, url
):
1008 return self
.playlist_result(self
._entries
())
1010 ydl
.add_info_extractor(VideoIE(ydl
))
1011 ydl
.add_info_extractor(PlaylistIE(ydl
))
1012 info
= ydl
.extract_info('playlist:')
1013 entries
= info
['entries']
1014 self
.assertEqual(len(entries
), 3)
1015 self
.assertTrue(entries
[0] is None)
1016 self
.assertTrue(entries
[1] is None)
1017 self
.assertEqual(len(ydl
.downloaded_info_dicts
), 1)
1018 downloaded
= ydl
.downloaded_info_dicts
[0]
1019 self
.assertEqual(entries
[2], downloaded
)
1020 self
.assertEqual(downloaded
['url'], TEST_URL
)
1021 self
.assertEqual(downloaded
['title'], 'Video Transparent 2')
1022 self
.assertEqual(downloaded
['id'], '2')
1023 self
.assertEqual(downloaded
['extractor'], 'Video')
1024 self
.assertEqual(downloaded
['extractor_key'], 'Video')
1027 if __name__
== '__main__':