deemix-py/deemix/app/queuemanager.py

530 lines
21 KiB
Python
Raw Normal View History

2020-05-04 16:23:09 +00:00
#!/usr/bin/env python3
from deemix.app.downloader import download
from deemix.utils.misc import getIDFromLink, getTypeFromLink, getBitrateInt
from deemix.api.deezer import APIError
2020-06-29 11:58:06 +00:00
from spotipy.exceptions import SpotifyException
import logging
import json
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('deemix')
queue = []
queueList = {}
2020-04-14 14:48:13 +00:00
queueComplete = []
currentItem = ""
"""
queueItem base structure
title
artist
cover
size
downloaded
failed
errors
progress
type
id
bitrate
uuid: type+id+bitrate
if its a single track
single
if its an album/playlist
collection
"""
def resetQueueItems(items, q):
result = {}
for item in items.keys():
result[item] = items[item].copy()
if item in q:
result[item]['downloaded'] = 0
result[item]['failed'] = 0
result[item]['progress'] = 0
result[item]['errors'] = []
return result
def slimQueueItems(items):
result = {}
for item in items.keys():
result[item] = slimQueueItem(items[item])
return result
def slimQueueItem(item):
light = item.copy()
propertiesToDelete = ['single', 'collection', 'unconverted', '_EXTRA']
for property in propertiesToDelete:
if property in light:
del light[property]
return light
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)
id = getIDFromLink(url, type)
result = {}
result['link'] = url
if type == None or id == None:
logger.warn("URL not recognized")
result['error'] = "URL not recognized"
2020-07-23 14:41:11 +00:00
result['errid'] = "invalidURL"
elif type == "track":
if id.startswith("isrc"):
try:
trackAPI = dz.get_track(id)
2020-07-23 13:10:18 +00:00
if 'id' in trackAPI and 'title' in trackAPI:
id = trackAPI['id']
else:
result['error'] = "Track ISRC is not available on deezer"
2020-07-23 14:41:11 +00:00
result['errid'] = "ISRCnotOnDeezer"
return result
except APIError as e:
e = json.loads(str(e))
2020-06-29 11:58:06 +00:00
result['error'] = f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}"
return result
2020-06-29 11:58:06 +00:00
try:
trackAPI = dz.get_track_gw(id)
except APIError as e:
e = json.loads(str(e))
result['error'] = "Wrong URL"
if "DATA_ERROR" in e:
result['error'] += f": {e['DATA_ERROR']}"
return result
if albumAPI:
trackAPI['_EXTRA_ALBUM'] = albumAPI
if settings['createSingleFolder']:
trackAPI['FILENAME_TEMPLATE'] = settings['albumTracknameTemplate']
else:
trackAPI['FILENAME_TEMPLATE'] = settings['tracknameTemplate']
trackAPI['SINGLE_TRACK'] = True
result['title'] = trackAPI['SNG_TITLE']
if 'VERSION' in trackAPI and trackAPI['VERSION']:
result['title'] += " " + trackAPI['VERSION']
result['artist'] = trackAPI['ART_NAME']
result[
'cover'] = f"https://e-cdns-images.dzcdn.net/images/cover/{trackAPI['ALB_PICTURE']}/75x75-000000-80-0-0.jpg"
result['size'] = 1
result['downloaded'] = 0
result['failed'] = 0
result['errors'] = []
result['progress'] = 0
result['type'] = 'track'
result['id'] = id
result['bitrate'] = bitrate
result['uuid'] = f"{result['type']}_{id}_{bitrate}"
result['settings'] = settings or {}
result['single'] = trackAPI
elif type == "album":
try:
albumAPI = dz.get_album(id)
except APIError as e:
e = json.loads(str(e))
2020-06-29 11:58:06 +00:00
result['error'] = f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}"
return result
if id.startswith('upc'):
id = albumAPI['id']
albumAPI_gw = dz.get_album_gw(id)
albumAPI['nb_disk'] = albumAPI_gw['NUMBER_DISK']
albumAPI['copyright'] = albumAPI_gw['COPYRIGHT']
if albumAPI['nb_tracks'] == 1:
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)
if albumAPI['nb_tracks'] == 255:
albumAPI['nb_tracks'] = len(tracksArray)
result['title'] = albumAPI['title']
result['artist'] = albumAPI['artist']['name']
if albumAPI['cover_small'] != None:
result['cover'] = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg'
else:
result['cover'] = f"https://e-cdns-images.dzcdn.net/images/cover/{albumAPI_gw['ALB_PICTURE']}/75x75-000000-80-0-0.jpg"
result['size'] = albumAPI['nb_tracks']
result['downloaded'] = 0
result['failed'] = 0
result['errors'] = []
result['progress'] = 0
result['type'] = 'album'
result['id'] = id
result['bitrate'] = bitrate
result['uuid'] = f"{result['type']}_{id}_{bitrate}"
result['settings'] = settings or {}
totalSize = len(tracksArray)
result['collection'] = []
for pos, trackAPI in enumerate(tracksArray, start=1):
trackAPI['_EXTRA_ALBUM'] = albumAPI
trackAPI['POSITION'] = pos
trackAPI['SIZE'] = totalSize
trackAPI['FILENAME_TEMPLATE'] = settings['albumTracknameTemplate']
result['collection'].append(trackAPI)
elif type == "playlist":
try:
playlistAPI = dz.get_playlist(id)
except:
2020-06-29 11:58:06 +00:00
try:
playlistAPI = dz.get_playlist_gw(id)['results']['DATA']
except APIError as e:
e = json.loads(str(e))
result['error'] = "Wrong URL"
if "DATA_ERROR" in e:
result['error'] += f": {e['DATA_ERROR']}"
return result
newPlaylist = {
'id': playlistAPI['PLAYLIST_ID'],
'title': playlistAPI['TITLE'],
'description': playlistAPI['DESCRIPTION'],
'duration': playlistAPI['DURATION'],
'public': False,
'is_loved_track': False,
'collaborative': False,
'nb_tracks': playlistAPI['NB_SONG'],
'fans': playlistAPI['NB_FAN'],
'link': "https://www.deezer.com/playlist/"+playlistAPI['PLAYLIST_ID'],
'share': None,
'picture': "https://api.deezer.com/playlist/"+playlistAPI['PLAYLIST_ID']+"/image",
'picture_small': "https://cdns-images.dzcdn.net/images/"+playlistAPI['PICTURE_TYPE']+"/"+playlistAPI['PLAYLIST_PICTURE']+"/56x56-000000-80-0-0.jpg",
'picture_medium': "https://cdns-images.dzcdn.net/images/"+playlistAPI['PICTURE_TYPE']+"/"+playlistAPI['PLAYLIST_PICTURE']+"/250x250-000000-80-0-0.jpg",
'picture_big': "https://cdns-images.dzcdn.net/images/"+playlistAPI['PICTURE_TYPE']+"/"+playlistAPI['PLAYLIST_PICTURE']+"/500x500-000000-80-0-0.jpg",
'picture_xl': "https://cdns-images.dzcdn.net/images/"+playlistAPI['PICTURE_TYPE']+"/"+playlistAPI['PLAYLIST_PICTURE']+"/1000x1000-000000-80-0-0.jpg",
'checksum': playlistAPI['CHECKSUM'],
'tracklist': "https://api.deezer.com/playlist/"+playlistAPI['PLAYLIST_ID']+"/tracks",
'creation_date': playlistAPI['DATE_ADD'],
'creator': {
'id': playlistAPI['PARENT_USER_ID'],
'name': playlistAPI['PARENT_USERNAME'],
'tracklist': "https://api.deezer.com/user/"+playlistAPI['PARENT_USER_ID']+"/flow",
'type': "user"
},
'type': "playlist"
}
playlistAPI = newPlaylist
if not playlistAPI['public'] and playlistAPI['creator']['id'] != str(dz.user['id']):
logger.warn("You can't download others private playlists.")
result['error'] = "You can't download others private playlists."
2020-07-23 14:41:11 +00:00
result['errid'] = "notYourPrivatePlaylist"
return result
playlistTracksAPI = dz.get_playlist_tracks_gw(id)
playlistAPI['various_artist'] = dz.get_artist(5080)
result['title'] = playlistAPI['title']
result['artist'] = playlistAPI['creator']['name']
result['cover'] = playlistAPI['picture_small'][:-24] + '/75x75-000000-80-0-0.jpg'
result['size'] = playlistAPI['nb_tracks']
result['downloaded'] = 0
result['failed'] = 0
result['errors'] = []
result['progress'] = 0
result['type'] = 'playlist'
result['id'] = id
result['bitrate'] = bitrate
result['uuid'] = f"{result['type']}_{id}_{bitrate}"
result['settings'] = settings or {}
totalSize = len(playlistTracksAPI)
result['collection'] = []
for pos, trackAPI in enumerate(playlistTracksAPI, start=1):
if 'EXPLICIT_TRACK_CONTENT' in trackAPI and 'EXPLICIT_LYRICS_STATUS' in trackAPI['EXPLICIT_TRACK_CONTENT'] and trackAPI['EXPLICIT_TRACK_CONTENT']['EXPLICIT_LYRICS_STATUS'] in [1,4]:
playlistAPI['explicit'] = True
trackAPI['_EXTRA_PLAYLIST'] = playlistAPI
trackAPI['POSITION'] = pos
trackAPI['SIZE'] = totalSize
trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate']
result['collection'].append(trackAPI)
if not 'explicit' in playlistAPI:
playlistAPI['explicit'] = False
elif type == "artist":
try:
artistAPI = dz.get_artist(id)
except APIError as e:
e = json.loads(str(e))
2020-06-29 11:58:06 +00:00
result['error'] = f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}"
return result
if interface:
interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
artistAPITracks = dz.get_artist_albums(id)
albumList = []
for album in artistAPITracks['data']:
albumList.append(generateQueueItem(dz, sp, album['link'], settings, bitrate))
if interface:
interface.send("finishAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
return albumList
elif type == "artistdiscography":
try:
artistAPI = dz.get_artist(id)
except APIError as e:
e = json.loads(str(e))
result['error'] = f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}"
return result
if interface:
interface.send("startAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
artistDiscographyAPI = dz.get_artist_discography_gw(id, 100)
albumList = []
for type in artistDiscographyAPI:
if type != 'all':
for album in artistDiscographyAPI[type]:
albumList.append(generateQueueItem(dz, sp, album['link'], settings, bitrate))
if interface:
interface.send("finishAddingArtist", {'name': artistAPI['name'], 'id': artistAPI['id']})
return albumList
elif type == "artisttop":
try:
artistAPI = dz.get_artist(id)
except APIError as e:
e = json.loads(str(e))
result['error'] = f"Wrong URL: {e['type']+': ' if 'type' in e else ''}{e['message'] if 'message' in e else ''}"
return result
playlistAPI = {
'id': str(artistAPI['id'])+"_top_track",
'title': artistAPI['name']+" - Top Tracks",
'description': "Top Tracks for "+artistAPI['name'],
'duration': 0,
'public': True,
'is_loved_track': False,
'collaborative': False,
'nb_tracks': 0,
'fans': artistAPI['nb_fan'],
'link': "https://www.deezer.com/artist/"+str(artistAPI['id'])+"/top_track",
'share': None,
'picture': artistAPI['picture'],
'picture_small': artistAPI['picture_small'],
'picture_medium': artistAPI['picture_medium'],
'picture_big': artistAPI['picture_big'],
'picture_xl': artistAPI['picture_xl'],
'checksum': None,
'tracklist': "https://api.deezer.com/artist/"+str(artistAPI['id'])+"/top",
'creation_date': "XXXX-00-00",
'creator': {
'id': "art_"+str(artistAPI['id']),
'name': artistAPI['name'],
'type': "user"
},
'type': "playlist"
}
artistTopTracksAPI_gw = dz.get_artist_toptracks_gw(id)
playlistAPI['various_artist'] = dz.get_artist(5080)
playlistAPI['nb_tracks'] = len(artistTopTracksAPI_gw)
result['title'] = playlistAPI['title']
result['artist'] = playlistAPI['creator']['name']
result['cover'] = playlistAPI['picture_small'][:-24] + '/75x75-000000-80-0-0.jpg'
result['size'] = playlistAPI['nb_tracks']
result['downloaded'] = 0
result['failed'] = 0
result['errors'] = []
result['progress'] = 0
result['type'] = 'playlist'
result['id'] = id
result['bitrate'] = bitrate
result['uuid'] = f"{result['type']}_{id}_{bitrate}"
result['settings'] = settings or {}
totalSize = len(artistTopTracksAPI_gw)
result['collection'] = []
for pos, trackAPI in enumerate(artistTopTracksAPI_gw, start=1):
if 'EXPLICIT_TRACK_CONTENT' in trackAPI and 'EXPLICIT_LYRICS_STATUS' in trackAPI['EXPLICIT_TRACK_CONTENT'] and trackAPI['EXPLICIT_TRACK_CONTENT']['EXPLICIT_LYRICS_STATUS'] in [1,4]:
playlistAPI['explicit'] = True
trackAPI['_EXTRA_PLAYLIST'] = playlistAPI
trackAPI['POSITION'] = pos
trackAPI['SIZE'] = totalSize
trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate']
result['collection'].append(trackAPI)
if not 'explicit' in playlistAPI:
playlistAPI['explicit'] = False
elif type == "spotifytrack":
if not sp.spotifyEnabled:
logger.warn("Spotify Features is not setted up correctly.")
result['error'] = "Spotify Features is not setted up correctly."
2020-07-23 14:41:11 +00:00
result['errid'] = "spotifyDisabled"
return result
2020-06-29 11:58:06 +00:00
try:
track_id = sp.get_trackid_spotify(dz, id, settings['fallbackSearch'])
except SpotifyException as e:
result['error'] = "Wrong URL: "+e.msg[e.msg.find('\n')+2:]
return result
if track_id != 0:
return generateQueueItem(dz, sp, f'https://www.deezer.com/track/{track_id}', settings, bitrate)
else:
logger.warn("Track not found on deezer!")
result['error'] = "Track not found on deezer!"
2020-07-23 14:41:11 +00:00
result['errid'] = "trackNotOnDeezer"
elif type == "spotifyalbum":
if not sp.spotifyEnabled:
logger.warn("Spotify Features is not setted up correctly.")
result['error'] = "Spotify Features is not setted up correctly."
2020-07-23 14:41:11 +00:00
result['errid'] = "spotifyDisabled"
return result
2020-06-29 11:58:06 +00:00
try:
album_id = sp.get_albumid_spotify(dz, id)
except SpotifyException as e:
result['error'] = "Wrong URL: "+e.msg[e.msg.find('\n')+2:]
return result
if album_id != 0:
return generateQueueItem(dz, sp, f'https://www.deezer.com/album/{album_id}', settings, bitrate)
else:
logger.warn("Album not found on deezer!")
result['error'] = "Album not found on deezer!"
2020-07-23 14:41:11 +00:00
result['errid'] = "albumNotOnDeezer"
elif type == "spotifyplaylist":
if not sp.spotifyEnabled:
logger.warn("Spotify Features is not setted up correctly.")
result['error'] = "Spotify Features is not setted up correctly."
2020-07-23 14:41:11 +00:00
result['errid'] = "spotifyDisabled"
return result
2020-06-29 11:58:06 +00:00
try:
playlist = sp.adapt_spotify_playlist(dz, id, settings)
2020-06-29 11:58:06 +00:00
except SpotifyException as e:
result['error'] = "Wrong URL: "+e.msg[e.msg.find('\n')+2:]
return result
playlist['bitrate'] = bitrate
playlist['uuid'] = f"{playlist['type']}_{id}_{bitrate}"
result = playlist
else:
logger.warn("URL not supported yet")
result['error'] = "URL not supported yet"
2020-07-23 14:41:11 +00:00
result['errid'] = "unsupportedURL"
return result
def addToQueue(dz, sp, url, settings, bitrate=None, interface=None):
global currentItem, queueList, queue
if not dz.logged_in:
return "Not logged in"
if type(url) is list:
queueItem = []
for link in url:
link = link.strip()
if link == "":
continue
logger.info("Generating queue item for: "+link)
item = generateQueueItem(dz, sp, link, settings, bitrate, interface=interface)
if type(item) is list:
queueItem += item
else:
queueItem.append(item)
else:
url = url.strip()
if url == "":
return False
logger.info("Generating queue item for: "+url)
queueItem = generateQueueItem(dz, sp, url, settings, bitrate, interface=interface)
if type(queueItem) is list:
for x in queueItem:
if 'error' in x:
logger.error(f"[{x['link']}] {x['error']}")
continue
if x['uuid'] in list(queueList.keys()):
logger.warn(f"[{x['uuid']}] Already in queue, will not be added again.")
continue
if interface:
interface.send("addedToQueue", slimQueueItem(x))
queue.append(x['uuid'])
queueList[x['uuid']] = x
logger.info(f"[{x['uuid']}] Added to queue.")
else:
if 'error' in queueItem:
logger.error(f"[{queueItem['link']}] {queueItem['error']}")
if interface:
2020-07-23 14:41:11 +00:00
interface.send("queueError", queueItem)
return False
if queueItem['uuid'] in list(queueList.keys()):
logger.warn(f"[{queueItem['uuid']}] Already in queue, will not be added again.")
if interface:
interface.send("alreadyInQueue", {'uuid': queueItem['uuid'], 'title': queueItem['title']})
return False
if interface:
interface.send("addedToQueue", slimQueueItem(queueItem))
logger.info(f"[{queueItem['uuid']}] Added to queue.")
queue.append(queueItem['uuid'])
queueList[queueItem['uuid']] = queueItem
nextItem(dz, sp, interface)
return True
def nextItem(dz, sp, interface=None):
global currentItem, queueList, queue
if currentItem != "":
return None
else:
if len(queue) > 0:
currentItem = queue.pop(0)
else:
return None
if interface:
interface.send("startDownload", currentItem)
logger.info(f"[{currentItem}] Started downloading.")
result = download(dz, sp, queueList[currentItem], interface)
callbackQueueDone(result)
def callbackQueueDone(result):
global currentItem, queueList, queueComplete
if 'cancel' in queueList[currentItem]:
del queueList[currentItem]
else:
queueComplete.append(currentItem)
logger.info(f"[{currentItem}] Finished downloading.")
currentItem = ""
nextItem(result['dz'], result['sp'], result['interface'])
2020-04-11 13:43:59 +00:00
def getQueue():
global currentItem, queueList, queue, queueComplete
return (queue, queueComplete, queueList, currentItem)
def restoreQueue(pqueue, pqueueComplete, pqueueList, dz, interface):
global currentItem, queueList, queue, queueComplete
queueComplete = pqueueComplete
queueList = pqueueList
queue = pqueue
nextItem(dz, interface)
def removeFromQueue(uuid, interface=None):
global currentItem, queueList, queue, queueComplete
if uuid == currentItem:
if interface:
interface.send("cancellingCurrentItem", currentItem)
queueList[uuid]['cancel'] = True
elif uuid in queue:
queue.remove(uuid)
del queueList[uuid]
if interface:
interface.send("removedFromQueue", uuid)
elif uuid in queueComplete:
queueComplete.remove(uuid)
del queueList[uuid]
if interface:
interface.send("removedFromQueue", uuid)
def cancelAllDownloads(interface=None):
global currentItem, queueList, queue, queueComplete
queue = []
queueComplete = []
if currentItem != "":
if interface:
interface.send("cancellingCurrentItem", currentItem)
queueList[currentItem]['cancel'] = True
for uuid in list(queueList.keys()):
if uuid != currentItem:
del queueList[uuid]
if interface:
interface.send("removedAllDownloads", currentItem)
2020-04-14 14:48:13 +00:00
def removeFinishedDownloads(interface=None):
global queueList, queueComplete
for uuid in queueComplete:
del queueList[uuid]
queueComplete = []
if interface:
interface.send("removedFinishedDownloads")