]>
Commit | Line | Data |
---|---|---|
ed86f38a JMF |
1 | from __future__ import unicode_literals |
2 | ||
e9bf7479 JMF |
3 | import re |
4 | import json | |
9fb2f1cd S |
5 | import time |
6 | import hmac | |
7 | import binascii | |
8 | import hashlib | |
9 | ||
e9bf7479 | 10 | |
8807f127 | 11 | from .common import InfoExtractor |
1cc79574 | 12 | from ..compat import ( |
e35cb78c | 13 | compat_str, |
1cc79574 PH |
14 | ) |
15 | from ..utils import ( | |
10e3d734 | 16 | determine_ext, |
f8b56e95 | 17 | ExtractorError, |
e9bf7479 | 18 | xpath_with_ns, |
9fb2f1cd | 19 | unsmuggle_url, |
402a3efc | 20 | int_or_none, |
e9bf7479 JMF |
21 | ) |
22 | ||
23 | _x = lambda p: xpath_with_ns(p, {'smil': 'http://www.w3.org/2005/SMIL21/Language'}) | |
24 | ||
25 | ||
8807f127 | 26 | class ThePlatformIE(InfoExtractor): |
a97bcd80 | 27 | _VALID_URL = r'''(?x) |
9fb2f1cd | 28 | (?:https?://(?:link|player)\.theplatform\.com/[sp]/(?P<provider_id>[^/]+)/ |
020cf5eb | 29 | (?P<config>(?:[^/\?]+/(?:swf|config)|onsite)/select/)? |
a97bcd80 | 30 | |theplatform:)(?P<id>[^/\?&]+)''' |
e9bf7479 | 31 | |
bd7a6478 | 32 | _TESTS = [{ |
e9bf7479 | 33 | # from http://www.metacafe.com/watch/cb-e9I_cZgTgIPd/blackberrys_big_bold_z30/ |
ed86f38a JMF |
34 | 'url': 'http://link.theplatform.com/s/dJ5BDC/e9I_cZgTgIPd/meta.smil?format=smil&Tracking=true&mbr=true', |
35 | 'info_dict': { | |
36 | 'id': 'e9I_cZgTgIPd', | |
37 | 'ext': 'flv', | |
38 | 'title': 'Blackberry\'s big, bold Z30', | |
39 | 'description': 'The Z30 is Blackberry\'s biggest, baddest mobile messaging device yet.', | |
40 | 'duration': 247, | |
e9bf7479 | 41 | }, |
ed86f38a | 42 | 'params': { |
e9bf7479 | 43 | # rtmp download |
ed86f38a | 44 | 'skip_download': True, |
e9bf7479 | 45 | }, |
bd7a6478 | 46 | }, { |
372f08c9 | 47 | # from http://www.cnet.com/videos/tesla-model-s-a-second-step-towards-a-cleaner-motoring-future/ |
bd7a6478 YCH |
48 | 'url': 'http://link.theplatform.com/s/kYEXFC/22d_qsQ6MIRT', |
49 | 'info_dict': { | |
50 | 'id': '22d_qsQ6MIRT', | |
51 | 'ext': 'flv', | |
52 | 'description': 'md5:ac330c9258c04f9d7512cf26b9595409', | |
53 | 'title': 'Tesla Model S: A second step towards a cleaner motoring future', | |
54 | }, | |
55 | 'params': { | |
56 | # rtmp download | |
57 | 'skip_download': True, | |
58 | } | |
59 | }] | |
5f6a1245 | 60 | |
9fb2f1cd S |
61 | @staticmethod |
62 | def _sign_url(url, sig_key, sig_secret, life=600, include_qs=False): | |
63 | flags = '10' if include_qs else '00' | |
64 | expiration_date = '%x' % (int(time.time()) + life) | |
65 | ||
66 | def str_to_hex(str): | |
67 | return binascii.b2a_hex(str.encode('ascii')).decode('ascii') | |
68 | ||
69 | def hex_to_str(hex): | |
70 | return binascii.a2b_hex(hex) | |
71 | ||
72 | relative_path = url.split('http://link.theplatform.com/s/')[1].split('?')[0] | |
73 | clear_text = hex_to_str(flags + expiration_date + str_to_hex(relative_path)) | |
74 | checksum = hmac.new(sig_key.encode('ascii'), clear_text, hashlib.sha1).hexdigest() | |
75 | sig = flags + expiration_date + checksum + str_to_hex(sig_secret) | |
76 | return '%s&sig=%s' % (url, sig) | |
77 | ||
10e3d734 | 78 | def _real_extract(self, url): |
9fb2f1cd S |
79 | url, smuggled_data = unsmuggle_url(url, {}) |
80 | ||
10e3d734 | 81 | mobj = re.match(self._VALID_URL, url) |
9fb2f1cd | 82 | provider_id = mobj.group('provider_id') |
10e3d734 | 83 | video_id = mobj.group('id') |
9fb2f1cd S |
84 | |
85 | if not provider_id: | |
86 | provider_id = 'dJ5BDC' | |
87 | ||
6140baf4 JMF |
88 | if smuggled_data.get('force_smil_url', False): |
89 | smil_url = url | |
90 | elif mobj.group('config'): | |
5f6a1245 | 91 | config_url = url + '&form=json' |
10e3d734 PH |
92 | config_url = config_url.replace('swf/', 'config/') |
93 | config_url = config_url.replace('onsite/', 'onsite/config/') | |
94 | config = self._download_json(config_url, video_id, 'Downloading config') | |
95 | smil_url = config['releaseUrl'] + '&format=SMIL&formats=MPEG4&manifest=f4m' | |
96 | else: | |
9fb2f1cd S |
97 | smil_url = ('http://link.theplatform.com/s/{0}/{1}/meta.smil?' |
98 | 'format=smil&mbr=true'.format(provider_id, video_id)) | |
99 | ||
100 | sig = smuggled_data.get('sig') | |
101 | if sig: | |
102 | smil_url = self._sign_url(smil_url, sig['key'], sig['secret']) | |
e9bf7479 | 103 | |
10e3d734 | 104 | meta = self._download_xml(smil_url, video_id) |
f8b56e95 PH |
105 | try: |
106 | error_msg = next( | |
107 | n.attrib['abstract'] | |
108 | for n in meta.findall(_x('.//smil:ref')) | |
a2edf2e7 | 109 | if n.attrib.get('title') == 'Geographic Restriction' or n.attrib.get('title') == 'Expired') |
f8b56e95 PH |
110 | except StopIteration: |
111 | pass | |
112 | else: | |
113 | raise ExtractorError(error_msg, expected=True) | |
114 | ||
9fb2f1cd | 115 | info_url = 'http://link.theplatform.com/s/{0}/{1}?format=preview'.format(provider_id, video_id) |
e9bf7479 JMF |
116 | info_json = self._download_webpage(info_url, video_id) |
117 | info = json.loads(info_json) | |
118 | ||
748ec667 S |
119 | subtitles = {} |
120 | captions = info.get('captions') | |
121 | if isinstance(captions, list): | |
122 | for caption in captions: | |
8807f127 JMF |
123 | lang, src, mime = caption.get('lang', 'en'), caption.get('src'), caption.get('type') |
124 | subtitles[lang] = [{ | |
125 | 'ext': 'srt' if mime == 'text/srt' else 'ttml', | |
126 | 'url': src, | |
127 | }] | |
748ec667 | 128 | |
e9bf7479 JMF |
129 | head = meta.find(_x('smil:head')) |
130 | body = meta.find(_x('smil:body')) | |
360babf7 | 131 | |
1dbd717e S |
132 | f4m_node = body.find(_x('smil:seq//smil:video')) |
133 | if f4m_node is None: | |
134 | f4m_node = body.find(_x('smil:seq/smil:video')) | |
e35cb78c | 135 | if f4m_node is not None and '.f4m' in f4m_node.attrib['src']: |
020cf5eb JMF |
136 | f4m_url = f4m_node.attrib['src'] |
137 | if 'manifest.f4m?' not in f4m_url: | |
138 | f4m_url += '?' | |
139 | # the parameters are from syfy.com, other sites may use others, | |
140 | # they also work for nbc.com | |
141 | f4m_url += '&g=UXWGVKRWHFSP&hdcore=3.0.3' | |
dd41e8c8 | 142 | formats = self._extract_f4m_formats(f4m_url, video_id) |
a97bcd80 | 143 | else: |
a97bcd80 | 144 | formats = [] |
a662163f YCH |
145 | switch = body.find(_x('smil:switch')) |
146 | if switch is None: | |
1dbd717e S |
147 | switch = body.find(_x('smil:par//smil:switch')) |
148 | if switch is None: | |
149 | switch = body.find(_x('smil:par/smil:switch')) | |
aef8fdba S |
150 | if switch is None: |
151 | switch = body.find(_x('smil:par')) | |
e35cb78c JMF |
152 | if switch is not None: |
153 | base_url = head.find(_x('smil:meta')).attrib['base'] | |
154 | for f in switch.findall(_x('smil:video')): | |
155 | attr = f.attrib | |
402a3efc S |
156 | width = int_or_none(attr.get('width')) |
157 | height = int_or_none(attr.get('height')) | |
158 | vbr = int_or_none(attr.get('system-bitrate'), 1000) | |
e35cb78c JMF |
159 | format_id = '%dx%d_%dk' % (width, height, vbr) |
160 | formats.append({ | |
161 | 'format_id': format_id, | |
162 | 'url': base_url, | |
163 | 'play_path': 'mp4:' + attr['src'], | |
164 | 'ext': 'flv', | |
165 | 'width': width, | |
166 | 'height': height, | |
167 | 'vbr': vbr, | |
168 | }) | |
169 | else: | |
1dbd717e S |
170 | switch = body.find(_x('smil:seq//smil:switch')) |
171 | if switch is None: | |
172 | switch = body.find(_x('smil:seq/smil:switch')) | |
e35cb78c JMF |
173 | for f in switch.findall(_x('smil:video')): |
174 | attr = f.attrib | |
402a3efc | 175 | vbr = int_or_none(attr.get('system-bitrate'), 1000) |
10e3d734 PH |
176 | ext = determine_ext(attr['src']) |
177 | if ext == 'once': | |
178 | ext = 'mp4' | |
e35cb78c JMF |
179 | formats.append({ |
180 | 'format_id': compat_str(vbr), | |
181 | 'url': attr['src'], | |
182 | 'vbr': vbr, | |
10e3d734 | 183 | 'ext': ext, |
e35cb78c | 184 | }) |
a97bcd80 | 185 | self._sort_formats(formats) |
e9bf7479 JMF |
186 | |
187 | return { | |
188 | 'id': video_id, | |
189 | 'title': info['title'], | |
748ec667 | 190 | 'subtitles': subtitles, |
e9bf7479 JMF |
191 | 'formats': formats, |
192 | 'description': info['description'], | |
193 | 'thumbnail': info['defaultThumbnailUrl'], | |
402a3efc | 194 | 'duration': int_or_none(info.get('duration'), 1000), |
e9bf7479 | 195 | } |