[vevo] extract all formats and bypass geo restriction

This commit is contained in:
remitamine 2016-01-25 22:35:06 +01:00
parent de691a498d
commit 2975fe1a7b

View File

@ -3,22 +3,18 @@
import re import re
from .common import InfoExtractor from .common import InfoExtractor
from ..compat import ( from ..compat import compat_etree_fromstring
compat_etree_fromstring,
compat_urlparse,
)
from ..utils import ( from ..utils import (
ExtractorError, ExtractorError,
int_or_none, int_or_none,
sanitized_Request,
) )
class VevoIE(InfoExtractor): class VevoIE(InfoExtractor):
""" '''
Accepts urls from vevo.com or in the format 'vevo:{id}' Accepts urls from vevo.com or in the format 'vevo:{id}'
(currently used by MTVIE and MySpaceIE) (currently used by MTVIE and MySpaceIE)
""" '''
_VALID_URL = r'''(?x) _VALID_URL = r'''(?x)
(?:https?://www\.vevo\.com/watch/(?:[^/]+/(?:[^/]+/)?)?| (?:https?://www\.vevo\.com/watch/(?:[^/]+/(?:[^/]+/)?)?|
https?://cache\.vevo\.com/m/html/embed\.html\?video=| https?://cache\.vevo\.com/m/html/embed\.html\?video=|
@ -28,48 +24,51 @@ class VevoIE(InfoExtractor):
_TESTS = [{ _TESTS = [{
'url': 'http://www.vevo.com/watch/hurts/somebody-to-die-for/GB1101300280', 'url': 'http://www.vevo.com/watch/hurts/somebody-to-die-for/GB1101300280',
"md5": "95ee28ee45e70130e3ab02b0f579ae23", 'md5': '2dbc7e9fd4f1c60436c9aa73a5406193',
'info_dict': { 'info_dict': {
'id': 'GB1101300280', 'id': 'Pt1kc_FniKM',
'ext': 'mp4', 'ext': 'mp4',
"upload_date": "20130624", 'title': 'Hurts - Somebody to Die For',
"uploader": "Hurts", 'description': 'md5:13e925b89af6b01c7e417332bd23c4bf',
"title": "Somebody to Die For", 'uploader_id': 'HurtsVEVO',
"duration": 230.12, 'uploader': 'HurtsVEVO',
"width": 1920, 'upload_date': '20130624',
"height": 1080, 'duration': 230,
# timestamp and upload_date are often incorrect; seem to change randomly },
'timestamp': int, 'add_ie': ['Youtube'],
}
}, { }, {
'note': 'v3 SMIL format', 'note': 'v3 SMIL format',
'url': 'http://www.vevo.com/watch/cassadee-pope/i-wish-i-could-break-your-heart/USUV71302923', 'url': 'http://www.vevo.com/watch/cassadee-pope/i-wish-i-could-break-your-heart/USUV71302923',
'md5': 'f6ab09b034f8c22969020b042e5ac7fc', 'md5': '13d5204f520af905eeffa675040b8e76',
'info_dict': { 'info_dict': {
'id': 'USUV71302923', 'id': 'ByGmQn1uxJw',
'ext': 'mp4', 'ext': 'mp4',
'title': 'Cassadee Pope - I Wish I Could Break Your Heart',
'description': 'md5:5e9721c92ef117a6f69d00e9b42ceba7',
'uploader_id': 'CassadeeVEVO',
'uploader': 'CassadeeVEVO',
'upload_date': '20140219', 'upload_date': '20140219',
'uploader': 'Cassadee Pope', 'duration': 226,
'title': 'I Wish I Could Break Your Heart',
'duration': 226.101,
'age_limit': 0, 'age_limit': 0,
'timestamp': int, },
} 'add_ie': ['Youtube'],
}, { }, {
'note': 'Age-limited video', 'note': 'Age-limited video',
'url': 'https://www.vevo.com/watch/justin-timberlake/tunnel-vision-explicit/USRV81300282', 'url': 'https://www.vevo.com/watch/justin-timberlake/tunnel-vision-explicit/USRV81300282',
'info_dict': { 'info_dict': {
'id': 'USRV81300282', 'id': '07FYdnEawAQ',
'ext': 'mp4', 'ext': 'mp4',
'age_limit': 18, 'age_limit': 18,
'title': 'Tunnel Vision (Explicit)', 'title': 'Justin Timberlake - Tunnel Vision (Explicit)',
'uploader': 'Justin Timberlake', 'description': 'md5:64249768eec3bc4276236606ea996373',
'upload_date': 're:2013070[34]', 'uploader_id': 'justintimberlakeVEVO',
'timestamp': int, 'uploader': 'justintimberlakeVEVO',
'upload_date': '20130703',
}, },
'params': { 'params': {
'skip_download': 'true', 'skip_download': 'true',
} },
'add_ie': ['Youtube'],
}, { }, {
'note': 'No video_info', 'note': 'No video_info',
'url': 'http://www.vevo.com/watch/k-camp-1/Till-I-Die/USUV71503000', 'url': 'http://www.vevo.com/watch/k-camp-1/Till-I-Die/USUV71503000',
@ -80,66 +79,40 @@ class VevoIE(InfoExtractor):
'title': 'Till I Die - K Camp ft. T.I.', 'title': 'Till I Die - K Camp ft. T.I.',
'duration': 193, 'duration': 193,
}, },
'expected_warnings': ['Unable to download SMIL file'],
}] }]
_SMIL_BASE_URL = 'http://smil.lvl3.vevo.com/' _SMIL_BASE_URL = 'http://smil.lvl3.vevo.com'
_SOURCE_TYPES = {
0: 'youtube',
1: 'brightcove',
2: 'http',
3: 'hls_ios',
4: 'hls',
5: 'smil', # http
7: 'f4m_cc',
8: 'f4m_ak',
9: 'f4m_l3',
10: 'ism',
13: 'smil', # rtmp
18: 'dash',
}
_VERSIONS = {
0: 'youtube',
1: 'level3',
2: 'akamai',
3: 'level3',
4: 'amazon',
}
def _real_initialize(self): def _parse_smil_formats(self, smil, smil_url, video_id, namespace=None, f4m_params=None, transform_rtmp_url=None):
req = sanitized_Request(
'http://www.vevo.com/auth', data=b'')
webpage = self._download_webpage(
req, None,
note='Retrieving oauth token',
errnote='Unable to retrieve oauth token',
fatal=False)
if webpage is False:
self._oauth_token = None
else:
if 'THIS PAGE IS CURRENTLY UNAVAILABLE IN YOUR REGION' in webpage:
raise ExtractorError('%s said: This page is currently unavailable in your region.' % self.IE_NAME, expected=True)
self._oauth_token = self._search_regex(
r'access_token":\s*"([^"]+)"',
webpage, 'access token', fatal=False)
def _formats_from_json(self, video_info):
if not video_info:
return []
last_version = {'version': -1}
for version in video_info['videoVersions']:
# These are the HTTP downloads, other types are for different manifests
if version['sourceType'] == 2:
if version['version'] > last_version['version']:
last_version = version
if last_version['version'] == -1:
raise ExtractorError('Unable to extract last version of the video')
renditions = compat_etree_fromstring(last_version['data'])
formats = [] formats = []
# Already sorted from worst to best quality els = smil.findall('.//{http://www.w3.org/2001/SMIL20/Language}video')
for rend in renditions.findall('rendition'):
attr = rend.attrib
format_note = '%(videoCodec)s@%(videoBitrate)4sk, %(audioCodec)s@%(audioBitrate)3sk' % attr
formats.append({
'url': attr['url'],
'format_id': attr['name'],
'format_note': format_note,
'height': int(attr['frameheight']),
'width': int(attr['frameWidth']),
})
return formats
def _formats_from_smil(self, smil_doc):
formats = []
els = smil_doc.findall('.//{http://www.w3.org/2001/SMIL20/Language}video')
for el in els: for el in els:
src = el.attrib['src'] src = el.attrib['src']
m = re.match(r'''(?xi) m = re.match(r'''(?xi)
(?P<ext>[a-z0-9]+): (?P<ext>[a-z0-9]+):
(?P<path> (?P<path>
[/a-z0-9]+ # The directory and main part of the URL [/a-z0-9]+ # The directory and main part of the URL
_(?P<cbr>[0-9]+)k _(?P<tbr>[0-9]+)k
_(?P<width>[0-9]+)x(?P<height>[0-9]+) _(?P<width>[0-9]+)x(?P<height>[0-9]+)
_(?P<vcodec>[a-z0-9]+) _(?P<vcodec>[a-z0-9]+)
_(?P<vbr>[0-9]+) _(?P<vbr>[0-9]+)
@ -153,9 +126,10 @@ def _formats_from_smil(self, smil_doc):
format_url = self._SMIL_BASE_URL + m.group('path') format_url = self._SMIL_BASE_URL + m.group('path')
formats.append({ formats.append({
'url': format_url, 'url': format_url,
'format_id': 'SMIL_' + m.group('cbr'), 'format_id': 'smil_' + m.group('tbr'),
'vcodec': m.group('vcodec'), 'vcodec': m.group('vcodec'),
'acodec': m.group('acodec'), 'acodec': m.group('acodec'),
'tbr': int(m.group('tbr')),
'vbr': int(m.group('vbr')), 'vbr': int(m.group('vbr')),
'abr': int(m.group('abr')), 'abr': int(m.group('abr')),
'ext': m.group('ext'), 'ext': m.group('ext'),
@ -164,26 +138,6 @@ def _formats_from_smil(self, smil_doc):
}) })
return formats return formats
def _download_api_formats(self, video_id, video_url):
if not self._oauth_token:
self._downloader.report_warning(
'No oauth token available, skipping API HLS download')
return []
api_url = compat_urlparse.urljoin(video_url, '//apiv2.vevo.com/video/%s/streams/hls?token=%s' % (
video_id, self._oauth_token))
api_data = self._download_json(
api_url, video_id,
note='Downloading HLS formats',
errnote='Failed to download HLS format list', fatal=False)
if api_data is None:
return []
m3u8_url = api_data[0]['url']
return self._extract_m3u8_formats(
m3u8_url, video_id, entry_protocol='m3u8_native', ext='mp4',
preference=0)
def _real_extract(self, url): def _real_extract(self, url):
video_id = self._match_id(url) video_id = self._match_id(url)
@ -193,19 +147,57 @@ def _real_extract(self, url):
response = self._download_json(json_url, video_id) response = self._download_json(json_url, video_id)
video_info = response['video'] or {} video_info = response['video'] or {}
if not video_info and response.get('statusCode') != 909: if not video_info:
ytid = response.get('errorInfo', {}).get('ytid')
if ytid:
return self.url_result(ytid, 'Youtube', ytid)
if response.get('statusCode') != 909:
if 'statusMessage' in response: if 'statusMessage' in response:
raise ExtractorError('%s said: %s' % (self.IE_NAME, response['statusMessage']), expected=True) raise ExtractorError('%s said: %s' % (
self.IE_NAME, response['statusMessage']), expected=True)
raise ExtractorError('Unable to extract videos') raise ExtractorError('Unable to extract videos')
if not video_info:
if url.startswith('vevo:'): if url.startswith('vevo:'):
raise ExtractorError('Please specify full Vevo URL for downloading', expected=True) raise ExtractorError(
'Please specify full Vevo URL for downloading', expected=True)
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
title = video_info.get('title') or self._og_search_title(webpage) title = video_info.get('title') or self._og_search_title(webpage)
formats = self._formats_from_json(video_info) smil_parsed = False
formats = []
for video_version in video_info['videoVersions']:
version = self._VERSIONS.get(video_version['version'])
if version == 'youtube':
return self.url_result(
video_version['id'], 'Youtube', video_version['id'])
else:
source_type = self._SOURCE_TYPES.get(video_version['sourceType'])
renditions = compat_etree_fromstring(video_version['data'])
if source_type == 'http':
for rend in renditions.findall('rendition'):
attr = rend.attrib
formats.append({
'url': attr['url'],
'format_id': '%s-%s' % (version, attr['name']),
'height': int_or_none(attr.get('frameheight')),
'width': int_or_none(attr.get('frameWidth')),
'tbr': int_or_none(attr.get('totalBitrate')),
'vbr': int_or_none(attr.get('videoBitrate')),
'abr': int_or_none(attr.get('audioBitrate')),
'vcodec': attr.get('videoCodec'),
'acodec': attr.get('audioCodec'),
})
elif source_type == 'hls':
formats.extend(self._extract_m3u8_formats(
renditions.find('rendition').attrib['url'], video_id,
'mp4', 'm3u8_native', m3u8_id='hls-%s' % version, fatal=False))
elif source_type == 'smil' and not smil_parsed:
formats.extend(self._extract_smil_formats(
renditions.find('rendition').attrib['url'], video_id, False))
smil_parsed = True
self._sort_formats(formats)
is_explicit = video_info.get('isExplicit') is_explicit = video_info.get('isExplicit')
if is_explicit is True: if is_explicit is True:
@ -215,28 +207,6 @@ def _real_extract(self, url):
else: else:
age_limit = None age_limit = None
# Download via HLS API
formats.extend(self._download_api_formats(video_id, url))
# Download SMIL
smil_blocks = sorted((
f for f in video_info.get('videoVersions', [])
if f['sourceType'] == 13),
key=lambda f: f['version'])
smil_url = '%s/Video/V2/VFILE/%s/%sr.smil' % (
self._SMIL_BASE_URL, video_id, video_id.lower())
if smil_blocks:
smil_url_m = self._search_regex(
r'url="([^"]+)"', smil_blocks[-1]['data'], 'SMIL URL',
default=None)
if smil_url_m is not None:
smil_url = smil_url_m
if smil_url:
smil_doc = self._download_smil(smil_url, video_id, fatal=False)
if smil_doc:
formats.extend(self._formats_from_smil(smil_doc))
self._sort_formats(formats)
timestamp = int_or_none(self._search_regex( timestamp = int_or_none(self._search_regex(
r'/Date\((\d+)\)/', r'/Date\((\d+)\)/',
video_info['launchDate'], 'launch date', fatal=False), video_info['launchDate'], 'launch date', fatal=False),