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

dacast.py (7370B)


  1. import functools
  2. import hashlib
  3. import re
  4. import time
  5. from .common import InfoExtractor
  6. from ..networking.exceptions import HTTPError
  7. from ..utils import (
  8. ExtractorError,
  9. classproperty,
  10. float_or_none,
  11. traverse_obj,
  12. url_or_none,
  13. )
  14. class DacastBaseIE(InfoExtractor):
  15. _URL_TYPE = None
  16. @classproperty
  17. def _VALID_URL(cls):
  18. return fr'https?://iframe\.dacast\.com/{cls._URL_TYPE}/(?P<user_id>[\w-]+)/(?P<id>[\w-]+)'
  19. @classproperty
  20. def _EMBED_REGEX(cls):
  21. return [rf'<iframe[^>]+\bsrc=["\'](?P<url>{cls._VALID_URL})']
  22. _API_INFO_URL = 'https://playback.dacast.com/content/info'
  23. @classmethod
  24. def _get_url_from_id(cls, content_id):
  25. user_id, media_id = content_id.split(f'-{cls._URL_TYPE}-')
  26. return f'https://iframe.dacast.com/{cls._URL_TYPE}/{user_id}/{media_id}'
  27. @classmethod
  28. def _extract_embed_urls(cls, url, webpage):
  29. yield from super()._extract_embed_urls(url, webpage)
  30. for content_id in re.findall(
  31. rf'<script[^>]+\bsrc=["\']https://player\.dacast\.com/js/player\.js\?contentId=([\w-]+-{cls._URL_TYPE}-[\w-]+)["\']', webpage):
  32. yield cls._get_url_from_id(content_id)
  33. class DacastVODIE(DacastBaseIE):
  34. _URL_TYPE = 'vod'
  35. _TESTS = [{
  36. 'url': 'https://iframe.dacast.com/vod/acae82153ef4d7a7344ae4eaa86af534/1c6143e3-5a06-371d-8695-19b96ea49090',
  37. 'info_dict': {
  38. 'id': '1c6143e3-5a06-371d-8695-19b96ea49090',
  39. 'ext': 'mp4',
  40. 'uploader_id': 'acae82153ef4d7a7344ae4eaa86af534',
  41. 'title': '2_4||Adnexal mass characterisation: O-RADS US and MRI||N. Bharwani, London/UK',
  42. 'thumbnail': 'https://universe-files.dacast.com/26137208-5858-65c1-5e9a-9d6b6bd2b6c2',
  43. },
  44. 'params': {'skip_download': 'm3u8'},
  45. }, { # /uspaes/ in hls_url
  46. 'url': 'https://iframe.dacast.com/vod/f9823fc6-faba-b98f-0d00-4a7b50a58c5b/348c5c84-b6af-4859-bb9d-1d01009c795b',
  47. 'info_dict': {
  48. 'id': '348c5c84-b6af-4859-bb9d-1d01009c795b',
  49. 'ext': 'mp4',
  50. 'title': 'pl1-edyta-rubas-211124.mp4',
  51. 'uploader_id': 'f9823fc6-faba-b98f-0d00-4a7b50a58c5b',
  52. 'thumbnail': 'https://universe-files.dacast.com/4d0bd042-a536-752d-fc34-ad2fa44bbcbb.png',
  53. },
  54. }]
  55. _WEBPAGE_TESTS = [{
  56. 'url': 'https://www.dacast.com/support/knowledgebase/how-can-i-embed-a-video-on-my-website/',
  57. 'info_dict': {
  58. 'id': 'b6674869-f08a-23c5-1d7b-81f5309e1a90',
  59. 'ext': 'mp4',
  60. 'title': '4-HowToEmbedVideo.mp4',
  61. 'uploader_id': '3b67c4a9-3886-4eb1-d0eb-39b23b14bef3',
  62. 'thumbnail': 'https://universe-files.dacast.com/d26ab48f-a52a-8783-c42e-a90290ba06b6.png',
  63. },
  64. 'params': {'skip_download': 'm3u8'},
  65. }, {
  66. 'url': 'https://gist.githubusercontent.com/bashonly/4ad249ef2910346fbdf3809b220f11ee/raw/87349778d4af1a80b1fcc3beb9c88108de5858f5/dacast_embeds.html',
  67. 'info_dict': {
  68. 'id': 'e7df418e-a83b-7a7f-7b5e-1a667981e8fa',
  69. 'ext': 'mp4',
  70. 'title': 'Evening Service 2-5-23',
  71. 'uploader_id': '943bb1ab3c03695ba85330d92d6d226e',
  72. 'thumbnail': 'https://universe-files.dacast.com/337472b3-e92c-2ea4-7eb7-5700da477f67',
  73. },
  74. 'params': {'skip_download': 'm3u8'},
  75. }]
  76. @functools.cached_property
  77. def _usp_signing_secret(self):
  78. player_js = self._download_webpage(
  79. 'https://player.dacast.com/js/player.js', None, 'Downloading player JS')
  80. # Rotates every so often, but hardcode a fallback in case of JS change/breakage before rotation
  81. return self._search_regex(
  82. r'\bUSP_SIGNING_SECRET\s*=\s*(["\'])(?P<secret>(?:(?!\1).)+)', player_js,
  83. 'usp signing secret', group='secret', fatal=False) or 'odnInCGqhvtyRTtIiddxtuRtawYYICZP'
  84. def _real_extract(self, url):
  85. user_id, video_id = self._match_valid_url(url).group('user_id', 'id')
  86. query = {'contentId': f'{user_id}-vod-{video_id}', 'provider': 'universe'}
  87. info = self._download_json(self._API_INFO_URL, video_id, query=query, fatal=False)
  88. access = self._download_json(
  89. 'https://playback.dacast.com/content/access', video_id,
  90. note='Downloading access JSON', query=query, expected_status=403)
  91. error = access.get('error')
  92. if error in ('Broadcaster has been blocked', 'Content is offline'):
  93. raise ExtractorError(error, expected=True)
  94. elif error:
  95. raise ExtractorError(f'Dacast API says "{error}"')
  96. hls_url = access['hls']
  97. hls_aes = {}
  98. if 'DRM_EXT' in hls_url:
  99. self.report_drm(video_id)
  100. elif '/uspaes/' in hls_url:
  101. # Ref: https://player.dacast.com/js/player.js
  102. ts = int(time.time())
  103. signature = hashlib.sha1(
  104. f'{10413792000 - ts}{ts}{self._usp_signing_secret}'.encode()).digest().hex()
  105. hls_aes['uri'] = f'https://keys.dacast.com/uspaes/{video_id}.key?s={signature}&ts={ts}'
  106. for retry in self.RetryManager():
  107. try:
  108. formats = self._extract_m3u8_formats(hls_url, video_id, 'mp4', m3u8_id='hls')
  109. except ExtractorError as e:
  110. # CDN will randomly respond with 403
  111. if isinstance(e.cause, HTTPError) and e.cause.status == 403:
  112. retry.error = e
  113. continue
  114. raise
  115. return {
  116. 'id': video_id,
  117. 'uploader_id': user_id,
  118. 'formats': formats,
  119. 'hls_aes': hls_aes or None,
  120. **traverse_obj(info, ('contentInfo', {
  121. 'title': 'title',
  122. 'duration': ('duration', {float_or_none}),
  123. 'thumbnail': ('thumbnailUrl', {url_or_none}),
  124. })),
  125. }
  126. class DacastPlaylistIE(DacastBaseIE):
  127. _URL_TYPE = 'playlist'
  128. _TESTS = [{
  129. 'url': 'https://iframe.dacast.com/playlist/943bb1ab3c03695ba85330d92d6d226e/b632eb053cac17a9c9a02bcfc827f2d8',
  130. 'playlist_mincount': 28,
  131. 'info_dict': {
  132. 'id': 'b632eb053cac17a9c9a02bcfc827f2d8',
  133. 'title': 'Archive Sermons',
  134. },
  135. }]
  136. _WEBPAGE_TESTS = [{
  137. 'url': 'https://gist.githubusercontent.com/bashonly/7efb606f49f3c6e07ea0327de5a661d1/raw/05a16eac830245ea301fb0a585023bec71e6093c/dacast_playlist_embed.html',
  138. 'playlist_mincount': 28,
  139. 'info_dict': {
  140. 'id': 'b632eb053cac17a9c9a02bcfc827f2d8',
  141. 'title': 'Archive Sermons',
  142. },
  143. }]
  144. def _real_extract(self, url):
  145. user_id, playlist_id = self._match_valid_url(url).group('user_id', 'id')
  146. info = self._download_json(
  147. self._API_INFO_URL, playlist_id, note='Downloading playlist JSON', query={
  148. 'contentId': f'{user_id}-playlist-{playlist_id}',
  149. 'provider': 'universe',
  150. })['contentInfo']
  151. def entries(info):
  152. for video in traverse_obj(info, ('features', 'playlist', 'contents', lambda _, v: v['id'])):
  153. yield self.url_result(
  154. DacastVODIE._get_url_from_id(video['id']), DacastVODIE, video['id'], video.get('title'))
  155. return self.playlist_result(entries(info), playlist_id, info.get('title'))