323 lines
13 KiB
Python
323 lines
13 KiB
Python
import re
|
|
from datetime import datetime
|
|
|
|
from deezer.utils import map_track, map_album
|
|
from deezer.errors import APIError, GWAPIError
|
|
from deemix.errors import NoDataToParse, AlbumDoesntExists
|
|
|
|
from deemix.utils import removeFeatures, andCommaConcat, removeDuplicateArtists, generateReplayGainString, changeCase
|
|
|
|
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.types import VARIOUS_ARTISTS
|
|
|
|
from deemix.settings import FeaturesOption
|
|
|
|
class Track:
|
|
def __init__(self, sng_id="0", name=""):
|
|
self.id = sng_id
|
|
self.title = name
|
|
self.MD5 = ""
|
|
self.mediaVersion = ""
|
|
self.trackToken = ""
|
|
self.trackTokenExpiration = 0
|
|
self.duration = 0
|
|
self.fallbackID = "0"
|
|
self.albumsFallback = []
|
|
self.filesizes = {}
|
|
self.local = False
|
|
self.mainArtist = None
|
|
self.artist = {"Main": []}
|
|
self.artists = []
|
|
self.album = None
|
|
self.trackNumber = "0"
|
|
self.discNumber = "0"
|
|
self.date = Date()
|
|
self.lyrics = None
|
|
self.bpm = 0
|
|
self.contributors = {}
|
|
self.copyright = ""
|
|
self.explicit = False
|
|
self.ISRC = ""
|
|
self.replayGain = ""
|
|
self.rank = 0
|
|
self.playlist = None
|
|
self.position = None
|
|
self.searched = False
|
|
self.selectedFormat = 0
|
|
self.singleDownload = False
|
|
self.dateString = ""
|
|
self.artistsString = ""
|
|
self.mainArtistsString = ""
|
|
self.featArtistsString = ""
|
|
self.urls = {}
|
|
|
|
def parseEssentialData(self, trackAPI):
|
|
self.id = str(trackAPI['id'])
|
|
self.duration = trackAPI['duration']
|
|
self.trackToken = trackAPI['track_token']
|
|
self.trackTokenExpiration = trackAPI['track_token_expire']
|
|
self.MD5 = trackAPI.get('md5_origin')
|
|
self.mediaVersion = trackAPI['media_version']
|
|
self.filesizes = trackAPI['filesizes']
|
|
self.fallbackID = "0"
|
|
if 'fallback_id' in trackAPI:
|
|
self.fallbackID = trackAPI['fallback_id']
|
|
self.local = int(self.id) < 0
|
|
self.urls = {}
|
|
|
|
def parseData(self, dz, track_id=None, trackAPI=None, albumAPI=None, playlistAPI=None):
|
|
if track_id and (not trackAPI or trackAPI and not trackAPI.get('track_token')):
|
|
trackAPI_new = dz.gw.get_track_with_fallback(track_id)
|
|
trackAPI_new = map_track(trackAPI_new)
|
|
if not trackAPI: trackAPI = {}
|
|
trackAPI_new.update(trackAPI)
|
|
trackAPI = trackAPI_new
|
|
elif not trackAPI: raise NoDataToParse
|
|
|
|
self.parseEssentialData(trackAPI)
|
|
|
|
# only public api has bpm
|
|
if not trackAPI.get('bpm') and not self.local:
|
|
try:
|
|
trackAPI_new = dz.api.get_track(trackAPI['id'])
|
|
trackAPI_new['release_date'] = trackAPI['release_date']
|
|
trackAPI.update(trackAPI_new)
|
|
except APIError: pass
|
|
|
|
if self.local:
|
|
self.parseLocalTrackData(trackAPI)
|
|
else:
|
|
self.parseTrack(trackAPI)
|
|
|
|
# Get Lyrics data
|
|
if not trackAPI.get("lyrics") and self.lyrics.id != "0":
|
|
try: trackAPI["lyrics"] = dz.gw.get_track_lyrics(self.id)
|
|
except GWAPIError: self.lyrics.id = "0"
|
|
if self.lyrics.id != "0": self.lyrics.parseLyrics(trackAPI["lyrics"])
|
|
|
|
# Parse Album Data
|
|
self.album = Album(
|
|
alb_id = trackAPI['album']['id'],
|
|
title = trackAPI['album']['title'],
|
|
pic_md5 = trackAPI['album'].get('md5_origin')
|
|
)
|
|
|
|
# Get album Data
|
|
if not albumAPI:
|
|
try: albumAPI = dz.api.get_album(self.album.id)
|
|
except APIError: albumAPI = None
|
|
|
|
# Get album_gw Data
|
|
# Only gw has disk number
|
|
if not albumAPI or albumAPI and not albumAPI.get('nb_disk'):
|
|
try:
|
|
albumAPI_gw = dz.gw.get_album(self.album.id)
|
|
albumAPI_gw = map_album(albumAPI_gw)
|
|
except GWAPIError: albumAPI_gw = {}
|
|
if not albumAPI: albumAPI = {}
|
|
albumAPI_gw.update(albumAPI)
|
|
albumAPI = albumAPI_gw
|
|
|
|
if not albumAPI: raise AlbumDoesntExists
|
|
|
|
self.album.parseAlbum(albumAPI)
|
|
# 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
|
|
if not self.album.mainArtist.pic.md5 or self.album.mainArtist.pic.md5 == "":
|
|
artistAPI = dz.api.get_artist(self.album.mainArtist.id)
|
|
self.album.mainArtist.pic.md5 = artistAPI['picture_small'][artistAPI['picture_small'].find('artist/') + 7:-24]
|
|
|
|
# Fill missing data
|
|
if self.album.date and not self.date: self.date = self.album.date
|
|
if 'genres' in trackAPI:
|
|
for genre in trackAPI['genres']:
|
|
if genre not in self.album.genre: self.album.genre.append(genre)
|
|
|
|
# Remove unwanted charaters in track name
|
|
# Example: track/127793
|
|
self.title = ' '.join(self.title.split())
|
|
|
|
# Make sure there is at least one artist
|
|
if len(self.artist['Main']) == 0:
|
|
self.artist['Main'] = [self.mainArtist.name]
|
|
|
|
self.position = trackAPI.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):
|
|
# Local tracks has only the trackAPI_gw page and
|
|
# contains only the tags provided by the file
|
|
self.title = trackAPI['title']
|
|
self.album = Album(title=trackAPI['album']['title'])
|
|
self.album.pic = Picture(
|
|
md5 = trackAPI.get('md5_image', ""),
|
|
pic_type = "cover"
|
|
)
|
|
self.mainArtist = Artist(name=trackAPI['artist']['name'], role="Main")
|
|
self.artists = [trackAPI['artist']['name']]
|
|
self.artist = {
|
|
'Main': [trackAPI['artist']['name']]
|
|
}
|
|
self.album.artist = self.artist
|
|
self.album.artists = self.artists
|
|
self.album.date = self.date
|
|
self.album.mainArtist = self.mainArtist
|
|
|
|
def parseTrack(self, trackAPI):
|
|
self.title = trackAPI['title']
|
|
|
|
self.discNumber = trackAPI.get('disk_number')
|
|
self.explicit = trackAPI.get('explicit_lyrics', False)
|
|
self.copyright = trackAPI.get('copyright')
|
|
if 'gain' in trackAPI: self.replayGain = generateReplayGainString(trackAPI['gain'])
|
|
self.ISRC = trackAPI.get('isrc')
|
|
self.trackNumber = trackAPI['track_position']
|
|
self.contributors = trackAPI.get('song_contributors')
|
|
self.rank = trackAPI['rank']
|
|
self.bpm = trackAPI['bpm']
|
|
|
|
self.lyrics = Lyrics(trackAPI.get('lyrics_id', "0"))
|
|
|
|
self.mainArtist = Artist(
|
|
art_id = trackAPI['artist']['id'],
|
|
name = trackAPI['artist']['name'],
|
|
role = "Main",
|
|
pic_md5 = trackAPI['artist'].get('md5_image')
|
|
)
|
|
|
|
if 'physical_release_date' in trackAPI:
|
|
self.date.day = trackAPI["physical_release_date"][8:10]
|
|
self.date.month = trackAPI["physical_release_date"][5:7]
|
|
self.date.year = trackAPI["physical_release_date"][0:4]
|
|
self.date.fixDayMonth()
|
|
|
|
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'])
|
|
|
|
if 'alternative_albums' in trackAPI and trackAPI['alternative_albums']:
|
|
for album in trackAPI['alternative_albums']['data']:
|
|
if 'RIGHTS' in album and album['RIGHTS'].get('STREAM_ADS_AVAILABLE') or album['RIGHTS'].get('STREAM_SUB_AVAILABLE'):
|
|
self.albumsFallback.append(album['ALB_ID'])
|
|
|
|
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 "feat." not in self.title.lower():
|
|
return f"{self.title} ({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 checkAndRenewTrackToken(self, dz):
|
|
now = datetime.now()
|
|
expiration = datetime.fromtimestamp(self.trackTokenExpiration)
|
|
if now > expiration:
|
|
newTrack = dz.gw.get_track_with_fallback(self.id)
|
|
self.trackToken = newTrack['TRACK_TOKEN']
|
|
self.trackTokenExpiration = newTrack['TRACK_TOKEN_EXPIRE']
|
|
|
|
def applySettings(self, settings):
|
|
|
|
# Check if should save the playlist as a compilation
|
|
if self.playlist and settings['tags']['savePlaylistAsCompilation']:
|
|
self.trackNumber = self.position
|
|
self.discNumber = "1"
|
|
self.album.makePlaylistCompilation(self.playlist)
|
|
else:
|
|
if self.album.date: self.date = self.album.date
|
|
|
|
self.dateString = self.date.format(settings['dateFormat'])
|
|
self.album.dateString = self.album.date.format(settings['dateFormat'])
|
|
if self.playlist: self.playlist.dateString = self.playlist.date.format(settings['dateFormat'])
|
|
|
|
# Check various artist option
|
|
if settings['albumVariousArtists'] and self.album.variousArtists:
|
|
artist = self.album.variousArtists
|
|
isMainArtist = artist.role == "Main"
|
|
|
|
if artist.name not in self.album.artists:
|
|
self.album.artists.insert(0, artist.name)
|
|
|
|
if isMainArtist or artist.name not in self.album.artist['Main'] and not isMainArtist:
|
|
if artist.role not in self.album.artist:
|
|
self.album.artist[artist.role] = []
|
|
self.album.artist[artist.role].insert(0, artist.name)
|
|
self.album.mainArtist.save = not self.album.mainArtist.isVariousArtists() or settings['albumVariousArtists'] and self.album.mainArtist.isVariousArtists()
|
|
|
|
# Check removeDuplicateArtists
|
|
if settings['removeDuplicateArtists']: self.removeDuplicateArtists()
|
|
|
|
# Check if user wants the feat in the title
|
|
if str(settings['featuredToTitle']) == FeaturesOption.REMOVE_TITLE:
|
|
self.title = self.getCleanTitle()
|
|
elif str(settings['featuredToTitle']) == FeaturesOption.MOVE_TITLE:
|
|
self.title = self.getFeatTitle()
|
|
elif str(settings['featuredToTitle']) == FeaturesOption.REMOVE_TITLE_ALBUM:
|
|
self.title = self.getCleanTitle()
|
|
self.album.title = self.album.getCleanTitle()
|
|
|
|
# Remove (Album Version) from tracks that have that
|
|
if settings['removeAlbumVersion'] and "Album Version" in self.title:
|
|
self.title = re.sub(r' ?\(Album Version\)', "", self.title).strip()
|
|
|
|
# Change Title and Artists casing if needed
|
|
if settings['titleCasing'] != "nothing":
|
|
self.title = changeCase(self.title, settings['titleCasing'])
|
|
if settings['artistCasing'] != "nothing":
|
|
self.mainArtist.name = changeCase(self.mainArtist.name, settings['artistCasing'])
|
|
for i, artist in enumerate(self.artists):
|
|
self.artists[i] = changeCase(artist, settings['artistCasing'])
|
|
for art_type in self.artist:
|
|
for i, artist in enumerate(self.artist[art_type]):
|
|
self.artist[art_type][i] = changeCase(artist, settings['artistCasing'])
|
|
self.generateMainFeatStrings()
|
|
|
|
# Generate artist tag
|
|
if settings['tags']['multiArtistSeparator'] == "default":
|
|
if str(settings['featuredToTitle']) == FeaturesOption.MOVE_TITLE:
|
|
self.artistsString = ", ".join(self.artist['Main'])
|
|
else:
|
|
self.artistsString = ", ".join(self.artists)
|
|
elif settings['tags']['multiArtistSeparator'] == "andFeat":
|
|
self.artistsString = self.mainArtistsString
|
|
if self.featArtistsString and str(settings['featuredToTitle']) != FeaturesOption.MOVE_TITLE:
|
|
self.artistsString += " " + self.featArtistsString
|
|
else:
|
|
separator = settings['tags']['multiArtistSeparator']
|
|
if str(settings['featuredToTitle']) == FeaturesOption.MOVE_TITLE:
|
|
self.artistsString = separator.join(self.artist['Main'])
|
|
else:
|
|
self.artistsString = separator.join(self.artists)
|