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 /dist
# local env files # local env files
/venv/
.env.local .env.local
.env.*.local .env.*.local

View File

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

View File

@ -1,14 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import binascii import binascii
import time
import requests
from Cryptodome.Cipher import Blowfish, AES
from Cryptodome.Hash import MD5 from Cryptodome.Hash import MD5
from Cryptodome.Util.Padding import pad from Cryptodome.Util.Padding import pad
from Cryptodome.Cipher import Blowfish, AES USER_AGENT_HEADER = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " \
import requests "Chrome/79.0.3945.130 Safari/537.36"
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"
class Deezer: class Deezer:
@ -51,7 +51,9 @@ class Deezer:
response = site.json() response = site.json()
return response['results']['PUID'] 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: try:
result = self.session.post( result = self.session.post(
self.api_url, self.api_url,
@ -70,7 +72,9 @@ class Deezer:
return self.gw_api_call(method, args) return self.gw_api_call(method, args)
return result.json() return result.json()
def api_call(self, method, args={}): def api_call(self, method, args=None):
if args is None:
args = {}
try: try:
result = self.session.get( result = self.session.get(
self.legacy_api_url + method, self.legacy_api_url + method,
@ -203,7 +207,9 @@ class Deezer:
return tracks_array return tracks_array
def search_main_gw(self, term): 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 = [] order = []
for x in results['ORDER']: for x in results['ORDER']:
if x in ['TOP_RESULT', 'TRACK', 'ALBUM', 'ARTIST', 'PLAYLIST']: if x in ['TOP_RESULT', 'TRACK', 'ALBUM', 'ARTIST', 'PLAYLIST']:
@ -212,7 +218,10 @@ class Deezer:
return results return results
def search_gw(self, term, type, start, nb=20): 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): def get_lyrics_gw(self, sng_id):
return self.gw_api_call('song.getLyrics', {'sng_id': sng_id})["results"] return self.gw_api_call('song.getLyrics', {'sng_id': sng_id})["results"]
@ -263,7 +272,8 @@ class Deezer:
if not chunk: if not chunk:
break break
if (i % 3) == 0 and len(chunk) == 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)
outfile.write(chunk) outfile.write(chunk)
i += 1 i += 1
@ -278,7 +288,8 @@ class Deezer:
i = 0 i = 0
for chunk in request.iter_content(2048): for chunk in request.iter_content(2048):
if (i % 3) == 0 and len(chunk) == 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) stream.write(chunk)
i += 1 i += 1

View File

@ -1,16 +1,16 @@
#!/usr/bin/env python3 #!/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 import os.path as path
from os import mkdir 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() dz = Deezer()
sp = SpotifyHelper() sp = SpotifyHelper()
def requestValidArl(): def requestValidArl():
while True: while True:
arl = input("Paste here your arl:") arl = input("Paste here your arl:")
@ -18,6 +18,7 @@ def requestValidArl():
break break
return arl return arl
def login(): def login():
configFolder = localpaths.getConfigFolder() configFolder = localpaths.getConfigFolder()
if not path.isdir(configFolder): if not path.isdir(configFolder):
@ -32,5 +33,6 @@ def login():
with open(path.join(configFolder, '.arl'), 'w') as f: with open(path.join(configFolder, '.arl'), 'w') as f:
f.write(arl) f.write(arl)
def downloadLink(url, settings, bitrate=None): def downloadLink(url, settings, bitrate=None):
addToQueue(dz, sp, url, settings, bitrate) addToQueue(dz, sp, url, settings, bitrate)

View File

@ -1,18 +1,20 @@
#!/usr/bin/env python3 #!/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 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 re
import traceback 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') TEMPDIR = os.path.join(gettempdir(), 'deemix-imgs')
if not os.path.isdir(TEMPDIR): if not os.path.isdir(TEMPDIR):
@ -30,6 +32,7 @@ extensions = {
downloadPercentage = 0 downloadPercentage = 0
lastPercentage = 0 lastPercentage = 0
def stream_track(dz, track, stream, trackAPI, queueItem, interface=None): def stream_track(dz, track, stream, trackAPI, queueItem, interface=None):
global downloadPercentage, lastPercentage global downloadPercentage, lastPercentage
if 'cancel' in queueItem: 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}) interface.send("updateQueue", {'uuid': queueItem['uuid'], 'progress': lastPercentage})
i += 1 i += 1
def downloadImage(url, path): def downloadImage(url, path):
if not os.path.isfile(path): if not os.path.isfile(path):
try: try:
@ -82,6 +86,7 @@ def downloadImage(url, path):
else: else:
return path return path
def formatDate(date, template): def formatDate(date, template):
if 'YYYY' in template: if 'YYYY' in template:
template = template.replace('YYYY', str(date['year'])) template = template.replace('YYYY', str(date['year']))
@ -99,6 +104,7 @@ def formatDate(date, template):
template = template.replace('D', str(date['day'])) template = template.replace('D', str(date['day']))
return template return template
def getPreferredBitrate(filesize, bitrate, fallback=True): def getPreferredBitrate(filesize, bitrate, fallback=True):
if not fallback: if not fallback:
formats = {9: 'flac', 3: 'mp3_320', 1: 'mp3_128', 15: '360_hq', 14: '360_mq', 13: '360_lq'} formats = {9: 'flac', 3: 'mp3_320', 1: 'mp3_128', 15: '360_hq', 14: '360_mq', 13: '360_lq'}
@ -126,6 +132,7 @@ def getPreferredBitrate(filesize, bitrate, fallback=True):
break break
return (selectedFormat, selectedFilesize) return (selectedFormat, selectedFilesize)
def parseEssentialTrackData(track, trackAPI): def parseEssentialTrackData(track, trackAPI):
track['id'] = trackAPI['SNG_ID'] track['id'] = trackAPI['SNG_ID']
track['duration'] = trackAPI['DURATION'] track['duration'] = trackAPI['DURATION']
@ -146,6 +153,7 @@ def parseEssentialTrackData(track, trackAPI):
return track 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: if not 'MD5_ORIGIN' in trackAPI_gw:
trackAPI_gw['MD5_ORIGIN'] = dz.get_track_md5(trackAPI_gw['SNG_ID']) 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" track['explicit'] = trackAPI_gw['EXPLICIT_LYRICS'] != "0"
if 'COPYRIGHT' in trackAPI_gw: if 'COPYRIGHT' in trackAPI_gw:
track['copyright'] = trackAPI_gw['COPYRIGHT'] 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['ISRC'] = trackAPI_gw['ISRC']
track['trackNumber'] = trackAPI_gw['TRACK_NUMBER'] track['trackNumber'] = trackAPI_gw['TRACK_NUMBER']
track['contributors'] = trackAPI_gw['SNG_CONTRIBUTORS'] track['contributors'] = trackAPI_gw['SNG_CONTRIBUTORS']
@ -273,7 +282,8 @@ def getTrackData(dz, trackAPI_gw, trackAPI = None, albumAPI_gw = None, albumAPI
} }
artistAPI = dz.get_artist(track['album']['mainArtist']['id']) artistAPI = dz.get_artist(track['album']['mainArtist']['id'])
track['album']['artists'] = albumAPI_gw['ART_NAME'] 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']['trackTotal'] = albumAPI_gw['NUMBER_TRACK']
track['album']['discTotal'] = albumAPI_gw['NUMBER_DISK'] track['album']['discTotal'] = albumAPI_gw['NUMBER_DISK']
track['album']['recordType'] = "Album" track['album']['recordType'] = "Album"
@ -372,6 +382,7 @@ def getTrackData(dz, trackAPI_gw, trackAPI = None, albumAPI_gw = None, albumAPI
return track return track
def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None, interface=None): def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None, interface=None):
result = {} result = {}
if 'cancel' in queueItem: if 'cancel' in queueItem:
@ -390,7 +401,8 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
} }
if interface: if interface:
queueItem['failed'] += 1 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 return result
# Get the metadata # Get the metadata
if extraTrack: if extraTrack:
@ -415,14 +427,16 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
return downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=track, interface=interface) return downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=track, interface=interface)
elif not 'searched' in track and settings['fallbackSearch']: elif not 'searched' in track and settings['fallbackSearch']:
print("Track not yet encoded, searching for alternative") 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: if searchedId != 0:
trackNew = dz.get_track_gw(searchedId) trackNew = dz.get_track_gw(searchedId)
if not 'MD5_ORIGIN' in trackNew: if not 'MD5_ORIGIN' in trackNew:
trackNew['MD5_ORIGIN'] = dz.get_track_md5(trackNew['SNG_ID']) trackNew['MD5_ORIGIN'] = dz.get_track_md5(trackNew['SNG_ID'])
track = parseEssentialTrackData(track, trackNew) track = parseEssentialTrackData(track, trackNew)
track['searched'] = True 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: else:
print("ERROR: Track not yet encoded and no alternative found!") print("ERROR: Track not yet encoded and no alternative found!")
result['error'] = { result['error'] = {
@ -431,7 +445,8 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
} }
if interface: if interface:
queueItem['failed'] += 1 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 return result
else: else:
print("ERROR: Track not yet encoded!") print("ERROR: Track not yet encoded!")
@ -441,7 +456,8 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
} }
if interface: if interface:
queueItem['failed'] += 1 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 return result
# Get the selected bitrate # Get the selected bitrate
@ -454,7 +470,8 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
} }
if interface: if interface:
queueItem['failed'] += 1 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 return result
elif format == -200: elif format == -200:
print("ERROR: This track is not available in 360 Reality Audio format. Please select another format.") 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: if interface:
queueItem['failed'] += 1 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 return result
track['selectedFormat'] = format track['selectedFormat'] = format
track['selectedFilesize'] = filesize track['selectedFilesize'] = filesize
track['dateString'] = formatDate(track['date'], settings['dateFormat']) track['dateString'] = formatDate(track['date'], settings['dateFormat'])
if settings['tags']['savePlaylistAsCompilation'] and "_EXTRA_PLAYLIST" in trackAPI: if settings['tags']['savePlaylistAsCompilation'] and "_EXTRA_PLAYLIST" in trackAPI:
if 'dzcdn.net' in trackAPI["_EXTRA_PLAYLIST"]['picture_small']: 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') 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: else:
track['album']['picUrl'] = trackAPI["_EXTRA_PLAYLIST"]['picture_xl'] track['album']['picUrl'] = trackAPI["_EXTRA_PLAYLIST"]['picture_xl']
track['album']['title'] = trackAPI["_EXTRA_PLAYLIST"]['title'] track['album']['title'] = trackAPI["_EXTRA_PLAYLIST"]['title']
track['album']['mainArtist'] = { track['album']['mainArtist'] = {
'id': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['id'], 'id': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['id'],
'name': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['name'], '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']['artist'] = {"Main": [trackAPI["_EXTRA_PLAYLIST"]['various_artist']['name'], ]}
track['album']['artists'] = [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: else:
if 'date' in track['album']: if 'date' in track['album']:
track['date'] = track['album']['date'] 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']['bitrate'] = format
track['album']['dateString'] = formatDate(track['album']['date'], settings['dateFormat']) track['album']['dateString'] = formatDate(track['album']['date'], settings['dateFormat'])
@ -543,9 +566,11 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
return result return result
# Download and cache coverart # Download and cache coverart
if settings['tags']['savePlaylistAsCompilation'] and "_EXTRA_PLAYLIST" in trackAPI: 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: 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']) track['album']['picPath'] = downloadImage(track['album']['picUrl'], track['album']['picPath'])
if os.path.sep in filename: if os.path.sep in filename:
@ -562,20 +587,27 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
# Save local album art # Save local album art
if coverPath: if coverPath:
result['albumURL'] = track['album']['picUrl'].replace(f"{settings['embeddedArtworkSize']}x{settings['embeddedArtworkSize']}", f"{settings['localArtworkSize']}x{settings['localArtworkSize']}") result['albumURL'] = track['album']['picUrl'].replace(
result['albumPath'] = os.path.join(coverPath, f"{settingsRegexAlbum(settings['coverImageTemplate'], track['album'], settings, trackAPI)}.{'png' if settings['PNGcovers'] else 'jpg'}") 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 # Save artist art
if artistPath: 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['artistURL'] = "https://e-cdns-images.dzcdn.net/images/artist/{}/{}x{}-{}".format(
result['artistPath'] = os.path.join(artistPath, f"{settingsRegexArtist(settings['artistImageTemplate'], track['album']['mainArtist'], settings)}.{'png' if settings['PNGcovers'] else 'jpg'}") 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 # Data for m3u file
if extrasPath: if extrasPath:
result['extrasPath'] = extrasPath result['extrasPath'] = extrasPath
result['playlistPosition'] = writepath[len(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: try:
with open(writepath, 'wb') as stream: with open(writepath, 'wb') as stream:
stream_track(dz, track, stream, trackAPI, queueItem, interface) 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) return downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=track, interface=interface)
elif not 'searched' in track and settings['fallbackSearch']: elif not 'searched' in track and settings['fallbackSearch']:
print("Track not available, searching for alternative") 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: if searchedId != 0:
trackNew = dz.get_track_gw(searchedId) trackNew = dz.get_track_gw(searchedId)
if not 'MD5_ORIGIN' in trackNew: if not 'MD5_ORIGIN' in trackNew:
trackNew['MD5_ORIGIN'] = dz.get_track_md5(trackNew['SNG_ID']) trackNew['MD5_ORIGIN'] = dz.get_track_md5(trackNew['SNG_ID'])
track = parseEssentialTrackData(track, trackNew) track = parseEssentialTrackData(track, trackNew)
track['searched'] = True 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: else:
print("ERROR: Track not available on deezer's servers and no alternative found!") print("ERROR: Track not available on deezer's servers and no alternative found!")
result['error'] = { result['error'] = {
@ -614,7 +648,8 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
} }
if interface: if interface:
queueItem['failed'] += 1 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 return result
else: else:
print("ERROR: Track not available on deezer's servers!") print("ERROR: Track not available on deezer's servers!")
@ -624,7 +659,8 @@ def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None
} }
if interface: if interface:
queueItem['failed'] += 1 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 return result
if track['selectedFormat'] in [3, 1, 8]: if track['selectedFormat'] in [3, 1, 8]:
tagID3(writepath, track, settings['tags']) 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}) interface.send("updateQueue", {'uuid': queueItem['uuid'], 'downloaded': True})
return result return result
def downloadTrackObj_wrap(dz, track, settings, bitrate, queueItem, interface): def downloadTrackObj_wrap(dz, track, settings, bitrate, queueItem, interface):
try: try:
result = downloadTrackObj(dz, track, settings, bitrate, queueItem, interface=interface) 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), 'message': str(e),
'data': { 'data': {
'id': track['SNG_ID'], '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']} '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}) interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True})
return result return result
def download(dz, queueItem, interface=None): def download(dz, queueItem, interface=None):
global downloadPercentage, lastPercentage global downloadPercentage, lastPercentage
settings = queueItem['settings'] settings = queueItem['settings']
@ -671,7 +710,9 @@ def download(dz, queueItem, interface=None):
'message': str(e), 'message': str(e),
'data': { 'data': {
'id': queueItem['single']['SNG_ID'], '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']} 'mainArtist': {'name': queueItem['single']['ART_NAME']}
} }
} }
@ -685,11 +726,13 @@ def download(dz, queueItem, interface=None):
playlist = [None] * len(queueItem['collection']) playlist = [None] * len(queueItem['collection'])
with ThreadPoolExecutor(settings['queueConcurrency']) as executor: with ThreadPoolExecutor(settings['queueConcurrency']) as executor:
for pos, track in enumerate(queueItem['collection'], start=0): 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) download_path = after_download(playlist, settings, queueItem)
if interface: if interface:
if 'cancel' in queueItem: 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']) interface.send("removedFromQueue", queueItem['uuid'])
else: else:
interface.send("finishDownload", queueItem['uuid']) interface.send("finishDownload", queueItem['uuid'])
@ -699,6 +742,7 @@ def download(dz, queueItem, interface=None):
'download_path': download_path 'download_path': download_path
} }
def after_download(tracks, settings, queueItem): def after_download(tracks, settings, queueItem):
extrasPath = None extrasPath = None
playlist = [None] * len(tracks) playlist = [None] * len(tracks)
@ -740,6 +784,7 @@ def after_download(tracks, settings, queueItem):
execute(settings['executeCommand'].replace("%folder%", extrasPath)) execute(settings['executeCommand'].replace("%folder%", extrasPath))
return extrasPath return extrasPath
def after_download_single(track, settings, queueItem): def after_download_single(track, settings, queueItem):
if 'cancel' in track: if 'cancel' in track:
return None return None
@ -757,6 +802,7 @@ def after_download_single(track, settings, queueItem):
execute(settings['executeCommand'].replace("%folder%", track['extrasPath'])) execute(settings['executeCommand'].replace("%folder%", track['extrasPath']))
return track['extrasPath'] return track['extrasPath']
class downloadCancelled(Exception): class downloadCancelled(Exception):
"""Base class for exceptions in this module.""" """Base class for exceptions in this module."""
pass 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.app.downloader import download
from deemix.utils.misc import getIDFromLink, getTypeFromLink, getBitrateInt
queue = [] queue = []
queueList = {} queueList = {}
@ -26,6 +25,7 @@ if its an album/playlist
collection collection
""" """
def generateQueueItem(dz, sp, url, settings, bitrate=None, albumAPI=None, interface=None): def generateQueueItem(dz, sp, url, settings, bitrate=None, albumAPI=None, interface=None):
forcedBitrate = getBitrateInt(bitrate) forcedBitrate = getBitrateInt(bitrate)
bitrate = forcedBitrate if forcedBitrate else settings['maxBitrate'] 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']: if 'VERSION' in trackAPI and trackAPI['VERSION']:
result['title'] += " " + trackAPI['VERSION'] result['title'] += " " + trackAPI['VERSION']
result['artist'] = trackAPI['ART_NAME'] 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['size'] = 1
result['downloaded'] = 0 result['downloaded'] = 0
result['failed'] = 0 result['failed'] = 0
@ -64,7 +65,8 @@ def generateQueueItem(dz, sp, url, settings, bitrate=None, albumAPI=None, interf
albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK'] albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK']
albumAPI['copyright'] = albumAPI_gw['COPYRIGHT'] albumAPI['copyright'] = albumAPI_gw['COPYRIGHT']
if albumAPI['nb_tracks'] == 1: 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) tracksArray = dz.get_album_tracks_gw(id)
result['title'] = albumAPI['title'] result['title'] = albumAPI['title']
@ -117,13 +119,17 @@ def generateQueueItem(dz, sp, url, settings, bitrate=None, albumAPI=None, interf
elif type == "artist": elif type == "artist":
artistAPI = dz.get_artist(id) artistAPI = dz.get_artist(id)
if interface: 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) artistAPITracks = dz.get_artist_albums(id)
albumList = [] albumList = []
for album in artistAPITracks['data']: for album in artistAPITracks['data']:
albumList.append(generateQueueItem(dz, sp, album['link'], settings, bitrate)) albumList.append(generateQueueItem(dz, sp, album['link'], settings, bitrate))
if interface: 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 return albumList
elif type == "spotifytrack": elif type == "spotifytrack":
result = {} 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." result['error'] = "Spotify Features is not setted up correctly."
return result return result
if interface: 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 = sp.convert_spotify_playlist(dz, id, settings)
playlist['bitrate'] = bitrate playlist['bitrate'] = bitrate
playlist['uuid'] = f"{playlist['type']}_{id}_{bitrate}" playlist['uuid'] = f"{playlist['type']}_{id}_{bitrate}"
result = playlist result = playlist
if interface: 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: else:
print("URL not supported yet") print("URL not supported yet")
result['error'] = "URL not supported yet" result['error'] = "URL not supported yet"
return result return result
def addToQueue(dz, sp, url, settings, bitrate=None, interface=None): def addToQueue(dz, sp, url, settings, bitrate=None, interface=None):
global currentItem, queueList, queue global currentItem, queueList, queue
if not dz.logged_in: 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()): if queueItem['uuid'] in list(queueList.keys()):
print("Already in queue!") print("Already in queue!")
if interface: 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 return False
if interface: if interface:
interface.send("addedToQueue", queueItem) interface.send("addedToQueue", queueItem)
@ -202,6 +213,7 @@ def addToQueue(dz, sp, url, settings, bitrate=None, interface=None):
nextItem(dz, interface) nextItem(dz, interface)
return True return True
def nextItem(dz, interface=None): def nextItem(dz, interface=None):
global currentItem, queueList, queue global currentItem, queueList, queue
if currentItem != "": if currentItem != "":
@ -216,6 +228,7 @@ def nextItem(dz, interface=None):
result = download(dz, queueList[currentItem], interface) result = download(dz, queueList[currentItem], interface)
callbackQueueDone(result) callbackQueueDone(result)
def callbackQueueDone(result): def callbackQueueDone(result):
global currentItem, queueList, queueComplete global currentItem, queueList, queueComplete
if 'cancel' in queueList[currentItem]: if 'cancel' in queueList[currentItem]:
@ -225,15 +238,18 @@ def callbackQueueDone(result):
currentItem = "" currentItem = ""
nextItem(result['dz'], result['interface']) nextItem(result['dz'], result['interface'])
def getQueue(): def getQueue():
global currentItem, queueList, queue, queueComplete global currentItem, queueList, queue, queueComplete
return (queue, queueComplete, queueList, currentItem) return (queue, queueComplete, queueList, currentItem)
def removeFromQueue(uuid, interface=None): def removeFromQueue(uuid, interface=None):
global currentItem, queueList, queue, queueComplete global currentItem, queueList, queue, queueComplete
if uuid == currentItem: if uuid == currentItem:
if interface: 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 queueList[uuid]['cancel'] = True
elif uuid in queue: elif uuid in queue:
queue.remove(uuid) queue.remove(uuid)
@ -246,13 +262,15 @@ def removeFromQueue(uuid, interface=None):
if interface: if interface:
interface.send("removedFromQueue", uuid) interface.send("removedFromQueue", uuid)
def cancelAllDownloads(interface=None): def cancelAllDownloads(interface=None):
global currentItem, queueList, queue, queueComplete global currentItem, queueList, queue, queueComplete
queue = [] queue = []
queueComplete = [] queueComplete = []
if currentItem != "": if currentItem != "":
if interface: 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 queueList[currentItem]['cancel'] = True
for uuid in list(queueList.keys()): for uuid in list(queueList.keys()):
if uuid != currentItem: if uuid != currentItem:
@ -260,6 +278,7 @@ def cancelAllDownloads(interface=None):
if interface: if interface:
interface.send("removedAllDownloads", currentItem) interface.send("removedAllDownloads", currentItem)
def removeFinishedDownloads(interface=None): def removeFinishedDownloads(interface=None):
global queueList, queueComplete global queueList, queueComplete
for uuid in queueComplete: for uuid in queueComplete:

View File

@ -1,13 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os.path as path
from os import mkdir, rmdir
import json import json
import os.path as path
from os import mkdir
import deemix.utils.localpaths as localpaths import deemix.utils.localpaths as localpaths
settings = {} settings = {}
defaultSettings = {} defaultSettings = {}
def initSettings(): def initSettings():
global settings global settings
global defaultSettings global defaultSettings
@ -30,10 +31,12 @@ def initSettings():
mkdir(settings['downloadLocation']) mkdir(settings['downloadLocation'])
return settings return settings
def getSettings(): def getSettings():
global settings global settings
return settings return settings
def saveSettings(newSettings): def saveSettings(newSettings):
global settings global settings
settings = newSettings settings = newSettings
@ -41,6 +44,7 @@ def saveSettings(newSettings):
json.dump(settings, configFile, indent=2) json.dump(settings, configFile, indent=2)
return True return True
def settingsCheck(): def settingsCheck():
global settings global settings
global defaultSettings global defaultSettings

View File

@ -1,13 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os.path as path
from os import mkdir, rmdir
import json import json
import os.path as path
import deemix.utils.localpaths as localpaths from os import mkdir
import spotipy import spotipy
from spotipy.oauth2 import SpotifyClientCredentials from spotipy.oauth2 import SpotifyClientCredentials
import deemix.utils.localpaths as localpaths
class SpotifyHelper: class SpotifyHelper:
def __init__(self): def __init__(self):
self.credentials = {} self.credentials = {}
@ -49,7 +50,8 @@ class SpotifyHelper:
self.checkCredentials() self.checkCredentials()
def createSpotifyConnection(self): 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) self.sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager)
def _convert_playlist_structure(self, spotify_obj): def _convert_playlist_structure(self, spotify_obj):
@ -61,7 +63,8 @@ class SpotifyHelper:
'checksum': spotify_obj['snapshot_id'], 'checksum': spotify_obj['snapshot_id'],
'collaborative': spotify_obj['collaborative'], 'collaborative': spotify_obj['collaborative'],
'creation_date': "????-00-00", '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'], 'description': spotify_obj['description'],
'duration': 0, 'duration': 0,
'fans': spotify_obj['followers']['total'], '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.get_track_by_ISRC(spotify_track['external_ids']['isrc'])
dz_track = dz_track['id'] if 'id' in dz_track else 0 dz_track = dz_track['id'] if 'id' in dz_track else 0
except: 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: 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 return dz_track
def get_albumid_spotify(self, dz, album_id): def get_albumid_spotify(self, dz, album_id):
@ -135,7 +140,8 @@ class SpotifyHelper:
if len(spotify_playlist['images']): if len(spotify_playlist['images']):
result['cover'] = spotify_playlist['images'][0]['url'] result['cover'] = spotify_playlist['images'][0]['url']
else: 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 = self._convert_playlist_structure(spotify_playlist)
playlistAPI['various_artist'] = dz.get_artist(5080) playlistAPI['various_artist'] = dz.get_artist(5080)
tracklist = spotify_playlist['tracks']['items'] tracklist = spotify_playlist['tracks']['items']
@ -168,5 +174,6 @@ class SpotifyHelper:
result['collection'].append(deezerTrack) result['collection'].append(deezerTrack)
return result return result
class spotifyFeaturesNotEnabled(Exception): class spotifyFeaturesNotEnabled(Exception):
pass pass

