You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

247 lines
9.6 KiB

  1. # coding: utf-8
  2. from __future__ import unicode_literals
  3. import re
  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. unified_timestamp,
  12. )
  13. class DPlayIE(InfoExtractor):
  14. _VALID_URL = r'''(?x)https?://
  15. (?P<domain>
  16. (?:www\.)?(?P<host>dplay\.(?P<country>dk|fi|jp|se|no))|
  17. (?P<subdomain_country>es|it)\.dplay\.com
  18. )/[^/]+/(?P<id>[^/]+/[^/?#]+)'''
  19. _TESTS = [{
  20. # non geo restricted, via secure api, unsigned download hls URL
  21. 'url': 'https://www.dplay.se/videos/nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101',
  22. 'info_dict': {
  23. 'id': '13628',
  24. 'display_id': 'nugammalt-77-handelser-som-format-sverige/nugammalt-77-handelser-som-format-sverige-101',
  25. 'ext': 'mp4',
  26. 'title': 'Svensken lär sig njuta av livet',
  27. 'description': 'md5:d3819c9bccffd0fe458ca42451dd50d8',
  28. 'duration': 2649.856,
  29. 'timestamp': 1365453720,
  30. 'upload_date': '20130408',
  31. 'creator': 'Kanal 5',
  32. 'series': 'Nugammalt - 77 händelser som format Sverige',
  33. 'season_number': 1,
  34. 'episode_number': 1,
  35. },
  36. 'params': {
  37. 'format': 'bestvideo',
  38. 'skip_download': True,
  39. },
  40. }, {
  41. # geo restricted, via secure api, unsigned download hls URL
  42. 'url': 'http://www.dplay.dk/videoer/ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster',
  43. 'info_dict': {
  44. 'id': '104465',
  45. 'display_id': 'ted-bundy-mind-of-a-monster/ted-bundy-mind-of-a-monster',
  46. 'ext': 'mp4',
  47. 'title': 'Ted Bundy: Mind Of A Monster',
  48. 'description': 'md5:8b780f6f18de4dae631668b8a9637995',
  49. 'duration': 5290.027,
  50. 'timestamp': 1570694400,
  51. 'upload_date': '20191010',
  52. 'creator': 'ID - Investigation Discovery',
  53. 'series': 'Ted Bundy: Mind Of A Monster',
  54. 'season_number': 1,
  55. 'episode_number': 1,
  56. },
  57. 'params': {
  58. 'format': 'bestvideo',
  59. 'skip_download': True,
  60. },
  61. }, {
  62. # disco-api
  63. 'url': 'https://www.dplay.no/videoer/i-kongens-klr/sesong-1-episode-7',
  64. 'info_dict': {
  65. 'id': '40206',
  66. 'display_id': 'i-kongens-klr/sesong-1-episode-7',
  67. 'ext': 'mp4',
  68. 'title': 'Episode 7',
  69. 'description': 'md5:e3e1411b2b9aebeea36a6ec5d50c60cf',
  70. 'duration': 2611.16,
  71. 'timestamp': 1516726800,
  72. 'upload_date': '20180123',
  73. 'series': 'I kongens klær',
  74. 'season_number': 1,
  75. 'episode_number': 7,
  76. },
  77. 'params': {
  78. 'format': 'bestvideo',
  79. 'skip_download': True,
  80. },
  81. 'skip': 'Available for Premium users',
  82. }, {
  83. 'url': 'http://it.dplay.com/nove/biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij/',
  84. 'md5': '2b808ffb00fc47b884a172ca5d13053c',
  85. 'info_dict': {
  86. 'id': '6918',
  87. 'display_id': 'biografie-imbarazzanti/luigi-di-maio-la-psicosi-di-stanislawskij',
  88. 'ext': 'mp4',
  89. 'title': 'Luigi Di Maio: la psicosi di Stanislawskij',
  90. 'description': 'md5:3c7a4303aef85868f867a26f5cc14813',
  91. 'thumbnail': r're:^https?://.*\.jpe?g',
  92. 'upload_date': '20160524',
  93. 'timestamp': 1464076800,
  94. 'series': 'Biografie imbarazzanti',
  95. 'season_number': 1,
  96. 'episode': 'Episode 1',
  97. 'episode_number': 1,
  98. },
  99. }, {
  100. 'url': 'https://es.dplay.com/dmax/la-fiebre-del-oro/temporada-8-episodio-1/',
  101. 'info_dict': {
  102. 'id': '21652',
  103. 'display_id': 'la-fiebre-del-oro/temporada-8-episodio-1',
  104. 'ext': 'mp4',
  105. 'title': 'Episodio 1',
  106. 'description': 'md5:b9dcff2071086e003737485210675f69',
  107. 'thumbnail': r're:^https?://.*\.png',
  108. 'upload_date': '20180709',
  109. 'timestamp': 1531173540,
  110. 'series': 'La fiebre del oro',
  111. 'season_number': 8,
  112. 'episode': 'Episode 1',
  113. 'episode_number': 1,
  114. },
  115. 'params': {
  116. 'skip_download': True,
  117. },
  118. }, {
  119. 'url': 'https://www.dplay.fi/videot/shifting-gears-with-aaron-kaufman/episode-16',
  120. 'only_matching': True,
  121. }, {
  122. 'url': 'https://www.dplay.jp/video/gold-rush/24086',
  123. 'only_matching': True,
  124. }]
  125. def _get_disco_api_info(self, url, display_id, disco_host, realm, country):
  126. geo_countries = [country.upper()]
  127. self._initialize_geo_bypass({
  128. 'countries': geo_countries,
  129. })
  130. disco_base = 'https://%s/' % disco_host
  131. token = self._download_json(
  132. disco_base + 'token', display_id, 'Downloading token',
  133. query={
  134. 'realm': realm,
  135. })['data']['attributes']['token']
  136. headers = {
  137. 'Referer': url,
  138. 'Authorization': 'Bearer ' + token,
  139. }
  140. video = self._download_json(
  141. disco_base + 'content/videos/' + display_id, display_id,
  142. headers=headers, query={
  143. 'fields[channel]': 'name',
  144. 'fields[image]': 'height,src,width',
  145. 'fields[show]': 'name',
  146. 'fields[tag]': 'name',
  147. 'fields[video]': 'description,episodeNumber,name,publishStart,seasonNumber,videoDuration',
  148. 'include': 'images,primaryChannel,show,tags'
  149. })
  150. video_id = video['data']['id']
  151. info = video['data']['attributes']
  152. title = info['name'].strip()
  153. formats = []
  154. try:
  155. streaming = self._download_json(
  156. disco_base + 'playback/videoPlaybackInfo/' + video_id,
  157. display_id, headers=headers)['data']['attributes']['streaming']
  158. except ExtractorError as e:
  159. if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
  160. info = self._parse_json(e.cause.read().decode('utf-8'), display_id)
  161. error = info['errors'][0]
  162. error_code = error.get('code')
  163. if error_code == 'access.denied.geoblocked':
  164. self.raise_geo_restricted(countries=geo_countries)
  165. elif error_code == 'access.denied.missingpackage':
  166. self.raise_login_required()
  167. raise ExtractorError(info['errors'][0]['detail'], expected=True)
  168. raise
  169. for format_id, format_dict in streaming.items():
  170. if not isinstance(format_dict, dict):
  171. continue
  172. format_url = format_dict.get('url')
  173. if not format_url:
  174. continue
  175. ext = determine_ext(format_url)
  176. if format_id == 'dash' or ext == 'mpd':
  177. formats.extend(self._extract_mpd_formats(
  178. format_url, display_id, mpd_id='dash', fatal=False))
  179. elif format_id == 'hls' or ext == 'm3u8':
  180. formats.extend(self._extract_m3u8_formats(
  181. format_url, display_id, 'mp4',
  182. entry_protocol='m3u8_native', m3u8_id='hls',
  183. fatal=False))
  184. else:
  185. formats.append({
  186. 'url': format_url,
  187. 'format_id': format_id,
  188. })
  189. self._sort_formats(formats)
  190. creator = series = None
  191. tags = []
  192. thumbnails = []
  193. included = video.get('included') or []
  194. if isinstance(included, list):
  195. for e in included:
  196. attributes = e.get('attributes')
  197. if not attributes:
  198. continue
  199. e_type = e.get('type')
  200. if e_type == 'channel':
  201. creator = attributes.get('name')
  202. elif e_type == 'image':
  203. src = attributes.get('src')
  204. if src:
  205. thumbnails.append({
  206. 'url': src,
  207. 'width': int_or_none(attributes.get('width')),
  208. 'height': int_or_none(attributes.get('height')),
  209. })
  210. if e_type == 'show':
  211. series = attributes.get('name')
  212. elif e_type == 'tag':
  213. name = attributes.get('name')
  214. if name:
  215. tags.append(name)
  216. return {
  217. 'id': video_id,
  218. 'display_id': display_id,
  219. 'title': title,
  220. 'description': info.get('description'),
  221. 'duration': float_or_none(info.get('videoDuration'), 1000),
  222. 'timestamp': unified_timestamp(info.get('publishStart')),
  223. 'series': series,
  224. 'season_number': int_or_none(info.get('seasonNumber')),
  225. 'episode_number': int_or_none(info.get('episodeNumber')),
  226. 'creator': creator,
  227. 'tags': tags,
  228. 'thumbnails': thumbnails,
  229. 'formats': formats,
  230. }
  231. def _real_extract(self, url):
  232. mobj = re.match(self._VALID_URL, url)
  233. display_id = mobj.group('id')
  234. domain = mobj.group('domain').lstrip('www.')
  235. country = mobj.group('country') or mobj.group('subdomain_country')
  236. host = 'disco-api.' + domain if domain.startswith('dplay.') else 'eu2-prod.disco-api.com'
  237. return self._get_disco_api_info(
  238. url, display_id, host, 'dplay' + country, country)