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
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
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
type = None
id = None
link_type = 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:
type = 'track'
id = re.search("\/track\/(.+)", link).group(1)
link_type = 'track'
link_id = re.search(r"/track/(.+)", link).group(1)
elif '/playlist' in link:
type = 'playlist'
id = re.search("\/playlist\/(\d+)", link).group(1)
link_type = 'playlist'
link_id = re.search(r"/playlist/(\d+)", link).group(1)
elif '/album' in link:
type = 'album'
id = re.search("\/album\/(.+)", link).group(1)
elif re.search("\/artist\/(\d+)\/top_track", link):
type = 'artist_top'
id = re.search("\/artist\/(\d+)\/top_track", link).group(1)
elif re.search("\/artist\/(\d+)\/discography", link):
type = 'artist_discography'
id = re.search("\/artist\/(\d+)\/discography", link).group(1)
link_type = 'album'
link_id = re.search(r"/album/(.+)", link).group(1)
elif re.search(r"/artist/(\d+)/top_track", link):
link_type = 'artist_top'
link_id = re.search(r"/artist/(\d+)/top_track", link).group(1)
elif re.search(r"/artist/(\d+)/discography", link):
link_type = 'artist_discography'
link_id = re.search(r"/artist/(\d+)/discography", link).group(1)
elif '/artist' in link:
type = 'artist'
id = re.search("\/artist\/(\d+)", link).group(1)
link_type = 'artist'
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):
(link, type, id) = parseLink(link)
(link, link_type, link_id) = parseLink(link)
if type == None or id == None: return None
if type == "track":
return generateTrackItem(dz, id, bitrate)
elif type == "album":
return generateAlbumItem(dz, id, bitrate)
elif type == "playlist":
return generatePlaylistItem(dz, id, bitrate)
elif type == "artist":
return generateArtistItem(dz, id, bitrate)
elif type == "artist_discography":
return generateArtistDiscographyItem(dz, id, bitrate)
elif type == "artist_top":
return generateArtistTopItem(dz, id, bitrate)
if link_type is None or link_id is None:
return None
if link_type == "track":
return generateTrackItem(dz, link_id, bitrate)
if link_type == "album":
return generateAlbumItem(dz, link_id, bitrate)
if link_type == "playlist":
return generatePlaylistItem(dz, link_id, bitrate)
if link_type == "artist":
return generateArtistItem(dz, link_id, bitrate)
if link_type == "artist_discography":
return generateArtistDiscographyItem(dz, link_id, bitrate)
if link_type == "artist_top":
return generateArtistTopItem(dz, link_id, bitrate)
return None

View File

@ -73,4 +73,4 @@ def download(url, bitrate, portable, path):
click.echo("All done!")
if __name__ == '__main__':
download()
download() # pylint: disable=E1120

View File

