Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,7 @@ prof/
# Json session files
tidal*.json
*.m3u8
*.mpd
*.mpd

# Local devtools
devtools
12 changes: 8 additions & 4 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
History
=======

Upcoming
v0.8.7
--------
* OAuth Client ID, secret updated - tehkillerbee_
* Bugfix: `albums_paginated` using `get_artists_count` instead of `get_albums_count` - rafrombrc_
* TooManyRequests now includes the retry_after header in its data. - semohr_
* Added a central error class (TidalAPIError) to allow for unified error handling. - semohr_

v0.8.6
------
* Add support for get<track, album, artist, playlist>count(), Workers: Use get_*_count to get the actual number of items. - tehkillerbee_
* Bugfix: Use get_*_count in workers to get the actual number of items. (Fixes #360) - tehkillerbee_
* Feature: Add support for get<track, album, artist, playlist>count(), Workers: Use get_*_count to get the actual number of items. - tehkillerbee_
* Feature: Get playlist tracks, items count. Get playlist tracks paginated. - tehkillerbee_
* Only return warning if page itemtype (v2) is not implemented (Fixes: #362) - tehkillerbee_
* Add legacy home endpoint for backwards compatibility - tehkillerbee_
* Get playlist tracks, items count. Get playlist tracks paginated. - tehkillerbee_

v0.8.5
------
Expand Down Expand Up @@ -248,7 +251,6 @@ v0.6.2
* Add version tag for Track - Husky22_
* Switch to netlify for documentation - morguldir_

.. _semohr: https://github.com/semohr
.. _morguldir: https://github.com/morguldir
.. _Husky22: https://github.com/Husky22
.. _ktnrg45: https://github.com/ktnrg45
Expand Down Expand Up @@ -278,4 +280,6 @@ v0.6.2
.. _C0rn3j: https://github.com/C0rn3j
.. _Nokse22: https://github.com/Nokse22
.. _nilathedragon: https://github.com/nilathedragon
.. _semohr: https://github.com/semohr
.. _rafrombrc: https://github.com/rafrombrc

2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
author = "The tidalapi Developers"

# The full version, including alpha/beta/rc tags
release = "0.8.6"
release = "0.8.7"


# -- General configuration ---------------------------------------------------
Expand Down
3 changes: 2 additions & 1 deletion examples/pkce_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@
album_id = "77646169" # Beck / Sea Change (Max quality: HI_RES_LOSSLESS FLAC, 24bit/192000Hz)
album = session.album(album_id)
res = album.get_audio_resolution()
tracks = album.tracks()

# list album tracks
tracks = album.tracks()
for track in tracks:
print("{}: '{}' by '{}'".format(track.id, track.name, track.artist.name))
stream = track.get_stream()
Expand Down
34 changes: 31 additions & 3 deletions examples/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,38 @@
# album_id = "110827651" # The Black Keys / Let's Rock (Max quality: LOSSLESS FLAC, 24bit/48000Hz)
album_id = "77646169" # Beck / Sea Change (Max quality: HI_RES_LOSSLESS FLAC, 24bit/192000Hz)
album = session.album(album_id)
tracks = album.tracks()
# list album tracks
print(album.name)

# list album tracks
tracks = album.tracks()
for track in tracks:
print("{}: '{}' by '{}'".format(track.id, track.name, track.artist.name))
print(track.get_url())
stream = track.get_stream()
print("MimeType:{}".format(stream.manifest_mime_type))

manifest = stream.get_stream_manifest()
audio_resolution = stream.get_audio_resolution()

print(
"track:{}, (quality:{}, codec:{}, {}bit/{}Hz)".format(
track.id,
stream.audio_quality,
manifest.get_codecs(),
audio_resolution[0],
audio_resolution[1],
)
)
if stream.is_mpd:
# HI_RES_LOSSLESS quality supported when using MPEG-DASH stream (PKCE only!)
# 1. Export as MPD manifest
mpd = stream.get_manifest_data()
# 2. Export as HLS m3u8 playlist
hls = manifest.get_hls()
# with open("{}_{}.mpd".format(album_id, track.id), "w") as my_file:
# my_file.write(mpd)
# with open("{}_{}.m3u8".format(album_id, track.id), "w") as my_file:
# my_file.write(hls)
elif stream.is_bts:
# Direct URL (m4a or flac) is available for Quality < HI_RES_LOSSLESS
url = manifest.get_urls()
break
935 changes: 786 additions & 149 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "tidalapi"
version = "0.8.6"
version = "0.8.7"
description = "Unofficial API for TIDAL music streaming service."
authors = ["Thomas Amland <thomas.amland@googlemail.com>"]
maintainers = ["tehkillerbee <tehkillerbee@users.noreply.github.com>"]
Expand Down Expand Up @@ -30,6 +30,7 @@ typing-extensions = "^4.12.2"
ratelimit = "^2.2.1"
isodate = "^0.7.2"
mpegdash = "^0.4.0"
pyaes = "^1.6.1"

[tool.poetry.group.dev.dependencies]
mypy = "^1.3.0"
Expand Down
2 changes: 1 addition & 1 deletion tidalapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
User,
)

__version__ = "0.8.6"
__version__ = "0.8.7"
76 changes: 18 additions & 58 deletions tidalapi/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
)
from urllib.parse import parse_qs, urlencode, urlsplit

