mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-02-01 11:36:55 +00:00
Compare commits
8 Commits
2026.01.29
...
2026.01.31
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
891613b098 | ||
|
|
9a9a6b6fe4 | ||
|
|
8eb794366e | ||
|
|
c3674575fa | ||
|
|
bb1c05752c | ||
|
|
bf5d8c2a66 | ||
|
|
d0bf3d0fc3 | ||
|
|
0d8ee637e8 |
1
.github/workflows/challenge-tests.yml
vendored
1
.github/workflows/challenge-tests.yml
vendored
@@ -26,6 +26,7 @@ concurrency:
|
||||
jobs:
|
||||
tests:
|
||||
name: Challenge Tests
|
||||
if: ${{ !contains(github.event.head_commit.message, ':ci skip') }}
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
2
.github/workflows/core.yml
vendored
2
.github/workflows/core.yml
vendored
@@ -32,7 +32,7 @@ concurrency:
|
||||
jobs:
|
||||
tests:
|
||||
name: Core Tests
|
||||
if: ${{ !contains(github.event.head_commit.message, 'ci skip') }}
|
||||
if: ${{ !contains(github.event.head_commit.message, ':ci skip') }}
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
4
.github/workflows/quick-test.yml
vendored
4
.github/workflows/quick-test.yml
vendored
@@ -10,7 +10,7 @@ concurrency:
|
||||
jobs:
|
||||
tests:
|
||||
name: Core Test
|
||||
if: ${{ !contains(github.event.head_commit.message, 'ci skip all') }}
|
||||
if: ${{ !contains(github.event.head_commit.message, ':ci skip all') }}
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
python3 ./devscripts/run_tests.py --pytest-args '--reruns 2 --reruns-delay 3.0' core
|
||||
check:
|
||||
name: Code check
|
||||
if: ${{ !contains(github.event.head_commit.message, 'ci skip all') }}
|
||||
if: ${{ !contains(github.event.head_commit.message, ':ci skip all') }}
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -862,3 +862,5 @@ quietvoid
|
||||
romainreignier
|
||||
Sytm
|
||||
zahlman
|
||||
azdlonky
|
||||
thematuu
|
||||
|
||||
14
Changelog.md
14
Changelog.md
@@ -4,6 +4,20 @@
|
||||
# To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master
|
||||
-->
|
||||
|
||||
### 2026.01.31
|
||||
|
||||
#### Extractor changes
|
||||
- **soop**: [Support subscription-only VODs](https://github.com/yt-dlp/yt-dlp/commit/d0bf3d0fc3455d411ae44c0a5dc974dd1481e3aa) ([#15523](https://github.com/yt-dlp/yt-dlp/issues/15523)) by [thematuu](https://github.com/thematuu)
|
||||
- **unsupported**: [Update unsupported URLs](https://github.com/yt-dlp/yt-dlp/commit/bf5d8c2a663ac690711262aebc733c1b06a54b26) ([#15410](https://github.com/yt-dlp/yt-dlp/issues/15410)) by [bashonly](https://github.com/bashonly)
|
||||
- **whyp**: [Extract more metadata](https://github.com/yt-dlp/yt-dlp/commit/0d8ee637e83d62edaf22aa85833a51c70d560389) ([#15757](https://github.com/yt-dlp/yt-dlp/issues/15757)) by [azdlonky](https://github.com/azdlonky)
|
||||
- **youtube**
|
||||
- [Add `web_embedded` fallback for `android_vr` client](https://github.com/yt-dlp/yt-dlp/commit/bb1c05752c288a81e0e281f1caf5395411936376) ([#15785](https://github.com/yt-dlp/yt-dlp/issues/15785)) by [bashonly](https://github.com/bashonly)
|
||||
- [Remove broken `ios_downgraded` player client](https://github.com/yt-dlp/yt-dlp/commit/c3674575faa23b20e97be8b73f68b9f7b4cea9ab) ([#15786](https://github.com/yt-dlp/yt-dlp/issues/15786)) by [bashonly](https://github.com/bashonly)
|
||||
- [Remove broken `tv_embedded` player client](https://github.com/yt-dlp/yt-dlp/commit/8eb794366eb69e7377ff88eed7929c00195c8d74) ([#15787](https://github.com/yt-dlp/yt-dlp/issues/15787)) by [bashonly](https://github.com/bashonly)
|
||||
|
||||
#### Misc. changes
|
||||
- **cleanup**: Miscellaneous: [9a9a6b6](https://github.com/yt-dlp/yt-dlp/commit/9a9a6b6fe44a30458c1754ef064f354f04a84004) by [bashonly](https://github.com/bashonly)
|
||||
|
||||
### 2026.01.29
|
||||
|
||||
#### Core changes
|
||||
|
||||
@@ -1860,7 +1860,7 @@ The following extractors use this feature:
|
||||
#### youtube
|
||||
* `lang`: Prefer translated metadata (`title`, `description` etc) of this language code (case-sensitive). By default, the video primary language metadata is preferred, with a fallback to `en` translated. See [youtube/_base.py](https://github.com/yt-dlp/yt-dlp/blob/415b4c9f955b1a0391204bd24a7132590e7b3bdb/yt_dlp/extractor/youtube/_base.py#L402-L409) for the list of supported content language codes
|
||||
* `skip`: One or more of `hls`, `dash` or `translated_subs` to skip extraction of the m3u8 manifests, dash manifests and [auto-translated subtitles](https://github.com/yt-dlp/yt-dlp/issues/4090#issuecomment-1158102032) respectively
|
||||
* `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `ios_downgraded`, `android`, `android_vr`, `tv`, `tv_simply`, `tv_downgraded`, and `tv_embedded`. By default, `android_vr,ios_downgraded,web,web_safari` is used. If no JavaScript runtime/engine is available, then `android_vr,ios_downgraded` is used. If logged-in cookies are passed to yt-dlp, then `tv_downgraded,web,web_safari` is used for free accounts and `tv_downgraded,web_creator,web` is used for premium accounts. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `web_embedded` client is added for age-restricted videos but only works if the video is embeddable. The `tv_embedded` and `web_creator` clients are added for age-restricted videos if account age-verification is required. Some clients, such as `web` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-ios`
|
||||
* `player_client`: Clients to extract video data from. The currently available clients are `web`, `web_safari`, `web_embedded`, `web_music`, `web_creator`, `mweb`, `ios`, `android`, `android_vr`, `tv`, `tv_downgraded`, and `tv_simply`. By default, `android_vr,web,web_safari` is used. If no JavaScript runtime/engine is available, then only `android_vr` is used. If logged-in cookies are passed to yt-dlp, then `tv_downgraded,web,web_safari` is used for free accounts and `tv_downgraded,web_creator,web` is used for premium accounts. The `web_music` client is added for `music.youtube.com` URLs when logged-in cookies are used. The `web_embedded` client is added for age-restricted videos but only successfully works around the age-restriction sometimes (e.g. if the video is embeddable), and may be added as a fallback if `android_vr` is unable to access a video. The `web_creator` client is added for age-restricted videos if account age-verification is required. Some clients, such as `web_creator` and `web_music`, require a `po_token` for their formats to be downloadable. Some clients, such as `web_creator`, will only work with authentication. Not all clients support authentication via cookies. You can use `default` for the default clients, or you can use `all` for all clients (not recommended). You can prefix a client with `-` to exclude it, e.g. `youtube:player_client=default,-web`
|
||||
* `player_skip`: Skip some network requests that are generally needed for robust extraction. One or more of `configs` (skip client configs), `webpage` (skip initial webpage), `js` (skip js player), `initial_data` (skip initial data/next ep request). While these options can help reduce the number of requests needed or avoid some rate-limiting, they could cause issues such as missing formats or metadata. See [#860](https://github.com/yt-dlp/yt-dlp/pull/860) and [#12826](https://github.com/yt-dlp/yt-dlp/issues/12826) for more details
|
||||
* `webpage_skip`: Skip extraction of embedded webpage data. One or both of `player_response`, `initial_data`. These options are for testing purposes and don't skip any network requests
|
||||
* `player_params`: YouTube player parameters to use for player requests. Will overwrite any default ones set by yt-dlp.
|
||||
|
||||
@@ -36,7 +36,6 @@ class Variant(enum.Enum):
|
||||
tv = 'tv-player-ias.vflset/tv-player-ias.js'
|
||||
tv_es6 = 'tv-player-es6.vflset/tv-player-es6.js'
|
||||
phone = 'player-plasma-ias-phone-en_US.vflset/base.js'
|
||||
tablet = 'player-plasma-ias-tablet-en_US.vflset/base.js'
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
|
||||
@@ -36,6 +36,7 @@ from .rtsp import RtspFD
|
||||
from .websocket import WebSocketFragmentFD
|
||||
from .youtube_live_chat import YoutubeLiveChatFD
|
||||
from .bunnycdn import BunnyCdnFD
|
||||
from .soop import SoopVodFD
|
||||
|
||||
PROTOCOL_MAP = {
|
||||
'rtmp': RtmpFD,
|
||||
@@ -56,6 +57,7 @@ PROTOCOL_MAP = {
|
||||
'youtube_live_chat': YoutubeLiveChatFD,
|
||||
'youtube_live_chat_replay': YoutubeLiveChatFD,
|
||||
'bunnycdn': BunnyCdnFD,
|
||||
'soopvod': SoopVodFD,
|
||||
}
|
||||
|
||||
|
||||
|
||||
61
yt_dlp/downloader/soop.py
Normal file
61
yt_dlp/downloader/soop.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import threading
|
||||
import time
|
||||
|
||||
from .common import FileDownloader
|
||||
from . import HlsFD
|
||||
from ..extractor.afreecatv import _cloudfront_auth_request
|
||||
from ..networking.exceptions import network_exceptions
|
||||
|
||||
|
||||
class SoopVodFD(FileDownloader):
|
||||
"""
|
||||
Downloads Soop subscription VODs with required cookie refresh requests
|
||||
Note, this is not a part of public API, and will be removed without notice.
|
||||
DO NOT USE
|
||||
"""
|
||||
|
||||
def real_download(self, filename, info_dict):
|
||||
self.to_screen(f'[{self.FD_NAME}] Downloading Soop subscription VOD HLS')
|
||||
fd = HlsFD(self.ydl, self.params)
|
||||
refresh_params = info_dict['_cookie_refresh_params']
|
||||
referer_url = info_dict['webpage_url']
|
||||
|
||||
stop_event = threading.Event()
|
||||
refresh_thread = threading.Thread(
|
||||
target=self._cookie_refresh_thread,
|
||||
args=(stop_event, refresh_params, referer_url),
|
||||
)
|
||||
refresh_thread.start()
|
||||
|
||||
try:
|
||||
return fd.real_download(filename, info_dict)
|
||||
finally:
|
||||
stop_event.set()
|
||||
|
||||
def _cookie_refresh_thread(self, stop_event, refresh_params, referer_url):
|
||||
m3u8_url = refresh_params['m3u8_url']
|
||||
strm_id = refresh_params['strm_id']
|
||||
video_id = refresh_params['video_id']
|
||||
|
||||
def _get_cloudfront_cookie_expiration(m3u8_url):
|
||||
cookies = self.ydl.cookiejar.get_cookies_for_url(m3u8_url)
|
||||
return min((cookie.expires for cookie in cookies if 'CloudFront' in cookie.name and cookie.expires), default=0)
|
||||
|
||||
while not stop_event.wait(5):
|
||||
current_time = time.time()
|
||||
expiration_time = _get_cloudfront_cookie_expiration(m3u8_url)
|
||||
last_refresh_check = refresh_params.get('_last_refresh', 0)
|
||||
|
||||
# Cookie TTL is 90 seconds, but let's give ourselves a 15-second cushion
|
||||
should_refresh = (
|
||||
(expiration_time and current_time >= expiration_time - 15)
|
||||
or (not expiration_time and current_time - last_refresh_check >= 75)
|
||||
)
|
||||
|
||||
if should_refresh:
|
||||
try:
|
||||
self.ydl.urlopen(_cloudfront_auth_request(
|
||||
m3u8_url, strm_id, video_id, referer_url)).read()
|
||||
refresh_params['_last_refresh'] = current_time
|
||||
except network_exceptions as e:
|
||||
self.to_screen(f'[{self.FD_NAME}] Cookie refresh attempt failed: {e}')
|
||||
@@ -1,5 +1,6 @@
|
||||
import datetime as dt
|
||||
import functools
|
||||
import time
|
||||
|
||||
from .common import InfoExtractor
|
||||
from ..networking import Request
|
||||
@@ -16,7 +17,23 @@ from ..utils import (
|
||||
urlencode_postdata,
|
||||
urljoin,
|
||||
)
|
||||
from ..utils.traversal import traverse_obj
|
||||
from ..utils.traversal import require, traverse_obj
|
||||
|
||||
|
||||
def _cloudfront_auth_request(m3u8_url, strm_id, video_id, referer_url):
|
||||
return Request(
|
||||
'https://live.sooplive.co.kr/api/private_auth.php',
|
||||
method='POST',
|
||||
headers={
|
||||
'Referer': referer_url,
|
||||
'Origin': 'https://vod.sooplive.co.kr',
|
||||
},
|
||||
data=urlencode_postdata({
|
||||
'type': 'vod',
|
||||
'strm_id': strm_id,
|
||||
'title_no': video_id,
|
||||
'url': m3u8_url,
|
||||
}))
|
||||
|
||||
|
||||
class AfreecaTVBaseIE(InfoExtractor):
|
||||
@@ -153,6 +170,13 @@ class AfreecaTVIE(AfreecaTVBaseIE):
|
||||
'nApiLevel': 10,
|
||||
}))['data']
|
||||
|
||||
initial_refresh_time = 0
|
||||
strm_id = None
|
||||
# For subscriber-only VODs, we need to call private_auth.php to get CloudFront cookies
|
||||
needs_private_auth = traverse_obj(data, ('sub_upload_type', {str}))
|
||||
if needs_private_auth:
|
||||
strm_id = traverse_obj(data, ('bj_id', {str}, {require('stream ID')}))
|
||||
|
||||
error_code = traverse_obj(data, ('code', {int}))
|
||||
if error_code == -6221:
|
||||
raise ExtractorError('The VOD does not exist', expected=True)
|
||||
@@ -172,9 +196,23 @@ class AfreecaTVIE(AfreecaTVBaseIE):
|
||||
traverse_obj(data, ('files', lambda _, v: url_or_none(v['file']))), start=1):
|
||||
file_url = file_element['file']
|
||||
if determine_ext(file_url) == 'm3u8':
|
||||
if needs_private_auth:
|
||||
self._request_webpage(
|
||||
_cloudfront_auth_request(file_url, strm_id, video_id, url),
|
||||
video_id, 'Requesting CloudFront cookies', 'Failed to get CloudFront cookies')
|
||||
initial_refresh_time = time.time()
|
||||
formats = self._extract_m3u8_formats(
|
||||
file_url, video_id, 'mp4', m3u8_id='hls',
|
||||
note=f'Downloading part {file_num} m3u8 information')
|
||||
if needs_private_auth:
|
||||
for fmt in formats:
|
||||
fmt['protocol'] = 'soopvod'
|
||||
fmt['_cookie_refresh_params'] = {
|
||||
'm3u8_url': file_url,
|
||||
'strm_id': strm_id,
|
||||
'video_id': video_id,
|
||||
'_last_refresh': initial_refresh_time,
|
||||
}
|
||||
else:
|
||||
formats = [{
|
||||
'url': file_url,
|
||||
|
||||
@@ -66,6 +66,7 @@ class KnownDRMIE(UnsupportedInfoExtractor):
|
||||
r'rtlmost\.hu',
|
||||
r'plus\.rtl\.de(?!/podcast/)',
|
||||
r'mediasetinfinity\.es',
|
||||
r'tv5mondeplus\.com',
|
||||
)
|
||||
|
||||
_TESTS = [{
|
||||
@@ -226,6 +227,10 @@ class KnownDRMIE(UnsupportedInfoExtractor):
|
||||
}, {
|
||||
'url': 'https://www.mediasetinfinity.es/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# https://github.com/yt-dlp/yt-dlp/issues/14743
|
||||
'url': 'https://www.tv5mondeplus.com/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
||||
@@ -2,6 +2,7 @@ from .common import InfoExtractor
|
||||
from ..utils import (
|
||||
float_or_none,
|
||||
int_or_none,
|
||||
parse_iso8601,
|
||||
str_or_none,
|
||||
traverse_obj,
|
||||
url_or_none,
|
||||
@@ -16,9 +17,12 @@ class WhypIE(InfoExtractor):
|
||||
'info_dict': {
|
||||
'id': '18337',
|
||||
'title': 'Example Track',
|
||||
'display_id': 'example-track',
|
||||
'description': 'md5:e0b1bcf1d267dc1a0f15efff09c8f297',
|
||||
'ext': 'flac',
|
||||
'duration': 135.63,
|
||||
'timestamp': 1643216583,
|
||||
'upload_date': '20220126',
|
||||
'uploader': 'Brad',
|
||||
'uploader_id': '1',
|
||||
'thumbnail': 'https://cdn.whyp.it/6ad0bbd9-577d-42bb-9b61-2a4f57f647eb.jpg',
|
||||
@@ -44,10 +48,12 @@ class WhypIE(InfoExtractor):
|
||||
'http_headers': {'Referer': 'https://whyp.it/'},
|
||||
} for prefix in ('audio', 'lossy', 'lossless') if url_or_none(data.get(f'{prefix}_url'))],
|
||||
**traverse_obj(data, {
|
||||
'title': 'title',
|
||||
'title': ('title', {str}),
|
||||
'display_id': ('slug', {str}),
|
||||
'description': 'description',
|
||||
'duration': ('duration', {float_or_none}),
|
||||
'uploader': ('user', 'username'),
|
||||
'timestamp': ('created_at', {parse_iso8601}),
|
||||
'uploader': ('user', 'username', {str}),
|
||||
'uploader_id': ('user', 'id', {str_or_none}),
|
||||
'thumbnail': ('artwork_url', {url_or_none}),
|
||||
}),
|
||||
|
||||
@@ -271,34 +271,6 @@ INNERTUBE_CLIENTS = {
|
||||
'PLAYER_PO_TOKEN_POLICY': PlayerPoTokenPolicy(required=False, recommended=True),
|
||||
'REQUIRE_JS_PLAYER': False,
|
||||
},
|
||||
'ios_downgraded': {
|
||||
'INNERTUBE_CONTEXT': {
|
||||
'client': {
|
||||
'clientName': 'IOS',
|
||||
'clientVersion': '19.49.7',
|
||||
'deviceMake': 'Apple',
|
||||
'deviceModel': 'iPhone16,2',
|
||||
'userAgent': 'com.google.ios.youtube/19.49.7 (iPhone16,2; U; CPU iOS 17_5_1 like Mac OS X;)',
|
||||
'osName': 'iPhone',
|
||||
'osVersion': '17.5.1.21F90',
|
||||
},
|
||||
},
|
||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 5,
|
||||
'GVS_PO_TOKEN_POLICY': {
|
||||
StreamingProtocol.HTTPS: GvsPoTokenPolicy(
|
||||
required=True,
|
||||
recommended=True,
|
||||
not_required_with_player_token=True,
|
||||
),
|
||||
StreamingProtocol.HLS: GvsPoTokenPolicy(
|
||||
required=False,
|
||||
recommended=True,
|
||||
not_required_with_player_token=True,
|
||||
),
|
||||
},
|
||||
'PLAYER_PO_TOKEN_POLICY': PlayerPoTokenPolicy(required=False, recommended=True),
|
||||
'REQUIRE_JS_PLAYER': False,
|
||||
},
|
||||
# mweb has 'ultralow' formats
|
||||
# See: https://github.com/yt-dlp/yt-dlp/pull/557
|
||||
'mweb': {
|
||||
@@ -379,20 +351,6 @@ INNERTUBE_CLIENTS = {
|
||||
},
|
||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 75,
|
||||
},
|
||||
# This client now requires sign-in for every video
|
||||
# It was previously an age-gate workaround for videos that were `playable_in_embed`
|
||||
# It may still be useful if signed into an EU account that is not age-verified
|
||||
'tv_embedded': {
|
||||
'INNERTUBE_CONTEXT': {
|
||||
'client': {
|
||||
'clientName': 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
|
||||
'clientVersion': '2.0',
|
||||
},
|
||||
},
|
||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 85,
|
||||
'REQUIRE_AUTH': True,
|
||||
'SUPPORTS_COOKIES': True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -145,8 +145,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
r'\b(?P<id>vfl[a-zA-Z0-9_-]+)\b.*?\.js$',
|
||||
)
|
||||
_SUBTITLE_FORMATS = ('json3', 'srv1', 'srv2', 'srv3', 'ttml', 'srt', 'vtt')
|
||||
_DEFAULT_CLIENTS = ('android_vr', 'ios_downgraded', 'web', 'web_safari')
|
||||
_DEFAULT_JSLESS_CLIENTS = ('android_vr', 'ios_downgraded')
|
||||
_DEFAULT_CLIENTS = ('android_vr', 'web', 'web_safari')
|
||||
_DEFAULT_JSLESS_CLIENTS = ('android_vr',)
|
||||
_DEFAULT_AUTHED_CLIENTS = ('tv_downgraded', 'web', 'web_safari')
|
||||
# Premium does not require POT (except for subtitles)
|
||||
_DEFAULT_PREMIUM_CLIENTS = ('tv_downgraded', 'web_creator', 'web')
|
||||
@@ -1443,7 +1443,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
'view_count': int,
|
||||
},
|
||||
'params': {
|
||||
'extractor_args': {'youtube': {'player_client': ['tv_embedded']}},
|
||||
'format': '251-drc',
|
||||
'skip_download': True,
|
||||
},
|
||||
@@ -3118,6 +3117,15 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
else:
|
||||
prs.append(pr)
|
||||
|
||||
if (
|
||||
# Is this a "made for kids" video that can't be downloaded with android_vr?
|
||||
client == 'android_vr' and self._is_unplayable(pr)
|
||||
and webpage and 'made for kids' in webpage
|
||||
# ...and is a JS runtime is available?
|
||||
and any(p.is_available() for p in self._jsc_director.providers.values())
|
||||
):
|
||||
append_client('web_embedded')
|
||||
|
||||
# web_embedded can work around age-gate and age-verification for some embeddable videos
|
||||
if self._is_agegated(pr) and variant != 'web_embedded':
|
||||
append_client(f'web_embedded.{base_client}')
|
||||
@@ -3134,9 +3142,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
self.to_screen(
|
||||
f'{video_id}: This video is age-restricted and YouTube is requiring '
|
||||
'account age-verification; some formats may be missing', only_once=True)
|
||||
# tv_embedded can work around the age-verification requirement for embeddable videos
|
||||
# web_creator may work around age-verification for all videos but requires PO token
|
||||
append_client('tv_embedded', 'web_creator')
|
||||
append_client('web_creator')
|
||||
|
||||
status = traverse_obj(pr, ('playabilityStatus', 'status', {str}))
|
||||
if status not in ('OK', 'LIVE_STREAM_OFFLINE', 'AGE_CHECK_REQUIRED', 'AGE_VERIFICATION_REQUIRED'):
|
||||
@@ -3595,10 +3602,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||
if client_name == 'web_safari' and proto == 'hls' and live_status != 'is_live':
|
||||
f['source_preference'] -= 1
|
||||
|
||||
# Safeguard against inevitable ios_downgraded client breakage
|
||||
if client_name == 'ios_downgraded' and proto == 'hls' and live_status != 'is_live':
|
||||
f['__needs_testing'] = True
|
||||
|
||||
if missing_pot:
|
||||
f['format_note'] = join_nonempty(f.get('format_note'), 'MISSING POT', delim=' ')
|
||||
f['source_preference'] -= 20
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Autogenerated by devscripts/update-version.py
|
||||
|
||||
__version__ = '2026.01.29'
|
||||
__version__ = '2026.01.31'
|
||||
|
||||
RELEASE_GIT_HEAD = '8b275536d945c4b3d07b6c520677922c67a7c10f'
|
||||
RELEASE_GIT_HEAD = '9a9a6b6fe44a30458c1754ef064f354f04a84004'
|
||||
|
||||
VARIANT = None
|
||||
|
||||
@@ -12,4 +12,4 @@ CHANNEL = 'stable'
|
||||
|
||||
ORIGIN = 'yt-dlp/yt-dlp'
|
||||
|
||||
_pkg_version = '2026.01.29'
|
||||
_pkg_version = '2026.01.31'
|
||||
|
||||
Reference in New Issue
Block a user