]>
Commit | Line | Data |
---|---|---|
940b606a | 1 | # coding: utf-8 |
4cd759f7 JMF |
2 | from __future__ import unicode_literals |
3 | ||
bc2ca1bb | 4 | import json |
5118d2ec | 5 | import uuid |
4cd759f7 JMF |
6 | |
7 | from .common import InfoExtractor | |
0b98f3a7 | 8 | from ..compat import compat_HTTPError |
5448b781 | 9 | from ..utils import ( |
864a4576 | 10 | determine_ext, |
0cf2352e | 11 | ExtractorError, |
864a4576 | 12 | float_or_none, |
5448b781 | 13 | int_or_none, |
bc2ca1bb | 14 | strip_or_none, |
5118d2ec | 15 | try_get, |
864a4576 | 16 | unified_timestamp, |
5448b781 | 17 | ) |
940b606a | 18 | |
4cd759f7 | 19 | |
5118d2ec | 20 | class DPlayBaseIE(InfoExtractor): |
bc2ca1bb | 21 | _PATH_REGEX = r'/(?P<id>[^/]+/[^/?#]+)' |
5118d2ec AG |
22 | _auth_token_cache = {} |
23 | ||
24 | def _get_auth(self, disco_base, display_id, realm, needs_device_id=True): | |
25 | key = (disco_base, realm) | |
26 | st = self._get_cookies(disco_base).get('st') | |
27 | token = (st and st.value) or self._auth_token_cache.get(key) | |
28 | ||
29 | if not token: | |
30 | query = {'realm': realm} | |
31 | if needs_device_id: | |
32 | query['deviceId'] = uuid.uuid4().hex | |
33 | token = self._download_json( | |
34 | disco_base + 'token', display_id, 'Downloading token', | |
35 | query=query)['data']['attributes']['token'] | |
36 | ||
37 | # Save cache only if cookies are not being set | |
38 | if not self._get_cookies(disco_base).get('st'): | |
39 | self._auth_token_cache[key] = token | |
40 | ||
41 | return f'Bearer {token}' | |
42 | ||
43 | def _process_errors(self, e, geo_countries): | |
44 | info = self._parse_json(e.cause.read().decode('utf-8'), None) | |
45 | error = info['errors'][0] | |
46 | error_code = error.get('code') | |
47 | if error_code == 'access.denied.geoblocked': | |
48 | self.raise_geo_restricted(countries=geo_countries) | |
49 | elif error_code in ('access.denied.missingpackage', 'invalid.token'): | |
50 | raise ExtractorError( | |
51 | 'This video is only available for registered users. You may want to use --cookies.', expected=True) | |
52 | raise ExtractorError(info['errors'][0]['detail'], expected=True) | |
53 | ||
54 | def _update_disco_api_headers(self, headers, disco_base, display_id, realm): | |
55 | headers['Authorization'] = self._get_auth(disco_base, display_id, realm, False) | |
56 | ||
57 | def _download_video_playback_info(self, disco_base, video_id, headers): | |
58 | streaming = self._download_json( | |
59 | disco_base + 'playback/videoPlaybackInfo/' + video_id, | |
60 | video_id, headers=headers)['data']['attributes']['streaming'] | |
61 | streaming_list = [] | |
62 | for format_id, format_dict in streaming.items(): | |
63 | streaming_list.append({ | |
64 | 'type': format_id, | |
65 | 'url': format_dict.get('url'), | |
66 | }) | |
67 | return streaming_list | |
68 | ||
69 | def _get_disco_api_info(self, url, display_id, disco_host, realm, country, domain=''): | |
70 | geo_countries = [country.upper()] | |
71 | self._initialize_geo_bypass({ | |
72 | 'countries': geo_countries, | |
73 | }) | |
74 | disco_base = 'https://%s/' % disco_host | |
75 | headers = { | |
76 | 'Referer': url, | |
77 | } | |
78 | self._update_disco_api_headers(headers, disco_base, display_id, realm) | |
79 | try: | |
80 | video = self._download_json( | |
81 | disco_base + 'content/videos/' + display_id, display_id, | |
82 | headers=headers, query={ | |
83 | 'fields[channel]': 'name', | |
84 | 'fields[image]': 'height,src,width', | |
85 | 'fields[show]': 'name', | |
86 | 'fields[tag]': 'name', | |
87 | 'fields[video]': 'description,episodeNumber,name,publishStart,seasonNumber,videoDuration', | |
88 | 'include': 'images,primaryChannel,show,tags' | |
89 | }) | |
90 | except ExtractorError as e: | |
91 | if isinstance(e.cause, compat_HTTPError) and e.cause.code == 400: | |
92 | self._process_errors(e, geo_countries) | |
93 | raise | |
94 | video_id = video['data']['id'] | |
95 | info = video['data']['attributes'] | |
96 | title = info['name'].strip() | |
97 | formats = [] | |
98 | subtitles = {} | |
99 | try: | |
100 | streaming = self._download_video_playback_info( | |
101 | disco_base, video_id, headers) | |
102 | except ExtractorError as e: | |
103 | if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403: | |
104 | self._process_errors(e, geo_countries) | |
105 | raise | |
106 | for format_dict in streaming: | |
107 | if not isinstance(format_dict, dict): | |
108 | continue | |
109 | format_url = format_dict.get('url') | |
110 | if not format_url: | |
111 | continue | |
112 | format_id = format_dict.get('type') | |
113 | ext = determine_ext(format_url) | |
114 | if format_id == 'dash' or ext == 'mpd': | |
115 | dash_fmts, dash_subs = self._extract_mpd_formats_and_subtitles( | |
116 | format_url, display_id, mpd_id='dash', fatal=False) | |
117 | formats.extend(dash_fmts) | |
118 | subtitles = self._merge_subtitles(subtitles, dash_subs) | |
119 | elif format_id == 'hls' or ext == 'm3u8': | |
120 | m3u8_fmts, m3u8_subs = self._extract_m3u8_formats_and_subtitles( | |
121 | format_url, display_id, 'mp4', | |
122 | entry_protocol='m3u8_native', m3u8_id='hls', | |
123 | fatal=False) | |
124 | formats.extend(m3u8_fmts) | |
125 | subtitles = self._merge_subtitles(subtitles, m3u8_subs) | |
126 | else: | |
127 | formats.append({ | |
128 | 'url': format_url, | |
129 | 'format_id': format_id, | |
130 | }) | |
131 | self._sort_formats(formats) | |
132 | ||
133 | creator = series = None | |
134 | tags = [] | |
135 | thumbnails = [] | |
136 | included = video.get('included') or [] | |
137 | if isinstance(included, list): | |
138 | for e in included: | |
139 | attributes = e.get('attributes') | |
140 | if not attributes: | |
141 | continue | |
142 | e_type = e.get('type') | |
143 | if e_type == 'channel': | |
144 | creator = attributes.get('name') | |
145 | elif e_type == 'image': | |
146 | src = attributes.get('src') | |
147 | if src: | |
148 | thumbnails.append({ | |
149 | 'url': src, | |
150 | 'width': int_or_none(attributes.get('width')), | |
151 | 'height': int_or_none(attributes.get('height')), | |
152 | }) | |
153 | if e_type == 'show': | |
154 | series = attributes.get('name') | |
155 | elif e_type == 'tag': | |
156 | name = attributes.get('name') | |
157 | if name: | |
158 | tags.append(name) | |
159 | return { | |
160 | 'id': video_id, | |
161 | 'display_id': display_id, | |
162 | 'title': title, | |
163 | 'description': strip_or_none(info.get('description')), | |
164 | 'duration': float_or_none(info.get('videoDuration'), 1000), | |
165 | 'timestamp': unified_timestamp(info.get('publishStart')), | |
166 | 'series': series, | |
167 | 'season_number': int_or_none(info.get('seasonNumber')), | |
168 | 'episode_number': int_or_none(info.get('episodeNumber')), | |
169 | 'creator': creator, | |
170 | 'tags': tags, | |
171 | 'thumbnails': thumbnails, | |
172 | 'formats': formats, | |
173 | 'subtitles': subtitles, | |
174 | 'http_headers': { | |
175 | 'referer': domain, | |
176 | }, | |
177 | } | |
178 | ||
179 | ||
180 | class DPlayIE(DPlayBaseIE): | |
0b98f3a7 RA |
181 | _VALID_URL = r'''(?x)https?:// |
182 | (?P<domain> | |
00dd0cd5 | 183 | (?:www\.)?(?P<host>d |
184 | (?: | |
185 | play\.(?P<country>dk|fi|jp|se|no)| | |
186 | iscoveryplus\.(?P<plus_country>dk|es|fi|it|se|no) | |
187 | ) | |
188 | )| | |
0b98f3a7 | 189 | (?P<subdomain_country>es|it)\.dplay\.com |
5118d2ec | 190 | )/[^/]+''' + DPlayBaseIE._PATH_REGEX |
95050537 | 191 | |
940b606a | 192 | _TESTS = [{ |
5448b781 | 193 | # non geo restricted, via secure api, unsigned download hls URL |
0b98f3a7 | 194 | 'url': 'https://www.dplay.se/videos/nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101', |
940b606a | 195 | 'info_dict': { |
0b98f3a7 RA |
196 | 'id': '13628', |
197 | 'display_id': 'nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101', | |
5448b781 | 198 | 'ext': 'mp4', |
940b606a S |
199 | 'title': 'Svensken lär sig njuta av livet', |
200 | 'description': 'md5:d3819c9bccffd0fe458ca42451dd50d8', | |
0b98f3a7 RA |
201 | 'duration': 2649.856, |
202 | 'timestamp': 1365453720, | |
940b606a | 203 | 'upload_date': '20130408', |
0b98f3a7 | 204 | 'creator': 'Kanal 5', |
940b606a S |
205 | 'series': 'Nugammalt - 77 händelser som format Sverige', |
206 | 'season_number': 1, | |
207 | 'episode_number': 1, | |
0b98f3a7 RA |
208 | }, |
209 | 'params': { | |
0b98f3a7 | 210 | 'skip_download': True, |
95050537 | 211 | }, |
940b606a | 212 | }, { |
5448b781 | 213 | # geo restricted, via secure api, unsigned download hls URL |
0b98f3a7 | 214 | 'url': 'http://www.dplay.dk/videoer/ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster', |
940b606a | 215 | 'info_dict': { |
0b98f3a7 RA |
216 | 'id': '104465', |
217 | 'display_id': 'ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster', | |
5448b781 | 218 | 'ext': 'mp4', |
0b98f3a7 RA |
219 | 'title': 'Ted Bundy: Mind Of A Monster', |
220 | 'description': 'md5:8b780f6f18de4dae631668b8a9637995', | |
221 | 'duration': 5290.027, | |
222 | 'timestamp': 1570694400, | |
223 | 'upload_date': '20191010', | |
224 | 'creator': 'ID - Investigation Discovery', | |
225 | 'series': 'Ted Bundy: Mind Of A Monster', | |
226 | 'season_number': 1, | |
227 | 'episode_number': 1, | |
228 | }, | |
229 | 'params': { | |
0b98f3a7 | 230 | 'skip_download': True, |
940b606a | 231 | }, |
864a4576 S |
232 | }, { |
233 | # disco-api | |
234 | 'url': 'https://www.dplay.no/videoer/i-kongens-klr/sesong-1-episode-7', | |
235 | 'info_dict': { | |
236 | 'id': '40206', | |
237 | 'display_id': 'i-kongens-klr/sesong-1-episode-7', | |
238 | 'ext': 'mp4', | |
239 | 'title': 'Episode 7', | |
240 | 'description': 'md5:e3e1411b2b9aebeea36a6ec5d50c60cf', | |
241 | 'duration': 2611.16, | |
242 | 'timestamp': 1516726800, | |
243 | 'upload_date': '20180123', | |
244 | 'series': 'I kongens klær', | |
245 | 'season_number': 1, | |
246 | 'episode_number': 7, | |
247 | }, | |
248 | 'params': { | |
864a4576 S |
249 | 'skip_download': True, |
250 | }, | |
0b98f3a7 | 251 | 'skip': 'Available for Premium users', |
a0ee342b | 252 | }, { |
0b98f3a7 RA |
253 | 'url': 'http://it.dplay.com/nove/biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij/', |
254 | 'md5': '2b808ffb00fc47b884a172ca5d13053c', | |
255 | 'info_dict': { | |
256 | 'id': '6918', | |
257 | 'display_id': 'biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij', | |
258 | 'ext': 'mp4', | |
259 | 'title': 'Luigi Di Maio: la psicosi di Stanislawskij', | |
260 | 'description': 'md5:3c7a4303aef85868f867a26f5cc14813', | |
261 | 'thumbnail': r're:^https?://.*\.jpe?g', | |
262 | 'upload_date': '20160524', | |
263 | 'timestamp': 1464076800, | |
264 | 'series': 'Biografie imbarazzanti', | |
265 | 'season_number': 1, | |
266 | 'episode': 'Episode 1', | |
267 | 'episode_number': 1, | |
268 | }, | |
269 | }, { | |
270 | 'url': 'https://es.dplay.com/dmax/la-fiebre-del-oro/temporada-8-episodio-1/', | |
271 | 'info_dict': { | |
272 | 'id': '21652', | |
273 | 'display_id': 'la-fiebre-del-oro/temporada-8-episodio-1', | |
274 | 'ext': 'mp4', | |
275 | 'title': 'Episodio 1', | |
276 | 'description': 'md5:b9dcff2071086e003737485210675f69', | |
277 | 'thumbnail': r're:^https?://.*\.png', | |
278 | 'upload_date': '20180709', | |
279 | 'timestamp': 1531173540, | |
280 | 'series': 'La fiebre del oro', | |
281 | 'season_number': 8, | |
282 | 'episode': 'Episode 1', | |
283 | 'episode_number': 1, | |
284 | }, | |
285 | 'params': { | |
286 | 'skip_download': True, | |
287 | }, | |
288 | }, { | |
289 | 'url': 'https://www.dplay.fi/videot/shifting-gears-with-aaron-kaufman/episode-16', | |
a0ee342b | 290 | 'only_matching': True, |
d6b15291 | 291 | }, { |
0b98f3a7 | 292 | 'url': 'https://www.dplay.jp/video/gold-rush/24086', |
d6b15291 | 293 | 'only_matching': True, |
00dd0cd5 | 294 | }, { |
295 | 'url': 'https://www.discoveryplus.se/videos/nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101', | |
296 | 'only_matching': True, | |
297 | }, { | |
298 | 'url': 'https://www.discoveryplus.dk/videoer/ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster', | |
299 | 'only_matching': True, | |
300 | }, { | |
301 | 'url': 'https://www.discoveryplus.no/videoer/i-kongens-klr/sesong-1-episode-7', | |
302 | 'only_matching': True, | |
303 | }, { | |
304 | 'url': 'https://www.discoveryplus.it/videos/biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij', | |
305 | 'only_matching': True, | |
306 | }, { | |
307 | 'url': 'https://www.discoveryplus.es/videos/la-fiebre-del-oro/temporada-8-episodio-1', | |
308 | 'only_matching': True, | |
309 | }, { | |
310 | 'url': 'https://www.discoveryplus.fi/videot/shifting-gears-with-aaron-kaufman/episode-16', | |
311 | 'only_matching': True, | |
940b606a | 312 | }] |
4cd759f7 JMF |
313 | |
314 | def _real_extract(self, url): | |
5ad28e7f | 315 | mobj = self._match_valid_url(url) |
940b606a | 316 | display_id = mobj.group('id') |
0b98f3a7 | 317 | domain = mobj.group('domain').lstrip('www.') |
00dd0cd5 | 318 | country = mobj.group('country') or mobj.group('subdomain_country') or mobj.group('plus_country') |
319 | host = 'disco-api.' + domain if domain[0] == 'd' else 'eu2-prod.disco-api.com' | |
0b98f3a7 | 320 | return self._get_disco_api_info( |
5118d2ec | 321 | url, display_id, host, 'dplay' + country, country, domain) |
bc2ca1bb | 322 | |
323 | ||
5118d2ec AG |
324 | class HGTVDeIE(DPlayBaseIE): |
325 | _VALID_URL = r'https?://de\.hgtv\.com/sendungen' + DPlayBaseIE._PATH_REGEX | |
45d1f157 S |
326 | _TESTS = [{ |
327 | 'url': 'https://de.hgtv.com/sendungen/tiny-house-klein-aber-oho/wer-braucht-schon-eine-toilette/', | |
328 | 'info_dict': { | |
329 | 'id': '151205', | |
330 | 'display_id': 'tiny-house-klein-aber-oho/wer-braucht-schon-eine-toilette', | |
331 | 'ext': 'mp4', | |
332 | 'title': 'Wer braucht schon eine Toilette', | |
333 | 'description': 'md5:05b40a27e7aed2c9172de34d459134e2', | |
334 | 'duration': 1177.024, | |
335 | 'timestamp': 1595705400, | |
336 | 'upload_date': '20200725', | |
337 | 'creator': 'HGTV', | |
338 | 'series': 'Tiny House - klein, aber oho', | |
339 | 'season_number': 3, | |
340 | 'episode_number': 3, | |
341 | }, | |
45d1f157 S |
342 | }] |
343 | ||
344 | def _real_extract(self, url): | |
345 | display_id = self._match_id(url) | |
346 | return self._get_disco_api_info( | |
347 | url, display_id, 'eu1-prod.disco-api.com', 'hgtv', 'de') | |
348 | ||
349 | ||
5118d2ec AG |
350 | class DiscoveryPlusIE(DPlayBaseIE): |
351 | _VALID_URL = r'https?://(?:www\.)?discoveryplus\.com/(?:\w{2}/)?video' + DPlayBaseIE._PATH_REGEX | |
bc2ca1bb | 352 | _TESTS = [{ |
353 | 'url': 'https://www.discoveryplus.com/video/property-brothers-forever-home/food-and-family', | |
354 | 'info_dict': { | |
355 | 'id': '1140794', | |
356 | 'display_id': 'property-brothers-forever-home/food-and-family', | |
357 | 'ext': 'mp4', | |
358 | 'title': 'Food and Family', | |
359 | 'description': 'The brothers help a Richmond family expand their single-level home.', | |
360 | 'duration': 2583.113, | |
361 | 'timestamp': 1609304400, | |
362 | 'upload_date': '20201230', | |
363 | 'creator': 'HGTV', | |
364 | 'series': 'Property Brothers: Forever Home', | |
365 | 'season_number': 1, | |
366 | 'episode_number': 1, | |
367 | }, | |
368 | 'skip': 'Available for Premium users', | |
7e59ca44 | 369 | }, { |
370 | 'url': 'https://discoveryplus.com/ca/video/bering-sea-gold-discovery-ca/goldslingers', | |
371 | 'only_matching': True, | |
bc2ca1bb | 372 | }] |
373 | ||
45d1f157 S |
374 | _PRODUCT = 'dplus_us' |
375 | _API_URL = 'us1-prod-direct.discoveryplus.com' | |
376 | ||
bc2ca1bb | 377 | def _update_disco_api_headers(self, headers, disco_base, display_id, realm): |
c12977bd | 378 | headers['x-disco-client'] = f'WEB:UNKNOWN:{self._PRODUCT}:25.2.6' |
bc2ca1bb | 379 | |
380 | def _download_video_playback_info(self, disco_base, video_id, headers): | |
381 | return self._download_json( | |
382 | disco_base + 'playback/v3/videoPlaybackInfo', | |
383 | video_id, headers=headers, data=json.dumps({ | |
384 | 'deviceInfo': { | |
385 | 'adBlocker': False, | |
386 | }, | |
387 | 'videoId': video_id, | |
388 | 'wisteriaProperties': { | |
389 | 'platform': 'desktop', | |
45d1f157 | 390 | 'product': self._PRODUCT, |
bc2ca1bb | 391 | }, |
392 | }).encode('utf-8'))['data']['attributes']['streaming'] | |
393 | ||
394 | def _real_extract(self, url): | |
395 | display_id = self._match_id(url) | |
396 | return self._get_disco_api_info( | |
45d1f157 | 397 | url, display_id, self._API_URL, 'go', 'us') |
bc2ca1bb | 398 | |
399 | ||
45d1f157 | 400 | class ScienceChannelIE(DiscoveryPlusIE): |
5118d2ec | 401 | _VALID_URL = r'https?://(?:www\.)?sciencechannel\.com/video' + DPlayBaseIE._PATH_REGEX |
bc2ca1bb | 402 | _TESTS = [{ |
45d1f157 | 403 | 'url': 'https://www.sciencechannel.com/video/strangest-things-science-atve-us/nazi-mystery-machine', |
bc2ca1bb | 404 | 'info_dict': { |
45d1f157 S |
405 | 'id': '2842849', |
406 | 'display_id': 'strangest-things-science-atve-us/nazi-mystery-machine', | |
bc2ca1bb | 407 | 'ext': 'mp4', |
45d1f157 S |
408 | 'title': 'Nazi Mystery Machine', |
409 | 'description': 'Experts investigate the secrets of a revolutionary encryption machine.', | |
410 | 'season_number': 1, | |
411 | 'episode_number': 1, | |
bc2ca1bb | 412 | }, |
45d1f157 | 413 | 'skip': 'Available for Premium users', |
bc2ca1bb | 414 | }] |
415 | ||
45d1f157 S |
416 | _PRODUCT = 'sci' |
417 | _API_URL = 'us1-prod-direct.sciencechannel.com' | |
b5a39ed4 S |
418 | |
419 | ||
420 | class DIYNetworkIE(DiscoveryPlusIE): | |
5118d2ec | 421 | _VALID_URL = r'https?://(?:watch\.)?diynetwork\.com/video' + DPlayBaseIE._PATH_REGEX |
b5a39ed4 S |
422 | _TESTS = [{ |
423 | 'url': 'https://watch.diynetwork.com/video/pool-kings-diy-network/bringing-beach-life-to-texas', | |
424 | 'info_dict': { | |
425 | 'id': '2309730', | |
426 | 'display_id': 'pool-kings-diy-network/bringing-beach-life-to-texas', | |
427 | 'ext': 'mp4', | |
428 | 'title': 'Bringing Beach Life to Texas', | |
429 | 'description': 'The Pool Kings give a family a day at the beach in their own backyard.', | |
430 | 'season_number': 10, | |
431 | 'episode_number': 2, | |
432 | }, | |
433 | 'skip': 'Available for Premium users', | |
434 | }] | |
435 | ||
436 | _PRODUCT = 'diy' | |
437 | _API_URL = 'us1-prod-direct.watch.diynetwork.com' | |
c12977bd S |
438 | |
439 | ||
440 | class AnimalPlanetIE(DiscoveryPlusIE): | |
5118d2ec | 441 | _VALID_URL = r'https?://(?:www\.)?animalplanet\.com/video' + DPlayBaseIE._PATH_REGEX |
c12977bd S |
442 | _TESTS = [{ |
443 | 'url': 'https://www.animalplanet.com/video/north-woods-law-animal-planet/squirrel-showdown', | |
444 | 'info_dict': { | |
445 | 'id': '3338923', | |
446 | 'display_id': 'north-woods-law-animal-planet/squirrel-showdown', | |
447 | 'ext': 'mp4', | |
448 | 'title': 'Squirrel Showdown', | |
449 | 'description': 'A woman is suspected of being in possession of flying squirrel kits.', | |
450 | 'season_number': 16, | |
451 | 'episode_number': 11, | |
452 | }, | |
453 | 'skip': 'Available for Premium users', | |
454 | }] | |
455 | ||
456 | _PRODUCT = 'apl' | |
457 | _API_URL = 'us1-prod-direct.animalplanet.com' | |
5118d2ec AG |
458 | |
459 | ||
460 | class DiscoveryPlusIndiaIE(DPlayBaseIE): | |
461 | _VALID_URL = r'https?://(?:www\.)?discoveryplus\.in/videos?' + DPlayBaseIE._PATH_REGEX | |
462 | _TESTS = [{ | |
463 | 'url': 'https://www.discoveryplus.in/videos/how-do-they-do-it/fugu-and-more?seasonId=8&type=EPISODE', | |
464 | 'info_dict': { | |
465 | 'id': '27104', | |
466 | 'ext': 'mp4', | |
467 | 'display_id': 'how-do-they-do-it/fugu-and-more', | |
468 | 'title': 'Fugu and More', | |
469 | 'description': 'The Japanese catch, prepare and eat the deadliest fish on the planet.', | |
470 | 'duration': 1319, | |
471 | 'timestamp': 1582309800, | |
472 | 'upload_date': '20200221', | |
473 | 'series': 'How Do They Do It?', | |
474 | 'season_number': 8, | |
475 | 'episode_number': 2, | |
476 | 'creator': 'Discovery Channel', | |
477 | }, | |
478 | 'params': { | |
479 | 'skip_download': True, | |
480 | } | |
481 | }] | |
482 | ||
483 | def _update_disco_api_headers(self, headers, disco_base, display_id, realm): | |
484 | headers.update({ | |
485 | 'x-disco-params': 'realm=%s' % realm, | |
486 | 'x-disco-client': 'WEB:UNKNOWN:dplus-india:17.0.0', | |
487 | 'Authorization': self._get_auth(disco_base, display_id, realm), | |
488 | }) | |
489 | ||
490 | def _download_video_playback_info(self, disco_base, video_id, headers): | |
491 | return self._download_json( | |
492 | disco_base + 'playback/v3/videoPlaybackInfo', | |
493 | video_id, headers=headers, data=json.dumps({ | |
494 | 'deviceInfo': { | |
495 | 'adBlocker': False, | |
496 | }, | |
497 | 'videoId': video_id, | |
498 | }).encode('utf-8'))['data']['attributes']['streaming'] | |
499 | ||
500 | def _real_extract(self, url): | |
501 | display_id = self._match_id(url) | |
502 | return self._get_disco_api_info( | |
503 | url, display_id, 'ap2-prod-direct.discoveryplus.in', 'dplusindia', 'in', 'https://www.discoveryplus.in/') | |
504 | ||
505 | ||
506 | class DiscoveryNetworksDeIE(DPlayBaseIE): | |
507 | _VALID_URL = r'https?://(?:www\.)?(?P<domain>(?:tlc|dmax)\.de|dplay\.co\.uk)/(?:programme|show|sendungen)/(?P<programme>[^/]+)/(?:video/)?(?P<alternate_id>[^/]+)' | |
508 | ||
509 | _TESTS = [{ | |
510 | 'url': 'https://www.tlc.de/programme/breaking-amish/video/die-welt-da-drauen/DCB331270001100', | |
511 | 'info_dict': { | |
512 | 'id': '78867', | |
513 | 'ext': 'mp4', | |
514 | 'title': 'Die Welt da draußen', | |
515 | 'description': 'md5:61033c12b73286e409d99a41742ef608', | |
516 | 'timestamp': 1554069600, | |
517 | 'upload_date': '20190331', | |
518 | }, | |
519 | 'params': { | |
520 | 'skip_download': True, | |
521 | }, | |
522 | }, { | |
523 | 'url': 'https://www.dmax.de/programme/dmax-highlights/video/tuning-star-sidney-hoffmann-exklusiv-bei-dmax/191023082312316', | |
524 | 'only_matching': True, | |
525 | }, { | |
526 | 'url': 'https://www.dplay.co.uk/show/ghost-adventures/video/hotel-leger-103620/EHD_280313B', | |
527 | 'only_matching': True, | |
528 | }, { | |
529 | 'url': 'https://tlc.de/sendungen/breaking-amish/die-welt-da-drauen/', | |
530 | 'only_matching': True, | |
531 | }] | |
532 | ||
533 | def _real_extract(self, url): | |
534 | domain, programme, alternate_id = self._match_valid_url(url).groups() | |
535 | country = 'GB' if domain == 'dplay.co.uk' else 'DE' | |
536 | realm = 'questuk' if country == 'GB' else domain.replace('.', '') | |
537 | return self._get_disco_api_info( | |
538 | url, '%s/%s' % (programme, alternate_id), | |
539 | 'sonic-eu1-prod.disco-api.com', realm, country) | |
540 | ||
541 | ||
542 | class DiscoveryPlusShowBaseIE(DPlayBaseIE): | |
543 | ||
544 | def _entries(self, show_name): | |
545 | headers = { | |
546 | 'x-disco-client': self._X_CLIENT, | |
547 | 'x-disco-params': f'realm={self._REALM}', | |
548 | 'referer': self._DOMAIN, | |
549 | 'Authentication': self._get_auth(self._BASE_API, None, self._REALM), | |
550 | } | |
551 | show_json = self._download_json( | |
552 | f'{self._BASE_API}cms/routes/{self._SHOW_STR}/{show_name}?include=default', | |
553 | video_id=show_name, headers=headers)['included'][self._INDEX]['attributes']['component'] | |
554 | show_id = show_json['mandatoryParams'].split('=')[-1] | |
555 | season_url = self._BASE_API + 'content/videos?sort=episodeNumber&filter[seasonNumber]={}&filter[show.id]={}&page[size]=100&page[number]={}' | |
556 | for season in show_json['filters'][0]['options']: | |
557 | season_id = season['id'] | |
558 | total_pages, page_num = 1, 0 | |
559 | while page_num < total_pages: | |
560 | season_json = self._download_json( | |
561 | season_url.format(season_id, show_id, str(page_num + 1)), show_name, headers=headers, | |
562 | note='Downloading season %s JSON metadata%s' % (season_id, ' page %d' % page_num if page_num else '')) | |
563 | if page_num == 0: | |
564 | total_pages = try_get(season_json, lambda x: x['meta']['totalPages'], int) or 1 | |
565 | episodes_json = season_json['data'] | |
566 | for episode in episodes_json: | |
86f3d52f | 567 | video_path = episode['attributes']['path'] |
5118d2ec | 568 | yield self.url_result( |
86f3d52f AG |
569 | '%svideos/%s' % (self._DOMAIN, video_path), |
570 | ie=self._VIDEO_IE.ie_key(), video_id=episode.get('id') or video_path) | |
5118d2ec AG |
571 | page_num += 1 |
572 | ||
573 | def _real_extract(self, url): | |
574 | show_name = self._match_valid_url(url).group('show_name') | |
575 | return self.playlist_result(self._entries(show_name), playlist_id=show_name) | |
576 | ||
577 | ||
578 | class DiscoveryPlusItalyShowIE(DiscoveryPlusShowBaseIE): | |
579 | _VALID_URL = r'https?://(?:www\.)?discoveryplus\.it/programmi/(?P<show_name>[^/]+)/?(?:[?#]|$)' | |
580 | _TESTS = [{ | |
581 | 'url': 'https://www.discoveryplus.it/programmi/deal-with-it-stai-al-gioco', | |
582 | 'playlist_mincount': 168, | |
583 | 'info_dict': { | |
584 | 'id': 'deal-with-it-stai-al-gioco', | |
585 | }, | |
586 | }] | |
587 | ||
588 | _BASE_API = 'https://disco-api.discoveryplus.it/' | |
589 | _DOMAIN = 'https://www.discoveryplus.it/' | |
590 | _X_CLIENT = 'WEB:UNKNOWN:dplay-client:2.6.0' | |
591 | _REALM = 'dplayit' | |
592 | _SHOW_STR = 'programmi' | |
593 | _INDEX = 1 | |
594 | _VIDEO_IE = DPlayIE | |
595 | ||
596 | ||
597 | class DiscoveryPlusIndiaShowIE(DiscoveryPlusShowBaseIE): | |
598 | _VALID_URL = r'https?://(?:www\.)?discoveryplus\.in/show/(?P<show_name>[^/]+)/?(?:[?#]|$)' | |
599 | _TESTS = [{ | |
600 | 'url': 'https://www.discoveryplus.in/show/how-do-they-do-it', | |
601 | 'playlist_mincount': 140, | |
602 | 'info_dict': { | |
603 | 'id': 'how-do-they-do-it', | |
604 | }, | |
605 | }] | |
606 | ||
607 | _BASE_API = 'https://ap2-prod-direct.discoveryplus.in/' | |
608 | _DOMAIN = 'https://www.discoveryplus.in/' | |
609 | _X_CLIENT = 'WEB:UNKNOWN:dplus-india:prod' | |
610 | _REALM = 'dplusindia' | |
611 | _SHOW_STR = 'show' | |
612 | _INDEX = 4 | |
613 | _VIDEO_IE = DiscoveryPlusIndiaIE |