diff options
| -rw-r--r-- | youtube_dl/extractor/__init__.py | 1 | ||||
| -rw-r--r-- | youtube_dl/extractor/common.py | 5 | ||||
| -rw-r--r-- | youtube_dl/extractor/generic.py | 118 | ||||
| -rw-r--r-- | youtube_dl/extractor/orf.py | 120 | ||||
| -rw-r--r-- | youtube_dl/extractor/veehd.py | 39 | ||||
| -rw-r--r-- | youtube_dl/extractor/vimeo.py | 25 | ||||
| -rw-r--r-- | youtube_dl/utils.py | 1 | 
7 files changed, 196 insertions, 113 deletions
diff --git a/youtube_dl/extractor/__init__.py b/youtube_dl/extractor/__init__.py index 21d564dba..f1167989e 100644 --- a/youtube_dl/extractor/__init__.py +++ b/youtube_dl/extractor/__init__.py @@ -199,6 +199,7 @@ from .vimeo import (      VimeoUserIE,      VimeoAlbumIE,      VimeoGroupsIE, +    VimeoReviewIE,  )  from .vine import VineIE  from .viki import VikiIE diff --git a/youtube_dl/extractor/common.py b/youtube_dl/extractor/common.py index f498bcf6f..2a5e8076c 100644 --- a/youtube_dl/extractor/common.py +++ b/youtube_dl/extractor/common.py @@ -73,6 +73,10 @@ class InfoExtractor(object):                                   by this field.                                   -1 for default (order by other properties),                                   -2 or smaller for less than default. +                    * quality    Order number of the video quality of this +                                 format, irrespective of the file format. +                                 -1 for default (order by other properties), +                                 -2 or smaller for less than default.      url:            Final video URL.      ext:            Video filename extension.      format:         The video format, defaults to ext (used for --get-format) @@ -483,6 +487,7 @@ class InfoExtractor(object):              return (                  preference, +                f.get('quality') if f.get('quality') is not None else -1,                  f.get('height') if f.get('height') is not None else -1,                  f.get('width') if f.get('width') is not None else -1,                  ext_preference, diff --git a/youtube_dl/extractor/generic.py b/youtube_dl/extractor/generic.py index 62c918b6a..2bfdf8f01 100644 --- a/youtube_dl/extractor/generic.py +++ b/youtube_dl/extractor/generic.py @@ -1,5 +1,7 @@  # encoding: utf-8 +from __future__ import unicode_literals +  import os  import re @@ -23,78 +25,78 @@ from .ooyala import OoyalaIE  class GenericIE(InfoExtractor): -    IE_DESC = u'Generic downloader that works on some sites' +    IE_DESC = 'Generic downloader that works on some sites'      _VALID_URL = r'.*' -    IE_NAME = u'generic' +    IE_NAME = 'generic'      _TESTS = [          { -            u'url': u'http://www.hodiho.fr/2013/02/regis-plante-sa-jeep.html', -            u'file': u'13601338388002.mp4', -            u'md5': u'6e15c93721d7ec9e9ca3fdbf07982cfd', -            u'info_dict': { -                u"uploader": u"www.hodiho.fr", -                u"title": u"R\u00e9gis plante sa Jeep" +            'url': 'http://www.hodiho.fr/2013/02/regis-plante-sa-jeep.html', +            'file': '13601338388002.mp4', +            'md5': '6e15c93721d7ec9e9ca3fdbf07982cfd', +            'info_dict': { +                'uploader': 'www.hodiho.fr', +                'title': 'R\u00e9gis plante sa Jeep',              }          },          # embedded vimeo video          { -            u'add_ie': ['Vimeo'], -            u'url': u'http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvalue-references', -            u'file': u'22444065.mp4', -            u'md5': u'2903896e23df39722c33f015af0666e2', -            u'info_dict': { -                u'title': u'ACCU 2011: Move Semantics,Perfect Forwarding, and Rvalue references- Scott Meyers- 13/04/2011', -                u"uploader_id": u"skillsmatter", -                u"uploader": u"Skills Matter", +            'add_ie': ['Vimeo'], +            'url': 'http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvalue-references', +            'file': '22444065.mp4', +            'md5': '2903896e23df39722c33f015af0666e2', +            'info_dict': { +                'title': 'ACCU 2011: Move Semantics,Perfect Forwarding, and Rvalue references- Scott Meyers- 13/04/2011', +                'uploader_id': 'skillsmatter', +                'uploader': 'Skills Matter',              }          },          # bandcamp page with custom domain          { -            u'add_ie': ['Bandcamp'], -            u'url': u'http://bronyrock.com/track/the-pony-mash', -            u'file': u'3235767654.mp3', -            u'info_dict': { -                u'title': u'The Pony Mash', -                u'uploader': u'M_Pallante', +            'add_ie': ['Bandcamp'], +            'url': 'http://bronyrock.com/track/the-pony-mash', +            'file': '3235767654.mp3', +            'info_dict': { +                'title': 'The Pony Mash', +                'uploader': 'M_Pallante',              }, -            u'skip': u'There is a limit of 200 free downloads / month for the test song', +            'skip': 'There is a limit of 200 free downloads / month for the test song',          },          # embedded brightcove video          # it also tests brightcove videos that need to set the 'Referer' in the          # http requests          { -            u'add_ie': ['Brightcove'], -            u'url': u'http://www.bfmtv.com/video/bfmbusiness/cours-bourse/cours-bourse-l-analyse-technique-154522/', -            u'info_dict': { -                u'id': u'2765128793001', -                u'ext': u'mp4', -                u'title': u'Le cours de bourse : l’analyse technique', -                u'description': u'md5:7e9ad046e968cb2d1114004aba466fd9', -                u'uploader': u'BFM BUSINESS', +            'add_ie': ['Brightcove'], +            'url': 'http://www.bfmtv.com/video/bfmbusiness/cours-bourse/cours-bourse-l-analyse-technique-154522/', +            'info_dict': { +                'id': '2765128793001', +                'ext': 'mp4', +                'title': 'Le cours de bourse : l’analyse technique', +                'description': 'md5:7e9ad046e968cb2d1114004aba466fd9', +                'uploader': 'BFM BUSINESS',              }, -            u'params': { -                u'skip_download': True, +            'params': { +                'skip_download': True,              },          },          # Direct link to a video          { -            u'url': u'http://media.w3.org/2010/05/sintel/trailer.mp4', -            u'file': u'trailer.mp4', -            u'md5': u'67d406c2bcb6af27fa886f31aa934bbe', -            u'info_dict': { -                u'id': u'trailer', -                u'title': u'trailer', -                u'upload_date': u'20100513', +            'url': 'http://media.w3.org/2010/05/sintel/trailer.mp4', +            'file': 'trailer.mp4', +            'md5': '67d406c2bcb6af27fa886f31aa934bbe', +            'info_dict': { +                'id': 'trailer', +                'title': 'trailer', +                'upload_date': '20100513',              }          },          # ooyala video          { -            u'url': u'http://www.rollingstone.com/music/videos/norwegian-dj-cashmere-cat-goes-spartan-on-with-me-premiere-20131219', -            u'md5': u'5644c6ca5d5782c1d0d350dad9bd840c', -            u'info_dict': { -                u'id': u'BwY2RxaTrTkslxOfcan0UCf0YqyvWysJ', -                u'ext': u'mp4', -                u'title': u'2cc213299525360.mov', #that's what we get +            'url': 'http://www.rollingstone.com/music/videos/norwegian-dj-cashmere-cat-goes-spartan-on-with-me-premiere-20131219', +            'md5': '5644c6ca5d5782c1d0d350dad9bd840c', +            'info_dict': { +                'id': 'BwY2RxaTrTkslxOfcan0UCf0YqyvWysJ', +                'ext': 'mp4', +                'title': '2cc213299525360.mov', #that's what we get              },          },      ] @@ -102,12 +104,12 @@ class GenericIE(InfoExtractor):      def report_download_webpage(self, video_id):          """Report webpage download."""          if not self._downloader.params.get('test', False): -            self._downloader.report_warning(u'Falling back on generic information extractor.') +            self._downloader.report_warning('Falling back on generic information extractor.')          super(GenericIE, self).report_download_webpage(video_id)      def report_following_redirect(self, new_url):          """Report information extraction.""" -        self._downloader.to_screen(u'[redirect] Following redirect to %s' % new_url) +        self._downloader.to_screen('[redirect] Following redirect to %s' % new_url)      def _send_head(self, url):          """Check if it is a redirect, like url shorteners, in case return the new url.""" @@ -153,7 +155,7 @@ class GenericIE(InfoExtractor):          response = opener.open(HEADRequest(url))          if response is None: -            raise ExtractorError(u'Invalid URL protocol') +            raise ExtractorError('Invalid URL protocol')          return response      def _real_extract(self, url): @@ -163,7 +165,7 @@ class GenericIE(InfoExtractor):              return self.url_result('http://' + url)          video_id = os.path.splitext(url.split('/')[-1])[0] -        self.to_screen(u'%s: Requesting header' % video_id) +        self.to_screen('%s: Requesting header' % video_id)          try:              response = self._send_head(url) @@ -187,7 +189,7 @@ class GenericIE(InfoExtractor):                      'formats': [{                          'format_id': m.group('format_id'),                          'url': url, -                        'vcodec': u'none' if m.group('type') == 'audio' else None +                        'vcodec': 'none' if m.group('type') == 'audio' else None                      }],                      'upload_date': upload_date,                  } @@ -201,7 +203,7 @@ class GenericIE(InfoExtractor):          except ValueError:              # since this is the last-resort InfoExtractor, if              # this error is thrown, it'll be thrown here -            raise ExtractorError(u'Failed to download URL: %s' % url) +            raise ExtractorError('Failed to download URL: %s' % url)          self.report_extraction(video_id) @@ -212,17 +214,17 @@ class GenericIE(InfoExtractor):          #   Video Title - Tagline | Site Name          # and so on and so forth; it's just not practical          video_title = self._html_search_regex( -            r'(?s)<title>(.*?)</title>', webpage, u'video title', -            default=u'video') +            r'(?s)<title>(.*?)</title>', webpage, 'video title', +            default='video')          # video uploader is domain name          video_uploader = self._search_regex( -            r'^(?:https?://)?([^/]*)/.*', url, u'video uploader') +            r'^(?:https?://)?([^/]*)/.*', url, 'video uploader')          # Look for BrightCove:          bc_url = BrightcoveIE._extract_brightcove_url(webpage)          if bc_url is not None: -            self.to_screen(u'Brightcove video detected.') +            self.to_screen('Brightcove video detected.')              return self.url_result(bc_url, 'Brightcove')          # Look for embedded (iframe) Vimeo player @@ -329,12 +331,12 @@ class GenericIE(InfoExtractor):              # HTML5 video              mobj = re.search(r'<video[^<]*(?:>.*?<source.*?)? src="([^"]+)"', webpage, flags=re.DOTALL)          if mobj is None: -            raise ExtractorError(u'Unsupported URL: %s' % url) +            raise ExtractorError('Unsupported URL: %s' % url)          # It's possible that one of the regexes          # matched, but returned an empty group:          if mobj.group(1) is None: -            raise ExtractorError(u'Did not find a valid video URL at %s' % url) +            raise ExtractorError('Did not find a valid video URL at %s' % url)          video_url = mobj.group(1)          video_url = compat_urlparse.urljoin(url, video_url) diff --git a/youtube_dl/extractor/orf.py b/youtube_dl/extractor/orf.py index b42eae89a..88f03608b 100644 --- a/youtube_dl/extractor/orf.py +++ b/youtube_dl/extractor/orf.py @@ -1,54 +1,98 @@  # coding: utf-8 +from __future__ import unicode_literals -import re -import xml.etree.ElementTree  import json +import re  from .common import InfoExtractor  from ..utils import ( -    compat_urlparse, -    ExtractorError, -    find_xpath_attr, +    HEADRequest, +    unified_strdate,  ) +  class ORFIE(InfoExtractor): -    _VALID_URL = r'https?://tvthek\.orf\.at/(programs/.+?/episodes|topics/.+?)/(?P<id>\d+)' +    _VALID_URL = r'https?://tvthek\.orf\.at/(?:programs/.+?/episodes|topics/.+?|program/[^/]+)/(?P<id>\d+)' + +    _TEST = { +        'url': 'http://tvthek.orf.at/program/matinee-Was-Sie-schon-immer-ueber-Klassik-wissen-wollten/7317210/Was-Sie-schon-immer-ueber-Klassik-wissen-wollten/7319746/Was-Sie-schon-immer-ueber-Klassik-wissen-wollten/7319747', +        'file': '7319747.mp4', +        'md5': 'bd803c5d8c32d3c64a0ea4b4eeddf375', +        'info_dict': { +            'title': 'Was Sie schon immer über Klassik wissen wollten', +            'description': 'md5:0ddf0d5f0060bd53f744edaa5c2e04a4', +            'duration': 3508, +            'upload_date': '20140105', +        }, +        'skip': 'Blocked outside of Austria', +    }      def _real_extract(self, url):          mobj = re.match(self._VALID_URL, url)          playlist_id = mobj.group('id')          webpage = self._download_webpage(url, playlist_id) -        flash_xml = self._search_regex('ORF.flashXML = \'(.+?)\'', webpage, u'flash xml') -        flash_xml = compat_urlparse.parse_qs('xml='+flash_xml)['xml'][0] -        flash_config = xml.etree.ElementTree.fromstring(flash_xml.encode('utf-8')) -        playlist_json = self._search_regex(r'playlist\': \'(\[.*?\])\'', webpage, u'playlist').replace(r'\"','"') -        playlist = json.loads(playlist_json) - -        videos = [] -        ns = '{http://tempuri.org/XMLSchema.xsd}' -        xpath = '%(ns)sPlaylist/%(ns)sItems/%(ns)sItem' % {'ns': ns} -        webpage_description = self._og_search_description(webpage) -        for (i, (item, info)) in enumerate(zip(flash_config.findall(xpath), playlist), 1): -            # Get best quality url -            rtmp_url = None -            for q in ['Q6A', 'Q4A', 'Q1A']: -                video_url = find_xpath_attr(item, '%sVideoUrl' % ns, 'quality', q) -                if video_url is not None: -                    rtmp_url = video_url.text -                    break -            if rtmp_url is None: -                raise ExtractorError(u'Couldn\'t get video url: %s' % info['id']) -            description = self._html_search_regex( -                r'id="playlist_entry_%s".*?<p>(.*?)</p>' % i, webpage, -                u'description', default=webpage_description, flags=re.DOTALL) -            videos.append({ +        data_json = self._search_regex( +            r'initializeAdworx\((.+?)\);\n', webpage, 'video info') +        all_data = json.loads(data_json) +        sdata = all_data[0]['values']['segments'] + +        def quality_to_int(s): +            m = re.search('([0-9]+)', s) +            if m is None: +                return -1 +            return int(m.group(1)) + +        entries = [] +        for sd in sdata: +            video_id = sd['id'] +            formats = [{ +                'preference': -10 if fd['delivery'] == 'hls' else None, +                'format_id': '%s-%s-%s' % ( +                    fd['delivery'], fd['quality'], fd['quality_string']), +                'url': fd['src'], +                'protocol': fd['protocol'], +                'quality': quality_to_int(fd['quality']), +            } for fd in sd['playlist_item_array']['sources']] + +            # Check for geoblocking. +            # There is a property is_geoprotection, but that's always false +            geo_str = sd.get('geoprotection_string') +            if geo_str: +                try: +                    http_url = next( +                        f['url'] +                        for f in formats +                        if re.match(r'^https?://.*\.mp4$', f['url'])) +                except StopIteration: +                    pass +                else: +                    req = HEADRequest(http_url) +                    response = self._request_webpage( +                        req, video_id, +                        note='Testing for geoblocking', +                        errnote=(( +                            'This video seems to be blocked outside of %s. ' +                            'You may want to try the streaming-* formats.') +                            % geo_str), +                        fatal=False) + +            self._sort_formats(formats) + +            upload_date = unified_strdate(sd['created_date']) +            entries.append({                  '_type': 'video', -                'id': info['id'], -                'title': info['title'], -                'url': rtmp_url, -                'ext': 'flv', -                'description': description, -                }) - -        return videos +                'id': video_id, +                'title': sd['header'], +                'formats': formats, +                'description': sd.get('description'), +                'duration': int(sd['duration_in_seconds']), +                'upload_date': upload_date, +                'thumbnail': sd.get('image_full_url'), +            }) + +        return { +            '_type': 'playlist', +            'entries': entries, +            'id': playlist_id, +        } diff --git a/youtube_dl/extractor/veehd.py b/youtube_dl/extractor/veehd.py index 3cf8c853d..b1c854a64 100644 --- a/youtube_dl/extractor/veehd.py +++ b/youtube_dl/extractor/veehd.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals +  import re  import json @@ -8,16 +10,17 @@ from ..utils import (      clean_html,  ) +  class VeeHDIE(InfoExtractor):      _VALID_URL = r'https?://veehd\.com/video/(?P<id>\d+)'      _TEST = { -        u'url': u'http://veehd.com/video/4686958', -        u'file': u'4686958.mp4', -        u'info_dict': { -            u'title': u'Time Lapse View from Space ( ISS)', -            u'uploader_id': u'spotted', -            u'description': u'md5:f0094c4cf3a72e22bc4e4239ef767ad7', +        'url': 'http://veehd.com/video/4686958', +        'file': '4686958.mp4', +        'info_dict': { +            'title': 'Time Lapse View from Space ( ISS)', +            'uploader_id': 'spotted', +            'description': 'md5:f0094c4cf3a72e22bc4e4239ef767ad7',          },      } @@ -25,24 +28,30 @@ class VeeHDIE(InfoExtractor):          mobj = re.match(self._VALID_URL, url)          video_id = mobj.group('id') +        # VeeHD seems to send garbage on the first request. +        # See https://github.com/rg3/youtube-dl/issues/2102 +        self._download_webpage(url, video_id, 'Requesting webpage')          webpage = self._download_webpage(url, video_id) -        player_path = self._search_regex(r'\$\("#playeriframe"\).attr\({src : "(.+?)"', -            webpage, u'player path') +        player_path = self._search_regex( +            r'\$\("#playeriframe"\).attr\({src : "(.+?)"', +            webpage, 'player path')          player_url = compat_urlparse.urljoin(url, player_path) -        player_page = self._download_webpage(player_url, video_id, -            u'Downloading player page') -        config_json = self._search_regex(r'value=\'config=({.+?})\'', -            player_page, u'config json') + +        self._download_webpage(player_url, video_id, 'Requesting player page') +        player_page = self._download_webpage( +            player_url, video_id, 'Downloading player page') +        config_json = self._search_regex( +            r'value=\'config=({.+?})\'', player_page, 'config json')          config = json.loads(config_json)          video_url = compat_urlparse.unquote(config['clip']['url'])          title = clean_html(get_element_by_id('videoName', webpage).rpartition('|')[0])          uploader_id = self._html_search_regex(r'<a href="/profile/\d+">(.+?)</a>', -            webpage, u'uploader') +            webpage, 'uploader')          thumbnail = self._search_regex(r'<img id="veehdpreview" src="(.+?)"', -            webpage, u'thumbnail') +            webpage, 'thumbnail')          description = self._html_search_regex(r'<td class="infodropdown".*?<div>(.*?)<ul', -            webpage, u'description', flags=re.DOTALL) +            webpage, 'description', flags=re.DOTALL)          return {              '_type': 'video', diff --git a/youtube_dl/extractor/vimeo.py b/youtube_dl/extractor/vimeo.py index c3623fcbe..05e1aa1f2 100644 --- a/youtube_dl/extractor/vimeo.py +++ b/youtube_dl/extractor/vimeo.py @@ -311,7 +311,7 @@ class VimeoChannelIE(InfoExtractor):  class VimeoUserIE(VimeoChannelIE):      IE_NAME = u'vimeo:user' -    _VALID_URL = r'(?:https?://)?vimeo.\com/(?P<name>[^/]+)' +    _VALID_URL = r'(?:https?://)?vimeo.\com/(?P<name>[^/]+)(?:[#?]|$)'      _TITLE_RE = r'<a[^>]+?class="user">([^<>]+?)</a>'      @classmethod @@ -336,7 +336,7 @@ class VimeoAlbumIE(VimeoChannelIE):      def _real_extract(self, url):          mobj = re.match(self._VALID_URL, url) -        album_id =  mobj.group('id') +        album_id = mobj.group('id')          return self._extract_videos(album_id, 'http://vimeo.com/album/%s' % album_id) @@ -351,3 +351,24 @@ class VimeoGroupsIE(VimeoAlbumIE):          mobj = re.match(self._VALID_URL, url)          name = mobj.group('name')          return self._extract_videos(name, 'http://vimeo.com/groups/%s' % name) + + +class VimeoReviewIE(InfoExtractor): +    IE_NAME = u'vimeo:review' +    IE_DESC = u'Review pages on vimeo' +    _VALID_URL = r'(?:https?://)?vimeo.\com/[^/]+/review/(?P<id>[^/]+)' +    _TEST = { +        'url': 'https://vimeo.com/user21297594/review/75524534/3c257a1b5d', +        'file': '75524534.mp4', +        'md5': 'c507a72f780cacc12b2248bb4006d253', +        'info_dict': { +            'title': "DICK HARDWICK 'Comedian'", +            'uploader': 'Richard Hardwick', +        } +    } + +    def _real_extract(self, url): +        mobj = re.match(self._VALID_URL, url) +        video_id = mobj.group('id') +        player_url = 'https://player.vimeo.com/player/' + video_id +        return self.url_result(player_url, 'Vimeo', video_id) diff --git a/youtube_dl/utils.py b/youtube_dl/utils.py index 536504e7e..918a127ca 100644 --- a/youtube_dl/utils.py +++ b/youtube_dl/utils.py @@ -764,6 +764,7 @@ def unified_strdate(date_str):          '%Y-%m-%d',          '%d/%m/%Y',          '%Y/%m/%d %H:%M:%S', +        '%Y-%m-%d %H:%M:%S',          '%d.%m.%Y %H:%M',          '%Y-%m-%dT%H:%M:%SZ',          '%Y-%m-%dT%H:%M:%S.%fZ',  | 
