1#!/usr/bin/env python3 2# coding: utf-8 3 4f'You are using an unsupported version of Python. Only Python versions 3.6 and above are supported by yt-dlp' # noqa: F541 5 6__license__ = 'Public Domain' 7 8import codecs 9import io 10import itertools 11import os 12import random 13import re 14import sys 15 16from .options import ( 17 parseOpts, 18) 19from .compat import ( 20 compat_getpass, 21 compat_os_name, 22 compat_shlex_quote, 23 workaround_optparse_bug9161, 24) 25from .cookies import SUPPORTED_BROWSERS, SUPPORTED_KEYRINGS 26from .utils import ( 27 DateRange, 28 decodeOption, 29 DownloadCancelled, 30 DownloadError, 31 error_to_compat_str, 32 expand_path, 33 GeoUtils, 34 float_or_none, 35 int_or_none, 36 match_filter_func, 37 parse_duration, 38 preferredencoding, 39 read_batch_urls, 40 render_table, 41 SameFileError, 42 setproctitle, 43 std_headers, 44 write_string, 45) 46from .update import run_update 47from .downloader import ( 48 FileDownloader, 49) 50from .extractor import gen_extractors, list_extractors 51from .extractor.common import InfoExtractor 52from .extractor.adobepass import MSO_INFO 53from .postprocessor import ( 54 FFmpegExtractAudioPP, 55 FFmpegSubtitlesConvertorPP, 56 FFmpegThumbnailsConvertorPP, 57 FFmpegVideoConvertorPP, 58 FFmpegVideoRemuxerPP, 59 MetadataFromFieldPP, 60 MetadataParserPP, 61) 62from .YoutubeDL import YoutubeDL 63 64 65def _real_main(argv=None): 66 # Compatibility fixes for Windows 67 if sys.platform == 'win32': 68 # https://github.com/ytdl-org/youtube-dl/issues/820 69 codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None) 70 71 workaround_optparse_bug9161() 72 73 setproctitle('yt-dlp') 74 75 parser, opts, args = parseOpts(argv) 76 warnings, deprecation_warnings = [], [] 77 78 # Set user agent 79 if opts.user_agent is not None: 80 std_headers['User-Agent'] = opts.user_agent 81 82 # Set referer 83 if opts.referer is not None: 84 std_headers['Referer'] = opts.referer 85 86 # Custom HTTP headers 87 std_headers.update(opts.headers) 88 89 # Dump user agent 90 if opts.dump_user_agent: 91 write_string(std_headers['User-Agent'] + '\n', out=sys.stdout) 92 sys.exit(0) 93 94 # Batch file verification 95 batch_urls = [] 96 if opts.batchfile is not None: 97 try: 98 if opts.batchfile == '-': 99 write_string('Reading URLs from stdin - EOF (%s) to end:\n' % ( 100 'Ctrl+Z' if compat_os_name == 'nt' else 'Ctrl+D')) 101 batchfd = sys.stdin 102 else: 103 batchfd = io.open( 104 expand_path(opts.batchfile), 105 'r', encoding='utf-8', errors='ignore') 106 batch_urls = read_batch_urls(batchfd) 107 if opts.verbose: 108 write_string('[debug] Batch file urls: ' + repr(batch_urls) + '\n') 109 except IOError: 110 sys.exit('ERROR: batch file %s could not be read' % opts.batchfile) 111 all_urls = batch_urls + [url.strip() for url in args] # batch_urls are already striped in read_batch_urls 112 _enc = preferredencoding() 113 all_urls = [url.decode(_enc, 'ignore') if isinstance(url, bytes) else url for url in all_urls] 114 115 if opts.list_extractors: 116 for ie in list_extractors(opts.age_limit): 117 write_string(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie.working() else '') + '\n', out=sys.stdout) 118 matchedUrls = [url for url in all_urls if ie.suitable(url)] 119 for mu in matchedUrls: 120 write_string(' ' + mu + '\n', out=sys.stdout) 121 sys.exit(0) 122 if opts.list_extractor_descriptions: 123 for ie in list_extractors(opts.age_limit): 124 if not ie.working(): 125 continue 126 desc = getattr(ie, 'IE_DESC', ie.IE_NAME) 127 if desc is False: 128 continue 129 if getattr(ie, 'SEARCH_KEY', None) is not None: 130 _SEARCHES = ('cute kittens', 'slithering pythons', 'falling cat', 'angry poodle', 'purple fish', 'running tortoise', 'sleeping bunny', 'burping cow') 131 _COUNTS = ('', '5', '10', 'all') 132 desc += f'; "{ie.SEARCH_KEY}:" prefix (Example: "{ie.SEARCH_KEY}{random.choice(_COUNTS)}:{random.choice(_SEARCHES)}")' 133 write_string(desc + '\n', out=sys.stdout) 134 sys.exit(0) 135 if opts.ap_list_mso: 136 table = [[mso_id, mso_info['name']] for mso_id, mso_info in MSO_INFO.items()] 137 write_string('Supported TV Providers:\n' + render_table(['mso', 'mso name'], table) + '\n', out=sys.stdout) 138 sys.exit(0) 139 140 # Conflicting, missing and erroneous options 141 if opts.format == 'best': 142 warnings.append('.\n '.join(( 143 '"-f best" selects the best pre-merged format which is often not the best option', 144 'To let yt-dlp download and merge the best available formats, simply do not pass any format selection', 145 'If you know what you are doing and want only the best pre-merged format, use "-f b" instead to suppress this warning'))) 146 if opts.usenetrc and (opts.username is not None or opts.password is not None): 147 parser.error('using .netrc conflicts with giving username/password') 148 if opts.password is not None and opts.username is None: 149 parser.error('account username missing\n') 150 if opts.ap_password is not None and opts.ap_username is None: 151 parser.error('TV Provider account username missing\n') 152 if opts.autonumber_size is not None: 153 if opts.autonumber_size <= 0: 154 parser.error('auto number size must be positive') 155 if opts.autonumber_start is not None: 156 if opts.autonumber_start < 0: 157 parser.error('auto number start must be positive or 0') 158 if opts.username is not None and opts.password is None: 159 opts.password = compat_getpass('Type account password and press [Return]: ') 160 if opts.ap_username is not None and opts.ap_password is None: 161 opts.ap_password = compat_getpass('Type TV provider account password and press [Return]: ') 162 if opts.ratelimit is not None: 163 numeric_limit = FileDownloader.parse_bytes(opts.ratelimit) 164 if numeric_limit is None: 165 parser.error('invalid rate limit specified') 166 opts.ratelimit = numeric_limit 167 if opts.throttledratelimit is not None: 168 numeric_limit = FileDownloader.parse_bytes(opts.throttledratelimit) 169 if numeric_limit is None: 170 parser.error('invalid rate limit specified') 171 opts.throttledratelimit = numeric_limit 172 if opts.min_filesize is not None: 173 numeric_limit = FileDownloader.parse_bytes(opts.min_filesize) 174 if numeric_limit is None: 175 parser.error('invalid min_filesize specified') 176 opts.min_filesize = numeric_limit 177 if opts.max_filesize is not None: 178 numeric_limit = FileDownloader.parse_bytes(opts.max_filesize) 179 if numeric_limit is None: 180 parser.error('invalid max_filesize specified') 181 opts.max_filesize = numeric_limit 182 if opts.sleep_interval is not None: 183 if opts.sleep_interval < 0: 184 parser.error('sleep interval must be positive or 0') 185 if opts.max_sleep_interval is not None: 186 if opts.max_sleep_interval < 0: 187 parser.error('max sleep interval must be positive or 0') 188 if opts.sleep_interval is None: 189 parser.error('min sleep interval must be specified, use --min-sleep-interval') 190 if opts.max_sleep_interval < opts.sleep_interval: 191 parser.error('max sleep interval must be greater than or equal to min sleep interval') 192 else: 193 opts.max_sleep_interval = opts.sleep_interval 194 if opts.sleep_interval_subtitles is not None: 195 if opts.sleep_interval_subtitles < 0: 196 parser.error('subtitles sleep interval must be positive or 0') 197 if opts.sleep_interval_requests is not None: 198 if opts.sleep_interval_requests < 0: 199 parser.error('requests sleep interval must be positive or 0') 200 if opts.ap_mso and opts.ap_mso not in MSO_INFO: 201 parser.error('Unsupported TV Provider, use --ap-list-mso to get a list of supported TV Providers') 202 if opts.overwrites: # --yes-overwrites implies --no-continue 203 opts.continue_dl = False 204 if opts.concurrent_fragment_downloads <= 0: 205 parser.error('Concurrent fragments must be positive') 206 if opts.wait_for_video is not None: 207 min_wait, max_wait, *_ = map(parse_duration, opts.wait_for_video.split('-', 1) + [None]) 208 if min_wait is None or (max_wait is None and '-' in opts.wait_for_video): 209 parser.error('Invalid time range to wait') 210 elif max_wait is not None and max_wait < min_wait: 211 parser.error('Minimum time range to wait must not be longer than the maximum') 212 opts.wait_for_video = (min_wait, max_wait) 213 214 def parse_retries(retries, name=''): 215 if retries in ('inf', 'infinite'): 216 parsed_retries = float('inf') 217 else: 218 try: 219 parsed_retries = int(retries) 220 except (TypeError, ValueError): 221 parser.error('invalid %sretry count specified' % name) 222 return parsed_retries 223 if opts.retries is not None: 224 opts.retries = parse_retries(opts.retries) 225 if opts.file_access_retries is not None: 226 opts.file_access_retries = parse_retries(opts.file_access_retries, 'file access ') 227 if opts.fragment_retries is not None: 228 opts.fragment_retries = parse_retries(opts.fragment_retries, 'fragment ') 229 if opts.extractor_retries is not None: 230 opts.extractor_retries = parse_retries(opts.extractor_retries, 'extractor ') 231 if opts.buffersize is not None: 232 numeric_buffersize = FileDownloader.parse_bytes(opts.buffersize) 233 if numeric_buffersize is None: 234 parser.error('invalid buffer size specified') 235 opts.buffersize = numeric_buffersize 236 if opts.http_chunk_size is not None: 237 numeric_chunksize = FileDownloader.parse_bytes(opts.http_chunk_size) 238 if not numeric_chunksize: 239 parser.error('invalid http chunk size specified') 240 opts.http_chunk_size = numeric_chunksize 241 if opts.playliststart <= 0: 242 raise parser.error('Playlist start must be positive') 243 if opts.playlistend not in (-1, None) and opts.playlistend < opts.playliststart: 244 raise parser.error('Playlist end must be greater than playlist start') 245 if opts.extractaudio: 246 opts.audioformat = opts.audioformat.lower() 247 if opts.audioformat not in ['best'] + list(FFmpegExtractAudioPP.SUPPORTED_EXTS): 248 parser.error('invalid audio format specified') 249 if opts.audioquality: 250 opts.audioquality = opts.audioquality.strip('k').strip('K') 251 audioquality = int_or_none(float_or_none(opts.audioquality)) # int_or_none prevents inf, nan 252 if audioquality is None or audioquality < 0: 253 parser.error('invalid audio quality specified') 254 if opts.recodevideo is not None: 255 opts.recodevideo = opts.recodevideo.replace(' ', '') 256 if not re.match(FFmpegVideoConvertorPP.FORMAT_RE, opts.recodevideo): 257 parser.error('invalid video remux format specified') 258 if opts.remuxvideo is not None: 259 opts.remuxvideo = opts.remuxvideo.replace(' ', '') 260 if not re.match(FFmpegVideoRemuxerPP.FORMAT_RE, opts.remuxvideo): 261 parser.error('invalid video remux format specified') 262 if opts.convertsubtitles is not None: 263 if opts.convertsubtitles not in FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS: 264 parser.error('invalid subtitle format specified') 265 if opts.convertthumbnails is not None: 266 if opts.convertthumbnails not in FFmpegThumbnailsConvertorPP.SUPPORTED_EXTS: 267 parser.error('invalid thumbnail format specified') 268 if opts.cookiesfrombrowser is not None: 269 mobj = re.match(r'(?P<name>[^+:]+)(\s*\+\s*(?P<keyring>[^:]+))?(\s*:(?P<profile>.+))?', opts.cookiesfrombrowser) 270 if mobj is None: 271 parser.error(f'invalid cookies from browser arguments: {opts.cookiesfrombrowser}') 272 browser_name, keyring, profile = mobj.group('name', 'keyring', 'profile') 273 browser_name = browser_name.lower() 274 if browser_name not in SUPPORTED_BROWSERS: 275 parser.error(f'unsupported browser specified for cookies: "{browser_name}". ' 276 f'Supported browsers are: {", ".join(sorted(SUPPORTED_BROWSERS))}') 277 if keyring is not None: 278 keyring = keyring.upper() 279 if keyring not in SUPPORTED_KEYRINGS: 280 parser.error(f'unsupported keyring specified for cookies: "{keyring}". ' 281 f'Supported keyrings are: {", ".join(sorted(SUPPORTED_KEYRINGS))}') 282 opts.cookiesfrombrowser = (browser_name, profile, keyring) 283 geo_bypass_code = opts.geo_bypass_ip_block or opts.geo_bypass_country 284 if geo_bypass_code is not None: 285 try: 286 GeoUtils.random_ipv4(geo_bypass_code) 287 except Exception: 288 parser.error('unsupported geo-bypass country or ip-block') 289 290 if opts.date is not None: 291 date = DateRange.day(opts.date) 292 else: 293 date = DateRange(opts.dateafter, opts.datebefore) 294 295 compat_opts = opts.compat_opts 296 297 def report_conflict(arg1, arg2): 298 warnings.append(f'{arg2} is ignored since {arg1} was given') 299 300 def _unused_compat_opt(name): 301 if name not in compat_opts: 302 return False 303 compat_opts.discard(name) 304 compat_opts.update(['*%s' % name]) 305 return True 306 307 def set_default_compat(compat_name, opt_name, default=True, remove_compat=True): 308 attr = getattr(opts, opt_name) 309 if compat_name in compat_opts: 310 if attr is None: 311 setattr(opts, opt_name, not default) 312 return True 313 else: 314 if remove_compat: 315 _unused_compat_opt(compat_name) 316 return False 317 elif attr is None: 318 setattr(opts, opt_name, default) 319 return None 320 321 set_default_compat('abort-on-error', 'ignoreerrors', 'only_download') 322 set_default_compat('no-playlist-metafiles', 'allow_playlist_files') 323 set_default_compat('no-clean-infojson', 'clean_infojson') 324 if 'no-attach-info-json' in compat_opts: 325 if opts.embed_infojson: 326 _unused_compat_opt('no-attach-info-json') 327 else: 328 opts.embed_infojson = False 329 if 'format-sort' in compat_opts: 330 opts.format_sort.extend(InfoExtractor.FormatSort.ytdl_default) 331 _video_multistreams_set = set_default_compat('multistreams', 'allow_multiple_video_streams', False, remove_compat=False) 332 _audio_multistreams_set = set_default_compat('multistreams', 'allow_multiple_audio_streams', False, remove_compat=False) 333 if _video_multistreams_set is False and _audio_multistreams_set is False: 334 _unused_compat_opt('multistreams') 335 outtmpl_default = opts.outtmpl.get('default') 336 if opts.useid: 337 if outtmpl_default is None: 338 outtmpl_default = opts.outtmpl['default'] = '%(id)s.%(ext)s' 339 else: 340 report_conflict('--output', '--id') 341 if 'filename' in compat_opts: 342 if outtmpl_default is None: 343 outtmpl_default = opts.outtmpl['default'] = '%(title)s-%(id)s.%(ext)s' 344 else: 345 _unused_compat_opt('filename') 346 347 def validate_outtmpl(tmpl, msg): 348 err = YoutubeDL.validate_outtmpl(tmpl) 349 if err: 350 parser.error('invalid %s %r: %s' % (msg, tmpl, error_to_compat_str(err))) 351 352 for k, tmpl in opts.outtmpl.items(): 353 validate_outtmpl(tmpl, f'{k} output template') 354 opts.forceprint = opts.forceprint or [] 355 for tmpl in opts.forceprint or []: 356 validate_outtmpl(tmpl, 'print template') 357 validate_outtmpl(opts.sponsorblock_chapter_title, 'SponsorBlock chapter title') 358 for k, tmpl in opts.progress_template.items(): 359 k = f'{k[:-6]} console title' if '-title' in k else f'{k} progress' 360 validate_outtmpl(tmpl, f'{k} template') 361 362 if opts.extractaudio and not opts.keepvideo and opts.format is None: 363 opts.format = 'bestaudio/best' 364 365 if outtmpl_default is not None and not os.path.splitext(outtmpl_default)[1] and opts.extractaudio: 366 parser.error('Cannot download a video and extract audio into the same' 367 ' file! Use "{0}.%(ext)s" instead of "{0}" as the output' 368 ' template'.format(outtmpl_default)) 369 370 for f in opts.format_sort: 371 if re.match(InfoExtractor.FormatSort.regex, f) is None: 372 parser.error('invalid format sort string "%s" specified' % f) 373 374 def metadataparser_actions(f): 375 if isinstance(f, str): 376 cmd = '--parse-metadata %s' % compat_shlex_quote(f) 377 try: 378 actions = [MetadataFromFieldPP.to_action(f)] 379 except Exception as err: 380 parser.error(f'{cmd} is invalid; {err}') 381 else: 382 cmd = '--replace-in-metadata %s' % ' '.join(map(compat_shlex_quote, f)) 383 actions = ((MetadataParserPP.Actions.REPLACE, x, *f[1:]) for x in f[0].split(',')) 384 385 for action in actions: 386 try: 387 MetadataParserPP.validate_action(*action) 388 except Exception as err: 389 parser.error(f'{cmd} is invalid; {err}') 390 yield action 391 392 if opts.parse_metadata is None: 393 opts.parse_metadata = [] 394 if opts.metafromtitle is not None: 395 opts.parse_metadata.append('title:%s' % opts.metafromtitle) 396 opts.parse_metadata = list(itertools.chain(*map(metadataparser_actions, opts.parse_metadata))) 397 398 any_getting = opts.forceprint or opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration or opts.dumpjson or opts.dump_single_json 399 any_printing = opts.print_json 400 download_archive_fn = expand_path(opts.download_archive) if opts.download_archive is not None else opts.download_archive 401 402 # If JSON is not printed anywhere, but comments are requested, save it to file 403 printing_json = opts.dumpjson or opts.print_json or opts.dump_single_json 404 if opts.getcomments and not printing_json: 405 opts.writeinfojson = True 406 407 if opts.no_sponsorblock: 408 opts.sponsorblock_mark = set() 409 opts.sponsorblock_remove = set() 410 sponsorblock_query = opts.sponsorblock_mark | opts.sponsorblock_remove 411 412 opts.remove_chapters = opts.remove_chapters or [] 413 414 if (opts.remove_chapters or sponsorblock_query) and opts.sponskrub is not False: 415 if opts.sponskrub: 416 if opts.remove_chapters: 417 report_conflict('--remove-chapters', '--sponskrub') 418 if opts.sponsorblock_mark: 419 report_conflict('--sponsorblock-mark', '--sponskrub') 420 if opts.sponsorblock_remove: 421 report_conflict('--sponsorblock-remove', '--sponskrub') 422 opts.sponskrub = False 423 if opts.sponskrub_cut and opts.split_chapters and opts.sponskrub is not False: 424 report_conflict('--split-chapter', '--sponskrub-cut') 425 opts.sponskrub_cut = False 426 427 if opts.remuxvideo and opts.recodevideo: 428 report_conflict('--recode-video', '--remux-video') 429 opts.remuxvideo = False 430 431 if opts.allow_unplayable_formats: 432 def report_unplayable_conflict(opt_name, arg, default=False, allowed=None): 433 val = getattr(opts, opt_name) 434 if (not allowed and val) or (allowed and not allowed(val)): 435 report_conflict('--allow-unplayable-formats', arg) 436 setattr(opts, opt_name, default) 437 438 report_unplayable_conflict('extractaudio', '--extract-audio') 439 report_unplayable_conflict('remuxvideo', '--remux-video') 440 report_unplayable_conflict('recodevideo', '--recode-video') 441 report_unplayable_conflict('addmetadata', '--embed-metadata') 442 report_unplayable_conflict('addchapters', '--embed-chapters') 443 report_unplayable_conflict('embed_infojson', '--embed-info-json') 444 opts.embed_infojson = False 445 report_unplayable_conflict('embedsubtitles', '--embed-subs') 446 report_unplayable_conflict('embedthumbnail', '--embed-thumbnail') 447 report_unplayable_conflict('xattrs', '--xattrs') 448 report_unplayable_conflict('fixup', '--fixup', default='never', allowed=lambda x: x in (None, 'never', 'ignore')) 449 opts.fixup = 'never' 450 report_unplayable_conflict('remove_chapters', '--remove-chapters', default=[]) 451 report_unplayable_conflict('sponsorblock_remove', '--sponsorblock-remove', default=set()) 452 report_unplayable_conflict('sponskrub', '--sponskrub', default=set()) 453 opts.sponskrub = False 454 455 if (opts.addmetadata or opts.sponsorblock_mark) and opts.addchapters is None: 456 opts.addchapters = True 457 458 # PostProcessors 459 postprocessors = list(opts.add_postprocessors) 460 if sponsorblock_query: 461 postprocessors.append({ 462 'key': 'SponsorBlock', 463 'categories': sponsorblock_query, 464 'api': opts.sponsorblock_api, 465 # Run this immediately after extraction is complete 466 'when': 'pre_process' 467 }) 468 if opts.parse_metadata: 469 postprocessors.append({ 470 'key': 'MetadataParser', 471 'actions': opts.parse_metadata, 472 # Run this immediately after extraction is complete 473 'when': 'pre_process' 474 }) 475 if opts.convertsubtitles: 476 postprocessors.append({ 477 'key': 'FFmpegSubtitlesConvertor', 478 'format': opts.convertsubtitles, 479 # Run this before the actual video download 480 'when': 'before_dl' 481 }) 482 if opts.convertthumbnails: 483 postprocessors.append({ 484 'key': 'FFmpegThumbnailsConvertor', 485 'format': opts.convertthumbnails, 486 # Run this before the actual video download 487 'when': 'before_dl' 488 }) 489 # Must be after all other before_dl 490 if opts.exec_before_dl_cmd: 491 postprocessors.append({ 492 'key': 'Exec', 493 'exec_cmd': opts.exec_before_dl_cmd, 494 'when': 'before_dl' 495 }) 496 if opts.extractaudio: 497 postprocessors.append({ 498 'key': 'FFmpegExtractAudio', 499 'preferredcodec': opts.audioformat, 500 'preferredquality': opts.audioquality, 501 'nopostoverwrites': opts.nopostoverwrites, 502 }) 503 if opts.remuxvideo: 504 postprocessors.append({ 505 'key': 'FFmpegVideoRemuxer', 506 'preferedformat': opts.remuxvideo, 507 }) 508 if opts.recodevideo: 509 postprocessors.append({ 510 'key': 'FFmpegVideoConvertor', 511 'preferedformat': opts.recodevideo, 512 }) 513 # If ModifyChapters is going to remove chapters, subtitles must already be in the container. 514 if opts.embedsubtitles: 515 already_have_subtitle = opts.writesubtitles and 'no-keep-subs' not in compat_opts 516 postprocessors.append({ 517 'key': 'FFmpegEmbedSubtitle', 518 # already_have_subtitle = True prevents the file from being deleted after embedding 519 'already_have_subtitle': already_have_subtitle 520 }) 521 if not opts.writeautomaticsub and 'no-keep-subs' not in compat_opts: 522 opts.writesubtitles = True 523 # --all-sub automatically sets --write-sub if --write-auto-sub is not given 524 # this was the old behaviour if only --all-sub was given. 525 if opts.allsubtitles and not opts.writeautomaticsub: 526 opts.writesubtitles = True 527 # ModifyChapters must run before FFmpegMetadataPP 528 remove_chapters_patterns, remove_ranges = [], [] 529 for regex in opts.remove_chapters: 530 if regex.startswith('*'): 531 dur = list(map(parse_duration, regex[1:].split('-'))) 532 if len(dur) == 2 and all(t is not None for t in dur): 533 remove_ranges.append(tuple(dur)) 534 continue 535 parser.error(f'invalid --remove-chapters time range {regex!r}. Must be of the form *start-end') 536 try: 537 remove_chapters_patterns.append(re.compile(regex)) 538 except re.error as err: 539 parser.error(f'invalid --remove-chapters regex {regex!r} - {err}') 540 if opts.remove_chapters or sponsorblock_query: 541 postprocessors.append({ 542 'key': 'ModifyChapters', 543 'remove_chapters_patterns': remove_chapters_patterns, 544 'remove_sponsor_segments': opts.sponsorblock_remove, 545 'remove_ranges': remove_ranges, 546 'sponsorblock_chapter_title': opts.sponsorblock_chapter_title, 547 'force_keyframes': opts.force_keyframes_at_cuts 548 }) 549 # FFmpegMetadataPP should be run after FFmpegVideoConvertorPP and 550 # FFmpegExtractAudioPP as containers before conversion may not support 551 # metadata (3gp, webm, etc.) 552 # By default ffmpeg preserves metadata applicable for both 553 # source and target containers. From this point the container won't change, 554 # so metadata can be added here. 555 if opts.addmetadata or opts.addchapters or opts.embed_infojson: 556 if opts.embed_infojson is None: 557 opts.embed_infojson = 'if_exists' 558 postprocessors.append({ 559 'key': 'FFmpegMetadata', 560 'add_chapters': opts.addchapters, 561 'add_metadata': opts.addmetadata, 562 'add_infojson': opts.embed_infojson, 563 }) 564 # Deprecated 565 # This should be above EmbedThumbnail since sponskrub removes the thumbnail attachment 566 # but must be below EmbedSubtitle and FFmpegMetadata 567 # See https://github.com/yt-dlp/yt-dlp/issues/204 , https://github.com/faissaloo/SponSkrub/issues/29 568 # If opts.sponskrub is None, sponskrub is used, but it silently fails if the executable can't be found 569 if opts.sponskrub is not False: 570 postprocessors.append({ 571 'key': 'SponSkrub', 572 'path': opts.sponskrub_path, 573 'args': opts.sponskrub_args, 574 'cut': opts.sponskrub_cut, 575 'force': opts.sponskrub_force, 576 'ignoreerror': opts.sponskrub is None, 577 '_from_cli': True, 578 }) 579 if opts.embedthumbnail: 580 postprocessors.append({ 581 'key': 'EmbedThumbnail', 582 # already_have_thumbnail = True prevents the file from being deleted after embedding 583 'already_have_thumbnail': opts.writethumbnail 584 }) 585 if not opts.writethumbnail: 586 opts.writethumbnail = True 587 opts.outtmpl['pl_thumbnail'] = '' 588 if opts.split_chapters: 589 postprocessors.append({ 590 'key': 'FFmpegSplitChapters', 591 'force_keyframes': opts.force_keyframes_at_cuts, 592 }) 593 # XAttrMetadataPP should be run after post-processors that may change file contents 594 if opts.xattrs: 595 postprocessors.append({'key': 'XAttrMetadata'}) 596 # Exec must be the last PP 597 if opts.exec_cmd: 598 postprocessors.append({ 599 'key': 'Exec', 600 'exec_cmd': opts.exec_cmd, 601 # Run this only after the files have been moved to their final locations 602 'when': 'after_move' 603 }) 604 605 def report_args_compat(arg, name): 606 warnings.append('%s given without specifying name. The arguments will be given to all %s' % (arg, name)) 607 608 if 'default' in opts.external_downloader_args: 609 report_args_compat('--downloader-args', 'external downloaders') 610 611 if 'default-compat' in opts.postprocessor_args and 'default' not in opts.postprocessor_args: 612 report_args_compat('--post-processor-args', 'post-processors') 613 opts.postprocessor_args.setdefault('sponskrub', []) 614 opts.postprocessor_args['default'] = opts.postprocessor_args['default-compat'] 615 616 def report_deprecation(val, old, new=None): 617 if not val: 618 return 619 deprecation_warnings.append( 620 f'{old} is deprecated and may be removed in a future version. Use {new} instead' if new 621 else f'{old} is deprecated and may not work as expected') 622 623 report_deprecation(opts.sponskrub, '--sponskrub', '--sponsorblock-mark or --sponsorblock-remove') 624 report_deprecation(not opts.prefer_ffmpeg, '--prefer-avconv', 'ffmpeg') 625 report_deprecation(opts.include_ads, '--include-ads') 626 # report_deprecation(opts.call_home, '--call-home') # We may re-implement this in future 627 # report_deprecation(opts.writeannotations, '--write-annotations') # It's just that no website has it 628 629 final_ext = ( 630 opts.recodevideo if opts.recodevideo in FFmpegVideoConvertorPP.SUPPORTED_EXTS 631 else opts.remuxvideo if opts.remuxvideo in FFmpegVideoRemuxerPP.SUPPORTED_EXTS 632 else opts.audioformat if (opts.extractaudio and opts.audioformat != 'best') 633 else None) 634 635 match_filter = ( 636 None if opts.match_filter is None 637 else match_filter_func(opts.match_filter)) 638 639 ydl_opts = { 640 'usenetrc': opts.usenetrc, 641 'netrc_location': opts.netrc_location, 642 'username': opts.username, 643 'password': opts.password, 644 'twofactor': opts.twofactor, 645 'videopassword': opts.videopassword, 646 'ap_mso': opts.ap_mso, 647 'ap_username': opts.ap_username, 648 'ap_password': opts.ap_password, 649 'quiet': (opts.quiet or any_getting or any_printing), 650 'no_warnings': opts.no_warnings, 651 'forceurl': opts.geturl, 652 'forcetitle': opts.gettitle, 653 'forceid': opts.getid, 654 'forcethumbnail': opts.getthumbnail, 655 'forcedescription': opts.getdescription, 656 'forceduration': opts.getduration, 657 'forcefilename': opts.getfilename, 658 'forceformat': opts.getformat, 659 'forceprint': opts.forceprint, 660 'forcejson': opts.dumpjson or opts.print_json, 661 'dump_single_json': opts.dump_single_json, 662 'force_write_download_archive': opts.force_write_download_archive, 663 'simulate': (any_getting or None) if opts.simulate is None else opts.simulate, 664 'skip_download': opts.skip_download, 665 'format': opts.format, 666 'allow_unplayable_formats': opts.allow_unplayable_formats, 667 'ignore_no_formats_error': opts.ignore_no_formats_error, 668 'format_sort': opts.format_sort, 669 'format_sort_force': opts.format_sort_force, 670 'allow_multiple_video_streams': opts.allow_multiple_video_streams, 671 'allow_multiple_audio_streams': opts.allow_multiple_audio_streams, 672 'check_formats': opts.check_formats, 673 'listformats': opts.listformats, 674 'listformats_table': opts.listformats_table, 675 'outtmpl': opts.outtmpl, 676 'outtmpl_na_placeholder': opts.outtmpl_na_placeholder, 677 'paths': opts.paths, 678 'autonumber_size': opts.autonumber_size, 679 'autonumber_start': opts.autonumber_start, 680 'restrictfilenames': opts.restrictfilenames, 681 'windowsfilenames': opts.windowsfilenames, 682 'ignoreerrors': opts.ignoreerrors, 683 'force_generic_extractor': opts.force_generic_extractor, 684 'ratelimit': opts.ratelimit, 685 'throttledratelimit': opts.throttledratelimit, 686 'overwrites': opts.overwrites, 687 'retries': opts.retries, 688 'file_access_retries': opts.file_access_retries, 689 'fragment_retries': opts.fragment_retries, 690 'extractor_retries': opts.extractor_retries, 691 'skip_unavailable_fragments': opts.skip_unavailable_fragments, 692 'keep_fragments': opts.keep_fragments, 693 'concurrent_fragment_downloads': opts.concurrent_fragment_downloads, 694 'buffersize': opts.buffersize, 695 'noresizebuffer': opts.noresizebuffer, 696 'http_chunk_size': opts.http_chunk_size, 697 'continuedl': opts.continue_dl, 698 'noprogress': opts.quiet if opts.noprogress is None else opts.noprogress, 699 'progress_with_newline': opts.progress_with_newline, 700 'progress_template': opts.progress_template, 701 'playliststart': opts.playliststart, 702 'playlistend': opts.playlistend, 703 'playlistreverse': opts.playlist_reverse, 704 'playlistrandom': opts.playlist_random, 705 'noplaylist': opts.noplaylist, 706 'logtostderr': outtmpl_default == '-', 707 'consoletitle': opts.consoletitle, 708 'nopart': opts.nopart, 709 'updatetime': opts.updatetime, 710 'writedescription': opts.writedescription, 711 'writeannotations': opts.writeannotations, 712 'writeinfojson': opts.writeinfojson, 713 'allow_playlist_files': opts.allow_playlist_files, 714 'clean_infojson': opts.clean_infojson, 715 'getcomments': opts.getcomments, 716 'writethumbnail': opts.writethumbnail is True, 717 'write_all_thumbnails': opts.writethumbnail == 'all', 718 'writelink': opts.writelink, 719 'writeurllink': opts.writeurllink, 720 'writewebloclink': opts.writewebloclink, 721 'writedesktoplink': opts.writedesktoplink, 722 'writesubtitles': opts.writesubtitles, 723 'writeautomaticsub': opts.writeautomaticsub, 724 'allsubtitles': opts.allsubtitles, 725 'listsubtitles': opts.listsubtitles, 726 'subtitlesformat': opts.subtitlesformat, 727 'subtitleslangs': opts.subtitleslangs, 728 'matchtitle': decodeOption(opts.matchtitle), 729 'rejecttitle': decodeOption(opts.rejecttitle), 730 'max_downloads': opts.max_downloads, 731 'prefer_free_formats': opts.prefer_free_formats, 732 'trim_file_name': opts.trim_file_name, 733 'verbose': opts.verbose, 734 'dump_intermediate_pages': opts.dump_intermediate_pages, 735 'write_pages': opts.write_pages, 736 'test': opts.test, 737 'keepvideo': opts.keepvideo, 738 'min_filesize': opts.min_filesize, 739 'max_filesize': opts.max_filesize, 740 'min_views': opts.min_views, 741 'max_views': opts.max_views, 742 'daterange': date, 743 'cachedir': opts.cachedir, 744 'youtube_print_sig_code': opts.youtube_print_sig_code, 745 'age_limit': opts.age_limit, 746 'download_archive': download_archive_fn, 747 'break_on_existing': opts.break_on_existing, 748 'break_on_reject': opts.break_on_reject, 749 'break_per_url': opts.break_per_url, 750 'skip_playlist_after_errors': opts.skip_playlist_after_errors, 751 'cookiefile': opts.cookiefile, 752 'cookiesfrombrowser': opts.cookiesfrombrowser, 753 'nocheckcertificate': opts.no_check_certificate, 754 'prefer_insecure': opts.prefer_insecure, 755 'proxy': opts.proxy, 756 'socket_timeout': opts.socket_timeout, 757 'bidi_workaround': opts.bidi_workaround, 758 'debug_printtraffic': opts.debug_printtraffic, 759 'prefer_ffmpeg': opts.prefer_ffmpeg, 760 'include_ads': opts.include_ads, 761 'default_search': opts.default_search, 762 'dynamic_mpd': opts.dynamic_mpd, 763 'extractor_args': opts.extractor_args, 764 'youtube_include_dash_manifest': opts.youtube_include_dash_manifest, 765 'youtube_include_hls_manifest': opts.youtube_include_hls_manifest, 766 'encoding': opts.encoding, 767 'extract_flat': opts.extract_flat, 768 'live_from_start': opts.live_from_start, 769 'wait_for_video': opts.wait_for_video, 770 'mark_watched': opts.mark_watched, 771 'merge_output_format': opts.merge_output_format, 772 'final_ext': final_ext, 773 'postprocessors': postprocessors, 774 'fixup': opts.fixup, 775 'source_address': opts.source_address, 776 'call_home': opts.call_home, 777 'sleep_interval_requests': opts.sleep_interval_requests, 778 'sleep_interval': opts.sleep_interval, 779 'max_sleep_interval': opts.max_sleep_interval, 780 'sleep_interval_subtitles': opts.sleep_interval_subtitles, 781 'external_downloader': opts.external_downloader, 782 'list_thumbnails': opts.list_thumbnails, 783 'playlist_items': opts.playlist_items, 784 'xattr_set_filesize': opts.xattr_set_filesize, 785 'match_filter': match_filter, 786 'no_color': opts.no_color, 787 'ffmpeg_location': opts.ffmpeg_location, 788 'hls_prefer_native': opts.hls_prefer_native, 789 'hls_use_mpegts': opts.hls_use_mpegts, 790 'hls_split_discontinuity': opts.hls_split_discontinuity, 791 'external_downloader_args': opts.external_downloader_args, 792 'postprocessor_args': opts.postprocessor_args, 793 'cn_verification_proxy': opts.cn_verification_proxy, 794 'geo_verification_proxy': opts.geo_verification_proxy, 795 'geo_bypass': opts.geo_bypass, 796 'geo_bypass_country': opts.geo_bypass_country, 797 'geo_bypass_ip_block': opts.geo_bypass_ip_block, 798 '_warnings': warnings, 799 '_deprecation_warnings': deprecation_warnings, 800 'compat_opts': compat_opts, 801 } 802 803 with YoutubeDL(ydl_opts) as ydl: 804 actual_use = all_urls or opts.load_info_filename 805 806 # Remove cache dir 807 if opts.rm_cachedir: 808 ydl.cache.remove() 809 810 # Maybe do nothing 811 if not actual_use: 812 if opts.rm_cachedir: 813 sys.exit() 814 815 ydl.warn_if_short_id(sys.argv[1:] if argv is None else argv) 816 parser.error( 817 'You must provide at least one URL.\n' 818 'Type yt-dlp --help to see a list of all options.') 819 820 try: 821 if opts.load_info_filename is not None: 822 retcode = ydl.download_with_info_file(expand_path(opts.load_info_filename)) 823 else: 824 retcode = ydl.download(all_urls) 825 except DownloadCancelled: 826 ydl.to_screen('Aborting remaining downloads') 827 retcode = 101 828 829 sys.exit(retcode) 830 831 832def main(argv=None): 833 try: 834 _real_main(argv) 835 except DownloadError: 836 sys.exit(1) 837 except SameFileError as e: 838 sys.exit(f'ERROR: {e}') 839 except KeyboardInterrupt: 840 sys.exit('\nERROR: Interrupted by user') 841 except BrokenPipeError as e: 842 # https://docs.python.org/3/library/signal.html#note-on-sigpipe 843 devnull = os.open(os.devnull, os.O_WRONLY) 844 os.dup2(devnull, sys.stdout.fileno()) 845 sys.exit(f'\nERROR: {e}') 846 847 848__all__ = ['main', 'YoutubeDL', 'gen_extractors', 'list_extractors'] 849