]>
Commit | Line | Data |
---|---|---|
1 | import json | |
2 | import uuid | |
3 | ||
4 | from .common import InfoExtractor | |
5 | from ..networking.exceptions import HTTPError | |
6 | from ..utils import ( | |
7 | determine_ext, | |
8 | ExtractorError, | |
9 | float_or_none, | |
10 | int_or_none, | |
11 | remove_start, | |
12 | strip_or_none, | |
13 | try_get, | |
14 | unified_timestamp, | |
15 | ) | |
16 | ||
17 | ||
18 | class DPlayBaseIE(InfoExtractor): | |
19 | _PATH_REGEX = r'/(?P<id>[^/]+/[^/?#]+)' | |
20 | _auth_token_cache = {} | |
21 | ||
22 | def _get_auth(self, disco_base, display_id, realm, needs_device_id=True): | |
23 | key = (disco_base, realm) | |
24 | st = self._get_cookies(disco_base).get('st') | |
25 | token = (st and st.value) or self._auth_token_cache.get(key) | |
26 | ||
27 | if not token: | |
28 | query = {'realm': realm} | |
29 | if needs_device_id: | |
30 | query['deviceId'] = uuid.uuid4().hex | |
31 | token = self._download_json( | |
32 | disco_base + 'token', display_id, 'Downloading token', | |
33 | query=query)['data']['attributes']['token'] | |
34 | ||
35 | # Save cache only if cookies are not being set | |
36 | if not self._get_cookies(disco_base).get('st'): | |
37 | self._auth_token_cache[key] = token | |
38 | ||
39 | return f'Bearer {token}' | |
40 | ||
41 | def _process_errors(self, e, geo_countries): | |
42 | info = self._parse_json(e.cause.response.read().decode('utf-8'), None) | |
43 | error = info['errors'][0] | |
44 | error_code = error.get('code') | |
45 | if error_code == 'access.denied.geoblocked': | |
46 | self.raise_geo_restricted(countries=geo_countries) | |
47 | elif error_code in ('access.denied.missingpackage', 'invalid.token'): | |
48 | raise ExtractorError( | |
49 | 'This video is only available for registered users. You may want to use --cookies.', expected=True) | |
50 | raise ExtractorError(info['errors'][0]['detail'], expected=True) | |
51 | ||
52 | def _update_disco_api_headers(self, headers, disco_base, display_id, realm): | |
53 | headers['Authorization'] = self._get_auth(disco_base, display_id, realm, False) | |
54 | ||
55 | def _download_video_playback_info(self, disco_base, video_id, headers): | |
56 | streaming = self._download_json( | |
57 | disco_base + 'playback/videoPlaybackInfo/' + video_id, | |
58 | video_id, headers=headers)['data']['attributes']['streaming'] | |
59 | streaming_list = [] | |
60 | for format_id, format_dict in streaming.items(): | |
61 | streaming_list.append({ | |
62 | 'type': format_id, | |
63 | 'url': format_dict.get('url'), | |
64 | }) | |
65 | return streaming_list | |
66 | ||
67 | def _get_disco_api_info(self, url, display_id, disco_host, realm, country, domain=''): | |
68 | country = self.get_param('geo_bypass_country') or country | |
69 | geo_countries = [country.upper()] | |
70 | self._initialize_geo_bypass({ | |
71 | 'countries': geo_countries, | |
72 | }) | |
73 | disco_base = 'https://%s/' % disco_host | |
74 | headers = { | |
75 | 'Referer': url, | |
76 | } | |
77 | self._update_disco_api_headers(headers, disco_base, display_id, realm) | |
78 | try: | |
79 | video = self._download_json( | |
80 | disco_base + 'content/videos/' + display_id, display_id, | |
81 | headers=headers, query={ | |
82 | 'fields[channel]': 'name', | |
83 | 'fields[image]': 'height,src,width', | |
84 | 'fields[show]': 'name', | |
85 | 'fields[tag]': 'name', | |
86 | 'fields[video]': 'description,episodeNumber,name,publishStart,seasonNumber,videoDuration', | |
87 | 'include': 'images,primaryChannel,show,tags' | |
88 | }) | |
89 | except ExtractorError as e: | |
90 | if isinstance(e.cause, HTTPError) and e.cause.status == 400: | |
91 | self._process_errors(e, geo_countries) | |
92 | raise | |
93 | video_id = video['data']['id'] | |
94 | info = video['data']['attributes'] | |
95 | title = info['name'].strip() | |
96 | formats = [] | |
97 | subtitles = {} | |
98 | try: | |
99 | streaming = self._download_video_playback_info( | |
100 | disco_base, video_id, headers) | |
101 | except ExtractorError as e: | |
102 | if isinstance(e.cause, HTTPError) and e.cause.status == 403: | |
103 | self._process_errors(e, geo_countries) | |
104 | raise | |
105 | for format_dict in streaming: | |
106 | if not isinstance(format_dict, dict): | |
107 | continue | |
108 | format_url = format_dict.get('url') | |
109 | if not format_url: | |
110 | continue | |
111 | format_id = format_dict.get('type') | |
112 | ext = determine_ext(format_url) | |
113 | if format_id == 'dash' or ext == 'mpd': | |
114 | dash_fmts, dash_subs = self._extract_mpd_formats_and_subtitles( | |
115 | format_url, display_id, mpd_id='dash', fatal=False) | |
116 | formats.extend(dash_fmts) | |
117 | subtitles = self._merge_subtitles(subtitles, dash_subs) | |
118 | elif format_id == 'hls' or ext == 'm3u8': | |
119 | m3u8_fmts, m3u8_subs = self._extract_m3u8_formats_and_subtitles( | |
120 | format_url, display_id, 'mp4', | |
121 | entry_protocol='m3u8_native', m3u8_id='hls', | |
122 | fatal=False) | |
123 | formats.extend(m3u8_fmts) | |
124 | subtitles = self._merge_subtitles(subtitles, m3u8_subs) | |
125 | else: | |
126 | formats.append({ | |
127 | 'url': format_url, | |
128 | 'format_id': format_id, | |
129 | }) | |
130 | ||
131 | creator = series = None | |
132 | tags = [] | |
133 | thumbnails = [] | |
134 | included = video.get('included') or [] | |
135 | if isinstance(included, list): | |
136 | for e in included: | |
137 | attributes = e.get('attributes') | |
138 | if not attributes: | |
139 | continue | |
140 | e_type = e.get('type') | |
141 | if e_type == 'channel': | |
142 | creator = attributes.get('name') | |
143 | elif e_type == 'image': | |
144 | src = attributes.get('src') | |
145 | if src: | |
146 | thumbnails.append({ | |
147 | 'url': src, | |
148 | 'width': int_or_none(attributes.get('width')), | |
149 | 'height': int_or_none(attributes.get('height')), | |
150 | }) | |
151 | if e_type == 'show': | |
152 | series = attributes.get('name') | |
153 | elif e_type == 'tag': | |
154 | name = attributes.get('name') | |
155 | if name: | |
156 | tags.append(name) | |
157 | return { | |
158 | 'id': video_id, | |
159 | 'display_id': display_id, | |
160 | 'title': title, | |
161 | 'description': strip_or_none(info.get('description')), | |
162 | 'duration': float_or_none(info.get('videoDuration'), 1000), | |
163 | 'timestamp': unified_timestamp(info.get('publishStart')), | |
164 | 'series': series, | |
165 | 'season_number': int_or_none(info.get('seasonNumber')), | |
166 | 'episode_number': int_or_none(info.get('episodeNumber')), | |
167 | 'creator': creator, | |
168 | 'tags': tags, | |
169 | 'thumbnails': thumbnails, | |
170 | 'formats': formats, | |
171 | 'subtitles': subtitles, | |
172 | 'http_headers': { | |
173 | 'referer': domain, | |
174 | }, | |
175 | } | |
176 | ||
177 | ||
178 | class DPlayIE(DPlayBaseIE): | |
179 | _VALID_URL = r'''(?x)https?:// | |
180 | (?P<domain> | |
181 | (?:www\.)?(?P<host>d | |
182 | (?: | |
183 | play\.(?P<country>dk|fi|jp|se|no)| | |
184 | iscoveryplus\.(?P<plus_country>dk|es|fi|it|se|no) | |
185 | ) | |
186 | )| | |
187 | (?P<subdomain_country>es|it)\.dplay\.com | |
188 | )/[^/]+''' + DPlayBaseIE._PATH_REGEX | |
189 | ||
190 | _TESTS = [{ | |
191 | # non geo restricted, via secure api, unsigned download hls URL | |
192 | 'url': 'https://www.dplay.se/videos/nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101', | |
193 | 'info_dict': { | |
194 | 'id': '13628', | |
195 | 'display_id': 'nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101', | |
196 | 'ext': 'mp4', | |
197 | 'title': 'Svensken lär sig njuta av livet', | |
198 | 'description': 'md5:d3819c9bccffd0fe458ca42451dd50d8', | |
199 | 'duration': 2649.856, | |
200 | 'timestamp': 1365453720, | |
201 | 'upload_date': '20130408', | |
202 | 'creator': 'Kanal 5', | |
203 | 'series': 'Nugammalt - 77 händelser som format Sverige', | |
204 | 'season_number': 1, | |
205 | 'episode_number': 1, | |
206 | }, | |
207 | 'params': { | |
208 | 'skip_download': True, | |
209 | }, | |
210 | }, { | |
211 | # geo restricted, via secure api, unsigned download hls URL | |
212 | 'url': 'http://www.dplay.dk/videoer/ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster', | |
213 | 'info_dict': { | |
214 | 'id': '104465', | |
215 | 'display_id': 'ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster', | |
216 | 'ext': 'mp4', | |
217 | 'title': 'Ted Bundy: Mind Of A Monster', | |
218 | 'description': 'md5:8b780f6f18de4dae631668b8a9637995', | |
219 | 'duration': 5290.027, | |
220 | 'timestamp': 1570694400, | |
221 | 'upload_date': '20191010', | |
222 | 'creator': 'ID - Investigation Discovery', | |
223 | 'series': 'Ted Bundy: Mind Of A Monster', | |
224 | 'season_number': 1, | |
225 | 'episode_number': 1, | |
226 | }, | |
227 | 'params': { | |
228 | 'skip_download': True, | |
229 | }, | |
230 | }, { | |
231 | # disco-api | |
232 | 'url': 'https://www.dplay.no/videoer/i-kongens-klr/sesong-1-episode-7', | |
233 | 'info_dict': { | |
234 | 'id': '40206', | |
235 | 'display_id': 'i-kongens-klr/sesong-1-episode-7', | |
236 | 'ext': 'mp4', | |
237 | 'title': 'Episode 7', | |
238 | 'description': 'md5:e3e1411b2b9aebeea36a6ec5d50c60cf', | |
239 | 'duration': 2611.16, | |
240 | 'timestamp': 1516726800, | |
241 | 'upload_date': '20180123', | |
242 | 'series': 'I kongens klær', | |
243 | 'season_number': 1, | |
244 | 'episode_number': 7, | |
245 | }, | |
246 | 'params': { | |
247 | 'skip_download': True, | |
248 | }, | |
249 | 'skip': 'Available for Premium users', | |
250 | }, { | |
251 | 'url': 'http://it.dplay.com/nove/biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij/', | |
252 | 'md5': '2b808ffb00fc47b884a172ca5d13053c', | |
253 | 'info_dict': { | |
254 | 'id': '6918', | |
255 | 'display_id': 'biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij', | |
256 | 'ext': 'mp4', | |
257 | 'title': 'Luigi Di Maio: la psicosi di Stanislawskij', | |
258 | 'description': 'md5:3c7a4303aef85868f867a26f5cc14813', | |
259 | 'thumbnail': r're:^https?://.*\.jpe?g', | |
260 | 'upload_date': '20160524', | |
261 | 'timestamp': 1464076800, | |
262 | 'series': 'Biografie imbarazzanti', | |
263 | 'season_number': 1, | |
264 | 'episode': 'Episode 1', | |
265 | 'episode_number': 1, | |
266 | }, | |
267 | }, { | |
268 | 'url': 'https://es.dplay.com/dmax/la-fiebre-del-oro/temporada-8-episodio-1/', | |
269 | 'info_dict': { | |
270 | 'id': '21652', | |
271 | 'display_id': 'la-fiebre-del-oro/temporada-8-episodio-1', | |
272 | 'ext': 'mp4', | |
273 | 'title': 'Episodio 1', | |
274 | 'description': 'md5:b9dcff2071086e003737485210675f69', | |
275 | 'thumbnail': r're:^https?://.*\.png', | |
276 | 'upload_date': '20180709', | |
277 | 'timestamp': 1531173540, | |
278 | 'series': 'La fiebre del oro', | |
279 | 'season_number': 8, | |
280 | 'episode': 'Episode 1', | |
281 | 'episode_number': 1, | |
282 | }, | |
283 | 'params': { | |
284 | 'skip_download': True, | |
285 | }, | |
286 | }, { | |
287 | 'url': 'https://www.dplay.fi/videot/shifting-gears-with-aaron-kaufman/episode-16', | |
288 | 'only_matching': True, | |
289 | }, { | |
290 | 'url': 'https://www.dplay.jp/video/gold-rush/24086', | |
291 | 'only_matching': True, | |
292 | }, { | |
293 | 'url': 'https://www.discoveryplus.se/videos/nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101', | |
294 | 'only_matching': True, | |
295 | }, { | |
296 | 'url': 'https://www.discoveryplus.dk/videoer/ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster', | |
297 | 'only_matching': True, | |
298 | }, { | |
299 | 'url': 'https://www.discoveryplus.no/videoer/i-kongens-klr/sesong-1-episode-7', | |
300 | 'only_matching': True, | |
301 | }, { | |
302 | 'url': 'https://www.discoveryplus.it/videos/biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij', | |
303 | 'only_matching': True, | |
304 | }, { | |
305 | 'url': 'https://www.discoveryplus.es/videos/la-fiebre-del-oro/temporada-8-episodio-1', | |
306 | 'only_matching': True, | |
307 | }, { | |
308 | 'url': 'https://www.discoveryplus.fi/videot/shifting-gears-with-aaron-kaufman/episode-16', | |
309 | 'only_matching': True, | |
310 | }] | |
311 | ||
312 | def _real_extract(self, url): | |
313 | mobj = self._match_valid_url(url) | |
314 | display_id = mobj.group('id') | |
315 | domain = remove_start(mobj.group('domain'), 'www.') | |
316 | country = mobj.group('country') or mobj.group('subdomain_country') or mobj.group('plus_country') | |
317 | host = 'disco-api.' + domain if domain[0] == 'd' else 'eu2-prod.disco-api.com' | |
318 | return self._get_disco_api_info( | |
319 | url, display_id, host, 'dplay' + country, country, domain) | |
320 | ||
321 | ||
322 | class HGTVDeIE(DPlayBaseIE): | |
323 | _VALID_URL = r'https?://de\.hgtv\.com/sendungen' + DPlayBaseIE._PATH_REGEX | |
324 | _TESTS = [{ | |
325 | 'url': 'https://de.hgtv.com/sendungen/tiny-house-klein-aber-oho/wer-braucht-schon-eine-toilette/', | |
326 | 'info_dict': { | |
327 | 'id': '151205', | |
328 | 'display_id': 'tiny-house-klein-aber-oho/wer-braucht-schon-eine-toilette', | |
329 | 'ext': 'mp4', | |
330 | 'title': 'Wer braucht schon eine Toilette', | |
331 | 'description': 'md5:05b40a27e7aed2c9172de34d459134e2', | |
332 | 'duration': 1177.024, | |
333 | 'timestamp': 1595705400, | |
334 | 'upload_date': '20200725', | |
335 | 'creator': 'HGTV', | |
336 | 'series': 'Tiny House - klein, aber oho', | |
337 | 'season_number': 3, | |
338 | 'episode_number': 3, | |
339 | }, | |
340 | }] | |
341 | ||
342 | def _real_extract(self, url): | |
343 | display_id = self._match_id(url) | |
344 | return self._get_disco_api_info( | |
345 | url, display_id, 'eu1-prod.disco-api.com', 'hgtv', 'de') | |
346 | ||
347 | ||
348 | class DiscoveryPlusBaseIE(DPlayBaseIE): | |
349 | def _update_disco_api_headers(self, headers, disco_base, display_id, realm): | |
350 | headers['x-disco-client'] = f'WEB:UNKNOWN:{self._PRODUCT}:25.2.6' | |
351 | ||
352 | def _download_video_playback_info(self, disco_base, video_id, headers): | |
353 | return self._download_json( | |
354 | disco_base + 'playback/v3/videoPlaybackInfo', | |
355 | video_id, headers=headers, data=json.dumps({ | |
356 | 'deviceInfo': { | |
357 | 'adBlocker': False, | |
358 | }, | |
359 | 'videoId': video_id, | |
360 | 'wisteriaProperties': { | |
361 | 'platform': 'desktop', | |
362 | 'product': self._PRODUCT, | |
363 | }, | |
364 | }).encode('utf-8'))['data']['attributes']['streaming'] | |
365 | ||
366 | def _real_extract(self, url): | |
367 | return self._get_disco_api_info(url, self._match_id(url), **self._DISCO_API_PARAMS) | |
368 | ||
369 | ||
370 | class GoDiscoveryIE(DiscoveryPlusBaseIE): | |
371 | _VALID_URL = r'https?://(?:go\.)?discovery\.com/video' + DPlayBaseIE._PATH_REGEX | |
372 | _TESTS = [{ | |
373 | 'url': 'https://go.discovery.com/video/dirty-jobs-discovery-atve-us/rodbuster-galvanizer', | |
374 | 'info_dict': { | |
375 | 'id': '4164906', | |
376 | 'display_id': 'dirty-jobs-discovery-atve-us/rodbuster-galvanizer', | |
377 | 'ext': 'mp4', | |
378 | 'title': 'Rodbuster / Galvanizer', | |
379 | 'description': 'Mike installs rebar with a team of rodbusters, then he galvanizes steel.', | |
380 | 'season_number': 9, | |
381 | 'episode_number': 1, | |
382 | }, | |
383 | 'skip': 'Available for Premium users', | |
384 | }, { | |
385 | 'url': 'https://discovery.com/video/dirty-jobs-discovery-atve-us/rodbuster-galvanizer', | |
386 | 'only_matching': True, | |
387 | }] | |
388 | ||
389 | _PRODUCT = 'dsc' | |
390 | _DISCO_API_PARAMS = { | |
391 | 'disco_host': 'us1-prod-direct.go.discovery.com', | |
392 | 'realm': 'go', | |
393 | 'country': 'us', | |
394 | } | |
395 | ||
396 | ||
397 | class TravelChannelIE(DiscoveryPlusBaseIE): | |
398 | _VALID_URL = r'https?://(?:watch\.)?travelchannel\.com/video' + DPlayBaseIE._PATH_REGEX | |
399 | _TESTS = [{ | |
400 | 'url': 'https://watch.travelchannel.com/video/ghost-adventures-travel-channel/ghost-train-of-ely', | |
401 | 'info_dict': { | |
402 | 'id': '2220256', | |
403 | 'display_id': 'ghost-adventures-travel-channel/ghost-train-of-ely', | |
404 | 'ext': 'mp4', | |
405 | 'title': 'Ghost Train of Ely', | |
406 | 'description': 'The crew investigates the dark history of the Nevada Northern Railway.', | |
407 | 'season_number': 24, | |
408 | 'episode_number': 1, | |
409 | }, | |
410 | 'skip': 'Available for Premium users', | |
411 | }, { | |
412 | 'url': 'https://watch.travelchannel.com/video/ghost-adventures-travel-channel/ghost-train-of-ely', | |
413 | 'only_matching': True, | |
414 | }] | |
415 | ||
416 | _PRODUCT = 'trav' | |
417 | _DISCO_API_PARAMS = { | |
418 | 'disco_host': 'us1-prod-direct.watch.travelchannel.com', | |
419 | 'realm': 'go', | |
420 | 'country': 'us', | |
421 | } | |
422 | ||
423 | ||
424 | class CookingChannelIE(DiscoveryPlusBaseIE): | |
425 | _VALID_URL = r'https?://(?:watch\.)?cookingchanneltv\.com/video' + DPlayBaseIE._PATH_REGEX | |
426 | _TESTS = [{ | |
427 | 'url': 'https://watch.cookingchanneltv.com/video/carnival-eats-cooking-channel/the-postman-always-brings-rice-2348634', | |
428 | 'info_dict': { | |
429 | 'id': '2348634', | |
430 | 'display_id': 'carnival-eats-cooking-channel/the-postman-always-brings-rice-2348634', | |
431 | 'ext': 'mp4', | |
432 | 'title': 'The Postman Always Brings Rice', | |
433 | 'description': 'Noah visits the Maui Fair and the Aurora Winter Festival in Vancouver.', | |
434 | 'season_number': 9, | |
435 | 'episode_number': 1, | |
436 | }, | |
437 | 'skip': 'Available for Premium users', | |
438 | }, { | |
439 | 'url': 'https://watch.cookingchanneltv.com/video/carnival-eats-cooking-channel/the-postman-always-brings-rice-2348634', | |
440 | 'only_matching': True, | |
441 | }] | |
442 | ||
443 | _PRODUCT = 'cook' | |
444 | _DISCO_API_PARAMS = { | |
445 | 'disco_host': 'us1-prod-direct.watch.cookingchanneltv.com', | |
446 | 'realm': 'go', | |
447 | 'country': 'us', | |
448 | } | |
449 | ||
450 | ||
451 | class HGTVUsaIE(DiscoveryPlusBaseIE): | |
452 | _VALID_URL = r'https?://(?:watch\.)?hgtv\.com/video' + DPlayBaseIE._PATH_REGEX | |
453 | _TESTS = [{ | |
454 | 'url': 'https://watch.hgtv.com/video/home-inspector-joe-hgtv-atve-us/this-mold-house', | |
455 | 'info_dict': { | |
456 | 'id': '4289736', | |
457 | 'display_id': 'home-inspector-joe-hgtv-atve-us/this-mold-house', | |
458 | 'ext': 'mp4', | |
459 | 'title': 'This Mold House', | |
460 | 'description': 'Joe and Noel help take a familys dream home from hazardous to fabulous.', | |
461 | 'season_number': 1, | |
462 | 'episode_number': 1, | |
463 | }, | |
464 | 'skip': 'Available for Premium users', | |
465 | }, { | |
466 | 'url': 'https://watch.hgtv.com/video/home-inspector-joe-hgtv-atve-us/this-mold-house', | |
467 | 'only_matching': True, | |
468 | }] | |
469 | ||
470 | _PRODUCT = 'hgtv' | |
471 | _DISCO_API_PARAMS = { | |
472 | 'disco_host': 'us1-prod-direct.watch.hgtv.com', | |
473 | 'realm': 'go', | |
474 | 'country': 'us', | |
475 | } | |
476 | ||
477 | ||
478 | class FoodNetworkIE(DiscoveryPlusBaseIE): | |
479 | _VALID_URL = r'https?://(?:watch\.)?foodnetwork\.com/video' + DPlayBaseIE._PATH_REGEX | |
480 | _TESTS = [{ | |
481 | 'url': 'https://watch.foodnetwork.com/video/kids-baking-championship-food-network/float-like-a-butterfly', | |
482 | 'info_dict': { | |
483 | 'id': '4116449', | |
484 | 'display_id': 'kids-baking-championship-food-network/float-like-a-butterfly', | |
485 | 'ext': 'mp4', | |
486 | 'title': 'Float Like a Butterfly', | |
487 | 'description': 'The 12 kid bakers create colorful carved butterfly cakes.', | |
488 | 'season_number': 10, | |
489 | 'episode_number': 1, | |
490 | }, | |
491 | 'skip': 'Available for Premium users', | |
492 | }, { | |
493 | 'url': 'https://watch.foodnetwork.com/video/kids-baking-championship-food-network/float-like-a-butterfly', | |
494 | 'only_matching': True, | |
495 | }] | |
496 | ||
497 | _PRODUCT = 'food' | |
498 | _DISCO_API_PARAMS = { | |
499 | 'disco_host': 'us1-prod-direct.watch.foodnetwork.com', | |
500 | 'realm': 'go', | |
501 | 'country': 'us', | |
502 | } | |
503 | ||
504 | ||
505 | class DestinationAmericaIE(DiscoveryPlusBaseIE): | |
506 | _VALID_URL = r'https?://(?:www\.)?destinationamerica\.com/video' + DPlayBaseIE._PATH_REGEX | |
507 | _TESTS = [{ | |
508 | 'url': 'https://www.destinationamerica.com/video/alaska-monsters-destination-america-atve-us/central-alaskas-bigfoot', | |
509 | 'info_dict': { | |
510 | 'id': '4210904', | |
511 | 'display_id': 'alaska-monsters-destination-america-atve-us/central-alaskas-bigfoot', | |
512 | 'ext': 'mp4', | |
513 | 'title': 'Central Alaskas Bigfoot', | |
514 | 'description': 'A team heads to central Alaska to investigate an aggressive Bigfoot.', | |
515 | 'season_number': 1, | |
516 | 'episode_number': 1, | |
517 | }, | |
518 | 'skip': 'Available for Premium users', | |
519 | }, { | |
520 | 'url': 'https://www.destinationamerica.com/video/alaska-monsters-destination-america-atve-us/central-alaskas-bigfoot', | |
521 | 'only_matching': True, | |
522 | }] | |
523 | ||
524 | _PRODUCT = 'dam' | |
525 | _DISCO_API_PARAMS = { | |
526 | 'disco_host': 'us1-prod-direct.destinationamerica.com', | |
527 | 'realm': 'go', | |
528 | 'country': 'us', | |
529 | } | |
530 | ||
531 | ||
532 | class InvestigationDiscoveryIE(DiscoveryPlusBaseIE): | |
533 | _VALID_URL = r'https?://(?:www\.)?investigationdiscovery\.com/video' + DPlayBaseIE._PATH_REGEX | |
534 | _TESTS = [{ | |
535 | 'url': 'https://www.investigationdiscovery.com/video/unmasked-investigation-discovery/the-killer-clown', | |
536 | 'info_dict': { | |
537 | 'id': '2139409', | |
538 | 'display_id': 'unmasked-investigation-discovery/the-killer-clown', | |
539 | 'ext': 'mp4', | |
540 | 'title': 'The Killer Clown', | |
541 | 'description': 'A wealthy Florida woman is fatally shot in the face by a clown at her door.', | |
542 | 'season_number': 1, | |
543 | 'episode_number': 1, | |
544 | }, | |
545 | 'skip': 'Available for Premium users', | |
546 | }, { | |
547 | 'url': 'https://www.investigationdiscovery.com/video/unmasked-investigation-discovery/the-killer-clown', | |
548 | 'only_matching': True, | |
549 | }] | |
550 | ||
551 | _PRODUCT = 'ids' | |
552 | _DISCO_API_PARAMS = { | |
553 | 'disco_host': 'us1-prod-direct.investigationdiscovery.com', | |
554 | 'realm': 'go', | |
555 | 'country': 'us', | |
556 | } | |
557 | ||
558 | ||
559 | class AmHistoryChannelIE(DiscoveryPlusBaseIE): | |
560 | _VALID_URL = r'https?://(?:www\.)?ahctv\.com/video' + DPlayBaseIE._PATH_REGEX | |
561 | _TESTS = [{ | |
562 | 'url': 'https://www.ahctv.com/video/modern-sniper-ahc/army', | |
563 | 'info_dict': { | |
564 | 'id': '2309730', | |
565 | 'display_id': 'modern-sniper-ahc/army', | |
566 | 'ext': 'mp4', | |
567 | 'title': 'Army', | |
568 | 'description': 'Snipers today face challenges their predecessors couldve only dreamed of.', | |
569 | 'season_number': 1, | |
570 | 'episode_number': 1, | |
571 | }, | |
572 | 'skip': 'Available for Premium users', | |
573 | }, { | |
574 | 'url': 'https://www.ahctv.com/video/modern-sniper-ahc/army', | |
575 | 'only_matching': True, | |
576 | }] | |
577 | ||
578 | _PRODUCT = 'ahc' | |
579 | _DISCO_API_PARAMS = { | |
580 | 'disco_host': 'us1-prod-direct.ahctv.com', | |
581 | 'realm': 'go', | |
582 | 'country': 'us', | |
583 | } | |
584 | ||
585 | ||
586 | class ScienceChannelIE(DiscoveryPlusBaseIE): | |
587 | _VALID_URL = r'https?://(?:www\.)?sciencechannel\.com/video' + DPlayBaseIE._PATH_REGEX | |
588 | _TESTS = [{ | |
589 | 'url': 'https://www.sciencechannel.com/video/strangest-things-science-atve-us/nazi-mystery-machine', | |
590 | 'info_dict': { | |
591 | 'id': '2842849', | |
592 | 'display_id': 'strangest-things-science-atve-us/nazi-mystery-machine', | |
593 | 'ext': 'mp4', | |
594 | 'title': 'Nazi Mystery Machine', | |
595 | 'description': 'Experts investigate the secrets of a revolutionary encryption machine.', | |
596 | 'season_number': 1, | |
597 | 'episode_number': 1, | |
598 | }, | |
599 | 'skip': 'Available for Premium users', | |
600 | }, { | |
601 | 'url': 'https://www.sciencechannel.com/video/strangest-things-science-atve-us/nazi-mystery-machine', | |
602 | 'only_matching': True, | |
603 | }] | |
604 | ||
605 | _PRODUCT = 'sci' | |
606 | _DISCO_API_PARAMS = { | |
607 | 'disco_host': 'us1-prod-direct.sciencechannel.com', | |
608 | 'realm': 'go', | |
609 | 'country': 'us', | |
610 | } | |
611 | ||
612 | ||
613 | class DIYNetworkIE(DiscoveryPlusBaseIE): | |
614 | _VALID_URL = r'https?://(?:watch\.)?diynetwork\.com/video' + DPlayBaseIE._PATH_REGEX | |
615 | _TESTS = [{ | |
616 | 'url': 'https://watch.diynetwork.com/video/pool-kings-diy-network/bringing-beach-life-to-texas', | |
617 | 'info_dict': { | |
618 | 'id': '2309730', | |
619 | 'display_id': 'pool-kings-diy-network/bringing-beach-life-to-texas', | |
620 | 'ext': 'mp4', | |
621 | 'title': 'Bringing Beach Life to Texas', | |
622 | 'description': 'The Pool Kings give a family a day at the beach in their own backyard.', | |
623 | 'season_number': 10, | |
624 | 'episode_number': 2, | |
625 | }, | |
626 | 'skip': 'Available for Premium users', | |
627 | }, { | |
628 | 'url': 'https://watch.diynetwork.com/video/pool-kings-diy-network/bringing-beach-life-to-texas', | |
629 | 'only_matching': True, | |
630 | }] | |
631 | ||
632 | _PRODUCT = 'diy' | |
633 | _DISCO_API_PARAMS = { | |
634 | 'disco_host': 'us1-prod-direct.watch.diynetwork.com', | |
635 | 'realm': 'go', | |
636 | 'country': 'us', | |
637 | } | |
638 | ||
639 | ||
640 | class DiscoveryLifeIE(DiscoveryPlusBaseIE): | |
641 | _VALID_URL = r'https?://(?:www\.)?discoverylife\.com/video' + DPlayBaseIE._PATH_REGEX | |
642 | _TESTS = [{ | |
643 | 'url': 'https://www.discoverylife.com/video/surviving-death-discovery-life-atve-us/bodily-trauma', | |
644 | 'info_dict': { | |
645 | 'id': '2218238', | |
646 | 'display_id': 'surviving-death-discovery-life-atve-us/bodily-trauma', | |
647 | 'ext': 'mp4', | |
648 | 'title': 'Bodily Trauma', | |
649 | 'description': 'Meet three people who tested the limits of the human body.', | |
650 | 'season_number': 1, | |
651 | 'episode_number': 2, | |
652 | }, | |
653 | 'skip': 'Available for Premium users', | |
654 | }, { | |
655 | 'url': 'https://www.discoverylife.com/video/surviving-death-discovery-life-atve-us/bodily-trauma', | |
656 | 'only_matching': True, | |
657 | }] | |
658 | ||
659 | _PRODUCT = 'dlf' | |
660 | _DISCO_API_PARAMS = { | |
661 | 'disco_host': 'us1-prod-direct.discoverylife.com', | |
662 | 'realm': 'go', | |
663 | 'country': 'us', | |
664 | } | |
665 | ||
666 | ||
667 | class AnimalPlanetIE(DiscoveryPlusBaseIE): | |
668 | _VALID_URL = r'https?://(?:www\.)?animalplanet\.com/video' + DPlayBaseIE._PATH_REGEX | |
669 | _TESTS = [{ | |
670 | 'url': 'https://www.animalplanet.com/video/north-woods-law-animal-planet/squirrel-showdown', | |
671 | 'info_dict': { | |
672 | 'id': '3338923', | |
673 | 'display_id': 'north-woods-law-animal-planet/squirrel-showdown', | |
674 | 'ext': 'mp4', | |
675 | 'title': 'Squirrel Showdown', | |
676 | 'description': 'A woman is suspected of being in possession of flying squirrel kits.', | |
677 | 'season_number': 16, | |
678 | 'episode_number': 11, | |
679 | }, | |
680 | 'skip': 'Available for Premium users', | |
681 | }, { | |
682 | 'url': 'https://www.animalplanet.com/video/north-woods-law-animal-planet/squirrel-showdown', | |
683 | 'only_matching': True, | |
684 | }] | |
685 | ||
686 | _PRODUCT = 'apl' | |
687 | _DISCO_API_PARAMS = { | |
688 | 'disco_host': 'us1-prod-direct.animalplanet.com', | |
689 | 'realm': 'go', | |
690 | 'country': 'us', | |
691 | } | |
692 | ||
693 | ||
694 | class TLCIE(DiscoveryPlusBaseIE): | |
695 | _VALID_URL = r'https?://(?:go\.)?tlc\.com/video' + DPlayBaseIE._PATH_REGEX | |
696 | _TESTS = [{ | |
697 | 'url': 'https://go.tlc.com/video/my-600-lb-life-tlc/melissas-story-part-1', | |
698 | 'info_dict': { | |
699 | 'id': '2206540', | |
700 | 'display_id': 'my-600-lb-life-tlc/melissas-story-part-1', | |
701 | 'ext': 'mp4', | |
702 | 'title': 'Melissas Story (Part 1)', | |
703 | 'description': 'At 650 lbs, Melissa is ready to begin her seven-year weight loss journey.', | |
704 | 'season_number': 1, | |
705 | 'episode_number': 1, | |
706 | }, | |
707 | 'skip': 'Available for Premium users', | |
708 | }, { | |
709 | 'url': 'https://go.tlc.com/video/my-600-lb-life-tlc/melissas-story-part-1', | |
710 | 'only_matching': True, | |
711 | }] | |
712 | ||
713 | _PRODUCT = 'tlc' | |
714 | _DISCO_API_PARAMS = { | |
715 | 'disco_host': 'us1-prod-direct.tlc.com', | |
716 | 'realm': 'go', | |
717 | 'country': 'us', | |
718 | } | |
719 | ||
720 | ||
721 | class MotorTrendIE(DiscoveryPlusBaseIE): | |
722 | _VALID_URL = r'https?://(?:watch\.)?motortrend\.com/video' + DPlayBaseIE._PATH_REGEX | |
723 | _TESTS = [{ | |
724 | 'url': 'https://watch.motortrend.com/video/car-issues-motortrend-atve-us/double-dakotas', | |
725 | 'info_dict': { | |
726 | 'id': '"4859182"', | |
727 | 'display_id': 'double-dakotas', | |
728 | 'ext': 'mp4', | |
729 | 'title': 'Double Dakotas', | |
730 | 'description': 'Tylers buy-one-get-one Dakota deal has the Wizard pulling double duty.', | |
731 | 'season_number': 2, | |
732 | 'episode_number': 3, | |
733 | }, | |
734 | 'skip': 'Available for Premium users', | |
735 | }, { | |
736 | 'url': 'https://watch.motortrend.com/video/car-issues-motortrend-atve-us/double-dakotas', | |
737 | 'only_matching': True, | |
738 | }] | |
739 | ||
740 | _PRODUCT = 'vel' | |
741 | _DISCO_API_PARAMS = { | |
742 | 'disco_host': 'us1-prod-direct.watch.motortrend.com', | |
743 | 'realm': 'go', | |
744 | 'country': 'us', | |
745 | } | |
746 | ||
747 | ||
748 | class MotorTrendOnDemandIE(DiscoveryPlusBaseIE): | |
749 | _VALID_URL = r'https?://(?:www\.)?motortrend(?:ondemand\.com|\.com/plus)/detail' + DPlayBaseIE._PATH_REGEX | |
750 | _TESTS = [{ | |
751 | 'url': 'https://www.motortrendondemand.com/detail/wheelstanding-dump-truck-stubby-bobs-comeback/37699/784', | |
752 | 'info_dict': { | |
753 | 'id': '37699', | |
754 | 'display_id': 'wheelstanding-dump-truck-stubby-bobs-comeback/37699', | |
755 | 'ext': 'mp4', | |
756 | 'title': 'Wheelstanding Dump Truck! Stubby Bob’s Comeback', | |
757 | 'description': 'md5:996915abe52a1c3dfc83aecea3cce8e7', | |
758 | 'season_number': 5, | |
759 | 'episode_number': 52, | |
760 | 'episode': 'Episode 52', | |
761 | 'season': 'Season 5', | |
762 | 'thumbnail': r're:^https?://.+\.jpe?g$', | |
763 | 'timestamp': 1388534401, | |
764 | 'duration': 1887.345, | |
765 | 'creator': 'Originals', | |
766 | 'series': 'Roadkill', | |
767 | 'upload_date': '20140101', | |
768 | 'tags': [], | |
769 | }, | |
770 | }, { | |
771 | 'url': 'https://www.motortrend.com/plus/detail/roadworthy-rescues-teaser-trailer/4922860/', | |
772 | 'info_dict': { | |
773 | 'id': '4922860', | |
774 | 'ext': 'mp4', | |
775 | 'title': 'Roadworthy Rescues | Teaser Trailer', | |
776 | 'description': 'Derek Bieri helps Freiburger and Finnegan with their \'68 big-block Dart.', | |
777 | 'display_id': 'roadworthy-rescues-teaser-trailer/4922860', | |
778 | 'creator': 'Originals', | |
779 | 'series': 'Roadworthy Rescues', | |
780 | 'thumbnail': r're:^https?://.+\.jpe?g$', | |
781 | 'upload_date': '20220907', | |
782 | 'timestamp': 1662523200, | |
783 | 'duration': 1066.356, | |
784 | 'tags': [], | |
785 | }, | |
786 | }, { | |
787 | 'url': 'https://www.motortrend.com/plus/detail/ugly-duckling/2450033/12439', | |
788 | 'only_matching': True, | |
789 | }] | |
790 | ||
791 | _PRODUCT = 'MTOD' | |
792 | _DISCO_API_PARAMS = { | |
793 | 'disco_host': 'us1-prod-direct.motortrendondemand.com', | |
794 | 'realm': 'motortrend', | |
795 | 'country': 'us', | |
796 | } | |
797 | ||
798 | def _update_disco_api_headers(self, headers, disco_base, display_id, realm): | |
799 | headers.update({ | |
800 | 'x-disco-params': f'realm={realm}', | |
801 | 'x-disco-client': f'WEB:UNKNOWN:{self._PRODUCT}:4.39.1-gi1', | |
802 | 'Authorization': self._get_auth(disco_base, display_id, realm), | |
803 | }) | |
804 | ||
805 | ||
806 | class DiscoveryPlusIE(DiscoveryPlusBaseIE): | |
807 | _VALID_URL = r'https?://(?:www\.)?discoveryplus\.com/(?!it/)(?:\w{2}/)?video' + DPlayBaseIE._PATH_REGEX | |
808 | _TESTS = [{ | |
809 | 'url': 'https://www.discoveryplus.com/video/property-brothers-forever-home/food-and-family', | |
810 | 'info_dict': { | |
811 | 'id': '1140794', | |
812 | 'display_id': 'property-brothers-forever-home/food-and-family', | |
813 | 'ext': 'mp4', | |
814 | 'title': 'Food and Family', | |
815 | 'description': 'The brothers help a Richmond family expand their single-level home.', | |
816 | 'duration': 2583.113, | |
817 | 'timestamp': 1609304400, | |
818 | 'upload_date': '20201230', | |
819 | 'creator': 'HGTV', | |
820 | 'series': 'Property Brothers: Forever Home', | |
821 | 'season_number': 1, | |
822 | 'episode_number': 1, | |
823 | }, | |
824 | 'skip': 'Available for Premium users', | |
825 | }, { | |
826 | 'url': 'https://discoveryplus.com/ca/video/bering-sea-gold-discovery-ca/goldslingers', | |
827 | 'only_matching': True, | |
828 | }] | |
829 | ||
830 | _PRODUCT = 'dplus_us' | |
831 | _DISCO_API_PARAMS = { | |
832 | 'disco_host': 'us1-prod-direct.discoveryplus.com', | |
833 | 'realm': 'go', | |
834 | 'country': 'us', | |
835 | } | |
836 | ||
837 | ||
838 | class DiscoveryPlusIndiaIE(DiscoveryPlusBaseIE): | |
839 | _VALID_URL = r'https?://(?:www\.)?discoveryplus\.in/videos?' + DPlayBaseIE._PATH_REGEX | |
840 | _TESTS = [{ | |
841 | 'url': 'https://www.discoveryplus.in/videos/how-do-they-do-it/fugu-and-more?seasonId=8&type=EPISODE', | |
842 | 'info_dict': { | |
843 | 'id': '27104', | |
844 | 'ext': 'mp4', | |
845 | 'display_id': 'how-do-they-do-it/fugu-and-more', | |
846 | 'title': 'Fugu and More', | |
847 | 'description': 'The Japanese catch, prepare and eat the deadliest fish on the planet.', | |
848 | 'duration': 1319.32, | |
849 | 'timestamp': 1582309800, | |
850 | 'upload_date': '20200221', | |
851 | 'series': 'How Do They Do It?', | |
852 | 'season_number': 8, | |
853 | 'episode_number': 2, | |
854 | 'creator': 'Discovery Channel', | |
855 | 'thumbnail': r're:https://.+\.jpeg', | |
856 | 'episode': 'Episode 2', | |
857 | 'season': 'Season 8', | |
858 | 'tags': [], | |
859 | }, | |
860 | 'params': { | |
861 | 'skip_download': True, | |
862 | } | |
863 | }] | |
864 | ||
865 | _PRODUCT = 'dplus-india' | |
866 | _DISCO_API_PARAMS = { | |
867 | 'disco_host': 'ap2-prod-direct.discoveryplus.in', | |
868 | 'realm': 'dplusindia', | |
869 | 'country': 'in', | |
870 | 'domain': 'https://www.discoveryplus.in/', | |
871 | } | |
872 | ||
873 | def _update_disco_api_headers(self, headers, disco_base, display_id, realm): | |
874 | headers.update({ | |
875 | 'x-disco-params': 'realm=%s' % realm, | |
876 | 'x-disco-client': f'WEB:UNKNOWN:{self._PRODUCT}:17.0.0', | |
877 | 'Authorization': self._get_auth(disco_base, display_id, realm), | |
878 | }) | |
879 | ||
880 | ||
881 | class DiscoveryNetworksDeIE(DPlayBaseIE): | |
882 | _VALID_URL = r'https?://(?:www\.)?(?P<domain>(?:tlc|dmax)\.de|dplay\.co\.uk)/(?:programme|show|sendungen)/(?P<programme>[^/]+)/(?:video/)?(?P<alternate_id>[^/]+)' | |
883 | ||
884 | _TESTS = [{ | |
885 | 'url': 'https://www.tlc.de/programme/breaking-amish/video/die-welt-da-drauen/DCB331270001100', | |
886 | 'info_dict': { | |
887 | 'id': '78867', | |
888 | 'ext': 'mp4', | |
889 | 'title': 'Die Welt da draußen', | |
890 | 'description': 'md5:61033c12b73286e409d99a41742ef608', | |
891 | 'timestamp': 1554069600, | |
892 | 'upload_date': '20190331', | |
893 | 'creator': 'TLC', | |
894 | 'season': 'Season 1', | |
895 | 'series': 'Breaking Amish', | |
896 | 'episode_number': 1, | |
897 | 'tags': ['new york', 'großstadt', 'amische', 'landleben', 'modern', 'infos', 'tradition', 'herausforderung'], | |
898 | 'display_id': 'breaking-amish/die-welt-da-drauen', | |
899 | 'episode': 'Episode 1', | |
900 | 'duration': 2625.024, | |
901 | 'season_number': 1, | |
902 | 'thumbnail': r're:https://.+\.jpg', | |
903 | }, | |
904 | 'params': { | |
905 | 'skip_download': True, | |
906 | }, | |
907 | }, { | |
908 | 'url': 'https://www.dmax.de/programme/dmax-highlights/video/tuning-star-sidney-hoffmann-exklusiv-bei-dmax/191023082312316', | |
909 | 'only_matching': True, | |
910 | }, { | |
911 | 'url': 'https://www.dplay.co.uk/show/ghost-adventures/video/hotel-leger-103620/EHD_280313B', | |
912 | 'only_matching': True, | |
913 | }, { | |
914 | 'url': 'https://tlc.de/sendungen/breaking-amish/die-welt-da-drauen/', | |
915 | 'only_matching': True, | |
916 | }] | |
917 | ||
918 | def _real_extract(self, url): | |
919 | domain, programme, alternate_id = self._match_valid_url(url).groups() | |
920 | country = 'GB' if domain == 'dplay.co.uk' else 'DE' | |
921 | realm = 'questuk' if country == 'GB' else domain.replace('.', '') | |
922 | return self._get_disco_api_info( | |
923 | url, '%s/%s' % (programme, alternate_id), | |
924 | 'sonic-eu1-prod.disco-api.com', realm, country) | |
925 | ||
926 | ||
927 | class DiscoveryPlusShowBaseIE(DPlayBaseIE): | |
928 | ||
929 | def _entries(self, show_name): | |
930 | headers = { | |
931 | 'x-disco-client': self._X_CLIENT, | |
932 | 'x-disco-params': f'realm={self._REALM}', | |
933 | 'referer': self._DOMAIN, | |
934 | 'Authentication': self._get_auth(self._BASE_API, None, self._REALM), | |
935 | } | |
936 | show_json = self._download_json( | |
937 | f'{self._BASE_API}cms/routes/{self._SHOW_STR}/{show_name}?include=default', | |
938 | video_id=show_name, headers=headers)['included'][self._INDEX]['attributes']['component'] | |
939 | show_id = show_json['mandatoryParams'].split('=')[-1] | |
940 | season_url = self._BASE_API + 'content/videos?sort=episodeNumber&filter[seasonNumber]={}&filter[show.id]={}&page[size]=100&page[number]={}' | |
941 | for season in show_json['filters'][0]['options']: | |
942 | season_id = season['id'] | |
943 | total_pages, page_num = 1, 0 | |
944 | while page_num < total_pages: | |
945 | season_json = self._download_json( | |
946 | season_url.format(season_id, show_id, str(page_num + 1)), show_name, headers=headers, | |
947 | note='Downloading season %s JSON metadata%s' % (season_id, ' page %d' % page_num if page_num else '')) | |
948 | if page_num == 0: | |
949 | total_pages = try_get(season_json, lambda x: x['meta']['totalPages'], int) or 1 | |
950 | episodes_json = season_json['data'] | |
951 | for episode in episodes_json: | |
952 | video_path = episode['attributes']['path'] | |
953 | yield self.url_result( | |
954 | '%svideos/%s' % (self._DOMAIN, video_path), | |
955 | ie=self._VIDEO_IE.ie_key(), video_id=episode.get('id') or video_path) | |
956 | page_num += 1 | |
957 | ||
958 | def _real_extract(self, url): | |
959 | show_name = self._match_valid_url(url).group('show_name') | |
960 | return self.playlist_result(self._entries(show_name), playlist_id=show_name) | |
961 | ||
962 | ||
963 | class DiscoveryPlusItalyIE(DiscoveryPlusBaseIE): | |
964 | _VALID_URL = r'https?://(?:www\.)?discoveryplus\.com/it/video' + DPlayBaseIE._PATH_REGEX | |
965 | _TESTS = [{ | |
966 | 'url': 'https://www.discoveryplus.com/it/video/i-signori-della-neve/stagione-2-episodio-1-i-preparativi', | |
967 | 'only_matching': True, | |
968 | }, { | |
969 | 'url': 'https://www.discoveryplus.com/it/video/super-benny/trailer', | |
970 | 'only_matching': True, | |
971 | }] | |
972 | ||
973 | _PRODUCT = 'dplus_us' | |
974 | _DISCO_API_PARAMS = { | |
975 | 'disco_host': 'eu1-prod-direct.discoveryplus.com', | |
976 | 'realm': 'dplay', | |
977 | 'country': 'it', | |
978 | } | |
979 | ||
980 | def _update_disco_api_headers(self, headers, disco_base, display_id, realm): | |
981 | headers.update({ | |
982 | 'x-disco-params': 'realm=%s' % realm, | |
983 | 'x-disco-client': f'WEB:UNKNOWN:{self._PRODUCT}:25.2.6', | |
984 | 'Authorization': self._get_auth(disco_base, display_id, realm), | |
985 | }) | |
986 | ||
987 | ||
988 | class DiscoveryPlusItalyShowIE(DiscoveryPlusShowBaseIE): | |
989 | _VALID_URL = r'https?://(?:www\.)?discoveryplus\.it/programmi/(?P<show_name>[^/]+)/?(?:[?#]|$)' | |
990 | _TESTS = [{ | |
991 | 'url': 'https://www.discoveryplus.it/programmi/deal-with-it-stai-al-gioco', | |
992 | 'playlist_mincount': 168, | |
993 | 'info_dict': { | |
994 | 'id': 'deal-with-it-stai-al-gioco', | |
995 | }, | |
996 | }] | |
997 | ||
998 | _BASE_API = 'https://disco-api.discoveryplus.it/' | |
999 | _DOMAIN = 'https://www.discoveryplus.it/' | |
1000 | _X_CLIENT = 'WEB:UNKNOWN:dplay-client:2.6.0' | |
1001 | _REALM = 'dplayit' | |
1002 | _SHOW_STR = 'programmi' | |
1003 | _INDEX = 1 | |
1004 | _VIDEO_IE = DPlayIE | |
1005 | ||
1006 | ||
1007 | class DiscoveryPlusIndiaShowIE(DiscoveryPlusShowBaseIE): | |
1008 | _VALID_URL = r'https?://(?:www\.)?discoveryplus\.in/show/(?P<show_name>[^/]+)/?(?:[?#]|$)' | |
1009 | _TESTS = [{ | |
1010 | 'url': 'https://www.discoveryplus.in/show/how-do-they-do-it', | |
1011 | 'playlist_mincount': 140, | |
1012 | 'info_dict': { | |
1013 | 'id': 'how-do-they-do-it', | |
1014 | }, | |
1015 | }] | |
1016 | ||
1017 | _BASE_API = 'https://ap2-prod-direct.discoveryplus.in/' | |
1018 | _DOMAIN = 'https://www.discoveryplus.in/' | |
1019 | _X_CLIENT = 'WEB:UNKNOWN:dplus-india:prod' | |
1020 | _REALM = 'dplusindia' | |
1021 | _SHOW_STR = 'show' | |
1022 | _INDEX = 4 | |
1023 | _VIDEO_IE = DiscoveryPlusIndiaIE | |
1024 | ||
1025 | ||
1026 | class GlobalCyclingNetworkPlusIE(DiscoveryPlusBaseIE): | |
1027 | _VALID_URL = r'https?://plus\.globalcyclingnetwork\.com/watch/(?P<id>\d+)' | |
1028 | _TESTS = [{ | |
1029 | 'url': 'https://plus.globalcyclingnetwork.com/watch/1397691', | |
1030 | 'info_dict': { | |
1031 | 'id': '1397691', | |
1032 | 'ext': 'mp4', | |
1033 | 'title': 'The Athertons: Mountain Biking\'s Fastest Family', | |
1034 | 'description': 'md5:75a81937fcd8b989eec6083a709cd837', | |
1035 | 'thumbnail': 'https://us1-prod-images.disco-api.com/2021/03/04/eb9e3026-4849-3001-8281-9356466f0557.png', | |
1036 | 'series': 'gcn', | |
1037 | 'creator': 'Gcn', | |
1038 | 'upload_date': '20210309', | |
1039 | 'timestamp': 1615248000, | |
1040 | 'duration': 2531.0, | |
1041 | 'tags': [], | |
1042 | }, | |
1043 | 'skip': 'Subscription required', | |
1044 | 'params': {'skip_download': 'm3u8'}, | |
1045 | }] | |
1046 | ||
1047 | _PRODUCT = 'web' | |
1048 | _DISCO_API_PARAMS = { | |
1049 | 'disco_host': 'disco-api-prod.globalcyclingnetwork.com', | |
1050 | 'realm': 'gcn', | |
1051 | 'country': 'us', | |
1052 | } | |
1053 | ||
1054 | def _update_disco_api_headers(self, headers, disco_base, display_id, realm): | |
1055 | headers.update({ | |
1056 | 'x-disco-params': f'realm={realm}', | |
1057 | 'x-disco-client': f'WEB:UNKNOWN:{self._PRODUCT}:27.3.2', | |
1058 | 'Authorization': self._get_auth(disco_base, display_id, realm), | |
1059 | }) |