Merge refactoring (#4)

Removed saveDownloadQueue and tagsLanguage from lib settings

Revert embedded cover change

Fixed bitrate fallback check

Use overwriteFile setting when downloading embedded covers

Fixed bitrate fallback not working

Fixed some issues to make the lib work

Implemented spotify plugin back

Better handling of albums upcs

Fixed queue item not cancelling correctly

Code parity with deemix-js

Code cleanup with pylint

Even more rework on the library

More work on the library (WIP)

Total rework of the library (WIP)

Some rework done on types

Added start queue function

Made nextitem work on a thread

Removed dz as first parameter

Started queuemanager refactoring

Removed eventlet

Co-authored-by: RemixDev <RemixDev64@gmail.com>
Reviewed-on: https://git.freezer.life/RemixDev/deemix-py/pulls/4
Co-Authored-By: RemixDev <remixdev@noreply.localhost>
Co-Committed-By: RemixDev <remixdev@noreply.localhost>
This commit is contained in:
RemixDev
2021-06-27 16:29:41 -04:00
parent 67fcb7d37f
commit f530a4e89f
37 changed files with 2236 additions and 2463 deletions

View File

@ -1,41 +1,42 @@
import re
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)
def getBitrateInt(txt):
def getBitrateNumberFromText(txt):
txt = str(txt).lower()
if txt in ['flac', 'lossless', '9']:
return TrackFormats.FLAC
elif txt in ['mp3', '320', '3']:
if txt in ['mp3', '320', '3']:
return TrackFormats.MP3_320
elif txt in ['128', '1']:
if txt in ['128', '1']:
return TrackFormats.MP3_128
elif txt in ['360', '360_hq', '15']:
if txt in ['360', '360_hq', '15']:
return TrackFormats.MP4_RA3
elif txt in ['360_mq', '14']:
if txt in ['360_mq', '14']:
return TrackFormats.MP4_RA2
elif txt in ['360_lq', '13']:
if txt in ['360_lq', '13']:
return TrackFormats.MP4_RA1
else:
return None
def changeCase(str, type):
if type == "lower":
return str.lower()
elif type == "upper":
return str.upper()
elif type == "start":
return string.capwords(str)
elif type == "sentence":
return str.capitalize()
else:
return str
return None
def changeCase(txt, case_type):
if case_type == "lower":
return txt.lower()
if case_type == "upper":
return txt.upper()
if case_type == "start":
return string.capwords(txt)
if case_type == "sentence":
return txt.capitalize()
return str
def removeFeatures(title):
clean = title
@ -48,7 +49,6 @@ def removeFeatures(title):
clean = ' '.join(clean.split())
return clean
def andCommaConcat(lst):
tot = len(lst)
result = ""
@ -61,62 +61,6 @@ def andCommaConcat(lst):
result += ", "
return result
def getIDFromLink(link, type):
if '?' in link:
link = link[:link.find('?')]
if link.endswith("/"):
link = link[:-1]
if link.startswith("http") and 'open.spotify.com/' in link:
if '&' in link: link = link[:link.find('&')]
if type == "spotifyplaylist":
return link[link.find("/playlist/") + 10:]
if type == "spotifytrack":
return link[link.find("/track/") + 7:]
if type == "spotifyalbum":
return link[link.find("/album/") + 7:]
elif link.startswith("spotify:"):
if type == "spotifyplaylist":
return link[link.find("playlist:") + 9:]
if type == "spotifytrack":
return link[link.find("track:") + 6:]
if type == "spotifyalbum":
return link[link.find("album:") + 6:]
elif type == "artisttop":
return re.search(r"\/artist\/(\d+)\/top_track", link)[1]
elif type == "artistdiscography":
return re.search(r"\/artist\/(\d+)\/discography", link)[1]
else:
return link[link.rfind("/") + 1:]
def getTypeFromLink(link):
type = ''
if 'spotify' in link:
type = 'spotify'
if 'playlist' in link:
type += 'playlist'
elif 'track' in link:
type += 'track'
elif 'album' in link:
type += 'album'
elif 'deezer' in link:
if '/track' in link:
type = 'track'
elif '/playlist' in link:
type = 'playlist'
elif '/album' in link:
type = 'album'
elif re.search("\/artist\/(\d+)\/top_track", link):
type = 'artisttop'
elif re.search("\/artist\/(\d+)\/discography", link):
type = 'artistdiscography'
elif '/artist' in link:
type = 'artist'
return type
def uniqueArray(arr):
for iPrinc, namePrinc in enumerate(arr):
for iRest, nRest in enumerate(arr):
@ -129,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)