@ -1,22 +1,24 @@
import binascii
from ssl import SSLError
from time import sleep
import logging
from Cryptodome.Cipher import Blowfish, AES
from Cryptodome.Hash import MD5
from deemix import USER_AGENT_HEADER
from deemix.types.DownloadObjects import Single, Collection
from requests import get
from requests.exceptions import ConnectionError, ReadTimeout
from ssl import SSLError
from requests.exceptions import ConnectionError as RequestsConnectionError, ReadTimeout
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')
def _md5(data):
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()
def generateBlowfishKey(trackId):
@ -27,36 +29,35 @@ def generateBlowfishKey(trackId):
bfKey += chr(ord(idMd5[i]) ^ ord(idMd5[i + 16]) ^ ord(SECRET[i]))
return bfKey
def generateStreamPath(sng_id, md5, media_version, format):
def generateStreamPath(sng_id, md5, media_version, media_format):
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)
step2 = str.encode(md5val) + b'\xa4' + urlPart + b'\xa4'
step2 = md5val.encode() + b'\xa4' + urlPart + b'\xa4'
step2 = step2 + (b'.' * (16 - (len(step2) % 16)))
urlPart = binascii.hexlify(AES.new(b'jo6aey6haid2Teih', AES.MODE_ECB).encrypt(step2))
return urlPart.decode("utf-8")
def reverseStreamPath(urlPart):
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')
return (sng_id.decode('utf-8'), md5.decode('utf-8'), media_version.decode('utf-8'), format.decode('utf-8'))
(_, 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'), media_format.decode('utf-8'))
def generateStreamURL(sng_id, md5, media_version, format):
urlPart = generateStreamPath(sng_id, md5, media_version, format)
def generateStreamURL(sng_id, md5, media_version, media_format):
urlPart = generateStreamPath(sng_id, md5, media_version, media_format)
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/mobile/1/" + urlPart
def generateUnencryptedStreamURL(sng_id, md5, media_version, format):
urlPart = generateStreamPath(sng_id, md5, media_version, format)
def generateUnencryptedStreamURL(sng_id, md5, media_version, media_format):
urlPart = generateStreamPath(sng_id, md5, media_version, media_format)
return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/api/1/" + urlPart
def reverseStreamURL(url):
urlPart = url[url.find("/1/")+3:]
return generateStreamPath(urlPart)
return reverseStreamPath(urlPart)
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}]"
@ -68,9 +69,9 @@ def streamUnencryptedTrack(outputStream, track, start=0, downloadObject=None, in
if complete == 0: raise DownloadEmpty
if start != 0:
responseRange = request.headers["Content-Range"]
logger.info(f'{itemName} downloading range {responseRange}')
logger.info('%s downloading range %s', itemName, responseRange)
else:
logger.info(f'{itemName} downloading {complete} bytes')
logger.info('%s downloading %s bytes', itemName, complete)
for chunk in request.iter_content(2048 * 3):
outputStream.write(chunk)
@ -85,51 +86,12 @@ def streamUnencryptedTrack(outputStream, track, start=0, downloadObject=None, in
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):
except (SSLError, u3SSLError):
logger.info('%s retrying from byte %s', itemName, chunkLength)
streamUnencryptedTrack(outputStream, track, chunkLength, downloadObject, interface)
except (RequestsConnectionError, ReadTimeout):
sleep(2)
return 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)
streamUnencryptedTrack(outputStream, track, start, downloadObject, interface)
def streamTrack(outputStream, track, start=0, downloadObject=None, interface=None):
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 start != 0:
responseRange = request.headers["Content-Range"]
logger.info(f'{itemName} downloading range {responseRange}')
logger.info('%s downloading range %s', itemName, responseRange)
else:
logger.info(f'{itemName} downloading {complete} bytes')
logger.info('%s downloading %s bytes', itemName, complete)
for chunk in request.iter_content(2048 * 3):
if len(chunk) >= 2048:
@ -167,12 +129,12 @@ def streamTrack(outputStream, track, start=0, downloadObject=None, interface=Non
downloadObject.progressNext += chunkProgres
downloadObject.updateProgress(interface)
except (SSLError, u3SSLError) as e:
logger.info(f'{itemName} retrying from byte {chunkLength}')
return streamTrack(outputStream, track, chunkLength, downloadObject, interface)
except (ConnectionError, ReadTimeout):
except (SSLError, u3SSLError):
logger.info('%s retrying from byte %s', itemName, chunkLength)
streamTrack(outputStream, track, chunkLength, downloadObject, interface)
except (RequestsConnectionError, ReadTimeout):
sleep(2)
return streamTrack(outputStream, track, start, downloadObject, interface)
streamTrack(outputStream, track, start, downloadObject, interface)
class DownloadEmpty(Exception):
pass

View File

@ -1,35 +1,33 @@
import requests
from requests import get
from concurrent.futures import ThreadPoolExecutor
from time import sleep
from os.path import sep as pathSep
from os import makedirs, system as execute
from pathlib import Path
from shlex import quote
import re
import errno
from ssl import SSLError
from urllib3.exceptions import SSLError as u3SSLError
from os import makedirs
import logging
from tempfile import gettempdir
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.Track import Track, AlbumDoesntExists
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.decryption import generateUnencryptedStreamURL, streamUnencryptedTrack
from deemix.settings import OverwriteOption
from mutagen.flac import FLACNoHeaderError, error as FLACError
import logging
logger = logging.getLogger('deemix')
from tempfile import gettempdir
TEMPDIR = Path(gettempdir()) / 'deemix-imgs'
if not TEMPDIR.is_dir(): makedirs(TEMPDIR)
@ -71,23 +69,22 @@ def downloadImage(url, path, overwrite=OverwriteOption.DONT_OVERWRITE):
pictureUrl = url[len(urlBase):]
pictureSize = int(pictureUrl[:pictureUrl.find("x")])
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)
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:
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)
return downloadImage(url, path, overwrite)
except OSError as e:
if e.errno == errno.ENOSPC: raise DownloadFailed("noSpaceLeft")
else: logger.exception(f"Error while downloading an image, you should report this to the developers: {str(e)}")
if e.errno == errno.ENOSPC: raise DownloadFailed("noSpaceLeft") from e
logger.exception("Error while downloading an image, you should report this to the developers: %s", 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()
return None
else:
return path
return path
def getPreferredBitrate(track, preferredBitrate, shouldFallback, downloadObjectUUID=None, interface=None):
if track.localTrack: return TrackFormats.LOCAL
@ -116,36 +113,36 @@ def getPreferredBitrate(track, preferredBitrate, shouldFallback, downloadObjectU
formats = formats_non_360
for formatNumber, formatName in formats.items():
if formatNumber <= int(preferredBitrate):
if f"FILESIZE_{formatName}" in track.filesizes:
if int(track.filesizes[f"FILESIZE_{formatName}"]) != 0: return formatNumber
if not track.filesizes[f"FILESIZE_{formatName}_TESTED"]:
request = requests.head(
generateUnencryptedStreamURL(track.id, track.MD5, track.mediaVersion, formatNumber),
headers={'User-Agent': USER_AGENT_HEADER},
timeout=30
)
try:
request.raise_for_status()
return formatNumber
except requests.exceptions.HTTPError: # if the format is not available, Deezer returns a 403 error
pass
if not shouldFallback:
raise PreferredBitrateNotFound
else:
if not falledBack:
falledBack = True
logger.info(f"[{track.mainArtist.name} - {track.title}] Fallback to lower bitrate")
if interface and downloadObjectUUID:
interface.send('queueUpdate', {
'uuid': downloadObjectUUID,
'bitrateFallback': True,
'data': {
'id': track.id,
'title': track.title,
'artist': track.mainArtist.name
},
})
if formatNumber >= int(preferredBitrate): continue
if f"FILESIZE_{formatName}" in track.filesizes:
if int(track.filesizes[f"FILESIZE_{formatName}"]) != 0: return formatNumber
if not track.filesizes[f"FILESIZE_{formatName}_TESTED"]:
request = requests.head(
generateUnencryptedStreamURL(track.id, track.MD5, track.mediaVersion, formatNumber),
headers={'User-Agent': USER_AGENT_HEADER},
timeout=30
)
try:
request.raise_for_status()
return formatNumber
except requests.exceptions.HTTPError: # if the format is not available, Deezer returns a 403 error
pass
if not shouldFallback:
raise PreferredBitrateNotFound
if not falledBack:
falledBack = True
logger.info("%s Fallback to lower bitrate", f"[{track.mainArtist.name} - {track.title}]")
if interface and downloadObjectUUID:
interface.send('queueUpdate', {
'uuid': downloadObjectUUID,
'bitrateFallback': True,
'data': {
'id': track.id,
'title': track.title,
'artist': track.mainArtist.name
},
})
if is360format: raise TrackNot360
return TrackFormats.DEFAULT
@ -178,9 +175,11 @@ class Downloader:
result = {}
if trackAPI_gw['SNG_ID'] == "0": raise DownloadFailed("notOnDeezer")
itemName = f"[{trackAPI_gw['ART_NAME']} - {trackAPI_gw['SNG_TITLE']}]"
# Create Track object
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:
track = Track().parseData(
dz=self.dz,
@ -189,8 +188,10 @@ class Downloader:
albumAPI=albumAPI,
playlistAPI=playlistAPI
)
except AlbumDoesntExists:
raise DownloadError('albumDoesntExists')
except AlbumDoesntExists as e:
raise DownloadError('albumDoesntExists') from e
itemName = f"[{track.mainArtist.name} - {track.title}]"
# Check if track not yet encoded
if track.MD5 == '': raise DownloadFailed("notEncoded", track)
@ -203,16 +204,16 @@ class Downloader:
self.settings['fallbackBitrate'],
self.downloadObject.uuid, self.interface
)
except PreferredBitrateNotFound:
raise DownloadFailed("wrongBitrate", track)
except TrackNot360:
raise DownloadFailed("no360RA")
except PreferredBitrateNotFound as e:
raise DownloadFailed("wrongBitrate", track) from e
except TrackNot360 as e:
raise DownloadFailed("no360RA") from e
track.selectedFormat = selectedFormat
track.album.bitrate = selectedFormat
# Generate covers URLs
embeddedImageFormat = f'jpg-{self.settings["jpegImageQuality"]}'
if self.settings['embeddedArtworkPNG']: imageFormat = 'png'
if self.settings['embeddedArtworkPNG']: embeddedImageFormat = 'png'
track.applySettings(self.settings, TEMPDIR, embeddedImageFormat)
@ -233,49 +234,49 @@ class Downloader:
result['filename'] = str(writepath)[len(str(extrasPath))+ len(pathSep):]
# 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)
# Save local album art
if coverPath:
result['albumURLs'] = []
for format in self.settings['localArtworkFormat'].split(","):
if format in ["png","jpg"]:
extendedFormat = format
for pic_format in self.settings['localArtworkFormat'].split(","):
if pic_format in ["png","jpg"]:
extendedFormat = pic_format
if extendedFormat == "jpg": extendedFormat += f"-{self.settings['jpegImageQuality']}"
url = track.album.pic.generatePictureURL(self.settings['localArtworkSize'], extendedFormat)
if self.settings['tags']['savePlaylistAsCompilation'] \
and track.playlist \
and track.playlist.pic.staticUrl \
and not format.startswith("jpg"):
continue
result['albumURLs'].append({'url': url, 'ext': format})
and not pic_format.startswith("jpg"):
continue
result['albumURLs'].append({'url': url, 'ext': pic_format})
result['albumPath'] = coverPath
result['albumFilename'] = f"{settingsRegexAlbum(self.settings['coverImageTemplate'], track.album, self.settings, track.playlist)}"
# Save artist art
if artistPath:
result['artistURLs'] = []
for format in self.settings['localArtworkFormat'].split(","):
if format in ["png","jpg"]:
extendedFormat = format
for pic_format in self.settings['localArtworkFormat'].split(","):
if pic_format in ["png","jpg"]:
extendedFormat = pic_format
if extendedFormat == "jpg": extendedFormat += f"-{self.settings['jpegImageQuality']}"
url = track.album.mainArtist.pic.generatePictureURL(self.settings['localArtworkSize'], extendedFormat)
if track.album.mainArtist.pic.md5 == "" and not format.startswith("jpg"): continue
result['artistURLs'].append({'url': url, 'ext': format})
if track.album.mainArtist.pic.md5 == "" and not pic_format.startswith("jpg"): continue
result['artistURLs'].append({'url': url, 'ext': pic_format})
result['artistPath'] = artistPath
result['artistFilename'] = f"{settingsRegexArtist(self.settings['artistImageTemplate'], track.album.mainArtist, self.settings, rootArtist=track.album.rootArtist)}"
# Save playlist art
if track.playlist:
if not len(self.playlistURLs):
for format in self.settings['localArtworkFormat'].split(","):
if format in ["png","jpg"]:
extendedFormat = format
if self.playlistURLs == []:
for pic_format in self.settings['localArtworkFormat'].split(","):
if pic_format in ["png","jpg"]:
extendedFormat = pic_format
if extendedFormat == "jpg": extendedFormat += f"-{self.settings['jpegImageQuality']}"
url = track.playlist.pic.generatePictureURL(self.settings['localArtworkSize'], extendedFormat)
if track.playlist.pic.staticUrl and not format.startswith("jpg"): continue
self.playlistURLs.append({'url': url, 'ext': format})
if track.playlist.pic.staticUrl and not pic_format.startswith("jpg"): continue
self.playlistURLs.append({'url': url, 'ext': pic_format})
if not self.playlistCoverName:
track.playlist.bitrate = selectedFormat
track.playlist.dateString = track.playlist.date.format(self.settings['dateFormat'])
@ -309,26 +310,26 @@ class Downloader:
writepath = Path(currentFilename)
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)
def downloadMusic(track, trackAPI_gw):
try:
with open(writepath, 'wb') as stream:
streamUnencryptedTrack(stream, track, downloadObject=self.downloadObject, interface=self.interface)
except DownloadCancelled:
except DownloadCancelled as e:
if writepath.is_file(): writepath.unlink()
raise DownloadCancelled
except (requests.exceptions.HTTPError, DownloadEmpty):
raise e
except (requests.exceptions.HTTPError, DownloadEmpty) as e:
if writepath.is_file(): writepath.unlink()
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)
track.parseEssentialData(newTrack)
track.retriveFilesizes(self.dz)
return False
elif not track.searched and self.settings['fallbackSearch']:
logger.warn(f"[{track.mainArtist.name} - {track.title}] Track not available, searching for alternative")
if not track.searched and self.settings['fallbackSearch']:
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)
if searchedId != "0":
newTrack = self.dz.gw.get_track_with_fallback(searchedId)
@ -346,25 +347,21 @@ class Downloader:
},
})
return False
else:
raise DownloadFailed("notAvailableNoAlternative")
else:
raise DownloadFailed("notAvailable")
raise DownloadFailed("notAvailableNoAlternative") from e
raise DownloadFailed("notAvailable") from e
except (requests.exceptions.ConnectionError, requests.exceptions.ChunkedEncodingError) as e:
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)
return downloadMusic(track, trackAPI_gw)
except OSError as e:
if e.errno == errno.ENOSPC:
raise DownloadFailed("noSpaceLeft")
else:
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)}")
raise e
if writepath.is_file(): writepath.unlink()
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
except Exception as e:
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
return True
@ -375,12 +372,12 @@ class Downloader:
if not trackDownloaded: return self.download(trackAPI_gw, track=track)
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)
# Adding tags
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]:
tagID3(writepath, track, self.settings['tags'])
elif track.selectedFormat == TrackFormats.FLAC:
@ -388,14 +385,14 @@ class Downloader:
tagFLAC(writepath, track, self.settings['tags'])
except (FLACNoHeaderError, FLACError):
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)
track.filesizes['FILESIZE_FLAC'] = "0"
track.filesizes['FILESIZE_FLAC_TESTED'] = True
return self.download(trackAPI_gw, track=track)
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.files.append(str(writepath))
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']:
tempTrack['title'] += f" {trackAPI_gw['VERSION']}".strip()
itemName = f"[{track.mainArtist.name} - {track.title}]"
try:
result = self.download(trackAPI_gw, trackAPI, albumAPI, playlistAPI, track)
except DownloadFailed as error:
if error.track:
track = error.track
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)
track.parseEssentialData(newTrack)
track.retriveFilesizes(self.dz)
return self.downloadWrapper(trackAPI_gw, trackAPI, albumAPI, playlistAPI, track)
elif not track.searched and self.settings['fallbackSearch']:
logger.warn(f"[{track.mainArtist.name} - {track.title}] {error.message} Searching for alternative")
if not track.searched and self.settings['fallbackSearch']:
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)
if searchedId != "0":
newTrack = self.dz.gw.get_track_with_fallback(searchedId)
@ -434,7 +433,7 @@ class Downloader:
track.searched = True
if self.interface:
self.interface.send('queueUpdate', {
'uuid': self.queueItem.uuid,
'uuid': self.downloadObject.uuid,
'searchFallback': True,
'data': {
'id': track.id,
@ -443,17 +442,16 @@ class Downloader:
},
})
return self.downloadWrapper(trackAPI_gw, trackAPI, albumAPI, playlistAPI, track)
else:
error.errid += "NoAlternative"
error.message = errorMessages[error.errid]
logger.error(f"[{tempTrack['artist']} - {tempTrack['title']}] {error.message}")
error.errid += "NoAlternative"
error.message = errorMessages[error.errid]
logger.error("%s %s", itemName, error.message)
result = {'error': {
'message': error.message,
'errid': error.errid,
'data': tempTrack
}}
'message': error.message,
'errid': error.errid,
'data': tempTrack
}}
except Exception as e:
logger.exception(f"[{tempTrack['artist']} - {tempTrack['title']}] {str(e)}")
logger.exception("%s %s", itemName, e)
result = {'error': {
'message': str(e),
'data': tempTrack
@ -505,9 +503,9 @@ class Downloader:
errors = ""
searched = ""
for i in range(len(tracks)):
for i in enumerate(tracks):
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
if result.get('error'):
@ -558,10 +556,10 @@ class Downloader:
class DownloadError(Exception):
"""Base class for exceptions in this module."""
pass
class DownloadFailed(DownloadError):
def __init__(self, errid, track=None):
super().__init__()
self.errid = errid
self.message = errorMessages[self.errid]
self.track = track
@ -569,6 +567,9 @@ class DownloadFailed(DownloadError):
class DownloadCancelled(DownloadError):
pass
class DownloadEmpty(DownloadError):
pass
class PreferredBitrateNotFound(DownloadError):
pass

View File

@ -1,9 +1,15 @@
import logging
from deemix.types.DownloadObjects import Single, Collection
from deezer.utils import map_user_playlist
from deezer.api import APIError
from deezer.gw import GWAPIError, LyricsStatus
logger = logging.getLogger('deemix')
class GenerationError(Exception):
def __init__(self, link, message, errid=None):
super().__init__()
self.link = link
self.message = message
self.errid = errid
@ -15,27 +21,26 @@ class GenerationError(Exception):
'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
if str(id).startswith("isrc"):
if str(link_id).startswith("isrc"):
try:
trackAPI = dz.api.get_track(id)
trackAPI = dz.api.get_track(link_id)
except APIError as e:
e = str(e)
raise GenerationError("https://deezer.com/track/"+str(id), f"Wrong URL: {e}")
raise GenerationError("https://deezer.com/track/"+str(link_id), f"Wrong URL: {e}") from e
if 'id' in trackAPI and 'title' in trackAPI:
id = trackAPI['id']
link_id = trackAPI['id']
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
try:
trackAPI_gw = dz.gw.get_track_with_fallback(id)
trackAPI_gw = dz.gw.get_track_with_fallback(link_id)
except GWAPIError as e:
e = str(e)
message = "Wrong URL"
if "DATA_ERROR" in e: message += f": {e['DATA_ERROR']}"
raise GenerationError("https://deezer.com/track/"+str(id), message)
# TODO: FIX
# 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()
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({
'type': 'track',
'id': id,
'id': link_id,
'bitrate': bitrate,
'title': title,
'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
try:
albumAPI = dz.api.get_album(id)
albumAPI = dz.api.get_album(link_id)
except APIError as e:
e = str(e)
raise GenerationError("https://deezer.com/album/"+str(id), f"Wrong URL: {e}")
raise GenerationError("https://deezer.com/album/"+str(link_id), f"Wrong URL: {e}") from e
if str(id).startswith('upc'): id = albumAPI['id']
if str(link_id).startswith('upc'): link_id = albumAPI['id']
# Get extra info about album
# 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['copyright'] = albumAPI_gw['COPYRIGHT']
albumAPI['root_artist'] = rootArtist
@ -78,9 +82,9 @@ def generateAlbumItem(dz, id, bitrate, rootArtist=None):
if albumAPI['nb_tracks'] == 1:
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'
else:
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({
'type': 'album',
'id': id,
'id': link_id,
'bitrate': bitrate,
'title': albumAPI['title'],
'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:
# Get essential playlist info
try:
playlistAPI = dz.api.get_playlist(id)
except:
playlistAPI = dz.api.get_playlist(link_id)
except APIError:
playlistAPI = None
# Fallback to gw api if the playlist is private
if not playlistAPI:
try:
userPlaylist = dz.gw.get_playlist_page(id)
userPlaylist = dz.gw.get_playlist_page(link_id)
playlistAPI = map_user_playlist(userPlaylist['DATA'])
except GWAPIError as e:
e = str(e)
message = "Wrong URL"
if "DATA_ERROR" in e:
message += f": {e['DATA_ERROR']}"
raise GenerationError("https://deezer.com/playlist/"+str(id), message)
# TODO: FIX
# if "DATA_ERROR" in e: message += f": {e['DATA_ERROR']}"
raise GenerationError("https://deezer.com/playlist/"+str(link_id), message) from e
# Check if private playlist and owner
if not playlistAPI.get('public', False) and playlistAPI['creator']['id'] != str(dz.current_user['id']):
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:
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
totalSize = len(playlistTracksAPI)
@ -148,11 +151,11 @@ def generatePlaylistItem(dz, id, bitrate, playlistAPI=None, playlistTracksAPI=No
trackAPI['SIZE'] = totalSize
collection.append(trackAPI)
if not 'explicit' in playlistAPI: playlistAPI['explicit'] = False
if 'explicit' not in playlistAPI: playlistAPI['explicit'] = False
return Collection({
'type': 'playlist',
'id': id,
'id': link_id,
'bitrate': bitrate,
'title': playlistAPI['title'],
'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
try:
artistAPI = dz.api.get_artist(id)
artistAPI = dz.api.get_artist(link_id)
except APIError as e:
e = str(e)
raise GenerationError("https://deezer.com/artist/"+str(id), f"Wrong URL: {e}")
raise GenerationError("https://deezer.com/artist/"+str(link_id), f"Wrong URL: {e}") from e
if interface: interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
rootArtist = {
'id': artistAPI['id'],
'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', [])
albumList = []
for album in allReleases:
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
def generateArtistDiscographyItem(dz, id, bitrate, interface=None):
def generateArtistDiscographyItem(dz, link_id, bitrate, interface=None):
# Get essential artist info
try:
artistAPI = dz.api.get_artist(id)
artistAPI = dz.api.get_artist(link_id)
except APIError as 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 = {
'id': artistAPI['id'],
'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
albumList = []
for type in artistDiscographyAPI:
for album in artistDiscographyAPI[type]:
for releaseType in artistDiscographyAPI:
for album in artistDiscographyAPI[releaseType]:
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
def generateArtistTopItem(dz, id, bitrate, interface=None):
def generateArtistTopItem(dz, link_id, bitrate, interface=None):
# Get essential artist info
try:
artistAPI = dz.api.get_artist(id)
artistAPI = dz.api.get_artist(link_id)
except APIError as 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
# 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"
}
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)

View File

@ -4,16 +4,16 @@ from os import makedirs
from deezer import TrackFormats
import deemix.utils.localpaths as localpaths
"""Should the lib overwrite files?"""
class OverwriteOption():
"""Should the lib overwrite files?"""
OVERWRITE = 'y' # Yes, overwrite the file
DONT_OVERWRITE = 'n' # No, don't overwrite the file
DONT_CHECK_EXT = 'e' # No, and don't check for extensions
KEEP_BOTH = 'b' # No, and keep both files
ONLY_TAGS = 't' # Overwrite only the tags
"""What should I do with featured artists?"""
class FeaturesOption():
"""What should I do with featured artists?"""
NO_CHANGE = "0" # Do nothing
REMOVE_TITLE = "1" # Remove from track title
REMOVE_TITLE_ALBUM = "3" # Remove from track title and album title
@ -121,13 +121,13 @@ def loadSettings(configFolder=None):
def checkSettings(settings):
changes = 0
for set in DEFAULTS:
if not set in settings or type(settings[set]) != type(DEFAULTS[set]):
settings[set] = DEFAULTS[set]
for i_set in DEFAULTS:
if not i_set in settings or not isinstance(settings[i_set], DEFAULTS[i_set]):
settings[i_set] = DEFAULTS[i_set]
changes += 1
for set in DEFAULTS['tags']:
if not set in settings['tags'] or type(settings['tags'][set]) != type(DEFAULTS['tags'][set]):
settings['tags'][set] = DEFAULTS['tags'][set]
for i_set in DEFAULTS['tags']:
if not i_set in settings['tags'] or not isinstance(settings['tags'][i_set], DEFAULTS['tags'][i_set]):
settings['tags'][i_set] = DEFAULTS['tags'][i_set]
changes += 1
if settings['downloadLocation'] == "":
settings['downloadLocation'] = DEFAULTS['downloadLocation']

View File

@ -7,8 +7,8 @@ from deemix.types.Picture import Picture
from deemix.types import VARIOUS_ARTISTS
class Album:
def __init__(self, id="0", title="", pic_md5=""):
self.id = id
def __init__(self, alb_id="0", title="", pic_md5=""):
self.id = alb_id
self.title = title
self.pic = Picture(md5=pic_md5, type="cover")
self.artist = {"Main": []}
@ -24,11 +24,15 @@ class Album:
self.genre = []
self.barcode = "Unknown"
self.label = "Unknown"
self.copyright = None
self.recordType = "album"
self.bitrate = 0
self.rootArtist = None
self.variousArtists = None
self.playlistId = None
self.owner = None
def parseAlbum(self, albumAPI):
self.title = albumAPI['title']
@ -80,7 +84,7 @@ class Album:
day = albumAPI["release_date"][8:10]
month = albumAPI["release_date"][5:7]
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.copyright = albumAPI.get('copyright')
@ -115,7 +119,7 @@ class Album:
day = albumAPI_gw["PHYSICAL_RELEASE_DATE"][8:10]
month = albumAPI_gw["PHYSICAL_RELEASE_DATE"][5:7]
year = albumAPI_gw["PHYSICAL_RELEASE_DATE"][0:4]
self.date = Date(year, month, day)
self.date = Date(day, month, year)
def makePlaylistCompilation(self, playlist):
self.variousArtists = playlist.variousArtists
@ -136,8 +140,9 @@ class Album:
self.pic = playlist.pic
def removeDuplicateArtists(self):
"""Removes duplicate artists for both artist array and artists dict"""
(self.artist, self.artists) = removeDuplicateArtists(self.artist, self.artists)
# Removes featuring from the album name
def getCleanTitle(self):
"""Removes featuring from the album name"""
return removeFeatures(self.title)

View File

@ -2,8 +2,8 @@ from deemix.types.Picture import Picture
from deemix.types import VARIOUS_ARTISTS
class Artist:
def __init__(self, id="0", name="", role="", pic_md5=""):
self.id = str(id)
def __init__(self, art_id="0", name="", role="", pic_md5=""):
self.id = str(art_id)
self.name = name
self.pic = Picture(md5=pic_md5, type="artist")
self.role = role

View File

@ -1,4 +1,4 @@
class Date(object):
class Date:
def __init__(self, day="00", month="00", year="XXXX"):
self.year = year
self.month = month

View File

@ -1,4 +1,5 @@
class IDownloadObject:
"""DownloadObject interface"""
def __init__(self, obj):
self.type = obj['type']
self.id = obj['id']
@ -50,9 +51,9 @@ class IDownloadObject:
def getSlimmedDict(self):
light = self.toDict()
propertiesToDelete = ['single', 'collection', 'convertable']
for property in propertiesToDelete:
if property in light:
del light[property]
for prop in propertiesToDelete:
if prop in light:
del light[prop]
return light
def updateProgress(self, interface=None):

View File

@ -1,6 +1,6 @@
class Lyrics:
def __init__(self, id="0"):
self.id = id
def __init__(self, lyr_id="0"):
self.id = lyr_id
self.sync = ""
self.unsync = ""
self.syncID3 = []
@ -11,7 +11,7 @@ class Lyrics:
syncLyricsJson = lyricsAPI["LYRICS_SYNC_JSON"]
timestamp = ""
milliseconds = 0
for line in range(len(syncLyricsJson)):
for line in enumerate(syncLyricsJson):
if syncLyricsJson[line]["line"] != "":
timestamp = syncLyricsJson[line]["lrc_timestamp"]
milliseconds = int(syncLyricsJson[line]["milliseconds"])

View File

@ -1,25 +1,25 @@
class Picture:
def __init__(self, md5="", type="", url=None):
def __init__(self, md5="", pic_type="", url=None):
self.md5 = md5
self.type = type
self.type = pic_type
self.staticUrl = url
def generatePictureURL(self, size, format):
def generatePictureURL(self, size, pic_format):
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.md5,
size, size
size=size
)
if format.startswith("jpg"):
if pic_format.startswith("jpg"):
quality = 80
if '-' in format:
quality = format[4:]
format = 'jpg'
if '-' in pic_format:
quality = pic_format[4:]
pic_format = '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+'.jpg'

View File

@ -32,7 +32,7 @@ class Playlist:
md5 = url[url.find(picType+'/') + len(picType)+1:-24]
self.pic = Picture(
md5 = md5,
type = picType
pic_type = picType
)
else:
self.pic = Picture(url = playlistAPI['picture_xl'])
@ -41,7 +41,7 @@ class Playlist:
pic_md5 = playlistAPI['various_artist']['picture_small']
pic_md5 = pic_md5[pic_md5.find('artist/') + 7:-24]
self.variousArtists = Artist(
id = playlistAPI['various_artist']['id'],
art_id = playlistAPI['various_artist']['id'],
name = playlistAPI['various_artist']['name'],
role = "Main",
pic_md5 = pic_md5

View File

@ -1,5 +1,6 @@
import requests
from time import sleep
import re
import requests
from deezer.gw import GWAPIError
from deezer.api import APIError
@ -14,9 +15,11 @@ from deemix.types.Playlist import Playlist
from deemix.types.Lyrics import Lyrics
from deemix.types import VARIOUS_ARTISTS
from deemix.settings import FeaturesOption
class Track:
def __init__(self, id="0", name=""):
self.id = id
def __init__(self, sng_id="0", name=""):
self.id = sng_id
self.title = name
self.MD5 = ""
self.mediaVersion = ""
@ -82,9 +85,9 @@ class Track:
result_json = site.json()
except:
sleep(2)
return self.retriveFilesizes(dz)
self.retriveFilesizes(dz)
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")
filesizes = {}
for key, value in response.items():
@ -116,7 +119,7 @@ class Track:
# Parse Album Data
self.album = Album(
id = trackAPI_gw['ALB_ID'],
alb_id = trackAPI_gw['ALB_ID'],
title = trackAPI_gw['ALB_TITLE'],
pic_md5 = trackAPI_gw.get('ALB_PICTURE')
)
@ -157,7 +160,7 @@ class Track:
if not len(self.artist['Main']):
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')
# 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.pic = Picture(
md5 = trackAPI_gw.get('ALB_PICTURE', ""),
type = "cover"
pic_type = "cover"
)
self.mainArtist = Artist(name=trackAPI_gw['ART_NAME'])
self.artists = [trackAPI_gw['ART_NAME']]
@ -188,7 +191,7 @@ class Track:
def parseTrackGW(self, trackAPI_gw):
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.discNumber = trackAPI_gw.get('DISK_NUMBER')
@ -202,7 +205,7 @@ class Track:
self.lyrics = Lyrics(trackAPI_gw.get('LYRICS_ID', "0"))
self.mainArtist = Artist(
id = trackAPI_gw['ART_ID'],
art_id = trackAPI_gw['ART_ID'],
name = trackAPI_gw['ART_NAME'],
pic_md5 = trackAPI_gw.get('ART_PICTURE')
)
@ -257,7 +260,6 @@ class Track:
self.featArtistsString = "feat. "+andCommaConcat(self.artist['Featured'])
def applySettings(self, settings, TEMPDIR, embeddedImageFormat):
from deemix.settings import FeaturesOption
# Check if should save the playlist as a compilation
if self.playlist and settings['tags']['savePlaylistAsCompilation']:
@ -269,7 +271,8 @@ class Track:
ext = self.album.embeddedCoverURL[-4:]
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:
if self.album.date: self.date = self.album.date
self.album.embeddedCoverURL = self.album.pic.generatePictureURL(settings['embeddedArtworkSize'], embeddedImageFormat)
@ -290,7 +293,7 @@ class Track:
self.album.artists.insert(0, artist.name)
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].insert(0, artist.name)
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'])
for i, artist in enumerate(self.artists):
self.artists[i] = changeCase(artist, settings['artistCasing'])
for type in self.artist:
for i, artist in enumerate(self.artist[type]):
self.artist[type][i] = changeCase(artist, settings['artistCasing'])
for art_type in self.artist:
for i, artist in enumerate(self.artist[art_type]):
self.artist[art_type][i] = changeCase(artist, settings['artistCasing'])
self.generateMainFeatStrings()
# Generate artist tag
@ -343,7 +346,6 @@ class Track:
class TrackError(Exception):
"""Base class for exceptions in this module."""
pass
class AlbumDoesntExists(TrackError):
pass

View File

@ -9,30 +9,28 @@ def getBitrateNumberFromText(txt):
txt = str(txt).lower()
if txt in ['flac', 'lossless', '9']:
return TrackFormats.FLAC
elif txt in ['mp3', '320', '3']:
if txt in ['mp3', '320', '3']:
return TrackFormats.MP3_320
elif txt in ['128', '1']:
if txt in ['128', '1']:
return TrackFormats.MP3_128
elif txt in ['360', '360_hq', '15']:
if txt in ['360', '360_hq', '15']:
return TrackFormats.MP4_RA3
elif txt in ['360_mq', '14']:
if txt in ['360_mq', '14']:
return TrackFormats.MP4_RA2
elif txt in ['360_lq', '13']:
if txt in ['360_lq', '13']:
return TrackFormats.MP4_RA1
else:
return None
return None
def changeCase(str, type):
if type == "lower":
return str.lower()
elif type == "upper":
return str.upper()
elif type == "start":
return string.capwords(str)
elif type == "sentence":
return str.capitalize()
else:
return str
def changeCase(txt, case_type):
if case_type == "lower":
return txt.lower()
if case_type == "upper":
return txt.upper()
if case_type == "start":
return string.capwords(txt)
if case_type == "sentence":
return txt.capitalize()
return str
def removeFeatures(title):
clean = title

View File

@ -1,6 +1,8 @@
from pathlib import Path
import sys
import os
if os.name == 'nt':
import winreg # pylint: disable=E0401
homedata = Path.home()
userdata = ""
@ -23,7 +25,6 @@ if os.getenv("DEEMIX_MUSIC_DIR"):
elif os.getenv("XDG_MUSIC_DIR"):
musicdata = Path(os.getenv("XDG_MUSIC_DIR")) / "deemix Music"
elif os.name == 'nt':
import winreg
sub_key = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders'
music_guid = '{4BD8D571-6D19-48D3-BE97-422220080E43}'
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, sub_key) as key:

View File

@ -52,17 +52,16 @@ def antiDot(string):
return string
def pad(num, max, settings):
def pad(num, max_val, settings):
if int(settings['paddingSize']) == 0:
paddingSize = len(str(max))
paddingSize = len(str(max_val))
else:
paddingSize = len(str(10 ** (int(settings['paddingSize']) - 1)))
if paddingSize == 1:
paddingSize = 2
if settings['padTracks']:
return str(num).zfill(paddingSize)
else:
return str(num)
return str(num)
def generateFilename(track, settings, template):
filename = template or "%artist% - %title%"