]> jfr.im git - yt-dlp.git/blame - yt_dlp/extractor/dplay.py
[VrtNU] Handle login errors (#977)
[yt-dlp.git] / yt_dlp / extractor / dplay.py
CommitLineData
940b606a 1# coding: utf-8
4cd759f7
JMF
2from __future__ import unicode_literals
3
bc2ca1bb 4import json
4cd759f7
JMF
5
6from .common import InfoExtractor
0b98f3a7 7from ..compat import compat_HTTPError
5448b781 8from ..utils import (
864a4576 9 determine_ext,
0cf2352e 10 ExtractorError,
864a4576 11 float_or_none,
5448b781 12 int_or_none,
bc2ca1bb 13 strip_or_none,
864a4576 14 unified_timestamp,
5448b781 15)
940b606a 16
4cd759f7
JMF
17
18class DPlayIE(InfoExtractor):
bc2ca1bb 19 _PATH_REGEX = r'/(?P<id>[^/]+/[^/?#]+)'
0b98f3a7
RA
20 _VALID_URL = r'''(?x)https?://
21 (?P<domain>
00dd0cd5 22 (?:www\.)?(?P<host>d
23 (?:
24 play\.(?P<country>dk|fi|jp|se|no)|
25 iscoveryplus\.(?P<plus_country>dk|es|fi|it|se|no)
26 )
27 )|
0b98f3a7 28 (?P<subdomain_country>es|it)\.dplay\.com
bc2ca1bb 29 )/[^/]+''' + _PATH_REGEX
95050537 30
940b606a 31 _TESTS = [{
5448b781 32 # non geo restricted, via secure api, unsigned download hls URL
0b98f3a7 33 'url': 'https://www.dplay.se/videos/nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101',
940b606a 34 'info_dict': {
0b98f3a7
RA
35 'id': '13628',
36 'display_id': 'nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101',
5448b781 37 'ext': 'mp4',
940b606a
S
38 'title': 'Svensken lär sig njuta av livet',
39 'description': 'md5:d3819c9bccffd0fe458ca42451dd50d8',
0b98f3a7
RA
40 'duration': 2649.856,
41 'timestamp': 1365453720,
940b606a 42 'upload_date': '20130408',
0b98f3a7 43 'creator': 'Kanal 5',
940b606a
S
44 'series': 'Nugammalt - 77 händelser som format Sverige',
45 'season_number': 1,
46 'episode_number': 1,
0b98f3a7
RA
47 },
48 'params': {
49 'format': 'bestvideo',
50 'skip_download': True,
95050537 51 },
940b606a 52 }, {
5448b781 53 # geo restricted, via secure api, unsigned download hls URL
0b98f3a7 54 'url': 'http://www.dplay.dk/videoer/ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster',
940b606a 55 'info_dict': {
0b98f3a7
RA
56 'id': '104465',
57 'display_id': 'ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster',
5448b781 58 'ext': 'mp4',
0b98f3a7
RA
59 'title': 'Ted Bundy: Mind Of A Monster',
60 'description': 'md5:8b780f6f18de4dae631668b8a9637995',
61 'duration': 5290.027,
62 'timestamp': 1570694400,
63 'upload_date': '20191010',
64 'creator': 'ID - Investigation Discovery',
65 'series': 'Ted Bundy: Mind Of A Monster',
66 'season_number': 1,
67 'episode_number': 1,
68 },
69 'params': {
70 'format': 'bestvideo',
71 'skip_download': True,
940b606a 72 },
864a4576
S
73 }, {
74 # disco-api
75 'url': 'https://www.dplay.no/videoer/i-kongens-klr/sesong-1-episode-7',
76 'info_dict': {
77 'id': '40206',
78 'display_id': 'i-kongens-klr/sesong-1-episode-7',
79 'ext': 'mp4',
80 'title': 'Episode 7',
81 'description': 'md5:e3e1411b2b9aebeea36a6ec5d50c60cf',
82 'duration': 2611.16,
83 'timestamp': 1516726800,
84 'upload_date': '20180123',
85 'series': 'I kongens klær',
86 'season_number': 1,
87 'episode_number': 7,
88 },
89 'params': {
90 'format': 'bestvideo',
91 'skip_download': True,
92 },
0b98f3a7 93 'skip': 'Available for Premium users',
a0ee342b 94 }, {
0b98f3a7
RA
95 'url': 'http://it.dplay.com/nove/biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij/',
96 'md5': '2b808ffb00fc47b884a172ca5d13053c',
97 'info_dict': {
98 'id': '6918',
99 'display_id': 'biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij',
100 'ext': 'mp4',
101 'title': 'Luigi Di Maio: la psicosi di Stanislawskij',
102 'description': 'md5:3c7a4303aef85868f867a26f5cc14813',
103 'thumbnail': r're:^https?://.*\.jpe?g',
104 'upload_date': '20160524',
105 'timestamp': 1464076800,
106 'series': 'Biografie imbarazzanti',
107 'season_number': 1,
108 'episode': 'Episode 1',
109 'episode_number': 1,
110 },
111 }, {
112 'url': 'https://es.dplay.com/dmax/la-fiebre-del-oro/temporada-8-episodio-1/',
113 'info_dict': {
114 'id': '21652',
115 'display_id': 'la-fiebre-del-oro/temporada-8-episodio-1',
116 'ext': 'mp4',
117 'title': 'Episodio 1',
118 'description': 'md5:b9dcff2071086e003737485210675f69',
119 'thumbnail': r're:^https?://.*\.png',
120 'upload_date': '20180709',
121 'timestamp': 1531173540,
122 'series': 'La fiebre del oro',
123 'season_number': 8,
124 'episode': 'Episode 1',
125 'episode_number': 1,
126 },
127 'params': {
128 'skip_download': True,
129 },
130 }, {
131 'url': 'https://www.dplay.fi/videot/shifting-gears-with-aaron-kaufman/episode-16',
a0ee342b 132 'only_matching': True,
d6b15291 133 }, {
0b98f3a7 134 'url': 'https://www.dplay.jp/video/gold-rush/24086',
d6b15291 135 'only_matching': True,
00dd0cd5 136 }, {
137 'url': 'https://www.discoveryplus.se/videos/nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101',
138 'only_matching': True,
139 }, {
140 'url': 'https://www.discoveryplus.dk/videoer/ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster',
141 'only_matching': True,
142 }, {
143 'url': 'https://www.discoveryplus.no/videoer/i-kongens-klr/sesong-1-episode-7',
144 'only_matching': True,
145 }, {
146 'url': 'https://www.discoveryplus.it/videos/biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij',
147 'only_matching': True,
148 }, {
149 'url': 'https://www.discoveryplus.es/videos/la-fiebre-del-oro/temporada-8-episodio-1',
150 'only_matching': True,
151 }, {
152 'url': 'https://www.discoveryplus.fi/videot/shifting-gears-with-aaron-kaufman/episode-16',
153 'only_matching': True,
940b606a 154 }]
4cd759f7 155
bc2ca1bb 156 def _process_errors(self, e, geo_countries):
157 info = self._parse_json(e.cause.read().decode('utf-8'), None)
158 error = info['errors'][0]
159 error_code = error.get('code')
160 if error_code == 'access.denied.geoblocked':
161 self.raise_geo_restricted(countries=geo_countries)
162 elif error_code in ('access.denied.missingpackage', 'invalid.token'):
163 raise ExtractorError(
164 'This video is only available for registered users. You may want to use --cookies.', expected=True)
165 raise ExtractorError(info['errors'][0]['detail'], expected=True)
166
167 def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
168 headers['Authorization'] = 'Bearer ' + self._download_json(
169 disco_base + 'token', display_id, 'Downloading token',
170 query={
171 'realm': realm,
172 })['data']['attributes']['token']
173
174 def _download_video_playback_info(self, disco_base, video_id, headers):
175 streaming = self._download_json(
176 disco_base + 'playback/videoPlaybackInfo/' + video_id,
177 video_id, headers=headers)['data']['attributes']['streaming']
178 streaming_list = []
179 for format_id, format_dict in streaming.items():
180 streaming_list.append({
181 'type': format_id,
182 'url': format_dict.get('url'),
183 })
184 return streaming_list
185
0b98f3a7
RA
186 def _get_disco_api_info(self, url, display_id, disco_host, realm, country):
187 geo_countries = [country.upper()]
188 self._initialize_geo_bypass({
189 'countries': geo_countries,
190 })
191 disco_base = 'https://%s/' % disco_host
aa560616
RA
192 headers = {
193 'Referer': url,
aa560616 194 }
bc2ca1bb 195 self._update_disco_api_headers(headers, disco_base, display_id, realm)
196 try:
197 video = self._download_json(
198 disco_base + 'content/videos/' + display_id, display_id,
199 headers=headers, query={
200 'fields[channel]': 'name',
201 'fields[image]': 'height,src,width',
202 'fields[show]': 'name',
203 'fields[tag]': 'name',
204 'fields[video]': 'description,episodeNumber,name,publishStart,seasonNumber,videoDuration',
205 'include': 'images,primaryChannel,show,tags'
206 })
207 except ExtractorError as e:
208 if isinstance(e.cause, compat_HTTPError) and e.cause.code == 400:
209 self._process_errors(e, geo_countries)
210 raise
aa560616
RA
211 video_id = video['data']['id']
212 info = video['data']['attributes']
0b98f3a7 213 title = info['name'].strip()
aa560616 214 formats = []
0b98f3a7 215 try:
bc2ca1bb 216 streaming = self._download_video_playback_info(
217 disco_base, video_id, headers)
0b98f3a7
RA
218 except ExtractorError as e:
219 if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
bc2ca1bb 220 self._process_errors(e, geo_countries)
0b98f3a7 221 raise
bc2ca1bb 222 for format_dict in streaming:
aa560616
RA
223 if not isinstance(format_dict, dict):
224 continue
225 format_url = format_dict.get('url')
226 if not format_url:
227 continue
bc2ca1bb 228 format_id = format_dict.get('type')
aa560616
RA
229 ext = determine_ext(format_url)
230 if format_id == 'dash' or ext == 'mpd':
231 formats.extend(self._extract_mpd_formats(
232 format_url, display_id, mpd_id='dash', fatal=False))
233 elif format_id == 'hls' or ext == 'm3u8':
234 formats.extend(self._extract_m3u8_formats(
235 format_url, display_id, 'mp4',
236 entry_protocol='m3u8_native', m3u8_id='hls',
237 fatal=False))
238 else:
239 formats.append({
240 'url': format_url,
241 'format_id': format_id,
242 })
243 self._sort_formats(formats)
244
0b98f3a7
RA
245 creator = series = None
246 tags = []
247 thumbnails = []
248 included = video.get('included') or []
249 if isinstance(included, list):
250 for e in included:
251 attributes = e.get('attributes')
252 if not attributes:
253 continue
254 e_type = e.get('type')
255 if e_type == 'channel':
256 creator = attributes.get('name')
257 elif e_type == 'image':
258 src = attributes.get('src')
259 if src:
260 thumbnails.append({
261 'url': src,
262 'width': int_or_none(attributes.get('width')),
263 'height': int_or_none(attributes.get('height')),
264 })
265 if e_type == 'show':
266 series = attributes.get('name')
267 elif e_type == 'tag':
268 name = attributes.get('name')
269 if name:
270 tags.append(name)
aa560616
RA
271
272 return {
273 'id': video_id,
274 'display_id': display_id,
275 'title': title,
bc2ca1bb 276 'description': strip_or_none(info.get('description')),
0b98f3a7 277 'duration': float_or_none(info.get('videoDuration'), 1000),
aa560616
RA
278 'timestamp': unified_timestamp(info.get('publishStart')),
279 'series': series,
280 'season_number': int_or_none(info.get('seasonNumber')),
281 'episode_number': int_or_none(info.get('episodeNumber')),
0b98f3a7
RA
282 'creator': creator,
283 'tags': tags,
284 'thumbnails': thumbnails,
aa560616
RA
285 'formats': formats,
286 }
287
4cd759f7 288 def _real_extract(self, url):
5ad28e7f 289 mobj = self._match_valid_url(url)
940b606a 290 display_id = mobj.group('id')
0b98f3a7 291 domain = mobj.group('domain').lstrip('www.')
00dd0cd5 292 country = mobj.group('country') or mobj.group('subdomain_country') or mobj.group('plus_country')
293 host = 'disco-api.' + domain if domain[0] == 'd' else 'eu2-prod.disco-api.com'
0b98f3a7
RA
294 return self._get_disco_api_info(
295 url, display_id, host, 'dplay' + country, country)
bc2ca1bb 296
297
45d1f157
S
298class HGTVDeIE(DPlayIE):
299 _VALID_URL = r'https?://de\.hgtv\.com/sendungen' + DPlayIE._PATH_REGEX
300 _TESTS = [{
301 'url': 'https://de.hgtv.com/sendungen/tiny-house-klein-aber-oho/wer-braucht-schon-eine-toilette/',
302 'info_dict': {
303 'id': '151205',
304 'display_id': 'tiny-house-klein-aber-oho/wer-braucht-schon-eine-toilette',
305 'ext': 'mp4',
306 'title': 'Wer braucht schon eine Toilette',
307 'description': 'md5:05b40a27e7aed2c9172de34d459134e2',
308 'duration': 1177.024,
309 'timestamp': 1595705400,
310 'upload_date': '20200725',
311 'creator': 'HGTV',
312 'series': 'Tiny House - klein, aber oho',
313 'season_number': 3,
314 'episode_number': 3,
315 },
316 'params': {
317 'format': 'bestvideo',
318 },
319 }]
320
321 def _real_extract(self, url):
322 display_id = self._match_id(url)
323 return self._get_disco_api_info(
324 url, display_id, 'eu1-prod.disco-api.com', 'hgtv', 'de')
325
326
bc2ca1bb 327class DiscoveryPlusIE(DPlayIE):
328 _VALID_URL = r'https?://(?:www\.)?discoveryplus\.com/video' + DPlayIE._PATH_REGEX
329 _TESTS = [{
330 'url': 'https://www.discoveryplus.com/video/property-brothers-forever-home/food-and-family',
331 'info_dict': {
332 'id': '1140794',
333 'display_id': 'property-brothers-forever-home/food-and-family',
334 'ext': 'mp4',
335 'title': 'Food and Family',
336 'description': 'The brothers help a Richmond family expand their single-level home.',
337 'duration': 2583.113,
338 'timestamp': 1609304400,
339 'upload_date': '20201230',
340 'creator': 'HGTV',
341 'series': 'Property Brothers: Forever Home',
342 'season_number': 1,
343 'episode_number': 1,
344 },
345 'skip': 'Available for Premium users',
346 }]
347
45d1f157
S
348 _PRODUCT = 'dplus_us'
349 _API_URL = 'us1-prod-direct.discoveryplus.com'
350
bc2ca1bb 351 def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
45d1f157 352 headers['x-disco-client'] = f'WEB:UNKNOWN:{self._PRODUCT}:15.0.0'
bc2ca1bb 353
354 def _download_video_playback_info(self, disco_base, video_id, headers):
355 return self._download_json(
356 disco_base + 'playback/v3/videoPlaybackInfo',
357 video_id, headers=headers, data=json.dumps({
358 'deviceInfo': {
359 'adBlocker': False,
360 },
361 'videoId': video_id,
362 'wisteriaProperties': {
363 'platform': 'desktop',
45d1f157 364 'product': self._PRODUCT,
bc2ca1bb 365 },
366 }).encode('utf-8'))['data']['attributes']['streaming']
367
368 def _real_extract(self, url):
369 display_id = self._match_id(url)
370 return self._get_disco_api_info(
45d1f157 371 url, display_id, self._API_URL, 'go', 'us')
bc2ca1bb 372
373
45d1f157
S
374class ScienceChannelIE(DiscoveryPlusIE):
375 _VALID_URL = r'https?://(?:www\.)?sciencechannel\.com/video' + DPlayIE._PATH_REGEX
bc2ca1bb 376 _TESTS = [{
45d1f157 377 'url': 'https://www.sciencechannel.com/video/strangest-things-science-atve-us/nazi-mystery-machine',
bc2ca1bb 378 'info_dict': {
45d1f157
S
379 'id': '2842849',
380 'display_id': 'strangest-things-science-atve-us/nazi-mystery-machine',
bc2ca1bb 381 'ext': 'mp4',
45d1f157
S
382 'title': 'Nazi Mystery Machine',
383 'description': 'Experts investigate the secrets of a revolutionary encryption machine.',
384 'season_number': 1,
385 'episode_number': 1,
bc2ca1bb 386 },
45d1f157 387 'skip': 'Available for Premium users',
bc2ca1bb 388 }]
389
45d1f157
S
390 _PRODUCT = 'sci'
391 _API_URL = 'us1-prod-direct.sciencechannel.com'