2323
2424import dateutil .parser
2525
26- from tidalapi .exceptions import MetadataNotAvailable , ObjectNotFound
26+ from tidalapi .exceptions import MetadataNotAvailable , ObjectNotFound , TooManyRequests
2727from tidalapi .types import JsonObj
2828
2929if TYPE_CHECKING :
3333 from tidalapi .session import Session
3434
3535
36- DEFAULT_ALBUM_IMAGE = (
37- "https://tidal.com/browse/assets/images/defaultImages/defaultAlbumImage.png"
38- )
36+ DEFAULT_ALBUM_IMG = "0dfd3368-3aa1-49a3-935f-10ffb39803c0"
3937
4038
4139class Album :
@@ -53,6 +51,10 @@ class Album:
5351
5452 duration : Optional [int ] = - 1
5553 available : Optional [bool ] = False
54+ ad_supported_ready : Optional [bool ] = False
55+ dj_ready : Optional [bool ] = False
56+ allow_streaming : Optional [bool ] = False
57+ premium_streaming_only : Optional [bool ] = False
5658 num_tracks : Optional [int ] = - 1
5759 num_videos : Optional [int ] = - 1
5860 num_volumes : Optional [int ] = - 1
@@ -64,6 +66,9 @@ class Album:
6466 universal_product_number : Optional [int ] = - 1
6567 popularity : Optional [int ] = - 1
6668 user_date_added : Optional [datetime ] = None
69+ audio_quality : Optional [str ] = ""
70+ audio_modes : Optional [str ] = ""
71+ media_metadata_tags : Optional [List [str ]] = ["" ]
6772
6873 artist : Optional ["Artist" ] = None
6974 artists : Optional [List ["Artist" ]] = None
@@ -75,9 +80,12 @@ def __init__(self, session: "Session", album_id: Optional[str]):
7580 self .id = album_id
7681
7782 if self .id :
78- request = self .request .request ("GET" , "albums/%s" % self .id )
79- if request .status_code and request .status_code == 404 :
83+ try :
84+ request = self .request .request ("GET" , "albums/%s" % self .id )
85+ except ObjectNotFound :
8086 raise ObjectNotFound ("Album not found" )
87+ except TooManyRequests :
88+ raise TooManyRequests ("Album unavailable" )
8189 else :
8290 self .request .map_json (request .json (), parse = self .parse )
8391
@@ -102,6 +110,10 @@ def parse(
102110 self .video_cover = json_obj ["videoCover" ]
103111 self .duration = json_obj .get ("duration" )
104112 self .available = json_obj .get ("streamReady" )
113+ self .ad_supported_ready = json_obj .get ("adSupportedStreamReady" )
114+ self .dj_ready = json_obj .get ("djReady" )
115+ self .allow_streaming = json_obj .get ("allowStreaming" )
116+ self .premium_streaming_only = json_obj .get ("premiumStreamingOnly" )
105117 self .num_tracks = json_obj .get ("numberOfTracks" )
106118 self .num_videos = json_obj .get ("numberOfVideos" )
107119 self .num_volumes = json_obj .get ("numberOfVolumes" )
@@ -112,6 +124,13 @@ def parse(
112124 self .popularity = json_obj .get ("popularity" )
113125 self .type = json_obj .get ("type" )
114126
127+ # Certain fields may not be available
128+ self .audio_quality = json_obj .get ("audioQuality" )
129+ self .audio_modes = json_obj .get ("audioModes" )
130+
131+ if "mediaMetadata" in json_obj :
132+ self .media_metadata_tags = json_obj .get ("mediaMetadata" )["tags" ]
133+
115134 self .artist = artist
116135 self .artists = artists
117136
@@ -183,7 +202,7 @@ def items(self, limit: int = 100, offset: int = 0) -> List[Union["Track", "Video
183202 assert isinstance (items , list )
184203 return cast (List [Union ["Track" , "Video" ]], items )
185204
186- def image (self , dimensions : int = 320 , default : str = DEFAULT_ALBUM_IMAGE ) -> str :
205+ def image (self , dimensions : int = 320 , default : str = DEFAULT_ALBUM_IMG ) -> str :
187206 """A url to an album image cover.
188207
189208 :param dimensions: The width and height that you want from the image
@@ -192,17 +211,22 @@ def image(self, dimensions: int = 320, default: str = DEFAULT_ALBUM_IMAGE) -> st
192211
193212 Valid resolutions: 80x80, 160x160, 320x320, 640x640, 1280x1280
194213 """
195- if not self .cover :
196- return default
197214
198215 if dimensions not in [80 , 160 , 320 , 640 , 1280 ]:
199216 raise ValueError ("Invalid resolution {0} x {0}" .format (dimensions ))
200217
201- return self .session .config .image_url % (
202- self .cover .replace ("-" , "/" ),
203- dimensions ,
204- dimensions ,
205- )
218+ if not self .cover :
219+ return self .session .config .image_url % (
220+ default .replace ("-" , "/" ),
221+ dimensions ,
222+ dimensions ,
223+ )
224+ else :
225+ return self .session .config .image_url % (
226+ self .cover .replace ("-" , "/" ),
227+ dimensions ,
228+ dimensions ,
229+ )
206230
207231 def video (self , dimensions : int ) -> str :
208232 """Creates a url to an mp4 video cover for the album.
@@ -239,9 +263,12 @@ def similar(self) -> List["Album"]:
239263
240264 :return: A :any:`list` of similar albums
241265 """
242- request = self .request .request ("GET" , "albums/%s/similar" % self .id )
243- if request .status_code and request .status_code == 404 :
266+ try :
267+ request = self .request .request ("GET" , "albums/%s/similar" % self .id )
268+ except ObjectNotFound :
244269 raise MetadataNotAvailable ("No similar albums exist for this album" )
270+ except TooManyRequests :
271+ raise TooManyRequests ("Similar artists unavailable" )
245272 else :
246273 albums = self .request .map_json (
247274 request .json (), parse = self .session .parse_album
@@ -261,3 +288,23 @@ def review(self) -> str:
261288 ]
262289 assert isinstance (review , str )
263290 return review
291+
292+ def get_audio_resolution (self , individual_tracks : bool = False ) -> [[int , int ]]:
293+ """Retrieve the audio resolution (bit rate + sample rate) for the album track(s)
294+
295+ This function assumes that all album tracks use the same audio resolution.
296+ Some albums may consist of tracks with multiple audio resolution(s).
297+ The audio resolution can therefore be fetched for individual tracks by setting the `all_tracks` argument accordingly.
298+
299+ WARNING: For individual tracks, many additional requests are needed. Handle with care!
300+
301+ :param individual_tracks: Fetch individual track resolutions
302+ :type individual_tracks: bool
303+ :return: A :class:`tuple` containing the (bit_rate, sample_rate) for one or more tracks
304+ """
305+ if individual_tracks :
306+ # Return for individual tracks
307+ return [res .get_stream ().get_audio_resolution () for res in self .tracks ()]
308+ else :
309+ # Return for first track only
310+ return [self .tracks ()[0 ].get_stream ().get_audio_resolution ()]
0 commit comments