View File

@ -1,31 +0,0 @@
import binascii
from Cryptodome.Cipher import Blowfish, AES
from Cryptodome.Hash import MD5
def _md5(data):
h = MD5.new()
h.update(str.encode(data) if isinstance(data, str) else data)
return h.hexdigest()
def generateBlowfishKey(trackId):
SECRET = 'g4el58wc' + '0zvf9na1'
idMd5 = _md5(trackId)
bfKey = ""
for i in range(16):
bfKey += chr(ord(idMd5[i]) ^ ord(idMd5[i + 16]) ^ ord(SECRET[i]))
return bfKey
def generateStreamURL(sng_id, md5, media_version, format):
urlPart = b'\xa4'.join(
[str.encode(md5), str.encode(str(format)), str.encode(str(sng_id)), str.encode(str(media_version))])
md5val = _md5(urlPart)
step2 = str.encode(md5val) + b'\xa4' + urlPart + b'\xa4'
step2 = step2 + (b'.' * (16 - (len(step2) % 16)))
urlPart = binascii.hexlify(AES.new(b'jo6aey6haid2Teih', AES.MODE_ECB).encrypt(step2))
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/mobile/1/" + urlPart.decode("utf-8")
def reverseStreamURL(url):
urlPart = url[42:]
step2 = AES.new(b'jo6aey6haid2Teih', AES.MODE_ECB).decrypt(binascii.unhexlify(urlPart.encode("utf-8")))
(md5val, md5, format, sng_id, media_version, _) = step2.split(b'\xa4')
return (sng_id.decode('utf-8'), md5.decode('utf-8'), media_version.decode('utf-8'), format.decode('utf-8'))

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,44 +1,72 @@
from pathlib import Path
import sys
import os
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':
import winreg
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 = ""
@ -52,30 +51,40 @@ def antiDot(string):
return string
def pad(num, max, settings):
def pad(num, max_val, settings):
if int(settings['paddingSize']) == 0:
paddingSize = len(str(max))
paddingSize = len(str(max_val))
else:
paddingSize = len(str(10 ** (int(settings['paddingSize']) - 1)))
if paddingSize == 1:
paddingSize = 2
if settings['padTracks']:
return str(num).zfill(paddingSize)
return str(num)
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:
return str(num)
filenameTemplate = settings['playlistTracknameTemplate']
def generateFilename(track, settings, template):
filename = template or "%artist% - %title%"
return settingsRegex(filename, track, settings)
filename = generateTrackName(filenameTemplate, track, settings)
def generateFilepath(track, settings):
filepath = Path(settings['downloadLocation'])
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
@ -85,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 "")
@ -148,40 +162,41 @@ def settingsRegex(filename, track, settings):
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("%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.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("%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)])
@ -190,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))
@ -214,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))

View File

