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