Moved deezer api over to deezer-py pipy package; Version bump to 2.0.1

This commit is contained in:
RemixDev
2020-11-19 22:08:35 +01:00
parent 8840855a96
commit 94f3bc95c3
13 changed files with 134 additions and 918 deletions

View File

@ -1,4 +1,4 @@
from deemix.api.deezer import Deezer
from deezer import Deezer
from deemix.app.settings import Settings
from deemix.app.queuemanager import QueueManager
from deemix.app.spotifyhelper import SpotifyHelper

View File

@ -18,8 +18,10 @@ from deemix.app.queueitem import QISingle, QICollection
from deemix.app.track import Track, AlbumDoesntExists
from deemix.utils import changeCase
from deemix.utils.pathtemplates import generateFilename, generateFilepath, settingsRegexAlbum, settingsRegexArtist, settingsRegexPlaylistFile
from deemix.api.deezer import USER_AGENT_HEADER, TrackFormats
from deezer import TrackFormats
from deemix import USER_AGENT_HEADER
from deemix.utils.taggers import tagID3, tagFLAC
from deemix.utils.decryption import generateStreamURL, generateBlowfishKey
from deemix.app.settings import OverwriteOption, FeaturesOption
from Cryptodome.Cipher import Blowfish
@ -240,14 +242,14 @@ class DownloadJob:
if track.MD5 == '':
if track.fallbackId != "0":
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not yet encoded, using fallback id")
newTrack = self.dz.get_track_gw(track.fallbackId)
newTrack = self.dz.gw.get_track_with_fallback(track.fallbackId)
track.parseEssentialData(self.dz, newTrack)
return self.download(trackAPI_gw, track)
elif not track.searched and self.settings['fallbackSearch']:
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not yet encoded, searching for alternative")
searchedId = self.dz.get_track_from_metadata(track.mainArtist['name'], track.title, track.album['title'])
searchedId = self.dz.api.get_track_id_from_metadata(track.mainArtist['name'], track.title, track.album['title'])
if searchedId != "0":
newTrack = self.dz.get_track_gw(searchedId)
newTrack = self.dz.gw.get_track_with_fallback(searchedId)
track.parseEssentialData(self.dz, newTrack)
track.searched = True
if self.interface:
@ -271,14 +273,14 @@ class DownloadJob:
except PreferredBitrateNotFound:
if track.fallbackId != "0":
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not found at desired bitrate, using fallback id")
newTrack = self.dz.get_track_gw(track.fallbackId)
newTrack = self.dz.gw.get_track_with_fallback(track.fallbackId)
track.parseEssentialData(self.dz, newTrack)
return self.download(trackAPI_gw, track)
elif not track.searched and self.settings['fallbackSearch']:
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not found at desired bitrate, searching for alternative")
searchedId = self.dz.get_track_from_metadata(track.mainArtist['name'], track.title, track.album['title'])
searchedId = self.dz.api.get_track_id_from_metadata(track.mainArtist['name'], track.title, track.album['title'])
if searchedId != "0":
newTrack = self.dz.get_track_gw(searchedId)
newTrack = self.dz.gw.get_track_with_fallback(searchedId)
track.parseEssentialData(self.dz, newTrack)
track.searched = True
if self.interface:
@ -514,7 +516,7 @@ class DownloadJob:
if not trackAlreadyDownloaded or self.settings['overwriteFile'] == OverwriteOption.OVERWRITE:
logger.info(f"[{track.mainArtist['name']} - {track.title}] Downloading the track")
track.downloadUrl = self.dz.get_track_stream_url(track.id, track.MD5, track.mediaVersion, track.selectedFormat)
track.downloadUrl = generateStreamURL(track.id, track.MD5, track.mediaVersion, track.selectedFormat)
def downloadMusic(track, trackAPI_gw):
try:
@ -527,14 +529,14 @@ class DownloadJob:
if writepath.is_file(): writepath.unlink()
if track.fallbackId != "0":
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not available, using fallback id")
newTrack = self.dz.get_track_gw(track.fallbackId)
newTrack = self.dz.gw.get_track_with_fallback(track.fallbackId)
track.parseEssentialData(self.dz, newTrack)
return False
elif not track.searched and self.settings['fallbackSearch']:
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not available, searching for alternative")
searchedId = self.dz.get_track_from_metadata(track.mainArtist['name'], track.title, track.album['title'])
searchedId = self.dz.api.get_track_id_from_metadata(track.mainArtist['name'], track.title, track.album['title'])
if searchedId != "0":
newTrack = self.dz.get_track_gw(searchedId)
newTrack = self.dz.gw.get_track_with_fallback(searchedId)
track.parseEssentialData(self.dz, newTrack)
track.searched = True
if self.interface:
@ -638,7 +640,7 @@ class DownloadJob:
if int(track.filesizes[f"FILESIZE_{formatName}"]) != 0: return formatNumber
if not track.filesizes[f"FILESIZE_{formatName}_TESTED"]:
request = requests.head(
self.dz.get_track_stream_url(track.id, track.MD5, track.mediaVersion, formatNumber),
generateStreamURL(track.id, track.MD5, track.mediaVersion, formatNumber),
headers={'User-Agent': USER_AGENT_HEADER},
timeout=30
)
@ -679,8 +681,7 @@ class DownloadJob:
try:
with self.dz.session.get(track.downloadUrl, headers=headers, stream=True, timeout=10) as request:
request.raise_for_status()
blowfish_key = str.encode(self.dz._get_blowfish_key(str(track.id)))
blowfish_key = str.encode(generateBlowfishKey(str(track.id)))
complete = int(request.headers["Content-Length"])
if complete == 0: raise DownloadEmpty

