Refactoring

This commit is contained in:
RemixDev
2021-01-31 19:59:15 +03:00
parent 44eeb3e28e
commit 8750881108
14 changed files with 732 additions and 673 deletions

View File

@ -16,7 +16,7 @@ from tempfile import gettempdir
from urllib3.exceptions import SSLError as u3SSLError
from deemix.app.queueitem import QISingle, QICollection
from deemix.app.track import Track, AlbumDoesntExists
from deemix.types.Track import Track, AlbumDoesntExists
from deemix.utils import changeCase
from deemix.utils.pathtemplates import generateFilename, generateFilepath, settingsRegexAlbum, settingsRegexArtist, settingsRegexPlaylistFile
from deezer import TrackFormats
@ -93,39 +93,6 @@ def downloadImage(url, path, overwrite=OverwriteOption.DONT_OVERWRITE):
else:
return path
def generatePictureURL(pic, size, format):
if pic['url']: return pic['url']
if format.startswith("jpg"):
if '-' in format:
quality = format[4:]
else:
quality = 80
format = 'jpg'
return "https://e-cdns-images.dzcdn.net/images/{}/{}/{}x{}-{}".format(
pic['type'],
pic['md5'],
size, size,
f'000000-{quality}-0-0.jpg'
)
if format == 'png':
return "https://e-cdns-images.dzcdn.net/images/{}/{}/{}x{}-{}".format(
pic['type'],
pic['md5'],
size, size,
'none-100-0-0.png'
)
def formatDate(date, template):
elements = {
'year': ['YYYY', 'YY', 'Y'],
'month': ['MM', 'M'],
'day': ['DD', 'D']
}
for element, placeholders in elements.items():
for placeholder in placeholders:
if placeholder in template:
template = template.replace(placeholder, str(date[element]))
return template
class DownloadJob:
def __init__(self, dz, queueItem, interface=None):
@ -251,11 +218,13 @@ class DownloadJob:
if not track:
logger.info(f"[{trackAPI_gw['ART_NAME']} - {trackAPI_gw['SNG_TITLE']}] Getting the tags")
try:
track = Track(self.dz,
trackAPI_gw=trackAPI_gw,
trackAPI=trackAPI_gw['_EXTRA_TRACK'] if '_EXTRA_TRACK' in trackAPI_gw else None,
albumAPI=trackAPI_gw['_EXTRA_ALBUM'] if '_EXTRA_ALBUM' in trackAPI_gw else None
)
track = Track().parseData(
dz=self.dz,
trackAPI_gw=trackAPI_gw,
trackAPI=trackAPI_gw['_EXTRA_TRACK'] if '_EXTRA_TRACK' in trackAPI_gw else None,
albumAPI=trackAPI_gw['_EXTRA_ALBUM'] if '_EXTRA_ALBUM' in trackAPI_gw else None,
playlistAPI = trackAPI_gw['_EXTRA_PLAYLIST'] if '_EXTRA_PLAYLIST' in trackAPI_gw else None
)
except AlbumDoesntExists:
raise DownloadError('albumDoesntExists')
if self.queueItem.cancel: raise DownloadCancelled
@ -263,16 +232,18 @@ class DownloadJob:
# Check if track not yet encoded
if track.MD5 == '':
if track.fallbackId != "0":
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not yet encoded, using fallback id")
logger.warn(f"[{track.mainArtist.name} - {track.title}] Track not yet encoded, using fallback id")
newTrack = self.dz.gw.get_track_with_fallback(track.fallbackId)
track.parseEssentialData(self.dz, newTrack)
track.parseEssentialData(newTrack)
track.retriveFilesizes(self.dz)
return self.download(trackAPI_gw, track)
elif not track.searched and self.settings['fallbackSearch']:
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not yet encoded, searching for alternative")
searchedId = self.dz.api.get_track_id_from_metadata(track.mainArtist['name'], track.title, track.album['title'])
logger.warn(f"[{track.mainArtist.name} - {track.title}] Track not yet encoded, searching for alternative")
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)
track.parseEssentialData(self.dz, newTrack)
track.parseEssentialData(newTrack)
track.retriveFilesizes(self.dz)
track.searched = True
if self.interface:
self.interface.send('queueUpdate', {
@ -281,7 +252,7 @@ class DownloadJob:
'data': {
'id': track.id,
'title': track.title,
'artist': track.mainArtist['name']
'artist': track.mainArtist.name
},
})
return self.download(trackAPI_gw, track)
@ -295,16 +266,18 @@ class DownloadJob:
selectedFormat = self.getPreferredBitrate(track)
except PreferredBitrateNotFound:
if track.fallbackId != "0":
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not found at desired bitrate, using fallback id")
logger.warn(f"[{track.mainArtist.name} - {track.title}] Track not found at desired bitrate, using fallback id")
newTrack = self.dz.gw.get_track_with_fallback(track.fallbackId)
track.parseEssentialData(self.dz, newTrack)
track.parseEssentialData(newTrack)
track.retriveFilesizes(self.dz)
return self.download(trackAPI_gw, track)
elif not track.searched and self.settings['fallbackSearch']:
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not found at desired bitrate, searching for alternative")
searchedId = self.dz.api.get_track_id_from_metadata(track.mainArtist['name'], track.title, track.album['title'])
logger.warn(f"[{track.mainArtist.name} - {track.title}] Track not found at desired bitrate, searching for alternative")
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)
track.parseEssentialData(self.dz, newTrack)
track.parseEssentialData(newTrack)
track.retriveFilesizes(self.dz)
track.searched = True
if self.interface:
self.interface.send('queueUpdate', {
@ -313,7 +286,7 @@ class DownloadJob:
'data': {
'id': track.id,
'title': track.title,
'artist': track.mainArtist['name']
'artist': track.mainArtist.name
},
})
return self.download(trackAPI_gw, track)
@ -324,7 +297,7 @@ class DownloadJob:
except TrackNot360:
raise DownloadFailed("no360RA")
track.selectedFormat = selectedFormat
track.album['bitrate'] = selectedFormat
track.album.bitrate = selectedFormat
# Generate covers URLs
embeddedImageFormat = f'jpg-{self.settings["jpegImageQuality"]}'
@ -333,37 +306,37 @@ class DownloadJob:
if self.settings['tags']['savePlaylistAsCompilation'] and track.playlist:
track.trackNumber = track.position
track.discNumber = "1"
track.album = {**track.album, **track.playlist}
track.album['embeddedCoverURL'] = generatePictureURL(track.playlist['pic'], self.settings['embeddedArtworkSize'], embeddedImageFormat)
track.album.makePlaylistCompilation(track.playlist)
track.album.embeddedCoverURL = track.playlist.pic.generatePictureURL(self.settings['embeddedArtworkSize'], embeddedImageFormat)
ext = track.album['embeddedCoverURL'][-4:]
ext = track.album.embeddedCoverURL[-4:]
if ext[0] != ".": ext = ".jpg" # Check for Spotify images
track.album['embeddedCoverPath'] = TEMPDIR / f"pl{trackAPI_gw['_EXTRA_PLAYLIST']['id']}_{self.settings['embeddedArtworkSize']}{ext}"
track.album.embeddedCoverPath = TEMPDIR / f"pl{trackAPI_gw['_EXTRA_PLAYLIST']['id']}_{self.settings['embeddedArtworkSize']}{ext}"
else:
if track.album['date']: track.date = track.album['date']
track.album['embeddedCoverURL'] = generatePictureURL(track.album['pic'], self.settings['embeddedArtworkSize'], embeddedImageFormat)
if track.album.date: track.date = track.album.date
track.album.embeddedCoverURL = track.album.pic.generatePictureURL(self.settings['embeddedArtworkSize'], embeddedImageFormat)
ext = track.album['embeddedCoverURL'][-4:]
track.album['embeddedCoverPath'] = TEMPDIR / f"alb{track.album['id']}_{self.settings['embeddedArtworkSize']}{ext}"
ext = track.album.embeddedCoverURL[-4:]
track.album.embeddedCoverPath = TEMPDIR / f"alb{track.album.id}_{self.settings['embeddedArtworkSize']}{ext}"
track.dateString = formatDate(track.date, self.settings['dateFormat'])
track.album['dateString'] = formatDate(track.album['date'], self.settings['dateFormat'])
if track.playlist: track.playlist['dateString'] = formatDate(track.playlist['date'], self.settings['dateFormat'])
track.dateString = track.date.format(self.settings['dateFormat'])
track.album.dateString = track.album.date.format(self.settings['dateFormat'])
if track.playlist: track.playlist.dateString = track.playlist.date.format(self.settings['dateFormat'])
# Check various artist option
if self.settings['albumVariousArtists'] and track.album['variousArtists']:
artist = track.album['variousArtists']
isMainArtist = artist['role'] == "Main"
if self.settings['albumVariousArtists'] and track.album.variousArtists:
artist = track.album.variousArtists
isMainArtist = artist.role == "Main"
if artist['name'] not in track.album['artists']:
track.album['artists'].insert(0, artist['name'])
if artist.name not in track.album.artists:
track.album.artists.insert(0, artist.name)
if isMainArtist or artist['name'] not in track.album['artist']['Main'] and not isMainArtist:
if not artist['role'] in track.album['artist']:
track.album['artist'][artist['role']] = []
track.album['artist'][artist['role']].insert(0, artist['name'])
track.album['mainArtist']['save'] = not track.album['mainArtist']['isVariousArtists'] or self.settings['albumVariousArtists'] and track.album['mainArtist']['isVariousArtists']
if isMainArtist or artist.name not in track.album.artist['Main'] and not isMainArtist:
if not artist.role in track.album.artist:
track.album.artist[artist.role] = []
track.album.artist[artist.role].insert(0, artist.name)
track.album.mainArtist.save = not track.album.mainArtist.isVariousArtists() or self.settings['albumVariousArtists'] and track.album.mainArtist.isVariousArtists()
# Check removeDuplicateArtists
if self.settings['removeDuplicateArtists']: track.removeDuplicateArtists()
@ -375,7 +348,7 @@ class DownloadJob:
track.title = track.getFeatTitle()
elif str(self.settings['featuredToTitle']) == FeaturesOption.REMOVE_TITLE_ALBUM:
track.title = track.getCleanTitle()
track.album['title'] = track.getCleanAlbumTitle()
track.album.title = track.album.getCleanTitle()
# Remove (Album Version) from tracks that have that
if self.settings['removeAlbumVersion']:
@ -386,7 +359,7 @@ class DownloadJob:
if self.settings['titleCasing'] != "nothing":
track.title = changeCase(track.title, self.settings['titleCasing'])
if self.settings['artistCasing'] != "nothing":
track.mainArtist['name'] = changeCase(track.mainArtist['name'], self.settings['artistCasing'])
track.mainArtist.name = changeCase(track.mainArtist.name, self.settings['artistCasing'])
for i, artist in enumerate(track.artists):
track.artists[i] = changeCase(artist, self.settings['artistCasing'])
for type in track.artist:
@ -418,8 +391,8 @@ class DownloadJob:
if self.queueItem.cancel: raise DownloadCancelled
# Download and cache coverart
logger.info(f"[{track.mainArtist['name']} - {track.title}] Getting the album cover")
track.album['embeddedCoverPath'] = downloadImage(track.album['embeddedCoverURL'], track.album['embeddedCoverPath'])
logger.info(f"[{track.mainArtist.name} - {track.title}] Getting the album cover")
track.album.embeddedCoverPath = downloadImage(track.album.embeddedCoverURL, track.album.embeddedCoverPath)
# Save local album art
if coverPath:
@ -428,10 +401,10 @@ class DownloadJob:
if format in ["png","jpg"]:
extendedFormat = format
if extendedFormat == "jpg": extendedFormat += f"-{self.settings['jpegImageQuality']}"
url = generatePictureURL(track.album['pic'], self.settings['localArtworkSize'], extendedFormat)
url = track.album.pic.generatePictureURL(self.settings['localArtworkSize'], extendedFormat)
if self.settings['tags']['savePlaylistAsCompilation'] \
and track.playlist \
and track.playlist['pic']['url'] \
and track.playlist.pic.url \
and not format.startswith("jpg"):
continue
result['albumURLs'].append({'url': url, 'ext': format})
@ -445,11 +418,11 @@ class DownloadJob:
if format in ["png","jpg"]:
extendedFormat = format
if extendedFormat == "jpg": extendedFormat += f"-{self.settings['jpegImageQuality']}"
url = generatePictureURL(track.album['mainArtist']['pic'], self.settings['localArtworkSize'], extendedFormat)
if track.album['mainArtist']['pic']['md5'] == "" and not format.startswith("jpg"): continue
url = track.album.mainArtist.pic.generatePictureURL(self.settings['localArtworkSize'], extendedFormat)
if track.album.mainArtist.pic.md5 == "" and not format.startswith("jpg"): continue
result['artistURLs'].append({'url': url, 'ext': format})
result['artistPath'] = artistPath
result['artistFilename'] = f"{settingsRegexArtist(self.settings['artistImageTemplate'], track.album['mainArtist'], self.settings, rootArtist=track.album['rootArtist'])}"
result['artistFilename'] = f"{settingsRegexArtist(self.settings['artistImageTemplate'], track.album.mainArtist, self.settings, rootArtist=track.album.rootArtist)}"
# Save playlist cover
if track.playlist:
@ -458,14 +431,12 @@ class DownloadJob:
if format in ["png","jpg"]:
extendedFormat = format
if extendedFormat == "jpg": extendedFormat += f"-{self.settings['jpegImageQuality']}"
url = generatePictureURL(track.playlist['pic'], self.settings['localArtworkSize'], extendedFormat)
if track.playlist['pic']['url'] and not format.startswith("jpg"): continue
url = track.playlist.pic.generatePictureURL(self.settings['localArtworkSize'], extendedFormat)
if track.playlist.pic.url and not format.startswith("jpg"): continue
self.playlistURLs.append({'url': url, 'ext': format})
if not self.playlistCoverName:
track.playlist['id'] = "pl_" + str(trackAPI_gw['_EXTRA_PLAYLIST']['id'])
track.playlist['genre'] = ["Compilation", ]
track.playlist['bitrate'] = selectedFormat
track.playlist['dateString'] = formatDate(track.playlist['date'], self.settings['dateFormat'])
track.playlist.bitrate = selectedFormat
track.playlist.dateString = track.playlist.date.format(self.settings['dateFormat'])
self.playlistCoverName = f"{settingsRegexAlbum(self.settings['coverImageTemplate'], track.playlist, self.settings, track.playlist)}"
# Remove subfolders from filename and add it to filepath
@ -510,7 +481,7 @@ class DownloadJob:
result['filename'] = str(writepath)[len(str(extrasPath))+ len(pathSep):]
if not trackAlreadyDownloaded or self.settings['overwriteFile'] == OverwriteOption.OVERWRITE:
logger.info(f"[{track.mainArtist['name']} - {track.title}] Downloading the track")
logger.info(f"[{track.mainArtist.name} - {track.title}] Downloading the track")
track.downloadUrl = generateStreamURL(track.id, track.MD5, track.mediaVersion, track.selectedFormat)
def downloadMusic(track, trackAPI_gw):
@ -523,16 +494,18 @@ class DownloadJob:
except (request_exception.HTTPError, DownloadEmpty):
if writepath.is_file(): writepath.unlink()
if track.fallbackId != "0":
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not available, using fallback id")
logger.warn(f"[{track.mainArtist.name} - {track.title}] Track not available, using fallback id")
newTrack = self.dz.gw.get_track_with_fallback(track.fallbackId)
track.parseEssentialData(self.dz, newTrack)
track.parseEssentialData(newTrack)
track.retriveFilesizes(self.dz)
return False
elif not track.searched and self.settings['fallbackSearch']:
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not available, searching for alternative")
searchedId = self.dz.api.get_track_id_from_metadata(track.mainArtist['name'], track.title, track.album['title'])
logger.warn(f"[{track.mainArtist.name} - {track.title}] Track not available, searching for alternative")
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)
track.parseEssentialData(self.dz, newTrack)
track.parseEssentialData(newTrack)
track.retriveFilesizes(self.dz)
track.searched = True
if self.interface:
self.interface.send('queueUpdate', {
@ -541,7 +514,7 @@ class DownloadJob:
'data': {
'id': track.id,
'title': track.title,
'artist': track.mainArtist['name']
'artist': track.mainArtist.name
},
})
return False
@ -551,7 +524,7 @@ class DownloadJob:
raise DownloadFailed("notAvailable")
except (request_exception.ConnectionError, request_exception.ChunkedEncodingError) as e:
if writepath.is_file(): writepath.unlink()
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Error while downloading the track, trying again in 5s...")
logger.warn(f"[{track.mainArtist.name} - {track.title}] Error while downloading the track, trying again in 5s...")
eventlet.sleep(5)
return downloadMusic(track, trackAPI_gw)
except OSError as e:
@ -559,11 +532,11 @@ class DownloadJob:
raise DownloadFailed("noSpaceLeft")
else:
if writepath.is_file(): writepath.unlink()
logger.exception(f"[{track.mainArtist['name']} - {track.title}] Error while downloading the track, you should report this to the developers: {str(e)}")
logger.exception(f"[{track.mainArtist.name} - {track.title}] Error while downloading the track, you should report this to the developers: {str(e)}")
raise e
except Exception as e:
if writepath.is_file(): writepath.unlink()
logger.exception(f"[{track.mainArtist['name']} - {track.title}] Error while downloading the track, you should report this to the developers: {str(e)}")
logger.exception(f"[{track.mainArtist.name} - {track.title}] Error while downloading the track, you should report this to the developers: {str(e)}")
raise e
return True
@ -574,12 +547,12 @@ class DownloadJob:
if not trackDownloaded: return self.download(trackAPI_gw, track)
else:
logger.info(f"[{track.mainArtist['name']} - {track.title}] Skipping track as it's already downloaded")
logger.info(f"[{track.mainArtist.name} - {track.title}] Skipping track as it's already downloaded")
self.completeTrackPercentage()
# Adding tags
if (not trackAlreadyDownloaded or self.settings['overwriteFile'] in [OverwriteOption.ONLY_TAGS, OverwriteOption.OVERWRITE]) and not track.localTrack:
logger.info(f"[{track.mainArtist['name']} - {track.title}] Applying tags to the track")
logger.info(f"[{track.mainArtist.name} - {track.title}] Applying tags to the track")
if track.selectedFormat in [TrackFormats.MP3_320, TrackFormats.MP3_128, TrackFormats.DEFAULT]:
tagID3(writepath, track, self.settings['tags'])
elif track.selectedFormat == TrackFormats.FLAC:
@ -587,14 +560,14 @@ class DownloadJob:
tagFLAC(writepath, track, self.settings['tags'])
except (FLACNoHeaderError, FLACError):
if writepath.is_file(): writepath.unlink()
logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not available in FLAC, falling back if necessary")
logger.warn(f"[{track.mainArtist.name} - {track.title}] Track not available in FLAC, falling back if necessary")
self.removeTrackPercentage()
track.filesizes['FILESIZE_FLAC'] = "0"
track.filesizes['FILESIZE_FLAC_TESTED'] = True
return self.download(trackAPI_gw, track)
if track.searched: result['searched'] = f"{track.mainArtist['name']} - {track.title}"
logger.info(f"[{track.mainArtist['name']} - {track.title}] Track download completed\n{str(writepath)}")
if track.searched: result['searched'] = f"{track.mainArtist.name} - {track.title}"
logger.info(f"[{track.mainArtist.name} - {track.title}] Track download completed\n{str(writepath)}")
self.queueItem.downloaded += 1
self.queueItem.files.append(str(writepath))
self.queueItem.extrasPath = str(self.extrasPath)
@ -649,7 +622,7 @@ class DownloadJob:
else:
if not falledBack:
falledBack = True
logger.info(f"[{track.mainArtist['name']} - {track.title}] Fallback to lower bitrate")
logger.info(f"[{track.mainArtist.name} - {track.title}] Fallback to lower bitrate")
if self.interface:
self.interface.send('queueUpdate', {
'uuid': self.queueItem.uuid,
@ -657,7 +630,7 @@ class DownloadJob:
'data': {
'id': track.id,
'title': track.title,
'artist': track.mainArtist['name']
'artist': track.mainArtist.name
},
})
if is360format: raise TrackNot360
@ -671,7 +644,7 @@ class DownloadJob:
chunkLength = start
percentage = 0
itemName = f"[{track.mainArtist['name']} - {track.title}]"
itemName = f"[{track.mainArtist.name} - {track.title}]"
try:
with self.dz.session.get(track.downloadUrl, headers=headers, stream=True, timeout=10) as request:

