mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2026-05-03 22:16:10 +00:00
[ie/vimeo] Extract from mobile API
Authored by: bashonly
This commit is contained in:
parent
8cb08028f5
commit
faac5f3464
@ -39,6 +39,8 @@ class VimeoBaseInfoExtractor(InfoExtractor):
|
|||||||
_NETRC_MACHINE = 'vimeo'
|
_NETRC_MACHINE = 'vimeo'
|
||||||
_LOGIN_REQUIRED = False
|
_LOGIN_REQUIRED = False
|
||||||
_LOGIN_URL = 'https://vimeo.com/log_in'
|
_LOGIN_URL = 'https://vimeo.com/log_in'
|
||||||
|
_APP_AUTH = 'MTMxNzViY2Y0NDE0YTQ5YzhjZTc0YmU0NjVjNDQxYzNkYWVjOWRlOTpHKzRvMmgzVUh4UkxjdU5FRW80cDNDbDhDWGR5dVJLNUJZZ055dHBHTTB4V1VzaG41bEx1a2hiN0NWYWNUcldSSW53dzRUdFRYZlJEZmFoTTArOTBUZkJHS3R4V2llYU04Qnl1bERSWWxUdXRidjNqR2J4SHFpVmtFSUcyRktuQw=='
|
||||||
|
_APP_USER_AGENT = 'Vimeo/11.9.0 (com.vimeo; build:110900.64.0; iOS 16.1.1) Alamofire/5.9.0 VimeoNetworking/5.0.0'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _smuggle_referrer(url, referrer_url):
|
def _smuggle_referrer(url, referrer_url):
|
||||||
@ -239,16 +241,16 @@ class VimeoBaseInfoExtractor(InfoExtractor):
|
|||||||
'_format_sort_fields': ('quality', 'res', 'fps', 'hdr:12', 'source'),
|
'_format_sort_fields': ('quality', 'res', 'fps', 'hdr:12', 'source'),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _call_videos_api(self, video_id, jwt_token, unlisted_hash=None, **kwargs):
|
def _call_videos_api(self, video_id, unlisted_hash=None, **kwargs):
|
||||||
return self._download_json(
|
return self._download_json(
|
||||||
join_nonempty(f'https://api.vimeo.com/videos/{video_id}', unlisted_hash, delim=':'),
|
join_nonempty(f'https://api.vimeo.com/videos/{video_id}', unlisted_hash, delim=':'),
|
||||||
video_id, 'Downloading API JSON', headers={
|
video_id, 'Downloading API JSON', headers={
|
||||||
'Authorization': f'jwt {jwt_token}',
|
'Authorization': f'Basic {self._APP_AUTH}',
|
||||||
'Accept': 'application/vnd.vimeo.*+json;version=3.4.10',
|
'Accept': 'application/vnd.vimeo.*+json; version=3.4.10',
|
||||||
|
'Accept-Language': 'en',
|
||||||
|
'User-Agent': self._APP_USER_AGENT,
|
||||||
}, query={
|
}, query={
|
||||||
# TODO: Reverse-engineer generating the 'anon_signature' param
|
'outro': 'beginning',
|
||||||
# Ref: https://f.vimeocdn.com/js_opt/app/vimeo-next/_next/static/chunks/60908-af70235e46909bce.js
|
|
||||||
'outro': 'beginning', # Needed to avoid https://github.com/yt-dlp/yt-dlp/issues/12974
|
|
||||||
'fields': ','.join((
|
'fields': ','.join((
|
||||||
# 'embed_player_config_url' is a viable alternative to 'config_url'
|
# 'embed_player_config_url' is a viable alternative to 'config_url'
|
||||||
'config_url', 'created_time', 'description', 'download', 'license',
|
'config_url', 'created_time', 'description', 'download', 'license',
|
||||||
@ -256,7 +258,7 @@ class VimeoBaseInfoExtractor(InfoExtractor):
|
|||||||
'release_time', 'stats.plays')),
|
'release_time', 'stats.plays')),
|
||||||
}, **kwargs)
|
}, **kwargs)
|
||||||
|
|
||||||
def _extract_original_format(self, url, video_id, unlisted_hash=None, jwt=None, api_data=None):
|
def _extract_original_format(self, url, video_id, unlisted_hash=None, api_data=None):
|
||||||
# Original/source formats are only available when logged in
|
# Original/source formats are only available when logged in
|
||||||
if not self._get_cookies('https://vimeo.com/').get('vimeo'):
|
if not self._get_cookies('https://vimeo.com/').get('vimeo'):
|
||||||
return
|
return
|
||||||
@ -287,12 +289,8 @@ class VimeoBaseInfoExtractor(InfoExtractor):
|
|||||||
'quality': 1,
|
'quality': 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
jwt = jwt or traverse_obj(self._download_json(
|
|
||||||
'https://vimeo.com/_rv/viewer', video_id, 'Downloading jwt token', fatal=False), ('jwt', {str}))
|
|
||||||
if not jwt:
|
|
||||||
return
|
|
||||||
original_response = api_data or self._call_videos_api(
|
original_response = api_data or self._call_videos_api(
|
||||||
video_id, jwt, unlisted_hash, fatal=False, expected_status=(403, 404))
|
video_id, unlisted_hash, fatal=False, expected_status=(403, 404))
|
||||||
for download_data in traverse_obj(original_response, ('download', ..., {dict})):
|
for download_data in traverse_obj(original_response, ('download', ..., {dict})):
|
||||||
download_url = download_data.get('link')
|
download_url = download_data.get('link')
|
||||||
if not download_url or download_data.get('quality') != 'source':
|
if not download_url or download_data.get('quality') != 'source':
|
||||||
@ -871,12 +869,9 @@ class VimeoIE(VimeoBaseInfoExtractor):
|
|||||||
return checked
|
return checked
|
||||||
|
|
||||||
def _extract_from_api(self, video_id, unlisted_hash=None):
|
def _extract_from_api(self, video_id, unlisted_hash=None):
|
||||||
viewer = self._download_json(
|
|
||||||
'https://vimeo.com/_next/viewer', video_id, 'Downloading viewer info')
|
|
||||||
|
|
||||||
for retry in (False, True):
|
for retry in (False, True):
|
||||||
try:
|
try:
|
||||||
video = self._call_videos_api(video_id, viewer['jwt'], unlisted_hash)
|
video = self._call_videos_api(video_id, unlisted_hash)
|
||||||
break
|
break
|
||||||
except ExtractorError as e:
|
except ExtractorError as e:
|
||||||
if (not retry and isinstance(e.cause, HTTPError) and e.cause.status == 400
|
if (not retry and isinstance(e.cause, HTTPError) and e.cause.status == 400
|
||||||
@ -884,6 +879,8 @@ class VimeoIE(VimeoBaseInfoExtractor):
|
|||||||
self._webpage_read_content(e.cause.response, e.cause.response.url, video_id, fatal=False),
|
self._webpage_read_content(e.cause.response, e.cause.response.url, video_id, fatal=False),
|
||||||
({json.loads}, 'invalid_parameters', ..., 'field'),
|
({json.loads}, 'invalid_parameters', ..., 'field'),
|
||||||
)):
|
)):
|
||||||
|
viewer = self._download_json(
|
||||||
|
'https://vimeo.com/_next/viewer', video_id, 'Downloading viewer info')
|
||||||
self._verify_video_password(
|
self._verify_video_password(
|
||||||
video_id, self._get_video_password(), viewer['xsrft'])
|
video_id, self._get_video_password(), viewer['xsrft'])
|
||||||
continue
|
continue
|
||||||
@ -892,7 +889,7 @@ class VimeoIE(VimeoBaseInfoExtractor):
|
|||||||
info = self._parse_config(self._download_json(
|
info = self._parse_config(self._download_json(
|
||||||
video['config_url'], video_id), video_id)
|
video['config_url'], video_id), video_id)
|
||||||
source_format = self._extract_original_format(
|
source_format = self._extract_original_format(
|
||||||
f'https://vimeo.com/{video_id}', video_id, unlisted_hash, jwt=viewer['jwt'], api_data=video)
|
f'https://vimeo.com/{video_id}', video_id, unlisted_hash, api_data=video)
|
||||||
if source_format:
|
if source_format:
|
||||||
info['formats'].append(source_format)
|
info['formats'].append(source_format)
|
||||||
|
|
||||||
@ -1448,7 +1445,7 @@ class VimeoReviewIE(VimeoBaseInfoExtractor):
|
|||||||
info_dict = self._parse_config(config, video_id)
|
info_dict = self._parse_config(config, video_id)
|
||||||
source_format = self._extract_original_format(
|
source_format = self._extract_original_format(
|
||||||
f'https://vimeo.com/{user}/review/{video_id}/{review_hash}/action',
|
f'https://vimeo.com/{user}/review/{video_id}/{review_hash}/action',
|
||||||
video_id, unlisted_hash=clip_data.get('unlistedHash'), jwt=viewer.get('jwt'))
|
video_id, unlisted_hash=clip_data.get('unlistedHash'))
|
||||||
if source_format:
|
if source_format:
|
||||||
info_dict['formats'].append(source_format)
|
info_dict['formats'].append(source_format)
|
||||||
info_dict['description'] = clean_html(clip_data.get('description'))
|
info_dict['description'] = clean_html(clip_data.get('description'))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user