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