View File

@ -1,476 +0,0 @@
import eventlet
requests = eventlet.import_patched('requests')
import logging
from deezer.gw import APIError as gwAPIError, LyricsStatus
from deezer.api import APIError
from deemix.utils import removeFeatures, andCommaConcat, uniqueArray, generateReplayGainString
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('deemix')
VARIOUS_ARTISTS = 5080
class Track:
def __init__(self, dz, trackAPI_gw, trackAPI=None, albumAPI_gw=None, albumAPI=None):
self.parseEssentialData(dz, trackAPI_gw)
self.title = trackAPI_gw['SNG_TITLE'].strip()
if trackAPI_gw.get('VERSION') and not trackAPI_gw['VERSION'] in trackAPI_gw['SNG_TITLE']:
self.title += " " + trackAPI_gw['VERSION'].strip()
self.position = trackAPI_gw.get('POSITION')
self.localTrack = int(self.id) < 0
if self.localTrack:
self.parseLocalTrackData(trackAPI_gw)
else:
self.parseData(dz, trackAPI_gw, trackAPI, albumAPI_gw, albumAPI)
# Make sure there is at least one artist
if not 'Main' in self.artist:
self.artist['Main'] = [self.mainArtist['name']]
# Fix incorrect day month when detectable
if int(self.date['month']) > 12:
monthTemp = self.date['month']
self.date['month'] = self.date['day']
self.date['day'] = monthTemp
if int(self.album['date']['month']) > 12:
monthTemp = self.album['date']['month']
self.album['date']['month'] = self.album['date']['day']
self.album['date']['day'] = monthTemp
# Add playlist data if track is in a playlist
self.playlist = None
if "_EXTRA_PLAYLIST" in trackAPI_gw:
self.parsePlaylistData(trackAPI_gw["_EXTRA_PLAYLIST"])
self.singleDownload = trackAPI_gw.get('SINGLE_TRACK', False)
self.generateMainFeatStrings()
# Bits useful for later
self.searched = False
self.selectedFormat = 0
self.dateString = None
self.album['embeddedCoverURL'] = None
self.album['embeddedCoverPath'] = None
self.album['bitrate'] = 0
self.album['dateString'] = None
self.artistsString = ""
def parseEssentialData(self, dz, trackAPI_gw):
self.id = trackAPI_gw['SNG_ID']
self.duration = trackAPI_gw['DURATION']
self.MD5 = trackAPI_gw['MD5_ORIGIN']
self.mediaVersion = trackAPI_gw['MEDIA_VERSION']
self.fallbackId = "0"
if 'FALLBACK' in trackAPI_gw:
self.fallbackId = trackAPI_gw['FALLBACK']['SNG_ID']
if int(self.id) > 0:
self.filesizes = self.getFilesizes(dz)
def parseLocalTrackData(self, trackAPI_gw):
# Local tracks has only the trackAPI_gw page and
# contains only the tags provided by the file
self.album = {
'id': "0",
'title': trackAPI_gw['ALB_TITLE'],
'pic': {
'md5': trackAPI_gw.get('ALB_PICTURE', ""),
'type': "cover",
'url': None
}
}
self.mainArtist = {
'id': "0",
'name': trackAPI_gw['ART_NAME'],
'pic': {
'md5': "",
'type': "artist",
'url': None
}
}
self.artists = [trackAPI_gw['ART_NAME']]
self.artist = {
'Main': [trackAPI_gw['ART_NAME']]
}
self.date = {
'day': "00",
'month': "00",
'year': "XXXX"
}
# Defaulting all the missing data
self.ISRC = ""
self.album['artist'] = self.artist
self.album['artists'] = self.artists
self.album['barcode'] = "Unknown"
self.album['date'] = self.date
self.album['discTotal'] = "0"
self.album['explicit'] = False
self.album['genre'] = []
self.album['label'] = "Unknown"
self.album['mainArtist'] = self.mainArtist
self.album['mainArtist']['isVariousArtists'] = False
self.album['variousArtists'] = None
self.album['rootArtist'] = None
self.album['recordType'] = "album"
self.album['trackTotal'] = "0"
self.bpm = 0
self.contributors = {}
self.copyright = ""
self.discNumber = "0"
self.explicit = False
self.lyrics = {}
self.replayGain = ""
self.trackNumber = "0"
def parseData(self, dz, trackAPI_gw, trackAPI, albumAPI_gw, albumAPI):
self.discNumber = trackAPI_gw.get('DISK_NUMBER')
self.explicit = bool(int(trackAPI_gw.get('EXPLICIT_LYRICS', "0")))
self.copyright = trackAPI_gw.get('COPYRIGHT')
self.replayGain = ""
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.lyrics = {
'id': int(trackAPI_gw.get('LYRICS_ID', "0")),
'unsync': None,
'sync': None,
'syncID3': None
}
if not "LYRICS" in trackAPI_gw and self.lyrics['id'] != 0:
logger.info(f"[{trackAPI_gw['ART_NAME']} - {self.title}] Getting lyrics")
try:
trackAPI_gw["LYRICS"] = dz.gw.get_track_lyrics(self.id)
except gwAPIError:
self.lyrics['id'] = 0
if self.lyrics['id'] != 0:
self.lyrics['unsync'] = trackAPI_gw["LYRICS"].get("LYRICS_TEXT")
if "LYRICS_SYNC_JSON" in trackAPI_gw["LYRICS"]:
syncLyricsJson = trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"]
self.lyrics['sync'] = ""
self.lyrics['syncID3'] = []
timestamp = ""
milliseconds = 0
for line in range(len(syncLyricsJson)):
if syncLyricsJson[line]["line"] != "":
timestamp = syncLyricsJson[line]["lrc_timestamp"]
milliseconds = int(syncLyricsJson[line]["milliseconds"])
self.lyrics['syncID3'].append((syncLyricsJson[line]["line"], milliseconds))
else:
notEmptyLine = line + 1
while syncLyricsJson[notEmptyLine]["line"] == "":
notEmptyLine = notEmptyLine + 1
timestamp = syncLyricsJson[notEmptyLine]["lrc_timestamp"]
self.lyrics['sync'] += timestamp + syncLyricsJson[line]["line"] + "\r\n"
self.mainArtist = {
'id': trackAPI_gw['ART_ID'],
'name': trackAPI_gw['ART_NAME'],
'pic': {
'md5': trackAPI_gw.get('ART_PICTURE'),
'type': "artist",
'url': None
}
}
self.date = None
if 'PHYSICAL_RELEASE_DATE' in trackAPI_gw:
self.date = {
'day': trackAPI_gw["PHYSICAL_RELEASE_DATE"][8:10],
'month': trackAPI_gw["PHYSICAL_RELEASE_DATE"][5:7],
'year': trackAPI_gw["PHYSICAL_RELEASE_DATE"][0:4]
}
self.album = {
'id': trackAPI_gw['ALB_ID'],
'title': trackAPI_gw['ALB_TITLE'],
'pic': {
'md5': trackAPI_gw.get('ALB_PICTURE'),
'type': "cover",
'url': None
},
'barcode': "Unknown",
'label': "Unknown",
'explicit': False,
'date': None,
'genre': []
}
# Try the public API first (as it has more data)
if not albumAPI:
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting album infos")
try:
albumAPI = dz.api.get_album(self.album['id'])
except APIError:
albumAPI = None
self.album['variousArtists'] = None
if albumAPI:
self.album['title'] = albumAPI['title']
# Getting artist image ID
# ex: https://e-cdns-images.dzcdn.net/images/artist/f2bc007e9133c946ac3c3907ddc5d2ea/56x56-000000-80-0-0.jpg
artistPicture = albumAPI['artist']['picture_small']
artistPicture = artistPicture[artistPicture.find('artist/') + 7:-24]
self.album['mainArtist'] = {
'id': albumAPI['artist']['id'],
'name': albumAPI['artist']['name'],
'pic': {
'md5': artistPicture,
'type': "artist",
'url': None
}
}
self.album['rootArtist'] = albumAPI.get('root_artist', None)
self.album['artist'] = {'Main': []}
self.album['artists'] = []
for artist in albumAPI['contributors']:
isVariousArtists = artist['id'] == VARIOUS_ARTISTS
isMainArtist = artist['role'] == "Main"
if isVariousArtists:
self.album['variousArtists'] = artist
continue
if artist['name'] not in self.album['artists']:
self.album['artists'].append(artist['name'])
if isMainArtist or artist['name'] not in self.album['artist']['Main'] and not isMainArtist:
if not artist['role'] in self.album['artist']:
self.album['artist'][artist['role']] = []
self.album['artist'][artist['role']].append(artist['name'])
self.album['trackTotal'] = albumAPI['nb_tracks']
self.album['recordType'] = albumAPI['record_type']
self.album['barcode'] = albumAPI.get('upc', self.album['barcode'])
self.album['label'] = albumAPI.get('label', self.album['label'])
self.album['explicit'] = bool(albumAPI.get('explicit_lyrics', False))
if 'release_date' in albumAPI:
self.album['date'] = {
'day': albumAPI["release_date"][8:10],
'month': albumAPI["release_date"][5:7],
'year': albumAPI["release_date"][0:4]
}
self.album['discTotal'] = albumAPI.get('nb_disk', "1")
self.copyright = albumAPI.get('copyright')
if not self.album['pic']['md5']:
# Getting album cover MD5
# ex: https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/56x56-000000-80-0-0.jpg
self.album['pic']['md5'] = albumAPI['cover_small'][albumAPI['cover_small'].find('cover/') + 6:-24]
if albumAPI.get('genres') and len(albumAPI['genres'].get('data', [])) > 0:
for genre in albumAPI['genres']['data']:
self.album['genre'].append(genre['name'])
else:
if not albumAPI_gw:
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting more album infos")
try:
albumAPI_gw = dz.gw.get_album(self.album['id'])
except gwAPIError:
albumAPI_gw = None
raise AlbumDoesntExists
self.album['title'] = albumAPI_gw['ALB_TITLE']
self.album['mainArtist'] = {
'id': albumAPI_gw['ART_ID'],
'name': albumAPI_gw['ART_NAME'],
'pic': {
'md5': "",
'type': "artist",
'url': None
}
}
self.album['rootArtist'] = None
# 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
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting artist picture fallback")
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['artists'] = [albumAPI_gw['ART_NAME']]
self.album['trackTotal'] = albumAPI_gw['NUMBER_TRACK']
self.album['discTotal'] = albumAPI_gw['NUMBER_DISK']
self.album['recordType'] = "album"
self.album['label'] = albumAPI_gw.get('LABEL_NAME', self.album['label'])
explicitLyricsStatus = albumAPI_gw.get('EXPLICIT_ALBUM_CONTENT', {}).get('EXPLICIT_LYRICS_STATUS', LyricsStatus.UNKNOWN)
self.album['explicit'] = explicitLyricsStatus in [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT]
if not self.album['pic']['md5']:
self.album['pic']['md5'] = albumAPI_gw['ALB_PICTURE']
if 'PHYSICAL_RELEASE_DATE' in albumAPI_gw:
self.album['date'] = {
'day': albumAPI_gw["PHYSICAL_RELEASE_DATE"][8:10],
'month': albumAPI_gw["PHYSICAL_RELEASE_DATE"][5:7],
'year': albumAPI_gw["PHYSICAL_RELEASE_DATE"][0:4]
}
self.album['mainArtist']['isVariousArtists'] = self.album['mainArtist']['id'] == VARIOUS_ARTISTS
if self.album['date'] and not self.date:
self.date = self.album['date']
if not trackAPI:
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting extra track infos")
trackAPI = dz.api.get_track(self.id)
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.artist = {'Main': []}
self.artists = []
for artist in trackAPI['contributors']:
isVariousArtists = artist['id'] == VARIOUS_ARTISTS
isMainArtist = artist['role'] == "Main"
if len(trackAPI['contributors']) > 1 and isVariousArtists:
continue
if artist['name'] not in self.artists:
self.artists.append(artist['name'])
if isMainArtist or artist['name'] not in self.artist['Main'] and not isMainArtist:
if not artist['role'] in self.artist:
self.artist[artist['role']] = []
self.artist[artist['role']].append(artist['name'])
if not self.album['discTotal']:
if not albumAPI_gw:
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting more album infos")
albumAPI_gw = dz.gw.get_album(self.album['id'])
self.album['discTotal'] = albumAPI_gw['NUMBER_DISK']
if not self.copyright:
if not albumAPI_gw:
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting more album infos")
albumAPI_gw = dz.gw.get_album(self.album['id'])
self.copyright = albumAPI_gw['COPYRIGHT']
def parsePlaylistData(self, playlist):
self.playlist = {}
playlist['various_artist']['role'] = "Main"
if 'dzcdn.net' in playlist['picture_small']:
url = playlist['picture_small']
picType = url[url.find('images/')+7:]
picType = picType[:picType.find('/')]
self.playlist['pic'] = {
'md5': url[url.find(picType+'/') + len(picType)+1:-24],
'type': picType,
'url': None
}
else:
self.playlist['pic'] = {
'md5': None,
'type': None,
'url': playlist['picture_xl']
}
self.playlist['title'] = playlist['title']
self.playlist['mainArtist'] = {
'id': playlist['various_artist']['id'],
'name': playlist['various_artist']['name'],
'isVariousArtists': True,
'pic': {
'md5': playlist['various_artist']['picture_small'][
playlist['various_artist']['picture_small'].find('artist/') + 7:-24],
'type': "artist",
'url': None
}
}
self.playlist['rootArtist'] = None
self.playlist['artist'] = {"Main": []}
self.playlist['artists'] = []
self.playlist['variousArtists'] = playlist['various_artist']
self.playlist['trackTotal'] = playlist['nb_tracks']
self.playlist['recordType'] = "compile"
self.playlist['barcode'] = ""
self.playlist['label'] = ""
self.playlist['explicit'] = playlist['explicit']
self.playlist['date'] = {
'day': playlist["creation_date"][8:10],
'month': playlist["creation_date"][5:7],
'year': playlist["creation_date"][0:4]
}
self.playlist['discTotal'] = "1"
self.playlist['playlistId'] = playlist['id']
self.playlist['owner'] = playlist['creator']
def removeDuplicateArtists(self):
self.artists = uniqueArray(self.artists)
for role in self.artist.keys():
self.artist[role] = uniqueArray(self.artist[role])
self.album['artists'] = uniqueArray(self.album['artists'])
for role in self.album['artist'].keys():
self.album['artist'][role] = uniqueArray(self.album['artist'][role])
# Removes featuring from the title
def getCleanTitle(self):
return removeFeatures(self.title)
# Removes featuring from the album name
def getCleanAlbumTitle(self):
return removeFeatures(self.album['title'])
def getFeatTitle(self):
if self.featArtistsString and not "(feat." in self.title.lower():
return self.title + " ({})".format(self.featArtistsString)
return self.title
def generateMainFeatStrings(self):
self.mainArtistsString = andCommaConcat(self.artist['Main'])
self.featArtistsString = ""
if 'Featured' in self.artist:
self.featArtistsString = "feat. "+andCommaConcat(self.artist['Featured'])
def getFilesizes(self, dz):
try:
guest_sid = dz.session.cookies.get('sid')
site = requests.post(
"https://api.deezer.com/1.0/gateway.php",
params={
'api_key': "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE",
'sid': guest_sid,
'input': '3',
'output': '3',
'method': 'song_getData'
},
timeout=30,
json={'sng_id': self.id},
headers=dz.http_headers
)
result_json = site.json()
except:
eventlet.sleep(2)
return self.getFilesizes(dz)
if len(result_json['error']):
raise APIError(json.dumps(result_json['error']))
response = result_json.get("results")
filesizes = {}
for key, value in response.items():
if key.startswith("FILESIZE_"):
filesizes[key] = value
filesizes[key+"_TESTED"] = False
return filesizes
class TrackError(Exception):
"""Base class for exceptions in this module."""
pass
class AlbumDoesntExists(TrackError):
pass