View File

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

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import re import re
def getBitrateInt(txt): def getBitrateInt(txt):
txt = str(txt) txt = str(txt)
if txt in ['flac', 'lossless', '9']: if txt in ['flac', 'lossless', '9']:
@ -18,6 +19,7 @@ def getBitrateInt(txt):
else: else:
return None return None
def changeCase(string, type): def changeCase(string, type):
if type == "lower": if type == "lower":
return string.lower() return string.lower()
@ -36,6 +38,7 @@ def changeCase(string, type):
else: else:
return string return string
def getIDFromLink(link, type): def getIDFromLink(link, type):
if '?' in link: if '?' in link:
link = link[:link.find('?')] link = link[:link.find('?')]
@ -85,6 +88,7 @@ def getTypeFromLink(link):
type = 'artist' type = 'artist'
return type return type
def isValidLink(text): def isValidLink(text):
if text.lower().startswith("http"): if text.lower().startswith("http"):
if "deezer.com" in text.lower() or "open.spotify.com" in text.lower(): if "deezer.com" in text.lower() or "open.spotify.com" in text.lower():

View File

@ -12,11 +12,13 @@ bitrateLabels = {
8: "128" 8: "128"
} }
def fixName(txt, char='_'): def fixName(txt, char='_'):
txt = str(txt) txt = str(txt)
txt = re.sub(r'[\0\/\\:*?"<>|]', char, txt) txt = re.sub(r'[\0\/\\:*?"<>|]', char, txt)
return txt return txt
def fixLongName(name): def fixLongName(name):
if pathSep in name: if pathSep in name:
name2 = name.split(pathSep) name2 = name.split(pathSep)
@ -29,6 +31,7 @@ def fixLongName(name):
name = name[:200] name = name[:200]
return name return name
def antiDot(string): def antiDot(string):
while string[-1:] == "." or string[-1:] == " " or string[-1:] == "\n": while string[-1:] == "." or string[-1:] == " " or string[-1:] == "\n":
string = string[:-1] string = string[:-1]
@ -36,6 +39,7 @@ def antiDot(string):
string = "dot" string = "dot"
return string return string
def pad(num, max, dopad=True): def pad(num, max, dopad=True):
paddingsize = len(str(max)) paddingsize = len(str(max))
if dopad: if dopad:
@ -43,12 +47,15 @@ def pad(num, max, dopad=True):
else: else:
return str(num) return str(num)
def generateFilename(track, trackAPI, settings): def generateFilename(track, trackAPI, settings):
if trackAPI['FILENAME_TEMPLATE'] == "": if trackAPI['FILENAME_TEMPLATE'] == "":
filename = "%artist% - %title%" filename = "%artist% - %title%"
else: else:
filename = trackAPI['FILENAME_TEMPLATE'] 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): def generateFilepath(track, trackAPI, settings):
filepath = settings['downloadLocation'] filepath = settings['downloadLocation']
@ -58,27 +65,34 @@ def generateFilepath(track, trackAPI, settings):
coverPath = None coverPath = None
extrasPath = None extrasPath = None
if settings['createPlaylistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']: if settings['createPlaylistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and not settings['tags'][
filepath += antiDot(settingsRegexPlaylist(settings['playlistNameTemplate'], trackAPI['_EXTRA_PLAYLIST'], settings)) + pathSep 'savePlaylistAsCompilation']:
filepath += antiDot(
settingsRegexPlaylist(settings['playlistNameTemplate'], trackAPI['_EXTRA_PLAYLIST'], settings)) + pathSep
if '_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']: if '_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']:
extrasPath = filepath extrasPath = filepath
if ( if (
settings['createArtistFolder'] and not '_EXTRA_PLAYLIST' in trackAPI or 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']) (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'] 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 artistPath = filepath
if (settings['createAlbumFolder'] and if (settings['createAlbumFolder'] and
(not 'SINGLE_TRACK' in trackAPI or ('SINGLE_TRACK' in trackAPI and settings['createSingleFolder'])) 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 coverPath = filepath
if not ('_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']): 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 ( int(track['album']['discTotal']) > 1 and (
(settings['createAlbumFolder'] and settings['createCDFolder']) and (settings['createAlbumFolder'] and settings['createCDFolder']) and
(not 'SINGLE_TRACK' in trackAPI or ('SINGLE_TRACK' in trackAPI and settings['createSingleFolder'])) 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 filepath += 'CD' + str(track['discNumber']) + pathSep
return (filepath, artistPath, coverPath, extrasPath) return (filepath, artistPath, coverPath, extrasPath)
def settingsRegex(filename, track, settings, playlist=None): def settingsRegex(filename, track, settings, playlist=None):
filename = filename.replace("%title%", fixName(track['title'], settings['illegalCharacterReplacer'])) filename = filename.replace("%title%", fixName(track['title'], settings['illegalCharacterReplacer']))
filename = filename.replace("%artist%", fixName(track['mainArtist']['name'], settings['illegalCharacterReplacer'])) filename = filename.replace("%artist%", fixName(track['mainArtist']['name'], settings['illegalCharacterReplacer']))
filename = filename.replace("%artists%", fixName(track['mainArtistsString'], settings['illegalCharacterReplacer'])) filename = filename.replace("%artists%", fixName(track['mainArtistsString'], settings['illegalCharacterReplacer']))
filename = filename.replace("%album%", fixName(track['album']['title'], 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("%albumartist%",
filename = filename.replace("%tracknumber%", pad(track['trackNumber'], track['album']['trackTotal'] if int(settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize'])-1), settings['padTracks'])) 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("%tracktotal%", str(track['album']['trackTotal']))
filename = filename.replace("%discnumber%", str(track['discNumber'])) filename = filename.replace("%discnumber%", str(track['discNumber']))
filename = filename.replace("%disctotal%", str(track['album']['discTotal'])) filename = filename.replace("%disctotal%", str(track['album']['discTotal']))
if len(track['album']['genre']) > 0: if len(track['album']['genre']) > 0:
filename = filename.replace("%genre%", fixName(track['album']['genre'][0], settings['illegalCharacterReplacer'])) filename = filename.replace("%genre%",
fixName(track['album']['genre'][0], settings['illegalCharacterReplacer']))
else: else:
filename = filename.replace("%genre%", "Unknown") filename = filename.replace("%genre%", "Unknown")
filename = filename.replace("%year%", str(track['date']['year'])) 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'])) filename = filename.replace("%artist_id%", str(track['mainArtist']['id']))
if playlist: if playlist:
filename = filename.replace("%playlist_id%", str(playlist['id'])) 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: 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) filename = filename.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(filename)) return antiDot(fixLongName(filename))
def settingsRegexAlbum(foldername, album, settings, trackAPI): def settingsRegexAlbum(foldername, album, settings, trackAPI):
if trackAPI and '_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']: 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: else:
foldername = foldername.replace("%album_id%", str(album['id'])) foldername = foldername.replace("%album_id%", str(album['id']))
foldername = foldername.replace("%album%", fixName(album['title'], settings['illegalCharacterReplacer'])) 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("%artist_id%", str(album['mainArtist']['id']))
foldername = foldername.replace("%tracktotal%", str(album['trackTotal'])) foldername = foldername.replace("%tracktotal%", str(album['trackTotal']))
foldername = foldername.replace("%disctotal%", str(album['discTotal'])) 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("%upc%", album['barcode'])
foldername = foldername.replace("%label%", fixName(album['label'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%label%", fixName(album['label'], settings['illegalCharacterReplacer']))
if len(album['genre']) > 0: if len(album['genre']) > 0:
@ -151,16 +176,19 @@ def settingsRegexAlbum(foldername, album, settings, trackAPI):
foldername = foldername.replace('\\', pathSep).replace('/', pathSep) foldername = foldername.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(foldername)) return antiDot(fixLongName(foldername))
def settingsRegexArtist(foldername, artist, settings): def settingsRegexArtist(foldername, artist, settings):
foldername = foldername.replace("%artist%", fixName(artist['name'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%artist%", fixName(artist['name'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%artist_id%", str(artist['id'])) foldername = foldername.replace("%artist_id%", str(artist['id']))
foldername = foldername.replace('\\', pathSep).replace('/', pathSep) foldername = foldername.replace('\\', pathSep).replace('/', pathSep)
return antiDot(fixLongName(foldername)) return antiDot(fixLongName(foldername))
def settingsRegexPlaylist(foldername, playlist, settings): def settingsRegexPlaylist(foldername, playlist, settings):
foldername = foldername.replace("%playlist%", fixName(playlist['title'], settings['illegalCharacterReplacer'])) foldername = foldername.replace("%playlist%", fixName(playlist['title'], settings['illegalCharacterReplacer']))
foldername = foldername.replace("%playlist_id%", fixName(playlist['id'], 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("%owner_id%", str(playlist['creator']['id']))
foldername = foldername.replace("%year%", str(playlist['creation_date'][:4])) foldername = foldername.replace("%year%", str(playlist['creation_date'][:4]))
foldername = foldername.replace("%date%", str(playlist['creation_date'][:10])) foldername = foldername.replace("%date%", str(playlist['creation_date'][:10]))

View File

@ -23,9 +23,11 @@ def tagID3(stream, track, save):
if save['albumArtist']: if save['albumArtist']:
tag.add(TPE2(text=track['album']['artists'])) tag.add(TPE2(text=track['album']['artists']))
if save['trackNumber']: 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']: 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']: if save['genre']:
tag.add(TCON(text=track['album']['genre'])) tag.add(TCON(text=track['album']['genre']))
if save['year']: if save['year']:
@ -63,9 +65,11 @@ def tagID3(stream, track, save):
tag.add(TCMP(text="1")) tag.add(TCMP(text="1"))
if save['cover'] and track['album']['picPath']: if save['cover'] and track['album']['picPath']:
with open(track['album']['picPath'], 'rb') as f: 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): def tagFLAC(stream, track, save):