View File

@ -1,6 +1,7 @@
from deemix.app.downloadjob import DownloadJob
from deemix.utils import getIDFromLink, getTypeFromLink, getBitrateInt
from deemix.api.deezer import APIError, LyricsStatus
from deezer.gw import APIError as gwAPIError, LyricsStatus
from deezer.api import APIError
from spotipy.exceptions import SpotifyException
from deemix.app.queueitem import QueueItem, QISingle, QICollection, QIConvertable
import logging
@ -26,7 +27,7 @@ class QueueManager:
# Check if is an isrc: url
if str(id).startswith("isrc"):
try:
trackAPI = dz.get_track(id)
trackAPI = dz.api.get_track(id)
except APIError as e:
e = json.loads(str(e))
return QueueError("https://deezer.com/track/"+str(id), f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}")
@ -37,8 +38,8 @@ class QueueManager:
# Get essential track info
try:
trackAPI_gw = dz.get_track_gw(id)
except APIError as e:
trackAPI_gw = dz.gw.get_track_with_fallback(id)
except gwAPIError as e:
e = json.loads(str(e))
message = "Wrong URL"
if "DATA_ERROR" in e: message += f": {e['DATA_ERROR']}"
@ -74,7 +75,7 @@ class QueueManager:
def generateAlbumQueueItem(self, dz, id, settings, bitrate):
# Get essential album info
try:
albumAPI = dz.get_album(id)
albumAPI = dz.api.get_album(id)
except APIError as e:
e = json.loads(str(e))
return QueueError("https://deezer.com/album/"+str(id), f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}")
@ -83,7 +84,7 @@ class QueueManager:
# Get extra info about album
# This saves extra api calls when downloading
albumAPI_gw = dz.get_album_gw(id)
albumAPI_gw = dz.gw.get_album(id)
albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK']
albumAPI['copyright'] = albumAPI_gw['COPYRIGHT']
@ -91,7 +92,7 @@ class QueueManager:
if albumAPI['nb_tracks'] == 1:
return self.generateTrackQueueItem(dz, albumAPI['tracks']['data'][0]['id'], settings, bitrate, albumAPI=albumAPI)
tracksArray = dz.get_album_tracks_gw(id)
tracksArray = dz.gw.get_album_tracks(id)
if albumAPI['cover_small'] != None:
cover = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg'
@ -126,14 +127,14 @@ class QueueManager:
def generatePlaylistQueueItem(self, dz, id, settings, bitrate):
# Get essential playlist info
try:
playlistAPI = dz.get_playlist(id)
playlistAPI = dz.api.get_playlist(id)
except:
playlistAPI = None
# Fallback to gw api if the playlist is private
if not playlistAPI:
try:
playlistAPI = dz.get_playlist_gw(id)
except APIError as e:
playlistAPI = dz.gw.get_playlist_page(id)
except gwAPIError as e:
e = json.loads(str(e))
message = "Wrong URL"
if "DATA_ERROR" in e:
@ -141,12 +142,12 @@ class QueueManager:
return QueueError("https://deezer.com/playlist/"+str(id), message)
# Check if private playlist and owner
if not playlistAPI['public'] and playlistAPI['creator']['id'] != str(dz.user['id']):
if not playlistAPI['public'] and playlistAPI['creator']['id'] != str(dz.current_user['id']):
logger.warn("You can't download others private playlists.")
return QueueError("https://deezer.com/playlist/"+str(id), "You can't download others private playlists.", "notYourPrivatePlaylist")
playlistTracksAPI = dz.get_playlist_tracks_gw(id)
playlistAPI['various_artist'] = dz.get_artist(5080) # Useful for save as compilation
playlistTracksAPI = dz.gw.get_playlist_tracks(id)
playlistAPI['various_artist'] = dz.api.get_artist(5080) # Useful for save as compilation
totalSize = len(playlistTracksAPI)
playlistAPI['nb_tracks'] = totalSize
@ -178,14 +179,14 @@ class QueueManager:
def generateArtistQueueItem(self, dz, id, settings, bitrate, interface=None):
# Get essential artist info
try:
artistAPI = dz.get_artist(id)
artistAPI = dz.api.get_artist(id)
except APIError as e:
e = json.loads(str(e))
return QueueError("https://deezer.com/artist/"+str(id), f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}")
if interface: interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
artistDiscographyAPI = dz.get_artist_discography_gw(id, 100)
artistDiscographyAPI = dz.gw.get_artist_discography_tabs(id, 100)
allReleases = artistDiscographyAPI.pop('all', [])
albumList = []
for album in allReleases:
@ -197,14 +198,14 @@ class QueueManager:
def generateArtistDiscographyQueueItem(self, dz, id, settings, bitrate, interface=None):
# Get essential artist info
try:
artistAPI = dz.get_artist(id)
artistAPI = dz.api.get_artist(id)
except APIError as e:
e = json.loads(str(e))
return QueueError("https://deezer.com/artist/"+str(id)+"/discography", f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}")
if interface: interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
artistDiscographyAPI = dz.get_artist_discography_gw(id, 100)
artistDiscographyAPI = dz.gw.get_artist_discography_tabs(id, 100)
artistDiscographyAPI.pop('all', None) # all contains albums and singles, so its all duplicates. This removes them
albumList = []
for type in artistDiscographyAPI:
@ -217,7 +218,7 @@ class QueueManager:
def generateArtistTopQueueItem(self, dz, id, settings, bitrate, interface=None):
# Get essential artist info
try:
artistAPI = dz.get_artist(id)
artistAPI = dz.api.get_artist(id)
except APIError as e:
e = json.loads(str(e))
return QueueError("https://deezer.com/artist/"+str(id)+"/top_track", f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}")
@ -252,8 +253,8 @@ class QueueManager:
'type': "playlist"
}
artistTopTracksAPI_gw = dz.get_artist_toptracks_gw(id)
playlistAPI['various_artist'] = dz.get_artist(5080) # Useful for save as compilation
artistTopTracksAPI_gw = dz.gw.get_artist_toptracks(id)
playlistAPI['various_artist'] = dz.api.get_artist(5080) # Useful for save as compilation
totalSize = len(artistTopTracksAPI_gw)
playlistAPI['nb_tracks'] = totalSize

