From 7d765c3cda83d46ebdcf9f181dc874e5ed7cdf9a Mon Sep 17 00:00:00 2001 From: Nokse Date: Wed, 14 May 2025 13:48:27 +0200 Subject: [PATCH 1/4] Add Track.get_radio_mix and Artist.get_radio_mix to get radio as Mix --- tests/test_artist.py | 6 ++++++ tests/test_media.py | 6 ++++++ tidalapi/artist.py | 12 ++++++++++++ tidalapi/media.py | 12 ++++++++++++ 4 files changed, 36 insertions(+) diff --git a/tests/test_artist.py b/tests/test_artist.py index df2e6bc7..361c2925 100644 --- a/tests/test_artist.py +++ b/tests/test_artist.py @@ -140,6 +140,12 @@ def test_get_radio(session): assert radio[0].artist.name == artist.name +def test_get_radio_mix(session): + artist = session.artist(3514310) + radio = artist.get_radio_mix() + assert radio.id == "000038b3b74d5ce3a17b43a36d62bb" + + def test_artist_image(session): artist = session.artist(4822757) verify_image_cover(session, artist, [160, 320, 480, 750]) diff --git a/tests/test_media.py b/tests/test_media.py index 04f4be4f..534f98ad 100644 --- a/tests/test_media.py +++ b/tests/test_media.py @@ -448,6 +448,12 @@ def test_get_track_radio_limit_100(session): assert len(similar_tracks) == 100 +def test_get_radio_mix(session): + track = session.track(12445712) + radio = track.get_radio_mix() + assert radio.id == "001c2cbc32b5b7c17f8c0aa55d9541" + + def test_get_stream_bts(session): track = session.track(77646170) # Beck: Sea Change, Track: The Golden Age # Set session as BTS type (i.e. low_320k/HIGH Quality) diff --git a/tidalapi/artist.py b/tidalapi/artist.py index 23cdf6fc..003ce25e 100644 --- a/tidalapi/artist.py +++ b/tidalapi/artist.py @@ -28,6 +28,8 @@ from tidalapi.exceptions import ObjectNotFound, TooManyRequests from tidalapi.types import JsonObj +from . import mix + if TYPE_CHECKING: from tidalapi.album import Album from tidalapi.media import Track, Video @@ -242,6 +244,16 @@ def get_radio(self) -> List["Track"]: ), ) + def get_radio_mix(self) -> mix.Mix: + """Queries TIDAL for the artist radio, which is a mix of tracks that are similar + to what the artist makes. + + :return: A :class:`Mix ` + """ + json = self.request.request("GET", f"artists/{self.id}/mix").json() + + return self.session.mix(json.get("id")) + def items(self) -> List[NoReturn]: """The artist page does not supply any items. This only exists for symmetry with other model types. diff --git a/tidalapi/media.py b/tidalapi/media.py index 598a823e..17a10381 100644 --- a/tidalapi/media.py +++ b/tidalapi/media.py @@ -51,6 +51,8 @@ ) from tidalapi.types import JsonObj +from . import mix + class Quality(str, Enum): low_96k: str = "LOW" @@ -407,6 +409,16 @@ def get_track_radio(self, limit: int = 100) -> List["Track"]: assert isinstance(tracks, list) return cast(List["Track"], tracks) + def get_radio_mix(self) -> mix.Mix: + """Queries TIDAL for the track radio, which is a mix of tracks that are similar + to this track. + + :return: A :class:`Mix ` + """ + json = self.request.request("GET", f"tracks/{self.id}/mix").json() + + return self.session.mix(json.get("id")) + def get_stream(self) -> "Stream": """Retrieves the track streaming object, allowing for audio transmission. From 7bca56ae541819c1341da8ffb3fe6cf537b0dc81 Mon Sep 17 00:00:00 2001 From: tehkillerbee Date: Tue, 10 Jun 2025 22:44:40 +0200 Subject: [PATCH 2/4] Cleanup function descriptions. Check request result and throw error if necessary. --- tidalapi/artist.py | 46 +++++++++++++++++++++++++++++----------------- tidalapi/media.py | 23 ++++++++++++++--------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/tidalapi/artist.py b/tidalapi/artist.py index 003ce25e..341b79ec 100644 --- a/tidalapi/artist.py +++ b/tidalapi/artist.py @@ -25,7 +25,7 @@ import dateutil.parser from typing_extensions import NoReturn -from tidalapi.exceptions import ObjectNotFound, TooManyRequests +from tidalapi.exceptions import ObjectNotFound, TooManyRequests, MetadataNotAvailable from tidalapi.types import JsonObj from . import mix @@ -228,31 +228,43 @@ def get_similar(self) -> List["Artist"]: ), ) - def get_radio(self) -> List["Track"]: - """Queries TIDAL for the artist radio, which is a mix of tracks that are similar - to what the artist makes. + def get_radio(self, limit: int = 100) -> List["Track"]: + """Queries TIDAL for the artist radio, i.e. a list of tracks similar to this artist. :return: A list of :class:`Tracks ` """ - params = {"limit": 100} - return cast( - List["Track"], - self.request.map_request( - f"artists/{self.id}/radio", - params=params, - parse=self.session.parse_track, - ), - ) + params = {"limit": limit} + + try: + request = self.request.request( + "GET", "artists/%s/radio" % self.id, params=params + ) + except ObjectNotFound: + raise MetadataNotAvailable("Track radio not available for this track") + except TooManyRequests: + raise TooManyRequests("Track radio unavailable") + else: + json_obj = request.json() + radio = self.request.map_json(json_obj, parse=self.session.parse_track) + assert isinstance(radio, list) + return cast(List["Track"], radio) def get_radio_mix(self) -> mix.Mix: - """Queries TIDAL for the artist radio, which is a mix of tracks that are similar - to what the artist makes. + """Queries TIDAL for the artist radio, i.e. mix of tracks that are similar to this artist. :return: A :class:`Mix ` + :raises: A :class:`exceptions.MetadataNotAvailable` if no track radio mix is available """ - json = self.request.request("GET", f"artists/{self.id}/mix").json() - return self.session.mix(json.get("id")) + try: + request = self.request.request("GET", "artists/%s/mix" % self.id) + except ObjectNotFound: + raise MetadataNotAvailable("Artist radio not available for this artist") + except TooManyRequests: + raise TooManyRequests("Artist radio unavailable") + else: + json_obj = request.json() + return self.session.mix(json_obj.get("id")) def items(self) -> List[NoReturn]: """The artist page does not supply any items. This only exists for symmetry with diff --git a/tidalapi/media.py b/tidalapi/media.py index 17a10381..e12912a4 100644 --- a/tidalapi/media.py +++ b/tidalapi/media.py @@ -387,11 +387,10 @@ def lyrics(self) -> "Lyrics": return cast("Lyrics", lyrics) def get_track_radio(self, limit: int = 100) -> List["Track"]: - """Queries TIDAL for the track radio, which is a mix of tracks that are similar - to this track. + """Queries TIDAL for the track radio mix as a list of tracks similar to this track. :return: A list of :class:`Tracks ` - :raises: A :class:`exceptions.MetadataNotAvailable` if no track radio is available + :raises: A :class:`exceptions.MetadataNotAvailable` if no track radio mix is available """ params = {"limit": limit} @@ -402,7 +401,7 @@ def get_track_radio(self, limit: int = 100) -> List["Track"]: except ObjectNotFound: raise MetadataNotAvailable("Track radio not available for this track") except TooManyRequests: - raise TooManyRequests("Track radio unavailable)") + raise TooManyRequests("Track radio unavailable") else: json_obj = request.json() tracks = self.requests.map_json(json_obj, parse=self.session.parse_track) @@ -410,14 +409,20 @@ def get_track_radio(self, limit: int = 100) -> List["Track"]: return cast(List["Track"], tracks) def get_radio_mix(self) -> mix.Mix: - """Queries TIDAL for the track radio, which is a mix of tracks that are similar - to this track. + """Queries TIDAL for the track radio mix of tracks that are similar to this track. :return: A :class:`Mix ` + :raises: A :class:`exceptions.MetadataNotAvailable` if no track radio mix is available """ - json = self.request.request("GET", f"tracks/{self.id}/mix").json() - - return self.session.mix(json.get("id")) + try: + request = self.requests.request("GET", "tracks/%s/mix" % self.id) + except ObjectNotFound: + raise MetadataNotAvailable("Track radio not available for this track") + except TooManyRequests: + raise TooManyRequests("Track radio unavailable") + else: + json_obj = request.json() + return self.session.mix(json_obj.get("id")) def get_stream(self) -> "Stream": """Retrieves the track streaming object, allowing for audio transmission. From e6b0e7fa4b07a4740342245e8197e3a9bb8bbabe Mon Sep 17 00:00:00 2001 From: tehkillerbee Date: Tue, 10 Jun 2025 22:45:06 +0200 Subject: [PATCH 3/4] Check MixType --- tests/test_artist.py | 4 ++++ tests/test_media.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tests/test_artist.py b/tests/test_artist.py index 361c2925..f33d03ef 100644 --- a/tests/test_artist.py +++ b/tests/test_artist.py @@ -20,6 +20,7 @@ import tidalapi from tidalapi.exceptions import ObjectNotFound +from tidalapi.mix import MixType from .cover import verify_image_cover @@ -144,6 +145,9 @@ def test_get_radio_mix(session): artist = session.artist(3514310) radio = artist.get_radio_mix() assert radio.id == "000038b3b74d5ce3a17b43a36d62bb" + assert radio.title == "The Turtles" + assert radio.sub_title == "Artist Radio" + assert radio.mix_type == MixType.artist def test_artist_image(session): diff --git a/tests/test_media.py b/tests/test_media.py index 534f98ad..67f6ec1e 100644 --- a/tests/test_media.py +++ b/tests/test_media.py @@ -31,6 +31,7 @@ MimeType, Quality, ) +from tidalapi.mix import MixType from .cover import verify_image_resolution, verify_video_resolution @@ -452,6 +453,9 @@ def test_get_radio_mix(session): track = session.track(12445712) radio = track.get_radio_mix() assert radio.id == "001c2cbc32b5b7c17f8c0aa55d9541" + assert radio.title == "Happy Together" + assert radio.sub_title == "The Turtles" + assert radio.mix_type == MixType.track def test_get_stream_bts(session): From ebdbe76bd1b1e94b849fa7243708bc0793830435 Mon Sep 17 00:00:00 2001 From: tehkillerbee Date: Tue, 10 Jun 2025 22:45:42 +0200 Subject: [PATCH 4/4] Formatting fix --- tidalapi/artist.py | 8 +++++--- tidalapi/media.py | 6 ++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tidalapi/artist.py b/tidalapi/artist.py index 341b79ec..0c61948b 100644 --- a/tidalapi/artist.py +++ b/tidalapi/artist.py @@ -25,7 +25,7 @@ import dateutil.parser from typing_extensions import NoReturn -from tidalapi.exceptions import ObjectNotFound, TooManyRequests, MetadataNotAvailable +from tidalapi.exceptions import MetadataNotAvailable, ObjectNotFound, TooManyRequests from tidalapi.types import JsonObj from . import mix @@ -229,7 +229,8 @@ def get_similar(self) -> List["Artist"]: ) def get_radio(self, limit: int = 100) -> List["Track"]: - """Queries TIDAL for the artist radio, i.e. a list of tracks similar to this artist. + """Queries TIDAL for the artist radio, i.e. a list of tracks similar to this + artist. :return: A list of :class:`Tracks ` """ @@ -250,7 +251,8 @@ def get_radio(self, limit: int = 100) -> List["Track"]: return cast(List["Track"], radio) def get_radio_mix(self) -> mix.Mix: - """Queries TIDAL for the artist radio, i.e. mix of tracks that are similar to this artist. + """Queries TIDAL for the artist radio, i.e. mix of tracks that are similar to + this artist. :return: A :class:`Mix ` :raises: A :class:`exceptions.MetadataNotAvailable` if no track radio mix is available diff --git a/tidalapi/media.py b/tidalapi/media.py index e12912a4..eda54141 100644 --- a/tidalapi/media.py +++ b/tidalapi/media.py @@ -387,7 +387,8 @@ def lyrics(self) -> "Lyrics": return cast("Lyrics", lyrics) def get_track_radio(self, limit: int = 100) -> List["Track"]: - """Queries TIDAL for the track radio mix as a list of tracks similar to this track. + """Queries TIDAL for the track radio mix as a list of tracks similar to this + track. :return: A list of :class:`Tracks ` :raises: A :class:`exceptions.MetadataNotAvailable` if no track radio mix is available @@ -409,7 +410,8 @@ def get_track_radio(self, limit: int = 100) -> List["Track"]: return cast(List["Track"], tracks) def get_radio_mix(self) -> mix.Mix: - """Queries TIDAL for the track radio mix of tracks that are similar to this track. + """Queries TIDAL for the track radio mix of tracks that are similar to this + track. :return: A :class:`Mix ` :raises: A :class:`exceptions.MetadataNotAvailable` if no track radio mix is available