Merge branch 'master' of TrDex/deemix into master

This commit is contained in:
RemixDev 2020-04-17 10:49:33 +00:00 committed by Gogs
commit a3d42b82b3
14 changed files with 1969 additions and 1837 deletions

3
.gitignore vendored
View File

@ -6,6 +6,7 @@ __pycache__
/dist
# local env files
/venv/
.env.local
.env.*.local
@ -21,4 +22,4 @@ yarn-error.log*
*.ntvs*
*.njsproj
*.sln
*.sw?
*.sw?

View File

@ -1,16 +1,19 @@
#!/usr/bin/env python3
import click
import deemix.app.cli as app
from deemix.app.settings import initSettings
@click.command()
@click.option('-b', '--bitrate', default=None, help='Overwrites the default bitrate selected')
@click.argument('url')
def download(bitrate, url):
settings = initSettings()
app.login()
app.downloadLink(url, settings, bitrate)
click.echo("All done!")
settings = initSettings()
app.login()
app.downloadLink(url, settings, bitrate)
click.echo("All done!")
if __name__ == '__main__':
download()
download()

View File

@ -1,2 +1,2 @@
#!/usr/bin/env python3
#Empty File
# Empty File

View File

@ -1,332 +1,343 @@
#!/usr/bin/env python3
import binascii
import time
import requests
from Cryptodome.Cipher import Blowfish, AES
from Cryptodome.Hash import MD5
from Cryptodome.Util.Padding import pad
from Cryptodome.Cipher import Blowfish, AES
import requests
import time
USER_AGENT_HEADER = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"
USER_AGENT_HEADER = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " \
"Chrome/79.0.3945.130 Safari/537.36"
class Deezer:
def __init__(self):
self.api_url = "http://www.deezer.com/ajax/gw-light.php"
self.legacy_api_url = "https://api.deezer.com/"
self.http_headers = {
"User-Agent": USER_AGENT_HEADER
}
self.album_pictures_host = "https://e-cdns-images.dzcdn.net/images/cover/"
self.artist_pictures_host = "https://e-cdns-images.dzcdn.net/images/artist/"
self.user = {}
self.session = requests.Session()
self.logged_in = False
self.session.post("http://www.deezer.com/", headers=self.http_headers)
self.sid = self.session.cookies.get('sid')
def __init__(self):
self.api_url = "http://www.deezer.com/ajax/gw-light.php"
self.legacy_api_url = "https://api.deezer.com/"
self.http_headers = {
"User-Agent": USER_AGENT_HEADER
}
self.album_pictures_host = "https://e-cdns-images.dzcdn.net/images/cover/"
self.artist_pictures_host = "https://e-cdns-images.dzcdn.net/images/artist/"
self.user = {}
self.session = requests.Session()
self.logged_in = False
self.session.post("http://www.deezer.com/", headers=self.http_headers)
self.sid = self.session.cookies.get('sid')
def get_token(self):
token_data = self.gw_api_call('deezer.getUserData')
return token_data["results"]["checkForm"]
def get_token(self):
token_data = self.gw_api_call('deezer.getUserData')
return token_data["results"]["checkForm"]
def get_track_md5(self, sng_id):
try:
site = self.session.post(
"https://api.deezer.com/1.0/gateway.php",
params={
'api_key': "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE",
'sid': self.sid,
'input': '3',
'output': '3',
'method': 'song_getData'
},
timeout=30,
json={'sng_id': sng_id},
headers=self.http_headers
)
except:
time.sleep(2)
return self.get_track_md5(sng_id)
response = site.json()
return response['results']['PUID']
def get_track_md5(self, sng_id):
try:
site = self.session.post(
"https://api.deezer.com/1.0/gateway.php",
params={
'api_key': "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE",
'sid': self.sid,
'input': '3',
'output': '3',
'method': 'song_getData'
},
timeout=30,
json={'sng_id': sng_id},
headers=self.http_headers
)
except:
time.sleep(2)
return self.get_track_md5(sng_id)
response = site.json()
return response['results']['PUID']
def gw_api_call(self, method, args={}):
try:
result = self.session.post(
self.api_url,
params={
'api_version': "1.0",
'api_token': 'null' if method == 'deezer.getUserData' else self.get_token(),
'input': '3',
'method': method
},
timeout=30,
json=args,
headers=self.http_headers
)
except:
time.sleep(2)
return self.gw_api_call(method, args)
return result.json()
def gw_api_call(self, method, args=None):
if args is None:
args = {}
try:
result = self.session.post(
self.api_url,
params={
'api_version': "1.0",
'api_token': 'null' if method == 'deezer.getUserData' else self.get_token(),
'input': '3',
'method': method
},
timeout=30,
json=args,
headers=self.http_headers
)
except:
time.sleep(2)
return self.gw_api_call(method, args)
return result.json()
def api_call(self, method, args={}):
try:
result = self.session.get(
self.legacy_api_url + method,
params=args,
headers=self.http_headers,
timeout=30
)
result_json = result.json()
except:
time.sleep(2)
return self.api_call(method, args)
if 'error' in result_json.keys():
raise APIError()
return result_json
def api_call(self, method, args=None):
if args is None:
args = {}
try:
result = self.session.get(
self.legacy_api_url + method,
params=args,
headers=self.http_headers,
timeout=30
)
result_json = result.json()
except:
time.sleep(2)
return self.api_call(method, args)
if 'error' in result_json.keys():
raise APIError()
return result_json
def login(self, email, password, re_captcha_token):
check_form_login = self.gw_api_call("deezer.getUserData")
login = self.session.post(
"https://www.deezer.com/ajax/action.php",
data={
'type': 'login',
'mail': email,
'password': password,
'checkFormLogin': check_form_login['results']['checkFormLogin'],
'reCaptchaToken': re_captcha_token
},
headers={'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', **self.http_headers}
)
if 'success' not in login.text:
self.logged_in = False
return False
user_data = self.gw_api_call("deezer.getUserData")
self.user = {
'email': email,
'id': user_data["results"]["USER"]["USER_ID"],
'name': user_data["results"]["USER"]["BLOG_NAME"],
'picture': user_data["results"]["USER"]["USER_PICTURE"] if "USER_PICTURE" in user_data["results"][
"USER"] else ""
}
self.logged_in = True
return True
def login(self, email, password, re_captcha_token):
check_form_login = self.gw_api_call("deezer.getUserData")
login = self.session.post(
"https://www.deezer.com/ajax/action.php",
data={
'type': 'login',
'mail': email,
'password': password,
'checkFormLogin': check_form_login['results']['checkFormLogin'],
'reCaptchaToken': re_captcha_token
},
headers={'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', **self.http_headers}
)
if 'success' not in login.text:
self.logged_in = False
return False
user_data = self.gw_api_call("deezer.getUserData")
self.user = {
'email': email,
'id': user_data["results"]["USER"]["USER_ID"],
'name': user_data["results"]["USER"]["BLOG_NAME"],
'picture': user_data["results"]["USER"]["USER_PICTURE"] if "USER_PICTURE" in user_data["results"][
"USER"] else ""
}
self.logged_in = True
return True
def login_via_arl(self, arl):
cookie_obj = requests.cookies.create_cookie(
domain='deezer.com',
name='arl',
value=arl,
path="/",
rest={'HttpOnly': True}
)
self.session.cookies.set_cookie(cookie_obj)
user_data = self.gw_api_call("deezer.getUserData")
if user_data["results"]["USER"]["USER_ID"] == 0:
self.logged_in = False
return 0
self.user = {
'id': user_data["results"]["USER"]["USER_ID"],
'name': user_data["results"]["USER"]["BLOG_NAME"],
'picture': user_data["results"]["USER"]["USER_PICTURE"] if "USER_PICTURE" in user_data["results"][
"USER"] else ""
}
self.logged_in = True
return 1
def login_via_arl(self, arl):
cookie_obj = requests.cookies.create_cookie(
domain='deezer.com',
name='arl',
value=arl,
path="/",
rest={'HttpOnly': True}
)
self.session.cookies.set_cookie(cookie_obj)
user_data = self.gw_api_call("deezer.getUserData")
if user_data["results"]["USER"]["USER_ID"] == 0:
self.logged_in = False
return 0
self.user = {
'id': user_data["results"]["USER"]["USER_ID"],
'name': user_data["results"]["USER"]["BLOG_NAME"],
'picture': user_data["results"]["USER"]["USER_PICTURE"] if "USER_PICTURE" in user_data["results"][
"USER"] else ""
}
self.logged_in = True
return 1
def get_track_gw(self, sng_id):
if int(sng_id) < 0:
body = self.gw_api_call('song.getData', {'sng_id': sng_id})
else:
body = self.gw_api_call('deezer.pageTrack', {'sng_id': sng_id})
if 'LYRICS' in body['results']:
body['results']['DATA']['LYRICS'] = body['results']['LYRICS']
body['results'] = body['results']['DATA']
return body['results']
def get_track_gw(self, sng_id):
if int(sng_id) < 0:
body = self.gw_api_call('song.getData', {'sng_id': sng_id})
else:
body = self.gw_api_call('deezer.pageTrack', {'sng_id': sng_id})
if 'LYRICS' in body['results']:
body['results']['DATA']['LYRICS'] = body['results']['LYRICS']
body['results'] = body['results']['DATA']
return body['results']
def get_tracks_gw(self, ids):
tracks_array = []
body = self.gw_api_call('song.getListData', {'sng_ids': ids})
errors = 0
for i in range(len(ids)):
if ids[i] != 0:
tracks_array.append(body['results']['data'][i - errors])
else:
errors += 1
tracks_array.append({
'SNG_ID': 0,
'SNG_TITLE': '',
'DURATION': 0,
'MD5_ORIGIN': 0,
'MEDIA_VERSION': 0,
'FILESIZE': 0,
'ALB_TITLE': "",
'ALB_PICTURE': "",
'ART_ID': 0,
'ART_NAME': ""
})
return tracks_array
def get_tracks_gw(self, ids):
tracks_array = []
body = self.gw_api_call('song.getListData', {'sng_ids': ids})
errors = 0
for i in range(len(ids)):
if ids[i] != 0:
tracks_array.append(body['results']['data'][i - errors])
else:
errors += 1
tracks_array.append({
'SNG_ID': 0,
'SNG_TITLE': '',
'DURATION': 0,
'MD5_ORIGIN': 0,
'MEDIA_VERSION': 0,
'FILESIZE': 0,
'ALB_TITLE': "",
'ALB_PICTURE': "",
'ART_ID': 0,
'ART_NAME': ""
})
return tracks_array
def get_album_gw(self, alb_id):
return self.gw_api_call('album.getData', {'alb_id': alb_id})['results']
def get_album_gw(self, alb_id):
return self.gw_api_call('album.getData', {'alb_id': alb_id})['results']
def get_album_tracks_gw(self, alb_id):
tracks_array = []
body = self.gw_api_call('song.getListByAlbum', {'alb_id': alb_id, 'nb': -1})
for track in body['results']['data']:
_track = track
_track['position'] = body['results']['data'].index(track)
tracks_array.append(_track)
return tracks_array
def get_album_tracks_gw(self, alb_id):
tracks_array = []
body = self.gw_api_call('song.getListByAlbum', {'alb_id': alb_id, 'nb': -1})
for track in body['results']['data']:
_track = track
_track['position'] = body['results']['data'].index(track)
tracks_array.append(_track)
return tracks_array
def get_artist_gw(self, art_id):
return self.gw_api_call('deezer.pageArtist', {'art_id': art_id})
def get_artist_gw(self, art_id):
return self.gw_api_call('deezer.pageArtist', {'art_id': art_id})
def get_playlist_gw(self, playlist_id):
return self.gw_api_call('deezer.pagePlaylist', {'playlist_id': playlist_id})
def get_playlist_gw(self, playlist_id):
return self.gw_api_call('deezer.pagePlaylist', {'playlist_id': playlist_id})
def get_playlist_tracks_gw(self, playlist_id):
tracks_array = []
body = self.gw_api_call('playlist.getSongs', {'playlist_id': playlist_id, 'nb': -1})
for track in body['results']['data']:
track['position'] = body['results']['data'].index(track)
tracks_array.append(track)
return tracks_array
def get_playlist_tracks_gw(self, playlist_id):
tracks_array = []
body = self.gw_api_call('playlist.getSongs', {'playlist_id': playlist_id, 'nb': -1})
for track in body['results']['data']:
track['position'] = body['results']['data'].index(track)
tracks_array.append(track)
return tracks_array
def get_artist_toptracks_gw(self, art_id):
tracks_array = []
body = self.gw_api_call('artist.getTopTrack', {'art_id': art_id, 'nb': 100})
for track in body['results']['data']:
track['position'] = body['results']['data'].index(track)
tracks_array.append(track)
return tracks_array
def get_artist_toptracks_gw(self, art_id):
tracks_array = []
body = self.gw_api_call('artist.getTopTrack', {'art_id': art_id, 'nb': 100})
for track in body['results']['data']:
track['position'] = body['results']['data'].index(track)
tracks_array.append(track)
return tracks_array
def search_main_gw(self, term):
results = self.gw_api_call('deezer.pageSearch', {"query": term, "start": 0, "nb": 40, "suggest": True, "artist_suggest": True, "top_tracks": True})['results']
order = []
for x in results['ORDER']:
if x in ['TOP_RESULT', 'TRACK', 'ALBUM', 'ARTIST', 'PLAYLIST']:
order.append(x)
results['ORDER'] = order
return results
def search_main_gw(self, term):
results = self.gw_api_call('deezer.pageSearch',
{"query": term, "start": 0, "nb": 40, "suggest": True, "artist_suggest": True,
"top_tracks": True})['results']
order = []
for x in results['ORDER']:
if x in ['TOP_RESULT', 'TRACK', 'ALBUM', 'ARTIST', 'PLAYLIST']:
order.append(x)
results['ORDER'] = order
return results
def search_gw(self, term, type, start, nb=20):
return self.gw_api_call('search.music', {"query": term, "filter":"ALL", "output":type, "start": start, "nb": nb})['results']
def search_gw(self, term, type, start, nb=20):
return \
self.gw_api_call('search.music',
{"query": term, "filter": "ALL", "output": type, "start": start, "nb": nb})[
'results']
def get_lyrics_gw(self, sng_id):
return self.gw_api_call('song.getLyrics', {'sng_id': sng_id})["results"]
def get_lyrics_gw(self, sng_id):
return self.gw_api_call('song.getLyrics', {'sng_id': sng_id})["results"]
def get_user_playlist(self, user_id):
return self.api_call('user/' + str(user_id) + '/playlists', {'limit': -1})
def get_user_playlist(self, user_id):
return self.api_call('user/' + str(user_id) + '/playlists', {'limit': -1})
def get_track(self, user_id):
return self.api_call('track/' + str(user_id))
def get_track(self, user_id):
return self.api_call('track/' + str(user_id))
def get_track_by_ISRC(self, isrc):
return self.api_call('track/isrc:' + isrc)
def get_track_by_ISRC(self, isrc):
return self.api_call('track/isrc:' + isrc)
def get_charts_top_country(self):
return self.get_user_playlist('637006841')
def get_charts_top_country(self):
return self.get_user_playlist('637006841')
def get_playlist(self, playlist_id):
return self.api_call('playlist/' + str(playlist_id))
def get_playlist(self, playlist_id):
return self.api_call('playlist/' + str(playlist_id))
def get_playlist_tracks(self, playlist_id):
return self.api_call('playlist/' + str(playlist_id) + '/tracks', {'limit': -1})
def get_playlist_tracks(self, playlist_id):
return self.api_call('playlist/' + str(playlist_id) + '/tracks', {'limit': -1})
def get_album(self, album_id):
return self.api_call('album/' + str(album_id))
def get_album(self, album_id):
return self.api_call('album/' + str(album_id))
def get_album_by_UPC(self, upc):
return self.api_call('album/upc:' + str(upc))
def get_album_by_UPC(self, upc):
return self.api_call('album/upc:' + str(upc))
def get_album_tracks(self, album_id):
return self.api_call('album/' + str(album_id) + '/tracks', {'limit': -1})
def get_album_tracks(self, album_id):
return self.api_call('album/' + str(album_id) + '/tracks', {'limit': -1})
def get_artist(self, artist_id):
return self.api_call('artist/' + str(artist_id))
def get_artist(self, artist_id):
return self.api_call('artist/' + str(artist_id))
def get_artist_albums(self, artist_id):
return self.api_call('artist/' + str(artist_id) + '/albums', {'limit': -1})
def get_artist_albums(self, artist_id):
return self.api_call('artist/' + str(artist_id) + '/albums', {'limit': -1})
def search(self, term, search_type, limit=30):
return self.api_call('search/' + search_type, {'q': term, 'limit': limit})
def search(self, term, search_type, limit=30):
return self.api_call('search/' + search_type, {'q': term, 'limit': limit})
def decrypt_track(self, track_id, input, output):
response = open(input, 'rb')
outfile = open(output, 'wb')
blowfish_key = str.encode(self._get_blowfish_key(str(track_id)))
i = 0
while True:
chunk = response.read(2048)
if not chunk:
break
if (i % 3) == 0 and len(chunk) == 2048:
chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(chunk)
outfile.write(chunk)
i += 1
def decrypt_track(self, track_id, input, output):
response = open(input, 'rb')
outfile = open(output, 'wb')
blowfish_key = str.encode(self._get_blowfish_key(str(track_id)))
i = 0
while True:
chunk = response.read(2048)
if not chunk:
break
if (i % 3) == 0 and len(chunk) == 2048:
chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(
chunk)
outfile.write(chunk)
i += 1
def stream_track(self, track_id, url, stream):
try:
request = requests.get(url, headers=self.http_headers, stream=True, timeout=30)
except:
time.sleep(2)
return self.stream_track(track_id, url, stream)
request.raise_for_status()
blowfish_key = str.encode(self._get_blowfish_key(str(track_id)))
i = 0
for chunk in request.iter_content(2048):
if (i % 3) == 0 and len(chunk) == 2048:
chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(chunk)
stream.write(chunk)
i += 1
def stream_track(self, track_id, url, stream):
try:
request = requests.get(url, headers=self.http_headers, stream=True, timeout=30)
except:
time.sleep(2)
return self.stream_track(track_id, url, stream)
request.raise_for_status()
blowfish_key = str.encode(self._get_blowfish_key(str(track_id)))
i = 0
for chunk in request.iter_content(2048):
if (i % 3) == 0 and len(chunk) == 2048:
chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(
chunk)
stream.write(chunk)
i += 1
def _md5(self, data):
h = MD5.new()
h.update(str.encode(data) if isinstance(data, str) else data)
return h.hexdigest()
def _md5(self, data):
h = MD5.new()
h.update(str.encode(data) if isinstance(data, str) else data)
return h.hexdigest()
def _get_blowfish_key(self, trackId):
SECRET = 'g4el58wc' + '0zvf9na1'
idMd5 = self._md5(trackId)
bfKey = ""
for i in range(16):
bfKey += chr(ord(idMd5[i]) ^ ord(idMd5[i + 16]) ^ ord(SECRET[i]))
return bfKey
def _get_blowfish_key(self, trackId):
SECRET = 'g4el58wc' + '0zvf9na1'
idMd5 = self._md5(trackId)
bfKey = ""
for i in range(16):
bfKey += chr(ord(idMd5[i]) ^ ord(idMd5[i + 16]) ^ ord(SECRET[i]))
return bfKey
def get_track_stream_url(self, 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 = self._md5(urlPart)
step2 = str.encode(md5val) + b'\xa4' + urlPart + b'\xa4'
step2 = pad(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 get_track_stream_url(self, 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 = self._md5(urlPart)
step2 = str.encode(md5val) + b'\xa4' + urlPart + b'\xa4'
step2 = pad(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 get_track_from_metadata(self, artist, track, album):
artist = artist.replace("","-").replace("", "'")
track = track.replace("","-").replace("", "'")
album = album.replace("","-").replace("", "'")
def get_track_from_metadata(self, artist, track, album):
artist = artist.replace("", "-").replace("", "'")
track = track.replace("", "-").replace("", "'")
album = album.replace("", "-").replace("", "'")
resp = self.search(f'artist:"{artist}" track:"{track}" album:"{album}"', "track", 1)
if len(resp['data'])>0:
return resp['data'][0]['id']
resp = self.search(f'artist:"{artist}" track:"{track}"', "track", 1)
if len(resp['data'])>0:
return resp['data'][0]['id']
if "(" in track and ")" in track and track.find("(") < track.find(")"):
resp = self.search(f'artist:"{artist}" track:"{track[:track.find("(")]}"', "track", 1)
if len(resp['data'])>0:
return resp['data'][0]['id']
elif " - " in track:
resp = self.search(f'artist:"{artist}" track:"{track[:track.find(" - ")]}"', "track", 1)
if len(resp['data'])>0:
return resp['data'][0]['id']
else:
return 0
return 0
resp = self.search(f'artist:"{artist}" track:"{track}" album:"{album}"', "track", 1)
if len(resp['data']) > 0:
return resp['data'][0]['id']
resp = self.search(f'artist:"{artist}" track:"{track}"', "track", 1)
if len(resp['data']) > 0:
return resp['data'][0]['id']
if "(" in track and ")" in track and track.find("(") < track.find(")"):
resp = self.search(f'artist:"{artist}" track:"{track[:track.find("(")]}"', "track", 1)
if len(resp['data']) > 0:
return resp['data'][0]['id']
elif " - " in track:
resp = self.search(f'artist:"{artist}" track:"{track[:track.find(" - ")]}"', "track", 1)
if len(resp['data']) > 0:
return resp['data'][0]['id']
else:
return 0
return 0
class APIError(Exception):
pass
pass

View File

@ -1,36 +1,38 @@
#!/usr/bin/env python3
from deemix.api.deezer import Deezer
import deemix.utils.localpaths as localpaths
from deemix.utils.misc import getIDFromLink, getTypeFromLink, getBitrateInt
from deemix.app.queuemanager import addToQueue
from deemix.app.spotify import SpotifyHelper
from os import system as execute
import os.path as path
from os import mkdir
import deemix.utils.localpaths as localpaths
from deemix.api.deezer import Deezer
from deemix.app.queuemanager import addToQueue
from deemix.app.spotify import SpotifyHelper
dz = Deezer()
sp = SpotifyHelper()
def requestValidArl():
while True:
arl = input("Paste here your arl:")
if dz.login_via_arl(arl):
break
return arl
while True:
arl = input("Paste here your arl:")
if dz.login_via_arl(arl):
break
return arl
def login():
configFolder = localpaths.getConfigFolder()
if not path.isdir(configFolder):
mkdir(configFolder)
if path.isfile(path.join(configFolder, '.arl')):
with open(path.join(configFolder, '.arl'), 'r') as f:
arl = f.read()
if not dz.login_via_arl(arl):
arl = requestValidArl()
else:
arl = requestValidArl()
with open(path.join(configFolder, '.arl'), 'w') as f:
f.write(arl)
configFolder = localpaths.getConfigFolder()
if not path.isdir(configFolder):
mkdir(configFolder)
if path.isfile(path.join(configFolder, '.arl')):
with open(path.join(configFolder, '.arl'), 'r') as f:
arl = f.read()
if not dz.login_via_arl(arl):
arl = requestValidArl()
else:
arl = requestValidArl()
with open(path.join(configFolder, '.arl'), 'w') as f:
f.write(arl)
def downloadLink(url, settings, bitrate=None):
addToQueue(dz, sp, url, settings, bitrate)
addToQueue(dz, sp, url, settings, bitrate)

View File

@ -1,69 +1,69 @@
{
"downloadLocation": "",
"tracknameTemplate": "%artist% - %title%",
"albumTracknameTemplate": "%tracknumber% - %title%",
"playlistTracknameTemplate": "%position% - %artist% - %title%",
"createPlaylistFolder": true,
"playlistNameTemplate": "%playlist%",
"createArtistFolder": false,
"artistNameTemplate": "%artist%",
"createAlbumFolder": true,
"albumNameTemplate": "%artist% - %album%",
"createCDFolder": true,
"createStructurePlaylist": false,
"createSingleFolder": false,
"padTracks": true,
"paddingSize": "0",
"illegalCharacterReplacer": "_",
"queueConcurrency": 3,
"maxBitrate": "3",
"fallbackBitrate": true,
"fallbackSearch": false,
"logErrors": true,
"logSearched": false,
"createM3U8File": false,
"syncedLyrics": false,
"embeddedArtworkSize": 800,
"localArtworkSize": 1400,
"saveArtwork": true,
"coverImageTemplate": "cover",
"saveArtworkArtist": false,
"artistImageTemplate": "folder",
"PNGcovers": false,
"jpegImageQuality": 80,
"dateFormat": "Y-M-D",
"removeAlbumVersion": false,
"featuredToTitle": "0",
"titleCasing": "nothing",
"artistCasing": "nothing",
"executeCommand": "",
"tags": {
"title": true,
"artist": true,
"album": true,
"cover": true,
"trackNumber": true,
"trackTotal": false,
"discNumber": true,
"discTotal": false,
"albumArtist": true,
"genre": true,
"year": true,
"date": true,
"explicit": false,
"isrc": true,
"length": true,
"barcode": true,
"bpm": true,
"replayGain": false,
"label": true,
"lyrics": false,
"copyright": false,
"composer": false,
"involvedPeople": false,
"savePlaylistAsCompilation": false,
"useNullSeparator": false,
"saveID3v1": true,
"multitagSeparator": "default"
}
"downloadLocation": "",
"tracknameTemplate": "%artist% - %title%",
"albumTracknameTemplate": "%tracknumber% - %title%",
"playlistTracknameTemplate": "%position% - %artist% - %title%",
"createPlaylistFolder": true,
"playlistNameTemplate": "%playlist%",
"createArtistFolder": false,
"artistNameTemplate": "%artist%",
"createAlbumFolder": true,
"albumNameTemplate": "%artist% - %album%",
"createCDFolder": true,
"createStructurePlaylist": false,
"createSingleFolder": false,
"padTracks": true,
"paddingSize": "0",
"illegalCharacterReplacer": "_",
"queueConcurrency": 3,
"maxBitrate": "3",
"fallbackBitrate": true,
"fallbackSearch": false,
"logErrors": true,
"logSearched": false,
"createM3U8File": false,
"syncedLyrics": false,
"embeddedArtworkSize": 800,
"localArtworkSize": 1400,
"saveArtwork": true,
"coverImageTemplate": "cover",
"saveArtworkArtist": false,
"artistImageTemplate": "folder",
"PNGcovers": false,
"jpegImageQuality": 80,
"dateFormat": "Y-M-D",
"removeAlbumVersion": false,
"featuredToTitle": "0",
"titleCasing": "nothing",
"artistCasing": "nothing",
"executeCommand": "",
"tags": {
"title": true,
"artist": true,
"album": true,
"cover": true,
"trackNumber": true,
"trackTotal": false,
"discNumber": true,
"discTotal": false,
"albumArtist": true,
"genre": true,
"year": true,
"date": true,
"explicit": false,
"isrc": true,
"length": true,
"barcode": true,
"bpm": true,
"replayGain": false,
"label": true,
"lyrics": false,
"copyright": false,
"composer": false,
"involvedPeople": false,
"savePlaylistAsCompilation": false,
"useNullSeparator": false,
"saveID3v1": true,
"multitagSeparator": "default"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,5 @@
from deemix.utils.misc import getIDFromLink, getTypeFromLink, getBitrateInt
from concurrent.futures import ProcessPoolExecutor
from deemix.app.downloader import download
from deemix.utils.misc import getIDFromLink, getTypeFromLink, getBitrateInt
queue = []
queueList = {}
@ -26,244 +25,264 @@ if its an album/playlist
collection
"""
def generateQueueItem(dz, sp, url, settings, bitrate=None, albumAPI=None, interface=None):
forcedBitrate = getBitrateInt(bitrate)
bitrate = forcedBitrate if forcedBitrate else settings['maxBitrate']
type = getTypeFromLink(url)
id = getIDFromLink(url, type)
result = {}
if type == None or id == None:
print("URL not recognized")
result['error'] = "URL not recognized"
elif type == "track":
trackAPI = dz.get_track_gw(id)
if albumAPI:
trackAPI['_EXTRA_ALBUM'] = albumAPI
trackAPI['FILENAME_TEMPLATE'] = settings['tracknameTemplate']
trackAPI['SINGLE_TRACK'] = True
forcedBitrate = getBitrateInt(bitrate)
bitrate = forcedBitrate if forcedBitrate else settings['maxBitrate']
type = getTypeFromLink(url)
id = getIDFromLink(url, type)
result = {}
if type == None or id == None:
print("URL not recognized")
result['error'] = "URL not recognized"
elif type == "track":
trackAPI = dz.get_track_gw(id)
if albumAPI:
trackAPI['_EXTRA_ALBUM'] = albumAPI
trackAPI['FILENAME_TEMPLATE'] = settings['tracknameTemplate']
trackAPI['SINGLE_TRACK'] = True
result['title'] = trackAPI['SNG_TITLE']
if 'VERSION' in trackAPI and trackAPI['VERSION']:
result['title'] += " " + trackAPI['VERSION']
result['artist'] = trackAPI['ART_NAME']
result['cover'] = f"https://e-cdns-images.dzcdn.net/images/cover/{trackAPI['ALB_PICTURE']}/75x75-000000-80-0-0.jpg"
result['size'] = 1
result['downloaded'] = 0
result['failed'] = 0
result['progress'] = 0
result['type'] = 'track'
result['id'] = id
result['bitrate'] = bitrate
result['uuid'] = f"{result['type']}_{id}_{bitrate}"
result['settings'] = settings or {}
result['single'] = trackAPI
result['title'] = trackAPI['SNG_TITLE']
if 'VERSION' in trackAPI and trackAPI['VERSION']:
result['title'] += " " + trackAPI['VERSION']
result['artist'] = trackAPI['ART_NAME']
result[
'cover'] = f"https://e-cdns-images.dzcdn.net/images/cover/{trackAPI['ALB_PICTURE']}/75x75-000000-80-0-0.jpg"
result['size'] = 1
result['downloaded'] = 0
result['failed'] = 0
result['progress'] = 0
result['type'] = 'track'
result['id'] = id
result['bitrate'] = bitrate
result['uuid'] = f"{result['type']}_{id}_{bitrate}"
result['settings'] = settings or {}
result['single'] = trackAPI
elif type == "album":
albumAPI = dz.get_album(id)
albumAPI_gw = dz.get_album_gw(id)
albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK']
albumAPI['copyright'] = albumAPI_gw['COPYRIGHT']
if albumAPI['nb_tracks'] == 1:
return generateQueueItem(dz, sp, f"https://www.deezer.com/track/{albumAPI['tracks']['data'][0]['id']}", settings, bitrate, albumAPI)
tracksArray = dz.get_album_tracks_gw(id)
elif type == "album":
albumAPI = dz.get_album(id)
albumAPI_gw = dz.get_album_gw(id)
albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK']
albumAPI['copyright'] = albumAPI_gw['COPYRIGHT']
if albumAPI['nb_tracks'] == 1:
return generateQueueItem(dz, sp, f"https://www.deezer.com/track/{albumAPI['tracks']['data'][0]['id']}",
settings, bitrate, albumAPI)
tracksArray = dz.get_album_tracks_gw(id)
result['title'] = albumAPI['title']
result['artist'] = albumAPI['artist']['name']
result['cover'] = albumAPI['cover_small'][:-24]+'/75x75-000000-80-0-0.jpg'
result['size'] = albumAPI['nb_tracks']
result['downloaded'] = 0
result['failed'] = 0
result['progress'] = 0
result['type'] = 'album'
result['id'] = id
result['bitrate'] = bitrate
result['uuid'] = f"{result['type']}_{id}_{bitrate}"
result['settings'] = settings or {}
totalSize = len(tracksArray)
result['collection'] = []
for pos, trackAPI in enumerate(tracksArray, start=1):
trackAPI['_EXTRA_ALBUM'] = albumAPI
trackAPI['POSITION'] = pos
trackAPI['SIZE'] = totalSize
trackAPI['FILENAME_TEMPLATE'] = settings['albumTracknameTemplate']
result['collection'].append(trackAPI)
result['title'] = albumAPI['title']
result['artist'] = albumAPI['artist']['name']
result['cover'] = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg'
result['size'] = albumAPI['nb_tracks']
result['downloaded'] = 0
result['failed'] = 0
result['progress'] = 0
result['type'] = 'album'
result['id'] = id
result['bitrate'] = bitrate
result['uuid'] = f"{result['type']}_{id}_{bitrate}"
result['settings'] = settings or {}
totalSize = len(tracksArray)
result['collection'] = []
for pos, trackAPI in enumerate(tracksArray, start=1):
trackAPI['_EXTRA_ALBUM'] = albumAPI
trackAPI['POSITION'] = pos
trackAPI['SIZE'] = totalSize
trackAPI['FILENAME_TEMPLATE'] = settings['albumTracknameTemplate']
result['collection'].append(trackAPI)
elif type == "playlist":
playlistAPI = dz.get_playlist(id)
playlistTracksAPI = dz.get_playlist_tracks_gw(id)
playlistAPI['various_artist'] = dz.get_artist(5080)
elif type == "playlist":
playlistAPI = dz.get_playlist(id)
playlistTracksAPI = dz.get_playlist_tracks_gw(id)
playlistAPI['various_artist'] = dz.get_artist(5080)
result['title'] = playlistAPI['title']
result['artist'] = playlistAPI['creator']['name']
result['cover'] = playlistAPI['picture_small'][:-24]+'/75x75-000000-80-0-0.jpg'
result['size'] = playlistAPI['nb_tracks']
result['downloaded'] = 0
result['failed'] = 0
result['progress'] = 0
result['type'] = 'playlist'
result['id'] = id
result['bitrate'] = bitrate
result['uuid'] = f"{result['type']}_{id}_{bitrate}"
result['settings'] = settings or {}
totalSize = len(playlistTracksAPI)
result['collection'] = []
for pos, trackAPI in enumerate(playlistTracksAPI, start=1):
trackAPI['_EXTRA_PLAYLIST'] = playlistAPI
trackAPI['POSITION'] = pos
trackAPI['SIZE'] = totalSize
trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate']
result['collection'].append(trackAPI)
result['title'] = playlistAPI['title']
result['artist'] = playlistAPI['creator']['name']
result['cover'] = playlistAPI['picture_small'][:-24] + '/75x75-000000-80-0-0.jpg'
result['size'] = playlistAPI['nb_tracks']
result['downloaded'] = 0
result['failed'] = 0
result['progress'] = 0
result['type'] = 'playlist'
result['id'] = id
result['bitrate'] = bitrate
result['uuid'] = f"{result['type']}_{id}_{bitrate}"
result['settings'] = settings or {}
totalSize = len(playlistTracksAPI)
result['collection'] = []
for pos, trackAPI in enumerate(playlistTracksAPI, start=1):
trackAPI['_EXTRA_PLAYLIST'] = playlistAPI
trackAPI['POSITION'] = pos
trackAPI['SIZE'] = totalSize
trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate']
result['collection'].append(trackAPI)
elif type == "artist":
artistAPI = dz.get_artist(id)
if interface:
interface.send("toast",
{'msg': f"Adding {artistAPI['name']} albums to queue", 'icon': 'loading', 'dismiss': False,
'id': 'artist_' + str(artistAPI['id'])})
artistAPITracks = dz.get_artist_albums(id)
albumList = []
for album in artistAPITracks['data']:
albumList.append(generateQueueItem(dz, sp, album['link'], settings, bitrate))
if interface:
interface.send("toast",
{'msg': f"Added {artistAPI['name']} albums to queue", 'icon': 'done', 'dismiss': True,
'id': 'artist_' + str(artistAPI['id'])})
return albumList
elif type == "spotifytrack":
result = {}
if not sp.spotifyEnabled:
print("Spotify Features is not setted up correctly.")
result['error'] = "Spotify Features is not setted up correctly."
return result
track_id = sp.get_trackid_spotify(dz, id, settings['fallbackSearch'])
if track_id != 0:
return generateQueueItem(dz, sp, f'https://www.deezer.com/track/{track_id}', settings, bitrate)
else:
print("Track not found on deezer!")
result['error'] = "Track not found on deezer!"
elif type == "spotifyalbum":
result = {}
if not sp.spotifyEnabled:
print("Spotify Features is not setted up correctly.")
result['error'] = "Spotify Features is not setted up correctly."
return result
album_id = sp.get_albumid_spotify(dz, id)
if album_id != 0:
return generateQueueItem(dz, sp, f'https://www.deezer.com/album/{album_id}', settings, bitrate)
else:
print("Album not found on deezer!")
result['error'] = "Album not found on deezer!"
elif type == "spotifyplaylist":
result = {}
if not sp.spotifyEnabled:
print("Spotify Features is not setted up correctly.")
result['error'] = "Spotify Features is not setted up correctly."
return result
if interface:
interface.send("toast",
{'msg': f"Converting spotify tracks to deezer tracks", 'icon': 'loading', 'dismiss': False,
'id': 'spotifyplaylist_' + str(id)})
playlist = sp.convert_spotify_playlist(dz, id, settings)
playlist['bitrate'] = bitrate
playlist['uuid'] = f"{playlist['type']}_{id}_{bitrate}"
result = playlist
if interface:
interface.send("toast", {'msg': f"Spotify playlist converted", 'icon': 'done', 'dismiss': True,
'id': 'spotifyplaylist_' + str(id)})
else:
print("URL not supported yet")
result['error'] = "URL not supported yet"
return result
elif type == "artist":
artistAPI = dz.get_artist(id)
if interface:
interface.send("toast", {'msg': f"Adding {artistAPI['name']} albums to queue", 'icon': 'loading', 'dismiss': False, 'id': 'artist_'+str(artistAPI['id'])})
artistAPITracks = dz.get_artist_albums(id)
albumList = []
for album in artistAPITracks['data']:
albumList.append(generateQueueItem(dz, sp, album['link'], settings, bitrate))
if interface:
interface.send("toast", {'msg': f"Added {artistAPI['name']} albums to queue", 'icon': 'done', 'dismiss': True, 'id': 'artist_'+str(artistAPI['id'])})
return albumList
elif type == "spotifytrack":
result = {}
if not sp.spotifyEnabled:
print("Spotify Features is not setted up correctly.")
result['error'] = "Spotify Features is not setted up correctly."
return result
track_id = sp.get_trackid_spotify(dz, id, settings['fallbackSearch'])
if track_id != 0:
return generateQueueItem(dz, sp, f'https://www.deezer.com/track/{track_id}', settings, bitrate)
else:
print("Track not found on deezer!")
result['error'] = "Track not found on deezer!"
elif type == "spotifyalbum":
result = {}
if not sp.spotifyEnabled:
print("Spotify Features is not setted up correctly.")
result['error'] = "Spotify Features is not setted up correctly."
return result
album_id = sp.get_albumid_spotify(dz, id)
if album_id != 0:
return generateQueueItem(dz, sp, f'https://www.deezer.com/album/{album_id}', settings, bitrate)
else:
print("Album not found on deezer!")
result['error'] = "Album not found on deezer!"
elif type == "spotifyplaylist":
result = {}
if not sp.spotifyEnabled:
print("Spotify Features is not setted up correctly.")
result['error'] = "Spotify Features is not setted up correctly."
return result
if interface:
interface.send("toast", {'msg': f"Converting spotify tracks to deezer tracks", 'icon': 'loading', 'dismiss': False, 'id': 'spotifyplaylist_'+str(id)})
playlist = sp.convert_spotify_playlist(dz, id, settings)
playlist['bitrate'] = bitrate
playlist['uuid'] = f"{playlist['type']}_{id}_{bitrate}"
result = playlist
if interface:
interface.send("toast", {'msg': f"Spotify playlist converted", 'icon': 'done', 'dismiss': True, 'id': 'spotifyplaylist_'+str(id)})
else:
print("URL not supported yet")
result['error'] = "URL not supported yet"
return result
def addToQueue(dz, sp, url, settings, bitrate=None, interface=None):
global currentItem, queueList, queue
if not dz.logged_in:
return "Not logged in"
queueItem = generateQueueItem(dz, sp, url, settings, bitrate, interface=interface)
if type(queueItem) is list:
for x in queueItem:
if 'error' in x:
continue
if x['uuid'] in list(queueList.keys()):
print("Already in queue!")
continue
if interface:
interface.send("addedToQueue", x)
queue.append(x['uuid'])
queueList[x['uuid']] = x
else:
if 'error' in queueItem:
if interface:
interface.send("toast", {'msg': queueItem['error'], 'icon': 'error'})
return False
if queueItem['uuid'] in list(queueList.keys()):
print("Already in queue!")
if interface:
interface.send("toast", {'msg': f"{queueItem['title']} is already in queue!", 'icon': 'playlist_add_check'})
return False
if interface:
interface.send("addedToQueue", queueItem)
interface.send("toast", {'msg': f"{queueItem['title']} added to queue", 'icon': 'playlist_add'})
queue.append(queueItem['uuid'])
queueList[queueItem['uuid']] = queueItem
nextItem(dz, interface)
return True
global currentItem, queueList, queue
if not dz.logged_in:
return "Not logged in"
queueItem = generateQueueItem(dz, sp, url, settings, bitrate, interface=interface)
if type(queueItem) is list:
for x in queueItem:
if 'error' in x:
continue
if x['uuid'] in list(queueList.keys()):
print("Already in queue!")
continue
if interface:
interface.send("addedToQueue", x)
queue.append(x['uuid'])
queueList[x['uuid']] = x
else:
if 'error' in queueItem:
if interface:
interface.send("toast", {'msg': queueItem['error'], 'icon': 'error'})
return False
if queueItem['uuid'] in list(queueList.keys()):
print("Already in queue!")
if interface:
interface.send("toast",
{'msg': f"{queueItem['title']} is already in queue!", 'icon': 'playlist_add_check'})
return False
if interface:
interface.send("addedToQueue", queueItem)
interface.send("toast", {'msg': f"{queueItem['title']} added to queue", 'icon': 'playlist_add'})
queue.append(queueItem['uuid'])
queueList[queueItem['uuid']] = queueItem
nextItem(dz, interface)
return True
def nextItem(dz, interface=None):
global currentItem, queueList, queue
if currentItem != "":
return None
else:
if len(queue)>0:
currentItem = queue.pop(0)
else:
return None
if interface:
interface.send("startDownload", currentItem)
result = download(dz, queueList[currentItem], interface)
callbackQueueDone(result)
global currentItem, queueList, queue
if currentItem != "":
return None
else:
if len(queue) > 0:
currentItem = queue.pop(0)
else:
return None
if interface:
interface.send("startDownload", currentItem)
result = download(dz, queueList[currentItem], interface)
callbackQueueDone(result)
def callbackQueueDone(result):
global currentItem, queueList, queueComplete
if 'cancel' in queueList[currentItem]:
del queueList[currentItem]
else:
queueComplete.append(currentItem)
currentItem = ""
nextItem(result['dz'], result['interface'])
global currentItem, queueList, queueComplete
if 'cancel' in queueList[currentItem]:
del queueList[currentItem]
else:
queueComplete.append(currentItem)
currentItem = ""
nextItem(result['dz'], result['interface'])
def getQueue():
global currentItem, queueList, queue, queueComplete
return (queue, queueComplete, queueList, currentItem)
global currentItem, queueList, queue, queueComplete
return (queue, queueComplete, queueList, currentItem)
def removeFromQueue(uuid, interface=None):
global currentItem, queueList, queue, queueComplete
if uuid == currentItem:
if interface:
interface.send('toast', {'msg': "Cancelling current item.", 'icon':'loading', 'dismiss': False, 'id':'cancelling_'+uuid})
queueList[uuid]['cancel'] = True
elif uuid in queue:
queue.remove(uuid)
del queueList[uuid]
if interface:
interface.send("removedFromQueue", uuid)
elif uuid in queueComplete:
queueComplete.remove(uuid)
del queueList[uuid]
if interface:
interface.send("removedFromQueue", uuid)
global currentItem, queueList, queue, queueComplete
if uuid == currentItem:
if interface:
interface.send('toast', {'msg': "Cancelling current item.", 'icon': 'loading', 'dismiss': False,
'id': 'cancelling_' + uuid})
queueList[uuid]['cancel'] = True
elif uuid in queue:
queue.remove(uuid)
del queueList[uuid]
if interface:
interface.send("removedFromQueue", uuid)
elif uuid in queueComplete:
queueComplete.remove(uuid)
del queueList[uuid]
if interface:
interface.send("removedFromQueue", uuid)
def cancelAllDownloads(interface=None):
global currentItem, queueList, queue, queueComplete
queue = []
queueComplete = []
if currentItem != "":
if interface:
interface.send('toast', {'msg': "Cancelling current item.", 'icon':'loading', 'dismiss': False, 'id':'cancelling_'+currentItem})
queueList[currentItem]['cancel'] = True
for uuid in list(queueList.keys()):
if uuid != currentItem:
del queueList[uuid]
if interface:
interface.send("removedAllDownloads", currentItem)
global currentItem, queueList, queue, queueComplete
queue = []
queueComplete = []
if currentItem != "":
if interface:
interface.send('toast', {'msg': "Cancelling current item.", 'icon': 'loading', 'dismiss': False,
'id': 'cancelling_' + currentItem})
queueList[currentItem]['cancel'] = True
for uuid in list(queueList.keys()):
if uuid != currentItem:
del queueList[uuid]
if interface:
interface.send("removedAllDownloads", currentItem)
def removeFinishedDownloads(interface=None):
global queueList, queueComplete
for uuid in queueComplete:
del queueList[uuid]
queueComplete = []
if interface:
interface.send("removedFinishedDownloads")
global queueList, queueComplete
for uuid in queueComplete:
del queueList[uuid]
queueComplete = []
if interface:
interface.send("removedFinishedDownloads")

View File

@ -1,57 +1,61 @@
#!/usr/bin/env python3
import os.path as path
from os import mkdir, rmdir
import json
import os.path as path
from os import mkdir
import deemix.utils.localpaths as localpaths
settings = {}
defaultSettings = {}
def initSettings():
global settings
global defaultSettings
currentFolder = path.abspath(path.dirname(__file__))
configFolder = localpaths.getConfigFolder()
if not path.isdir(configFolder):
mkdir(configFolder)
with open(path.join(currentFolder, 'default.json'), 'r') as d:
defaultSettings = json.load(d)
if not path.isfile(path.join(configFolder, 'config.json')):
with open(path.join(configFolder, 'config.json'), 'w') as f:
json.dump(defaultSettings, f, indent=2)
with open(path.join(configFolder, 'config.json'), 'r') as configFile:
settings = json.load(configFile)
settingsCheck()
if settings['downloadLocation'] == "":
settings['downloadLocation'] = path.join(localpaths.getHomeFolder(), 'deemix Music')
saveSettings(settings)
if not path.isdir(settings['downloadLocation']):
mkdir(settings['downloadLocation'])
return settings
global settings
global defaultSettings
currentFolder = path.abspath(path.dirname(__file__))
configFolder = localpaths.getConfigFolder()
if not path.isdir(configFolder):
mkdir(configFolder)
with open(path.join(currentFolder, 'default.json'), 'r') as d:
defaultSettings = json.load(d)
if not path.isfile(path.join(configFolder, 'config.json')):
with open(path.join(configFolder, 'config.json'), 'w') as f:
json.dump(defaultSettings, f, indent=2)
with open(path.join(configFolder, 'config.json'), 'r') as configFile:
settings = json.load(configFile)
settingsCheck()
if settings['downloadLocation'] == "":
settings['downloadLocation'] = path.join(localpaths.getHomeFolder(), 'deemix Music')
saveSettings(settings)
if not path.isdir(settings['downloadLocation']):
mkdir(settings['downloadLocation'])
return settings
def getSettings():
global settings
return settings
global settings
return settings
def saveSettings(newSettings):
global settings
settings = newSettings
with open(path.join(localpaths.getConfigFolder(), 'config.json'), 'w') as configFile:
json.dump(settings, configFile, indent=2)
return True
global settings
settings = newSettings
with open(path.join(localpaths.getConfigFolder(), 'config.json'), 'w') as configFile:
json.dump(settings, configFile, indent=2)
return True
def settingsCheck():
global settings
global defaultSettings
changes = 0
for x in defaultSettings:
if not x in settings or type(settings[x]) != type(defaultSettings[x]):
settings[x] = defaultSettings[x]
changes+=1
for x in defaultSettings['tags']:
if not x in settings['tags'] or type(settings['tags'][x]) != type(defaultSettings['tags'][x]):
settings['tags'][x] = defaultSettings['tags'][x]
changes+=1
if changes > 0:
saveSettings(settings)
global settings
global defaultSettings
changes = 0
for x in defaultSettings:
if not x in settings or type(settings[x]) != type(defaultSettings[x]):
settings[x] = defaultSettings[x]
changes += 1
for x in defaultSettings['tags']:
if not x in settings['tags'] or type(settings['tags'][x]) != type(defaultSettings['tags'][x]):
settings['tags'][x] = defaultSettings['tags'][x]
changes += 1
if changes > 0:
saveSettings(settings)

View File

@ -1,172 +1,179 @@
#!/usr/bin/env python3
import os.path as path
from os import mkdir, rmdir
import json
import deemix.utils.localpaths as localpaths
import os.path as path
from os import mkdir
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials
import deemix.utils.localpaths as localpaths
class SpotifyHelper:
def __init__(self):
self.credentials = {}
self.spotifyEnabled = False
self.sp = None
self.initCredentials()
def __init__(self):
self.credentials = {}
self.spotifyEnabled = False
self.sp = None
self.initCredentials()
def initCredentials(self):
configFolder = localpaths.getConfigFolder()
if not path.isdir(configFolder):
mkdir(configFolder)
if not path.isfile(path.join(configFolder, 'authCredentials.json')):
with open(path.join(configFolder, 'authCredentials.json'), 'w') as f:
json.dump({'clientId': "", 'clientSecret': ""}, f, indent=2)
with open(path.join(configFolder, 'authCredentials.json'), 'r') as credentialsFile:
self.credentials = json.load(credentialsFile)
self.checkCredentials()
def initCredentials(self):
configFolder = localpaths.getConfigFolder()
if not path.isdir(configFolder):
mkdir(configFolder)
if not path.isfile(path.join(configFolder, 'authCredentials.json')):
with open(path.join(configFolder, 'authCredentials.json'), 'w') as f:
json.dump({'clientId': "", 'clientSecret': ""}, f, indent=2)
with open(path.join(configFolder, 'authCredentials.json'), 'r') as credentialsFile:
self.credentials = json.load(credentialsFile)
self.checkCredentials()
def checkCredentials(self):
if self.credentials['clientId'] == "" or self.credentials['clientSecret'] == "":
spotifyEnabled = False
else:
try:
self.createSpotifyConnection()
self.sp.user_playlists('spotify')
self.spotifyEnabled = True
except Exception as e:
self.spotifyEnabled = False
return self.spotifyEnabled
def checkCredentials(self):
if self.credentials['clientId'] == "" or self.credentials['clientSecret'] == "":
spotifyEnabled = False
else:
try:
self.createSpotifyConnection()
self.sp.user_playlists('spotify')
self.spotifyEnabled = True
except Exception as e:
self.spotifyEnabled = False
return self.spotifyEnabled
def getCredentials(self):
return self.credentials
def getCredentials(self):
return self.credentials
def setCredentials(self, spotifyCredentials):
configFolder = localpaths.getConfigFolder()
with open(path.join(configFolder, 'authCredentials.json'), 'w') as f:
json.dump(spotifyCredentials, f, indent=2)
self.credentials = spotifyCredentials
self.checkCredentials()
def setCredentials(self, spotifyCredentials):
configFolder = localpaths.getConfigFolder()
with open(path.join(configFolder, 'authCredentials.json'), 'w') as f:
json.dump(spotifyCredentials, f, indent=2)
self.credentials = spotifyCredentials
self.checkCredentials()
def createSpotifyConnection(self):
client_credentials_manager = SpotifyClientCredentials(client_id=self.credentials['clientId'], client_secret=self.credentials['clientSecret'])
self.sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
def createSpotifyConnection(self):
client_credentials_manager = SpotifyClientCredentials(client_id=self.credentials['clientId'],
client_secret=self.credentials['clientSecret'])
self.sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
def _convert_playlist_structure(self, spotify_obj):
if len(spotify_obj['images']):
url = spotify_obj['images'][0]['url']
else:
url = "https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/75x75-000000-80-0-0.jpg"
deezer_obj = {
'checksum': spotify_obj['snapshot_id'],
'collaborative': spotify_obj['collaborative'],
'creation_date': "????-00-00",
'creator': {'id': spotify_obj['owner']['id'], 'name': spotify_obj['owner']['display_name'], 'tracklist': spotify_obj['owner']['href'], 'type': "user"},
'description': spotify_obj['description'],
'duration': 0,
'fans': spotify_obj['followers']['total'],
'id': spotify_obj['id'],
'is_loved_track': False,
'link': spotify_obj['external_urls']['spotify'],
'nb_tracks': spotify_obj['tracks']['total'],
'picture': url,
'picture_big': url,
'picture_medium': url,
'picture_small': url,
'picture_xl': url,
'public': spotify_obj['public'],
'share': spotify_obj['external_urls']['spotify'],
'title': spotify_obj['name'],
'tracklist': spotify_obj['tracks']['href'],
'type': "playlist"
}
return deezer_obj
def _convert_playlist_structure(self, spotify_obj):
if len(spotify_obj['images']):
url = spotify_obj['images'][0]['url']
else:
url = "https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/75x75-000000-80-0-0.jpg"
deezer_obj = {
'checksum': spotify_obj['snapshot_id'],
'collaborative': spotify_obj['collaborative'],
'creation_date': "????-00-00",
'creator': {'id': spotify_obj['owner']['id'], 'name': spotify_obj['owner']['display_name'],
'tracklist': spotify_obj['owner']['href'], 'type': "user"},
'description': spotify_obj['description'],
'duration': 0,
'fans': spotify_obj['followers']['total'],
'id': spotify_obj['id'],
'is_loved_track': False,
'link': spotify_obj['external_urls']['spotify'],
'nb_tracks': spotify_obj['tracks']['total'],
'picture': url,
'picture_big': url,
'picture_medium': url,
'picture_small': url,
'picture_xl': url,
'public': spotify_obj['public'],
'share': spotify_obj['external_urls']['spotify'],
'title': spotify_obj['name'],
'tracklist': spotify_obj['tracks']['href'],
'type': "playlist"
}
return deezer_obj
def get_trackid_spotify(self, dz, track_id, fallbackSearch, spotifyTrack=None):
if not self.spotifyEnabled:
raise spotifyFeaturesNotEnabled
if not spotifyTrack:
spotify_track = self.sp.track(track_id)
else:
spotify_track = spotifyTrack
dz_track = 0
if 'external_ids' in spotify_track and 'isrc' in spotify_track['external_ids']:
try:
dz_track = dz.get_track_by_ISRC(spotify_track['external_ids']['isrc'])
dz_track = dz_track['id'] if 'id' in dz_track else 0
except:
dz_track = dz.get_track_from_metadata(spotify_track['artists'][0]['name'], spotify_track['name'], spotify_track['album']['name']) if fallbackSearch else 0
elif fallbackSearch:
dz_track = dz.get_track_from_metadata(spotify_track['artists'][0]['name'], spotify_track['name'], spotify_track['album']['name'])
return dz_track
def get_trackid_spotify(self, dz, track_id, fallbackSearch, spotifyTrack=None):
if not self.spotifyEnabled:
raise spotifyFeaturesNotEnabled
if not spotifyTrack:
spotify_track = self.sp.track(track_id)
else:
spotify_track = spotifyTrack
dz_track = 0
if 'external_ids' in spotify_track and 'isrc' in spotify_track['external_ids']:
try:
dz_track = dz.get_track_by_ISRC(spotify_track['external_ids']['isrc'])
dz_track = dz_track['id'] if 'id' in dz_track else 0
except:
dz_track = dz.get_track_from_metadata(spotify_track['artists'][0]['name'], spotify_track['name'],
spotify_track['album']['name']) if fallbackSearch else 0
elif fallbackSearch:
dz_track = dz.get_track_from_metadata(spotify_track['artists'][0]['name'], spotify_track['name'],
spotify_track['album']['name'])
return dz_track
def get_albumid_spotify(self, dz, album_id):
if not self.spotifyEnabled:
raise spotifyFeaturesNotEnabled
spotify_album = self.sp.album(album_id)
dz_album = 0
if 'external_ids' in spotify_album and 'upc' in spotify_album['external_ids']:
try:
dz_album = dz.get_album_by_UPC(spotify_album['external_ids']['upc'])
dz_album = dz_album['id'] if 'id' in dz_album else 0
except:
try:
dz_album = dz.get_album_by_UPC(int(spotify_album['external_ids']['upc']))
dz_album = dz_album['id'] if 'id' in dz_album else 0
except:
dz_album = 0
return dz_album
def get_albumid_spotify(self, dz, album_id):
if not self.spotifyEnabled:
raise spotifyFeaturesNotEnabled
spotify_album = self.sp.album(album_id)
dz_album = 0
if 'external_ids' in spotify_album and 'upc' in spotify_album['external_ids']:
try:
dz_album = dz.get_album_by_UPC(spotify_album['external_ids']['upc'])
dz_album = dz_album['id'] if 'id' in dz_album else 0
except:
try:
dz_album = dz.get_album_by_UPC(int(spotify_album['external_ids']['upc']))
dz_album = dz_album['id'] if 'id' in dz_album else 0
except:
dz_album = 0
return dz_album
def convert_spotify_playlist(self, dz, playlist_id, settings):
if not self.spotifyEnabled:
raise spotifyFeaturesNotEnabled
spotify_playlist = self.sp.playlist(playlist_id)
result = {
'title': spotify_playlist['name'],
'artist': spotify_playlist['owner']['display_name'],
'size': spotify_playlist['tracks']['total'],
'downloaded': 0,
'failed': 0,
'progress': 0,
'type': 'spotify_playlist',
'settings': settings or {},
'id': playlist_id
}
if len(spotify_playlist['images']):
result['cover'] = spotify_playlist['images'][0]['url']
else:
result[
'cover'] = "https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/75x75-000000-80-0-0.jpg"
playlistAPI = self._convert_playlist_structure(spotify_playlist)
playlistAPI['various_artist'] = dz.get_artist(5080)
tracklist = spotify_playlist['tracks']['items']
result['collection'] = []
while spotify_playlist['tracks']['next']:
spotify_playlist['tracks'] = self.sp.next(spotify_playlist['tracks'])
tracklist += spotify_playlist['tracks']['items']
totalSize = len(tracklist)
for pos, track in enumerate(tracklist, start=1):
trackID = self.get_trackid_spotify(dz, 0, settings['fallbackSearch'], track['track'])
if trackID == 0:
deezerTrack = {
'SNG_ID': 0,
'SNG_TITLE': track['track']['name'],
'DURATION': 0,
'MD5_ORIGIN': 0,
'MEDIA_VERSION': 0,
'FILESIZE': 0,
'ALB_TITLE': track['track']['album']['name'],
'ALB_PICTURE': "",
'ART_ID': 0,
'ART_NAME': track['track']['artists'][0]['name']
}
else:
deezerTrack = dz.get_track_gw(trackID)
deezerTrack['_EXTRA_PLAYLIST'] = playlistAPI
deezerTrack['POSITION'] = pos
deezerTrack['SIZE'] = totalSize
deezerTrack['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate']
result['collection'].append(deezerTrack)
return result
def convert_spotify_playlist(self, dz, playlist_id, settings):
if not self.spotifyEnabled:
raise spotifyFeaturesNotEnabled
spotify_playlist = self.sp.playlist(playlist_id)
result = {
'title': spotify_playlist['name'],
'artist': spotify_playlist['owner']['display_name'],
'size': spotify_playlist['tracks']['total'],
'downloaded': 0,
'failed': 0,
'progress': 0,
'type': 'spotify_playlist',
'settings': settings or {},
'id': playlist_id
}
if len(spotify_playlist['images']):
result['cover'] = spotify_playlist['images'][0]['url']
else:
result['cover'] = "https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/75x75-000000-80-0-0.jpg"
playlistAPI = self._convert_playlist_structure(spotify_playlist)
playlistAPI['various_artist'] = dz.get_artist(5080)
tracklist = spotify_playlist['tracks']['items']
result['collection'] = []
while spotify_playlist['tracks']['next']:
spotify_playlist['tracks'] = self.sp.next(spotify_playlist['tracks'])
tracklist += spotify_playlist['tracks']['items']
totalSize = len(tracklist)
for pos, track in enumerate(tracklist, start=1):
trackID = self.get_trackid_spotify(dz, 0, settings['fallbackSearch'], track['track'])
if trackID == 0:
deezerTrack = {
'SNG_ID': 0,
'SNG_TITLE': track['track']['name'],
'DURATION': 0,
'MD5_ORIGIN': 0,
'MEDIA_VERSION': 0,
'FILESIZE': 0,
'ALB_TITLE': track['track']['album']['name'],
'ALB_PICTURE': "",
'ART_ID': 0,
'ART_NAME': track['track']['artists'][0]['name']
}
else:
deezerTrack = dz.get_track_gw(trackID)
deezerTrack['_EXTRA_PLAYLIST'] = playlistAPI
deezerTrack['POSITION'] = pos
deezerTrack['SIZE'] = totalSize
deezerTrack['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate']
result['collection'].append(deezerTrack)
return result
class spotifyFeaturesNotEnabled(Exception):
pass
pass

View File

@ -1,21 +1,24 @@
#!/usr/bin/env python3
import sys
import os.path as path
import sys
from os import getenv
userdata = ""
homedata = path.expanduser("~")
if getenv("APPDATA"):
userdata = getenv("APPDATA") + path.sep + "deemix" + path.sep
userdata = getenv("APPDATA") + path.sep + "deemix" + path.sep
elif sys.platform.startswith('darwin'):
userdata = homedata + '/Library/Application Support/deemix/'
userdata = homedata + '/Library/Application Support/deemix/'
elif getenv("XDG_CONFIG_HOME"):
userdata = getenv("XDG_CONFIG_HOME") + '/deemix/';
userdata = getenv("XDG_CONFIG_HOME") + '/deemix/';
else:
userdata = homedata + '/.config/deemix/';
userdata = homedata + '/.config/deemix/';
def getHomeFolder():
return homedata
return homedata
def getConfigFolder():
return userdata
return userdata

View File

@ -1,94 +1,98 @@
#!/usr/bin/env python3
import re
def getBitrateInt(txt):
txt = str(txt)
if txt in ['flac', 'lossless', '9']:
return 9
elif txt in ['mp3', '320', '3']:
return 3
elif txt in ['128', '1']:
return 1
elif txt in ['360', '360_hq', '15']:
return 15
elif txt in ['360_mq', '14']:
return 14
elif txt in ['360_lq', '13']:
return 13
else:
return None
txt = str(txt)
if txt in ['flac', 'lossless', '9']:
return 9
elif txt in ['mp3', '320', '3']:
return 3
elif txt in ['128', '1']:
return 1
elif txt in ['360', '360_hq', '15']:
return 15
elif txt in ['360_mq', '14']:
return 14
elif txt in ['360_lq', '13']:
return 13
else:
return None
def changeCase(string, type):
if type == "lower":
return string.lower()
elif type == "upper":
return string.upper()
elif type == "start":
string = string.split(" ")
res = []
for index, value in enumerate(string):
res.append(value[0].upper() + value[0:].lower())
res = " ".join(res)
return res
elif type == "sentence":
res = string[0].upper() + string[0:].lower()
return res
else:
return string
if type == "lower":
return string.lower()
elif type == "upper":
return string.upper()
elif type == "start":
string = string.split(" ")
res = []
for index, value in enumerate(string):
res.append(value[0].upper() + value[0:].lower())
res = " ".join(res)
return res
elif type == "sentence":
res = string[0].upper() + string[0:].lower()
return res
else:
return string
def getIDFromLink(link, type):
if '?' in link:
link = link[:link.find('?')]
if link.endswith("/"):
link = link[:-1]
if '?' in link:
link = link[:link.find('?')]
if link.endswith("/"):
link = link[:-1]
if link.startswith("http") and 'open.spotify.com/' in link:
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]
else:
return link[link.rfind("/") + 1:]
if link.startswith("http") and 'open.spotify.com/' in link:
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]
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 '/artist' in link:
type = 'artist'
return type
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 '/artist' in link:
type = 'artist'
return type
def isValidLink(text):
if text.lower().startswith("http"):
if "deezer.com" in text.lower() or "open.spotify.com" in text.lower():
return True
elif text.lower().startswith("spotify:"):
return True
return False
if text.lower().startswith("http"):
if "deezer.com" in text.lower() or "open.spotify.com" in text.lower():
return True
elif text.lower().startswith("spotify:"):
return True
return False

View File

@ -3,166 +3,194 @@ import re
from os.path import sep as pathSep
bitrateLabels = {
15: "360 HQ",
14: "360 MQ",
13: "360 LQ",
9: "FLAC",
3: "320",
1: "128",
8: "128"
15: "360 HQ",
14: "360 MQ",
13: "360 LQ",
9: "FLAC",
3: "320",
1: "128",
8: "128"
}
def fixName(txt, char='_'):
txt = str(txt)
txt = re.sub(r'[\0\/\\:*?"<>|]', char, txt)
return txt
txt = str(txt)
txt = re.sub(r'[\0\/\\:*?"<>|]', char, txt)
return txt
def fixLongName(name):
if pathSep in name:
name2 = name.split(pathSep)
name = ""
for txt in name2:
txt = txt[:200]
name += txt+pathSep
name = name[:-1]
else:
name = name[:200]
return name
if pathSep in name:
name2 = name.split(pathSep)
name = ""
for txt in name2:
txt = txt[:200]
name += txt + pathSep
name = name[:-1]
else:
name = name[:200]
return name
def antiDot(string):
while string[-1:] == "." or string[-1:] == " " or string[-1:] == "\n":
string = string[:-1]
if len(string) < 1:
string = "dot"
return string
while string[-1:] == "." or string[-1:] == " " or string[-1:] == "\n":
string = string[:-1]
if len(string) < 1:
string = "dot"
return string
def pad(num, max, dopad=True):
paddingsize = len(str(max))
if dopad:
return str(num).zfill(paddingsize)
else:
return str(num)
paddingsize = len(str(max))
if dopad:
return str(num).zfill(paddingsize)
else:
return str(num)
def generateFilename(track, trackAPI, settings):
if trackAPI['FILENAME_TEMPLATE'] == "":
filename = "%artist% - %title%"
else:
filename = trackAPI['FILENAME_TEMPLATE']
return settingsRegex(filename, track, settings, trackAPI['_EXTRA_PLAYLIST'] if '_EXTRA_PLAYLIST' in trackAPI else None)
if trackAPI['FILENAME_TEMPLATE'] == "":
filename = "%artist% - %title%"
else:
filename = trackAPI['FILENAME_TEMPLATE']
return settingsRegex(filename, track, settings,
trackAPI['_EXTRA_PLAYLIST'] if '_EXTRA_PLAYLIST' in trackAPI else None)
def generateFilepath(track, trackAPI, settings):
filepath = settings['downloadLocation']
if filepath[-1:] != pathSep:
filepath += pathSep
artistPath = None
coverPath = None
extrasPath = None
filepath = settings['downloadLocation']
if filepath[-1:] != pathSep:
filepath += pathSep
artistPath = None
coverPath = None
extrasPath = None
if settings['createPlaylistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']:
filepath += antiDot(settingsRegexPlaylist(settings['playlistNameTemplate'], trackAPI['_EXTRA_PLAYLIST'], settings)) + pathSep
if settings['createPlaylistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and not settings['tags'][
'savePlaylistAsCompilation']:
filepath += antiDot(
settingsRegexPlaylist(settings['playlistNameTemplate'], trackAPI['_EXTRA_PLAYLIST'], settings)) + pathSep
if '_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']:
extrasPath = filepath
if '_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']:
extrasPath = filepath
if (
settings['createArtistFolder'] and not '_EXTRA_PLAYLIST' in trackAPI or
(settings['createArtistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']) or
(settings['createArtistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist'])
):
if (int(track['id'])<0 and not 'mainArtist' in track['album']):
track['album']['mainArtist'] = track['mainArtist']
filepath += antiDot(settingsRegexArtist(settings['artistNameTemplate'], track['album']['mainArtist'], settings)) + pathSep
artistPath = filepath
if (
settings['createArtistFolder'] and not '_EXTRA_PLAYLIST' in trackAPI or
(settings['createArtistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and settings['tags'][
'savePlaylistAsCompilation']) or
(settings['createArtistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist'])
):
if (int(track['id']) < 0 and not 'mainArtist' in track['album']):
track['album']['mainArtist'] = track['mainArtist']
filepath += antiDot(
settingsRegexArtist(settings['artistNameTemplate'], track['album']['mainArtist'], settings)) + pathSep
artistPath = filepath
if (settings['createAlbumFolder'] and
(not 'SINGLE_TRACK' in trackAPI or ('SINGLE_TRACK' in trackAPI and settings['createSingleFolder'])) and
(not '_EXTRA_PLAYLIST' in trackAPI or ('_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']) or ('_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist']))
):
filepath += antiDot(settingsRegexAlbum(settings['albumNameTemplate'], track['album'], settings, trackAPI)) + pathSep
coverPath = filepath
if (settings['createAlbumFolder'] and
(not 'SINGLE_TRACK' in trackAPI or ('SINGLE_TRACK' in trackAPI and settings['createSingleFolder'])) and
(not '_EXTRA_PLAYLIST' in trackAPI or (
'_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']) or (
'_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist']))
):
filepath += antiDot(
settingsRegexAlbum(settings['albumNameTemplate'], track['album'], settings, trackAPI)) + pathSep
coverPath = filepath
if not ('_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']):
extrasPath = filepath
if not ('_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']):
extrasPath = filepath
if (
int(track['album']['discTotal']) > 1 and (
(settings['createAlbumFolder'] and settings['createCDFolder']) and
(not 'SINGLE_TRACK' in trackAPI or ('SINGLE_TRACK' in trackAPI and settings['createSingleFolder'])) and
(not '_EXTRA_PLAYLIST' in trackAPI or ('_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']) or ('_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist']))
)):
filepath += 'CD' + str(track['discNumber']) + pathSep
if (
int(track['album']['discTotal']) > 1 and (
(settings['createAlbumFolder'] and settings['createCDFolder']) and
(not 'SINGLE_TRACK' in trackAPI or ('SINGLE_TRACK' in trackAPI and settings['createSingleFolder'])) and
(not '_EXTRA_PLAYLIST' in trackAPI or (
'_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']) or (
'_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist']))
)):
filepath += 'CD' + str(track['discNumber']) + pathSep
return (filepath, artistPath, coverPath, extrasPath)
return (filepath, artistPath, coverPath, extrasPath)
def settingsRegex(filename, track, settings, playlist=None):
filename = filename.replace("%title%", fixName(track['title'], settings['illegalCharacterReplacer']))
filename = filename.replace("%artist%", fixName(track['mainArtist']['name'], settings['illegalCharacterReplacer']))
filename = filename.replace("%artists%", fixName(track['mainArtistsString'], settings['illegalCharacterReplacer']))
filename = filename.replace("%album%", fixName(track['album']['title'], settings['illegalCharacterReplacer']))
filename = filename.replace("%albumartist%", fixName(track['album']['mainArtist']['name'], settings['illegalCharacterReplacer']))
filename = filename.replace("%tracknumber%", pad(track['trackNumber'], track['album']['trackTotal'] if int(settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize'])-1), settings['padTracks']))
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']))
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("%isrc%", track['ISRC'])
filename = filename.replace("%upc%", track['album']['barcode'])
filename = filename.replace("%explicit%", "(Explicit)" if track['explicit'] else "")
filename = filename.replace("%title%", fixName(track['title'], settings['illegalCharacterReplacer']))
filename = filename.replace("%artist%", fixName(track['mainArtist']['name'], settings['illegalCharacterReplacer']))
filename = filename.replace("%artists%", fixName(track['mainArtistsString'], settings['illegalCharacterReplacer']))
filename = filename.replace("%album%", fixName(track['album']['title'], settings['illegalCharacterReplacer']))
filename = filename.replace("%albumartist%",
fixName(track['album']['mainArtist']['name'], settings['illegalCharacterReplacer']))
filename = filename.replace("%tracknumber%", pad(track['trackNumber'], track['album']['trackTotal'] if int(
settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize']) - 1), settings['padTracks']))
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']))
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("%isrc%", track['ISRC'])
filename = filename.replace("%upc%", track['album']['barcode'])
filename = filename.replace("%explicit%", "(Explicit)" if track['explicit'] else "")
filename = filename.replace("%track_id%", str(track['id']))
filename = filename.replace("%album_id%", str(track['album']['id']))
filename = filename.replace("%artist_id%", str(track['mainArtist']['id']))
if playlist:
filename = filename.replace("%playlist_id%", str(playlist['id']))
filename = filename.replace("%position%", pad(track['position'], playlist['nb_tracks'] if int(
settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize']) - 1), settings['padTracks']))
else:
filename = filename.replace("%position%", pad(track['trackNumber'], track['album']['trackTotal'] if int(
settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize']) - 1), settings['padTracks']))
filename = filename.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(filename))
filename = filename.replace("%track_id%", str(track['id']))
filename = filename.replace("%album_id%", str(track['album']['id']))
filename = filename.replace("%artist_id%", str(track['mainArtist']['id']))
if playlist:
filename = filename.replace("%playlist_id%", str(playlist['id']))
filename = filename.replace("%position%", pad(track['position'], playlist['nb_tracks'] if int(settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize'])-1), settings['padTracks']))
else:
filename = filename.replace("%position%", pad(track['trackNumber'], track['album']['trackTotal'] if int(settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize'])-1), settings['padTracks']))
filename = filename.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(filename))
def settingsRegexAlbum(foldername, album, settings, trackAPI):
if trackAPI and '_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']:
foldername = foldername.replace("%album_id%", "pl_"+str(trackAPI['_EXTRA_PLAYLIST']['id']))
else:
foldername = foldername.replace("%album_id%", str(album['id']))
foldername = foldername.replace("%album%", fixName(album['title'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%artist%", fixName(album['mainArtist']['name'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%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'][0].upper()+album['recordType'][1:].lower(), settings['illegalCharacterReplacer']))
foldername = foldername.replace("%upc%", album['barcode'])
foldername = foldername.replace("%label%", fixName(album['label'], settings['illegalCharacterReplacer']))
if len(album['genre']) > 0:
foldername = foldername.replace("%genre%", fixName(album['genre'][0], settings['illegalCharacterReplacer']))
else:
foldername = foldername.replace("%genre%", "Unknown")
foldername = foldername.replace("%year%", str(album['date']['year']))
foldername = foldername.replace("%date%", album['dateString'])
foldername = foldername.replace("%bitrate%", bitrateLabels[int(album['bitrate'])])
if trackAPI and '_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']:
foldername = foldername.replace("%album_id%", "pl_" + str(trackAPI['_EXTRA_PLAYLIST']['id']))
else:
foldername = foldername.replace("%album_id%", str(album['id']))
foldername = foldername.replace("%album%", fixName(album['title'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%artist%",
fixName(album['mainArtist']['name'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%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'][0].upper() + album['recordType'][1:].lower(),
settings['illegalCharacterReplacer']))
foldername = foldername.replace("%upc%", album['barcode'])
foldername = foldername.replace("%label%", fixName(album['label'], settings['illegalCharacterReplacer']))
if len(album['genre']) > 0:
foldername = foldername.replace("%genre%", fixName(album['genre'][0], settings['illegalCharacterReplacer']))
else:
foldername = foldername.replace("%genre%", "Unknown")
foldername = foldername.replace("%year%", str(album['date']['year']))
foldername = foldername.replace("%date%", album['dateString'])
foldername = foldername.replace("%bitrate%", bitrateLabels[int(album['bitrate'])])
foldername = foldername.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(foldername))
foldername = foldername.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(foldername))
def settingsRegexArtist(foldername, artist, settings):
foldername = foldername.replace("%artist%", fixName(artist['name'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%artist_id%", str(artist['id']))
foldername = foldername.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(foldername))
foldername = foldername.replace("%artist%", fixName(artist['name'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%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['id'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%owner%", fixName(playlist['creator']['name'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%owner_id%", str(playlist['creator']['id']))
foldername = foldername.replace("%year%", str(playlist['creation_date'][:4]))
foldername = foldername.replace("%date%", str(playlist['creation_date'][:10]))
foldername = foldername.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(foldername))
foldername = foldername.replace("%playlist%", fixName(playlist['title'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%playlist_id%", fixName(playlist['id'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%owner%",
fixName(playlist['creator']['name'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%owner_id%", str(playlist['creator']['id']))
foldername = foldername.replace("%year%", str(playlist['creation_date'][:4]))
foldername = foldername.replace("%date%", str(playlist['creation_date'][:10]))
foldername = foldername.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(foldername))

View File

@ -1,135 +1,139 @@
#!/usr/bin/env python3
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, APIC, IPLS, TCOM, TCOP, TCMP
TPUB, TSRC, USLT, APIC, IPLS, TCOM, TCOP, TCMP
def tagID3(stream, track, save):
try:
tag = ID3(stream)
except ID3NoHeaderError:
tag = ID3()
try:
tag = ID3(stream)
except ID3NoHeaderError:
tag = ID3()
if save['title']:
tag.add(TIT2(text=track['title']))
if save['artist']:
if save['multitagSeparator'] != "default":
tag.add(TPE1(text=track['artistsString']))
tag.add(TXXX(desc="ARTISTS", text=track['artists']))
else:
tag.add(TPE1(text=track['artists']))
if save['album']:
tag.add(TALB(text=track['album']['title']))
if save['albumArtist']:
tag.add(TPE2(text=track['album']['artists']))
if save['trackNumber']:
tag.add(TRCK(text=str(track['trackNumber'])+("/"+str(track['album']['trackTotal']) if save['trackTotal'] else "")))
if save['discNumber']:
tag.add(TPOS(text=str(track['discNumber'])+("/"+str(track['album']['discTotal']) if save['discTotal'] else "")))
if save['genre']:
tag.add(TCON(text=track['album']['genre']))
if save['year']:
tag.add(TYER(text=str(track['date']['year'])))
if save['date']:
tag.add(TDAT(text=str(track['date']['month']) + str(track['date']['day'])))
if save['length']:
tag.add(TLEN(text=str(track['duration'])))
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 'unsync' in track['lyrics'] and save['lyrics']:
tag.add(USLT(text=track['lyrics']['unsync']))
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']:
tag.add(TCMP(text="1"))
if save['cover'] and track['album']['picPath']:
with open(track['album']['picPath'], 'rb') as f:
tag.add(APIC(3, 'image/jpeg' if track['album']['picPath'].endswith('jpg') else 'image/png', 3, data=f.read()))
if save['title']:
tag.add(TIT2(text=track['title']))
if save['artist']:
if save['multitagSeparator'] != "default":
tag.add(TPE1(text=track['artistsString']))
tag.add(TXXX(desc="ARTISTS", text=track['artists']))
else:
tag.add(TPE1(text=track['artists']))
if save['album']:
tag.add(TALB(text=track['album']['title']))
if save['albumArtist']:
tag.add(TPE2(text=track['album']['artists']))
if save['trackNumber']:
tag.add(TRCK(
text=str(track['trackNumber']) + ("/" + str(track['album']['trackTotal']) if save['trackTotal'] else "")))
if save['discNumber']:
tag.add(
TPOS(text=str(track['discNumber']) + ("/" + str(track['album']['discTotal']) if save['discTotal'] else "")))
if save['genre']:
tag.add(TCON(text=track['album']['genre']))
if save['year']:
tag.add(TYER(text=str(track['date']['year'])))
if save['date']:
tag.add(TDAT(text=str(track['date']['month']) + str(track['date']['day'])))
if save['length']:
tag.add(TLEN(text=str(track['duration'])))
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 'unsync' in track['lyrics'] and save['lyrics']:
tag.add(USLT(text=track['lyrics']['unsync']))
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']:
tag.add(TCMP(text="1"))
if save['cover'] and track['album']['picPath']:
with open(track['album']['picPath'], 'rb') as f:
tag.add(
APIC(3, 'image/jpeg' if track['album']['picPath'].endswith('jpg') else 'image/png', 3, data=f.read()))
tag.save(stream, v1=2 if save['saveID3v1'] else 0, v2_version=3, v23_sep=None if save['useNullSeparator'] else ' / ')
tag.save(stream, v1=2 if save['saveID3v1'] else 0, v2_version=3,
v23_sep=None if save['useNullSeparator'] else ' / ')
def tagFLAC(stream, track, save):
tag = FLAC(stream)
tag = FLAC(stream)
if save['title']:
tag["TITLE"] = track['title']
if save['artist']:
if save['multitagSeparator'] != "default":
tag["ARTIST"] = track['artistsString']
tag["ARTISTS"] = track['artists']
else:
tag["ARTIST"] = track['artists']
if save['album']:
tag["ALBUM"] = track['album']['title']
if save['albumArtist']:
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']
if save['year']:
tag["YEAR"] = str(track['date']['year'])
if save['date']:
tag["DATE"] = track['dateString']
if save['length']:
tag["LENGTH"] = str(track['duration'])
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 'unsync' in track['lyrics'] 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 role == 'composer' and save['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']:
tag["COMPILATION"] = "1"
if save['title']:
tag["TITLE"] = track['title']
if save['artist']:
if save['multitagSeparator'] != "default":
tag["ARTIST"] = track['artistsString']
tag["ARTISTS"] = track['artists']
else:
tag["ARTIST"] = track['artists']
if save['album']:
tag["ALBUM"] = track['album']['title']
if save['albumArtist']:
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']
if save['year']:
tag["YEAR"] = str(track['date']['year'])
if save['date']:
tag["DATE"] = track['dateString']
if save['length']:
tag["LENGTH"] = str(track['duration'])
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 'unsync' in track['lyrics'] 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 role == 'composer' and save['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']:
tag["COMPILATION"] = "1"
if save['cover'] and track['album']['picPath']:
image = Picture()
image.type = 3
image.mime = 'image/jpeg' if track['album']['picPath'].endswith('jpg') else 'image/png'
with open(track['album']['picPath'], 'rb') as f:
image.data = f.read()
tag.add_picture(image)
if save['cover'] and track['album']['picPath']:
image = Picture()
image.type = 3
image.mime = 'image/jpeg' if track['album']['picPath'].endswith('jpg') else 'image/png'
with open(track['album']['picPath'], 'rb') as f:
image.data = f.read()
tag.add_picture(image)
tag.save(deleteid3=True)
tag.save(deleteid3=True)