2021-06-27 20:29:41 +00:00
|
|
|
import logging
|
|
|
|
|
2021-08-02 19:55:22 +00:00
|
|
|
from deezer.errors import GWAPIError, APIError
|
2021-12-23 18:02:33 +00:00
|
|
|
from deezer.utils import map_user_playlist, map_track, map_album
|
2021-06-27 20:29:41 +00:00
|
|
|
|
2021-08-02 19:55:22 +00:00
|
|
|
from deemix.types.DownloadObjects import Single, Collection
|
|
|
|
from deemix.errors import GenerationError, ISRCnotOnDeezer, InvalidID, NotYourPrivatePlaylist
|
|
|
|
|
2021-06-27 20:29:41 +00:00
|
|
|
logger = logging.getLogger('deemix')
|
|
|
|
|
|
|
|
def generateTrackItem(dz, link_id, bitrate, trackAPI=None, albumAPI=None):
|
2021-12-23 18:02:33 +00:00
|
|
|
# Get essential track info
|
|
|
|
if not trackAPI:
|
|
|
|
if str(link_id).startswith("isrc") or int(link_id) > 0:
|
|
|
|
try:
|
|
|
|
trackAPI = dz.api.get_track(link_id)
|
|
|
|
except APIError as e:
|
|
|
|
raise GenerationError(f"https://deezer.com/track/{link_id}", str(e)) from e
|
|
|
|
|
|
|
|
# Check if is an isrc: url
|
|
|
|
if str(link_id).startswith("isrc"):
|
|
|
|
if 'id' in trackAPI and 'title' in trackAPI:
|
|
|
|
link_id = trackAPI['id']
|
|
|
|
else:
|
|
|
|
raise ISRCnotOnDeezer(f"https://deezer.com/track/{link_id}")
|
2021-06-27 20:29:41 +00:00
|
|
|
else:
|
2021-12-23 18:02:33 +00:00
|
|
|
trackAPI_gw = dz.gw.get_track(link_id)
|
|
|
|
trackAPI = map_track(trackAPI_gw)
|
|
|
|
else:
|
|
|
|
link_id = trackAPI['id']
|
2021-09-22 06:17:59 +00:00
|
|
|
if not str(link_id).strip('-').isdecimal(): raise InvalidID(f"https://deezer.com/track/{link_id}")
|
2021-06-27 20:29:41 +00:00
|
|
|
|
2021-12-23 18:02:33 +00:00
|
|
|
cover = None
|
|
|
|
if trackAPI['album']['cover_small']:
|
|
|
|
cover = trackAPI['album']['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg'
|
|
|
|
else:
|
|
|
|
cover = f"https://e-cdns-images.dzcdn.net/images/cover/{trackAPI['md5_image']}/75x75-000000-80-0-0.jpg"
|
2021-06-27 20:29:41 +00:00
|
|
|
|
2021-12-23 18:02:33 +00:00
|
|
|
del trackAPI['track_token']
|
2021-06-27 20:29:41 +00:00
|
|
|
|
|
|
|
return Single({
|
|
|
|
'type': 'track',
|
|
|
|
'id': link_id,
|
|
|
|
'bitrate': bitrate,
|
2021-12-23 18:02:33 +00:00
|
|
|
'title': trackAPI['title'],
|
|
|
|
'artist': trackAPI['artist']['name'],
|
|
|
|
'cover': cover,
|
|
|
|
'explicit': trackAPI.explicit_lyrics,
|
2021-06-27 20:29:41 +00:00
|
|
|
'single': {
|
|
|
|
'trackAPI': trackAPI,
|
|
|
|
'albumAPI': albumAPI
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
def generateAlbumItem(dz, link_id, bitrate, rootArtist=None):
|
|
|
|
# Get essential album info
|
|
|
|
if str(link_id).startswith('upc'):
|
|
|
|
upcs = [link_id[4:],]
|
|
|
|
upcs.append(int(upcs[0]))
|
|
|
|
lastError = None
|
|
|
|
for upc in upcs:
|
|
|
|
try:
|
|
|
|
albumAPI = dz.api.get_album(f"upc:{upc}")
|
|
|
|
except APIError as e:
|
|
|
|
lastError = e
|
|
|
|
albumAPI = None
|
|
|
|
if not albumAPI:
|
|
|
|
raise GenerationError(f"https://deezer.com/album/{link_id}", str(lastError)) from lastError
|
|
|
|
link_id = albumAPI['id']
|
|
|
|
else:
|
|
|
|
try:
|
2021-12-23 18:02:33 +00:00
|
|
|
albumAPI_gw_page = dz.gw.get_album_page(link_id)
|
|
|
|
if 'DATA' in albumAPI_gw_page:
|
|
|
|
albumAPI = map_album(albumAPI_gw_page['DATA'])
|
|
|
|
link_id = albumAPI_gw_page['DATA']['ALB_ID']
|
|
|
|
albumAPI_new = dz.api.get_album(link_id)
|
|
|
|
albumAPI.update(albumAPI_new)
|
|
|
|
else:
|
|
|
|
raise GenerationError(f"https://deezer.com/album/{link_id}", "Can't find the album")
|
2021-06-27 20:29:41 +00:00
|
|
|
except APIError as e:
|
|
|
|
raise GenerationError(f"https://deezer.com/album/{link_id}", str(e)) from e
|
|
|
|
|
2021-07-03 08:59:05 +00:00
|
|
|
if not str(link_id).isdecimal(): raise InvalidID(f"https://deezer.com/album/{link_id}")
|
2021-06-27 20:29:41 +00:00
|
|
|
|
|
|
|
# Get extra info about album
|
|
|
|
# This saves extra api calls when downloading
|
|
|
|
albumAPI_gw = dz.gw.get_album(link_id)
|
2021-12-23 18:02:33 +00:00
|
|
|
albumAPI_gw = map_album(albumAPI_gw)
|
|
|
|
albumAPI_gw.update(albumAPI)
|
|
|
|
albumAPI = albumAPI_gw
|
2021-06-27 20:29:41 +00:00
|
|
|
albumAPI['root_artist'] = rootArtist
|
|
|
|
|
|
|
|
# If the album is a single download as a track
|
|
|
|
if albumAPI['nb_tracks'] == 1:
|
|
|
|
if len(albumAPI['tracks']['data']):
|
|
|
|
return generateTrackItem(dz, albumAPI['tracks']['data'][0]['id'], bitrate, albumAPI=albumAPI)
|
|
|
|
raise GenerationError(f"https://deezer.com/album/{link_id}", "Single has no tracks.")
|
|
|
|
|
|
|
|
tracksArray = dz.gw.get_album_tracks(link_id)
|
|
|
|
|
|
|
|
if albumAPI['cover_small'] is not None:
|
|
|
|
cover = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg'
|
|
|
|
else:
|
2021-12-23 18:02:33 +00:00
|
|
|
cover = f"https://e-cdns-images.dzcdn.net/images/cover/{albumAPI['md5_image']}/75x75-000000-80-0-0.jpg"
|
2021-06-27 20:29:41 +00:00
|
|
|
|
|
|
|
totalSize = len(tracksArray)
|
|
|
|
albumAPI['nb_tracks'] = totalSize
|
|
|
|
collection = []
|
|
|
|
for pos, trackAPI in enumerate(tracksArray, start=1):
|
2021-12-23 18:02:33 +00:00
|
|
|
trackAPI = map_track(trackAPI)
|
|
|
|
del trackAPI['track_token']
|
|
|
|
trackAPI['position'] = pos
|
2021-06-27 20:29:41 +00:00
|
|
|
collection.append(trackAPI)
|
|
|
|
|
|
|
|
return Collection({
|
|
|
|
'type': 'album',
|
|
|
|
'id': link_id,
|
|
|
|
'bitrate': bitrate,
|
|
|
|
'title': albumAPI['title'],
|
|
|
|
'artist': albumAPI['artist']['name'],
|
|
|
|
'cover': cover,
|
2021-12-23 18:02:33 +00:00
|
|
|
'explicit': albumAPI['explicit_lyrics'],
|
2021-06-27 20:29:41 +00:00
|
|
|
'size': totalSize,
|
|
|
|
'collection': {
|
2021-12-23 18:02:33 +00:00
|
|
|
'tracks': collection,
|
2021-06-27 20:29:41 +00:00
|
|
|
'albumAPI': albumAPI
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
def generatePlaylistItem(dz, link_id, bitrate, playlistAPI=None, playlistTracksAPI=None):
|
|
|
|
if not playlistAPI:
|
2021-07-03 08:59:05 +00:00
|
|
|
if not str(link_id).isdecimal(): raise InvalidID(f"https://deezer.com/playlist/{link_id}")
|
2021-06-27 20:29:41 +00:00
|
|
|
# Get essential playlist info
|
|
|
|
try:
|
|
|
|
playlistAPI = dz.api.get_playlist(link_id)
|
|
|
|
except APIError:
|
|
|
|
playlistAPI = None
|
|
|
|
# Fallback to gw api if the playlist is private
|
|
|
|
if not playlistAPI:
|
|
|
|
try:
|
|
|
|
userPlaylist = dz.gw.get_playlist_page(link_id)
|
|
|
|
playlistAPI = map_user_playlist(userPlaylist['DATA'])
|
|
|
|
except GWAPIError as e:
|
|
|
|
raise GenerationError(f"https://deezer.com/playlist/{link_id}", str(e)) from e
|
|
|
|
|
|
|
|
# Check if private playlist and owner
|
|
|
|
if not playlistAPI.get('public', False) and playlistAPI['creator']['id'] != str(dz.current_user['id']):
|
|
|
|
logger.warning("You can't download others private playlists.")
|
|
|
|
raise NotYourPrivatePlaylist(f"https://deezer.com/playlist/{link_id}")
|
|
|
|
|
|
|
|
if not playlistTracksAPI:
|
|
|
|
playlistTracksAPI = dz.gw.get_playlist_tracks(link_id)
|
|
|
|
playlistAPI['various_artist'] = dz.api.get_artist(5080) # Useful for save as compilation
|
|
|
|
|
|
|
|
totalSize = len(playlistTracksAPI)
|
|
|
|
playlistAPI['nb_tracks'] = totalSize
|
|
|
|
collection = []
|
|
|
|
for pos, trackAPI in enumerate(playlistTracksAPI, start=1):
|
2021-12-23 18:02:33 +00:00
|
|
|
trackAPI = map_track(trackAPI)
|
|
|
|
if trackAPI['explicit_lyrics']:
|
2021-06-27 20:29:41 +00:00
|
|
|
playlistAPI['explicit'] = True
|
2021-12-23 18:02:33 +00:00
|
|
|
del trackAPI['track_token']
|
|
|
|
trackAPI['position'] = pos
|
2021-06-27 20:29:41 +00:00
|
|
|
collection.append(trackAPI)
|
|
|
|
|
|
|
|
if 'explicit' not in playlistAPI: playlistAPI['explicit'] = False
|
|
|
|
|
|
|
|
return Collection({
|
|
|
|
'type': 'playlist',
|
|
|
|
'id': link_id,
|
|
|
|
'bitrate': bitrate,
|
|
|
|
'title': playlistAPI['title'],
|
|
|
|
'artist': playlistAPI['creator']['name'],
|
|
|
|
'cover': playlistAPI['picture_small'][:-24] + '/75x75-000000-80-0-0.jpg',
|
|
|
|
'explicit': playlistAPI['explicit'],
|
|
|
|
'size': totalSize,
|
|
|
|
'collection': {
|
2021-12-23 18:02:33 +00:00
|
|
|
'tracks': collection,
|
2021-06-27 20:29:41 +00:00
|
|
|
'playlistAPI': playlistAPI
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
def generateArtistItem(dz, link_id, bitrate, listener=None):
|
2021-07-03 08:59:05 +00:00
|
|
|
if not str(link_id).isdecimal(): raise InvalidID(f"https://deezer.com/artist/{link_id}")
|
2021-06-27 20:29:41 +00:00
|
|
|
# Get essential artist info
|
|
|
|
try:
|
|
|
|
artistAPI = dz.api.get_artist(link_id)
|
|
|
|
except APIError as e:
|
|
|
|
raise GenerationError(f"https://deezer.com/artist/{link_id}", str(e)) from e
|
|
|
|
|
|
|
|
rootArtist = {
|
|
|
|
'id': artistAPI['id'],
|
|
|
|
'name': artistAPI['name'],
|
|
|
|
'picture_small': artistAPI['picture_small']
|
|
|
|
}
|
|
|
|
if listener: listener.send("startAddingArtist", rootArtist)
|
|
|
|
|
|
|
|
artistDiscographyAPI = dz.gw.get_artist_discography_tabs(link_id, 100)
|
|
|
|
allReleases = artistDiscographyAPI.pop('all', [])
|
|
|
|
albumList = []
|
|
|
|
for album in allReleases:
|
|
|
|
try:
|
|
|
|
albumList.append(generateAlbumItem(dz, album['id'], bitrate, rootArtist=rootArtist))
|
|
|
|
except GenerationError as e:
|
|
|
|
logger.warning("Album %s has no data: %s", str(album['id']), str(e))
|
|
|
|
|
|
|
|
if listener: listener.send("finishAddingArtist", rootArtist)
|
|
|
|
return albumList
|
|
|
|
|
|
|
|
def generateArtistDiscographyItem(dz, link_id, bitrate, listener=None):
|
2021-07-03 08:59:05 +00:00
|
|
|
if not str(link_id).isdecimal(): raise InvalidID(f"https://deezer.com/artist/{link_id}/discography")
|
2021-06-27 20:29:41 +00:00
|
|
|
# Get essential artist info
|
|
|
|
try:
|
|
|
|
artistAPI = dz.api.get_artist(link_id)
|
|
|
|
except APIError as e:
|
|
|
|
raise GenerationError(f"https://deezer.com/artist/{link_id}/discography", str(e)) from e
|
|
|
|
|
|
|
|
rootArtist = {
|
|
|
|
'id': artistAPI['id'],
|
|
|
|
'name': artistAPI['name'],
|
|
|
|
'picture_small': artistAPI['picture_small']
|
|
|
|
}
|
|
|
|
if listener: listener.send("startAddingArtist", rootArtist)
|
|
|
|
|
|
|
|
artistDiscographyAPI = dz.gw.get_artist_discography_tabs(link_id, 100)
|
|
|
|
artistDiscographyAPI.pop('all', None) # all contains albums and singles, so its all duplicates. This removes them
|
|
|
|
albumList = []
|
|
|
|
for releaseType in artistDiscographyAPI:
|
|
|
|
for album in artistDiscographyAPI[releaseType]:
|
|
|
|
try:
|
|
|
|
albumList.append(generateAlbumItem(dz, album['id'], bitrate, rootArtist=rootArtist))
|
|
|
|
except GenerationError as e:
|
|
|
|
logger.warning("Album %s has no data: %s", str(album['id']), str(e))
|
|
|
|
|
|
|
|
if listener: listener.send("finishAddingArtist", rootArtist)
|
|
|
|
return albumList
|
|
|
|
|
|
|
|
def generateArtistTopItem(dz, link_id, bitrate):
|
2021-07-03 08:59:05 +00:00
|
|
|
if not str(link_id).isdecimal(): raise InvalidID(f"https://deezer.com/artist/{link_id}/top_track")
|
2021-06-27 20:29:41 +00:00
|
|
|
# Get essential artist info
|
|
|
|
try:
|
|
|
|
artistAPI = dz.api.get_artist(link_id)
|
|
|
|
except APIError as e:
|
|
|
|
raise GenerationError(f"https://deezer.com/artist/{link_id}/top_track", str(e)) from e
|
|
|
|
|
|
|
|
# Emulate the creation of a playlist
|
|
|
|
# Can't use generatePlaylistItem directly as this is not a real playlist
|
|
|
|
playlistAPI = {
|
|
|
|
'id':f"{artistAPI['id']}_top_track",
|
|
|
|
'title': f"{artistAPI['name']} - Top Tracks",
|
|
|
|
'description': f"Top Tracks for {artistAPI['name']}",
|
|
|
|
'duration': 0,
|
|
|
|
'public': True,
|
|
|
|
'is_loved_track': False,
|
|
|
|
'collaborative': False,
|
|
|
|
'nb_tracks': 0,
|
|
|
|
'fans': artistAPI['nb_fan'],
|
|
|
|
'link': f"https://www.deezer.com/artist/{artistAPI['id']}/top_track",
|
|
|
|
'share': None,
|
|
|
|
'picture': artistAPI['picture'],
|
|
|
|
'picture_small': artistAPI['picture_small'],
|
|
|
|
'picture_medium': artistAPI['picture_medium'],
|
|
|
|
'picture_big': artistAPI['picture_big'],
|
|
|
|
'picture_xl': artistAPI['picture_xl'],
|
|
|
|
'checksum': None,
|
|
|
|
'tracklist': f"https://api.deezer.com/artist/{artistAPI['id']}/top",
|
|
|
|
'creation_date': "XXXX-00-00",
|
|
|
|
'creator': {
|
|
|
|
'id': f"art_{artistAPI['id']}",
|
|
|
|
'name': artistAPI['name'],
|
|
|
|
'type': "user"
|
|
|
|
},
|
|
|
|
'type': "playlist"
|
|
|
|
}
|
|
|
|
|
|
|
|
artistTopTracksAPI_gw = dz.gw.get_artist_toptracks(link_id)
|
|
|
|
return generatePlaylistItem(dz, playlistAPI['id'], bitrate, playlistAPI=playlistAPI, playlistTracksAPI=artistTopTracksAPI_gw)
|