deemix-py/deemix/app/downloader.py

615 lines
25 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
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
import os.path
from os import makedirs, remove
from requests import get
from requests.exceptions import HTTPError
from tempfile import gettempdir
from concurrent.futures import ThreadPoolExecutor
import re
TEMPDIR = os.path.join(gettempdir(), 'deezloader-imgs')
if not os.path.isdir(TEMPDIR):
makedirs(TEMPDIR)
extensions = {
9: '.flac',
3: '.mp3',
1: '.mp3',
8: '.mp3',
15: '.mp4',
14: '.mp4',
13: '.mp4'
}
def downloadImage(url, path):
if not os.path.isfile(path):
with open(path, 'wb') as f:
try:
f.write(get(url, headers={'User-Agent': USER_AGENT_HEADER}).content)
return path
except HTTPError:
print("Couldn't download Image")
remove(path)
return None
else:
return path
def formatDate(date, template):
if 'YYYY' in template:
template = template.replace('YYYY', str(date['year']))
if 'YY' in template:
template = template.replace('YY', str(date['year']))
if 'Y' in template:
template = template.replace('Y', str(date['year']))
if 'MM' in template:
template = template.replace('MM', str(date['month']))
if 'M' in template:
template = template.replace('M', str(date['month']))
if 'DD' in template:
template = template.replace('DD', str(date['day']))
if 'D' in template:
template = template.replace('D', str(date['day']))
return template
def getPreferredBitrate(filesize, bitrate, fallback=True):
if not fallback:
formats = {9: 'flac', 3: 'mp3_320', 1: 'mp3_128', 15: '360_hq', 14: '360_mq', 13: '360_lq'}
if filesize[formats[int(bitrate)]] > 0:
return (int(bitrate), filesize[formats[int(bitrate)]])
else:
return (-100, 0)
if int(bitrate) in [13,14,15]:
formats = {'360_hq': 15, '360_mq': 14, '360_lq': 13}
selectedFormat = -200
selectedFilesize = 0
for format, formatNum in formats.items():
if formatNum <= int(bitrate) and filesize[format] > 0:
selectedFormat = formatNum
selectedFilesize = filesize[format]
break
else:
formats = {'flac': 9, 'mp3_320': 3, 'mp3_128': 1}
selectedFormat = 8
selectedFilesize = filesize['default']
for format, formatNum in formats.items():
if formatNum <= int(bitrate) and filesize[format] > 0:
selectedFormat = formatNum
selectedFilesize = filesize[format]
break
return (selectedFormat, selectedFilesize)
def convertMetadata2Deezer(dz, artist, track, album):
artist = artist.replace("","-").replace("", "'")
track = track.replace("","-").replace("", "'")
album = album.replace("","-").replace("", "'")
resp = dz.search(f'artist:"{artist}" track:"{track}" album:"{album}"', "track", 1)
if len(resp['data'])>0:
return resp['data'][0]['id']
resp = dz.search(f'artist:"{artist}" track:"{track}"', "track", 1)
if len(resp['data'])>0:
return resp['data'][0]['id']
if "(" in track and ")" in track and track.find("(") < track.find(")"):
resp = dz.search(f'artist:"{artist}" track:"{track[:track.find("(")]}"', "track", 1)
if len(resp['data'])>0:
return resp['data'][0]['id']
elif " - " in track:
resp = dz.search(f'artist:"{artist}" track:"{track[:track.find(" - ")]}"', "track", 1)
if len(resp['data'])>0:
return resp['data'][0]['id']
else:
return 0
return 0
def parseEssentialTrackData(track, trackAPI):
track['id'] = trackAPI['SNG_ID']
track['duration'] = trackAPI['DURATION']
track['MD5'] = trackAPI['MD5_ORIGIN']
track['mediaVersion'] = trackAPI['MEDIA_VERSION']
if 'FALLBACK' in trackAPI:
track['fallbackId'] = trackAPI['FALLBACK']['SNG_ID']
else:
track['fallbackId'] = 0
track['filesize'] = {}
track['filesize']['default'] = int(trackAPI['FILESIZE']) if 'FILESIZE' in trackAPI else 0
track['filesize']['mp3_128'] = int(trackAPI['FILESIZE_MP3_128']) if 'FILESIZE_MP3_128' in trackAPI else 0
track['filesize']['mp3_320'] = int(trackAPI['FILESIZE_MP3_320']) if 'FILESIZE_MP3_320' in trackAPI else 0
track['filesize']['flac'] = int(trackAPI['FILESIZE_FLAC']) if 'FILESIZE_FLAC' in trackAPI else 0
track['filesize']['360_lq'] = int(trackAPI['FILESIZE_MP4_RA1']) if 'FILESIZE_MP4_RA1' in trackAPI else 0
track['filesize']['360_mq'] = int(trackAPI['FILESIZE_MP4_RA2']) if 'FILESIZE_MP4_RA2' in trackAPI else 0
track['filesize']['360_hq'] = int(trackAPI['FILESIZE_MP4_RA3']) if 'FILESIZE_MP4_RA3' in trackAPI else 0
return track
def getTrackData(dz, trackAPI_gw, trackAPI = None, albumAPI_gw = None, albumAPI = None):
if not 'MD5_ORIGIN' in trackAPI_gw:
trackAPI_gw['MD5_ORIGIN'] = dz.get_track_md5(trackAPI_gw['SNG_ID'])
track = {}
track['title'] = trackAPI_gw['SNG_TITLE']
if 'VERSION' in trackAPI_gw and trackAPI_gw['VERSION']:
track['title'] += " " + trackAPI_gw['VERSION']
track = parseEssentialTrackData(track, trackAPI_gw)
if int(track['id']) < 0:
track['filesize'] = trackAPI_gw['FILESIZE']
track['album'] = {}
track['album']['id'] = 0
track['album']['title'] = trackAPI_gw['ALB_TITLE']
if 'ALB_PICTURE' in trackAPI_gw:
track['album']['pic'] = trackAPI_gw['ALB_PICTURE']
track['mainArtist'] = {}
track['mainArtist']['id'] = 0
track['mainArtist']['name'] = trackAPI_gw['ART_NAME']
track['artists'] = [trackAPI_gw['ART_NAME']]
track['aritst'] = {
'Main': [trackAPI_gw['ART_NAME']]
}
track['date'] = {
'day': 0,
'month': 0,
'year': 0
}
track['localTrack'] = True
return track
if 'DISK_NUMBER' in trackAPI_gw:
track['discNumber'] = trackAPI_gw['DISK_NUMBER']
if 'EXPLICIT_LYRICS' in trackAPI_gw:
track['explicit'] = trackAPI_gw['EXPLICIT_LYRICS'] != "0"
if 'COPYRIGHT' in trackAPI_gw:
track['copyright'] = trackAPI_gw['COPYRIGHT']
track['replayGain'] = "{0:.2f} dB".format((float(trackAPI_gw['GAIN']) + 18.4) * -1) if 'GAIN' in trackAPI_gw else None
track['ISRC'] = trackAPI_gw['ISRC']
track['trackNumber'] = trackAPI_gw['TRACK_NUMBER']
track['contributors'] = trackAPI_gw['SNG_CONTRIBUTORS']
if 'POSITION' in trackAPI_gw:
track['position'] = trackAPI_gw['POSITION']
track['lyrics'] = {}
if 'LYRICS_ID' in trackAPI_gw:
track['lyrics']['id'] = trackAPI_gw['LYRICS_ID']
if not "LYRICS" in trackAPI_gw and int(track['lyrics']['id']) != 0:
trackAPI_gw["LYRICS"] = dz.get_lyrics_gw(track['id'])
if int(track['lyrics']['id']) != 0:
if "LYRICS_TEXT" in trackAPI_gw["LYRICS"]:
track['lyrics']['unsync'] = trackAPI_gw["LYRICS"]["LYRICS_TEXT"]
if "LYRICS_SYNC_JSON" in trackAPI_gw["LYRICS"]:
track['lyrics']['sync'] = ""
for i in range(len(trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"])):
if "lrc_timestamp" in trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]:
track['lyrics']['sync'] += trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]["lrc_timestamp"] + \
trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]["line"] + "\r\n"
elif i + 1 < len(trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"]):
track['lyrics']['sync'] += trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i + 1]["lrc_timestamp"] + \
trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]["line"] + "\r\n"
track['mainArtist'] = {}
track['mainArtist']['id'] = trackAPI_gw['ART_ID']
track['mainArtist']['name'] = trackAPI_gw['ART_NAME']
if 'ART_PICTURE' in trackAPI_gw:
track['mainArtist']['pic'] = trackAPI_gw['ART_PICTURE']
if 'PHYSICAL_RELEASE_DATE' in trackAPI_gw:
track['date'] = {
'day': trackAPI_gw["PHYSICAL_RELEASE_DATE"][8:10],
'month': trackAPI_gw["PHYSICAL_RELEASE_DATE"][5:7],
'year': trackAPI_gw["PHYSICAL_RELEASE_DATE"][0:4]
}
track['album'] = {}
track['album']['id'] = trackAPI_gw['ALB_ID']
track['album']['title'] = trackAPI_gw['ALB_TITLE']
if 'ALB_PICTURE' in trackAPI_gw:
track['album']['pic'] = trackAPI_gw['ALB_PICTURE']
try:
if not albumAPI:
albumAPI = dz.get_album(track['album']['id'])
track['album']['mainArtist'] = {
'id': albumAPI['artist']['id'],
'name': albumAPI['artist']['name'],
'pic': albumAPI['artist']['picture_small'][albumAPI['artist']['picture_small'].find('artist/')+7:-24]
}
track['album']['artist'] = {}
track['album']['artists'] = []
for artist in albumAPI['contributors']:
if artist['id'] != 5080:
track['album']['artists'].append(artist['name'])
if not artist['role'] in track['album']['artist']:
track['album']['artist'][artist['role']] = []
track['album']['artist'][artist['role']].append(artist['name'])
track['album']['trackTotal'] = albumAPI['nb_tracks']
track['album']['recordType'] = albumAPI['record_type']
track['album']['barcode'] = albumAPI['upc'] if 'upc' in albumAPI else "Unknown"
track['album']['label'] = albumAPI['label'] if 'label' in albumAPI else "Unknown"
if not 'pic' in track['album']:
track['album']['pic'] = albumAPI['cover_small'][albumAPI['cover_small'].find('cover/')+6:-24]
if 'release_date' in albumAPI:
track['album']['date'] = {
'day': albumAPI["release_date"][8:10],
'month': albumAPI["release_date"][5:7],
'year': albumAPI["release_date"][0:4]
}
track['album']['discTotal'] = albumAPI['nb_disk'] if 'nb_disk' in albumAPI else None
track['copyright'] = albumAPI['copyright'] if 'copyright' in albumAPI else None
track['album']['genre'] = []
if 'genres' in albumAPI and 'data' in albumAPI['genres'] and len(albumAPI['genres']['data']) > 0:
for genre in albumAPI['genres']['data']:
track['album']['genre'].append(genre['name'])
except APIError:
if not albumAPI_gw:
albumAPI_gw = dz.get_album_gw(track['album']['id'])
track['album']['mainArtist'] = {
'id': albumAPI_gw['ART_ID'],
'name': albumAPI_gw['ART_NAME']
}
artistAPI = dz.get_artist(track['album']['mainArtist']['id'])
track['album']['artists'] = albumAPI_gw['ART_NAME']
track['album']['mainArtist']['pic'] = artistAPI['picture_small'][artistAPI['picture_small'].find('artist/')+7:-24]
track['album']['trackTotal'] = albumAPI_gw['NUMBER_TRACK']
track['album']['discTotal'] = albumAPI_gw['NUMBER_DISK']
track['album']['recordType'] = "Album"
track['album']['barcode'] = "Unknown"
track['album']['label'] = albumAPI_gw['LABEL_NAME'] if 'LABEL_NAME' in albumAPI_gw else "Unknown"
if not 'pic' in track['album']:
track['album']['pic'] = albumAPI_gw['ALB_PICTURE']
if 'PHYSICAL_RELEASE_DATE' in albumAPI_gw:
track['album']['date'] = {
'day': albumAPI_gw["PHYSICAL_RELEASE_DATE"][8:10],
'month': albumAPI_gw["PHYSICAL_RELEASE_DATE"][5:7],
'year': albumAPI_gw["PHYSICAL_RELEASE_DATE"][0:4]
}
track['album']['genre'] = []
if 'date' in track['album']:
track['date'] = track['album']['date']
if not trackAPI:
trackAPI = dz.get_track(track['id'])
track['bpm'] = trackAPI['bpm']
if not 'replayGain' in track:
track['replayGain'] = "{0:.2f} dB".format((float(trackAPI['gain']) + 18.4) * -1) if 'gain' in trackAPI else ""
if not 'explicit' in track:
track['explicit'] = trackAPI['explicit_lyrics']
if not 'discNumber' in track:
track['discNumber'] = trackAPI['disk_number']
track['artist'] = {}
track['artists'] = []
for artist in trackAPI['contributors']:
if artist['id'] != 5080:
track['artists'].append(artist['name'])
if not artist['role'] in track['artist']:
track['artist'][artist['role']] = []
track['artist'][artist['role']].append(artist['name'])
if not 'discTotal' in track['album'] or not track['album']['discTotal']:
if not albumAPI_gw:
albumAPI_gw = dz.get_album_gw(track['album']['id'])
track['album']['discTotal'] = albumAPI_gw['NUMBER_DISK']
if not 'copyright' in track or not track['copyright']:
if not albumAPI_gw:
albumAPI_gw = dz.get_album_gw(track['album']['id'])
track['copyright'] = albumAPI_gw['COPYRIGHT']
# Fix incorrect day month when detectable
if int(track['date']['month']) > 12:
monthTemp = track['date']['month']
track['date']['month'] = track['date']['day']
track['date']['day'] = monthTemp
if int(track['album']['date']['month']) > 12:
monthTemp = track['album']['date']['month']
track['album']['date']['month'] = track['album']['date']['day']
track['album']['date']['day'] = monthTemp
# Remove featuring from the title
track['title_clean'] = track['title']
if "(feat." in track['title_clean'].lower():
pos = track['title_clean'].lower().find("(feat.")
tempTrack = track['title_clean'][:pos]
if ")" in track['title_clean']:
tempTrack += track['title_clean'][track['title_clean'].find(")",pos+1)+1:]
track['title_clean'] = tempTrack.strip()
# Create artists strings
track['mainArtistsString'] = ""
if 'Main' in track['artist']:
tot = len(track['artist']['Main'])
for i, art in enumerate(track['artist']['Main']):
track['mainArtistsString'] += art
if tot != i+1:
if tot-1 == i+1:
track['mainArtistsString'] += " & "
else:
track['mainArtistsString'] += ", "
else:
track['mainArtistsString'] = track['mainArtist']['name']
if 'Featured' in track['artist']:
tot = len(track['artist']['Featured'])
track['featArtistsString'] = "feat. "
for i, art in enumerate(track['artist']['Featured']):
track['featArtistsString'] += art
if tot != i+1:
if tot-1 == i+1:
track['featArtistsString'] += " & "
else:
track['featArtistsString'] += ", "
# Create title with feat
if "(feat." in track['title'].lower():
track['title_feat'] = track['title']
elif 'Featured' in track['artist']:
track['title_feat'] = track['title']+" ({})".format(track['featArtistsString'])
else:
track['title_feat'] = track['title']
return track
def downloadTrackObj(dz, trackAPI, settings, overwriteBitrate=False, extraTrack=None):
result = {}
# Get the metadata
if extraTrack:
track = extraTrack
else:
track = getTrackData(dz,
trackAPI_gw = trackAPI,
trackAPI = trackAPI['_EXTRA_TRACK'] if '_EXTRA_TRACK' in trackAPI else None,
albumAPI = trackAPI['_EXTRA_ALBUM'] if '_EXTRA_ALBUM' in trackAPI else None
)
print('Downloading: {} - {}'.format(track['mainArtist']['name'], track['title']))
if track['MD5'] == '':
if track['fallbackId'] != 0:
print("Track not yet encoded, using fallback id")
trackNew = dz.get_track_gw(track['fallbackId'])
if not 'MD5_ORIGIN' in trackNew:
trackNew['MD5_ORIGIN'] = dz.get_track_md5(trackNew['SNG_ID'])
track = parseEssentialTrackData(track, trackNew)
return downloadTrackObj(dz, trackAPI, settings, extraTrack=track)
elif not 'searched' in track and settings['fallbackSearch']:
print("Track not yet encoded, searching for alternative")
searchedId = convertMetadata2Deezer(dz, track['mainArtist']['name'], track['title'], track['album']['title'])
if searchedId != 0:
trackNew = dz.get_track_gw(searchedId)
if not 'MD5_ORIGIN' in trackNew:
trackNew['MD5_ORIGIN'] = dz.get_track_md5(trackNew['SNG_ID'])
track = parseEssentialTrackData(track, trackNew)
track['searched'] = True
return downloadTrackObj(dz, trackAPI, settings, extraTrack=track)
else:
print("ERROR: Track not yet encoded and no alternative found!")
result['error'] = {
'message': "Track not yet encoded and no alternative found!",
'data': track
}
return result
else:
print("ERROR: Track not yet encoded!")
result['error'] = {
'message': "Track not yet encoded!",
'data': track
}
return result
# Get the selected bitrate
bitrate = overwriteBitrate if overwriteBitrate else settings['maxBitrate']
(format, filesize) = getPreferredBitrate(track['filesize'], bitrate, settings['fallbackBitrate'])
if format == -100:
print("ERROR: Track not found at desired bitrate. Enable fallback to lower bitrates to fix this issue.")
result['error'] = {
'message': "Track not found at desired bitrate.",
'data': track
}
return result
elif format == -200:
print("ERROR: This track is not available in 360 Reality Audio format. Please select another format.")
result['error'] = {
'message': "Track is not available in Reality Audio 360.",
'data': track
}
return result
track['selectedFormat'] = format
track['selectedFilesize'] = filesize
track['album']['bitrate'] = format
track['album']['picUrl'] = "https://e-cdns-images.dzcdn.net/images/cover/{}/{}x{}-000000-80-0-0.{}".format(track['album']['pic'], settings['embeddedArtworkSize'], settings['embeddedArtworkSize'], 'png' if settings['PNGcovers'] else 'jpg')
track['dateString'] = formatDate(track['date'], settings['dateFormat'])
track['album']['dateString'] = formatDate(track['album']['date'], settings['dateFormat'])
# Remove (Album Version) from tracks that have that
if settings['removeAlbumVersion']:
if "Album Version" in track['title']:
track['title'] = re.sub(r' ?\(Album Version\)', "", track['title']).strip()
track['title_clean'] = re.sub(r' ?\(Album Version\)', "", track['title_clean']).strip()
track['title_feat'] = re.sub(r' ?\(Album Version\)', "", track['title_feat']).strip()
# Generate filename and filepath from metadata
filename = generateFilename(track, trackAPI, settings)
(filepath, artistPath, coverPath, extrasPath) = generateFilepath(track, trackAPI, settings)
# Download and cache coverart
track['album']['picPath'] = os.path.join(TEMPDIR, f"alb{track['album']['id']}_{settings['embeddedArtworkSize']}.{'png' if settings['PNGcovers'] else 'jpg'}")
track['album']['picPath'] = downloadImage(track['album']['picUrl'], track['album']['picPath'])
makedirs(filepath, exist_ok=True)
writepath = os.path.join(filepath, filename + extensions[track['selectedFormat']])
# Save lyrics in lrc file
if settings['syncedLyrics'] and 'sync' in track['lyrics']:
with open(os.path.join(filepath, filename + '.lrc'), 'w') as f:
f.write(track['lyrics']['sync'])
# Save local album art
if coverPath:
result['albumURL'] = track['album']['picUrl'].replace(f"{settings['embeddedArtworkSize']}x{settings['embeddedArtworkSize']}", f"{settings['localArtworkSize']}x{settings['localArtworkSize']}")
result['albumPath'] = os.path.join(coverPath, f"{settingsRegexAlbum(settings['coverImageTemplate'], track['album'], settings)}.{'png' if settings['PNGcovers'] else 'jpg'}")
# Save artist art
if artistPath:
result['artistURL'] = "https://e-cdns-images.dzcdn.net/images/artist/{}/{}x{}-000000-80-0-0.{}".format(track['album']['artist']['pic'], settings['localArtworkSize'], settings['localArtworkSize'], 'png' if settings['PNGcovers'] else 'jpg')
result['artistPath'] = os.path.join(artistPath, f"{settingsRegexArtist(settings['artistImageTemplate'], track['album']['artist'], settings)}.{'png' if settings['PNGcovers'] else 'jpg'}")
# Data for m3u file
if extrasPath:
result['extrasPath'] = extrasPath
result['playlistPosition'] = writepath[len(extrasPath):]
# Generate artist tag if needed
if settings['multitagSeparator'] != "default":
if settings['multitagSeparator'] == "andFeat":
track['artistsString'] = track['mainArtistsString']
if 'featArtistsString' in track:
track['artistsString'] += " "+track['featArtistsString']
else:
track['artistsString'] = settings['multitagSeparator'].join(track['artists'])
track['downloadUrl'] = dz.get_track_stream_url(track['id'], track['MD5'], track['mediaVersion'], track['selectedFormat'])
try:
with open(writepath, 'wb') as stream:
dz.stream_track(track['id'], track['downloadUrl'], stream)
except HTTPError:
remove(writepath)
if track['selectedFormat'] == 9 and settings['fallbackBitrate']:
print("Track not available in flac, trying mp3")
track['filesize']['flac'] = 0
return downloadTrackObj(dz, trackAPI, settings, extraTrack=track)
elif track['fallbackId'] != 0:
print("Track not available, using fallback id")
trackNew = dz.get_track_gw(track['fallbackId'])
if not 'MD5_ORIGIN' in trackNew:
trackNew['MD5_ORIGIN'] = dz.get_track_md5(trackNew['SNG_ID'])
track = parseEssentialTrackData(track, trackNew)
return downloadTrackObj(dz, trackAPI, settings, extraTrack=track)
elif not 'searched' in track and settings['fallbackSearch']:
print("Track not available, searching for alternative")
searchedId = convertMetadata2Deezer(dz, track['mainArtist']['name'], track['title'], track['album']['title'])
if searchedId != 0:
trackNew = dz.get_track_gw(searchedId)
if not 'MD5_ORIGIN' in trackNew:
trackNew['MD5_ORIGIN'] = dz.get_track_md5(trackNew['SNG_ID'])
track = parseEssentialTrackData(track, trackNew)
track['searched'] = True
return downloadTrackObj(dz, trackAPI, settings, extraTrack=track)
else:
print("ERROR: Track not available on deezer's servers and no alternative found!")
result['error'] = {
'message': "Track not available on deezer's servers and no alternative found!",
'data': track
}
return result
else:
print("ERROR: Track not available on deezer's servers!")
result['error'] = {
'message': "Track not available on deezer's servers!",
'data': track
}
return result
if track['selectedFormat'] in [3, 1, 8]:
tagID3(writepath, track, settings['tags'], settings['saveID3v1'], settings['useNullSeparator'])
elif track['selectedFormat'] == 9:
tagFLAC(writepath, track, settings['tags'])
if 'searched' in track:
result['searched'] = f'{track["mainArtist"]["name"]} - {track["title"]}'
print("Done!")
return result
def after_download(tracks, settings):
extrasPath = None
playlist = [None] * len(tracks)
errors = ""
searched = ""
for index in range(len(tracks)):
result = tracks[index].result()
if 'error' in result:
errors += f"{result['error']['data']['id']} | {result['error']['data']['mainArtist']['name']} - {result['error']['data']['title']} | {result['error']['message']}\r\n"
if 'searched' in result:
searched += result['searched']+"\r\n"
if not extrasPath and 'extrasPath' in result:
extrasPath = result['extrasPath']
if settings['saveArtwork'] and result['albumPath']:
downloadImage(result['albumURL'], result['albumPath'])
if settings['saveArtworkArtist'] and result['artistPath']:
downloadImage(result['artistURL'], result['artistPath'])
if 'playlistPosition' in result:
playlist[index] = result['playlistPosition']
else:
playlist[index] = ""
if settings['logErrors'] and extrasPath and errors != "":
with open(os.path.join(extrasPath, 'errors.txt'), 'w') as f:
f.write(errors)
if settings['logSearched'] and extrasPath and searched != "":
with open(os.path.join(extrasPath, 'searched.txt'), 'w') as f:
f.write(searched)
if settings['createM3U8File'] and extrasPath:
with open(os.path.join(extrasPath, 'playlist.m3u8'), 'w') as f:
for line in playlist:
f.write(line+"\n")
return extrasPath
def after_download_single(track, settings):
if settings['logSearched'] and 'extrasPath' in track and 'searched' in track:
with open(os.path.join(track['extrasPath'], 'searched.txt'), 'w+') as f:
orig = f.read()
if not track['searched'] in orig:
if orig != "":
orig += "\r\n"
orig += track['searched']+"\r\n"
f.write(orig)
if 'extrasPath' in track:
return track['extrasPath']
else:
return None
def download_track(dz, id, settings, overwriteBitrate=False):
trackAPI = dz.get_track_gw(id)
trackAPI['FILENAME_TEMPLATE'] = settings['tracknameTemplate']
trackAPI['SINGLE_TRACK'] = True
result = downloadTrackObj(dz, trackAPI, settings, overwriteBitrate)
return after_download_single(result, settings)
def download_album(dz, id, settings, overwriteBitrate=False):
albumAPI = dz.get_album(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:
trackAPI = dz.get_track_gw(albumAPI['tracks']['data'][0]['id'])
trackAPI['_EXTRA_ALBUM'] = albumAPI
trackAPI['FILENAME_TEMPLATE'] = settings['tracknameTemplate']
trackAPI['SINGLE_TRACK'] = True
result = downloadTrackObj(dz, trackAPI, settings, overwriteBitrate)
return after_download_single(result, settings)
else:
tracksArray = dz.get_album_tracks_gw(id)
playlist = [None] * len(tracksArray)
with ThreadPoolExecutor(settings['queueConcurrency']) as executor:
for pos, trackAPI in enumerate(tracksArray, start=1):
trackAPI['_EXTRA_ALBUM'] = albumAPI
trackAPI['POSITION'] = pos
trackAPI['FILENAME_TEMPLATE'] = settings['albumTracknameTemplate']
playlist[pos-1] = executor.submit(downloadTrackObj, dz, trackAPI, settings, overwriteBitrate)
return after_download(playlist, settings)
def download_artist(dz, id, settings, overwriteBitrate=False):
artistAPI = dz.get_artist_albums(id)
for album in artistAPI['data']:
print(f"Album: {album['title']}")
download_album(dz, album['id'], settings, overwriteBitrate)
def download_playlist(dz, id, settings, overwriteBitrate=False):
playlistAPI = dz.get_playlist(id)
playlistTracksAPI = dz.get_playlist_tracks_gw(id)
playlist = [None] * len(playlistTracksAPI)
with ThreadPoolExecutor(settings['queueConcurrency']) as executor:
for pos, trackAPI in enumerate(playlistTracksAPI, start=1):
trackAPI['_EXTRA_PLAYLIST'] = playlistAPI
trackAPI['POSITION'] = pos
trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate']
playlist[pos-1] = executor.submit(downloadTrackObj, dz, trackAPI, settings, overwriteBitrate)
return after_download(playlist, settings)