version parity
This commit is contained in:
parent
ef3c9fbf57
commit
6b41e6cb0a
|
@ -20,6 +20,7 @@ from mutagen.flac import FLACNoHeaderError, error as FLACError
|
|||
|
||||
from deezer import TrackFormats
|
||||
from deezer.errors import WrongLicense, WrongGeolocation
|
||||
from deezer.utils import map_track
|
||||
from deemix.types.DownloadObjects import Single, Collection
|
||||
from deemix.types.Track import Track
|
||||
from deemix.types.Picture import StaticPicture
|
||||
|
@ -74,7 +75,7 @@ def downloadImage(url, path, overwrite=OverwriteOption.DONT_OVERWRITE):
|
|||
pictureSize = int(pictureUrl[:pictureUrl.find("x")])
|
||||
if pictureSize > 1200:
|
||||
return downloadImage(urlBase+pictureUrl.replace(f"{pictureSize}x{pictureSize}", '1200x1200'), path, overwrite)
|
||||
except (requests.exceptions.ConnectionError, requests.exceptions.ChunkedEncodingError, u3SSLError) as e:
|
||||
except (requests.exceptions.ConnectionError, requests.exceptions.ChunkedEncodingError, u3SSLError):
|
||||
if path.is_file(): path.unlink()
|
||||
sleep(5)
|
||||
return downloadImage(url, path, overwrite)
|
||||
|
@ -84,7 +85,7 @@ def downloadImage(url, path, overwrite=OverwriteOption.DONT_OVERWRITE):
|
|||
logger.exception("Error while downloading an image, you should report this to the developers: %s", e)
|
||||
return None
|
||||
|
||||
def getPreferredBitrate(dz, track, preferredBitrate, shouldFallback, uuid=None, listener=None):
|
||||
def getPreferredBitrate(dz, track, preferredBitrate, shouldFallback, feelingLucky, uuid=None, listener=None):
|
||||
preferredBitrate = int(preferredBitrate)
|
||||
|
||||
falledBack = False
|
||||
|
@ -101,32 +102,31 @@ def getPreferredBitrate(dz, track, preferredBitrate, shouldFallback, uuid=None,
|
|||
)
|
||||
try:
|
||||
request.raise_for_status()
|
||||
track.filesizes[f"FILESIZE_{formatName}"] = int(request.headers["Content-Length"])
|
||||
track.filesizes[f"FILESIZE_{formatName}_TESTED"] = True
|
||||
return track.filesizes[f"FILESIZE_{formatName}"] != 0
|
||||
track.filesizes[f"{formatName.lower()}"] = int(request.headers["Content-Length"])
|
||||
track.filesizes[f"{formatName.lower()}_TESTED"] = True
|
||||
return track.filesizes[f"{formatName.lower()}"] != 0
|
||||
except requests.exceptions.HTTPError: # if the format is not available, Deezer returns a 403 error
|
||||
return False
|
||||
|
||||
def getCorrectURL(track, formatName, formatNumber):
|
||||
def getCorrectURL(track, formatName, formatNumber, feelingLucky):
|
||||
nonlocal wrongLicense, isGeolocked
|
||||
url = None
|
||||
# Check the track with the legit method
|
||||
if formatName.lower() in track.filesizes and track.filesizes[formatName.lower()] != "0":
|
||||
try:
|
||||
url = dz.get_track_url(track.trackToken, formatName)
|
||||
if testURL(track, url, formatName): return url
|
||||
url = None
|
||||
except (WrongLicense, WrongGeolocation) as e:
|
||||
wrongLicense = isinstance(e, WrongLicense)
|
||||
isGeolocked = isinstance(e, WrongGeolocation)
|
||||
# Fallback to old method
|
||||
if not url:
|
||||
if not url and feelingLucky:
|
||||
url = generateCryptedStreamURL(track.id, track.MD5, track.mediaVersion, formatNumber)
|
||||
if testURL(track, url, formatName): return url
|
||||
url = None
|
||||
return url
|
||||
|
||||
if track.local:
|
||||
url = getCorrectURL(track, "MP3_MISC", TrackFormats.LOCAL)
|
||||
url = getCorrectURL(track, "MP3_MISC", TrackFormats.LOCAL, feelingLucky)
|
||||
track.urls["MP3_MISC"] = url
|
||||
return TrackFormats.LOCAL
|
||||
|
||||
|
@ -150,20 +150,23 @@ def getPreferredBitrate(dz, track, preferredBitrate, shouldFallback, uuid=None,
|
|||
else:
|
||||
formats = formats_non_360
|
||||
|
||||
# check and renew trackToken before starting the check
|
||||
track.checkAndRenewTrackToken(dz)
|
||||
for formatNumber, formatName in formats.items():
|
||||
# Current bitrate is higher than preferred bitrate; skip
|
||||
if formatNumber > preferredBitrate: continue
|
||||
|
||||
currentTrack = track
|
||||
url = getCorrectURL(currentTrack, formatName, formatNumber)
|
||||
url = getCorrectURL(currentTrack, formatName, formatNumber, feelingLucky)
|
||||
newTrack = None
|
||||
while True:
|
||||
if not url and hasAlternative:
|
||||
newTrack = dz.gw.get_track_with_fallback(currentTrack.fallbackID)
|
||||
newTrack = map_track(newTrack)
|
||||
currentTrack = Track()
|
||||
currentTrack.parseEssentialData(newTrack)
|
||||
hasAlternative = currentTrack.fallbackID != "0"
|
||||
if not url: getCorrectURL(currentTrack, formatName, formatNumber)
|
||||
if not url: getCorrectURL(currentTrack, formatName, formatNumber, feelingLucky)
|
||||
if (url or not hasAlternative): break
|
||||
|
||||
if url:
|
||||
|
@ -189,7 +192,7 @@ def getPreferredBitrate(dz, track, preferredBitrate, shouldFallback, uuid=None,
|
|||
},
|
||||
})
|
||||
if is360format: raise TrackNot360
|
||||
url = getCorrectURL(track, "MP3_MISC", TrackFormats.DEFAULT)
|
||||
url = getCorrectURL(track, "MP3_MISC", TrackFormats.DEFAULT, feelingLucky)
|
||||
track.urls["MP3_MISC"] = url
|
||||
return TrackFormats.DEFAULT
|
||||
|
||||
|
@ -208,17 +211,16 @@ class Downloader:
|
|||
if not self.downloadObject.isCanceled:
|
||||
if isinstance(self.downloadObject, Single):
|
||||
track = self.downloadWrapper({
|
||||
'trackAPI_gw': self.downloadObject.single['trackAPI_gw'],
|
||||
'trackAPI': self.downloadObject.single.get('trackAPI'),
|
||||
'albumAPI': self.downloadObject.single.get('albumAPI')
|
||||
})
|
||||
if track: self.afterDownloadSingle(track)
|
||||
elif isinstance(self.downloadObject, Collection):
|
||||
tracks = [None] * len(self.downloadObject.collection['tracks_gw'])
|
||||
tracks = [None] * len(self.downloadObject.collection['tracks'])
|
||||
with ThreadPoolExecutor(self.settings['queueConcurrency']) as executor:
|
||||
for pos, track in enumerate(self.downloadObject.collection['tracks_gw'], start=0):
|
||||
for pos, track in enumerate(self.downloadObject.collection['tracks'], start=0):
|
||||
tracks[pos] = executor.submit(self.downloadWrapper, {
|
||||
'trackAPI_gw': track,
|
||||
'trackAPI': track,
|
||||
'albumAPI': self.downloadObject.collection.get('albumAPI'),
|
||||
'playlistAPI': self.downloadObject.collection.get('playlistAPI')
|
||||
})
|
||||
|
@ -241,17 +243,17 @@ class Downloader:
|
|||
|
||||
def download(self, extraData, track=None):
|
||||
returnData = {}
|
||||
trackAPI_gw = extraData['trackAPI_gw']
|
||||
trackAPI = extraData.get('trackAPI')
|
||||
albumAPI = extraData.get('albumAPI')
|
||||
playlistAPI = extraData.get('playlistAPI')
|
||||
trackAPI['size'] = self.downloadObject.size
|
||||
if self.downloadObject.isCanceled: raise DownloadCanceled
|
||||
if trackAPI_gw['SNG_ID'] == "0": raise DownloadFailed("notOnDeezer")
|
||||
if int(trackAPI['id']) == 0: raise DownloadFailed("notOnDeezer")
|
||||
|
||||
itemData = {
|
||||
'id': trackAPI_gw['SNG_ID'],
|
||||
'title': trackAPI_gw['SNG_TITLE'].strip(),
|
||||
'artist': trackAPI_gw['ART_NAME']
|
||||
'id': trackAPI['id'],
|
||||
'title': trackAPI['title'],
|
||||
'artist': trackAPI['artist']['name']
|
||||
}
|
||||
|
||||
# Create Track object
|
||||
|
@ -260,7 +262,7 @@ class Downloader:
|
|||
try:
|
||||
track = Track().parseData(
|
||||
dz=self.dz,
|
||||
trackAPI_gw=trackAPI_gw,
|
||||
track_id=trackAPI['id'],
|
||||
trackAPI=trackAPI,
|
||||
albumAPI=albumAPI,
|
||||
playlistAPI=playlistAPI
|
||||
|
@ -287,13 +289,13 @@ class Downloader:
|
|||
self.dz,
|
||||
track,
|
||||
self.bitrate,
|
||||
self.settings['fallbackBitrate'],
|
||||
self.settings['fallbackBitrate'], self.settings['feelingLucky'],
|
||||
self.downloadObject.uuid, self.listener
|
||||
)
|
||||
except WrongLicense as e:
|
||||
raise DownloadFailed("wrongLicense") from e
|
||||
except WrongGeolocation as e:
|
||||
raise DownloadFailed("wrongGeolocation") from e
|
||||
raise DownloadFailed("wrongGeolocation", track) from e
|
||||
except PreferredBitrateNotFound as e:
|
||||
raise DownloadFailed("wrongBitrate", track) from e
|
||||
except TrackNot360 as e:
|
||||
|
@ -432,7 +434,7 @@ class Downloader:
|
|||
self.downloadObject.removeTrackProgress(self.listener)
|
||||
track.filesizes['FILESIZE_FLAC'] = "0"
|
||||
track.filesizes['FILESIZE_FLAC_TESTED'] = True
|
||||
return self.download(trackAPI_gw, track=track)
|
||||
return self.download(extraData, track=track)
|
||||
self.log(itemData, "tagged")
|
||||
|
||||
if track.searched: returnData['searched'] = True
|
||||
|
@ -450,18 +452,13 @@ class Downloader:
|
|||
return returnData
|
||||
|
||||
def downloadWrapper(self, extraData, track=None):
|
||||
trackAPI_gw = extraData['trackAPI_gw']
|
||||
if trackAPI_gw.get('_EXTRA_TRACK'):
|
||||
extraData['trackAPI'] = trackAPI_gw['_EXTRA_TRACK'].copy()
|
||||
del extraData['trackAPI_gw']['_EXTRA_TRACK']
|
||||
trackAPI = extraData['trackAPI']
|
||||
# Temp metadata to generate logs
|
||||
itemData = {
|
||||
'id': trackAPI_gw['SNG_ID'],
|
||||
'title': trackAPI_gw['SNG_TITLE'].strip(),
|
||||
'artist': trackAPI_gw['ART_NAME']
|
||||
'id': trackAPI['id'],
|
||||
'title': trackAPI['title'],
|
||||
'artist': trackAPI['artist']['name']
|
||||
}
|
||||
if trackAPI_gw.get('VERSION') and trackAPI_gw['VERSION'] not in trackAPI_gw['SNG_TITLE']:
|
||||
itemData['title'] += f" {trackAPI_gw['VERSION']}".strip()
|
||||
|
||||
try:
|
||||
result = self.download(extraData, track)
|
||||
|
@ -471,16 +468,30 @@ class Downloader:
|
|||
if track.fallbackID != "0":
|
||||
self.warn(itemData, error.errid, 'fallback')
|
||||
newTrack = self.dz.gw.get_track_with_fallback(track.fallbackID)
|
||||
newTrack = map_track(newTrack)
|
||||
track.parseEssentialData(newTrack)
|
||||
return self.downloadWrapper(extraData, track)
|
||||
if len(track.albumsFallback) != 0 and self.settings.fallbackISRC:
|
||||
newAlbumID = track.albumsFallback.pop()
|
||||
newAlbum = self.dz.gw.get_album_page(newAlbumID)
|
||||
fallbackID = 0
|
||||
for newTrack in newAlbum['SONGS']['data']:
|
||||
if newTrack['ISRC'] == track.ISRC:
|
||||
fallbackID = newTrack['SNG_ID']
|
||||
break
|
||||
if fallbackID != 0:
|
||||
self.warn(itemData, error.errid, 'fallback')
|
||||
newTrack = self.dz.gw.get_track_with_fallback(fallbackID)
|
||||
newTrack = map_track(newTrack)
|
||||
track.parseEssentialData(newTrack)
|
||||
track.retriveFilesizes(self.dz)
|
||||
return self.downloadWrapper(extraData, track)
|
||||
if not track.searched and self.settings['fallbackSearch']:
|
||||
self.warn(itemData, error.errid, 'search')
|
||||
searchedId = self.dz.api.get_track_id_from_metadata(track.mainArtist.name, track.title, track.album.title)
|
||||
if searchedId != "0":
|
||||
newTrack = self.dz.gw.get_track_with_fallback(searchedId)
|
||||
newTrack = map_track(newTrack)
|
||||
track.parseEssentialData(newTrack)
|
||||
track.retriveFilesizes(self.dz)
|
||||
track.searched = True
|
||||
self.log(itemData, "searchFallback")
|
||||
return self.downloadWrapper(extraData, track)
|
||||
|
|
|
@ -60,7 +60,8 @@ ErrorMessages = {
|
|||
'noSpaceLeft': "No space left on target drive, clean up some space for the tracks",
|
||||
'albumDoesntExists': "Track's album does not exsist, failed to gather info.",
|
||||
'notLoggedIn': "You need to login to download tracks.",
|
||||
'wrongGeolocation': "Your account can't stream the track from your current country."
|
||||
'wrongGeolocation': "Your account can't stream the track from your current country.",
|
||||
'wrongGeolocationNoAlternative': "Your account can't stream the track from your current country and no alternative found."
|
||||
}
|
||||
|
||||
class DownloadFailed(DownloadError):
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import logging
|
||||
|
||||
from deezer.gw import LyricsStatus
|
||||
from deezer.errors import GWAPIError, APIError
|
||||
from deezer.utils import map_user_playlist
|
||||
from deezer.utils import map_user_playlist, map_track, map_album
|
||||
|
||||
from deemix.types.DownloadObjects import Single, Collection
|
||||
from deemix.errors import GenerationError, ISRCnotOnDeezer, InvalidID, NotYourPrivatePlaylist
|
||||
|
@ -10,40 +9,44 @@ from deemix.errors import GenerationError, ISRCnotOnDeezer, InvalidID, NotYourPr
|
|||
logger = logging.getLogger('deemix')
|
||||
|
||||
def generateTrackItem(dz, link_id, bitrate, trackAPI=None, albumAPI=None):
|
||||
# Check if is an isrc: url
|
||||
if str(link_id).startswith("isrc"):
|
||||
# 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}")
|
||||
else:
|
||||
trackAPI_gw = dz.gw.get_track(link_id)
|
||||
trackAPI = map_track(trackAPI_gw)
|
||||
else:
|
||||
link_id = trackAPI['id']
|
||||
if not str(link_id).strip('-').isdecimal(): raise InvalidID(f"https://deezer.com/track/{link_id}")
|
||||
|
||||
# Get essential track info
|
||||
try:
|
||||
trackAPI_gw = dz.gw.get_track_with_fallback(link_id)
|
||||
except GWAPIError as e:
|
||||
raise GenerationError(f"https://deezer.com/track/{link_id}", str(e)) from e
|
||||
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"
|
||||
|
||||
title = trackAPI_gw['SNG_TITLE'].strip()
|
||||
if trackAPI_gw.get('VERSION') and trackAPI_gw['VERSION'] not in trackAPI_gw['SNG_TITLE']:
|
||||
title += f" {trackAPI_gw['VERSION']}".strip()
|
||||
explicit = bool(int(trackAPI_gw.get('EXPLICIT_LYRICS', 0)))
|
||||
del trackAPI['track_token']
|
||||
|
||||
return Single({
|
||||
'type': 'track',
|
||||
'id': link_id,
|
||||
'bitrate': bitrate,
|
||||
'title': title,
|
||||
'artist': trackAPI_gw['ART_NAME'],
|
||||
'cover': f"https://e-cdns-images.dzcdn.net/images/cover/{trackAPI_gw['ALB_PICTURE']}/75x75-000000-80-0-0.jpg",
|
||||
'explicit': explicit,
|
||||
'title': trackAPI['title'],
|
||||
'artist': trackAPI['artist']['name'],
|
||||
'cover': cover,
|
||||
'explicit': trackAPI.explicit_lyrics,
|
||||
'single': {
|
||||
'trackAPI_gw': trackAPI_gw,
|
||||
'trackAPI': trackAPI,
|
||||
'albumAPI': albumAPI
|
||||
}
|
||||
|
@ -66,7 +69,14 @@ def generateAlbumItem(dz, link_id, bitrate, rootArtist=None):
|
|||
link_id = albumAPI['id']
|
||||
else:
|
||||
try:
|
||||
albumAPI = dz.api.get_album(link_id)
|
||||
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")
|
||||
except APIError as e:
|
||||
raise GenerationError(f"https://deezer.com/album/{link_id}", str(e)) from e
|
||||
|
||||
|
@ -75,9 +85,9 @@ def generateAlbumItem(dz, link_id, bitrate, rootArtist=None):
|
|||
# Get extra info about album
|
||||
# This saves extra api calls when downloading
|
||||
albumAPI_gw = dz.gw.get_album(link_id)
|
||||
albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK']
|
||||
albumAPI['copyright'] = albumAPI_gw['COPYRIGHT']
|
||||
albumAPI['release_date'] = albumAPI_gw['PHYSICAL_RELEASE_DATE']
|
||||
albumAPI_gw = map_album(albumAPI_gw)
|
||||
albumAPI_gw.update(albumAPI)
|
||||
albumAPI = albumAPI_gw
|
||||
albumAPI['root_artist'] = rootArtist
|
||||
|
||||
# If the album is a single download as a track
|
||||
|
@ -91,18 +101,17 @@ def generateAlbumItem(dz, link_id, bitrate, rootArtist=None):
|
|||
if albumAPI['cover_small'] is not None:
|
||||
cover = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg'
|
||||
else:
|
||||
cover = f"https://e-cdns-images.dzcdn.net/images/cover/{albumAPI_gw['ALB_PICTURE']}/75x75-000000-80-0-0.jpg"
|
||||
cover = f"https://e-cdns-images.dzcdn.net/images/cover/{albumAPI['md5_image']}/75x75-000000-80-0-0.jpg"
|
||||
|
||||
totalSize = len(tracksArray)
|
||||
albumAPI['nb_tracks'] = totalSize
|
||||
collection = []
|
||||
for pos, trackAPI in enumerate(tracksArray, start=1):
|
||||
trackAPI['POSITION'] = pos
|
||||
trackAPI['SIZE'] = totalSize
|
||||
trackAPI = map_track(trackAPI)
|
||||
del trackAPI['track_token']
|
||||
trackAPI['position'] = pos
|
||||
collection.append(trackAPI)
|
||||
|
||||
explicit = albumAPI_gw.get('EXPLICIT_ALBUM_CONTENT', {}).get('EXPLICIT_LYRICS_STATUS', LyricsStatus.UNKNOWN) in [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT]
|
||||
|
||||
return Collection({
|
||||
'type': 'album',
|
||||
'id': link_id,
|
||||
|
@ -110,10 +119,10 @@ def generateAlbumItem(dz, link_id, bitrate, rootArtist=None):
|
|||
'title': albumAPI['title'],
|
||||
'artist': albumAPI['artist']['name'],
|
||||
'cover': cover,
|
||||
'explicit': explicit,
|
||||
'explicit': albumAPI['explicit_lyrics'],
|
||||
'size': totalSize,
|
||||
'collection': {
|
||||
'tracks_gw': collection,
|
||||
'tracks': collection,
|
||||
'albumAPI': albumAPI
|
||||
}
|
||||
})
|
||||
|
@ -147,10 +156,11 @@ def generatePlaylistItem(dz, link_id, bitrate, playlistAPI=None, playlistTracksA
|
|||
playlistAPI['nb_tracks'] = totalSize
|
||||
collection = []
|
||||
for pos, trackAPI in enumerate(playlistTracksAPI, start=1):
|
||||
if trackAPI.get('EXPLICIT_TRACK_CONTENT', {}).get('EXPLICIT_LYRICS_STATUS', LyricsStatus.UNKNOWN) in [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT]:
|
||||
trackAPI = map_track(trackAPI)
|
||||
if trackAPI['explicit_lyrics']:
|
||||
playlistAPI['explicit'] = True
|
||||
trackAPI['POSITION'] = pos
|
||||
trackAPI['SIZE'] = totalSize
|
||||
del trackAPI['track_token']
|
||||
trackAPI['position'] = pos
|
||||
collection.append(trackAPI)
|
||||
|
||||
if 'explicit' not in playlistAPI: playlistAPI['explicit'] = False
|
||||
|
@ -165,7 +175,7 @@ def generatePlaylistItem(dz, link_id, bitrate, playlistAPI=None, playlistTracksA
|
|||
'explicit': playlistAPI['explicit'],
|
||||
'size': totalSize,
|
||||
'collection': {
|
||||
'tracks_gw': collection,
|
||||
'tracks': collection,
|
||||
'playlistAPI': playlistAPI
|
||||
}
|
||||
})
|
||||
|
|
|
@ -143,7 +143,7 @@ class Spotify(Plugin):
|
|||
'explicit': playlistAPI['explicit'],
|
||||
'size': len(tracklist),
|
||||
'collection': {
|
||||
'tracks_gw': [],
|
||||
'tracks': [],
|
||||
'playlistAPI': playlistAPI
|
||||
},
|
||||
'plugin': 'spotify',
|
||||
|
@ -217,31 +217,31 @@ class Spotify(Plugin):
|
|||
if cachedTrack.get('id', "0") != "0":
|
||||
trackAPI = dz.api.get_track(cachedTrack['id'])
|
||||
|
||||
deezerTrack = None
|
||||
if not trackAPI:
|
||||
deezerTrack = {
|
||||
'SNG_ID': "0",
|
||||
'SNG_TITLE': track['name'],
|
||||
'DURATION': 0,
|
||||
'MD5_ORIGIN': 0,
|
||||
'MEDIA_VERSION': 0,
|
||||
'FILESIZE': 0,
|
||||
'ALB_TITLE': track['album']['name'],
|
||||
'ALB_PICTURE': "",
|
||||
'ART_ID': 0,
|
||||
'ART_NAME': track['artists'][0]['name']
|
||||
trackAPI = {
|
||||
'id': "0",
|
||||
'title': track['name'],
|
||||
'duration': 0,
|
||||
'md5_origin': 0,
|
||||
'media_version': 0,
|
||||
'filesizes': {},
|
||||
'album': {
|
||||
'title': track['album']['name'],
|
||||
'md5_image': ""
|
||||
},
|
||||
'artist': {
|
||||
'id': 0,
|
||||
'name': track['artists'][0]['name']
|
||||
}
|
||||
else:
|
||||
deezerTrack = dz.gw.get_track_with_fallback(trackAPI['id'])
|
||||
deezerTrack['_EXTRA_TRACK'] = trackAPI
|
||||
deezerTrack['POSITION'] = pos+1
|
||||
}
|
||||
trackAPI['position'] = pos+1
|
||||
|
||||
conversion['next'] += (1 / downloadObject.size) * 100
|
||||
if round(conversion['next']) != conversion['now'] and round(conversion['next']) % 2 == 0:
|
||||
conversion['now'] = round(conversion['next'])
|
||||
if listener: listener.send("updateQueue", {'uuid': downloadObject.uuid, 'conversion': conversion['now']})
|
||||
|
||||
return deezerTrack
|
||||
return trackAPI
|
||||
|
||||
def convert(self, dz, downloadObject, settings, listener=None):
|
||||
cache = self.loadCache()
|
||||
|
@ -259,7 +259,7 @@ class Spotify(Plugin):
|
|||
cache, listener
|
||||
).result()
|
||||
|
||||
downloadObject.collection['tracks_gw'] = collection
|
||||
downloadObject.collection['tracks'] = collection
|
||||
downloadObject.size = len(collection)
|
||||
downloadObject = Collection(downloadObject.toDict())
|
||||
if listener: listener.send("finishConversion", downloadObject.getSlimmedDict())
|
||||
|
|
|
@ -39,8 +39,10 @@ DEFAULTS = {
|
|||
"illegalCharacterReplacer": "_",
|
||||
"queueConcurrency": 3,
|
||||
"maxBitrate": str(TrackFormats.MP3_320),
|
||||
"feelingLucky": False,
|
||||
"fallbackBitrate": False,
|
||||
"fallbackSearch": False,
|
||||
"fallbackISRC": False,
|
||||
"logErrors": True,
|
||||
"logSearched": False,
|
||||
"overwriteFile": OverwriteOption.DONT_OVERWRITE,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from deezer.gw import LyricsStatus
|
||||
|
||||
from deemix.utils import removeDuplicateArtists, removeFeatures
|
||||
from deemix.types.Artist import Artist
|
||||
from deemix.types.Date import Date
|
||||
|
@ -30,7 +28,7 @@ class Album:
|
|||
self.rootArtist = None
|
||||
self.variousArtists = None
|
||||
|
||||
self.playlistId = None
|
||||
self.playlistID = None
|
||||
self.owner = None
|
||||
self.isPlaylist = False
|
||||
|
||||
|
@ -39,8 +37,9 @@ class Album:
|
|||
|
||||
# Getting artist image ID
|
||||
# ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg
|
||||
art_pic = albumAPI['artist']['picture_small']
|
||||
art_pic = art_pic[art_pic.find('artist/') + 7:-24]
|
||||
art_pic = albumAPI['artist'].get('picture_small')
|
||||
if art_pic: art_pic = art_pic[art_pic.find('artist/') + 7:-24]
|
||||
else: art_pic = ""
|
||||
self.mainArtist = Artist(
|
||||
albumAPI['artist']['id'],
|
||||
albumAPI['artist']['name'],
|
||||
|
@ -83,16 +82,22 @@ class Album:
|
|||
self.barcode = albumAPI.get('upc', self.barcode)
|
||||
self.label = albumAPI.get('label', self.label)
|
||||
self.explicit = bool(albumAPI.get('explicit_lyrics', False))
|
||||
if 'release_date' in albumAPI:
|
||||
self.date.day = albumAPI["release_date"][8:10]
|
||||
self.date.month = albumAPI["release_date"][5:7]
|
||||
self.date.year = albumAPI["release_date"][0:4]
|
||||
release_date = albumAPI.get('release_date')
|
||||
if 'physical_release_date' in albumAPI:
|
||||
release_date = albumAPI['physical_release_date']
|
||||
if release_date:
|
||||
self.date.day = release_date[8:10]
|
||||
self.date.month = release_date[5:7]
|
||||
self.date.year = release_date[0:4]
|
||||
self.date.fixDayMonth()
|
||||
|
||||
self.discTotal = albumAPI.get('nb_disk')
|
||||
self.copyright = albumAPI.get('copyright')
|
||||
|
||||
if self.pic.md5 == "" and albumAPI.get('cover_small'):
|
||||
if self.pic.md5 == "":
|
||||
if albumAPI.get('md5_image'):
|
||||
self.pic.md5 = albumAPI['md5_image']
|
||||
elif albumAPI.get('cover_small'):
|
||||
# Getting album cover MD5
|
||||
# ex: https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/56x56-000000-80-0-0.jpg
|
||||
alb_pic = albumAPI['cover_small']
|
||||
|
@ -102,33 +107,6 @@ class Album:
|
|||
for genre in albumAPI['genres']['data']:
|
||||
self.genre.append(genre['name'])
|
||||
|
||||
def parseAlbumGW(self, albumAPI_gw):
|
||||
self.title = albumAPI_gw['ALB_TITLE']
|
||||
self.mainArtist = Artist(
|
||||
art_id = albumAPI_gw['ART_ID'],
|
||||
name = albumAPI_gw['ART_NAME'],
|
||||
role = "Main"
|
||||
)
|
||||
|
||||
self.artists = [albumAPI_gw['ART_NAME']]
|
||||
self.trackTotal = albumAPI_gw['NUMBER_TRACK']
|
||||
self.discTotal = albumAPI_gw['NUMBER_DISK']
|
||||
self.label = albumAPI_gw.get('LABEL_NAME', self.label)
|
||||
|
||||
explicitLyricsStatus = albumAPI_gw.get('EXPLICIT_ALBUM_CONTENT', {}).get('EXPLICIT_LYRICS_STATUS', LyricsStatus.UNKNOWN)
|
||||
self.explicit = explicitLyricsStatus in [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT]
|
||||
|
||||
self.addExtraAlbumGWData(albumAPI_gw)
|
||||
|
||||
def addExtraAlbumGWData(self, albumAPI_gw):
|
||||
if self.pic.md5 == "" and albumAPI_gw.get('ALB_PICTURE'):
|
||||
self.pic.md5 = albumAPI_gw['ALB_PICTURE']
|
||||
if 'PHYSICAL_RELEASE_DATE' in albumAPI_gw:
|
||||
self.date.day = albumAPI_gw["PHYSICAL_RELEASE_DATE"][8:10]
|
||||
self.date.month = albumAPI_gw["PHYSICAL_RELEASE_DATE"][5:7]
|
||||
self.date.year = albumAPI_gw["PHYSICAL_RELEASE_DATE"][0:4]
|
||||
self.date.fixDayMonth()
|
||||
|
||||
def makePlaylistCompilation(self, playlist):
|
||||
self.variousArtists = playlist.variousArtists
|
||||
self.mainArtist = playlist.mainArtist
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from time import sleep
|
||||
import re
|
||||
import requests
|
||||
from datetime import datetime
|
||||
|
||||
from deezer.utils import map_track, map_album
|
||||
from deezer.errors import APIError, GWAPIError
|
||||
from deemix.errors import TrackError, NoDataToParse, AlbumDoesntExists
|
||||
from deemix.errors import NoDataToParse, AlbumDoesntExists
|
||||
|
||||
from deemix.utils import removeFeatures, andCommaConcat, removeDuplicateArtists, generateReplayGainString, changeCase
|
||||
|
||||
|
@ -24,8 +24,10 @@ class Track:
|
|||
self.MD5 = ""
|
||||
self.mediaVersion = ""
|
||||
self.trackToken = ""
|
||||
self.trackTokenExpiration = 0
|
||||
self.duration = 0
|
||||
self.fallbackID = "0"
|
||||
self.albumsFallback = []
|
||||
self.filesizes = {}
|
||||
self.local = False
|
||||
self.mainArtist = None
|
||||
|
@ -42,6 +44,7 @@ class Track:
|
|||
self.explicit = False
|
||||
self.ISRC = ""
|
||||
self.replayGain = ""
|
||||
self.rank = 0
|
||||
self.playlist = None
|
||||
self.position = None
|
||||
self.searched = False
|
||||
|
@ -53,79 +56,54 @@ class Track:
|
|||
self.featArtistsString = ""
|
||||
self.urls = {}
|
||||
|
||||
def parseEssentialData(self, trackAPI_gw, trackAPI=None):
|
||||
self.id = str(trackAPI_gw['SNG_ID'])
|
||||
self.duration = trackAPI_gw['DURATION']
|
||||
self.trackToken = trackAPI_gw['TRACK_TOKEN']
|
||||
self.MD5 = trackAPI_gw.get('MD5_ORIGIN')
|
||||
if not self.MD5:
|
||||
if trackAPI and trackAPI.get('md5_origin'):
|
||||
self.MD5 = trackAPI['md5_origin']
|
||||
#else:
|
||||
# raise MD5NotFound
|
||||
self.mediaVersion = trackAPI_gw['MEDIA_VERSION']
|
||||
def parseEssentialData(self, trackAPI):
|
||||
self.id = str(trackAPI['id'])
|
||||
self.duration = trackAPI['duration']
|
||||
self.trackToken = trackAPI['track_token']
|
||||
self.trackTokenExpiration = trackAPI['track_token_expire']
|
||||
self.MD5 = trackAPI.get('md5_origin')
|
||||
self.mediaVersion = trackAPI['media_version']
|
||||
self.fallbackID = "0"
|
||||
if 'FALLBACK' in trackAPI_gw:
|
||||
self.fallbackID = trackAPI_gw['FALLBACK']['SNG_ID']
|
||||
if 'fallback_id' in trackAPI:
|
||||
self.fallbackID = trackAPI['fallback_id']
|
||||
self.local = int(self.id) < 0
|
||||
self.urls = {}
|
||||
|
||||
def retriveFilesizes(self, dz):
|
||||
guest_sid = dz.session.cookies.get('sid')
|
||||
def parseData(self, dz, track_id=None, trackAPI=None, albumAPI=None, playlistAPI=None):
|
||||
if track_id and (not trackAPI or trackAPI and not trackAPI.get('track_token')):
|
||||
trackAPI_new = dz.gw.get_track_with_fallback(track_id)
|
||||
trackAPI_new = map_track(trackAPI_new)
|
||||
if not trackAPI: trackAPI = {}
|
||||
trackAPI_new.update(trackAPI)
|
||||
trackAPI = trackAPI_new
|
||||
elif not trackAPI: raise NoDataToParse
|
||||
|
||||
self.parseEssentialData(trackAPI)
|
||||
|
||||
# only public api has bpm
|
||||
if not trackAPI.get('bpm') and not self.local:
|
||||
try:
|
||||
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:
|
||||
sleep(2)
|
||||
self.retriveFilesizes(dz)
|
||||
if len(result_json['error']):
|
||||
raise TrackError(result_json.dumps(result_json['error']))
|
||||
response = result_json.get("results", {})
|
||||
filesizes = {}
|
||||
for key, value in response.items():
|
||||
if key.startswith("FILESIZE_"):
|
||||
filesizes[key] = int(value)
|
||||
filesizes[key+"_TESTED"] = False
|
||||
self.filesizes = filesizes
|
||||
|
||||
def parseData(self, dz, track_id=None, trackAPI_gw=None, trackAPI=None, albumAPI_gw=None, albumAPI=None, playlistAPI=None):
|
||||
if track_id and not trackAPI_gw: trackAPI_gw = dz.gw.get_track_with_fallback(track_id)
|
||||
elif not trackAPI_gw: raise NoDataToParse
|
||||
if not trackAPI:
|
||||
try: trackAPI = dz.api.get_track(trackAPI_gw['SNG_ID'])
|
||||
except APIError: trackAPI = None
|
||||
|
||||
self.parseEssentialData(trackAPI_gw, trackAPI)
|
||||
trackAPI_new = dz.api.get_track(trackAPI['id'])
|
||||
trackAPI_new['release_date'] = trackAPI['release_date']
|
||||
trackAPI.update(trackAPI_new)
|
||||
except APIError: pass
|
||||
|
||||
if self.local:
|
||||
self.parseLocalTrackData(trackAPI_gw)
|
||||
self.parseLocalTrackData(trackAPI)
|
||||
else:
|
||||
self.retriveFilesizes(dz)
|
||||
self.parseTrackGW(trackAPI_gw)
|
||||
self.parseTrack(trackAPI)
|
||||
|
||||
# Get Lyrics data
|
||||
if not "LYRICS" in trackAPI_gw and self.lyrics.id != "0":
|
||||
try: trackAPI_gw["LYRICS"] = dz.gw.get_track_lyrics(self.id)
|
||||
if not trackAPI.get("lyrics") and self.lyrics.id != "0":
|
||||
try: trackAPI["lyrics"] = dz.gw.get_track_lyrics(self.id)
|
||||
except GWAPIError: self.lyrics.id = "0"
|
||||
if self.lyrics.id != "0": self.lyrics.parseLyrics(trackAPI_gw["LYRICS"])
|
||||
if self.lyrics.id != "0": self.lyrics.parseLyrics(trackAPI["lyrics"])
|
||||
|
||||
# Parse Album Data
|
||||
self.album = Album(
|
||||
alb_id = trackAPI_gw['ALB_ID'],
|
||||
title = trackAPI_gw['ALB_TITLE'],
|
||||
pic_md5 = trackAPI_gw.get('ALB_PICTURE')
|
||||
alb_id = trackAPI['album']['id'],
|
||||
title = trackAPI['album']['title'],
|
||||
pic_md5 = trackAPI['album'].get('md5_origin')
|
||||
)
|
||||
|
||||
# Get album Data
|
||||
|
@ -134,31 +112,31 @@ class Track:
|
|||
except APIError: albumAPI = None
|
||||
|
||||
# Get album_gw Data
|
||||
if not albumAPI_gw:
|
||||
try: albumAPI_gw = dz.gw.get_album(self.album.id)
|
||||
except GWAPIError: albumAPI_gw = None
|
||||
# Only gw has disk number
|
||||
if not albumAPI or albumAPI and not albumAPI.get('nb_disk'):
|
||||
try:
|
||||
albumAPI_gw = dz.gw.get_album(self.album.id)
|
||||
albumAPI_gw = map_album(albumAPI_gw)
|
||||
except GWAPIError: albumAPI_gw = {}
|
||||
if not albumAPI: albumAPI = {}
|
||||
albumAPI_gw.update(albumAPI)
|
||||
albumAPI = albumAPI_gw
|
||||
|
||||
if not albumAPI: raise AlbumDoesntExists
|
||||
|
||||
if albumAPI:
|
||||
self.album.parseAlbum(albumAPI)
|
||||
elif albumAPI_gw:
|
||||
self.album.parseAlbumGW(albumAPI_gw)
|
||||
# albumAPI_gw doesn't contain the artist cover
|
||||
# Getting artist image ID
|
||||
# ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg
|
||||
if not self.album.mainArtist.pic.md5 or self.album.mainArtist.pic.md5 == "":
|
||||
artistAPI = dz.api.get_artist(self.album.mainArtist.id)
|
||||
self.album.mainArtist.pic.md5 = artistAPI['picture_small'][artistAPI['picture_small'].find('artist/') + 7:-24]
|
||||
else:
|
||||
raise AlbumDoesntExists
|
||||
|
||||
# Fill missing data
|
||||
if albumAPI_gw: self.album.addExtraAlbumGWData(albumAPI_gw)
|
||||
if self.album.date and not self.date: self.date = self.album.date
|
||||
if not self.album.discTotal: self.album.discTotal = albumAPI_gw.get('NUMBER_DISK', "1")
|
||||
if not self.copyright: self.copyright = albumAPI_gw['COPYRIGHT']
|
||||
if 'GENRES' in trackAPI_gw:
|
||||
for genre in trackAPI_gw['GENRES']:
|
||||
if 'genres' in trackAPI:
|
||||
for genre in trackAPI['genres']:
|
||||
if genre not in self.album.genre: self.album.genre.append(genre)
|
||||
self.parseTrack(trackAPI)
|
||||
|
||||
# Remove unwanted charaters in track name
|
||||
# Example: track/127793
|
||||
|
@ -168,7 +146,7 @@ class Track:
|
|||
if len(self.artist['Main']) == 0:
|
||||
self.artist['Main'] = [self.mainArtist.name]
|
||||
|
||||
self.position = trackAPI_gw.get('POSITION')
|
||||
self.position = trackAPI.get('position')
|
||||
|
||||
# Add playlist data if track is in a playlist
|
||||
if playlistAPI: self.playlist = Playlist(playlistAPI)
|
||||
|
@ -176,63 +154,52 @@ class Track:
|
|||
self.generateMainFeatStrings()
|
||||
return self
|
||||
|
||||
def parseLocalTrackData(self, trackAPI_gw):
|
||||
def parseLocalTrackData(self, trackAPI):
|
||||
# Local tracks has only the trackAPI_gw page and
|
||||
# contains only the tags provided by the file
|
||||
self.title = trackAPI_gw['SNG_TITLE']
|
||||
self.album = Album(title=trackAPI_gw['ALB_TITLE'])
|
||||
self.title = trackAPI['title']
|
||||
self.album = Album(title=trackAPI['album']['title'])
|
||||
self.album.pic = Picture(
|
||||
md5 = trackAPI_gw.get('ALB_PICTURE', ""),
|
||||
md5 = trackAPI.get('md5_image', ""),
|
||||
pic_type = "cover"
|
||||
)
|
||||
self.mainArtist = Artist(name=trackAPI_gw['ART_NAME'], role="Main")
|
||||
self.artists = [trackAPI_gw['ART_NAME']]
|
||||
self.mainArtist = Artist(name=trackAPI['artist']['name'], role="Main")
|
||||
self.artists = [trackAPI['artist']['name']]
|
||||
self.artist = {
|
||||
'Main': [trackAPI_gw['ART_NAME']]
|
||||
'Main': [trackAPI['artist']['name']]
|
||||
}
|
||||
self.album.artist = self.artist
|
||||
self.album.artists = self.artists
|
||||
self.album.date = self.date
|
||||
self.album.mainArtist = self.mainArtist
|
||||
|
||||
def parseTrackGW(self, trackAPI_gw):
|
||||
self.title = trackAPI_gw['SNG_TITLE'].strip()
|
||||
if trackAPI_gw.get('VERSION') and not trackAPI_gw['VERSION'].strip() in self.title:
|
||||
self.title += f" {trackAPI_gw['VERSION'].strip()}"
|
||||
|
||||
self.discNumber = trackAPI_gw.get('DISK_NUMBER')
|
||||
self.explicit = bool(int(trackAPI_gw.get('EXPLICIT_LYRICS', "0")))
|
||||
self.copyright = trackAPI_gw.get('COPYRIGHT')
|
||||
if 'GAIN' in trackAPI_gw: self.replayGain = generateReplayGainString(trackAPI_gw['GAIN'])
|
||||
self.ISRC = trackAPI_gw.get('ISRC')
|
||||
self.trackNumber = trackAPI_gw['TRACK_NUMBER']
|
||||
self.contributors = trackAPI_gw['SNG_CONTRIBUTORS']
|
||||
self.rank = trackAPI_gw['RANK_SNG']
|
||||
|
||||
self.lyrics = Lyrics(trackAPI_gw.get('LYRICS_ID', "0"))
|
||||
|
||||
self.mainArtist = Artist(
|
||||
art_id = trackAPI_gw['ART_ID'],
|
||||
name = trackAPI_gw['ART_NAME'],
|
||||
role = "Main",
|
||||
pic_md5 = trackAPI_gw.get('ART_PICTURE')
|
||||
)
|
||||
|
||||
if 'PHYSICAL_RELEASE_DATE' in trackAPI_gw:
|
||||
self.date.day = trackAPI_gw["PHYSICAL_RELEASE_DATE"][8:10]
|
||||
self.date.month = trackAPI_gw["PHYSICAL_RELEASE_DATE"][5:7]
|
||||
self.date.year = trackAPI_gw["PHYSICAL_RELEASE_DATE"][0:4]
|
||||
self.date.fixDayMonth()
|
||||
|
||||
def parseTrack(self, trackAPI):
|
||||
self.title = trackAPI['title']
|
||||
|
||||
self.discNumber = trackAPI.get('disk_number')
|
||||
self.explicit = trackAPI.get('explicit_lyrics', False)
|
||||
self.copyright = trackAPI.get('copyright')
|
||||
if 'gain' in trackAPI: self.replayGain = generateReplayGainString(trackAPI['gain'])
|
||||
self.ISRC = trackAPI.get('isrc')
|
||||
self.trackNumber = trackAPI['track_position']
|
||||
self.contributors = trackAPI.get('song_contributors')
|
||||
self.rank = trackAPI['rank']
|
||||
self.bpm = trackAPI['bpm']
|
||||
|
||||
if not self.replayGain and 'gain' in trackAPI:
|
||||
self.replayGain = generateReplayGainString(trackAPI['gain'])
|
||||
if not self.explicit:
|
||||
self.explicit = trackAPI['explicit_lyrics']
|
||||
if not self.discNumber:
|
||||
self.discNumber = trackAPI['disk_number']
|
||||
self.lyrics = Lyrics(trackAPI.get('lyrics_id', "0"))
|
||||
|
||||
self.mainArtist = Artist(
|
||||
art_id = trackAPI['artist']['id'],
|
||||
name = trackAPI['artist']['name'],
|
||||
role = "Main",
|
||||
pic_md5 = trackAPI['artist'].get('md5_image')
|
||||
)
|
||||
|
||||
if 'physical_release_date' in trackAPI:
|
||||
self.date.day = trackAPI["physical_release_date"][8:10]
|
||||
self.date.month = trackAPI["physical_release_date"][5:7]
|
||||
self.date.year = trackAPI["physical_release_date"][0:4]
|
||||
self.date.fixDayMonth()
|
||||
|
||||
for artist in trackAPI['contributors']:
|
||||
isVariousArtists = str(artist['id']) == VARIOUS_ARTISTS
|
||||
|
@ -249,6 +216,11 @@ class Track:
|
|||
self.artist[artist['role']] = []
|
||||
self.artist[artist['role']].append(artist['name'])
|
||||
|
||||
if 'alternative_albums' in trackAPI and trackAPI['alternative_albums']:
|
||||
for album in trackAPI['alternative_albums']['data']:
|
||||
if 'RIGHTS' in album and album['RIGHTS'].get('STREAM_ADS_AVAILABLE') or album['RIGHTS'].get('STREAM_SUB_AVAILABLE'):
|
||||
self.albumsFallback.append(album['ALB_ID'])
|
||||
|
||||
def removeDuplicateArtists(self):
|
||||
(self.artist, self.artists) = removeDuplicateArtists(self.artist, self.artists)
|
||||
|
||||
|
@ -267,6 +239,14 @@ class Track:
|
|||
if 'Featured' in self.artist:
|
||||
self.featArtistsString = "feat. "+andCommaConcat(self.artist['Featured'])
|
||||
|
||||
def checkAndRenewTrackToken(self, dz):
|
||||
now = datetime.now()
|
||||
expiration = datetime.fromtimestamp(self.trackTokenExpiration)
|
||||
if now > expiration:
|
||||
newTrack = dz.gw.get_track_with_fallback(self.id)
|
||||
self.trackToken = newTrack['TRACK_TOKEN']
|
||||
self.trackTokenExpiration = newTrack['TRACK_TOKEN_EXPIRE']
|
||||
|
||||
def applySettings(self, settings):
|
||||
|
||||
# Check if should save the playlist as a compilation
|
||||
|
|
2
setup.py
2
setup.py
|
@ -23,7 +23,7 @@ setup(
|
|||
python_requires='>=3.7',
|
||||
packages=find_packages(exclude=("tests",)),
|
||||
include_package_data=True,
|
||||
install_requires=["click", "pycryptodomex", "mutagen", "requests", "deezer-py>=1.2.8"],
|
||||
install_requires=["click", "pycryptodomex", "mutagen", "requests", "deezer-py>=1.3.0"],
|
||||
extras_require={
|
||||
"spotify": ["spotipy>=2.11.0"]
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue