import base64
import functools
-import re
import itertools
+import re
from .common import InfoExtractor
-from ..compat import (
- compat_HTTPError,
- compat_str,
- compat_urlparse,
-)
+from ..compat import compat_str, compat_urlparse
+from ..networking import HEADRequest, Request
+from ..networking.exceptions import HTTPError
from ..utils import (
+ ExtractorError,
+ OnDemandPagedList,
clean_html,
determine_ext,
- ExtractorError,
get_element_by_class,
- HEADRequest,
- js_to_json,
int_or_none,
+ js_to_json,
merge_dicts,
- OnDemandPagedList,
parse_filesize,
parse_iso8601,
parse_qs,
- sanitized_Request,
smuggle_url,
str_or_none,
+ traverse_obj,
try_get,
unified_timestamp,
unsmuggle_url,
urlencode_postdata,
- urljoin,
urlhandle_detect_ext,
+ urljoin,
)
@staticmethod
def _smuggle_referrer(url, referrer_url):
- return smuggle_url(url, {'http_headers': {'Referer': referrer_url}})
+ return smuggle_url(url, {'referer': referrer_url})
def _unsmuggle_headers(self, url):
"""@returns (url, smuggled_data, headers)"""
url, data = unsmuggle_url(url, {})
headers = self.get_param('http_headers').copy()
- if 'http_headers' in data:
- headers.update(data['http_headers'])
+ if 'referer' in data:
+ headers['Referer'] = data['referer']
return url, data, headers
def _perform_login(self, username, password):
- webpage = self._download_webpage(
- self._LOGIN_URL, None, 'Downloading login page')
- token, vuid = self._extract_xsrft_and_vuid(webpage)
+ viewer = self._download_json('https://vimeo.com/_next/viewer', None, 'Downloading login token')
data = {
'action': 'login',
'email': username,
'password': password,
'service': 'vimeo',
- 'token': token,
+ 'token': viewer['xsrft'],
}
- self._set_vimeo_cookie('vuid', vuid)
+ self._set_vimeo_cookie('vuid', viewer['vuid'])
try:
self._download_webpage(
self._LOGIN_URL, None, 'Logging in',
'Referer': self._LOGIN_URL,
})
except ExtractorError as e:
- if isinstance(e.cause, compat_HTTPError) and e.cause.code == 418:
+ if isinstance(e.cause, HTTPError) and e.cause.status == 418:
raise ExtractorError(
'Unable to log in: bad username or password',
expected=True)
def _set_vimeo_cookie(self, name, value):
self._set_cookie('vimeo.com', name, value)
- def _vimeo_sort_formats(self, formats):
- # Note: Bitrates are completely broken. Single m3u8 may contain entries in kbps and bps
- # at the same time without actual units specified.
- self._sort_formats(formats, ('quality', 'res', 'fps', 'hdr:12', 'source'))
-
def _parse_config(self, config, video_id):
video_data = config['video']
video_title = video_data.get('title')
live_event = video_data.get('live_event') or {}
- is_live = live_event.get('status') == 'started'
+ live_status = {
+ 'pending': 'is_upcoming',
+ 'active': 'is_upcoming',
+ 'started': 'is_live',
+ 'ended': 'post_live',
+ }.get(live_event.get('status'))
+ is_live = live_status == 'is_live'
request = config.get('request') or {}
formats = []
'chapters': chapters or None,
'formats': formats,
'subtitles': subtitles,
- 'is_live': is_live,
+ 'live_status': live_status,
+ 'release_timestamp': traverse_obj(live_event, ('ingest', 'scheduled_start_time', {parse_iso8601})),
+ # Note: Bitrates are completely broken. Single m3u8 may contain entries in kbps and bps
+ # at the same time without actual units specified.
+ '_format_sort_fields': ('quality', 'res', 'fps', 'hdr:12', 'source'),
}
def _extract_original_format(self, url, video_id, unlisted_hash=None):
'https://vimeo.com/_rv/viewer', video_id, note='Downloading jwt token', fatal=False) or {}
if not jwt_response.get('jwt'):
return
- headers = {'Authorization': 'jwt %s' % jwt_response['jwt']}
+ headers = {'Authorization': 'jwt %s' % jwt_response['jwt'], 'Accept': 'application/json'}
original_response = self._download_json(
f'https://api.vimeo.com/videos/{video_id}', video_id,
headers=headers, fatal=False, expected_status=(403, 404)) or {}
# _VALID_URL matches Vimeo URLs
_VALID_URL = r'''(?x)
- https?://
- (?:
- (?:
- www|
- player
- )
- \.
- )?
- vimeo(?:pro)?\.com/
- (?!(?:channels|album|showcase)/[^/?#]+/?(?:$|[?#])|[^/]+/review/|ondemand/)
- (?:[^/]+/)*?
- (?:
- (?:
- play_redirect_hls|
- moogaloop\.swf)\?clip_id=
- )?
- (?:videos?/)?
- (?P<id>[0-9]+)
- (?:/(?P<unlisted_hash>[\da-f]{10}))?
- /?(?:[?&].*)?(?:[#].*)?$
- '''
+ https?://
+ (?:
+ (?:
+ www|
+ player
+ )
+ \.
+ )?
+ vimeo\.com/
+ (?:
+ (?P<u>user)|
+ (?!(?:channels|album|showcase)/[^/?#]+/?(?:$|[?#])|[^/]+/review/|ondemand/)
+ (?:.*?/)??
+ (?P<q>
+ (?:
+ play_redirect_hls|
+ moogaloop\.swf)\?clip_id=
+ )?
+ (?:videos?/)?
+ )
+ (?P<id>[0-9]+)
+ (?(u)
+ /(?!videos|likes)[^/?#]+/?|
+ (?(q)|/(?P<unlisted_hash>[\da-f]{10}))?
+ )
+ (?:(?(q)[&]|(?(u)|/?)[?]).*?)?(?:[#].*)?$
+ '''
IE_NAME = 'vimeo'
_EMBED_REGEX = [
# iframe
},
'skip': 'No longer available'
},
- {
- 'url': 'http://vimeopro.com/openstreetmapus/state-of-the-map-us-2013/video/68093876',
- 'md5': '3b5ca6aa22b60dfeeadf50b72e44ed82',
- 'note': 'Vimeo Pro video (#1197)',
- 'info_dict': {
- 'id': '68093876',
- 'ext': 'mp4',
- 'uploader_url': r're:https?://(?:www\.)?vimeo\.com/openstreetmapus',
- 'uploader_id': 'openstreetmapus',
- 'uploader': 'OpenStreetMap US',
- 'title': 'Andy Allan - Putting the Carto into OpenStreetMap Cartography',
- 'description': 'md5:2c362968038d4499f4d79f88458590c1',
- 'duration': 1595,
- 'upload_date': '20130610',
- 'timestamp': 1370893156,
- 'license': 'by',
- 'thumbnail': 'https://i.vimeocdn.com/video/440260469-19b0d92fca3bd84066623b53f1eb8aaa3980c6c809e2d67b6b39ab7b4a77a344-d_960',
- 'view_count': int,
- 'comment_count': int,
- 'like_count': int,
- },
- 'params': {
- 'format': 'best[protocol=https]',
- },
- },
{
'url': 'http://player.vimeo.com/video/54469442',
- 'md5': 'b3e7f4d2cbb53bd7dc3bb6ff4ed5cfbd',
+ 'md5': '619b811a4417aa4abe78dc653becf511',
'note': 'Videos that embed the url in the player page',
'info_dict': {
'id': '54469442',
'uploader_url': r're:https?://(?:www\.)?vimeo\.com/businessofsoftware',
'uploader_id': 'businessofsoftware',
'duration': 3610,
- 'description': None,
'thumbnail': 'https://i.vimeocdn.com/video/376682406-f34043e7b766af6bef2af81366eacd6724f3fc3173179a11a97a1e26587c9529-d_1280',
},
'params': {
'uploader_id': 'user18948128',
'uploader': 'Jaime Marquínez Ferrándiz',
'duration': 10,
- 'description': 'md5:dca3ea23adb29ee387127bc4ddfce63f',
- 'thumbnail': 'https://i.vimeocdn.com/video/440665496-b2c5aee2b61089442c794f64113a8e8f7d5763c3e6b3ebfaf696ae6413f8b1f4-d_960',
+ 'description': 'md5:6173f270cd0c0119f22817204b3eb86c',
+ 'thumbnail': 'https://i.vimeocdn.com/video/440665496-b2c5aee2b61089442c794f64113a8e8f7d5763c3e6b3ebfaf696ae6413f8b1f4-d_1280',
'view_count': int,
'comment_count': int,
'like_count': int,
'id': '75629013',
'ext': 'mp4',
'title': 'Key & Peele: Terrorist Interrogation',
- 'description': 'md5:8678b246399b070816b12313e8b4eb5c',
+ 'description': 'md5:6173f270cd0c0119f22817204b3eb86c',
'uploader_url': r're:https?://(?:www\.)?vimeo\.com/atencio',
'uploader_id': 'atencio',
'uploader': 'Peter Atencio',
'uploader_id': 'user18948128',
'uploader': 'Jaime Marquínez Ferrándiz',
'duration': 10,
- 'description': 'md5:dca3ea23adb29ee387127bc4ddfce63f',
- 'thumbnail': 'https://i.vimeocdn.com/video/440665496-b2c5aee2b61089442c794f64113a8e8f7d5763c3e6b3ebfaf696ae6413f8b1f4-d_960',
+ 'description': 'md5:6173f270cd0c0119f22817204b3eb86c',
+ 'thumbnail': 'https://i.vimeocdn.com/video/440665496-b2c5aee2b61089442c794f64113a8e8f7d5763c3e6b3ebfaf696ae6413f8b1f4-d_1280',
'view_count': int,
'comment_count': int,
'like_count': int,
'params': {
'skip_download': True,
},
- }
+ },
+ {
+ # user playlist alias -> https://vimeo.com/258705797
+ 'url': 'https://vimeo.com/user26785108/newspiritualguide',
+ 'only_matching': True,
+ },
# https://gettingthingsdone.com/workflowmap/
# vimeo embed with check-password page protected by Referer header
]
video = self._download_json(
api_url, video_id, headers={
'Authorization': 'jwt ' + token,
+ 'Accept': 'application/json',
}, query={
'fields': 'config_url,created_time,description,license,metadata.connections.comments.total,metadata.connections.likes.total,release_time,stats.plays',
})
info = self._parse_config(self._download_json(
video['config_url'], video_id), video_id)
- self._vimeo_sort_formats(info['formats'])
get_timestamp = lambda x: parse_iso8601(video.get(x + '_time'))
info.update({
'description': video.get('description'),
jwt = viewer['jwt']
album = self._download_json(
'https://api.vimeo.com/albums/' + album_id,
- album_id, headers={'Authorization': 'jwt ' + jwt},
+ album_id, headers={'Authorization': 'jwt ' + jwt, 'Accept': 'application/json'},
query={'fields': 'description,name,privacy'})
if try_get(album, lambda x: x['privacy']['view']) == 'password':
password = self.get_param('videopassword')
'X-Requested-With': 'XMLHttpRequest',
})
except ExtractorError as e:
- if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
+ if isinstance(e.cause, HTTPError) and e.cause.status == 401:
raise ExtractorError('Wrong password', expected=True)
raise
if unlisted_hash:
return self._extract_from_api(video_id, unlisted_hash)
- orig_url = url
- is_pro = 'vimeopro.com/' in url
- if is_pro:
- # some videos require portfolio_id to be present in player url
- # https://github.com/ytdl-org/youtube-dl/issues/20070
- url = self._extract_url(url, self._download_webpage(url, video_id))
- if not url:
- url = 'https://vimeo.com/' + video_id
- elif any(p in url for p in ('play_redirect_hls', 'moogaloop.swf')):
+ if any(p in url for p in ('play_redirect_hls', 'moogaloop.swf')):
url = 'https://vimeo.com/' + video_id
self._try_album_password(url)
# Retrieve video webpage to extract further information
webpage, urlh = self._download_webpage_handle(
url, video_id, headers=headers)
- redirect_url = urlh.geturl()
+ redirect_url = urlh.url
except ExtractorError as ee:
- if isinstance(ee.cause, compat_HTTPError) and ee.cause.code == 403:
- errmsg = ee.cause.read()
+ if isinstance(ee.cause, HTTPError) and ee.cause.status == 403:
+ errmsg = ee.cause.response.read()
if b'Because of its privacy settings, this video cannot be played here' in errmsg:
raise ExtractorError(
'Cannot download embed-only video without embedding '
raise
if '://player.vimeo.com/video/' in url:
- config = self._parse_json(self._search_regex(
- r'\bconfig\s*=\s*({.+?})\s*;', webpage, 'info section'), video_id)
+ config = self._search_json(
+ r'\b(?:playerC|c)onfig\s*=', webpage, 'info section', video_id)
if config.get('view') == 4:
config = self._verify_player_video_password(
redirect_url, video_id, headers)
- info = self._parse_config(config, video_id)
- self._vimeo_sort_formats(info['formats'])
- return info
+ return self._parse_config(config, video_id)
if re.search(r'<form[^>]+?id="pw_form"', webpage):
video_password = self._get_video_password()
video_description = self._html_search_meta(
['description', 'og:description', 'twitter:description'],
webpage, default=None)
- if not video_description and is_pro:
- orig_webpage = self._download_webpage(
- orig_url, video_id,
- note='Downloading webpage for description',
- fatal=False)
- if orig_webpage:
- video_description = self._html_search_meta(
- 'description', orig_webpage, default=None)
if not video_description:
self.report_warning('Cannot find video description')
info_dict_config = self._parse_config(config, video_id)
formats.extend(info_dict_config['formats'])
- self._vimeo_sort_formats(formats)
+ info_dict['_format_sort_fields'] = info_dict_config['_format_sort_fields']
json_ld = self._search_json_ld(webpage, video_id, default={})
return merge_dicts(info_dict, info_dict_config, json_ld)
-class VimeoOndemandIE(VimeoIE):
+class VimeoOndemandIE(VimeoIE): # XXX: Do not subclass from concrete IE
IE_NAME = 'vimeo:ondemand'
_VALID_URL = r'https?://(?:www\.)?vimeo\.com/ondemand/(?:[^/]+/)?(?P<id>[^/?#&]+)'
_TESTS = [{
return self._extract_videos(channel_id, self._BASE_URL_TEMPL % channel_id)
-class VimeoUserIE(VimeoChannelIE):
+class VimeoUserIE(VimeoChannelIE): # XXX: Do not subclass from concrete IE
IE_NAME = 'vimeo:user'
- _VALID_URL = r'https://vimeo\.com/(?!(?:[0-9]+|watchlater)(?:$|[?#/]))(?P<id>[^/]+)(?:/videos|[#?]|$)'
+ _VALID_URL = r'https://vimeo\.com/(?!(?:[0-9]+|watchlater)(?:$|[?#/]))(?P<id>[^/]+)(?:/videos)?/?(?:$|[?#])'
_TITLE_RE = r'<a[^>]+?class="user">([^<>]+?)</a>'
_TESTS = [{
'url': 'https://vimeo.com/nkistudio/videos',
'id': 'nkistudio',
},
'playlist_mincount': 66,
+ }, {
+ 'url': 'https://vimeo.com/nkistudio/',
+ 'only_matching': True,
}]
_BASE_URL_TEMPL = 'https://vimeo.com/%s'
'https://api.vimeo.com/albums/%s/videos' % album_id,
album_id, 'Downloading page %d' % api_page, query=query, headers={
'Authorization': 'jwt ' + authorization,
+ 'Accept': 'application/json',
})['data']
except ExtractorError as e:
- if isinstance(e.cause, compat_HTTPError) and e.cause.code == 400:
+ if isinstance(e.cause, HTTPError) and e.cause.status == 400:
return
+ raise
for video in videos:
link = video.get('link')
if not link:
jwt = viewer['jwt']
album = self._download_json(
'https://api.vimeo.com/albums/' + album_id,
- album_id, headers={'Authorization': 'jwt ' + jwt},
+ album_id, headers={'Authorization': 'jwt ' + jwt, 'Accept': 'application/json'},
query={'fields': 'description,name,privacy'})
hashed_pass = None
if try_get(album, lambda x: x['privacy']['view']) == 'password':
'X-Requested-With': 'XMLHttpRequest',
})['hashed_pass']
except ExtractorError as e:
- if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
+ if isinstance(e.cause, HTTPError) and e.cause.status == 401:
raise ExtractorError('Wrong password', expected=True)
raise
entries = OnDemandPagedList(functools.partial(
entries, album_id, album.get('name'), album.get('description'))
-class VimeoGroupsIE(VimeoChannelIE):
+class VimeoGroupsIE(VimeoChannelIE): # XXX: Do not subclass from concrete IE
IE_NAME = 'vimeo:group'
_VALID_URL = r'https://vimeo\.com/groups/(?P<id>[^/]+)(?:/(?!videos?/\d+)|$)'
_TESTS = [{
page_url + '/action', video_id)
if source_format:
info_dict['formats'].append(source_format)
- self._vimeo_sort_formats(info_dict['formats'])
info_dict['description'] = clean_html(clip_data.get('description'))
return info_dict
-class VimeoWatchLaterIE(VimeoChannelIE):
+class VimeoWatchLaterIE(VimeoChannelIE): # XXX: Do not subclass from concrete IE
IE_NAME = 'vimeo:watchlater'
IE_DESC = 'Vimeo watch later list, ":vimeowatchlater" keyword (requires authentication)'
_VALID_URL = r'https://vimeo\.com/(?:home/)?watchlater|:vimeowatchlater'
def _page_url(self, base_url, pagenum):
url = '%s/page:%d/' % (base_url, pagenum)
- request = sanitized_Request(url)
+ request = Request(url)
# Set the header to get a partial html page with the ids,
# the normal page doesn't contain them.
- request.add_header('X-Requested-With', 'XMLHttpRequest')
+ request.headers['X-Requested-With'] = 'XMLHttpRequest'
return request
def _real_extract(self, url):
return self._extract_videos('watchlater', 'https://vimeo.com/watchlater')
-class VimeoLikesIE(VimeoChannelIE):
+class VimeoLikesIE(VimeoChannelIE): # XXX: Do not subclass from concrete IE
_VALID_URL = r'https://(?:www\.)?vimeo\.com/(?P<id>[^/]+)/likes/?(?:$|[?#]|sort:)'
IE_NAME = 'vimeo:likes'
IE_DESC = 'Vimeo user likes'
config = self._download_json(config_url, video_id)
info = self._parse_config(config, video_id)
info['id'] = video_id
- self._vimeo_sort_formats(info['formats'])
return info
+
+
+class VimeoProIE(VimeoBaseInfoExtractor):
+ IE_NAME = 'vimeo:pro'
+ _VALID_URL = r'https?://(?:www\.)?vimeopro\.com/[^/?#]+/(?P<slug>[^/?#]+)(?:(?:/videos?/(?P<id>[0-9]+)))?'
+ _TESTS = [{
+ # Vimeo URL derived from video_id
+ 'url': 'http://vimeopro.com/openstreetmapus/state-of-the-map-us-2013/video/68093876',
+ 'md5': '3b5ca6aa22b60dfeeadf50b72e44ed82',
+ 'note': 'Vimeo Pro video (#1197)',
+ 'info_dict': {
+ 'id': '68093876',
+ 'ext': 'mp4',
+ 'uploader_url': r're:https?://(?:www\.)?vimeo\.com/openstreetmapus',
+ 'uploader_id': 'openstreetmapus',
+ 'uploader': 'OpenStreetMap US',
+ 'title': 'Andy Allan - Putting the Carto into OpenStreetMap Cartography',
+ 'description': 'md5:2c362968038d4499f4d79f88458590c1',
+ 'duration': 1595,
+ 'upload_date': '20130610',
+ 'timestamp': 1370893156,
+ 'license': 'by',
+ 'thumbnail': 'https://i.vimeocdn.com/video/440260469-19b0d92fca3bd84066623b53f1eb8aaa3980c6c809e2d67b6b39ab7b4a77a344-d_960',
+ 'view_count': int,
+ 'comment_count': int,
+ 'like_count': int,
+ 'tags': 'count:1',
+ },
+ 'params': {
+ 'format': 'best[protocol=https]',
+ },
+ }, {
+ # password-protected VimeoPro page with Vimeo player embed
+ 'url': 'https://vimeopro.com/cadfem/simulation-conference-mechanische-systeme-in-perfektion',
+ 'info_dict': {
+ 'id': '764543723',
+ 'ext': 'mp4',
+ 'title': 'Mechanische Systeme in Perfektion: Realität erfassen, Innovation treiben',
+ 'thumbnail': 'https://i.vimeocdn.com/video/1543784598-a1a750494a485e601110136b9fe11e28c2131942452b3a5d30391cb3800ca8fd-d_1280',
+ 'description': 'md5:2a9d195cd1b0f6f79827107dc88c2420',
+ 'uploader': 'CADFEM',
+ 'uploader_id': 'cadfem',
+ 'uploader_url': 'https://vimeo.com/cadfem',
+ 'duration': 12505,
+ 'chapters': 'count:10',
+ },
+ 'params': {
+ 'videopassword': 'Conference2022',
+ 'skip_download': True,
+ },
+ }]
+
+ def _real_extract(self, url):
+ display_id, video_id = self._match_valid_url(url).group('slug', 'id')
+ if video_id:
+ display_id = video_id
+ webpage = self._download_webpage(url, display_id)
+
+ password_form = self._search_regex(
+ r'(?is)<form[^>]+?method=["\']post["\'][^>]*>(.+?password.+?)</form>',
+ webpage, 'password form', default=None)
+ if password_form:
+ try:
+ webpage = self._download_webpage(url, display_id, data=urlencode_postdata({
+ 'password': self._get_video_password(),
+ **self._hidden_inputs(password_form),
+ }), note='Logging in with video password')
+ except ExtractorError as e:
+ if isinstance(e.cause, HTTPError) and e.cause.status == 418:
+ raise ExtractorError('Wrong video password', expected=True)
+ raise
+
+ description = None
+ # even if we have video_id, some videos require player URL with portfolio_id query param
+ # https://github.com/ytdl-org/youtube-dl/issues/20070
+ vimeo_url = VimeoIE._extract_url(url, webpage)
+ if vimeo_url:
+ description = self._html_search_meta('description', webpage, default=None)
+ elif video_id:
+ vimeo_url = f'https://vimeo.com/{video_id}'
+ else:
+ raise ExtractorError(
+ 'No Vimeo embed or video ID could be found in VimeoPro page', expected=True)
+
+ return self.url_result(vimeo_url, VimeoIE, video_id, url_transparent=True,
+ description=description)