1#!/usr/bin/env python3 2# coding: utf-8 3 4from __future__ import unicode_literals 5 6# Allow direct execution 7import os 8import sys 9import unittest 10sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 11 12import copy 13import json 14 15from test.helper import FakeYDL, assertRegexpMatches 16from yt_dlp import YoutubeDL 17from yt_dlp.compat import compat_os_name, compat_setenv, compat_str, compat_urllib_error 18from yt_dlp.extractor import YoutubeIE 19from yt_dlp.extractor.common import InfoExtractor 20from yt_dlp.postprocessor.common import PostProcessor 21from yt_dlp.utils import ExtractorError, int_or_none, match_filter_func, LazyList 22 23TEST_URL = 'http://localhost/sample.mp4' 24 25 26class YDL(FakeYDL): 27 def __init__(self, *args, **kwargs): 28 super(YDL, self).__init__(*args, **kwargs) 29 self.downloaded_info_dicts = [] 30 self.msgs = [] 31 32 def process_info(self, info_dict): 33 info_dict.pop('__original_infodict', None) 34 self.downloaded_info_dicts.append(info_dict) 35 36 def to_screen(self, msg): 37 self.msgs.append(msg) 38 39 def dl(self, *args, **kwargs): 40 assert False, 'Downloader must not be invoked for test_YoutubeDL' 41 42 43def _make_result(formats, **kwargs): 44 res = { 45 'formats': formats, 46 'id': 'testid', 47 'title': 'testttitle', 48 'extractor': 'testex', 49 'extractor_key': 'TestEx', 50 'webpage_url': 'http://example.com/watch?v=shenanigans', 51 } 52 res.update(**kwargs) 53 return res 54 55 56class TestFormatSelection(unittest.TestCase): 57 def test_prefer_free_formats(self): 58 # Same resolution => download webm 59 ydl = YDL() 60 ydl.params['prefer_free_formats'] = True 61 formats = [ 62 {'ext': 'webm', 'height': 460, 'url': TEST_URL}, 63 {'ext': 'mp4', 'height': 460, 'url': TEST_URL}, 64 ] 65 info_dict = _make_result(formats) 66 yie = YoutubeIE(ydl) 67 yie._sort_formats(info_dict['formats']) 68 ydl.process_ie_result(info_dict) 69 downloaded = ydl.downloaded_info_dicts[0] 70 self.assertEqual(downloaded['ext'], 'webm') 71 72 # Different resolution => download best quality (mp4) 73 ydl = YDL() 74 ydl.params['prefer_free_formats'] = True 75 formats = [ 76 {'ext': 'webm', 'height': 720, 'url': TEST_URL}, 77 {'ext': 'mp4', 'height': 1080, 'url': TEST_URL}, 78 ] 79 info_dict['formats'] = formats 80 yie = YoutubeIE(ydl) 81 yie._sort_formats(info_dict['formats']) 82 ydl.process_ie_result(info_dict) 83 downloaded = ydl.downloaded_info_dicts[0] 84 self.assertEqual(downloaded['ext'], 'mp4') 85 86 # No prefer_free_formats => prefer mp4 and webm 87 ydl = YDL() 88 ydl.params['prefer_free_formats'] = False 89 formats = [ 90 {'ext': 'webm', 'height': 720, 'url': TEST_URL}, 91 {'ext': 'mp4', 'height': 720, 'url': TEST_URL}, 92 {'ext': 'flv', 'height': 720, 'url': TEST_URL}, 93 ] 94 info_dict['formats'] = formats 95 yie = YoutubeIE(ydl) 96 yie._sort_formats(info_dict['formats']) 97 ydl.process_ie_result(info_dict) 98 downloaded = ydl.downloaded_info_dicts[0] 99 self.assertEqual(downloaded['ext'], 'mp4') 100 101 ydl = YDL() 102 ydl.params['prefer_free_formats'] = False 103 formats = [ 104 {'ext': 'flv', 'height': 720, 'url': TEST_URL}, 105 {'ext': 'webm', 'height': 720, 'url': TEST_URL}, 106 ] 107 info_dict['formats'] = formats 108 yie = YoutubeIE(ydl) 109 yie._sort_formats(info_dict['formats']) 110 ydl.process_ie_result(info_dict) 111 downloaded = ydl.downloaded_info_dicts[0] 112 self.assertEqual(downloaded['ext'], 'webm') 113 114 def test_format_selection(self): 115 formats = [ 116 {'format_id': '35', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}, 117 {'format_id': 'example-with-dashes', 'ext': 'webm', 'preference': 1, 'url': TEST_URL}, 118 {'format_id': '45', 'ext': 'webm', 'preference': 2, 'url': TEST_URL}, 119 {'format_id': '47', 'ext': 'webm', 'preference': 3, 'url': TEST_URL}, 120 {'format_id': '2', 'ext': 'flv', 'preference': 4, 'url': TEST_URL}, 121 ] 122 info_dict = _make_result(formats) 123 124 def test(inp, *expected, multi=False): 125 ydl = YDL({ 126 'format': inp, 127 'allow_multiple_video_streams': multi, 128 'allow_multiple_audio_streams': multi, 129 }) 130 ydl.process_ie_result(info_dict.copy()) 131 downloaded = map(lambda x: x['format_id'], ydl.downloaded_info_dicts) 132 self.assertEqual(list(downloaded), list(expected)) 133 134 test('20/47', '47') 135 test('20/71/worst', '35') 136 test(None, '2') 137 test('webm/mp4', '47') 138 test('3gp/40/mp4', '35') 139 test('example-with-dashes', 'example-with-dashes') 140 test('all', '2', '47', '45', 'example-with-dashes', '35') 141 test('mergeall', '2+47+45+example-with-dashes+35', multi=True) 142 143 def test_format_selection_audio(self): 144 formats = [ 145 {'format_id': 'audio-low', 'ext': 'webm', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}, 146 {'format_id': 'audio-mid', 'ext': 'webm', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}, 147 {'format_id': 'audio-high', 'ext': 'flv', 'preference': 3, 'vcodec': 'none', 'url': TEST_URL}, 148 {'format_id': 'vid', 'ext': 'mp4', 'preference': 4, 'url': TEST_URL}, 149 ] 150 info_dict = _make_result(formats) 151 152 ydl = YDL({'format': 'bestaudio'}) 153 ydl.process_ie_result(info_dict.copy()) 154 downloaded = ydl.downloaded_info_dicts[0] 155 self.assertEqual(downloaded['format_id'], 'audio-high') 156 157 ydl = YDL({'format': 'worstaudio'}) 158 ydl.process_ie_result(info_dict.copy()) 159 downloaded = ydl.downloaded_info_dicts[0] 160 self.assertEqual(downloaded['format_id'], 'audio-low') 161 162 formats = [ 163 {'format_id': 'vid-low', 'ext': 'mp4', 'preference': 1, 'url': TEST_URL}, 164 {'format_id': 'vid-high', 'ext': 'mp4', 'preference': 2, 'url': TEST_URL}, 165 ] 166 info_dict = _make_result(formats) 167 168 ydl = YDL({'format': 'bestaudio/worstaudio/best'}) 169 ydl.process_ie_result(info_dict.copy()) 170 downloaded = ydl.downloaded_info_dicts[0] 171 self.assertEqual(downloaded['format_id'], 'vid-high') 172 173 def test_format_selection_audio_exts(self): 174 formats = [ 175 {'format_id': 'mp3-64', 'ext': 'mp3', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}, 176 {'format_id': 'ogg-64', 'ext': 'ogg', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}, 177 {'format_id': 'aac-64', 'ext': 'aac', 'abr': 64, 'url': 'http://_', 'vcodec': 'none'}, 178 {'format_id': 'mp3-32', 'ext': 'mp3', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}, 179 {'format_id': 'aac-32', 'ext': 'aac', 'abr': 32, 'url': 'http://_', 'vcodec': 'none'}, 180 ] 181 182 info_dict = _make_result(formats) 183 ydl = YDL({'format': 'best'}) 184 ie = YoutubeIE(ydl) 185 ie._sort_formats(info_dict['formats']) 186 ydl.process_ie_result(copy.deepcopy(info_dict)) 187 downloaded = ydl.downloaded_info_dicts[0] 188 self.assertEqual(downloaded['format_id'], 'aac-64') 189 190 ydl = YDL({'format': 'mp3'}) 191 ie = YoutubeIE(ydl) 192 ie._sort_formats(info_dict['formats']) 193 ydl.process_ie_result(copy.deepcopy(info_dict)) 194 downloaded = ydl.downloaded_info_dicts[0] 195 self.assertEqual(downloaded['format_id'], 'mp3-64') 196 197 ydl = YDL({'prefer_free_formats': True}) 198 ie = YoutubeIE(ydl) 199 ie._sort_formats(info_dict['formats']) 200 ydl.process_ie_result(copy.deepcopy(info_dict)) 201 downloaded = ydl.downloaded_info_dicts[0] 202 self.assertEqual(downloaded['format_id'], 'ogg-64') 203 204 def test_format_selection_video(self): 205 formats = [ 206 {'format_id': 'dash-video-low', 'ext': 'mp4', 'preference': 1, 'acodec': 'none', 'url': TEST_URL}, 207 {'format_id': 'dash-video-high', 'ext': 'mp4', 'preference': 2, 'acodec': 'none', 'url': TEST_URL}, 208 {'format_id': 'vid', 'ext': 'mp4', 'preference': 3, 'url': TEST_URL}, 209 ] 210 info_dict = _make_result(formats) 211 212 ydl = YDL({'format': 'bestvideo'}) 213 ydl.process_ie_result(info_dict.copy()) 214 downloaded = ydl.downloaded_info_dicts[0] 215 self.assertEqual(downloaded['format_id'], 'dash-video-high') 216 217 ydl = YDL({'format': 'worstvideo'}) 218 ydl.process_ie_result(info_dict.copy()) 219 downloaded = ydl.downloaded_info_dicts[0] 220 self.assertEqual(downloaded['format_id'], 'dash-video-low') 221 222 ydl = YDL({'format': 'bestvideo[format_id^=dash][format_id$=low]'}) 223 ydl.process_ie_result(info_dict.copy()) 224 downloaded = ydl.downloaded_info_dicts[0] 225 self.assertEqual(downloaded['format_id'], 'dash-video-low') 226 227 formats = [ 228 {'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL}, 229 ] 230 info_dict = _make_result(formats) 231 232 ydl = YDL({'format': 'bestvideo[vcodec=avc1.123456]'}) 233 ydl.process_ie_result(info_dict.copy()) 234 downloaded = ydl.downloaded_info_dicts[0] 235 self.assertEqual(downloaded['format_id'], 'vid-vcodec-dot') 236 237 def test_format_selection_string_ops(self): 238 formats = [ 239 {'format_id': 'abc-cba', 'ext': 'mp4', 'url': TEST_URL}, 240 {'format_id': 'zxc-cxz', 'ext': 'webm', 'url': TEST_URL}, 241 ] 242 info_dict = _make_result(formats) 243 244 # equals (=) 245 ydl = YDL({'format': '[format_id=abc-cba]'}) 246 ydl.process_ie_result(info_dict.copy()) 247 downloaded = ydl.downloaded_info_dicts[0] 248 self.assertEqual(downloaded['format_id'], 'abc-cba') 249 250 # does not equal (!=) 251 ydl = YDL({'format': '[format_id!=abc-cba]'}) 252 ydl.process_ie_result(info_dict.copy()) 253 downloaded = ydl.downloaded_info_dicts[0] 254 self.assertEqual(downloaded['format_id'], 'zxc-cxz') 255 256 ydl = YDL({'format': '[format_id!=abc-cba][format_id!=zxc-cxz]'}) 257 self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy()) 258 259 # starts with (^=) 260 ydl = YDL({'format': '[format_id^=abc]'}) 261 ydl.process_ie_result(info_dict.copy()) 262 downloaded = ydl.downloaded_info_dicts[0] 263 self.assertEqual(downloaded['format_id'], 'abc-cba') 264 265 # does not start with (!^=) 266 ydl = YDL({'format': '[format_id!^=abc]'}) 267 ydl.process_ie_result(info_dict.copy()) 268 downloaded = ydl.downloaded_info_dicts[0] 269 self.assertEqual(downloaded['format_id'], 'zxc-cxz') 270 271 ydl = YDL({'format': '[format_id!^=abc][format_id!^=zxc]'}) 272 self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy()) 273 274 # ends with ($=) 275 ydl = YDL({'format': '[format_id$=cba]'}) 276 ydl.process_ie_result(info_dict.copy()) 277 downloaded = ydl.downloaded_info_dicts[0] 278 self.assertEqual(downloaded['format_id'], 'abc-cba') 279 280 # does not end with (!$=) 281 ydl = YDL({'format': '[format_id!$=cba]'}) 282 ydl.process_ie_result(info_dict.copy()) 283 downloaded = ydl.downloaded_info_dicts[0] 284 self.assertEqual(downloaded['format_id'], 'zxc-cxz') 285 286 ydl = YDL({'format': '[format_id!$=cba][format_id!$=cxz]'}) 287 self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy()) 288 289 # contains (*=) 290 ydl = YDL({'format': '[format_id*=bc-cb]'}) 291 ydl.process_ie_result(info_dict.copy()) 292 downloaded = ydl.downloaded_info_dicts[0] 293 self.assertEqual(downloaded['format_id'], 'abc-cba') 294 295 # does not contain (!*=) 296 ydl = YDL({'format': '[format_id!*=bc-cb]'}) 297 ydl.process_ie_result(info_dict.copy()) 298 downloaded = ydl.downloaded_info_dicts[0] 299 self.assertEqual(downloaded['format_id'], 'zxc-cxz') 300 301 ydl = YDL({'format': '[format_id!*=abc][format_id!*=zxc]'}) 302 self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy()) 303 304 ydl = YDL({'format': '[format_id!*=-]'}) 305 self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy()) 306 307 def test_youtube_format_selection(self): 308 # FIXME: Rewrite in accordance with the new format sorting options 309 return 310 311 order = [ 312 '38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '17', '36', '13', 313 # Apple HTTP Live Streaming 314 '96', '95', '94', '93', '92', '132', '151', 315 # 3D 316 '85', '84', '102', '83', '101', '82', '100', 317 # Dash video 318 '137', '248', '136', '247', '135', '246', 319 '245', '244', '134', '243', '133', '242', '160', 320 # Dash audio 321 '141', '172', '140', '171', '139', 322 ] 323 324 def format_info(f_id): 325 info = YoutubeIE._formats[f_id].copy() 326 327 # XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec' 328 # and 'vcodec', while in tests such information is incomplete since 329 # commit a6c2c24479e5f4827ceb06f64d855329c0a6f593 330 # test_YoutubeDL.test_youtube_format_selection is broken without 331 # this fix 332 if 'acodec' in info and 'vcodec' not in info: 333 info['vcodec'] = 'none' 334 elif 'vcodec' in info and 'acodec' not in info: 335 info['acodec'] = 'none' 336 337 info['format_id'] = f_id 338 info['url'] = 'url:' + f_id 339 return info 340 formats_order = [format_info(f_id) for f_id in order] 341 342 info_dict = _make_result(list(formats_order), extractor='youtube') 343 ydl = YDL({'format': 'bestvideo+bestaudio'}) 344 yie = YoutubeIE(ydl) 345 yie._sort_formats(info_dict['formats']) 346 ydl.process_ie_result(info_dict) 347 downloaded = ydl.downloaded_info_dicts[0] 348 self.assertEqual(downloaded['format_id'], '248+172') 349 self.assertEqual(downloaded['ext'], 'mp4') 350 351 info_dict = _make_result(list(formats_order), extractor='youtube') 352 ydl = YDL({'format': 'bestvideo[height>=999999]+bestaudio/best'}) 353 yie = YoutubeIE(ydl) 354 yie._sort_formats(info_dict['formats']) 355 ydl.process_ie_result(info_dict) 356 downloaded = ydl.downloaded_info_dicts[0] 357 self.assertEqual(downloaded['format_id'], '38') 358 359 info_dict = _make_result(list(formats_order), extractor='youtube') 360 ydl = YDL({'format': 'bestvideo/best,bestaudio'}) 361 yie = YoutubeIE(ydl) 362 yie._sort_formats(info_dict['formats']) 363 ydl.process_ie_result(info_dict) 364 downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts] 365 self.assertEqual(downloaded_ids, ['137', '141']) 366 367 info_dict = _make_result(list(formats_order), extractor='youtube') 368 ydl = YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])+bestaudio'}) 369 yie = YoutubeIE(ydl) 370 yie._sort_formats(info_dict['formats']) 371 ydl.process_ie_result(info_dict) 372 downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts] 373 self.assertEqual(downloaded_ids, ['137+141', '248+141']) 374 375 info_dict = _make_result(list(formats_order), extractor='youtube') 376 ydl = YDL({'format': '(bestvideo[ext=mp4],bestvideo[ext=webm])[height<=720]+bestaudio'}) 377 yie = YoutubeIE(ydl) 378 yie._sort_formats(info_dict['formats']) 379 ydl.process_ie_result(info_dict) 380 downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts] 381 self.assertEqual(downloaded_ids, ['136+141', '247+141']) 382 383 info_dict = _make_result(list(formats_order), extractor='youtube') 384 ydl = YDL({'format': '(bestvideo[ext=none]/bestvideo[ext=webm])+bestaudio'}) 385 yie = YoutubeIE(ydl) 386 yie._sort_formats(info_dict['formats']) 387 ydl.process_ie_result(info_dict) 388 downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts] 389 self.assertEqual(downloaded_ids, ['248+141']) 390 391 for f1, f2 in zip(formats_order, formats_order[1:]): 392 info_dict = _make_result([f1, f2], extractor='youtube') 393 ydl = YDL({'format': 'best/bestvideo'}) 394 yie = YoutubeIE(ydl) 395 yie._sort_formats(info_dict['formats']) 396 ydl.process_ie_result(info_dict) 397 downloaded = ydl.downloaded_info_dicts[0] 398 self.assertEqual(downloaded['format_id'], f1['format_id']) 399 400 info_dict = _make_result([f2, f1], extractor='youtube') 401 ydl = YDL({'format': 'best/bestvideo'}) 402 yie = YoutubeIE(ydl) 403 yie._sort_formats(info_dict['formats']) 404 ydl.process_ie_result(info_dict) 405 downloaded = ydl.downloaded_info_dicts[0] 406 self.assertEqual(downloaded['format_id'], f1['format_id']) 407 408 def test_audio_only_extractor_format_selection(self): 409 # For extractors with incomplete formats (all formats are audio-only or 410 # video-only) best and worst should fallback to corresponding best/worst 411 # video-only or audio-only formats (as per 412 # https://github.com/ytdl-org/youtube-dl/pull/5556) 413 formats = [ 414 {'format_id': 'low', 'ext': 'mp3', 'preference': 1, 'vcodec': 'none', 'url': TEST_URL}, 415 {'format_id': 'high', 'ext': 'mp3', 'preference': 2, 'vcodec': 'none', 'url': TEST_URL}, 416 ] 417 info_dict = _make_result(formats) 418 419 ydl = YDL({'format': 'best'}) 420 ydl.process_ie_result(info_dict.copy()) 421 downloaded = ydl.downloaded_info_dicts[0] 422 self.assertEqual(downloaded['format_id'], 'high') 423 424 ydl = YDL({'format': 'worst'}) 425 ydl.process_ie_result(info_dict.copy()) 426 downloaded = ydl.downloaded_info_dicts[0] 427 self.assertEqual(downloaded['format_id'], 'low') 428 429 def test_format_not_available(self): 430 formats = [ 431 {'format_id': 'regular', 'ext': 'mp4', 'height': 360, 'url': TEST_URL}, 432 {'format_id': 'video', 'ext': 'mp4', 'height': 720, 'acodec': 'none', 'url': TEST_URL}, 433 ] 434 info_dict = _make_result(formats) 435 436 # This must fail since complete video-audio format does not match filter 437 # and extractor does not provide incomplete only formats (i.e. only 438 # video-only or audio-only). 439 ydl = YDL({'format': 'best[height>360]'}) 440 self.assertRaises(ExtractorError, ydl.process_ie_result, info_dict.copy()) 441 442 def test_format_selection_issue_10083(self): 443 # See https://github.com/ytdl-org/youtube-dl/issues/10083 444 formats = [ 445 {'format_id': 'regular', 'height': 360, 'url': TEST_URL}, 446 {'format_id': 'video', 'height': 720, 'acodec': 'none', 'url': TEST_URL}, 447 {'format_id': 'audio', 'vcodec': 'none', 'url': TEST_URL}, 448 ] 449 info_dict = _make_result(formats) 450 451 ydl = YDL({'format': 'best[height>360]/bestvideo[height>360]+bestaudio'}) 452 ydl.process_ie_result(info_dict.copy()) 453 self.assertEqual(ydl.downloaded_info_dicts[0]['format_id'], 'video+audio') 454 455 def test_invalid_format_specs(self): 456 def assert_syntax_error(format_spec): 457 self.assertRaises(SyntaxError, YDL, {'format': format_spec}) 458 459 assert_syntax_error('bestvideo,,best') 460 assert_syntax_error('+bestaudio') 461 assert_syntax_error('bestvideo+') 462 assert_syntax_error('/') 463 assert_syntax_error('[720<height]') 464 465 def test_format_filtering(self): 466 formats = [ 467 {'format_id': 'A', 'filesize': 500, 'width': 1000}, 468 {'format_id': 'B', 'filesize': 1000, 'width': 500}, 469 {'format_id': 'C', 'filesize': 1000, 'width': 400}, 470 {'format_id': 'D', 'filesize': 2000, 'width': 600}, 471 {'format_id': 'E', 'filesize': 3000}, 472 {'format_id': 'F'}, 473 {'format_id': 'G', 'filesize': 1000000}, 474 ] 475 for f in formats: 476 f['url'] = 'http://_/' 477 f['ext'] = 'unknown' 478 info_dict = _make_result(formats) 479 480 ydl = YDL({'format': 'best[filesize<3000]'}) 481 ydl.process_ie_result(info_dict) 482 downloaded = ydl.downloaded_info_dicts[0] 483 self.assertEqual(downloaded['format_id'], 'D') 484 485 ydl = YDL({'format': 'best[filesize<=3000]'}) 486 ydl.process_ie_result(info_dict) 487 downloaded = ydl.downloaded_info_dicts[0] 488 self.assertEqual(downloaded['format_id'], 'E') 489 490 ydl = YDL({'format': 'best[filesize <= ? 3000]'}) 491 ydl.process_ie_result(info_dict) 492 downloaded = ydl.downloaded_info_dicts[0] 493 self.assertEqual(downloaded['format_id'], 'F') 494 495 ydl = YDL({'format': 'best [filesize = 1000] [width>450]'}) 496 ydl.process_ie_result(info_dict) 497 downloaded = ydl.downloaded_info_dicts[0] 498 self.assertEqual(downloaded['format_id'], 'B') 499 500 ydl = YDL({'format': 'best [filesize = 1000] [width!=450]'}) 501 ydl.process_ie_result(info_dict) 502 downloaded = ydl.downloaded_info_dicts[0] 503 self.assertEqual(downloaded['format_id'], 'C') 504 505 ydl = YDL({'format': '[filesize>?1]'}) 506 ydl.process_ie_result(info_dict) 507 downloaded = ydl.downloaded_info_dicts[0] 508 self.assertEqual(downloaded['format_id'], 'G') 509 510 ydl = YDL({'format': '[filesize<1M]'}) 511 ydl.process_ie_result(info_dict) 512 downloaded = ydl.downloaded_info_dicts[0] 513 self.assertEqual(downloaded['format_id'], 'E') 514 515 ydl = YDL({'format': '[filesize<1MiB]'}) 516 ydl.process_ie_result(info_dict) 517 downloaded = ydl.downloaded_info_dicts[0] 518 self.assertEqual(downloaded['format_id'], 'G') 519 520 ydl = YDL({'format': 'all[width>=400][width<=600]'}) 521 ydl.process_ie_result(info_dict) 522 downloaded_ids = [info['format_id'] for info in ydl.downloaded_info_dicts] 523 self.assertEqual(downloaded_ids, ['D', 'C', 'B']) 524 525 ydl = YDL({'format': 'best[height<40]'}) 526 try: 527 ydl.process_ie_result(info_dict) 528 except ExtractorError: 529 pass 530 self.assertEqual(ydl.downloaded_info_dicts, []) 531 532 def test_default_format_spec(self): 533 ydl = YDL({'simulate': True}) 534 self.assertEqual(ydl._default_format_spec({}), 'bestvideo*+bestaudio/best') 535 536 ydl = YDL({}) 537 self.assertEqual(ydl._default_format_spec({'is_live': True}), 'best/bestvideo+bestaudio') 538 539 ydl = YDL({'simulate': True}) 540 self.assertEqual(ydl._default_format_spec({'is_live': True}), 'bestvideo*+bestaudio/best') 541 542 ydl = YDL({'outtmpl': '-'}) 543 self.assertEqual(ydl._default_format_spec({}), 'best/bestvideo+bestaudio') 544 545 ydl = YDL({}) 546 self.assertEqual(ydl._default_format_spec({}, download=False), 'bestvideo*+bestaudio/best') 547 self.assertEqual(ydl._default_format_spec({'is_live': True}), 'best/bestvideo+bestaudio') 548 549 550class TestYoutubeDL(unittest.TestCase): 551 def test_subtitles(self): 552 def s_formats(lang, autocaption=False): 553 return [{ 554 'ext': ext, 555 'url': 'http://localhost/video.%s.%s' % (lang, ext), 556 '_auto': autocaption, 557 } for ext in ['vtt', 'srt', 'ass']] 558 subtitles = dict((l, s_formats(l)) for l in ['en', 'fr', 'es']) 559 auto_captions = dict((l, s_formats(l, True)) for l in ['it', 'pt', 'es']) 560 info_dict = { 561 'id': 'test', 562 'title': 'Test', 563 'url': 'http://localhost/video.mp4', 564 'subtitles': subtitles, 565 'automatic_captions': auto_captions, 566 'extractor': 'TEST', 567 'webpage_url': 'http://example.com/watch?v=shenanigans', 568 } 569 570 def get_info(params={}): 571 params.setdefault('simulate', True) 572 ydl = YDL(params) 573 ydl.report_warning = lambda *args, **kargs: None 574 return ydl.process_video_result(info_dict, download=False) 575 576 result = get_info() 577 self.assertFalse(result.get('requested_subtitles')) 578 self.assertEqual(result['subtitles'], subtitles) 579 self.assertEqual(result['automatic_captions'], auto_captions) 580 581 result = get_info({'writesubtitles': True}) 582 subs = result['requested_subtitles'] 583 self.assertTrue(subs) 584 self.assertEqual(set(subs.keys()), set(['en'])) 585 self.assertTrue(subs['en'].get('data') is None) 586 self.assertEqual(subs['en']['ext'], 'ass') 587 588 result = get_info({'writesubtitles': True, 'subtitlesformat': 'foo/srt'}) 589 subs = result['requested_subtitles'] 590 self.assertEqual(subs['en']['ext'], 'srt') 591 592 result = get_info({'writesubtitles': True, 'subtitleslangs': ['es', 'fr', 'it']}) 593 subs = result['requested_subtitles'] 594 self.assertTrue(subs) 595 self.assertEqual(set(subs.keys()), set(['es', 'fr'])) 596 597 result = get_info({'writesubtitles': True, 'subtitleslangs': ['all', '-en']}) 598 subs = result['requested_subtitles'] 599 self.assertTrue(subs) 600 self.assertEqual(set(subs.keys()), set(['es', 'fr'])) 601 602 result = get_info({'writesubtitles': True, 'subtitleslangs': ['en', 'fr', '-en']}) 603 subs = result['requested_subtitles'] 604 self.assertTrue(subs) 605 self.assertEqual(set(subs.keys()), set(['fr'])) 606 607 result = get_info({'writesubtitles': True, 'subtitleslangs': ['-en', 'en']}) 608 subs = result['requested_subtitles'] 609 self.assertTrue(subs) 610 self.assertEqual(set(subs.keys()), set(['en'])) 611 612 result = get_info({'writesubtitles': True, 'subtitleslangs': ['e.+']}) 613 subs = result['requested_subtitles'] 614 self.assertTrue(subs) 615 self.assertEqual(set(subs.keys()), set(['es', 'en'])) 616 617 result = get_info({'writesubtitles': True, 'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}) 618 subs = result['requested_subtitles'] 619 self.assertTrue(subs) 620 self.assertEqual(set(subs.keys()), set(['es', 'pt'])) 621 self.assertFalse(subs['es']['_auto']) 622 self.assertTrue(subs['pt']['_auto']) 623 624 result = get_info({'writeautomaticsub': True, 'subtitleslangs': ['es', 'pt']}) 625 subs = result['requested_subtitles'] 626 self.assertTrue(subs) 627 self.assertEqual(set(subs.keys()), set(['es', 'pt'])) 628 self.assertTrue(subs['es']['_auto']) 629 self.assertTrue(subs['pt']['_auto']) 630 631 def test_add_extra_info(self): 632 test_dict = { 633 'extractor': 'Foo', 634 } 635 extra_info = { 636 'extractor': 'Bar', 637 'playlist': 'funny videos', 638 } 639 YDL.add_extra_info(test_dict, extra_info) 640 self.assertEqual(test_dict['extractor'], 'Foo') 641 self.assertEqual(test_dict['playlist'], 'funny videos') 642 643 outtmpl_info = { 644 'id': '1234', 645 'ext': 'mp4', 646 'width': None, 647 'height': 1080, 648 'title1': '$PATH', 649 'title2': '%PATH%', 650 'title3': 'foo/bar\\test', 651 'title4': 'foo "bar" test', 652 'title5': 'áéí ', 653 'timestamp': 1618488000, 654 'duration': 100000, 655 'playlist_index': 1, 656 'playlist_autonumber': 2, 657 '_last_playlist_index': 100, 658 'n_entries': 10, 659 'formats': [{'id': 'id 1'}, {'id': 'id 2'}, {'id': 'id 3'}] 660 } 661 662 def test_prepare_outtmpl_and_filename(self): 663 def test(tmpl, expected, *, info=None, **params): 664 params['outtmpl'] = tmpl 665 ydl = YoutubeDL(params) 666 ydl._num_downloads = 1 667 self.assertEqual(ydl.validate_outtmpl(tmpl), None) 668 669 out = ydl.evaluate_outtmpl(tmpl, info or self.outtmpl_info) 670 fname = ydl.prepare_filename(info or self.outtmpl_info) 671 672 if not isinstance(expected, (list, tuple)): 673 expected = (expected, expected) 674 for (name, got), expect in zip((('outtmpl', out), ('filename', fname)), expected): 675 if callable(expect): 676 self.assertTrue(expect(got), f'Wrong {name} from {tmpl}') 677 else: 678 self.assertEqual(got, expect, f'Wrong {name} from {tmpl}') 679 680 # Side-effects 681 original_infodict = dict(self.outtmpl_info) 682 test('foo.bar', 'foo.bar') 683 original_infodict['epoch'] = self.outtmpl_info.get('epoch') 684 self.assertTrue(isinstance(original_infodict['epoch'], int)) 685 test('%(epoch)d', int_or_none) 686 self.assertEqual(original_infodict, self.outtmpl_info) 687 688 # Auto-generated fields 689 test('%(id)s.%(ext)s', '1234.mp4') 690 test('%(duration_string)s', ('27:46:40', '27-46-40')) 691 test('%(resolution)s', '1080p') 692 test('%(playlist_index)s', '001') 693 test('%(playlist_autonumber)s', '02') 694 test('%(autonumber)s', '00001') 695 test('%(autonumber+2)03d', '005', autonumber_start=3) 696 test('%(autonumber)s', '001', autonumber_size=3) 697 698 # Escaping % 699 test('%', '%') 700 test('%%', '%') 701 test('%%%%', '%%') 702 test('%s', '%s') 703 test('%%%s', '%%s') 704 test('%d', '%d') 705 test('%abc%', '%abc%') 706 test('%%(width)06d.%(ext)s', '%(width)06d.mp4') 707 test('%%%(height)s', '%1080') 708 test('%(width)06d.%(ext)s', 'NA.mp4') 709 test('%(width)06d.%%(ext)s', 'NA.%(ext)s') 710 test('%%(width)06d.%(ext)s', '%(width)06d.mp4') 711 712 # ID sanitization 713 test('%(id)s', '_abcd', info={'id': '_abcd'}) 714 test('%(some_id)s', '_abcd', info={'some_id': '_abcd'}) 715 test('%(formats.0.id)s', '_abcd', info={'formats': [{'id': '_abcd'}]}) 716 test('%(id)s', '-abcd', info={'id': '-abcd'}) 717 test('%(id)s', '.abcd', info={'id': '.abcd'}) 718 test('%(id)s', 'ab__cd', info={'id': 'ab__cd'}) 719 test('%(id)s', ('ab:cd', 'ab -cd'), info={'id': 'ab:cd'}) 720 test('%(id.0)s', '-', info={'id': '--'}) 721 722 # Invalid templates 723 self.assertTrue(isinstance(YoutubeDL.validate_outtmpl('%(title)'), ValueError)) 724 test('%(invalid@tmpl|def)s', 'none', outtmpl_na_placeholder='none') 725 test('%(..)s', 'NA') 726 727 # Entire info_dict 728 def expect_same_infodict(out): 729 got_dict = json.loads(out) 730 for info_field, expected in self.outtmpl_info.items(): 731 self.assertEqual(got_dict.get(info_field), expected, info_field) 732 return True 733 734 test('%()j', (expect_same_infodict, str)) 735 736 # NA placeholder 737 NA_TEST_OUTTMPL = '%(uploader_date)s-%(width)d-%(x|def)s-%(id)s.%(ext)s' 738 test(NA_TEST_OUTTMPL, 'NA-NA-def-1234.mp4') 739 test(NA_TEST_OUTTMPL, 'none-none-def-1234.mp4', outtmpl_na_placeholder='none') 740 test(NA_TEST_OUTTMPL, '--def-1234.mp4', outtmpl_na_placeholder='') 741 test('%(non_existent.0)s', 'NA') 742 743 # String formatting 744 FMT_TEST_OUTTMPL = '%%(height)%s.%%(ext)s' 745 test(FMT_TEST_OUTTMPL % 's', '1080.mp4') 746 test(FMT_TEST_OUTTMPL % 'd', '1080.mp4') 747 test(FMT_TEST_OUTTMPL % '6d', ' 1080.mp4') 748 test(FMT_TEST_OUTTMPL % '-6d', '1080 .mp4') 749 test(FMT_TEST_OUTTMPL % '06d', '001080.mp4') 750 test(FMT_TEST_OUTTMPL % ' 06d', ' 01080.mp4') 751 test(FMT_TEST_OUTTMPL % ' 06d', ' 01080.mp4') 752 test(FMT_TEST_OUTTMPL % '0 6d', ' 01080.mp4') 753 test(FMT_TEST_OUTTMPL % '0 6d', ' 01080.mp4') 754 test(FMT_TEST_OUTTMPL % ' 0 6d', ' 01080.mp4') 755 756 # Type casting 757 test('%(id)d', '1234') 758 test('%(height)c', '1') 759 test('%(ext)c', 'm') 760 test('%(id)d %(id)r', "1234 '1234'") 761 test('%(id)r %(height)r', "'1234' 1080") 762 test('%(ext)s-%(ext|def)d', 'mp4-def') 763 test('%(width|0)04d', '0000') 764 test('a%(width|)d', 'a', outtmpl_na_placeholder='none') 765 766 FORMATS = self.outtmpl_info['formats'] 767 sanitize = lambda x: x.replace(':', ' -').replace('"', "'").replace('\n', ' ') 768 769 # Custom type casting 770 test('%(formats.:.id)l', 'id 1, id 2, id 3') 771 test('%(formats.:.id)#l', ('id 1\nid 2\nid 3', 'id 1 id 2 id 3')) 772 test('%(ext)l', 'mp4') 773 test('%(formats.:.id) 18l', ' id 1, id 2, id 3') 774 test('%(formats)j', (json.dumps(FORMATS), sanitize(json.dumps(FORMATS)))) 775 test('%(formats)#j', (json.dumps(FORMATS, indent=4), sanitize(json.dumps(FORMATS, indent=4)))) 776 test('%(title5).3B', 'á') 777 test('%(title5)U', 'áéí ') 778 test('%(title5)#U', 'a\u0301e\u0301i\u0301 ') 779 test('%(title5)+U', 'áéí A') 780 test('%(title5)+#U', 'a\u0301e\u0301i\u0301 A') 781 test('%(height)D', '1K') 782 test('%(height)5.2D', ' 1.08K') 783 test('%(title4)#S', 'foo_bar_test') 784 test('%(title4).10S', ('foo \'bar\' ', 'foo \'bar\'' + ('#' if compat_os_name == 'nt' else ' '))) 785 if compat_os_name == 'nt': 786 test('%(title4)q', ('"foo \\"bar\\" test"', "'foo _'bar_' test'")) 787 test('%(formats.:.id)#q', ('"id 1" "id 2" "id 3"', "'id 1' 'id 2' 'id 3'")) 788 test('%(formats.0.id)#q', ('"id 1"', "'id 1'")) 789 else: 790 test('%(title4)q', ('\'foo "bar" test\'', "'foo 'bar' test'")) 791 test('%(formats.:.id)#q', "'id 1' 'id 2' 'id 3'") 792 test('%(formats.0.id)#q', "'id 1'") 793 794 # Internal formatting 795 test('%(timestamp-1000>%H-%M-%S)s', '11-43-20') 796 test('%(title|%)s %(title|%%)s', '% %%') 797 test('%(id+1-height+3)05d', '00158') 798 test('%(width+100)05d', 'NA') 799 test('%(formats.0) 15s', ('% 15s' % FORMATS[0], '% 15s' % sanitize(str(FORMATS[0])))) 800 test('%(formats.0)r', (repr(FORMATS[0]), sanitize(repr(FORMATS[0])))) 801 test('%(height.0)03d', '001') 802 test('%(-height.0)04d', '-001') 803 test('%(formats.-1.id)s', FORMATS[-1]['id']) 804 test('%(formats.0.id.-1)d', FORMATS[0]['id'][-1]) 805 test('%(formats.3)s', 'NA') 806 test('%(formats.:2:-1)r', repr(FORMATS[:2:-1])) 807 test('%(formats.0.id.-1+id)f', '1235.000000') 808 test('%(formats.0.id.-1+formats.1.id.-1)d', '3') 809 810 # Alternates 811 test('%(title,id)s', '1234') 812 test('%(width-100,height+20|def)d', '1100') 813 test('%(width-100,height+width|def)s', 'def') 814 test('%(timestamp-x>%H\\,%M\\,%S,timestamp>%H\\,%M\\,%S)s', '12,00,00') 815 816 # Replacement 817 test('%(id&foo)s.bar', 'foo.bar') 818 test('%(title&foo)s.bar', 'NA.bar') 819 test('%(title&foo|baz)s.bar', 'baz.bar') 820 821 # Laziness 822 def gen(): 823 yield from range(5) 824 raise self.assertTrue(False, 'LazyList should not be evaluated till here') 825 test('%(key.4)s', '4', info={'key': LazyList(gen())}) 826 827 # Empty filename 828 test('%(foo|)s-%(bar|)s.%(ext)s', '-.mp4') 829 # test('%(foo|)s.%(ext)s', ('.mp4', '_.mp4')) # fixme 830 # test('%(foo|)s', ('', '_')) # fixme 831 832 # Environment variable expansion for prepare_filename 833 compat_setenv('__yt_dlp_var', 'expanded') 834 envvar = '%__yt_dlp_var%' if compat_os_name == 'nt' else '$__yt_dlp_var' 835 test(envvar, (envvar, 'expanded')) 836 if compat_os_name == 'nt': 837 test('%s%', ('%s%', '%s%')) 838 compat_setenv('s', 'expanded') 839 test('%s%', ('%s%', 'expanded')) # %s% should be expanded before escaping %s 840 compat_setenv('(test)s', 'expanded') 841 test('%(test)s%', ('NA%', 'expanded')) # Environment should take priority over template 842 843 # Path expansion and escaping 844 test('Hello %(title1)s', 'Hello $PATH') 845 test('Hello %(title2)s', 'Hello %PATH%') 846 test('%(title3)s', ('foo/bar\\test', 'foo_bar_test')) 847 test('folder/%(title3)s', ('folder/foo/bar\\test', 'folder%sfoo_bar_test' % os.path.sep)) 848 849 def test_format_note(self): 850 ydl = YoutubeDL() 851 self.assertEqual(ydl._format_note({}), '') 852 assertRegexpMatches(self, ydl._format_note({ 853 'vbr': 10, 854 }), r'^\s*10k$') 855 assertRegexpMatches(self, ydl._format_note({ 856 'fps': 30, 857 }), r'^30fps$') 858 859 def test_postprocessors(self): 860 filename = 'post-processor-testfile.mp4' 861 audiofile = filename + '.mp3' 862 863 class SimplePP(PostProcessor): 864 def run(self, info): 865 with open(audiofile, 'wt') as f: 866 f.write('EXAMPLE') 867 return [info['filepath']], info 868 869 def run_pp(params, PP): 870 with open(filename, 'wt') as f: 871 f.write('EXAMPLE') 872 ydl = YoutubeDL(params) 873 ydl.add_post_processor(PP()) 874 ydl.post_process(filename, {'filepath': filename}) 875 876 run_pp({'keepvideo': True}, SimplePP) 877 self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename) 878 self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile) 879 os.unlink(filename) 880 os.unlink(audiofile) 881 882 run_pp({'keepvideo': False}, SimplePP) 883 self.assertFalse(os.path.exists(filename), '%s exists' % filename) 884 self.assertTrue(os.path.exists(audiofile), '%s doesn\'t exist' % audiofile) 885 os.unlink(audiofile) 886 887 class ModifierPP(PostProcessor): 888 def run(self, info): 889 with open(info['filepath'], 'wt') as f: 890 f.write('MODIFIED') 891 return [], info 892 893 run_pp({'keepvideo': False}, ModifierPP) 894 self.assertTrue(os.path.exists(filename), '%s doesn\'t exist' % filename) 895 os.unlink(filename) 896 897 def test_match_filter(self): 898 class FilterYDL(YDL): 899 def __init__(self, *args, **kwargs): 900 super(FilterYDL, self).__init__(*args, **kwargs) 901 self.params['simulate'] = True 902 903 def process_info(self, info_dict): 904 super(YDL, self).process_info(info_dict) 905 906 def _match_entry(self, info_dict, incomplete=False): 907 res = super(FilterYDL, self)._match_entry(info_dict, incomplete) 908 if res is None: 909 self.downloaded_info_dicts.append(info_dict) 910 return res 911 912 first = { 913 'id': '1', 914 'url': TEST_URL, 915 'title': 'one', 916 'extractor': 'TEST', 917 'duration': 30, 918 'filesize': 10 * 1024, 919 'playlist_id': '42', 920 'uploader': "變態妍字幕版 太妍 тест", 921 'creator': "тест ' 123 ' тест--", 922 'webpage_url': 'http://example.com/watch?v=shenanigans', 923 } 924 second = { 925 'id': '2', 926 'url': TEST_URL, 927 'title': 'two', 928 'extractor': 'TEST', 929 'duration': 10, 930 'description': 'foo', 931 'filesize': 5 * 1024, 932 'playlist_id': '43', 933 'uploader': "тест 123", 934 'webpage_url': 'http://example.com/watch?v=SHENANIGANS', 935 } 936 videos = [first, second] 937 938 def get_videos(filter_=None): 939 ydl = FilterYDL({'match_filter': filter_}) 940 for v in videos: 941 ydl.process_ie_result(v, download=True) 942 return [v['id'] for v in ydl.downloaded_info_dicts] 943 944 res = get_videos() 945 self.assertEqual(res, ['1', '2']) 946 947 def f(v): 948 if v['id'] == '1': 949 return None 950 else: 951 return 'Video id is not 1' 952 res = get_videos(f) 953 self.assertEqual(res, ['1']) 954 955 f = match_filter_func('duration < 30') 956 res = get_videos(f) 957 self.assertEqual(res, ['2']) 958 959 f = match_filter_func('description = foo') 960 res = get_videos(f) 961 self.assertEqual(res, ['2']) 962 963 f = match_filter_func('description =? foo') 964 res = get_videos(f) 965 self.assertEqual(res, ['1', '2']) 966 967 f = match_filter_func('filesize > 5KiB') 968 res = get_videos(f) 969 self.assertEqual(res, ['1']) 970 971 f = match_filter_func('playlist_id = 42') 972 res = get_videos(f) 973 self.assertEqual(res, ['1']) 974 975 f = match_filter_func('uploader = "變態妍字幕版 太妍 тест"') 976 res = get_videos(f) 977 self.assertEqual(res, ['1']) 978 979 f = match_filter_func('uploader != "變態妍字幕版 太妍 тест"') 980 res = get_videos(f) 981 self.assertEqual(res, ['2']) 982 983 f = match_filter_func('creator = "тест \' 123 \' тест--"') 984 res = get_videos(f) 985 self.assertEqual(res, ['1']) 986 987 f = match_filter_func("creator = 'тест \\' 123 \\' тест--'") 988 res = get_videos(f) 989 self.assertEqual(res, ['1']) 990 991 f = match_filter_func(r"creator = 'тест \' 123 \' тест--' & duration > 30") 992 res = get_videos(f) 993 self.assertEqual(res, []) 994 995 def test_playlist_items_selection(self): 996 entries = [{ 997 'id': compat_str(i), 998 'title': compat_str(i), 999 'url': TEST_URL, 1000 } for i in range(1, 5)] 1001 playlist = { 1002 '_type': 'playlist', 1003 'id': 'test', 1004 'entries': entries, 1005 'extractor': 'test:playlist', 1006 'extractor_key': 'test:playlist', 1007 'webpage_url': 'http://example.com', 1008 } 1009 1010 def get_downloaded_info_dicts(params): 1011 ydl = YDL(params) 1012 # make a deep copy because the dictionary and nested entries 1013 # can be modified 1014 ydl.process_ie_result(copy.deepcopy(playlist)) 1015 return ydl.downloaded_info_dicts 1016 1017 def test_selection(params, expected_ids): 1018 results = [ 1019 (v['playlist_autonumber'] - 1, (int(v['id']), v['playlist_index'])) 1020 for v in get_downloaded_info_dicts(params)] 1021 self.assertEqual(results, list(enumerate(zip(expected_ids, expected_ids)))) 1022 1023 test_selection({}, [1, 2, 3, 4]) 1024 test_selection({'playlistend': 10}, [1, 2, 3, 4]) 1025 test_selection({'playlistend': 2}, [1, 2]) 1026 test_selection({'playliststart': 10}, []) 1027 test_selection({'playliststart': 2}, [2, 3, 4]) 1028 test_selection({'playlist_items': '2-4'}, [2, 3, 4]) 1029 test_selection({'playlist_items': '2,4'}, [2, 4]) 1030 test_selection({'playlist_items': '10'}, []) 1031 test_selection({'playlist_items': '0'}, []) 1032 1033 # Tests for https://github.com/ytdl-org/youtube-dl/issues/10591 1034 test_selection({'playlist_items': '2-4,3-4,3'}, [2, 3, 4]) 1035 test_selection({'playlist_items': '4,2'}, [4, 2]) 1036 1037 # Tests for https://github.com/yt-dlp/yt-dlp/issues/720 1038 # https://github.com/yt-dlp/yt-dlp/issues/302 1039 test_selection({'playlistreverse': True}, [4, 3, 2, 1]) 1040 test_selection({'playliststart': 2, 'playlistreverse': True}, [4, 3, 2]) 1041 test_selection({'playlist_items': '2,4', 'playlistreverse': True}, [4, 2]) 1042 test_selection({'playlist_items': '4,2'}, [4, 2]) 1043 1044 def test_urlopen_no_file_protocol(self): 1045 # see https://github.com/ytdl-org/youtube-dl/issues/8227 1046 ydl = YDL() 1047 self.assertRaises(compat_urllib_error.URLError, ydl.urlopen, 'file:///etc/passwd') 1048 1049 def test_do_not_override_ie_key_in_url_transparent(self): 1050 ydl = YDL() 1051 1052 class Foo1IE(InfoExtractor): 1053 _VALID_URL = r'foo1:' 1054 1055 def _real_extract(self, url): 1056 return { 1057 '_type': 'url_transparent', 1058 'url': 'foo2:', 1059 'ie_key': 'Foo2', 1060 'title': 'foo1 title', 1061 'id': 'foo1_id', 1062 } 1063 1064 class Foo2IE(InfoExtractor): 1065 _VALID_URL = r'foo2:' 1066 1067 def _real_extract(self, url): 1068 return { 1069 '_type': 'url', 1070 'url': 'foo3:', 1071 'ie_key': 'Foo3', 1072 } 1073 1074 class Foo3IE(InfoExtractor): 1075 _VALID_URL = r'foo3:' 1076 1077 def _real_extract(self, url): 1078 return _make_result([{'url': TEST_URL}], title='foo3 title') 1079 1080 ydl.add_info_extractor(Foo1IE(ydl)) 1081 ydl.add_info_extractor(Foo2IE(ydl)) 1082 ydl.add_info_extractor(Foo3IE(ydl)) 1083 ydl.extract_info('foo1:') 1084 downloaded = ydl.downloaded_info_dicts[0] 1085 self.assertEqual(downloaded['url'], TEST_URL) 1086 self.assertEqual(downloaded['title'], 'foo1 title') 1087 self.assertEqual(downloaded['id'], 'testid') 1088 self.assertEqual(downloaded['extractor'], 'testex') 1089 self.assertEqual(downloaded['extractor_key'], 'TestEx') 1090 1091 # Test case for https://github.com/ytdl-org/youtube-dl/issues/27064 1092 def test_ignoreerrors_for_playlist_with_url_transparent_iterable_entries(self): 1093 1094 class _YDL(YDL): 1095 def __init__(self, *args, **kwargs): 1096 super(_YDL, self).__init__(*args, **kwargs) 1097 1098 def trouble(self, s, tb=None): 1099 pass 1100 1101 ydl = _YDL({ 1102 'format': 'extra', 1103 'ignoreerrors': True, 1104 }) 1105 1106 class VideoIE(InfoExtractor): 1107 _VALID_URL = r'video:(?P<id>\d+)' 1108 1109 def _real_extract(self, url): 1110 video_id = self._match_id(url) 1111 formats = [{ 1112 'format_id': 'default', 1113 'url': 'url:', 1114 }] 1115 if video_id == '0': 1116 raise ExtractorError('foo') 1117 if video_id == '2': 1118 formats.append({ 1119 'format_id': 'extra', 1120 'url': TEST_URL, 1121 }) 1122 return { 1123 'id': video_id, 1124 'title': 'Video %s' % video_id, 1125 'formats': formats, 1126 } 1127 1128 class PlaylistIE(InfoExtractor): 1129 _VALID_URL = r'playlist:' 1130 1131 def _entries(self): 1132 for n in range(3): 1133 video_id = compat_str(n) 1134 yield { 1135 '_type': 'url_transparent', 1136 'ie_key': VideoIE.ie_key(), 1137 'id': video_id, 1138 'url': 'video:%s' % video_id, 1139 'title': 'Video Transparent %s' % video_id, 1140 } 1141 1142 def _real_extract(self, url): 1143 return self.playlist_result(self._entries()) 1144 1145 ydl.add_info_extractor(VideoIE(ydl)) 1146 ydl.add_info_extractor(PlaylistIE(ydl)) 1147 info = ydl.extract_info('playlist:') 1148 entries = info['entries'] 1149 self.assertEqual(len(entries), 3) 1150 self.assertTrue(entries[0] is None) 1151 self.assertTrue(entries[1] is None) 1152 self.assertEqual(len(ydl.downloaded_info_dicts), 1) 1153 downloaded = ydl.downloaded_info_dicts[0] 1154 self.assertEqual(entries[2], downloaded) 1155 self.assertEqual(downloaded['url'], TEST_URL) 1156 self.assertEqual(downloaded['title'], 'Video Transparent 2') 1157 self.assertEqual(downloaded['id'], '2') 1158 self.assertEqual(downloaded['extractor'], 'Video') 1159 self.assertEqual(downloaded['extractor_key'], 'Video') 1160 1161 1162if __name__ == '__main__': 1163 unittest.main() 1164