@ -1,212 +0,0 @@
from mutagen.flac import FLAC, Picture
from mutagen.id3 import ID3, ID3NoHeaderError, \
TXXX, TIT2, TPE1, TALB, TPE2, TRCK, TPOS, TCON, TYER, TDAT, TLEN, TBPM, \
TPUB, TSRC, USLT, SYLT, APIC, IPLS, TCOM, TCOP, TCMP, Encoding, PictureType
# Adds tags to a MP3 file
def tagID3(stream, track, save):
# Delete exsisting tags
try:
tag = ID3(stream)
tag.delete()
except ID3NoHeaderError:
tag = ID3()
if save['title']:
tag.add(TIT2(text=track.title))
if save['artist'] and len(track.artists):
if save['multiArtistSeparator'] == "default":
tag.add(TPE1(text=track.artists))
else:
if save['multiArtistSeparator'] == "nothing":
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
# https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html#artists
tag.add(TXXX(desc="ARTISTS", text=track.artists))
if save['album']:
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))
else:
tag.add(TPE2(text=track.album.artists))
if save['trackNumber']:
trackNumber = str(track.trackNumber)
if save['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)
tag.add(TPOS(text=discNumber))
if save['genre']:
tag.add(TCON(text=track.album.genre))
if save['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)))
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))
if save['isrc']:
tag.add(TSRC(text=track.ISRC))
if save['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']:
# 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))
involved_people = []
for role in track.contributors:
if role in ['author', 'engineer', 'mixer', 'producer', 'writer']:
for person in track.contributors[role]:
involved_people.append([role, person])
elif role == 'composer' and save['composer']:
tag.add(TCOM(text=track.contributors['composer']))
if len(involved_people) > 0 and save['involvedPeople']:
tag.add(IPLS(people=involved_people))
if save['copyright']:
tag.add(TCOP(text=track.copyright))
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:
descEncoding = Encoding.LATIN1
if save['coverDescriptionUTF8']:
descEncoding = Encoding.UTF8
mimeType = 'image/jpeg'
if str(track.album.embeddedCoverPath).endswith('png'):
mimeType = 'image/png'
with open(track.album.embeddedCoverPath, 'rb') as f:
tag.add(APIC(descEncoding, mimeType, PictureType.COVER_FRONT, desc='cover', data=f.read()))
tag.save( stream,
v1=2 if save['saveID3v1'] else 0,
v2_version=3,
v23_sep=None if save['useNullSeparator'] else '/' )
# Adds tags to a FLAC file
def tagFLAC(stream, track, save):
# Delete exsisting tags
tag = FLAC(stream)
tag.delete()
tag.clear_pictures()
if save['title']:
tag["TITLE"] = track.title
if save['artist'] and len(track.artists):
if save['multiArtistSeparator'] == "default":
tag["ARTIST"] = track.artists
else:
if save['multiArtistSeparator'] == "nothing":
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
# https://picard-docs.musicbrainz.org/en/technical/tag_mapping.html#artists
tag["ARTISTS"] = track.artists
if save['album']:
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
else:
tag["ALBUMARTIST"] = track.album.artists
if save['trackNumber']:
tag["TRACKNUMBER"] = str(track.trackNumber)
if save['trackTotal']:
tag["TRACKTOTAL"] = str(track.album.trackTotal)
if save['discNumber']:
tag["DISCNUMBER"] = str(track.discNumber)
if save['discTotal']:
tag["DISCTOTAL"] = str(track.album.discTotal)
if save['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
# Reference: https://www.xiph.org/vorbis/doc/v-comment.html#fieldnames
if save['date']:
tag["DATE"] = track.dateString
elif save['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
if save['isrc']:
tag["ISRC"] = track.ISRC
if save['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
for role in track.contributors:
if role in ['author', 'engineer', 'mixer', 'producer', 'writer', 'composer']:
if save['involvedPeople'] and role != 'composer' or save['composer'] and role == 'composer':
tag[role] = track.contributors[role]
elif role == 'musicpublisher' and save['involvedPeople']:
tag["ORGANIZATION"] = track.contributors['musicpublisher']
if save['copyright']:
tag["COPYRIGHT"] = track.copyright
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:
image = Picture()
image.type = PictureType.COVER_FRONT
image.mime = 'image/jpeg'
if str(track.album.embeddedCoverPath).endswith('png'):
image.mime = 'image/png'
with open(track.album.embeddedCoverPath, 'rb') as f:
image.data = f.read()
tag.add_picture(image)
tag.save(deleteid3=True)