deemix-py/deemix/app/track.py

477 lines
19 KiB
Python
Raw Normal View History

import eventlet
requests = eventlet.import_patched('requests')
2020-08-15 21:34:10 +02:00
import logging
from deezer.gw import APIError as gwAPIError, LyricsStatus
from deezer.api import APIError
2020-09-24 19:20:01 +02:00
from deemix.utils import removeFeatures, andCommaConcat, uniqueArray, generateReplayGainString
2020-08-15 21:34:10 +02:00
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('deemix')
2020-09-24 19:20:01 +02:00
VARIOUS_ARTISTS = 5080
2020-08-15 21:34:10 +02:00
class Track:
def __init__(self, dz, trackAPI_gw, trackAPI=None, albumAPI_gw=None, albumAPI=None):
2020-08-15 21:34:10 +02:00
self.parseEssentialData(dz, trackAPI_gw)
self.title = trackAPI_gw['SNG_TITLE'].strip()
2020-08-18 15:13:32 +02:00
if trackAPI_gw.get('VERSION') and not trackAPI_gw['VERSION'] in trackAPI_gw['SNG_TITLE']:
2020-08-15 23:03:05 +02:00
self.title += " " + trackAPI_gw['VERSION'].strip()
2020-08-15 21:34:10 +02:00
2020-08-18 15:13:32 +02:00
self.position = trackAPI_gw.get('POSITION')
2020-08-15 21:34:10 +02:00
self.localTrack = int(self.id) < 0
if self.localTrack:
self.parseLocalTrackData(trackAPI_gw)
else:
self.parseData(dz, trackAPI_gw, trackAPI, albumAPI_gw, albumAPI)
2020-08-15 21:34:10 +02:00
2020-09-24 19:20:01 +02:00
# Make sure there is at least one artist
2020-08-15 21:34:10 +02:00
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"])
2020-08-15 21:34:10 +02:00
self.singleDownload = trackAPI_gw.get('SINGLE_TRACK', False)
self.generateMainFeatStrings()
2020-08-15 21:34:10 +02:00
# Bits useful for later
self.searched = False
self.selectedFormat = 0
self.dateString = None
self.album['embeddedCoverURL'] = None
self.album['embeddedCoverPath'] = None
2020-08-15 21:34:10 +02:00
self.album['bitrate'] = 0
self.album['dateString'] = None
2020-08-15 23:03:05 +02:00
self.artistsString = ""
2020-08-15 21:34:10 +02:00
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']
2020-10-14 23:25:29 +02:00
if int(self.id) > 0:
self.filesizes = self.getFilesizes(dz)
2020-08-15 21:34:10 +02:00
def parseLocalTrackData(self, trackAPI_gw):
2020-09-24 19:20:01 +02:00
# Local tracks has only the trackAPI_gw page and
# contains only the tags provided by the file
2020-08-15 21:34:10 +02:00
self.album = {
'id': "0",
'title': trackAPI_gw['ALB_TITLE'],
'pic': {
'md5': trackAPI_gw.get('ALB_PICTURE', ""),
'type': "cover",
'url': None
}
2020-08-15 21:34:10 +02:00
}
self.mainArtist = {
'id': "0",
'name': trackAPI_gw['ART_NAME'],
'pic': {
'md5': "",
'type': "artist",
'url': None
}
2020-08-15 21:34:10 +02:00
}
self.artists = [trackAPI_gw['ART_NAME']]
self.artist = {
'Main': [trackAPI_gw['ART_NAME']]
}
self.date = {
'day': "00",
'month': "00",
'year': "XXXX"
}
2020-09-24 19:20:01 +02:00
# Defaulting all the missing data
2020-08-15 21:34:10 +02:00
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
2020-12-29 22:25:03 +01:00
self.album['variousArtists'] = None
self.album['rootArtist'] = None
self.album['recordType'] = "album"
2020-08-15 21:34:10 +02:00
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):
2020-08-18 15:13:32 +02:00
self.discNumber = trackAPI_gw.get('DISK_NUMBER')
2020-09-12 13:08:28 +02:00
self.explicit = bool(int(trackAPI_gw.get('EXPLICIT_LYRICS', "0")))
2020-08-18 15:13:32 +02:00
self.copyright = trackAPI_gw.get('COPYRIGHT')
2020-08-15 21:34:10 +02:00
self.replayGain = ""
if 'GAIN' in trackAPI_gw: self.replayGain = generateReplayGainString(trackAPI_gw['GAIN'])
2020-09-24 19:20:01 +02:00
self.ISRC = trackAPI_gw.get('ISRC')
2020-08-15 21:34:10 +02:00
self.trackNumber = trackAPI_gw['TRACK_NUMBER']
self.contributors = trackAPI_gw['SNG_CONTRIBUTORS']
2020-08-15 23:03:05 +02:00
self.lyrics = {
2020-09-24 19:20:01 +02:00
'id': int(trackAPI_gw.get('LYRICS_ID', "0")),
2020-08-15 21:34:10 +02:00
'unsync': None,
'sync': None,
'syncID3': None
2020-08-15 21:34:10 +02:00
}
2020-09-24 19:20:01 +02:00
if not "LYRICS" in trackAPI_gw and self.lyrics['id'] != 0:
2020-08-15 21:34:10 +02:00
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
2020-09-24 19:20:01 +02:00
if self.lyrics['id'] != 0:
2020-08-18 15:13:32 +02:00
self.lyrics['unsync'] = trackAPI_gw["LYRICS"].get("LYRICS_TEXT")
2020-08-15 21:34:10 +02:00
if "LYRICS_SYNC_JSON" in trackAPI_gw["LYRICS"]:
2020-09-24 19:20:01 +02:00
syncLyricsJson = trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"]
2020-08-15 21:34:10 +02:00
self.lyrics['sync'] = ""
self.lyrics['syncID3'] = []
2020-09-22 11:32:07 +02:00
timestamp = ""
milliseconds = 0
2020-09-24 19:20:01 +02:00
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))
2020-08-15 21:34:10 +02:00
else:
2020-09-24 19:20:01 +02:00
notEmptyLine = line + 1
while syncLyricsJson[notEmptyLine]["line"] == "":
notEmptyLine = notEmptyLine + 1
timestamp = syncLyricsJson[notEmptyLine]["lrc_timestamp"]
self.lyrics['sync'] += timestamp + syncLyricsJson[line]["line"] + "\r\n"
2020-08-15 21:34:10 +02:00
2020-08-15 23:03:05 +02:00
self.mainArtist = {
2020-08-15 21:34:10 +02:00
'id': trackAPI_gw['ART_ID'],
'name': trackAPI_gw['ART_NAME'],
'pic': {
'md5': trackAPI_gw.get('ART_PICTURE'),
'type': "artist",
'url': None
}
2020-08-15 21:34:10 +02:00
}
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
},
2020-08-15 21:34:10 +02:00
'barcode': "Unknown",
'label': "Unknown",
'explicit': False,
'date': None,
'genre': []
}
2020-09-24 19:20:01 +02:00
# 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'])
2020-09-24 19:20:01 +02:00
except APIError:
albumAPI = None
2020-12-29 22:25:03 +01:00
self.album['variousArtists'] = None
2020-09-24 19:20:01 +02:00
if albumAPI:
2020-08-15 21:34:10 +02:00
self.album['title'] = albumAPI['title']
2020-09-24 19:20:01 +02:00
# 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]
2020-08-15 21:34:10 +02:00
self.album['mainArtist'] = {
'id': albumAPI['artist']['id'],
'name': albumAPI['artist']['name'],
'pic': {
'md5': artistPicture,
'type': "artist",
'url': None
}
2020-08-15 21:34:10 +02:00
}
2020-11-21 16:45:40 +01:00
self.album['rootArtist'] = albumAPI.get('root_artist', None)
2020-08-15 21:34:10 +02:00
2020-12-29 22:25:03 +01:00
self.album['artist'] = {'Main': []}
2020-08-15 21:34:10 +02:00
self.album['artists'] = []
for artist in albumAPI['contributors']:
2020-09-24 19:20:01 +02:00
isVariousArtists = artist['id'] == VARIOUS_ARTISTS
isMainArtist = artist['role'] == "Main"
if isVariousArtists:
self.album['variousArtists'] = artist
continue
2020-09-24 19:20:01 +02:00
if artist['name'] not in self.album['artists']:
self.album['artists'].append(artist['name'])
2020-09-24 19:20:01 +02:00
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'])
2020-08-15 21:34:10 +02:00
self.album['trackTotal'] = albumAPI['nb_tracks']
self.album['recordType'] = albumAPI['record_type']
2020-09-12 13:08:28 +02:00
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))
2020-08-15 21:34:10 +02:00
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]
}
2020-09-12 13:08:28 +02:00
self.album['discTotal'] = albumAPI.get('nb_disk', "1")
2020-08-18 15:13:32 +02:00
self.copyright = albumAPI.get('copyright')
2020-08-15 21:34:10 +02:00
if not self.album['pic']['md5']:
# Getting album cover MD5
2020-09-24 19:20:01 +02:00
# 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]
2020-08-15 21:34:10 +02:00
2020-09-24 19:20:01 +02:00
if albumAPI.get('genres') and len(albumAPI['genres'].get('data', [])) > 0:
2020-08-15 21:34:10 +02:00
for genre in albumAPI['genres']['data']:
self.album['genre'].append(genre['name'])
2020-09-24 19:20:01 +02:00
else:
2020-08-15 21:34:10 +02:00
if not albumAPI_gw:
logger.info(f"[{self.mainArtist['name']} - {self.title}] Getting more album infos")
2020-09-24 19:20:01 +02:00
try:
albumAPI_gw = dz.gw.get_album(self.album['id'])
except gwAPIError:
2020-09-24 19:20:01 +02:00
albumAPI_gw = None
2020-10-04 11:56:32 +02:00
raise AlbumDoesntExists
2020-09-24 19:20:01 +02:00
2020-08-15 21:34:10 +02:00
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
}
2020-08-15 21:34:10 +02:00
}
2020-11-22 21:37:57 +01:00
self.album['rootArtist'] = None
2020-09-24 19:20:01 +02:00
# 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
2020-08-15 21:34:10 +02:00
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]
2020-09-24 19:20:01 +02:00
self.album['artists'] = [albumAPI_gw['ART_NAME']]
2020-08-15 21:34:10 +02:00
self.album['trackTotal'] = albumAPI_gw['NUMBER_TRACK']
self.album['discTotal'] = albumAPI_gw['NUMBER_DISK']
self.album['recordType'] = "album"
2020-09-12 13:08:28 +02:00
self.album['label'] = albumAPI_gw.get('LABEL_NAME', self.album['label'])
2020-09-24 19:20:01 +02:00
explicitLyricsStatus = albumAPI_gw.get('EXPLICIT_ALBUM_CONTENT', {}).get('EXPLICIT_LYRICS_STATUS', LyricsStatus.UNKNOWN)
self.album['explicit'] = explicitLyricsStatus in [LyricsStatus.EXPLICIT, LyricsStatus.PARTIALLY_EXPLICIT]
2020-09-24 19:20:01 +02:00
if not self.album['pic']['md5']:
self.album['pic']['md5'] = albumAPI_gw['ALB_PICTURE']
2020-08-15 21:34:10 +02:00
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
2020-08-15 21:34:10 +02:00
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)
2020-08-15 21:34:10 +02:00
self.bpm = trackAPI['bpm']
if not self.replayGain and 'gain' in trackAPI:
2020-09-24 19:20:01 +02:00
self.replayGain = generateReplayGainString(trackAPI['gain'])
2020-08-15 21:34:10 +02:00
if not self.explicit:
self.explicit = trackAPI['explicit_lyrics']
if not self.discNumber:
self.discNumber = trackAPI['disk_number']
2020-12-29 22:25:03 +01:00
self.artist = {'Main': []}
2020-08-15 21:34:10 +02:00
self.artists = []
for artist in trackAPI['contributors']:
2020-09-24 19:20:01 +02:00
isVariousArtists = artist['id'] == VARIOUS_ARTISTS
isMainArtist = artist['role'] == "Main"
if len(trackAPI['contributors']) > 1 and isVariousArtists:
continue
2020-09-24 19:20:01 +02:00
if artist['name'] not in self.artists:
self.artists.append(artist['name'])
2020-09-24 19:20:01 +02:00
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'])
2020-08-15 21:34:10 +02:00
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'])
2020-08-15 21:34:10 +02:00
self.album['discTotal'] = albumAPI_gw['NUMBER_DISK']
2020-09-24 19:20:01 +02:00
2020-08-15 21:34:10 +02:00
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'])
2020-08-15 21:34:10 +02:00
self.copyright = albumAPI_gw['COPYRIGHT']
def parsePlaylistData(self, playlist):
2020-09-24 19:20:01 +02:00
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
}
2020-09-24 19:20:01 +02:00
else:
self.playlist['pic'] = {
'md5': None,
'type': None,
'url': playlist['picture_xl']
}
self.playlist['title'] = playlist['title']
2020-09-24 19:20:01 +02:00
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
}
2020-09-24 19:20:01 +02:00
}
2020-11-22 21:37:57 +01:00
self.playlist['rootArtist'] = None
self.playlist['artist'] = {"Main": []}
self.playlist['artists'] = []
self.playlist['variousArtists'] = playlist['various_artist']
self.playlist['trackTotal'] = playlist['nb_tracks']
2020-09-24 19:20:01 +02:00
self.playlist['recordType'] = "compile"
self.playlist['barcode'] = ""
self.playlist['label'] = ""
self.playlist['explicit'] = playlist['explicit']
2020-09-24 19:20:01 +02:00
self.playlist['date'] = {
'day': playlist["creation_date"][8:10],
'month': playlist["creation_date"][5:7],
'year': playlist["creation_date"][0:4]
2020-09-24 19:20:01 +02:00
}
self.playlist['discTotal'] = "1"
self.playlist['playlistId'] = playlist['id']
self.playlist['owner'] = playlist['creator']
2020-09-24 19:20:01 +02:00
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])
2020-08-15 21:34:10 +02:00
# 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'])
2020-09-24 19:20:01 +02:00
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
2020-09-24 19:20:01 +02:00
class TrackError(Exception):
"""Base class for exceptions in this module."""
pass
2020-10-04 11:56:32 +02:00
class AlbumDoesntExists(TrackError):
2020-09-24 19:20:01 +02:00
pass