]> jfr.im git - yt-dlp.git/blame - yt_dlp/extractor/voot.py
[cleanup] Remove dead extractors (#8604)
[yt-dlp.git] / yt_dlp / extractor / voot.py
CommitLineData
4f7b11cc 1import json
2import time
4f7b11cc 3import uuid
4
daaaf5f5 5from .common import InfoExtractor
a3ed14cb 6from ..compat import compat_str
3d2623a8 7from ..networking.exceptions import HTTPError
e2b4808f
S
8from ..utils import (
9 ExtractorError,
4f7b11cc 10 float_or_none,
e2b4808f 11 int_or_none,
4f7b11cc 12 jwt_decode_hs256,
13 parse_age_limit,
14 traverse_obj,
15 try_call,
e2b4808f 16 try_get,
4f7b11cc 17 unified_strdate,
e2b4808f 18)
daaaf5f5
AC
19
20
4f7b11cc 21class VootBaseIE(InfoExtractor):
22 _NETRC_MACHINE = 'voot'
23 _GEO_BYPASS = False
24 _LOGIN_HINT = 'Log in with "-u <email_address> -p <password>", or use "-u token -p <auth_token>" to login with auth token.'
25 _TOKEN = None
26 _EXPIRY = 0
27 _API_HEADERS = {'Origin': 'https://www.voot.com', 'Referer': 'https://www.voot.com/'}
28
29 def _perform_login(self, username, password):
30 if self._TOKEN and self._EXPIRY:
31 return
32
33 if username.lower() == 'token' and try_call(lambda: jwt_decode_hs256(password)):
34 VootBaseIE._TOKEN = password
35 VootBaseIE._EXPIRY = jwt_decode_hs256(password)['exp']
36 self.report_login()
37
38 # Mobile number as username is not supported
39 elif not username.isdigit():
40 check_username = self._download_json(
41 'https://userauth.voot.com/usersV3/v3/checkUser', None, data=json.dumps({
42 'type': 'email',
43 'email': username
44 }, separators=(',', ':')).encode(), headers={
45 **self._API_HEADERS,
46 'Content-Type': 'application/json;charset=utf-8',
47 }, note='Checking username', expected_status=403)
48 if not traverse_obj(check_username, ('isExist', {bool})):
49 if traverse_obj(check_username, ('status', 'code', {int})) == 9999:
50 self.raise_geo_restricted(countries=['IN'])
51 raise ExtractorError('Incorrect username', expected=True)
52 auth_token = traverse_obj(self._download_json(
53 'https://userauth.voot.com/usersV3/v3/login', None, data=json.dumps({
54 'type': 'traditional',
55 'deviceId': str(uuid.uuid4()),
56 'deviceBrand': 'PC/MAC',
57 'data': {
58 'email': username,
59 'password': password
60 }
61 }, separators=(',', ':')).encode(), headers={
62 **self._API_HEADERS,
63 'Content-Type': 'application/json;charset=utf-8',
64 }, note='Logging in', expected_status=400), ('data', 'authToken', {dict}))
65 if not auth_token:
66 raise ExtractorError('Incorrect password', expected=True)
67 VootBaseIE._TOKEN = auth_token['accessToken']
68 VootBaseIE._EXPIRY = auth_token['expirationTime']
69
70 else:
71 raise ExtractorError(self._LOGIN_HINT, expected=True)
72
73 def _check_token_expiry(self):
74 if int(time.time()) >= self._EXPIRY:
75 raise ExtractorError('Access token has expired', expected=True)
76
77 def _real_initialize(self):
78 if not self._TOKEN:
79 self.raise_login_required(self._LOGIN_HINT, method=None)
80 self._check_token_expiry()
81
82
83class VootIE(VootBaseIE):
9751a457 84 _WORKING = False
a3ed14cb
A
85 _VALID_URL = r'''(?x)
86 (?:
87 voot:|
73f035e1 88 https?://(?:www\.)?voot\.com/?
a3ed14cb 89 (?:
a4713ba9 90 movies?/[^/]+/|
a3ed14cb
A
91 (?:shows|kids)/(?:[^/]+/){4}
92 )
93 )
94 (?P<id>\d{3,})
95 '''
e2b4808f 96 _TESTS = [{
daaaf5f5
AC
97 'url': 'https://www.voot.com/shows/ishq-ka-rang-safed/1/360558/is-this-the-end-of-kamini-/441353',
98 'info_dict': {
4f7b11cc 99 'id': '441353',
daaaf5f5 100 'ext': 'mp4',
4f7b11cc 101 'title': 'Is this the end of Kamini?',
e2b4808f 102 'description': 'md5:06291fbbbc4dcbe21235c40c262507c1',
4f7b11cc 103 'timestamp': 1472103000,
e2b4808f 104 'upload_date': '20160825',
e2b4808f
S
105 'series': 'Ishq Ka Rang Safed',
106 'season_number': 1,
107 'episode': 'Is this the end of Kamini?',
108 'episode_number': 340,
4f7b11cc 109 'release_date': '20160825',
110 'season': 'Season 1',
111 'age_limit': 13,
112 'duration': 1146.0,
e2b4808f 113 },
4f7b11cc 114 'params': {'skip_download': 'm3u8'},
e2b4808f
S
115 }, {
116 'url': 'https://www.voot.com/kids/characters/mighty-cat-masked-niyander-e-/400478/school-bag-disappears/440925',
117 'only_matching': True,
118 }, {
119 'url': 'https://www.voot.com/movies/pandavas-5/424627',
120 'only_matching': True,
a4713ba9
AM
121 }, {
122 'url': 'https://www.voot.com/movie/fight-club/621842',
123 'only_matching': True,
e2b4808f 124 }]
daaaf5f5
AC
125
126 def _real_extract(self, url):
127 video_id = self._match_id(url)
e2b4808f 128 media_info = self._download_json(
4f7b11cc 129 'https://psapi.voot.com/jio/voot/v1/voot-web/content/query/asset-details', video_id,
130 query={'ids': f'include:{video_id}', 'responseType': 'common'}, headers={'accesstoken': self._TOKEN})
131
132 try:
133 m3u8_url = self._download_json(
134 'https://vootapi.media.jio.com/playback/v1/playbackrights', video_id,
135 'Downloading playback JSON', data=b'{}', headers={
136 **self.geo_verification_headers(),
137 **self._API_HEADERS,
138 'Content-Type': 'application/json;charset=utf-8',
139 'platform': 'androidwebdesktop',
140 'vootid': video_id,
141 'voottoken': self._TOKEN,
142 })['m3u8']
143 except ExtractorError as e:
3d2623a8 144 if isinstance(e.cause, HTTPError) and e.cause.status == 400:
4f7b11cc 145 self._check_token_expiry()
146 raise
147
148 formats = self._extract_m3u8_formats(m3u8_url, video_id, 'mp4', m3u8_id='hls')
149 self._remove_duplicate_formats(formats)
150
daaaf5f5 151 return {
4f7b11cc 152 'id': video_id,
153 # '/_definst_/smil:vod/' m3u8 manifests claim to have 720p+ formats but max out at 480p
154 'formats': traverse_obj(formats, (
155 lambda _, v: '/_definst_/smil:vod/' not in v['url'] or v['height'] <= 480)),
156 'http_headers': self._API_HEADERS,
157 **traverse_obj(media_info, ('result', 0, {
158 'title': ('fullTitle', {str}),
159 'description': ('fullSynopsis', {str}),
160 'series': ('showName', {str}),
161 'season_number': ('season', {int_or_none}),
162 'episode': ('fullTitle', {str}),
163 'episode_number': ('episode', {int_or_none}),
164 'timestamp': ('uploadTime', {int_or_none}),
165 'release_date': ('telecastDate', {unified_strdate}),
166 'age_limit': ('ageNemonic', {parse_age_limit}),
167 'duration': ('duration', {float_or_none}),
168 })),
daaaf5f5 169 }
a3ed14cb
A
170
171
4f7b11cc 172class VootSeriesIE(VootBaseIE):
9751a457 173 _WORKING = False
a3ed14cb
A
174 _VALID_URL = r'https?://(?:www\.)?voot\.com/shows/[^/]+/(?P<id>\d{3,})'
175 _TESTS = [{
176 'url': 'https://www.voot.com/shows/chakravartin-ashoka-samrat/100002',
177 'playlist_mincount': 442,
178 'info_dict': {
179 'id': '100002',
180 },
181 }, {
182 'url': 'https://www.voot.com/shows/ishq-ka-rang-safed/100003',
183 'playlist_mincount': 341,
184 'info_dict': {
185 'id': '100003',
186 },
187 }]
188 _SHOW_API = 'https://psapi.voot.com/media/voot/v1/voot-web/content/generic/season-by-show?sort=season%3Aasc&id={}&responseType=common'
189 _SEASON_API = 'https://psapi.voot.com/media/voot/v1/voot-web/content/generic/series-wise-episode?sort=episode%3Aasc&id={}&responseType=common&page={:d}'
190
191 def _entries(self, show_id):
192 show_json = self._download_json(self._SHOW_API.format(show_id), video_id=show_id)
193 for season in show_json.get('result', []):
194 page_num = 1
195 season_id = try_get(season, lambda x: x['id'], compat_str)
196 season_json = self._download_json(self._SEASON_API.format(season_id, page_num),
197 video_id=season_id,
198 note='Downloading JSON metadata page %d' % page_num)
199 episodes_json = season_json.get('result', [])
200 while episodes_json:
201 page_num += 1
202 for episode in episodes_json:
203 video_id = episode.get('id')
204 yield self.url_result(
205 'voot:%s' % video_id, ie=VootIE.ie_key(), video_id=video_id)
206 episodes_json = self._download_json(self._SEASON_API.format(season_id, page_num),
207 video_id=season_id,
208 note='Downloading JSON metadata page %d' % page_num)['result']
209
210 def _real_extract(self, url):
211 show_id = self._match_id(url)
212 return self.playlist_result(self._entries(show_id), playlist_id=show_id)