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

View File

@ -60,7 +60,8 @@ ErrorMessages = {
'noSpaceLeft': "No space left on target drive, clean up some space for the tracks",
'albumDoesntExists': "Track's album does not exsist, failed to gather info.",
'notLoggedIn': "You need to login to download tracks.",
'wrongGeolocation': "Your account can't stream the track from your current country."
'wrongGeolocation': "Your account can't stream the track from your current country.",
'wrongGeolocationNoAlternative': "Your account can't stream the track from your current country and no alternative found."
}
class DownloadFailed(DownloadError):

View File

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

View File

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

View File

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

View File

@ -1,5 +1,3 @@
from deezer.gw import LyricsStatus
from deemix.utils import removeDuplicateArtists, removeFeatures
from deemix.types.Artist import Artist
from deemix.types.Date import Date
@ -30,7 +28,7 @@ class Album:
self.rootArtist = None
self.variousArtists = None
self.playlistId = None
self.playlistID = None
self.owner = None
self.isPlaylist = False
@ -39,8 +37,9 @@ class Album:
# Getting artist image ID
# ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg
art_pic = albumAPI['artist']['picture_small']
art_pic = art_pic[art_pic.find('artist/') + 7:-24]
art_pic = albumAPI['artist'].get('picture_small')
if art_pic: art_pic = art_pic[art_pic.find('artist/') + 7:-24]
else: art_pic = ""
self.mainArtist = Artist(
albumAPI['artist']['id'],
albumAPI['artist']['name'],
@ -83,16 +82,22 @@ class Album:
self.barcode = albumAPI.get('upc', self.barcode)
self.label = albumAPI.get('label', self.label)
self.explicit = bool(albumAPI.get('explicit_lyrics', False))
if 'release_date' in albumAPI:
self.date.day = albumAPI["release_date"][8:10]
self.date.month = albumAPI["release_date"][5:7]
self.date.year = albumAPI["release_date"][0:4]
release_date = albumAPI.get('release_date')
if 'physical_release_date' in albumAPI:
release_date = albumAPI['physical_release_date']
if release_date:
self.date.day = release_date[8:10]
self.date.month = release_date[5:7]
self.date.year = release_date[0:4]
self.date.fixDayMonth()
self.discTotal = albumAPI.get('nb_disk')
self.copyright = albumAPI.get('copyright')
if self.pic.md5 == "" and albumAPI.get('cover_small'):
if self.pic.md5 == "":
if albumAPI.get('md5_image'):
self.pic.md5 = albumAPI['md5_image']
elif albumAPI.get('cover_small'):
# Getting album cover MD5
# ex: https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/56x56-000000-80-0-0.jpg
alb_pic = albumAPI['cover_small']
@ -102,33 +107,6 @@ class Album:
for genre in albumAPI['genres']['data']:
self.genre.append(genre['name'])
def parseAlbumGW(self, albumAPI_gw):
self.title = albumAPI_gw['ALB_TITLE']
self.mainArtist = Artist(
art_id = albumAPI_gw['ART_ID'],
name = albumAPI_gw['ART_NAME'],
role = "Main"
)
self.artists = [albumAPI_gw['ART_NAME']]
self.trackTotal = albumAPI_gw['NUMBER_TRACK']
self.discTotal = albumAPI_gw['NUMBER_DISK']
self.label = albumAPI_gw.get('LABEL_NAME', self.label)
explicitLyricsStatus = albumAPI_gw.get('EXPLICIT_ALBUM_CONTENT', {}).get('EXPLICIT_LYRICS_STATUS', LyricsStatus.UNKNOWN)
self.explicit = explicitLyricsStatus in [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT]
self.addExtraAlbumGWData(albumAPI_gw)
def addExtraAlbumGWData(self, albumAPI_gw):
if self.pic.md5 == "" and albumAPI_gw.get('ALB_PICTURE'):
self.pic.md5 = albumAPI_gw['ALB_PICTURE']
if 'PHYSICAL_RELEASE_DATE' in albumAPI_gw:
self.date.day = albumAPI_gw["PHYSICAL_RELEASE_DATE"][8:10]
self.date.month = albumAPI_gw["PHYSICAL_RELEASE_DATE"][5:7]
self.date.year = albumAPI_gw["PHYSICAL_RELEASE_DATE"][0:4]
self.date.fixDayMonth()
def makePlaylistCompilation(self, playlist):
self.variousArtists = playlist.variousArtists
self.mainArtist = playlist.mainArtist

View File

@ -1,9 +1,9 @@
from time import sleep
import re
import requests
from datetime import datetime
from deezer.utils import map_track, map_album
from deezer.errors import APIError, GWAPIError
from deemix.errors import TrackError, NoDataToParse, AlbumDoesntExists
from deemix.errors import NoDataToParse, AlbumDoesntExists
from deemix.utils import removeFeatures, andCommaConcat, removeDuplicateArtists, generateReplayGainString, changeCase
@ -24,8 +24,10 @@ class Track:
self.MD5 = ""
self.mediaVersion = ""
self.trackToken = ""
self.trackTokenExpiration = 0
self.duration = 0
self.fallbackID = "0"
self.albumsFallback = []
self.filesizes = {}
self.local = False
self.mainArtist = None
@ -42,6 +44,7 @@ class Track:
self.explicit = False
self.ISRC = ""
self.replayGain = ""
self.rank = 0
self.playlist = None
self.position = None
self.searched = False
@ -53,79 +56,54 @@ class Track:
self.featArtistsString = ""
self.urls = {}
def parseEssentialData(self, trackAPI_gw, trackAPI=None):
self.id = str(trackAPI_gw['SNG_ID'])
self.duration = trackAPI_gw['DURATION']
self.trackToken = trackAPI_gw['TRACK_TOKEN']
self.MD5 = trackAPI_gw.get('MD5_ORIGIN')
if not self.MD5:
if trackAPI and trackAPI.get('md5_origin'):
self.MD5 = trackAPI['md5_origin']
#else:
# raise MD5NotFound
self.mediaVersion = trackAPI_gw['MEDIA_VERSION']
def parseEssentialData(self, trackAPI):
self.id = str(trackAPI['id'])
self.duration = trackAPI['duration']
self.trackToken = trackAPI['track_token']
self.trackTokenExpiration = trackAPI['track_token_expire']
self.MD5 = trackAPI.get('md5_origin')
self.mediaVersion = trackAPI['media_version']
self.fallbackID = "0"
if 'FALLBACK' in trackAPI_gw:
self.fallbackID = trackAPI_gw['FALLBACK']['SNG_ID']
if 'fallback_id' in trackAPI:
self.fallbackID = trackAPI['fallback_id']
self.local = int(self.id) < 0
self.urls = {}
def retriveFilesizes(self, dz):
guest_sid = dz.session.cookies.get('sid')
def parseData(self, dz, track_id=None, trackAPI=None, albumAPI=None, playlistAPI=None):
if track_id and (not trackAPI or trackAPI and not trackAPI.get('track_token')):
trackAPI_new = dz.gw.get_track_with_fallback(track_id)
trackAPI_new = map_track(trackAPI_new)
if not trackAPI: trackAPI = {}
trackAPI_new.update(trackAPI)
trackAPI = trackAPI_new
elif not trackAPI: raise NoDataToParse
self.parseEssentialData(trackAPI)
# only public api has bpm
if not trackAPI.get('bpm') and not self.local:
try:
site = requests.post(
"https://api.deezer.com/1.0/gateway.php",
params={
'api_key': "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE",
'sid': guest_sid,
'input': '3',
'output': '3',
'method': 'song_getData'
},
timeout=30,
json={'sng_id': self.id},
headers=dz.http_headers
)
result_json = site.json()
except:
sleep(2)
self.retriveFilesizes(dz)
if len(result_json['error']):
raise TrackError(result_json.dumps(result_json['error']))
response = result_json.get("results", {})
filesizes = {}
for key, value in response.items():
if key.startswith("FILESIZE_"):
filesizes[key] = int(value)
filesizes[key+"_TESTED"] = False
self.filesizes = filesizes
def parseData(self, dz, track_id=None, trackAPI_gw=None, trackAPI=None, albumAPI_gw=None, albumAPI=None, playlistAPI=None):
if track_id and not trackAPI_gw: trackAPI_gw = dz.gw.get_track_with_fallback(track_id)
elif not trackAPI_gw: raise NoDataToParse
if not trackAPI:
try: trackAPI = dz.api.get_track(trackAPI_gw['SNG_ID'])
except APIError: trackAPI = None
self.parseEssentialData(trackAPI_gw, trackAPI)
trackAPI_new = dz.api.get_track(trackAPI['id'])
trackAPI_new['release_date'] = trackAPI['release_date']
trackAPI.update(trackAPI_new)
except APIError: pass
if self.local:
self.parseLocalTrackData(trackAPI_gw)
self.parseLocalTrackData(trackAPI)
else:
self.retriveFilesizes(dz)
self.parseTrackGW(trackAPI_gw)
self.parseTrack(trackAPI)
# Get Lyrics data
if not "LYRICS" in trackAPI_gw and self.lyrics.id != "0":
try: trackAPI_gw["LYRICS"] = dz.gw.get_track_lyrics(self.id)
if not trackAPI.get("lyrics") and self.lyrics.id != "0":
try: trackAPI["lyrics"] = dz.gw.get_track_lyrics(self.id)
except GWAPIError: self.lyrics.id = "0"
if self.lyrics.id != "0": self.lyrics.parseLyrics(trackAPI_gw["LYRICS"])
if self.lyrics.id != "0": self.lyrics.parseLyrics(trackAPI["lyrics"])
# Parse Album Data
self.album = Album(
alb_id = trackAPI_gw['ALB_ID'],
title = trackAPI_gw['ALB_TITLE'],
pic_md5 = trackAPI_gw.get('ALB_PICTURE')
alb_id = trackAPI['album']['id'],
title = trackAPI['album']['title'],
pic_md5 = trackAPI['album'].get('md5_origin')
)
# Get album Data
@ -134,31 +112,31 @@ class Track:
except APIError: albumAPI = None
# Get album_gw Data
if not albumAPI_gw:
try: albumAPI_gw = dz.gw.get_album(self.album.id)
except GWAPIError: albumAPI_gw = None
# Only gw has disk number
if not albumAPI or albumAPI and not albumAPI.get('nb_disk'):
try:
albumAPI_gw = dz.gw.get_album(self.album.id)
albumAPI_gw = map_album(albumAPI_gw)
except GWAPIError: albumAPI_gw = {}
if not albumAPI: albumAPI = {}
albumAPI_gw.update(albumAPI)
albumAPI = albumAPI_gw
if not albumAPI: raise AlbumDoesntExists
if albumAPI:
self.album.parseAlbum(albumAPI)
elif albumAPI_gw:
self.album.parseAlbumGW(albumAPI_gw)
# albumAPI_gw doesn't contain the artist cover
# Getting artist image ID
# ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg
if not self.album.mainArtist.pic.md5 or self.album.mainArtist.pic.md5 == "":
artistAPI = dz.api.get_artist(self.album.mainArtist.id)
self.album.mainArtist.pic.md5 = artistAPI['picture_small'][artistAPI['picture_small'].find('artist/') + 7:-24]
else:
raise AlbumDoesntExists
# Fill missing data
if albumAPI_gw: self.album.addExtraAlbumGWData(albumAPI_gw)
if self.album.date and not self.date: self.date = self.album.date
if not self.album.discTotal: self.album.discTotal = albumAPI_gw.get('NUMBER_DISK', "1")
if not self.copyright: self.copyright = albumAPI_gw['COPYRIGHT']
if 'GENRES' in trackAPI_gw:
for genre in trackAPI_gw['GENRES']:
if 'genres' in trackAPI:
for genre in trackAPI['genres']:
if genre not in self.album.genre: self.album.genre.append(genre)
self.parseTrack(trackAPI)
# Remove unwanted charaters in track name
# Example: track/127793
@ -168,7 +146,7 @@ class Track:
if len(self.artist['Main']) == 0:
self.artist['Main'] = [self.mainArtist.name]
self.position = trackAPI_gw.get('POSITION')
self.position = trackAPI.get('position')
# Add playlist data if track is in a playlist
if playlistAPI: self.playlist = Playlist(playlistAPI)
@ -176,63 +154,52 @@ class Track:
self.generateMainFeatStrings()
return self
def parseLocalTrackData(self, trackAPI_gw):
def parseLocalTrackData(self, trackAPI):
# Local tracks has only the trackAPI_gw page and
# contains only the tags provided by the file
self.title = trackAPI_gw['SNG_TITLE']
self.album = Album(title=trackAPI_gw['ALB_TITLE'])
self.title = trackAPI['title']
self.album = Album(title=trackAPI['album']['title'])
self.album.pic = Picture(
md5 = trackAPI_gw.get('ALB_PICTURE', ""),
md5 = trackAPI.get('md5_image', ""),
pic_type = "cover"
)
self.mainArtist = Artist(name=trackAPI_gw['ART_NAME'], role="Main")
self.artists = [trackAPI_gw['ART_NAME']]
self.mainArtist = Artist(name=trackAPI['artist']['name'], role="Main")
self.artists = [trackAPI['artist']['name']]
self.artist = {
'Main': [trackAPI_gw['ART_NAME']]
'Main': [trackAPI['artist']['name']]
}
self.album.artist = self.artist
self.album.artists = self.artists
self.album.date = self.date
self.album.mainArtist = self.mainArtist
def parseTrackGW(self, trackAPI_gw):
self.title = trackAPI_gw['SNG_TITLE'].strip()
if trackAPI_gw.get('VERSION') and not trackAPI_gw['VERSION'].strip() in self.title:
self.title += f" {trackAPI_gw['VERSION'].strip()}"
self.discNumber = trackAPI_gw.get('DISK_NUMBER')
self.explicit = bool(int(trackAPI_gw.get('EXPLICIT_LYRICS', "0")))
self.copyright = trackAPI_gw.get('COPYRIGHT')
if 'GAIN' in trackAPI_gw: self.replayGain = generateReplayGainString(trackAPI_gw['GAIN'])
self.ISRC = trackAPI_gw.get('ISRC')
self.trackNumber = trackAPI_gw['TRACK_NUMBER']
self.contributors = trackAPI_gw['SNG_CONTRIBUTORS']
self.rank = trackAPI_gw['RANK_SNG']
self.lyrics = Lyrics(trackAPI_gw.get('LYRICS_ID', "0"))
self.mainArtist = Artist(
art_id = trackAPI_gw['ART_ID'],
name = trackAPI_gw['ART_NAME'],
role = "Main",
pic_md5 = trackAPI_gw.get('ART_PICTURE')
)
if 'PHYSICAL_RELEASE_DATE' in trackAPI_gw:
self.date.day = trackAPI_gw["PHYSICAL_RELEASE_DATE"][8:10]
self.date.month = trackAPI_gw["PHYSICAL_RELEASE_DATE"][5:7]
self.date.year = trackAPI_gw["PHYSICAL_RELEASE_DATE"][0:4]
self.date.fixDayMonth()
def parseTrack(self, trackAPI):
self.title = trackAPI['title']
self.discNumber = trackAPI.get('disk_number')
self.explicit = trackAPI.get('explicit_lyrics', False)
self.copyright = trackAPI.get('copyright')
if 'gain' in trackAPI: self.replayGain = generateReplayGainString(trackAPI['gain'])
self.ISRC = trackAPI.get('isrc')
self.trackNumber = trackAPI['track_position']
self.contributors = trackAPI.get('song_contributors')
self.rank = trackAPI['rank']
self.bpm = trackAPI['bpm']
if not self.replayGain and 'gain' in trackAPI:
self.replayGain = generateReplayGainString(trackAPI['gain'])
if not self.explicit:
self.explicit = trackAPI['explicit_lyrics']
if not self.discNumber:
self.discNumber = trackAPI['disk_number']
self.lyrics = Lyrics(trackAPI.get('lyrics_id', "0"))
self.mainArtist = Artist(
art_id = trackAPI['artist']['id'],
name = trackAPI['artist']['name'],
role = "Main",
pic_md5 = trackAPI['artist'].get('md5_image')
)
if 'physical_release_date' in trackAPI:
self.date.day = trackAPI["physical_release_date"][8:10]
self.date.month = trackAPI["physical_release_date"][5:7]
self.date.year = trackAPI["physical_release_date"][0:4]
self.date.fixDayMonth()
for artist in trackAPI['contributors']:
isVariousArtists = str(artist['id']) == VARIOUS_ARTISTS
@ -249,6 +216,11 @@ class Track:
self.artist[artist['role']] = []
self.artist[artist['role']].append(artist['name'])
if 'alternative_albums' in trackAPI and trackAPI['alternative_albums']:
for album in trackAPI['alternative_albums']['data']:
if 'RIGHTS' in album and album['RIGHTS'].get('STREAM_ADS_AVAILABLE') or album['RIGHTS'].get('STREAM_SUB_AVAILABLE'):
self.albumsFallback.append(album['ALB_ID'])
def removeDuplicateArtists(self):
(self.artist, self.artists) = removeDuplicateArtists(self.artist, self.artists)
@ -267,6 +239,14 @@ class Track:
if 'Featured' in self.artist:
self.featArtistsString = "feat. "+andCommaConcat(self.artist['Featured'])
def checkAndRenewTrackToken(self, dz):
now = datetime.now()
expiration = datetime.fromtimestamp(self.trackTokenExpiration)
if now > expiration:
newTrack = dz.gw.get_track_with_fallback(self.id)
self.trackToken = newTrack['TRACK_TOKEN']
self.trackTokenExpiration = newTrack['TRACK_TOKEN_EXPIRE']
def applySettings(self, settings):
# Check if should save the playlist as a compilation

View File

@ -23,7 +23,7 @@ setup(
python_requires='>=3.7',
packages=find_packages(exclude=("tests",)),
include_package_data=True,
install_requires=["click", "pycryptodomex", "mutagen", "requests", "deezer-py>=1.2.8"],
install_requires=["click", "pycryptodomex", "mutagen", "requests", "deezer-py>=1.3.0"],
extras_require={
"spotify": ["spotipy>=2.11.0"]
},