]> jfr.im git - yt-dlp.git/blobdiff - yt_dlp/extractor/vimeo.py
[extractor] Deprecate `_sort_formats`
[yt-dlp.git] / yt_dlp / extractor / vimeo.py
index b2c92937389168645af7116222988939074c5aed..26fe566b03130f500a7e2fe793faa5bffd783bdc 100644 (file)
@@ -30,7 +30,6 @@
     unsmuggle_url,
     urlencode_postdata,
     urljoin,
-    unescapeHTML,
     urlhandle_detect_ext,
 )
 
@@ -40,6 +39,18 @@ class VimeoBaseInfoExtractor(InfoExtractor):
     _LOGIN_REQUIRED = False
     _LOGIN_URL = 'https://vimeo.com/log_in'
 
+    @staticmethod
+    def _smuggle_referrer(url, referrer_url):
+        return smuggle_url(url, {'http_headers': {'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'])
+        return url, data, headers
+
     def _perform_login(self, username, password):
         webpage = self._download_webpage(
             self._LOGIN_URL, None, 'Downloading login page')
@@ -112,14 +123,9 @@ def _extract_vimeo_config(self, webpage, video_id, *args, **kwargs):
     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['title']
+        video_title = video_data.get('title')
         live_event = video_data.get('live_event') or {}
         is_live = live_event.get('status') == 'started'
         request = config.get('request') or {}
@@ -231,6 +237,9 @@ def _parse_config(self, config, video_id):
             'formats': formats,
             'subtitles': subtitles,
             'is_live': is_live,
+            # 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):
@@ -316,6 +325,14 @@ class VimeoIE(VimeoBaseInfoExtractor):
                         /?(?:[?&].*)?(?:[#].*)?$
                     '''
     IE_NAME = 'vimeo'
+    _EMBED_REGEX = [
+        # iframe
+        r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//player\.vimeo\.com/video/\d+.*?)\1',
+        # Embedded (swf embed) Vimeo player
+        r'<embed[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?vimeo\.com/moogaloop\.swf.+?)\1',
+        # Non-standard embedded Vimeo player
+        r'<video[^>]+src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?vimeo\.com/[0-9]+)\1',
+    ]
     _TESTS = [
         {
             'url': 'http://vimeo.com/56015672#at=0',
@@ -717,33 +734,14 @@ class VimeoIE(VimeoBaseInfoExtractor):
         # vimeo embed with check-password page protected by Referer header
     ]
 
-    @staticmethod
-    def _smuggle_referrer(url, referrer_url):
-        return smuggle_url(url, {'http_headers': {'Referer': referrer_url}})
-
-    @staticmethod
-    def _extract_urls(url, webpage):
-        urls = []
-        # Look for embedded (iframe) Vimeo player
-        for mobj in re.finditer(
-                r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//player\.vimeo\.com/video/\d+.*?)\1',
-                webpage):
-            urls.append(VimeoIE._smuggle_referrer(unescapeHTML(mobj.group('url')), url))
-        PLAIN_EMBED_RE = (
-            # Look for embedded (swf embed) Vimeo player
-            r'<embed[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?vimeo\.com/moogaloop\.swf.+?)\1',
-            # Look more for non-standard embedded Vimeo player
-            r'<video[^>]+src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?vimeo\.com/[0-9]+)\1',
-        )
-        for embed_re in PLAIN_EMBED_RE:
-            for mobj in re.finditer(embed_re, webpage):
-                urls.append(mobj.group('url'))
-        return urls
+    @classmethod
+    def _extract_embed_urls(cls, url, webpage):
+        for embed_url in super()._extract_embed_urls(url, webpage):
+            yield cls._smuggle_referrer(embed_url, url)
 
-    @staticmethod
-    def _extract_url(url, webpage):
-        urls = VimeoIE._extract_urls(url, webpage)
-        return urls[0] if urls else None
+    @classmethod
+    def _extract_url(cls, url, webpage):
+        return next(cls._extract_embed_urls(url, webpage), None)
 
     def _verify_player_video_password(self, url, video_id, headers):
         password = self._get_video_password()
@@ -754,8 +752,8 @@ def _verify_player_video_password(self, url, video_id, headers):
             'Content-Type': 'application/x-www-form-urlencoded',
         })
         checked = self._download_json(
-            url + '/check-password', video_id,
-            'Verifying the password', data=data, headers=headers)
+            f'{compat_urlparse.urlsplit(url)._replace(query=None).geturl()}/check-password',
+            video_id, 'Verifying the password', data=data, headers=headers)
         if checked is False:
             raise ExtractorError('Wrong video password', expected=True)
         return checked
@@ -776,7 +774,6 @@ def _extract_from_api(self, video_id, unlisted_hash=None):
             })
         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'),
@@ -830,10 +827,7 @@ def _try_album_password(self, url):
                 raise
 
     def _real_extract(self, url):
-        url, data = unsmuggle_url(url, {})
-        headers = self.get_param('http_headers').copy()
-        if 'http_headers' in data:
-            headers.update(data['http_headers'])
+        url, data, headers = self._unsmuggle_headers(url)
         if 'Referer' not in headers:
             headers['Referer'] = url
 
@@ -873,13 +867,11 @@ def _real_extract(self, url):
 
         if '://player.vimeo.com/video/' in url:
             config = self._parse_json(self._search_regex(
-                r'\bconfig\s*=\s*({.+?})\s*;', webpage, 'info section'), video_id)
+                r'\b(?:playerC|c)onfig\s*=\s*({.+?})\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()
@@ -984,7 +976,7 @@ def is_rented():
 
         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={})
 
@@ -1007,7 +999,7 @@ def is_rented():
         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 = [{
@@ -1132,9 +1124,9 @@ def _real_extract(self, url):
         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',
@@ -1143,6 +1135,9 @@ class VimeoUserIE(VimeoChannelIE):
             'id': 'nkistudio',
         },
         'playlist_mincount': 66,
+    }, {
+        'url': 'https://vimeo.com/nkistudio/',
+        'only_matching': True,
     }]
     _BASE_URL_TEMPL = 'https://vimeo.com/%s'
 
@@ -1239,7 +1234,7 @@ def _real_extract(self, url):
             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 = [{
@@ -1326,14 +1321,13 @@ def _real_extract(self, url):
             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)'
+    IE_DESC = 'Vimeo watch later list, ":vimeowatchlater" keyword (requires authentication)'
     _VALID_URL = r'https://vimeo\.com/(?:home/)?watchlater|:vimeowatchlater'
     _TITLE = 'Watch Later'
     _LOGIN_REQUIRED = True
@@ -1354,7 +1348,7 @@ 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'
@@ -1381,21 +1375,21 @@ def _real_extract(self, url):
 class VHXEmbedIE(VimeoBaseInfoExtractor):
     IE_NAME = 'vhx:embed'
     _VALID_URL = r'https?://embed\.vhx\.tv/videos/(?P<id>\d+)'
+    _EMBED_REGEX = [r'<iframe[^>]+src="(?P<url>https?://embed\.vhx\.tv/videos/\d+[^"]*)"']
 
-    @staticmethod
-    def _extract_url(webpage):
-        mobj = re.search(
-            r'<iframe[^>]+src="(https?://embed\.vhx\.tv/videos/\d+[^"]*)"', webpage)
-        return unescapeHTML(mobj.group(1)) if mobj else None
+    @classmethod
+    def _extract_embed_urls(cls, url, webpage):
+        for embed_url in super()._extract_embed_urls(url, webpage):
+            yield cls._smuggle_referrer(embed_url, url)
 
     def _real_extract(self, url):
         video_id = self._match_id(url)
-        webpage = self._download_webpage(url, video_id)
+        url, _, headers = self._unsmuggle_headers(url)
+        webpage = self._download_webpage(url, video_id, headers=headers)
         config_url = self._parse_json(self._search_regex(
             r'window\.OTTData\s*=\s*({.+})', webpage,
             'ott data'), video_id, js_to_json)['config_url']
         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