From 87508811087d60521707dc5b6d0ba09a1b5e9afc Mon Sep 17 00:00:00 2001 From: RemixDev Date: Sun, 31 Jan 2021 19:59:15 +0300 Subject: [PATCH] Refactoring --- deemix/__init__.py | 1 + deemix/app/downloadjob.py | 185 ++++++------- deemix/app/track.py | 476 ---------------------------------- deemix/types/Album.py | 140 ++++++++++ deemix/types/Artist.py | 13 + deemix/types/Date.py | 25 ++ deemix/types/Lyrics.py | 26 ++ deemix/types/Picture.py | 27 ++ deemix/types/Playlist.py | 48 ++++ deemix/types/Track.py | 269 +++++++++++++++++++ deemix/types/__init__.py | 7 + deemix/utils/__init__.py | 6 + deemix/utils/pathtemplates.py | 104 ++++---- deemix/utils/taggers.py | 78 +++--- 14 files changed, 732 insertions(+), 673 deletions(-) delete mode 100644 deemix/app/track.py create mode 100644 deemix/types/Album.py create mode 100644 deemix/types/Artist.py create mode 100644 deemix/types/Date.py create mode 100644 deemix/types/Lyrics.py create mode 100644 deemix/types/Picture.py create mode 100644 deemix/types/Playlist.py create mode 100644 deemix/types/Track.py create mode 100644 deemix/types/__init__.py diff --git a/deemix/__init__.py b/deemix/__init__.py index 93d028b..296a874 100644 --- a/deemix/__init__.py +++ b/deemix/__init__.py @@ -3,3 +3,4 @@ __version__ = "2.0.11" USER_AGENT_HEADER = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " \ "Chrome/79.0.3945.130 Safari/537.36" +VARIOUS_ARTISTS = "5080" diff --git a/deemix/app/downloadjob.py b/deemix/app/downloadjob.py index 3c84da6..105559a 100644 --- a/deemix/app/downloadjob.py +++ b/deemix/app/downloadjob.py @@ -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: diff --git a/deemix/app/track.py b/deemix/app/track.py deleted file mode 100644 index 0a9833f..0000000 --- a/deemix/app/track.py +++ /dev/null @@ -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 diff --git a/deemix/types/Album.py b/deemix/types/Album.py new file mode 100644 index 0000000..b47c4c3 --- /dev/null +++ b/deemix/types/Album.py @@ -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) diff --git a/deemix/types/Artist.py b/deemix/types/Artist.py new file mode 100644 index 0000000..42cb573 --- /dev/null +++ b/deemix/types/Artist.py @@ -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 diff --git a/deemix/types/Date.py b/deemix/types/Date.py new file mode 100644 index 0000000..b74e04f --- /dev/null +++ b/deemix/types/Date.py @@ -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 diff --git a/deemix/types/Lyrics.py b/deemix/types/Lyrics.py new file mode 100644 index 0000000..a21beb1 --- /dev/null +++ b/deemix/types/Lyrics.py @@ -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" diff --git a/deemix/types/Picture.py b/deemix/types/Picture.py new file mode 100644 index 0000000..ca00f49 --- /dev/null +++ b/deemix/types/Picture.py @@ -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' + ) diff --git a/deemix/types/Playlist.py b/deemix/types/Playlist.py new file mode 100644 index 0000000..9625719 --- /dev/null +++ b/deemix/types/Playlist.py @@ -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']) diff --git a/deemix/types/Track.py b/deemix/types/Track.py new file mode 100644 index 0000000..edf72a1 --- /dev/null +++ b/deemix/types/Track.py @@ -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 diff --git a/deemix/types/__init__.py b/deemix/types/__init__.py new file mode 100644 index 0000000..9db5426 --- /dev/null +++ b/deemix/types/__init__.py @@ -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 diff --git a/deemix/utils/__init__.py b/deemix/utils/__init__.py index ad6c691..180f2ab 100644 --- a/deemix/utils/__init__.py +++ b/deemix/utils/__init__.py @@ -122,6 +122,12 @@ def uniqueArray(arr): del arr[iRest] 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): try: os.makedirs(folder, exist_ok=True) diff --git a/deemix/utils/pathtemplates.py b/deemix/utils/pathtemplates.py index 5e64b11..3d04dce 100644 --- a/deemix/utils/pathtemplates.py +++ b/deemix/utils/pathtemplates.py @@ -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['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 if (settings['createAlbumFolder'] and @@ -102,7 +102,7 @@ def generateFilepath(track, settings): extrasPath = filepath if ( - int(track.album['discTotal']) > 1 and ( + int(track.album.discTotal) > 1 and ( (settings['createAlbumFolder'] and settings['createCDFolder']) and (not track.singleDownload or (track.singleDownload and settings['createSingleFolder'])) and (not track.playlist or @@ -117,7 +117,7 @@ def generateFilepath(track, settings): def settingsRegex(filename, track, settings): 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("%allartists%", fixName(track.artistsString, 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'])) else: filename = filename.replace("%featartists%", '') - filename = filename.replace("%album%", fixName(track.album['title'], 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("%tracktotal%", str(track.album['trackTotal'])) + filename = filename.replace("%album%", fixName(track.album.title, 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("%tracktotal%", str(track.album.trackTotal)) filename = filename.replace("%discnumber%", str(track.discNumber)) - filename = filename.replace("%disctotal%", str(track.album['discTotal'])) - if len(track.album['genre']) > 0: + filename = filename.replace("%disctotal%", str(track.album.discTotal)) + if len(track.album.genre) > 0: filename = filename.replace("%genre%", - fixName(track.album['genre'][0], settings['illegalCharacterReplacer'])) + fixName(track.album.genre[0], settings['illegalCharacterReplacer'])) else: 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("%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("%upc%", track.album['barcode']) + filename = filename.replace("%upc%", track.album.barcode) filename = filename.replace("%explicit%", "(Explicit)" if track.explicit else "") filename = filename.replace("%track_id%", str(track.id)) - filename = filename.replace("%album_id%", str(track.album['id'])) - filename = filename.replace("%artist_id%", str(track.mainArtist['id'])) + filename = filename.replace("%album_id%", str(track.album.id)) + filename = filename.replace("%artist_id%", str(track.mainArtist.id)) if track.playlist: - filename = filename.replace("%playlist_id%", str(track.playlist['playlistId'])) - filename = filename.replace("%position%", pad(track.position, track.playlist['trackTotal'], settings)) + filename = filename.replace("%playlist_id%", str(track.playlist.playlistId)) + filename = filename.replace("%position%", pad(track.position, track.playlist.trackTotal, settings)) else: 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) return antiDot(fixLongName(filename)) def settingsRegexAlbum(foldername, album, settings, playlist=None): 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") else: - foldername = foldername.replace("%album_id%", str(album['id'])) - if len(album['genre']) > 0: - foldername = foldername.replace("%genre%", fixName(album['genre'][0], settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%album_id%", str(album.id)) + if len(album.genre) > 0: + foldername = foldername.replace("%genre%", fixName(album.genre[0], settings['illegalCharacterReplacer'])) else: foldername = foldername.replace("%genre%", "Unknown") - foldername = foldername.replace("%album%", fixName(album['title'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%artist%", fixName(album['mainArtist']['name'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%artist_id%", str(album['mainArtist']['id'])) - if album['rootArtist']: - foldername = foldername.replace("%root_artist%", fixName(album['rootArtist']['name'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%root_artist_id%", str(album['rootArtist']['id'])) + foldername = foldername.replace("%album%", fixName(album.title, settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%artist%", fixName(album.mainArtist.name, settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%artist_id%", str(album.mainArtist.id)) + if album.rootArtist: + foldername = foldername.replace("%root_artist%", fixName(album.rootArtist.name, settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%root_artist_id%", str(album.rootArtist.id)) else: - foldername = foldername.replace("%root_artist%", fixName(album['mainArtist']['name'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%root_artist_id%", str(album['mainArtist']['id'])) - foldername = foldername.replace("%tracktotal%", str(album['trackTotal'])) - foldername = foldername.replace("%disctotal%", str(album['discTotal'])) - foldername = foldername.replace("%type%", fixName(album['recordType'].capitalize(), settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%upc%", album['barcode']) - foldername = foldername.replace("%explicit%", "(Explicit)" if album['explicit'] else "") - foldername = foldername.replace("%label%", fixName(album['label'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%year%", str(album['date']['year'])) - foldername = foldername.replace("%date%", album['dateString']) - foldername = foldername.replace("%bitrate%", bitrateLabels[int(album['bitrate'])]) + foldername = foldername.replace("%root_artist%", fixName(album.mainArtist.name, settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%root_artist_id%", str(album.mainArtist.id)) + foldername = foldername.replace("%tracktotal%", str(album.trackTotal)) + foldername = foldername.replace("%disctotal%", str(album.discTotal)) + foldername = foldername.replace("%type%", fixName(album.recordType.capitalize(), settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%upc%", album.barcode) + foldername = foldername.replace("%explicit%", "(Explicit)" if album.explicit else "") + foldername = foldername.replace("%label%", fixName(album.label, settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%year%", str(album.date.year)) + foldername = foldername.replace("%date%", album.dateString) + foldername = foldername.replace("%bitrate%", bitrateLabels[int(album.bitrate)]) foldername = foldername.replace('\\', pathSep).replace('/', pathSep) return antiDot(fixLongName(foldername)) def settingsRegexArtist(foldername, artist, settings, rootArtist=None): - foldername = foldername.replace("%artist%", fixName(artist['name'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%artist_id%", str(artist['id'])) + foldername = foldername.replace("%artist%", fixName(artist.name, settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%artist_id%", str(artist.id)) if rootArtist: - foldername = foldername.replace("%root_artist%", fixName(rootArtist['name'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%root_artist_id%", str(rootArtist['id'])) + foldername = foldername.replace("%root_artist%", fixName(rootArtist.name, settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%root_artist_id%", str(rootArtist.id)) else: - foldername = foldername.replace("%root_artist%", fixName(artist['name'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%root_artist_id%", str(artist['id'])) + foldername = foldername.replace("%root_artist%", fixName(artist.name, settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%root_artist_id%", str(artist.id)) foldername = foldername.replace('\\', pathSep).replace('/', pathSep) return antiDot(fixLongName(foldername)) def settingsRegexPlaylist(foldername, playlist, settings): - foldername = foldername.replace("%playlist%", fixName(playlist['title'], 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_id%", str(playlist['owner']['id'])) - foldername = foldername.replace("%year%", str(playlist['date']['year'])) - foldername = foldername.replace("%date%", str(playlist['dateString'])) - foldername = foldername.replace("%explicit%", "(Explicit)" if playlist['explicit'] else "") + foldername = foldername.replace("%playlist%", fixName(playlist.title, 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_id%", str(playlist.owner['id'])) + foldername = foldername.replace("%year%", str(playlist.date.year)) + foldername = foldername.replace("%date%", str(playlist.dateString)) + foldername = foldername.replace("%explicit%", "(Explicit)" if playlist.explicit else "") foldername = foldername.replace('\\', pathSep).replace('/', pathSep) return antiDot(fixLongName(foldername)) diff --git a/deemix/utils/taggers.py b/deemix/utils/taggers.py index a2b7c1e..b363752 100644 --- a/deemix/utils/taggers.py +++ b/deemix/utils/taggers.py @@ -20,7 +20,7 @@ def tagID3(stream, track, save): tag.add(TPE1(text=track.artists)) else: if save['multiArtistSeparator'] == "nothing": - tag.add(TPE1(text=track.mainArtist['name'])) + tag.add(TPE1(text=track.mainArtist.name)) else: tag.add(TPE1(text=track.artistsString)) # 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)) 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['singleAlbumArtist'] and track.album['mainArtist']['save']: - tag.add(TPE2(text=track.album['mainArtist']['name'])) + if save['albumArtist'] and len(track.album.artists): + if save['singleAlbumArtist'] and track.album.mainArtist.save: + tag.add(TPE2(text=track.album.mainArtist.name)) else: - tag.add(TPE2(text=track.album['artists'])) + tag.add(TPE2(text=track.album.artists)) if save['trackNumber']: trackNumber = str(track.trackNumber) if save['trackTotal']: - trackNumber += "/" + str(track.album['trackTotal']) + trackNumber += "/" + str(track.album.trackTotal) tag.add(TRCK(text=trackNumber)) if save['discNumber']: discNumber = str(track.discNumber) if save['discTotal']: - discNumber += "/" + str(track.album['discTotal']) + discNumber += "/" + str(track.album.discTotal) tag.add(TPOS(text=discNumber)) if save['genre']: - tag.add(TCON(text=track.album['genre'])) + tag.add(TCON(text=track.album.genre)) if save['year']: - tag.add(TYER(text=str(track.date['year']))) + tag.add(TYER(text=str(track.date.year))) if save['date']: # Referencing ID3 standard # https://id3.org/id3v2.3.0#TDAT # 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']: tag.add(TLEN(text=str(int(track.duration)*1000))) if save['bpm']: tag.add(TBPM(text=str(track.bpm))) if save['label']: - tag.add(TPUB(text=track.album['label'])) + tag.add(TPUB(text=track.album.label)) if save['isrc']: tag.add(TSRC(text=track.ISRC)) if save['barcode']: - tag.add(TXXX(desc="BARCODE", text=track.album['barcode'])) + tag.add(TXXX(desc="BARCODE", text=track.album.barcode)) if save['explicit']: tag.add(TXXX(desc="ITUNESADVISORY", text= "1" if track.explicit else "0" )) if save['replayGain']: tag.add(TXXX(desc="REPLAYGAIN_TRACK_GAIN", text=track.replayGain)) - if track.lyrics['unsync'] and save['lyrics']: - tag.add(USLT(text=track.lyrics['unsync'])) - if track.lyrics['syncID3'] and save['syncedLyrics']: + if track.lyrics.unsync and save['lyrics']: + tag.add(USLT(text=track.lyrics.unsync)) + if track.lyrics.syncID3 and save['syncedLyrics']: # Referencing ID3 standard # https://id3.org/id3v2.3.0#sec4.10 # Type: 1 => is lyrics # 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 = [] for role in track.contributors: @@ -91,24 +91,24 @@ def tagID3(stream, track, save): if save['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")) if save['source']: tag.add(TXXX(desc="SOURCE", text='Deezer')) 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 if save['coverDescriptionUTF8']: descEncoding = Encoding.UTF8 mimeType = 'image/jpeg' - if str(track.album['embeddedCoverPath']).endswith('png'): + if str(track.album.embeddedCoverPath).endswith('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.save( stream, @@ -131,7 +131,7 @@ def tagFLAC(stream, track, save): tag["ARTIST"] = track.artists else: if save['multiArtistSeparator'] == "nothing": - tag["ARTIST"] = track.mainArtist['name'] + tag["ARTIST"] = track.mainArtist.name else: tag["ARTIST"] = track.artistsString # 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 if save['album']: - tag["ALBUM"] = track.album['title'] + tag["ALBUM"] = track.album.title - if save['albumArtist'] and len(track.album['artists']): - if save['singleAlbumArtist'] and track.album['mainArtist']['save']: - tag["ALBUMARTIST"] = track.album['mainArtist']['name'] + if save['albumArtist'] and len(track.album.artists): + if save['singleAlbumArtist'] and track.album.mainArtist.save: + tag["ALBUMARTIST"] = track.album.mainArtist.name else: - tag["ALBUMARTIST"] = track.album['artists'] + tag["ALBUMARTIST"] = track.album.artists if save['trackNumber']: tag["TRACKNUMBER"] = str(track.trackNumber) if save['trackTotal']: - tag["TRACKTOTAL"] = str(track.album['trackTotal']) + tag["TRACKTOTAL"] = str(track.album.trackTotal) if save['discNumber']: tag["DISCNUMBER"] = str(track.discNumber) if save['discTotal']: - tag["DISCTOTAL"] = str(track.album['discTotal']) + tag["DISCTOTAL"] = str(track.album.discTotal) if save['genre']: - tag["GENRE"] = track.album['genre'] + tag["GENRE"] = track.album.genre # YEAR tag is not suggested as a standard tag # Being YEAR already contained in DATE will only use DATE instead @@ -164,24 +164,24 @@ def tagFLAC(stream, track, save): if save['date']: tag["DATE"] = track.dateString elif save['year']: - tag["DATE"] = str(track.date['year']) + tag["DATE"] = str(track.date.year) if save['length']: tag["LENGTH"] = str(int(track.duration)*1000) if save['bpm']: tag["BPM"] = str(track.bpm) if save['label']: - tag["PUBLISHER"] = track.album['label'] + tag["PUBLISHER"] = track.album.label if save['isrc']: tag["ISRC"] = track.ISRC if save['barcode']: - tag["BARCODE"] = track.album['barcode'] + tag["BARCODE"] = track.album.barcode if save['explicit']: tag["ITUNESADVISORY"] = "1" if track.explicit else "0" if save['replayGain']: tag["REPLAYGAIN_TRACK_GAIN"] = track.replayGain - if track.lyrics['unsync'] and save['lyrics']: - tag["LYRICS"] = track.lyrics['unsync'] + if track.lyrics.unsync and save['lyrics']: + tag["LYRICS"] = track.lyrics.unsync for role in track.contributors: if role in ['author', 'engineer', 'mixer', 'producer', 'writer', 'composer']: @@ -192,20 +192,20 @@ def tagFLAC(stream, track, save): if save['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" if save['source']: tag["SOURCE"] = 'Deezer' tag["SOURCEID"] = str(track.id) - if save['cover'] and track.album['embeddedCoverPath']: + if save['cover'] and track.album.embeddedCoverPath: image = Picture() image.type = PictureType.COVER_FRONT image.mime = 'image/jpeg' - if str(track.album['embeddedCoverPath']).endswith('png'): + if str(track.album.embeddedCoverPath).endswith('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() tag.add_picture(image)