]> jfr.im git - yt-dlp.git/blame - yt_dlp/__init__.py
Improve `--download-sections`
[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
db3ad8a6
ND
191 validate(sum(map(bool, (opts.usenetrc, opts.netrc_cmd, opts.username))) <= 1, '.netrc',
192 msg='{name}, netrc command and username/password are mutually exclusive options')
d1b5f70b 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
b4e0d758 323 def parse_chapters(name, value, advanced=False):
fc2ba496 324 parse_timestamp = lambda x: float('inf') if x in ('inf', 'infinite') else parse_duration(x)
b4e0d758 325 TIMESTAMP_RE = r'''(?x)(?:
326 (?P<start_sign>-?)(?P<start>[^-]+)
327 )?\s*-\s*(?:
328 (?P<end_sign>-?)(?P<end>[^-]+)
329 )?'''
330
331 chapters, ranges, from_url = [], [], False
5ec1b6b7 332 for regex in value or []:
b4e0d758 333 if advanced and regex == '*from-url':
334 from_url = True
335 continue
336 elif not regex.startswith('*'):
337 try:
338 chapters.append(re.compile(regex))
339 except re.error as err:
340 raise ValueError(f'invalid {name} regex "{regex}" - {err}')
d1b5f70b 341 continue
b4e0d758 342
343 for range_ in map(str.strip, regex[1:].split(',')):
344 mobj = range_ != '-' and re.fullmatch(TIMESTAMP_RE, range_)
345 dur = mobj and [parse_timestamp(mobj.group('start') or '0'), parse_timestamp(mobj.group('end') or 'inf')]
346 signs = mobj and (mobj.group('start_sign'), mobj.group('end_sign'))
347
348 err = None
349 if None in (dur or [None]):
350 err = 'Must be of the form "*start-end"'
351 elif not advanced and any(signs):
352 err = 'Negative timestamps are not allowed'
353 else:
354 dur[0] *= -1 if signs[0] else 1
355 dur[1] *= -1 if signs[1] else 1
356 if dur[1] == float('-inf'):
357 err = '"-inf" is not a valid end'
358 if err:
359 raise ValueError(f'invalid {name} time range "{regex}". {err}')
360 ranges.append(dur)
361
362 return chapters, ranges, from_url
363
364 opts.remove_chapters, opts.remove_ranges, _ = parse_chapters('--remove-chapters', opts.remove_chapters)
365 opts.download_ranges = download_range_func(*parse_chapters('--download-sections', opts.download_ranges, True))
0202b52a 366
d1b5f70b 367 # Cookies from browser
368 if opts.cookiesfrombrowser:
9bd13fe5 369 container = None
825d3ce3 370 mobj = re.fullmatch(r'''(?x)
371 (?P<name>[^+:]+)
372 (?:\s*\+\s*(?P<keyring>[^:]+))?
935bac1e 373 (?:\s*:\s*(?!:)(?P<profile>.+?))?
825d3ce3 374 (?:\s*::\s*(?P<container>.+))?
375 ''', opts.cookiesfrombrowser)
d1b5f70b 376 if mobj is None:
377 raise ValueError(f'invalid cookies from browser arguments: {opts.cookiesfrombrowser}')
825d3ce3 378 browser_name, keyring, profile, container = mobj.group('name', 'keyring', 'profile', 'container')
d1b5f70b 379 browser_name = browser_name.lower()
380 if browser_name not in SUPPORTED_BROWSERS:
381 raise ValueError(f'unsupported browser specified for cookies: "{browser_name}". '
382 f'Supported browsers are: {", ".join(sorted(SUPPORTED_BROWSERS))}')
383 if keyring is not None:
384 keyring = keyring.upper()
385 if keyring not in SUPPORTED_KEYRINGS:
386 raise ValueError(f'unsupported keyring specified for cookies: "{keyring}". '
387 f'Supported keyrings are: {", ".join(sorted(SUPPORTED_KEYRINGS))}')
d2c8aadf 388 opts.cookiesfrombrowser = (browser_name, profile, keyring, container)
29c7a63d 389
d1b5f70b 390 # MetadataParser
e9f4ccd1 391 def metadataparser_actions(f):
392 if isinstance(f, str):
393 cmd = '--parse-metadata %s' % compat_shlex_quote(f)
394 try:
395 actions = [MetadataFromFieldPP.to_action(f)]
396 except Exception as err:
d1b5f70b 397 raise ValueError(f'{cmd} is invalid; {err}')
e9f4ccd1 398 else:
399 cmd = '--replace-in-metadata %s' % ' '.join(map(compat_shlex_quote, f))
400 actions = ((MetadataParserPP.Actions.REPLACE, x, *f[1:]) for x in f[0].split(','))
401
402 for action in actions:
403 try:
404 MetadataParserPP.validate_action(*action)
405 except Exception as err:
d1b5f70b 406 raise ValueError(f'{cmd} is invalid; {err}')
e9f4ccd1 407 yield action
408
5bfa4862 409 if opts.metafromtitle is not None:
fe74d5b5 410 opts.parse_metadata.setdefault('pre_process', []).append('title:%s' % opts.metafromtitle)
411 opts.parse_metadata = {
412 k: list(itertools.chain(*map(metadataparser_actions, v)))
413 for k, v in opts.parse_metadata.items()
414 }
5bfa4862 415
d1b5f70b 416 # Other options
7e88d7d7 417 if opts.playlist_items is not None:
418 try:
419 tuple(PlaylistEntries.parse_playlist_items(opts.playlist_items))
420 except Exception as err:
421 raise ValueError(f'Invalid playlist-items {opts.playlist_items!r}: {err}')
422
c1664464 423 opts.geo_bypass_country, opts.geo_bypass_ip_block = None, None
424 if opts.geo_bypass.lower() not in ('default', 'never'):
d1b5f70b 425 try:
c1664464 426 GeoUtils.random_ipv4(opts.geo_bypass)
d1b5f70b 427 except Exception:
c1664464 428 raise ValueError(f'Unsupported --xff "{opts.geo_bypass}"')
429 if len(opts.geo_bypass) == 2:
430 opts.geo_bypass_country = opts.geo_bypass
431 else:
432 opts.geo_bypass_ip_block = opts.geo_bypass
433 opts.geo_bypass = opts.geo_bypass.lower() != 'never'
ca30f449 434
fe2ce85a 435 opts.match_filter = match_filter_func(opts.match_filter, opts.breaking_match_filter)
525ef922 436
d1b5f70b 437 if opts.download_archive is not None:
438 opts.download_archive = expand_path(opts.download_archive)
439
2b24afa6 440 if opts.ffmpeg_location is not None:
441 opts.ffmpeg_location = expand_path(opts.ffmpeg_location)
442
d1b5f70b 443 if opts.user_agent is not None:
444 opts.headers.setdefault('User-Agent', opts.user_agent)
445 if opts.referer is not None:
446 opts.headers.setdefault('Referer', opts.referer)
f0884c8b 447
7a340e0d 448 if opts.no_sponsorblock:
d1b5f70b 449 opts.sponsorblock_mark = opts.sponsorblock_remove = set()
450
6d1b3489 451 default_downloader = None
7b2c3f47 452 for proto, path in opts.external_downloader.items():
28163422 453 if path == 'native':
454 continue
6d1b3489 455 ed = get_external_downloader(path)
456 if ed is None:
7b2c3f47 457 raise ValueError(
458 f'No such {format_field(proto, None, "%s ", ignore="default")}external downloader "{path}"')
6d1b3489 459 elif ed and proto == 'default':
460 default_downloader = ed.get_basename()
461
8417f26b
SS
462 for policy in opts.color.values():
463 if policy not in ('always', 'auto', 'no_color', 'never'):
464 raise ValueError(f'"{policy}" is not a valid color policy')
465
d1b5f70b 466 warnings, deprecation_warnings = [], []
7a340e0d 467
d1b5f70b 468 # Common mistake: -f best
469 if opts.format == 'best':
470 warnings.append('.\n '.join((
471 '"-f best" selects the best pre-merged format which is often not the best option',
472 'To let yt-dlp download and merge the best available formats, simply do not pass any format selection',
473 'If you know what you are doing and want only the best pre-merged format, use "-f b" instead to suppress this warning')))
474
a44ca5a4 475 # --(postprocessor/downloader)-args without name
6d1b3489 476 def report_args_compat(name, value, key1, key2=None, where=None):
d1b5f70b 477 if key1 in value and key2 not in value:
6d1b3489 478 warnings.append(f'{name.title()} arguments given without specifying name. '
479 f'The arguments will be given to {where or f"all {name}s"}')
d1b5f70b 480 return True
481 return False
482
6d1b3489 483 if report_args_compat('external downloader', opts.external_downloader_args,
484 'default', where=default_downloader) and default_downloader:
485 # Compat with youtube-dl's behavior. See https://github.com/ytdl-org/youtube-dl/commit/49c5293014bc11ec8c009856cd63cffa6296c1e1
486 opts.external_downloader_args.setdefault(default_downloader, opts.external_downloader_args.pop('default'))
487
d1b5f70b 488 if report_args_compat('post-processor', opts.postprocessor_args, 'default-compat', 'default'):
489 opts.postprocessor_args['default'] = opts.postprocessor_args.pop('default-compat')
490 opts.postprocessor_args.setdefault('sponskrub', [])
491
492 def report_conflict(arg1, opt1, arg2='--allow-unplayable-formats', opt2='allow_unplayable_formats',
493 val1=NO_DEFAULT, val2=NO_DEFAULT, default=False):
494 if val2 is NO_DEFAULT:
495 val2 = getattr(opts, opt2)
496 if not val2:
497 return
498
499 if val1 is NO_DEFAULT:
500 val1 = getattr(opts, opt1)
501 if val1:
502 warnings.append(f'{arg1} is ignored since {arg2} was given')
503 setattr(opts, opt1, default)
504
505 # Conflicting options
7e9a6125 506 report_conflict('--playlist-reverse', 'playlist_reverse', '--playlist-random', 'playlist_random')
507 report_conflict('--playlist-reverse', 'playlist_reverse', '--lazy-playlist', 'lazy_playlist')
508 report_conflict('--playlist-random', 'playlist_random', '--lazy-playlist', 'lazy_playlist')
da1d734f 509 report_conflict('--dateafter', 'dateafter', '--date', 'date', default=None)
510 report_conflict('--datebefore', 'datebefore', '--date', 'date', default=None)
3d3bb168 511 report_conflict('--exec-before-download', 'exec_before_dl_cmd',
512 '"--exec before_dl:"', 'exec_cmd', val2=opts.exec_cmd.get('before_dl'))
d1b5f70b 513 report_conflict('--id', 'useid', '--output', 'outtmpl', val2=opts.outtmpl.get('default'))
514 report_conflict('--remux-video', 'remuxvideo', '--recode-video', 'recodevideo')
515 report_conflict('--sponskrub', 'sponskrub', '--remove-chapters', 'remove_chapters')
516 report_conflict('--sponskrub', 'sponskrub', '--sponsorblock-mark', 'sponsorblock_mark')
517 report_conflict('--sponskrub', 'sponskrub', '--sponsorblock-remove', 'sponsorblock_remove')
19a03940 518 report_conflict('--sponskrub-cut', 'sponskrub_cut', '--split-chapter', 'split_chapters',
519 val1=opts.sponskrub and opts.sponskrub_cut)
d1b5f70b 520
521 # Conflicts with --allow-unplayable-formats
2fa669f7 522 report_conflict('--embed-metadata', 'addmetadata')
d1b5f70b 523 report_conflict('--embed-chapters', 'addchapters')
524 report_conflict('--embed-info-json', 'embed_infojson')
525 report_conflict('--embed-subs', 'embedsubtitles')
526 report_conflict('--embed-thumbnail', 'embedthumbnail')
527 report_conflict('--extract-audio', 'extractaudio')
3d3bb168 528 report_conflict('--fixup', 'fixup', val1=opts.fixup not in (None, 'never', 'ignore'), default='never')
d1b5f70b 529 report_conflict('--recode-video', 'recodevideo')
530 report_conflict('--remove-chapters', 'remove_chapters', default=[])
531 report_conflict('--remux-video', 'remuxvideo')
532 report_conflict('--sponskrub', 'sponskrub')
533 report_conflict('--sponsorblock-remove', 'sponsorblock_remove', default=set())
534 report_conflict('--xattrs', 'xattrs')
535
536 # Fully deprecated options
537 def report_deprecation(val, old, new=None):
538 if not val:
539 return
540 deprecation_warnings.append(
541 f'{old} is deprecated and may be removed in a future version. Use {new} instead' if new
542 else f'{old} is deprecated and may not work as expected')
543
544 report_deprecation(opts.sponskrub, '--sponskrub', '--sponsorblock-mark or --sponsorblock-remove')
545 report_deprecation(not opts.prefer_ffmpeg, '--prefer-avconv', 'ffmpeg')
546 # report_deprecation(opts.include_ads, '--include-ads') # We may re-implement this in future
547 # report_deprecation(opts.call_home, '--call-home') # We may re-implement this in future
548 # report_deprecation(opts.writeannotations, '--write-annotations') # It's just that no website has it
549
550 # Dependent options
da1d734f 551 opts.date = DateRange.day(opts.date) if opts.date else DateRange(opts.dateafter, opts.datebefore)
552
d1b5f70b 553 if opts.exec_before_dl_cmd:
554 opts.exec_cmd['before_dl'] = opts.exec_before_dl_cmd
555
556 if opts.useid: # --id is not deprecated in youtube-dl
557 opts.outtmpl['default'] = '%(id)s.%(ext)s'
558
559 if opts.overwrites: # --force-overwrites implies --no-continue
560 opts.continue_dl = False
63ad4d43 561
9222c381 562 if (opts.addmetadata or opts.sponsorblock_mark) and opts.addchapters is None:
d1b5f70b 563 # Add chapters when adding metadata or marking sponsors
9222c381 564 opts.addchapters = True
565
d1b5f70b 566 if opts.extractaudio and not opts.keepvideo and opts.format is None:
567 # Do not unnecessarily download audio
568 opts.format = 'bestaudio/best'
569
f2df4071 570 if opts.getcomments and opts.writeinfojson is None and not opts.embed_infojson:
d1b5f70b 571 # If JSON is not printed anywhere, but comments are requested, save it to file
572 if not opts.dumpjson or opts.print_json or opts.dump_single_json:
573 opts.writeinfojson = True
574
575 if opts.allsubtitles and not (opts.embedsubtitles or opts.writeautomaticsub):
576 # --all-sub automatically sets --write-sub if --write-auto-sub is not given
577 opts.writesubtitles = True
578
579 if opts.addmetadata and opts.embed_infojson is None:
580 # If embedding metadata and infojson is present, embed it
581 opts.embed_infojson = 'if_exists'
582
583 # Ask for passwords
584 if opts.username is not None and opts.password is None:
ac668111 585 opts.password = getpass.getpass('Type account password and press [Return]: ')
d1b5f70b 586 if opts.ap_username is not None and opts.ap_password is None:
ac668111 587 opts.ap_password = getpass.getpass('Type TV provider account password and press [Return]: ')
d1b5f70b 588
589 return warnings, deprecation_warnings
590
591
592def get_postprocessors(opts):
593 yield from opts.add_postprocessors
594
fe74d5b5 595 for when, actions in opts.parse_metadata.items():
d1b5f70b 596 yield {
597 'key': 'MetadataParser',
fe74d5b5 598 'actions': actions,
599 'when': when
d1b5f70b 600 }
601 sponsorblock_query = opts.sponsorblock_mark | opts.sponsorblock_remove
7a340e0d 602 if sponsorblock_query:
d1b5f70b 603 yield {
7a340e0d
NA
604 'key': 'SponsorBlock',
605 'categories': sponsorblock_query,
606 'api': opts.sponsorblock_api,
09b49e1f 607 'when': 'after_filter'
d1b5f70b 608 }
56d868db 609 if opts.convertsubtitles:
d1b5f70b 610 yield {
56d868db 611 'key': 'FFmpegSubtitlesConvertor',
612 'format': opts.convertsubtitles,
56d868db 613 'when': 'before_dl'
d1b5f70b 614 }
8fa43c73 615 if opts.convertthumbnails:
d1b5f70b 616 yield {
8fa43c73 617 'key': 'FFmpegThumbnailsConvertor',
618 'format': opts.convertthumbnails,
8fa43c73 619 'when': 'before_dl'
d1b5f70b 620 }
4f026faf 621 if opts.extractaudio:
d1b5f70b 622 yield {
4f026faf
PH
623 'key': 'FFmpegExtractAudio',
624 'preferredcodec': opts.audioformat,
625 'preferredquality': opts.audioquality,
626 'nopostoverwrites': opts.nopostoverwrites,
d1b5f70b 627 }
efe87a10 628 if opts.remuxvideo:
d1b5f70b 629 yield {
efe87a10
FS
630 'key': 'FFmpegVideoRemuxer',
631 'preferedformat': opts.remuxvideo,
d1b5f70b 632 }
4f026faf 633 if opts.recodevideo:
d1b5f70b 634 yield {
4f026faf
PH
635 'key': 'FFmpegVideoConvertor',
636 'preferedformat': opts.recodevideo,
d1b5f70b 637 }
7a340e0d 638 # If ModifyChapters is going to remove chapters, subtitles must already be in the container.
4f026faf 639 if opts.embedsubtitles:
d1b5f70b 640 keep_subs = 'no-keep-subs' not in opts.compat_opts
641 yield {
4f026faf 642 'key': 'FFmpegEmbedSubtitle',
56d868db 643 # already_have_subtitle = True prevents the file from being deleted after embedding
d1b5f70b 644 'already_have_subtitle': opts.writesubtitles and keep_subs
645 }
646 if not opts.writeautomaticsub and keep_subs:
cffab0ee 647 opts.writesubtitles = True
d1b5f70b 648
7a340e0d 649 # ModifyChapters must run before FFmpegMetadataPP
7a340e0d 650 if opts.remove_chapters or sponsorblock_query:
d1b5f70b 651 yield {
7a340e0d 652 'key': 'ModifyChapters',
d1b5f70b 653 'remove_chapters_patterns': opts.remove_chapters,
7a340e0d 654 'remove_sponsor_segments': opts.sponsorblock_remove,
d1b5f70b 655 'remove_ranges': opts.remove_ranges,
7a340e0d
NA
656 'sponsorblock_chapter_title': opts.sponsorblock_chapter_title,
657 'force_keyframes': opts.force_keyframes_at_cuts
d1b5f70b 658 }
7a340e0d
NA
659 # FFmpegMetadataPP should be run after FFmpegVideoConvertorPP and
660 # FFmpegExtractAudioPP as containers before conversion may not support
661 # metadata (3gp, webm, etc.)
662 # By default ffmpeg preserves metadata applicable for both
663 # source and target containers. From this point the container won't change,
664 # so metadata can be added here.
dac5df5a 665 if opts.addmetadata or opts.addchapters or opts.embed_infojson:
d1b5f70b 666 yield {
7a340e0d
NA
667 'key': 'FFmpegMetadata',
668 'add_chapters': opts.addchapters,
669 'add_metadata': opts.addmetadata,
dac5df5a 670 'add_infojson': opts.embed_infojson,
d1b5f70b 671 }
ee8dd27a 672 # Deprecated
f4e4be19 673 # This should be above EmbedThumbnail since sponskrub removes the thumbnail attachment
674 # but must be below EmbedSubtitle and FFmpegMetadata
675 # See https://github.com/yt-dlp/yt-dlp/issues/204 , https://github.com/faissaloo/SponSkrub/issues/29
a9e7f546 676 # If opts.sponskrub is None, sponskrub is used, but it silently fails if the executable can't be found
677 if opts.sponskrub is not False:
d1b5f70b 678 yield {
a9e7f546 679 'key': 'SponSkrub',
680 'path': opts.sponskrub_path,
681 'args': opts.sponskrub_args,
682 'cut': opts.sponskrub_cut,
683 'force': opts.sponskrub_force,
684 'ignoreerror': opts.sponskrub is None,
ee8dd27a 685 '_from_cli': True,
d1b5f70b 686 }
f4e4be19 687 if opts.embedthumbnail:
d1b5f70b 688 yield {
f4e4be19 689 'key': 'EmbedThumbnail',
56d868db 690 # already_have_thumbnail = True prevents the file from being deleted after embedding
acc0d6a4 691 'already_have_thumbnail': opts.writethumbnail
d1b5f70b 692 }
acc0d6a4 693 if not opts.writethumbnail:
f4e4be19 694 opts.writethumbnail = True
80c03fa9 695 opts.outtmpl['pl_thumbnail'] = ''
72755351 696 if opts.split_chapters:
d1b5f70b 697 yield {
7a340e0d
NA
698 'key': 'FFmpegSplitChapters',
699 'force_keyframes': opts.force_keyframes_at_cuts,
d1b5f70b 700 }
72755351 701 # XAttrMetadataPP should be run after post-processors that may change file contents
702 if opts.xattrs:
d1b5f70b 703 yield {'key': 'XAttrMetadata'}
3b603dbd 704 if opts.concat_playlist != 'never':
d1b5f70b 705 yield {
3b603dbd 706 'key': 'FFmpegConcat',
707 'only_multi_video': opts.concat_playlist != 'always',
708 'when': 'playlist',
d1b5f70b 709 }
1e43a6f7 710 # Exec must be the last PP of each category
1e43a6f7 711 for when, exec_cmd in opts.exec_cmd.items():
d1b5f70b 712 yield {
ad3dc496 713 'key': 'Exec',
1e43a6f7 714 'exec_cmd': exec_cmd,
1e43a6f7 715 'when': when,
d1b5f70b 716 }
1b77b347 717
0d1bb027 718
f2df4071 719ParsedOptions = collections.namedtuple('ParsedOptions', ('parser', 'options', 'urls', 'ydl_opts'))
720
721
d1b5f70b 722def parse_options(argv=None):
f2df4071 723 """@returns ParsedOptions(parser, opts, urls, ydl_opts)"""
d1b5f70b 724 parser, opts, urls = parseOpts(argv)
725 urls = get_urls(urls, opts.batchfile, opts.verbose)
b8f6bbe6 726
d1b5f70b 727 set_compat_opts(opts)
728 try:
729 warnings, deprecation_warnings = validate_options(opts)
730 except ValueError as err:
731 parser.error(f'{err}\n')
1b77b347 732
d1b5f70b 733 postprocessors = list(get_postprocessors(opts))
ee8dd27a 734
193fb150 735 print_only = bool(opts.forceprint) and all(k not in opts.forceprint for k in POSTPROCESS_WHEN[3:])
62f6f1cb 736 any_getting = any(getattr(opts, k) for k in (
737 'dumpjson', 'dump_single_json', 'getdescription', 'getduration', 'getfilename',
738 'getformat', 'getid', 'getthumbnail', 'gettitle', 'geturl'
739 ))
d669772c 740 if opts.quiet is None:
741 opts.quiet = any_getting or opts.print_json or bool(opts.forceprint)
ee8dd27a 742
134c913c 743 playlist_pps = [pp for pp in postprocessors if pp.get('when') == 'playlist']
744 write_playlist_infojson = (opts.writeinfojson and not opts.clean_infojson
745 and opts.allow_playlist_files and opts.outtmpl.get('pl_infojson') != '')
746 if not any((
747 opts.extract_flat,
748 opts.dump_single_json,
749 opts.forceprint.get('playlist'),
750 opts.print_to_file.get('playlist'),
751 write_playlist_infojson,
752 )):
753 if not playlist_pps:
754 opts.extract_flat = 'discard'
755 elif playlist_pps == [{'key': 'FFmpegConcat', 'only_multi_video': True, 'when': 'playlist'}]:
756 opts.extract_flat = 'discard_in_playlist'
757
df692c5a 758 final_ext = (
81a23040 759 opts.recodevideo if opts.recodevideo in FFmpegVideoConvertorPP.SUPPORTED_EXTS
760 else opts.remuxvideo if opts.remuxvideo in FFmpegVideoRemuxerPP.SUPPORTED_EXTS
35faefee 761 else opts.audioformat if (opts.extractaudio and opts.audioformat in FFmpegExtractAudioPP.SUPPORTED_EXTS)
81a23040 762 else None)
f6d7624f 763
f2df4071 764 return ParsedOptions(parser, opts, urls, {
59ae15a5 765 'usenetrc': opts.usenetrc,
0001fcb5 766 'netrc_location': opts.netrc_location,
db3ad8a6 767 'netrc_cmd': opts.netrc_cmd,
59ae15a5
PH
768 'username': opts.username,
769 'password': opts.password,
83317f69 770 'twofactor': opts.twofactor,
c6c19746 771 'videopassword': opts.videopassword,
797c636b 772 'ap_mso': opts.ap_mso,
1b6712ab
RA
773 'ap_username': opts.ap_username,
774 'ap_password': opts.ap_password,
bb58c9ed 775 'client_certificate': opts.client_certificate,
776 'client_certificate_key': opts.client_certificate_key,
777 'client_certificate_password': opts.client_certificate_password,
5712943b 778 'quiet': opts.quiet,
ad8915b7 779 'no_warnings': opts.no_warnings,
59ae15a5
PH
780 'forceurl': opts.geturl,
781 'forcetitle': opts.gettitle,
1a2adf3f 782 'forceid': opts.getid,
59ae15a5
PH
783 'forcethumbnail': opts.getthumbnail,
784 'forcedescription': opts.getdescription,
525ef922 785 'forceduration': opts.getduration,
59ae15a5
PH
786 'forcefilename': opts.getfilename,
787 'forceformat': opts.getformat,
d2a1fad9 788 'forceprint': opts.forceprint,
bb66c247 789 'print_to_file': opts.print_to_file,
c0bdf32a 790 'forcejson': opts.dumpjson or opts.print_json,
63e0be34 791 'dump_single_json': opts.dump_single_json,
2d30509f 792 'force_write_download_archive': opts.force_write_download_archive,
62f6f1cb 793 'simulate': (print_only or any_getting or None) if opts.simulate is None else opts.simulate,
1bdeb7be 794 'skip_download': opts.skip_download,
59ae15a5 795 'format': opts.format,
63ad4d43 796 'allow_unplayable_formats': opts.allow_unplayable_formats,
b7da73eb 797 'ignore_no_formats_error': opts.ignore_no_formats_error,
eb8a4433 798 'format_sort': opts.format_sort,
799 'format_sort_force': opts.format_sort_force,
909d24dd 800 'allow_multiple_video_streams': opts.allow_multiple_video_streams,
801 'allow_multiple_audio_streams': opts.allow_multiple_audio_streams,
e8e73840 802 'check_formats': opts.check_formats,
59ae15a5 803 'listformats': opts.listformats,
76d321f6 804 'listformats_table': opts.listformats_table,
486fb179 805 'outtmpl': opts.outtmpl,
a820dc72 806 'outtmpl_na_placeholder': opts.outtmpl_na_placeholder,
0202b52a 807 'paths': opts.paths,
213c31ae 808 'autonumber_size': opts.autonumber_size,
acbb2374 809 'autonumber_start': opts.autonumber_start,
59ae15a5 810 'restrictfilenames': opts.restrictfilenames,
c2934512 811 'windowsfilenames': opts.windowsfilenames,
59ae15a5 812 'ignoreerrors': opts.ignoreerrors,
d22dec74 813 'force_generic_extractor': opts.force_generic_extractor,
fe7866d0 814 'allowed_extractors': opts.allowed_extractors or ['default'],
59ae15a5 815 'ratelimit': opts.ratelimit,
51d9739f 816 'throttledratelimit': opts.throttledratelimit,
0c3d0f51 817 'overwrites': opts.overwrites,
52bb437e 818 'retries': opts.retries,
205a0654 819 'file_access_retries': opts.file_access_retries,
52bb437e 820 'fragment_retries': opts.fragment_retries,
62bff2c1 821 'extractor_retries': opts.extractor_retries,
23326151 822 'retry_sleep_functions': opts.retry_sleep,
9603b660 823 'skip_unavailable_fragments': opts.skip_unavailable_fragments,
0eee52f3 824 'keep_fragments': opts.keep_fragments,
4cf1e5d2 825 'concurrent_fragment_downloads': opts.concurrent_fragment_downloads,
59ae15a5
PH
826 'buffersize': opts.buffersize,
827 'noresizebuffer': opts.noresizebuffer,
ba515388 828 'http_chunk_size': opts.http_chunk_size,
59ae15a5 829 'continuedl': opts.continue_dl,
819e0531 830 'noprogress': opts.quiet if opts.noprogress is None else opts.noprogress,
5717d91a 831 'progress_with_newline': opts.progress_with_newline,
819e0531 832 'progress_template': opts.progress_template,
59ae15a5
PH
833 'playliststart': opts.playliststart,
834 'playlistend': opts.playlistend,
ff815fe6 835 'playlistreverse': opts.playlist_reverse,
75822ca7 836 'playlistrandom': opts.playlist_random,
7e9a6125 837 'lazy_playlist': opts.lazy_playlist,
47192f92 838 'noplaylist': opts.noplaylist,
d1b5f70b 839 'logtostderr': opts.outtmpl.get('default') == '-',
59ae15a5
PH
840 'consoletitle': opts.consoletitle,
841 'nopart': opts.nopart,
842 'updatetime': opts.updatetime,
843 'writedescription': opts.writedescription,
1fb07d10 844 'writeannotations': opts.writeannotations,
f0884c8b 845 'writeinfojson': opts.writeinfojson,
1ea24129 846 'allow_playlist_files': opts.allow_playlist_files,
75d43ca0 847 'clean_infojson': opts.clean_infojson,
06167fbb 848 'getcomments': opts.getcomments,
acc0d6a4 849 'writethumbnail': opts.writethumbnail is True,
850 'write_all_thumbnails': opts.writethumbnail == 'all',
732044af 851 'writelink': opts.writelink,
852 'writeurllink': opts.writeurllink,
853 'writewebloclink': opts.writewebloclink,
854 'writedesktoplink': opts.writedesktoplink,
59ae15a5 855 'writesubtitles': opts.writesubtitles,
b004821f 856 'writeautomaticsub': opts.writeautomaticsub,
ae608b80 857 'allsubtitles': opts.allsubtitles,
2a4093ea 858 'listsubtitles': opts.listsubtitles,
9e62bc44 859 'subtitlesformat': opts.subtitlesformat,
d6e203b3 860 'subtitleslangs': opts.subtitleslangs,
8271226a
PH
861 'matchtitle': decodeOption(opts.matchtitle),
862 'rejecttitle': decodeOption(opts.rejecttitle),
59ae15a5
PH
863 'max_downloads': opts.max_downloads,
864 'prefer_free_formats': opts.prefer_free_formats,
bdc3fd2f 865 'trim_file_name': opts.trim_file_name,
59ae15a5 866 'verbose': opts.verbose,
855703e5 867 'dump_intermediate_pages': opts.dump_intermediate_pages,
d41e6efc 868 'write_pages': opts.write_pages,
f95b9dee 869 'load_pages': opts.load_pages,
8d5d3a5d 870 'test': opts.test,
7851b379 871 'keepvideo': opts.keepvideo,
9e982f9e 872 'min_filesize': opts.min_filesize,
bd558525 873 'max_filesize': opts.max_filesize,
5fe18bdb
PH
874 'min_views': opts.min_views,
875 'max_views': opts.max_views,
d1b5f70b 876 'daterange': opts.date,
7f747732 877 'cachedir': opts.cachedir,
f8061589 878 'youtube_print_sig_code': opts.youtube_print_sig_code,
8dbe9899 879 'age_limit': opts.age_limit,
d1b5f70b 880 'download_archive': opts.download_archive,
ea6e0c2b 881 'break_on_existing': opts.break_on_existing,
8b0d7497 882 'break_on_reject': opts.break_on_reject,
b222c271 883 'break_per_url': opts.break_per_url,
26e2805c 884 'skip_playlist_after_errors': opts.skip_playlist_after_errors,
dca08720 885 'cookiefile': opts.cookiefile,
982ee69a 886 'cookiesfrombrowser': opts.cookiesfrombrowser,
f81c62a6 887 'legacyserverconnect': opts.legacy_server_connect,
dca08720 888 'nocheckcertificate': opts.no_check_certificate,
7e8c0af0 889 'prefer_insecure': opts.prefer_insecure,
8300774c 890 'enable_file_urls': opts.enable_file_urls,
8b7539d2 891 'http_headers': opts.headers,
c2e52508 892 'proxy': opts.proxy,
6ad14cab 893 'socket_timeout': opts.socket_timeout,
0783b09b 894 'bidi_workaround': opts.bidi_workaround,
a0ddb8a2 895 'debug_printtraffic': opts.debug_printtraffic,
76b1bd67 896 'prefer_ffmpeg': opts.prefer_ffmpeg,
7b0817e8 897 'include_ads': opts.include_ads,
04b4d394 898 'default_search': opts.default_search,
78895bd3 899 'dynamic_mpd': opts.dynamic_mpd,
5d3a0e79 900 'extractor_args': opts.extractor_args,
4919603f 901 'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
78895bd3 902 'youtube_include_hls_manifest': opts.youtube_include_hls_manifest,
62fec3b2 903 'encoding': opts.encoding,
057a5206 904 'extract_flat': opts.extract_flat,
adbc4ec4 905 'live_from_start': opts.live_from_start,
f2ebc5c7 906 'wait_for_video': opts.wait_for_video,
d77ab8e2 907 'mark_watched': opts.mark_watched,
34c781a2 908 'merge_output_format': opts.merge_output_format,
df692c5a 909 'final_ext': final_ext,
4f026faf 910 'postprocessors': postprocessors,
6271f1ca 911 'fixup': opts.fixup,
be4a824d 912 'source_address': opts.source_address,
58b1f00d 913 'call_home': opts.call_home,
1cf376f5 914 'sleep_interval_requests': opts.sleep_interval_requests,
5f0d813d 915 'sleep_interval': opts.sleep_interval,
065bc354 916 'max_sleep_interval': opts.max_sleep_interval,
0c9df79e 917 'sleep_interval_subtitles': opts.sleep_interval_subtitles,
222516d9 918 'external_downloader': opts.external_downloader,
5ec1b6b7 919 'download_ranges': opts.download_ranges,
920 'force_keyframes_at_cuts': opts.force_keyframes_at_cuts,
cfb56d1a 921 'list_thumbnails': opts.list_thumbnails,
c14e88f0 922 'playlist_items': opts.playlist_items,
881e6a1f 923 'xattr_set_filesize': opts.xattr_set_filesize,
d1b5f70b 924 'match_filter': opts.match_filter,
8417f26b 925 'color': opts.color,
73fac4e9 926 'ffmpeg_location': opts.ffmpeg_location,
85729c51 927 'hls_prefer_native': opts.hls_prefer_native,
7d106a65 928 'hls_use_mpegts': opts.hls_use_mpegts,
310c2ed2 929 'hls_split_discontinuity': opts.hls_split_discontinuity,
46ee996e 930 'external_downloader_args': opts.external_downloader_args,
45016689 931 'postprocessor_args': opts.postprocessor_args,
91410c9b 932 'cn_verification_proxy': opts.cn_verification_proxy,
38cce791 933 'geo_verification_proxy': opts.geo_verification_proxy,
0a840f58
S
934 'geo_bypass': opts.geo_bypass,
935 'geo_bypass_country': opts.geo_bypass_country,
5f95927a 936 'geo_bypass_ip_block': opts.geo_bypass_ip_block,
49a57e70 937 '_warnings': warnings,
ee8dd27a 938 '_deprecation_warnings': deprecation_warnings,
d1b5f70b 939 'compat_opts': opts.compat_opts,
f2df4071 940 })
59ae15a5 941
d1b5f70b 942
943def _real_main(argv=None):
d1b5f70b 944 setproctitle('yt-dlp')
945
946 parser, opts, all_urls, ydl_opts = parse_options(argv)
947
948 # Dump user agent
949 if opts.dump_user_agent:
950 ua = traverse_obj(opts.headers, 'User-Agent', casesense=False, default=std_headers['User-Agent'])
951 write_string(f'{ua}\n', out=sys.stdout)
9e491463 952 return
d1b5f70b 953
954 if print_extractor_information(opts, all_urls):
9e491463 955 return
d1b5f70b 956
6a7d3a0a 957 # We may need ffmpeg_location without having access to the YoutubeDL instance
958 # See https://github.com/yt-dlp/yt-dlp/issues/2191
959 if opts.ffmpeg_location:
960 FFmpegPostProcessor._ffmpeg_location.set(opts.ffmpeg_location)
961
bdde425c 962 with YoutubeDL(ydl_opts) as ydl:
8372be74 963 pre_process = opts.update_self or opts.rm_cachedir
f304da8a 964 actual_use = all_urls or opts.load_info_filename
bdde425c 965
052421ff 966 if opts.rm_cachedir:
a0e07d31 967 ydl.cache.remove()
052421ff 968
d2e84d5e 969 try:
665472a7 970 updater = Updater(ydl, opts.update_self)
d2e84d5e
SS
971 if opts.update_self and updater.update() and actual_use:
972 if updater.cmd:
973 return updater.restart()
974 # This code is reachable only for zip variant in py < 3.10
975 # It makes sense to exit here, but the old behavior is to continue
976 ydl.report_warning('Restart yt-dlp to use the updated version')
977 # return 100, 'ERROR: The program must exit for the update to complete'
978 except Exception:
979 traceback.print_exc()
980 ydl._download_retcode = 100
e5813e53 981
e5813e53 982 if not actual_use:
8372be74 983 if pre_process:
e79969b2 984 return ydl._download_retcode
59ae15a5 985
7d4111ed 986 ydl.warn_if_short_id(sys.argv[1:] if argv is None else argv)
adc0ae3c
PH
987 parser.error(
988 'You must provide at least one URL.\n'
7a5c1cfe 989 'Type yt-dlp --help to see a list of all options.')
7d4111ed 990
c487cf00 991 parser.destroy()
bdde425c 992 try:
1dcc4c0c 993 if opts.load_info_filename is not None:
ab1de9cb 994 if all_urls:
995 ydl.report_warning('URLs are ignored due to --load-info-json')
9e491463 996 return ydl.download_with_info_file(expand_path(opts.load_info_filename))
1dcc4c0c 997 else:
9e491463 998 return ydl.download(all_urls)
f304da8a 999 except DownloadCancelled:
8b0d7497 1000 ydl.to_screen('Aborting remaining downloads')
9e491463 1001 return 101
235b3ba4 1002
a27b9e8b 1003
b8ad4f02 1004def main(argv=None):
d5d1df8a 1005 global _IN_CLI
1006 _IN_CLI = True
59ae15a5 1007 try:
9e491463 1008 _exit(*variadic(_real_main(argv)))
59ae15a5 1009 except DownloadError:
9e491463 1010 _exit(1)
aa9369a2 1011 except SameFileError as e:
9e491463 1012 _exit(f'ERROR: {e}')
59ae15a5 1013 except KeyboardInterrupt:
9e491463 1014 _exit('\nERROR: Interrupted by user')
aa9369a2 1015 except BrokenPipeError as e:
cc3fa8d3 1016 # https://docs.python.org/3/library/signal.html#note-on-sigpipe
1017 devnull = os.open(os.devnull, os.O_WRONLY)
1018 os.dup2(devnull, sys.stdout.fileno())
9e491463 1019 _exit(f'\nERROR: {e}')
1020 except optparse.OptParseError as e:
1021 _exit(2, f'\n{e}')
2bad0e5d 1022
582be358 1023
82d02080 1024from .extractor import gen_extractors, list_extractors
21633673 1025
d1b5f70b 1026__all__ = [
1027 'main',
1028 'YoutubeDL',
1029 'parse_options',
1030 'gen_extractors',
1031 'list_extractors',
1032]