Code cleanup with pylint

This commit is contained in:
RemixDev 2021-04-10 11:53:52 +02:00
parent eda8fd3d13
commit 69c165e2bc
No known key found for this signature in database
GPG Key ID: B33962B465BDB51C
18 changed files with 323 additions and 350 deletions

2
.pylintrc Normal file
View File

@ -0,0 +1,2 @@
[MESSAGES CONTROL]
disable=C0301,C0103,R0902,R0903,C0321,R0911,R0912,R0913,R0914,R0915,R0916

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,23 +69,22 @@ 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):
if track.localTrack: return TrackFormats.LOCAL if track.localTrack: return TrackFormats.LOCAL
@ -116,36 +113,36 @@ 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"]:
request = requests.head( request = requests.head(
generateUnencryptedStreamURL(track.id, track.MD5, track.mediaVersion, formatNumber), generateUnencryptedStreamURL(track.id, track.MD5, track.mediaVersion, formatNumber),
headers={'User-Agent': USER_AGENT_HEADER}, headers={'User-Agent': USER_AGENT_HEADER},
timeout=30 timeout=30
) )
try: try:
request.raise_for_status() request.raise_for_status()
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:
raise PreferredBitrateNotFound if not shouldFallback:
else: raise PreferredBitrateNotFound
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,
'bitrateFallback': True, 'bitrateFallback': True,
'data': { 'data': {
'id': track.id, 'id': track.id,
'title': track.title, 'title': track.title,
'artist': track.mainArtist.name 'artist': track.mainArtist.name
}, },
}) })
if is360format: raise TrackNot360 if is360format: raise TrackNot360
return TrackFormats.DEFAULT return TrackFormats.DEFAULT
@ -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: if writepath.is_file(): writepath.unlink()
raise DownloadFailed("noSpaceLeft") if e.errno == errno.ENOSPC: raise DownloadFailed("noSpaceLeft") from e
else: logger.exception("%s Error while downloading the track, you should report this to the developers: %s", itemName, e)
if writepath.is_file(): writepath.unlink() raise e
logger.exception(f"[{track.mainArtist.name} - {track.title}] Error while downloading the track, you should report this to the developers: {str(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("%s %s", itemName, error.message)
logger.error(f"[{tempTrack['artist']} - {tempTrack['title']}] {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

View File

@ -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)

View File

@ -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']

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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"])

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -9,30 +9,28 @@ 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):
clean = title clean = title

View File

@ -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:

View File

@ -52,17 +52,16 @@ 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):
filename = template or "%artist% - %title%" filename = template or "%artist% - %title%"