View File

@ -2,7 +2,7 @@ import json
from pathlib import Path
from os import makedirs, listdir
from deemix import __version__ as deemixVersion
from deemix.api.deezer import TrackFormats
from deezer import TrackFormats
from deemix.utils import checkFolder
import logging
import datetime

View File

@ -151,7 +151,7 @@ class SpotifyHelper:
if str(track_id) in cache['tracks']:
dz_track = None
if cache['tracks'][str(track_id)]['isrc']:
dz_track = dz.get_track_by_ISRC(cache['tracks'][str(track_id)]['isrc'])
dz_track = dz.api.get_track_by_ISRC(cache['tracks'][str(track_id)]['isrc'])
dz_id = dz_track['id'] if 'id' in dz_track and 'title' in dz_track else "0"
cache['tracks'][str(track_id)]['id'] = dz_id
return (cache['tracks'][str(track_id)]['id'], dz_track, cache['tracks'][str(track_id)]['isrc'])
@ -164,15 +164,21 @@ class SpotifyHelper:
isrc = None
if 'external_ids' in spotify_track and 'isrc' in spotify_track['external_ids']:
try:
dz_track = dz.get_track_by_ISRC(spotify_track['external_ids']['isrc'])
dz_track = dz.api.get_track_by_ISRC(spotify_track['external_ids']['isrc'])
dz_id = dz_track['id'] if 'id' in dz_track and 'title' in dz_track else "0"
isrc = spotify_track['external_ids']['isrc']
except:
dz_id = dz.get_track_from_metadata(spotify_track['artists'][0]['name'], spotify_track['name'],
spotify_track['album']['name']) if fallbackSearch else "0"
dz_id = dz.api.get_track_id_from_metadata(
artist=spotify_track['artists'][0]['name'],
track=spotify_track['name'],
album=spotify_track['album']['name']
) if fallbackSearch else "0"
elif fallbackSearch:
dz_id = dz.get_track_from_metadata(spotify_track['artists'][0]['name'], spotify_track['name'],
spotify_track['album']['name'])
dz_id = dz.api.get_track_id_from_metadata(
artist=spotify_track['artists'][0]['name'],
track=spotify_track['name'],
album=spotify_track['album']['name']
)
if singleTrack:
cache['tracks'][str(track_id)] = {'id': dz_id, 'isrc': isrc}
with open(self.configFolder / 'spotifyCache.json', 'w') as spotifyCache:
@ -195,12 +201,12 @@ class SpotifyHelper:
upc = None
if 'external_ids' in spotify_album and 'upc' in spotify_album['external_ids']:
try:
dz_album = dz.get_album_by_UPC(spotify_album['external_ids']['upc'])
dz_album = dz.api.get_album_by_UPC(spotify_album['external_ids']['upc'])
dz_album = dz_album['id'] if 'id' in dz_album else "0"
upc = spotify_album['external_ids']['upc']
except:
try:
dz_album = dz.get_album_by_UPC(int(spotify_album['external_ids']['upc']))
dz_album = dz.api.get_album_by_UPC(int(spotify_album['external_ids']['upc']))
dz_album = dz_album['id'] if 'id' in dz_album else "0"
except:
dz_album = "0"
@ -221,7 +227,7 @@ class SpotifyHelper:
cover = "https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/75x75-000000-80-0-0.jpg"
playlistAPI = self._convert_playlist_structure(spotify_playlist)
playlistAPI['various_artist'] = dz.get_artist(5080)
playlistAPI['various_artist'] = dz.api.get_artist(5080)
extra = {}
extra['unconverted'] = []
@ -271,7 +277,7 @@ class SpotifyHelper:
trackID = cache['tracks'][str(track['id'])]['id']
trackAPI = None
if cache['tracks'][str(track['id'])]['isrc']:
trackAPI = dz.get_track_by_ISRC(cache['tracks'][str(track['id'])]['isrc'])
trackAPI = dz.api.get_track_by_ISRC(cache['tracks'][str(track['id'])]['isrc'])
else:
(trackID, trackAPI, isrc) = self.get_trackid_spotify(dz, "0", queueItem.settings['fallbackSearch'], track)
cache['tracks'][str(track['id'])] = {
@ -292,7 +298,7 @@ class SpotifyHelper:
'ART_NAME': track['artists'][0]['name']
}
else:
deezerTrack = dz.get_track_gw(trackID)
deezerTrack = dz.gw.get_track_with_fallback(trackID)
deezerTrack['_EXTRA_PLAYLIST'] = queueItem.extra['playlistAPI']
if trackAPI:
deezerTrack['_EXTRA_TRACK'] = trackAPI

