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