Merge branch 'refactoring' into 'main'

Refactoring

See merge request RemixDev/deemix!4
This commit is contained in:
RemixDev 2021-01-31 19:59:16 +03:00
commit e9434ceeb6
14 changed files with 732 additions and 673 deletions

View file

@ -3,3 +3,4 @@
__version__ = "2.0.11" __version__ = "2.0.11"
USER_AGENT_HEADER = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " \ USER_AGENT_HEADER = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " \
"Chrome/79.0.3945.130 Safari/537.36" "Chrome/79.0.3945.130 Safari/537.36"
VARIOUS_ARTISTS = "5080"

View file

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

140
deemix/types/Album.py Normal file
View file

@ -0,0 +1,140 @@
from deezer.gw import LyricsStatus
from deemix.utils import removeDuplicateArtists
from deemix.types.Artist import Artist
from deemix.types.Date import Date
from deemix.types.Picture import Picture
from deemix import VARIOUS_ARTISTS
class Album:
def __init__(self, id="0", title="", pic_md5=""):
self.id = id
self.title = title
self.pic = Picture(md5=pic_md5, type="cover")
self.artist = {"Main": []}
self.artists = []
self.mainArtist = None
self.dateString = None
self.barcode = "Unknown"
self.date = None
self.discTotal = "0"
self.embeddedCoverPath = None
self.embeddedCoverURL = None
self.explicit = False
self.genre = []
self.label = "Unknown"
self.recordType = "album"
self.rootArtist = None
self.trackTotal = "0"
self.bitrate = 0
self.variousArtists = None
def parseAlbum(self, albumAPI):
self.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.mainArtist = Artist(
id = albumAPI['artist']['id'],
name = albumAPI['artist']['name'],
pic_md5 = artistPicture
)
if albumAPI.get('root_artist'):
self.rootArtist = Artist(
id = albumAPI['root_artist']['id'],
name = albumAPI['root_artist']['name']
)
for artist in albumAPI['contributors']:
isVariousArtists = str(artist['id']) == VARIOUS_ARTISTS
isMainArtist = artist['role'] == "Main"
if isVariousArtists:
self.variousArtists = Artist(
id = artist['id'],
name = artist['name'],
role = artist['role']
)
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'])
self.trackTotal = albumAPI['nb_tracks']
self.recordType = albumAPI['record_type']
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:
day = albumAPI["release_date"][8:10]
month = albumAPI["release_date"][5:7]
year = albumAPI["release_date"][0:4]
self.date = Date(year, month, day)
self.discTotal = albumAPI.get('nb_disk')
self.copyright = albumAPI.get('copyright')
if not self.pic.md5:
# Getting album cover MD5
# ex: https://e-cdns-images.dzcdn.net/images/cover/2e018122cb56986277102d2041a592c8/56x56-000000-80-0-0.jpg
self.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.genre.append(genre['name'])
def parseAlbumGW(self, albumAPI_gw):
self.title = albumAPI_gw['ALB_TITLE']
self.mainArtist = Artist(
id = albumAPI_gw['ART_ID'],
name = albumAPI_gw['ART_NAME']
)
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]
if not self.pic.md5:
self.pic.md5 = albumAPI_gw['ALB_PICTURE']
if 'PHYSICAL_RELEASE_DATE' in albumAPI_gw:
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.date = Date(year, month, day)
def makePlaylistCompilation(self, playlist):
self.variousArtists = playlist.variousArtists
self.mainArtist = playlist.mainArtist
self.title = playlist.title
self.rootArtist = playlist.rootArtist
self.artist = playlist.artist
self.artists = playlist.artists
self.trackTotal = playlist.trackTotal
self.recordType = playlist.recordType
self.barcode = playlist.barcode
self.label = playlist.label
self.explicit = playlist.explicit
self.date = playlist.date
self.discTotal = playlist.discTotal
self.playlistId = playlist.playlistId
self.owner = playlist.owner
self.pic = playlist.pic
def removeDuplicateArtists(self):
(self.artist, self.artists) = removeDuplicateArtists(self.artist, self.artists)
# Removes featuring from the album name
def getCleanTitle(self):
return removeFeatures(self.title)

13
deemix/types/Artist.py Normal file
View file

@ -0,0 +1,13 @@
from deemix.types.Picture import Picture
from deemix import VARIOUS_ARTISTS
class Artist:
def __init__(self, id="0", name="", pic_md5="", role=""):
self.id = str(id)
self.name = name
self.pic = Picture(md5=pic_md5, type="artist")
self.role = ""
self.save = True
def isVariousArtists(self):
return self.id == VARIOUS_ARTISTS

25
deemix/types/Date.py Normal file
View file

@ -0,0 +1,25 @@
class Date(object):
def __init__(self, year="XXXX", month="00", day="00"):
self.year = year
self.month = month
self.day = day
self.fixDayMonth()
# Fix incorrect day month when detectable
def fixDayMonth(self):
if int(self.month) > 12:
monthTemp = self.month
self.month = self.day
self.day = monthTemp
def format(self, 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(getattr(self, element)))
return template

