|
1 | 1 | # coding: utf-8
|
2 | 2 | from __future__ import unicode_literals
|
3 | 3 |
|
| 4 | +import json |
4 | 5 | import re
|
5 | 6 |
|
6 | 7 | from .common import InfoExtractor
|
@@ -151,56 +152,79 @@ class DPlayIE(InfoExtractor):
|
151 | 152 | 'only_matching': True,
|
152 | 153 | }]
|
153 | 154 |
|
| 155 | + def _process_errors(self, e, geo_countries): |
| 156 | + info = self._parse_json(e.cause.read().decode('utf-8'), None) |
| 157 | + error = info['errors'][0] |
| 158 | + error_code = error.get('code') |
| 159 | + if error_code == 'access.denied.geoblocked': |
| 160 | + self.raise_geo_restricted(countries=geo_countries) |
| 161 | + elif error_code in ('access.denied.missingpackage', 'invalid.token'): |
| 162 | + raise ExtractorError( |
| 163 | + 'This video is only available for registered users. You may want to use --cookies.', expected=True) |
| 164 | + raise ExtractorError(info['errors'][0]['detail'], expected=True) |
| 165 | + |
| 166 | + def _update_disco_api_headers(self, headers, disco_base, display_id, realm): |
| 167 | + headers['Authorization'] = 'Bearer ' + self._download_json( |
| 168 | + disco_base + 'token', display_id, 'Downloading token', |
| 169 | + query={ |
| 170 | + 'realm': realm, |
| 171 | + })['data']['attributes']['token'] |
| 172 | + |
| 173 | + def _download_video_playback_info(self, disco_base, video_id, headers): |
| 174 | + streaming = self._download_json( |
| 175 | + disco_base + 'playback/videoPlaybackInfo/' + video_id, |
| 176 | + video_id, headers=headers)['data']['attributes']['streaming'] |
| 177 | + streaming_list = [] |
| 178 | + for format_id, format_dict in streaming.items(): |
| 179 | + streaming_list.append({ |
| 180 | + 'type': format_id, |
| 181 | + 'url': format_dict.get('url'), |
| 182 | + }) |
| 183 | + return streaming_list |
| 184 | + |
154 | 185 | def _get_disco_api_info(self, url, display_id, disco_host, realm, country):
|
155 | 186 | geo_countries = [country.upper()]
|
156 | 187 | self._initialize_geo_bypass({
|
157 | 188 | 'countries': geo_countries,
|
158 | 189 | })
|
159 | 190 | disco_base = 'https://%s/' % disco_host
|
160 |
| - token = self._download_json( |
161 |
| - disco_base + 'token', display_id, 'Downloading token', |
162 |
| - query={ |
163 |
| - 'realm': realm, |
164 |
| - })['data']['attributes']['token'] |
165 | 191 | headers = {
|
166 | 192 | 'Referer': url,
|
167 |
| - 'Authorization': 'Bearer ' + token, |
168 | 193 | }
|
169 |
| - video = self._download_json( |
170 |
| - disco_base + 'content/videos/' + display_id, display_id, |
171 |
| - headers=headers, query={ |
172 |
| - 'fields[channel]': 'name', |
173 |
| - 'fields[image]': 'height,src,width', |
174 |
| - 'fields[show]': 'name', |
175 |
| - 'fields[tag]': 'name', |
176 |
| - 'fields[video]': 'description,episodeNumber,name,publishStart,seasonNumber,videoDuration', |
177 |
| - 'include': 'images,primaryChannel,show,tags' |
178 |
| - }) |
| 194 | + self._update_disco_api_headers(headers, disco_base, display_id, realm) |
| 195 | + try: |
| 196 | + video = self._download_json( |
| 197 | + disco_base + 'content/videos/' + display_id, display_id, |
| 198 | + headers=headers, query={ |
| 199 | + 'fields[channel]': 'name', |
| 200 | + 'fields[image]': 'height,src,width', |
| 201 | + 'fields[show]': 'name', |
| 202 | + 'fields[tag]': 'name', |
| 203 | + 'fields[video]': 'description,episodeNumber,name,publishStart,seasonNumber,videoDuration', |
| 204 | + 'include': 'images,primaryChannel,show,tags' |
| 205 | + }) |
| 206 | + except ExtractorError as e: |
| 207 | + if isinstance(e.cause, compat_HTTPError) and e.cause.code == 400: |
| 208 | + self._process_errors(e, geo_countries) |
| 209 | + raise |
179 | 210 | video_id = video['data']['id']
|
180 | 211 | info = video['data']['attributes']
|
181 | 212 | title = info['name'].strip()
|
182 | 213 | formats = []
|
183 | 214 | try:
|
184 |
| - streaming = self._download_json( |
185 |
| - disco_base + 'playback/videoPlaybackInfo/' + video_id, |
186 |
| - display_id, headers=headers)['data']['attributes']['streaming'] |
| 215 | + streaming = self._download_video_playback_info( |
| 216 | + disco_base, video_id, headers) |
187 | 217 | except ExtractorError as e:
|
188 | 218 | if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
|
189 |
| - info = self._parse_json(e.cause.read().decode('utf-8'), display_id) |
190 |
| - error = info['errors'][0] |
191 |
| - error_code = error.get('code') |
192 |
| - if error_code == 'access.denied.geoblocked': |
193 |
| - self.raise_geo_restricted(countries=geo_countries) |
194 |
| - elif error_code == 'access.denied.missingpackage': |
195 |
| - self.raise_login_required() |
196 |
| - raise ExtractorError(info['errors'][0]['detail'], expected=True) |
| 219 | + self._process_errors(e, geo_countries) |
197 | 220 | raise
|
198 |
| - for format_id, format_dict in streaming.items(): |
| 221 | + for format_dict in streaming: |
199 | 222 | if not isinstance(format_dict, dict):
|
200 | 223 | continue
|
201 | 224 | format_url = format_dict.get('url')
|
202 | 225 | if not format_url:
|
203 | 226 | continue
|
| 227 | + format_id = format_dict.get('type') |
204 | 228 | ext = determine_ext(format_url)
|
205 | 229 | if format_id == 'dash' or ext == 'mpd':
|
206 | 230 | formats.extend(self._extract_mpd_formats(
|
@@ -268,3 +292,46 @@ def _real_extract(self, url):
|
268 | 292 | host = 'disco-api.' + domain if domain[0] == 'd' else 'eu2-prod.disco-api.com'
|
269 | 293 | return self._get_disco_api_info(
|
270 | 294 | url, display_id, host, 'dplay' + country, country)
|
| 295 | + |
| 296 | + |
| 297 | +class DiscoveryPlusIE(DPlayIE): |
| 298 | + _VALID_URL = r'https?://(?:www\.)?discoveryplus\.com/video/(?P<id>[^/]+/[^/]+)' |
| 299 | + _TESTS = [{ |
| 300 | + 'url': 'https://www.discoveryplus.com/video/property-brothers-forever-home/food-and-family', |
| 301 | + 'info_dict': { |
| 302 | + 'id': '1140794', |
| 303 | + 'display_id': 'property-brothers-forever-home/food-and-family', |
| 304 | + 'ext': 'mp4', |
| 305 | + 'title': 'Food and Family', |
| 306 | + 'description': 'The brothers help a Richmond family expand their single-level home.', |
| 307 | + 'duration': 2583.113, |
| 308 | + 'timestamp': 1609304400, |
| 309 | + 'upload_date': '20201230', |
| 310 | + 'creator': 'HGTV', |
| 311 | + 'series': 'Property Brothers: Forever Home', |
| 312 | + 'season_number': 1, |
| 313 | + 'episode_number': 1, |
| 314 | + }, |
| 315 | + 'skip': 'Available for Premium users', |
| 316 | + }] |
| 317 | + |
| 318 | + def _update_disco_api_headers(self, headers, disco_base, display_id, realm): |
| 319 | + headers['x-disco-client'] = 'WEB:UNKNOWN:dplus_us:15.0.0' |
| 320 | + |
| 321 | + def _download_video_playback_info(self, disco_base, video_id, headers): |
| 322 | + return self._download_json( |
| 323 | + disco_base + 'playback/v3/videoPlaybackInfo', |
| 324 | + video_id, headers=headers, data=json.dumps({ |
| 325 | + 'deviceInfo': { |
| 326 | + 'adBlocker': False, |
| 327 | + }, |
| 328 | + 'videoId': video_id, |
| 329 | + 'wisteriaProperties': { |
| 330 | + 'platform': 'desktop', |
| 331 | + }, |
| 332 | + }).encode('utf-8'))['data']['attributes']['streaming'] |
| 333 | + |
| 334 | + def _real_extract(self, url): |
| 335 | + display_id = self._match_id(url) |
| 336 | + return self._get_disco_api_info( |
| 337 | + url, display_id, 'us1-prod-direct.discoveryplus.com', 'go', 'us') |
0 commit comments