| 
									
										
										
										
											2013-06-24 12:31:41 +02:00
										 |  |  | import re | 
					
						
							|  |  |  | import json | 
					
						
							| 
									
										
										
										
											2013-10-08 21:23:55 +02:00
										 |  |  | import xml.etree.ElementTree | 
					
						
							|  |  |  | import datetime | 
					
						
							| 
									
										
										
										
											2013-06-24 12:31:41 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | from .common import InfoExtractor | 
					
						
							|  |  |  | from ..utils import ( | 
					
						
							| 
									
										
										
										
											2013-10-30 01:14:17 +01:00
										 |  |  |     compat_HTTPError, | 
					
						
							| 
									
										
										
										
											2013-06-24 12:31:41 +02:00
										 |  |  |     ExtractorError, | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-08 21:23:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-06-24 12:31:41 +02:00
										 |  |  | class VevoIE(InfoExtractor): | 
					
						
							| 
									
										
										
										
											2013-06-24 13:54:19 +02:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2013-08-11 07:12:38 +02:00
										 |  |  |     Accepts urls from vevo.com or in the format 'vevo:{id}' | 
					
						
							| 
									
										
										
										
											2013-06-24 13:54:19 +02:00
										 |  |  |     (currently used by MTVIE) | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2013-08-21 18:20:03 +02:00
										 |  |  |     _VALID_URL = r'((http://www.vevo.com/watch/.*?/.*?/)|(vevo:))(?P<id>.*?)(\?|$)' | 
					
						
							| 
									
										
										
										
											2013-10-30 01:14:17 +01:00
										 |  |  |     _TESTS = [{ | 
					
						
							| 
									
										
										
										
											2013-06-27 20:46:46 +02:00
										 |  |  |         u'url': u'http://www.vevo.com/watch/hurts/somebody-to-die-for/GB1101300280', | 
					
						
							|  |  |  |         u'file': u'GB1101300280.mp4', | 
					
						
							| 
									
										
										
										
											2013-10-30 01:14:17 +01:00
										 |  |  |         u"md5": u"06bea460acb744eab74a9d7dcb4bfd61", | 
					
						
							| 
									
										
										
										
											2013-06-27 20:46:46 +02:00
										 |  |  |         u'info_dict': { | 
					
						
							| 
									
										
										
										
											2013-08-21 18:20:03 +02:00
										 |  |  |             u"upload_date": u"20130624", | 
					
						
							|  |  |  |             u"uploader": u"Hurts", | 
					
						
							| 
									
										
										
										
											2013-10-08 21:23:55 +02:00
										 |  |  |             u"title": u"Somebody to Die For", | 
					
						
							| 
									
										
										
										
											2013-10-30 01:14:17 +01:00
										 |  |  |             u"duration": 230, | 
					
						
							|  |  |  |             u"width": 1920, | 
					
						
							|  |  |  |             u"height": 1080, | 
					
						
							| 
									
										
										
										
											2013-06-27 20:46:46 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2013-10-30 01:14:17 +01:00
										 |  |  |     }] | 
					
						
							|  |  |  |     _SMIL_BASE_URL = 'http://smil.lvl3.vevo.com/' | 
					
						
							| 
									
										
										
										
											2013-06-24 12:31:41 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-30 01:14:17 +01:00
										 |  |  |     def _formats_from_json(self, video_info): | 
					
						
							| 
									
										
										
										
											2013-10-08 21:23:55 +02:00
										 |  |  |         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(u'Unable to extract last version of the video') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         renditions = xml.etree.ElementTree.fromstring(last_version['data']) | 
					
						
							|  |  |  |         formats = [] | 
					
						
							|  |  |  |         # Already sorted from worst to best quality | 
					
						
							|  |  |  |         for rend in renditions.findall('rendition'): | 
					
						
							|  |  |  |             attr = rend.attrib | 
					
						
							| 
									
										
										
										
											2013-10-30 01:14:17 +01:00
										 |  |  |             format_note = '%(videoCodec)s@%(videoBitrate)4sk, %(audioCodec)s@%(audioBitrate)3sk' % attr | 
					
						
							| 
									
										
										
										
											2013-10-08 21:23:55 +02:00
										 |  |  |             formats.append({ | 
					
						
							| 
									
										
										
										
											2013-10-29 15:10:09 +01:00
										 |  |  |                 'url': attr['url'], | 
					
						
							|  |  |  |                 'format_id': attr['name'], | 
					
						
							|  |  |  |                 'format_note': format_note, | 
					
						
							| 
									
										
										
										
											2013-10-08 21:23:55 +02:00
										 |  |  |                 'height': int(attr['frameheight']), | 
					
						
							|  |  |  |                 'width': int(attr['frameWidth']), | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2013-10-30 01:14:17 +01:00
										 |  |  |         return formats | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _formats_from_smil(self, smil_xml): | 
					
						
							|  |  |  |         formats = [] | 
					
						
							|  |  |  |         smil_doc = xml.etree.ElementTree.fromstring(smil_xml.encode('utf-8')) | 
					
						
							|  |  |  |         els = smil_doc.findall('.//{http://www.w3.org/2001/SMIL20/Language}video') | 
					
						
							|  |  |  |         for el in els: | 
					
						
							|  |  |  |             src = el.attrib['src'] | 
					
						
							|  |  |  |             m = re.match(r'''(?xi)
 | 
					
						
							|  |  |  |                 (?P<ext>[a-z0-9]+): | 
					
						
							|  |  |  |                 (?P<path> | 
					
						
							|  |  |  |                     [/a-z0-9]+     # The directory and main part of the URL | 
					
						
							|  |  |  |                     _(?P<cbr>[0-9]+)k | 
					
						
							|  |  |  |                     _(?P<width>[0-9]+)x(?P<height>[0-9]+) | 
					
						
							|  |  |  |                     _(?P<vcodec>[a-z0-9]+) | 
					
						
							|  |  |  |                     _(?P<vbr>[0-9]+) | 
					
						
							|  |  |  |                     _(?P<acodec>[a-z0-9]+) | 
					
						
							|  |  |  |                     _(?P<abr>[0-9]+) | 
					
						
							|  |  |  |                     \.[a-z0-9]+  # File extension | 
					
						
							|  |  |  |                 )''', src)
 | 
					
						
							|  |  |  |             if not m: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             format_url = self._SMIL_BASE_URL + m.group('path') | 
					
						
							|  |  |  |             formats.append({ | 
					
						
							|  |  |  |                 'url': format_url, | 
					
						
							|  |  |  |                 'format_id': u'SMIL_' + m.group('cbr'), | 
					
						
							| 
									
										
										
										
											2013-11-16 01:08:43 +01:00
										 |  |  |                 'vcodec': m.group('vcodec'), | 
					
						
							|  |  |  |                 'acodec': m.group('acodec'), | 
					
						
							|  |  |  |                 'vbr': int(m.group('vbr')), | 
					
						
							|  |  |  |                 'abr': int(m.group('abr')), | 
					
						
							| 
									
										
										
										
											2013-10-30 01:14:17 +01:00
										 |  |  |                 'ext': m.group('ext'), | 
					
						
							|  |  |  |                 'width': int(m.group('width')), | 
					
						
							|  |  |  |                 'height': int(m.group('height')), | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         return formats | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _real_extract(self, url): | 
					
						
							|  |  |  |         mobj = re.match(self._VALID_URL, url) | 
					
						
							|  |  |  |         video_id = mobj.group('id') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         json_url = 'http://videoplayer.vevo.com/VideoService/AuthenticateVideo?isrc=%s' % video_id | 
					
						
							|  |  |  |         info_json = self._download_webpage(json_url, video_id, u'Downloading json info') | 
					
						
							|  |  |  |         video_info = json.loads(info_json)['video'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         formats = self._formats_from_json(video_info) | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             smil_url = '%s/Video/V2/VFILE/%s/%sr.smil' % ( | 
					
						
							|  |  |  |                 self._SMIL_BASE_URL, video_id, video_id.lower()) | 
					
						
							|  |  |  |             smil_xml = self._download_webpage(smil_url, video_id, | 
					
						
							|  |  |  |                                               u'Downloading SMIL info') | 
					
						
							|  |  |  |             formats.extend(self._formats_from_smil(smil_xml)) | 
					
						
							|  |  |  |         except ExtractorError as ee: | 
					
						
							|  |  |  |             if not isinstance(ee.cause, compat_HTTPError): | 
					
						
							|  |  |  |                 raise | 
					
						
							|  |  |  |             self._downloader.report_warning( | 
					
						
							|  |  |  |                 u'Cannot download SMIL information, falling back to JSON ..') | 
					
						
							| 
									
										
										
										
											2013-10-08 21:23:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-29 14:00:01 +01:00
										 |  |  |         timestamp_ms = int(self._search_regex( | 
					
						
							|  |  |  |             r'/Date\((\d+)\)/', video_info['launchDate'], u'launch date')) | 
					
						
							|  |  |  |         upload_date = datetime.datetime.fromtimestamp(timestamp_ms // 1000) | 
					
						
							| 
									
										
										
										
											2013-10-08 21:23:55 +02:00
										 |  |  |         info = { | 
					
						
							|  |  |  |             'id': video_id, | 
					
						
							|  |  |  |             'title': video_info['title'], | 
					
						
							|  |  |  |             'formats': formats, | 
					
						
							|  |  |  |             'thumbnail': video_info['imageUrl'], | 
					
						
							|  |  |  |             'upload_date': upload_date.strftime('%Y%m%d'), | 
					
						
							|  |  |  |             'uploader': video_info['mainArtists'][0]['artistName'], | 
					
						
							|  |  |  |             'duration': video_info['duration'], | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return info |