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

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ __pycache__
/dist
# local env files
/venv/
.env.local
.env.*.local

View File

@ -1,8 +1,10 @@
#!/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')
@ -12,5 +14,6 @@ def download(bitrate, url):
app.downloadLink(url, settings, bitrate)
click.echo("All done!")
if __name__ == '__main__':
download()

View File

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

View File

@ -1,14 +1,14 @@
#!/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:
@ -51,7 +51,9 @@ class Deezer:
response = site.json()
return response['results']['PUID']
def gw_api_call(self, method, args={}):
def gw_api_call(self, method, args=None):
if args is None:
args = {}
try:
result = self.session.post(
self.api_url,
@ -70,7 +72,9 @@ class Deezer:
return self.gw_api_call(method, args)
return result.json()
def api_call(self, method, args={}):
def api_call(self, method, args=None):
if args is None:
args = {}
try:
result = self.session.get(
self.legacy_api_url + method,
@ -203,7 +207,9 @@ class Deezer:
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']
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']:
@ -212,7 +218,10 @@ class Deezer:
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']
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"]
@ -263,7 +272,8 @@ class Deezer:
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)
chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(
chunk)
outfile.write(chunk)
i += 1
@ -278,7 +288,8 @@ class Deezer:
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)
chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(
chunk)
stream.write(chunk)
i += 1
@ -305,23 +316,23 @@ class Deezer:
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("", "'")
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:
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:
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:
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:
if len(resp['data']) > 0:
return resp['data'][0]['id']
else:
return 0

View File

@ -1,16 +1,16 @@
#!/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:")
@ -18,6 +18,7 @@ def requestValidArl():
break
return arl
def login():
configFolder = localpaths.getConfigFolder()
if not path.isdir(configFolder):
@ -32,5 +33,6 @@ def login():
with open(path.join(configFolder, '.arl'), 'w') as f:
f.write(arl)
def downloadLink(url, settings, bitrate=None):
addToQueue(dz, sp, url, settings, bitrate)

View File