import pyaes
import requests
from requests.exceptions import HTTPError

Expand Down Expand Up @@ -111,7 +112,6 @@ class Config:
api_v1_location: str = "https://api.tidal.com/v1/"
api_v2_location: str = "https://api.tidal.com/v2/"
openapi_v2_location: str = "https://openapi.tidal.com/v2/"
api_token: str
client_id: str
client_secret: str
image_url: str = "https://resources.tidal.com/images/%s/%ix%i.jpg"
Expand Down Expand Up @@ -151,65 +151,25 @@ def __init__(
else:
self.item_limit = item_limit

self.api_token = eval("\x67\x6c\x6f\x62\x61\x6c\x73".encode("437"))()[
"\x5f\x5f\x6e\x61\x6d\x65\x5f\x5f".encode(
"".join(map(chr, [105, 105, 99, 115, 97][::-1]))
).decode("".join(map(chr, [117, 116, 70, 95, 56])))
]
self.api_token += "." + eval(
"\x74\x79\x70\x65\x28\x73\x65\x6c\x66\x29\x2e\x5f\x5f\x6e\x61\x6d\x65\x5f\x5f".encode(
"".join(map(chr, [105, 105, 99, 115, 97][::-1]))
).decode(
"".join(map(chr, [117, 116, 70, 95, 56]))
# OAuth Client Authorization
self.client_id = base64.b64decode(
base64.b64decode(b"V214bmVWTnVhR3RpVnpVdw==")
+ base64.b64decode(b"VjJ4a1RFMUhiRFJXUVQwOQ==")
).decode("utf-8")
self.client_secret = base64.b64decode(
base64.b64decode(
b"VFZVMWRVOVZSbTFTUlVaeFpVaEtibE5yV2t0WmEzUlBWakI0YkZGWQ=="
)
)
token = self.api_token
token = token[:8] + token[16:]
self.api_token = list(
(base64.b64decode("d3RjaThkamFfbHlhQnBKaWQuMkMwb3puT2ZtaXhnMA==").decode())
)
tok = "".join(([chr(ord(x) - 2) for x in token[-6:]]))
token2 = token
token = token[:9]
token += tok
tok2 = "".join(([chr(ord(x) - 2) for x in token[:-7]]))
token = token[8:]
token = tok2 + token
self.api_token = list(
(base64.b64decode("enJVZzRiWF9IalZfVm5rZ2MuMkF0bURsUGRvZzRldA==").decode())
)
for word in token:
self.api_token.remove(word)
self.api_token = "".join(self.api_token)
string = ""
save = False
if not isinstance(token2, str):
save = True
string = "".encode("ISO-8859-1")
token2 = token2.encode("ISO-8859-1")
tok = string.join(([chr(ord(x) + 24) for x in token2[:-7]]))
token2 = token2[8:]
token2 = tok + token2
tok2 = string.join(([chr(ord(x) + 23) for x in token2[-6:]]))
token2 = token2[:9]
token2 += tok2
self.client_id = list(
(
base64.b64decode(
"VoxKgUt8aHlEhEZ5cYhKgVAucVp2hnOFUH1WgE5+QlY2"
"dWtYVEptd2x2YnR0UDd3bE1scmM3MnNlND0="
).decode("ISO-8859-1")
+ base64.b64decode(
b"YkV4U01WcElZbFZzVDJSV2FGRlZSWGhKVm14b1FtUnVhRUphZWpBOQ=="
)
)
if save:
token2.decode("ISO-8859-1").encode("utf-16")
self.client_id = [x.encode("ISO-8859-1") for x in self.client_id]
for word in token2:
self.client_id.remove(word)
self.client_id = "".join(self.client_id)
self.client_secret = self.client_id
self.client_id = self.api_token
# PKCE Authorization. We will keep the former `client_id` as a fallback / will only be used for non PCKE
).decode("utf-8")

# If client_secret not supplied, fall back to client_id (matching original behavior)
if not self.client_secret and self.client_id:
self.client_secret = self.client_id

# PKCE Client Authorization. We will keep the former `client_id` as a fallback / will only be used for non PCKE
# authorizations.
self.client_unique_key = format(random.getrandbits(64), "02x")
self.code_verifier = base64.urlsafe_b64encode(os.urandom(32))[:-1].decode(
Expand Down