View File

@ -1,6 +1,10 @@
import eventlet
requests = eventlet.import_patched('requests')
import logging
from deemix.api.deezer import APIError, LyricsStatus
from deezer.gw import APIError as gwAPIError, LyricsStatus
from deezer.api import APIError
from deemix.utils import removeFeatures, andCommaConcat, uniqueArray, generateReplayGainString
logging.basicConfig(level=logging.INFO)
@ -66,7 +70,7 @@ class Track:
if 'FALLBACK' in trackAPI_gw:
self.fallbackId = trackAPI_gw['FALLBACK']['SNG_ID']
if int(self.id) > 0:
self.filesizes = dz.get_track_filesizes(self.id)
self.filesizes = self.getFilesizes(dz)
def parseLocalTrackData(self, trackAPI_gw):
# Local tracks has only the trackAPI_gw page and
@ -132,8 +136,8 @@ class Track:
if not "LYRICS" in trackAPI_gw and self.lyrics['id'] != 0:
logger.info(f"[{trackAPI_gw['ART_NAME']} - {self.title}] Getting lyrics")
try:
trackAPI_gw["LYRICS"] = dz.get_lyrics_gw(self.id)
except APIError:
trackAPI_gw["LYRICS"] = dz.gw.get_track_lyrics(self.id)
except gwAPIError:
self.lyrics['id'] = 0
if self.lyrics['id'] != 0:
self.lyrics['unsync'] = trackAPI_gw["LYRICS"].get("LYRICS_TEXT")
@ -184,7 +188,7 @@ class Track:
if not albumAPI:
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting album infos")
try:
albumAPI = dz.get_album(self.album['id'])
albumAPI = dz.api.get_album(self.album['id'])
except APIError:
albumAPI = None
@ -248,8 +252,8 @@ class Track:
if not albumAPI_gw:
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting more album infos")
try:
albumAPI_gw = dz.get_album_gw(self.album['id'])
except APIError:
albumAPI_gw = dz.gw.get_album(self.album['id'])
except gwAPIError:
albumAPI_gw = None
raise AlbumDoesntExists
@ -264,7 +268,7 @@ class Track:
# Getting artist image ID
# ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting artist picture fallback")
artistAPI = dz.get_artist(self.album['mainArtist']['id'])
artistAPI = dz.api.get_artist(self.album['mainArtist']['id'])
self.album['mainArtist']['pic'] = artistAPI['picture_small'][artistAPI['picture_small'].find('artist/') + 7:-24]
self.album['artists'] = [albumAPI_gw['ART_NAME']]
@ -294,7 +298,7 @@ class Track:
if not trackAPI:
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting extra track infos")
trackAPI = dz.get_track(self.id)
trackAPI = dz.api.get_track(self.id)
self.bpm = trackAPI['bpm']
if not self.replayGain and 'gain' in trackAPI:
@ -327,13 +331,13 @@ class Track:
if not self.album['discTotal']:
if not albumAPI_gw:
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting more album infos")
albumAPI_gw = dz.get_album_gw(self.album['id'])
albumAPI_gw = dz.gw.get_album(self.album['id'])
self.album['discTotal'] = albumAPI_gw['NUMBER_DISK']
if not self.copyright:
if not albumAPI_gw:
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting more album infos")
albumAPI_gw = dz.get_album_gw(self.album['id'])
albumAPI_gw = dz.gw.get_album(self.album['id'])
self.copyright = albumAPI_gw['COPYRIGHT']
def parsePlaylistData(self, playlist, settings):
@ -393,6 +397,36 @@ class Track:
if 'Featured' in self.artist:
self.featArtistsString = "feat. "+andCommaConcat(self.artist['Featured'])
def getFilesizes(self, dz):
try:
guest_sid = dz.session.cookies.get('sid')
site = requests.post(
"https://api.deezer.com/1.0/gateway.php",
params={
'api_key': "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE",
'sid': guest_sid,
'input': '3',
'output': '3',
'method': 'song_getData'
},
timeout=30,
json={'sng_id': self.id},
headers=dz.http_headers
)
result_json = site.json()
except:
eventlet.sleep(2)
return self.getFilesizes(dz)
if len(result_json['error']):
raise APIError(json.dumps(result_json['error']))
response = result_json.get("results")
filesizes = {}
for key, value in response.items():
if key.startswith("FILESIZE_"):
filesizes[key] = value
filesizes[key+"_TESTED"] = False
return filesizes
class TrackError(Exception):
"""Base class for exceptions in this module."""
pass