Code parity with deemix-js

This commit is contained in:
RemixDev
2021-06-07 20:25:51 +02:00
parent 69c165e2bc
commit 224a62aad2
21 changed files with 715 additions and 555 deletions

View File

@ -2,6 +2,12 @@ import string
from deezer import TrackFormats
import os
USER_AGENT_HEADER = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " \
"Chrome/79.0.3945.130 Safari/537.36"
def canWrite(folder):
return os.access(folder, os.W_OK)
def generateReplayGainString(trackGain):
return "{0:.2f} dB".format((float(trackGain) + 18.4) * -1)
@ -67,11 +73,3 @@ def removeDuplicateArtists(artist, artists):
for role in artist.keys():
artist[role] = uniqueArray(artist[role])
return (artist, artists)
def checkFolder(folder):
try:
os.makedirs(folder, exist_ok=True)
except Exception as e:
print(str(e))
return False
return os.access(folder, os.W_OK)

26
deemix/utils/crypto.py Normal file
View File

@ -0,0 +1,26 @@
import binascii
from Cryptodome.Cipher import Blowfish, AES
from Cryptodome.Hash import MD5
def _md5(data):
h = MD5.new()
h.update(data.encode() if isinstance(data, str) else data)
return h.hexdigest()
def _ecbCrypt(key, data):
return binascii.hexlify(AES.new(key.encode(), AES.MODE_ECB).encrypt(data))
def _ecbDecrypt(key, data):
return AES.new(key.encode(), AES.MODE_ECB).decrypt(binascii.unhexlify(data.encode("utf-8")))
def generateBlowfishKey(trackId):
SECRET = 'g4el58wc0zvf9na1'
idMd5 = _md5(trackId)
bfKey = ""
for i in range(16):
bfKey += chr(ord(idMd5[i]) ^ ord(idMd5[i + 16]) ^ ord(SECRET[i]))
return bfKey
def decryptChunk(key, data):
return Blowfish.new(key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(data)

32
deemix/utils/deezer.py Normal file
View File

@ -0,0 +1,32 @@
import requests
from deemix.utils.crypto import _md5
from deemix.utils import USER_AGENT_HEADER
CLIENT_ID = "172365"
CLIENT_SECRET = "fb0bec7ccc063dab0417eb7b0d847f34"
def getAccessToken(email, password):
password = _md5(password)
request_hash = _md5(''.join([CLIENT_ID, email, password, CLIENT_SECRET]))
response = requests.get(
'https://api.deezer.com/auth/token',
params={
'app_id': CLIENT_ID,
'login': email,
'password': password,
'hash': request_hash
},
headers={"User-Agent": USER_AGENT_HEADER}
).json()
return response.get('access_token')
def getArtFromAccessToken(accessToken):
session = requests.Session()
session.get(
"https://api.deezer.com/platform/generic/track/3135556",
headers={"Authorization": f"Bearer {accessToken}", "User-Agent": USER_AGENT_HEADER}
)
response = session.get(
'https://www.deezer.com/ajax/gw-light.php?method=user.getArl&input=3&api_version=1.0&api_token=null',
headers={"User-Agent": USER_AGENT_HEADER}
).json()
return response.get('results')

View File

@ -1,45 +1,72 @@
from pathlib import Path
import sys
import os
if os.name == 'nt':
import winreg # pylint: disable=E0401
import re
from deemix.utils import canWrite
homedata = Path.home()
userdata = ""
musicdata = ""
if os.getenv("DEEMIX_DATA_DIR"):
userdata = Path(os.getenv("DEEMIX_DATA_DIR"))
elif os.getenv("XDG_CONFIG_HOME"):
userdata = Path(os.getenv("XDG_CONFIG_HOME")) / 'deemix'
elif os.getenv("APPDATA"):
userdata = Path(os.getenv("APPDATA")) / "deemix"
elif sys.platform.startswith('darwin'):
userdata = homedata / 'Library' / 'Application Support' / 'deemix'
else:
userdata = homedata / '.config' / 'deemix'
if os.getenv("DEEMIX_MUSIC_DIR"):
musicdata = Path(os.getenv("DEEMIX_MUSIC_DIR"))
elif os.getenv("XDG_MUSIC_DIR"):
musicdata = Path(os.getenv("XDG_MUSIC_DIR")) / "deemix Music"
elif os.name == 'nt':
sub_key = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
music_guid = '{4BD8D571-6D19-48D3-BE97-422220080E43}'
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, sub_key) as key:
location = None
try: location = winreg.QueryValueEx(key, music_guid)[0]
except: pass
try: location = winreg.QueryValueEx(key, 'My Music')[0]
except: pass
if not location: location = homedata / "Music"
musicdata = Path(location) / "deemix Music"
else:
musicdata = homedata / "Music" / "deemix Music"
def checkPath(path):
if path == "": return ""
if not path.is_dir(): return ""
if not canWrite(path): return ""
return path
def getConfigFolder():
global userdata
if userdata != "": return userdata
if os.getenv("XDG_CONFIG_HOME") and userdata == "":
userdata = Path(os.getenv("XDG_CONFIG_HOME"))
userdata = checkPath(userdata)
if os.getenv("APPDATA") and userdata == "":
userdata = Path(os.getenv("APPDATA"))
userdata = checkPath(userdata)
if sys.platform.startswith('darwin') and userdata == "":
userdata = homedata / 'Library' / 'Application Support'
userdata = checkPath(userdata)
if userdata == "":
userdata = homedata / '.config'
userdata = checkPath(userdata)
if userdata == "": userdata = Path(os.getcwd()) / 'config'
else: userdata = userdata / 'deemix'
if os.getenv("DEEMIX_DATA_DIR"):
userdata = Path(os.getenv("DEEMIX_DATA_DIR"))
return userdata
def getMusicFolder():
global musicdata
if musicdata != "": return musicdata
if os.getenv("XDG_MUSIC_DIR") and musicdata == "":
musicdata = Path(os.getenv("XDG_MUSIC_DIR"))
musicdata = checkPath(musicdata)
if (homedata / '.config' / 'user-dirs.dirs').is_file() and musicdata == "":
with open(homedata / '.config' / 'user-dirs.dirs', 'r') as f:
userDirs = f.read()
musicdata = re.search(r"XDG_MUSIC_DIR=\"(.*)\"", userDirs).group(1)
musicdata = Path(os.path.expandvars(musicdata))
musicdata = checkPath(musicdata)
if os.name == 'nt' and musicdata == "":
musicKeys = ['My Music', '{4BD8D571-6D19-48D3-BE97-422220080E43}']
regData = os.popen(r'reg.exe query "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"').read().split('\r\n')
for i, line in enumerate(regData):
if line == "": continue
if i == 1: continue
line = line.split(' ')
if line[1] in musicKeys:
musicdata = Path(line[3])
break
musicdata = checkPath(musicdata)
if musicdata == "":
musicdata = homedata / 'Music'
musicdata = checkPath(musicdata)
if musicdata == "": musicdata = Path(os.getcwd()) / 'music'
else: musicdata = musicdata / 'deemix Music'
if os.getenv("DEEMIX_MUSIC_DIR"):
musicdata = Path(os.getenv("DEEMIX_MUSIC_DIR"))
return musicdata

