diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e523154c41..389f6d6025 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -208,7 +208,7 @@ jobs: python3.9 -m pip install -U pip wheel 'setuptools>=71.0.2' # XXX: Keep this in sync with pyproject.toml (it can't be accessed at this stage) and exclude secretstorage python3.9 -m pip install -U Pyinstaller mutagen pycryptodomex brotli certifi cffi \ - 'requests>=2.32.2,<3' 'urllib3>=1.26.17,<3' 'websockets>=13.0' 'protobug==0.3.0' + 'requests>=2.32.2,<3' 'urllib3>=2.0.2,<3' 'websockets>=13.0' 'protobug==0.3.0' run: | cd repo diff --git a/README.md b/README.md index 1f8c7936e4..b3b77caa6d 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,9 @@ # To install nightly with pip: python3 -m pip install -U --pre "yt-dlp[default]" ``` +When running a yt-dlp version that is older than 90 days, you will see a warning message suggesting to update to the latest version. +You can suppress this warning by adding `--no-update` to your command or configuration file. + ## DEPENDENCIES Python versions 3.9+ (CPython) and 3.11+ (PyPy) are supported. Other versions and implementations may or may not work correctly. diff --git a/devscripts/cli_to_api.py b/devscripts/cli_to_api.py index 9c2710e09f..cc86b413f4 100755 --- a/devscripts/cli_to_api.py +++ b/devscripts/cli_to_api.py @@ -20,6 +20,7 @@ def parse_patched_options(opts): 'fragment_retries': 0, 'extract_flat': False, 'concat_playlist': 'never', + 'update_self': False, }) yt_dlp.options.create_parser = lambda: patched_parser try: diff --git a/pyproject.toml b/pyproject.toml index e3e8baf996..51e4f5f0e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ default = [ "mutagen", "pycryptodomex", "requests>=2.32.2,<3", - "urllib3>=1.26.17,<3", + "urllib3>=2.0.2,<3", "websockets>=13.0", "protobug==0.3.0", ] diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 37d6eae4c5..7049c70e08 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -73,6 +73,7 @@ from .update import ( REPOSITORY, _get_system_deprecation, + _get_outdated_warning, _make_label, current_git_head, detect_variant, @@ -504,6 +505,7 @@ class YoutubeDL: force_keyframes_at_cuts: Re-encode the video when downloading ranges to get precise cuts noprogress: Do not print the progress bar live_from_start: Whether to download livestreams videos from the start + warn_when_outdated: Emit a warning if the yt-dlp version is older than 90 days The following parameters are not used by YoutubeDL itself, they are used by the downloader (see yt_dlp/downloader/common.py): @@ -703,6 +705,9 @@ def process_color_policy(stream): system_deprecation = _get_system_deprecation() if system_deprecation: self.deprecated_feature(system_deprecation.replace('\n', '\n ')) + elif self.params.get('warn_when_outdated'): + if outdated_warning := _get_outdated_warning(): + self.report_warning(outdated_warning) if self.params.get('allow_unplayable_formats'): self.report_warning( diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index 2e7646b7ec..f705bed1bf 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -971,6 +971,7 @@ def parse_options(argv=None): 'geo_bypass': opts.geo_bypass, 'geo_bypass_country': opts.geo_bypass_country, 'geo_bypass_ip_block': opts.geo_bypass_ip_block, + 'warn_when_outdated': opts.update_self is None, '_warnings': warnings, '_deprecation_warnings': deprecation_warnings, 'compat_opts': opts.compat_opts, diff --git a/yt_dlp/downloader/dash.py b/yt_dlp/downloader/dash.py index afc79b6caf..bf8652d8b0 100644 --- a/yt_dlp/downloader/dash.py +++ b/yt_dlp/downloader/dash.py @@ -3,7 +3,7 @@ from . import get_suitable_downloader from .fragment import FragmentFD -from ..utils import update_url_query, urljoin +from ..utils import ReExtractInfo, update_url_query, urljoin class DashSegmentsFD(FragmentFD): @@ -28,6 +28,11 @@ def real_download(self, filename, info_dict): requested_formats = [{**info_dict, **fmt} for fmt in info_dict.get('requested_formats', [])] args = [] for fmt in requested_formats or [info_dict]: + # Re-extract if --load-info-json is used and 'fragments' was originally a generator + # See https://github.com/yt-dlp/yt-dlp/issues/13906 + if isinstance(fmt['fragments'], str): + raise ReExtractInfo('the stream needs to be re-extracted', expected=True) + try: fragment_count = 1 if self.params.get('test') else len(fmt['fragments']) except TypeError: diff --git a/yt_dlp/extractor/_extractors.py b/yt_dlp/extractor/_extractors.py index 3eea0cdf6b..bb595f924b 100644 --- a/yt_dlp/extractor/_extractors.py +++ b/yt_dlp/extractor/_extractors.py @@ -1866,6 +1866,7 @@ from .sharepoint import SharePointIE from .sharevideos import ShareVideosEmbedIE from .shemaroome import ShemarooMeIE +from .shiey import ShieyIE from .showroomlive import ShowRoomLiveIE from .sibnet import SibnetEmbedIE from .simplecast import ( diff --git a/yt_dlp/extractor/digitalconcerthall.py b/yt_dlp/extractor/digitalconcerthall.py index 4c4fe470da..be16f5e873 100644 --- a/yt_dlp/extractor/digitalconcerthall.py +++ b/yt_dlp/extractor/digitalconcerthall.py @@ -4,6 +4,7 @@ from ..networking.exceptions import HTTPError from ..utils import ( ExtractorError, + determine_ext, jwt_decode_hs256, parse_codecs, try_get, @@ -222,11 +223,18 @@ def _entries(self, items, language, type_, **kwargs): raise formats = [] - for m3u8_url in traverse_obj(stream_info, ('channel', ..., 'stream', ..., 'url', {url_or_none})): - formats.extend(self._extract_m3u8_formats(m3u8_url, video_id, 'mp4', m3u8_id='hls', fatal=False)) - for fmt in formats: - if fmt.get('format_note') and fmt.get('vcodec') == 'none': - fmt.update(parse_codecs(fmt['format_note'])) + for fmt_url in traverse_obj(stream_info, ('channel', ..., 'stream', ..., 'url', {url_or_none})): + ext = determine_ext(fmt_url) + if ext == 'm3u8': + fmts = self._extract_m3u8_formats(fmt_url, video_id, 'mp4', m3u8_id='hls', fatal=False) + for fmt in fmts: + if fmt.get('format_note') and fmt.get('vcodec') == 'none': + fmt.update(parse_codecs(fmt['format_note'])) + formats.extend(fmts) + elif ext == 'mpd': + formats.extend(self._extract_mpd_formats(fmt_url, video_id, mpd_id='dash', fatal=False)) + else: + self.report_warning(f'Skipping unsupported format extension "{ext}"') yield { 'id': video_id, diff --git a/yt_dlp/extractor/motherless.py b/yt_dlp/extractor/motherless.py index 86551950b7..e236ec3db8 100644 --- a/yt_dlp/extractor/motherless.py +++ b/yt_dlp/extractor/motherless.py @@ -51,23 +51,7 @@ class MotherlessIE(InfoExtractor): 'skip': '404', }, { 'url': 'http://motherless.com/g/cosplay/633979F', - 'md5': '0b2a43f447a49c3e649c93ad1fafa4a0', - 'info_dict': { - 'id': '633979F', - 'ext': 'mp4', - 'title': 'Turtlette', - 'categories': ['superheroine heroine superher'], - 'upload_date': '20140827', - 'uploader_id': 'shade0230', - 'thumbnail': r're:https?://.*\.jpg', - 'age_limit': 18, - 'like_count': int, - 'comment_count': int, - 'view_count': int, - }, - 'params': { - 'nocheckcertificate': True, - }, + 'expected_exception': 'ExtractorError', }, { 'url': 'http://motherless.com/8B4BBC1', 'info_dict': { @@ -113,8 +97,10 @@ def _real_extract(self, url): webpage = self._download_webpage(url, video_id) if any(p in webpage for p in ( - '