]>
Commit | Line | Data |
---|---|---|
48652590 | 1 | import json |
2 | ||
3 | from .common import InfoExtractor | |
4 | from ..utils import ExtractorError, int_or_none, traverse_obj, try_get | |
5 | ||
6 | ||
a9d069f5 | 7 | class AmazonMiniTVBaseIE(InfoExtractor): |
8 | def _real_initialize(self): | |
9 | self._download_webpage( | |
10 | 'https://www.amazon.in/minitv', None, | |
11 | note='Fetching guest session cookies') | |
12 | AmazonMiniTVBaseIE.session_id = self._get_cookies('https://www.amazon.in')['session-id'].value | |
13 | ||
14 | def _call_api(self, asin, data=None, note=None): | |
15 | device = {'clientId': 'ATVIN', 'deviceLocale': 'en_GB'} | |
16 | if data: | |
17 | data['variables'].update({ | |
18 | 'contentType': 'VOD', | |
19 | 'sessionIdToken': self.session_id, | |
20 | **device, | |
21 | }) | |
22 | ||
23 | resp = self._download_json( | |
24 | f'https://www.amazon.in/minitv/api/web/{"graphql" if data else "prs"}', | |
25 | asin, note=note, headers={'Content-Type': 'application/json'}, | |
26 | data=json.dumps(data).encode() if data else None, | |
27 | query=None if data else { | |
28 | 'deviceType': 'A1WMMUXPCUJL4N', | |
29 | 'contentId': asin, | |
30 | **device, | |
31 | }) | |
32 | ||
33 | if resp.get('errors'): | |
34 | raise ExtractorError(f'MiniTV said: {resp["errors"][0]["message"]}') | |
35 | elif not data: | |
36 | return resp | |
37 | return resp['data'][data['operationName']] | |
38 | ||
39 | ||
40 | class AmazonMiniTVIE(AmazonMiniTVBaseIE): | |
48652590 | 41 | _VALID_URL = r'(?:https?://(?:www\.)?amazon\.in/minitv/tp/|amazonminitv:(?:amzn1\.dv\.gti\.)?)(?P<id>[a-f0-9-]+)' |
48652590 | 42 | _TESTS = [{ |
43 | 'url': 'https://www.amazon.in/minitv/tp/75fe3a75-b8fe-4499-8100-5c9424344840?referrer=https%3A%2F%2Fwww.amazon.in%2Fminitv', | |
48652590 | 44 | 'info_dict': { |
45 | 'id': 'amzn1.dv.gti.75fe3a75-b8fe-4499-8100-5c9424344840', | |
46 | 'ext': 'mp4', | |
47 | 'title': 'May I Kiss You?', | |
48 | 'language': 'Hindi', | |
49 | 'thumbnail': r're:^https?://.*\.jpg$', | |
50 | 'description': 'md5:a549bfc747973e04feb707833474e59d', | |
51 | 'release_timestamp': 1644710400, | |
52 | 'release_date': '20220213', | |
53 | 'duration': 846, | |
a9d069f5 | 54 | 'chapters': 'count:2', |
48652590 | 55 | 'series': 'Couple Goals', |
56 | 'series_id': 'amzn1.dv.gti.56521d46-b040-4fd5-872e-3e70476a04b0', | |
57 | 'season': 'Season 3', | |
58 | 'season_number': 3, | |
59 | 'season_id': 'amzn1.dv.gti.20331016-d9b9-4968-b991-c89fa4927a36', | |
60 | 'episode': 'May I Kiss You?', | |
61 | 'episode_number': 2, | |
62 | 'episode_id': 'amzn1.dv.gti.75fe3a75-b8fe-4499-8100-5c9424344840', | |
63 | }, | |
64 | }, { | |
65 | 'url': 'https://www.amazon.in/minitv/tp/280d2564-584f-452f-9c98-7baf906e01ab?referrer=https%3A%2F%2Fwww.amazon.in%2Fminitv', | |
48652590 | 66 | 'info_dict': { |
67 | 'id': 'amzn1.dv.gti.280d2564-584f-452f-9c98-7baf906e01ab', | |
68 | 'ext': 'mp4', | |
69 | 'title': 'Jahaan', | |
70 | 'language': 'Hindi', | |
71 | 'thumbnail': r're:^https?://.*\.jpg', | |
72 | 'description': 'md5:05eb765a77bf703f322f120ec6867339', | |
73 | 'release_timestamp': 1647475200, | |
74 | 'release_date': '20220317', | |
75 | 'duration': 783, | |
76 | 'chapters': [], | |
77 | }, | |
78 | }, { | |
79 | 'url': 'https://www.amazon.in/minitv/tp/280d2564-584f-452f-9c98-7baf906e01ab', | |
80 | 'only_matching': True, | |
81 | }, { | |
82 | 'url': 'amazonminitv:amzn1.dv.gti.280d2564-584f-452f-9c98-7baf906e01ab', | |
83 | 'only_matching': True, | |
84 | }, { | |
85 | 'url': 'amazonminitv:280d2564-584f-452f-9c98-7baf906e01ab', | |
86 | 'only_matching': True, | |
87 | }] | |
a9d069f5 | 88 | |
48652590 | 89 | _GRAPHQL_QUERY_CONTENT = ''' |
90 | query content($sessionIdToken: String!, $deviceLocale: String, $contentId: ID!, $contentType: ContentType!, $clientId: String) { | |
91 | content( | |
92 | applicationContextInput: {deviceLocale: $deviceLocale, sessionIdToken: $sessionIdToken, clientId: $clientId} | |
93 | contentId: $contentId | |
94 | contentType: $contentType | |
95 | ) { | |
96 | contentId | |
97 | name | |
98 | ... on Episode { | |
99 | contentId | |
100 | vodType | |
101 | name | |
102 | images | |
103 | description { | |
104 | synopsis | |
105 | contentLengthInSeconds | |
106 | } | |
107 | publicReleaseDateUTC | |
108 | audioTracks | |
109 | seasonId | |
110 | seriesId | |
111 | seriesName | |
112 | seasonNumber | |
113 | episodeNumber | |
114 | timecode { | |
115 | endCreditsTime | |
116 | } | |
117 | } | |
118 | ... on MovieContent { | |
119 | contentId | |
120 | vodType | |
121 | name | |
122 | description { | |
123 | synopsis | |
124 | contentLengthInSeconds | |
125 | } | |
126 | images | |
127 | publicReleaseDateUTC | |
128 | audioTracks | |
129 | } | |
130 | } | |
131 | }''' | |
132 | ||
48652590 | 133 | def _real_extract(self, url): |
134 | asin = f'amzn1.dv.gti.{self._match_id(url)}' | |
48652590 | 135 | prs = self._call_api(asin, note='Downloading playback info') |
136 | ||
a9d069f5 | 137 | formats, subtitles = [], {} |
48652590 | 138 | for type_, asset in prs['playbackAssets'].items(): |
a9d069f5 | 139 | if not traverse_obj(asset, 'manifestUrl'): |
48652590 | 140 | continue |
141 | if type_ == 'hls': | |
142 | m3u8_fmts, m3u8_subs = self._extract_m3u8_formats_and_subtitles( | |
143 | asset['manifestUrl'], asin, ext='mp4', entry_protocol='m3u8_native', | |
144 | m3u8_id=type_, fatal=False) | |
145 | formats.extend(m3u8_fmts) | |
146 | subtitles = self._merge_subtitles(subtitles, m3u8_subs) | |
147 | elif type_ == 'dash': | |
148 | mpd_fmts, mpd_subs = self._extract_mpd_formats_and_subtitles( | |
149 | asset['manifestUrl'], asin, mpd_id=type_, fatal=False) | |
150 | formats.extend(mpd_fmts) | |
151 | subtitles = self._merge_subtitles(subtitles, mpd_subs) | |
a9d069f5 | 152 | else: |
153 | self.report_warning(f'Unknown asset type: {type_}') | |
48652590 | 154 | |
a9d069f5 | 155 | title_info = self._call_api( |
156 | asin, note='Downloading title info', data={ | |
157 | 'operationName': 'content', | |
158 | 'variables': {'contentId': asin}, | |
159 | 'query': self._GRAPHQL_QUERY_CONTENT, | |
160 | }) | |
48652590 | 161 | credits_time = try_get(title_info, lambda x: x['timecode']['endCreditsTime'] / 1000) |
48652590 | 162 | is_episode = title_info.get('vodType') == 'EPISODE' |
163 | ||
164 | return { | |
165 | 'id': asin, | |
166 | 'title': title_info.get('name'), | |
167 | 'formats': formats, | |
168 | 'subtitles': subtitles, | |
169 | 'language': traverse_obj(title_info, ('audioTracks', 0)), | |
170 | 'thumbnails': [{ | |
171 | 'id': type_, | |
172 | 'url': url, | |
173 | } for type_, url in (title_info.get('images') or {}).items()], | |
174 | 'description': traverse_obj(title_info, ('description', 'synopsis')), | |
175 | 'release_timestamp': int_or_none(try_get(title_info, lambda x: x['publicReleaseDateUTC'] / 1000)), | |
a9d069f5 | 176 | 'duration': traverse_obj(title_info, ('description', 'contentLengthInSeconds')), |
177 | 'chapters': [{ | |
178 | 'start_time': credits_time, | |
179 | 'title': 'End Credits', | |
180 | }] if credits_time else [], | |
48652590 | 181 | 'series': title_info.get('seriesName'), |
182 | 'series_id': title_info.get('seriesId'), | |
183 | 'season_number': title_info.get('seasonNumber'), | |
184 | 'season_id': title_info.get('seasonId'), | |
185 | 'episode': title_info.get('name') if is_episode else None, | |
186 | 'episode_number': title_info.get('episodeNumber'), | |
187 | 'episode_id': asin if is_episode else None, | |
188 | } | |
189 | ||
190 | ||
a9d069f5 | 191 | class AmazonMiniTVSeasonIE(AmazonMiniTVBaseIE): |
48652590 | 192 | IE_NAME = 'amazonminitv:season' |
193 | _VALID_URL = r'amazonminitv:season:(?:amzn1\.dv\.gti\.)?(?P<id>[a-f0-9-]+)' | |
a5387729 | 194 | IE_DESC = 'Amazon MiniTV Season, "minitv:season:" prefix' |
48652590 | 195 | _TESTS = [{ |
196 | 'url': 'amazonminitv:season:amzn1.dv.gti.0aa996eb-6a1b-4886-a342-387fbd2f1db0', | |
197 | 'playlist_mincount': 6, | |
198 | 'info_dict': { | |
199 | 'id': 'amzn1.dv.gti.0aa996eb-6a1b-4886-a342-387fbd2f1db0', | |
200 | }, | |
201 | }, { | |
202 | 'url': 'amazonminitv:season:0aa996eb-6a1b-4886-a342-387fbd2f1db0', | |
203 | 'only_matching': True, | |
204 | }] | |
a9d069f5 | 205 | |
48652590 | 206 | _GRAPHQL_QUERY = ''' |
207 | query getEpisodes($sessionIdToken: String!, $clientId: String, $episodeOrSeasonId: ID!, $deviceLocale: String) { | |
208 | getEpisodes( | |
209 | applicationContextInput: {sessionIdToken: $sessionIdToken, deviceLocale: $deviceLocale, clientId: $clientId} | |
210 | episodeOrSeasonId: $episodeOrSeasonId | |
211 | ) { | |
212 | episodes { | |
213 | ... on Episode { | |
214 | contentId | |
215 | name | |
216 | images | |
217 | seriesName | |
218 | seasonId | |
219 | seriesId | |
220 | seasonNumber | |
221 | episodeNumber | |
222 | description { | |
223 | synopsis | |
224 | contentLengthInSeconds | |
225 | } | |
226 | publicReleaseDateUTC | |
227 | } | |
228 | } | |
229 | } | |
230 | } | |
231 | ''' | |
232 | ||
233 | def _entries(self, asin): | |
234 | season_info = self._call_api( | |
a9d069f5 | 235 | asin, note='Downloading season info', data={ |
48652590 | 236 | 'operationName': 'getEpisodes', |
a9d069f5 | 237 | 'variables': {'episodeOrSeasonId': asin}, |
48652590 | 238 | 'query': self._GRAPHQL_QUERY, |
a9d069f5 | 239 | }) |
48652590 | 240 | |
241 | for episode in season_info['episodes']: | |
a9d069f5 | 242 | yield self.url_result( |
243 | f'amazonminitv:{episode["contentId"]}', AmazonMiniTVIE, episode['contentId']) | |
48652590 | 244 | |
245 | def _real_extract(self, url): | |
246 | asin = f'amzn1.dv.gti.{self._match_id(url)}' | |
a9d069f5 | 247 | return self.playlist_result(self._entries(asin), asin) |
48652590 | 248 | |
249 | ||
a9d069f5 | 250 | class AmazonMiniTVSeriesIE(AmazonMiniTVBaseIE): |
48652590 | 251 | IE_NAME = 'amazonminitv:series' |
252 | _VALID_URL = r'amazonminitv:series:(?:amzn1\.dv\.gti\.)?(?P<id>[a-f0-9-]+)' | |
a5387729 | 253 | IE_DESC = 'Amazon MiniTV Series, "minitv:series:" prefix' |
48652590 | 254 | _TESTS = [{ |
255 | 'url': 'amazonminitv:series:amzn1.dv.gti.56521d46-b040-4fd5-872e-3e70476a04b0', | |
256 | 'playlist_mincount': 3, | |
257 | 'info_dict': { | |
258 | 'id': 'amzn1.dv.gti.56521d46-b040-4fd5-872e-3e70476a04b0', | |
259 | }, | |
260 | }, { | |
261 | 'url': 'amazonminitv:series:56521d46-b040-4fd5-872e-3e70476a04b0', | |
262 | 'only_matching': True, | |
263 | }] | |
a9d069f5 | 264 | |
48652590 | 265 | _GRAPHQL_QUERY = ''' |
266 | query getSeasons($sessionIdToken: String!, $deviceLocale: String, $episodeOrSeasonOrSeriesId: ID!, $clientId: String) { | |
267 | getSeasons( | |
268 | applicationContextInput: {deviceLocale: $deviceLocale, sessionIdToken: $sessionIdToken, clientId: $clientId} | |
269 | episodeOrSeasonOrSeriesId: $episodeOrSeasonOrSeriesId | |
270 | ) { | |
271 | seasons { | |
272 | seasonId | |
273 | } | |
274 | } | |
275 | } | |
276 | ''' | |
277 | ||
278 | def _entries(self, asin): | |
279 | season_info = self._call_api( | |
a9d069f5 | 280 | asin, note='Downloading series info', data={ |
48652590 | 281 | 'operationName': 'getSeasons', |
a9d069f5 | 282 | 'variables': {'episodeOrSeasonOrSeriesId': asin}, |
48652590 | 283 | 'query': self._GRAPHQL_QUERY, |
a9d069f5 | 284 | }) |
48652590 | 285 | |
286 | for season in season_info['seasons']: | |
287 | yield self.url_result(f'amazonminitv:season:{season["seasonId"]}', AmazonMiniTVSeasonIE, season['seasonId']) | |
288 | ||
289 | def _real_extract(self, url): | |
290 | asin = f'amzn1.dv.gti.{self._match_id(url)}' | |
a9d069f5 | 291 | return self.playlist_result(self._entries(asin), asin) |