Refactoring

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

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