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.

880 lines
34 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. # coding: utf-8
  3. from __future__ import unicode_literals
  4. # Allow direct execution
  5. import os
  6. import sys
  7. import unittest
  8. sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
  9. import copy
  10. from test.helper import FakeYDL, assertRegexpMatches
  11. from youtube_dl import YoutubeDL
  12. from youtube_dl.compat import compat_str, compat_urllib_error
  13. from youtube_dl.extractor import YoutubeIE
  14. from youtube_dl.extractor.common import InfoExtractor
  15. from youtube_dl.postprocessor.common import PostProcessor
  16. from youtube_dl.utils import ExtractorError, match_filter_func
  17. TEST_URL = 'http://localhost/sample.mp4'
  18. class YDL(FakeYDL):
  19. def __init__(self, *args, **kwargs):
  20. super(YDL, self).__init__(*args, **kwargs)
  21. self.downloaded_info_dicts = []
  22. self.msgs = []
  23. def process_info(self, info_dict):
  24. self.downloaded_info_dicts.append(info_dict)
  25. def to_screen(self, msg):
  26. self.msgs.append(msg)
  27. def _make_result(formats, **kwargs):
  28. res = {
  29. 'formats': formats,
  30. 'id': 'testid',
  31. 'title': 'testttitle',
  32. 'extractor': 'testex',
  33. 'extractor_key': 'TestEx',
  34. }
  35. res.update(**kwargs)
  36. return res
  37. class TestFormatSelection(unittest.TestCase):
  38. def test_prefer_free_formats(self):
  39. # Same resolution => download webm
  40. ydl = YDL()
  41. ydl.params['prefer_free_formats'] = True
  42. formats = [
  43. {'ext': 'webm', 'height': 460, 'url': TEST_URL},
  44. {'ext': 'mp4', 'height': 460, 'url': TEST_URL},
  45. ]
  46. info_dict = _make_result(formats)
  47. yie = YoutubeIE(ydl)
  48. yie._sort_formats(info_dict['formats'])
  49. ydl.process_ie_result(info_dict)
  50. downloaded = ydl.downloaded_info_dicts[0]
  51. self.assertEqual(downloaded['ext'], 'webm')
  52. # Different resolution => download best quality (mp4)
  53. ydl = YDL()
  54. ydl.params['prefer_free_formats'] = True
  55. formats = [
  56. {'ext': 'webm', 'height': 720, 'url': TEST_URL},
  57. {'ext': 'mp4', 'height': 1080, 'url': TEST_URL},
  58. ]
  59. info_dict['formats'] = formats
  60. yie = YoutubeIE(ydl)
  61. yie._sort_formats(info_dict['formats'])
  62. ydl.process_ie_result(info_dict)
  63. downloaded = ydl.downloaded_info_dicts[0]
  64. self.assertEqual(downloaded['ext'], 'mp4')
  65. # No prefer_free_formats => prefer mp4 and flv for greater compatibility
  66. ydl = YDL()
  67. ydl.params['prefer_free_formats'] = False
  68. formats = [
  69. {'ext': 'webm', 'height': 720, 'url': TEST_URL},
  70. {'ext': 'mp4', 'height': 720, 'url': TEST_URL},
  71. {'ext': 'flv', 'height': 720, 'url': TEST_URL},
  72. ]
  73. info_dict['formats'] = formats
  74. yie = YoutubeIE(ydl)
  75. yie._sort_formats(info_dict['formats'])
  76. ydl.process_ie_result(info_dict)
  77. downloaded = ydl.downloaded_info_dicts[0]
  78. self.assertEqual(downloaded['ext'], 'mp4')
  79. ydl = YDL()
  80. ydl.params['prefer_free_formats'] = False
  81. formats = [
  82. {'ext': 'flv', 'height': 720, 'url': TEST_URL},
  83. {'ext': 'webm', 'height': 720, 'url': TEST_URL},
  84. ]
  85. info_dict['formats'] = formats
  86. yie = YoutubeIE(ydl)
  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'], 'flv')
  91. def test_format_selection(self):
  92. formats = [
  93. {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL},
  94. {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL},
  95. {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL},
  96. {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL},
  97. {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL},
  98. ]
  99. info_dict = _make_result(formats)
  100. ydl = YDL({'format': '20/47'})
  101. ydl.process_ie_result(info_dict.copy())
  102. downloaded = ydl.downloaded_info_dicts[0]
  103. self.assertEqual(downloaded['format_id'], '47')
  104. ydl = YDL({'format': '20/71/worst'})
  105. ydl.process_ie_result(info_dict.copy())
  106. downloaded = ydl.downloaded_info_dicts[0]
  107. self.assertEqual(downloaded['format_id'], '35')
  108. ydl = YDL()
  109. ydl.process_ie_result(info_dict.copy())
  110. downloaded = ydl.downloaded_info_dicts[0]
  111. self.assertEqual(downloaded['format_id'], '2')
  112. ydl = YDL({'format': 'webm/mp4'})
  113. ydl.process_ie_result(info_dict.copy())
  114. downloaded = ydl.downloaded_info_dicts[0]
  115. self.assertEqual(downloaded['format_id'], '47')
  116. ydl = YDL({'format': '3gp/40/mp4'})
  117. ydl.process_ie_result(info_dict.copy())
  118. downloaded = ydl.downloaded_info_dicts[0]
  119. self.assertEqual(downloaded['format_id'], '35')
  120. ydl = YDL({'format': 'example-with-dashes'})
  121. ydl.process_ie_result(info_dict.copy())
  122. downloaded = ydl.downloaded_info_dicts[0]
  123. self.assertEqual(downloaded['format_id'], 'example-with-dashes')
  124. def test_format_selection_audio(self):
  125. formats = [
  126. {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL},
  127. {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL},
  128. {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL},
  129. {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL},
  130. ]
  131. info_dict = _make_result(formats)
  132. ydl = YDL({'format': 'bestaudio'})
  133. ydl.process_ie_result(info_dict.copy())
  134. downloaded = ydl.downloaded_info_dicts[0]
  135. self.assertEqual(downloaded['format_id'], 'audio-high')
  136. ydl = YDL({'format': 'worstaudio'})
  137. ydl.process_ie_result(info_dict.copy())
  138. downloaded = ydl.downloaded_info_dicts[0]
  139. self.assertEqual(downloaded['format_id'], 'audio-low')
  140. formats = [
  141. {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL},
  142. {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL},
  143. ]
  144. info_dict = _make_result(formats)
  145. ydl = YDL({'format': 'bestaudio/worstaudio/best'})
  146. ydl.process_ie_result(info_dict.copy())
  147. downloaded = ydl.downloaded_info_dicts[0]
  148. self.assertEqual(downloaded['format_id'], 'vid-high')
  149. def test_format_selection_audio_exts(self):
  150. formats = [
  151. {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
  152. {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
  153. {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'},
  154. {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
  155. {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'},
  156. ]
  157. info_dict = _make_result(formats)
  158. ydl = YDL({'format': 'best'})
  159. ie = YoutubeIE(ydl)
  160. ie._sort_formats(info_dict['formats'])
  161. ydl.process_ie_result(copy.deepcopy(info_dict))
  162. downloaded = ydl.downloaded_info_dicts[0]
  163. self.assertEqual(downloaded['format_id'], 'aac-64')
  164. ydl = YDL({'format': 'mp3'})
  165. ie = YoutubeIE(ydl)
  166. ie._sort_formats(info_dict['formats'])
  167. ydl.process_ie_result(copy.deepcopy(info_dict))
  168. downloaded = ydl.downloaded_info_dicts[0]
  169. self.assertEqual(downloaded['format_id'], 'mp3-64')
  170. ydl = YDL({'prefer_free_formats': True})
  171. ie = YoutubeIE(ydl)
  172. ie._sort_formats(info_dict['formats'])
  173. ydl.process_ie_result(copy.deepcopy(info_dict))
  174. downloaded = ydl.downloaded_info_dicts[0]
  175. self.assertEqual(downloaded['format_id'], 'ogg-64')
  176. def test_format_selection_video(self):
  177. formats = [
  178. {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL},
  179. {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL},
  180. {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL},
  181. ]
  182. info_dict = _make_result(formats)
  183. ydl = YDL({'format': 'bestvideo'})
  184. ydl.process_ie_result(info_dict.copy())
  185. downloaded = ydl.downloaded_info_dicts[0]
  186. self.assertEqual(downloaded['format_id'], 'dash-video-high')
  187. ydl = YDL({'format': 'worstvideo'})
  188. ydl.process_ie_result(info_dict.copy())
  189. downloaded = ydl.downloaded_info_dicts[0]
  190. self.assertEqual(downloaded['format_id'], 'dash-video-low')
  191. ydl = YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'})
  192. ydl.process_ie_result(info_dict.copy())
  193. downloaded = ydl.downloaded_info_dicts[0]
  194. self.assertEqual(downloaded['format_id'], 'dash-video-low')
  195. formats = [
  196. {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL},
  197. ]
  198. info_dict = _make_result(formats)
  199. ydl = YDL({'format': 'bestvideo[vcodec=avc1.123456]'})
  200. ydl.process_ie_result(info_dict.copy())
  201. downloaded = ydl.downloaded_info_dicts[0]
  202. self.assertEqual(downloaded['format_id'], 'vid-vcodec-dot')
  203. def test_format_selection_string_ops(self):
  204. formats = [
  205. {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL},
  206. ]
  207. info_dict = _make_result(formats)
  208. # equals (=)
  209. ydl = YDL({'format': '[format_id=abc-cba]'})
  210. ydl.process_ie_result(info_dict.copy())
  211. downloaded = ydl.downloaded_info_dicts[0]
  212. self.assertEqual(downloaded['format_id'], 'abc-cba')
  213. # does not equal (!=)
  214. ydl = YDL({'format': '[format_id!=abc-cba]'})
  215. self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
  216. # starts with (^=)
  217. ydl = YDL({'format': '[format_id^=abc]'})
  218. ydl.process_ie_result(info_dict.copy())
  219. downloaded = ydl.downloaded_info_dicts[0]
  220. self.assertEqual(downloaded['format_id'], 'abc-cba')
  221. # does not start with (!^=)
  222. ydl = YDL({'format': '[format_id!^=abc-cba]'})
  223. self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
  224. # ends with ($=)
  225. ydl = YDL({'format': '[format_id$=cba]'})
  226. ydl.process_ie_result(info_dict.copy())
  227. downloaded = ydl.downloaded_info_dicts[0]
  228. self.assertEqual(downloaded['format_id'], 'abc-cba')
  229. # does not end with (!$=)
  230. ydl = YDL({'format': '[format_id!$=abc-cba]'})
  231. self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
  232. # contains (*=)
  233. ydl = YDL({'format': '[format_id*=-]'})
  234. ydl.process_ie_result(info_dict.copy())
  235. downloaded = ydl.downloaded_info_dicts[0]
  236. self.assertEqual(downloaded['format_id'], 'abc-cba')
  237. # does not contain (!*=)
  238. ydl = YDL({'format': '[format_id!*=-]'})
  239. self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
  240. def test_youtube_format_selection(self):
  241. order = [
  242. '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
  243. # Apple HTTP Live Streaming
  244. '96', '95', '94', '93', '92', '132', '151',
  245. # 3D
  246. '85', '84', '102', '83', '101', '82', '100',
  247. # Dash video
  248. '137', '248', '136', '247', '135', '246',
  249. '245', '244', '134', '243', '133', '242', '160',
  250. # Dash audio
  251. '141', '172', '140', '171', '139',
  252. ]
  253. def format_info(f_id):
  254. info = YoutubeIE._formats[f_id].copy()
  255. # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
  256. # and 'vcodec', while in tests such information is incomplete since
  257. # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
  258. # test_YoutubeDL.test_youtube_format_selection is broken without
  259. # this fix
  260. if 'acodec' in info and 'vcodec' not in info:
  261. info['vcodec'] = 'none'
  262. elif 'vcodec' in info and 'acodec' not in info:
  263. info['acodec'] = 'none'
  264. info['format_id'] = f_id
  265. info['url'] = 'url:' + f_id
  266. return info
  267. formats_order = [format_info(f_id) for f_id in order]
  268. info_dict = _make_result(list(formats_order), extractor='youtube')
  269. ydl = YDL({'format': 'bestvideo+bestaudio'})
  270. yie = YoutubeIE(ydl)
  271. yie._sort_formats(info_dict['formats'])
  272. ydl.process_ie_result(info_dict)
  273. downloaded = ydl.downloaded_info_dicts[0]
  274. self.assertEqual(downloaded['format_id'], '137+141')
  275. self.assertEqual(downloaded['ext'], 'mp4')
  276. info_dict = _make_result(list(formats_order), extractor='youtube')
  277. ydl = YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'})
  278. yie = YoutubeIE(ydl)
  279. yie._sort_formats(info_dict['formats'])
  280. ydl.process_ie_result(info_dict)
  281. downloaded = ydl.downloaded_info_dicts[0]
  282. self.assertEqual(downloaded['format_id'], '38')
  283. info_dict = _make_result(list(formats_order), extractor='youtube')
  284. ydl = YDL({'format': 'bestvideo/best,bestaudio'})
  285. yie = YoutubeIE(ydl)
  286. yie._sort_formats(info_dict['formats'])
  287. ydl.process_ie_result(info_dict)
  288. downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
  289. self.assertEqual(downloaded_ids, ['137', '141'])
  290. info_dict = _make_result(list(formats_order), extractor='youtube')
  291. ydl = YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'})
  292. yie = YoutubeIE(ydl)
  293. yie._sort_formats(info_dict['formats'])
  294. ydl.process_ie_result(info_dict)
  295. downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
  296. self.assertEqual(downloaded_ids, ['137+141', '248+141'])
  297. info_dict = _make_result(list(formats_order), extractor='youtube')
  298. ydl = YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'})
  299. yie = YoutubeIE(ydl)
  300. yie._sort_formats(info_dict['formats'])
  301. ydl.process_ie_result(info_dict)
  302. downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
  303. self.assertEqual(downloaded_ids, ['136+141', '247+141'])
  304. info_dict = _make_result(list(formats_order), extractor='youtube')
  305. ydl = YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'})
  306. yie = YoutubeIE(ydl)
  307. yie._sort_formats(info_dict['formats'])
  308. ydl.process_ie_result(info_dict)
  309. downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
  310. self.assertEqual(downloaded_ids, ['248+141'])
  311. for f1, f2 in zip(formats_order, formats_order[1:]):
  312. info_dict = _make_result([f1, f2], extractor='youtube')
  313. ydl = YDL({'format': 'best/bestvideo'})
  314. yie = YoutubeIE(ydl)
  315. yie._sort_formats(info_dict['formats'])
  316. ydl.process_ie_result(info_dict)
  317. downloaded = ydl.downloaded_info_dicts[0]
  318. self.assertEqual(downloaded['format_id'], f1['format_id'])
  319. info_dict = _make_result([f2, f1], extractor='youtube')
  320. ydl = YDL({'format': 'best/bestvideo'})
  321. yie = YoutubeIE(ydl)
  322. yie._sort_formats(info_dict['formats'])
  323. ydl.process_ie_result(info_dict)
  324. downloaded = ydl.downloaded_info_dicts[0]
  325. self.assertEqual(downloaded['format_id'], f1['format_id'])
  326. def test_audio_only_extractor_format_selection(self):
  327. # For extractors with incomplete formats (all formats are audio-only or
  328. # video-only) best and worst should fallback to corresponding best/worst
  329. # video-only or audio-only formats (as per
  330. # https://github.com/rg3/youtube-dl/pull/5556)
  331. formats = [
  332. {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL},
  333. {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL},
  334. ]
  335. info_dict = _make_result(formats)
  336. ydl = YDL({'format': 'best'})
  337. ydl.process_ie_result(info_dict.copy())
  338. downloaded = ydl.downloaded_info_dicts[0]
  339. self.assertEqual(downloaded['format_id'], 'high')
  340. ydl = YDL({'format': 'worst'})
  341. ydl.process_ie_result(info_dict.copy())
  342. downloaded = ydl.downloaded_info_dicts[0]
  343. self.assertEqual(downloaded['format_id'], 'low')
  344. def test_format_not_available(self):
  345. formats = [
  346. {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL},
  347. {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL},
  348. ]
  349. info_dict = _make_result(formats)
  350. # This must fail since complete video-audio format does not match filter
  351. # and extractor does not provide incomplete only formats (i.e. only
  352. # video-only or audio-only).
  353. ydl = YDL({'format': 'best[height>360]'})
  354. self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
  355. def test_format_selection_issue_10083(self):
  356. # See https://github.com/rg3/youtube-dl/issues/10083
  357. formats = [
  358. {'format_id': 'regular', 'height': 360, 'url': TEST_URL},
  359. {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL},
  360. {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL},
  361. ]
  362. info_dict = _make_result(formats)
  363. ydl = YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'})
  364. ydl.process_ie_result(info_dict.copy())
  365. self.assertEqual(ydl.downloaded_info_dicts[0]['format_id'], 'video+audio')
  366. def test_invalid_format_specs(self):
  367. def assert_syntax_error(format_spec):
  368. ydl = YDL({'format': format_spec})
  369. info_dict = _make_result([{'format_id': 'foo', 'url': TEST_URL}])
  370. self.assertRaises(SyntaxError, ydl.process_ie_result, info_dict)
  371. assert_syntax_error('bestvideo,,best')
  372. assert_syntax_error('+bestaudio')
  373. assert_syntax_error('bestvideo+')
  374. assert_syntax_error('/')
  375. def test_format_filtering(self):
  376. formats = [
  377. {'format_id': 'A', 'filesize': 500, 'width': 1000},
  378. {'format_id': 'B', 'filesize': 1000, 'width': 500},
  379. {'format_id': 'C', 'filesize': 1000, 'width': 400},
  380. {'format_id': 'D', 'filesize': 2000, 'width': 600},
  381. {'format_id': 'E', 'filesize': 3000},
  382. {'format_id': 'F'},
  383. {'format_id': 'G', 'filesize': 1000000},
  384. ]
  385. for f in formats:
  386. f['url'] = 'http://_/'
  387. f['ext'] = 'unknown'
  388. info_dict = _make_result(formats)
  389. ydl = YDL({'format': 'best[filesize<3000]'})
  390. ydl.process_ie_result(info_dict)
  391. downloaded = ydl.downloaded_info_dicts[0]
  392. self.assertEqual(downloaded['format_id'], 'D')
  393. ydl = YDL({'format': 'best[filesize<=3000]'})
  394. ydl.process_ie_result(info_dict)
  395. downloaded = ydl.downloaded_info_dicts[0]
  396. self.assertEqual(downloaded['format_id'], 'E')
  397. ydl = YDL({'format': 'best[filesize <= ? 3000]'})
  398. ydl.process_ie_result(info_dict)
  399. downloaded = ydl.downloaded_info_dicts[0]
  400. self.assertEqual(downloaded['format_id'], 'F')
  401. ydl = YDL({'format': 'best [filesize = 1000] [width>450]'})
  402. ydl.process_ie_result(info_dict)
  403. downloaded = ydl.downloaded_info_dicts[0]
  404. self.assertEqual(downloaded['format_id'], 'B')
  405. ydl = YDL({'format': 'best [filesize = 1000] [width!=450]'})
  406. ydl.process_ie_result(info_dict)
  407. downloaded = ydl.downloaded_info_dicts[0]
  408. self.assertEqual(downloaded['format_id'], 'C')
  409. ydl = YDL({'format': '[filesize>?1]'})
  410. ydl.process_ie_result(info_dict)
  411. downloaded = ydl.downloaded_info_dicts[0]
  412. self.assertEqual(downloaded['format_id'], 'G')
  413. ydl = YDL({'format': '[filesize<1M]'})
  414. ydl.process_ie_result(info_dict)
  415. downloaded = ydl.downloaded_info_dicts[0]
  416. self.assertEqual(downloaded['format_id'], 'E')
  417. ydl = YDL({'format': '[filesize<1MiB]'})
  418. ydl.process_ie_result(info_dict)
  419. downloaded = ydl.downloaded_info_dicts[0]
  420. self.assertEqual(downloaded['format_id'], 'G')
  421. ydl = YDL({'format': 'all[width>=400][width<=600]'})
  422. ydl.process_ie_result(info_dict)
  423. downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
  424. self.assertEqual(downloaded_ids, ['B', 'C', 'D'])
  425. ydl = YDL({'format': 'best[height<40]'})
  426. try:
  427. ydl.process_ie_result(info_dict)
  428. except ExtractorError:
  429. pass
  430. self.assertEqual(ydl.downloaded_info_dicts, [])
  431. def test_default_format_spec(self):
  432. ydl = YDL({'simulate': True})
  433. self.assertEqual(ydl._default_format_spec({}), 'bestvideo+bestaudio/best')
  434. ydl = YDL({})
  435. self.assertEqual(ydl._default_format_spec({'is_live': True}), 'best/bestvideo+bestaudio')
  436. ydl = YDL({'simulate': True})
  437. self.assertEqual(ydl._default_format_spec({'is_live': True}), 'bestvideo+bestaudio/best')
  438. ydl = YDL({'outtmpl': '-'})
  439. self.assertEqual(ydl._default_format_spec({}), 'best/bestvideo+bestaudio')
  440. ydl = YDL({})
  441. self.assertEqual(ydl._default_format_spec({}, download=False), 'bestvideo+bestaudio/best')
  442. self.assertEqual(ydl._default_format_spec({'is_live': True}), 'best/bestvideo+bestaudio')
  443. class TestYoutubeDL(unittest.TestCase):
  444. def test_subtitles(self):
  445. def s_formats(lang, autocaption=False):
  446. return [{
  447. 'ext': ext,
  448. 'url': 'http://localhost/video.%s.%s' % (lang, ext),
  449. '_auto': autocaption,
  450. } for ext in ['vtt', 'srt', 'ass']]
  451. subtitles = dict((l, s_formats(l)) for l in ['en', 'fr', 'es'])
  452. auto_captions = dict((l, s_formats(l, True)) for l in ['it', 'pt', 'es'])
  453. info_dict = {
  454. 'id': 'test',
  455. 'title': 'Test',
  456. 'url': 'http://localhost/video.mp4',
  457. 'subtitles': subtitles,
  458. 'automatic_captions': auto_captions,
  459. 'extractor': 'TEST',
  460. }
  461. def get_info(params={}):
  462. params.setdefault('simulate', True)
  463. ydl = YDL(params)
  464. ydl.report_warning = lambda *args, **kargs: None
  465. return ydl.process_video_result(info_dict, download=False)
  466. result = get_info()
  467. self.assertFalse(result.get('requested_subtitles'))
  468. self.assertEqual(result['subtitles'], subtitles)
  469. self.assertEqual(result['automatic_captions'], auto_captions)
  470. result = get_info({'writesubtitles': True})
  471. subs = result['requested_subtitles']
  472. self.assertTrue(subs)
  473. self.assertEqual(set(subs.keys()), set(['en']))
  474. self.assertTrue(subs['en'].get('data') is None)
  475. self.assertEqual(subs['en']['ext'], 'ass')
  476. result = get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'})
  477. subs = result['requested_subtitles']
  478. self.assertEqual(subs['en']['ext'], 'srt')
  479. result = get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']})
  480. subs = result['requested_subtitles']
  481. self.assertTrue(subs)
  482. self.assertEqual(set(subs.keys()), set(['es', 'fr']))
  483. result = get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
  484. subs = result['requested_subtitles']
  485. self.assertTrue(subs)
  486. self.assertEqual(set(subs.keys()), set(['es', 'pt']))
  487. self.assertFalse(subs['es']['_auto'])
  488. self.assertTrue(subs['pt']['_auto'])
  489. result = get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
  490. subs = result['requested_subtitles']
  491. self.assertTrue(subs)
  492. self.assertEqual(set(subs.keys()), set(['es', 'pt']))
  493. self.assertTrue(subs['es']['_auto'])
  494. self.assertTrue(subs['pt']['_auto'])
  495. def test_add_extra_info(self):
  496. test_dict = {
  497. 'extractor': 'Foo',
  498. }
  499. extra_info = {
  500. 'extractor': 'Bar',
  501. 'playlist': 'funny videos',
  502. }
  503. YDL.add_extra_info(test_dict, extra_info)
  504. self.assertEqual(test_dict['extractor'], 'Foo')
  505. self.assertEqual(test_dict['playlist'], 'funny videos')
  506. def test_prepare_filename(self):
  507. info = {
  508. 'id': '1234',
  509. 'ext': 'mp4',
  510. 'width': None,
  511. 'height': 1080,
  512. 'title1': '$PATH',
  513. 'title2': '%PATH%',
  514. }
  515. def fname(templ):
  516. ydl = YoutubeDL({'outtmpl': templ})
  517. return ydl.prepare_filename(info)
  518. self.assertEqual(fname('%(id)s.%(ext)s'), '1234.mp4')
  519. self.assertEqual(fname('%(id)s-%(width)s.%(ext)s'), '1234-NA.mp4')
  520. # Replace missing fields with 'NA'
  521. self.assertEqual(fname('%(uploader_date)s-%(id)s.%(ext)s'), 'NA-1234.mp4')
  522. self.assertEqual(fname('%(height)d.%(ext)s'), '1080.mp4')
  523. self.assertEqual(fname('%(height)6d.%(ext)s'), ' 1080.mp4')
  524. self.assertEqual(fname('%(height)-6d.%(ext)s'), '1080 .mp4')
  525. self.assertEqual(fname('%(height)06d.%(ext)s'), '001080.mp4')
  526. self.assertEqual(fname('%(height) 06d.%(ext)s'), ' 01080.mp4')
  527. self.assertEqual(fname('%(height) 06d.%(ext)s'), ' 01080.mp4')
  528. self.assertEqual(fname('%(height)0 6d.%(ext)s'), ' 01080.mp4')
  529. self.assertEqual(fname('%(height)0 6d.%(ext)s'), ' 01080.mp4')
  530. self.assertEqual(fname('%(height) 0 6d.%(ext)s'), ' 01080.mp4')
  531. self.assertEqual(fname('%%'), '%')
  532. self.assertEqual(fname('%%%%'), '%%')
  533. self.assertEqual(fname('%%(height)06d.%(ext)s'), '%(height)06d.mp4')
  534. self.assertEqual(fname('%(width)06d.%(ext)s'), 'NA.mp4')
  535. self.assertEqual(fname('%(width)06d.%%(ext)s'), 'NA.%(ext)s')
  536. self.assertEqual(fname('%%(width)06d.%(ext)s'), '%(width)06d.mp4')
  537. self.assertEqual(fname('Hello %(title1)s'), 'Hello $PATH')
  538. self.assertEqual(fname('Hello %(title2)s'), 'Hello %PATH%')
  539. def test_format_note(self):
  540. ydl = YoutubeDL()
  541. self.assertEqual(ydl._format_note({}), '')
  542. assertRegexpMatches(self, ydl._format_note({
  543. 'vbr': 10,
  544. }), r'^\s*10k$')
  545. assertRegexpMatches(self, ydl._format_note({
  546. 'fps': 30,
  547. }), r'^30fps$')
  548. def test_postprocessors(self):
  549. filename = 'post-processor-testfile.mp4'
  550. audiofile = filename + '.mp3'
  551. class SimplePP(PostProcessor):
  552. def run(self, info):
  553. with open(audiofile, 'wt') as f:
  554. f.write('EXAMPLE')
  555. return [info['filepath']], info
  556. def run_pp(params, PP):
  557. with open(filename, 'wt') as f:
  558. f.write('EXAMPLE')
  559. ydl = YoutubeDL(params)
  560. ydl.add_post_processor(PP())
  561. ydl.post_process(filename, {'filepath': filename})
  562. run_pp({'keepvideo': True}, SimplePP)
  563. self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
  564. self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
  565. os.unlink(filename)
  566. os.unlink(audiofile)
  567. run_pp({'keepvideo': False}, SimplePP)
  568. self.assertFalse(os.path.exists(filename), '%s exists' % filename)
  569. self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
  570. os.unlink(audiofile)
  571. class ModifierPP(PostProcessor):
  572. def run(self, info):
  573. with open(info['filepath'], 'wt') as f:
  574. f.write('MODIFIED')
  575. return [], info
  576. run_pp({'keepvideo': False}, ModifierPP)
  577. self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
  578. os.unlink(filename)
  579. def test_match_filter(self):
  580. class FilterYDL(YDL):
  581. def __init__(self, *args, **kwargs):
  582. super(FilterYDL, self).__init__(*args, **kwargs)
  583. self.params['simulate'] = True
  584. def process_info(self, info_dict):
  585. super(YDL, self).process_info(info_dict)
  586. def _match_entry(self, info_dict, incomplete):
  587. res = super(FilterYDL, self)._match_entry(info_dict, incomplete)
  588. if res is None:
  589. self.downloaded_info_dicts.append(info_dict)
  590. return res
  591. first = {
  592. 'id': '1',
  593. 'url': TEST_URL,
  594. 'title': 'one',
  595. 'extractor': 'TEST',
  596. 'duration': 30,
  597. 'filesize': 10 * 1024,
  598. 'playlist_id': '42',
  599. 'uploader': "變態妍字幕版 太妍 тест",
  600. 'creator': "тест ' 123 ' тест--",
  601. }
  602. second = {
  603. 'id': '2',
  604. 'url': TEST_URL,
  605. 'title': 'two',
  606. 'extractor': 'TEST',
  607. 'duration': 10,
  608. 'description': 'foo',
  609. 'filesize': 5 * 1024,
  610. 'playlist_id': '43',
  611. 'uploader': "тест 123",
  612. }
  613. videos = [first, second]
  614. def get_videos(filter_=None):
  615. ydl = FilterYDL({'match_filter': filter_})
  616. for v in videos:
  617. ydl.process_ie_result(v, download=True)
  618. return [v['id'] for v in ydl.downloaded_info_dicts]
  619. res = get_videos()
  620. self.assertEqual(res, ['1', '2'])
  621. def f(v):
  622. if v['id'] == '1':
  623. return None
  624. else:
  625. return 'Video id is not 1'
  626. res = get_videos(f)
  627. self.assertEqual(res, ['1'])
  628. f = match_filter_func('duration < 30')
  629. res = get_videos(f)
  630. self.assertEqual(res, ['2'])
  631. f = match_filter_func('description = foo')
  632. res = get_videos(f)
  633. self.assertEqual(res, ['2'])
  634. f = match_filter_func('description =? foo')
  635. res = get_videos(f)
  636. self.assertEqual(res, ['1', '2'])
  637. f = match_filter_func('filesize > 5KiB')
  638. res = get_videos(f)
  639. self.assertEqual(res, ['1'])
  640. f = match_filter_func('playlist_id = 42')
  641. res = get_videos(f)
  642. self.assertEqual(res, ['1'])
  643. f = match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
  644. res = get_videos(f)
  645. self.assertEqual(res, ['1'])
  646. f = match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
  647. res = get_videos(f)
  648. self.assertEqual(res, ['2'])
  649. f = match_filter_func('creator = "тест \' 123 \' тест--"')
  650. res = get_videos(f)
  651. self.assertEqual(res, ['1'])
  652. f = match_filter_func("creator = 'тест \\' 123 \\' тест--'")
  653. res = get_videos(f)
  654. self.assertEqual(res, ['1'])
  655. f = match_filter_func(r"creator = 'тест \' 123 \' тест--' & duration > 30")
  656. res = get_videos(f)
  657. self.assertEqual(res, [])
  658. def test_playlist_items_selection(self):
  659. entries = [{
  660. 'id': compat_str(i),
  661. 'title': compat_str(i),
  662. 'url': TEST_URL,
  663. } for i in range(1, 5)]
  664. playlist = {
  665. '_type': 'playlist',
  666. 'id': 'test',
  667. 'entries': entries,
  668. 'extractor': 'test:playlist',
  669. 'extractor_key': 'test:playlist',
  670. 'webpage_url': 'http://example.com',
  671. }
  672. def get_ids(params):
  673. ydl = YDL(params)
  674. # make a copy because the dictionary can be modified
  675. ydl.process_ie_result(playlist.copy())
  676. return [int(v['id']) for v in ydl.downloaded_info_dicts]
  677. result = get_ids({})
  678. self.assertEqual(result, [1, 2, 3, 4])
  679. result = get_ids({'playlistend': 10})
  680. self.assertEqual(result, [1, 2, 3, 4])
  681. result = get_ids({'playlistend': 2})
  682. self.assertEqual(result, [1, 2])
  683. result = get_ids({'playliststart': 10})
  684. self.assertEqual(result, [])
  685. result = get_ids({'playliststart': 2})
  686. self.assertEqual(result, [2, 3, 4])
  687. result = get_ids({'playlist_items': '2-4'})
  688. self.assertEqual(result, [2, 3, 4])
  689. result = get_ids({'playlist_items': '2,4'})
  690. self.assertEqual(result, [2, 4])
  691. result = get_ids({'playlist_items': '10'})
  692. self.assertEqual(result, [])
  693. result = get_ids({'playlist_items': '3-10'})
  694. self.assertEqual(result, [3, 4])
  695. result = get_ids({'playlist_items': '2-4,3-4,3'})
  696. self.assertEqual(result, [2, 3, 4])
  697. def test_urlopen_no_file_protocol(self):
  698. # see https://github.com/rg3/youtube-dl/issues/8227
  699. ydl = YDL()
  700. self.assertRaises(compat_urllib_error.URLError, ydl.urlopen, 'file:///etc/passwd')
  701. def test_do_not_override_ie_key_in_url_transparent(self):
  702. ydl = YDL()
  703. class Foo1IE(InfoExtractor):
  704. _VALID_URL = r'foo1:'
  705. def _real_extract(self, url):
  706. return {
  707. '_type': 'url_transparent',
  708. 'url': 'foo2:',
  709. 'ie_key': 'Foo2',
  710. 'title': 'foo1 title',
  711. 'id': 'foo1_id',
  712. }
  713. class Foo2IE(InfoExtractor):
  714. _VALID_URL = r'foo2:'
  715. def _real_extract(self, url):
  716. return {
  717. '_type': 'url',
  718. 'url': 'foo3:',
  719. 'ie_key': 'Foo3',
  720. }
  721. class Foo3IE(InfoExtractor):
  722. _VALID_URL = r'foo3:'
  723. def _real_extract(self, url):
  724. return _make_result([{'url': TEST_URL}], title='foo3 title')
  725. ydl.add_info_extractor(Foo1IE(ydl))
  726. ydl.add_info_extractor(Foo2IE(ydl))
  727. ydl.add_info_extractor(Foo3IE(ydl))
  728. ydl.extract_info('foo1:')
  729. downloaded = ydl.downloaded_info_dicts[0]
  730. self.assertEqual(downloaded['url'], TEST_URL)
  731. self.assertEqual(downloaded['title'], 'foo1 title')
  732. self.assertEqual(downloaded['id'], 'testid')
  733. self.assertEqual(downloaded['extractor'], 'testex')
  734. self.assertEqual(downloaded['extractor_key'], 'TestEx')
  735. if __name__ == '__main__':
  736. unittest.main()