version parity

This commit is contained in:
RemixDev 2021-12-23 19:02:33 +01:00
parent ef3c9fbf57
commit 6b41e6cb0a
No known key found for this signature in database
GPG Key ID: B33962B465BDB51C
8 changed files with 248 additions and 266 deletions

View File

@ -20,6 +20,7 @@ from mutagen.flac import FLACNoHeaderError, error as FLACError
from deezer import TrackFormats from deezer import TrackFormats
from deezer.errors import WrongLicense, WrongGeolocation from deezer.errors import WrongLicense, WrongGeolocation
from deezer.utils import map_track
from deemix.types.DownloadObjects import Single, Collection from deemix.types.DownloadObjects import Single, Collection
from deemix.types.Track import Track from deemix.types.Track import Track
from deemix.types.Picture import StaticPicture from deemix.types.Picture import StaticPicture
@ -74,7 +75,7 @@ def downloadImage(url, path, overwrite=OverwriteOption.DONT_OVERWRITE):
pictureSize = int(pictureUrl[:pictureUrl.find("x")]) pictureSize = int(pictureUrl[:pictureUrl.find("x")])
if pictureSize > 1200: if pictureSize > 1200:
return downloadImage(urlBase+pictureUrl.replace(f"{pictureSize}x{pictureSize}", '1200x1200'), path, overwrite) 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() if path.is_file(): path.unlink()
sleep(5) sleep(5)
return downloadImage(url, path, overwrite) 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) logger.exception("Error while downloading an image, you should report this to the developers: %s", e)
return None 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) preferredBitrate = int(preferredBitrate)
falledBack = False falledBack = False
@ -101,32 +102,31 @@ def getPreferredBitrate(dz, track, preferredBitrate, shouldFallback, uuid=None,
) )
try: try:
request.raise_for_status() request.raise_for_status()
track.filesizes[f"FILESIZE_{formatName}"] = int(request.headers["Content-Length"]) track.filesizes[f"{formatName.lower()}"] = int(request.headers["Content-Length"])
track.filesizes[f"FILESIZE_{formatName}_TESTED"] = True track.filesizes[f"{formatName.lower()}_TESTED"] = True
return track.filesizes[f"FILESIZE_{formatName}"] != 0 return track.filesizes[f"{formatName.lower()}"] != 0
except requests.exceptions.HTTPError: # if the format is not available, Deezer returns a 403 error except requests.exceptions.HTTPError: # if the format is not available, Deezer returns a 403 error
return False return False
def getCorrectURL(track, formatName, formatNumber): def getCorrectURL(track, formatName, formatNumber, feelingLucky):
nonlocal wrongLicense, isGeolocked nonlocal wrongLicense, isGeolocked
url = None url = None
# Check the track with the legit method # Check the track with the legit method
if formatName.lower() in track.filesizes and track.filesizes[formatName.lower()] != "0":
try: try:
url = dz.get_track_url(track.trackToken, formatName) url = dz.get_track_url(track.trackToken, formatName)
if testURL(track, url, formatName): return url
url = None
except (WrongLicense, WrongGeolocation) as e: except (WrongLicense, WrongGeolocation) as e:
wrongLicense = isinstance(e, WrongLicense) wrongLicense = isinstance(e, WrongLicense)
isGeolocked = isinstance(e, WrongGeolocation) isGeolocked = isinstance(e, WrongGeolocation)
# Fallback to old method # Fallback to old method
if not url: if not url and feelingLucky:
url = generateCryptedStreamURL(track.id, track.MD5, track.mediaVersion, formatNumber) url = generateCryptedStreamURL(track.id, track.MD5, track.mediaVersion, formatNumber)
if testURL(track, url, formatName): return url if testURL(track, url, formatName): return url
url = None url = None
return url return url
if track.local: if track.local:
url = getCorrectURL(track, "MP3_MISC", TrackFormats.LOCAL) url = getCorrectURL(track, "MP3_MISC", TrackFormats.LOCAL, feelingLucky)
track.urls["MP3_MISC"] = url track.urls["MP3_MISC"] = url
return TrackFormats.LOCAL return TrackFormats.LOCAL
@ -150,20 +150,23 @@ def getPreferredBitrate(dz, track, preferredBitrate, shouldFallback, uuid=None,
else: else:
formats = formats_non_360 formats = formats_non_360
# check and renew trackToken before starting the check
track.checkAndRenewTrackToken(dz)
for formatNumber, formatName in formats.items(): for formatNumber, formatName in formats.items():
# Current bitrate is higher than preferred bitrate; skip # Current bitrate is higher than preferred bitrate; skip
if formatNumber > preferredBitrate: continue if formatNumber > preferredBitrate: continue
currentTrack = track currentTrack = track
url = getCorrectURL(currentTrack, formatName, formatNumber) url = getCorrectURL(currentTrack, formatName, formatNumber, feelingLucky)
newTrack = None newTrack = None
while True: while True:
if not url and hasAlternative: if not url and hasAlternative:
newTrack = dz.gw.get_track_with_fallback(currentTrack.fallbackID) newTrack = dz.gw.get_track_with_fallback(currentTrack.fallbackID)
newTrack = map_track(newTrack)
currentTrack = Track() currentTrack = Track()
currentTrack.parseEssentialData(newTrack) currentTrack.parseEssentialData(newTrack)
hasAlternative = currentTrack.fallbackID != "0" 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 or not hasAlternative): break
if url: if url:
@ -189,7 +192,7 @@ def getPreferredBitrate(dz, track, preferredBitrate, shouldFallback, uuid=None,
}, },
}) })
if is360format: raise TrackNot360 if is360format: raise TrackNot360
url = getCorrectURL(track, "MP3_MISC", TrackFormats.DEFAULT) url = getCorrectURL(track, "MP3_MISC", TrackFormats.DEFAULT, feelingLucky)
track.urls["MP3_MISC"] = url track.urls["MP3_MISC"] = url
return TrackFormats.DEFAULT return TrackFormats.DEFAULT
@ -208,17 +211,16 @@ class Downloader:
if not self.downloadObject.isCanceled: if not self.downloadObject.isCanceled:
if isinstance(self.downloadObject, Single): if isinstance(self.downloadObject, Single):
track = self.downloadWrapper({ track = self.downloadWrapper({
'trackAPI_gw': self.downloadObject.single['trackAPI_gw'],
'trackAPI': self.downloadObject.single.get('trackAPI'), 'trackAPI': self.downloadObject.single.get('trackAPI'),
'albumAPI': self.downloadObject.single.get('albumAPI') 'albumAPI': self.downloadObject.single.get('albumAPI')
}) })
if track: self.afterDownloadSingle(track) if track: self.afterDownloadSingle(track)
elif isinstance(self.downloadObject, Collection): 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: 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, { tracks[pos] = executor.submit(self.downloadWrapper, {
'trackAPI_gw': track, 'trackAPI': track,
'albumAPI': self.downloadObject.collection.get('albumAPI'), 'albumAPI': self.downloadObject.collection.get('albumAPI'),
'playlistAPI': self.downloadObject.collection.get('playlistAPI') 'playlistAPI': self.downloadObject.collection.get('playlistAPI')
}) })
@ -241,17 +243,17 @@ class Downloader:
def download(self, extraData, track=None): def download(self, extraData, track=None):
returnData = {} returnData = {}
trackAPI_gw = extraData['trackAPI_gw']
trackAPI = extraData.get('trackAPI') trackAPI = extraData.get('trackAPI')
albumAPI = extraData.get('albumAPI') albumAPI = extraData.get('albumAPI')
playlistAPI = extraData.get('playlistAPI') playlistAPI = extraData.get('playlistAPI')
trackAPI['size'] = self.downloadObject.size
if self.downloadObject.isCanceled: raise DownloadCanceled if self.downloadObject.isCanceled: raise DownloadCanceled
if trackAPI_gw['SNG_ID'] == "0": raise DownloadFailed("notOnDeezer") if int(trackAPI['id']) == 0: raise DownloadFailed("notOnDeezer")
itemData = { itemData = {
'id': trackAPI_gw['SNG_ID'], 'id': trackAPI['id'],
'title': trackAPI_gw['SNG_TITLE'].strip(), 'title': trackAPI['title'],
'artist': trackAPI_gw['ART_NAME'] 'artist': trackAPI['artist']['name']
} }
# Create Track object # Create Track object
@ -260,7 +262,7 @@ class Downloader:
try: try:
track = Track().parseData( track = Track().parseData(
dz=self.dz, dz=self.dz,
trackAPI_gw=trackAPI_gw, track_id=trackAPI['id'],
trackAPI=trackAPI, trackAPI=trackAPI,
albumAPI=albumAPI, albumAPI=albumAPI,
playlistAPI=playlistAPI playlistAPI=playlistAPI
@ -287,13 +289,13 @@ class Downloader:
self.dz, self.dz,
track, track,
self.bitrate, self.bitrate,
self.settings['fallbackBitrate'], self.settings['fallbackBitrate'], self.settings['feelingLucky'],
self.downloadObject.uuid, self.listener self.downloadObject.uuid, self.listener
) )
except WrongLicense as e: except WrongLicense as e:
raise DownloadFailed("wrongLicense") from e raise DownloadFailed("wrongLicense") from e
except WrongGeolocation as e: except WrongGeolocation as e:
raise DownloadFailed("wrongGeolocation") from e raise DownloadFailed("wrongGeolocation", track) from e
except PreferredBitrateNotFound as e: except PreferredBitrateNotFound as e:
raise DownloadFailed("wrongBitrate", track) from e raise DownloadFailed("wrongBitrate", track) from e
except TrackNot360 as e: except TrackNot360 as e:
@ -432,7 +434,7 @@ class Downloader:
self.downloadObject.removeTrackProgress(self.listener) self.downloadObject.removeTrackProgress(self.listener)
track.filesizes['FILESIZE_FLAC'] = "0" track.filesizes['FILESIZE_FLAC'] = "0"
track.filesizes['FILESIZE_FLAC_TESTED'] = True track.filesizes['FILESIZE_FLAC_TESTED'] = True
return self.download(trackAPI_gw, track=track) return self.download(extraData, track=track)
self.log(itemData, "tagged") self.log(itemData, "tagged")
if track.searched: returnData['searched'] = True if track.searched: returnData['searched'] = True
@ -450,18 +452,13 @@ class Downloader:
return returnData return returnData
def downloadWrapper(self, extraData, track=None): def downloadWrapper(self, extraData, track=None):
trackAPI_gw = extraData['trackAPI_gw'] trackAPI = extraData['trackAPI']
if trackAPI_gw.get('_EXTRA_TRACK'):
extraData['trackAPI'] = trackAPI_gw['_EXTRA_TRACK'].copy()
del extraData['trackAPI_gw']['_EXTRA_TRACK']
# Temp metadata to generate logs # Temp metadata to generate logs
itemData = { itemData = {
'id': trackAPI_gw['SNG_ID'], 'id': trackAPI['id'],
'title': trackAPI_gw['SNG_TITLE'].strip(), 'title': trackAPI['title'],
'artist': trackAPI_gw['ART_NAME'] '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: try:
result = self.download(extraData, track) result = self.download(extraData, track)
@ -471,16 +468,30 @@ class Downloader:
if track.fallbackID != "0": if track.fallbackID != "0":
self.warn(itemData, error.errid, 'fallback') self.warn(itemData, error.errid, 'fallback')
newTrack = self.dz.gw.get_track_with_fallback(track.fallbackID) 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.parseEssentialData(newTrack)
track.retriveFilesizes(self.dz)
return self.downloadWrapper(extraData, track) return self.downloadWrapper(extraData, track)
if not track.searched and self.settings['fallbackSearch']: if not track.searched and self.settings['fallbackSearch']:
self.warn(itemData, error.errid, 'search') self.warn(itemData, error.errid, 'search')
searchedId = self.dz.api.get_track_id_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": if searchedId != "0":
newTrack = self.dz.gw.get_track_with_fallback(searchedId) newTrack = self.dz.gw.get_track_with_fallback(searchedId)
newTrack = map_track(newTrack)
track.parseEssentialData(newTrack) track.parseEssentialData(newTrack)
track.retriveFilesizes(self.dz)
track.searched = True track.searched = True
self.log(itemData, "searchFallback") self.log(itemData, "searchFallback")
return self.downloadWrapper(extraData, track) return self.downloadWrapper(extraData, track)

View File

@ -60,7 +60,8 @@ ErrorMessages = {
'noSpaceLeft': "No space left on target drive, clean up some space for the tracks", '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.", 'albumDoesntExists': "Track's album does not exsist, failed to gather info.",
'notLoggedIn': "You need to login to download tracks.", '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): class DownloadFailed(DownloadError):

View File

@ -1,8 +1,7 @@
import logging import logging
from deezer.gw import LyricsStatus
from deezer.errors import GWAPIError, APIError 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.types.DownloadObjects import Single, Collection
from deemix.errors import GenerationError, ISRCnotOnDeezer, InvalidID, NotYourPrivatePlaylist from deemix.errors import GenerationError, ISRCnotOnDeezer, InvalidID, NotYourPrivatePlaylist
@ -10,40 +9,44 @@ from deemix.errors import GenerationError, ISRCnotOnDeezer, InvalidID, NotYourPr
logger = logging.getLogger('deemix') logger = logging.getLogger('deemix')
def generateTrackItem(dz, link_id, bitrate, trackAPI=None, albumAPI=None): def generateTrackItem(dz, link_id, bitrate, trackAPI=None, albumAPI=None):
# Check if is an isrc: url # Get essential track info
if str(link_id).startswith("isrc"): if not trackAPI:
if str(link_id).startswith("isrc") or int(link_id) > 0:
try: try:
trackAPI = dz.api.get_track(link_id) trackAPI = dz.api.get_track(link_id)
except APIError as e: except APIError as e:
raise GenerationError(f"https://deezer.com/track/{link_id}", str(e)) from 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: if 'id' in trackAPI and 'title' in trackAPI:
link_id = trackAPI['id'] link_id = trackAPI['id']
else: else:
raise ISRCnotOnDeezer(f"https://deezer.com/track/{link_id}") 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}") if not str(link_id).strip('-').isdecimal(): raise InvalidID(f"https://deezer.com/track/{link_id}")
# Get essential track info cover = None
try: if trackAPI['album']['cover_small']:
trackAPI_gw = dz.gw.get_track_with_fallback(link_id) cover = trackAPI['album']['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg'
except GWAPIError as e: else:
raise GenerationError(f"https://deezer.com/track/{link_id}", str(e)) from e 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() del trackAPI['track_token']
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)))
return Single({ return Single({
'type': 'track', 'type': 'track',
'id': link_id, 'id': link_id,
'bitrate': bitrate, 'bitrate': bitrate,
'title': title, 'title': trackAPI['title'],
'artist': trackAPI_gw['ART_NAME'], 'artist': trackAPI['artist']['name'],
'cover': f"https://e-cdns-images.dzcdn.net/images/cover/{trackAPI_gw['ALB_PICTURE']}/75x75-000000-80-0-0.jpg", 'cover': cover,
'explicit': explicit, 'explicit': trackAPI.explicit_lyrics,
'single': { 'single': {
'trackAPI_gw': trackAPI_gw,
'trackAPI': trackAPI, 'trackAPI': trackAPI,
'albumAPI': albumAPI 'albumAPI': albumAPI
} }
@ -66,7 +69,14 @@ def generateAlbumItem(dz, link_id, bitrate, rootArtist=None):
link_id = albumAPI['id'] link_id = albumAPI['id']
else: else:
try: 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: except APIError as e:
raise GenerationError(f"https://deezer.com/album/{link_id}", str(e)) from 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 # Get extra info about album
# This saves extra api calls when downloading # This saves extra api calls when downloading
albumAPI_gw = dz.gw.get_album(link_id) albumAPI_gw = dz.gw.get_album(link_id)
albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK'] albumAPI_gw = map_album(albumAPI_gw)
albumAPI['copyright'] = albumAPI_gw['COPYRIGHT'] albumAPI_gw.update(albumAPI)
albumAPI['release_date'] = albumAPI_gw['PHYSICAL_RELEASE_DATE'] albumAPI = albumAPI_gw
albumAPI['root_artist'] = rootArtist albumAPI['root_artist'] = rootArtist
# If the album is a single download as a track # 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: if albumAPI['cover_small'] is not None:
cover = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg' cover = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg'
else: 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) totalSize = len(tracksArray)
albumAPI['nb_tracks'] = totalSize albumAPI['nb_tracks'] = totalSize
collection = [] collection = []
for pos, trackAPI in enumerate(tracksArray, start=1): for pos, trackAPI in enumerate(tracksArray, start=1):
trackAPI['POSITION'] = pos trackAPI = map_track(trackAPI)
trackAPI['SIZE'] = totalSize del trackAPI['track_token']
trackAPI['position'] = pos
collection.append(trackAPI) collection.append(trackAPI)
explicit = albumAPI_gw.get('EXPLICIT_ALBUM_CONTENT', {}).get('EXPLICIT_LYRICS_STATUS', LyricsStatus.UNKNOWN) in [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT]
return Collection({ return Collection({
'type': 'album', 'type': 'album',
'id': link_id, 'id': link_id,
@ -110,10 +119,10 @@ def generateAlbumItem(dz, link_id, bitrate, rootArtist=None):
'title': albumAPI['title'], 'title': albumAPI['title'],
'artist': albumAPI['artist']['name'], 'artist': albumAPI['artist']['name'],
'cover': cover, 'cover': cover,
'explicit': explicit, 'explicit': albumAPI['explicit_lyrics'],
'size': totalSize, 'size': totalSize,
'collection': { 'collection': {
'tracks_gw': collection, 'tracks': collection,
'albumAPI': albumAPI 'albumAPI': albumAPI
} }
}) })
@ -147,10 +156,11 @@ def generatePlaylistItem(dz, link_id, bitrate, playlistAPI=None, playlistTracksA
playlistAPI['nb_tracks'] = totalSize playlistAPI['nb_tracks'] = totalSize
collection = [] collection = []
for pos, trackAPI in enumerate(playlistTracksAPI, start=1): 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 playlistAPI['explicit'] = True
trackAPI['POSITION'] = pos del trackAPI['track_token']
trackAPI['SIZE'] = totalSize trackAPI['position'] = pos
collection.append(trackAPI) collection.append(trackAPI)
if 'explicit' not in playlistAPI: playlistAPI['explicit'] = False if 'explicit' not in playlistAPI: playlistAPI['explicit'] = False
@ -165,7 +175,7 @@ def generatePlaylistItem(dz, link_id, bitrate, playlistAPI=None, playlistTracksA
'explicit': playlistAPI['explicit'], 'explicit': playlistAPI['explicit'],
'size': totalSize, 'size': totalSize,
'collection': { 'collection': {
'tracks_gw': collection, 'tracks': collection,
'playlistAPI': playlistAPI 'playlistAPI': playlistAPI
} }
}) })

View File

@ -143,7 +143,7 @@ class Spotify(Plugin):
'explicit': playlistAPI['explicit'], 'explicit': playlistAPI['explicit'],
'size': len(tracklist), 'size': len(tracklist),
'collection': { 'collection': {
'tracks_gw': [], 'tracks': [],
'playlistAPI': playlistAPI 'playlistAPI': playlistAPI
}, },
'plugin': 'spotify', 'plugin': 'spotify',
@ -217,31 +217,31 @@ class Spotify(Plugin):
if cachedTrack.get('id', "0") != "0": if cachedTrack.get('id', "0") != "0":
trackAPI = dz.api.get_track(cachedTrack['id']) trackAPI = dz.api.get_track(cachedTrack['id'])
deezerTrack = None
if not trackAPI: if not trackAPI:
deezerTrack = { trackAPI = {
'SNG_ID': "0", 'id': "0",
'SNG_TITLE': track['name'], 'title': track['name'],
'DURATION': 0, 'duration': 0,
'MD5_ORIGIN': 0, 'md5_origin': 0,
'MEDIA_VERSION': 0, 'media_version': 0,
'FILESIZE': 0, 'filesizes': {},
'ALB_TITLE': track['album']['name'], 'album': {
'ALB_PICTURE': "", 'title': track['album']['name'],
'ART_ID': 0, 'md5_image': ""
'ART_NAME': track['artists'][0]['name'] },
'artist': {
'id': 0,
'name': track['artists'][0]['name']
} }
else: }
deezerTrack = dz.gw.get_track_with_fallback(trackAPI['id']) trackAPI['position'] = pos+1
deezerTrack['_EXTRA_TRACK'] = trackAPI
deezerTrack['POSITION'] = pos+1
conversion['next'] += (1 / downloadObject.size) * 100 conversion['next'] += (1 / downloadObject.size) * 100
if round(conversion['next']) != conversion['now'] and round(conversion['next']) % 2 == 0: if round(conversion['next']) != conversion['now'] and round(conversion['next']) % 2 == 0:
conversion['now'] = round(conversion['next']) conversion['now'] = round(conversion['next'])
if listener: listener.send("updateQueue", {'uuid': downloadObject.uuid, 'conversion': conversion['now']}) if listener: listener.send("updateQueue", {'uuid': downloadObject.uuid, 'conversion': conversion['now']})
return deezerTrack return trackAPI
def convert(self, dz, downloadObject, settings, listener=None): def convert(self, dz, downloadObject, settings, listener=None):
cache = self.loadCache() cache = self.loadCache()
@ -259,7 +259,7 @@ class Spotify(Plugin):
cache, listener cache, listener
).result() ).result()
downloadObject.collection['tracks_gw'] = collection downloadObject.collection['tracks'] = collection
downloadObject.size = len(collection) downloadObject.size = len(collection)
downloadObject = Collection(downloadObject.toDict()) downloadObject = Collection(downloadObject.toDict())
if listener: listener.send("finishConversion", downloadObject.getSlimmedDict()) if listener: listener.send("finishConversion", downloadObject.getSlimmedDict())

