SaavnAPI 2026.4.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- saavnapi-2026.4.11/JioSaavn/Core/Client.py +16 -0
- saavnapi-2026.4.11/JioSaavn/Core/Parser.py +17 -0
- saavnapi-2026.4.11/JioSaavn/Core/Request.py +33 -0
- saavnapi-2026.4.11/JioSaavn/Formatter/Album.py +19 -0
- saavnapi-2026.4.11/JioSaavn/Formatter/Playlist.py +17 -0
- saavnapi-2026.4.11/JioSaavn/Formatter/Song.py +60 -0
- saavnapi-2026.4.11/JioSaavn/Modules/Album.py +19 -0
- saavnapi-2026.4.11/JioSaavn/Modules/Lyrics.py +17 -0
- saavnapi-2026.4.11/JioSaavn/Modules/Playlist.py +19 -0
- saavnapi-2026.4.11/JioSaavn/Modules/Search.py +46 -0
- saavnapi-2026.4.11/JioSaavn/Modules/Song.py +18 -0
- saavnapi-2026.4.11/JioSaavn/Utils/Crypto.py +21 -0
- saavnapi-2026.4.11/JioSaavn/Utils/Text.py +8 -0
- saavnapi-2026.4.11/JioSaavn/__init__.py +13 -0
- saavnapi-2026.4.11/JioSaavn/endpoints.py +9 -0
- saavnapi-2026.4.11/PKG-INFO +23 -0
- saavnapi-2026.4.11/SaavnAPI.egg-info/PKG-INFO +23 -0
- saavnapi-2026.4.11/SaavnAPI.egg-info/SOURCES.txt +21 -0
- saavnapi-2026.4.11/SaavnAPI.egg-info/dependency_links.txt +1 -0
- saavnapi-2026.4.11/SaavnAPI.egg-info/requires.txt +2 -0
- saavnapi-2026.4.11/SaavnAPI.egg-info/top_level.txt +1 -0
- saavnapi-2026.4.11/pyproject.toml +51 -0
- saavnapi-2026.4.11/setup.cfg +4 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .Request import Request
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class JioSaavnClient:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self._req = Request()
|
|
7
|
+
|
|
8
|
+
async def __aenter__(self):
|
|
9
|
+
await self._req.__aenter__()
|
|
10
|
+
return self
|
|
11
|
+
|
|
12
|
+
async def __aexit__(self, *args):
|
|
13
|
+
await self._req.__aexit__(*args)
|
|
14
|
+
|
|
15
|
+
async def get(self, url):
|
|
16
|
+
return await self._req.get(url)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
def extract_json(text):
|
|
2
|
+
start = text.find("{")
|
|
3
|
+
if start == -1:
|
|
4
|
+
return None
|
|
5
|
+
|
|
6
|
+
stack = 0
|
|
7
|
+
|
|
8
|
+
for i in range(start, len(text)):
|
|
9
|
+
if text[i] == "{":
|
|
10
|
+
stack += 1
|
|
11
|
+
elif text[i] == "}":
|
|
12
|
+
stack -= 1
|
|
13
|
+
|
|
14
|
+
if stack == 0:
|
|
15
|
+
return text[start:i + 1]
|
|
16
|
+
|
|
17
|
+
return None
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
import json
|
|
3
|
+
from .Parser import extract_json
|
|
4
|
+
|
|
5
|
+
HEADERS = {
|
|
6
|
+
"User-Agent": "Mozilla/5.0"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Request:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.session = None
|
|
13
|
+
|
|
14
|
+
async def __aenter__(self):
|
|
15
|
+
self.session = aiohttp.ClientSession(headers=HEADERS)
|
|
16
|
+
return self
|
|
17
|
+
|
|
18
|
+
async def __aexit__(self, *args):
|
|
19
|
+
await self.session.close()
|
|
20
|
+
|
|
21
|
+
async def get(self, url):
|
|
22
|
+
try:
|
|
23
|
+
async with self.session.get(url, timeout=10) as res:
|
|
24
|
+
text = await res.text()
|
|
25
|
+
raw = extract_json(text)
|
|
26
|
+
|
|
27
|
+
if not raw:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
return json.loads(raw)
|
|
31
|
+
|
|
32
|
+
except Exception:
|
|
33
|
+
return None
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ..Utils.Text import clean
|
|
2
|
+
from .Song import format_song
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def format_album(data, lyrics_func=None):
|
|
6
|
+
data["image"] = data.get("image", "").replace("150x150", "500x500")
|
|
7
|
+
data["name"] = clean(data.get("name"))
|
|
8
|
+
data["primary_artists"] = clean(data.get("primary_artists"))
|
|
9
|
+
data["title"] = clean(data.get("title"))
|
|
10
|
+
|
|
11
|
+
songs = data.get("songs", [])
|
|
12
|
+
|
|
13
|
+
formatted = []
|
|
14
|
+
for s in songs:
|
|
15
|
+
formatted.append(await format_song(s, lyrics_func))
|
|
16
|
+
|
|
17
|
+
data["songs"] = formatted
|
|
18
|
+
|
|
19
|
+
return data
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ..Utils.Text import clean
|
|
2
|
+
from .Song import format_song
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def format_playlist(data, lyrics_func=None):
|
|
6
|
+
data["firstname"] = clean(data.get("firstname"))
|
|
7
|
+
data["listname"] = clean(data.get("listname"))
|
|
8
|
+
|
|
9
|
+
songs = data.get("songs", [])
|
|
10
|
+
|
|
11
|
+
formatted = []
|
|
12
|
+
for s in songs:
|
|
13
|
+
formatted.append(await format_song(s, lyrics_func))
|
|
14
|
+
|
|
15
|
+
data["songs"] = formatted
|
|
16
|
+
|
|
17
|
+
return data
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from ..Utils.Crypto import decrypt
|
|
2
|
+
from ..Utils.Text import clean
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def format_song(data, lyrics_func=None):
|
|
6
|
+
more = data.get("more_info", {})
|
|
7
|
+
|
|
8
|
+
data["id"] = data.get("id")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
data["song"] = clean(data.get("title"))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
data["album"] = clean(more.get("album"))
|
|
15
|
+
data["music"] = clean(more.get("music"))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
artists = more.get("artistMap", {}).get("primary_artists", [])
|
|
19
|
+
data["primary_artists"] = ", ".join([a.get("name", "") for a in artists])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if data.get("image"):
|
|
23
|
+
data["image"] = data["image"].replace("150x150", "500x500")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
duration = data.get("duration")
|
|
27
|
+
if duration:
|
|
28
|
+
try:
|
|
29
|
+
duration = int(duration)
|
|
30
|
+
mins = duration // 60
|
|
31
|
+
secs = duration % 60
|
|
32
|
+
data["duration"] = f"{mins}:{secs:02d}"
|
|
33
|
+
except:
|
|
34
|
+
data["duration"] = None
|
|
35
|
+
else:
|
|
36
|
+
data["duration"] = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
views = more.get("play_count") or more.get("view_count")
|
|
40
|
+
data["views"] = views
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
enc = more.get("encrypted_media_url")
|
|
44
|
+
if enc:
|
|
45
|
+
media = decrypt(enc)
|
|
46
|
+
|
|
47
|
+
if media and more.get("320kbps") != "true":
|
|
48
|
+
media = media.replace("_320.mp4", "_160.mp4")
|
|
49
|
+
|
|
50
|
+
data["media_url"] = media
|
|
51
|
+
else:
|
|
52
|
+
data["media_url"] = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if lyrics_func and more.get("has_lyrics") == "true":
|
|
56
|
+
data["lyrics"] = await lyrics_func(data.get("id"))
|
|
57
|
+
else:
|
|
58
|
+
data["lyrics"] = None
|
|
59
|
+
|
|
60
|
+
return data
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ..Core.Request import Request
|
|
2
|
+
from .. import endpoints
|
|
3
|
+
from ..Formatter.Album import format_album
|
|
4
|
+
from .Lyrics import get_lyrics
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def get_album(album_id, client=None):
|
|
8
|
+
url = endpoints.ALBUM + album_id
|
|
9
|
+
|
|
10
|
+
if client:
|
|
11
|
+
data = await client.get(url)
|
|
12
|
+
else:
|
|
13
|
+
async with Request() as req:
|
|
14
|
+
data = await req.get(url)
|
|
15
|
+
|
|
16
|
+
if not data:
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
return await format_album(data, get_lyrics)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ..Core.Request import Request
|
|
2
|
+
from .. import endpoints
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async def get_lyrics(song_id, client=None):
|
|
6
|
+
url = endpoints.LYRICS + song_id
|
|
7
|
+
|
|
8
|
+
if client:
|
|
9
|
+
data = await client.get(url)
|
|
10
|
+
else:
|
|
11
|
+
async with Request() as req:
|
|
12
|
+
data = await req.get(url)
|
|
13
|
+
|
|
14
|
+
if not data:
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
return data.get("lyrics")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from ..Core.Request import Request
|
|
2
|
+
from .. import endpoints
|
|
3
|
+
from ..Formatter.Playlist import format_playlist
|
|
4
|
+
from .Lyrics import get_lyrics
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def get_playlist(list_id, client=None):
|
|
8
|
+
url = endpoints.PLAYLIST + list_id
|
|
9
|
+
|
|
10
|
+
if client:
|
|
11
|
+
data = await client.get(url)
|
|
12
|
+
else:
|
|
13
|
+
async with Request() as req:
|
|
14
|
+
data = await req.get(url)
|
|
15
|
+
|
|
16
|
+
if not data:
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
return await format_playlist(data, get_lyrics)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import urllib.parse
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
from ..Core.Request import Request
|
|
5
|
+
from .. import endpoints
|
|
6
|
+
from ..Formatter.Song import format_song
|
|
7
|
+
from .Lyrics import get_lyrics
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def search(query, limit=10, lyrics=False, client=None):
|
|
11
|
+
query = urllib.parse.quote(query)
|
|
12
|
+
url = endpoints.SEARCH + query
|
|
13
|
+
|
|
14
|
+
if client:
|
|
15
|
+
data = await client.get(url)
|
|
16
|
+
else:
|
|
17
|
+
async with Request() as req:
|
|
18
|
+
data = await req.get(url)
|
|
19
|
+
|
|
20
|
+
if not data:
|
|
21
|
+
return []
|
|
22
|
+
|
|
23
|
+
songs = data.get("results") or data.get("songs", {}).get("data", [])
|
|
24
|
+
songs = songs[:limit]
|
|
25
|
+
|
|
26
|
+
sem = asyncio.Semaphore(5)
|
|
27
|
+
|
|
28
|
+
async def process(song):
|
|
29
|
+
try:
|
|
30
|
+
async with sem:
|
|
31
|
+
if not song.get("id"):
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
return await format_song(
|
|
35
|
+
song,
|
|
36
|
+
get_lyrics if lyrics else None
|
|
37
|
+
)
|
|
38
|
+
except:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
results = await asyncio.gather(*[
|
|
42
|
+
process(s) for s in songs
|
|
43
|
+
], return_exceptions=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
return [r for r in results if r and not isinstance(r, Exception)]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from ..Core.Request import Request
|
|
2
|
+
from .. import endpoints
|
|
3
|
+
from ..Formatter.Song import format_song
|
|
4
|
+
from .Lyrics import get_lyrics
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def get_song(song_id):
|
|
8
|
+
async with Request() as req:
|
|
9
|
+
data = await req.get(endpoints.SONG + song_id)
|
|
10
|
+
|
|
11
|
+
if not data:
|
|
12
|
+
return None
|
|
13
|
+
|
|
14
|
+
song = data.get(song_id)
|
|
15
|
+
if not song:
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
return await format_song(song, get_lyrics)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from pyDes import des, ECB, PAD_PKCS5
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
def decrypt(url):
|
|
6
|
+
try:
|
|
7
|
+
cipher = des(
|
|
8
|
+
b"38346591",
|
|
9
|
+
ECB,
|
|
10
|
+
b"\0\0\0\0\0\0\0\0",
|
|
11
|
+
pad=None,
|
|
12
|
+
padmode=PAD_PKCS5
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
decoded = base64.b64decode(url.strip())
|
|
16
|
+
decrypted = cipher.decrypt(decoded, padmode=PAD_PKCS5).decode("utf-8")
|
|
17
|
+
|
|
18
|
+
return decrypted.replace("_96.mp4", "_320.mp4")
|
|
19
|
+
|
|
20
|
+
except Exception:
|
|
21
|
+
return None
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .Modules.Search import search
|
|
2
|
+
from .Modules.Song import get_song
|
|
3
|
+
from .Modules.Album import get_album
|
|
4
|
+
from .Modules.Playlist import get_playlist
|
|
5
|
+
from .Modules.Lyrics import get_lyrics
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"search",
|
|
9
|
+
"get_song",
|
|
10
|
+
"get_album",
|
|
11
|
+
"get_playlist",
|
|
12
|
+
"get_lyrics"
|
|
13
|
+
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
BASE_URL = "https://www.jiosaavn.com/api.php"
|
|
2
|
+
|
|
3
|
+
COMMON = "&_format=json&_marker=0&api_version=4&cc=in"
|
|
4
|
+
|
|
5
|
+
SEARCH = BASE_URL + "?__call=search.getResults" + COMMON + "&q="
|
|
6
|
+
SONG = BASE_URL + "?__call=song.getDetails" + COMMON + "&pids="
|
|
7
|
+
ALBUM = BASE_URL + "?__call=content.getAlbumDetails" + COMMON + "&albumid="
|
|
8
|
+
PLAYLIST = BASE_URL + "?__call=playlist.getDetails" + COMMON + "&listid="
|
|
9
|
+
LYRICS = BASE_URL + "?__call=lyrics.getLyrics" + COMMON + "&lyrics_id="
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: SaavnAPI
|
|
3
|
+
Version: 2026.4.11
|
|
4
|
+
Summary: Fast async JioSaavn API wrapper for Python
|
|
5
|
+
Author: ABHISHEK THAKUR
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: jiosaavn,music,api,async,python,songs,lyrics,streaming
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Framework :: AsyncIO
|
|
19
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
23
|
+
Requires-Dist: pyDes>=2.0.1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: SaavnAPI
|
|
3
|
+
Version: 2026.4.11
|
|
4
|
+
Summary: Fast async JioSaavn API wrapper for Python
|
|
5
|
+
Author: ABHISHEK THAKUR
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: jiosaavn,music,api,async,python,songs,lyrics,streaming
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Framework :: AsyncIO
|
|
19
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
23
|
+
Requires-Dist: pyDes>=2.0.1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
JioSaavn/__init__.py
|
|
3
|
+
JioSaavn/endpoints.py
|
|
4
|
+
JioSaavn/Core/Client.py
|
|
5
|
+
JioSaavn/Core/Parser.py
|
|
6
|
+
JioSaavn/Core/Request.py
|
|
7
|
+
JioSaavn/Formatter/Album.py
|
|
8
|
+
JioSaavn/Formatter/Playlist.py
|
|
9
|
+
JioSaavn/Formatter/Song.py
|
|
10
|
+
JioSaavn/Modules/Album.py
|
|
11
|
+
JioSaavn/Modules/Lyrics.py
|
|
12
|
+
JioSaavn/Modules/Playlist.py
|
|
13
|
+
JioSaavn/Modules/Search.py
|
|
14
|
+
JioSaavn/Modules/Song.py
|
|
15
|
+
JioSaavn/Utils/Crypto.py
|
|
16
|
+
JioSaavn/Utils/Text.py
|
|
17
|
+
SaavnAPI.egg-info/PKG-INFO
|
|
18
|
+
SaavnAPI.egg-info/SOURCES.txt
|
|
19
|
+
SaavnAPI.egg-info/dependency_links.txt
|
|
20
|
+
SaavnAPI.egg-info/requires.txt
|
|
21
|
+
SaavnAPI.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
JioSaavn
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "SaavnAPI"
|
|
7
|
+
version = "2026.4.11"
|
|
8
|
+
description = "Fast async JioSaavn API wrapper for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "ABHISHEK THAKUR" }
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
license = { text = "MIT" }
|
|
17
|
+
|
|
18
|
+
keywords = [
|
|
19
|
+
"jiosaavn",
|
|
20
|
+
"music",
|
|
21
|
+
"api",
|
|
22
|
+
"async",
|
|
23
|
+
"python",
|
|
24
|
+
"songs",
|
|
25
|
+
"lyrics",
|
|
26
|
+
"streaming"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
dependencies = [
|
|
30
|
+
"aiohttp>=3.9.0",
|
|
31
|
+
"pyDes>=2.0.1"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
classifiers = [
|
|
35
|
+
"Development Status :: 5 - Production/Stable",
|
|
36
|
+
"Intended Audience :: Developers",
|
|
37
|
+
"Programming Language :: Python :: 3",
|
|
38
|
+
"Programming Language :: Python :: 3.8",
|
|
39
|
+
"Programming Language :: Python :: 3.9",
|
|
40
|
+
"Programming Language :: Python :: 3.10",
|
|
41
|
+
"Programming Language :: Python :: 3.11",
|
|
42
|
+
"Programming Language :: Python :: 3.12",
|
|
43
|
+
"License :: OSI Approved :: MIT License",
|
|
44
|
+
"Operating System :: OS Independent",
|
|
45
|
+
"Framework :: AsyncIO",
|
|
46
|
+
"Topic :: Multimedia :: Sound/Audio"
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.packages.find]
|
|
50
|
+
where = ["."]
|
|
51
|
+
include = ["JioSaavn*"]
|