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.
@@ -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,8 @@
1
+ def clean(text):
2
+ if not text:
3
+ return ""
4
+ return (
5
+ text.replace(""", "'")
6
+ .replace("&", "&")
7
+ .replace("'", "'")
8
+ )
@@ -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,2 @@
1
+ aiohttp>=3.9.0
2
+ pyDes>=2.0.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*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+