26
deemix/types/Lyrics.py Normal file
View file

@ -0,0 +1,26 @@
class Lyrics:
def __init__(self, id="0"):
self.id = id
self.sync = None
self.unsync = None
self.syncID3 = None
def parseLyrics(self, lyricsAPI):
self.unsync = lyricsAPI.get("LYRICS_TEXT")
if "LYRICS_SYNC_JSON" in lyricsAPI:
syncLyricsJson = lyricsAPI["LYRICS_SYNC_JSON"]
self.sync = ""
self.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.syncID3.append((syncLyricsJson[line]["line"], milliseconds))
else:
notEmptyLine = line + 1
while syncLyricsJson[notEmptyLine]["line"] == "":
notEmptyLine = notEmptyLine + 1
timestamp = syncLyricsJson[notEmptyLine]["lrc_timestamp"]
self.sync += timestamp + syncLyricsJson[line]["line"] + "\r\n"

27
deemix/types/Picture.py Normal file
View file

@ -0,0 +1,27 @@
class Picture:
def __init__(self, md5="", type=None, url=None):
self.md5 = md5
self.type = type
self.url = url
def generatePictureURL(self, size, format):
if self.url: return self.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(
self.type,
self.md5,
size, size,
f'000000-{quality}-0-0.jpg'
)
if format == 'png':
return "https://e-cdns-images.dzcdn.net/images/{}/{}/{}x{}-{}".format(
self.type,
self.md5,
size, size,
'none-100-0-0.png'
)

48
deemix/types/Playlist.py Normal file
View file

@ -0,0 +1,48 @@
from deemix.types.Artist import Artist
from deemix.types.Date import Date
from deemix.types.Picture import Picture
class Playlist:
def __init__(self, playlistAPI):
if 'various_artist' in playlistAPI:
playlistAPI['various_artist']['role'] = "Main"
self.variousArtists = Artist(
id = playlistAPI['various_artist']['id'],
name = playlistAPI['various_artist']['name'],
pic_md5 = playlistAPI['various_artist']['picture_small'][
playlistAPI['various_artist']['picture_small'].find('artist/') + 7:-24],
role = playlistAPI['various_artist']['role']
)
self.mainArtist = self.variousArtists
self.id = "pl_" + str(playlistAPI['id'])
self.title = playlistAPI['title']
self.rootArtist = None
self.artist = {"Main": []}
self.artists = []
self.trackTotal = playlistAPI['nb_tracks']
self.recordType = "compile"
self.barcode = ""
self.label = ""
self.explicit = playlistAPI['explicit']
self.genre = ["Compilation", ]
year = playlistAPI["creation_date"][0:4]
month = playlistAPI["creation_date"][5:7]
day = playlistAPI["creation_date"][8:10]
self.date = Date(year, month, day)
self.discTotal = "1"
self.playlistId = playlistAPI['id']
self.owner = playlistAPI['creator']
if 'dzcdn.net' in playlistAPI['picture_small']:
url = playlistAPI['picture_small']
picType = url[url.find('images/')+7:]
picType = picType[:picType.find('/')]
md5 = url[url.find(picType+'/') + len(picType)+1:-24]
self.pic = Picture(
md5 = md5,
type = picType
)
else:
self.pic = Picture(url = playlistAPI['picture_xl'])

269
deemix/types/Track.py Normal file
View file

