aboutsummaryrefslogtreecommitdiffstats
path: root/yt_dlp/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'yt_dlp/__init__.py')
-rw-r--r--yt_dlp/__init__.py169
1 files changed, 63 insertions, 106 deletions
diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py
index f1a347514..0c68f8571 100644
--- a/yt_dlp/__init__.py
+++ b/yt_dlp/__init__.py
@@ -1,12 +1,8 @@
-try:
- import contextvars # noqa: F401
-except Exception:
- raise Exception(
- f'You are using an unsupported version of Python. Only Python versions 3.7 and above are supported by yt-dlp') # noqa: F541
+#!/usr/bin/python
+f'You are using an unsupported version of Python. Only Python versions 3.6 and above are supported by yt-dlp' # noqa: F541
-__license__ = 'Public Domain'
+__license__ = 'CC0-1.0'
-import collections
import getpass
import itertools
import optparse
@@ -16,14 +12,14 @@ import sys
from .compat import compat_shlex_quote
from .cookies import SUPPORTED_BROWSERS, SUPPORTED_KEYRINGS
+from .downloader import FileDownloader
from .downloader.external import get_external_downloader
from .extractor import list_extractor_classes
from .extractor.adobepass import MSO_INFO
+from .extractor.common import InfoExtractor
from .options import parseOpts
from .postprocessor import (
FFmpegExtractAudioPP,
- FFmpegMergerPP,
- FFmpegPostProcessor,
FFmpegSubtitlesConvertorPP,
FFmpegThumbnailsConvertorPP,
FFmpegVideoConvertorPP,
@@ -38,7 +34,6 @@ from .utils import (
DateRange,
DownloadCancelled,
DownloadError,
- FormatSorter,
GeoUtils,
PlaylistEntries,
SameFileError,
@@ -49,7 +44,6 @@ from .utils import (
format_field,
int_or_none,
match_filter_func,
- parse_bytes,
parse_duration,
preferredencoding,
read_batch_urls,
@@ -63,8 +57,6 @@ from .utils import (
)
from .YoutubeDL import YoutubeDL
-_IN_CLI = False
-
def _exit(status=0, *args):
for msg in args:
@@ -97,27 +89,28 @@ def print_extractor_information(opts, urls):
out = ''
if opts.list_extractors:
- urls = dict.fromkeys(urls, False)
- for ie in list_extractor_classes(opts.age_limit):
- out += ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie.working() else '') + '\n'
- if ie == GenericIE:
- matched_urls = [url for url, matched in urls.items() if not matched]
- else:
- matched_urls = tuple(filter(ie.suitable, urls.keys()))
- urls.update(dict.fromkeys(matched_urls, True))
- out += ''.join(f' {url}\n' for url in matched_urls)
+ for ie in list_extractors(opts.age_limit):
+ write_string(ie.IE_NAME + (' (CURRENTLY BROKEN)' if not ie.working() else '') + '\n', out=sys.stdout)
+ matchedUrls = [url for url in urls if ie.suitable(url)]
+ for mu in matchedUrls:
+ write_string(' ' + mu + '\n', out=sys.stdout)
elif opts.list_extractor_descriptions:
- _SEARCHES = ('cute kittens', 'slithering pythons', 'falling cat', 'angry poodle', 'purple fish', 'running tortoise', 'sleeping bunny', 'burping cow')
- out = '\n'.join(
- ie.description(markdown=False, search_examples=_SEARCHES)
- for ie in list_extractor_classes(opts.age_limit) if ie.working() and ie.IE_DESC is not False)
+ for ie in list_extractors(opts.age_limit):
+ if not ie.working():
+ continue
+ if ie.IE_DESC is False:
+ continue
+ desc = ie.IE_DESC or ie.IE_NAME
+ if getattr(ie, 'SEARCH_KEY', None) is not None:
+ _SEARCHES = ('cute kittens', 'slithering pythons', 'falling cat', 'angry poodle', 'purple fish', 'running tortoise', 'sleeping bunny', 'burping cow')
+ _COUNTS = ('', '5', '10', 'all')
+ desc += f'; "{ie.SEARCH_KEY}:" prefix (Example: "{ie.SEARCH_KEY}{random.choice(_COUNTS)}:{random.choice(_SEARCHES)}")'
+ write_string(desc + '\n', out=sys.stdout)
elif opts.ap_list_mso:
- out = 'Supported TV Providers:\n%s\n' % render_table(
- ['mso', 'mso name'],
- [[mso_id, mso_info['name']] for mso_id, mso_info in MSO_INFO.items()])
+ table = [[mso_id, mso_info['name']] for mso_id, mso_info in MSO_INFO.items()]
+ write_string('Supported TV Providers:\n' + render_table(['mso', 'mso name'], table) + '\n', out=sys.stdout)
else:
return False
- write_string(out, out=sys.stdout)
return True
@@ -152,7 +145,7 @@ def set_compat_opts(opts):
else:
opts.embed_infojson = False
if 'format-sort' in opts.compat_opts:
- opts.format_sort.extend(FormatSorter.ytdl_default)
+ opts.format_sort.extend(InfoExtractor.FormatSort.ytdl_default)
_video_multistreams_set = set_default_compat('multistreams', 'allow_multiple_video_streams', False, remove_compat=False)
_audio_multistreams_set = set_default_compat('multistreams', 'allow_multiple_audio_streams', False, remove_compat=False)
if _video_multistreams_set is False and _audio_multistreams_set is False:
@@ -227,11 +220,9 @@ def validate_options(opts):
# Format sort
for f in opts.format_sort:
- validate_regex('format sorting', f, FormatSorter.regex)
+ validate_regex('format sorting', f, InfoExtractor.FormatSort.regex)
# Postprocessor formats
- validate_regex('merge output format', opts.merge_output_format,
- r'({0})(/({0}))*'.format('|'.join(map(re.escape, FFmpegMergerPP.SUPPORTED_EXTS))))
validate_regex('audio format', opts.audioformat, FFmpegExtractAudioPP.FORMAT_RE)
validate_in('subtitle format', opts.convertsubtitles, FFmpegSubtitlesConvertorPP.SUPPORTED_EXTS)
validate_regex('thumbnail format', opts.convertthumbnails, FFmpegThumbnailsConvertorPP.FORMAT_RE)
@@ -281,19 +272,19 @@ def validate_options(opts):
raise ValueError(f'invalid {key} retry sleep expression {expr!r}')
# Bytes
- def validate_bytes(name, value):
+ def parse_bytes(name, value):
if value is None:
return None
- numeric_limit = parse_bytes(value)
+ numeric_limit = FileDownloader.parse_bytes(value)
validate(numeric_limit is not None, 'rate limit', value)
return numeric_limit
- opts.ratelimit = validate_bytes('rate limit', opts.ratelimit)
- opts.throttledratelimit = validate_bytes('throttled rate limit', opts.throttledratelimit)
- opts.min_filesize = validate_bytes('min filesize', opts.min_filesize)
- opts.max_filesize = validate_bytes('max filesize', opts.max_filesize)
- opts.buffersize = validate_bytes('buffer size', opts.buffersize)
- opts.http_chunk_size = validate_bytes('http chunk size', opts.http_chunk_size)
+ opts.ratelimit = parse_bytes('rate limit', opts.ratelimit)
+ opts.throttledratelimit = parse_bytes('throttled rate limit', opts.throttledratelimit)
+ opts.min_filesize = parse_bytes('min filesize', opts.min_filesize)
+ opts.max_filesize = parse_bytes('max filesize', opts.max_filesize)
+ opts.buffersize = parse_bytes('buffer size', opts.buffersize)
+ opts.http_chunk_size = parse_bytes('http chunk size', opts.http_chunk_size)
# Output templates
def validate_outtmpl(tmpl, msg):
@@ -326,15 +317,14 @@ def validate_options(opts):
def parse_chapters(name, value):
chapters, ranges = [], []
- parse_timestamp = lambda x: float('inf') if x in ('inf', 'infinite') else parse_duration(x)
for regex in value or []:
if regex.startswith('*'):
- for range_ in map(str.strip, regex[1:].split(',')):
- mobj = range_ != '-' and re.fullmatch(r'([^-]+)?\s*-\s*([^-]+)?', range_)
- dur = mobj and (parse_timestamp(mobj.group(1) or '0'), parse_timestamp(mobj.group(2) or 'inf'))
- if None in (dur or [None]):
+ for range in regex[1:].split(','):
+ dur = tuple(map(parse_duration, range.strip().split('-')))
+ if len(dur) == 2 and all(t is not None for t in dur):
+ ranges.append(dur)
+ else:
raise ValueError(f'invalid {name} time range "{regex}". Must be of the form *start-end')
- ranges.append(dur)
continue
try:
chapters.append(re.compile(regex))
@@ -347,16 +337,10 @@ def validate_options(opts):
# Cookies from browser
if opts.cookiesfrombrowser:
- container = None
- mobj = re.fullmatch(r'''(?x)
- (?P<name>[^+:]+)
- (?:\s*\+\s*(?P<keyring>[^:]+))?
- (?:\s*:\s*(?P<profile>.+?))?
- (?:\s*::\s*(?P<container>.+))?
- ''', opts.cookiesfrombrowser)
+ mobj = re.match(r'(?P<name>[^+:]+)(\s*\+\s*(?P<keyring>[^:]+))?(\s*:(?P<profile>.+))?', opts.cookiesfrombrowser)
if mobj is None:
raise ValueError(f'invalid cookies from browser arguments: {opts.cookiesfrombrowser}')
- browser_name, keyring, profile, container = mobj.group('name', 'keyring', 'profile', 'container')
+ browser_name, keyring, profile = mobj.group('name', 'keyring', 'profile')
browser_name = browser_name.lower()
if browser_name not in SUPPORTED_BROWSERS:
raise ValueError(f'unsupported browser specified for cookies: "{browser_name}". '
@@ -366,7 +350,7 @@ def validate_options(opts):
if keyring not in SUPPORTED_KEYRINGS:
raise ValueError(f'unsupported keyring specified for cookies: "{keyring}". '
f'Supported keyrings are: {", ".join(sorted(SUPPORTED_KEYRINGS))}')
- opts.cookiesfrombrowser = (browser_name, profile, keyring, container)
+ opts.cookiesfrombrowser = (browser_name, profile, keyring)
# MetadataParser
def metadataparser_actions(f):
@@ -411,9 +395,6 @@ def validate_options(opts):
if opts.download_archive is not None:
opts.download_archive = expand_path(opts.download_archive)
- if opts.ffmpeg_location is not None:
- opts.ffmpeg_location = expand_path(opts.ffmpeg_location)
-
if opts.user_agent is not None:
opts.headers.setdefault('User-Agent', opts.user_agent)
if opts.referer is not None:
@@ -478,24 +459,22 @@ def validate_options(opts):
report_conflict('--playlist-random', 'playlist_random', '--lazy-playlist', 'lazy_playlist')
report_conflict('--dateafter', 'dateafter', '--date', 'date', default=None)
report_conflict('--datebefore', 'datebefore', '--date', 'date', default=None)
- report_conflict('--exec-before-download', 'exec_before_dl_cmd',
- '"--exec before_dl:"', 'exec_cmd', val2=opts.exec_cmd.get('before_dl'))
+ report_conflict('--exec-before-download', 'exec_before_dl_cmd', '"--exec before_dl:"', 'exec_cmd', opts.exec_cmd.get('before_dl'))
report_conflict('--id', 'useid', '--output', 'outtmpl', val2=opts.outtmpl.get('default'))
report_conflict('--remux-video', 'remuxvideo', '--recode-video', 'recodevideo')
report_conflict('--sponskrub', 'sponskrub', '--remove-chapters', 'remove_chapters')
report_conflict('--sponskrub', 'sponskrub', '--sponsorblock-mark', 'sponsorblock_mark')
report_conflict('--sponskrub', 'sponskrub', '--sponsorblock-remove', 'sponsorblock_remove')
- report_conflict('--sponskrub-cut', 'sponskrub_cut', '--split-chapter', 'split_chapters',
- val1=opts.sponskrub and opts.sponskrub_cut)
+ report_conflict('--sponskrub-cut', 'sponskrub_cut', '--split-chapter', 'split_chapters', val1=opts.sponskrub and opts.sponskrub_cut)
# Conflicts with --allow-unplayable-formats
- report_conflict('--embed-metadata', 'addmetadata')
+ report_conflict('--add-metadata', 'addmetadata')
report_conflict('--embed-chapters', 'addchapters')
report_conflict('--embed-info-json', 'embed_infojson')
report_conflict('--embed-subs', 'embedsubtitles')
report_conflict('--embed-thumbnail', 'embedthumbnail')
report_conflict('--extract-audio', 'extractaudio')
- report_conflict('--fixup', 'fixup', val1=opts.fixup not in (None, 'never', 'ignore'), default='never')
+ report_conflict('--fixup', 'fixup', val1=(opts.fixup or '').lower() in ('', 'never', 'ignore'), default='never')
report_conflict('--recode-video', 'recodevideo')
report_conflict('--remove-chapters', 'remove_chapters', default=[])
report_conflict('--remux-video', 'remuxvideo')
@@ -537,7 +516,7 @@ def validate_options(opts):
# Do not unnecessarily download audio
opts.format = 'bestaudio/best'
- if opts.getcomments and opts.writeinfojson is None and not opts.embed_infojson:
+ if opts.getcomments and opts.writeinfojson is None:
# If JSON is not printed anywhere, but comments are requested, save it to file
if not opts.dumpjson or opts.print_json or opts.dump_single_json:
opts.writeinfojson = True
@@ -686,11 +665,8 @@ def get_postprocessors(opts):
}
-ParsedOptions = collections.namedtuple('ParsedOptions', ('parser', 'options', 'urls', 'ydl_opts'))
-
-
def parse_options(argv=None):
- """@returns ParsedOptions(parser, opts, urls, ydl_opts)"""
+ """ @returns (parser, opts, urls, ydl_opts) """
parser, opts, urls = parseOpts(argv)
urls = get_urls(urls, opts.batchfile, opts.verbose)
@@ -702,26 +678,11 @@ def parse_options(argv=None):
postprocessors = list(get_postprocessors(opts))
- print_only = bool(opts.forceprint) and all(k not in opts.forceprint for k in POSTPROCESS_WHEN[2:])
- any_getting = any(getattr(opts, k) for k in (
- 'dumpjson', 'dump_single_json', 'getdescription', 'getduration', 'getfilename',
- 'getformat', 'getid', 'getthumbnail', 'gettitle', 'geturl'
- ))
-
- playlist_pps = [pp for pp in postprocessors if pp.get('when') == 'playlist']
- write_playlist_infojson = (opts.writeinfojson and not opts.clean_infojson
- and opts.allow_playlist_files and opts.outtmpl.get('pl_infojson') != '')
- if not any((
- opts.extract_flat,
- opts.dump_single_json,
- opts.forceprint.get('playlist'),
- opts.print_to_file.get('playlist'),
- write_playlist_infojson,
- )):
- if not playlist_pps:
- opts.extract_flat = 'discard'
- elif playlist_pps == [{'key': 'FFmpegConcat', 'only_multi_video': True, 'when': 'playlist'}]:
- opts.extract_flat = 'discard_in_playlist'
+ any_getting = (any(opts.forceprint.values()) or opts.dumpjson or opts.dump_single_json
+ or opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail
+ or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration)
+
+ any_printing = opts.print_json
final_ext = (
opts.recodevideo if opts.recodevideo in FFmpegVideoConvertorPP.SUPPORTED_EXTS
@@ -729,7 +690,7 @@ def parse_options(argv=None):
else opts.audioformat if (opts.extractaudio and opts.audioformat in FFmpegExtractAudioPP.SUPPORTED_EXTS)
else None)
- return ParsedOptions(parser, opts, urls, {
+ return parser, opts, urls, {
'usenetrc': opts.usenetrc,
'netrc_location': opts.netrc_location,
'username': opts.username,
@@ -739,10 +700,7 @@ def parse_options(argv=None):
'ap_mso': opts.ap_mso,
'ap_username': opts.ap_username,
'ap_password': opts.ap_password,
- 'client_certificate': opts.client_certificate,
- 'client_certificate_key': opts.client_certificate_key,
- 'client_certificate_password': opts.client_certificate_password,
- 'quiet': opts.quiet or any_getting or opts.print_json or bool(opts.forceprint),
+ 'quiet': (opts.quiet or any_getting or any_printing),
'no_warnings': opts.no_warnings,
'forceurl': opts.geturl,
'forcetitle': opts.gettitle,
@@ -757,7 +715,7 @@ def parse_options(argv=None):
'forcejson': opts.dumpjson or opts.print_json,
'dump_single_json': opts.dump_single_json,
'force_write_download_archive': opts.force_write_download_archive,
- 'simulate': (print_only or any_getting or None) if opts.simulate is None else opts.simulate,
+ 'simulate': (any_getting or None) if opts.simulate is None else opts.simulate,
'skip_download': opts.skip_download,
'format': opts.format,
'allow_unplayable_formats': opts.allow_unplayable_formats,
@@ -778,7 +736,6 @@ def parse_options(argv=None):
'windowsfilenames': opts.windowsfilenames,
'ignoreerrors': opts.ignoreerrors,
'force_generic_extractor': opts.force_generic_extractor,
- 'allowed_extractors': opts.allowed_extractors or ['default'],
'ratelimit': opts.ratelimit,
'throttledratelimit': opts.throttledratelimit,
'overwrites': opts.overwrites,
@@ -903,10 +860,17 @@ def parse_options(argv=None):
'_warnings': warnings,
'_deprecation_warnings': deprecation_warnings,
'compat_opts': opts.compat_opts,
- })
+ }
def _real_main(argv=None):
+ # Compatibility fixes for Windows
+ if sys.platform == 'win32':
+ # https://github.com/ytdl-org/youtube-dl/issues/820
+ codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None)
+
+ workaround_optparse_bug9161()
+
setproctitle('yt-dlp')
parser, opts, all_urls, ydl_opts = parse_options(argv)
@@ -920,11 +884,6 @@ def _real_main(argv=None):
if print_extractor_information(opts, all_urls):
return
- # We may need ffmpeg_location without having access to the YoutubeDL instance
- # See https://github.com/yt-dlp/yt-dlp/issues/2191
- if opts.ffmpeg_location:
- FFmpegPostProcessor._ffmpeg_location.set(opts.ffmpeg_location)
-
with YoutubeDL(ydl_opts) as ydl:
pre_process = opts.update_self or opts.rm_cachedir
actual_use = all_urls or opts.load_info_filename
@@ -962,8 +921,6 @@ def _real_main(argv=None):
def main(argv=None):
- global _IN_CLI
- _IN_CLI = True
try:
_exit(*variadic(_real_main(argv)))
except DownloadError: