| 
									
										
										
										
											2014-05-30 21:10:48 +02:00
										 |  |  | from __future__ import unicode_literals | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  | import re | 
					
						
							|  |  |  | import json | 
					
						
							| 
									
										
										
										
											2015-02-15 04:56:12 +06:00
										 |  |  | import time | 
					
						
							|  |  |  | import hmac | 
					
						
							|  |  |  | import binascii | 
					
						
							|  |  |  | import hashlib | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-02-19 14:54:50 +01:00
										 |  |  | from .common import InfoExtractor | 
					
						
							| 
									
										
										
										
											2014-12-13 12:24:42 +01:00
										 |  |  | from ..compat import ( | 
					
						
							| 
									
										
										
										
											2014-09-21 16:08:38 +02:00
										 |  |  |     compat_str, | 
					
						
							| 
									
										
										
										
											2014-12-13 12:24:42 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | from ..utils import ( | 
					
						
							| 
									
										
										
										
											2014-10-27 01:14:17 +01:00
										 |  |  |     determine_ext, | 
					
						
							| 
									
										
										
										
											2013-12-16 03:34:46 +01:00
										 |  |  |     ExtractorError, | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  |     xpath_with_ns, | 
					
						
							| 
									
										
										
										
											2015-02-15 04:56:12 +06:00
										 |  |  |     unsmuggle_url, | 
					
						
							| 
									
										
										
										
											2015-04-08 22:29:10 +06:00
										 |  |  |     int_or_none, | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | _x = lambda p: xpath_with_ns(p, {'smil': 'http://www.w3.org/2005/SMIL21/Language'}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-02-19 14:54:50 +01:00
										 |  |  | class ThePlatformIE(InfoExtractor): | 
					
						
							| 
									
										
										
										
											2013-12-23 16:41:35 +01:00
										 |  |  |     _VALID_URL = r'''(?x)
 | 
					
						
							| 
									
										
										
										
											2015-02-15 04:56:12 +06:00
										 |  |  |         (?:https?://(?:link|player)\.theplatform\.com/[sp]/(?P<provider_id>[^/]+)/ | 
					
						
							| 
									
										
										
										
											2014-02-25 23:57:54 +01:00
										 |  |  |            (?P<config>(?:[^/\?]+/(?:swf|config)|onsite)/select/)? | 
					
						
							| 
									
										
										
										
											2013-12-23 16:41:35 +01:00
										 |  |  |          |theplatform:)(?P<id>[^/\?&]+)'''
 | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-08 19:20:34 +08:00
										 |  |  |     _TESTS = [{ | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  |         # from http://www.metacafe.com/watch/cb-e9I_cZgTgIPd/blackberrys_big_bold_z30/ | 
					
						
							| 
									
										
										
										
											2014-05-30 21:10:48 +02:00
										 |  |  |         'url': 'http://link.theplatform.com/s/dJ5BDC/e9I_cZgTgIPd/meta.smil?format=smil&Tracking=true&mbr=true', | 
					
						
							|  |  |  |         'info_dict': { | 
					
						
							|  |  |  |             'id': 'e9I_cZgTgIPd', | 
					
						
							|  |  |  |             'ext': 'flv', | 
					
						
							|  |  |  |             'title': 'Blackberry\'s big, bold Z30', | 
					
						
							|  |  |  |             'description': 'The Z30 is Blackberry\'s biggest, baddest mobile messaging device yet.', | 
					
						
							|  |  |  |             'duration': 247, | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  |         }, | 
					
						
							| 
									
										
										
										
											2014-05-30 21:10:48 +02:00
										 |  |  |         'params': { | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  |             # rtmp download | 
					
						
							| 
									
										
										
										
											2014-05-30 21:10:48 +02:00
										 |  |  |             'skip_download': True, | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  |         }, | 
					
						
							| 
									
										
										
										
											2015-04-08 19:20:34 +08:00
										 |  |  |     }, { | 
					
						
							| 
									
										
										
										
											2015-04-08 22:27:25 +06:00
										 |  |  |         # from http://www.cnet.com/videos/tesla-model-s-a-second-step-towards-a-cleaner-motoring-future/ | 
					
						
							| 
									
										
										
										
											2015-04-08 19:20:34 +08:00
										 |  |  |         'url': 'http://link.theplatform.com/s/kYEXFC/22d_qsQ6MIRT', | 
					
						
							|  |  |  |         'info_dict': { | 
					
						
							|  |  |  |             'id': '22d_qsQ6MIRT', | 
					
						
							|  |  |  |             'ext': 'flv', | 
					
						
							|  |  |  |             'description': 'md5:ac330c9258c04f9d7512cf26b9595409', | 
					
						
							|  |  |  |             'title': 'Tesla Model S: A second step towards a cleaner motoring future', | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         'params': { | 
					
						
							|  |  |  |             # rtmp download | 
					
						
							|  |  |  |             'skip_download': True, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }] | 
					
						
							| 
									
										
										
										
											2014-11-23 20:41:03 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-02-15 04:56:12 +06:00
										 |  |  |     @staticmethod | 
					
						
							|  |  |  |     def _sign_url(url, sig_key, sig_secret, life=600, include_qs=False): | 
					
						
							|  |  |  |         flags = '10' if include_qs else '00' | 
					
						
							|  |  |  |         expiration_date = '%x' % (int(time.time()) + life) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def str_to_hex(str): | 
					
						
							|  |  |  |             return binascii.b2a_hex(str.encode('ascii')).decode('ascii') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def hex_to_str(hex): | 
					
						
							|  |  |  |             return binascii.a2b_hex(hex) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         relative_path = url.split('http://link.theplatform.com/s/')[1].split('?')[0] | 
					
						
							|  |  |  |         clear_text = hex_to_str(flags + expiration_date + str_to_hex(relative_path)) | 
					
						
							|  |  |  |         checksum = hmac.new(sig_key.encode('ascii'), clear_text, hashlib.sha1).hexdigest() | 
					
						
							|  |  |  |         sig = flags + expiration_date + checksum + str_to_hex(sig_secret) | 
					
						
							|  |  |  |         return '%s&sig=%s' % (url, sig) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-10-27 01:14:17 +01:00
										 |  |  |     def _real_extract(self, url): | 
					
						
							| 
									
										
										
										
											2015-02-15 04:56:12 +06:00
										 |  |  |         url, smuggled_data = unsmuggle_url(url, {}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-10-27 01:14:17 +01:00
										 |  |  |         mobj = re.match(self._VALID_URL, url) | 
					
						
							| 
									
										
										
										
											2015-02-15 04:56:12 +06:00
										 |  |  |         provider_id = mobj.group('provider_id') | 
					
						
							| 
									
										
										
										
											2014-10-27 01:14:17 +01:00
										 |  |  |         video_id = mobj.group('id') | 
					
						
							| 
									
										
										
										
											2015-02-15 04:56:12 +06:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if not provider_id: | 
					
						
							|  |  |  |             provider_id = 'dJ5BDC' | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-02-19 18:17:31 +01:00
										 |  |  |         if smuggled_data.get('force_smil_url', False): | 
					
						
							|  |  |  |             smil_url = url | 
					
						
							|  |  |  |         elif mobj.group('config'): | 
					
						
							| 
									
										
										
										
											2014-11-23 20:41:03 +01:00
										 |  |  |             config_url = url + '&form=json' | 
					
						
							| 
									
										
										
										
											2014-10-27 01:14:17 +01:00
										 |  |  |             config_url = config_url.replace('swf/', 'config/') | 
					
						
							|  |  |  |             config_url = config_url.replace('onsite/', 'onsite/config/') | 
					
						
							|  |  |  |             config = self._download_json(config_url, video_id, 'Downloading config') | 
					
						
							|  |  |  |             smil_url = config['releaseUrl'] + '&format=SMIL&formats=MPEG4&manifest=f4m' | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2015-02-15 04:56:12 +06:00
										 |  |  |             smil_url = ('http://link.theplatform.com/s/{0}/{1}/meta.smil?' | 
					
						
							|  |  |  |                         'format=smil&mbr=true'.format(provider_id, video_id)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         sig = smuggled_data.get('sig') | 
					
						
							|  |  |  |         if sig: | 
					
						
							|  |  |  |             smil_url = self._sign_url(smil_url, sig['key'], sig['secret']) | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-10-27 01:14:17 +01:00
										 |  |  |         meta = self._download_xml(smil_url, video_id) | 
					
						
							| 
									
										
										
										
											2013-12-16 03:34:46 +01:00
										 |  |  |         try: | 
					
						
							|  |  |  |             error_msg = next( | 
					
						
							|  |  |  |                 n.attrib['abstract'] | 
					
						
							|  |  |  |                 for n in meta.findall(_x('.//smil:ref')) | 
					
						
							| 
									
										
										
										
											2015-03-31 03:36:09 +08:00
										 |  |  |                 if n.attrib.get('title') == 'Geographic Restriction' or n.attrib.get('title') == 'Expired') | 
					
						
							| 
									
										
										
										
											2013-12-16 03:34:46 +01:00
										 |  |  |         except StopIteration: | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise ExtractorError(error_msg, expected=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-02-15 04:56:12 +06:00
										 |  |  |         info_url = 'http://link.theplatform.com/s/{0}/{1}?format=preview'.format(provider_id, video_id) | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  |         info_json = self._download_webpage(info_url, video_id) | 
					
						
							|  |  |  |         info = json.loads(info_json) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-12-17 20:20:40 +06:00
										 |  |  |         subtitles = {} | 
					
						
							|  |  |  |         captions = info.get('captions') | 
					
						
							|  |  |  |         if isinstance(captions, list): | 
					
						
							|  |  |  |             for caption in captions: | 
					
						
							| 
									
										
										
										
											2015-02-19 14:54:50 +01:00
										 |  |  |                 lang, src, mime = caption.get('lang', 'en'), caption.get('src'), caption.get('type') | 
					
						
							|  |  |  |                 subtitles[lang] = [{ | 
					
						
							|  |  |  |                     'ext': 'srt' if mime == 'text/srt' else 'ttml', | 
					
						
							|  |  |  |                     'url': src, | 
					
						
							|  |  |  |                 }] | 
					
						
							| 
									
										
										
										
											2014-12-17 20:20:40 +06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  |         head = meta.find(_x('smil:head')) | 
					
						
							|  |  |  |         body = meta.find(_x('smil:body')) | 
					
						
							| 
									
										
										
										
											2013-12-26 21:18:18 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-01 02:51:55 +06:00
										 |  |  |         f4m_node = body.find(_x('smil:seq//smil:video')) | 
					
						
							|  |  |  |         if f4m_node is None: | 
					
						
							|  |  |  |             f4m_node = body.find(_x('smil:seq/smil:video')) | 
					
						
							| 
									
										
										
										
											2014-09-21 16:08:38 +02:00
										 |  |  |         if f4m_node is not None and '.f4m' in f4m_node.attrib['src']: | 
					
						
							| 
									
										
										
										
											2014-02-25 23:57:54 +01:00
										 |  |  |             f4m_url = f4m_node.attrib['src'] | 
					
						
							|  |  |  |             if 'manifest.f4m?' not in f4m_url: | 
					
						
							|  |  |  |                 f4m_url += '?' | 
					
						
							|  |  |  |             # the parameters are from syfy.com, other sites may use others, | 
					
						
							|  |  |  |             # they also work for nbc.com | 
					
						
							|  |  |  |             f4m_url += '&g=UXWGVKRWHFSP&hdcore=3.0.3' | 
					
						
							| 
									
										
										
										
											2014-09-21 15:47:58 +02:00
										 |  |  |             formats = self._extract_f4m_formats(f4m_url, video_id) | 
					
						
							| 
									
										
										
										
											2013-12-23 16:41:35 +01:00
										 |  |  |         else: | 
					
						
							|  |  |  |             formats = [] | 
					
						
							| 
									
										
										
										
											2015-04-08 20:21:34 +08:00
										 |  |  |             switch = body.find(_x('smil:switch')) | 
					
						
							|  |  |  |             if switch is None: | 
					
						
							| 
									
										
										
										
											2015-05-01 02:51:55 +06:00
										 |  |  |                 switch = body.find(_x('smil:par//smil:switch')) | 
					
						
							|  |  |  |             if switch is None: | 
					
						
							|  |  |  |                 switch = body.find(_x('smil:par/smil:switch')) | 
					
						
							| 
									
										
										
										
											2015-04-08 21:03:11 +06:00
										 |  |  |             if switch is None: | 
					
						
							|  |  |  |                 switch = body.find(_x('smil:par')) | 
					
						
							| 
									
										
										
										
											2014-09-21 16:08:38 +02:00
										 |  |  |             if switch is not None: | 
					
						
							|  |  |  |                 base_url = head.find(_x('smil:meta')).attrib['base'] | 
					
						
							|  |  |  |                 for f in switch.findall(_x('smil:video')): | 
					
						
							|  |  |  |                     attr = f.attrib | 
					
						
							| 
									
										
										
										
											2015-04-08 22:29:10 +06:00
										 |  |  |                     width = int_or_none(attr.get('width')) | 
					
						
							|  |  |  |                     height = int_or_none(attr.get('height')) | 
					
						
							|  |  |  |                     vbr = int_or_none(attr.get('system-bitrate'), 1000) | 
					
						
							| 
									
										
										
										
											2014-09-21 16:08:38 +02:00
										 |  |  |                     format_id = '%dx%d_%dk' % (width, height, vbr) | 
					
						
							|  |  |  |                     formats.append({ | 
					
						
							|  |  |  |                         'format_id': format_id, | 
					
						
							|  |  |  |                         'url': base_url, | 
					
						
							|  |  |  |                         'play_path': 'mp4:' + attr['src'], | 
					
						
							|  |  |  |                         'ext': 'flv', | 
					
						
							|  |  |  |                         'width': width, | 
					
						
							|  |  |  |                         'height': height, | 
					
						
							|  |  |  |                         'vbr': vbr, | 
					
						
							|  |  |  |                     }) | 
					
						
							|  |  |  |             else: | 
					
						
							| 
									
										
										
										
											2015-05-01 02:51:55 +06:00
										 |  |  |                 switch = body.find(_x('smil:seq//smil:switch')) | 
					
						
							|  |  |  |                 if switch is None: | 
					
						
							|  |  |  |                     switch = body.find(_x('smil:seq/smil:switch')) | 
					
						
							| 
									
										
										
										
											2014-09-21 16:08:38 +02:00
										 |  |  |                 for f in switch.findall(_x('smil:video')): | 
					
						
							|  |  |  |                     attr = f.attrib | 
					
						
							| 
									
										
										
										
											2015-04-08 22:29:10 +06:00
										 |  |  |                     vbr = int_or_none(attr.get('system-bitrate'), 1000) | 
					
						
							| 
									
										
										
										
											2014-10-27 01:14:17 +01:00
										 |  |  |                     ext = determine_ext(attr['src']) | 
					
						
							|  |  |  |                     if ext == 'once': | 
					
						
							|  |  |  |                         ext = 'mp4' | 
					
						
							| 
									
										
										
										
											2014-09-21 16:08:38 +02:00
										 |  |  |                     formats.append({ | 
					
						
							|  |  |  |                         'format_id': compat_str(vbr), | 
					
						
							|  |  |  |                         'url': attr['src'], | 
					
						
							|  |  |  |                         'vbr': vbr, | 
					
						
							| 
									
										
										
										
											2014-10-27 01:14:17 +01:00
										 |  |  |                         'ext': ext, | 
					
						
							| 
									
										
										
										
											2014-09-21 16:08:38 +02:00
										 |  |  |                     }) | 
					
						
							| 
									
										
										
										
											2013-12-23 16:41:35 +01:00
										 |  |  |             self._sort_formats(formats) | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             'id': video_id, | 
					
						
							|  |  |  |             'title': info['title'], | 
					
						
							| 
									
										
										
										
											2014-12-17 20:20:40 +06:00
										 |  |  |             'subtitles': subtitles, | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  |             'formats': formats, | 
					
						
							|  |  |  |             'description': info['description'], | 
					
						
							|  |  |  |             'thumbnail': info['defaultThumbnailUrl'], | 
					
						
							| 
									
										
										
										
											2015-04-08 22:29:10 +06:00
										 |  |  |             'duration': int_or_none(info.get('duration'), 1000), | 
					
						
							| 
									
										
										
										
											2013-12-04 23:28:40 +01:00
										 |  |  |         } |