]>
jfr.im git - yt-dlp.git/blob - yt_dlp/extractor/sonyliv.py
2 from __future__
import unicode_literals
10 from .common
import InfoExtractor
11 from ..compat
import compat_HTTPError
19 class SonyLIVIE(InfoExtractor
):
23 https?://(?:www\.)?sonyliv\.com/(?:s(?:how|port)s/[^/]+|movies|clip|trailer|music-videos)/[^/?#&]+-
28 'url': 'https://www.sonyliv.com/shows/bachelors-delight-1700000113/achaari-cheese-toast-1000022678?watch=true',
30 'title': 'Achaari Cheese Toast',
33 'upload_date': '20200411',
34 'description': 'md5:3957fa31d9309bf336ceb3f37ad5b7cb',
35 'timestamp': 1586632091,
38 'series': 'Bachelors Delight',
43 'skip_download': True,
46 'url': 'https://www.sonyliv.com/movies/tahalka-1000050121?watch=true',
47 'only_matching': True,
49 'url': 'https://www.sonyliv.com/clip/jigarbaaz-1000098925',
50 'only_matching': True,
52 'url': 'https://www.sonyliv.com/trailer/sandwiched-forever-1000100286?watch=true',
53 'only_matching': True,
55 'url': 'https://www.sonyliv.com/sports/india-tour-of-australia-2020-21-1700000286/cricket-hls-day-3-1st-test-aus-vs-ind-19-dec-2020-1000100959?watch=true',
56 'only_matching': True,
58 'url': 'https://www.sonyliv.com/music-videos/yeh-un-dinon-ki-baat-hai-1000018779',
59 'only_matching': True,
61 _GEO_COUNTRIES
= ['IN']
63 _LOGIN_HINT
= 'Use "--username <mobile_number>" to login using OTP or "--username token --password <auth_token>" to login using auth token.'
64 _NETRC_MACHINE
= 'sonyliv'
66 def _get_device_id(self
):
67 e
= int(time
.time() * 1000)
68 t
= list('xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx')
69 for i
, c
in enumerate(t
):
70 n
= int((e
+ 16 * random
.random()) % 16) |
0
71 e
= math
.floor(e
/ 16)
75 t
[i
] = '{:x}'.format(3 & n |
8)
76 return ''.join(t
) + '-' + str(int(time
.time() * 1000))
78 def _login(self
, username
, password
):
79 if username
.lower() == 'token' and len(password
) > 1198:
81 elif len(username
) != 10 or not username
.isdigit():
82 raise ExtractorError(f
'Invalid username/password; {self._LOGIN_HINT}')
85 data
= '''{"mobileNumber":"%s","channelPartnerID":"MSMIND","country":"IN","timestamp":"%s",
86 "otpSize":6,"loginType":"REGISTERORSIGNIN","isMobileMandatory":true}
87 ''' % (username
, datetime
.datetime
.now().strftime("%Y-%m-%dT%H:%M:%S.%MZ"))
88 otp_request_json
= self
._download
_json
(
89 'https://apiv2.sonyliv.com/AGL/1.6/A/ENG/WEB/IN/HR/CREATEOTP-V2',
90 None, note
='Sending OTP', data
=data
.encode(), headers
=self
._HEADERS
)
91 if otp_request_json
['resultCode'] == 'KO':
92 raise ExtractorError(otp_request_json
['message'], expected
=True)
93 otp_code
= self
._get
_tfa
_info
('OTP')
94 data
= '''{"channelPartnerID":"MSMIND","mobileNumber":"%s","country":"IN","otp":"%s",
95 "dmaId":"IN","ageConfirmation":true,"timestamp":"%s","isMobileMandatory":true}
96 ''' % (username
, otp_code
, datetime
.datetime
.now().strftime("%Y-%m-%dT%H:%M:%S.%MZ"))
97 otp_verify_json
= self
._download
_json
(
98 'https://apiv2.sonyliv.com/AGL/2.0/A/ENG/WEB/IN/HR/CONFIRMOTP-V2',
99 None, note
='Verifying OTP', data
=data
.encode(), headers
=self
._HEADERS
)
100 if otp_verify_json
['resultCode'] == 'KO':
101 raise ExtractorError(otp_request_json
['message'], expected
=True)
102 return otp_verify_json
['resultObj']['accessToken']
104 def _call_api(self
, version
, path
, video_id
):
106 return self
._download
_json
(
107 'https://apiv2.sonyliv.com/AGL/%s/A/ENG/WEB/%s' % (version
, path
),
108 video_id
, headers
=self
._HEADERS
)['resultObj']
109 except ExtractorError
as e
:
110 if isinstance(e
.cause
, compat_HTTPError
) and e
.cause
.code
== 406 and self
._parse
_json
(
111 e
.cause
.read().decode(), video_id
)['message'] == 'Please subscribe to watch this content':
112 self
.raise_login_required(self
._LOGIN
_HINT
, method
=None)
113 if isinstance(e
.cause
, compat_HTTPError
) and e
.cause
.code
== 403:
114 message
= self
._parse
_json
(
115 e
.cause
.read().decode(), video_id
)['message']
116 if message
== 'Geoblocked Country':
117 self
.raise_geo_restricted(countries
=self
._GEO
_COUNTRIES
)
118 raise ExtractorError(message
)
121 def _real_initialize(self
):
122 self
._HEADERS
['security_token'] = self
._call
_api
('1.4', 'ALL/GETTOKEN', None)
123 username
, password
= self
._get
_login
_info
()
125 self
._HEADERS
['device_id'] = self
._get
_device
_id
()
126 self
._HEADERS
['content-type'] = 'application/json'
127 self
._HEADERS
['authorization'] = self
._login
(username
, password
)
129 def _real_extract(self
, url
):
130 video_id
= self
._match
_id
(url
)
131 content
= self
._call
_api
(
132 '1.5', 'IN/CONTENT/VIDEOURL/VOD/' + video_id
, video_id
)
133 if not self
.get_param('allow_unplayable_formats') and content
.get('isEncrypted'):
134 self
.report_drm(video_id
)
135 dash_url
= content
['videoURL']
137 'x-playback-session-id': '%s-%d' % (uuid
.uuid4().hex, time
.time() * 1000)
139 formats
= self
._extract
_mpd
_formats
(
140 dash_url
, video_id
, mpd_id
='dash', headers
=headers
, fatal
=False)
141 formats
.extend(self
._extract
_m
3u8_formats
(
142 dash_url
.replace('.mpd', '.m3u8').replace('/DASH/', '/HLS/'),
143 video_id
, 'mp4', m3u8_id
='hls', headers
=headers
, fatal
=False))
145 f
.setdefault('http_headers', {}).update(headers
)
146 self
._sort
_formats
(formats
)
148 metadata
= self
._call
_api
(
149 '1.6', 'IN/DETAIL/' + video_id
, video_id
)['containers'][0]['metadata']
150 title
= metadata
['episodeTitle']
152 for sub
in content
.get('subtitle', []):
153 sub_url
= sub
.get('subtitleUrl')
156 subtitles
.setdefault(sub
.get('subtitleLanguageName', 'ENG'), []).append({
163 'thumbnail': content
.get('posterURL'),
164 'description': metadata
.get('longDescription') or metadata
.get('shortDescription'),
165 'timestamp': int_or_none(metadata
.get('creationDate'), 1000),
166 'duration': int_or_none(metadata
.get('duration')),
167 'season_number': int_or_none(metadata
.get('season')),
168 'series': metadata
.get('title'),
169 'episode_number': int_or_none(metadata
.get('episodeNumber')),
170 'release_year': int_or_none(metadata
.get('year')),
171 'subtitles': subtitles
,
175 class SonyLIVSeriesIE(InfoExtractor
):
176 _VALID_URL
= r
'https?://(?:www\.)?sonyliv\.com/shows/[^/?#&]+-(?P<id>\d{10})$'
178 'url': 'https://www.sonyliv.com/shows/adaalat-1700000091',
179 'playlist_mincount': 456,
184 _API_SHOW_URL
= "https://apiv2.sonyliv.com/AGL/1.9/R/ENG/WEB/IN/DL/DETAIL/{}?kids_safe=false&from=0&to=49"
185 _API_EPISODES_URL
= "https://apiv2.sonyliv.com/AGL/1.4/R/ENG/WEB/IN/CONTENT/DETAIL/BUNDLE/{}?from=0&to=1000&orderBy=episodeNumber&sortOrder=asc"
186 _API_SECURITY_URL
= 'https://apiv2.sonyliv.com/AGL/1.4/A/ENG/WEB/ALL/GETTOKEN'
188 def _entries(self
, show_id
):
190 'Accept': 'application/json, text/plain, */*',
191 'Referer': 'https://www.sonyliv.com',
193 headers
['security_token'] = self
._download
_json
(
194 self
._API
_SECURITY
_URL
, video_id
=show_id
, headers
=headers
,
195 note
='Downloading security token')['resultObj']
197 self
._download
_json
(self
._API
_SHOW
_URL
.format(show_id
), video_id
=show_id
, headers
=headers
),
198 lambda x
: x
['resultObj']['containers'][0]['containers'], list)
199 for season
in seasons
or []:
200 season_id
= season
['id']
202 self
._download
_json
(self
._API
_EPISODES
_URL
.format(season_id
), video_id
=season_id
, headers
=headers
),
203 lambda x
: x
['resultObj']['containers'][0]['containers'], list)
204 for episode
in episodes
or []:
205 video_id
= episode
.get('id')
206 yield self
.url_result('sonyliv:%s' % video_id
, ie
=SonyLIVIE
.ie_key(), video_id
=video_id
)
208 def _real_extract(self
, url
):
209 show_id
= self
._match
_id
(url
)
210 return self
.playlist_result(self
._entries
(show_id
), playlist_id
=show_id
)