fancode.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. from .common import InfoExtractor
  2. from ..utils import ExtractorError, mimetype2ext, parse_iso8601, try_get
  3. class FancodeVodIE(InfoExtractor):
  4. _WORKING = False
  5. IE_NAME = 'fancode:vod'
  6. _VALID_URL = r'https?://(?:www\.)?fancode\.com/video/(?P<id>[0-9]+)\b'
  7. _TESTS = [{
  8. 'url': 'https://fancode.com/video/15043/match-preview-pbks-vs-mi',
  9. 'params': {
  10. 'skip_download': True,
  11. },
  12. 'info_dict': {
  13. 'id': '6249806281001',
  14. 'ext': 'mp4',
  15. 'title': 'Match Preview: PBKS vs MI',
  16. 'thumbnail': r're:^https?://.*\.jpg$',
  17. 'timestamp': 1619081590,
  18. 'view_count': int,
  19. 'like_count': int,
  20. 'upload_date': '20210422',
  21. 'uploader_id': '6008340455001',
  22. },
  23. }, {
  24. 'url': 'https://fancode.com/video/15043',
  25. 'only_matching': True,
  26. }]
  27. _ACCESS_TOKEN = None
  28. _NETRC_MACHINE = 'fancode'
  29. _LOGIN_HINT = 'Use "--username refresh --password <refresh_token>" to login using a refresh token'
  30. headers = {
  31. 'content-type': 'application/json',
  32. 'origin': 'https://fancode.com',
  33. 'referer': 'https://fancode.com',
  34. }
  35. def _perform_login(self, username, password):
  36. # Access tokens are shortlived, so get them using the refresh token.
  37. if username != 'refresh':
  38. self.report_warning(f'Login using username and password is not currently supported. {self._LOGIN_HINT}')
  39. self.report_login()
  40. data = '''{
  41. "query":"mutation RefreshToken($refreshToken: String\\u0021) { refreshToken(refreshToken: $refreshToken) { accessToken }}",
  42. "variables":{
  43. "refreshToken":"%s"
  44. },
  45. "operationName":"RefreshToken"
  46. }''' % password # noqa: UP031
  47. token_json = self.download_gql('refresh token', data, 'Getting the Access token')
  48. self._ACCESS_TOKEN = try_get(token_json, lambda x: x['data']['refreshToken']['accessToken'])
  49. if self._ACCESS_TOKEN is None:
  50. self.report_warning('Failed to get Access token')
  51. else:
  52. self.headers.update({'Authorization': f'Bearer {self._ACCESS_TOKEN}'})
  53. def _check_login_required(self, is_available, is_premium):
  54. msg = None
  55. if is_premium and self._ACCESS_TOKEN is None:
  56. msg = f'This video is only available for registered users. {self._LOGIN_HINT}'
  57. elif not is_available and self._ACCESS_TOKEN is not None:
  58. msg = 'This video isn\'t available to the current logged in account'
  59. if msg:
  60. self.raise_login_required(msg, metadata_available=True, method=None)
  61. def download_gql(self, variable, data, note, fatal=False, headers=headers):
  62. return self._download_json(
  63. 'https://www.fancode.com/graphql', variable,
  64. data=data.encode(), note=note,
  65. headers=headers, fatal=fatal)
  66. def _real_extract(self, url):
  67. BRIGHTCOVE_URL_TEMPLATE = 'https://players.brightcove.net/%s/default_default/index.html?videoId=%s'
  68. video_id = self._match_id(url)
  69. brightcove_user_id = '6008340455001'
  70. data = '''{
  71. "query":"query Video($id: Int\\u0021, $filter: SegmentFilter) { media(id: $id, filter: $filter) { id contentId title contentId publishedTime totalViews totalUpvotes provider thumbnail { src } mediaSource {brightcove } duration isPremium isUserEntitled tags duration }}",
  72. "variables":{
  73. "id":%s,
  74. "filter":{
  75. "contentDataType":"DEFAULT"
  76. }
  77. },
  78. "operationName":"Video"
  79. }''' % video_id # noqa: UP031
  80. metadata_json = self.download_gql(video_id, data, note='Downloading metadata')
  81. media = try_get(metadata_json, lambda x: x['data']['media'], dict) or {}
  82. brightcove_video_id = try_get(media, lambda x: x['mediaSource']['brightcove'], str)
  83. if brightcove_video_id is None:
  84. raise ExtractorError('Unable to extract brightcove Video ID')
  85. is_premium = media.get('isPremium')
  86. self._check_login_required(media.get('isUserEntitled'), is_premium)
  87. return {
  88. '_type': 'url_transparent',
  89. 'url': BRIGHTCOVE_URL_TEMPLATE % (brightcove_user_id, brightcove_video_id),
  90. 'ie_key': 'BrightcoveNew',
  91. 'id': video_id,
  92. 'title': media['title'],
  93. 'like_count': media.get('totalUpvotes'),
  94. 'view_count': media.get('totalViews'),
  95. 'tags': media.get('tags'),
  96. 'release_timestamp': parse_iso8601(media.get('publishedTime')),
  97. 'availability': self._availability(needs_premium=is_premium),
  98. }
  99. class FancodeLiveIE(FancodeVodIE): # XXX: Do not subclass from concrete IE
  100. _WORKING = False
  101. IE_NAME = 'fancode:live'
  102. _VALID_URL = r'https?://(www\.)?fancode\.com/match/(?P<id>[0-9]+).+'
  103. _TESTS = [{
  104. 'url': 'https://fancode.com/match/35328/cricket-fancode-ecs-hungary-2021-bub-vs-blb?slug=commentary',
  105. 'info_dict': {
  106. 'id': '35328',
  107. 'ext': 'mp4',
  108. 'title': 'BUB vs BLB',
  109. 'timestamp': 1624863600,
  110. 'is_live': True,
  111. 'upload_date': '20210628',
  112. },
  113. 'skip': 'Ended',
  114. }, {
  115. 'url': 'https://fancode.com/match/35328/',
  116. 'only_matching': True,
  117. }, {
  118. 'url': 'https://fancode.com/match/35567?slug=scorecard',
  119. 'only_matching': True,
  120. }]
  121. def _real_extract(self, url):
  122. video_id = self._match_id(url)
  123. data = '''{
  124. "query":"query MatchResponse($id: Int\\u0021, $isLoggedIn: Boolean\\u0021) { match: matchWithScores(id: $id) { id matchDesc mediaId videoStreamId videoStreamUrl { ...VideoSource } liveStreams { videoStreamId videoStreamUrl { ...VideoSource } contentId } name startTime streamingStatus isPremium isUserEntitled @include(if: $isLoggedIn) status metaTags bgImage { src } sport { name slug } tour { id name } squads { name shortName } liveStreams { contentId } mediaId }}fragment VideoSource on VideoSource { title description posterUrl url deliveryType playerType}",
  125. "variables":{
  126. "id":%s,
  127. "isLoggedIn":true
  128. },
  129. "operationName":"MatchResponse"
  130. }''' % video_id # noqa: UP031
  131. info_json = self.download_gql(video_id, data, 'Info json')
  132. match_info = try_get(info_json, lambda x: x['data']['match'])
  133. if match_info.get('streamingStatus') != 'STARTED':
  134. raise ExtractorError('The stream can\'t be accessed', expected=True)
  135. self._check_login_required(match_info.get('isUserEntitled'), True) # all live streams are premium only
  136. return {
  137. 'id': video_id,
  138. 'title': match_info.get('name'),
  139. 'formats': self._extract_akamai_formats(try_get(match_info, lambda x: x['videoStreamUrl']['url']), video_id),
  140. 'ext': mimetype2ext(try_get(match_info, lambda x: x['videoStreamUrl']['deliveryType'])),
  141. 'is_live': True,
  142. 'release_timestamp': parse_iso8601(match_info.get('startTime')),
  143. }