mirror of
https://github.com/yt-dlp/yt-dlp
synced 2024-12-26 21:59:08 +01:00
Merge branch 'yt-dlp:master' into fix/tct
This commit is contained in:
commit
34d057efc3
13 changed files with 223 additions and 126 deletions
|
@ -707,3 +707,7 @@ Sakura286
|
||||||
SamDecrock
|
SamDecrock
|
||||||
stratus-ss
|
stratus-ss
|
||||||
subrat-lima
|
subrat-lima
|
||||||
|
gitninja1234
|
||||||
|
jkruse
|
||||||
|
xiaomac
|
||||||
|
wesson09
|
||||||
|
|
49
Changelog.md
49
Changelog.md
|
@ -4,6 +4,55 @@
|
||||||
# To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master
|
# To create a release, dispatch the https://github.com/yt-dlp/yt-dlp/actions/workflows/release.yml workflow on master
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
### 2024.12.06
|
||||||
|
|
||||||
|
#### Core changes
|
||||||
|
- **cookies**: [Add `--cookies-from-browser` support for MS Store Firefox](https://github.com/yt-dlp/yt-dlp/commit/354cb4026cf2191e1a130ec2a627b95cabfbc60a) ([#11731](https://github.com/yt-dlp/yt-dlp/issues/11731)) by [wesson09](https://github.com/wesson09)
|
||||||
|
|
||||||
|
#### Extractor changes
|
||||||
|
- **bilibili**: [Fix HD formats extraction](https://github.com/yt-dlp/yt-dlp/commit/fca3eb5f8be08d5fab2e18b45b7281a12e566725) ([#11734](https://github.com/yt-dlp/yt-dlp/issues/11734)) by [grqz](https://github.com/grqz)
|
||||||
|
- **soundcloud**: [Fix formats extraction](https://github.com/yt-dlp/yt-dlp/commit/2feb28028ee48f2185d2d95076e62accb09b9e2e) ([#11742](https://github.com/yt-dlp/yt-dlp/issues/11742)) by [bashonly](https://github.com/bashonly)
|
||||||
|
- **youtube**
|
||||||
|
- [Fix `n` sig extraction for player `3bb1f723`](https://github.com/yt-dlp/yt-dlp/commit/a95ee6d8803fca9157adecf63732ab58bf87fd88) ([#11750](https://github.com/yt-dlp/yt-dlp/issues/11750)) by [bashonly](https://github.com/bashonly) (With fixes in [4bd2655](https://github.com/yt-dlp/yt-dlp/commit/4bd2655398aed450456197a6767639114a24eac2))
|
||||||
|
- [Fix signature function extraction](https://github.com/yt-dlp/yt-dlp/commit/4c85ccd1366c88cf93982f8350f58eed17355981) ([#11751](https://github.com/yt-dlp/yt-dlp/issues/11751)) by [bashonly](https://github.com/bashonly)
|
||||||
|
- [Player client maintenance](https://github.com/yt-dlp/yt-dlp/commit/2e49c789d3eebc39af8910705d65a98bca0e4c4f) ([#11724](https://github.com/yt-dlp/yt-dlp/issues/11724)) by [bashonly](https://github.com/bashonly)
|
||||||
|
|
||||||
|
### 2024.12.03
|
||||||
|
|
||||||
|
#### Core changes
|
||||||
|
- [Add `playlist_webpage_url` field](https://github.com/yt-dlp/yt-dlp/commit/7d6c259a03bc4707a319e5e8c6eff0278707874b) ([#11613](https://github.com/yt-dlp/yt-dlp/issues/11613)) by [seproDev](https://github.com/seproDev)
|
||||||
|
|
||||||
|
#### Extractor changes
|
||||||
|
- [Handle fragmented formats in `_remove_duplicate_formats`](https://github.com/yt-dlp/yt-dlp/commit/e0500cbf796323551bbabe5b8ed8c75a511ba47a) ([#11637](https://github.com/yt-dlp/yt-dlp/issues/11637)) by [Grub4K](https://github.com/Grub4K)
|
||||||
|
- **bilibili**
|
||||||
|
- [Always try to extract HD formats](https://github.com/yt-dlp/yt-dlp/commit/dc1687648077c5bf64863b307ecc5ab7e029bd8d) ([#10559](https://github.com/yt-dlp/yt-dlp/issues/10559)) by [grqz](https://github.com/grqz)
|
||||||
|
- [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/239f5f36fe04603bec59c8b975f6a792f10246db) ([#11667](https://github.com/yt-dlp/yt-dlp/issues/11667)) by [grqz](https://github.com/grqz) (With fixes in [f05a1cd](https://github.com/yt-dlp/yt-dlp/commit/f05a1cd1492fc98dc8d80d2081d632a1879913d2) by [bashonly](https://github.com/bashonly), [grqz](https://github.com/grqz))
|
||||||
|
- [Fix subtitles and chapters extraction](https://github.com/yt-dlp/yt-dlp/commit/a13a336aa6f906812701abec8101b73b73db8ff7) ([#11708](https://github.com/yt-dlp/yt-dlp/issues/11708)) by [xiaomac](https://github.com/xiaomac)
|
||||||
|
- **chaturbate**: [Fix support for non-public streams](https://github.com/yt-dlp/yt-dlp/commit/4b5eec0aaa7c02627f27a386591b735b90e681a8) ([#11624](https://github.com/yt-dlp/yt-dlp/issues/11624)) by [jkruse](https://github.com/jkruse)
|
||||||
|
- **dacast**: [Fix HLS AES formats extraction](https://github.com/yt-dlp/yt-dlp/commit/0a0d80800b9350d1a4c4b18d82cfb77ffbc3c507) ([#11644](https://github.com/yt-dlp/yt-dlp/issues/11644)) by [bashonly](https://github.com/bashonly)
|
||||||
|
- **dropbox**: [Fix password-protected video extraction](https://github.com/yt-dlp/yt-dlp/commit/00dcde728635633eee969ad4d498b9f233c4a94e) ([#11636](https://github.com/yt-dlp/yt-dlp/issues/11636)) by [bashonly](https://github.com/bashonly)
|
||||||
|
- **duoplay**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/62cba8a1bedbfc0ddde7267ae57b72bf5f7ea7b1) ([#11588](https://github.com/yt-dlp/yt-dlp/issues/11588)) by [bashonly](https://github.com/bashonly), [glensc](https://github.com/glensc)
|
||||||
|
- **facebook**: [Support more groups URLs](https://github.com/yt-dlp/yt-dlp/commit/e0f1ae813b36e783e2348ba2a1566e12f5cd8f6e) ([#11576](https://github.com/yt-dlp/yt-dlp/issues/11576)) by [grqz](https://github.com/grqz)
|
||||||
|
- **instagram**: [Support `share` URLs](https://github.com/yt-dlp/yt-dlp/commit/360aed810ad85db950df586282d256516c98cd2d) ([#11677](https://github.com/yt-dlp/yt-dlp/issues/11677)) by [grqz](https://github.com/grqz)
|
||||||
|
- **microsoftembed**: [Make format extraction non fatal](https://github.com/yt-dlp/yt-dlp/commit/2bea7936323ca4b6f3b9b1fdd892566223e30efa) ([#11654](https://github.com/yt-dlp/yt-dlp/issues/11654)) by [seproDev](https://github.com/seproDev)
|
||||||
|
- **mitele**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/cd0f934604587ed793e9177f6a127e5dcf99a7dd) ([#11683](https://github.com/yt-dlp/yt-dlp/issues/11683)) by [DarkZeros](https://github.com/DarkZeros)
|
||||||
|
- **stripchat**: [Fix extractor](https://github.com/yt-dlp/yt-dlp/commit/16336c51d0848a6868a4fa04e749fa03548b4913) ([#11596](https://github.com/yt-dlp/yt-dlp/issues/11596)) by [gitninja1234](https://github.com/gitninja1234)
|
||||||
|
- **tiktok**: [Deprioritize animated thumbnails](https://github.com/yt-dlp/yt-dlp/commit/910ecc422930bca14e2abe4986f5f92359e3cea8) ([#11645](https://github.com/yt-dlp/yt-dlp/issues/11645)) by [bashonly](https://github.com/bashonly)
|
||||||
|
- **vk**: [Fix extractors](https://github.com/yt-dlp/yt-dlp/commit/c038a7b187ba24360f14134842a7a2cf897c33b1) ([#11715](https://github.com/yt-dlp/yt-dlp/issues/11715)) by [bashonly](https://github.com/bashonly)
|
||||||
|
- **youtube**
|
||||||
|
- [Adjust player clients for site changes](https://github.com/yt-dlp/yt-dlp/commit/0d146c1e36f467af30e87b7af651bdee67b73500) ([#11663](https://github.com/yt-dlp/yt-dlp/issues/11663)) by [bashonly](https://github.com/bashonly)
|
||||||
|
- tab: [Fix playlists tab extraction](https://github.com/yt-dlp/yt-dlp/commit/fe70f20aedf528fdee332131bc9b6710e54e6f10) ([#11615](https://github.com/yt-dlp/yt-dlp/issues/11615)) by [seproDev](https://github.com/seproDev)
|
||||||
|
|
||||||
|
#### Networking changes
|
||||||
|
- **Request Handler**: websockets: [Support websockets 14.0+](https://github.com/yt-dlp/yt-dlp/commit/c7316373c0a886f65a07a51e50ee147bb3294c85) ([#11616](https://github.com/yt-dlp/yt-dlp/issues/11616)) by [coletdjnz](https://github.com/coletdjnz)
|
||||||
|
|
||||||
|
#### Misc. changes
|
||||||
|
- **cleanup**
|
||||||
|
- [Bump ruff to 0.8.x](https://github.com/yt-dlp/yt-dlp/commit/d8fb3490863653182864d2a53522f350d67a9ff8) ([#11608](https://github.com/yt-dlp/yt-dlp/issues/11608)) by [seproDev](https://github.com/seproDev)
|
||||||
|
- Miscellaneous
|
||||||
|
- [ccf0a6b](https://github.com/yt-dlp/yt-dlp/commit/ccf0a6b86b7f68a75463804fe485ec240b8635f0) by [bashonly](https://github.com/bashonly), [pzhlkj6612](https://github.com/pzhlkj6612)
|
||||||
|
- [2b67ac3](https://github.com/yt-dlp/yt-dlp/commit/2b67ac300ac8b44368fb121637d1743cea8c5b6b) by [bashonly](https://github.com/bashonly), [seproDev](https://github.com/seproDev)
|
||||||
|
|
||||||
### 2024.11.18
|
### 2024.11.18
|
||||||
|
|
||||||
#### Important changes
|
#### Important changes
|
||||||
|
|
|
@ -1860,7 +1860,7 @@ The following extractors use this feature:
|
||||||
* `cdn`: One or more CDN IDs to use with the API call for stream URLs, e.g. `gcp_cdn`, `gs_cdn_pc_app`, `gs_cdn_mobile_web`, `gs_cdn_pc_web`
|
* `cdn`: One or more CDN IDs to use with the API call for stream URLs, e.g. `gcp_cdn`, `gs_cdn_pc_app`, `gs_cdn_mobile_web`, `gs_cdn_pc_web`
|
||||||
|
|
||||||
#### soundcloud
|
#### soundcloud
|
||||||
* `formats`: Formats to request from the API. Requested values should be in the format of `{protocol}_{extension}` (omitting the bitrate), e.g. `hls_opus,http_aac`. The `*` character functions as a wildcard, e.g. `*_mp3`, and can be passed by itself to request all formats. Known protocols include `http`, `hls` and `hls-aes`; known extensions include `aac`, `opus` and `mp3`. Original `download` formats are always extracted. Default is `http_aac,hls_aac,http_opus,hls_opus,http_mp3,hls_mp3`
|
* `formats`: Formats to request from the API. Requested values should be in the format of `{protocol}_{codec}`, e.g. `hls_opus,http_aac`. The `*` character functions as a wildcard, e.g. `*_mp3`, and can be passed by itself to request all formats. Known protocols include `http`, `hls` and `hls-aes`; known codecs include `aac`, `opus` and `mp3`. Original `download` formats are always extracted. Default is `http_aac,hls_aac,http_opus,hls_opus,http_mp3,hls_mp3`
|
||||||
|
|
||||||
#### orfon (orf:on)
|
#### orfon (orf:on)
|
||||||
* `prefer_segments_playlist`: Prefer a playlist of program segments instead of a single complete video when available. If individual segments are desired, use `--concat-playlist never --extractor-args "orfon:prefer_segments_playlist"`
|
* `prefer_segments_playlist`: Prefer a playlist of program segments instead of a single complete video when available. If individual segments are desired, use `--concat-playlist never --extractor-args "orfon:prefer_segments_playlist"`
|
||||||
|
|
|
@ -68,6 +68,11 @@ _SIG_TESTS = [
|
||||||
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
'AOq0QJ8wRAIgXmPlOPSBkkUs1bYFYlJCfe29xx8j7v1pDL2QwbdV96sCIEzpWqMGkFR20CFOg51Tp-7vj_EMu-m37KtXJoOySqa0',
|
'AOq0QJ8wRAIgXmPlOPSBkkUs1bYFYlJCfe29xx8j7v1pDL2QwbdV96sCIEzpWqMGkFR20CFOg51Tp-7vj_EMu-m37KtXJoOySqa0',
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/3bb1f723/player_ias.vflset/en_US/base.js',
|
||||||
|
'2aq0aqSyOoJXtK73m-uME_jv7-pT15gOFC02RFkGMqWpzEICs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
|
'MyOSJXtKI3m-uME_jv7-pT12gOFC02RFkGoqWpzE0Cs69VdbwQ0LDp1v7j8xx92efCJlYFYb1sUkkBSPOlPmXgIARw8JQ0qOAOAA',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
_NSIG_TESTS = [
|
_NSIG_TESTS = [
|
||||||
|
@ -183,6 +188,10 @@ _NSIG_TESTS = [
|
||||||
'https://www.youtube.com/s/player/b12cc44b/player_ias.vflset/en_US/base.js',
|
'https://www.youtube.com/s/player/b12cc44b/player_ias.vflset/en_US/base.js',
|
||||||
'keLa5R2U00sR9SQK', 'N1OGyujjEwMnLw',
|
'keLa5R2U00sR9SQK', 'N1OGyujjEwMnLw',
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/3bb1f723/player_ias.vflset/en_US/base.js',
|
||||||
|
'gK15nzVyaXE9RsMP3z', 'ZFFWFLPWx9DEgQ',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -254,8 +263,11 @@ def signature(jscode, sig_input):
|
||||||
|
|
||||||
|
|
||||||
def n_sig(jscode, sig_input):
|
def n_sig(jscode, sig_input):
|
||||||
funcname = YoutubeIE(FakeYDL())._extract_n_function_name(jscode)
|
ie = YoutubeIE(FakeYDL())
|
||||||
return JSInterpreter(jscode).call_function(funcname, sig_input)
|
funcname = ie._extract_n_function_name(jscode)
|
||||||
|
jsi = JSInterpreter(jscode)
|
||||||
|
func = jsi.extract_function_from_code(*ie._fixup_n_function_code(*jsi.extract_function_code(funcname)))
|
||||||
|
return func([sig_input])
|
||||||
|
|
||||||
|
|
||||||
make_sig_test = t_factory(
|
make_sig_test = t_factory(
|
||||||
|
|
|
@ -195,7 +195,10 @@ def _extract_firefox_cookies(profile, container, logger):
|
||||||
|
|
||||||
def _firefox_browser_dirs():
|
def _firefox_browser_dirs():
|
||||||
if sys.platform in ('cygwin', 'win32'):
|
if sys.platform in ('cygwin', 'win32'):
|
||||||
yield os.path.expandvars(R'%APPDATA%\Mozilla\Firefox\Profiles')
|
yield from map(os.path.expandvars, (
|
||||||
|
R'%APPDATA%\Mozilla\Firefox\Profiles',
|
||||||
|
R'%LOCALAPPDATA%\Packages\Mozilla.Firefox_n80bbvh6b1yt2\LocalCache\Roaming\Mozilla\Firefox\Profiles',
|
||||||
|
))
|
||||||
|
|
||||||
elif sys.platform == 'darwin':
|
elif sys.platform == 'darwin':
|
||||||
yield os.path.expanduser('~/Library/Application Support/Firefox/Profiles')
|
yield os.path.expanduser('~/Library/Application Support/Firefox/Profiles')
|
||||||
|
|
|
@ -232,7 +232,7 @@ Format: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text'''
|
||||||
|
|
||||||
error = self._parse_json(e.cause.response.read(), video_id)
|
error = self._parse_json(e.cause.response.read(), video_id)
|
||||||
message = error.get('message')
|
message = error.get('message')
|
||||||
if e.cause.code == 403 and error.get('code') == 'player-bad-geolocation-country':
|
if e.cause.status == 403 and error.get('code') == 'player-bad-geolocation-country':
|
||||||
self.raise_geo_restricted(msg=message)
|
self.raise_geo_restricted(msg=message)
|
||||||
raise ExtractorError(message)
|
raise ExtractorError(message)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -681,12 +681,6 @@ class BiliBiliIE(BilibiliBaseIE):
|
||||||
old_video_id = format_field(aid, None, f'%s_part{part_id or 1}')
|
old_video_id = format_field(aid, None, f'%s_part{part_id or 1}')
|
||||||
cid = traverse_obj(video_data, ('pages', part_id - 1, 'cid')) if part_id else video_data.get('cid')
|
cid = traverse_obj(video_data, ('pages', part_id - 1, 'cid')) if part_id else video_data.get('cid')
|
||||||
|
|
||||||
play_info = (
|
|
||||||
traverse_obj(
|
|
||||||
self._search_json(r'window\.__playinfo__\s*=', webpage, 'play info', video_id, default=None),
|
|
||||||
('data', {dict}))
|
|
||||||
or self._download_playinfo(video_id, cid, headers=headers, query={'try_look': 1}))
|
|
||||||
|
|
||||||
festival_info = {}
|
festival_info = {}
|
||||||
if is_festival:
|
if is_festival:
|
||||||
festival_info = traverse_obj(initial_state, {
|
festival_info = traverse_obj(initial_state, {
|
||||||
|
@ -724,6 +718,13 @@ class BiliBiliIE(BilibiliBaseIE):
|
||||||
duration=traverse_obj(initial_state, ('videoData', 'duration', {int_or_none})),
|
duration=traverse_obj(initial_state, ('videoData', 'duration', {int_or_none})),
|
||||||
__post_extractor=self.extract_comments(aid))
|
__post_extractor=self.extract_comments(aid))
|
||||||
|
|
||||||
|
play_info = None
|
||||||
|
if self.is_logged_in:
|
||||||
|
play_info = traverse_obj(
|
||||||
|
self._search_json(r'window\.__playinfo__\s*=', webpage, 'play info', video_id, default=None),
|
||||||
|
('data', {dict}))
|
||||||
|
if not play_info:
|
||||||
|
play_info = self._download_playinfo(video_id, cid, headers=headers, query={'try_look': 1})
|
||||||
formats = self.extract_formats(play_info)
|
formats = self.extract_formats(play_info)
|
||||||
|
|
||||||
if video_data.get('is_upower_exclusive'):
|
if video_data.get('is_upower_exclusive'):
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import time
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..networking import HEADRequest
|
|
||||||
from ..utils import int_or_none
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,9 +28,6 @@ class CultureUnpluggedIE(InfoExtractor):
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
display_id = mobj.group('display_id') or video_id
|
display_id = mobj.group('display_id') or video_id
|
||||||
|
|
||||||
# request setClientTimezone.php to get PHPSESSID cookie which is need to get valid json data in the next request
|
|
||||||
self._request_webpage(HEADRequest(
|
|
||||||
'http://www.cultureunplugged.com/setClientTimezone.php?timeOffset=%d' % -(time.timezone / 3600)), display_id)
|
|
||||||
movie_data = self._download_json(
|
movie_data = self._download_json(
|
||||||
f'http://www.cultureunplugged.com/movie-data/cu-{video_id}.json', display_id)
|
f'http://www.cultureunplugged.com/movie-data/cu-{video_id}.json', display_id)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..networking.exceptions import HTTPError
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
traverse_obj,
|
traverse_obj,
|
||||||
|
@ -110,8 +111,8 @@ class PixivSketchUserIE(PixivSketchBaseIE):
|
||||||
if not traverse_obj(data, 'is_broadcasting'):
|
if not traverse_obj(data, 'is_broadcasting'):
|
||||||
try:
|
try:
|
||||||
self._call_api(user_id, 'users/current.json', url, 'Investigating reason for request failure')
|
self._call_api(user_id, 'users/current.json', url, 'Investigating reason for request failure')
|
||||||
except ExtractorError as ex:
|
except ExtractorError as e:
|
||||||
if ex.cause and ex.cause.code == 401:
|
if isinstance(e.cause, HTTPError) and e.cause.status == 401:
|
||||||
self.raise_login_required(f'Please log in, or use direct link like https://sketch.pixiv.net/@{user_id}/1234567890', method='cookies')
|
self.raise_login_required(f'Please log in, or use direct link like https://sketch.pixiv.net/@{user_id}/1234567890', method='cookies')
|
||||||
raise ExtractorError('This user is offline', expected=True)
|
raise ExtractorError('This user is offline', expected=True)
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ from .common import InfoExtractor, SearchInfoExtractor
|
||||||
from ..networking import HEADRequest
|
from ..networking import HEADRequest
|
||||||
from ..networking.exceptions import HTTPError
|
from ..networking.exceptions import HTTPError
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
KNOWN_EXTENSIONS,
|
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
@ -251,50 +250,15 @@ class SoundcloudBaseIE(InfoExtractor):
|
||||||
def invalid_url(url):
|
def invalid_url(url):
|
||||||
return not url or url in format_urls
|
return not url or url in format_urls
|
||||||
|
|
||||||
def add_format(f, protocol, is_preview=False):
|
|
||||||
mobj = re.search(r'\.(?P<abr>\d+)\.(?P<ext>[0-9a-z]{3,4})(?=[/?])', stream_url)
|
|
||||||
if mobj:
|
|
||||||
for k, v in mobj.groupdict().items():
|
|
||||||
if not f.get(k):
|
|
||||||
f[k] = v
|
|
||||||
format_id_list = []
|
|
||||||
if protocol:
|
|
||||||
format_id_list.append(protocol)
|
|
||||||
ext = f.get('ext')
|
|
||||||
if ext == 'aac':
|
|
||||||
f.update({
|
|
||||||
'abr': 256,
|
|
||||||
'quality': 5,
|
|
||||||
'format_note': 'Premium',
|
|
||||||
})
|
|
||||||
for k in ('ext', 'abr'):
|
|
||||||
v = str_or_none(f.get(k))
|
|
||||||
if v:
|
|
||||||
format_id_list.append(v)
|
|
||||||
preview = is_preview or re.search(r'/(?:preview|playlist)/0/30/', f['url'])
|
|
||||||
if preview:
|
|
||||||
format_id_list.append('preview')
|
|
||||||
abr = f.get('abr')
|
|
||||||
if abr:
|
|
||||||
f['abr'] = int(abr)
|
|
||||||
if protocol in ('hls', 'hls-aes'):
|
|
||||||
protocol = 'm3u8' if ext == 'aac' else 'm3u8_native'
|
|
||||||
else:
|
|
||||||
protocol = 'http'
|
|
||||||
f.update({
|
|
||||||
'format_id': '_'.join(format_id_list),
|
|
||||||
'protocol': protocol,
|
|
||||||
'preference': -10 if preview else None,
|
|
||||||
})
|
|
||||||
formats.append(f)
|
|
||||||
|
|
||||||
# New API
|
# New API
|
||||||
for t in traverse_obj(info, ('media', 'transcodings', lambda _, v: url_or_none(v['url']))):
|
for t in traverse_obj(info, ('media', 'transcodings', lambda _, v: url_or_none(v['url']) and v['preset'])):
|
||||||
if extract_flat:
|
if extract_flat:
|
||||||
break
|
break
|
||||||
format_url = t['url']
|
format_url = t['url']
|
||||||
|
preset = t['preset']
|
||||||
|
preset_base = preset.partition('_')[0]
|
||||||
|
|
||||||
protocol = traverse_obj(t, ('format', 'protocol', {str}))
|
protocol = traverse_obj(t, ('format', 'protocol', {str})) or 'http'
|
||||||
if protocol == 'progressive':
|
if protocol == 'progressive':
|
||||||
protocol = 'http'
|
protocol = 'http'
|
||||||
if protocol != 'hls' and '/hls' in format_url:
|
if protocol != 'hls' and '/hls' in format_url:
|
||||||
|
@ -302,32 +266,54 @@ class SoundcloudBaseIE(InfoExtractor):
|
||||||
if protocol == 'encrypted-hls' or '/encrypted-hls' in format_url:
|
if protocol == 'encrypted-hls' or '/encrypted-hls' in format_url:
|
||||||
protocol = 'hls-aes'
|
protocol = 'hls-aes'
|
||||||
|
|
||||||
ext = None
|
short_identifier = f'{protocol}_{preset_base}'
|
||||||
if preset := traverse_obj(t, ('preset', {str_or_none})):
|
if preset_base == 'abr':
|
||||||
ext = preset.split('_')[0]
|
self.write_debug(f'Skipping broken "{short_identifier}" format')
|
||||||
if ext not in KNOWN_EXTENSIONS:
|
continue
|
||||||
ext = mimetype2ext(traverse_obj(t, ('format', 'mime_type', {str})))
|
if not self._is_requested(short_identifier):
|
||||||
|
self.write_debug(f'"{short_identifier}" is not a requested format, skipping')
|
||||||
identifier = join_nonempty(protocol, ext, delim='_')
|
|
||||||
if not self._is_requested(identifier):
|
|
||||||
self.write_debug(f'"{identifier}" is not a requested format, skipping')
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# XXX: if not extract_flat, 429 error must be caught where _extract_info_dict is called
|
# XXX: if not extract_flat, 429 error must be caught where _extract_info_dict is called
|
||||||
stream_url = traverse_obj(self._call_api(
|
stream_url = traverse_obj(self._call_api(
|
||||||
format_url, track_id, f'Downloading {identifier} format info JSON',
|
format_url, track_id, f'Downloading {short_identifier} format info JSON',
|
||||||
query=query, headers=self._HEADERS), ('url', {url_or_none}))
|
query=query, headers=self._HEADERS), ('url', {url_or_none}))
|
||||||
|
|
||||||
if invalid_url(stream_url):
|
if invalid_url(stream_url):
|
||||||
continue
|
continue
|
||||||
format_urls.add(stream_url)
|
format_urls.add(stream_url)
|
||||||
add_format({
|
|
||||||
|
mime_type = traverse_obj(t, ('format', 'mime_type', {str}))
|
||||||
|
codec = self._search_regex(r'codecs="([^"]+)"', mime_type, 'codec', default=None)
|
||||||
|
ext = {
|
||||||
|
'mp4a': 'm4a',
|
||||||
|
'opus': 'opus',
|
||||||
|
}.get(codec[:4] if codec else None) or mimetype2ext(mime_type, default=None)
|
||||||
|
if not ext or ext == 'm3u8':
|
||||||
|
ext = preset_base
|
||||||
|
|
||||||
|
is_premium = t.get('quality') == 'hq'
|
||||||
|
abr = int_or_none(
|
||||||
|
self._search_regex(r'(\d+)k$', preset, 'abr', default=None)
|
||||||
|
or self._search_regex(r'\.(\d+)\.(?:opus|mp3)[/?]', stream_url, 'abr', default=None)
|
||||||
|
or (256 if (is_premium and 'aac' in preset) else None))
|
||||||
|
|
||||||
|
is_preview = (t.get('snipped')
|
||||||
|
or '/preview/' in format_url
|
||||||
|
or re.search(r'/(?:preview|playlist)/0/30/', stream_url))
|
||||||
|
|
||||||
|
formats.append({
|
||||||
|
'format_id': join_nonempty(protocol, preset, is_preview and 'preview', delim='_'),
|
||||||
'url': stream_url,
|
'url': stream_url,
|
||||||
'ext': ext,
|
'ext': ext,
|
||||||
}, protocol, t.get('snipped') or '/preview/' in format_url)
|
'acodec': codec,
|
||||||
|
'vcodec': 'none',
|
||||||
for f in formats:
|
'abr': abr,
|
||||||
f['vcodec'] = 'none'
|
'protocol': 'm3u8_native' if protocol in ('hls', 'hls-aes') else 'http',
|
||||||
|
'container': 'm4a_dash' if ext == 'm4a' else None,
|
||||||
|
'quality': 5 if is_premium else 0 if (abr and abr >= 160) else -1,
|
||||||
|
'format_note': 'Premium' if is_premium else None,
|
||||||
|
'preference': -10 if is_preview else None,
|
||||||
|
})
|
||||||
|
|
||||||
if not formats and info.get('policy') == 'BLOCK':
|
if not formats and info.get('policy') == 'BLOCK':
|
||||||
self.raise_geo_restricted(metadata_available=True)
|
self.raise_geo_restricted(metadata_available=True)
|
||||||
|
|
|
@ -17,10 +17,10 @@ from ..utils import (
|
||||||
get_element_html_by_id,
|
get_element_html_by_id,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
join_nonempty,
|
join_nonempty,
|
||||||
|
parse_qs,
|
||||||
parse_resolution,
|
parse_resolution,
|
||||||
str_or_none,
|
str_or_none,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
traverse_obj,
|
|
||||||
try_call,
|
try_call,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
|
@ -29,6 +29,7 @@ from ..utils import (
|
||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
urljoin,
|
urljoin,
|
||||||
)
|
)
|
||||||
|
from ..utils.traversal import require, traverse_obj
|
||||||
|
|
||||||
|
|
||||||
class VKBaseIE(InfoExtractor):
|
class VKBaseIE(InfoExtractor):
|
||||||
|
@ -91,17 +92,17 @@ class VKBaseIE(InfoExtractor):
|
||||||
class VKIE(VKBaseIE):
|
class VKIE(VKBaseIE):
|
||||||
IE_NAME = 'vk'
|
IE_NAME = 'vk'
|
||||||
IE_DESC = 'VK'
|
IE_DESC = 'VK'
|
||||||
_EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>https?://vk\.com/video_ext\.php.+?)\1']
|
_EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>https?://vk(?:(?:video)?\.ru|\.com)/video_ext\.php.+?)\1']
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https?://
|
https?://
|
||||||
(?:
|
(?:
|
||||||
(?:
|
(?:
|
||||||
(?:(?:m|new)\.)?vk\.com/video_|
|
(?:(?:m|new)\.)?vk(?:(?:video)?\.ru|\.com)/video_|
|
||||||
(?:www\.)?daxab\.com/
|
(?:www\.)?daxab\.com/
|
||||||
)
|
)
|
||||||
ext\.php\?(?P<embed_query>.*?\boid=(?P<oid>-?\d+).*?\bid=(?P<id>\d+).*)|
|
ext\.php\?(?P<embed_query>.*?\boid=(?P<oid>-?\d+).*?\bid=(?P<id>\d+).*)|
|
||||||
(?:
|
(?:
|
||||||
(?:(?:m|new)\.)?vk\.com/(?:.+?\?.*?z=)?(?:video|clip)|
|
(?:(?:m|new)\.)?vk(?:(?:video)?\.ru|\.com)/(?:.+?\?.*?z=)?(?:video|clip)|
|
||||||
(?:www\.)?daxab\.com/embed/
|
(?:www\.)?daxab\.com/embed/
|
||||||
)
|
)
|
||||||
(?P<videoid>-?\d+_\d+)(?:.*\blist=(?P<list_id>([\da-f]+)|(ln-[\da-zA-Z]+)))?
|
(?P<videoid>-?\d+_\d+)(?:.*\blist=(?P<list_id>([\da-f]+)|(ln-[\da-zA-Z]+)))?
|
||||||
|
@ -110,7 +111,7 @@ class VKIE(VKBaseIE):
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://vk.com/videos-77521?z=video-77521_162222515%2Fclub77521',
|
'url': 'https://vk.com/videos-77521?z=video-77521_162222515%2Fclub77521',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '-77521_162222515',
|
'id': '-77521_162222515',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
|
@ -127,7 +128,7 @@ class VKIE(VKBaseIE):
|
||||||
'params': {'skip_download': 'm3u8'},
|
'params': {'skip_download': 'm3u8'},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://vk.com/video205387401_165548505',
|
'url': 'https://vk.com/video205387401_165548505',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '205387401_165548505',
|
'id': '205387401_165548505',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
|
@ -182,10 +183,10 @@ class VKIE(VKBaseIE):
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': "DSWD Awards 'Children's Joy Foundation, Inc.' Certificate of Registration and License to Operate",
|
'title': "DSWD Awards 'Children's Joy Foundation, Inc.' Certificate of Registration and License to Operate",
|
||||||
'description': 'md5:bf9c26cfa4acdfb146362682edd3827a',
|
'description': 'md5:bf9c26cfa4acdfb146362682edd3827a',
|
||||||
'duration': 178,
|
'duration': 179,
|
||||||
'upload_date': '20130117',
|
'upload_date': '20130117',
|
||||||
'uploader': "Children's Joy Foundation Inc.",
|
'uploader': "Children's Joy Foundation Inc.",
|
||||||
'uploader_id': 'thecjf',
|
'uploader_id': '@CJFIofficial',
|
||||||
'view_count': int,
|
'view_count': int,
|
||||||
'channel_id': 'UCgzCNQ11TmR9V97ECnhi3gw',
|
'channel_id': 'UCgzCNQ11TmR9V97ECnhi3gw',
|
||||||
'availability': 'public',
|
'availability': 'public',
|
||||||
|
@ -193,7 +194,7 @@ class VKIE(VKBaseIE):
|
||||||
'live_status': 'not_live',
|
'live_status': 'not_live',
|
||||||
'playable_in_embed': True,
|
'playable_in_embed': True,
|
||||||
'channel': 'Children\'s Joy Foundation Inc.',
|
'channel': 'Children\'s Joy Foundation Inc.',
|
||||||
'uploader_url': 'http://www.youtube.com/user/thecjf',
|
'uploader_url': 'https://www.youtube.com/@CJFIofficial',
|
||||||
'thumbnail': r're:https?://.+\.jpg$',
|
'thumbnail': r're:https?://.+\.jpg$',
|
||||||
'tags': 'count:27',
|
'tags': 'count:27',
|
||||||
'start_time': 0.0,
|
'start_time': 0.0,
|
||||||
|
@ -201,6 +202,7 @@ class VKIE(VKBaseIE):
|
||||||
'channel_url': 'https://www.youtube.com/channel/UCgzCNQ11TmR9V97ECnhi3gw',
|
'channel_url': 'https://www.youtube.com/channel/UCgzCNQ11TmR9V97ECnhi3gw',
|
||||||
'channel_follower_count': int,
|
'channel_follower_count': int,
|
||||||
'age_limit': 0,
|
'age_limit': 0,
|
||||||
|
'timestamp': 1358394935,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -222,6 +224,7 @@ class VKIE(VKBaseIE):
|
||||||
'thumbnail': r're:https?://.+x1080$',
|
'thumbnail': r're:https?://.+x1080$',
|
||||||
'tags': list,
|
'tags': list,
|
||||||
},
|
},
|
||||||
|
'skip': 'This video has been deleted and is no longer available.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'https://vk.com/clips-74006511?z=clip-74006511_456247211',
|
'url': 'https://vk.com/clips-74006511?z=clip-74006511_456247211',
|
||||||
|
@ -235,13 +238,13 @@ class VKIE(VKBaseIE):
|
||||||
'timestamp': 1664995597,
|
'timestamp': 1664995597,
|
||||||
'title': 'Clip by @madempress',
|
'title': 'Clip by @madempress',
|
||||||
'upload_date': '20221005',
|
'upload_date': '20221005',
|
||||||
'uploader': 'Шальная императрица',
|
'uploader': 'Шальная Императрица',
|
||||||
'uploader_id': '-74006511',
|
'uploader_id': '-74006511',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# video key is extra_data not url\d+
|
# video key is extra_data not url\d+
|
||||||
'url': 'http://vk.com/video-110305615_171782105',
|
'url': 'https://vk.com/video-110305615_171782105',
|
||||||
'md5': 'e13fcda136f99764872e739d13fac1d1',
|
'md5': 'e13fcda136f99764872e739d13fac1d1',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '-110305615_171782105',
|
'id': '-110305615_171782105',
|
||||||
|
@ -273,6 +276,7 @@ class VKIE(VKBaseIE):
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
|
'skip': 'No formats found',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# live stream, hls and rtmp links, most likely already finished live
|
# live stream, hls and rtmp links, most likely already finished live
|
||||||
|
@ -312,7 +316,16 @@ class VKIE(VKBaseIE):
|
||||||
{
|
{
|
||||||
'url': 'https://vk.com/clip30014565_456240946',
|
'url': 'https://vk.com/clip30014565_456240946',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
},
|
||||||
|
{
|
||||||
|
'url': 'https://vkvideo.ru/video-127553155_456242961',
|
||||||
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'https://vk.ru/video-220754053_456242564',
|
||||||
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = self._match_valid_url(url)
|
mobj = self._match_valid_url(url)
|
||||||
|
@ -338,7 +351,7 @@ class VKIE(VKBaseIE):
|
||||||
video_id = '{}_{}'.format(mobj.group('oid'), mobj.group('id'))
|
video_id = '{}_{}'.format(mobj.group('oid'), mobj.group('id'))
|
||||||
|
|
||||||
info_page = self._download_webpage(
|
info_page = self._download_webpage(
|
||||||
'http://vk.com/video_ext.php?' + mobj.group('embed_query'), video_id)
|
'https://vk.com/video_ext.php?' + mobj.group('embed_query'), video_id)
|
||||||
|
|
||||||
error_message = self._html_search_regex(
|
error_message = self._html_search_regex(
|
||||||
[r'(?s)<!><div[^>]+class="video_layer_message"[^>]*>(.+?)</div>',
|
[r'(?s)<!><div[^>]+class="video_layer_message"[^>]*>(.+?)</div>',
|
||||||
|
@ -432,7 +445,7 @@ class VKIE(VKBaseIE):
|
||||||
if m_opts_url:
|
if m_opts_url:
|
||||||
opts_url = m_opts_url.group(1)
|
opts_url = m_opts_url.group(1)
|
||||||
if opts_url.startswith('//'):
|
if opts_url.startswith('//'):
|
||||||
opts_url = 'http:' + opts_url
|
opts_url = 'https:' + opts_url
|
||||||
return self.url_result(opts_url)
|
return self.url_result(opts_url)
|
||||||
|
|
||||||
data = player['params'][0]
|
data = player['params'][0]
|
||||||
|
@ -512,8 +525,11 @@ class VKIE(VKBaseIE):
|
||||||
class VKUserVideosIE(VKBaseIE):
|
class VKUserVideosIE(VKBaseIE):
|
||||||
IE_NAME = 'vk:uservideos'
|
IE_NAME = 'vk:uservideos'
|
||||||
IE_DESC = "VK - User's Videos"
|
IE_DESC = "VK - User's Videos"
|
||||||
_VALID_URL = r'https?://(?:(?:m|new)\.)?vk\.com/video/(?:playlist/)?(?P<id>[^?$#/&]+)(?!\?.*\bz=video)(?:[/?#&](?:.*?\bsection=(?P<section>\w+))?|$)'
|
_BASE_URL_RE = r'https?://(?:(?:m|new)\.)?vk(?:video\.ru|\.com/video)'
|
||||||
_TEMPLATE_URL = 'https://vk.com/videos'
|
_VALID_URL = [
|
||||||
|
rf'{_BASE_URL_RE}/playlist/(?P<id>-?\d+_\d+)',
|
||||||
|
rf'{_BASE_URL_RE}/(?P<id>@[^/?#]+)(?:/all)?/?(?!\?.*\bz=video)(?:[?#]|$)',
|
||||||
|
]
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://vk.com/video/@mobidevices',
|
'url': 'https://vk.com/video/@mobidevices',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
|
@ -527,12 +543,20 @@ class VKUserVideosIE(VKBaseIE):
|
||||||
},
|
},
|
||||||
'playlist_mincount': 182,
|
'playlist_mincount': 182,
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://vk.com/video/playlist/-174476437_2',
|
'url': 'https://vkvideo.ru/playlist/-204353299_426',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '-174476437_playlist_2',
|
'id': '-204353299_playlist_426',
|
||||||
'title': 'Анонсы',
|
|
||||||
},
|
},
|
||||||
'playlist_mincount': 108,
|
'playlist_mincount': 33,
|
||||||
|
}, {
|
||||||
|
'url': 'https://vk.com/video/@gorkyfilmstudio/all',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://vkvideo.ru/@mobidevices',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://vk.com/video/playlist/-174476437_2',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
_VIDEO = collections.namedtuple('Video', ['owner_id', 'id'])
|
_VIDEO = collections.namedtuple('Video', ['owner_id', 'id'])
|
||||||
|
|
||||||
|
@ -552,7 +576,7 @@ class VKUserVideosIE(VKBaseIE):
|
||||||
v = self._VIDEO._make(video[:2])
|
v = self._VIDEO._make(video[:2])
|
||||||
video_id = '%d_%d' % (v.owner_id, v.id)
|
video_id = '%d_%d' % (v.owner_id, v.id)
|
||||||
yield self.url_result(
|
yield self.url_result(
|
||||||
'http://vk.com/video' + video_id, VKIE.ie_key(), video_id)
|
'https://vk.com/video' + video_id, VKIE.ie_key(), video_id)
|
||||||
if count >= total:
|
if count >= total:
|
||||||
break
|
break
|
||||||
video_list_json = self._download_payload('al_video', page_id, {
|
video_list_json = self._download_payload('al_video', page_id, {
|
||||||
|
@ -561,23 +585,25 @@ class VKUserVideosIE(VKBaseIE):
|
||||||
'oid': page_id,
|
'oid': page_id,
|
||||||
'section': section,
|
'section': section,
|
||||||
})[0][section]
|
})[0][section]
|
||||||
count += video_list_json['count']
|
new_count = video_list_json['count']
|
||||||
|
if not new_count:
|
||||||
|
self.to_screen(f'{page_id}: Skipping {total - count} unavailable videos')
|
||||||
|
break
|
||||||
|
count += new_count
|
||||||
video_list = video_list_json['list']
|
video_list = video_list_json['list']
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
u_id, section = self._match_valid_url(url).groups()
|
u_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, u_id)
|
webpage = self._download_webpage(url, u_id)
|
||||||
|
|
||||||
if u_id.startswith('@'):
|
if u_id.startswith('@'):
|
||||||
page_id = self._search_regex(r'data-owner-id\s?=\s?"([^"]+)"', webpage, 'page_id')
|
page_id = traverse_obj(
|
||||||
elif '_' in u_id:
|
self._search_json(r'\bvar newCur\s*=', webpage, 'cursor data', u_id),
|
||||||
page_id, section = u_id.split('_', 1)
|
('oid', {int}, {str_or_none}, {require('page id')}))
|
||||||
section = f'playlist_{section}'
|
section = traverse_obj(parse_qs(url), ('section', 0)) or 'all'
|
||||||
else:
|
else:
|
||||||
raise ExtractorError('Invalid URL', expected=True)
|
page_id, _, section = u_id.partition('_')
|
||||||
|
section = f'playlist_{section}'
|
||||||
if not section:
|
|
||||||
section = 'all'
|
|
||||||
|
|
||||||
playlist_title = clean_html(get_element_by_class('VideoInfoPanel__title', webpage))
|
playlist_title = clean_html(get_element_by_class('VideoInfoPanel__title', webpage))
|
||||||
return self.playlist_result(self._entries(page_id, section), f'{page_id}_{section}', playlist_title)
|
return self.playlist_result(self._entries(page_id, section), f'{page_id}_{section}', playlist_title)
|
||||||
|
@ -717,7 +743,7 @@ class VKWallPostIE(VKBaseIE):
|
||||||
|
|
||||||
|
|
||||||
class VKPlayBaseIE(InfoExtractor):
|
class VKPlayBaseIE(InfoExtractor):
|
||||||
_BASE_URL_RE = r'https?://(?:vkplay\.live|live\.vkplay\.ru)/'
|
_BASE_URL_RE = r'https?://(?:vkplay\.live|live\.vk(?:play|video)\.ru)/'
|
||||||
_RESOLUTIONS = {
|
_RESOLUTIONS = {
|
||||||
'tiny': '256x144',
|
'tiny': '256x144',
|
||||||
'lowest': '426x240',
|
'lowest': '426x240',
|
||||||
|
@ -797,6 +823,9 @@ class VKPlayIE(VKPlayBaseIE):
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://live.vkplay.ru/lebwa/record/33a4e4ce-e3ef-49db-bb14-f006cc6fabc9/records',
|
'url': 'https://live.vkplay.ru/lebwa/record/33a4e4ce-e3ef-49db-bb14-f006cc6fabc9/records',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://live.vkvideo.ru/lebwa/record/33a4e4ce-e3ef-49db-bb14-f006cc6fabc9/records',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -839,6 +868,9 @@ class VKPlayLiveIE(VKPlayBaseIE):
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://live.vkplay.ru/lebwa',
|
'url': 'https://live.vkplay.ru/lebwa',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://live.vkvideo.ru/panterka',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
|
|
@ -78,7 +78,7 @@ INNERTUBE_CLIENTS = {
|
||||||
'INNERTUBE_CONTEXT': {
|
'INNERTUBE_CONTEXT': {
|
||||||
'client': {
|
'client': {
|
||||||
'clientName': 'WEB',
|
'clientName': 'WEB',
|
||||||
'clientVersion': '2.20240726.00.00',
|
'clientVersion': '2.20241126.01.00',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 1,
|
'INNERTUBE_CONTEXT_CLIENT_NAME': 1,
|
||||||
|
@ -90,7 +90,7 @@ INNERTUBE_CLIENTS = {
|
||||||
'INNERTUBE_CONTEXT': {
|
'INNERTUBE_CONTEXT': {
|
||||||
'client': {
|
'client': {
|
||||||
'clientName': 'WEB',
|
'clientName': 'WEB',
|
||||||
'clientVersion': '2.20240726.00.00',
|
'clientVersion': '2.20241126.01.00',
|
||||||
'userAgent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15,gzip(gfe)',
|
'userAgent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.5 Safari/605.1.15,gzip(gfe)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -102,7 +102,7 @@ INNERTUBE_CLIENTS = {
|
||||||
'INNERTUBE_CONTEXT': {
|
'INNERTUBE_CONTEXT': {
|
||||||
'client': {
|
'client': {
|
||||||
'clientName': 'WEB_EMBEDDED_PLAYER',
|
'clientName': 'WEB_EMBEDDED_PLAYER',
|
||||||
'clientVersion': '1.20240723.01.00',
|
'clientVersion': '1.20241201.00.00',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 56,
|
'INNERTUBE_CONTEXT_CLIENT_NAME': 56,
|
||||||
|
@ -113,7 +113,7 @@ INNERTUBE_CLIENTS = {
|
||||||
'INNERTUBE_CONTEXT': {
|
'INNERTUBE_CONTEXT': {
|
||||||
'client': {
|
'client': {
|
||||||
'clientName': 'WEB_REMIX',
|
'clientName': 'WEB_REMIX',
|
||||||
'clientVersion': '1.20240724.00.00',
|
'clientVersion': '1.20241127.01.00',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 67,
|
'INNERTUBE_CONTEXT_CLIENT_NAME': 67,
|
||||||
|
@ -124,7 +124,7 @@ INNERTUBE_CLIENTS = {
|
||||||
'INNERTUBE_CONTEXT': {
|
'INNERTUBE_CONTEXT': {
|
||||||
'client': {
|
'client': {
|
||||||
'clientName': 'WEB_CREATOR',
|
'clientName': 'WEB_CREATOR',
|
||||||
'clientVersion': '1.20240723.03.00',
|
'clientVersion': '1.20241203.01.00',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 62,
|
'INNERTUBE_CONTEXT_CLIENT_NAME': 62,
|
||||||
|
@ -257,7 +257,8 @@ INNERTUBE_CLIENTS = {
|
||||||
'INNERTUBE_CONTEXT': {
|
'INNERTUBE_CONTEXT': {
|
||||||
'client': {
|
'client': {
|
||||||
'clientName': 'MWEB',
|
'clientName': 'MWEB',
|
||||||
'clientVersion': '2.20240726.01.00',
|
'clientVersion': '2.20241202.07.00',
|
||||||
|
'userAgent': 'Mozilla/5.0 (iPad; CPU OS 16_7_10 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1,gzip(gfe)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 2,
|
'INNERTUBE_CONTEXT_CLIENT_NAME': 2,
|
||||||
|
@ -267,7 +268,7 @@ INNERTUBE_CLIENTS = {
|
||||||
'INNERTUBE_CONTEXT': {
|
'INNERTUBE_CONTEXT': {
|
||||||
'client': {
|
'client': {
|
||||||
'clientName': 'TVHTML5',
|
'clientName': 'TVHTML5',
|
||||||
'clientVersion': '7.20240724.13.00',
|
'clientVersion': '7.20241201.18.00',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 7,
|
'INNERTUBE_CONTEXT_CLIENT_NAME': 7,
|
||||||
|
@ -3118,19 +3119,26 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||||
self.to_screen('Extracted signature function:\n' + code)
|
self.to_screen('Extracted signature function:\n' + code)
|
||||||
|
|
||||||
def _parse_sig_js(self, jscode):
|
def _parse_sig_js(self, jscode):
|
||||||
|
# Examples where `sig` is funcname:
|
||||||
|
# sig=function(a){a=a.split(""); ... ;return a.join("")};
|
||||||
|
# ;c&&(c=sig(decodeURIComponent(c)),a.set(b,encodeURIComponent(c)));return a};
|
||||||
|
# {var l=f,m=h.sp,n=sig(decodeURIComponent(h.s));l.set(m,encodeURIComponent(n))}
|
||||||
|
# sig=function(J){J=J.split(""); ... ;return J.join("")};
|
||||||
|
# ;N&&(N=sig(decodeURIComponent(N)),J.set(R,encodeURIComponent(N)));return J};
|
||||||
|
# {var H=u,k=f.sp,v=sig(decodeURIComponent(f.s));H.set(k,encodeURIComponent(v))}
|
||||||
funcname = self._search_regex(
|
funcname = self._search_regex(
|
||||||
(r'\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
(r'\b(?P<var>[a-zA-Z0-9$]+)&&\((?P=var)=(?P<sig>[a-zA-Z0-9$]{2,})\(decodeURIComponent\((?P=var)\)\)',
|
||||||
|
r'(?P<sig>[a-zA-Z0-9$]+)\s*=\s*function\(\s*(?P<arg>[a-zA-Z0-9$]+)\s*\)\s*{\s*(?P=arg)\s*=\s*(?P=arg)\.split\(\s*""\s*\)\s*;\s*[^}]+;\s*return\s+(?P=arg)\.join\(\s*""\s*\)',
|
||||||
|
r'(?:\b|[^a-zA-Z0-9$])(?P<sig>[a-zA-Z0-9$]{2,})\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)(?:;[a-zA-Z0-9$]{2}\.[a-zA-Z0-9$]{2}\(a,\d+\))?',
|
||||||
|
# Old patterns
|
||||||
|
r'\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
||||||
r'\b[a-zA-Z0-9]+\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
r'\b[a-zA-Z0-9]+\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
||||||
r'\bm=(?P<sig>[a-zA-Z0-9$]{2,})\(decodeURIComponent\(h\.s\)\)',
|
r'\bm=(?P<sig>[a-zA-Z0-9$]{2,})\(decodeURIComponent\(h\.s\)\)',
|
||||||
r'\bc&&\(c=(?P<sig>[a-zA-Z0-9$]{2,})\(decodeURIComponent\(c\)\)',
|
|
||||||
r'(?:\b|[^a-zA-Z0-9$])(?P<sig>[a-zA-Z0-9$]{2,})\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)(?:;[a-zA-Z0-9$]{2}\.[a-zA-Z0-9$]{2}\(a,\d+\))?',
|
|
||||||
r'(?P<sig>[a-zA-Z0-9$]+)\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)',
|
|
||||||
# Obsolete patterns
|
# Obsolete patterns
|
||||||
r'("|\')signature\1\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
r'("|\')signature\1\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
||||||
r'\.sig\|\|(?P<sig>[a-zA-Z0-9$]+)\(',
|
r'\.sig\|\|(?P<sig>[a-zA-Z0-9$]+)\(',
|
||||||
r'yt\.akamaized\.net/\)\s*\|\|\s*.*?\s*[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?:encodeURIComponent\s*\()?\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
r'yt\.akamaized\.net/\)\s*\|\|\s*.*?\s*[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?:encodeURIComponent\s*\()?\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
||||||
r'\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
r'\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
||||||
r'\b[a-zA-Z0-9]+\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(',
|
|
||||||
r'\bc\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\('),
|
r'\bc\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\('),
|
||||||
jscode, 'Initial JS player signature function name', group='sig')
|
jscode, 'Initial JS player signature function name', group='sig')
|
||||||
|
|
||||||
|
@ -3204,6 +3212,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||||
# * a.D&&(b="nn"[+a.D],c=a.get(b))&&(c=narray[idx](c),a.set(b,c),narray.length||nfunc("")
|
# * a.D&&(b="nn"[+a.D],c=a.get(b))&&(c=narray[idx](c),a.set(b,c),narray.length||nfunc("")
|
||||||
# * a.D&&(PL(a),b=a.j.n||null)&&(b=narray[0](b),a.set("n",b),narray.length||nfunc("")
|
# * a.D&&(PL(a),b=a.j.n||null)&&(b=narray[0](b),a.set("n",b),narray.length||nfunc("")
|
||||||
# * a.D&&(b="nn"[+a.D],vL(a),c=a.j[b]||null)&&(c=narray[idx](c),a.set(b,c),narray.length||nfunc("")
|
# * a.D&&(b="nn"[+a.D],vL(a),c=a.j[b]||null)&&(c=narray[idx](c),a.set(b,c),narray.length||nfunc("")
|
||||||
|
# * J.J="";J.url="";J.Z&&(R="nn"[+J.Z],mW(J),N=J.K[R]||null)&&(N=narray[idx](N),J.set(R,N))}};
|
||||||
funcname, idx = self._search_regex(
|
funcname, idx = self._search_regex(
|
||||||
r'''(?x)
|
r'''(?x)
|
||||||
(?:
|
(?:
|
||||||
|
@ -3220,7 +3229,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||||
)\)&&\(c=|
|
)\)&&\(c=|
|
||||||
\b(?P<var>[a-zA-Z0-9_$]+)=
|
\b(?P<var>[a-zA-Z0-9_$]+)=
|
||||||
)(?P<nfunc>[a-zA-Z0-9_$]+)(?:\[(?P<idx>\d+)\])?\([a-zA-Z]\)
|
)(?P<nfunc>[a-zA-Z0-9_$]+)(?:\[(?P<idx>\d+)\])?\([a-zA-Z]\)
|
||||||
(?(var),[a-zA-Z0-9_$]+\.set\("n"\,(?P=var)\),(?P=nfunc)\.length)''',
|
(?(var),[a-zA-Z0-9_$]+\.set\((?:"n+"|[a-zA-Z0-9_$]+)\,(?P=var)\))''',
|
||||||
jscode, 'n function name', group=('nfunc', 'idx'), default=(None, None))
|
jscode, 'n function name', group=('nfunc', 'idx'), default=(None, None))
|
||||||
if not funcname:
|
if not funcname:
|
||||||
self.report_warning(join_nonempty(
|
self.report_warning(join_nonempty(
|
||||||
|
@ -3229,7 +3238,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||||
return self._search_regex(
|
return self._search_regex(
|
||||||
r'''(?xs)
|
r'''(?xs)
|
||||||
;\s*(?P<name>[a-zA-Z0-9_$]+)\s*=\s*function\([a-zA-Z0-9_$]+\)
|
;\s*(?P<name>[a-zA-Z0-9_$]+)\s*=\s*function\([a-zA-Z0-9_$]+\)
|
||||||
\s*\{(?:(?!};).)+?["']enhanced_except_''',
|
\s*\{(?:(?!};).)+?return\s*(?P<q>["'])[\w-]+_w8_(?P=q)\s*\+\s*[a-zA-Z0-9_$]+''',
|
||||||
jscode, 'Initial JS player n function name', group='name')
|
jscode, 'Initial JS player n function name', group='name')
|
||||||
elif not idx:
|
elif not idx:
|
||||||
return funcname
|
return funcname
|
||||||
|
@ -3238,6 +3247,11 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||||
rf'var {re.escape(funcname)}\s*=\s*(\[.+?\])\s*[,;]', jscode,
|
rf'var {re.escape(funcname)}\s*=\s*(\[.+?\])\s*[,;]', jscode,
|
||||||
f'Initial JS player n function list ({funcname}.{idx})')))[int(idx)]
|
f'Initial JS player n function list ({funcname}.{idx})')))[int(idx)]
|
||||||
|
|
||||||
|
def _fixup_n_function_code(self, argnames, code):
|
||||||
|
return argnames, re.sub(
|
||||||
|
rf';\s*if\s*\(\s*typeof\s+[a-zA-Z0-9_$]+\s*===?\s*(["\'])undefined\1\s*\)\s*return\s+{argnames[0]};',
|
||||||
|
';', code)
|
||||||
|
|
||||||
def _extract_n_function_code(self, video_id, player_url):
|
def _extract_n_function_code(self, video_id, player_url):
|
||||||
player_id = self._extract_player_info(player_url)
|
player_id = self._extract_player_info(player_url)
|
||||||
func_code = self.cache.load('youtube-nsig', player_id, min_ver='2024.07.09')
|
func_code = self.cache.load('youtube-nsig', player_id, min_ver='2024.07.09')
|
||||||
|
@ -3249,7 +3263,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||||
|
|
||||||
func_name = self._extract_n_function_name(jscode, player_url=player_url)
|
func_name = self._extract_n_function_name(jscode, player_url=player_url)
|
||||||
|
|
||||||
func_code = jsi.extract_function_code(func_name)
|
# XXX: Workaround for the `typeof` gotcha
|
||||||
|
func_code = self._fixup_n_function_code(*jsi.extract_function_code(func_name))
|
||||||
|
|
||||||
self.cache.store('youtube-nsig', player_id, func_code)
|
self.cache.store('youtube-nsig', player_id, func_code)
|
||||||
return jsi, player_id, func_code
|
return jsi, player_id, func_code
|
||||||
|
@ -3265,7 +3280,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise JSInterpreter.Exception(traceback.format_exc(), cause=e)
|
raise JSInterpreter.Exception(traceback.format_exc(), cause=e)
|
||||||
|
|
||||||
if ret.startswith('enhanced_except_'):
|
if ret.startswith('enhanced_except_') or ret.endswith(s):
|
||||||
raise JSInterpreter.Exception('Signature function returned an exception')
|
raise JSInterpreter.Exception('Signature function returned an exception')
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# Autogenerated by devscripts/update-version.py
|
# Autogenerated by devscripts/update-version.py
|
||||||
|
|
||||||
__version__ = '2024.11.18'
|
__version__ = '2024.12.06'
|
||||||
|
|
||||||
RELEASE_GIT_HEAD = '7ea2787920cccc6b8ea30791993d114fbd564434'
|
RELEASE_GIT_HEAD = '4bd2655398aed450456197a6767639114a24eac2'
|
||||||
|
|
||||||
VARIANT = None
|
VARIANT = None
|
||||||
|
|
||||||
|
@ -12,4 +12,4 @@ CHANNEL = 'stable'
|
||||||
|
|
||||||
ORIGIN = 'yt-dlp/yt-dlp'
|
ORIGIN = 'yt-dlp/yt-dlp'
|
||||||
|
|
||||||
_pkg_version = '2024.11.18'
|
_pkg_version = '2024.12.06'
|
||||||
|
|
Loading…
Reference in a new issue