You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

558 lines
20 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 years ago
  1. #!/usr/bin/env python
  2. from __future__ import unicode_literals
  3. # Allow direct execution
  4. import os
  5. import sys
  6. import unittest
  7. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  8. import copy
  9. from test.helper import FakeYDL, assertRegexpMatches
  10. from youtube_dl import YoutubeDL
  11. from youtube_dl.compat import compat_str
  12. from youtube_dl.extractor import YoutubeIE
  13. from youtube_dl.postprocessor.common import PostProcessor
  14. from youtube_dl.utils import match_filter_func
  15. TEST_URL = 'http://localhost/sample.mp4'
  16. class YDL(FakeYDL):
  17. def __init__(self, *args, **kwargs):
  18. super(YDL, self).__init__(*args, **kwargs)
  19. self.downloaded_info_dicts = []
  20. self.msgs = []
  21. def process_info(self, info_dict):
  22. self.downloaded_info_dicts.append(info_dict)
  23. def to_screen(self, msg):
  24. self.msgs.append(msg)
  25. def _make_result(formats, **kwargs):
  26. res = {
  27. 'formats': formats,
  28. 'id': 'testid',
  29. 'title': 'testttitle',
  30. 'extractor': 'testex',
  31. }
  32. res.update(**kwargs)
  33. return res
  34. class TestFormatSelection(unittest.TestCase):
  35. def test_prefer_free_formats(self):
  36. # Same resolution => download webm
  37. ydl = YDL()
  38. ydl.params['prefer_free_formats'] = True
  39. formats = [
  40. {'ext': 'webm', 'height': 460, 'url': TEST_URL},
  41. {'ext': 'mp4', 'height': 460, 'url': TEST_URL},
  42. ]
  43. info_dict = _make_result(formats)
  44. yie = YoutubeIE(ydl)
  45. yie._sort_formats(info_dict['formats'])
  46. ydl.process_ie_result(info_dict)
  47. downloaded = ydl.downloaded_info_dicts[0]
  48. self.assertEqual(downloaded['ext'], 'webm')
  49. # Different resolution => download best quality (mp4)
  50. ydl = YDL()
  51. ydl.params['prefer_free_formats'] = True
  52. formats = [
  53. {'ext': 'webm', 'height': 720, 'url': TEST_URL},
  54. {'ext': 'mp4', 'height': 1080, 'url': TEST_URL},
  55. ]
  56. info_dict['formats'] = formats
  57. yie = YoutubeIE(ydl)
  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'], 'mp4')
  62. # No prefer_free_formats => prefer mp4 and flv for greater compatibility
  63. ydl = YDL()
  64. ydl.params['prefer_free_formats'] = False
  65. formats = [
  66. {'ext': 'webm', 'height': 720, 'url': TEST_URL},
  67. {'ext': 'mp4', 'height': 720, 'url': TEST_URL},
  68. {'ext': 'flv', 'height': 720, 'url': TEST_URL},
  69. ]
  70. info_dict['formats'] = formats
  71. yie = YoutubeIE(ydl)
  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')
  76. ydl = YDL()
  77. ydl.params['prefer_free_formats'] = False
  78. formats = [
  79. {'ext': 'flv', 'height': 720, 'url': TEST_URL},
  80. {'ext': 'webm', 'height': 720, 'url': TEST_URL},
  81. ]
  82. info_dict['formats'] = formats
  83. yie = YoutubeIE(ydl)
  84. yie._sort_formats(info_dict['formats'])
  85. ydl.process_ie_result(info_dict)
  86. downloaded = ydl.downloaded_info_dicts[0]
  87. self.assertEqual(downloaded['ext'], 'flv')
  88. def test_format_selection(self):
  89. formats = [
  90. {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL},
  91. {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL},
  92. {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL},
  93. {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL},
  94. ]
  95. info_dict = _make_result(formats)
  96. ydl = YDL({'format': '20/47'})
  97. ydl.process_ie_result(info_dict.copy())
  98. downloaded = ydl.downloaded_info_dicts[0]
  99. self.assertEqual(downloaded['format_id'], '47')
  100. ydl = YDL({'format': '20/71/worst'})
  101. ydl.process_ie_result(info_dict.copy())
  102. downloaded = ydl.downloaded_info_dicts[0]
  103. self.assertEqual(downloaded['format_id'], '35')
  104. ydl = YDL()
  105. ydl.process_ie_result(info_dict.copy())
  106. downloaded = ydl.downloaded_info_dicts[0]
  107. self.assertEqual(downloaded['format_id'], '2')
  108. ydl = YDL({'format': 'webm/mp4'})
  109. ydl.process_ie_result(info_dict.copy())
  110. downloaded = ydl.downloaded_info_dicts[0]
  111. self.assertEqual(downloaded['format_id'], '47')
  112. ydl = YDL({'format': '3gp/40/mp4'})
  113. ydl.process_ie_result(info_dict.copy())
  114. downloaded = ydl.downloaded_info_dicts[0]
  115. self.assertEqual(downloaded['format_id'], '35')
  116. def test_format_selection_audio(self):
  117. formats = [
  118. {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL},
  119. {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL},
  120. {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL},
  121. {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL},
  122. ]
  123. info_dict = _make_result(formats)
  124. ydl = YDL({'format': 'bestaudio'})
  125. ydl.process_ie_result(info_dict.copy())
  126. downloaded = ydl.downloaded_info_dicts[0]
  127. self.assertEqual(downloaded['format_id'], 'audio-high')
  128. ydl = YDL({'format': 'worstaudio'})
  129. ydl.process_ie_result(info_dict.copy())
  130. downloaded = ydl.downloaded_info_dicts[0]
  131. self.assertEqual(downloaded['format_id'], 'audio-low')
  132. formats = [
  133. {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL},
  134. {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL},
  135. ]
  136. info_dict = _make_result(formats)
  137. ydl = YDL({'format': 'bestaudio/worstaudio/best'})
  138. ydl.process_ie_result(info_dict.copy())
  139. downloaded = ydl.downloaded_info_dicts[0]
  140. self.assertEqual(downloaded['format_id'], 'vid-high')
  141. def test_format_selection_audio_exts(self):
  142. formats = [
  143. {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
  144. {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
  145. {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
  146. {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
  147. {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
  148. ]
  149. info_dict = _make_result(formats)
  150. ydl = YDL({'format': 'best'})
  151. ie = YoutubeIE(ydl)
  152. ie._sort_formats(info_dict['formats'])
  153. ydl.process_ie_result(copy.deepcopy(info_dict))
  154. downloaded = ydl.downloaded_info_dicts[0]
  155. self.assertEqual(downloaded['format_id'], 'aac-64')
  156. ydl = YDL({'format': 'mp3'})
  157. ie = YoutubeIE(ydl)
  158. ie._sort_formats(info_dict['formats'])
  159. ydl.process_ie_result(copy.deepcopy(info_dict))
  160. downloaded = ydl.downloaded_info_dicts[0]
  161. self.assertEqual(downloaded['format_id'], 'mp3-64')
  162. ydl = YDL({'prefer_free_formats': True})
  163. ie = YoutubeIE(ydl)
  164. ie._sort_formats(info_dict['formats'])
  165. ydl.process_ie_result(copy.deepcopy(info_dict))
  166. downloaded = ydl.downloaded_info_dicts[0]
  167. self.assertEqual(downloaded['format_id'], 'ogg-64')
  168. def test_format_selection_video(self):
  169. formats = [
  170. {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL},
  171. {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL},
  172. {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL},
  173. ]
  174. info_dict = _make_result(formats)
  175. ydl = YDL({'format': 'bestvideo'})
  176. ydl.process_ie_result(info_dict.copy())
  177. downloaded = ydl.downloaded_info_dicts[0]
  178. self.assertEqual(downloaded['format_id'], 'dash-video-high')
  179. ydl = YDL({'format': 'worstvideo'})
  180. ydl.process_ie_result(info_dict.copy())
  181. downloaded = ydl.downloaded_info_dicts[0]
  182. self.assertEqual(downloaded['format_id'], 'dash-video-low')
  183. def test_youtube_format_selection(self):
  184. order = [
  185. '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '36', '17', '13',
  186. # Apple HTTP Live Streaming
  187. '96', '95', '94', '93', '92', '132', '151',
  188. # 3D
  189. '85', '84', '102', '83', '101', '82', '100',
  190. # Dash video
  191. '137', '248', '136', '247', '135', '246',
  192. '245', '244', '134', '243', '133', '242', '160',
  193. # Dash audio
  194. '141', '172', '140', '171', '139',
  195. ]
  196. for f1id, f2id in zip(order, order[1:]):
  197. f1 = YoutubeIE._formats[f1id].copy()
  198. f1['format_id'] = f1id
  199. f1['url'] = 'url:' + f1id
  200. f2 = YoutubeIE._formats[f2id].copy()
  201. f2['format_id'] = f2id
  202. f2['url'] = 'url:' + f2id
  203. info_dict = _make_result([f1, f2], extractor='youtube')
  204. ydl = YDL({'format': 'best/bestvideo'})
  205. yie = YoutubeIE(ydl)
  206. yie._sort_formats(info_dict['formats'])
  207. ydl.process_ie_result(info_dict)
  208. downloaded = ydl.downloaded_info_dicts[0]
  209. self.assertEqual(downloaded['format_id'], f1id)
  210. info_dict = _make_result([f2, f1], extractor='youtube')
  211. ydl = YDL({'format': 'best/bestvideo'})
  212. yie = YoutubeIE(ydl)
  213. yie._sort_formats(info_dict['formats'])
  214. ydl.process_ie_result(info_dict)
  215. downloaded = ydl.downloaded_info_dicts[0]
  216. self.assertEqual(downloaded['format_id'], f1id)
  217. def test_format_filtering(self):
  218. formats = [
  219. {'format_id': 'A', 'filesize': 500, 'width': 1000},
  220. {'format_id': 'B', 'filesize': 1000, 'width': 500},
  221. {'format_id': 'C', 'filesize': 1000, 'width': 400},
  222. {'format_id': 'D', 'filesize': 2000, 'width': 600},
  223. {'format_id': 'E', 'filesize': 3000},
  224. {'format_id': 'F'},
  225. {'format_id': 'G', 'filesize': 1000000},
  226. ]
  227. for f in formats:
  228. f['url'] = 'http://_/'
  229. f['ext'] = 'unknown'
  230. info_dict = _make_result(formats)
  231. ydl = YDL({'format': 'best[filesize<3000]'})
  232. ydl.process_ie_result(info_dict)
  233. downloaded = ydl.downloaded_info_dicts[0]
  234. self.assertEqual(downloaded['format_id'], 'D')
  235. ydl = YDL({'format': 'best[filesize<=3000]'})
  236. ydl.process_ie_result(info_dict)
  237. downloaded = ydl.downloaded_info_dicts[0]
  238. self.assertEqual(downloaded['format_id'], 'E')
  239. ydl = YDL({'format': 'best[filesize <= ? 3000]'})
  240. ydl.process_ie_result(info_dict)
  241. downloaded = ydl.downloaded_info_dicts[0]
  242. self.assertEqual(downloaded['format_id'], 'F')
  243. ydl = YDL({'format': 'best [filesize = 1000] [width>450]'})
  244. ydl.process_ie_result(info_dict)
  245. downloaded = ydl.downloaded_info_dicts[0]
  246. self.assertEqual(downloaded['format_id'], 'B')
  247. ydl = YDL({'format': 'best [filesize = 1000] [width!=450]'})
  248. ydl.process_ie_result(info_dict)
  249. downloaded = ydl.downloaded_info_dicts[0]
  250. self.assertEqual(downloaded['format_id'], 'C')
  251. ydl = YDL({'format': '[filesize>?1]'})
  252. ydl.process_ie_result(info_dict)
  253. downloaded = ydl.downloaded_info_dicts[0]
  254. self.assertEqual(downloaded['format_id'], 'G')
  255. ydl = YDL({'format': '[filesize<1M]'})
  256. ydl.process_ie_result(info_dict)
  257. downloaded = ydl.downloaded_info_dicts[0]
  258. self.assertEqual(downloaded['format_id'], 'E')
  259. ydl = YDL({'format': '[filesize<1MiB]'})
  260. ydl.process_ie_result(info_dict)
  261. downloaded = ydl.downloaded_info_dicts[0]
  262. self.assertEqual(downloaded['format_id'], 'G')
  263. class TestYoutubeDL(unittest.TestCase):
  264. def test_subtitles(self):
  265. def s_formats(lang, autocaption=False):
  266. return [{
  267. 'ext': ext,
  268. 'url': 'http://localhost/video.%s.%s' % (lang, ext),
  269. '_auto': autocaption,
  270. } for ext in ['vtt', 'srt', 'ass']]
  271. subtitles = dict((l, s_formats(l)) for l in ['en', 'fr', 'es'])
  272. auto_captions = dict((l, s_formats(l, True)) for l in ['it', 'pt', 'es'])
  273. info_dict = {
  274. 'id': 'test',
  275. 'title': 'Test',
  276. 'url': 'http://localhost/video.mp4',
  277. 'subtitles': subtitles,
  278. 'automatic_captions': auto_captions,
  279. 'extractor': 'TEST',
  280. }
  281. def get_info(params={}):
  282. params.setdefault('simulate', True)
  283. ydl = YDL(params)
  284. ydl.report_warning = lambda *args, **kargs: None
  285. return ydl.process_video_result(info_dict, download=False)
  286. result = get_info()
  287. self.assertFalse(result.get('requested_subtitles'))
  288. self.assertEqual(result['subtitles'], subtitles)
  289. self.assertEqual(result['automatic_captions'], auto_captions)
  290. result = get_info({'writesubtitles': True})
  291. subs = result['requested_subtitles']
  292. self.assertTrue(subs)
  293. self.assertEqual(set(subs.keys()), set(['en']))
  294. self.assertTrue(subs['en'].get('data') is None)
  295. self.assertEqual(subs['en']['ext'], 'ass')
  296. result = get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'})
  297. subs = result['requested_subtitles']
  298. self.assertEqual(subs['en']['ext'], 'srt')
  299. result = get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']})
  300. subs = result['requested_subtitles']
  301. self.assertTrue(subs)
  302. self.assertEqual(set(subs.keys()), set(['es', 'fr']))
  303. result = get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
  304. subs = result['requested_subtitles']
  305. self.assertTrue(subs)
  306. self.assertEqual(set(subs.keys()), set(['es', 'pt']))
  307. self.assertFalse(subs['es']['_auto'])
  308. self.assertTrue(subs['pt']['_auto'])
  309. result = get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
  310. subs = result['requested_subtitles']
  311. self.assertTrue(subs)
  312. self.assertEqual(set(subs.keys()), set(['es', 'pt']))
  313. self.assertTrue(subs['es']['_auto'])
  314. self.assertTrue(subs['pt']['_auto'])
  315. def test_add_extra_info(self):
  316. test_dict = {
  317. 'extractor': 'Foo',
  318. }
  319. extra_info = {
  320. 'extractor': 'Bar',
  321. 'playlist': 'funny videos',
  322. }
  323. YDL.add_extra_info(test_dict, extra_info)
  324. self.assertEqual(test_dict['extractor'], 'Foo')
  325. self.assertEqual(test_dict['playlist'], 'funny videos')
  326. def test_prepare_filename(self):
  327. info = {
  328. 'id': '1234',
  329. 'ext': 'mp4',
  330. 'width': None,
  331. }
  332. def fname(templ):
  333. ydl = YoutubeDL({'outtmpl': templ})
  334. return ydl.prepare_filename(info)
  335. self.assertEqual(fname('%(id)s.%(ext)s'), '1234.mp4')
  336. self.assertEqual(fname('%(id)s-%(width)s.%(ext)s'), '1234-NA.mp4')
  337. # Replace missing fields with 'NA'
  338. self.assertEqual(fname('%(uploader_date)s-%(id)s.%(ext)s'), 'NA-1234.mp4')
  339. def test_format_note(self):
  340. ydl = YoutubeDL()
  341. self.assertEqual(ydl._format_note({}), '')
  342. assertRegexpMatches(self, ydl._format_note({
  343. 'vbr': 10,
  344. }), '^\s*10k$')
  345. def test_postprocessors(self):
  346. filename = 'post-processor-testfile.mp4'
  347. audiofile = filename + '.mp3'
  348. class SimplePP(PostProcessor):
  349. def run(self, info):
  350. with open(audiofile, 'wt') as f:
  351. f.write('EXAMPLE')
  352. return [info['filepath']], info
  353. def run_pp(params, PP):
  354. with open(filename, 'wt') as f:
  355. f.write('EXAMPLE')
  356. ydl = YoutubeDL(params)
  357. ydl.add_post_processor(PP())
  358. ydl.post_process(filename, {'filepath': filename})
  359. run_pp({'keepvideo': True}, SimplePP)
  360. self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
  361. self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
  362. os.unlink(filename)
  363. os.unlink(audiofile)
  364. run_pp({'keepvideo': False}, SimplePP)
  365. self.assertFalse(os.path.exists(filename), '%s exists' % filename)
  366. self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
  367. os.unlink(audiofile)
  368. class ModifierPP(PostProcessor):
  369. def run(self, info):
  370. with open(info['filepath'], 'wt') as f:
  371. f.write('MODIFIED')
  372. return [], info
  373. run_pp({'keepvideo': False}, ModifierPP)
  374. self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
  375. os.unlink(filename)
  376. def test_match_filter(self):
  377. class FilterYDL(YDL):
  378. def __init__(self, *args, **kwargs):
  379. super(FilterYDL, self).__init__(*args, **kwargs)
  380. self.params['simulate'] = True
  381. def process_info(self, info_dict):
  382. super(YDL, self).process_info(info_dict)
  383. def _match_entry(self, info_dict, incomplete):
  384. res = super(FilterYDL, self)._match_entry(info_dict, incomplete)
  385. if res is None:
  386. self.downloaded_info_dicts.append(info_dict)
  387. return res
  388. first = {
  389. 'id': '1',
  390. 'url': TEST_URL,
  391. 'title': 'one',
  392. 'extractor': 'TEST',
  393. 'duration': 30,
  394. 'filesize': 10 * 1024,
  395. }
  396. second = {
  397. 'id': '2',
  398. 'url': TEST_URL,
  399. 'title': 'two',
  400. 'extractor': 'TEST',
  401. 'duration': 10,
  402. 'description': 'foo',
  403. 'filesize': 5 * 1024,
  404. }
  405. videos = [first, second]
  406. def get_videos(filter_=None):
  407. ydl = FilterYDL({'match_filter': filter_})
  408. for v in videos:
  409. ydl.process_ie_result(v, download=True)
  410. return [v['id'] for v in ydl.downloaded_info_dicts]
  411. res = get_videos()
  412. self.assertEqual(res, ['1', '2'])
  413. def f(v):
  414. if v['id'] == '1':
  415. return None
  416. else:
  417. return 'Video id is not 1'
  418. res = get_videos(f)
  419. self.assertEqual(res, ['1'])
  420. f = match_filter_func('duration < 30')
  421. res = get_videos(f)
  422. self.assertEqual(res, ['2'])
  423. f = match_filter_func('description = foo')
  424. res = get_videos(f)
  425. self.assertEqual(res, ['2'])
  426. f = match_filter_func('description =? foo')
  427. res = get_videos(f)
  428. self.assertEqual(res, ['1', '2'])
  429. f = match_filter_func('filesize > 5KiB')
  430. res = get_videos(f)
  431. self.assertEqual(res, ['1'])
  432. def test_playlist_items_selection(self):
  433. entries = [{
  434. 'id': compat_str(i),
  435. 'title': compat_str(i),
  436. 'url': TEST_URL,
  437. } for i in range(1, 5)]
  438. playlist = {
  439. '_type': 'playlist',
  440. 'id': 'test',
  441. 'entries': entries,
  442. 'extractor': 'test:playlist',
  443. 'extractor_key': 'test:playlist',
  444. 'webpage_url': 'http://example.com',
  445. }
  446. def get_ids(params):
  447. ydl = YDL(params)
  448. # make a copy because the dictionary can be modified
  449. ydl.process_ie_result(playlist.copy())
  450. return [int(v['id']) for v in ydl.downloaded_info_dicts]
  451. result = get_ids({})
  452. self.assertEqual(result, [1, 2, 3, 4])
  453. result = get_ids({'playlistend': 10})
  454. self.assertEqual(result, [1, 2, 3, 4])
  455. result = get_ids({'playlistend': 2})
  456. self.assertEqual(result, [1, 2])
  457. result = get_ids({'playliststart': 10})
  458. self.assertEqual(result, [])
  459. result = get_ids({'playliststart': 2})
  460. self.assertEqual(result, [2, 3, 4])
  461. result = get_ids({'playlist_items': '2-4'})
  462. self.assertEqual(result, [2, 3, 4])
  463. result = get_ids({'playlist_items': '2,4'})
  464. self.assertEqual(result, [2, 4])
  465. result = get_ids({'playlist_items': '10'})
  466. self.assertEqual(result, [])
  467. if __name__ == '__main__':
  468. unittest.main()