View File

@ -21,14 +21,13 @@ def fixName(txt, char='_'):
txt = normalize("NFC", txt)
return txt
def fixEndOfData(bString):
try:
bString.decode()
return True
except:
return False
def fixLongName(name):
def fixEndOfData(bString):
try:
bString.decode()
return True
except Exception:
return False
if pathSep in name:
sepName = name.split(pathSep)
name = ""
@ -63,18 +62,29 @@ def pad(num, max_val, settings):
return str(num).zfill(paddingSize)
return str(num)
def generateFilename(track, settings, template):
filename = template or "%artist% - %title%"
return settingsRegex(filename, track, settings)
def generatePath(track, downloadObject, settings):
filenameTemplate = "%artist% - %title%"
singleTrack = False
if downloadObject.type == "track":
if settings['createSingleFolder']:
filenameTemplate = settings['albumTracknameTemplate']
else:
filenameTemplate = settings['tracknameTemplate']
singleTrack = True
elif downloadObject.type == "album":
filenameTemplate = settings['albumTracknameTemplate']
else:
filenameTemplate = settings['plyalistTracknameTemplate']
def generateFilepath(track, settings):
filepath = Path(settings['downloadLocation'])
filename = generateTrackName(filenameTemplate, track, settings)
filepath = Path(settings['downloadLocation'] or '.')
artistPath = None
coverPath = None
extrasPath = None
if settings['createPlaylistFolder'] and track.playlist and not settings['tags']['savePlaylistAsCompilation']:
filepath = filepath / settingsRegexPlaylist(settings['playlistNameTemplate'], track.playlist, settings)
filepath = filepath / generatePlaylistName(settings['playlistNameTemplate'], track.playlist, settings)
if track.playlist and not settings['tags']['savePlaylistAsCompilation']:
extrasPath = filepath
@ -84,61 +94,66 @@ 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 / generateArtistName(settings['artistNameTemplate'], track.album.mainArtist, settings, rootArtist=track.album.rootArtist)
artistPath = filepath
if (settings['createAlbumFolder'] and
(not track.singleDownload or (track.singleDownload and settings['createSingleFolder'])) and
(not singleTrack or (singleTrack and settings['createSingleFolder'])) and
(not track.playlist or
(track.playlist and settings['tags']['savePlaylistAsCompilation']) or
(track.playlist and settings['createStructurePlaylist'])
)
):
filepath = filepath / settingsRegexAlbum(settings['albumNameTemplate'], track.album, settings, track.playlist)
filepath = filepath / generateAlbumName(settings['albumNameTemplate'], track.album, settings, track.playlist)
coverPath = filepath
if not (track.playlist and not settings['tags']['savePlaylistAsCompilation']):
extrasPath = filepath
if not extrasPath: 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 singleTrack or (singleTrack and settings['createSingleFolder'])) and
(not track.playlist or
(track.playlist and settings['tags']['savePlaylistAsCompilation']) or
(track.playlist and settings['createStructurePlaylist'])
)
)
)):
filepath = filepath / f'CD{str(track.discNumber)}'
filepath = filepath / f'CD{track.discNumber}'
return (filepath, artistPath, coverPath, extrasPath)
# Remove subfolders from filename and add it to filepath
if pathSep in filename:
tempPath = filename[:filename.rfind(pathSep)]
filepath = filepath / tempPath
filename = filename[filename.rfind(pathSep) + len(pathSep):]
return (filename, filepath, artistPath, coverPath, extrasPath)
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("%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']))
def generateTrackName(filename, track, settings):
c = settings['illegalCharacterReplacer']
filename = filename.replace("%title%", fixName(track.title, c))
filename = filename.replace("%artist%", fixName(track.mainArtist.name, c))
filename = filename.replace("%artists%", fixName(", ".join(track.artists), c))
filename = filename.replace("%allartists%", fixName(track.artistsString, c))
filename = filename.replace("%mainartists%", fixName(track.mainArtistsString, c))
if track.featArtistsString:
filename = filename.replace("%featartists%", fixName('('+track.featArtistsString+')', settings['illegalCharacterReplacer']))
filename = filename.replace("%featartists%", fixName('('+track.featArtistsString+')', c))
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("%album%", fixName(track.album.title, c))
filename = filename.replace("%albumartist%", fixName(track.album.mainArtist.name, c))
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("%genre%",
fixName(track.album.genre[0], settings['illegalCharacterReplacer']))
filename = filename.replace("%genre%", fixName(track.album.genre[0], c))
else:
filename = filename.replace("%genre%", "Unknown")
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, c))
filename = filename.replace("%isrc%", track.ISRC)
filename = filename.replace("%upc%", track.album.barcode)
filename = filename.replace("%explicit%", "(Explicit)" if track.explicit else "")
@ -151,36 +166,37 @@ def settingsRegex(filename, track, settings):
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.position, track.album.trackTotal, settings))
filename = filename.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(filename))
def settingsRegexAlbum(foldername, album, settings, playlist=None):
def generateAlbumName(foldername, album, settings, playlist=None):
c = settings['illegalCharacterReplacer']
if playlist and settings['tags']['savePlaylistAsCompilation']:
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("%genre%", fixName(album.genre[0], c))
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("%album%", fixName(album.title, c))
foldername = foldername.replace("%artist%", fixName(album.mainArtist.name, c))
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%", fixName(album.rootArtist.name, c))
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%", fixName(album.mainArtist.name, c))
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("%type%", fixName(album.recordType.capitalize(), c))
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("%label%", fixName(album.label, c))
foldername = foldername.replace("%year%", str(album.date.year))
foldername = foldername.replace("%date%", album.dateString)
foldername = foldername.replace("%bitrate%", bitrateLabels[int(album.bitrate)])
@ -189,23 +205,25 @@ def settingsRegexAlbum(foldername, album, settings, playlist=None):
return antiDot(fixLongName(foldername))
def settingsRegexArtist(foldername, artist, settings, rootArtist=None):
foldername = foldername.replace("%artist%", fixName(artist.name, settings['illegalCharacterReplacer']))
def generateArtistName(foldername, artist, settings, rootArtist=None):
c = settings['illegalCharacterReplacer']
foldername = foldername.replace("%artist%", fixName(artist.name, c))
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%", fixName(rootArtist.name, c))
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%", fixName(artist.name, c))
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']))
def generatePlaylistName(foldername, playlist, settings):
c = settings['illegalCharacterReplacer']
foldername = foldername.replace("%playlist%", fixName(playlist.title, c))
foldername = foldername.replace("%playlist_id%", fixName(playlist.playlistID, c))
foldername = foldername.replace("%owner%", fixName(playlist.owner['name'], c))
foldername = foldername.replace("%owner_id%", str(playlist.owner['id']))
foldername = foldername.replace("%year%", str(playlist.date.year))
foldername = foldername.replace("%date%", str(playlist.dateString))
@ -213,12 +231,13 @@ def settingsRegexPlaylist(foldername, playlist, settings):
foldername = foldername.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(foldername))
def settingsRegexPlaylistFile(foldername, queueItem, settings):
foldername = foldername.replace("%title%", fixName(queueItem.title, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%artist%", fixName(queueItem.artist, settings['illegalCharacterReplacer']))
def generateDownloadObjectName(foldername, queueItem, settings):
c = settings['illegalCharacterReplacer']
foldername = foldername.replace("%title%", fixName(queueItem.title, c))
foldername = foldername.replace("%artist%", fixName(queueItem.artist, c))
foldername = foldername.replace("%size%", str(queueItem.size))
foldername = foldername.replace("%type%", fixName(queueItem.type, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%id%", fixName(queueItem.id, settings['illegalCharacterReplacer']))
foldername = foldername.replace("%type%", fixName(queueItem.type, c))
foldername = foldername.replace("%id%", fixName(queueItem.id, c))
foldername = foldername.replace("%bitrate%", bitrateLabels[int(queueItem.bitrate)])
foldername = foldername.replace('\\', pathSep).replace('/', pathSep).replace(pathSep, settings['illegalCharacterReplacer'])
foldername = foldername.replace('\\', pathSep).replace('/', pathSep).replace(pathSep, c)
return antiDot(fixLongName(foldername))