diff --git a/tests/test_artist.py b/tests/test_artist.py index df2e6bc7..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 @@ -140,6 +141,15 @@ 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" + assert radio.title == "The Turtles" + assert radio.sub_title == "Artist Radio" + assert radio.mix_type == MixType.artist + + 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..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 @@ -448,6 +449,15 @@ 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" + assert radio.title == "Happy Together" + assert radio.sub_title == "The Turtles" + assert radio.mix_type == MixType.track + + 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..0c61948b 100644 --- a/tidalapi/artist.py +++ b/tidalapi/artist.py @@ -25,9 +25,11 @@ import dateutil.parser from typing_extensions import NoReturn -from tidalapi.exceptions import ObjectNotFound, TooManyRequests +from tidalapi.exceptions import MetadataNotAvailable, ObjectNotFound, TooManyRequests from tidalapi.types import JsonObj +from . import mix + if TYPE_CHECKING: from tidalapi.album import Album from tidalapi.media import Track, Video @@ -226,21 +228,45 @@ 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, 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 + """ + + 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 598a823e..eda54141 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" @@ -385,11 +387,11 @@ 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} @@ -400,13 +402,30 @@ 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) assert isinstance(tracks, list) 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. + + :return: A :class:`Mix ` + :raises: A :class:`exceptions.MetadataNotAvailable` if no track radio mix is available + """ + 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.