5 from .common
import InfoExtractor
17 from ..utils
.traversal
import traverse_obj
20 class JioSaavnBaseIE(InfoExtractor
):
21 _API_URL
= 'https://www.jiosaavn.com/api.php'
22 _VALID_BITRATES
= {'16', '32', '64', '128', '320'}
24 @functools.cached_property
25 def requested_bitrates(self
):
26 requested_bitrates
= self
._configuration
_arg
('bitrate', ['128', '320'], ie_key
='JioSaavn')
27 if invalid_bitrates
:= set(requested_bitrates
) - self
._VALID
_BITRATES
:
29 f
'Invalid bitrate(s): {", ".join(invalid_bitrates)}. '
30 f
'Valid bitrates are: {", ".join(sorted(self._VALID_BITRATES, key=int))}')
31 return requested_bitrates
33 def _extract_formats(self
, song_data
):
34 for bitrate
in self
.requested_bitrates
:
35 media_data
= self
._download
_json
(
36 self
._API
_URL
, song_data
['id'],
37 f
'Downloading format info for {bitrate}',
38 fatal
=False, data
=urlencode_postdata({
39 '__call': 'song.generateAuthToken',
42 'url': song_data
['encrypted_media_url'],
44 if not traverse_obj(media_data
, ('auth_url', {url_or_none}
)):
45 self
.report_warning(f
'Unable to extract format info for {bitrate}')
47 ext
= media_data
.get('type')
49 'url': media_data
['auth_url'],
50 'ext': 'm4a' if ext
== 'mp4' else ext
,
56 def _extract_song(self
, song_data
, url
=None):
57 info
= traverse_obj(song_data
, {
59 'title': ('song', {clean_html}
),
60 'album': ('album', {clean_html}
),
61 'thumbnail': ('image', {url_or_none}
, {lambda x
: re
.sub(r
'-\d+x\d+\.', '-500x500.', x
)}),
62 'duration': ('duration', {int_or_none}
),
63 'view_count': ('play_count', {int_or_none}
),
64 'release_year': ('year', {int_or_none}
),
65 'artists': ('primary_artists', {lambda x
: x
.split(', ') if x
else None}),
66 'webpage_url': ('perma_url', {url_or_none}
),
68 if webpage_url
:= info
.get('webpage_url') or url
:
69 info
['display_id'] = url_basename(webpage_url
)
70 info
['_old_archive_ids'] = [make_archive_id(JioSaavnSongIE
, info
['display_id'])]
74 def _call_api(self
, type_
, token
, note
='API', params
={}):
75 return self
._download
_json
(
76 self
._API
_URL
, token
, f
'Downloading {note} JSON', f
'Unable to download {note} JSON',
78 '__call': 'webapi.get',
87 def _yield_songs(self
, playlist_data
):
88 for song_data
in traverse_obj(playlist_data
, ('songs', lambda _
, v
: v
['id'] and v
['perma_url'])):
89 song_info
= self
._extract
_song
(song_data
)
90 url
= smuggle_url(song_info
['webpage_url'], {
91 'id': song_data
['id'],
92 'encrypted_media_url': song_data
['encrypted_media_url'],
94 yield self
.url_result(url
, JioSaavnSongIE
, url_transparent
=True, **song_info
)
97 class JioSaavnSongIE(JioSaavnBaseIE
):
98 IE_NAME
= 'jiosaavn:song'
99 _VALID_URL
= r
'https?://(?:www\.)?(?:jiosaavn\.com/song/[^/?#]+/|saavn\.com/s/song/(?:[^/?#]+/){3})(?P<id>[^/?#]+)'
101 'url': 'https://www.jiosaavn.com/song/leja-re/OQsEfQFVUXk',
102 'md5': '3b84396d15ed9e083c3106f1fa589c04',
105 'display_id': 'OQsEfQFVUXk',
109 'thumbnail': r
're:https?://c.saavncdn.com/258/Leja-Re-Hindi-2018-20181124024539-500x500.jpg',
112 'release_year': 2018,
113 'artists': ['Sandesh Shandilya', 'Dhvani Bhanushali', 'Tanishk Bagchi'],
114 '_old_archive_ids': ['jiosaavnsong OQsEfQFVUXk'],
117 'url': 'https://www.saavn.com/s/song/hindi/Saathiya/O-Humdum-Suniyo-Re/KAMiazoCblU',
118 'only_matching': True,
121 def _real_extract(self
, url
):
122 url
, smuggled_data
= unsmuggle_url(url
)
123 song_data
= traverse_obj(smuggled_data
, ({
125 'encrypted_media_url': ('encrypted_media_url', {str}
),
128 if 'id' in song_data
and 'encrypted_media_url' in song_data
:
129 result
= {'id': song_data
['id']}
131 # only extract metadata if this is not a url_transparent result
132 song_data
= self
._call
_api
('song', self
._match
_id
(url
))['songs'][0]
133 result
= self
._extract
_song
(song_data
, url
)
135 result
['formats'] = list(self
._extract
_formats
(song_data
))
139 class JioSaavnAlbumIE(JioSaavnBaseIE
):
140 IE_NAME
= 'jiosaavn:album'
141 _VALID_URL
= r
'https?://(?:www\.)?(?:jio)?saavn\.com/album/[^/?#]+/(?P<id>[^/?#]+)'
143 'url': 'https://www.jiosaavn.com/album/96/buIOjYZDrNA_',
145 'id': 'buIOjYZDrNA_',
148 'playlist_count': 10,
151 def _real_extract(self
, url
):
152 display_id
= self
._match
_id
(url
)
153 album_data
= self
._call
_api
('album', display_id
)
155 return self
.playlist_result(
156 self
._yield
_songs
(album_data
), display_id
, traverse_obj(album_data
, ('title', {str}
)))
159 class JioSaavnPlaylistIE(JioSaavnBaseIE
):
160 IE_NAME
= 'jiosaavn:playlist'
161 _VALID_URL
= r
'https?://(?:www\.)?(?:jio)?saavn\.com/(?:s/playlist/(?:[^/?#]+/){2}|featured/[^/?#]+/)(?P<id>[^/?#]+)'
163 'url': 'https://www.jiosaavn.com/s/playlist/2279fbe391defa793ad7076929a2f5c9/mood-english/LlJ8ZWT1ibN5084vKHRj2Q__',
165 'id': 'LlJ8ZWT1ibN5084vKHRj2Q__',
166 'title': 'Mood English',
168 'playlist_mincount': 301,
170 'url': 'https://www.jiosaavn.com/s/playlist/2279fbe391defa793ad7076929a2f5c9/mood-hindi/DVR,pFUOwyXqIp77B1JF,A__',
172 'id': 'DVR,pFUOwyXqIp77B1JF,A__',
173 'title': 'Mood Hindi',
175 'playlist_mincount': 801,
177 'url': 'https://www.jiosaavn.com/featured/taaza-tunes/Me5RridRfDk_',
179 'id': 'Me5RridRfDk_',
180 'title': 'Taaza Tunes',
182 'playlist_mincount': 301,
186 def _fetch_page(self
, token
, page
):
187 return self
._call
_api
(
188 'playlist', token
, f
'playlist page {page}', {'p': page
, 'n': self
._PAGE
_SIZE
})
190 def _entries(self
, token
, first_page_data
, page
):
191 page_data
= first_page_data
if not page
else self
._fetch
_page
(token
, page
+ 1)
192 yield from self
._yield
_songs
(page_data
)
194 def _real_extract(self
, url
):
195 display_id
= self
._match
_id
(url
)
196 playlist_data
= self
._fetch
_page
(display_id
, 1)
197 total_pages
= math
.ceil(int(playlist_data
['list_count']) / self
._PAGE
_SIZE
)
199 return self
.playlist_result(InAdvancePagedList(
200 functools
.partial(self
._entries
, display_id
, playlist_data
),
201 total_pages
, self
._PAGE
_SIZE
), display_id
, traverse_obj(playlist_data
, ('listname', {str}
)))