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