3 from __future__ import unicode_literals
5 # Allow direct execution
9 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
13 from test.helper import FakeYDL, assertRegexpMatches
14 from youtube_dl import YoutubeDL
15 from youtube_dl.compat import compat_str
16 from youtube_dl.extractor import YoutubeIE
17 from youtube_dl.postprocessor.common import PostProcessor
18 from youtube_dl.utils import ExtractorError, match_filter_func
20 TEST_URL = 'http://localhost/sample.mp4'
24 def __init__(self, *args, **kwargs):
25 super(YDL, self).__init__(*args, **kwargs)
26 self.downloaded_info_dicts = []
29 def process_info(self, info_dict):
30 self.downloaded_info_dicts.append(info_dict)
32 def to_screen(self, msg):
36 def _make_result(formats, **kwargs):
40 'title': 'testttitle',
41 'extractor': 'testex',
47 class TestFormatSelection(unittest.TestCase):
48 def test_prefer_free_formats(self):
49 # Same resolution => download webm
51 ydl.params['prefer_free_formats'] = True
53 {'ext': 'webm', 'height': 460, 'url': TEST_URL},
54 {'ext': 'mp4', 'height': 460, 'url': TEST_URL},
56 info_dict = _make_result(formats)
58 yie._sort_formats(info_dict['formats'])
59 ydl.process_ie_result(info_dict)
60 downloaded = ydl.downloaded_info_dicts[0]
61 self.assertEqual(downloaded['ext'], 'webm')
63 # Different resolution => download best quality (mp4)
65 ydl.params['prefer_free_formats'] = True
67 {'ext': 'webm', 'height': 720, 'url': TEST_URL},
68 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL},
70 info_dict['formats'] = formats
72 yie._sort_formats(info_dict['formats'])
73 ydl.process_ie_result(info_dict)
74 downloaded = ydl.downloaded_info_dicts[0]
75 self.assertEqual(downloaded['ext'], 'mp4')
77 # No prefer_free_formats => prefer mp4 and flv for greater compatibility
79 ydl.params['prefer_free_formats'] = False
81 {'ext': 'webm', 'height': 720, 'url': TEST_URL},
82 {'ext': 'mp4', 'height': 720, 'url': TEST_URL},
83 {'ext': 'flv', 'height': 720, 'url': TEST_URL},
85 info_dict['formats'] = formats
87 yie._sort_formats(info_dict['formats'])
88 ydl.process_ie_result(info_dict)
89 downloaded = ydl.downloaded_info_dicts[0]
90 self.assertEqual(downloaded['ext'], 'mp4')
93 ydl.params['prefer_free_formats'] = False
95 {'ext': 'flv', 'height': 720, 'url': TEST_URL},
96 {'ext': 'webm', 'height': 720, 'url': TEST_URL},
98 info_dict['formats'] = formats
100 yie._sort_formats(info_dict['formats'])
101 ydl.process_ie_result(info_dict)
102 downloaded = ydl.downloaded_info_dicts[0]
103 self.assertEqual(downloaded['ext'], 'flv')
105 def test_format_selection(self):
107 {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL},
108 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL},
109 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL},
110 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL},
111 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL},
113 info_dict = _make_result(formats)
115 ydl = YDL({'format': '20/47'})
116 ydl.process_ie_result(info_dict.copy())
117 downloaded = ydl.downloaded_info_dicts[0]
118 self.assertEqual(downloaded['format_id'], '47')
120 ydl = YDL({'format': '20/71/worst'})
121 ydl.process_ie_result(info_dict.copy())
122 downloaded = ydl.downloaded_info_dicts[0]
123 self.assertEqual(downloaded['format_id'], '35')
126 ydl.process_ie_result(info_dict.copy())
127 downloaded = ydl.downloaded_info_dicts[0]
128 self.assertEqual(downloaded['format_id'], '2')
130 ydl = YDL({'format': 'webm/mp4'})
131 ydl.process_ie_result(info_dict.copy())
132 downloaded = ydl.downloaded_info_dicts[0]
133 self.assertEqual(downloaded['format_id'], '47')
135 ydl = YDL({'format': '3gp/40/mp4'})
136 ydl.process_ie_result(info_dict.copy())
137 downloaded = ydl.downloaded_info_dicts[0]
138 self.assertEqual(downloaded['format_id'], '35')
140 ydl = YDL({'format': 'example-with-dashes'})
141 ydl.process_ie_result(info_dict.copy())
142 downloaded = ydl.downloaded_info_dicts[0]
143 self.assertEqual(downloaded['format_id'], 'example-with-dashes')
145 def test_format_selection_audio(self):
147 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL},
148 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL},
149 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL},
150 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL},
152 info_dict = _make_result(formats)
154 ydl = YDL({'format': 'bestaudio'})
155 ydl.process_ie_result(info_dict.copy())
156 downloaded = ydl.downloaded_info_dicts[0]
157 self.assertEqual(downloaded['format_id'], 'audio-high')
159 ydl = YDL({'format': 'worstaudio'})
160 ydl.process_ie_result(info_dict.copy())
161 downloaded = ydl.downloaded_info_dicts[0]
162 self.assertEqual(downloaded['format_id'], 'audio-low')
165 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL},
166 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL},
168 info_dict = _make_result(formats)
170 ydl = YDL({'format': 'bestaudio/worstaudio/best'})
171 ydl.process_ie_result(info_dict.copy())
172 downloaded = ydl.downloaded_info_dicts[0]
173 self.assertEqual(downloaded['format_id'], 'vid-high')
175 def test_format_selection_audio_exts(self):
177 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
178 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
179 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
180 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
181 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
184 info_dict = _make_result(formats)
185 ydl = YDL({'format': 'best'})
187 ie._sort_formats(info_dict['formats'])
188 ydl.process_ie_result(copy.deepcopy(info_dict))
189 downloaded = ydl.downloaded_info_dicts[0]
190 self.assertEqual(downloaded['format_id'], 'aac-64')
192 ydl = YDL({'format': 'mp3'})
194 ie._sort_formats(info_dict['formats'])
195 ydl.process_ie_result(copy.deepcopy(info_dict))
196 downloaded = ydl.downloaded_info_dicts[0]
197 self.assertEqual(downloaded['format_id'], 'mp3-64')
199 ydl = YDL({'prefer_free_formats': True})
201 ie._sort_formats(info_dict['formats'])
202 ydl.process_ie_result(copy.deepcopy(info_dict))
203 downloaded = ydl.downloaded_info_dicts[0]
204 self.assertEqual(downloaded['format_id'], 'ogg-64')
206 def test_format_selection_video(self):
208 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL},
209 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL},
210 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL},
212 info_dict = _make_result(formats)
214 ydl = YDL({'format': 'bestvideo'})
215 ydl.process_ie_result(info_dict.copy())
216 downloaded = ydl.downloaded_info_dicts[0]
217 self.assertEqual(downloaded['format_id'], 'dash-video-high')
219 ydl = YDL({'format': 'worstvideo'})
220 ydl.process_ie_result(info_dict.copy())
221 downloaded = ydl.downloaded_info_dicts[0]
222 self.assertEqual(downloaded['format_id'], 'dash-video-low')
224 def test_youtube_format_selection(self):
226 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '36', '17', '13',
227 # Apple HTTP Live Streaming
228 '96', '95', '94', '93', '92', '132', '151',
230 '85', '84', '102', '83', '101', '82', '100',
232 '137', '248', '136', '247', '135', '246',
233 '245', '244', '134', '243', '133', '242', '160',
235 '141', '172', '140', '171', '139',
238 def format_info(f_id):
239 info = YoutubeIE._formats[f_id].copy()
240 info['format_id'] = f_id
241 info['url'] = 'url:' + f_id
243 formats_order = [format_info(f_id) for f_id in order]
245 info_dict = _make_result(list(formats_order), extractor='youtube')
246 ydl = YDL({'format': 'bestvideo+bestaudio'})
248 yie._sort_formats(info_dict['formats'])
249 ydl.process_ie_result(info_dict)
250 downloaded = ydl.downloaded_info_dicts[0]
251 self.assertEqual(downloaded['format_id'], '137+141')
252 self.assertEqual(downloaded['ext'], 'mp4')
254 info_dict = _make_result(list(formats_order), extractor='youtube')
255 ydl = YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'})
257 yie._sort_formats(info_dict['formats'])
258 ydl.process_ie_result(info_dict)
259 downloaded = ydl.downloaded_info_dicts[0]
260 self.assertEqual(downloaded['format_id'], '38')
262 info_dict = _make_result(list(formats_order), extractor='youtube')
263 ydl = YDL({'format': 'bestvideo/best,bestaudio'})
265 yie._sort_formats(info_dict['formats'])
266 ydl.process_ie_result(info_dict)
267 downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
268 self.assertEqual(downloaded_ids, ['137', '141'])
270 info_dict = _make_result(list(formats_order), extractor='youtube')
271 ydl = YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'})
273 yie._sort_formats(info_dict['formats'])
274 ydl.process_ie_result(info_dict)
275 downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
276 self.assertEqual(downloaded_ids, ['137+141', '248+141'])
278 info_dict = _make_result(list(formats_order), extractor='youtube')
279 ydl = YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'})
281 yie._sort_formats(info_dict['formats'])
282 ydl.process_ie_result(info_dict)
283 downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
284 self.assertEqual(downloaded_ids, ['136+141', '247+141'])
286 info_dict = _make_result(list(formats_order), extractor='youtube')
287 ydl = YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'})
289 yie._sort_formats(info_dict['formats'])
290 ydl.process_ie_result(info_dict)
291 downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
292 self.assertEqual(downloaded_ids, ['248+141'])
294 for f1, f2 in zip(formats_order, formats_order[1:]):
295 info_dict = _make_result([f1, f2], extractor='youtube')
296 ydl = YDL({'format': 'best/bestvideo'})
298 yie._sort_formats(info_dict['formats'])
299 ydl.process_ie_result(info_dict)
300 downloaded = ydl.downloaded_info_dicts[0]
301 self.assertEqual(downloaded['format_id'], f1['format_id'])
303 info_dict = _make_result([f2, f1], extractor='youtube')
304 ydl = YDL({'format': 'best/bestvideo'})
306 yie._sort_formats(info_dict['formats'])
307 ydl.process_ie_result(info_dict)
308 downloaded = ydl.downloaded_info_dicts[0]
309 self.assertEqual(downloaded['format_id'], f1['format_id'])
311 def test_invalid_format_specs(self):
312 def assert_syntax_error(format_spec):
313 ydl = YDL({'format': format_spec})
314 info_dict = _make_result([{'format_id': 'foo', 'url': TEST_URL}])
315 self.assertRaises(SyntaxError, ydl.process_ie_result, info_dict)
317 assert_syntax_error('bestvideo,,best')
318 assert_syntax_error('+bestaudio')
319 assert_syntax_error('bestvideo+')
320 assert_syntax_error('/')
322 def test_format_filtering(self):
324 {'format_id': 'A', 'filesize': 500, 'width': 1000},
325 {'format_id': 'B', 'filesize': 1000, 'width': 500},
326 {'format_id': 'C', 'filesize': 1000, 'width': 400},
327 {'format_id': 'D', 'filesize': 2000, 'width': 600},
328 {'format_id': 'E', 'filesize': 3000},
330 {'format_id': 'G', 'filesize': 1000000},
333 f['url'] = 'http://_/'
335 info_dict = _make_result(formats)
337 ydl = YDL({'format': 'best[filesize<3000]'})
338 ydl.process_ie_result(info_dict)
339 downloaded = ydl.downloaded_info_dicts[0]
340 self.assertEqual(downloaded['format_id'], 'D')
342 ydl = YDL({'format': 'best[filesize<=3000]'})
343 ydl.process_ie_result(info_dict)
344 downloaded = ydl.downloaded_info_dicts[0]
345 self.assertEqual(downloaded['format_id'], 'E')
347 ydl = YDL({'format': 'best[filesize <= ? 3000]'})
348 ydl.process_ie_result(info_dict)
349 downloaded = ydl.downloaded_info_dicts[0]
350 self.assertEqual(downloaded['format_id'], 'F')
352 ydl = YDL({'format': 'best [filesize = 1000] [width>450]'})
353 ydl.process_ie_result(info_dict)
354 downloaded = ydl.downloaded_info_dicts[0]
355 self.assertEqual(downloaded['format_id'], 'B')
357 ydl = YDL({'format': 'best [filesize = 1000] [width!=450]'})
358 ydl.process_ie_result(info_dict)
359 downloaded = ydl.downloaded_info_dicts[0]
360 self.assertEqual(downloaded['format_id'], 'C')
362 ydl = YDL({'format': '[filesize>?1]'})
363 ydl.process_ie_result(info_dict)
364 downloaded = ydl.downloaded_info_dicts[0]
365 self.assertEqual(downloaded['format_id'], 'G')
367 ydl = YDL({'format': '[filesize<1M]'})
368 ydl.process_ie_result(info_dict)
369 downloaded = ydl.downloaded_info_dicts[0]
370 self.assertEqual(downloaded['format_id'], 'E')
372 ydl = YDL({'format': '[filesize<1MiB]'})
373 ydl.process_ie_result(info_dict)
374 downloaded = ydl.downloaded_info_dicts[0]
375 self.assertEqual(downloaded['format_id'], 'G')
377 ydl = YDL({'format': 'all[width>=400][width<=600]'})
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, ['B', 'C', 'D'])
382 ydl = YDL({'format': 'best[height<40]'})
384 ydl.process_ie_result(info_dict)
385 except ExtractorError:
387 self.assertEqual(ydl.downloaded_info_dicts, [])
390 class TestYoutubeDL(unittest.TestCase):
391 def test_subtitles(self):
392 def s_formats(lang, autocaption=False):
395 'url': 'http://localhost/video.%s.%s' % (lang, ext),
396 '_auto': autocaption,
397 } for ext in ['vtt', 'srt', 'ass']]
398 subtitles = dict((l, s_formats(l)) for l in ['en', 'fr', 'es'])
399 auto_captions = dict((l, s_formats(l, True)) for l in ['it', 'pt', 'es'])
403 'url': 'http://localhost/video.mp4',
404 'subtitles': subtitles,
405 'automatic_captions': auto_captions,
409 def get_info(params={}):
410 params.setdefault('simulate', True)
412 ydl.report_warning = lambda *args, **kargs: None
413 return ydl.process_video_result(info_dict, download=False)
416 self.assertFalse(result.get('requested_subtitles'))
417 self.assertEqual(result['subtitles'], subtitles)
418 self.assertEqual(result['automatic_captions'], auto_captions)
420 result = get_info({'writesubtitles': True})
421 subs = result['requested_subtitles']
422 self.assertTrue(subs)
423 self.assertEqual(set(subs.keys()), set(['en']))
424 self.assertTrue(subs['en'].get('data') is None)
425 self.assertEqual(subs['en']['ext'], 'ass')
427 result = get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'})
428 subs = result['requested_subtitles']
429 self.assertEqual(subs['en']['ext'], 'srt')
431 result = get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']})
432 subs = result['requested_subtitles']
433 self.assertTrue(subs)
434 self.assertEqual(set(subs.keys()), set(['es', 'fr']))
436 result = get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
437 subs = result['requested_subtitles']
438 self.assertTrue(subs)
439 self.assertEqual(set(subs.keys()), set(['es', 'pt']))
440 self.assertFalse(subs['es']['_auto'])
441 self.assertTrue(subs['pt']['_auto'])
443 result = get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
444 subs = result['requested_subtitles']
445 self.assertTrue(subs)
446 self.assertEqual(set(subs.keys()), set(['es', 'pt']))
447 self.assertTrue(subs['es']['_auto'])
448 self.assertTrue(subs['pt']['_auto'])
450 def test_add_extra_info(self):
456 'playlist': 'funny videos',
458 YDL.add_extra_info(test_dict, extra_info)
459 self.assertEqual(test_dict['extractor'], 'Foo')
460 self.assertEqual(test_dict['playlist'], 'funny videos')
462 def test_prepare_filename(self):
470 ydl = YoutubeDL({'outtmpl': templ})
471 return ydl.prepare_filename(info)
472 self.assertEqual(fname('%(id)s.%(ext)s'), '1234.mp4')
473 self.assertEqual(fname('%(id)s-%(width)s.%(ext)s'), '1234-NA.mp4')
474 # Replace missing fields with 'NA'
475 self.assertEqual(fname('%(uploader_date)s-%(id)s.%(ext)s'), 'NA-1234.mp4')
477 def test_format_note(self):
479 self.assertEqual(ydl._format_note({}), '')
480 assertRegexpMatches(self, ydl._format_note({
484 def test_postprocessors(self):
485 filename = 'post-processor-testfile.mp4'
486 audiofile = filename + '.mp3'
488 class SimplePP(PostProcessor):
490 with open(audiofile, 'wt') as f:
492 return [info['filepath']], info
494 def run_pp(params, PP):
495 with open(filename, 'wt') as f:
497 ydl = YoutubeDL(params)
498 ydl.add_post_processor(PP())
499 ydl.post_process(filename, {'filepath': filename})
501 run_pp({'keepvideo': True}, SimplePP)
502 self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
503 self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
507 run_pp({'keepvideo': False}, SimplePP)
508 self.assertFalse(os.path.exists(filename), '%s exists' % filename)
509 self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
512 class ModifierPP(PostProcessor):
514 with open(info['filepath'], 'wt') as f:
518 run_pp({'keepvideo': False}, ModifierPP)
519 self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
522 def test_match_filter(self):
523 class FilterYDL(YDL):
524 def __init__(self, *args, **kwargs):
525 super(FilterYDL, self).__init__(*args, **kwargs)
526 self.params['simulate'] = True
528 def process_info(self, info_dict):
529 super(YDL, self).process_info(info_dict)
531 def _match_entry(self, info_dict, incomplete):
532 res = super(FilterYDL, self)._match_entry(info_dict, incomplete)
534 self.downloaded_info_dicts.append(info_dict)
543 'filesize': 10 * 1024,
551 'description': 'foo',
552 'filesize': 5 * 1024,
554 videos = [first, second]
556 def get_videos(filter_=None):
557 ydl = FilterYDL({'match_filter': filter_})
559 ydl.process_ie_result(v, download=True)
560 return [v['id'] for v in ydl.downloaded_info_dicts]
563 self.assertEqual(res, ['1', '2'])
569 return 'Video id is not 1'
571 self.assertEqual(res, ['1'])
573 f = match_filter_func('duration < 30')
575 self.assertEqual(res, ['2'])
577 f = match_filter_func('description = foo')
579 self.assertEqual(res, ['2'])
581 f = match_filter_func('description =? foo')
583 self.assertEqual(res, ['1', '2'])
585 f = match_filter_func('filesize > 5KiB')
587 self.assertEqual(res, ['1'])
589 def test_playlist_items_selection(self):
592 'title': compat_str(i),
594 } for i in range(1, 5)]
599 'extractor': 'test:playlist',
600 'extractor_key': 'test:playlist',
601 'webpage_url': 'http://example.com',
606 # make a copy because the dictionary can be modified
607 ydl.process_ie_result(playlist.copy())
608 return [int(v['id']) for v in ydl.downloaded_info_dicts]
611 self.assertEqual(result, [1, 2, 3, 4])
613 result = get_ids({'playlistend': 10})
614 self.assertEqual(result, [1, 2, 3, 4])
616 result = get_ids({'playlistend': 2})
617 self.assertEqual(result, [1, 2])
619 result = get_ids({'playliststart': 10})
620 self.assertEqual(result, [])
622 result = get_ids({'playliststart': 2})
623 self.assertEqual(result, [2, 3, 4])
625 result = get_ids({'playlist_items': '2-4'})
626 self.assertEqual(result, [2, 3, 4])
628 result = get_ids({'playlist_items': '2,4'})
629 self.assertEqual(result, [2, 4])
631 result = get_ids({'playlist_items': '10'})
632 self.assertEqual(result, [])
635 if __name__ == '__main__':