@ -1,18 +1,20 @@
#!/usr/bin/env python3
from deemix.api.deezer import APIError, USER_AGENT_HEADER
from deemix.utils.taggers import tagID3, tagFLAC
from deemix.utils.pathtemplates import generateFilename, generateFilepath, settingsRegexAlbum, settingsRegexArtist
from deemix.utils.misc import changeCase
import os.path
from os import makedirs, remove, system as execute
from requests import get
from requests.exceptions import HTTPError, ConnectionError
from tempfile import gettempdir
from concurrent.futures import ThreadPoolExecutor
from Cryptodome.Cipher import Blowfish
from time import sleep
import re
import traceback
from concurrent.futures import ThreadPoolExecutor
from os import makedirs, remove, system as execute
from tempfile import gettempdir
from time import sleep
from Cryptodome.Cipher import Blowfish
from requests import get
from requests.exceptions import HTTPError, ConnectionError
from deemix.api.deezer import APIError, USER_AGENT_HEADER
from deemix.utils.misc import changeCase
from deemix.utils.pathtemplates import generateFilename, generateFilepath, settingsRegexAlbum, settingsRegexArtist
from deemix.utils.taggers import tagID3, tagFLAC
TEMPDIR = os.path.join(gettempdir(), 'deemix-imgs')
if not os.path.isdir(TEMPDIR):
@ -30,6 +32,7 @@ extensions = {
downloadPercentage = 0
lastPercentage = 0
def stream_track(dz, track, stream, trackAPI, queueItem, interface=None):
global downloadPercentage, lastPercentage
if 'cancel' in queueItem:
@ -65,6 +68,7 @@ def stream_track(dz, track, stream, trackAPI, queueItem, interface=None):
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'progress': lastPercentage})
i += 1
def downloadImage(url, path):
if not os.path.isfile(path):
try:
@ -82,6 +86,7 @@ def downloadImage(url, path):
else:
return path
def formatDate(date, template):
if 'YYYY' in template:
template = template.replace('YYYY', str(date['year']))
@ -99,6 +104,7 @@ def formatDate(date, template):
template = template.replace('D', str(date['day']))
return template
def getPreferredBitrate(filesize, bitrate, fallback=True):
if not fallback:
formats = {9: 'flac', 3: 'mp3_320', 1: 'mp3_128', 15: '360_hq', 14: '360_mq', 13: '360_lq'}
@ -106,7 +112,7 @@ def getPreferredBitrate(filesize, bitrate, fallback=True):
return (int(bitrate), filesize[formats[int(bitrate)]])
else:
return (-100, 0)
if int(bitrate) in [13,14,15]:
if int(bitrate) in [13, 14, 15]:
formats = {'360_hq': 15, '360_mq': 14, '360_lq': 13}
selectedFormat = -200
selectedFilesize = 0
@ -126,6 +132,7 @@ def getPreferredBitrate(filesize, bitrate, fallback=True):
break
return (selectedFormat, selectedFilesize)
def parseEssentialTrackData(track, trackAPI):
track['id'] = trackAPI['SNG_ID']
track['duration'] = trackAPI['DURATION']
@ -146,7 +153,8 @@ def parseEssentialTrackData(track, trackAPI):
return track
def getTrackData(dz, trackAPI_gw, trackAPI = None, albumAPI_gw = None, albumAPI = None):
def getTrackData(dz, trackAPI_gw, trackAPI=None, albumAPI_gw=None, albumAPI=None):
if not 'MD5_ORIGIN' in trackAPI_gw:
trackAPI_gw['MD5_ORIGIN'] = dz.get_track_md5(trackAPI_gw['SNG_ID'])
@ -185,7 +193,8 @@ def getTrackData(dz, trackAPI_gw, trackAPI = None, albumAPI_gw = None, albumAPI
track['explicit'] = trackAPI_gw['EXPLICIT_LYRICS'] != "0"
if 'COPYRIGHT' in trackAPI_gw:
track['copyright'] = trackAPI_gw['COPYRIGHT']
track['replayGain'] = "{0:.2f} dB".format((float(trackAPI_gw['GAIN']) + 18.4) * -1) if 'GAIN' in trackAPI_gw else None
track['replayGain'] = "{0:.2f} dB".format(
(float(trackAPI_gw['GAIN']) + 18.4) * -1) if 'GAIN' in trackAPI_gw else None
track['ISRC'] = trackAPI_gw['ISRC']
track['trackNumber'] = trackAPI_gw['TRACK_NUMBER']
track['contributors'] = trackAPI_gw['SNG_CONTRIBUTORS']
@ -236,7 +245,7 @@ def getTrackData(dz, trackAPI_gw, trackAPI = None, albumAPI_gw = None, albumAPI
track['album']['mainArtist'] = {
'id': albumAPI['artist']['id'],
'name': albumAPI['artist']['name'],
'pic': albumAPI['artist']['picture_small'][albumAPI['artist']['picture_small'].find('artist/')+7:-24]
'pic': albumAPI['artist']['picture_small'][albumAPI['artist']['picture_small'].find('artist/') + 7:-24]
}
track['album']['artist'] = {}
track['album']['artists'] = []
@ -251,7 +260,7 @@ def getTrackData(dz, trackAPI_gw, trackAPI = None, albumAPI_gw = None, albumAPI
track['album']['barcode'] = albumAPI['upc'] if 'upc' in albumAPI else "Unknown"
track['album']['label'] = albumAPI['label'] if 'label' in albumAPI else "Unknown"
if not 'pic' in track['album']:
track['album']['pic'] = albumAPI['cover_small'][albumAPI['cover_small'].find('cover/')+6:-24]
track['album']['pic'] = albumAPI['cover_small'][albumAPI['cover_small'].find('cover/') + 6:-24]
if 'release_date' in albumAPI:
track['album']['date'] = {
'day': albumAPI["release_date"][8:10],
@ -273,7 +282,8 @@ def getTrackData(dz, trackAPI_gw, trackAPI = None, albumAPI_gw = None, albumAPI
}
artistAPI = dz.get_artist(track['album']['mainArtist']['id'])
track['album']['artists'] = albumAPI_gw['ART_NAME']
track['album']['mainArtist']['pic'] = artistAPI['picture_small'][artistAPI['picture_small'].find('artist/')+7:-24]
track['album']['mainArtist']['pic'] = artistAPI['picture_small'][
artistAPI['picture_small'].find('artist/') + 7:-24]
track['album']['trackTotal'] = albumAPI_gw['NUMBER_TRACK']
track['album']['discTotal'] = albumAPI_gw['NUMBER_DISK']
track['album']['recordType'] = "Album"
@ -335,7 +345,7 @@ def getTrackData(dz, trackAPI_gw, trackAPI = None, albumAPI_gw = None, albumAPI
pos = track['title_clean'].lower().find("(feat.")
tempTrack = track['title_clean'][:pos]
if ")" in track['title_clean']:
tempTrack += track['title_clean'][track['title_clean'].find(")",pos+1)+1:]
tempTrack += track['title_clean'][track['title_clean'].find(")", pos + 1) + 1:]
track['title_clean'] = tempTrack.strip()
# Create artists strings
@ -344,8 +354,8 @@ def getTrackData(dz, trackAPI_gw, trackAPI = None, albumAPI_gw = None, albumAPI
tot = len(track['artist']['Main'])
for i, art in enumerate(track['artist']['Main']):
track['mainArtistsString'] += art
if tot != i+1:
if tot-1 == i+1:
if tot != i + 1:
if tot - 1 == i + 1:
track['mainArtistsString'] += " & "
else:
track['mainArtistsString'] += ", "
@ -356,8 +366,8 @@ def getTrackData(dz, trackAPI_gw, trackAPI = None, albumAPI_gw = None, albumAPI
track['featArtistsString'] = "feat. "
for i, art in enumerate(track['artist']['Featured']):
track['featArtistsString'] += art
if tot != i+1:
if tot-1 == i+1:
if tot != i + 1:
if tot - 1 == i + 1:
track['featArtistsString'] += " & "
else:
track['featArtistsString'] += ", "
@ -366,12 +376,13 @@ def getTrackData(dz, trackAPI_gw, trackAPI = None, albumAPI_gw = None, albumAPI
if "(feat." in track['title'].lower():
track['title_feat'] = track['title']
elif 'Featured' in track['artist']:
track['title_feat'] = track['title']+" ({})".format(track['featArtistsString'])
track['title_feat'] = track['title'] + " ({})".format(track['featArtistsString'])
else:
track['title_feat'] = track['title']
return track
def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None, interface=None):
result = {}
if 'cancel' in queueItem:
@ -390,16 +401,17 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
}
if interface:
queueItem['failed'] += 1
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': result['error']['data'], 'error': "Track not available on Deezer!"})
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': result['error']['data'],
'error': "Track not available on Deezer!"})
return result
# Get the metadata
if extraTrack:
track = extraTrack
else:
track = getTrackData(dz,
trackAPI_gw = trackAPI,
trackAPI = trackAPI['_EXTRA_TRACK'] if '_EXTRA_TRACK' in trackAPI else None,
albumAPI = trackAPI['_EXTRA_ALBUM'] if '_EXTRA_ALBUM' in trackAPI else None
trackAPI_gw=trackAPI,
trackAPI=trackAPI['_EXTRA_TRACK'] if '_EXTRA_TRACK' in trackAPI else None,
albumAPI=trackAPI['_EXTRA_ALBUM'] if '_EXTRA_ALBUM' in trackAPI else None
)
if 'cancel' in queueItem:
result['cancel'] = True
@ -415,14 +427,16 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
return downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=track, interface=interface)
elif not 'searched' in track and settings['fallbackSearch']:
print("Track not yet encoded, searching for alternative")
searchedId = dz.get_track_from_metadata(track['mainArtist']['name'], track['title'], track['album']['title'])
searchedId = dz.get_track_from_metadata(track['mainArtist']['name'], track['title'],
track['album']['title'])
if searchedId != 0:
trackNew = dz.get_track_gw(searchedId)
if not 'MD5_ORIGIN' in trackNew:
trackNew['MD5_ORIGIN'] = dz.get_track_md5(trackNew['SNG_ID'])
track = parseEssentialTrackData(track, trackNew)
track['searched'] = True
return downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=track, interface=interface)
return downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=track,
interface=interface)
else:
print("ERROR: Track not yet encoded and no alternative found!")
result['error'] = {
@ -431,7 +445,8 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
}
if interface:
queueItem['failed'] += 1
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, 'error': "Track not yet encoded and no alternative found!"})
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track,
'error': "Track not yet encoded and no alternative found!"})
return result
else:
print("ERROR: Track not yet encoded!")
@ -441,7 +456,8 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
}
if interface:
queueItem['failed'] += 1
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, 'error': "Track not yet encoded!"})
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track,
'error': "Track not yet encoded!"})
return result
# Get the selected bitrate
@ -454,7 +470,8 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
}
if interface:
queueItem['failed'] += 1
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, 'error': "Track not found at desired bitrate."})
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track,
'error': "Track not found at desired bitrate."})
return result
elif format == -200:
print("ERROR: This track is not available in 360 Reality Audio format. Please select another format.")
@ -464,21 +481,25 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
}
if interface:
queueItem['failed'] += 1
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, 'error': "Track is not available in Reality Audio 360."})
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track,
'error': "Track is not available in Reality Audio 360."})
return result
track['selectedFormat'] = format
track['selectedFilesize'] = filesize
track['dateString'] = formatDate(track['date'], settings['dateFormat'])
if settings['tags']['savePlaylistAsCompilation'] and "_EXTRA_PLAYLIST" in trackAPI:
if 'dzcdn.net'in trackAPI["_EXTRA_PLAYLIST"]['picture_small']:
track['album']['picUrl'] = trackAPI["_EXTRA_PLAYLIST"]['picture_small'][:-24]+"/{}x{}-{}".format(track['album']['pic'], settings['embeddedArtworkSize'], settings['embeddedArtworkSize'], 'none-100-0-0.png' if settings['PNGcovers'] else f'000000-{settings["jpegImageQuality"]}-0-0.jpg')
if 'dzcdn.net' in trackAPI["_EXTRA_PLAYLIST"]['picture_small']:
track['album']['picUrl'] = trackAPI["_EXTRA_PLAYLIST"]['picture_small'][:-24] + "/{}x{}-{}".format(
track['album']['pic'], settings['embeddedArtworkSize'], settings['embeddedArtworkSize'],
'none-100-0-0.png' if settings['PNGcovers'] else f'000000-{settings["jpegImageQuality"]}-0-0.jpg')
else:
track['album']['picUrl'] = trackAPI["_EXTRA_PLAYLIST"]['picture_xl']
track['album']['title'] = trackAPI["_EXTRA_PLAYLIST"]['title']
track['album']['mainArtist'] = {
'id': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['id'],
'name': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['name'],
'pic': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['picture_small'][trackAPI["_EXTRA_PLAYLIST"]['various_artist']['picture_small'].find('artist/')+7:-24]
'pic': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['picture_small'][
trackAPI["_EXTRA_PLAYLIST"]['various_artist']['picture_small'].find('artist/') + 7:-24]
}
track['album']['artist'] = {"Main": [trackAPI["_EXTRA_PLAYLIST"]['various_artist']['name'], ]}
track['album']['artists'] = [trackAPI["_EXTRA_PLAYLIST"]['various_artist']['name'], ]
@ -497,7 +518,9 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
else:
if 'date' in track['album']:
track['date'] = track['album']['date']
track['album']['picUrl'] = "https://e-cdns-images.dzcdn.net/images/cover/{}/{}x{}-{}".format(track['album']['pic'], settings['embeddedArtworkSize'], settings['embeddedArtworkSize'], 'none-100-0-0.png' if settings['PNGcovers'] else f'000000-{settings["jpegImageQuality"]}-0-0.jpg')
track['album']['picUrl'] = "https://e-cdns-images.dzcdn.net/images/cover/{}/{}x{}-{}".format(
track['album']['pic'], settings['embeddedArtworkSize'], settings['embeddedArtworkSize'],
'none-100-0-0.png' if settings['PNGcovers'] else f'000000-{settings["jpegImageQuality"]}-0-0.jpg')
track['album']['bitrate'] = format
track['album']['dateString'] = formatDate(track['album']['date'], settings['dateFormat'])
@ -520,7 +543,7 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
if settings['tags']['multitagSeparator'] == "andFeat":
track['artistsString'] = track['mainArtistsString']
if 'featArtistsString' in track and settings['featuredToTitle'] != "2":
track['artistsString'] += " "+track['featArtistsString']
track['artistsString'] += " " + track['featArtistsString']
else:
track['artistsString'] = settings['tags']['multitagSeparator'].join(track['artists'])
else:
@ -543,15 +566,17 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
return result
# Download and cache coverart
if settings['tags']['savePlaylistAsCompilation'] and "_EXTRA_PLAYLIST" in trackAPI:
track['album']['picPath'] = os.path.join(TEMPDIR, f"pl{trackAPI['_EXTRA_PLAYLIST']['id']}_{settings['embeddedArtworkSize']}.{'png' if settings['PNGcovers'] else 'jpg'}")
track['album']['picPath'] = os.path.join(TEMPDIR,
f"pl{trackAPI['_EXTRA_PLAYLIST']['id']}_{settings['embeddedArtworkSize']}.{'png' if settings['PNGcovers'] else 'jpg'}")
else:
track['album']['picPath'] = os.path.join(TEMPDIR, f"alb{track['album']['id']}_{settings['embeddedArtworkSize']}.{'png' if settings['PNGcovers'] else 'jpg'}")
track['album']['picPath'] = os.path.join(TEMPDIR,
f"alb{track['album']['id']}_{settings['embeddedArtworkSize']}.{'png' if settings['PNGcovers'] else 'jpg'}")
track['album']['picPath'] = downloadImage(track['album']['picUrl'], track['album']['picPath'])
if os.path.sep in filename:
tempPath = filename[:filename.rfind(os.path.sep)]
filepath = os.path.join(filepath, tempPath)
filename = filename[filename.rfind(os.path.sep)+len(os.path.sep):]
filename = filename[filename.rfind(os.path.sep) + len(os.path.sep):]
makedirs(filepath, exist_ok=True)
writepath = os.path.join(filepath, filename + extensions[track['selectedFormat']])
@ -562,20 +587,27 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
# Save local album art
if coverPath:
result['albumURL'] = track['album']['picUrl'].replace(f"{settings['embeddedArtworkSize']}x{settings['embeddedArtworkSize']}", f"{settings['localArtworkSize']}x{settings['localArtworkSize']}")
result['albumPath'] = os.path.join(coverPath, f"{settingsRegexAlbum(settings['coverImageTemplate'], track['album'], settings, trackAPI)}.{'png' if settings['PNGcovers'] else 'jpg'}")
result['albumURL'] = track['album']['picUrl'].replace(
f"{settings['embeddedArtworkSize']}x{settings['embeddedArtworkSize']}",
f"{settings['localArtworkSize']}x{settings['localArtworkSize']}")
result['albumPath'] = os.path.join(coverPath,
f"{settingsRegexAlbum(settings['coverImageTemplate'], track['album'], settings, trackAPI)}.{'png' if settings['PNGcovers'] else 'jpg'}")
# Save artist art
if artistPath:
result['artistURL'] = "https://e-cdns-images.dzcdn.net/images/artist/{}/{}x{}-{}".format(track['album']['mainArtist']['pic'], settings['localArtworkSize'], settings['localArtworkSize'], 'none-100-0-0.png' if settings['PNGcovers'] else f'000000-{settings["jpegImageQuality"]}-0-0.jpg')
result['artistPath'] = os.path.join(artistPath, f"{settingsRegexArtist(settings['artistImageTemplate'], track['album']['mainArtist'], settings)}.{'png' if settings['PNGcovers'] else 'jpg'}")
result['artistURL'] = "https://e-cdns-images.dzcdn.net/images/artist/{}/{}x{}-{}".format(
track['album']['mainArtist']['pic'], settings['localArtworkSize'], settings['localArtworkSize'],
'none-100-0-0.png' if settings['PNGcovers'] else f'000000-{settings["jpegImageQuality"]}-0-0.jpg')
result['artistPath'] = os.path.join(artistPath,
f"{settingsRegexArtist(settings['artistImageTemplate'], track['album']['mainArtist'], settings)}.{'png' if settings['PNGcovers'] else 'jpg'}")
# Data for m3u file
if extrasPath:
result['extrasPath'] = extrasPath
result['playlistPosition'] = writepath[len(extrasPath):]
track['downloadUrl'] = dz.get_track_stream_url(track['id'], track['MD5'], track['mediaVersion'], track['selectedFormat'])
track['downloadUrl'] = dz.get_track_stream_url(track['id'], track['MD5'], track['mediaVersion'],
track['selectedFormat'])
try:
with open(writepath, 'wb') as stream:
stream_track(dz, track, stream, trackAPI, queueItem, interface)
@ -598,14 +630,16 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
return downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=track, interface=interface)
elif not 'searched' in track and settings['fallbackSearch']:
print("Track not available, searching for alternative")
searchedId = dz.get_track_from_metadata(track['mainArtist']['name'], track['title'], track['album']['title'])
searchedId = dz.get_track_from_metadata(track['mainArtist']['name'], track['title'],
track['album']['title'])
if searchedId != 0:
trackNew = dz.get_track_gw(searchedId)
if not 'MD5_ORIGIN' in trackNew:
trackNew['MD5_ORIGIN'] = dz.get_track_md5(trackNew['SNG_ID'])
track = parseEssentialTrackData(track, trackNew)
track['searched'] = True
return downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=track, interface=interface)
return downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=track,
interface=interface)
else:
print("ERROR: Track not available on deezer's servers and no alternative found!")
result['error'] = {
@ -614,7 +648,8 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
}
if interface:
queueItem['failed'] += 1
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, 'error': "Track not available on deezer's servers and no alternative found!"})
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track,
'error': "Track not available on deezer's servers and no alternative found!"})
return result
else:
print("ERROR: Track not available on deezer's servers!")
@ -624,7 +659,8 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
}
if interface:
queueItem['failed'] += 1
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, 'error': "Track not available on deezer's servers!"})
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track,
'error': "Track not available on deezer's servers!"})
return result
if track['selectedFormat'] in [3, 1, 8]:
tagID3(writepath, track, settings['tags'])
@ -638,6 +674,7 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'downloaded': True})
return result
def downloadTrackObj_wrap(dz, track, settings, bitrate, queueItem, interface):
try:
result = downloadTrackObj(dz, track, settings, bitrate, queueItem, interface=interface)
@ -647,7 +684,8 @@ def downloadTrackObj_wrap(dz, track, settings, bitrate, queueItem, interface):
'message': str(e),
'data': {
'id': track['SNG_ID'],
'title': track['SNG_TITLE'] + (" "+track['VERSION'] if 'VERSION' in track and track['VERSION'] else ""),
'title': track['SNG_TITLE'] + (
" " + track['VERSION'] if 'VERSION' in track and track['VERSION'] else ""),
'mainArtist': {'name': track['ART_NAME']}
}
}
@ -657,6 +695,7 @@ def downloadTrackObj_wrap(dz, track, settings, bitrate, queueItem, interface):
interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True})
return result
def download(dz, queueItem, interface=None):
global downloadPercentage, lastPercentage
settings = queueItem['settings']
@ -671,7 +710,9 @@ def download(dz, queueItem, interface=None):
'message': str(e),
'data': {
'id': queueItem['single']['SNG_ID'],
'title': queueItem['single']['SNG_TITLE'] + (" "+queueItem['single']['VERSION'] if 'VERSION' in queueItem['single'] and queueItem['single']['VERSION'] else ""),
'title': queueItem['single']['SNG_TITLE'] + (
" " + queueItem['single']['VERSION'] if 'VERSION' in queueItem['single'] and
queueItem['single']['VERSION'] else ""),
'mainArtist': {'name': queueItem['single']['ART_NAME']}
}
}
@ -685,11 +726,13 @@ def download(dz, queueItem, interface=None):
playlist = [None] * len(queueItem['collection'])
with ThreadPoolExecutor(settings['queueConcurrency']) as executor:
for pos, track in enumerate(queueItem['collection'], start=0):
playlist[pos] = executor.submit(downloadTrackObj_wrap, dz, track, settings, bitrate, queueItem, interface=interface)
playlist[pos] = executor.submit(downloadTrackObj_wrap, dz, track, settings, bitrate, queueItem,
interface=interface)
download_path = after_download(playlist, settings, queueItem)
if interface:
if 'cancel' in queueItem:
interface.send('toast', {'msg': "Current item cancelled.", 'icon':'done', 'dismiss': True, 'id':'cancelling_'+queueItem['uuid']})
interface.send('toast', {'msg': "Current item cancelled.", 'icon': 'done', 'dismiss': True,
'id': 'cancelling_' + queueItem['uuid']})
interface.send("removedFromQueue", queueItem['uuid'])
else:
interface.send("finishDownload", queueItem['uuid'])
@ -699,6 +742,7 @@ def download(dz, queueItem, interface=None):
'download_path': download_path
}
def after_download(tracks, settings, queueItem):
extrasPath = None
playlist = [None] * len(tracks)
@ -713,7 +757,7 @@ def after_download(tracks, settings, queueItem):
result['error']['data'] = {'id': 0, 'title': 'Unknown', 'mainArtist': {'name': 'Unknown'}}
errors += f"{result['error']['data']['id']} | {result['error']['data']['mainArtist']['name']} - {result['error']['data']['title']} | {result['error']['message']}\r\n"
if 'searched' in result:
searched += result['searched']+"\r\n"
searched += result['searched'] + "\r\n"
if not extrasPath and 'extrasPath' in result:
extrasPath = result['extrasPath']
if settings['saveArtwork'] and 'albumPath' in result:
@ -735,11 +779,12 @@ def after_download(tracks, settings, queueItem):
if settings['createM3U8File']:
with open(os.path.join(extrasPath, 'playlist.m3u8'), 'w') as f:
for line in playlist:
f.write(line+"\n")
f.write(line + "\n")
if settings['executeCommand'] != "":
execute(settings['executeCommand'].replace("%folder%", extrasPath))
return extrasPath
def after_download_single(track, settings, queueItem):
if 'cancel' in track:
return None
@ -751,12 +796,13 @@ def after_download_single(track, settings, queueItem):
if not track['searched'] in orig:
if orig != "":
orig += "\r\n"
orig += track['searched']+"\r\n"
orig += track['searched'] + "\r\n"
f.write(orig)
if settings['executeCommand'] != "":
execute(settings['executeCommand'].replace("%folder%", track['extrasPath']))
return track['extrasPath']
class downloadCancelled(Exception):
"""Base class for exceptions in this module."""
pass

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,6 +25,7 @@ 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']
@ -46,7 +46,8 @@ def generateQueueItem(dz, sp, url, settings, bitrate=None, albumAPI=None, interf
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[
'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
@ -64,12 +65,13 @@ def generateQueueItem(dz, sp, url, settings, bitrate=None, albumAPI=None, interf
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)
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['cover'] = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg'
result['size'] = albumAPI['nb_tracks']
result['downloaded'] = 0
result['failed'] = 0
@ -95,7 +97,7 @@ def generateQueueItem(dz, sp, url, settings, bitrate=None, albumAPI=None, interf
result['title'] = playlistAPI['title']
result['artist'] = playlistAPI['creator']['name']
result['cover'] = playlistAPI['picture_small'][:-24]+'/75x75-000000-80-0-0.jpg'
result['cover'] = playlistAPI['picture_small'][:-24] + '/75x75-000000-80-0-0.jpg'
result['size'] = playlistAPI['nb_tracks']
result['downloaded'] = 0
result['failed'] = 0
@ -117,13 +119,17 @@ def generateQueueItem(dz, sp, url, settings, bitrate=None, albumAPI=None, interf
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'])})
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'])})
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 = {}
@ -156,18 +162,22 @@ def generateQueueItem(dz, sp, url, settings, bitrate=None, albumAPI=None, interf
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)})
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)})
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:
@ -192,7 +202,8 @@ def addToQueue(dz, sp, url, settings, bitrate=None, interface=None):
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'})
interface.send("toast",
{'msg': f"{queueItem['title']} is already in queue!", 'icon': 'playlist_add_check'})
return False
if interface:
interface.send("addedToQueue", queueItem)
@ -202,12 +213,13 @@ def addToQueue(dz, sp, url, settings, bitrate=None, interface=None):
nextItem(dz, interface)
return True
def nextItem(dz, interface=None):
global currentItem, queueList, queue
if currentItem != "":
return None
else:
if len(queue)>0:
if len(queue) > 0:
currentItem = queue.pop(0)
else:
return None
@ -216,6 +228,7 @@ def nextItem(dz, interface=None):
result = download(dz, queueList[currentItem], interface)
callbackQueueDone(result)
def callbackQueueDone(result):
global currentItem, queueList, queueComplete
if 'cancel' in queueList[currentItem]:
@ -225,15 +238,18 @@ def callbackQueueDone(result):
currentItem = ""
nextItem(result['dz'], result['interface'])
def getQueue():
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})
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)
@ -246,13 +262,15 @@ def removeFromQueue(uuid, interface=None):
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})
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:
@ -260,6 +278,7 @@ def cancelAllDownloads(interface=None):
if interface:
interface.send("removedAllDownloads", currentItem)
def removeFinishedDownloads(interface=None):
global queueList, queueComplete
for uuid in queueComplete:

