diff --git a/deemix/__main__.py b/deemix/__main__.py index df63226..ed2533b 100644 --- a/deemix/__main__.py +++ b/deemix/__main__.py @@ -1,36 +1,25 @@ #!/usr/bin/env python3 import click -import deemix.app.cli as app -from deemix.app.settings import initSettings +from deemix.app.cli import cli from os.path import isfile -import random -import string - -def randomString(stringLength=8): - letters = string.ascii_lowercase - return ''.join(random.choice(letters) for i in range(stringLength)) - @click.command() @click.option('-b', '--bitrate', default=None, help='Overwrites the default bitrate selected') @click.option('-l', '--local', is_flag=True, help='Downloads in a local folder insted of using the default') @click.argument('url', nargs=-1, required=True) def download(bitrate, local, url): - settings = initSettings() - if local: - settings['downloadLocation'] = randomString(12) - click.echo("Using a local download folder: "+settings['downloadLocation']) + app = cli(local) app.login() url = list(url) if isfile(url[0]): filename = url[0] with open(filename) as f: url = f.readlines() - app.downloadLink(url, settings, bitrate) + app.downloadLink(url, bitrate) click.echo("All done!") if local: - click.echo(settings['downloadLocation']) #folder name output + click.echo(app.set.settings['downloadLocation']) #folder name output if __name__ == '__main__': download() diff --git a/deemix/api/deezer.py b/deemix/api/deezer.py index 6cb96bf..e0e09de 100755 --- a/deemix/api/deezer.py +++ b/deemix/api/deezer.py @@ -477,37 +477,37 @@ class Deezer: for track in data: item = { 'id': track['SNG_ID'], - 'title': track['SNG_TITLE'], - 'link': 'https://www.deezer.com/track/'+str(track['SNG_ID']), - 'duration': track['DURATION'], - 'rank': track['RANK_SNG'], - 'explicit_lyrics': int(track['EXPLICIT_LYRICS']) > 0, - 'explicit_content_lyrics': track['EXPLICIT_TRACK_CONTENT']['EXPLICIT_COVER_STATUS'], - 'explicit_content_cover': track['EXPLICIT_TRACK_CONTENT']['EXPLICIT_LYRICS_STATUS'], - 'time_add': track['DATE_ADD'], - 'album': { - 'id': track['ALB_ID'], - 'title': track['ALB_TITLE'], - 'cover': 'https://api.deezer.com/album/'+str(track['ALB_ID'])+'/image', - 'cover_small': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/56x56-000000-80-0-0.jpg', - 'cover_medium': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/250x250-000000-80-0-0.jpg', - 'cover_big': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/500x500-000000-80-0-0.jpg', - 'cover_xl': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/1000x1000-000000-80-0-0.jpg', - 'tracklist': 'https://api.deezer.com/album/'+str(track['ALB_ID'])+'/tracks', - 'type': 'album' - }, - 'artist': { - 'id': track['ART_ID'], - 'name': track['ART_NAME'], - 'picture': 'https://api.deezer.com/artist/'+str(track['ART_ID'])+'/image', - 'picture_small': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/56x56-000000-80-0-0.jpg', - 'picture_medium': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/250x250-000000-80-0-0.jpg', - 'picture_big': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/500x500-000000-80-0-0.jpg', - 'picture_xl': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/1000x1000-000000-80-0-0.jpg', - 'tracklist': 'https://api.deezer.com/artist/'+str(track['ART_ID'])+'/top?limit=50', - 'type': 'artist' - }, - 'type': 'track' + 'title': track['SNG_TITLE'], + 'link': 'https://www.deezer.com/track/'+str(track['SNG_ID']), + 'duration': track['DURATION'], + 'rank': track['RANK_SNG'], + 'explicit_lyrics': int(track['EXPLICIT_LYRICS']) > 0, + 'explicit_content_lyrics': track['EXPLICIT_TRACK_CONTENT']['EXPLICIT_COVER_STATUS'], + 'explicit_content_cover': track['EXPLICIT_TRACK_CONTENT']['EXPLICIT_LYRICS_STATUS'], + 'time_add': track['DATE_ADD'], + 'album': { + 'id': track['ALB_ID'], + 'title': track['ALB_TITLE'], + 'cover': 'https://api.deezer.com/album/'+str(track['ALB_ID'])+'/image', + 'cover_small': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/56x56-000000-80-0-0.jpg', + 'cover_medium': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/250x250-000000-80-0-0.jpg', + 'cover_big': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/500x500-000000-80-0-0.jpg', + 'cover_xl': 'https://e-cdns-images.dzcdn.net/images/cover/'+str(track['ALB_PICTURE'])+'/1000x1000-000000-80-0-0.jpg', + 'tracklist': 'https://api.deezer.com/album/'+str(track['ALB_ID'])+'/tracks', + 'type': 'album' + }, + 'artist': { + 'id': track['ART_ID'], + 'name': track['ART_NAME'], + 'picture': 'https://api.deezer.com/artist/'+str(track['ART_ID'])+'/image', + 'picture_small': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/56x56-000000-80-0-0.jpg', + 'picture_medium': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/250x250-000000-80-0-0.jpg', + 'picture_big': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/500x500-000000-80-0-0.jpg', + 'picture_xl': 'https://e-cdns-images.dzcdn.net/images/artist/'+str(track['ART_PICTURE'])+'/1000x1000-000000-80-0-0.jpg', + 'tracklist': 'https://api.deezer.com/artist/'+str(track['ART_ID'])+'/top?limit=50', + 'type': 'artist' + }, + 'type': 'track' } result.append(item) return result diff --git a/deemix/app/__init__.py b/deemix/app/__init__.py index f721132..d126811 100644 --- a/deemix/app/__init__.py +++ b/deemix/app/__init__.py @@ -2,11 +2,11 @@ from deemix.api.deezer import Deezer from deemix.app.settings import Settings from deemix.app.queuemanager import QueueManager -from deemix.app.spotify import SpotifyHelper +from deemix.app.spotifyhelper import SpotifyHelper class deemix: - def __init__(self): - self.set = Settings() + def __init__(self, configFolder=None): + self.set = Settings(configFolder) self.dz = Deezer() - self.sp = SpotifyHelper() + self.sp = SpotifyHelper(configFolder) self.qm = QueueManager() diff --git a/deemix/app/cli.py b/deemix/app/cli.py index f2812c8..0bba93f 100644 --- a/deemix/app/cli.py +++ b/deemix/app/cli.py @@ -1,43 +1,47 @@ #!/usr/bin/env python3 import os.path as path +import string +import random from os import mkdir -from deemix.utils import localpaths -from deemix.api.deezer import Deezer -from deemix.app.queuemanager import addToQueue -from deemix.app.spotify import SpotifyHelper +from deemix.app import deemix -dz = Deezer() -sp = SpotifyHelper() +def randomString(stringLength=8): + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for i in range(stringLength)) +class cli(deemix): + def __init__(self, local, configFolder=None): + super().__init__(configFolder) + if local: + self.set.settings['downloadLocation'] = randomString(12) + print("Using a local download folder: "+settings['downloadLocation']) -def requestValidArl(): - while True: - arl = input("Paste here your arl:") - if dz.login_via_arl(arl): - break - return arl + def downloadLink(self, url, bitrate=None): + for link in url: + if ';' in link: + for l in link.split(";"): + self.qm.addToQueue(self.dz, self.sp, l, self.set.settings, bitrate) + else: + self.qm.addToQueue(self.dz, self.sp, link, self.set.settings, bitrate) + def requestValidArl(self): + while True: + arl = input("Paste here your arl:") + if self.dz.login_via_arl(arl): + break + return arl -def login(): - configFolder = localpaths.getConfigFolder() - if not path.isdir(configFolder): - mkdir(configFolder) - if path.isfile(path.join(configFolder, '.arl')): - with open(path.join(configFolder, '.arl'), 'r') as f: - arl = f.readline().rstrip("\n") - if not dz.login_via_arl(arl): - arl = requestValidArl() - else: - arl = requestValidArl() - with open(path.join(configFolder, '.arl'), 'w') as f: - f.write(arl) - - -def downloadLink(url, settings, bitrate=None): - for link in url: - if ';' in link: - for l in link.split(";"): - addToQueue(dz, sp, l, settings, bitrate) + def login(self): + configFolder = self.set.configFolder + if not path.isdir(configFolder): + mkdir(configFolder) + if path.isfile(path.join(configFolder, '.arl')): + with open(path.join(configFolder, '.arl'), 'r') as f: + arl = f.readline().rstrip("\n") + if not self.dz.login_via_arl(arl): + arl = self.requestValidArl() else: - addToQueue(dz, sp, link, settings, bitrate) + arl = self.requestValidArl() + with open(path.join(configFolder, '.arl'), 'w') as f: + f.write(arl) diff --git a/deemix/app/DownloadJob.py b/deemix/app/downloadjob.py similarity index 90% rename from deemix/app/DownloadJob.py rename to deemix/app/downloadjob.py index 33d8f15..8a3adc8 100644 --- a/deemix/app/DownloadJob.py +++ b/deemix/app/downloadjob.py @@ -11,7 +11,7 @@ from tempfile import gettempdir from time import sleep from deemix.app.queueitem import QIConvertable, QISingle, QICollection -from deemix.app.Track import Track +from deemix.app.track import Track from deemix.utils.misc import changeCase from deemix.utils.pathtemplates import generateFilename, generateFilepath, settingsRegexAlbum, settingsRegexArtist, settingsRegexPlaylistFile from deemix.api.deezer import USER_AGENT_HEADER @@ -46,8 +46,8 @@ errorMessages = { 'wrongBitrate': "Track not found at desired bitrate.", 'wrongBitrateNoAlternative': "Track not found at desired bitrate and no alternative found!", 'no360RA': "Track is not available in Reality Audio 360.", - 'notAvailable': "Track not available on deezer's servers!" - 'notAvailableNoAlternative': "Track not available on deezer's servers and no alternative found!", + 'notAvailable': "Track not available on deezer's servers!", + 'notAvailableNoAlternative': "Track not available on deezer's servers and no alternative found!" } def after_download(tracks, settings, queueItem): @@ -168,8 +168,10 @@ class DownloadJob: def __init__(self, dz, sp, queueItem, interface=None): self.dz = dz self.sp = sp - self.queueItem = queueItem self.interface = interface + if isinstance(queueItem, QIConvertable): + self.sp.convert_spotify_playlist(self.dz, queueItem, interface=self.interface) + self.queueItem = queueItem self.settings = queueItem.settings self.bitrate = queueItem.bitrate self.downloadPercentage = 0 @@ -177,8 +179,6 @@ class DownloadJob: self.extrasPath = self.settings['downloadLocation'] def start(self): - if isinstance(self.queueItem, QIConvertable): - self.sp.convert_spotify_playlist(self.dz, self.queueItem, self.settings, interface=self.interface) if isinstance(self.queueItem, QISingle): result = self.downloadWrapper(self.queueItem.single) if result: @@ -215,15 +215,15 @@ class DownloadJob: ) if self.queueItem.cancel: raise DownloadCancelled - if self.MD5 == '': + if track.MD5 == '': if track.fallbackId != "0": logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not yet encoded, using fallback id") newTrack = self.dz.get_track_gw(track.fallbackId) track.parseEssentialData(self.dz, newTrack) return self.download(trackAPI_gw, track) elif not track.searched and self.settings['fallbackSearch']: - logger.warn(f"[{self.mainArtist['name']} - {self.title}] Track not yet encoded, searching for alternative") - searchedId = self.dz.get_track_from_metadata(self.mainArtist['name'], self.title, self.album['title']) + logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not yet encoded, searching for alternative") + searchedId = self.dz.get_track_from_metadata(track.mainArtist['name'], track.title, track.album['title']) if searchedId != 0: newTrack = self.dz.get_track_gw(searchedId) track.parseEssentialData(self.dz, newTrack) @@ -242,8 +242,8 @@ class DownloadJob: track.parseEssentialData(self.dz, newTrack) return self.download(trackAPI_gw, track) elif not track.searched and self.settings['fallbackSearch']: - logger.warn(f"[{self.mainArtist['name']} - {self.title}] Track not found at desired bitrate, searching for alternative") - searchedId = self.dz.get_track_from_metadata(self.mainArtist['name'], self.title, self.album['title']) + logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not found at desired bitrate, searching for alternative") + searchedId = self.dz.get_track_from_metadata(track.mainArtist['name'], track.title, track.album['title']) if searchedId != 0: newTrack = self.dz.get_track_gw(searchedId) track.parseEssentialData(self.dz, newTrack) @@ -261,8 +261,7 @@ class DownloadJob: track.trackNumber = track.position track.discNumber = "1" track.album = {**track.album, **track.playlist} - track.album['picPath'] = os.path.join(TEMPDIR, - f"pl{trackAPI_gw['_EXTRA_PLAYLIST']['id']}_{settings['embeddedArtworkSize']}.jpg") + track.album['picPath'] = os.path.join(TEMPDIR, f"pl{trackAPI_gw['_EXTRA_PLAYLIST']['id']}_{self.settings['embeddedArtworkSize']}.jpg") else: if track.album['date']: track.date = track.album['date'] @@ -271,10 +270,11 @@ class DownloadJob: self.settings['embeddedArtworkSize'], self.settings['embeddedArtworkSize'], f'000000-{self.settings["jpegImageQuality"]}-0-0.jpg' ) + track.album['picPath'] = os.path.join(TEMPDIR, f"alb{track.album['id']}_{self.settings['embeddedArtworkSize']}.jpg") track.album['bitrate'] = selectedFormat - track.dateString = formatDate(track.date, settings['dateFormat']) - track.album['dateString'] = formatDate(track.album['date'], settings['dateFormat']) + track.dateString = formatDate(track.date, self.settings['dateFormat']) + track.album['dateString'] = formatDate(track.album['date'], self.settings['dateFormat']) # Check if user wants the feat in the title # 0 => do not change @@ -320,11 +320,6 @@ class DownloadJob: if self.queueItem.cancel: raise DownloadCancelled # Download and cache coverart - if self.settings['tags']['savePlaylistAsCompilation'] and track.playlist: - - else: - track.album['picPath'] = os.path.join(TEMPDIR, - f"alb{track.album['id']}_{settings['embeddedArtworkSize']}.jpg") logger.info(f"[{track.mainArtist['name']} - {track.title}] Getting the album cover") track.album['picPath'] = downloadImage(track.album['picUrl'], track.album['picPath']) @@ -418,7 +413,7 @@ class DownloadJob: if not trackAlreadyDownloaded or self.settings['overwriteFile'] == 'y': logger.info(f"[{track.mainArtist['name']} - {track.title}] Downloading the track") - track.downloadUrl = dz.get_track_stream_url(track.id, track.MD5, track.mediaVersion, track.selectedFormat) + track.downloadUrl = self.dz.get_track_stream_url(track.id, track.MD5, track.mediaVersion, track.selectedFormat) def downloadMusic(track, trackAPI_gw): try: @@ -435,8 +430,8 @@ class DownloadJob: track.parseEssentialData(self.dz, newTrack) return False elif not track.searched and self.settings['fallbackSearch']: - logger.warn(f"[{self.mainArtist['name']} - {self.title}] Track not available, searching for alternative") - searchedId = self.dz.get_track_from_metadata(self.mainArtist['name'], self.title, self.album['title']) + logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not available, searching for alternative") + searchedId = self.dz.get_track_from_metadata(track.mainArtist['name'], track.title, track.album['title']) if searchedId != 0: newTrack = self.dz.get_track_gw(searchedId) track.parseEssentialData(self.dz, newTrack) @@ -460,7 +455,7 @@ class DownloadJob: try: trackDownloaded = downloadMusic(track, trackAPI_gw) except DownloadFailed as e: - raise DownloadFailed + raise e except Exception as e: raise e @@ -468,7 +463,7 @@ class DownloadJob: return self.download(trackAPI_gw, track) else: logger.info(f"[{track.mainArtist['name']} - {track.title}] Skipping track as it's already downloaded") - trackCompletePercentage(trackAPI, queueItem, interface) + self.completeTrackPercentage() # Adding tags if (not trackAlreadyDownloaded or self.settings['overwriteFile'] in ['t', 'y']) and not track.localTrack: @@ -482,10 +477,10 @@ class DownloadJob: remove(writepath) logger.warn(f"[{track.mainArtist['name']} - {track.title}] Track not available in FLAC, falling back if necessary") self.removeTrackPercentage(trackAPI, queueItem, interface) - track.formats['FILESIZE_FLAC'] = "0" + track.filesizes['FILESIZE_FLAC'] = "0" return self.download(trackAPI_gw, track) if track.searched: - result['searched'] = f'{track.mainArtist['name']} - {track.title}' + result['searched'] = f"{track.mainArtist['name']} - {track.title}" logger.info(f"[{track.mainArtist['name']} - {track.title}] Track download completed") self.queueItem.downloaded += 1 @@ -533,16 +528,16 @@ class DownloadJob: return error_num # fallback is enabled and loop went through all formats - def stream_track(self, stream, track, trackAPI): + def streamTrack(self, stream, track, trackAPI): if self.queueItem.cancel: raise DownloadCancelled try: - request = get(track.downloadUrl, headers=dz.http_headers, stream=True, timeout=30) + request = get(track.downloadUrl, headers=self.dz.http_headers, stream=True, timeout=30) except ConnectionError: sleep(2) - return stream_track(dz, track, stream, trackAPI, queueItem, interface) + return self.streamTrack(stream, track, trackAPI) request.raise_for_status() - blowfish_key = str.encode(dz._get_blowfish_key(str(track.id))) + blowfish_key = str.encode(self.dz._get_blowfish_key(str(track.id))) complete = int(request.headers["Content-Length"]) chunkLength = 0 percentage = 0 @@ -585,9 +580,9 @@ class DownloadJob: def downloadWrapper(self, trackAPI_gw): track = { - 'id': queueItem.single['SNG_ID'], - 'title': queueItem.single['SNG_TITLE'] + (queueItem.single['VERSION'] if 'VERSION' in queueItem.single and queueItem.single['VERSION'] and not queueItem.single['VERSION'] in queueItem.single['SNG_TITLE'] else ""), - 'mainArtist': {'name': queueItem.single['ART_NAME']} + 'id': trackAPI_gw['SNG_ID'], + 'title': trackAPI_gw['SNG_TITLE'] + (trackAPI_gw['VERSION'] if 'VERSION' in trackAPI_gw and trackAPI_gw['VERSION'] and not trackAPI_gw['VERSION'] in trackAPI_gw['SNG_TITLE'] else ""), + 'artist': trackAPI_gw['ART_NAME'] } try: @@ -595,14 +590,14 @@ class DownloadJob: except DownloadCancelled: return None except DownloadFailed as error: - logger.error(f"[{track['mainArtist']['name']} - {track['title']}] {error.message}") + logger.error(f"[{track['artist']} - {track['title']}] {error.message}") result = {'error': { 'message': error.message, 'errid': error.errid, 'data': track }} except Exception as e: - logger.exception(str(e)) + logger.exception(f"[{track['artist']} - {track['title']}] {str(e)}") result = {'error': { 'message': str(e), 'data': track @@ -611,10 +606,10 @@ class DownloadJob: if 'error' in result: self.completeTrackPercentage() self.queueItem.failed += 1 - self.queueItem.errors.append(error.message) - if interface: + self.queueItem.errors.append(result['error']['message']) + if self.interface: error = result['error'] - interface.send("updateQueue", { + self.interface.send("updateQueue", { 'uuid': self.queueItem.uuid, 'failed': True, 'data': error['data'], diff --git a/deemix/app/MessageInterface.py b/deemix/app/messageinterface.py similarity index 100% rename from deemix/app/MessageInterface.py rename to deemix/app/messageinterface.py diff --git a/deemix/app/queueitem.py b/deemix/app/queueitem.py index b61f61f..7023715 100644 --- a/deemix/app/queueitem.py +++ b/deemix/app/queueitem.py @@ -4,43 +4,43 @@ class QueueItem: def __init__(self, id=None, bitrate=None, title=None, artist=None, cover=None, size=None, type=None, settings=None, queueItemList=None): if queueItemList: self.title = queueItemList['title'] - self.artist = queueItemList['artist'] - self.cover = queueItemList['cover'] - self.size = queueItemList['size'] - self.type = queueItemList['type'] - self.id = queueItemList['id'] - self.bitrate = queueItemList['bitrate'] + self.artist = queueItemList['artist'] + self.cover = queueItemList['cover'] + self.size = queueItemList['size'] + self.type = queueItemList['type'] + self.id = queueItemList['id'] + self.bitrate = queueItemList['bitrate'] self.settings = queueItemList['settings'] else: self.title = title - self.artist = artist - self.cover = cover - self.size = size - self.type = type - self.id = id - self.bitrate = bitrate + self.artist = artist + self.cover = cover + self.size = size + self.type = type + self.id = id + self.bitrate = bitrate self.settings = settings - self.downloaded = 0 - self.failed = 0 + self.downloaded = 0 + self.failed = 0 self.errors = [] - self.progress = 0 - self.uuid = f"{self.type}_{self.id}_{self.bitrate}" + self.progress = 0 + self.uuid = f"{self.type}_{self.id}_{self.bitrate}" self.cancel = False def toDict(self): return { 'title': self.title, - 'artist': self.artist, - 'cover': self.cover, - 'size': self.size, - 'downloaded': self.downloaded, - 'failed': self.failed, + 'artist': self.artist, + 'cover': self.cover, + 'size': self.size, + 'downloaded': self.downloaded, + 'failed': self.failed, 'errors': self.errors, - 'progress': self.progress, - 'type': self.type, - 'id': self.id, - 'bitrate': self.bitrate, - 'uuid': self.uuid + 'progress': self.progress, + 'type': self.type, + 'id': self.id, + 'bitrate': self.bitrate, + 'uuid': self.uuid } def getResettedItem(self): @@ -87,13 +87,13 @@ class QICollection(QueueItem): queueItem['collection'] = self.collection return queueItem -class QIConvertable(QueueItem): +class QIConvertable(QICollection): def __init__(self, id=None, bitrate=None, title=None, artist=None, cover=None, size=None, type=None, settings=None, extra=None, queueItemList=None): if queueItemList: super().__init__(queueItemList=queueItemList) self.extra = queueItemList['_EXTRA'] else: - super().__init__(id, bitrate, title, artist, cover, size, type, settings) + super().__init__(id, bitrate, title, artist, cover, size, type, settings, []) self.extra = extra def toDict(self): diff --git a/deemix/app/QueueManager.py b/deemix/app/queuemanager.py similarity index 98% rename from deemix/app/QueueManager.py rename to deemix/app/queuemanager.py index d5252d8..d78e8db 100644 --- a/deemix/app/QueueManager.py +++ b/deemix/app/queuemanager.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from deemix.app.downloader import download +from deemix.app.downloadjob import DownloadJob from deemix.utils.misc import getIDFromLink, getTypeFromLink, getBitrateInt from deemix.api.deezer import APIError from spotipy.exceptions import SpotifyException @@ -35,7 +35,7 @@ class QueueManager: if 'id' in trackAPI and 'title' in trackAPI: id = trackAPI['id'] else: - + return QueueError(url, "Track ISRC is not available on deezer", "ISRCnotOnDeezer") except APIError as e: e = json.loads(str(e)) return QueueError(url, f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}") @@ -303,7 +303,7 @@ class QueueManager: try: playlist = sp.generate_playlist_queueitem(dz, id, settings) - playlist['bitrate'] = bitrate + playlist.bitrate = bitrate return playlist except SpotifyException as e: return QueueError(url, "Wrong URL: "+e.msg[e.msg.find('\n')+2:]) @@ -317,12 +317,14 @@ class QueueManager: if interface: interface.send("loginNeededToDownload") return False + def parseLink(link): link = link.strip() if link == "": return False logger.info("Generating queue item for: "+link) return self.generateQueueItem(dz, sp, link, settings, bitrate, interface=interface) + if type(url) is list: queueItem = [] for link in url: @@ -339,6 +341,7 @@ class QueueManager: queueItem = parseLink(url) if not queueItem: return False + if type(queueItem) is list: ogLen = len(self.queue) for x in queueItem: @@ -369,6 +372,7 @@ class QueueManager: logger.info(f"[{queueItem.uuid}] Added to queue.") self.queue.append(queueItem.uuid) self.queueList[queueItem.uuid] = queueItem + self.nextItem(dz, sp, interface) return True @@ -383,7 +387,7 @@ class QueueManager: if interface: interface.send("startDownload", self.currentItem) logger.info(f"[{self.currentItem}] Started downloading.") - download(dz, sp, self.queueList[self.currentItem], interface) + DownloadJob(dz, sp, self.queueList[self.currentItem]).start() self.afterDownload(dz, sp, interface) def afterDownload(self, dz, sp, interface): diff --git a/deemix/app/Settings.py b/deemix/app/settings.py similarity index 100% rename from deemix/app/Settings.py rename to deemix/app/settings.py diff --git a/deemix/app/SpotifyHelper.py b/deemix/app/spotifyhelper.py similarity index 92% rename from deemix/app/SpotifyHelper.py rename to deemix/app/spotifyhelper.py index 406441b..ae7cafe 100644 --- a/deemix/app/SpotifyHelper.py +++ b/deemix/app/spotifyhelper.py @@ -213,7 +213,7 @@ class SpotifyHelper: if not 'explicit' in playlistAPI: playlistAPI['explicit'] = False extra['playlistAPI'] = playlistAPI - return QICollection( + return QIConvertable( playlist_id, 0, spotify_playlist['name'], @@ -225,7 +225,7 @@ class SpotifyHelper: extra, ) - def convert_spotify_playlist(self, dz, item, settings, interface=None): + def convert_spotify_playlist(self, dz, queueItem, interface=None): convertPercentage = 0 lastPercentage = 0 if path.isfile(path.join(self.configFolder, 'spotifyCache.json')): @@ -234,13 +234,13 @@ class SpotifyHelper: else: cache = {'tracks': {}, 'albums': {}} if interface: - interface.send("startConversion", item.uuid) + interface.send("startConversion", queueItem.uuid) collection = [] - for pos, track in enumerate(item.extra['unconverted'], start=1): + for pos, track in enumerate(queueItem.extra['unconverted'], start=1): if str(track['id']) in cache['tracks']: trackID = cache['tracks'][str(track['id'])] else: - trackID = self.get_trackid_spotify(dz, 0, settings['fallbackSearch'], track) + trackID = self.get_trackid_spotify(dz, 0, queueItem.settings['fallbackSearch'], track) cache['tracks'][str(track['id'])] = trackID if trackID == 0: deezerTrack = { @@ -257,35 +257,25 @@ class SpotifyHelper: } else: deezerTrack = dz.get_track_gw(trackID) - deezerTrack['_EXTRA_PLAYLIST'] = item.extra['playlistAPI'] + deezerTrack['_EXTRA_PLAYLIST'] = queueItem.extra['playlistAPI'] deezerTrack['POSITION'] = pos - deezerTrack['SIZE'] = item.size - deezerTrack['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate'] + deezerTrack['SIZE'] = queueItem.size + deezerTrack['FILENAME_TEMPLATE'] = queueItem.settings['playlistTracknameTemplate'] collection.append(deezerTrack) - convertPercentage = (pos / item.size) * 100 - print(convertPercentage) + convertPercentage = (pos / queueItem.size) * 100 if round(convertPercentage) != lastPercentage and round(convertPercentage) % 2 == 0: lastPercentage = round(convertPercentage) if interface: - interface.send("updateQueue", {'uuid': item.uuid, 'conversion': lastPercentage}) + interface.send("updateQueue", {'uuid': queueItem.uuid, 'conversion': lastPercentage}) + + queueItem.extra = None + queueItem.collection = collection - item = QICollection( - item.id, - item.bitrate, - item.title, - item.artist, - item.cover, - item.size, - item.type, - item.settings, - collection, - ) - with open(path.join(self.configFolder, 'spotifyCache.json'), 'w') as spotifyCache: json.dump(cache, spotifyCache) if interface: - interface.send("startDownload", item['uuid']) + interface.send("startDownload", queueItem.uuid) def get_user_playlists(self, user): if not self.spotifyEnabled: diff --git a/deemix/app/Track.py b/deemix/app/track.py similarity index 98% rename from deemix/app/Track.py rename to deemix/app/track.py index d7e20cd..c9826d6 100644 --- a/deemix/app/Track.py +++ b/deemix/app/track.py @@ -13,7 +13,7 @@ class Track: self.title = trackAPI_gw['SNG_TITLE'].strip() if 'VERSION' in trackAPI_gw and trackAPI_gw['VERSION'] and not trackAPI_gw['VERSION'] in trackAPI_gw['SNG_TITLE']: - track.title += " " + trackAPI_gw['VERSION'].strip() + self.title += " " + trackAPI_gw['VERSION'].strip() self.position = None if 'POSITION' in trackAPI_gw: @@ -87,7 +87,7 @@ class Track: self.album['bitrate'] = 0 self.album['dateString'] = None - self.artistsString + self.artistsString = "" def parseEssentialData(self, dz, trackAPI_gw): self.id = trackAPI_gw['SNG_ID'] @@ -97,7 +97,7 @@ class Track: self.fallbackId = "0" if 'FALLBACK' in trackAPI_gw: self.fallbackId = trackAPI_gw['FALLBACK']['SNG_ID'] - self.formats = dz.get_track_filesizes(track["id"]) + self.filesizes = dz.get_track_filesizes(self.id) def parseLocalTrackData(self, trackAPI_gw): self.album = { @@ -160,7 +160,7 @@ class Track: self.trackNumber = trackAPI_gw['TRACK_NUMBER'] self.contributors = trackAPI_gw['SNG_CONTRIBUTORS'] - track.lyrics = { + self.lyrics = { 'id': None, 'unsync': None, 'sync': None @@ -184,13 +184,13 @@ class Track: self.lyrics['sync'] += lastTimestamp self.lyrics['sync'] += trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]["line"] + "\r\n" - track.mainArtist = { + self.mainArtist = { 'id': trackAPI_gw['ART_ID'], 'name': trackAPI_gw['ART_NAME'], 'pic': None } if 'ART_PICTURE' in trackAPI_gw: - track.mainArtist['pic'] = trackAPI_gw['ART_PICTURE'] + self.mainArtist['pic'] = trackAPI_gw['ART_PICTURE'] self.date = None if 'PHYSICAL_RELEASE_DATE' in trackAPI_gw: @@ -261,7 +261,7 @@ class Track: if 'copyright' in albumAPI: self.copyright = albumAPI['copyright'] - if not track.album['pic']: + if not self.album['pic']: self.album['pic'] = albumAPI['cover_small'][albumAPI['cover_small'].find('cover/') + 6:-24] if 'genres' in albumAPI and 'data' in albumAPI['genres'] and len(albumAPI['genres']['data']) > 0: diff --git a/deemix/utils/misc.py b/deemix/utils/misc.py index e30520f..2c57a4a 100644 --- a/deemix/utils/misc.py +++ b/deemix/utils/misc.py @@ -50,7 +50,6 @@ def andCommaConcat(lst): result = "" for i, art in enumerate(lst): result += art - track['commaArtistsString'] += art if tot != i + 1: if tot - 1 == i + 1: result += " & " diff --git a/deemix/utils/pathtemplates.py b/deemix/utils/pathtemplates.py index 96259cd..aef5ef7 100644 --- a/deemix/utils/pathtemplates.py +++ b/deemix/utils/pathtemplates.py @@ -130,10 +130,10 @@ def generateFilepath(track, trackAPI, settings): def settingsRegex(filename, track, settings, playlist=None): filename = filename.replace("%title%", fixName(track.title, settings['illegalCharacterReplacer'])) filename = filename.replace("%artist%", fixName(track.mainArtist['name'], settings['illegalCharacterReplacer'])) - filename = filename.replace("%artists%", fixName(track.commaArtistsString, settings['illegalCharacterReplacer'])) + filename = filename.replace("%artists%", fixName(", ".join(track.artists), settings['illegalCharacterReplacer'])) filename = filename.replace("%allartists%", fixName(track.artistsString, settings['illegalCharacterReplacer'])) filename = filename.replace("%mainartists%", fixName(track.mainArtistsString, settings['illegalCharacterReplacer'])) - filename = filename.replace("%featartists%", fixName('('+track.featArtistsString+')', settings['illegalCharacterReplacer']) if 'featArtistsString' in track else "") + filename = filename.replace("%featartists%", fixName('('+track.featArtistsString+')', settings['illegalCharacterReplacer']) if track.featArtistsString else "") filename = filename.replace("%album%", fixName(track.album['title'], settings['illegalCharacterReplacer'])) filename = filename.replace("%albumartist%", fixName(track.album['mainArtist']['name'], settings['illegalCharacterReplacer'])) diff --git a/deemix/utils/taggers.py b/deemix/utils/taggers.py index 26f63df..8c4fc96 100644 --- a/deemix/utils/taggers.py +++ b/deemix/utils/taggers.py @@ -75,7 +75,7 @@ def tagID3(stream, track, save): if save['copyright']: tag.add(TCOP(text=track.copyright)) - if save['savePlaylistAsCompilation'] and "playlist" in track: + if save['savePlaylistAsCompilation'] and track.playlist: tag.add(TCMP(text="1")) if save['cover'] and track.album['picPath']: @@ -155,7 +155,7 @@ def tagFLAC(stream, track, save): if save['copyright']: tag["COPYRIGHT"] = track.copyright - if save['savePlaylistAsCompilation'] and "playlist" in track: + if save['savePlaylistAsCompilation'] and track.playlist: tag["COMPILATION"] = "1" if save['cover'] and track.album['picPath']: