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 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
|
||||||
try:
|
if formatName.lower() in track.filesizes and track.filesizes[formatName.lower()] != "0":
|
||||||
url = dz.get_track_url(track.trackToken, formatName)
|
try:
|
||||||
if testURL(track, url, formatName): return url
|
url = dz.get_track_url(track.trackToken, formatName)
|
||||||
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)
|
track.parseEssentialData(newTrack)
|
||||||
track.retriveFilesizes(self.dz)
|
|
||||||
return self.downloadWrapper(extraData, track)
|
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)
|
||||||
|
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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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:
|
||||||
try:
|
if str(link_id).startswith("isrc") or int(link_id) > 0:
|
||||||
trackAPI = dz.api.get_track(link_id)
|
try:
|
||||||
except APIError as e:
|
trackAPI = dz.api.get_track(link_id)
|
||||||
raise GenerationError(f"https://deezer.com/track/{link_id}", str(e)) from e
|
except APIError as e:
|
||||||
|
raise GenerationError(f"https://deezer.com/track/{link_id}", str(e)) from e
|
||||||
|
|
||||||
if 'id' in trackAPI and 'title' in trackAPI:
|
# Check if is an isrc: url
|
||||||
link_id = trackAPI['id']
|
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:
|
else:
|
||||||
raise ISRCnotOnDeezer(f"https://deezer.com/track/{link_id}")
|
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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -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:
|
trackAPI['position'] = pos+1
|
||||||
deezerTrack = dz.gw.get_track_with_fallback(trackAPI['id'])
|
|
||||||
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())
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,52 +82,31 @@ 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 == "":
|
||||||
# Getting album cover MD5
|
if albumAPI.get('md5_image'):
|
||||||
# ex: https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/56x56-000000-80-0-0.jpg
|
self.pic.md5 = albumAPI['md5_image']
|
||||||
alb_pic = albumAPI['cover_small']
|
elif albumAPI.get('cover_small'):
|
||||||
self.pic.md5 = alb_pic[alb_pic.find('cover/') + 6:-24]
|
# 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']
|
||||||
|
self.pic.md5 = alb_pic[alb_pic.find('cover/') + 6:-24]
|
||||||
|
|
||||||
if albumAPI.get('genres') and len(albumAPI['genres'].get('data', [])) > 0:
|
if albumAPI.get('genres') and len(albumAPI['genres'].get('data', [])) > 0:
|
||||||
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
|
||||||
|
|
|
@ -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')):
|
||||||
try:
|
trackAPI_new = dz.gw.get_track_with_fallback(track_id)
|
||||||
site = requests.post(
|
trackAPI_new = map_track(trackAPI_new)
|
||||||
"https://api.deezer.com/1.0/gateway.php",
|
if not trackAPI: trackAPI = {}
|
||||||
params={
|
trackAPI_new.update(trackAPI)
|
||||||
'api_key': "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE",
|
trackAPI = trackAPI_new
|
||||||
'sid': guest_sid,
|
elif not trackAPI: raise NoDataToParse
|
||||||
'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):
|
self.parseEssentialData(trackAPI)
|
||||||
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)
|
# only public api has bpm
|
||||||
|
if not trackAPI.get('bpm') and not self.local:
|
||||||
|
try:
|
||||||
|
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:
|
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 albumAPI:
|
if not albumAPI: raise AlbumDoesntExists
|
||||||
self.album.parseAlbum(albumAPI)
|
|
||||||
elif albumAPI_gw:
|
self.album.parseAlbum(albumAPI)
|
||||||
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
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -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"]
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue