logo

oasis-root

Compiled tree of Oasis Linux based on own branch at <https://hacktivis.me/git/oasis/> git clone https://anongit.hacktivis.me/git/oasis-root.git

dplay.py (50210B)


  1. import json
  2. import uuid
  3. from .common import InfoExtractor
  4. from ..networking.exceptions import HTTPError
  5. from ..utils import (
  6. ExtractorError,
  7. determine_ext,
  8. float_or_none,
  9. int_or_none,
  10. remove_start,
  11. strip_or_none,
  12. try_get,
  13. unified_timestamp,
  14. )
  15. class DPlayBaseIE(InfoExtractor):
  16. _PATH_REGEX = r'/(?P<id>[^/]+/[^/?#]+)'
  17. _auth_token_cache = {}
  18. def _get_auth(self, disco_base, display_id, realm, needs_device_id=True):
  19. key = (disco_base, realm)
  20. st = self._get_cookies(disco_base).get('st')
  21. token = (st and st.value) or self._auth_token_cache.get(key)
  22. if not token:
  23. query = {'realm': realm}
  24. if needs_device_id:
  25. query['deviceId'] = uuid.uuid4().hex
  26. token = self._download_json(
  27. disco_base + 'token', display_id, 'Downloading token',
  28. query=query)['data']['attributes']['token']
  29. # Save cache only if cookies are not being set
  30. if not self._get_cookies(disco_base).get('st'):
  31. self._auth_token_cache[key] = token
  32. return f'Bearer {token}'
  33. def _process_errors(self, e, geo_countries):
  34. info = self._parse_json(e.cause.response.read().decode('utf-8'), None)
  35. error = info['errors'][0]
  36. error_code = error.get('code')
  37. if error_code == 'access.denied.geoblocked':
  38. self.raise_geo_restricted(countries=geo_countries)
  39. elif error_code in ('access.denied.missingpackage', 'invalid.token'):
  40. raise ExtractorError(
  41. 'This video is only available for registered users. You may want to use --cookies.', expected=True)
  42. raise ExtractorError(info['errors'][0]['detail'], expected=True)
  43. def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
  44. headers['Authorization'] = self._get_auth(disco_base, display_id, realm, False)
  45. def _download_video_playback_info(self, disco_base, video_id, headers):
  46. streaming = self._download_json(
  47. disco_base + 'playback/videoPlaybackInfo/' + video_id,
  48. video_id, headers=headers)['data']['attributes']['streaming']
  49. streaming_list = []
  50. for format_id, format_dict in streaming.items():
  51. streaming_list.append({
  52. 'type': format_id,
  53. 'url': format_dict.get('url'),
  54. })
  55. return streaming_list
  56. def _get_disco_api_info(self, url, display_id, disco_host, realm, country, domain=''):
  57. country = self.get_param('geo_bypass_country') or country
  58. geo_countries = [country.upper()]
  59. self._initialize_geo_bypass({
  60. 'countries': geo_countries,
  61. })
  62. disco_base = f'https://{disco_host}/'
  63. headers = {
  64. 'Referer': url,
  65. }
  66. self._update_disco_api_headers(headers, disco_base, display_id, realm)
  67. try:
  68. video = self._download_json(
  69. disco_base + 'content/videos/' + display_id, display_id,
  70. headers=headers, query={
  71. 'fields[channel]': 'name',
  72. 'fields[image]': 'height,src,width',
  73. 'fields[show]': 'name',
  74. 'fields[tag]': 'name',
  75. 'fields[video]': 'description,episodeNumber,name,publishStart,seasonNumber,videoDuration',
  76. 'include': 'images,primaryChannel,show,tags',
  77. })
  78. except ExtractorError as e:
  79. if isinstance(e.cause, HTTPError) and e.cause.status == 400:
  80. self._process_errors(e, geo_countries)
  81. raise
  82. video_id = video['data']['id']
  83. info = video['data']['attributes']
  84. title = info['name'].strip()
  85. formats = []
  86. subtitles = {}
  87. try:
  88. streaming = self._download_video_playback_info(
  89. disco_base, video_id, headers)
  90. except ExtractorError as e:
  91. if isinstance(e.cause, HTTPError) and e.cause.status == 403:
  92. self._process_errors(e, geo_countries)
  93. raise
  94. for format_dict in streaming:
  95. if not isinstance(format_dict, dict):
  96. continue
  97. format_url = format_dict.get('url')
  98. if not format_url:
  99. continue
  100. format_id = format_dict.get('type')
  101. ext = determine_ext(format_url)
  102. if format_id == 'dash' or ext == 'mpd':
  103. dash_fmts, dash_subs = self._extract_mpd_formats_and_subtitles(
  104. format_url, display_id, mpd_id='dash', fatal=False)
  105. formats.extend(dash_fmts)
  106. subtitles = self._merge_subtitles(subtitles, dash_subs)
  107. elif format_id == 'hls' or ext == 'm3u8':
  108. m3u8_fmts, m3u8_subs = self._extract_m3u8_formats_and_subtitles(
  109. format_url, display_id, 'mp4',
  110. entry_protocol='m3u8_native', m3u8_id='hls',
  111. fatal=False)
  112. formats.extend(m3u8_fmts)
  113. subtitles = self._merge_subtitles(subtitles, m3u8_subs)
  114. else:
  115. formats.append({
  116. 'url': format_url,
  117. 'format_id': format_id,
  118. })
  119. creator = series = None
  120. tags = []
  121. thumbnails = []
  122. included = video.get('included') or []
  123. if isinstance(included, list):
  124. for e in included:
  125. attributes = e.get('attributes')
  126. if not attributes:
  127. continue
  128. e_type = e.get('type')
  129. if e_type == 'channel':
  130. creator = attributes.get('name')
  131. elif e_type == 'image':
  132. src = attributes.get('src')
  133. if src:
  134. thumbnails.append({
  135. 'url': src,
  136. 'width': int_or_none(attributes.get('width')),
  137. 'height': int_or_none(attributes.get('height')),
  138. })
  139. if e_type == 'show':
  140. series = attributes.get('name')
  141. elif e_type == 'tag':
  142. name = attributes.get('name')
  143. if name:
  144. tags.append(name)
  145. return {
  146. 'id': video_id,
  147. 'display_id': display_id,
  148. 'title': title,
  149. 'description': strip_or_none(info.get('description')),
  150. 'duration': float_or_none(info.get('videoDuration'), 1000),
  151. 'timestamp': unified_timestamp(info.get('publishStart')),
  152. 'series': series,
  153. 'season_number': int_or_none(info.get('seasonNumber')),
  154. 'episode_number': int_or_none(info.get('episodeNumber')),
  155. 'creator': creator,
  156. 'tags': tags,
  157. 'thumbnails': thumbnails,
  158. 'formats': formats,
  159. 'subtitles': subtitles,
  160. 'http_headers': {
  161. 'referer': domain,
  162. },
  163. }
  164. class DPlayIE(DPlayBaseIE):
  165. _VALID_URL = r'''(?x)https?://
  166. (?P<domain>
  167. (?:www\.)?(?P<host>d
  168. (?:
  169. play\.(?P<country>dk|fi|jp|se|no)|
  170. iscoveryplus\.(?P<plus_country>dk|es|fi|it|se|no)
  171. )
  172. )|
  173. (?P<subdomain_country>es|it)\.dplay\.com
  174. )/[^/]+''' + DPlayBaseIE._PATH_REGEX
  175. _TESTS = [{
  176. # non geo restricted, via secure api, unsigned download hls URL
  177. 'url': 'https://www.dplay.se/videos/nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101',
  178. 'info_dict': {
  179. 'id': '13628',
  180. 'display_id': 'nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101',
  181. 'ext': 'mp4',
  182. 'title': 'Svensken lär sig njuta av livet',
  183. 'description': 'md5:d3819c9bccffd0fe458ca42451dd50d8',
  184. 'duration': 2649.856,
  185. 'timestamp': 1365453720,
  186. 'upload_date': '20130408',
  187. 'creator': 'Kanal 5',
  188. 'series': 'Nugammalt - 77 händelser som format Sverige',
  189. 'season_number': 1,
  190. 'episode_number': 1,
  191. },
  192. 'params': {
  193. 'skip_download': True,
  194. },
  195. }, {
  196. # geo restricted, via secure api, unsigned download hls URL
  197. 'url': 'http://www.dplay.dk/videoer/ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster',
  198. 'info_dict': {
  199. 'id': '104465',
  200. 'display_id': 'ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster',
  201. 'ext': 'mp4',
  202. 'title': 'Ted Bundy: Mind Of A Monster',
  203. 'description': 'md5:8b780f6f18de4dae631668b8a9637995',
  204. 'duration': 5290.027,
  205. 'timestamp': 1570694400,
  206. 'upload_date': '20191010',
  207. 'creator': 'ID - Investigation Discovery',
  208. 'series': 'Ted Bundy: Mind Of A Monster',
  209. 'season_number': 1,
  210. 'episode_number': 1,
  211. },
  212. 'params': {
  213. 'skip_download': True,
  214. },
  215. }, {
  216. # disco-api
  217. 'url': 'https://www.dplay.no/videoer/i-kongens-klr/sesong-1-episode-7',
  218. 'info_dict': {
  219. 'id': '40206',
  220. 'display_id': 'i-kongens-klr/sesong-1-episode-7',
  221. 'ext': 'mp4',
  222. 'title': 'Episode 7',
  223. 'description': 'md5:e3e1411b2b9aebeea36a6ec5d50c60cf',
  224. 'duration': 2611.16,
  225. 'timestamp': 1516726800,
  226. 'upload_date': '20180123',
  227. 'series': 'I kongens klær',
  228. 'season_number': 1,
  229. 'episode_number': 7,
  230. },
  231. 'params': {
  232. 'skip_download': True,
  233. },
  234. 'skip': 'Available for Premium users',
  235. }, {
  236. 'url': 'http://it.dplay.com/nove/biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij/',
  237. 'md5': '2b808ffb00fc47b884a172ca5d13053c',
  238. 'info_dict': {
  239. 'id': '6918',
  240. 'display_id': 'biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij',
  241. 'ext': 'mp4',
  242. 'title': 'Luigi Di Maio: la psicosi di Stanislawskij',
  243. 'description': 'md5:3c7a4303aef85868f867a26f5cc14813',
  244. 'thumbnail': r're:^https?://.*\.jpe?g',
  245. 'upload_date': '20160524',
  246. 'timestamp': 1464076800,
  247. 'series': 'Biografie imbarazzanti',
  248. 'season_number': 1,
  249. 'episode': 'Episode 1',
  250. 'episode_number': 1,
  251. },
  252. }, {
  253. 'url': 'https://es.dplay.com/dmax/la-fiebre-del-oro/temporada-8-episodio-1/',
  254. 'info_dict': {
  255. 'id': '21652',
  256. 'display_id': 'la-fiebre-del-oro/temporada-8-episodio-1',
  257. 'ext': 'mp4',
  258. 'title': 'Episodio 1',
  259. 'description': 'md5:b9dcff2071086e003737485210675f69',
  260. 'thumbnail': r're:^https?://.*\.png',
  261. 'upload_date': '20180709',
  262. 'timestamp': 1531173540,
  263. 'series': 'La fiebre del oro',
  264. 'season_number': 8,
  265. 'episode': 'Episode 1',
  266. 'episode_number': 1,
  267. },
  268. 'params': {
  269. 'skip_download': True,
  270. },
  271. }, {
  272. 'url': 'https://www.dplay.fi/videot/shifting-gears-with-aaron-kaufman/episode-16',
  273. 'only_matching': True,
  274. }, {
  275. 'url': 'https://www.dplay.jp/video/gold-rush/24086',
  276. 'only_matching': True,
  277. }, {
  278. 'url': 'https://www.discoveryplus.se/videos/nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101',
  279. 'only_matching': True,
  280. }, {
  281. 'url': 'https://www.discoveryplus.dk/videoer/ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster',
  282. 'only_matching': True,
  283. }, {
  284. 'url': 'https://www.discoveryplus.no/videoer/i-kongens-klr/sesong-1-episode-7',
  285. 'only_matching': True,
  286. }, {
  287. 'url': 'https://www.discoveryplus.it/videos/biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij',
  288. 'only_matching': True,
  289. }, {
  290. 'url': 'https://www.discoveryplus.es/videos/la-fiebre-del-oro/temporada-8-episodio-1',
  291. 'only_matching': True,
  292. }, {
  293. 'url': 'https://www.discoveryplus.fi/videot/shifting-gears-with-aaron-kaufman/episode-16',
  294. 'only_matching': True,
  295. }]
  296. def _real_extract(self, url):
  297. mobj = self._match_valid_url(url)
  298. display_id = mobj.group('id')
  299. domain = remove_start(mobj.group('domain'), 'www.')
  300. country = mobj.group('country') or mobj.group('subdomain_country') or mobj.group('plus_country')
  301. host = 'disco-api.' + domain if domain[0] == 'd' else 'eu2-prod.disco-api.com'
  302. return self._get_disco_api_info(
  303. url, display_id, host, 'dplay' + country, country, domain)
  304. class DiscoveryPlusBaseIE(DPlayBaseIE):
  305. """Subclasses must set _PRODUCT, _DISCO_API_PARAMS"""
  306. _DISCO_CLIENT_VER = '27.43.0'
  307. def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
  308. headers.update({
  309. 'x-disco-params': f'realm={realm},siteLookupKey={self._PRODUCT}',
  310. 'x-disco-client': f'WEB:UNKNOWN:{self._PRODUCT}:{self._DISCO_CLIENT_VER}',
  311. 'Authorization': self._get_auth(disco_base, display_id, realm),
  312. })
  313. def _download_video_playback_info(self, disco_base, video_id, headers):
  314. return self._download_json(
  315. disco_base + 'playback/v3/videoPlaybackInfo',
  316. video_id, headers=headers, data=json.dumps({
  317. 'deviceInfo': {
  318. 'adBlocker': False,
  319. 'drmSupported': False,
  320. },
  321. 'videoId': video_id,
  322. 'wisteriaProperties': {},
  323. }).encode())['data']['attributes']['streaming']
  324. def _real_extract(self, url):
  325. return self._get_disco_api_info(url, self._match_id(url), **self._DISCO_API_PARAMS)
  326. class HGTVDeIE(DiscoveryPlusBaseIE):
  327. _VALID_URL = r'https?://de\.hgtv\.com/sendungen' + DPlayBaseIE._PATH_REGEX
  328. _TESTS = [{
  329. 'url': 'https://de.hgtv.com/sendungen/mein-kleinstadt-traumhaus/vom-landleben-ins-loft',
  330. 'info_dict': {
  331. 'id': '7332936',
  332. 'ext': 'mp4',
  333. 'display_id': 'mein-kleinstadt-traumhaus/vom-landleben-ins-loft',
  334. 'title': 'Vom Landleben ins Loft',
  335. 'description': 'md5:e5f72c02c853970796dd3818f2e25745',
  336. 'episode': 'Episode 7',
  337. 'episode_number': 7,
  338. 'season': 'Season 7',
  339. 'season_number': 7,
  340. 'series': 'Mein Kleinstadt-Traumhaus',
  341. 'duration': 2645.0,
  342. 'timestamp': 1725998100,
  343. 'upload_date': '20240910',
  344. 'creators': ['HGTV'],
  345. 'tags': [],
  346. 'thumbnail': 'https://eu1-prod-images.disco-api.com/2024/08/09/82a386b9-c688-32c7-b9ff-0b13865f0bae.jpeg',
  347. },
  348. }]
  349. _PRODUCT = 'hgtv'
  350. _DISCO_API_PARAMS = {
  351. 'disco_host': 'eu1-prod.disco-api.com',
  352. 'realm': 'hgtv',
  353. 'country': 'de',
  354. }
  355. def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
  356. headers.update({
  357. 'x-disco-params': f'realm={realm}',
  358. 'x-disco-client': 'Alps:HyogaPlayer:0.0.0',
  359. 'Authorization': self._get_auth(disco_base, display_id, realm),
  360. })
  361. class GoDiscoveryIE(DiscoveryPlusBaseIE):
  362. _VALID_URL = r'https?://(?:go\.)?discovery\.com/video' + DPlayBaseIE._PATH_REGEX
  363. _TESTS = [{
  364. 'url': 'https://go.discovery.com/video/in-the-eye-of-the-storm-discovery-atve-us/trapped-in-a-twister',
  365. 'info_dict': {
  366. 'id': '5352642',
  367. 'display_id': 'in-the-eye-of-the-storm-discovery-atve-us/trapped-in-a-twister',
  368. 'ext': 'mp4',
  369. 'title': 'Trapped in a Twister',
  370. 'description': 'Twisters destroy Midwest towns, trapping spotters in the eye of the storm.',
  371. 'episode_number': 1,
  372. 'episode': 'Episode 1',
  373. 'season_number': 1,
  374. 'season': 'Season 1',
  375. 'series': 'In The Eye Of The Storm',
  376. 'duration': 2490.237,
  377. 'upload_date': '20240715',
  378. 'timestamp': 1721008800,
  379. 'tags': [],
  380. 'creators': ['Discovery'],
  381. 'thumbnail': 'https://us1-prod-images.disco-api.com/2024/07/10/5e39637d-cabf-3ab3-8e9a-f4e9d37bc036.jpeg',
  382. },
  383. }, {
  384. 'url': 'https://go.discovery.com/video/dirty-jobs-discovery-atve-us/rodbuster-galvanizer',
  385. 'info_dict': {
  386. 'id': '4164906',
  387. 'display_id': 'dirty-jobs-discovery-atve-us/rodbuster-galvanizer',
  388. 'ext': 'mp4',
  389. 'title': 'Rodbuster / Galvanizer',
  390. 'description': 'Mike installs rebar with a team of rodbusters, then he galvanizes steel.',
  391. 'season_number': 9,
  392. 'episode_number': 1,
  393. },
  394. 'skip': 'Available for Premium users',
  395. }, {
  396. 'url': 'https://discovery.com/video/dirty-jobs-discovery-atve-us/rodbuster-galvanizer',
  397. 'only_matching': True,
  398. }]
  399. _PRODUCT = 'dsc'
  400. _DISCO_API_PARAMS = {
  401. 'disco_host': 'us1-prod-direct.go.discovery.com',
  402. 'realm': 'go',
  403. 'country': 'us',
  404. }
  405. class TravelChannelIE(DiscoveryPlusBaseIE):
  406. _VALID_URL = r'https?://(?:watch\.)?travelchannel\.com/video' + DPlayBaseIE._PATH_REGEX
  407. _TESTS = [{
  408. 'url': 'https://watch.travelchannel.com/video/the-dead-files-travel-channel/protect-the-children',
  409. 'info_dict': {
  410. 'id': '4710177',
  411. 'display_id': 'the-dead-files-travel-channel/protect-the-children',
  412. 'ext': 'mp4',
  413. 'title': 'Protect the Children',
  414. 'description': 'An evil presence threatens an Ohio woman\'s children and marriage.',
  415. 'season_number': 14,
  416. 'season': 'Season 14',
  417. 'episode_number': 10,
  418. 'episode': 'Episode 10',
  419. 'series': 'The Dead Files',
  420. 'duration': 2550.481,
  421. 'timestamp': 1664510400,
  422. 'upload_date': '20220930',
  423. 'tags': [],
  424. 'creators': ['Travel Channel'],
  425. 'thumbnail': 'https://us1-prod-images.disco-api.com/2022/03/17/5e45eace-de5d-343a-9293-f400a2aa77d5.jpeg',
  426. },
  427. }, {
  428. 'url': 'https://watch.travelchannel.com/video/ghost-adventures-travel-channel/ghost-train-of-ely',
  429. 'info_dict': {
  430. 'id': '2220256',
  431. 'display_id': 'ghost-adventures-travel-channel/ghost-train-of-ely',
  432. 'ext': 'mp4',
  433. 'title': 'Ghost Train of Ely',
  434. 'description': 'The crew investigates the dark history of the Nevada Northern Railway.',
  435. 'season_number': 24,
  436. 'episode_number': 1,
  437. },
  438. 'skip': 'Available for Premium users',
  439. }, {
  440. 'url': 'https://watch.travelchannel.com/video/ghost-adventures-travel-channel/ghost-train-of-ely',
  441. 'only_matching': True,
  442. }]
  443. _PRODUCT = 'trav'
  444. _DISCO_API_PARAMS = {
  445. 'disco_host': 'us1-prod-direct.watch.travelchannel.com',
  446. 'realm': 'go',
  447. 'country': 'us',
  448. }
  449. class CookingChannelIE(DiscoveryPlusBaseIE):
  450. _VALID_URL = r'https?://(?:watch\.)?cookingchanneltv\.com/video' + DPlayBaseIE._PATH_REGEX
  451. _TESTS = [{
  452. 'url': 'https://watch.cookingchanneltv.com/video/bobbys-triple-threat-food-network-atve-us/titans-vs-marcus-samuelsson',
  453. 'info_dict': {
  454. 'id': '5350005',
  455. 'ext': 'mp4',
  456. 'display_id': 'bobbys-triple-threat-food-network-atve-us/titans-vs-marcus-samuelsson',
  457. 'title': 'Titans vs Marcus Samuelsson',
  458. 'description': 'Marcus Samuelsson throws his legendary global tricks at the Titans.',
  459. 'episode_number': 1,
  460. 'episode': 'Episode 1',
  461. 'season_number': 3,
  462. 'season': 'Season 3',
  463. 'series': 'Bobby\'s Triple Threat',
  464. 'duration': 2520.851,
  465. 'upload_date': '20240710',
  466. 'timestamp': 1720573200,
  467. 'tags': [],
  468. 'creators': ['Food Network'],
  469. 'thumbnail': 'https://us1-prod-images.disco-api.com/2024/07/04/529cd095-27ec-35c5-84e9-90ebd3e5d2da.jpeg',
  470. },
  471. }, {
  472. 'url': 'https://watch.cookingchanneltv.com/video/carnival-eats-cooking-channel/the-postman-always-brings-rice-2348634',
  473. 'info_dict': {
  474. 'id': '2348634',
  475. 'display_id': 'carnival-eats-cooking-channel/the-postman-always-brings-rice-2348634',
  476. 'ext': 'mp4',
  477. 'title': 'The Postman Always Brings Rice',
  478. 'description': 'Noah visits the Maui Fair and the Aurora Winter Festival in Vancouver.',
  479. 'season_number': 9,
  480. 'episode_number': 1,
  481. },
  482. 'skip': 'Available for Premium users',
  483. }, {
  484. 'url': 'https://watch.cookingchanneltv.com/video/carnival-eats-cooking-channel/the-postman-always-brings-rice-2348634',
  485. 'only_matching': True,
  486. }]
  487. _PRODUCT = 'cook'
  488. _DISCO_API_PARAMS = {
  489. 'disco_host': 'us1-prod-direct.watch.cookingchanneltv.com',
  490. 'realm': 'go',
  491. 'country': 'us',
  492. }
  493. class HGTVUsaIE(DiscoveryPlusBaseIE):
  494. _VALID_URL = r'https?://(?:watch\.)?hgtv\.com/video' + DPlayBaseIE._PATH_REGEX
  495. _TESTS = [{
  496. 'url': 'https://watch.hgtv.com/video/flip-or-flop-the-final-flip-hgtv-atve-us/flip-or-flop-the-final-flip',
  497. 'info_dict': {
  498. 'id': '5025585',
  499. 'display_id': 'flip-or-flop-the-final-flip-hgtv-atve-us/flip-or-flop-the-final-flip',
  500. 'ext': 'mp4',
  501. 'title': 'Flip or Flop: The Final Flip',
  502. 'description': 'Tarek and Christina are going their separate ways after one last flip!',
  503. 'series': 'Flip or Flop: The Final Flip',
  504. 'duration': 2580.644,
  505. 'upload_date': '20231101',
  506. 'timestamp': 1698811200,
  507. 'tags': [],
  508. 'creators': ['HGTV'],
  509. 'thumbnail': 'https://us1-prod-images.disco-api.com/2022/11/27/455caa6c-1462-3f14-b63d-a026d7a5e6d3.jpeg',
  510. },
  511. }, {
  512. 'url': 'https://watch.hgtv.com/video/home-inspector-joe-hgtv-atve-us/this-mold-house',
  513. 'info_dict': {
  514. 'id': '4289736',
  515. 'display_id': 'home-inspector-joe-hgtv-atve-us/this-mold-house',
  516. 'ext': 'mp4',
  517. 'title': 'This Mold House',
  518. 'description': 'Joe and Noel help take a familys dream home from hazardous to fabulous.',
  519. 'season_number': 1,
  520. 'episode_number': 1,
  521. },
  522. 'skip': 'Available for Premium users',
  523. }, {
  524. 'url': 'https://watch.hgtv.com/video/home-inspector-joe-hgtv-atve-us/this-mold-house',
  525. 'only_matching': True,
  526. }]
  527. _PRODUCT = 'hgtv'
  528. _DISCO_API_PARAMS = {
  529. 'disco_host': 'us1-prod-direct.watch.hgtv.com',
  530. 'realm': 'go',
  531. 'country': 'us',
  532. }
  533. class FoodNetworkIE(DiscoveryPlusBaseIE):
  534. _VALID_URL = r'https?://(?:watch\.)?foodnetwork\.com/video' + DPlayBaseIE._PATH_REGEX
  535. _TESTS = [{
  536. 'url': 'https://watch.foodnetwork.com/video/guys-grocery-games-food-network/wild-in-the-aisles',
  537. 'info_dict': {
  538. 'id': '2152549',
  539. 'display_id': 'guys-grocery-games-food-network/wild-in-the-aisles',
  540. 'ext': 'mp4',
  541. 'title': 'Wild in the Aisles',
  542. 'description': 'The chefs make spaghetti and meatballs with "Out of Stock" ingredients.',
  543. 'season_number': 1,
  544. 'season': 'Season 1',
  545. 'episode_number': 1,
  546. 'episode': 'Episode 1',
  547. 'series': 'Guy\'s Grocery Games',
  548. 'tags': [],
  549. 'creators': ['Food Network'],
  550. 'duration': 2520.651,
  551. 'upload_date': '20230623',
  552. 'timestamp': 1687492800,
  553. 'thumbnail': 'https://us1-prod-images.disco-api.com/2022/06/15/37fb5333-cad2-3dbb-af7c-c20ec77c89c6.jpeg',
  554. },
  555. }, {
  556. 'url': 'https://watch.foodnetwork.com/video/kids-baking-championship-food-network/float-like-a-butterfly',
  557. 'info_dict': {
  558. 'id': '4116449',
  559. 'display_id': 'kids-baking-championship-food-network/float-like-a-butterfly',
  560. 'ext': 'mp4',
  561. 'title': 'Float Like a Butterfly',
  562. 'description': 'The 12 kid bakers create colorful carved butterfly cakes.',
  563. 'season_number': 10,
  564. 'episode_number': 1,
  565. },
  566. 'skip': 'Available for Premium users',
  567. }, {
  568. 'url': 'https://watch.foodnetwork.com/video/kids-baking-championship-food-network/float-like-a-butterfly',
  569. 'only_matching': True,
  570. }]
  571. _PRODUCT = 'food'
  572. _DISCO_API_PARAMS = {
  573. 'disco_host': 'us1-prod-direct.watch.foodnetwork.com',
  574. 'realm': 'go',
  575. 'country': 'us',
  576. }
  577. class DestinationAmericaIE(DiscoveryPlusBaseIE):
  578. _VALID_URL = r'https?://(?:www\.)?destinationamerica\.com/video' + DPlayBaseIE._PATH_REGEX
  579. _TESTS = [{
  580. 'url': 'https://www.destinationamerica.com/video/bbq-pit-wars-destination-america/smoke-on-the-water',
  581. 'info_dict': {
  582. 'id': '2218409',
  583. 'display_id': 'bbq-pit-wars-destination-america/smoke-on-the-water',
  584. 'ext': 'mp4',
  585. 'title': 'Smoke on the Water',
  586. 'description': 'The pitmasters head to Georgia for the Smoke on the Water BBQ Festival.',
  587. 'season_number': 2,
  588. 'season': 'Season 2',
  589. 'episode_number': 1,
  590. 'episode': 'Episode 1',
  591. 'series': 'BBQ Pit Wars',
  592. 'tags': [],
  593. 'creators': ['Destination America'],
  594. 'duration': 2614.878,
  595. 'upload_date': '20230623',
  596. 'timestamp': 1687492800,
  597. 'thumbnail': 'https://us1-prod-images.disco-api.com/2020/05/11/c0f8e85d-9a10-3e6f-8e43-f6faafa81ba2.jpeg',
  598. },
  599. }, {
  600. 'url': 'https://www.destinationamerica.com/video/alaska-monsters-destination-america-atve-us/central-alaskas-bigfoot',
  601. 'info_dict': {
  602. 'id': '4210904',
  603. 'display_id': 'alaska-monsters-destination-america-atve-us/central-alaskas-bigfoot',
  604. 'ext': 'mp4',
  605. 'title': 'Central Alaskas Bigfoot',
  606. 'description': 'A team heads to central Alaska to investigate an aggressive Bigfoot.',
  607. 'season_number': 1,
  608. 'episode_number': 1,
  609. },
  610. 'skip': 'Available for Premium users',
  611. }, {
  612. 'url': 'https://www.destinationamerica.com/video/alaska-monsters-destination-america-atve-us/central-alaskas-bigfoot',
  613. 'only_matching': True,
  614. }]
  615. _PRODUCT = 'dam'
  616. _DISCO_API_PARAMS = {
  617. 'disco_host': 'us1-prod-direct.destinationamerica.com',
  618. 'realm': 'go',
  619. 'country': 'us',
  620. }
  621. class InvestigationDiscoveryIE(DiscoveryPlusBaseIE):
  622. _VALID_URL = r'https?://(?:www\.)?investigationdiscovery\.com/video' + DPlayBaseIE._PATH_REGEX
  623. _TESTS = [{
  624. 'url': 'https://www.investigationdiscovery.com/video/deadly-influence-the-social-media-murders-investigation-discovery-atve-us/rip-bianca',
  625. 'info_dict': {
  626. 'id': '5341132',
  627. 'display_id': 'deadly-influence-the-social-media-murders-investigation-discovery-atve-us/rip-bianca',
  628. 'ext': 'mp4',
  629. 'title': 'RIP Bianca',
  630. 'description': 'A teenage influencer discovers an online world of threat, harm and danger.',
  631. 'season_number': 1,
  632. 'season': 'Season 1',
  633. 'episode_number': 3,
  634. 'episode': 'Episode 3',
  635. 'series': 'Deadly Influence: The Social Media Murders',
  636. 'creators': ['Investigation Discovery'],
  637. 'tags': [],
  638. 'duration': 2490.888,
  639. 'upload_date': '20240618',
  640. 'timestamp': 1718672400,
  641. 'thumbnail': 'https://us1-prod-images.disco-api.com/2024/06/15/b567c774-9e44-3c6c-b0ba-db860a73e812.jpeg',
  642. },
  643. }, {
  644. 'url': 'https://www.investigationdiscovery.com/video/unmasked-investigation-discovery/the-killer-clown',
  645. 'info_dict': {
  646. 'id': '2139409',
  647. 'display_id': 'unmasked-investigation-discovery/the-killer-clown',
  648. 'ext': 'mp4',
  649. 'title': 'The Killer Clown',
  650. 'description': 'A wealthy Florida woman is fatally shot in the face by a clown at her door.',
  651. 'season_number': 1,
  652. 'episode_number': 1,
  653. },
  654. 'skip': 'Available for Premium users',
  655. }, {
  656. 'url': 'https://www.investigationdiscovery.com/video/unmasked-investigation-discovery/the-killer-clown',
  657. 'only_matching': True,
  658. }]
  659. _PRODUCT = 'ids'
  660. _DISCO_API_PARAMS = {
  661. 'disco_host': 'us1-prod-direct.investigationdiscovery.com',
  662. 'realm': 'go',
  663. 'country': 'us',
  664. }
  665. class AmHistoryChannelIE(DiscoveryPlusBaseIE):
  666. _VALID_URL = r'https?://(?:www\.)?ahctv\.com/video' + DPlayBaseIE._PATH_REGEX
  667. _TESTS = [{
  668. 'url': 'https://www.ahctv.com/video/blood-and-fury-americas-civil-war-ahc/battle-of-bull-run',
  669. 'info_dict': {
  670. 'id': '2139199',
  671. 'display_id': 'blood-and-fury-americas-civil-war-ahc/battle-of-bull-run',
  672. 'ext': 'mp4',
  673. 'title': 'Battle of Bull Run',
  674. 'description': 'Two untested armies clash in the first real battle of the Civil War.',
  675. 'season_number': 1,
  676. 'season': 'Season 1',
  677. 'episode_number': 1,
  678. 'episode': 'Episode 1',
  679. 'series': 'Blood and Fury: America\'s Civil War',
  680. 'duration': 2612.509,
  681. 'upload_date': '20220923',
  682. 'timestamp': 1663905600,
  683. 'creators': ['AHC'],
  684. 'tags': [],
  685. 'thumbnail': 'https://us1-prod-images.disco-api.com/2020/05/11/4af61bd7-d705-3108-82c4-1a6e541e20fa.jpeg',
  686. },
  687. }, {
  688. 'url': 'https://www.ahctv.com/video/modern-sniper-ahc/army',
  689. 'info_dict': {
  690. 'id': '2309730',
  691. 'display_id': 'modern-sniper-ahc/army',
  692. 'ext': 'mp4',
  693. 'title': 'Army',
  694. 'description': 'Snipers today face challenges their predecessors couldve only dreamed of.',
  695. 'season_number': 1,
  696. 'episode_number': 1,
  697. },
  698. 'skip': 'Available for Premium users',
  699. }, {
  700. 'url': 'https://www.ahctv.com/video/modern-sniper-ahc/army',
  701. 'only_matching': True,
  702. }]
  703. _PRODUCT = 'ahc'
  704. _DISCO_API_PARAMS = {
  705. 'disco_host': 'us1-prod-direct.ahctv.com',
  706. 'realm': 'go',
  707. 'country': 'us',
  708. }
  709. class ScienceChannelIE(DiscoveryPlusBaseIE):
  710. _VALID_URL = r'https?://(?:www\.)?sciencechannel\.com/video' + DPlayBaseIE._PATH_REGEX
  711. _TESTS = [{
  712. 'url': 'https://www.sciencechannel.com/video/spaces-deepest-secrets-science-atve-us/mystery-of-the-dead-planets',
  713. 'info_dict': {
  714. 'id': '2347335',
  715. 'display_id': 'spaces-deepest-secrets-science-atve-us/mystery-of-the-dead-planets',
  716. 'ext': 'mp4',
  717. 'title': 'Mystery of the Dead Planets',
  718. 'description': 'Astronomers unmask the truly destructive nature of the cosmos.',
  719. 'season_number': 7,
  720. 'season': 'Season 7',
  721. 'episode_number': 1,
  722. 'episode': 'Episode 1',
  723. 'series': 'Space\'s Deepest Secrets',
  724. 'duration': 2524.989,
  725. 'upload_date': '20230128',
  726. 'timestamp': 1674882000,
  727. 'creators': ['Science'],
  728. 'tags': [],
  729. 'thumbnail': 'https://us1-prod-images.disco-api.com/2021/03/30/3796829d-aead-3f9a-bd8d-e49048b3cdca.jpeg',
  730. },
  731. }, {
  732. 'url': 'https://www.sciencechannel.com/video/strangest-things-science-atve-us/nazi-mystery-machine',
  733. 'info_dict': {
  734. 'id': '2842849',
  735. 'display_id': 'strangest-things-science-atve-us/nazi-mystery-machine',
  736. 'ext': 'mp4',
  737. 'title': 'Nazi Mystery Machine',
  738. 'description': 'Experts investigate the secrets of a revolutionary encryption machine.',
  739. 'season_number': 1,
  740. 'episode_number': 1,
  741. },
  742. 'skip': 'Available for Premium users',
  743. }, {
  744. 'url': 'https://www.sciencechannel.com/video/strangest-things-science-atve-us/nazi-mystery-machine',
  745. 'only_matching': True,
  746. }]
  747. _PRODUCT = 'sci'
  748. _DISCO_API_PARAMS = {
  749. 'disco_host': 'us1-prod-direct.sciencechannel.com',
  750. 'realm': 'go',
  751. 'country': 'us',
  752. }
  753. class DiscoveryLifeIE(DiscoveryPlusBaseIE):
  754. _VALID_URL = r'https?://(?:www\.)?discoverylife\.com/video' + DPlayBaseIE._PATH_REGEX
  755. _TESTS = [{
  756. 'url': 'https://www.discoverylife.com/video/er-files-discovery-life-atve-us/sweet-charity',
  757. 'info_dict': {
  758. 'id': '2347614',
  759. 'display_id': 'er-files-discovery-life-atve-us/sweet-charity',
  760. 'ext': 'mp4',
  761. 'title': 'Sweet Charity',
  762. 'description': 'The staff at Charity Hospital treat a serious foot infection.',
  763. 'season_number': 1,
  764. 'season': 'Season 1',
  765. 'episode_number': 1,
  766. 'episode': 'Episode 1',
  767. 'series': 'ER Files',
  768. 'duration': 2364.261,
  769. 'upload_date': '20230721',
  770. 'timestamp': 1689912000,
  771. 'creators': ['Discovery Life'],
  772. 'tags': [],
  773. 'thumbnail': 'https://us1-prod-images.disco-api.com/2021/03/16/4b6f0124-360b-3546-b6a4-5552db886b86.jpeg',
  774. },
  775. }, {
  776. 'url': 'https://www.discoverylife.com/video/surviving-death-discovery-life-atve-us/bodily-trauma',
  777. 'info_dict': {
  778. 'id': '2218238',
  779. 'display_id': 'surviving-death-discovery-life-atve-us/bodily-trauma',
  780. 'ext': 'mp4',
  781. 'title': 'Bodily Trauma',
  782. 'description': 'Meet three people who tested the limits of the human body.',
  783. 'season_number': 1,
  784. 'episode_number': 2,
  785. },
  786. 'skip': 'Available for Premium users',
  787. }, {
  788. 'url': 'https://www.discoverylife.com/video/surviving-death-discovery-life-atve-us/bodily-trauma',
  789. 'only_matching': True,
  790. }]
  791. _PRODUCT = 'dlf'
  792. _DISCO_API_PARAMS = {
  793. 'disco_host': 'us1-prod-direct.discoverylife.com',
  794. 'realm': 'go',
  795. 'country': 'us',
  796. }
  797. class AnimalPlanetIE(DiscoveryPlusBaseIE):
  798. _VALID_URL = r'https?://(?:www\.)?animalplanet\.com/video' + DPlayBaseIE._PATH_REGEX
  799. _TESTS = [{
  800. 'url': 'https://www.animalplanet.com/video/mysterious-creatures-with-forrest-galante-animal-planet-atve-us/the-demon-of-peru',
  801. 'info_dict': {
  802. 'id': '4650835',
  803. 'display_id': 'mysterious-creatures-with-forrest-galante-animal-planet-atve-us/the-demon-of-peru',
  804. 'ext': 'mp4',
  805. 'title': 'The Demon of Peru',
  806. 'description': 'In Peru, a farming village is being terrorized by a “man-like beast.”',
  807. 'season_number': 1,
  808. 'season': 'Season 1',
  809. 'episode_number': 4,
  810. 'episode': 'Episode 4',
  811. 'series': 'Mysterious Creatures with Forrest Galante',
  812. 'duration': 2490.488,
  813. 'upload_date': '20230111',
  814. 'timestamp': 1673413200,
  815. 'creators': ['Animal Planet'],
  816. 'tags': [],
  817. 'thumbnail': 'https://us1-prod-images.disco-api.com/2022/03/01/6dbaa833-9a2e-3fee-9381-c19eddf67c0c.jpeg',
  818. },
  819. }, {
  820. 'url': 'https://www.animalplanet.com/video/north-woods-law-animal-planet/squirrel-showdown',
  821. 'info_dict': {
  822. 'id': '3338923',
  823. 'display_id': 'north-woods-law-animal-planet/squirrel-showdown',
  824. 'ext': 'mp4',
  825. 'title': 'Squirrel Showdown',
  826. 'description': 'A woman is suspected of being in possession of flying squirrel kits.',
  827. 'season_number': 16,
  828. 'episode_number': 11,
  829. },
  830. 'skip': 'Available for Premium users',
  831. }, {
  832. 'url': 'https://www.animalplanet.com/video/north-woods-law-animal-planet/squirrel-showdown',
  833. 'only_matching': True,
  834. }]
  835. _PRODUCT = 'apl'
  836. _DISCO_API_PARAMS = {
  837. 'disco_host': 'us1-prod-direct.animalplanet.com',
  838. 'realm': 'go',
  839. 'country': 'us',
  840. }
  841. class TLCIE(DiscoveryPlusBaseIE):
  842. _VALID_URL = r'https?://(?:go\.)?tlc\.com/video' + DPlayBaseIE._PATH_REGEX
  843. _TESTS = [{
  844. 'url': 'https://go.tlc.com/video/90-day-the-last-resort-tlc-atve-us/the-last-chance',
  845. 'info_dict': {
  846. 'id': '5186422',
  847. 'display_id': '90-day-the-last-resort-tlc-atve-us/the-last-chance',
  848. 'ext': 'mp4',
  849. 'title': 'The Last Chance',
  850. 'description': 'Infidelity shakes Kalani and Asuelu\'s world, and Angela threatens divorce.',
  851. 'season_number': 1,
  852. 'season': 'Season 1',
  853. 'episode_number': 1,
  854. 'episode': 'Episode 1',
  855. 'series': '90 Day: The Last Resort',
  856. 'duration': 5123.91,
  857. 'upload_date': '20230815',
  858. 'timestamp': 1692061200,
  859. 'creators': ['TLC'],
  860. 'tags': [],
  861. 'thumbnail': 'https://us1-prod-images.disco-api.com/2023/08/08/0ee367e2-ac76-334d-bf23-dbf796696a24.jpeg',
  862. },
  863. }, {
  864. 'url': 'https://go.tlc.com/video/my-600-lb-life-tlc/melissas-story-part-1',
  865. 'info_dict': {
  866. 'id': '2206540',
  867. 'display_id': 'my-600-lb-life-tlc/melissas-story-part-1',
  868. 'ext': 'mp4',
  869. 'title': 'Melissas Story (Part 1)',
  870. 'description': 'At 650 lbs, Melissa is ready to begin her seven-year weight loss journey.',
  871. 'season_number': 1,
  872. 'episode_number': 1,
  873. },
  874. 'skip': 'Available for Premium users',
  875. }, {
  876. 'url': 'https://go.tlc.com/video/my-600-lb-life-tlc/melissas-story-part-1',
  877. 'only_matching': True,
  878. }]
  879. _PRODUCT = 'tlc'
  880. _DISCO_API_PARAMS = {
  881. 'disco_host': 'us1-prod-direct.tlc.com',
  882. 'realm': 'go',
  883. 'country': 'us',
  884. }
  885. class DiscoveryPlusIE(DiscoveryPlusBaseIE):
  886. _VALID_URL = r'https?://(?:www\.)?discoveryplus\.com/(?!it/)(?:(?P<country>[a-z]{2})/)?video(?:/sport|/olympics)?' + DPlayBaseIE._PATH_REGEX
  887. _TESTS = [{
  888. 'url': 'https://www.discoveryplus.com/video/property-brothers-forever-home/food-and-family',
  889. 'info_dict': {
  890. 'id': '1140794',
  891. 'display_id': 'property-brothers-forever-home/food-and-family',
  892. 'ext': 'mp4',
  893. 'title': 'Food and Family',
  894. 'description': 'The brothers help a Richmond family expand their single-level home.',
  895. 'duration': 2583.113,
  896. 'timestamp': 1609304400,
  897. 'upload_date': '20201230',
  898. 'creator': 'HGTV',
  899. 'series': 'Property Brothers: Forever Home',
  900. 'season_number': 1,
  901. 'episode_number': 1,
  902. },
  903. 'skip': 'Available for Premium users',
  904. }, {
  905. 'url': 'https://discoveryplus.com/ca/video/bering-sea-gold-discovery-ca/goldslingers',
  906. 'only_matching': True,
  907. }, {
  908. 'url': 'https://www.discoveryplus.com/gb/video/sport/eurosport-1-british-eurosport-1-british-sport/6-hours-of-spa-review',
  909. 'only_matching': True,
  910. }, {
  911. 'url': 'https://www.discoveryplus.com/gb/video/olympics/dplus-sport-dplus-sport-sport/rugby-sevens-australia-samoa',
  912. 'only_matching': True,
  913. }]
  914. _PRODUCT = None
  915. _DISCO_API_PARAMS = None
  916. def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
  917. headers.update({
  918. 'x-disco-params': f'realm={realm},siteLookupKey={self._PRODUCT}',
  919. 'x-disco-client': f'WEB:UNKNOWN:dplus_us:{self._DISCO_CLIENT_VER}',
  920. 'Authorization': self._get_auth(disco_base, display_id, realm),
  921. })
  922. def _real_extract(self, url):
  923. video_id, country = self._match_valid_url(url).group('id', 'country')
  924. if not country:
  925. country = 'us'
  926. self._PRODUCT = f'dplus_{country}'
  927. if country in ('br', 'ca', 'us'):
  928. self._DISCO_API_PARAMS = {
  929. 'disco_host': 'us1-prod-direct.discoveryplus.com',
  930. 'realm': 'go',
  931. 'country': country,
  932. }
  933. else:
  934. self._DISCO_API_PARAMS = {
  935. 'disco_host': 'eu1-prod-direct.discoveryplus.com',
  936. 'realm': 'dplay',
  937. 'country': country,
  938. }
  939. return self._get_disco_api_info(url, video_id, **self._DISCO_API_PARAMS)
  940. class DiscoveryPlusIndiaIE(DiscoveryPlusBaseIE):
  941. _VALID_URL = r'https?://(?:www\.)?discoveryplus\.in/videos?' + DPlayBaseIE._PATH_REGEX
  942. _TESTS = [{
  943. 'url': 'https://www.discoveryplus.in/videos/how-do-they-do-it/fugu-and-more?seasonId=8&type=EPISODE',
  944. 'info_dict': {
  945. 'id': '27104',
  946. 'ext': 'mp4',
  947. 'display_id': 'how-do-they-do-it/fugu-and-more',
  948. 'title': 'Fugu and More',
  949. 'description': 'The Japanese catch, prepare and eat the deadliest fish on the planet.',
  950. 'duration': 1319.32,
  951. 'timestamp': 1582309800,
  952. 'upload_date': '20200221',
  953. 'series': 'How Do They Do It?',
  954. 'season_number': 8,
  955. 'episode_number': 2,
  956. 'creator': 'Discovery Channel',
  957. 'thumbnail': r're:https://.+\.jpeg',
  958. 'episode': 'Episode 2',
  959. 'season': 'Season 8',
  960. 'tags': [],
  961. },
  962. 'params': {
  963. 'skip_download': True,
  964. },
  965. }]
  966. _PRODUCT = 'dplus-india'
  967. _DISCO_API_PARAMS = {
  968. 'disco_host': 'ap2-prod-direct.discoveryplus.in',
  969. 'realm': 'dplusindia',
  970. 'country': 'in',
  971. 'domain': 'https://www.discoveryplus.in/',
  972. }
  973. def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
  974. headers.update({
  975. 'x-disco-params': f'realm={realm}',
  976. 'x-disco-client': f'WEB:UNKNOWN:{self._PRODUCT}:17.0.0',
  977. 'Authorization': self._get_auth(disco_base, display_id, realm),
  978. })
  979. class DiscoveryNetworksDeIE(DiscoveryPlusBaseIE):
  980. _VALID_URL = r'https?://(?:www\.)?(?P<domain>(?:tlc|dmax)\.de|dplay\.co\.uk)/(?:programme|show|sendungen)/(?P<programme>[^/]+)/(?:video/)?(?P<alternate_id>[^/]+)'
  981. _TESTS = [{
  982. 'url': 'https://dmax.de/sendungen/goldrausch-in-australien/german-gold',
  983. 'info_dict': {
  984. 'id': '4756322',
  985. 'ext': 'mp4',
  986. 'title': 'German Gold',
  987. 'description': 'md5:f3073306553a8d9b40e6ac4cdbf09fc6',
  988. 'display_id': 'goldrausch-in-australien/german-gold',
  989. 'episode': 'Episode 1',
  990. 'episode_number': 1,
  991. 'season': 'Season 5',
  992. 'season_number': 5,
  993. 'series': 'Goldrausch in Australien',
  994. 'duration': 2648.0,
  995. 'upload_date': '20230517',
  996. 'timestamp': 1684357500,
  997. 'creators': ['DMAX'],
  998. 'thumbnail': 'https://eu1-prod-images.disco-api.com/2023/05/09/f72fb510-7992-3b12-af7f-f16a2c22d1e3.jpeg',
  999. 'tags': ['schatzsucher', 'schatz', 'nugget', 'bodenschätze', 'down under', 'australien', 'goldrausch'],
  1000. },
  1001. 'params': {'skip_download': 'm3u8'},
  1002. }, {
  1003. 'url': 'https://www.tlc.de/programme/breaking-amish/video/die-welt-da-drauen/DCB331270001100',
  1004. 'info_dict': {
  1005. 'id': '78867',
  1006. 'ext': 'mp4',
  1007. 'title': 'Die Welt da draußen',
  1008. 'description': 'md5:61033c12b73286e409d99a41742ef608',
  1009. 'timestamp': 1554069600,
  1010. 'upload_date': '20190331',
  1011. 'creator': 'TLC',
  1012. 'season': 'Season 1',
  1013. 'series': 'Breaking Amish',
  1014. 'episode_number': 1,
  1015. 'tags': ['new york', 'großstadt', 'amische', 'landleben', 'modern', 'infos', 'tradition', 'herausforderung'],
  1016. 'display_id': 'breaking-amish/die-welt-da-drauen',
  1017. 'episode': 'Episode 1',
  1018. 'duration': 2625.024,
  1019. 'season_number': 1,
  1020. 'thumbnail': r're:https://.+\.jpg',
  1021. },
  1022. 'skip': '404 Not Found',
  1023. }, {
  1024. 'url': 'https://www.dmax.de/programme/dmax-highlights/video/tuning-star-sidney-hoffmann-exklusiv-bei-dmax/191023082312316',
  1025. 'only_matching': True,
  1026. }, {
  1027. 'url': 'https://www.dplay.co.uk/show/ghost-adventures/video/hotel-leger-103620/EHD_280313B',
  1028. 'only_matching': True,
  1029. }, {
  1030. 'url': 'https://tlc.de/sendungen/breaking-amish/die-welt-da-drauen/',
  1031. 'only_matching': True,
  1032. }]
  1033. def _real_extract(self, url):
  1034. domain, programme, alternate_id = self._match_valid_url(url).groups()
  1035. country = 'GB' if domain == 'dplay.co.uk' else 'DE'
  1036. realm = 'questuk' if country == 'GB' else domain.replace('.', '')
  1037. return self._get_disco_api_info(
  1038. url, f'{programme}/{alternate_id}', 'eu1-prod.disco-api.com', realm, country)
  1039. def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
  1040. headers.update({
  1041. 'x-disco-params': f'realm={realm}',
  1042. 'x-disco-client': 'Alps:HyogaPlayer:0.0.0',
  1043. 'Authorization': self._get_auth(disco_base, display_id, realm),
  1044. })
  1045. class DiscoveryPlusShowBaseIE(DPlayBaseIE):
  1046. def _entries(self, show_name):
  1047. headers = {
  1048. 'x-disco-client': self._X_CLIENT,
  1049. 'x-disco-params': f'realm={self._REALM}',
  1050. 'referer': self._DOMAIN,
  1051. 'Authentication': self._get_auth(self._BASE_API, None, self._REALM),
  1052. }
  1053. show_json = self._download_json(
  1054. f'{self._BASE_API}cms/routes/{self._SHOW_STR}/{show_name}?include=default',
  1055. video_id=show_name, headers=headers)['included'][self._INDEX]['attributes']['component']
  1056. show_id = show_json['mandatoryParams'].split('=')[-1]
  1057. season_url = self._BASE_API + 'content/videos?sort=episodeNumber&filter[seasonNumber]={}&filter[show.id]={}&page[size]=100&page[number]={}'
  1058. for season in show_json['filters'][0]['options']:
  1059. season_id = season['id']
  1060. total_pages, page_num = 1, 0
  1061. while page_num < total_pages:
  1062. season_json = self._download_json(
  1063. season_url.format(season_id, show_id, str(page_num + 1)), show_name, headers=headers,
  1064. note='Downloading season {} JSON metadata{}'.format(season_id, f' page {page_num}' if page_num else ''))
  1065. if page_num == 0:
  1066. total_pages = try_get(season_json, lambda x: x['meta']['totalPages'], int) or 1
  1067. episodes_json = season_json['data']
  1068. for episode in episodes_json:
  1069. video_path = episode['attributes']['path']
  1070. yield self.url_result(
  1071. f'{self._DOMAIN}videos/{video_path}',
  1072. ie=self._VIDEO_IE.ie_key(), video_id=episode.get('id') or video_path)
  1073. page_num += 1
  1074. def _real_extract(self, url):
  1075. show_name = self._match_valid_url(url).group('show_name')
  1076. return self.playlist_result(self._entries(show_name), playlist_id=show_name)
  1077. class DiscoveryPlusItalyIE(DiscoveryPlusBaseIE):
  1078. _VALID_URL = r'https?://(?:www\.)?discoveryplus\.com/it/video(?:/sport|/olympics)?' + DPlayBaseIE._PATH_REGEX
  1079. _TESTS = [{
  1080. 'url': 'https://www.discoveryplus.com/it/video/i-signori-della-neve/stagione-2-episodio-1-i-preparativi',
  1081. 'only_matching': True,
  1082. }, {
  1083. 'url': 'https://www.discoveryplus.com/it/video/super-benny/trailer',
  1084. 'only_matching': True,
  1085. }, {
  1086. 'url': 'https://www.discoveryplus.com/it/video/olympics/dplus-sport-dplus-sport-sport/water-polo-greece-italy',
  1087. 'only_matching': True,
  1088. }, {
  1089. 'url': 'https://www.discoveryplus.com/it/video/sport/dplus-sport-dplus-sport-sport/lisa-vittozzi-allinferno-e-ritorno',
  1090. 'only_matching': True,
  1091. }]
  1092. _PRODUCT = 'dplus_it'
  1093. _DISCO_API_PARAMS = {
  1094. 'disco_host': 'eu1-prod-direct.discoveryplus.com',
  1095. 'realm': 'dplay',
  1096. 'country': 'it',
  1097. }
  1098. def _update_disco_api_headers(self, headers, disco_base, display_id, realm):
  1099. headers.update({
  1100. 'x-disco-params': f'realm={realm},siteLookupKey={self._PRODUCT}',
  1101. 'x-disco-client': f'WEB:UNKNOWN:dplus_us:{self._DISCO_CLIENT_VER}',
  1102. 'Authorization': self._get_auth(disco_base, display_id, realm),
  1103. })
  1104. class DiscoveryPlusItalyShowIE(DiscoveryPlusShowBaseIE):
  1105. _VALID_URL = r'https?://(?:www\.)?discoveryplus\.it/programmi/(?P<show_name>[^/]+)/?(?:[?#]|$)'
  1106. _TESTS = [{
  1107. 'url': 'https://www.discoveryplus.it/programmi/deal-with-it-stai-al-gioco',
  1108. 'playlist_mincount': 168,
  1109. 'info_dict': {
  1110. 'id': 'deal-with-it-stai-al-gioco',
  1111. },
  1112. }]
  1113. _BASE_API = 'https://disco-api.discoveryplus.it/'
  1114. _DOMAIN = 'https://www.discoveryplus.it/'
  1115. _X_CLIENT = 'WEB:UNKNOWN:dplay-client:2.6.0'
  1116. _REALM = 'dplayit'
  1117. _SHOW_STR = 'programmi'
  1118. _INDEX = 1
  1119. _VIDEO_IE = DPlayIE
  1120. class DiscoveryPlusIndiaShowIE(DiscoveryPlusShowBaseIE):
  1121. _VALID_URL = r'https?://(?:www\.)?discoveryplus\.in/show/(?P<show_name>[^/]+)/?(?:[?#]|$)'
  1122. _TESTS = [{
  1123. 'url': 'https://www.discoveryplus.in/show/how-do-they-do-it',
  1124. 'playlist_mincount': 140,
  1125. 'info_dict': {
  1126. 'id': 'how-do-they-do-it',
  1127. },
  1128. }]
  1129. _BASE_API = 'https://ap2-prod-direct.discoveryplus.in/'
  1130. _DOMAIN = 'https://www.discoveryplus.in/'
  1131. _X_CLIENT = 'WEB:UNKNOWN:dplus-india:prod'
  1132. _REALM = 'dplusindia'
  1133. _SHOW_STR = 'show'
  1134. _INDEX = 4
  1135. _VIDEO_IE = DiscoveryPlusIndiaIE