View File

@ -1,13 +1,14 @@
#!/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
@ -30,10 +31,12 @@ def initSettings():
mkdir(settings['downloadLocation'])
return settings
def getSettings():
global settings
return settings
def saveSettings(newSettings):
global settings
settings = newSettings
@ -41,6 +44,7 @@ def saveSettings(newSettings):
json.dump(settings, configFile, indent=2)
return True
def settingsCheck():
global settings
global defaultSettings
@ -48,10 +52,10 @@ def settingsCheck():
for x in defaultSettings:
if not x in settings or type(settings[x]) != type(defaultSettings[x]):
settings[x] = defaultSettings[x]
changes+=1
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
changes += 1
if changes > 0:
saveSettings(settings)

View File

@ -1,13 +1,14 @@
#!/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 = {}
@ -49,7 +50,8 @@ class SpotifyHelper:
self.checkCredentials()
def createSpotifyConnection(self):
client_credentials_manager = SpotifyClientCredentials(client_id=self.credentials['clientId'], client_secret=self.credentials['clientSecret'])
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):
@ -61,7 +63,8 @@ class SpotifyHelper:
'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"},
'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'],
@ -95,9 +98,11 @@ class SpotifyHelper:
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
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'])
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):
@ -135,7 +140,8 @@ class SpotifyHelper:
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"
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']
@ -168,5 +174,6 @@ class SpotifyHelper:
result['collection'].append(deezerTrack)
return result
class spotifyFeaturesNotEnabled(Exception):
pass

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
import sys
import os.path as path
import sys
from os import getenv
userdata = ""
@ -15,7 +15,10 @@ elif getenv("XDG_CONFIG_HOME"):
else:
userdata = homedata + '/.config/deemix/';
def getHomeFolder():
return homedata
def getConfigFolder():
return userdata

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3
import re
def getBitrateInt(txt):
txt = str(txt)
if txt in ['flac', 'lossless', '9']:
@ -18,6 +19,7 @@ def getBitrateInt(txt):
else:
return None
def changeCase(string, type):
if type == "lower":
return string.lower()
@ -36,6 +38,7 @@ def changeCase(string, type):
else:
return string
def getIDFromLink(link, type):
if '?' in link:
link = link[:link.find('?')]
@ -85,6 +88,7 @@ def getTypeFromLink(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():

View File

@ -12,23 +12,26 @@ bitrateLabels = {
8: "128"
}
def fixName(txt, char='_'):
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 += 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]
@ -36,6 +39,7 @@ def antiDot(string):
string = "dot"
return string
def pad(num, max, dopad=True):
paddingsize = len(str(max))
if dopad:
@ -43,12 +47,15 @@ def pad(num, max, dopad=True):
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)
return settingsRegex(filename, track, settings,
trackAPI['_EXTRA_PLAYLIST'] if '_EXTRA_PLAYLIST' in trackAPI else None)
def generateFilepath(track, trackAPI, settings):
filepath = settings['downloadLocation']
@ -58,27 +65,34 @@ def generateFilepath(track, trackAPI, settings):
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 (
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['tags'][
'savePlaylistAsCompilation']) or
(settings['createArtistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist'])
):
if (int(track['id'])<0 and not 'mainArtist' in track['album']):
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
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']))
(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
filepath += antiDot(
settingsRegexAlbum(settings['albumNameTemplate'], track['album'], settings, trackAPI)) + pathSep
coverPath = filepath
if not ('_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']):
@ -88,24 +102,30 @@ def generateFilepath(track, trackAPI, settings):
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']))
(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)
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("%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']))
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']))
@ -121,23 +141,28 @@ def settingsRegex(filename, track, settings, playlist=None):
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']))
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("%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']))
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%",
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("%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:
@ -151,16 +176,19 @@ def settingsRegexAlbum(foldername, album, settings, trackAPI):
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))
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%",
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]))

View File

@ -23,9 +23,11 @@ def tagID3(stream, track, save):
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 "")))
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 "")))
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']:
@ -63,9 +65,11 @@ def tagID3(stream, track, save):
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.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):