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.

834 lines
32 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_youtube_format_selection(self):
  204. order = [
  205. '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13',
  206. # Apple HTTP Live Streaming
  207. '96', '95', '94', '93', '92', '132', '151',
  208. # 3D
  209. '85', '84', '102', '83', '101', '82', '100',
  210. # Dash video
  211. '137', '248', '136', '247', '135', '246',
  212. '245', '244', '134', '243', '133', '242', '160',
  213. # Dash audio
  214. '141', '172', '140', '171', '139',
  215. ]
  216. def format_info(f_id):
  217. info = YoutubeIE._formats[f_id].copy()
  218. # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
  219. # and 'vcodec', while in tests such information is incomplete since
  220. # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
  221. # test_YoutubeDL.test_youtube_format_selection is broken without
  222. # this fix
  223. if 'acodec' in info and 'vcodec' not in info:
  224. info['vcodec'] = 'none'
  225. elif 'vcodec' in info and 'acodec' not in info:
  226. info['acodec'] = 'none'
  227. info['format_id'] = f_id
  228. info['url'] = 'url:' + f_id
  229. return info
  230. formats_order = [format_info(f_id) for f_id in order]
  231. info_dict = _make_result(list(formats_order), extractor='youtube')
  232. ydl = YDL({'format': 'bestvideo+bestaudio'})
  233. yie = YoutubeIE(ydl)
  234. yie._sort_formats(info_dict['formats'])
  235. ydl.process_ie_result(info_dict)
  236. downloaded = ydl.downloaded_info_dicts[0]
  237. self.assertEqual(downloaded['format_id'], '137+141')
  238. self.assertEqual(downloaded['ext'], 'mp4')
  239. info_dict = _make_result(list(formats_order), extractor='youtube')
  240. ydl = YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'})
  241. yie = YoutubeIE(ydl)
  242. yie._sort_formats(info_dict['formats'])
  243. ydl.process_ie_result(info_dict)
  244. downloaded = ydl.downloaded_info_dicts[0]
  245. self.assertEqual(downloaded['format_id'], '38')
  246. info_dict = _make_result(list(formats_order), extractor='youtube')
  247. ydl = YDL({'format': 'bestvideo/best,bestaudio'})
  248. yie = YoutubeIE(ydl)
  249. yie._sort_formats(info_dict['formats'])
  250. ydl.process_ie_result(info_dict)
  251. downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
  252. self.assertEqual(downloaded_ids, ['137', '141'])
  253. info_dict = _make_result(list(formats_order), extractor='youtube')
  254. ydl = YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'})
  255. yie = YoutubeIE(ydl)
  256. yie._sort_formats(info_dict['formats'])
  257. ydl.process_ie_result(info_dict)
  258. downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
  259. self.assertEqual(downloaded_ids, ['137+141', '248+141'])
  260. info_dict = _make_result(list(formats_order), extractor='youtube')
  261. ydl = YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'})
  262. yie = YoutubeIE(ydl)
  263. yie._sort_formats(info_dict['formats'])
  264. ydl.process_ie_result(info_dict)
  265. downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
  266. self.assertEqual(downloaded_ids, ['136+141', '247+141'])
  267. info_dict = _make_result(list(formats_order), extractor='youtube')
  268. ydl = YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'})
  269. yie = YoutubeIE(ydl)
  270. yie._sort_formats(info_dict['formats'])
  271. ydl.process_ie_result(info_dict)
  272. downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts]
  273. self.assertEqual(downloaded_ids, ['248+141'])
  274. for f1, f2 in zip(formats_order, formats_order[1:]):
  275. info_dict = _make_result([f1, f2], extractor='youtube')
  276. ydl = YDL({'format': 'best/bestvideo'})
  277. yie = YoutubeIE(ydl)
  278. yie._sort_formats(info_dict['formats'])
  279. ydl.process_ie_result(info_dict)
  280. downloaded = ydl.downloaded_info_dicts[0]
  281. self.assertEqual(downloaded['format_id'], f1['format_id'])
  282. info_dict = _make_result([f2, f1], extractor='youtube')
  283. ydl = YDL({'format': 'best/bestvideo'})
  284. yie = YoutubeIE(ydl)
  285. yie._sort_formats(info_dict['formats'])
  286. ydl.process_ie_result(info_dict)
  287. downloaded = ydl.downloaded_info_dicts[0]
  288. self.assertEqual(downloaded['format_id'], f1['format_id'])
  289. def test_audio_only_extractor_format_selection(self):
  290. # For extractors with incomplete formats (all formats are audio-only or
  291. # video-only) best and worst should fallback to corresponding best/worst
  292. # video-only or audio-only formats (as per
  293. # https://github.com/rg3/youtube-dl/pull/5556)
  294. formats = [
  295. {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL},
  296. {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL},
  297. ]
  298. info_dict = _make_result(formats)
  299. ydl = YDL({'format': 'best'})
  300. ydl.process_ie_result(info_dict.copy())
  301. downloaded = ydl.downloaded_info_dicts[0]
  302. self.assertEqual(downloaded['format_id'], 'high')
  303. ydl = YDL({'format': 'worst'})
  304. ydl.process_ie_result(info_dict.copy())
  305. downloaded = ydl.downloaded_info_dicts[0]
  306. self.assertEqual(downloaded['format_id'], 'low')
  307. def test_format_not_available(self):
  308. formats = [
  309. {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL},
  310. {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL},
  311. ]
  312. info_dict = _make_result(formats)
  313. # This must fail since complete video-audio format does not match filter
  314. # and extractor does not provide incomplete only formats (i.e. only
  315. # video-only or audio-only).
  316. ydl = YDL({'format': 'best[height>360]'})
  317. self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy())
  318. def test_format_selection_issue_10083(self):
  319. # See https://github.com/rg3/youtube-dl/issues/10083
  320. formats = [
  321. {'format_id': 'regular', 'height': 360, 'url': TEST_URL},
  322. {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL},
  323. {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL},
  324. ]
  325. info_dict = _make_result(formats)
  326. ydl = YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'})
  327. ydl.process_ie_result(info_dict.copy())
  328. self.assertEqual(ydl.downloaded_info_dicts[0]['format_id'], 'video+audio')
  329. def test_invalid_format_specs(self):
  330. def assert_syntax_error(format_spec):
  331. ydl = YDL({'format': format_spec})
  332. info_dict = _make_result([{'format_id': 'foo', 'url': TEST_URL}])
  333. self.assertRaises(SyntaxError, ydl.process_ie_result, info_dict)
  334. assert_syntax_error('bestvideo,,best')
  335. assert_syntax_error('+bestaudio')
  336. assert_syntax_error('bestvideo+')
  337. assert_syntax_error('/')
  338. def test_format_filtering(self):
  339. formats = [
  340. {'format_id': 'A', 'filesize': 500, 'width': 1000},
  341. {'format_id': 'B', 'filesize': 1000, 'width': 500},
  342. {'format_id': 'C', 'filesize': 1000, 'width': 400},
  343. {'format_id': 'D', 'filesize': 2000, 'width': 600},
  344. {'format_id': 'E', 'filesize': 3000},
  345. {'format_id': 'F'},
  346. {'format_id': 'G', 'filesize': 1000000},
  347. ]
  348. for f in formats:
  349. f['url'] = 'http://_/'
  350. f['ext'] = 'unknown'
  351. info_dict = _make_result(formats)
  352. ydl = YDL({'format': 'best[filesize<3000]'})
  353. ydl.process_ie_result(info_dict)
  354. downloaded = ydl.downloaded_info_dicts[0]
  355. self.assertEqual(downloaded['format_id'], 'D')
  356. ydl = YDL({'format': 'best[filesize<=3000]'})
  357. ydl.process_ie_result(info_dict)
  358. downloaded = ydl.downloaded_info_dicts[0]
  359. self.assertEqual(downloaded['format_id'], 'E')
  360. ydl = YDL({'format': 'best[filesize <= ? 3000]'})
  361. ydl.process_ie_result(info_dict)
  362. downloaded = ydl.downloaded_info_dicts[0]
  363. self.assertEqual(downloaded['format_id'], 'F')
  364. ydl = YDL({'format': 'best [filesize = 1000] [width>450]'})
  365. ydl.process_ie_result(info_dict)
  366. downloaded = ydl.downloaded_info_dicts[0]
  367. self.assertEqual(downloaded['format_id'], 'B')
  368. ydl = YDL({'format': 'best [filesize = 1000] [width!=450]'})
  369. ydl.process_ie_result(info_dict)
  370. downloaded = ydl.downloaded_info_dicts[0]
  371. self.assertEqual(downloaded['format_id'], 'C')
  372. ydl = YDL({'format': '[filesize>?1]'})
  373. ydl.process_ie_result(info_dict)
  374. downloaded = ydl.downloaded_info_dicts[0]
  375. self.assertEqual(downloaded['format_id'], 'G')
  376. ydl = YDL({'format': '[filesize<1M]'})
  377. ydl.process_ie_result(info_dict)
  378. downloaded = ydl.downloaded_info_dicts[0]
  379. self.assertEqual(downloaded['format_id'], 'E')
  380. ydl = YDL({'format': '[filesize<1MiB]'})
  381. ydl.process_ie_result(info_dict)
  382. downloaded = ydl.downloaded_info_dicts[0]
  383. self.assertEqual(downloaded['format_id'], 'G')
  384. ydl = YDL({'format': 'all[width>=400][width<=600]'})
  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, ['B', 'C', 'D'])
  388. ydl = YDL({'format': 'best[height<40]'})
  389. try:
  390. ydl.process_ie_result(info_dict)
  391. except ExtractorError:
  392. pass
  393. self.assertEqual(ydl.downloaded_info_dicts, [])
  394. def test_default_format_spec(self):
  395. ydl = YDL({'simulate': True})
  396. self.assertEqual(ydl._default_format_spec({}), 'bestvideo+bestaudio/best')
  397. ydl = YDL({})
  398. self.assertEqual(ydl._default_format_spec({'is_live': True}), 'best/bestvideo+bestaudio')
  399. ydl = YDL({'simulate': True})
  400. self.assertEqual(ydl._default_format_spec({'is_live': True}), 'bestvideo+bestaudio/best')
  401. ydl = YDL({'outtmpl': '-'})
  402. self.assertEqual(ydl._default_format_spec({}), 'best/bestvideo+bestaudio')
  403. ydl = YDL({})
  404. self.assertEqual(ydl._default_format_spec({}, download=False), 'bestvideo+bestaudio/best')
  405. self.assertEqual(ydl._default_format_spec({'is_live': True}), 'best/bestvideo+bestaudio')
  406. class TestYoutubeDL(unittest.TestCase):
  407. def test_subtitles(self):
  408. def s_formats(lang, autocaption=False):
  409. return [{
  410. 'ext': ext,
  411. 'url': 'http://localhost/video.%s.%s' % (lang, ext),
  412. '_auto': autocaption,
  413. } for ext in ['vtt', 'srt', 'ass']]
  414. subtitles = dict((l, s_formats(l)) for l in ['en', 'fr', 'es'])
  415. auto_captions = dict((l, s_formats(l, True)) for l in ['it', 'pt', 'es'])
  416. info_dict = {
  417. 'id': 'test',
  418. 'title': 'Test',
  419. 'url': 'http://localhost/video.mp4',
  420. 'subtitles': subtitles,
  421. 'automatic_captions': auto_captions,
  422. 'extractor': 'TEST',
  423. }
  424. def get_info(params={}):
  425. params.setdefault('simulate', True)
  426. ydl = YDL(params)
  427. ydl.report_warning = lambda *args, **kargs: None
  428. return ydl.process_video_result(info_dict, download=False)
  429. result = get_info()
  430. self.assertFalse(result.get('requested_subtitles'))
  431. self.assertEqual(result['subtitles'], subtitles)
  432. self.assertEqual(result['automatic_captions'], auto_captions)
  433. result = get_info({'writesubtitles': True})
  434. subs = result['requested_subtitles']
  435. self.assertTrue(subs)
  436. self.assertEqual(set(subs.keys()), set(['en']))
  437. self.assertTrue(subs['en'].get('data') is None)
  438. self.assertEqual(subs['en']['ext'], 'ass')
  439. result = get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'})
  440. subs = result['requested_subtitles']
  441. self.assertEqual(subs['en']['ext'], 'srt')
  442. result = get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']})
  443. subs = result['requested_subtitles']
  444. self.assertTrue(subs)
  445. self.assertEqual(set(subs.keys()), set(['es', 'fr']))
  446. result = get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
  447. subs = result['requested_subtitles']
  448. self.assertTrue(subs)
  449. self.assertEqual(set(subs.keys()), set(['es', 'pt']))
  450. self.assertFalse(subs['es']['_auto'])
  451. self.assertTrue(subs['pt']['_auto'])
  452. result = get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']})
  453. subs = result['requested_subtitles']
  454. self.assertTrue(subs)
  455. self.assertEqual(set(subs.keys()), set(['es', 'pt']))
  456. self.assertTrue(subs['es']['_auto'])
  457. self.assertTrue(subs['pt']['_auto'])
  458. def test_add_extra_info(self):
  459. test_dict = {
  460. 'extractor': 'Foo',
  461. }
  462. extra_info = {
  463. 'extractor': 'Bar',
  464. 'playlist': 'funny videos',
  465. }
  466. YDL.add_extra_info(test_dict, extra_info)
  467. self.assertEqual(test_dict['extractor'], 'Foo')
  468. self.assertEqual(test_dict['playlist'], 'funny videos')
  469. def test_prepare_filename(self):
  470. info = {
  471. 'id': '1234',
  472. 'ext': 'mp4',
  473. 'width': None,
  474. 'height': 1080,
  475. 'title1': '$PATH',
  476. 'title2': '%PATH%',
  477. }
  478. def fname(templ):
  479. ydl = YoutubeDL({'outtmpl': templ})
  480. return ydl.prepare_filename(info)
  481. self.assertEqual(fname('%(id)s.%(ext)s'), '1234.mp4')
  482. self.assertEqual(fname('%(id)s-%(width)s.%(ext)s'), '1234-NA.mp4')
  483. # Replace missing fields with 'NA'
  484. self.assertEqual(fname('%(uploader_date)s-%(id)s.%(ext)s'), 'NA-1234.mp4')
  485. self.assertEqual(fname('%(height)d.%(ext)s'), '1080.mp4')
  486. self.assertEqual(fname('%(height)6d.%(ext)s'), ' 1080.mp4')
  487. self.assertEqual(fname('%(height)-6d.%(ext)s'), '1080 .mp4')
  488. self.assertEqual(fname('%(height)06d.%(ext)s'), '001080.mp4')
  489. self.assertEqual(fname('%(height) 06d.%(ext)s'), ' 01080.mp4')
  490. self.assertEqual(fname('%(height) 06d.%(ext)s'), ' 01080.mp4')
  491. self.assertEqual(fname('%(height)0 6d.%(ext)s'), ' 01080.mp4')
  492. self.assertEqual(fname('%(height)0 6d.%(ext)s'), ' 01080.mp4')
  493. self.assertEqual(fname('%(height) 0 6d.%(ext)s'), ' 01080.mp4')
  494. self.assertEqual(fname('%%'), '%')
  495. self.assertEqual(fname('%%%%'), '%%')
  496. self.assertEqual(fname('%%(height)06d.%(ext)s'), '%(height)06d.mp4')
  497. self.assertEqual(fname('%(width)06d.%(ext)s'), 'NA.mp4')
  498. self.assertEqual(fname('%(width)06d.%%(ext)s'), 'NA.%(ext)s')
  499. self.assertEqual(fname('%%(width)06d.%(ext)s'), '%(width)06d.mp4')
  500. self.assertEqual(fname('Hello %(title1)s'), 'Hello $PATH')
  501. self.assertEqual(fname('Hello %(title2)s'), 'Hello %PATH%')
  502. def test_format_note(self):
  503. ydl = YoutubeDL()
  504. self.assertEqual(ydl._format_note({}), '')
  505. assertRegexpMatches(self, ydl._format_note({
  506. 'vbr': 10,
  507. }), r'^\s*10k$')
  508. assertRegexpMatches(self, ydl._format_note({
  509. 'fps': 30,
  510. }), r'^30fps$')
  511. def test_postprocessors(self):
  512. filename = 'post-processor-testfile.mp4'
  513. audiofile = filename + '.mp3'
  514. class SimplePP(PostProcessor):
  515. def run(self, info):
  516. with open(audiofile, 'wt') as f:
  517. f.write('EXAMPLE')
  518. return [info['filepath']], info
  519. def run_pp(params, PP):
  520. with open(filename, 'wt') as f:
  521. f.write('EXAMPLE')
  522. ydl = YoutubeDL(params)
  523. ydl.add_post_processor(PP())
  524. ydl.post_process(filename, {'filepath': filename})
  525. run_pp({'keepvideo': True}, SimplePP)
  526. self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
  527. self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
  528. os.unlink(filename)
  529. os.unlink(audiofile)
  530. run_pp({'keepvideo': False}, SimplePP)
  531. self.assertFalse(os.path.exists(filename), '%s exists' % filename)
  532. self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile)
  533. os.unlink(audiofile)
  534. class ModifierPP(PostProcessor):
  535. def run(self, info):
  536. with open(info['filepath'], 'wt') as f:
  537. f.write('MODIFIED')
  538. return [], info
  539. run_pp({'keepvideo': False}, ModifierPP)
  540. self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename)
  541. os.unlink(filename)
  542. def test_match_filter(self):
  543. class FilterYDL(YDL):
  544. def __init__(self, *args, **kwargs):
  545. super(FilterYDL, self).__init__(*args, **kwargs)
  546. self.params['simulate'] = True
  547. def process_info(self, info_dict):
  548. super(YDL, self).process_info(info_dict)
  549. def _match_entry(self, info_dict, incomplete):
  550. res = super(FilterYDL, self)._match_entry(info_dict, incomplete)
  551. if res is None:
  552. self.downloaded_info_dicts.append(info_dict)
  553. return res
  554. first = {
  555. 'id': '1',
  556. 'url': TEST_URL,
  557. 'title': 'one',
  558. 'extractor': 'TEST',
  559. 'duration': 30,
  560. 'filesize': 10 * 1024,
  561. 'playlist_id': '42',
  562. 'uploader': "變態妍字幕版 太妍 тест",
  563. 'creator': "тест ' 123 ' тест--",
  564. }
  565. second = {
  566. 'id': '2',
  567. 'url': TEST_URL,
  568. 'title': 'two',
  569. 'extractor': 'TEST',
  570. 'duration': 10,
  571. 'description': 'foo',
  572. 'filesize': 5 * 1024,
  573. 'playlist_id': '43',
  574. 'uploader': "тест 123",
  575. }
  576. videos = [first, second]
  577. def get_videos(filter_=None):
  578. ydl = FilterYDL({'match_filter': filter_})
  579. for v in videos:
  580. ydl.process_ie_result(v, download=True)
  581. return [v['id'] for v in ydl.downloaded_info_dicts]
  582. res = get_videos()
  583. self.assertEqual(res, ['1', '2'])
  584. def f(v):
  585. if v['id'] == '1':
  586. return None
  587. else:
  588. return 'Video id is not 1'
  589. res = get_videos(f)
  590. self.assertEqual(res, ['1'])
  591. f = match_filter_func('duration < 30')
  592. res = get_videos(f)
  593. self.assertEqual(res, ['2'])
  594. f = match_filter_func('description = foo')
  595. res = get_videos(f)
  596. self.assertEqual(res, ['2'])
  597. f = match_filter_func('description =? foo')
  598. res = get_videos(f)
  599. self.assertEqual(res, ['1', '2'])
  600. f = match_filter_func('filesize > 5KiB')
  601. res = get_videos(f)
  602. self.assertEqual(res, ['1'])
  603. f = match_filter_func('playlist_id = 42')
  604. res = get_videos(f)
  605. self.assertEqual(res, ['1'])
  606. f = match_filter_func('uploader = "變態妍字幕版 太妍 тест"')
  607. res = get_videos(f)
  608. self.assertEqual(res, ['1'])
  609. f = match_filter_func('uploader != "變態妍字幕版 太妍 тест"')
  610. res = get_videos(f)
  611. self.assertEqual(res, ['2'])
  612. f = match_filter_func('creator = "тест \' 123 \' тест--"')
  613. res = get_videos(f)
  614. self.assertEqual(res, ['1'])
  615. f = match_filter_func("creator = 'тест \\' 123 \\' тест--'")
  616. res = get_videos(f)
  617. self.assertEqual(res, ['1'])
  618. f = match_filter_func(r"creator = 'тест \' 123 \' тест--' & duration > 30")
  619. res = get_videos(f)
  620. self.assertEqual(res, [])
  621. def test_playlist_items_selection(self):
  622. entries = [{
  623. 'id': compat_str(i),
  624. 'title': compat_str(i),
  625. 'url': TEST_URL,
  626. } for i in range(1, 5)]
  627. playlist = {
  628. '_type': 'playlist',
  629. 'id': 'test',
  630. 'entries': entries,
  631. 'extractor': 'test:playlist',
  632. 'extractor_key': 'test:playlist',
  633. 'webpage_url': 'http://example.com',
  634. }
  635. def get_ids(params):
  636. ydl = YDL(params)
  637. # make a copy because the dictionary can be modified
  638. ydl.process_ie_result(playlist.copy())
  639. return [int(v['id']) for v in ydl.downloaded_info_dicts]
  640. result = get_ids({})
  641. self.assertEqual(result, [1, 2, 3, 4])
  642. result = get_ids({'playlistend': 10})
  643. self.assertEqual(result, [1, 2, 3, 4])
  644. result = get_ids({'playlistend': 2})
  645. self.assertEqual(result, [1, 2])
  646. result = get_ids({'playliststart': 10})
  647. self.assertEqual(result, [])
  648. result = get_ids({'playliststart': 2})
  649. self.assertEqual(result, [2, 3, 4])
  650. result = get_ids({'playlist_items': '2-4'})
  651. self.assertEqual(result, [2, 3, 4])
  652. result = get_ids({'playlist_items': '2,4'})
  653. self.assertEqual(result, [2, 4])
  654. result = get_ids({'playlist_items': '10'})
  655. self.assertEqual(result, [])
  656. result = get_ids({'playlist_items': '3-10'})
  657. self.assertEqual(result, [3, 4])
  658. result = get_ids({'playlist_items': '2-4,3-4,3'})
  659. self.assertEqual(result, [2, 3, 4])
  660. def test_urlopen_no_file_protocol(self):
  661. # see https://github.com/rg3/youtube-dl/issues/8227
  662. ydl = YDL()
  663. self.assertRaises(compat_urllib_error.URLError, ydl.urlopen, 'file:///etc/passwd')
  664. def test_do_not_override_ie_key_in_url_transparent(self):
  665. ydl = YDL()
  666. class Foo1IE(InfoExtractor):
  667. _VALID_URL = r'foo1:'
  668. def _real_extract(self, url):
  669. return {
  670. '_type': 'url_transparent',
  671. 'url': 'foo2:',
  672. 'ie_key': 'Foo2',
  673. 'title': 'foo1 title',
  674. 'id': 'foo1_id',
  675. }
  676. class Foo2IE(InfoExtractor):
  677. _VALID_URL = r'foo2:'
  678. def _real_extract(self, url):
  679. return {
  680. '_type': 'url',
  681. 'url': 'foo3:',
  682. 'ie_key': 'Foo3',
  683. }
  684. class Foo3IE(InfoExtractor):
  685. _VALID_URL = r'foo3:'
  686. def _real_extract(self, url):
  687. return _make_result([{'url': TEST_URL}], title='foo3 title')
  688. ydl.add_info_extractor(Foo1IE(ydl))
  689. ydl.add_info_extractor(Foo2IE(ydl))
  690. ydl.add_info_extractor(Foo3IE(ydl))
  691. ydl.extract_info('foo1:')
  692. downloaded = ydl.downloaded_info_dicts[0]
  693. self.assertEqual(downloaded['url'], TEST_URL)
  694. self.assertEqual(downloaded['title'], 'foo1 title')
  695. self.assertEqual(downloaded['id'], 'testid')
  696. self.assertEqual(downloaded['extractor'], 'testex')
  697. self.assertEqual(downloaded['extractor_key'], 'TestEx')
  698. if __name__ == '__main__':
  699. unittest.main()