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