Skip to content

Commit a7356df

Browse files
committed
[dplay] Add support for discoveryplus.com (closes ytdl-org#24698)
1 parent e20ec43 commit a7356df

File tree

2 files changed

+99
-29
lines changed

2 files changed

+99
-29
lines changed

youtube_dl/extractor/dplay.py

Lines changed: 95 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# coding: utf-8
22
from __future__ import unicode_literals
33

4+
import json
45
import re
56

67
from .common import InfoExtractor
@@ -151,56 +152,79 @@ class DPlayIE(InfoExtractor):
151152
'only_matching': True,
152153
}]
153154

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+
154185
def _get_disco_api_info(self, url, display_id, disco_host, realm, country):
155186
geo_countries = [country.upper()]
156187
self._initialize_geo_bypass({
157188
'countries': geo_countries,
158189
})
159190
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']
165191
headers = {
166192
'Referer': url,
167-
'Authorization': 'Bearer ' + token,
168193
}
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
179210
video_id = video['data']['id']
180211
info = video['data']['attributes']
181212
title = info['name'].strip()
182213
formats = []
183214
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)
187217
except ExtractorError as e:
188218
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)
197220
raise
198-
for format_id, format_dict in streaming.items():
221+
for format_dict in streaming:
199222
if not isinstance(format_dict, dict):
200223
continue
201224
format_url = format_dict.get('url')
202225
if not format_url:
203226
continue
227+
format_id = format_dict.get('type')
204228
ext = determine_ext(format_url)
205229
if format_id == 'dash' or ext == 'mpd':
206230
formats.extend(self._extract_mpd_formats(
@@ -268,3 +292,46 @@ def _real_extract(self, url):
268292
host = 'disco-api.' + domain if domain[0] == 'd' else 'eu2-prod.disco-api.com'
269293
return self._get_disco_api_info(
270294
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')

youtube_dl/extractor/extractors.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,10 @@
288288
DouyuShowIE,
289289
DouyuTVIE,
290290
)
291-
from .dplay import DPlayIE
291+
from .dplay import (
292+
DPlayIE,
293+
DiscoveryPlusIE,
294+
)
292295
from .dreisat import DreiSatIE
293296
from .drbonanza import DRBonanzaIE
294297
from .drtuber import DrTuberIE

0 commit comments

Comments
 (0)