Code cleanup with pylint
This commit is contained in:
parent
eda8fd3d13
commit
69c165e2bc
|
@ -0,0 +1,2 @@
|
||||||
|
[MESSAGES CONTROL]
|
||||||
|
disable=C0301,C0103,R0902,R0903,C0321,R0911,R0912,R0913,R0914,R0915,R0916
|
|
@ -10,54 +10,54 @@ USER_AGENT_HEADER = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML,
|
||||||
|
|
||||||
# Returns the Resolved URL, the Type and the ID
|
# Returns the Resolved URL, the Type and the ID
|
||||||
def parseLink(link):
|
def parseLink(link):
|
||||||
if 'deezer.page.link' in link: link = urlopen(url).url # Resolve URL shortner
|
if 'deezer.page.link' in link: link = urlopen(link).url # Resolve URL shortner
|
||||||
# Remove extra stuff
|
# Remove extra stuff
|
||||||
if '?' in link: link = link[:link.find('?')]
|
if '?' in link: link = link[:link.find('?')]
|
||||||
if '&' in link: link = link[:link.find('&')]
|
if '&' in link: link = link[:link.find('&')]
|
||||||
if link.endswith('/'): link = link[:-1] # Remove last slash if present
|
if link.endswith('/'): link = link[:-1] # Remove last slash if present
|
||||||
|
|
||||||
type = None
|
link_type = None
|
||||||
id = None
|
link_id = None
|
||||||
|
|
||||||
if not 'deezer' in link: return (link, type, id) # return if not a deezer link
|
if not 'deezer' in link: return (link, link_type, link_id) # return if not a deezer link
|
||||||
|
|
||||||
if '/track' in link:
|
if '/track' in link:
|
||||||
type = 'track'
|
link_type = 'track'
|
||||||
id = re.search("\/track\/(.+)", link).group(1)
|
link_id = re.search(r"/track/(.+)", link).group(1)
|
||||||
elif '/playlist' in link:
|
elif '/playlist' in link:
|
||||||
type = 'playlist'
|
link_type = 'playlist'
|
||||||
id = re.search("\/playlist\/(\d+)", link).group(1)
|
link_id = re.search(r"/playlist/(\d+)", link).group(1)
|
||||||
elif '/album' in link:
|
elif '/album' in link:
|
||||||
type = 'album'
|
link_type = 'album'
|
||||||
id = re.search("\/album\/(.+)", link).group(1)
|
link_id = re.search(r"/album/(.+)", link).group(1)
|
||||||
elif re.search("\/artist\/(\d+)\/top_track", link):
|
elif re.search(r"/artist/(\d+)/top_track", link):
|
||||||
type = 'artist_top'
|
link_type = 'artist_top'
|
||||||
id = re.search("\/artist\/(\d+)\/top_track", link).group(1)
|
link_id = re.search(r"/artist/(\d+)/top_track", link).group(1)
|
||||||
elif re.search("\/artist\/(\d+)\/discography", link):
|
elif re.search(r"/artist/(\d+)/discography", link):
|
||||||
type = 'artist_discography'
|
link_type = 'artist_discography'
|
||||||
id = re.search("\/artist\/(\d+)\/discography", link).group(1)
|
link_id = re.search(r"/artist/(\d+)/discography", link).group(1)
|
||||||
elif '/artist' in link:
|
elif '/artist' in link:
|
||||||
type = 'artist'
|
link_type = 'artist'
|
||||||
id = re.search("\/artist\/(\d+)", link).group(1)
|
link_id = re.search(r"/artist/(\d+)", link).group(1)
|
||||||
|
|
||||||
return (link, type, id)
|
return (link, link_type, link_id)
|
||||||
|
|
||||||
def generateDownloadObject(dz, link, bitrate):
|
def generateDownloadObject(dz, link, bitrate):
|
||||||
(link, type, id) = parseLink(link)
|
(link, link_type, link_id) = parseLink(link)
|
||||||
|
|
||||||
if type == None or id == None: return None
|
if link_type is None or link_id is None:
|
||||||
|
return None
|
||||||
if type == "track":
|
if link_type == "track":
|
||||||
return generateTrackItem(dz, id, bitrate)
|
return generateTrackItem(dz, link_id, bitrate)
|
||||||
elif type == "album":
|
if link_type == "album":
|
||||||
return generateAlbumItem(dz, id, bitrate)
|
return generateAlbumItem(dz, link_id, bitrate)
|
||||||
elif type == "playlist":
|
if link_type == "playlist":
|
||||||
return generatePlaylistItem(dz, id, bitrate)
|
return generatePlaylistItem(dz, link_id, bitrate)
|
||||||
elif type == "artist":
|
if link_type == "artist":
|
||||||
return generateArtistItem(dz, id, bitrate)
|
return generateArtistItem(dz, link_id, bitrate)
|
||||||
elif type == "artist_discography":
|
if link_type == "artist_discography":
|
||||||
return generateArtistDiscographyItem(dz, id, bitrate)
|
return generateArtistDiscographyItem(dz, link_id, bitrate)
|
||||||
elif type == "artist_top":
|
if link_type == "artist_top":
|
||||||
return generateArtistTopItem(dz, id, bitrate)
|
return generateArtistTopItem(dz, link_id, bitrate)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -73,4 +73,4 @@ def download(url, bitrate, portable, path):
|
||||||
click.echo("All done!")
|
click.echo("All done!")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
download()
|
download() # pylint: disable=E1120
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
import binascii
|
import binascii
|
||||||
|
from ssl import SSLError
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from Cryptodome.Cipher import Blowfish, AES
|
from Cryptodome.Cipher import Blowfish, AES
|
||||||
from Cryptodome.Hash import MD5
|
from Cryptodome.Hash import MD5
|
||||||
|
|
||||||
from deemix import USER_AGENT_HEADER
|
|
||||||
from deemix.types.DownloadObjects import Single, Collection
|
|
||||||
|
|
||||||
from requests import get
|
from requests import get
|
||||||
|
from requests.exceptions import ConnectionError as RequestsConnectionError, ReadTimeout
|
||||||
from requests.exceptions import ConnectionError, ReadTimeout
|
|
||||||
from ssl import SSLError
|
|
||||||
from urllib3.exceptions import SSLError as u3SSLError
|
from urllib3.exceptions import SSLError as u3SSLError
|
||||||
|
|
||||||
import logging
|
from deemix import USER_AGENT_HEADER
|
||||||
|
from deemix.types.DownloadObjects import Single
|
||||||
|
|
||||||
logger = logging.getLogger('deemix')
|
logger = logging.getLogger('deemix')
|
||||||
|
|
||||||
def _md5(data):
|
def _md5(data):
|
||||||
h = MD5.new()
|
h = MD5.new()
|
||||||
h.update(str.encode(data) if isinstance(data, str) else data)
|
h.update(data.encode() if isinstance(data, str) else data)
|
||||||
return h.hexdigest()
|
return h.hexdigest()
|
||||||
|
|
||||||
def generateBlowfishKey(trackId):
|
def generateBlowfishKey(trackId):
|
||||||
|
@ -27,36 +29,35 @@ def generateBlowfishKey(trackId):
|
||||||
bfKey += chr(ord(idMd5[i]) ^ ord(idMd5[i + 16]) ^ ord(SECRET[i]))
|
bfKey += chr(ord(idMd5[i]) ^ ord(idMd5[i + 16]) ^ ord(SECRET[i]))
|
||||||
return bfKey
|
return bfKey
|
||||||
|
|
||||||
def generateStreamPath(sng_id, md5, media_version, format):
|
def generateStreamPath(sng_id, md5, media_version, media_format):
|
||||||
urlPart = b'\xa4'.join(
|
urlPart = b'\xa4'.join(
|
||||||
[str.encode(md5), str.encode(str(format)), str.encode(str(sng_id)), str.encode(str(media_version))])
|
[md5.encode(), str(media_format).encode(), str(sng_id).encode(), str(media_version).encode()])
|
||||||
md5val = _md5(urlPart)
|
md5val = _md5(urlPart)
|
||||||
step2 = str.encode(md5val) + b'\xa4' + urlPart + b'\xa4'
|
step2 = md5val.encode() + b'\xa4' + urlPart + b'\xa4'
|
||||||
step2 = step2 + (b'.' * (16 - (len(step2) % 16)))
|
step2 = step2 + (b'.' * (16 - (len(step2) % 16)))
|
||||||
urlPart = binascii.hexlify(AES.new(b'jo6aey6haid2Teih', AES.MODE_ECB).encrypt(step2))
|
urlPart = binascii.hexlify(AES.new(b'jo6aey6haid2Teih', AES.MODE_ECB).encrypt(step2))
|
||||||
return urlPart.decode("utf-8")
|
return urlPart.decode("utf-8")
|
||||||
|
|
||||||
def reverseStreamPath(urlPart):
|
def reverseStreamPath(urlPart):
|
||||||
step2 = AES.new(b'jo6aey6haid2Teih', AES.MODE_ECB).decrypt(binascii.unhexlify(urlPart.encode("utf-8")))
|
step2 = AES.new(b'jo6aey6haid2Teih', AES.MODE_ECB).decrypt(binascii.unhexlify(urlPart.encode("utf-8")))
|
||||||
(md5val, md5, format, sng_id, media_version, _) = step2.split(b'\xa4')
|
(_, md5, media_format, sng_id, media_version, _) = step2.split(b'\xa4')
|
||||||
return (sng_id.decode('utf-8'), md5.decode('utf-8'), media_version.decode('utf-8'), format.decode('utf-8'))
|
return (sng_id.decode('utf-8'), md5.decode('utf-8'), media_version.decode('utf-8'), media_format.decode('utf-8'))
|
||||||
|
|
||||||
def generateStreamURL(sng_id, md5, media_version, format):
|
def generateStreamURL(sng_id, md5, media_version, media_format):
|
||||||
urlPart = generateStreamPath(sng_id, md5, media_version, format)
|
urlPart = generateStreamPath(sng_id, md5, media_version, media_format)
|
||||||
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/mobile/1/" + urlPart
|
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/mobile/1/" + urlPart
|
||||||
|
|
||||||
def generateUnencryptedStreamURL(sng_id, md5, media_version, format):
|
def generateUnencryptedStreamURL(sng_id, md5, media_version, media_format):
|
||||||
urlPart = generateStreamPath(sng_id, md5, media_version, format)
|
urlPart = generateStreamPath(sng_id, md5, media_version, media_format)
|
||||||
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/api/1/" + urlPart
|
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/api/1/" + urlPart
|
||||||
|
|
||||||
def reverseStreamURL(url):
|
def reverseStreamURL(url):
|
||||||
urlPart = url[url.find("/1/")+3:]
|
urlPart = url[url.find("/1/")+3:]
|
||||||
return generateStreamPath(urlPart)
|
return reverseStreamPath(urlPart)
|
||||||
|
|
||||||
def streamUnencryptedTrack(outputStream, track, start=0, downloadObject=None, interface=None):
|
def streamUnencryptedTrack(outputStream, track, start=0, downloadObject=None, interface=None):
|
||||||
headers= {'User-Agent': USER_AGENT_HEADER}
|
headers= {'User-Agent': USER_AGENT_HEADER}
|
||||||
chunkLength = start
|
chunkLength = start
|
||||||
percentage = 0
|
|
||||||
|
|
||||||
itemName = f"[{track.mainArtist.name} - {track.title}]"
|
itemName = f"[{track.mainArtist.name} - {track.title}]"
|
||||||
|
|
||||||
|
@ -68,9 +69,9 @@ def streamUnencryptedTrack(outputStream, track, start=0, downloadObject=None, in
|
||||||
if complete == 0: raise DownloadEmpty
|
if complete == 0: raise DownloadEmpty
|
||||||
if start != 0:
|
if start != 0:
|
||||||
responseRange = request.headers["Content-Range"]
|
responseRange = request.headers["Content-Range"]
|
||||||
logger.info(f'{itemName} downloading range {responseRange}')
|
logger.info('%s downloading range %s', itemName, responseRange)
|
||||||
else:
|
else:
|
||||||
logger.info(f'{itemName} downloading {complete} bytes')
|
logger.info('%s downloading %s bytes', itemName, complete)
|
||||||
|
|
||||||
for chunk in request.iter_content(2048 * 3):
|
for chunk in request.iter_content(2048 * 3):
|
||||||
outputStream.write(chunk)
|
outputStream.write(chunk)
|
||||||
|
@ -85,51 +86,12 @@ def streamUnencryptedTrack(outputStream, track, start=0, downloadObject=None, in
|
||||||
downloadObject.progressNext += chunkProgres
|
downloadObject.progressNext += chunkProgres
|
||||||
downloadObject.updateProgress(interface)
|
downloadObject.updateProgress(interface)
|
||||||
|
|
||||||
except (SSLError, u3SSLError) as e:
|
except (SSLError, u3SSLError):
|
||||||
logger.info(f'{itemName} retrying from byte {chunkLength}')
|
logger.info('%s retrying from byte %s', itemName, chunkLength)
|
||||||
return streamUnencryptedTrack(outputStream, track, chunkLength, downloadObject, interface)
|
streamUnencryptedTrack(outputStream, track, chunkLength, downloadObject, interface)
|
||||||
except (ConnectionError, ReadTimeout):
|
except (RequestsConnectionError, ReadTimeout):
|
||||||
sleep(2)
|
sleep(2)
|
||||||
return streamUnencryptedTrack(outputStream, track, start, downloadObject, interface)
|
streamUnencryptedTrack(outputStream, track, start, downloadObject, interface)
|
||||||
|
|
||||||
def streamUnencryptedTrack(outputStream, track, start=0, downloadObject=None, interface=None):
|
|
||||||
headers= {'User-Agent': USER_AGENT_HEADER}
|
|
||||||
chunkLength = start
|
|
||||||
percentage = 0
|
|
||||||
|
|
||||||
itemName = f"[{track.mainArtist.name} - {track.title}]"
|
|
||||||
|
|
||||||
try:
|
|
||||||
with get(track.downloadUrl, headers=headers, stream=True, timeout=10) as request:
|
|
||||||
request.raise_for_status()
|
|
||||||
|
|
||||||
complete = int(request.headers["Content-Length"])
|
|
||||||
if complete == 0: raise DownloadEmpty
|
|
||||||
if start != 0:
|
|
||||||
responseRange = request.headers["Content-Range"]
|
|
||||||
logger.info(f'{itemName} downloading range {responseRange}')
|
|
||||||
else:
|
|
||||||
logger.info(f'{itemName} downloading {complete} bytes')
|
|
||||||
|
|
||||||
for chunk in request.iter_content(2048 * 3):
|
|
||||||
outputStream.write(chunk)
|
|
||||||
chunkLength += len(chunk)
|
|
||||||
|
|
||||||
if downloadObject:
|
|
||||||
if isinstance(downloadObject, Single):
|
|
||||||
percentage = (chunkLength / (complete + start)) * 100
|
|
||||||
downloadObject.progressNext = percentage
|
|
||||||
else:
|
|
||||||
chunkProgres = (len(chunk) / (complete + start)) / downloadObject.size * 100
|
|
||||||
downloadObject.progressNext += chunkProgres
|
|
||||||
downloadObject.updateProgress(interface)
|
|
||||||
|
|
||||||
except (SSLError, u3SSLError) as e:
|
|
||||||
logger.info(f'{itemName} retrying from byte {chunkLength}')
|
|
||||||
return streamUnencryptedTrack(outputStream, track, chunkLength, downloadObject, interface)
|
|
||||||
except (ConnectionError, ReadTimeout):
|
|
||||||
sleep(2)
|
|
||||||
return streamUnencryptedTrack(outputStream, track, start, downloadObject, interface)
|
|
||||||
|
|
||||||
def streamTrack(outputStream, track, start=0, downloadObject=None, interface=None):
|
def streamTrack(outputStream, track, start=0, downloadObject=None, interface=None):
|
||||||
headers= {'User-Agent': USER_AGENT_HEADER}
|
headers= {'User-Agent': USER_AGENT_HEADER}
|
||||||
|
@ -147,9 +109,9 @@ def streamTrack(outputStream, track, start=0, downloadObject=None, interface=Non
|
||||||
if complete == 0: raise DownloadEmpty
|
if complete == 0: raise DownloadEmpty
|
||||||
if start != 0:
|
if start != 0:
|
||||||
responseRange = request.headers["Content-Range"]
|
responseRange = request.headers["Content-Range"]
|
||||||
logger.info(f'{itemName} downloading range {responseRange}')
|
logger.info('%s downloading range %s', itemName, responseRange)
|
||||||
else:
|
else:
|
||||||
logger.info(f'{itemName} downloading {complete} bytes')
|
logger.info('%s downloading %s bytes', itemName, complete)
|
||||||
|
|
||||||
for chunk in request.iter_content(2048 * 3):
|
for chunk in request.iter_content(2048 * 3):
|
||||||
if len(chunk) >= 2048:
|
if len(chunk) >= 2048:
|
||||||
|
@ -167,12 +129,12 @@ def streamTrack(outputStream, track, start=0, downloadObject=None, interface=Non
|
||||||
downloadObject.progressNext += chunkProgres
|
downloadObject.progressNext += chunkProgres
|
||||||
downloadObject.updateProgress(interface)
|
downloadObject.updateProgress(interface)
|
||||||
|
|
||||||
except (SSLError, u3SSLError) as e:
|
except (SSLError, u3SSLError):
|
||||||
logger.info(f'{itemName} retrying from byte {chunkLength}')
|
logger.info('%s retrying from byte %s', itemName, chunkLength)
|
||||||
return streamTrack(outputStream, track, chunkLength, downloadObject, interface)
|
streamTrack(outputStream, track, chunkLength, downloadObject, interface)
|
||||||
except (ConnectionError, ReadTimeout):
|
except (RequestsConnectionError, ReadTimeout):
|
||||||
sleep(2)
|
sleep(2)
|
||||||
return streamTrack(outputStream, track, start, downloadObject, interface)
|
streamTrack(outputStream, track, start, downloadObject, interface)
|
||||||
|
|
||||||
class DownloadEmpty(Exception):
|
class DownloadEmpty(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -1,35 +1,33 @@
|
||||||
import requests
|
|
||||||
from requests import get
|
|
||||||
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from os.path import sep as pathSep
|
from os.path import sep as pathSep
|
||||||
|
from os import makedirs, system as execute
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from shlex import quote
|
from shlex import quote
|
||||||
import re
|
|
||||||
import errno
|
import errno
|
||||||
|
|
||||||
from ssl import SSLError
|
import logging
|
||||||
from urllib3.exceptions import SSLError as u3SSLError
|
from tempfile import gettempdir
|
||||||
from os import makedirs
|
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from requests import get
|
||||||
|
|
||||||
|
from urllib3.exceptions import SSLError as u3SSLError
|
||||||
|
|
||||||
|
from mutagen.flac import FLACNoHeaderError, error as FLACError
|
||||||
|
|
||||||
|
from deezer import TrackFormats
|
||||||
|
from deemix import USER_AGENT_HEADER
|
||||||
from deemix.types.DownloadObjects import Single, Collection
|
from deemix.types.DownloadObjects import Single, Collection
|
||||||
from deemix.types.Track import Track, AlbumDoesntExists
|
from deemix.types.Track import Track, AlbumDoesntExists
|
||||||
from deemix.utils.pathtemplates import generateFilename, generateFilepath, settingsRegexAlbum, settingsRegexArtist, settingsRegexPlaylistFile
|
from deemix.utils.pathtemplates import generateFilename, generateFilepath, settingsRegexAlbum, settingsRegexArtist, settingsRegexPlaylistFile
|
||||||
from deezer import TrackFormats
|
|
||||||
from deemix import USER_AGENT_HEADER
|
|
||||||
from deemix.taggers import tagID3, tagFLAC
|
from deemix.taggers import tagID3, tagFLAC
|
||||||
from deemix.decryption import generateUnencryptedStreamURL, streamUnencryptedTrack
|
from deemix.decryption import generateUnencryptedStreamURL, streamUnencryptedTrack
|
||||||
from deemix.settings import OverwriteOption
|
from deemix.settings import OverwriteOption
|
||||||
|
|
||||||
from mutagen.flac import FLACNoHeaderError, error as FLACError
|
|
||||||
|
|
||||||
import logging
|
|
||||||
logger = logging.getLogger('deemix')
|
logger = logging.getLogger('deemix')
|
||||||
|
|
||||||
from tempfile import gettempdir
|
|
||||||
|
|
||||||
TEMPDIR = Path(gettempdir()) / 'deemix-imgs'
|
TEMPDIR = Path(gettempdir()) / 'deemix-imgs'
|
||||||
if not TEMPDIR.is_dir(): makedirs(TEMPDIR)
|
if not TEMPDIR.is_dir(): makedirs(TEMPDIR)
|
||||||
|
|
||||||
|
@ -71,22 +69,21 @@ def downloadImage(url, path, overwrite=OverwriteOption.DONT_OVERWRITE):
|
||||||
pictureUrl = url[len(urlBase):]
|
pictureUrl = url[len(urlBase):]
|
||||||
pictureSize = int(pictureUrl[:pictureUrl.find("x")])
|
pictureSize = int(pictureUrl[:pictureUrl.find("x")])
|
||||||
if pictureSize > 1200:
|
if pictureSize > 1200:
|
||||||
logger.warn("Couldn't download "+str(pictureSize)+"x"+str(pictureSize)+" image, falling back to 1200x1200")
|
logger.warning("Couldn't download %sx%s image, falling back to 1200x1200", pictureSize, pictureSize)
|
||||||
sleep(1)
|
sleep(1)
|
||||||
return downloadImage(urlBase+pictureUrl.replace(str(pictureSize)+"x"+str(pictureSize), '1200x1200'), path, overwrite)
|
return downloadImage(urlBase+pictureUrl.replace(str(pictureSize)+"x"+str(pictureSize), '1200x1200'), path, overwrite)
|
||||||
logger.error("Image not found: "+url)
|
logger.error("Image not found: %s", url)
|
||||||
except (requests.exceptions.ConnectionError, requests.exceptions.ChunkedEncodingError, u3SSLError) as e:
|
except (requests.exceptions.ConnectionError, requests.exceptions.ChunkedEncodingError, u3SSLError) as e:
|
||||||
logger.error("Couldn't download Image, retrying in 5 seconds...: "+url+"\n")
|
logger.error("Couldn't download Image, retrying in 5 seconds...: %s", url)
|
||||||
sleep(5)
|
sleep(5)
|
||||||
return downloadImage(url, path, overwrite)
|
return downloadImage(url, path, overwrite)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno == errno.ENOSPC: raise DownloadFailed("noSpaceLeft")
|
if e.errno == errno.ENOSPC: raise DownloadFailed("noSpaceLeft") from e
|
||||||
else: logger.exception(f"Error while downloading an image, you should report this to the developers: {str(e)}")
|
logger.exception("Error while downloading an image, you should report this to the developers: %s", e)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"Error while downloading an image, you should report this to the developers: {str(e)}")
|
logger.exception("Error while downloading an image, you should report this to the developers: %s", e)
|
||||||
if path.is_file(): path.unlink()
|
if path.is_file(): path.unlink()
|
||||||
return None
|
return None
|
||||||
else:
|
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def getPreferredBitrate(track, preferredBitrate, shouldFallback, downloadObjectUUID=None, interface=None):
|
def getPreferredBitrate(track, preferredBitrate, shouldFallback, downloadObjectUUID=None, interface=None):
|
||||||
|
@ -116,7 +113,7 @@ def getPreferredBitrate(track, preferredBitrate, shouldFallback, downloadObjectU
|
||||||
formats = formats_non_360
|
formats = formats_non_360
|
||||||
|
|
||||||
for formatNumber, formatName in formats.items():
|
for formatNumber, formatName in formats.items():
|
||||||
if formatNumber <= int(preferredBitrate):
|
if formatNumber >= int(preferredBitrate): continue
|
||||||
if f"FILESIZE_{formatName}" in track.filesizes:
|
if f"FILESIZE_{formatName}" in track.filesizes:
|
||||||
if int(track.filesizes[f"FILESIZE_{formatName}"]) != 0: return formatNumber
|
if int(track.filesizes[f"FILESIZE_{formatName}"]) != 0: return formatNumber
|
||||||
if not track.filesizes[f"FILESIZE_{formatName}_TESTED"]:
|
if not track.filesizes[f"FILESIZE_{formatName}_TESTED"]:
|
||||||
|
@ -130,12 +127,12 @@ def getPreferredBitrate(track, preferredBitrate, shouldFallback, downloadObjectU
|
||||||
return formatNumber
|
return formatNumber
|
||||||
except requests.exceptions.HTTPError: # if the format is not available, Deezer returns a 403 error
|
except requests.exceptions.HTTPError: # if the format is not available, Deezer returns a 403 error
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not shouldFallback:
|
if not shouldFallback:
|
||||||
raise PreferredBitrateNotFound
|
raise PreferredBitrateNotFound
|
||||||
else:
|
|
||||||
if not falledBack:
|
if not falledBack:
|
||||||
falledBack = True
|
falledBack = True
|
||||||
logger.info(f"[{track.mainArtist.name} - {track.title}] Fallback to lower bitrate")
|
logger.info("%s Fallback to lower bitrate", f"[{track.mainArtist.name} - {track.title}]")
|
||||||
if interface and downloadObjectUUID:
|
if interface and downloadObjectUUID:
|
||||||
interface.send('queueUpdate', {
|
interface.send('queueUpdate', {
|
||||||
'uuid': downloadObjectUUID,
|
'uuid': downloadObjectUUID,
|
||||||
|
@ -178,9 +175,11 @@ class Downloader:
|
||||||
result = {}
|
result = {}
|
||||||
if trackAPI_gw['SNG_ID'] == "0": raise DownloadFailed("notOnDeezer")
|
if trackAPI_gw['SNG_ID'] == "0": raise DownloadFailed("notOnDeezer")
|
||||||
|
|
||||||
|
itemName = f"[{trackAPI_gw['ART_NAME']} - {trackAPI_gw['SNG_TITLE']}]"
|
||||||
|
|
||||||
# Create Track object
|
# Create Track object
|
||||||
if not track:
|
if not track:
|
||||||
logger.info(f"[{trackAPI_gw['ART_NAME']} - {trackAPI_gw['SNG_TITLE']}] Getting the tags")
|
logger.info("%s Getting the tags", itemName)
|
||||||
try:
|
try:
|
||||||
track = Track().parseData(
|
track = Track().parseData(
|
||||||
dz=self.dz,
|
dz=self.dz,
|
||||||
|
@ -189,8 +188,10 @@ class Downloader:
|
||||||
albumAPI=albumAPI,
|
albumAPI=albumAPI,
|
||||||
playlistAPI=playlistAPI
|
playlistAPI=playlistAPI
|
||||||
)
|
)
|
||||||
except AlbumDoesntExists:
|
except AlbumDoesntExists as e:
|
||||||
raise DownloadError('albumDoesntExists')
|
raise DownloadError('albumDoesntExists') from e
|
||||||
|
|
||||||
|
itemName = f"[{track.mainArtist.name} - {track.title}]"
|
||||||
|
|
||||||
# Check if track not yet encoded
|
# Check if track not yet encoded
|
||||||
if track.MD5 == '': raise DownloadFailed("notEncoded", track)
|
if track.MD5 == '': raise DownloadFailed("notEncoded", track)
|
||||||
|
@ -203,16 +204,16 @@ class Downloader:
|
||||||
self.settings['fallbackBitrate'],
|
self.settings['fallbackBitrate'],
|
||||||
self.downloadObject.uuid, self.interface
|
self.downloadObject.uuid, self.interface
|
||||||
)
|
)
|
||||||
except PreferredBitrateNotFound:
|
except PreferredBitrateNotFound as e:
|
||||||
raise DownloadFailed("wrongBitrate", track)
|
raise DownloadFailed("wrongBitrate", track) from e
|
||||||
except TrackNot360:
|
except TrackNot360 as e:
|
||||||
raise DownloadFailed("no360RA")
|
raise DownloadFailed("no360RA") from e
|
||||||
track.selectedFormat = selectedFormat
|
track.selectedFormat = selectedFormat
|
||||||
track.album.bitrate = selectedFormat
|
track.album.bitrate = selectedFormat
|
||||||
|
|
||||||
# Generate covers URLs
|
# Generate covers URLs
|
||||||
embeddedImageFormat = f'jpg-{self.settings["jpegImageQuality"]}'
|
embeddedImageFormat = f'jpg-{self.settings["jpegImageQuality"]}'
|
||||||
if self.settings['embeddedArtworkPNG']: imageFormat = 'png'
|
if self.settings['embeddedArtworkPNG']: embeddedImageFormat = 'png'
|
||||||
|
|
||||||
track.applySettings(self.settings, TEMPDIR, embeddedImageFormat)
|
track.applySettings(self.settings, TEMPDIR, embeddedImageFormat)
|
||||||
|
|
||||||
|
@ -233,49 +234,49 @@ class Downloader:
|
||||||
result['filename'] = str(writepath)[len(str(extrasPath))+ len(pathSep):]
|
result['filename'] = str(writepath)[len(str(extrasPath))+ len(pathSep):]
|
||||||
|
|
||||||
# Download and cache coverart
|
# Download and cache coverart
|
||||||
logger.info(f"[{track.mainArtist.name} - {track.title}] Getting the album cover")
|
logger.info("%s Getting the album cover", itemName)
|
||||||
track.album.embeddedCoverPath = downloadImage(track.album.embeddedCoverURL, track.album.embeddedCoverPath)
|
track.album.embeddedCoverPath = downloadImage(track.album.embeddedCoverURL, track.album.embeddedCoverPath)
|
||||||
|
|
||||||
# Save local album art
|
# Save local album art
|
||||||
if coverPath:
|
if coverPath:
|
||||||
result['albumURLs'] = []
|
result['albumURLs'] = []
|
||||||
for format in self.settings['localArtworkFormat'].split(","):
|
for pic_format in self.settings['localArtworkFormat'].split(","):
|
||||||
if format in ["png","jpg"]:
|
if pic_format in ["png","jpg"]:
|
||||||
extendedFormat = format
|
extendedFormat = pic_format
|
||||||
if extendedFormat == "jpg": extendedFormat += f"-{self.settings['jpegImageQuality']}"
|
if extendedFormat == "jpg": extendedFormat += f"-{self.settings['jpegImageQuality']}"
|
||||||
url = track.album.pic.generatePictureURL(self.settings['localArtworkSize'], extendedFormat)
|
url = track.album.pic.generatePictureURL(self.settings['localArtworkSize'], extendedFormat)
|
||||||
if self.settings['tags']['savePlaylistAsCompilation'] \
|
if self.settings['tags']['savePlaylistAsCompilation'] \
|
||||||
and track.playlist \
|
and track.playlist \
|
||||||
and track.playlist.pic.staticUrl \
|
and track.playlist.pic.staticUrl \
|
||||||
and not format.startswith("jpg"):
|
and not pic_format.startswith("jpg"):
|
||||||
continue
|
continue
|
||||||
result['albumURLs'].append({'url': url, 'ext': format})
|
result['albumURLs'].append({'url': url, 'ext': pic_format})
|
||||||
result['albumPath'] = coverPath
|
result['albumPath'] = coverPath
|
||||||
result['albumFilename'] = f"{settingsRegexAlbum(self.settings['coverImageTemplate'], track.album, self.settings, track.playlist)}"
|
result['albumFilename'] = f"{settingsRegexAlbum(self.settings['coverImageTemplate'], track.album, self.settings, track.playlist)}"
|
||||||
|
|
||||||
# Save artist art
|
# Save artist art
|
||||||
if artistPath:
|
if artistPath:
|
||||||
result['artistURLs'] = []
|
result['artistURLs'] = []
|
||||||
for format in self.settings['localArtworkFormat'].split(","):
|
for pic_format in self.settings['localArtworkFormat'].split(","):
|
||||||
if format in ["png","jpg"]:
|
if pic_format in ["png","jpg"]:
|
||||||
extendedFormat = format
|
extendedFormat = pic_format
|
||||||
if extendedFormat == "jpg": extendedFormat += f"-{self.settings['jpegImageQuality']}"
|
if extendedFormat == "jpg": extendedFormat += f"-{self.settings['jpegImageQuality']}"
|
||||||
url = track.album.mainArtist.pic.generatePictureURL(self.settings['localArtworkSize'], extendedFormat)
|
url = track.album.mainArtist.pic.generatePictureURL(self.settings['localArtworkSize'], extendedFormat)
|
||||||
if track.album.mainArtist.pic.md5 == "" and not format.startswith("jpg"): continue
|
if track.album.mainArtist.pic.md5 == "" and not pic_format.startswith("jpg"): continue
|
||||||
result['artistURLs'].append({'url': url, 'ext': format})
|
result['artistURLs'].append({'url': url, 'ext': pic_format})
|
||||||
result['artistPath'] = artistPath
|
result['artistPath'] = artistPath
|
||||||
result['artistFilename'] = f"{settingsRegexArtist(self.settings['artistImageTemplate'], track.album.mainArtist, self.settings, rootArtist=track.album.rootArtist)}"
|
result['artistFilename'] = f"{settingsRegexArtist(self.settings['artistImageTemplate'], track.album.mainArtist, self.settings, rootArtist=track.album.rootArtist)}"
|
||||||
|
|
||||||
# Save playlist art
|
# Save playlist art
|
||||||
if track.playlist:
|
if track.playlist:
|
||||||
if not len(self.playlistURLs):
|
if self.playlistURLs == []:
|
||||||
for format in self.settings['localArtworkFormat'].split(","):
|
for pic_format in self.settings['localArtworkFormat'].split(","):
|
||||||
if format in ["png","jpg"]:
|
if pic_format in ["png","jpg"]:
|
||||||
extendedFormat = format
|
extendedFormat = pic_format
|
||||||
if extendedFormat == "jpg": extendedFormat += f"-{self.settings['jpegImageQuality']}"
|
if extendedFormat == "jpg": extendedFormat += f"-{self.settings['jpegImageQuality']}"
|
||||||
url = track.playlist.pic.generatePictureURL(self.settings['localArtworkSize'], extendedFormat)
|
url = track.playlist.pic.generatePictureURL(self.settings['localArtworkSize'], extendedFormat)
|
||||||
if track.playlist.pic.staticUrl and not format.startswith("jpg"): continue
|
if track.playlist.pic.staticUrl and not pic_format.startswith("jpg"): continue
|
||||||
self.playlistURLs.append({'url': url, 'ext': format})
|
self.playlistURLs.append({'url': url, 'ext': pic_format})
|
||||||
if not self.playlistCoverName:
|
if not self.playlistCoverName:
|
||||||
track.playlist.bitrate = selectedFormat
|
track.playlist.bitrate = selectedFormat
|
||||||
track.playlist.dateString = track.playlist.date.format(self.settings['dateFormat'])
|
track.playlist.dateString = track.playlist.date.format(self.settings['dateFormat'])
|
||||||
|
@ -309,26 +310,26 @@ class Downloader:
|
||||||
writepath = Path(currentFilename)
|
writepath = Path(currentFilename)
|
||||||
|
|
||||||
if not trackAlreadyDownloaded or self.settings['overwriteFile'] == OverwriteOption.OVERWRITE:
|
if not trackAlreadyDownloaded or self.settings['overwriteFile'] == OverwriteOption.OVERWRITE:
|
||||||
logger.info(f"[{track.mainArtist.name} - {track.title}] Downloading the track")
|
logger.info("%s Downloading the track", itemName)
|
||||||
track.downloadUrl = generateUnencryptedStreamURL(track.id, track.MD5, track.mediaVersion, track.selectedFormat)
|
track.downloadUrl = generateUnencryptedStreamURL(track.id, track.MD5, track.mediaVersion, track.selectedFormat)
|
||||||
|
|
||||||
def downloadMusic(track, trackAPI_gw):
|
def downloadMusic(track, trackAPI_gw):
|
||||||
try:
|
try:
|
||||||
with open(writepath, 'wb') as stream:
|
with open(writepath, 'wb') as stream:
|
||||||
streamUnencryptedTrack(stream, track, downloadObject=self.downloadObject, interface=self.interface)
|
streamUnencryptedTrack(stream, track, downloadObject=self.downloadObject, interface=self.interface)
|
||||||
except DownloadCancelled:
|
except DownloadCancelled as e:
|
||||||
if writepath.is_file(): writepath.unlink()
|
if writepath.is_file(): writepath.unlink()
|
||||||
raise DownloadCancelled
|
raise e
|
||||||
except (requests.exceptions.HTTPError, DownloadEmpty):
|
except (requests.exceptions.HTTPError, DownloadEmpty) as e:
|
||||||
if writepath.is_file(): writepath.unlink()
|
if writepath.is_file(): writepath.unlink()
|
||||||
if track.fallbackID != "0":
|
if track.fallbackID != "0":
|
||||||
logger.warn(f"[{track.mainArtist.name} - {track.title}] Track not available, using fallback id")
|
logger.warning("%s Track not available, using fallback id", itemName)
|
||||||
newTrack = self.dz.gw.get_track_with_fallback(track.fallbackID)
|
newTrack = self.dz.gw.get_track_with_fallback(track.fallbackID)
|
||||||
track.parseEssentialData(newTrack)
|
track.parseEssentialData(newTrack)
|
||||||
track.retriveFilesizes(self.dz)
|
track.retriveFilesizes(self.dz)
|
||||||
return False
|
return False
|
||||||
elif not track.searched and self.settings['fallbackSearch']:
|
if not track.searched and self.settings['fallbackSearch']:
|
||||||
logger.warn(f"[{track.mainArtist.name} - {track.title}] Track not available, searching for alternative")
|
logger.warning("%s Track not available, searching for alternative", itemName)
|
||||||
searchedId = self.dz.api.get_track_id_from_metadata(track.mainArtist.name, track.title, track.album.title)
|
searchedId = self.dz.api.get_track_id_from_metadata(track.mainArtist.name, track.title, track.album.title)
|
||||||
if searchedId != "0":
|
if searchedId != "0":
|
||||||
newTrack = self.dz.gw.get_track_with_fallback(searchedId)
|
newTrack = self.dz.gw.get_track_with_fallback(searchedId)
|
||||||
|
@ -346,25 +347,21 @@ class Downloader:
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return False
|
return False
|
||||||
else:
|
raise DownloadFailed("notAvailableNoAlternative") from e
|
||||||
raise DownloadFailed("notAvailableNoAlternative")
|
raise DownloadFailed("notAvailable") from e
|
||||||
else:
|
|
||||||
raise DownloadFailed("notAvailable")
|
|
||||||
except (requests.exceptions.ConnectionError, requests.exceptions.ChunkedEncodingError) as e:
|
except (requests.exceptions.ConnectionError, requests.exceptions.ChunkedEncodingError) as e:
|
||||||
if writepath.is_file(): writepath.unlink()
|
if writepath.is_file(): writepath.unlink()
|
||||||
logger.warn(f"[{track.mainArtist.name} - {track.title}] Error while downloading the track, trying again in 5s...")
|
logger.warning("%s Error while downloading the track, trying again in 5s...", itemName)
|
||||||
sleep(5)
|
sleep(5)
|
||||||
return downloadMusic(track, trackAPI_gw)
|
return downloadMusic(track, trackAPI_gw)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.errno == errno.ENOSPC:
|
|
||||||
raise DownloadFailed("noSpaceLeft")
|
|
||||||
else:
|
|
||||||
if writepath.is_file(): writepath.unlink()
|
if writepath.is_file(): writepath.unlink()
|
||||||
logger.exception(f"[{track.mainArtist.name} - {track.title}] Error while downloading the track, you should report this to the developers: {str(e)}")
|
if e.errno == errno.ENOSPC: raise DownloadFailed("noSpaceLeft") from e
|
||||||
|
logger.exception("%s Error while downloading the track, you should report this to the developers: %s", itemName, e)
|
||||||
raise e
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if writepath.is_file(): writepath.unlink()
|
if writepath.is_file(): writepath.unlink()
|
||||||
logger.exception(f"[{track.mainArtist.name} - {track.title}] Error while downloading the track, you should report this to the developers: {str(e)}")
|
logger.exception("%s Error while downloading the track, you should report this to the developers: %s", itemName, e)
|
||||||
raise e
|
raise e
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -375,12 +372,12 @@ class Downloader:
|
||||||
|
|
||||||
if not trackDownloaded: return self.download(trackAPI_gw, track=track)
|
if not trackDownloaded: return self.download(trackAPI_gw, track=track)
|
||||||
else:
|
else:
|
||||||
logger.info(f"[{track.mainArtist.name} - {track.title}] Skipping track as it's already downloaded")
|
logger.info("%s Skipping track as it's already downloaded", itemName)
|
||||||
self.downloadObject.completeTrackProgress(self.interface)
|
self.downloadObject.completeTrackProgress(self.interface)
|
||||||
|
|
||||||
# Adding tags
|
# Adding tags
|
||||||
if (not trackAlreadyDownloaded or self.settings['overwriteFile'] in [OverwriteOption.ONLY_TAGS, OverwriteOption.OVERWRITE]) and not track.localTrack:
|
if (not trackAlreadyDownloaded or self.settings['overwriteFile'] in [OverwriteOption.ONLY_TAGS, OverwriteOption.OVERWRITE]) and not track.localTrack:
|
||||||
logger.info(f"[{track.mainArtist.name} - {track.title}] Applying tags to the track")
|
logger.info("%s Applying tags to the track", itemName)
|
||||||
if track.selectedFormat in [TrackFormats.MP3_320, TrackFormats.MP3_128, TrackFormats.DEFAULT]:
|
if track.selectedFormat in [TrackFormats.MP3_320, TrackFormats.MP3_128, TrackFormats.DEFAULT]:
|
||||||
tagID3(writepath, track, self.settings['tags'])
|
tagID3(writepath, track, self.settings['tags'])
|
||||||
elif track.selectedFormat == TrackFormats.FLAC:
|
elif track.selectedFormat == TrackFormats.FLAC:
|
||||||
|
@ -388,14 +385,14 @@ class Downloader:
|
||||||
tagFLAC(writepath, track, self.settings['tags'])
|
tagFLAC(writepath, track, self.settings['tags'])
|
||||||
except (FLACNoHeaderError, FLACError):
|
except (FLACNoHeaderError, FLACError):
|
||||||
if writepath.is_file(): writepath.unlink()
|
if writepath.is_file(): writepath.unlink()
|
||||||
logger.warn(f"[{track.mainArtist.name} - {track.title}] Track not available in FLAC, falling back if necessary")
|
logger.warning("%s Track not available in FLAC, falling back if necessary", itemName)
|
||||||
self.downloadObject.removeTrackProgress(self.interface)
|
self.downloadObject.removeTrackProgress(self.interface)
|
||||||
track.filesizes['FILESIZE_FLAC'] = "0"
|
track.filesizes['FILESIZE_FLAC'] = "0"
|
||||||
track.filesizes['FILESIZE_FLAC_TESTED'] = True
|
track.filesizes['FILESIZE_FLAC_TESTED'] = True
|
||||||
return self.download(trackAPI_gw, track=track)
|
return self.download(trackAPI_gw, track=track)
|
||||||
|
|
||||||
if track.searched: result['searched'] = f"{track.mainArtist.name} - {track.title}"
|
if track.searched: result['searched'] = f"{track.mainArtist.name} - {track.title}"
|
||||||
logger.info(f"[{track.mainArtist.name} - {track.title}] Track download completed\n{str(writepath)}")
|
logger.info("%s Track download completed\n%s", itemName, writepath)
|
||||||
self.downloadObject.downloaded += 1
|
self.downloadObject.downloaded += 1
|
||||||
self.downloadObject.files.append(str(writepath))
|
self.downloadObject.files.append(str(writepath))
|
||||||
self.downloadObject.extrasPath = str(self.extrasPath)
|
self.downloadObject.extrasPath = str(self.extrasPath)
|
||||||
|
@ -413,19 +410,21 @@ class Downloader:
|
||||||
if trackAPI_gw.get('VERSION') and trackAPI_gw['VERSION'] not in trackAPI_gw['SNG_TITLE']:
|
if trackAPI_gw.get('VERSION') and trackAPI_gw['VERSION'] not in trackAPI_gw['SNG_TITLE']:
|
||||||
tempTrack['title'] += f" {trackAPI_gw['VERSION']}".strip()
|
tempTrack['title'] += f" {trackAPI_gw['VERSION']}".strip()
|
||||||
|
|
||||||
|
itemName = f"[{track.mainArtist.name} - {track.title}]"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = self.download(trackAPI_gw, trackAPI, albumAPI, playlistAPI, track)
|
result = self.download(trackAPI_gw, trackAPI, albumAPI, playlistAPI, track)
|
||||||
except DownloadFailed as error:
|
except DownloadFailed as error:
|
||||||
if error.track:
|
if error.track:
|
||||||
track = error.track
|
track = error.track
|
||||||
if track.fallbackID != "0":
|
if track.fallbackID != "0":
|
||||||
logger.warn(f"[{track.mainArtist.name} - {track.title}] {error.message} Using fallback id")
|
logger.warning("%s %s Using fallback id", itemName, error.message)
|
||||||
newTrack = self.dz.gw.get_track_with_fallback(track.fallbackID)
|
newTrack = self.dz.gw.get_track_with_fallback(track.fallbackID)
|
||||||
track.parseEssentialData(newTrack)
|
track.parseEssentialData(newTrack)
|
||||||
track.retriveFilesizes(self.dz)
|
track.retriveFilesizes(self.dz)
|
||||||
return self.downloadWrapper(trackAPI_gw, trackAPI, albumAPI, playlistAPI, track)
|
return self.downloadWrapper(trackAPI_gw, trackAPI, albumAPI, playlistAPI, track)
|
||||||
elif not track.searched and self.settings['fallbackSearch']:
|
if not track.searched and self.settings['fallbackSearch']:
|
||||||
logger.warn(f"[{track.mainArtist.name} - {track.title}] {error.message} Searching for alternative")
|
logger.warning("%s %s Searching for alternative", itemName, error.message)
|
||||||
searchedId = self.dz.api.get_track_id_from_metadata(track.mainArtist.name, track.title, track.album.title)
|
searchedId = self.dz.api.get_track_id_from_metadata(track.mainArtist.name, track.title, track.album.title)
|
||||||
if searchedId != "0":
|
if searchedId != "0":
|
||||||
newTrack = self.dz.gw.get_track_with_fallback(searchedId)
|
newTrack = self.dz.gw.get_track_with_fallback(searchedId)
|
||||||
|
@ -434,7 +433,7 @@ class Downloader:
|
||||||
track.searched = True
|
track.searched = True
|
||||||
if self.interface:
|
if self.interface:
|
||||||
self.interface.send('queueUpdate', {
|
self.interface.send('queueUpdate', {
|
||||||
'uuid': self.queueItem.uuid,
|
'uuid': self.downloadObject.uuid,
|
||||||
'searchFallback': True,
|
'searchFallback': True,
|
||||||
'data': {
|
'data': {
|
||||||
'id': track.id,
|
'id': track.id,
|
||||||
|
@ -443,17 +442,16 @@ class Downloader:
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return self.downloadWrapper(trackAPI_gw, trackAPI, albumAPI, playlistAPI, track)
|
return self.downloadWrapper(trackAPI_gw, trackAPI, albumAPI, playlistAPI, track)
|
||||||
else:
|
|
||||||
error.errid += "NoAlternative"
|
error.errid += "NoAlternative"
|
||||||
error.message = errorMessages[error.errid]
|
error.message = errorMessages[error.errid]
|
||||||
logger.error(f"[{tempTrack['artist']} - {tempTrack['title']}] {error.message}")
|
logger.error("%s %s", itemName, error.message)
|
||||||
result = {'error': {
|
result = {'error': {
|
||||||
'message': error.message,
|
'message': error.message,
|
||||||
'errid': error.errid,
|
'errid': error.errid,
|
||||||
'data': tempTrack
|
'data': tempTrack
|
||||||
}}
|
}}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"[{tempTrack['artist']} - {tempTrack['title']}] {str(e)}")
|
logger.exception("%s %s", itemName, e)
|
||||||
result = {'error': {
|
result = {'error': {
|
||||||
'message': str(e),
|
'message': str(e),
|
||||||
'data': tempTrack
|
'data': tempTrack
|
||||||
|
@ -505,9 +503,9 @@ class Downloader:
|
||||||
errors = ""
|
errors = ""
|
||||||
searched = ""
|
searched = ""
|
||||||
|
|
||||||
for i in range(len(tracks)):
|
for i in enumerate(tracks):
|
||||||
result = tracks[i].result()
|
result = tracks[i].result()
|
||||||
if not result: return None # Check if item is cancelled
|
if not result: return # Check if item is cancelled
|
||||||
|
|
||||||
# Log errors to file
|
# Log errors to file
|
||||||
if result.get('error'):
|
if result.get('error'):
|
||||||
|
@ -558,10 +556,10 @@ class Downloader:
|
||||||
|
|
||||||
class DownloadError(Exception):
|
class DownloadError(Exception):
|
||||||
"""Base class for exceptions in this module."""
|
"""Base class for exceptions in this module."""
|
||||||
pass
|
|
||||||
|
|
||||||
class DownloadFailed(DownloadError):
|
class DownloadFailed(DownloadError):
|
||||||
def __init__(self, errid, track=None):
|
def __init__(self, errid, track=None):
|
||||||
|
super().__init__()
|
||||||
self.errid = errid
|
self.errid = errid
|
||||||
self.message = errorMessages[self.errid]
|
self.message = errorMessages[self.errid]
|
||||||
self.track = track
|
self.track = track
|
||||||
|
@ -569,6 +567,9 @@ class DownloadFailed(DownloadError):
|
||||||
class DownloadCancelled(DownloadError):
|
class DownloadCancelled(DownloadError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class DownloadEmpty(DownloadError):
|
||||||
|
pass
|
||||||
|
|
||||||
class PreferredBitrateNotFound(DownloadError):
|
class PreferredBitrateNotFound(DownloadError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
from deemix.types.DownloadObjects import Single, Collection
|
from deemix.types.DownloadObjects import Single, Collection
|
||||||
|
from deezer.utils import map_user_playlist
|
||||||
from deezer.api import APIError
|
from deezer.api import APIError
|
||||||
from deezer.gw import GWAPIError, LyricsStatus
|
from deezer.gw import GWAPIError, LyricsStatus
|
||||||
|
|
||||||
|
logger = logging.getLogger('deemix')
|
||||||
|
|
||||||
class GenerationError(Exception):
|
class GenerationError(Exception):
|
||||||
def __init__(self, link, message, errid=None):
|
def __init__(self, link, message, errid=None):
|
||||||
|
super().__init__()
|
||||||
self.link = link
|
self.link = link
|
||||||
self.message = message
|
self.message = message
|
||||||
self.errid = errid
|
self.errid = errid
|
||||||
|
@ -15,27 +21,26 @@ class GenerationError(Exception):
|
||||||
'errid': self.errid
|
'errid': self.errid
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateTrackItem(dz, id, bitrate, trackAPI=None, albumAPI=None):
|
def generateTrackItem(dz, link_id, bitrate, trackAPI=None, albumAPI=None):
|
||||||
# Check if is an isrc: url
|
# Check if is an isrc: url
|
||||||
if str(id).startswith("isrc"):
|
if str(link_id).startswith("isrc"):
|
||||||
try:
|
try:
|
||||||
trackAPI = dz.api.get_track(id)
|
trackAPI = dz.api.get_track(link_id)
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
e = str(e)
|
raise GenerationError("https://deezer.com/track/"+str(link_id), f"Wrong URL: {e}") from e
|
||||||
raise GenerationError("https://deezer.com/track/"+str(id), f"Wrong URL: {e}")
|
|
||||||
if 'id' in trackAPI and 'title' in trackAPI:
|
if 'id' in trackAPI and 'title' in trackAPI:
|
||||||
id = trackAPI['id']
|
link_id = trackAPI['id']
|
||||||
else:
|
else:
|
||||||
raise GenerationError("https://deezer.com/track/"+str(id), "Track ISRC is not available on deezer", "ISRCnotOnDeezer")
|
raise GenerationError("https://deezer.com/track/"+str(link_id), "Track ISRC is not available on deezer", "ISRCnotOnDeezer")
|
||||||
|
|
||||||
# Get essential track info
|
# Get essential track info
|
||||||
try:
|
try:
|
||||||
trackAPI_gw = dz.gw.get_track_with_fallback(id)
|
trackAPI_gw = dz.gw.get_track_with_fallback(link_id)
|
||||||
except GWAPIError as e:
|
except GWAPIError as e:
|
||||||
e = str(e)
|
|
||||||
message = "Wrong URL"
|
message = "Wrong URL"
|
||||||
if "DATA_ERROR" in e: message += f": {e['DATA_ERROR']}"
|
# TODO: FIX
|
||||||
raise GenerationError("https://deezer.com/track/"+str(id), message)
|
# if "DATA_ERROR" in e: message += f": {e['DATA_ERROR']}"
|
||||||
|
raise GenerationError("https://deezer.com/track/"+str(link_id), message) from e
|
||||||
|
|
||||||
title = trackAPI_gw['SNG_TITLE'].strip()
|
title = trackAPI_gw['SNG_TITLE'].strip()
|
||||||
if trackAPI_gw.get('VERSION') and trackAPI_gw['VERSION'] not in trackAPI_gw['SNG_TITLE']:
|
if trackAPI_gw.get('VERSION') and trackAPI_gw['VERSION'] not in trackAPI_gw['SNG_TITLE']:
|
||||||
|
@ -44,7 +49,7 @@ def generateTrackItem(dz, id, bitrate, trackAPI=None, albumAPI=None):
|
||||||
|
|
||||||
return Single({
|
return Single({
|
||||||
'type': 'track',
|
'type': 'track',
|
||||||
'id': id,
|
'id': link_id,
|
||||||
'bitrate': bitrate,
|
'bitrate': bitrate,
|
||||||
'title': title,
|
'title': title,
|
||||||
'artist': trackAPI_gw['ART_NAME'],
|
'artist': trackAPI_gw['ART_NAME'],
|
||||||
|
@ -57,19 +62,18 @@ def generateTrackItem(dz, id, bitrate, trackAPI=None, albumAPI=None):
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
def generateAlbumItem(dz, id, bitrate, rootArtist=None):
|
def generateAlbumItem(dz, link_id, bitrate, rootArtist=None):
|
||||||
# Get essential album info
|
# Get essential album info
|
||||||
try:
|
try:
|
||||||
albumAPI = dz.api.get_album(id)
|
albumAPI = dz.api.get_album(link_id)
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
e = str(e)
|
raise GenerationError("https://deezer.com/album/"+str(link_id), f"Wrong URL: {e}") from e
|
||||||
raise GenerationError("https://deezer.com/album/"+str(id), f"Wrong URL: {e}")
|
|
||||||
|
|
||||||
if str(id).startswith('upc'): id = albumAPI['id']
|
if str(link_id).startswith('upc'): link_id = albumAPI['id']
|
||||||
|
|
||||||
# Get extra info about album
|
# Get extra info about album
|
||||||
# This saves extra api calls when downloading
|
# This saves extra api calls when downloading
|
||||||
albumAPI_gw = dz.gw.get_album(id)
|
albumAPI_gw = dz.gw.get_album(link_id)
|
||||||
albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK']
|
albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK']
|
||||||
albumAPI['copyright'] = albumAPI_gw['COPYRIGHT']
|
albumAPI['copyright'] = albumAPI_gw['COPYRIGHT']
|
||||||
albumAPI['root_artist'] = rootArtist
|
albumAPI['root_artist'] = rootArtist
|
||||||
|
@ -78,9 +82,9 @@ def generateAlbumItem(dz, id, bitrate, rootArtist=None):
|
||||||
if albumAPI['nb_tracks'] == 1:
|
if albumAPI['nb_tracks'] == 1:
|
||||||
return generateTrackItem(dz, albumAPI['tracks']['data'][0]['id'], bitrate, albumAPI=albumAPI)
|
return generateTrackItem(dz, albumAPI['tracks']['data'][0]['id'], bitrate, albumAPI=albumAPI)
|
||||||
|
|
||||||
tracksArray = dz.gw.get_album_tracks(id)
|
tracksArray = dz.gw.get_album_tracks(link_id)
|
||||||
|
|
||||||
if albumAPI['cover_small'] != None:
|
if albumAPI['cover_small'] is not None:
|
||||||
cover = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg'
|
cover = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg'
|
||||||
else:
|
else:
|
||||||
cover = f"https://e-cdns-images.dzcdn.net/images/cover/{albumAPI_gw['ALB_PICTURE']}/75x75-000000-80-0-0.jpg"
|
cover = f"https://e-cdns-images.dzcdn.net/images/cover/{albumAPI_gw['ALB_PICTURE']}/75x75-000000-80-0-0.jpg"
|
||||||
|
@ -97,7 +101,7 @@ def generateAlbumItem(dz, id, bitrate, rootArtist=None):
|
||||||
|
|
||||||
return Collection({
|
return Collection({
|
||||||
'type': 'album',
|
'type': 'album',
|
||||||
'id': id,
|
'id': link_id,
|
||||||
'bitrate': bitrate,
|
'bitrate': bitrate,
|
||||||
'title': albumAPI['title'],
|
'title': albumAPI['title'],
|
||||||
'artist': albumAPI['artist']['name'],
|
'artist': albumAPI['artist']['name'],
|
||||||
|
@ -110,32 +114,31 @@ def generateAlbumItem(dz, id, bitrate, rootArtist=None):
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
def generatePlaylistItem(dz, id, bitrate, playlistAPI=None, playlistTracksAPI=None):
|
def generatePlaylistItem(dz, link_id, bitrate, playlistAPI=None, playlistTracksAPI=None):
|
||||||
if not playlistAPI:
|
if not playlistAPI:
|
||||||
# Get essential playlist info
|
# Get essential playlist info
|
||||||
try:
|
try:
|
||||||
playlistAPI = dz.api.get_playlist(id)
|
playlistAPI = dz.api.get_playlist(link_id)
|
||||||
except:
|
except APIError:
|
||||||
playlistAPI = None
|
playlistAPI = None
|
||||||
# Fallback to gw api if the playlist is private
|
# Fallback to gw api if the playlist is private
|
||||||
if not playlistAPI:
|
if not playlistAPI:
|
||||||
try:
|
try:
|
||||||
userPlaylist = dz.gw.get_playlist_page(id)
|
userPlaylist = dz.gw.get_playlist_page(link_id)
|
||||||
playlistAPI = map_user_playlist(userPlaylist['DATA'])
|
playlistAPI = map_user_playlist(userPlaylist['DATA'])
|
||||||
except GWAPIError as e:
|
except GWAPIError as e:
|
||||||
e = str(e)
|
|
||||||
message = "Wrong URL"
|
message = "Wrong URL"
|
||||||
if "DATA_ERROR" in e:
|
# TODO: FIX
|
||||||
message += f": {e['DATA_ERROR']}"
|
# if "DATA_ERROR" in e: message += f": {e['DATA_ERROR']}"
|
||||||
raise GenerationError("https://deezer.com/playlist/"+str(id), message)
|
raise GenerationError("https://deezer.com/playlist/"+str(link_id), message) from e
|
||||||
|
|
||||||
# Check if private playlist and owner
|
# Check if private playlist and owner
|
||||||
if not playlistAPI.get('public', False) and playlistAPI['creator']['id'] != str(dz.current_user['id']):
|
if not playlistAPI.get('public', False) and playlistAPI['creator']['id'] != str(dz.current_user['id']):
|
||||||
logger.warning("You can't download others private playlists.")
|
logger.warning("You can't download others private playlists.")
|
||||||
raise GenerationError("https://deezer.com/playlist/"+str(id), "You can't download others private playlists.", "notYourPrivatePlaylist")
|
raise GenerationError("https://deezer.com/playlist/"+str(link_id), "You can't download others private playlists.", "notYourPrivatePlaylist")
|
||||||
|
|
||||||
if not playlistTracksAPI:
|
if not playlistTracksAPI:
|
||||||
playlistTracksAPI = dz.gw.get_playlist_tracks(id)
|
playlistTracksAPI = dz.gw.get_playlist_tracks(link_id)
|
||||||
playlistAPI['various_artist'] = dz.api.get_artist(5080) # Useful for save as compilation
|
playlistAPI['various_artist'] = dz.api.get_artist(5080) # Useful for save as compilation
|
||||||
|
|
||||||
totalSize = len(playlistTracksAPI)
|
totalSize = len(playlistTracksAPI)
|
||||||
|
@ -148,11 +151,11 @@ def generatePlaylistItem(dz, id, bitrate, playlistAPI=None, playlistTracksAPI=No
|
||||||
trackAPI['SIZE'] = totalSize
|
trackAPI['SIZE'] = totalSize
|
||||||
collection.append(trackAPI)
|
collection.append(trackAPI)
|
||||||
|
|
||||||
if not 'explicit' in playlistAPI: playlistAPI['explicit'] = False
|
if 'explicit' not in playlistAPI: playlistAPI['explicit'] = False
|
||||||
|
|
||||||
return Collection({
|
return Collection({
|
||||||
'type': 'playlist',
|
'type': 'playlist',
|
||||||
'id': id,
|
'id': link_id,
|
||||||
'bitrate': bitrate,
|
'bitrate': bitrate,
|
||||||
'title': playlistAPI['title'],
|
'title': playlistAPI['title'],
|
||||||
'artist': playlistAPI['creator']['name'],
|
'artist': playlistAPI['creator']['name'],
|
||||||
|
@ -165,60 +168,59 @@ def generatePlaylistItem(dz, id, bitrate, playlistAPI=None, playlistTracksAPI=No
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
def generateArtistItem(dz, id, bitrate, interface=None):
|
def generateArtistItem(dz, link_id, bitrate, interface=None):
|
||||||
# Get essential artist info
|
# Get essential artist info
|
||||||
try:
|
try:
|
||||||
artistAPI = dz.api.get_artist(id)
|
artistAPI = dz.api.get_artist(link_id)
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
e = str(e)
|
raise GenerationError("https://deezer.com/artist/"+str(link_id), f"Wrong URL: {e}") from e
|
||||||
raise GenerationError("https://deezer.com/artist/"+str(id), f"Wrong URL: {e}")
|
|
||||||
|
|
||||||
if interface: interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
|
|
||||||
rootArtist = {
|
rootArtist = {
|
||||||
'id': artistAPI['id'],
|
'id': artistAPI['id'],
|
||||||
'name': artistAPI['name']
|
'name': artistAPI['name']
|
||||||
}
|
}
|
||||||
|
if interface: interface.send("startAddingArtist", rootArtist)
|
||||||
|
|
||||||
artistDiscographyAPI = dz.gw.get_artist_discography_tabs(id, 100)
|
artistDiscographyAPI = dz.gw.get_artist_discography_tabs(link_id, 100)
|
||||||
allReleases = artistDiscographyAPI.pop('all', [])
|
allReleases = artistDiscographyAPI.pop('all', [])
|
||||||
albumList = []
|
albumList = []
|
||||||
for album in allReleases:
|
for album in allReleases:
|
||||||
albumList.append(generateAlbumItem(dz, album['id'], bitrate, rootArtist=rootArtist))
|
albumList.append(generateAlbumItem(dz, album['id'], bitrate, rootArtist=rootArtist))
|
||||||
|
|
||||||
if interface: interface.send("finishAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
|
if interface: interface.send("finishAddingArtist", rootArtist)
|
||||||
return albumList
|
return albumList
|
||||||
|
|
||||||
def generateArtistDiscographyItem(dz, id, bitrate, interface=None):
|
def generateArtistDiscographyItem(dz, link_id, bitrate, interface=None):
|
||||||
# Get essential artist info
|
# Get essential artist info
|
||||||
try:
|
try:
|
||||||
artistAPI = dz.api.get_artist(id)
|
artistAPI = dz.api.get_artist(link_id)
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
e = str(e)
|
e = str(e)
|
||||||
raise GenerationError("https://deezer.com/artist/"+str(id)+"/discography", f"Wrong URL: {e}")
|
raise GenerationError("https://deezer.com/artist/"+str(link_id)+"/discography", f"Wrong URL: {e}")
|
||||||
|
|
||||||
if interface: interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
|
|
||||||
rootArtist = {
|
rootArtist = {
|
||||||
'id': artistAPI['id'],
|
'id': artistAPI['id'],
|
||||||
'name': artistAPI['name']
|
'name': artistAPI['name']
|
||||||
}
|
}
|
||||||
|
if interface: interface.send("startAddingArtist", rootArtist)
|
||||||
|
|
||||||
artistDiscographyAPI = dz.gw.get_artist_discography_tabs(id, 100)
|
artistDiscographyAPI = dz.gw.get_artist_discography_tabs(link_id, 100)
|
||||||
artistDiscographyAPI.pop('all', None) # all contains albums and singles, so its all duplicates. This removes them
|
artistDiscographyAPI.pop('all', None) # all contains albums and singles, so its all duplicates. This removes them
|
||||||
albumList = []
|
albumList = []
|
||||||
for type in artistDiscographyAPI:
|
for releaseType in artistDiscographyAPI:
|
||||||
for album in artistDiscographyAPI[type]:
|
for album in artistDiscographyAPI[releaseType]:
|
||||||
albumList.append(generateAlbumItem(dz, album['id'], bitrate, rootArtist=rootArtist))
|
albumList.append(generateAlbumItem(dz, album['id'], bitrate, rootArtist=rootArtist))
|
||||||
|
|
||||||
if interface: interface.send("finishAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
|
if interface: interface.send("finishAddingArtist", rootArtist)
|
||||||
return albumList
|
return albumList
|
||||||
|
|
||||||
def generateArtistTopItem(dz, id, bitrate, interface=None):
|
def generateArtistTopItem(dz, link_id, bitrate, interface=None):
|
||||||
# Get essential artist info
|
# Get essential artist info
|
||||||
try:
|
try:
|
||||||
artistAPI = dz.api.get_artist(id)
|
artistAPI = dz.api.get_artist(link_id)
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
e = str(e)
|
e = str(e)
|
||||||
raise GenerationError("https://deezer.com/artist/"+str(id)+"/top_track", f"Wrong URL: {e}")
|
raise GenerationError("https://deezer.com/artist/"+str(link_id)+"/top_track", f"Wrong URL: {e}")
|
||||||
|
|
||||||
# Emulate the creation of a playlist
|
# Emulate the creation of a playlist
|
||||||
# Can't use generatePlaylistItem directly as this is not a real playlist
|
# Can't use generatePlaylistItem directly as this is not a real playlist
|
||||||
|
@ -250,5 +252,5 @@ def generateArtistTopItem(dz, id, bitrate, interface=None):
|
||||||
'type': "playlist"
|
'type': "playlist"
|
||||||
}
|
}
|
||||||
|
|
||||||
artistTopTracksAPI_gw = dz.gw.get_artist_toptracks(id)
|
artistTopTracksAPI_gw = dz.gw.get_artist_toptracks(link_id)
|
||||||
return generatePlaylistItem(dz, playlistAPI['id'], bitrate, playlistAPI=playlistAPI, playlistTracksAPI=artistTopTracksAPI_gw)
|
return generatePlaylistItem(dz, playlistAPI['id'], bitrate, playlistAPI=playlistAPI, playlistTracksAPI=artistTopTracksAPI_gw)
|
||||||
|
|
|
@ -4,16 +4,16 @@ from os import makedirs
|
||||||
from deezer import TrackFormats
|
from deezer import TrackFormats
|
||||||
import deemix.utils.localpaths as localpaths
|
import deemix.utils.localpaths as localpaths
|
||||||
|
|
||||||
"""Should the lib overwrite files?"""
|
|
||||||
class OverwriteOption():
|
class OverwriteOption():
|
||||||
|
"""Should the lib overwrite files?"""
|
||||||
OVERWRITE = 'y' # Yes, overwrite the file
|
OVERWRITE = 'y' # Yes, overwrite the file
|
||||||
DONT_OVERWRITE = 'n' # No, don't overwrite the file
|
DONT_OVERWRITE = 'n' # No, don't overwrite the file
|
||||||
DONT_CHECK_EXT = 'e' # No, and don't check for extensions
|
DONT_CHECK_EXT = 'e' # No, and don't check for extensions
|
||||||
KEEP_BOTH = 'b' # No, and keep both files
|
KEEP_BOTH = 'b' # No, and keep both files
|
||||||
ONLY_TAGS = 't' # Overwrite only the tags
|
ONLY_TAGS = 't' # Overwrite only the tags
|
||||||
|
|
||||||
"""What should I do with featured artists?"""
|
|
||||||
class FeaturesOption():
|
class FeaturesOption():
|
||||||
|
"""What should I do with featured artists?"""
|
||||||
NO_CHANGE = "0" # Do nothing
|
NO_CHANGE = "0" # Do nothing
|
||||||
REMOVE_TITLE = "1" # Remove from track title
|
REMOVE_TITLE = "1" # Remove from track title
|
||||||
REMOVE_TITLE_ALBUM = "3" # Remove from track title and album title
|
REMOVE_TITLE_ALBUM = "3" # Remove from track title and album title
|
||||||
|
@ -121,13 +121,13 @@ def loadSettings(configFolder=None):
|
||||||
|
|
||||||
def checkSettings(settings):
|
def checkSettings(settings):
|
||||||
changes = 0
|
changes = 0
|
||||||
for set in DEFAULTS:
|
for i_set in DEFAULTS:
|
||||||
if not set in settings or type(settings[set]) != type(DEFAULTS[set]):
|
if not i_set in settings or not isinstance(settings[i_set], DEFAULTS[i_set]):
|
||||||
settings[set] = DEFAULTS[set]
|
settings[i_set] = DEFAULTS[i_set]
|
||||||
changes += 1
|
changes += 1
|
||||||
for set in DEFAULTS['tags']:
|
for i_set in DEFAULTS['tags']:
|
||||||
if not set in settings['tags'] or type(settings['tags'][set]) != type(DEFAULTS['tags'][set]):
|
if not i_set in settings['tags'] or not isinstance(settings['tags'][i_set], DEFAULTS['tags'][i_set]):
|
||||||
settings['tags'][set] = DEFAULTS['tags'][set]
|
settings['tags'][i_set] = DEFAULTS['tags'][i_set]
|
||||||
changes += 1
|
changes += 1
|
||||||
if settings['downloadLocation'] == "":
|
if settings['downloadLocation'] == "":
|
||||||
settings['downloadLocation'] = DEFAULTS['downloadLocation']
|
settings['downloadLocation'] = DEFAULTS['downloadLocation']
|
||||||
|
|
|
@ -7,8 +7,8 @@ from deemix.types.Picture import Picture
|
||||||
from deemix.types import VARIOUS_ARTISTS
|
from deemix.types import VARIOUS_ARTISTS
|
||||||
|
|
||||||
class Album:
|
class Album:
|
||||||
def __init__(self, id="0", title="", pic_md5=""):
|
def __init__(self, alb_id="0", title="", pic_md5=""):
|
||||||
self.id = id
|
self.id = alb_id
|
||||||
self.title = title
|
self.title = title
|
||||||
self.pic = Picture(md5=pic_md5, type="cover")
|
self.pic = Picture(md5=pic_md5, type="cover")
|
||||||
self.artist = {"Main": []}
|
self.artist = {"Main": []}
|
||||||
|
@ -24,11 +24,15 @@ class Album:
|
||||||
self.genre = []
|
self.genre = []
|
||||||
self.barcode = "Unknown"
|
self.barcode = "Unknown"
|
||||||
self.label = "Unknown"
|
self.label = "Unknown"
|
||||||
|
self.copyright = None
|
||||||
self.recordType = "album"
|
self.recordType = "album"
|
||||||
self.bitrate = 0
|
self.bitrate = 0
|
||||||
self.rootArtist = None
|
self.rootArtist = None
|
||||||
self.variousArtists = None
|
self.variousArtists = None
|
||||||
|
|
||||||
|
self.playlistId = None
|
||||||
|
self.owner = None
|
||||||
|
|
||||||
def parseAlbum(self, albumAPI):
|
def parseAlbum(self, albumAPI):
|
||||||
self.title = albumAPI['title']
|
self.title = albumAPI['title']
|
||||||
|
|
||||||
|
@ -80,7 +84,7 @@ class Album:
|
||||||
day = albumAPI["release_date"][8:10]
|
day = albumAPI["release_date"][8:10]
|
||||||
month = albumAPI["release_date"][5:7]
|
month = albumAPI["release_date"][5:7]
|
||||||
year = albumAPI["release_date"][0:4]
|
year = albumAPI["release_date"][0:4]
|
||||||
self.date = Date(year, month, day)
|
self.date = Date(day, month, year)
|
||||||
|
|
||||||
self.discTotal = albumAPI.get('nb_disk')
|
self.discTotal = albumAPI.get('nb_disk')
|
||||||
self.copyright = albumAPI.get('copyright')
|
self.copyright = albumAPI.get('copyright')
|
||||||
|
@ -115,7 +119,7 @@ class Album:
|
||||||
day = albumAPI_gw["PHYSICAL_RELEASE_DATE"][8:10]
|
day = albumAPI_gw["PHYSICAL_RELEASE_DATE"][8:10]
|
||||||
month = albumAPI_gw["PHYSICAL_RELEASE_DATE"][5:7]
|
month = albumAPI_gw["PHYSICAL_RELEASE_DATE"][5:7]
|
||||||
year = albumAPI_gw["PHYSICAL_RELEASE_DATE"][0:4]
|
year = albumAPI_gw["PHYSICAL_RELEASE_DATE"][0:4]
|
||||||
self.date = Date(year, month, day)
|
self.date = Date(day, month, year)
|
||||||
|
|
||||||
def makePlaylistCompilation(self, playlist):
|
def makePlaylistCompilation(self, playlist):
|
||||||
self.variousArtists = playlist.variousArtists
|
self.variousArtists = playlist.variousArtists
|
||||||
|
@ -136,8 +140,9 @@ class Album:
|
||||||
self.pic = playlist.pic
|
self.pic = playlist.pic
|
||||||
|
|
||||||
def removeDuplicateArtists(self):
|
def removeDuplicateArtists(self):
|
||||||
|
"""Removes duplicate artists for both artist array and artists dict"""
|
||||||
(self.artist, self.artists) = removeDuplicateArtists(self.artist, self.artists)
|
(self.artist, self.artists) = removeDuplicateArtists(self.artist, self.artists)
|
||||||
|
|
||||||
# Removes featuring from the album name
|
|
||||||
def getCleanTitle(self):
|
def getCleanTitle(self):
|
||||||
|
"""Removes featuring from the album name"""
|
||||||
return removeFeatures(self.title)
|
return removeFeatures(self.title)
|
||||||
|
|
|
@ -2,8 +2,8 @@ from deemix.types.Picture import Picture
|
||||||
from deemix.types import VARIOUS_ARTISTS
|
from deemix.types import VARIOUS_ARTISTS
|
||||||
|
|
||||||
class Artist:
|
class Artist:
|
||||||
def __init__(self, id="0", name="", role="", pic_md5=""):
|
def __init__(self, art_id="0", name="", role="", pic_md5=""):
|
||||||
self.id = str(id)
|
self.id = str(art_id)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.pic = Picture(md5=pic_md5, type="artist")
|
self.pic = Picture(md5=pic_md5, type="artist")
|
||||||
self.role = role
|
self.role = role
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
class Date(object):
|
class Date:
|
||||||
def __init__(self, day="00", month="00", year="XXXX"):
|
def __init__(self, day="00", month="00", year="XXXX"):
|
||||||
self.year = year
|
self.year = year
|
||||||
self.month = month
|
self.month = month
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
class IDownloadObject:
|
class IDownloadObject:
|
||||||
|
"""DownloadObject interface"""
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
self.type = obj['type']
|
self.type = obj['type']
|
||||||
self.id = obj['id']
|
self.id = obj['id']
|
||||||
|
@ -50,9 +51,9 @@ class IDownloadObject:
|
||||||
def getSlimmedDict(self):
|
def getSlimmedDict(self):
|
||||||
light = self.toDict()
|
light = self.toDict()
|
||||||
propertiesToDelete = ['single', 'collection', 'convertable']
|
propertiesToDelete = ['single', 'collection', 'convertable']
|
||||||
for property in propertiesToDelete:
|
for prop in propertiesToDelete:
|
||||||
if property in light:
|
if prop in light:
|
||||||
del light[property]
|
del light[prop]
|
||||||
return light
|
return light
|
||||||
|
|
||||||
def updateProgress(self, interface=None):
|
def updateProgress(self, interface=None):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class Lyrics:
|
class Lyrics:
|
||||||
def __init__(self, id="0"):
|
def __init__(self, lyr_id="0"):
|
||||||
self.id = id
|
self.id = lyr_id
|
||||||
self.sync = ""
|
self.sync = ""
|
||||||
self.unsync = ""
|
self.unsync = ""
|
||||||
self.syncID3 = []
|
self.syncID3 = []
|
||||||
|
@ -11,7 +11,7 @@ class Lyrics:
|
||||||
syncLyricsJson = lyricsAPI["LYRICS_SYNC_JSON"]
|
syncLyricsJson = lyricsAPI["LYRICS_SYNC_JSON"]
|
||||||
timestamp = ""
|
timestamp = ""
|
||||||
milliseconds = 0
|
milliseconds = 0
|
||||||
for line in range(len(syncLyricsJson)):
|
for line in enumerate(syncLyricsJson):
|
||||||
if syncLyricsJson[line]["line"] != "":
|
if syncLyricsJson[line]["line"] != "":
|
||||||
timestamp = syncLyricsJson[line]["lrc_timestamp"]
|
timestamp = syncLyricsJson[line]["lrc_timestamp"]
|
||||||
milliseconds = int(syncLyricsJson[line]["milliseconds"])
|
milliseconds = int(syncLyricsJson[line]["milliseconds"])
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
class Picture:
|
class Picture:
|
||||||
def __init__(self, md5="", type="", url=None):
|
def __init__(self, md5="", pic_type="", url=None):
|
||||||
self.md5 = md5
|
self.md5 = md5
|
||||||
self.type = type
|
self.type = pic_type
|
||||||
self.staticUrl = url
|
self.staticUrl = url
|
||||||
|
|
||||||
def generatePictureURL(self, size, format):
|
def generatePictureURL(self, size, pic_format):
|
||||||
if self.staticUrl: return self.staticUrl
|
if self.staticUrl: return self.staticUrl
|
||||||
|
|
||||||
url = "https://e-cdns-images.dzcdn.net/images/{}/{}/{}x{}".format(
|
url = "https://e-cdns-images.dzcdn.net/images/{}/{}/{size}x{size}".format(
|
||||||
self.type,
|
self.type,
|
||||||
self.md5,
|
self.md5,
|
||||||
size, size
|
size=size
|
||||||
)
|
)
|
||||||
|
|
||||||
if format.startswith("jpg"):
|
if pic_format.startswith("jpg"):
|
||||||
quality = 80
|
quality = 80
|
||||||
if '-' in format:
|
if '-' in pic_format:
|
||||||
quality = format[4:]
|
quality = pic_format[4:]
|
||||||
format = 'jpg'
|
pic_format = 'jpg'
|
||||||
return url + f'-000000-{quality}-0-0.jpg'
|
return url + f'-000000-{quality}-0-0.jpg'
|
||||||
if format == 'png':
|
if pic_format == 'png':
|
||||||
return url + '-none-100-0-0.png'
|
return url + '-none-100-0-0.png'
|
||||||
|
|
||||||
return url+'.jpg'
|
return url+'.jpg'
|
||||||
|
|
|
@ -32,7 +32,7 @@ class Playlist:
|
||||||
md5 = url[url.find(picType+'/') + len(picType)+1:-24]
|
md5 = url[url.find(picType+'/') + len(picType)+1:-24]
|
||||||
self.pic = Picture(
|
self.pic = Picture(
|
||||||
md5 = md5,
|
md5 = md5,
|
||||||
type = picType
|
pic_type = picType
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.pic = Picture(url = playlistAPI['picture_xl'])
|
self.pic = Picture(url = playlistAPI['picture_xl'])
|
||||||
|
@ -41,7 +41,7 @@ class Playlist:
|
||||||
pic_md5 = playlistAPI['various_artist']['picture_small']
|
pic_md5 = playlistAPI['various_artist']['picture_small']
|
||||||
pic_md5 = pic_md5[pic_md5.find('artist/') + 7:-24]
|
pic_md5 = pic_md5[pic_md5.find('artist/') + 7:-24]
|
||||||
self.variousArtists = Artist(
|
self.variousArtists = Artist(
|
||||||
id = playlistAPI['various_artist']['id'],
|
art_id = playlistAPI['various_artist']['id'],
|
||||||
name = playlistAPI['various_artist']['name'],
|
name = playlistAPI['various_artist']['name'],
|
||||||
role = "Main",
|
role = "Main",
|
||||||
pic_md5 = pic_md5
|
pic_md5 = pic_md5
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import requests
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
|
||||||
from deezer.gw import GWAPIError
|
from deezer.gw import GWAPIError
|
||||||
from deezer.api import APIError
|
from deezer.api import APIError
|
||||||
|
@ -14,9 +15,11 @@ from deemix.types.Playlist import Playlist
|
||||||
from deemix.types.Lyrics import Lyrics
|
from deemix.types.Lyrics import Lyrics
|
||||||
from deemix.types import VARIOUS_ARTISTS
|
from deemix.types import VARIOUS_ARTISTS
|
||||||
|
|
||||||
|
from deemix.settings import FeaturesOption
|
||||||
|
|
||||||
class Track:
|
class Track:
|
||||||
def __init__(self, id="0", name=""):
|
def __init__(self, sng_id="0", name=""):
|
||||||
self.id = id
|
self.id = sng_id
|
||||||
self.title = name
|
self.title = name
|
||||||
self.MD5 = ""
|
self.MD5 = ""
|
||||||
self.mediaVersion = ""
|
self.mediaVersion = ""
|
||||||
|
@ -82,9 +85,9 @@ class Track:
|
||||||
result_json = site.json()
|
result_json = site.json()
|
||||||
except:
|
except:
|
||||||
sleep(2)
|
sleep(2)
|
||||||
return self.retriveFilesizes(dz)
|
self.retriveFilesizes(dz)
|
||||||
if len(result_json['error']):
|
if len(result_json['error']):
|
||||||
raise APIError(json.dumps(result_json['error']))
|
raise APIError(result_json.dumps(result_json['error']))
|
||||||
response = result_json.get("results")
|
response = result_json.get("results")
|
||||||
filesizes = {}
|
filesizes = {}
|
||||||
for key, value in response.items():
|
for key, value in response.items():
|
||||||
|
@ -116,7 +119,7 @@ class Track:
|
||||||
|
|
||||||
# Parse Album Data
|
# Parse Album Data
|
||||||
self.album = Album(
|
self.album = Album(
|
||||||
id = trackAPI_gw['ALB_ID'],
|
alb_id = trackAPI_gw['ALB_ID'],
|
||||||
title = trackAPI_gw['ALB_TITLE'],
|
title = trackAPI_gw['ALB_TITLE'],
|
||||||
pic_md5 = trackAPI_gw.get('ALB_PICTURE')
|
pic_md5 = trackAPI_gw.get('ALB_PICTURE')
|
||||||
)
|
)
|
||||||
|
@ -157,7 +160,7 @@ class Track:
|
||||||
if not len(self.artist['Main']):
|
if not len(self.artist['Main']):
|
||||||
self.artist['Main'] = [self.mainArtist['name']]
|
self.artist['Main'] = [self.mainArtist['name']]
|
||||||
|
|
||||||
self.singleDownload = trackAPI_gw.get('SINGLE_TRACK', False) # TODO: To change
|
self.singleDownload = trackAPI_gw.get('SINGLE_TRACK', False) # TODO: Change
|
||||||
self.position = trackAPI_gw.get('POSITION')
|
self.position = trackAPI_gw.get('POSITION')
|
||||||
|
|
||||||
# Add playlist data if track is in a playlist
|
# Add playlist data if track is in a playlist
|
||||||
|
@ -173,7 +176,7 @@ class Track:
|
||||||
self.album = Album(title=trackAPI_gw['ALB_TITLE'])
|
self.album = Album(title=trackAPI_gw['ALB_TITLE'])
|
||||||
self.album.pic = Picture(
|
self.album.pic = Picture(
|
||||||
md5 = trackAPI_gw.get('ALB_PICTURE', ""),
|
md5 = trackAPI_gw.get('ALB_PICTURE', ""),
|
||||||
type = "cover"
|
pic_type = "cover"
|
||||||
)
|
)
|
||||||
self.mainArtist = Artist(name=trackAPI_gw['ART_NAME'])
|
self.mainArtist = Artist(name=trackAPI_gw['ART_NAME'])
|
||||||
self.artists = [trackAPI_gw['ART_NAME']]
|
self.artists = [trackAPI_gw['ART_NAME']]
|
||||||
|
@ -188,7 +191,7 @@ class Track:
|
||||||
|
|
||||||
def parseTrackGW(self, trackAPI_gw):
|
def parseTrackGW(self, trackAPI_gw):
|
||||||
self.title = trackAPI_gw['SNG_TITLE'].strip()
|
self.title = trackAPI_gw['SNG_TITLE'].strip()
|
||||||
if trackAPI_gw.get('VERSION') and not trackAPI_gw['VERSION'].strip() in this.title:
|
if trackAPI_gw.get('VERSION') and not trackAPI_gw['VERSION'].strip() in self.title:
|
||||||
self.title += f" {trackAPI_gw['VERSION'].strip()}"
|
self.title += f" {trackAPI_gw['VERSION'].strip()}"
|
||||||
|
|
||||||
self.discNumber = trackAPI_gw.get('DISK_NUMBER')
|
self.discNumber = trackAPI_gw.get('DISK_NUMBER')
|
||||||
|
@ -202,7 +205,7 @@ class Track:
|
||||||
self.lyrics = Lyrics(trackAPI_gw.get('LYRICS_ID', "0"))
|
self.lyrics = Lyrics(trackAPI_gw.get('LYRICS_ID', "0"))
|
||||||
|
|
||||||
self.mainArtist = Artist(
|
self.mainArtist = Artist(
|
||||||
id = trackAPI_gw['ART_ID'],
|
art_id = trackAPI_gw['ART_ID'],
|
||||||
name = trackAPI_gw['ART_NAME'],
|
name = trackAPI_gw['ART_NAME'],
|
||||||
pic_md5 = trackAPI_gw.get('ART_PICTURE')
|
pic_md5 = trackAPI_gw.get('ART_PICTURE')
|
||||||
)
|
)
|
||||||
|
@ -257,7 +260,6 @@ class Track:
|
||||||
self.featArtistsString = "feat. "+andCommaConcat(self.artist['Featured'])
|
self.featArtistsString = "feat. "+andCommaConcat(self.artist['Featured'])
|
||||||
|
|
||||||
def applySettings(self, settings, TEMPDIR, embeddedImageFormat):
|
def applySettings(self, settings, TEMPDIR, embeddedImageFormat):
|
||||||
from deemix.settings import FeaturesOption
|
|
||||||
|
|
||||||
# Check if should save the playlist as a compilation
|
# Check if should save the playlist as a compilation
|
||||||
if self.playlist and settings['tags']['savePlaylistAsCompilation']:
|
if self.playlist and settings['tags']['savePlaylistAsCompilation']:
|
||||||
|
@ -269,7 +271,8 @@ class Track:
|
||||||
ext = self.album.embeddedCoverURL[-4:]
|
ext = self.album.embeddedCoverURL[-4:]
|
||||||
if ext[0] != ".": ext = ".jpg" # Check for Spotify images
|
if ext[0] != ".": ext = ".jpg" # Check for Spotify images
|
||||||
|
|
||||||
self.album.embeddedCoverPath = TEMPDIR / f"pl{trackAPI_gw['_EXTRA_PLAYLIST']['id']}_{settings['embeddedArtworkSize']}{ext}"
|
# TODO: FIX
|
||||||
|
# self.album.embeddedCoverPath = TEMPDIR / f"pl{trackAPI_gw['_EXTRA_PLAYLIST']['id']}_{settings['embeddedArtworkSize']}{ext}"
|
||||||
else:
|
else:
|
||||||
if self.album.date: self.date = self.album.date
|
if self.album.date: self.date = self.album.date
|
||||||
self.album.embeddedCoverURL = self.album.pic.generatePictureURL(settings['embeddedArtworkSize'], embeddedImageFormat)
|
self.album.embeddedCoverURL = self.album.pic.generatePictureURL(settings['embeddedArtworkSize'], embeddedImageFormat)
|
||||||
|
@ -290,7 +293,7 @@ class Track:
|
||||||
self.album.artists.insert(0, artist.name)
|
self.album.artists.insert(0, artist.name)
|
||||||
|
|
||||||
if isMainArtist or artist.name not in self.album.artist['Main'] and not isMainArtist:
|
if isMainArtist or artist.name not in self.album.artist['Main'] and not isMainArtist:
|
||||||
if not artist.role in self.album.artist:
|
if artist.role not in self.album.artist:
|
||||||
self.album.artist[artist.role] = []
|
self.album.artist[artist.role] = []
|
||||||
self.album.artist[artist.role].insert(0, artist.name)
|
self.album.artist[artist.role].insert(0, artist.name)
|
||||||
self.album.mainArtist.save = not self.album.mainArtist.isVariousArtists() or settings['albumVariousArtists'] and self.album.mainArtist.isVariousArtists()
|
self.album.mainArtist.save = not self.album.mainArtist.isVariousArtists() or settings['albumVariousArtists'] and self.album.mainArtist.isVariousArtists()
|
||||||
|
@ -319,9 +322,9 @@ class Track:
|
||||||
self.mainArtist.name = changeCase(self.mainArtist.name, settings['artistCasing'])
|
self.mainArtist.name = changeCase(self.mainArtist.name, settings['artistCasing'])
|
||||||
for i, artist in enumerate(self.artists):
|
for i, artist in enumerate(self.artists):
|
||||||
self.artists[i] = changeCase(artist, settings['artistCasing'])
|
self.artists[i] = changeCase(artist, settings['artistCasing'])
|
||||||
for type in self.artist:
|
for art_type in self.artist:
|
||||||
for i, artist in enumerate(self.artist[type]):
|
for i, artist in enumerate(self.artist[art_type]):
|
||||||
self.artist[type][i] = changeCase(artist, settings['artistCasing'])
|
self.artist[art_type][i] = changeCase(artist, settings['artistCasing'])
|
||||||
self.generateMainFeatStrings()
|
self.generateMainFeatStrings()
|
||||||
|
|
||||||
# Generate artist tag
|
# Generate artist tag
|
||||||
|
@ -343,7 +346,6 @@ class Track:
|
||||||
|
|
||||||
class TrackError(Exception):
|
class TrackError(Exception):
|
||||||
"""Base class for exceptions in this module."""
|
"""Base class for exceptions in this module."""
|
||||||
pass
|
|
||||||
|
|
||||||
class AlbumDoesntExists(TrackError):
|
class AlbumDoesntExists(TrackError):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -9,29 +9,27 @@ def getBitrateNumberFromText(txt):
|
||||||
txt = str(txt).lower()
|
txt = str(txt).lower()
|
||||||
if txt in ['flac', 'lossless', '9']:
|
if txt in ['flac', 'lossless', '9']:
|
||||||
return TrackFormats.FLAC
|
return TrackFormats.FLAC
|
||||||
elif txt in ['mp3', '320', '3']:
|
if txt in ['mp3', '320', '3']:
|
||||||
return TrackFormats.MP3_320
|
return TrackFormats.MP3_320
|
||||||
elif txt in ['128', '1']:
|
if txt in ['128', '1']:
|
||||||
return TrackFormats.MP3_128
|
return TrackFormats.MP3_128
|
||||||
elif txt in ['360', '360_hq', '15']:
|
if txt in ['360', '360_hq', '15']:
|
||||||
return TrackFormats.MP4_RA3
|
return TrackFormats.MP4_RA3
|
||||||
elif txt in ['360_mq', '14']:
|
if txt in ['360_mq', '14']:
|
||||||
return TrackFormats.MP4_RA2
|
return TrackFormats.MP4_RA2
|
||||||
elif txt in ['360_lq', '13']:
|
if txt in ['360_lq', '13']:
|
||||||
return TrackFormats.MP4_RA1
|
return TrackFormats.MP4_RA1
|
||||||
else:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def changeCase(str, type):
|
def changeCase(txt, case_type):
|
||||||
if type == "lower":
|
if case_type == "lower":
|
||||||
return str.lower()
|
return txt.lower()
|
||||||
elif type == "upper":
|
if case_type == "upper":
|
||||||
return str.upper()
|
return txt.upper()
|
||||||
elif type == "start":
|
if case_type == "start":
|
||||||
return string.capwords(str)
|
return string.capwords(txt)
|
||||||
elif type == "sentence":
|
if case_type == "sentence":
|
||||||
return str.capitalize()
|
return txt.capitalize()
|
||||||
else:
|
|
||||||
return str
|
return str
|
||||||
|
|
||||||
def removeFeatures(title):
|
def removeFeatures(title):
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
if os.name == 'nt':
|
||||||
|
import winreg # pylint: disable=E0401
|
||||||
|
|
||||||
homedata = Path.home()
|
homedata = Path.home()
|
||||||
userdata = ""
|
userdata = ""
|
||||||
|
@ -23,7 +25,6 @@ if os.getenv("DEEMIX_MUSIC_DIR"):
|
||||||
elif os.getenv("XDG_MUSIC_DIR"):
|
elif os.getenv("XDG_MUSIC_DIR"):
|
||||||
musicdata = Path(os.getenv("XDG_MUSIC_DIR")) / "deemix Music"
|
musicdata = Path(os.getenv("XDG_MUSIC_DIR")) / "deemix Music"
|
||||||
elif os.name == 'nt':
|
elif os.name == 'nt':
|
||||||
import winreg
|
|
||||||
sub_key = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
|
sub_key = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
|
||||||
music_guid = '{4BD8D571-6D19-48D3-BE97-422220080E43}'
|
music_guid = '{4BD8D571-6D19-48D3-BE97-422220080E43}'
|
||||||
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, sub_key) as key:
|
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, sub_key) as key:
|
||||||
|
|
|
@ -52,16 +52,15 @@ def antiDot(string):
|
||||||
return string
|
return string
|
||||||
|
|
||||||
|
|
||||||
def pad(num, max, settings):
|
def pad(num, max_val, settings):
|
||||||
if int(settings['paddingSize']) == 0:
|
if int(settings['paddingSize']) == 0:
|
||||||
paddingSize = len(str(max))
|
paddingSize = len(str(max_val))
|
||||||
else:
|
else:
|
||||||
paddingSize = len(str(10 ** (int(settings['paddingSize']) - 1)))
|
paddingSize = len(str(10 ** (int(settings['paddingSize']) - 1)))
|
||||||
if paddingSize == 1:
|
if paddingSize == 1:
|
||||||
paddingSize = 2
|
paddingSize = 2
|
||||||
if settings['padTracks']:
|
if settings['padTracks']:
|
||||||
return str(num).zfill(paddingSize)
|
return str(num).zfill(paddingSize)
|
||||||
else:
|
|
||||||
return str(num)
|
return str(num)
|
||||||
|
|
||||||
def generateFilename(track, settings, template):
|
def generateFilename(track, settings, template):
|
||||||
|
|
Loading…
Reference in New Issue