View File

@ -39,8 +39,10 @@ DEFAULTS = {
"illegalCharacterReplacer": "_", "illegalCharacterReplacer": "_",
"queueConcurrency": 3, "queueConcurrency": 3,
"maxBitrate": str(TrackFormats.MP3_320), "maxBitrate": str(TrackFormats.MP3_320),
"feelingLucky": False,
"fallbackBitrate": False, "fallbackBitrate": False,
"fallbackSearch": False, "fallbackSearch": False,
"fallbackISRC": False,
"logErrors": True, "logErrors": True,
"logSearched": False, "logSearched": False,
"overwriteFile": OverwriteOption.DONT_OVERWRITE, "overwriteFile": OverwriteOption.DONT_OVERWRITE,

View File

@ -1,5 +1,3 @@
from deezer.gw import LyricsStatus
from deemix.utils import removeDuplicateArtists, removeFeatures from deemix.utils import removeDuplicateArtists, removeFeatures
from deemix.types.Artist import Artist from deemix.types.Artist import Artist
from deemix.types.Date import Date from deemix.types.Date import Date
@ -30,7 +28,7 @@ class Album:
self.rootArtist = None self.rootArtist = None
self.variousArtists = None self.variousArtists = None
self.playlistId = None self.playlistID = None
self.owner = None self.owner = None
self.isPlaylist = False self.isPlaylist = False
@ -39,8 +37,9 @@ class Album:
# Getting artist image ID # Getting artist image ID
# ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg # ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg
art_pic = albumAPI['artist']['picture_small'] art_pic = albumAPI['artist'].get('picture_small')
art_pic = art_pic[art_pic.find('artist/') + 7:-24] if art_pic: art_pic = art_pic[art_pic.find('artist/') + 7:-24]
else: art_pic = ""
self.mainArtist = Artist( self.mainArtist = Artist(
albumAPI['artist']['id'], albumAPI['artist']['id'],
albumAPI['artist']['name'], albumAPI['artist']['name'],
@ -83,16 +82,22 @@ class Album:
self.barcode = albumAPI.get('upc', self.barcode) self.barcode = albumAPI.get('upc', self.barcode)
self.label = albumAPI.get('label', self.label) self.label = albumAPI.get('label', self.label)
self.explicit = bool(albumAPI.get('explicit_lyrics', False)) self.explicit = bool(albumAPI.get('explicit_lyrics', False))
if 'release_date' in albumAPI: release_date = albumAPI.get('release_date')
self.date.day = albumAPI["release_date"][8:10] if 'physical_release_date' in albumAPI:
self.date.month = albumAPI["release_date"][5:7] release_date = albumAPI['physical_release_date']
self.date.year = albumAPI["release_date"][0:4] 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.date.fixDayMonth()
self.discTotal = albumAPI.get('nb_disk') self.discTotal = albumAPI.get('nb_disk')
self.copyright = albumAPI.get('copyright') 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 # Getting album cover MD5
# ex: https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/56x56-000000-80-0-0.jpg # ex: https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/56x56-000000-80-0-0.jpg
alb_pic = albumAPI['cover_small'] alb_pic = albumAPI['cover_small']
@ -102,33 +107,6 @@ class Album:
for genre in albumAPI['genres']['data']: for genre in albumAPI['genres']['data']:
self.genre.append(genre['name']) 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): def makePlaylistCompilation(self, playlist):
self.variousArtists = playlist.variousArtists self.variousArtists = playlist.variousArtists
self.mainArtist = playlist.mainArtist self.mainArtist = playlist.mainArtist

View File

@ -1,9 +1,9 @@
from time import sleep
import re import re
import requests from datetime import datetime
from deezer.utils import map_track, map_album
from deezer.errors import APIError, GWAPIError 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 from deemix.utils import removeFeatures, andCommaConcat, removeDuplicateArtists, generateReplayGainString, changeCase
@ -24,8 +24,10 @@ class Track:
self.MD5 = "" self.MD5 = ""
self.mediaVersion = "" self.mediaVersion = ""
self.trackToken = "" self.trackToken = ""
self.trackTokenExpiration = 0
self.duration = 0 self.duration = 0
self.fallbackID = "0" self.fallbackID = "0"
self.albumsFallback = []
self.filesizes = {} self.filesizes = {}
self.local = False self.local = False
self.mainArtist = None self.mainArtist = None
@ -42,6 +44,7 @@ class Track:
self.explicit = False self.explicit = False
self.ISRC = "" self.ISRC = ""
self.replayGain = "" self.replayGain = ""
self.rank = 0
self.playlist = None self.playlist = None
self.position = None self.position = None
self.searched = False self.searched = False
@ -53,79 +56,54 @@ class Track:
self.featArtistsString = "" self.featArtistsString = ""
self.urls = {} self.urls = {}
def parseEssentialData(self, trackAPI_gw, trackAPI=None): def parseEssentialData(self, trackAPI):
self.id = str(trackAPI_gw['SNG_ID']) self.id = str(trackAPI['id'])
self.duration = trackAPI_gw['DURATION'] self.duration = trackAPI['duration']
self.trackToken = trackAPI_gw['TRACK_TOKEN'] self.trackToken = trackAPI['track_token']
self.MD5 = trackAPI_gw.get('MD5_ORIGIN') self.trackTokenExpiration = trackAPI['track_token_expire']
if not self.MD5: self.MD5 = trackAPI.get('md5_origin')
if trackAPI and trackAPI.get('md5_origin'): self.mediaVersion = trackAPI['media_version']
self.MD5 = trackAPI['md5_origin']
#else:
# raise MD5NotFound
self.mediaVersion = trackAPI_gw['MEDIA_VERSION']
self.fallbackID = "0" self.fallbackID = "0"
if 'FALLBACK' in trackAPI_gw: if 'fallback_id' in trackAPI:
self.fallbackID = trackAPI_gw['FALLBACK']['SNG_ID'] self.fallbackID = trackAPI['fallback_id']
self.local = int(self.id) < 0 self.local = int(self.id) < 0
self.urls = {} self.urls = {}
def retriveFilesizes(self, dz): def parseData(self, dz, track_id=None, trackAPI=None, albumAPI=None, playlistAPI=None):
guest_sid = dz.session.cookies.get('sid') 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: try:
site = requests.post( trackAPI_new = dz.api.get_track(trackAPI['id'])
"https://api.deezer.com/1.0/gateway.php", trackAPI_new['release_date'] = trackAPI['release_date']
params={ trackAPI.update(trackAPI_new)
'api_key': "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE", except APIError: pass
'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)
if self.local: if self.local:
self.parseLocalTrackData(trackAPI_gw) self.parseLocalTrackData(trackAPI)
else: else:
self.retriveFilesizes(dz) self.parseTrack(trackAPI)
self.parseTrackGW(trackAPI_gw)
# Get Lyrics data # Get Lyrics data
if not "LYRICS" in trackAPI_gw and self.lyrics.id != "0": if not trackAPI.get("lyrics") and self.lyrics.id != "0":
try: trackAPI_gw["LYRICS"] = dz.gw.get_track_lyrics(self.id) try: trackAPI["lyrics"] = dz.gw.get_track_lyrics(self.id)
except GWAPIError: self.lyrics.id = "0" 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 # Parse Album Data
self.album = Album( self.album = Album(
alb_id = trackAPI_gw['ALB_ID'], alb_id = trackAPI['album']['id'],
title = trackAPI_gw['ALB_TITLE'], title = trackAPI['album']['title'],
pic_md5 = trackAPI_gw.get('ALB_PICTURE') pic_md5 = trackAPI['album'].get('md5_origin')
) )
# Get album Data # Get album Data
@ -134,31 +112,31 @@ class Track:
except APIError: albumAPI = None except APIError: albumAPI = None
# Get album_gw Data # Get album_gw Data
if not albumAPI_gw: # Only gw has disk number
try: albumAPI_gw = dz.gw.get_album(self.album.id) if not albumAPI or albumAPI and not albumAPI.get('nb_disk'):
except GWAPIError: albumAPI_gw = None 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) self.album.parseAlbum(albumAPI)
elif albumAPI_gw:
self.album.parseAlbumGW(albumAPI_gw)
# albumAPI_gw doesn't contain the artist cover # albumAPI_gw doesn't contain the artist cover
# Getting artist image ID # Getting artist image ID
# ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg # 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) artistAPI = dz.api.get_artist(self.album.mainArtist.id)
self.album.mainArtist.pic.md5 = artistAPI['picture_small'][artistAPI['picture_small'].find('artist/') + 7:-24] self.album.mainArtist.pic.md5 = artistAPI['picture_small'][artistAPI['picture_small'].find('artist/') + 7:-24]
else:
raise AlbumDoesntExists
# Fill missing data # 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 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 'genres' in trackAPI:
if not self.copyright: self.copyright = albumAPI_gw['COPYRIGHT'] for genre in trackAPI['genres']:
if 'GENRES' in trackAPI_gw:
for genre in trackAPI_gw['GENRES']:
if genre not in self.album.genre: self.album.genre.append(genre) if genre not in self.album.genre: self.album.genre.append(genre)
self.parseTrack(trackAPI)
# Remove unwanted charaters in track name # Remove unwanted charaters in track name
# Example: track/127793 # Example: track/127793
@ -168,7 +146,7 @@ class Track:
if len(self.artist['Main']) == 0: if len(self.artist['Main']) == 0:
self.artist['Main'] = [self.mainArtist.name] 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 # Add playlist data if track is in a playlist
if playlistAPI: self.playlist = Playlist(playlistAPI) if playlistAPI: self.playlist = Playlist(playlistAPI)
@ -176,63 +154,52 @@ class Track:
self.generateMainFeatStrings() self.generateMainFeatStrings()
return self return self
def parseLocalTrackData(self, trackAPI_gw): def parseLocalTrackData(self, trackAPI):
# Local tracks has only the trackAPI_gw page and # Local tracks has only the trackAPI_gw page and
# contains only the tags provided by the file # contains only the tags provided by the file
self.title = trackAPI_gw['SNG_TITLE'] self.title = trackAPI['title']
self.album = Album(title=trackAPI_gw['ALB_TITLE']) self.album = Album(title=trackAPI['album']['title'])
self.album.pic = Picture( self.album.pic = Picture(
md5 = trackAPI_gw.get('ALB_PICTURE', ""), md5 = trackAPI.get('md5_image', ""),
pic_type = "cover" pic_type = "cover"
) )
self.mainArtist = Artist(name=trackAPI_gw['ART_NAME'], role="Main") self.mainArtist = Artist(name=trackAPI['artist']['name'], role="Main")
self.artists = [trackAPI_gw['ART_NAME']] self.artists = [trackAPI['artist']['name']]
self.artist = { self.artist = {
'Main': [trackAPI_gw['ART_NAME']] 'Main': [trackAPI['artist']['name']]
} }
self.album.artist = self.artist self.album.artist = self.artist
self.album.artists = self.artists self.album.artists = self.artists
self.album.date = self.date self.album.date = self.date
self.album.mainArtist = self.mainArtist 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): 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'] self.bpm = trackAPI['bpm']
if not self.replayGain and 'gain' in trackAPI: self.lyrics = Lyrics(trackAPI.get('lyrics_id', "0"))
self.replayGain = generateReplayGainString(trackAPI['gain'])
if not self.explicit: self.mainArtist = Artist(
self.explicit = trackAPI['explicit_lyrics'] art_id = trackAPI['artist']['id'],
if not self.discNumber: name = trackAPI['artist']['name'],
self.discNumber = trackAPI['disk_number'] 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']: for artist in trackAPI['contributors']:
isVariousArtists = str(artist['id']) == VARIOUS_ARTISTS isVariousArtists = str(artist['id']) == VARIOUS_ARTISTS
@ -249,6 +216,11 @@ class Track:
self.artist[artist['role']] = [] self.artist[artist['role']] = []
self.artist[artist['role']].append(artist['name']) 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): def removeDuplicateArtists(self):
(self.artist, self.artists) = removeDuplicateArtists(self.artist, self.artists) (self.artist, self.artists) = removeDuplicateArtists(self.artist, self.artists)
@ -267,6 +239,14 @@ class Track:
if 'Featured' in self.artist: if 'Featured' in self.artist:
self.featArtistsString = "feat. "+andCommaConcat(self.artist['Featured']) 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): def applySettings(self, settings):
# Check if should save the playlist as a compilation # Check if should save the playlist as a compilation

View File

@ -23,7 +23,7 @@ setup(
python_requires='>=3.7', python_requires='>=3.7',
packages=find_packages(exclude=("tests",)), packages=find_packages(exclude=("tests",)),
include_package_data=True, 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={ extras_require={
"spotify": ["spotipy>=2.11.0"] "spotify": ["spotipy>=2.11.0"]
}, },