@ -0,0 +1,269 @@
import eventlet
requests = eventlet.import_patched('requests')
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('deemix')
from deezer.gw import APIError as gwAPIError
from deezer.api import APIError
from deemix.utils import removeFeatures, andCommaConcat, removeDuplicateArtists, generateReplayGainString
from deemix.types.Album import Album
from deemix.types.Artist import Artist
from deemix.types.Date import Date
from deemix.types.Picture import Picture
from deemix.types.Playlist import Playlist
from deemix.types.Lyrics import Lyrics
from deemix import VARIOUS_ARTISTS
class Track:
def __init__(self, id="0", name=""):
self.id = id
self.title = name
self.MD5 = ""
self.mediaVersion = ""
self.duration = 0
self.fallbackId = "0"
self.filesizes = {}
self.localTrack = False
self.mainArtist = None
self.artist = {"Main": []}
self.artists = []
self.album = None
self.trackNumber = "0"
self.discNumber = "0"
self.date = None
self.lyrics = None
self.bpm = 0
self.contributors = {}
self.copyright = ""
self.explicit = False
self.ISRC = ""
self.replayGain = ""
self.playlist = None
self.position = None
self.searched = False
self.selectedFormat = 0
self.singleDownload = False
self.dateString = None
self.artistsString = ""
self.mainArtistsString = ""
self.featArtistsString = ""
def parseEssentialData(self, trackAPI_gw, trackAPI=None):
self.id = str(trackAPI_gw['SNG_ID'])
self.duration = trackAPI_gw['DURATION']
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']
self.fallbackId = "0"
if 'FALLBACK' in trackAPI_gw:
self.fallbackId = trackAPI_gw['FALLBACK']['SNG_ID']
self.localTrack = int(self.id) < 0
def retriveFilesizes(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.retriveFilesizes(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
self.filesizes = filesizes
def parseData(self, dz, id=None, trackAPI_gw=None, trackAPI=None, albumAPI_gw=None, albumAPI=None, playlistAPI=None):
if id:
if not trackAPI_gw: trackAPI_gw = dz.gw.get_track_with_fallback(id)
elif not trackAPI_gw: raise NoDataToParse
if not trackAPI:
try: trackAPI = dz.api.get_track(trackAPI_gw['SNG_ID'])
except APIError: trackAPI = None
self.parseEssentialData(trackAPI_gw, trackAPI)
if self.localTrack:
self.parseLocalTrackData(trackAPI_gw)
else:
self.retriveFilesizes(dz)
self.parseTrackGW(trackAPI_gw)
# 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)
except gwAPIError: self.lyrics.id = "0"
if self.lyrics.id != "0": self.lyrics.parseLyrics(trackAPI_gw["LYRICS"])
# Parse Album data
self.album = Album(
id = trackAPI_gw['ALB_ID'],
title = trackAPI_gw['ALB_TITLE'],
pic_md5 = trackAPI_gw.get('ALB_PICTURE')
)
# Get album Data
if not albumAPI:
try: albumAPI = dz.api.get_album(self.album.id)
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
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
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 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']
self.parseTrack(trackAPI)
# Make sure there is at least one artist
if not len(self.artist['Main']):
self.artist['Main'] = [self.mainArtist['name']]
self.singleDownload = trackAPI_gw.get('SINGLE_TRACK', False)
self.position = trackAPI_gw.get('POSITION')
# Add playlist data if track is in a playlist
if playlistAPI: self.playlist = Playlist(playlistAPI)
self.generateMainFeatStrings()
return self
def parseLocalTrackData(self, trackAPI_gw):
# 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.album.pic = Picture(
md5 = trackAPI_gw.get('ALB_PICTURE', ""),
type = "cover"
)
self.mainArtist = Artist(name=trackAPI_gw['ART_NAME'])
self.artists = [trackAPI_gw['ART_NAME']]
self.artist = {
'Main': [trackAPI_gw['ART_NAME']]
}
self.album.artist = self.artist
self.album.artists = self.artists
self.album.date = self.date
self.album.mainArtist = self.mainArtist
self.date = Date()
def parseTrackGW(self, 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.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.lyrics = Lyrics(trackAPI_gw.get('LYRICS_ID', "0"))
self.mainArtist = Artist(
id = trackAPI_gw['ART_ID'],
name = trackAPI_gw['ART_NAME'],
pic_md5 = trackAPI_gw.get('ART_PICTURE')
)
if 'PHYSICAL_RELEASE_DATE' in trackAPI_gw:
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.date = Date(year, month, day)
def parseTrack(self, trackAPI):
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']
for artist in trackAPI['contributors']:
isVariousArtists = str(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'])
def removeDuplicateArtists(self):
(self.artist, self.artists) = removeDuplicateArtists(self.artist, self.artists)
# Removes featuring from the title
def getCleanTitle(self):
return removeFeatures(self.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'])
class TrackError(Exception):
"""Base class for exceptions in this module."""
pass
class AlbumDoesntExists(TrackError):
pass
class MD5NotFound(TrackError):
pass
class NoDataToParse(TrackError):
pass

7
deemix/types/__init__.py Normal file
View file

@ -0,0 +1,7 @@
from deemix.types.Date import Date
from deemix.types.Picture import Picture
from deemix.types.Lyrics import Lyrics
from deemix.types.Album import Album
from deemix.types.Artist import Artist
from deemix.types.Playlist import Playlist
from deemix.types.Track import Track

View file

@ -122,6 +122,12 @@ def uniqueArray(arr):
del arr[iRest] del arr[iRest]
return arr return arr
def removeDuplicateArtists(artist, artists):
artists = uniqueArray(artists)
for role in artist.keys():
artist[role] = uniqueArray(artist[role])
return (artist, artists)
def checkFolder(folder): def checkFolder(folder):
try: try:
os.makedirs(folder, exist_ok=True) os.makedirs(folder, exist_ok=True)

View file

@ -85,7 +85,7 @@ def generateFilepath(track, settings):
(settings['createArtistFolder'] and track.playlist and settings['tags']['savePlaylistAsCompilation']) or (settings['createArtistFolder'] and track.playlist and settings['tags']['savePlaylistAsCompilation']) or
(settings['createArtistFolder'] and track.playlist and settings['createStructurePlaylist']) (settings['createArtistFolder'] and track.playlist and settings['createStructurePlaylist'])
): ):
filepath = filepath / settingsRegexArtist(settings['artistNameTemplate'], track.album['mainArtist'], settings, rootArtist=track.album['rootArtist']) filepath = filepath / settingsRegexArtist(settings['artistNameTemplate'], track.album.mainArtist, settings, rootArtist=track.album.rootArtist)
artistPath = filepath artistPath = filepath
if (settings['createAlbumFolder'] and if (settings['createAlbumFolder'] and
@ -102,7 +102,7 @@ def generateFilepath(track, settings):
extrasPath = filepath extrasPath = filepath
if ( if (
int(track.album['discTotal']) > 1 and ( int(track.album.discTotal) > 1 and (
(settings['createAlbumFolder'] and settings['createCDFolder']) and (settings['createAlbumFolder'] and settings['createCDFolder']) and
(not track.singleDownload or (track.singleDownload and settings['createSingleFolder'])) and (not track.singleDownload or (track.singleDownload and settings['createSingleFolder'])) and
(not track.playlist or (not track.playlist or
@ -117,7 +117,7 @@ def generateFilepath(track, settings):
def settingsRegex(filename, track, settings): def settingsRegex(filename, track, settings):
filename = filename.replace("%title%", fixName(track.title, settings['illegalCharacterReplacer'])) filename = filename.replace("%title%", fixName(track.title, settings['illegalCharacterReplacer']))
filename = filename.replace("%artist%", fixName(track.mainArtist['name'], settings['illegalCharacterReplacer'])) filename = filename.replace("%artist%", fixName(track.mainArtist.name, settings['illegalCharacterReplacer']))
filename = filename.replace("%artists%", fixName(", ".join(track.artists), settings['illegalCharacterReplacer'])) filename = filename.replace("%artists%", fixName(", ".join(track.artists), settings['illegalCharacterReplacer']))
filename = filename.replace("%allartists%", fixName(track.artistsString, settings['illegalCharacterReplacer'])) filename = filename.replace("%allartists%", fixName(track.artistsString, settings['illegalCharacterReplacer']))
filename = filename.replace("%mainartists%", fixName(track.mainArtistsString, settings['illegalCharacterReplacer'])) filename = filename.replace("%mainartists%", fixName(track.mainArtistsString, settings['illegalCharacterReplacer']))
@ -125,92 +125,92 @@ def settingsRegex(filename, track, settings):
filename = filename.replace("%featartists%", fixName('('+track.featArtistsString+')', settings['illegalCharacterReplacer'])) filename = filename.replace("%featartists%", fixName('('+track.featArtistsString+')', settings['illegalCharacterReplacer']))
else: else:
filename = filename.replace("%featartists%", '') filename = filename.replace("%featartists%", '')
filename = filename.replace("%album%", fixName(track.album['title'], settings['illegalCharacterReplacer'])) filename = filename.replace("%album%", fixName(track.album.title, settings['illegalCharacterReplacer']))
filename = filename.replace("%albumartist%", fixName(track.album['mainArtist']['name'], settings['illegalCharacterReplacer'])) filename = filename.replace("%albumartist%", fixName(track.album.mainArtist.name, settings['illegalCharacterReplacer']))
filename = filename.replace("%tracknumber%", pad(track.trackNumber, track.album['trackTotal'], settings)) filename = filename.replace("%tracknumber%", pad(track.trackNumber, track.album.trackTotal, settings))
filename = filename.replace("%tracktotal%", str(track.album['trackTotal'])) filename = filename.replace("%tracktotal%", str(track.album.trackTotal))
filename = filename.replace("%discnumber%", str(track.discNumber)) filename = filename.replace("%discnumber%", str(track.discNumber))
filename = filename.replace("%disctotal%", str(track.album['discTotal'])) filename = filename.replace("%disctotal%", str(track.album.discTotal))
if len(track.album['genre']) > 0: if len(track.album.genre) > 0:
filename = filename.replace("%genre%", filename = filename.replace("%genre%",
fixName(track.album['genre'][0], settings['illegalCharacterReplacer'])) fixName(track.album.genre[0], settings['illegalCharacterReplacer']))
else: else:
filename = filename.replace("%genre%", "Unknown") filename = filename.replace("%genre%", "Unknown")
filename = filename.replace("%year%", str(track.date['year'])) filename = filename.replace("%year%", str(track.date.year))
filename = filename.replace("%date%", track.dateString) filename = filename.replace("%date%", track.dateString)
filename = filename.replace("%bpm%", str(track.bpm)) filename = filename.replace("%bpm%", str(track.bpm))
filename = filename.replace("%label%", fixName(track.album['label'], settings['illegalCharacterReplacer'])) filename = filename.replace("%label%", fixName(track.album.label, settings['illegalCharacterReplacer']))
filename = filename.replace("%isrc%", track.ISRC) filename = filename.replace("%isrc%", track.ISRC)
filename = filename.replace("%upc%", track.album['barcode']) filename = filename.replace("%upc%", track.album.barcode)
filename = filename.replace("%explicit%", "(Explicit)" if track.explicit else "") filename = filename.replace("%explicit%", "(Explicit)" if track.explicit else "")
filename = filename.replace("%track_id%", str(track.id)) filename = filename.replace("%track_id%", str(track.id))
filename = filename.replace("%album_id%", str(track.album['id'])) filename = filename.replace("%album_id%", str(track.album.id))
filename = filename.replace("%artist_id%", str(track.mainArtist['id'])) filename = filename.replace("%artist_id%", str(track.mainArtist.id))
if track.playlist: if track.playlist:
filename = filename.replace("%playlist_id%", str(track.playlist['playlistId'])) filename = filename.replace("%playlist_id%", str(track.playlist.playlistId))
filename = filename.replace("%position%", pad(track.position, track.playlist['trackTotal'], settings)) filename = filename.replace("%position%", pad(track.position, track.playlist.trackTotal, settings))
else: else:
filename = filename.replace("%playlist_id%", '') filename = filename.replace("%playlist_id%", '')
filename = filename.replace("%position%", pad(track.trackNumber, track.album['trackTotal'], settings)) filename = filename.replace("%position%", pad(track.trackNumber, track.album.trackTotal, settings))
filename = filename.replace('\\', pathSep).replace('/', pathSep) filename = filename.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(filename)) return antiDot(fixLongName(filename))
def settingsRegexAlbum(foldername, album, settings, playlist=None): def settingsRegexAlbum(foldername, album, settings, playlist=None):
if playlist and settings['tags']['savePlaylistAsCompilation']: if playlist and settings['tags']['savePlaylistAsCompilation']:
foldername = foldername.replace("%album_id%", "pl_" + str(playlist['playlistId'])) foldername = foldername.replace("%album_id%", "pl_" + str(playlist.playlistId))
foldername = foldername.replace("%genre%", "Compile") foldername = foldername.replace("%genre%", "Compile")
else: else:
foldername = foldername.replace("%album_id%", str(album['id'])) foldername = foldername.replace("%album_id%", str(album.id))
if len(album['genre']) > 0: if len(album.genre) > 0:
foldername = foldername.replace("%genre%", fixName(album['genre'][0], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%genre%", fixName(album.genre[0], settings['illegalCharacterReplacer']))
else: else:
foldername = foldername.replace("%genre%", "Unknown") foldername = foldername.replace("%genre%", "Unknown")
foldername = foldername.replace("%album%", fixName(album['title'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%album%", fixName(album.title, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%artist%", fixName(album['mainArtist']['name'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%artist%", fixName(album.mainArtist.name, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%artist_id%", str(album['mainArtist']['id'])) foldername = foldername.replace("%artist_id%", str(album.mainArtist.id))
if album['rootArtist']: if album.rootArtist:
foldername = foldername.replace("%root_artist%", fixName(album['rootArtist']['name'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%root_artist%", fixName(album.rootArtist.name, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%root_artist_id%", str(album['rootArtist']['id'])) foldername = foldername.replace("%root_artist_id%", str(album.rootArtist.id))
else: else:
foldername = foldername.replace("%root_artist%", fixName(album['mainArtist']['name'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%root_artist%", fixName(album.mainArtist.name, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%root_artist_id%", str(album['mainArtist']['id'])) foldername = foldername.replace("%root_artist_id%", str(album.mainArtist.id))
foldername = foldername.replace("%tracktotal%", str(album['trackTotal'])) foldername = foldername.replace("%tracktotal%", str(album.trackTotal))
foldername = foldername.replace("%disctotal%", str(album['discTotal'])) foldername = foldername.replace("%disctotal%", str(album.discTotal))
foldername = foldername.replace("%type%", fixName(album['recordType'].capitalize(), settings['illegalCharacterReplacer'])) foldername = foldername.replace("%type%", fixName(album.recordType.capitalize(), settings['illegalCharacterReplacer']))
foldername = foldername.replace("%upc%", album['barcode']) foldername = foldername.replace("%upc%", album.barcode)
foldername = foldername.replace("%explicit%", "(Explicit)" if album['explicit'] else "") foldername = foldername.replace("%explicit%", "(Explicit)" if album.explicit else "")
foldername = foldername.replace("%label%", fixName(album['label'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%label%", fixName(album.label, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%year%", str(album['date']['year'])) foldername = foldername.replace("%year%", str(album.date.year))
foldername = foldername.replace("%date%", album['dateString']) foldername = foldername.replace("%date%", album.dateString)
foldername = foldername.replace("%bitrate%", bitrateLabels[int(album['bitrate'])]) foldername = foldername.replace("%bitrate%", bitrateLabels[int(album.bitrate)])
foldername = foldername.replace('\\', pathSep).replace('/', pathSep) foldername = foldername.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(foldername)) return antiDot(fixLongName(foldername))
def settingsRegexArtist(foldername, artist, settings, rootArtist=None): def settingsRegexArtist(foldername, artist, settings, rootArtist=None):
foldername = foldername.replace("%artist%", fixName(artist['name'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%artist%", fixName(artist.name, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%artist_id%", str(artist['id'])) foldername = foldername.replace("%artist_id%", str(artist.id))
if rootArtist: if rootArtist:
foldername = foldername.replace("%root_artist%", fixName(rootArtist['name'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%root_artist%", fixName(rootArtist.name, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%root_artist_id%", str(rootArtist['id'])) foldername = foldername.replace("%root_artist_id%", str(rootArtist.id))
else: else:
foldername = foldername.replace("%root_artist%", fixName(artist['name'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%root_artist%", fixName(artist.name, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%root_artist_id%", str(artist['id'])) foldername = foldername.replace("%root_artist_id%", str(artist.id))
foldername = foldername.replace('\\', pathSep).replace('/', pathSep) foldername = foldername.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(foldername)) return antiDot(fixLongName(foldername))
def settingsRegexPlaylist(foldername, playlist, settings): def settingsRegexPlaylist(foldername, playlist, settings):
foldername = foldername.replace("%playlist%", fixName(playlist['title'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%playlist%", fixName(playlist.title, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%playlist_id%", fixName(playlist['playlistId'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%playlist_id%", fixName(playlist.playlistId, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%owner%", fixName(playlist['owner']['name'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%owner%", fixName(playlist.owner['name'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%owner_id%", str(playlist['owner']['id'])) foldername = foldername.replace("%owner_id%", str(playlist.owner['id']))
foldername = foldername.replace("%year%", str(playlist['date']['year'])) foldername = foldername.replace("%year%", str(playlist.date.year))
foldername = foldername.replace("%date%", str(playlist['dateString'])) foldername = foldername.replace("%date%", str(playlist.dateString))
foldername = foldername.replace("%explicit%", "(Explicit)" if playlist['explicit'] else "") foldername = foldername.replace("%explicit%", "(Explicit)" if playlist.explicit else "")
foldername = foldername.replace('\\', pathSep).replace('/', pathSep) foldername = foldername.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(foldername)) return antiDot(fixLongName(foldername))

View file

@ -20,7 +20,7 @@ def tagID3(stream, track, save):
tag.add(TPE1(text=track.artists)) tag.add(TPE1(text=track.artists))
else: else:
if save['multiArtistSeparator'] == "nothing": if save['multiArtistSeparator'] == "nothing":
tag.add(TPE1(text=track.mainArtist['name'])) tag.add(TPE1(text=track.mainArtist.name))
else: else:
tag.add(TPE1(text=track.artistsString)) tag.add(TPE1(text=track.artistsString))
# Tag ARTISTS is added to keep the multiartist support when using a non standard tagging method # Tag ARTISTS is added to keep the multiartist support when using a non standard tagging method
@ -28,56 +28,56 @@ def tagID3(stream, track, save):
tag.add(TXXX(desc="ARTISTS", text=track.artists)) tag.add(TXXX(desc="ARTISTS", text=track.artists))
if save['album']: if save['album']:
tag.add(TALB(text=track.album['title'])) tag.add(TALB(text=track.album.title))
if save['albumArtist'] and len(track.album['artists']): if save['albumArtist'] and len(track.album.artists):
if save['singleAlbumArtist'] and track.album['mainArtist']['save']: if save['singleAlbumArtist'] and track.album.mainArtist.save:
tag.add(TPE2(text=track.album['mainArtist']['name'])) tag.add(TPE2(text=track.album.mainArtist.name))
else: else:
tag.add(TPE2(text=track.album['artists'])) tag.add(TPE2(text=track.album.artists))
if save['trackNumber']: if save['trackNumber']:
trackNumber = str(track.trackNumber) trackNumber = str(track.trackNumber)
if save['trackTotal']: if save['trackTotal']:
trackNumber += "/" + str(track.album['trackTotal']) trackNumber += "/" + str(track.album.trackTotal)
tag.add(TRCK(text=trackNumber)) tag.add(TRCK(text=trackNumber))
if save['discNumber']: if save['discNumber']:
discNumber = str(track.discNumber) discNumber = str(track.discNumber)
if save['discTotal']: if save['discTotal']:
discNumber += "/" + str(track.album['discTotal']) discNumber += "/" + str(track.album.discTotal)
tag.add(TPOS(text=discNumber)) tag.add(TPOS(text=discNumber))
if save['genre']: if save['genre']:
tag.add(TCON(text=track.album['genre'])) tag.add(TCON(text=track.album.genre))
if save['year']: if save['year']:
tag.add(TYER(text=str(track.date['year']))) tag.add(TYER(text=str(track.date.year)))
if save['date']: if save['date']:
# Referencing ID3 standard # Referencing ID3 standard
# https://id3.org/id3v2.3.0#TDAT # https://id3.org/id3v2.3.0#TDAT
# The 'Date' frame is a numeric string in the DDMM format. # The 'Date' frame is a numeric string in the DDMM format.
tag.add(TDAT(text=str(track.date['day']) + str(track.date['month']))) tag.add(TDAT(text=str(track.date.day) + str(track.date.month)))
if save['length']: if save['length']:
tag.add(TLEN(text=str(int(track.duration)*1000))) tag.add(TLEN(text=str(int(track.duration)*1000)))
if save['bpm']: if save['bpm']:
tag.add(TBPM(text=str(track.bpm))) tag.add(TBPM(text=str(track.bpm)))
if save['label']: if save['label']:
tag.add(TPUB(text=track.album['label'])) tag.add(TPUB(text=track.album.label))
if save['isrc']: if save['isrc']:
tag.add(TSRC(text=track.ISRC)) tag.add(TSRC(text=track.ISRC))
if save['barcode']: if save['barcode']:
tag.add(TXXX(desc="BARCODE", text=track.album['barcode'])) tag.add(TXXX(desc="BARCODE", text=track.album.barcode))
if save['explicit']: if save['explicit']:
tag.add(TXXX(desc="ITUNESADVISORY", text= "1" if track.explicit else "0" )) tag.add(TXXX(desc="ITUNESADVISORY", text= "1" if track.explicit else "0" ))
if save['replayGain']: if save['replayGain']:
tag.add(TXXX(desc="REPLAYGAIN_TRACK_GAIN", text=track.replayGain)) tag.add(TXXX(desc="REPLAYGAIN_TRACK_GAIN", text=track.replayGain))
if track.lyrics['unsync'] and save['lyrics']: if track.lyrics.unsync and save['lyrics']:
tag.add(USLT(text=track.lyrics['unsync'])) tag.add(USLT(text=track.lyrics.unsync))
if track.lyrics['syncID3'] and save['syncedLyrics']: if track.lyrics.syncID3 and save['syncedLyrics']:
# Referencing ID3 standard # Referencing ID3 standard
# https://id3.org/id3v2.3.0#sec4.10 # https://id3.org/id3v2.3.0#sec4.10
# Type: 1 => is lyrics # Type: 1 => is lyrics
# Format: 2 => Absolute time, 32 bit sized, using milliseconds as unit # Format: 2 => Absolute time, 32 bit sized, using milliseconds as unit
tag.add(SYLT(Encoding.UTF8, type=1, format=2, text=track.lyrics['syncID3'])) tag.add(SYLT(Encoding.UTF8, type=1, format=2, text=track.lyrics.syncID3))
involved_people = [] involved_people = []
for role in track.contributors: for role in track.contributors:
@ -91,24 +91,24 @@ def tagID3(stream, track, save):
if save['copyright']: if save['copyright']:
tag.add(TCOP(text=track.copyright)) tag.add(TCOP(text=track.copyright))
if save['savePlaylistAsCompilation'] and track.playlist or track.album['recordType'] == "compile": if save['savePlaylistAsCompilation'] and track.playlist or track.album.recordType == "compile":
tag.add(TCMP(text="1")) tag.add(TCMP(text="1"))
if save['source']: if save['source']:
tag.add(TXXX(desc="SOURCE", text='Deezer')) tag.add(TXXX(desc="SOURCE", text='Deezer'))
tag.add(TXXX(desc="SOURCEID", text=str(track.id))) tag.add(TXXX(desc="SOURCEID", text=str(track.id)))
if save['cover'] and track.album['embeddedCoverPath']: if save['cover'] and track.album.embeddedCoverPath:
descEncoding = Encoding.LATIN1 descEncoding = Encoding.LATIN1
if save['coverDescriptionUTF8']: if save['coverDescriptionUTF8']:
descEncoding = Encoding.UTF8 descEncoding = Encoding.UTF8
mimeType = 'image/jpeg' mimeType = 'image/jpeg'
if str(track.album['embeddedCoverPath']).endswith('png'): if str(track.album.embeddedCoverPath).endswith('png'):
mimeType = 'image/png' mimeType = 'image/png'
with open(track.album['embeddedCoverPath'], 'rb') as f: with open(track.album.embeddedCoverPath, 'rb') as f:
tag.add(APIC(descEncoding, mimeType, PictureType.COVER_FRONT, desc='cover', data=f.read())) tag.add(APIC(descEncoding, mimeType, PictureType.COVER_FRONT, desc='cover', data=f.read()))
tag.save( stream, tag.save( stream,
@ -131,7 +131,7 @@ def tagFLAC(stream, track, save):
tag["ARTIST"] = track.artists tag["ARTIST"] = track.artists
else: else:
if save['multiArtistSeparator'] == "nothing": if save['multiArtistSeparator'] == "nothing":
tag["ARTIST"] = track.mainArtist['name'] tag["ARTIST"] = track.mainArtist.name
else: else:
tag["ARTIST"] = track.artistsString tag["ARTIST"] = track.artistsString
# Tag ARTISTS is added to keep the multiartist support when using a non standard tagging method # Tag ARTISTS is added to keep the multiartist support when using a non standard tagging method
@ -139,24 +139,24 @@ def tagFLAC(stream, track, save):
tag["ARTISTS"] = track.artists tag["ARTISTS"] = track.artists
if save['album']: if save['album']:
tag["ALBUM"] = track.album['title'] tag["ALBUM"] = track.album.title
if save['albumArtist'] and len(track.album['artists']): if save['albumArtist'] and len(track.album.artists):
if save['singleAlbumArtist'] and track.album['mainArtist']['save']: if save['singleAlbumArtist'] and track.album.mainArtist.save:
tag["ALBUMARTIST"] = track.album['mainArtist']['name'] tag["ALBUMARTIST"] = track.album.mainArtist.name
else: else:
tag["ALBUMARTIST"] = track.album['artists'] tag["ALBUMARTIST"] = track.album.artists
if save['trackNumber']: if save['trackNumber']:
tag["TRACKNUMBER"] = str(track.trackNumber) tag["TRACKNUMBER"] = str(track.trackNumber)
if save['trackTotal']: if save['trackTotal']:
tag["TRACKTOTAL"] = str(track.album['trackTotal']) tag["TRACKTOTAL"] = str(track.album.trackTotal)
if save['discNumber']: if save['discNumber']:
tag["DISCNUMBER"] = str(track.discNumber) tag["DISCNUMBER"] = str(track.discNumber)
if save['discTotal']: if save['discTotal']:
tag["DISCTOTAL"] = str(track.album['discTotal']) tag["DISCTOTAL"] = str(track.album.discTotal)
if save['genre']: if save['genre']:
tag["GENRE"] = track.album['genre'] tag["GENRE"] = track.album.genre
# YEAR tag is not suggested as a standard tag # YEAR tag is not suggested as a standard tag
# Being YEAR already contained in DATE will only use DATE instead # Being YEAR already contained in DATE will only use DATE instead
@ -164,24 +164,24 @@ def tagFLAC(stream, track, save):
if save['date']: if save['date']:
tag["DATE"] = track.dateString tag["DATE"] = track.dateString
elif save['year']: elif save['year']:
tag["DATE"] = str(track.date['year']) tag["DATE"] = str(track.date.year)
if save['length']: if save['length']:
tag["LENGTH"] = str(int(track.duration)*1000) tag["LENGTH"] = str(int(track.duration)*1000)
if save['bpm']: if save['bpm']:
tag["BPM"] = str(track.bpm) tag["BPM"] = str(track.bpm)
if save['label']: if save['label']:
tag["PUBLISHER"] = track.album['label'] tag["PUBLISHER"] = track.album.label
if save['isrc']: if save['isrc']:
tag["ISRC"] = track.ISRC tag["ISRC"] = track.ISRC
if save['barcode']: if save['barcode']:
tag["BARCODE"] = track.album['barcode'] tag["BARCODE"] = track.album.barcode
if save['explicit']: if save['explicit']:
tag["ITUNESADVISORY"] = "1" if track.explicit else "0" tag["ITUNESADVISORY"] = "1" if track.explicit else "0"
if save['replayGain']: if save['replayGain']:
tag["REPLAYGAIN_TRACK_GAIN"] = track.replayGain tag["REPLAYGAIN_TRACK_GAIN"] = track.replayGain
if track.lyrics['unsync'] and save['lyrics']: if track.lyrics.unsync and save['lyrics']:
tag["LYRICS"] = track.lyrics['unsync'] tag["LYRICS"] = track.lyrics.unsync
for role in track.contributors: for role in track.contributors:
if role in ['author', 'engineer', 'mixer', 'producer', 'writer', 'composer']: if role in ['author', 'engineer', 'mixer', 'producer', 'writer', 'composer']:
@ -192,20 +192,20 @@ def tagFLAC(stream, track, save):
if save['copyright']: if save['copyright']:
tag["COPYRIGHT"] = track.copyright tag["COPYRIGHT"] = track.copyright
if save['savePlaylistAsCompilation'] and track.playlist or track.album['recordType'] == "compile": if save['savePlaylistAsCompilation'] and track.playlist or track.album.recordType == "compile":
tag["COMPILATION"] = "1" tag["COMPILATION"] = "1"
if save['source']: if save['source']:
tag["SOURCE"] = 'Deezer' tag["SOURCE"] = 'Deezer'
tag["SOURCEID"] = str(track.id) tag["SOURCEID"] = str(track.id)
if save['cover'] and track.album['embeddedCoverPath']: if save['cover'] and track.album.embeddedCoverPath:
image = Picture() image = Picture()
image.type = PictureType.COVER_FRONT image.type = PictureType.COVER_FRONT
image.mime = 'image/jpeg' image.mime = 'image/jpeg'
if str(track.album['embeddedCoverPath']).endswith('png'): if str(track.album.embeddedCoverPath).endswith('png'):
image.mime = 'image/png' image.mime = 'image/png'
with open(track.album['embeddedCoverPath'], 'rb') as f: with open(track.album.embeddedCoverPath, 'rb') as f:
image.data = f.read() image.data = f.read()
tag.add_picture(image) tag.add_picture(image)