Source code for tidalapi.album

# -*- coding: utf-8 -*-

# Copyright (C) 2019-2022 morguldir
# Copyright (C) 2014 Thomas Amland
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import copy
import dateutil.parser

DEFAULT_ALBUM_IMAGE = "https://tidal.com/browse/assets/images/defaultImages/defaultAlbumImage.png"


[docs]class Album(object): """ Contains information about a TIDAL album. If the album is created from a media object, this object will only contain the id, name, cover and video cover. TIDAL does this to reduce the network load. """ id = None name = None cover = None video_cover = None duration = -1 available = False num_tracks = -1 num_videos = -1 num_volumes = -1 tidal_release_date = None release_date = None copyright = None version = None explicit = True universal_product_number = -1 popularity = -1 user_date_added = None artist = None artists = None def __init__(self, session, album_id): self.session = session self.requests = session.request self.artist = session.artist() self.id = album_id if album_id: self.requests.map_request('albums/%s' % album_id, parse=self.parse)
[docs] def parse(self, json_obj, artist=None, artists=None): if artists is None: artists = self.session.parse_artists(json_obj['artists']) # Sometimes the artist field is not filled, an example is 140196345 if not 'artist' in json_obj: artist = artists[0] elif artist is None: artist = self.session.parse_artist(json_obj['artist']) self.id = json_obj['id'] self.name = json_obj['title'] self.cover = json_obj['cover'] self.video_cover = json_obj['videoCover'] self.duration = json_obj.get('duration') self.available = json_obj.get('streamReady') self.num_tracks = json_obj.get('numberOfTracks') self.num_videos = json_obj.get('numberOfVideos') self.num_volumes = json_obj.get('numberOfVolumes') self.copyright = json_obj.get('copyright') self.version = json_obj.get('version') self.explicit = json_obj.get('explicit') self.universal_product_number = json_obj.get('upc') self.popularity = json_obj.get('popularity') self.artist = artist self.artists = artists release_date = json_obj.get('releaseDate') self.release_date = dateutil.parser.isoparse(release_date) if release_date else None tidal_release_date = json_obj.get('streamStartDate') self.tidal_release_date = dateutil.parser.isoparse(tidal_release_date) if tidal_release_date else None user_date_added = json_obj.get('dateAdded') self.user_date_added = dateutil.parser.isoparse(user_date_added) if user_date_added else None return copy.copy(self)
@property def year(self): """ Convenience function to get the year using :class:`available_release_date` :return: An :any:`python:int` containing the year the track was released """ return self.available_release_date.year if self.available_release_date else None @property def available_release_date(self): """ Get the release date if it's available, otherwise get the day it was released on TIDAL :return: A :any:`python:datetime.datetime` object with the release date, or the tidal release date, can be None """ if self.release_date: return self.release_date if self.tidal_release_date: return self.tidal_release_date return None
[docs] def tracks(self, limit=None, offset=0): """ Returns the tracks in classes album. :param limit: The amount of items you want returned. :param offset: The position of the first item you want to include. :return: A list of the :class:`Tracks <.Track>` in the album. """ params = {'limit': limit, 'offset': offset} return self.requests.map_request('albums/%s/tracks' % self.id, params, parse=self.session.parse_track)
[docs] def items(self, limit=100, offset=0): """ Gets the first 100 tracks and videos in the album from TIDAL. :param offset: The index you want to start retrieving items from :return: A list of :class:`Tracks<.Track>` and :class:`Videos`<.Video>` """ params = {'offset': offset, 'limit': limit} return self.requests.map_request('albums/%s/items' % self.id, params=params, parse=self.session.parse_media)
[docs] def image(self, dimensions, default=DEFAULT_ALBUM_IMAGE): """ A url to an album image cover :param dimensions: The width and height that you want from the image :type dimensions: int :return: A url to the image. Valid resolutions: 80x80, 160x160, 320x320, 640x640, 1280x1280 """ if not self.cover: return default if dimensions not in [80, 160, 320, 640, 1280]: raise ValueError("Invalid resolution {0} x {0}".format(dimensions)) return self.session.config.image_url % (self.cover.replace('-', '/'), dimensions, dimensions)
[docs] def video(self, dimensions): """ Creates a url to an mp4 video cover for the album. Valid resolutions: 80x80, 160x160, 320x320, 640x640, 1280x1280 :param dimensions: The width an height of the video :type dimensions: int :return: A url to an mp4 of the video cover. """ if not self.video_cover: raise AttributeError("This album does not have a video cover.") if dimensions not in [80, 160, 320, 640, 1280]: raise ValueError("Invalid resolution {0} x {0}".format(dimensions)) return self.session.config.video_url % (self.video_cover.replace('-', '/'), dimensions, dimensions)
[docs] def page(self): """ Retrieve the album page as seen on https://listen.tidal.com/album/$id :return: A :class:`Page` containing the different categories, e.g. similar artists and albums """ return self.session.page.get("pages/album", params={"albumId": self.id})
[docs] def similar(self): """ Retrieve albums similar to the current one :return: A :any:`list` of similar albums """ return self.requests.map_request('albums/%s/similar' % self.id, parse=self.session.parse_album)
[docs] def review(self) -> str: """ Retrieve the album review :return: A :class:`str` containing the album review, with wimp links :raises: :class:`requests.HTTPError` if there isn't a review yet """ # morguldir: TODO: Add parsing of wimplinks? return self.requests.request('GET', 'albums/%s/review' % self.id).json()['text']