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', |