]> jfr.im git - yt-dlp.git/blame - yt_dlp/__init__.py
[update] Implement `--update-to` repo
[yt-dlp.git] / yt_dlp / __init__.py
CommitLineData
0647d925 1try:
2 import contextvars # noqa: F401
3except Exception:
4 raise Exception(
5 f'You are using an unsupported version of Python. Only Python versions 3.7 and above are supported by yt-dlp') # noqa: F541
a4bc4336 6
235b3ba4 7__license__ = 'Public Domain'
235b3ba4 8
f2df4071 9import collections
ac668111 10import getpass
e9f4ccd1 11import itertools
9e491463 12import optparse
235b3ba4 13import os
43820c03 14import re
235b3ba4 15import sys
d2e84d5e 16import traceback
235b3ba4 17
ac668111 18from .compat import compat_shlex_quote
f8271158 19from .cookies import SUPPORTED_BROWSERS, SUPPORTED_KEYRINGS
7b2c3f47 20from .downloader.external import get_external_downloader
560738f3 21from .extractor import list_extractor_classes
f8271158 22from .extractor.adobepass import MSO_INFO
d1b5f70b 23from .options import parseOpts
f8271158 24from .postprocessor import (
25 FFmpegExtractAudioPP,
4f04be6a 26 FFmpegMergerPP,
8dc59305 27 FFmpegPostProcessor,
f8271158 28 FFmpegSubtitlesConvertorPP,
29 FFmpegThumbnailsConvertorPP,
30 FFmpegVideoConvertorPP,
31 FFmpegVideoRemuxerPP,
32 MetadataFromFieldPP,
33 MetadataParserPP,
8c25f81b 34)
8372be74 35from .update import Updater
8c25f81b 36from .utils import (
f8271158 37 NO_DEFAULT,
62f6f1cb 38 POSTPROCESS_WHEN,
a4fd0415 39 DateRange,
f304da8a 40 DownloadCancelled,
a4fd0415 41 DownloadError,
d0d74b71 42 FormatSorter,
f8271158 43 GeoUtils,
7e88d7d7 44 PlaylistEntries,
f8271158 45 SameFileError,
46 decodeOption,
5ec1b6b7 47 download_range_func,
590bc6f6 48 expand_path,
31c49255 49 float_or_none,
7b2c3f47 50 format_field,
31c49255 51 int_or_none,
347de493 52 match_filter_func,
64c464a1 53 parse_bytes,
2d9ec704 54 parse_duration,
a4fd0415 55 preferredencoding,
62e609ab 56 read_batch_urls,
6b9e832d 57 read_stdin,
df692c5a 58 render_table,
e3946f98 59 setproctitle,
a4fd0415 60 std_headers,
8b7539d2 61 traverse_obj,
9e491463 62 variadic,
a4fd0415 63 write_string,
a4fd0415 64)
8222d8de 65from .YoutubeDL import YoutubeDL
a4fd0415 66
da4db748 67_IN_CLI = False
68
235b3ba4 69
9e491463 70def _exit(status=0, *args):
71 for msg in args:
72 sys.stderr.write(msg)
73 raise SystemExit(status)
74
75
d1b5f70b 76def get_urls(urls, batchfile, verbose):
59ae15a5 77 # Batch file verification
62e609ab 78 batch_urls = []
d1b5f70b 79 if batchfile is not None:
59ae15a5 80 try:
6b9e832d 81 batch_urls = read_batch_urls(
82 read_stdin('URLs') if batchfile == '-'
83 else open(expand_path(batchfile), encoding='utf-8', errors='ignore'))
d1b5f70b 84 if verbose:
a4bc4336 85 write_string('[debug] Batch file urls: ' + repr(batch_urls) + '\n')
86e5f3ed 86 except OSError:
9e491463 87 _exit(f'ERROR: batch file {batchfile} could not be read')
c774b3c6 88 _enc = preferredencoding()
d1b5f70b 89 return [
90 url.strip().decode(_enc, 'ignore') if isinstance(url, bytes) else url.strip()
91 for url in batch_urls + urls]
59ae15a5 92
d1b5f70b 93
94def print_extractor_information(opts, urls):
8dcce6a8 95 out = ''
59ae15a5 96 if opts.list_extractors:
71df9b7f 97 # Importing GenericIE is currently slow since it imports YoutubeIE
98 from .extractor.generic import GenericIE
99
82d02080 100 urls = dict.fromkeys(urls, False)
101 for ie in list_extractor_classes(opts.age_limit):
8dcce6a8 102 out += ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie.working() else '') + '\n'
82d02080 103 if ie == GenericIE:
104 matched_urls = [url for url, matched in urls.items() if not matched]
105 else:
106 matched_urls = tuple(filter(ie.suitable, urls.keys()))
107 urls.update(dict.fromkeys(matched_urls, True))
108 out += ''.join(f' {url}\n' for url in matched_urls)
d1b5f70b 109 elif opts.list_extractor_descriptions:
8dcce6a8 110 _SEARCHES = ('cute kittens', 'slithering pythons', 'falling cat', 'angry poodle', 'purple fish', 'running tortoise', 'sleeping bunny', 'burping cow')
111 out = '\n'.join(
112 ie.description(markdown=False, search_examples=_SEARCHES)
82d02080 113 for ie in list_extractor_classes(opts.age_limit) if ie.working() and ie.IE_DESC is not False)
d1b5f70b 114 elif opts.ap_list_mso:
8dcce6a8 115 out = 'Supported TV Providers:\n%s\n' % render_table(
116 ['mso', 'mso name'],
117 [[mso_id, mso_info['name']] for mso_id, mso_info in MSO_INFO.items()])
bd558525 118 else:
d1b5f70b 119 return False
8dcce6a8 120 write_string(out, out=sys.stdout)
d1b5f70b 121 return True
53ed7066 122
19b824f6 123
d1b5f70b 124def set_compat_opts(opts):
53ed7066 125 def _unused_compat_opt(name):
d1b5f70b 126 if name not in opts.compat_opts:
53ed7066 127 return False
d1b5f70b 128 opts.compat_opts.discard(name)
129 opts.compat_opts.update(['*%s' % name])
53ed7066 130 return True
131
e4f02757 132 def set_default_compat(compat_name, opt_name, default=True, remove_compat=True):
53ed7066 133 attr = getattr(opts, opt_name)
d1b5f70b 134 if compat_name in opts.compat_opts:
53ed7066 135 if attr is None:
136 setattr(opts, opt_name, not default)
137 return True
138 else:
139 if remove_compat:
140 _unused_compat_opt(compat_name)
141 return False
142 elif attr is None:
143 setattr(opts, opt_name, default)
144 return None
145
b1940459 146 set_default_compat('abort-on-error', 'ignoreerrors', 'only_download')
53ed7066 147 set_default_compat('no-playlist-metafiles', 'allow_playlist_files')
e4f02757 148 set_default_compat('no-clean-infojson', 'clean_infojson')
d1b5f70b 149 if 'no-attach-info-json' in opts.compat_opts:
dac5df5a 150 if opts.embed_infojson:
151 _unused_compat_opt('no-attach-info-json')
152 else:
153 opts.embed_infojson = False
d1b5f70b 154 if 'format-sort' in opts.compat_opts:
d0d74b71 155 opts.format_sort.extend(FormatSorter.ytdl_default)
53ed7066 156 _video_multistreams_set = set_default_compat('multistreams', 'allow_multiple_video_streams', False, remove_compat=False)
157 _audio_multistreams_set = set_default_compat('multistreams', 'allow_multiple_audio_streams', False, remove_compat=False)
158 if _video_multistreams_set is False and _audio_multistreams_set is False:
159 _unused_compat_opt('multistreams')
d1b5f70b 160 if 'filename' in opts.compat_opts:
161 if opts.outtmpl.get('default') is None:
162 opts.outtmpl.update({'default': '%(title)s-%(id)s.%(ext)s'})
53ed7066 163 else:
164 _unused_compat_opt('filename')
165
d1b5f70b 166
167def validate_options(opts):
168 def validate(cndn, name, value=None, msg=None):
169 if cndn:
170 return True
171 raise ValueError((msg or 'invalid {name} "{value}" given').format(name=name, value=value))
172
173 def validate_in(name, value, items, msg=None):
174 return validate(value is None or value in items, name, value, msg)
175
176 def validate_regex(name, value, regex):
177 return validate(value is None or re.match(regex, value), name, value)
178
179 def validate_positive(name, value, strict=False):
180 return validate(value is None or value > 0 or (not strict and value == 0),
181 name, value, '{name} "{value}" must be positive' + ('' if strict else ' or 0'))
182
183 def validate_minmax(min_val, max_val, min_name, max_name=None):
184 if max_val is None or min_val is None or max_val >= min_val:
185 return
186 if not max_name:
187 min_name, max_name = f'min {min_name}', f'max {min_name}'
188 raise ValueError(f'{max_name} "{max_val}" must be must be greater than or equal to {min_name} "{min_val}"')
189
190 # Usernames and passwords
191 validate(not opts.usenetrc or (opts.username is None and opts.password is None),
192 '.netrc', msg='using {name} conflicts with giving username/password')
193 validate(opts.password is None or opts.username is not None, 'account username', msg='{name} missing')
194 validate(opts.ap_password is None or opts.ap_username is not None,
195 'TV Provider account username', msg='{name} missing')
196 validate_in('TV Provider', opts.ap_mso, MSO_INFO,
197 'Unsupported {name} "{value}", use --ap-list-mso to get a list of supported TV Providers')
198
199 # Numbers
200 validate_positive('autonumber start', opts.autonumber_start)
201 validate_positive('autonumber size', opts.autonumber_size, True)
202 validate_positive('concurrent fragments', opts.concurrent_fragment_downloads, True)
203 validate_positive('playlist start', opts.playliststart, True)
204 if opts.playlistend != -1:
205 validate_minmax(opts.playliststart, opts.playlistend, 'playlist start', 'playlist end')
206
207 # Time ranges
208 validate_positive('subtitles sleep interval', opts.sleep_interval_subtitles)
209 validate_positive('requests sleep interval', opts.sleep_interval_requests)
210 validate_positive('sleep interval', opts.sleep_interval)
211 validate_positive('max sleep interval', opts.max_sleep_interval)
07ff290d 212 if opts.sleep_interval is None:
d1b5f70b 213 validate(
07ff290d 214 opts.max_sleep_interval is None, 'min sleep interval',
d1b5f70b 215 msg='{name} must be specified; use --min-sleep-interval')
07ff290d 216 elif opts.max_sleep_interval is None:
217 opts.max_sleep_interval = opts.sleep_interval
218 else:
d1b5f70b 219 validate_minmax(opts.sleep_interval, opts.max_sleep_interval, 'sleep interval')
220
221 if opts.wait_for_video is not None:
222 min_wait, max_wait, *_ = map(parse_duration, opts.wait_for_video.split('-', 1) + [None])
223 validate(min_wait is not None and not (max_wait is None and '-' in opts.wait_for_video),
224 'time range to wait for video', opts.wait_for_video)
225 validate_minmax(min_wait, max_wait, 'time range to wait for video')
226 opts.wait_for_video = (min_wait, max_wait)
227
228 # Format sort
229 for f in opts.format_sort:
d0d74b71 230 validate_regex('format sorting', f, FormatSorter.regex)
d1b5f70b 231
232 # Postprocessor formats
fc61aff4
LL
233 validate_regex('merge output format', opts.merge_output_format,
234 r'({0})(/({0}))*'.format('|'.join(map(re.escape, FFmpegMergerPP.SUPPORTED_EXTS))))
e0ab9854 235 validate_regex('audio format', opts.audioformat, FFmpegExtractAudioPP.FORMAT_RE)
d1b5f70b 236 validate_in('subtitle format', opts.convertsubtitles, FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS)
35faefee 237 validate_regex('thumbnail format', opts.convertthumbnails, FFmpegThumbnailsConvertorPP.FORMAT_RE)
238 validate_regex('recode video format', opts.recodevideo, FFmpegVideoConvertorPP.FORMAT_RE)
239 validate_regex('remux video format', opts.remuxvideo, FFmpegVideoRemuxerPP.FORMAT_RE)
d1b5f70b 240 if opts.audioquality:
241 opts.audioquality = opts.audioquality.strip('k').strip('K')
242 # int_or_none prevents inf, nan
243 validate_positive('audio quality', int_or_none(float_or_none(opts.audioquality), default=0))
244
245 # Retries
246 def parse_retries(name, value):
247 if value is None:
248 return None
249 elif value in ('inf', 'infinite'):
250 return float('inf')
251 try:
252 return int(value)
253 except (TypeError, ValueError):
254 validate(False, f'{name} retry count', value)
255
256 opts.retries = parse_retries('download', opts.retries)
257 opts.fragment_retries = parse_retries('fragment', opts.fragment_retries)
258 opts.extractor_retries = parse_retries('extractor', opts.extractor_retries)
259 opts.file_access_retries = parse_retries('file access', opts.file_access_retries)
260
23326151 261 # Retry sleep function
262 def parse_sleep_func(expr):
263 NUMBER_RE = r'\d+(?:\.\d+)?'
264 op, start, limit, step, *_ = tuple(re.fullmatch(
c4a62b99 265 rf'(?:(linear|exp)=)?({NUMBER_RE})(?::({NUMBER_RE})?)?(?::({NUMBER_RE}))?',
23326151 266 expr.strip()).groups()) + (None, None)
267
268 if op == 'exp':
269 return lambda n: min(float(start) * (float(step or 2) ** n), float(limit or 'inf'))
270 else:
271 default_step = start if op or limit else 0
272 return lambda n: min(float(start) + float(step or default_step) * n, float(limit or 'inf'))
273
274 for key, expr in opts.retry_sleep.items():
275 if not expr:
276 del opts.retry_sleep[key]
277 continue
278 try:
279 opts.retry_sleep[key] = parse_sleep_func(expr)
c4a62b99 280 except AttributeError:
281 raise ValueError(f'invalid {key} retry sleep expression {expr!r}')
23326151 282
d1b5f70b 283 # Bytes
64c464a1 284 def validate_bytes(name, value):
d1b5f70b 285 if value is None:
286 return None
64c464a1 287 numeric_limit = parse_bytes(value)
d1b5f70b 288 validate(numeric_limit is not None, 'rate limit', value)
289 return numeric_limit
290
64c464a1 291 opts.ratelimit = validate_bytes('rate limit', opts.ratelimit)
292 opts.throttledratelimit = validate_bytes('throttled rate limit', opts.throttledratelimit)
293 opts.min_filesize = validate_bytes('min filesize', opts.min_filesize)
294 opts.max_filesize = validate_bytes('max filesize', opts.max_filesize)
295 opts.buffersize = validate_bytes('buffer size', opts.buffersize)
296 opts.http_chunk_size = validate_bytes('http chunk size', opts.http_chunk_size)
d1b5f70b 297
298 # Output templates
76a264ac 299 def validate_outtmpl(tmpl, msg):
300 err = YoutubeDL.validate_outtmpl(tmpl)
301 if err:
d1b5f70b 302 raise ValueError(f'invalid {msg} "{tmpl}": {err}')
76a264ac 303
304 for k, tmpl in opts.outtmpl.items():
819e0531 305 validate_outtmpl(tmpl, f'{k} output template')
ca30f449 306 for type_, tmpl_list in opts.forceprint.items():
307 for tmpl in tmpl_list:
308 validate_outtmpl(tmpl, f'{type_} print template')
bb66c247 309 for type_, tmpl_list in opts.print_to_file.items():
310 for tmpl, file in tmpl_list:
d1b5f70b 311 validate_outtmpl(tmpl, f'{type_} print to file template')
312 validate_outtmpl(file, f'{type_} print to file filename')
7a340e0d 313 validate_outtmpl(opts.sponsorblock_chapter_title, 'SponsorBlock chapter title')
819e0531 314 for k, tmpl in opts.progress_template.items():
315 k = f'{k[:-6]} console title' if '-title' in k else f'{k} progress'
316 validate_outtmpl(tmpl, f'{k} template')
76a264ac 317
d1b5f70b 318 outtmpl_default = opts.outtmpl.get('default')
319 if outtmpl_default == '':
320 opts.skip_download = None
321 del opts.outtmpl['default']
d1b5f70b 322
5ec1b6b7 323 def parse_chapters(name, value):
324 chapters, ranges = [], []
fc2ba496 325 parse_timestamp = lambda x: float('inf') if x in ('inf', 'infinite') else parse_duration(x)
5ec1b6b7 326 for regex in value or []:
327 if regex.startswith('*'):
fc2ba496
L
328 for range_ in map(str.strip, regex[1:].split(',')):
329 mobj = range_ != '-' and re.fullmatch(r'([^-]+)?\s*-\s*([^-]+)?', range_)
330 dur = mobj and (parse_timestamp(mobj.group(1) or '0'), parse_timestamp(mobj.group(2) or 'inf'))
331 if None in (dur or [None]):
08e29b9f 332 raise ValueError(f'invalid {name} time range "{regex}". Must be of the form "*start-end"')
fc2ba496 333 ranges.append(dur)
d1b5f70b 334 continue
5ec1b6b7 335 try:
336 chapters.append(re.compile(regex))
337 except re.error as err:
338 raise ValueError(f'invalid {name} regex "{regex}" - {err}')
339 return chapters, ranges
340
341 opts.remove_chapters, opts.remove_ranges = parse_chapters('--remove-chapters', opts.remove_chapters)
342 opts.download_ranges = download_range_func(*parse_chapters('--download-sections', opts.download_ranges))
0202b52a 343
d1b5f70b 344 # Cookies from browser
345 if opts.cookiesfrombrowser:
9bd13fe5 346 container = None
825d3ce3 347 mobj = re.fullmatch(r'''(?x)
348 (?P<name>[^+:]+)
349 (?:\s*\+\s*(?P<keyring>[^:]+))?
935bac1e 350 (?:\s*:\s*(?!:)(?P<profile>.+?))?
825d3ce3 351 (?:\s*::\s*(?P<container>.+))?
352 ''', opts.cookiesfrombrowser)
d1b5f70b 353 if mobj is None:
354 raise ValueError(f'invalid cookies from browser arguments: {opts.cookiesfrombrowser}')
825d3ce3 355 browser_name, keyring, profile, container = mobj.group('name', 'keyring', 'profile', 'container')
d1b5f70b 356 browser_name = browser_name.lower()
357 if browser_name not in SUPPORTED_BROWSERS:
358 raise ValueError(f'unsupported browser specified for cookies: "{browser_name}". '
359 f'Supported browsers are: {", ".join(sorted(SUPPORTED_BROWSERS))}')
360 if keyring is not None:
361 keyring = keyring.upper()
362 if keyring not in SUPPORTED_KEYRINGS:
363 raise ValueError(f'unsupported keyring specified for cookies: "{keyring}". '
364 f'Supported keyrings are: {", ".join(sorted(SUPPORTED_KEYRINGS))}')
d2c8aadf 365 opts.cookiesfrombrowser = (browser_name, profile, keyring, container)
29c7a63d 366
d1b5f70b 367 # MetadataParser
e9f4ccd1 368 def metadataparser_actions(f):
369 if isinstance(f, str):
370 cmd = '--parse-metadata %s' % compat_shlex_quote(f)
371 try:
372 actions = [MetadataFromFieldPP.to_action(f)]
373 except Exception as err:
d1b5f70b 374 raise ValueError(f'{cmd} is invalid; {err}')
e9f4ccd1 375 else:
376 cmd = '--replace-in-metadata %s' % ' '.join(map(compat_shlex_quote, f))
377 actions = ((MetadataParserPP.Actions.REPLACE, x, *f[1:]) for x in f[0].split(','))
378
379 for action in actions:
380 try:
381 MetadataParserPP.validate_action(*action)
382 except Exception as err:
d1b5f70b 383 raise ValueError(f'{cmd} is invalid; {err}')
e9f4ccd1 384 yield action
385
5bfa4862 386 if opts.metafromtitle is not None:
fe74d5b5 387 opts.parse_metadata.setdefault('pre_process', []).append('title:%s' % opts.metafromtitle)
388 opts.parse_metadata = {
389 k: list(itertools.chain(*map(metadataparser_actions, v)))
390 for k, v in opts.parse_metadata.items()
391 }
5bfa4862 392
d1b5f70b 393 # Other options
7e88d7d7 394 if opts.playlist_items is not None:
395 try:
396 tuple(PlaylistEntries.parse_playlist_items(opts.playlist_items))
397 except Exception as err:
398 raise ValueError(f'Invalid playlist-items {opts.playlist_items!r}: {err}')
399
c1664464 400 opts.geo_bypass_country, opts.geo_bypass_ip_block = None, None
401 if opts.geo_bypass.lower() not in ('default', 'never'):
d1b5f70b 402 try:
c1664464 403 GeoUtils.random_ipv4(opts.geo_bypass)
d1b5f70b 404 except Exception:
c1664464 405 raise ValueError(f'Unsupported --xff "{opts.geo_bypass}"')
406 if len(opts.geo_bypass) == 2:
407 opts.geo_bypass_country = opts.geo_bypass
408 else:
409 opts.geo_bypass_ip_block = opts.geo_bypass
410 opts.geo_bypass = opts.geo_bypass.lower() != 'never'
ca30f449 411
fe2ce85a 412 opts.match_filter = match_filter_func(opts.match_filter, opts.breaking_match_filter)
525ef922 413
d1b5f70b 414 if opts.download_archive is not None:
415 opts.download_archive = expand_path(opts.download_archive)
416
2b24afa6 417 if opts.ffmpeg_location is not None:
418 opts.ffmpeg_location = expand_path(opts.ffmpeg_location)
419
d1b5f70b 420 if opts.user_agent is not None:
421 opts.headers.setdefault('User-Agent', opts.user_agent)
422 if opts.referer is not None:
423 opts.headers.setdefault('Referer', opts.referer)
f0884c8b 424
7a340e0d 425 if opts.no_sponsorblock:
d1b5f70b 426 opts.sponsorblock_mark = opts.sponsorblock_remove = set()
427
6d1b3489 428 default_downloader = None
7b2c3f47 429 for proto, path in opts.external_downloader.items():
28163422 430 if path == 'native':
431 continue
6d1b3489 432 ed = get_external_downloader(path)
433 if ed is None:
7b2c3f47 434 raise ValueError(
435 f'No such {format_field(proto, None, "%s ", ignore="default")}external downloader "{path}"')
6d1b3489 436 elif ed and proto == 'default':
437 default_downloader = ed.get_basename()
438
d1b5f70b 439 warnings, deprecation_warnings = [], []
7a340e0d 440
d1b5f70b 441 # Common mistake: -f best
442 if opts.format == 'best':
443 warnings.append('.\n '.join((
444 '"-f best" selects the best pre-merged format which is often not the best option',
445 'To let yt-dlp download and merge the best available formats, simply do not pass any format selection',
446 'If you know what you are doing and want only the best pre-merged format, use "-f b" instead to suppress this warning')))
447
a44ca5a4 448 # --(postprocessor/downloader)-args without name
6d1b3489 449 def report_args_compat(name, value, key1, key2=None, where=None):
d1b5f70b 450 if key1 in value and key2 not in value:
6d1b3489 451 warnings.append(f'{name.title()} arguments given without specifying name. '
452 f'The arguments will be given to {where or f"all {name}s"}')
d1b5f70b 453 return True
454 return False
455
6d1b3489 456 if report_args_compat('external downloader', opts.external_downloader_args,
457 'default', where=default_downloader) and default_downloader:
458 # Compat with youtube-dl's behavior. See https://github.com/ytdl-org/youtube-dl/commit/49c5293014bc11ec8c009856cd63cffa6296c1e1
459 opts.external_downloader_args.setdefault(default_downloader, opts.external_downloader_args.pop('default'))
460
d1b5f70b 461 if report_args_compat('post-processor', opts.postprocessor_args, 'default-compat', 'default'):
462 opts.postprocessor_args['default'] = opts.postprocessor_args.pop('default-compat')
463 opts.postprocessor_args.setdefault('sponskrub', [])
464
465 def report_conflict(arg1, opt1, arg2='--allow-unplayable-formats', opt2='allow_unplayable_formats',
466 val1=NO_DEFAULT, val2=NO_DEFAULT, default=False):
467 if val2 is NO_DEFAULT:
468 val2 = getattr(opts, opt2)
469 if not val2:
470 return
471
472 if val1 is NO_DEFAULT:
473 val1 = getattr(opts, opt1)
474 if val1:
475 warnings.append(f'{arg1} is ignored since {arg2} was given')
476 setattr(opts, opt1, default)
477
478 # Conflicting options
7e9a6125 479 report_conflict('--playlist-reverse', 'playlist_reverse', '--playlist-random', 'playlist_random')
480 report_conflict('--playlist-reverse', 'playlist_reverse', '--lazy-playlist', 'lazy_playlist')
481 report_conflict('--playlist-random', 'playlist_random', '--lazy-playlist', 'lazy_playlist')
da1d734f 482 report_conflict('--dateafter', 'dateafter', '--date', 'date', default=None)
483 report_conflict('--datebefore', 'datebefore', '--date', 'date', default=None)
3d3bb168 484 report_conflict('--exec-before-download', 'exec_before_dl_cmd',
485 '"--exec before_dl:"', 'exec_cmd', val2=opts.exec_cmd.get('before_dl'))
d1b5f70b 486 report_conflict('--id', 'useid', '--output', 'outtmpl', val2=opts.outtmpl.get('default'))
487 report_conflict('--remux-video', 'remuxvideo', '--recode-video', 'recodevideo')
488 report_conflict('--sponskrub', 'sponskrub', '--remove-chapters', 'remove_chapters')
489 report_conflict('--sponskrub', 'sponskrub', '--sponsorblock-mark', 'sponsorblock_mark')
490 report_conflict('--sponskrub', 'sponskrub', '--sponsorblock-remove', 'sponsorblock_remove')
19a03940 491 report_conflict('--sponskrub-cut', 'sponskrub_cut', '--split-chapter', 'split_chapters',
492 val1=opts.sponskrub and opts.sponskrub_cut)
d1b5f70b 493
494 # Conflicts with --allow-unplayable-formats
2fa669f7 495 report_conflict('--embed-metadata', 'addmetadata')
d1b5f70b 496 report_conflict('--embed-chapters', 'addchapters')
497 report_conflict('--embed-info-json', 'embed_infojson')
498 report_conflict('--embed-subs', 'embedsubtitles')
499 report_conflict('--embed-thumbnail', 'embedthumbnail')
500 report_conflict('--extract-audio', 'extractaudio')
3d3bb168 501 report_conflict('--fixup', 'fixup', val1=opts.fixup not in (None, 'never', 'ignore'), default='never')
d1b5f70b 502 report_conflict('--recode-video', 'recodevideo')
503 report_conflict('--remove-chapters', 'remove_chapters', default=[])
504 report_conflict('--remux-video', 'remuxvideo')
505 report_conflict('--sponskrub', 'sponskrub')
506 report_conflict('--sponsorblock-remove', 'sponsorblock_remove', default=set())
507 report_conflict('--xattrs', 'xattrs')
508
509 # Fully deprecated options
510 def report_deprecation(val, old, new=None):
511 if not val:
512 return
513 deprecation_warnings.append(
514 f'{old} is deprecated and may be removed in a future version. Use {new} instead' if new
515 else f'{old} is deprecated and may not work as expected')
516
517 report_deprecation(opts.sponskrub, '--sponskrub', '--sponsorblock-mark or --sponsorblock-remove')
518 report_deprecation(not opts.prefer_ffmpeg, '--prefer-avconv', 'ffmpeg')
519 # report_deprecation(opts.include_ads, '--include-ads') # We may re-implement this in future
520 # report_deprecation(opts.call_home, '--call-home') # We may re-implement this in future
521 # report_deprecation(opts.writeannotations, '--write-annotations') # It's just that no website has it
522
523 # Dependent options
da1d734f 524 opts.date = DateRange.day(opts.date) if opts.date else DateRange(opts.dateafter, opts.datebefore)
525
d1b5f70b 526 if opts.exec_before_dl_cmd:
527 opts.exec_cmd['before_dl'] = opts.exec_before_dl_cmd
528
529 if opts.useid: # --id is not deprecated in youtube-dl
530 opts.outtmpl['default'] = '%(id)s.%(ext)s'
531
532 if opts.overwrites: # --force-overwrites implies --no-continue
533 opts.continue_dl = False
63ad4d43 534
9222c381 535 if (opts.addmetadata or opts.sponsorblock_mark) and opts.addchapters is None:
d1b5f70b 536 # Add chapters when adding metadata or marking sponsors
9222c381 537 opts.addchapters = True
538
d1b5f70b 539 if opts.extractaudio and not opts.keepvideo and opts.format is None:
540 # Do not unnecessarily download audio
541 opts.format = 'bestaudio/best'
542
f2df4071 543 if opts.getcomments and opts.writeinfojson is None and not opts.embed_infojson:
d1b5f70b 544 # If JSON is not printed anywhere, but comments are requested, save it to file
545 if not opts.dumpjson or opts.print_json or opts.dump_single_json:
546 opts.writeinfojson = True
547
548 if opts.allsubtitles and not (opts.embedsubtitles or opts.writeautomaticsub):
549 # --all-sub automatically sets --write-sub if --write-auto-sub is not given
550 opts.writesubtitles = True
551
552 if opts.addmetadata and opts.embed_infojson is None:
553 # If embedding metadata and infojson is present, embed it
554 opts.embed_infojson = 'if_exists'
555
556 # Ask for passwords
557 if opts.username is not None and opts.password is None:
ac668111 558 opts.password = getpass.getpass('Type account password and press [Return]: ')
d1b5f70b 559 if opts.ap_username is not None and opts.ap_password is None:
ac668111 560 opts.ap_password = getpass.getpass('Type TV provider account password and press [Return]: ')
d1b5f70b 561
562 return warnings, deprecation_warnings
563
564
565def get_postprocessors(opts):
566 yield from opts.add_postprocessors
567
fe74d5b5 568 for when, actions in opts.parse_metadata.items():
d1b5f70b 569 yield {
570 'key': 'MetadataParser',
fe74d5b5 571 'actions': actions,
572 'when': when
d1b5f70b 573 }
574 sponsorblock_query = opts.sponsorblock_mark | opts.sponsorblock_remove
7a340e0d 575 if sponsorblock_query:
d1b5f70b 576 yield {
7a340e0d
NA
577 'key': 'SponsorBlock',
578 'categories': sponsorblock_query,
579 'api': opts.sponsorblock_api,
09b49e1f 580 'when': 'after_filter'
d1b5f70b 581 }
56d868db 582 if opts.convertsubtitles:
d1b5f70b 583 yield {
56d868db 584 'key': 'FFmpegSubtitlesConvertor',
585 'format': opts.convertsubtitles,
56d868db 586 'when': 'before_dl'
d1b5f70b 587 }
8fa43c73 588 if opts.convertthumbnails:
d1b5f70b 589 yield {
8fa43c73 590 'key': 'FFmpegThumbnailsConvertor',
591 'format': opts.convertthumbnails,
8fa43c73 592 'when': 'before_dl'
d1b5f70b 593 }
4f026faf 594 if opts.extractaudio:
d1b5f70b 595 yield {
4f026faf
PH
596 'key': 'FFmpegExtractAudio',
597 'preferredcodec': opts.audioformat,
598 'preferredquality': opts.audioquality,
599 'nopostoverwrites': opts.nopostoverwrites,
d1b5f70b 600 }
efe87a10 601 if opts.remuxvideo:
d1b5f70b 602 yield {
efe87a10
FS
603 'key': 'FFmpegVideoRemuxer',
604 'preferedformat': opts.remuxvideo,
d1b5f70b 605 }
4f026faf 606 if opts.recodevideo:
d1b5f70b 607 yield {
4f026faf
PH
608 'key': 'FFmpegVideoConvertor',
609 'preferedformat': opts.recodevideo,
d1b5f70b 610 }
7a340e0d 611 # If ModifyChapters is going to remove chapters, subtitles must already be in the container.
4f026faf 612 if opts.embedsubtitles:
d1b5f70b 613 keep_subs = 'no-keep-subs' not in opts.compat_opts
614 yield {
4f026faf 615 'key': 'FFmpegEmbedSubtitle',
56d868db 616 # already_have_subtitle = True prevents the file from being deleted after embedding
d1b5f70b 617 'already_have_subtitle': opts.writesubtitles and keep_subs
618 }
619 if not opts.writeautomaticsub and keep_subs:
cffab0ee 620 opts.writesubtitles = True
d1b5f70b 621
7a340e0d 622 # ModifyChapters must run before FFmpegMetadataPP
7a340e0d 623 if opts.remove_chapters or sponsorblock_query:
d1b5f70b 624 yield {
7a340e0d 625 'key': 'ModifyChapters',
d1b5f70b 626 'remove_chapters_patterns': opts.remove_chapters,
7a340e0d 627 'remove_sponsor_segments': opts.sponsorblock_remove,
d1b5f70b 628 'remove_ranges': opts.remove_ranges,
7a340e0d
NA
629 'sponsorblock_chapter_title': opts.sponsorblock_chapter_title,
630 'force_keyframes': opts.force_keyframes_at_cuts
d1b5f70b 631 }
7a340e0d
NA
632 # FFmpegMetadataPP should be run after FFmpegVideoConvertorPP and
633 # FFmpegExtractAudioPP as containers before conversion may not support
634 # metadata (3gp, webm, etc.)
635 # By default ffmpeg preserves metadata applicable for both
636 # source and target containers. From this point the container won't change,
637 # so metadata can be added here.
dac5df5a 638 if opts.addmetadata or opts.addchapters or opts.embed_infojson:
d1b5f70b 639 yield {
7a340e0d
NA
640 'key': 'FFmpegMetadata',
641 'add_chapters': opts.addchapters,
642 'add_metadata': opts.addmetadata,
dac5df5a 643 'add_infojson': opts.embed_infojson,
d1b5f70b 644 }
ee8dd27a 645 # Deprecated
f4e4be19 646 # This should be above EmbedThumbnail since sponskrub removes the thumbnail attachment
647 # but must be below EmbedSubtitle and FFmpegMetadata
648 # See https://github.com/yt-dlp/yt-dlp/issues/204 , https://github.com/faissaloo/SponSkrub/issues/29
a9e7f546 649 # If opts.sponskrub is None, sponskrub is used, but it silently fails if the executable can't be found
650 if opts.sponskrub is not False:
d1b5f70b 651 yield {
a9e7f546 652 'key': 'SponSkrub',
653 'path': opts.sponskrub_path,
654 'args': opts.sponskrub_args,
655 'cut': opts.sponskrub_cut,
656 'force': opts.sponskrub_force,
657 'ignoreerror': opts.sponskrub is None,
ee8dd27a 658 '_from_cli': True,
d1b5f70b 659 }
f4e4be19 660 if opts.embedthumbnail:
d1b5f70b 661 yield {
f4e4be19 662 'key': 'EmbedThumbnail',
56d868db 663 # already_have_thumbnail = True prevents the file from being deleted after embedding
acc0d6a4 664 'already_have_thumbnail': opts.writethumbnail
d1b5f70b 665 }
acc0d6a4 666 if not opts.writethumbnail:
f4e4be19 667 opts.writethumbnail = True
80c03fa9 668 opts.outtmpl['pl_thumbnail'] = ''
72755351 669 if opts.split_chapters:
d1b5f70b 670 yield {
7a340e0d
NA
671 'key': 'FFmpegSplitChapters',
672 'force_keyframes': opts.force_keyframes_at_cuts,
d1b5f70b 673 }
72755351 674 # XAttrMetadataPP should be run after post-processors that may change file contents
675 if opts.xattrs:
d1b5f70b 676 yield {'key': 'XAttrMetadata'}
3b603dbd 677 if opts.concat_playlist != 'never':
d1b5f70b 678 yield {
3b603dbd 679 'key': 'FFmpegConcat',
680 'only_multi_video': opts.concat_playlist != 'always',
681 'when': 'playlist',
d1b5f70b 682 }
1e43a6f7 683 # Exec must be the last PP of each category
1e43a6f7 684 for when, exec_cmd in opts.exec_cmd.items():
d1b5f70b 685 yield {
ad3dc496 686 'key': 'Exec',
1e43a6f7 687 'exec_cmd': exec_cmd,
1e43a6f7 688 'when': when,
d1b5f70b 689 }
1b77b347 690
0d1bb027 691
f2df4071 692ParsedOptions = collections.namedtuple('ParsedOptions', ('parser', 'options', 'urls', 'ydl_opts'))
693
694
d1b5f70b 695def parse_options(argv=None):
f2df4071 696 """@returns ParsedOptions(parser, opts, urls, ydl_opts)"""
d1b5f70b 697 parser, opts, urls = parseOpts(argv)
698 urls = get_urls(urls, opts.batchfile, opts.verbose)
b8f6bbe6 699
d1b5f70b 700 set_compat_opts(opts)
701 try:
702 warnings, deprecation_warnings = validate_options(opts)
703 except ValueError as err:
704 parser.error(f'{err}\n')
1b77b347 705
d1b5f70b 706 postprocessors = list(get_postprocessors(opts))
ee8dd27a 707
193fb150 708 print_only = bool(opts.forceprint) and all(k not in opts.forceprint for k in POSTPROCESS_WHEN[3:])
62f6f1cb 709 any_getting = any(getattr(opts, k) for k in (
710 'dumpjson', 'dump_single_json', 'getdescription', 'getduration', 'getfilename',
711 'getformat', 'getid', 'getthumbnail', 'gettitle', 'geturl'
712 ))
d669772c 713 if opts.quiet is None:
714 opts.quiet = any_getting or opts.print_json or bool(opts.forceprint)
ee8dd27a 715
134c913c 716 playlist_pps = [pp for pp in postprocessors if pp.get('when') == 'playlist']
717 write_playlist_infojson = (opts.writeinfojson and not opts.clean_infojson
718 and opts.allow_playlist_files and opts.outtmpl.get('pl_infojson') != '')
719 if not any((
720 opts.extract_flat,
721 opts.dump_single_json,
722 opts.forceprint.get('playlist'),
723 opts.print_to_file.get('playlist'),
724 write_playlist_infojson,
725 )):
726 if not playlist_pps:
727 opts.extract_flat = 'discard'
728 elif playlist_pps == [{'key': 'FFmpegConcat', 'only_multi_video': True, 'when': 'playlist'}]:
729 opts.extract_flat = 'discard_in_playlist'
730
df692c5a 731 final_ext = (
81a23040 732 opts.recodevideo if opts.recodevideo in FFmpegVideoConvertorPP.SUPPORTED_EXTS
733 else opts.remuxvideo if opts.remuxvideo in FFmpegVideoRemuxerPP.SUPPORTED_EXTS
35faefee 734 else opts.audioformat if (opts.extractaudio and opts.audioformat in FFmpegExtractAudioPP.SUPPORTED_EXTS)
81a23040 735 else None)
f6d7624f 736
f2df4071 737 return ParsedOptions(parser, opts, urls, {
59ae15a5 738 'usenetrc': opts.usenetrc,
0001fcb5 739 'netrc_location': opts.netrc_location,
59ae15a5
PH
740 'username': opts.username,
741 'password': opts.password,
83317f69 742 'twofactor': opts.twofactor,
c6c19746 743 'videopassword': opts.videopassword,
797c636b 744 'ap_mso': opts.ap_mso,
1b6712ab
RA
745 'ap_username': opts.ap_username,
746 'ap_password': opts.ap_password,
bb58c9ed 747 'client_certificate': opts.client_certificate,
748 'client_certificate_key': opts.client_certificate_key,
749 'client_certificate_password': opts.client_certificate_password,
5712943b 750 'quiet': opts.quiet,
ad8915b7 751 'no_warnings': opts.no_warnings,
59ae15a5
PH
752 'forceurl': opts.geturl,
753 'forcetitle': opts.gettitle,
1a2adf3f 754 'forceid': opts.getid,
59ae15a5
PH
755 'forcethumbnail': opts.getthumbnail,
756 'forcedescription': opts.getdescription,
525ef922 757 'forceduration': opts.getduration,
59ae15a5
PH
758 'forcefilename': opts.getfilename,
759 'forceformat': opts.getformat,
d2a1fad9 760 'forceprint': opts.forceprint,
bb66c247 761 'print_to_file': opts.print_to_file,
c0bdf32a 762 'forcejson': opts.dumpjson or opts.print_json,
63e0be34 763 'dump_single_json': opts.dump_single_json,
2d30509f 764 'force_write_download_archive': opts.force_write_download_archive,
62f6f1cb 765 'simulate': (print_only or any_getting or None) if opts.simulate is None else opts.simulate,
1bdeb7be 766 'skip_download': opts.skip_download,
59ae15a5 767 'format': opts.format,
63ad4d43 768 'allow_unplayable_formats': opts.allow_unplayable_formats,
b7da73eb 769 'ignore_no_formats_error': opts.ignore_no_formats_error,
eb8a4433 770 'format_sort': opts.format_sort,
771 'format_sort_force': opts.format_sort_force,
909d24dd 772 'allow_multiple_video_streams': opts.allow_multiple_video_streams,
773 'allow_multiple_audio_streams': opts.allow_multiple_audio_streams,
e8e73840 774 'check_formats': opts.check_formats,
59ae15a5 775 'listformats': opts.listformats,
76d321f6 776 'listformats_table': opts.listformats_table,
486fb179 777 'outtmpl': opts.outtmpl,
a820dc72 778 'outtmpl_na_placeholder': opts.outtmpl_na_placeholder,
0202b52a 779 'paths': opts.paths,
213c31ae 780 'autonumber_size': opts.autonumber_size,
acbb2374 781 'autonumber_start': opts.autonumber_start,
59ae15a5 782 'restrictfilenames': opts.restrictfilenames,
c2934512 783 'windowsfilenames': opts.windowsfilenames,
59ae15a5 784 'ignoreerrors': opts.ignoreerrors,
d22dec74 785 'force_generic_extractor': opts.force_generic_extractor,
fe7866d0 786 'allowed_extractors': opts.allowed_extractors or ['default'],
59ae15a5 787 'ratelimit': opts.ratelimit,
51d9739f 788 'throttledratelimit': opts.throttledratelimit,
0c3d0f51 789 'overwrites': opts.overwrites,
52bb437e 790 'retries': opts.retries,
205a0654 791 'file_access_retries': opts.file_access_retries,
52bb437e 792 'fragment_retries': opts.fragment_retries,
62bff2c1 793 'extractor_retries': opts.extractor_retries,
23326151 794 'retry_sleep_functions': opts.retry_sleep,
9603b660 795 'skip_unavailable_fragments': opts.skip_unavailable_fragments,
0eee52f3 796 'keep_fragments': opts.keep_fragments,
4cf1e5d2 797 'concurrent_fragment_downloads': opts.concurrent_fragment_downloads,
59ae15a5
PH
798 'buffersize': opts.buffersize,
799 'noresizebuffer': opts.noresizebuffer,
ba515388 800 'http_chunk_size': opts.http_chunk_size,
59ae15a5 801 'continuedl': opts.continue_dl,
819e0531 802 'noprogress': opts.quiet if opts.noprogress is None else opts.noprogress,
5717d91a 803 'progress_with_newline': opts.progress_with_newline,
819e0531 804 'progress_template': opts.progress_template,
59ae15a5
PH
805 'playliststart': opts.playliststart,
806 'playlistend': opts.playlistend,
ff815fe6 807 'playlistreverse': opts.playlist_reverse,
75822ca7 808 'playlistrandom': opts.playlist_random,
7e9a6125 809 'lazy_playlist': opts.lazy_playlist,
47192f92 810 'noplaylist': opts.noplaylist,
d1b5f70b 811 'logtostderr': opts.outtmpl.get('default') == '-',
59ae15a5
PH
812 'consoletitle': opts.consoletitle,
813 'nopart': opts.nopart,
814 'updatetime': opts.updatetime,
815 'writedescription': opts.writedescription,
1fb07d10 816 'writeannotations': opts.writeannotations,
f0884c8b 817 'writeinfojson': opts.writeinfojson,
1ea24129 818 'allow_playlist_files': opts.allow_playlist_files,
75d43ca0 819 'clean_infojson': opts.clean_infojson,
06167fbb 820 'getcomments': opts.getcomments,
acc0d6a4 821 'writethumbnail': opts.writethumbnail is True,
822 'write_all_thumbnails': opts.writethumbnail == 'all',
732044af 823 'writelink': opts.writelink,
824 'writeurllink': opts.writeurllink,
825 'writewebloclink': opts.writewebloclink,
826 'writedesktoplink': opts.writedesktoplink,
59ae15a5 827 'writesubtitles': opts.writesubtitles,
b004821f 828 'writeautomaticsub': opts.writeautomaticsub,
ae608b80 829 'allsubtitles': opts.allsubtitles,
2a4093ea 830 'listsubtitles': opts.listsubtitles,
9e62bc44 831 'subtitlesformat': opts.subtitlesformat,
d6e203b3 832 'subtitleslangs': opts.subtitleslangs,
8271226a
PH
833 'matchtitle': decodeOption(opts.matchtitle),
834 'rejecttitle': decodeOption(opts.rejecttitle),
59ae15a5
PH
835 'max_downloads': opts.max_downloads,
836 'prefer_free_formats': opts.prefer_free_formats,
bdc3fd2f 837 'trim_file_name': opts.trim_file_name,
59ae15a5 838 'verbose': opts.verbose,
855703e5 839 'dump_intermediate_pages': opts.dump_intermediate_pages,
d41e6efc 840 'write_pages': opts.write_pages,
f95b9dee 841 'load_pages': opts.load_pages,
8d5d3a5d 842 'test': opts.test,
7851b379 843 'keepvideo': opts.keepvideo,
9e982f9e 844 'min_filesize': opts.min_filesize,
bd558525 845 'max_filesize': opts.max_filesize,
5fe18bdb
PH
846 'min_views': opts.min_views,
847 'max_views': opts.max_views,
d1b5f70b 848 'daterange': opts.date,
7f747732 849 'cachedir': opts.cachedir,
f8061589 850 'youtube_print_sig_code': opts.youtube_print_sig_code,
8dbe9899 851 'age_limit': opts.age_limit,
d1b5f70b 852 'download_archive': opts.download_archive,
ea6e0c2b 853 'break_on_existing': opts.break_on_existing,
8b0d7497 854 'break_on_reject': opts.break_on_reject,
b222c271 855 'break_per_url': opts.break_per_url,
26e2805c 856 'skip_playlist_after_errors': opts.skip_playlist_after_errors,
dca08720 857 'cookiefile': opts.cookiefile,
982ee69a 858 'cookiesfrombrowser': opts.cookiesfrombrowser,
f81c62a6 859 'legacyserverconnect': opts.legacy_server_connect,
dca08720 860 'nocheckcertificate': opts.no_check_certificate,
7e8c0af0 861 'prefer_insecure': opts.prefer_insecure,
8300774c 862 'enable_file_urls': opts.enable_file_urls,
8b7539d2 863 'http_headers': opts.headers,
c2e52508 864 'proxy': opts.proxy,
6ad14cab 865 'socket_timeout': opts.socket_timeout,
0783b09b 866 'bidi_workaround': opts.bidi_workaround,
a0ddb8a2 867 'debug_printtraffic': opts.debug_printtraffic,
76b1bd67 868 'prefer_ffmpeg': opts.prefer_ffmpeg,
7b0817e8 869 'include_ads': opts.include_ads,
04b4d394 870 'default_search': opts.default_search,
78895bd3 871 'dynamic_mpd': opts.dynamic_mpd,
5d3a0e79 872 'extractor_args': opts.extractor_args,
4919603f 873 'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
78895bd3 874 'youtube_include_hls_manifest': opts.youtube_include_hls_manifest,
62fec3b2 875 'encoding': opts.encoding,
057a5206 876 'extract_flat': opts.extract_flat,
adbc4ec4 877 'live_from_start': opts.live_from_start,
f2ebc5c7 878 'wait_for_video': opts.wait_for_video,
d77ab8e2 879 'mark_watched': opts.mark_watched,
34c781a2 880 'merge_output_format': opts.merge_output_format,
df692c5a 881 'final_ext': final_ext,
4f026faf 882 'postprocessors': postprocessors,
6271f1ca 883 'fixup': opts.fixup,
be4a824d 884 'source_address': opts.source_address,
58b1f00d 885 'call_home': opts.call_home,
1cf376f5 886 'sleep_interval_requests': opts.sleep_interval_requests,
5f0d813d 887 'sleep_interval': opts.sleep_interval,
065bc354 888 'max_sleep_interval': opts.max_sleep_interval,
0c9df79e 889 'sleep_interval_subtitles': opts.sleep_interval_subtitles,
222516d9 890 'external_downloader': opts.external_downloader,
5ec1b6b7 891 'download_ranges': opts.download_ranges,
892 'force_keyframes_at_cuts': opts.force_keyframes_at_cuts,
cfb56d1a 893 'list_thumbnails': opts.list_thumbnails,
c14e88f0 894 'playlist_items': opts.playlist_items,
881e6a1f 895 'xattr_set_filesize': opts.xattr_set_filesize,
d1b5f70b 896 'match_filter': opts.match_filter,
7e5db8c9 897 'no_color': opts.no_color,
73fac4e9 898 'ffmpeg_location': opts.ffmpeg_location,
85729c51 899 'hls_prefer_native': opts.hls_prefer_native,
7d106a65 900 'hls_use_mpegts': opts.hls_use_mpegts,
310c2ed2 901 'hls_split_discontinuity': opts.hls_split_discontinuity,
46ee996e 902 'external_downloader_args': opts.external_downloader_args,
45016689 903 'postprocessor_args': opts.postprocessor_args,
91410c9b 904 'cn_verification_proxy': opts.cn_verification_proxy,
38cce791 905 'geo_verification_proxy': opts.geo_verification_proxy,
0a840f58
S
906 'geo_bypass': opts.geo_bypass,
907 'geo_bypass_country': opts.geo_bypass_country,
5f95927a 908 'geo_bypass_ip_block': opts.geo_bypass_ip_block,
49a57e70 909 '_warnings': warnings,
ee8dd27a 910 '_deprecation_warnings': deprecation_warnings,
d1b5f70b 911 'compat_opts': opts.compat_opts,
f2df4071 912 })
59ae15a5 913
d1b5f70b 914
915def _real_main(argv=None):
d1b5f70b 916 setproctitle('yt-dlp')
917
918 parser, opts, all_urls, ydl_opts = parse_options(argv)
919
920 # Dump user agent
921 if opts.dump_user_agent:
922 ua = traverse_obj(opts.headers, 'User-Agent', casesense=False, default=std_headers['User-Agent'])
923 write_string(f'{ua}\n', out=sys.stdout)
9e491463 924 return
d1b5f70b 925
926 if print_extractor_information(opts, all_urls):
9e491463 927 return
d1b5f70b 928
6a7d3a0a 929 # We may need ffmpeg_location without having access to the YoutubeDL instance
930 # See https://github.com/yt-dlp/yt-dlp/issues/2191
931 if opts.ffmpeg_location:
932 FFmpegPostProcessor._ffmpeg_location.set(opts.ffmpeg_location)
933
bdde425c 934 with YoutubeDL(ydl_opts) as ydl:
8372be74 935 pre_process = opts.update_self or opts.rm_cachedir
f304da8a 936 actual_use = all_urls or opts.load_info_filename
bdde425c 937
052421ff 938 if opts.rm_cachedir:
a0e07d31 939 ydl.cache.remove()
052421ff 940
d2e84d5e 941 try:
665472a7 942 updater = Updater(ydl, opts.update_self)
d2e84d5e
SS
943 if opts.update_self and updater.update() and actual_use:
944 if updater.cmd:
945 return updater.restart()
946 # This code is reachable only for zip variant in py < 3.10
947 # It makes sense to exit here, but the old behavior is to continue
948 ydl.report_warning('Restart yt-dlp to use the updated version')
949 # return 100, 'ERROR: The program must exit for the update to complete'
950 except Exception:
951 traceback.print_exc()
952 ydl._download_retcode = 100
e5813e53 953
e5813e53 954 if not actual_use:
8372be74 955 if pre_process:
e79969b2 956 return ydl._download_retcode
59ae15a5 957
7d4111ed 958 ydl.warn_if_short_id(sys.argv[1:] if argv is None else argv)
adc0ae3c
PH
959 parser.error(
960 'You must provide at least one URL.\n'
7a5c1cfe 961 'Type yt-dlp --help to see a list of all options.')
7d4111ed 962
c487cf00 963 parser.destroy()
bdde425c 964 try:
1dcc4c0c 965 if opts.load_info_filename is not None:
ab1de9cb 966 if all_urls:
967 ydl.report_warning('URLs are ignored due to --load-info-json')
9e491463 968 return ydl.download_with_info_file(expand_path(opts.load_info_filename))
1dcc4c0c 969 else:
9e491463 970 return ydl.download(all_urls)
f304da8a 971 except DownloadCancelled:
8b0d7497 972 ydl.to_screen('Aborting remaining downloads')
9e491463 973 return 101
235b3ba4 974
a27b9e8b 975
b8ad4f02 976def main(argv=None):
d5d1df8a 977 global _IN_CLI
978 _IN_CLI = True
59ae15a5 979 try:
9e491463 980 _exit(*variadic(_real_main(argv)))
59ae15a5 981 except DownloadError:
9e491463 982 _exit(1)
aa9369a2 983 except SameFileError as e:
9e491463 984 _exit(f'ERROR: {e}')
59ae15a5 985 except KeyboardInterrupt:
9e491463 986 _exit('\nERROR: Interrupted by user')
aa9369a2 987 except BrokenPipeError as e:
cc3fa8d3 988 # https://docs.python.org/3/library/signal.html#note-on-sigpipe
989 devnull = os.open(os.devnull, os.O_WRONLY)
990 os.dup2(devnull, sys.stdout.fileno())
9e491463 991 _exit(f'\nERROR: {e}')
992 except optparse.OptParseError as e:
993 _exit(2, f'\n{e}')
2bad0e5d 994
582be358 995
82d02080 996from .extractor import gen_extractors, list_extractors
21633673 997
d1b5f70b 998__all__ = [
999 'main',
1000 'YoutubeDL',
1001 'parse_options',
1002 'gen_extractors',
1003 'list_extractors',
1004]