diff --git a/deemix/app/cli.py b/deemix/app/cli.py index 08d6e35..a1a0ea4 100644 --- a/deemix/app/cli.py +++ b/deemix/app/cli.py @@ -3,11 +3,13 @@ from deemix.api.deezer import Deezer import deemix.utils.localpaths as localpaths from deemix.utils.misc import getIDFromLink, getTypeFromLink, getBitrateInt from deemix.app.queuemanager import addToQueue +from deemix.app.spotify import SpotifyHelper from os import system as execute import os.path as path from os import mkdir dz = Deezer() +sp = SpotifyHelper() def requestValidArl(): while True: @@ -31,4 +33,4 @@ def login(): f.write(arl) def downloadLink(url, settings, bitrate=None): - addToQueue(dz, url, settings, bitrate) + addToQueue(dz, sp, url, settings, bitrate) diff --git a/deemix/app/downloader.py b/deemix/app/downloader.py index d089a57..bb473cc 100644 --- a/deemix/app/downloader.py +++ b/deemix/app/downloader.py @@ -3,7 +3,6 @@ from deemix.api.deezer import APIError, USER_AGENT_HEADER from deemix.utils.taggers import tagID3, tagFLAC from deemix.utils.pathtemplates import generateFilename, generateFilepath, settingsRegexAlbum, settingsRegexArtist from deemix.utils.misc import changeCase -from deemix.utils.spotifyHelper import get_trackid_spotify, get_albumid_spotify import os.path from os import makedirs, remove from requests import get diff --git a/deemix/app/queuemanager.py b/deemix/app/queuemanager.py index bd4a916..78cb51b 100644 --- a/deemix/app/queuemanager.py +++ b/deemix/app/queuemanager.py @@ -1,5 +1,4 @@ from deemix.utils.misc import getIDFromLink, getTypeFromLink, getBitrateInt -from deemix.utils.spotifyHelper import get_trackid_spotify, get_albumid_spotify, convert_spotify_playlist from concurrent.futures import ProcessPoolExecutor from deemix.app.downloader import download @@ -27,7 +26,7 @@ if its an album/playlist collection """ -def generateQueueItem(dz, url, settings, bitrate=None, albumAPI=None, interface=None): +def generateQueueItem(dz, sp, url, settings, bitrate=None, albumAPI=None, interface=None): forcedBitrate = getBitrateInt(bitrate) bitrate = forcedBitrate if forcedBitrate else settings['maxBitrate'] type = getTypeFromLink(url) @@ -65,7 +64,7 @@ def generateQueueItem(dz, url, settings, bitrate=None, albumAPI=None, interface= albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK'] albumAPI['copyright'] = albumAPI_gw['COPYRIGHT'] if albumAPI['nb_tracks'] == 1: - return generateQueueItem(dz, f"https://www.deezer.com/track/{albumAPI['tracks']['data'][0]['id']}", settings, bitrate, albumAPI) + return generateQueueItem(dz, sp, f"https://www.deezer.com/track/{albumAPI['tracks']['data'][0]['id']}", settings, bitrate, albumAPI) tracksArray = dz.get_album_tracks_gw(id) result['title'] = albumAPI['title'] @@ -121,37 +120,46 @@ def generateQueueItem(dz, url, settings, bitrate=None, albumAPI=None, interface= artistAPITracks = dz.get_artist_albums(id) albumList = [] for album in artistAPITracks['data']: - albumList.append(generateQueueItem(dz, album['link'], settings, bitrate)) + albumList.append(generateQueueItem(dz, sp, album['link'], settings, bitrate)) if interface: interface.send("toast", {'msg': f"Added {artistAPI['name']} albums to queue", 'icon': 'done', 'dismiss': True, 'id': 'artist_'+str(artistAPI['id'])}) return albumList elif type == "spotifytrack": - track_id = get_trackid_spotify(dz, id, settings['fallbackSearch']) result = {} - if track_id == "Not Enabled": + if not sp.spotifyEnabled: print("Spotify Features is not setted up correctly.") result['error'] = "Spotify Features is not setted up correctly." - elif track_id != 0: - return generateQueueItem(dz, f'https://www.deezer.com/track/{track_id}', settings, bitrate) + return result + track_id = sp.get_trackid_spotify(dz, id, settings['fallbackSearch']) + if track_id != 0: + return generateQueueItem(dz, sp, f'https://www.deezer.com/track/{track_id}', settings, bitrate) else: print("Track not found on deezer!") result['error'] = "Track not found on deezer!" elif type == "spotifyalbum": - album_id = get_albumid_spotify(dz, id) - if album_id == "Not Enabled": + result = {} + if not sp.spotifyEnabled: print("Spotify Features is not setted up correctly.") result['error'] = "Spotify Features is not setted up correctly." - elif album_id != 0: - return generateQueueItem(dz, f'https://www.deezer.com/album/{album_id}', settings, bitrate) + return result + album_id = sp.get_albumid_spotify(dz, id) + if album_id != 0: + return generateQueueItem(dz, sp, f'https://www.deezer.com/album/{album_id}', settings, bitrate) else: print("Album not found on deezer!") result['error'] = "Album not found on deezer!" elif type == "spotifyplaylist": + result = {} + if not sp.spotifyEnabled: + print("Spotify Features is not setted up correctly.") + result['error'] = "Spotify Features is not setted up correctly." + return result if interface: interface.send("toast", {'msg': f"Converting spotify tracks to deezer tracks", 'icon': 'loading', 'dismiss': False, 'id': 'spotifyplaylist_'+str(id)}) - result = convert_spotify_playlist(dz, id, settings) - result['bitrate'] = bitrate - result['uuid'] = f"{result['type']}_{id}_{bitrate}" + playlist = sp.convert_spotify_playlist(dz, id, settings) + playlist['bitrate'] = bitrate + playlist['uuid'] = f"{result['type']}_{id}_{bitrate}" + result = playlist if interface: interface.send("toast", {'msg': f"Spotify playlist converted", 'icon': 'done', 'dismiss': True, 'id': 'spotifyplaylist_'+str(id)}) else: @@ -159,11 +167,11 @@ def generateQueueItem(dz, url, settings, bitrate=None, albumAPI=None, interface= result['error'] = "URL not supported yet" return result -def addToQueue(dz, url, settings, bitrate=None, interface=None): +def addToQueue(dz, sp, url, settings, bitrate=None, interface=None): global currentItem, queueList, queue if not dz.logged_in: return "Not logged in" - queueItem = generateQueueItem(dz, url, settings, bitrate, interface=interface) + queueItem = generateQueueItem(dz, sp, url, settings, bitrate, interface=interface) if type(queueItem) is list: for x in queueItem: if 'error' in x: diff --git a/deemix/app/spotify.py b/deemix/app/spotify.py new file mode 100644 index 0000000..fbdda29 --- /dev/null +++ b/deemix/app/spotify.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +import os.path as path +from os import mkdir, rmdir +import json + +import deemix.utils.localpaths as localpaths + +import spotipy +from spotipy.oauth2 import SpotifyClientCredentials + +class SpotifyHelper: + def __init__(self): + self.credentials = {} + self.spotifyEnabled = False + self.sp = None + self.initCredentials() + + def initCredentials(self): + configFolder = localpaths.getConfigFolder() + if not path.isdir(configFolder): + mkdir(configFolder) + if not path.isfile(path.join(configFolder, 'authCredentials.json')): + with open(path.join(configFolder, 'authCredentials.json'), 'w') as f: + json.dump({'clientId': "", 'clientSecret': ""}, f, indent=2) + with open(path.join(configFolder, 'authCredentials.json'), 'r') as credentialsFile: + self.credentials = json.load(credentialsFile) + self.checkCredentials() + + def checkCredentials(self): + if self.credentials['clientId'] == "" or self.credentials['clientSecret'] == "": + spotifyEnabled = False + else: + try: + self.createSpotifyConnection() + self.sp.user_playlists('spotify') + self.spotifyEnabled = True + except Exception as e: + self.spotifyEnabled = False + return self.spotifyEnabled + + def getCredentials(self): + return self.credentials + + def setCredentials(self, spotifyCredentials): + configFolder = localpaths.getConfigFolder() + with open(path.join(configFolder, 'authCredentials.json'), 'w') as f: + json.dump(spotifyCredentials, f, indent=2) + self.credentials = spotifyCredentials + self.checkCredentials() + + def createSpotifyConnection(self): + client_credentials_manager = SpotifyClientCredentials(client_id=self.credentials['clientId'], client_secret=self.credentials['clientSecret']) + self.sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) + + def _convert_playlist_structure(self, spotify_obj): + if len(spotify_obj['images']): + url = spotify_obj['images'][0]['url'] + else: + url = "https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/75x75-000000-80-0-0.jpg" + deezer_obj = { + 'checksum': spotify_obj['snapshot_id'], + 'collaborative': spotify_obj['collaborative'], + 'creation_date': "???-??-??", + 'creator': {'id': spotify_obj['owner']['id'], 'name': spotify_obj['owner']['display_name'], 'tracklist': spotify_obj['owner']['href'], 'type': "user"}, + 'description': spotify_obj['description'], + 'duration': 0, + 'fans': spotify_obj['followers']['total'], + 'id': spotify_obj['id'], + 'is_loved_track': False, + 'link': spotify_obj['external_urls']['spotify'], + 'nb_tracks': spotify_obj['tracks']['total'], + 'picture': url, + 'picture_big': url, + 'picture_medium': url, + 'picture_small': url, + 'picture_xl': url, + 'public': spotify_obj['public'], + 'share': spotify_obj['external_urls']['spotify'], + 'title': spotify_obj['name'], + 'tracklist': spotify_obj['tracks']['href'], + 'type': "playlist" + } + return deezer_obj + + def get_trackid_spotify(self, dz, track_id, fallbackSearch, spotifyTrack=None): + if not self.spotifyEnabled: + raise spotifyFeaturesNotEnabled + if not spotifyTrack: + spotify_track = self.sp.track(track_id) + else: + spotify_track = spotifyTrack + dz_track = 0 + if 'external_ids' in spotify_track and 'isrc' in spotify_track['external_ids']: + try: + dz_track = dz.get_track_by_ISRC(spotify_track['external_ids']['isrc']) + dz_track = dz_track['id'] if 'id' in dz_track else 0 + except: + dz_track = dz.get_track_from_metadata(spotify_track['artists'][0]['name'], spotify_track['name'], spotify_track['album']['name']) if fallbackSearch else 0 + elif fallbackSearch: + dz_track = dz.get_track_from_metadata(spotify_track['artists'][0]['name'], spotify_track['name'], spotify_track['album']['name']) + return dz_track + + def get_albumid_spotify(self, dz, album_id): + if not self.spotifyEnabled: + raise spotifyFeaturesNotEnabled + spotify_album = self.sp.album(album_id) + dz_album = 0 + if 'external_ids' in spotify_album and 'upc' in spotify_album['external_ids']: + try: + dz_album = dz.get_album_by_UPC(spotify_album['external_ids']['upc']) + dz_album = dz_album['id'] if 'id' in dz_album else 0 + except: + try: + dz_album = dz.get_album_by_UPC(int(spotify_album['external_ids']['upc'])) + dz_album = dz_album['id'] if 'id' in dz_album else 0 + except: + dz_album = 0 + return dz_album + + def convert_spotify_playlist(self, sp, dz, playlist_id, settings): + if not self.spotifyEnabled: + raise spotifyFeaturesNotEnabled + spotify_playlist = self.sp.playlist(playlist_id) + result = { + 'title': spotify_playlist['name'], + 'artist': spotify_playlist['owner']['display_name'], + 'size': spotify_playlist['tracks']['total'], + 'downloaded': 0, + 'failed': 0, + 'progress': 0, + 'type': 'spotify_playlist', + 'settings': settings or {}, + 'id': playlist_id + } + if len(spotify_playlist['images']): + result['cover'] = spotify_playlist['images'][0]['url'] + else: + result['cover'] = "https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/75x75-000000-80-0-0.jpg" + playlistAPI = self._convert_playlist_structure(spotify_playlist) + tracklist = spotify_playlist['tracks']['items'] + result['collection'] = [] + while spotify_playlist['tracks']['next']: + spotify_playlist['tracks'] = self.sp.next(spotify_playlist['tracks']) + tracklist += spotify_playlist['tracks']['items'] + totalSize = len(tracklist) + for pos, track in enumerate(tracklist, start=1): + trackID = self.get_trackid_spotify(dz, 0, settings['fallbackSearch'], track['track']) + if trackID == 0: + deezerTrack = { + 'SNG_ID': 0, + 'SNG_TITLE': track['track']['name'], + 'DURATION': 0, + 'MD5_ORIGIN': 0, + 'MEDIA_VERSION': 0, + 'FILESIZE': 0, + 'ALB_TITLE': track['track']['album']['name'], + 'ALB_PICTURE': "", + 'ART_ID': 0, + 'ART_NAME': track['track']['artists'][0]['name'] + } + else: + deezerTrack = dz.get_track_gw(trackID) + deezerTrack['_EXTRA_PLAYLIST'] = playlistAPI + deezerTrack['POSITION'] = pos + deezerTrack['SIZE'] = totalSize + deezerTrack['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate'] + result['collection'].append(deezerTrack) + return result + +class spotifyFeaturesNotEnabled(Exception): + pass diff --git a/deemix/utils/spotifyHelper.py b/deemix/utils/spotifyHelper.py deleted file mode 100644 index 26948ea..0000000 --- a/deemix/utils/spotifyHelper.py +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env python3 -import os.path as path -from os import mkdir, rmdir -import json - -import deemix.utils.localpaths as localpaths - -import spotipy -from spotipy.oauth2 import SpotifyClientCredentials - -credentials = {} -spotifyEnabled = False - -def getCredentials(): - global credentials - global spotifyEnabled - configFolder = localpaths.getConfigFolder() - if not path.isdir(configFolder): - mkdir(configFolder) - if not path.isfile(path.join(configFolder, 'authCredentials.json')): - with open(path.join(configFolder, 'authCredentials.json'), 'w') as f: - json.dump({'clientId': "", 'clientSecret': ""}, f, indent=2) - with open(path.join(configFolder, 'authCredentials.json'), 'r') as credentialsFile: - credentials = json.load(credentialsFile) - checkCredentials() - -def checkCredentials(): - global credentials - global spotifyEnabled - if credentials['clientId'] == "" or credentials['clientSecret'] == "": - spotifyEnabled = False - else: - spotifyEnabled = True - -getCredentials() -if spotifyEnabled: - client_credentials_manager = SpotifyClientCredentials(client_id=credentials['clientId'], client_secret=credentials['clientSecret']) - sp = spotipy.Spotify(client_credentials_manager=client_credentials_manager) - -def _convert_playlist_structure(spotify_obj): - if len(spotify_obj['images']): - url = spotify_obj['images'][0]['url'] - else: - url = "https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/75x75-000000-80-0-0.jpg" - deezer_obj = { - 'checksum': spotify_obj['snapshot_id'], - 'collaborative': spotify_obj['collaborative'], - 'creation_date': "???-??-??", - 'creator': {'id': spotify_obj['owner']['id'], 'name': spotify_obj['owner']['display_name'], 'tracklist': spotify_obj['owner']['href'], 'type': "user"}, - 'description': spotify_obj['description'], - 'duration': 0, - 'fans': spotify_obj['followers']['total'], - 'id': spotify_obj['id'], - 'is_loved_track': False, - 'link': spotify_obj['external_urls']['spotify'], - 'nb_tracks': spotify_obj['tracks']['total'], - 'picture': url, - 'picture_big': url, - 'picture_medium': url, - 'picture_small': url, - 'picture_xl': url, - 'public': spotify_obj['public'], - 'share': spotify_obj['external_urls']['spotify'], - 'title': spotify_obj['name'], - 'tracklist': spotify_obj['tracks']['href'], - 'type': "playlist" - } - return deezer_obj - -def get_trackid_spotify(dz, track_id, fallbackSearch, spotifyTrack=None): - global spotifyEnabled - if not spotifyEnabled: - return "Not Enabled" - if not spotifyTrack: - spotify_track = sp.track(track_id) - else: - spotify_track = spotifyTrack - dz_track = 0 - if 'external_ids' in spotify_track and 'isrc' in spotify_track['external_ids']: - try: - dz_track = dz.get_track_by_ISRC(spotify_track['external_ids']['isrc']) - dz_track = dz_track['id'] if 'id' in dz_track else 0 - except: - dz_track = dz.get_track_from_metadata(spotify_track['artists'][0]['name'], spotify_track['name'], spotify_track['album']['name']) if fallbackSearch else 0 - elif fallbackSearch: - dz_track = dz.get_track_from_metadata(spotify_track['artists'][0]['name'], spotify_track['name'], spotify_track['album']['name']) - return dz_track - -def get_albumid_spotify(dz, album_id): - global spotifyEnabled - if not spotifyEnabled: - return "Not Enabled" - spotify_album = sp.album(album_id) - dz_album = 0 - if 'external_ids' in spotify_album and 'upc' in spotify_album['external_ids']: - try: - dz_album = dz.get_album_by_UPC(spotify_album['external_ids']['upc']) - dz_album = dz_album['id'] if 'id' in dz_album else 0 - except: - try: - dz_album = dz.get_album_by_UPC(int(spotify_album['external_ids']['upc'])) - dz_album = dz_album['id'] if 'id' in dz_album else 0 - except: - dz_album = 0 - return dz_album - -def convert_spotify_playlist(dz, playlist_id, settings): - global spotifyEnabled - if not spotifyEnabled: - return "Not Enabled" - spotify_playlist = sp.playlist(playlist_id) - result = { - 'title': spotify_playlist['name'], - 'artist': spotify_playlist['owner']['display_name'], - 'size': spotify_playlist['tracks']['total'], - 'downloaded': 0, - 'failed': 0, - 'progress': 0, - 'type': 'spotify_playlist', - 'settings': settings or {}, - 'id': playlist_id - } - if len(spotify_playlist['images']): - result['cover'] = spotify_playlist['images'][0]['url'] - else: - result['cover'] = "https://e-cdns-images.dzcdn.net/images/cover/d41d8cd98f00b204e9800998ecf8427e/75x75-000000-80-0-0.jpg" - playlistAPI = _convert_playlist_structure(spotify_playlist) - tracklist = spotify_playlist['tracks']['items'] - result['collection'] = [] - while spotify_playlist['tracks']['next']: - spotify_playlist['tracks'] = sp.next(spotify_playlist['tracks']) - tracklist += spotify_playlist['tracks']['items'] - totalSize = len(tracklist) - for pos, track in enumerate(tracklist, start=1): - trackID = get_trackid_spotify(dz, 0, settings['fallbackSearch'], track['track']) - if trackID == 0: - deezerTrack = { - 'SNG_ID': 0, - 'SNG_TITLE': track['track']['name'], - 'DURATION': 0, - 'MD5_ORIGIN': 0, - 'MEDIA_VERSION': 0, - 'FILESIZE': 0, - 'ALB_TITLE': track['track']['album']['name'], - 'ALB_PICTURE': "", - 'ART_ID': 0, - 'ART_NAME': track['track']['artists'][0]['name'] - } - else: - deezerTrack = dz.get_track_gw(trackID) - deezerTrack['_EXTRA_PLAYLIST'] = playlistAPI - deezerTrack['POSITION'] = pos - deezerTrack['SIZE'] = totalSize - deezerTrack['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate'] - result['collection'].append(deezerTrack) - return result