diff --git a/.gitignore b/.gitignore index 8714fbb..db1501c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__ /dist # local env files +/venv/ .env.local .env.*.local @@ -21,4 +22,4 @@ yarn-error.log* *.ntvs* *.njsproj *.sln -*.sw? \ No newline at end of file +*.sw? diff --git a/deemix/__main__.py b/deemix/__main__.py index 3ca28d8..c683f5b 100644 --- a/deemix/__main__.py +++ b/deemix/__main__.py @@ -1,16 +1,19 @@ #!/usr/bin/env python3 import click + import deemix.app.cli as app from deemix.app.settings import initSettings + @click.command() @click.option('-b', '--bitrate', default=None, help='Overwrites the default bitrate selected') @click.argument('url') def download(bitrate, url): - settings = initSettings() - app.login() - app.downloadLink(url, settings, bitrate) - click.echo("All done!") + settings = initSettings() + app.login() + app.downloadLink(url, settings, bitrate) + click.echo("All done!") + if __name__ == '__main__': - download() + download() diff --git a/deemix/api/__init__.py b/deemix/api/__init__.py index c5b2a57..9d389f1 100644 --- a/deemix/api/__init__.py +++ b/deemix/api/__init__.py @@ -1,2 +1,2 @@ #!/usr/bin/env python3 -#Empty File +# Empty File diff --git a/deemix/api/deezer.py b/deemix/api/deezer.py index 23d7161..506579d 100755 --- a/deemix/api/deezer.py +++ b/deemix/api/deezer.py @@ -1,332 +1,343 @@ #!/usr/bin/env python3 import binascii +import time + +import requests +from Cryptodome.Cipher import Blowfish, AES from Cryptodome.Hash import MD5 from Cryptodome.Util.Padding import pad -from Cryptodome.Cipher import Blowfish, AES -import requests - -import time - -USER_AGENT_HEADER = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36" +USER_AGENT_HEADER = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " \ + "Chrome/79.0.3945.130 Safari/537.36" class Deezer: - def __init__(self): - self.api_url = "http://www.deezer.com/ajax/gw-light.php" - self.legacy_api_url = "https://api.deezer.com/" - self.http_headers = { - "User-Agent": USER_AGENT_HEADER - } - self.album_pictures_host = "https://e-cdns-images.dzcdn.net/images/cover/" - self.artist_pictures_host = "https://e-cdns-images.dzcdn.net/images/artist/" - self.user = {} - self.session = requests.Session() - self.logged_in = False - self.session.post("http://www.deezer.com/", headers=self.http_headers) - self.sid = self.session.cookies.get('sid') + def __init__(self): + self.api_url = "http://www.deezer.com/ajax/gw-light.php" + self.legacy_api_url = "https://api.deezer.com/" + self.http_headers = { + "User-Agent": USER_AGENT_HEADER + } + self.album_pictures_host = "https://e-cdns-images.dzcdn.net/images/cover/" + self.artist_pictures_host = "https://e-cdns-images.dzcdn.net/images/artist/" + self.user = {} + self.session = requests.Session() + self.logged_in = False + self.session.post("http://www.deezer.com/", headers=self.http_headers) + self.sid = self.session.cookies.get('sid') - def get_token(self): - token_data = self.gw_api_call('deezer.getUserData') - return token_data["results"]["checkForm"] + def get_token(self): + token_data = self.gw_api_call('deezer.getUserData') + return token_data["results"]["checkForm"] - def get_track_md5(self, sng_id): - try: - site = self.session.post( - "https://api.deezer.com/1.0/gateway.php", - params={ - 'api_key': "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE", - 'sid': self.sid, - 'input': '3', - 'output': '3', - 'method': 'song_getData' - }, - timeout=30, - json={'sng_id': sng_id}, - headers=self.http_headers - ) - except: - time.sleep(2) - return self.get_track_md5(sng_id) - response = site.json() - return response['results']['PUID'] + def get_track_md5(self, sng_id): + try: + site = self.session.post( + "https://api.deezer.com/1.0/gateway.php", + params={ + 'api_key': "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE", + 'sid': self.sid, + 'input': '3', + 'output': '3', + 'method': 'song_getData' + }, + timeout=30, + json={'sng_id': sng_id}, + headers=self.http_headers + ) + except: + time.sleep(2) + return self.get_track_md5(sng_id) + response = site.json() + return response['results']['PUID'] - def gw_api_call(self, method, args={}): - try: - result = self.session.post( - self.api_url, - params={ - 'api_version': "1.0", - 'api_token': 'null' if method == 'deezer.getUserData' else self.get_token(), - 'input': '3', - 'method': method - }, - timeout=30, - json=args, - headers=self.http_headers - ) - except: - time.sleep(2) - return self.gw_api_call(method, args) - return result.json() + def gw_api_call(self, method, args=None): + if args is None: + args = {} + try: + result = self.session.post( + self.api_url, + params={ + 'api_version': "1.0", + 'api_token': 'null' if method == 'deezer.getUserData' else self.get_token(), + 'input': '3', + 'method': method + }, + timeout=30, + json=args, + headers=self.http_headers + ) + except: + time.sleep(2) + return self.gw_api_call(method, args) + return result.json() - def api_call(self, method, args={}): - try: - result = self.session.get( - self.legacy_api_url + method, - params=args, - headers=self.http_headers, - timeout=30 - ) - result_json = result.json() - except: - time.sleep(2) - return self.api_call(method, args) - if 'error' in result_json.keys(): - raise APIError() - return result_json + def api_call(self, method, args=None): + if args is None: + args = {} + try: + result = self.session.get( + self.legacy_api_url + method, + params=args, + headers=self.http_headers, + timeout=30 + ) + result_json = result.json() + except: + time.sleep(2) + return self.api_call(method, args) + if 'error' in result_json.keys(): + raise APIError() + return result_json - def login(self, email, password, re_captcha_token): - check_form_login = self.gw_api_call("deezer.getUserData") - login = self.session.post( - "https://www.deezer.com/ajax/action.php", - data={ - 'type': 'login', - 'mail': email, - 'password': password, - 'checkFormLogin': check_form_login['results']['checkFormLogin'], - 'reCaptchaToken': re_captcha_token - }, - headers={'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', **self.http_headers} - ) - if 'success' not in login.text: - self.logged_in = False - return False - user_data = self.gw_api_call("deezer.getUserData") - self.user = { - 'email': email, - 'id': user_data["results"]["USER"]["USER_ID"], - 'name': user_data["results"]["USER"]["BLOG_NAME"], - 'picture': user_data["results"]["USER"]["USER_PICTURE"] if "USER_PICTURE" in user_data["results"][ - "USER"] else "" - } - self.logged_in = True - return True + def login(self, email, password, re_captcha_token): + check_form_login = self.gw_api_call("deezer.getUserData") + login = self.session.post( + "https://www.deezer.com/ajax/action.php", + data={ + 'type': 'login', + 'mail': email, + 'password': password, + 'checkFormLogin': check_form_login['results']['checkFormLogin'], + 'reCaptchaToken': re_captcha_token + }, + headers={'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', **self.http_headers} + ) + if 'success' not in login.text: + self.logged_in = False + return False + user_data = self.gw_api_call("deezer.getUserData") + self.user = { + 'email': email, + 'id': user_data["results"]["USER"]["USER_ID"], + 'name': user_data["results"]["USER"]["BLOG_NAME"], + 'picture': user_data["results"]["USER"]["USER_PICTURE"] if "USER_PICTURE" in user_data["results"][ + "USER"] else "" + } + self.logged_in = True + return True - def login_via_arl(self, arl): - cookie_obj = requests.cookies.create_cookie( - domain='deezer.com', - name='arl', - value=arl, - path="/", - rest={'HttpOnly': True} - ) - self.session.cookies.set_cookie(cookie_obj) - user_data = self.gw_api_call("deezer.getUserData") - if user_data["results"]["USER"]["USER_ID"] == 0: - self.logged_in = False - return 0 - self.user = { - 'id': user_data["results"]["USER"]["USER_ID"], - 'name': user_data["results"]["USER"]["BLOG_NAME"], - 'picture': user_data["results"]["USER"]["USER_PICTURE"] if "USER_PICTURE" in user_data["results"][ - "USER"] else "" - } - self.logged_in = True - return 1 + def login_via_arl(self, arl): + cookie_obj = requests.cookies.create_cookie( + domain='deezer.com', + name='arl', + value=arl, + path="/", + rest={'HttpOnly': True} + ) + self.session.cookies.set_cookie(cookie_obj) + user_data = self.gw_api_call("deezer.getUserData") + if user_data["results"]["USER"]["USER_ID"] == 0: + self.logged_in = False + return 0 + self.user = { + 'id': user_data["results"]["USER"]["USER_ID"], + 'name': user_data["results"]["USER"]["BLOG_NAME"], + 'picture': user_data["results"]["USER"]["USER_PICTURE"] if "USER_PICTURE" in user_data["results"][ + "USER"] else "" + } + self.logged_in = True + return 1 - def get_track_gw(self, sng_id): - if int(sng_id) < 0: - body = self.gw_api_call('song.getData', {'sng_id': sng_id}) - else: - body = self.gw_api_call('deezer.pageTrack', {'sng_id': sng_id}) - if 'LYRICS' in body['results']: - body['results']['DATA']['LYRICS'] = body['results']['LYRICS'] - body['results'] = body['results']['DATA'] - return body['results'] + def get_track_gw(self, sng_id): + if int(sng_id) < 0: + body = self.gw_api_call('song.getData', {'sng_id': sng_id}) + else: + body = self.gw_api_call('deezer.pageTrack', {'sng_id': sng_id}) + if 'LYRICS' in body['results']: + body['results']['DATA']['LYRICS'] = body['results']['LYRICS'] + body['results'] = body['results']['DATA'] + return body['results'] - def get_tracks_gw(self, ids): - tracks_array = [] - body = self.gw_api_call('song.getListData', {'sng_ids': ids}) - errors = 0 - for i in range(len(ids)): - if ids[i] != 0: - tracks_array.append(body['results']['data'][i - errors]) - else: - errors += 1 - tracks_array.append({ - 'SNG_ID': 0, - 'SNG_TITLE': '', - 'DURATION': 0, - 'MD5_ORIGIN': 0, - 'MEDIA_VERSION': 0, - 'FILESIZE': 0, - 'ALB_TITLE': "", - 'ALB_PICTURE': "", - 'ART_ID': 0, - 'ART_NAME': "" - }) - return tracks_array + def get_tracks_gw(self, ids): + tracks_array = [] + body = self.gw_api_call('song.getListData', {'sng_ids': ids}) + errors = 0 + for i in range(len(ids)): + if ids[i] != 0: + tracks_array.append(body['results']['data'][i - errors]) + else: + errors += 1 + tracks_array.append({ + 'SNG_ID': 0, + 'SNG_TITLE': '', + 'DURATION': 0, + 'MD5_ORIGIN': 0, + 'MEDIA_VERSION': 0, + 'FILESIZE': 0, + 'ALB_TITLE': "", + 'ALB_PICTURE': "", + 'ART_ID': 0, + 'ART_NAME': "" + }) + return tracks_array - def get_album_gw(self, alb_id): - return self.gw_api_call('album.getData', {'alb_id': alb_id})['results'] + def get_album_gw(self, alb_id): + return self.gw_api_call('album.getData', {'alb_id': alb_id})['results'] - def get_album_tracks_gw(self, alb_id): - tracks_array = [] - body = self.gw_api_call('song.getListByAlbum', {'alb_id': alb_id, 'nb': -1}) - for track in body['results']['data']: - _track = track - _track['position'] = body['results']['data'].index(track) - tracks_array.append(_track) - return tracks_array + def get_album_tracks_gw(self, alb_id): + tracks_array = [] + body = self.gw_api_call('song.getListByAlbum', {'alb_id': alb_id, 'nb': -1}) + for track in body['results']['data']: + _track = track + _track['position'] = body['results']['data'].index(track) + tracks_array.append(_track) + return tracks_array - def get_artist_gw(self, art_id): - return self.gw_api_call('deezer.pageArtist', {'art_id': art_id}) + def get_artist_gw(self, art_id): + return self.gw_api_call('deezer.pageArtist', {'art_id': art_id}) - def get_playlist_gw(self, playlist_id): - return self.gw_api_call('deezer.pagePlaylist', {'playlist_id': playlist_id}) + def get_playlist_gw(self, playlist_id): + return self.gw_api_call('deezer.pagePlaylist', {'playlist_id': playlist_id}) - def get_playlist_tracks_gw(self, playlist_id): - tracks_array = [] - body = self.gw_api_call('playlist.getSongs', {'playlist_id': playlist_id, 'nb': -1}) - for track in body['results']['data']: - track['position'] = body['results']['data'].index(track) - tracks_array.append(track) - return tracks_array + def get_playlist_tracks_gw(self, playlist_id): + tracks_array = [] + body = self.gw_api_call('playlist.getSongs', {'playlist_id': playlist_id, 'nb': -1}) + for track in body['results']['data']: + track['position'] = body['results']['data'].index(track) + tracks_array.append(track) + return tracks_array - def get_artist_toptracks_gw(self, art_id): - tracks_array = [] - body = self.gw_api_call('artist.getTopTrack', {'art_id': art_id, 'nb': 100}) - for track in body['results']['data']: - track['position'] = body['results']['data'].index(track) - tracks_array.append(track) - return tracks_array + def get_artist_toptracks_gw(self, art_id): + tracks_array = [] + body = self.gw_api_call('artist.getTopTrack', {'art_id': art_id, 'nb': 100}) + for track in body['results']['data']: + track['position'] = body['results']['data'].index(track) + tracks_array.append(track) + return tracks_array - def search_main_gw(self, term): - results = self.gw_api_call('deezer.pageSearch', {"query": term, "start": 0, "nb": 40, "suggest": True, "artist_suggest": True, "top_tracks": True})['results'] - order = [] - for x in results['ORDER']: - if x in ['TOP_RESULT', 'TRACK', 'ALBUM', 'ARTIST', 'PLAYLIST']: - order.append(x) - results['ORDER'] = order - return results + def search_main_gw(self, term): + results = self.gw_api_call('deezer.pageSearch', + {"query": term, "start": 0, "nb": 40, "suggest": True, "artist_suggest": True, + "top_tracks": True})['results'] + order = [] + for x in results['ORDER']: + if x in ['TOP_RESULT', 'TRACK', 'ALBUM', 'ARTIST', 'PLAYLIST']: + order.append(x) + results['ORDER'] = order + return results - def search_gw(self, term, type, start, nb=20): - return self.gw_api_call('search.music', {"query": term, "filter":"ALL", "output":type, "start": start, "nb": nb})['results'] + def search_gw(self, term, type, start, nb=20): + return \ + self.gw_api_call('search.music', + {"query": term, "filter": "ALL", "output": type, "start": start, "nb": nb})[ + 'results'] - def get_lyrics_gw(self, sng_id): - return self.gw_api_call('song.getLyrics', {'sng_id': sng_id})["results"] + def get_lyrics_gw(self, sng_id): + return self.gw_api_call('song.getLyrics', {'sng_id': sng_id})["results"] - def get_user_playlist(self, user_id): - return self.api_call('user/' + str(user_id) + '/playlists', {'limit': -1}) + def get_user_playlist(self, user_id): + return self.api_call('user/' + str(user_id) + '/playlists', {'limit': -1}) - def get_track(self, user_id): - return self.api_call('track/' + str(user_id)) + def get_track(self, user_id): + return self.api_call('track/' + str(user_id)) - def get_track_by_ISRC(self, isrc): - return self.api_call('track/isrc:' + isrc) + def get_track_by_ISRC(self, isrc): + return self.api_call('track/isrc:' + isrc) - def get_charts_top_country(self): - return self.get_user_playlist('637006841') + def get_charts_top_country(self): + return self.get_user_playlist('637006841') - def get_playlist(self, playlist_id): - return self.api_call('playlist/' + str(playlist_id)) + def get_playlist(self, playlist_id): + return self.api_call('playlist/' + str(playlist_id)) - def get_playlist_tracks(self, playlist_id): - return self.api_call('playlist/' + str(playlist_id) + '/tracks', {'limit': -1}) + def get_playlist_tracks(self, playlist_id): + return self.api_call('playlist/' + str(playlist_id) + '/tracks', {'limit': -1}) - def get_album(self, album_id): - return self.api_call('album/' + str(album_id)) + def get_album(self, album_id): + return self.api_call('album/' + str(album_id)) - def get_album_by_UPC(self, upc): - return self.api_call('album/upc:' + str(upc)) + def get_album_by_UPC(self, upc): + return self.api_call('album/upc:' + str(upc)) - def get_album_tracks(self, album_id): - return self.api_call('album/' + str(album_id) + '/tracks', {'limit': -1}) + def get_album_tracks(self, album_id): + return self.api_call('album/' + str(album_id) + '/tracks', {'limit': -1}) - def get_artist(self, artist_id): - return self.api_call('artist/' + str(artist_id)) + def get_artist(self, artist_id): + return self.api_call('artist/' + str(artist_id)) - def get_artist_albums(self, artist_id): - return self.api_call('artist/' + str(artist_id) + '/albums', {'limit': -1}) + def get_artist_albums(self, artist_id): + return self.api_call('artist/' + str(artist_id) + '/albums', {'limit': -1}) - def search(self, term, search_type, limit=30): - return self.api_call('search/' + search_type, {'q': term, 'limit': limit}) + def search(self, term, search_type, limit=30): + return self.api_call('search/' + search_type, {'q': term, 'limit': limit}) - def decrypt_track(self, track_id, input, output): - response = open(input, 'rb') - outfile = open(output, 'wb') - blowfish_key = str.encode(self._get_blowfish_key(str(track_id))) - i = 0 - while True: - chunk = response.read(2048) - if not chunk: - break - if (i % 3) == 0 and len(chunk) == 2048: - chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(chunk) - outfile.write(chunk) - i += 1 + def decrypt_track(self, track_id, input, output): + response = open(input, 'rb') + outfile = open(output, 'wb') + blowfish_key = str.encode(self._get_blowfish_key(str(track_id))) + i = 0 + while True: + chunk = response.read(2048) + if not chunk: + break + if (i % 3) == 0 and len(chunk) == 2048: + chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt( + chunk) + outfile.write(chunk) + i += 1 - def stream_track(self, track_id, url, stream): - try: - request = requests.get(url, headers=self.http_headers, stream=True, timeout=30) - except: - time.sleep(2) - return self.stream_track(track_id, url, stream) - request.raise_for_status() - blowfish_key = str.encode(self._get_blowfish_key(str(track_id))) - i = 0 - for chunk in request.iter_content(2048): - if (i % 3) == 0 and len(chunk) == 2048: - chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(chunk) - stream.write(chunk) - i += 1 + def stream_track(self, track_id, url, stream): + try: + request = requests.get(url, headers=self.http_headers, stream=True, timeout=30) + except: + time.sleep(2) + return self.stream_track(track_id, url, stream) + request.raise_for_status() + blowfish_key = str.encode(self._get_blowfish_key(str(track_id))) + i = 0 + for chunk in request.iter_content(2048): + if (i % 3) == 0 and len(chunk) == 2048: + chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt( + chunk) + stream.write(chunk) + i += 1 - def _md5(self, data): - h = MD5.new() - h.update(str.encode(data) if isinstance(data, str) else data) - return h.hexdigest() + def _md5(self, data): + h = MD5.new() + h.update(str.encode(data) if isinstance(data, str) else data) + return h.hexdigest() - def _get_blowfish_key(self, trackId): - SECRET = 'g4el58wc' + '0zvf9na1' - idMd5 = self._md5(trackId) - bfKey = "" - for i in range(16): - bfKey += chr(ord(idMd5[i]) ^ ord(idMd5[i + 16]) ^ ord(SECRET[i])) - return bfKey + def _get_blowfish_key(self, trackId): + SECRET = 'g4el58wc' + '0zvf9na1' + idMd5 = self._md5(trackId) + bfKey = "" + for i in range(16): + bfKey += chr(ord(idMd5[i]) ^ ord(idMd5[i + 16]) ^ ord(SECRET[i])) + return bfKey - def get_track_stream_url(self, sng_id, md5, media_version, format): - urlPart = b'\xa4'.join( - [str.encode(md5), str.encode(str(format)), str.encode(str(sng_id)), str.encode(str(media_version))]) - md5val = self._md5(urlPart) - step2 = str.encode(md5val) + b'\xa4' + urlPart + b'\xa4' - step2 = pad(step2, 16) - urlPart = binascii.hexlify(AES.new(b'jo6aey6haid2Teih', AES.MODE_ECB).encrypt(step2)) - return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/mobile/1/" + urlPart.decode("utf-8") + def get_track_stream_url(self, sng_id, md5, media_version, format): + urlPart = b'\xa4'.join( + [str.encode(md5), str.encode(str(format)), str.encode(str(sng_id)), str.encode(str(media_version))]) + md5val = self._md5(urlPart) + step2 = str.encode(md5val) + b'\xa4' + urlPart + b'\xa4' + step2 = pad(step2, 16) + urlPart = binascii.hexlify(AES.new(b'jo6aey6haid2Teih', AES.MODE_ECB).encrypt(step2)) + return "https://e-cdns-proxy-" + md5[0] + ".dzcdn.net/mobile/1/" + urlPart.decode("utf-8") - def get_track_from_metadata(self, artist, track, album): - artist = artist.replace("–","-").replace("’", "'") - track = track.replace("–","-").replace("’", "'") - album = album.replace("–","-").replace("’", "'") + def get_track_from_metadata(self, artist, track, album): + artist = artist.replace("–", "-").replace("’", "'") + track = track.replace("–", "-").replace("’", "'") + album = album.replace("–", "-").replace("’", "'") - resp = self.search(f'artist:"{artist}" track:"{track}" album:"{album}"', "track", 1) - if len(resp['data'])>0: - return resp['data'][0]['id'] - resp = self.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 = self.search(f'artist:"{artist}" track:"{track[:track.find("(")]}"', "track", 1) - if len(resp['data'])>0: - return resp['data'][0]['id'] - elif " - " in track: - resp = self.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 + resp = self.search(f'artist:"{artist}" track:"{track}" album:"{album}"', "track", 1) + if len(resp['data']) > 0: + return resp['data'][0]['id'] + resp = self.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 = self.search(f'artist:"{artist}" track:"{track[:track.find("(")]}"', "track", 1) + if len(resp['data']) > 0: + return resp['data'][0]['id'] + elif " - " in track: + resp = self.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 class APIError(Exception): - pass + pass diff --git a/deemix/app/cli.py b/deemix/app/cli.py index a1a0ea4..bd28fe6 100644 --- a/deemix/app/cli.py +++ b/deemix/app/cli.py @@ -1,36 +1,38 @@ #!/usr/bin/env python3 -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 +import deemix.utils.localpaths as localpaths +from deemix.api.deezer import Deezer +from deemix.app.queuemanager import addToQueue +from deemix.app.spotify import SpotifyHelper + dz = Deezer() sp = SpotifyHelper() + def requestValidArl(): - while True: - arl = input("Paste here your arl:") - if dz.login_via_arl(arl): - break - return arl + while True: + arl = input("Paste here your arl:") + if 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.read() - if not dz.login_via_arl(arl): - arl = requestValidArl() - else: - arl = requestValidArl() - with open(path.join(configFolder, '.arl'), 'w') as f: - f.write(arl) + 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.read() + 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): - addToQueue(dz, sp, url, settings, bitrate) + addToQueue(dz, sp, url, settings, bitrate) diff --git a/deemix/app/default.json b/deemix/app/default.json index d1b5428..faa464b 100644 --- a/deemix/app/default.json +++ b/deemix/app/default.json @@ -1,69 +1,69 @@ { - "downloadLocation": "", - "tracknameTemplate": "%artist% - %title%", - "albumTracknameTemplate": "%tracknumber% - %title%", - "playlistTracknameTemplate": "%position% - %artist% - %title%", - "createPlaylistFolder": true, - "playlistNameTemplate": "%playlist%", - "createArtistFolder": false, - "artistNameTemplate": "%artist%", - "createAlbumFolder": true, - "albumNameTemplate": "%artist% - %album%", - "createCDFolder": true, - "createStructurePlaylist": false, - "createSingleFolder": false, - "padTracks": true, - "paddingSize": "0", - "illegalCharacterReplacer": "_", - "queueConcurrency": 3, - "maxBitrate": "3", - "fallbackBitrate": true, - "fallbackSearch": false, - "logErrors": true, - "logSearched": false, - "createM3U8File": false, - "syncedLyrics": false, - "embeddedArtworkSize": 800, - "localArtworkSize": 1400, - "saveArtwork": true, - "coverImageTemplate": "cover", - "saveArtworkArtist": false, - "artistImageTemplate": "folder", - "PNGcovers": false, - "jpegImageQuality": 80, - "dateFormat": "Y-M-D", - "removeAlbumVersion": false, - "featuredToTitle": "0", - "titleCasing": "nothing", - "artistCasing": "nothing", - "executeCommand": "", - "tags": { - "title": true, - "artist": true, - "album": true, - "cover": true, - "trackNumber": true, - "trackTotal": false, - "discNumber": true, - "discTotal": false, - "albumArtist": true, - "genre": true, - "year": true, - "date": true, - "explicit": false, - "isrc": true, - "length": true, - "barcode": true, - "bpm": true, - "replayGain": false, - "label": true, - "lyrics": false, - "copyright": false, - "composer": false, - "involvedPeople": false, - "savePlaylistAsCompilation": false, - "useNullSeparator": false, - "saveID3v1": true, - "multitagSeparator": "default" - } + "downloadLocation": "", + "tracknameTemplate": "%artist% - %title%", + "albumTracknameTemplate": "%tracknumber% - %title%", + "playlistTracknameTemplate": "%position% - %artist% - %title%", + "createPlaylistFolder": true, + "playlistNameTemplate": "%playlist%", + "createArtistFolder": false, + "artistNameTemplate": "%artist%", + "createAlbumFolder": true, + "albumNameTemplate": "%artist% - %album%", + "createCDFolder": true, + "createStructurePlaylist": false, + "createSingleFolder": false, + "padTracks": true, + "paddingSize": "0", + "illegalCharacterReplacer": "_", + "queueConcurrency": 3, + "maxBitrate": "3", + "fallbackBitrate": true, + "fallbackSearch": false, + "logErrors": true, + "logSearched": false, + "createM3U8File": false, + "syncedLyrics": false, + "embeddedArtworkSize": 800, + "localArtworkSize": 1400, + "saveArtwork": true, + "coverImageTemplate": "cover", + "saveArtworkArtist": false, + "artistImageTemplate": "folder", + "PNGcovers": false, + "jpegImageQuality": 80, + "dateFormat": "Y-M-D", + "removeAlbumVersion": false, + "featuredToTitle": "0", + "titleCasing": "nothing", + "artistCasing": "nothing", + "executeCommand": "", + "tags": { + "title": true, + "artist": true, + "album": true, + "cover": true, + "trackNumber": true, + "trackTotal": false, + "discNumber": true, + "discTotal": false, + "albumArtist": true, + "genre": true, + "year": true, + "date": true, + "explicit": false, + "isrc": true, + "length": true, + "barcode": true, + "bpm": true, + "replayGain": false, + "label": true, + "lyrics": false, + "copyright": false, + "composer": false, + "involvedPeople": false, + "savePlaylistAsCompilation": false, + "useNullSeparator": false, + "saveID3v1": true, + "multitagSeparator": "default" + } } diff --git a/deemix/app/downloader.py b/deemix/app/downloader.py index 0c5599f..acedc2b 100644 --- a/deemix/app/downloader.py +++ b/deemix/app/downloader.py @@ -1,762 +1,808 @@ #!/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 -from deemix.utils.misc import changeCase import os.path -from os import makedirs, remove, system as execute -from requests import get -from requests.exceptions import HTTPError, ConnectionError -from tempfile import gettempdir -from concurrent.futures import ThreadPoolExecutor -from Cryptodome.Cipher import Blowfish -from time import sleep import re import traceback +from concurrent.futures import ThreadPoolExecutor +from os import makedirs, remove, system as execute +from tempfile import gettempdir +from time import sleep + +from Cryptodome.Cipher import Blowfish +from requests import get +from requests.exceptions import HTTPError, ConnectionError + +from deemix.api.deezer import APIError, USER_AGENT_HEADER +from deemix.utils.misc import changeCase +from deemix.utils.pathtemplates import generateFilename, generateFilepath, settingsRegexAlbum, settingsRegexArtist +from deemix.utils.taggers import tagID3, tagFLAC TEMPDIR = os.path.join(gettempdir(), 'deemix-imgs') if not os.path.isdir(TEMPDIR): - makedirs(TEMPDIR) + makedirs(TEMPDIR) extensions = { - 9: '.flac', - 3: '.mp3', - 1: '.mp3', - 8: '.mp3', - 15: '.mp4', - 14: '.mp4', - 13: '.mp4' + 9: '.flac', + 3: '.mp3', + 1: '.mp3', + 8: '.mp3', + 15: '.mp4', + 14: '.mp4', + 13: '.mp4' } downloadPercentage = 0 lastPercentage = 0 + def stream_track(dz, track, stream, trackAPI, queueItem, interface=None): - global downloadPercentage, lastPercentage - if 'cancel' in queueItem: - raise downloadCancelled - try: - request = get(track['downloadUrl'], headers=dz.http_headers, stream=True, timeout=30) - except ConnectionError: - sleep(2) - return stream_track(dz, track, stream, trackAPI, queueItem, interface) - request.raise_for_status() - blowfish_key = str.encode(dz._get_blowfish_key(str(track['id']))) - complete = track['selectedFilesize'] - chunkLength = 0 - percentage = 0 - i = 0 - for chunk in request.iter_content(2048): - if 'cancel' in queueItem: - raise downloadCancelled - if (i % 3) == 0 and len(chunk) == 2048: - chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(chunk) - stream.write(chunk) - chunkLength += len(chunk) - if 'SINGLE_TRACK' in trackAPI: - percentage = (chunkLength / complete) * 100 - downloadPercentage = percentage - else: - chunkProgres = (len(chunk) / complete) / trackAPI['SIZE'] * 100 - downloadPercentage += chunkProgres - if round(downloadPercentage) != lastPercentage and round(percentage) % 5 == 0: - lastPercentage = round(downloadPercentage) - if interface: - queueItem['progress'] = lastPercentage - interface.send("updateQueue", {'uuid': queueItem['uuid'], 'progress': lastPercentage}) - i += 1 + global downloadPercentage, lastPercentage + if 'cancel' in queueItem: + raise downloadCancelled + try: + request = get(track['downloadUrl'], headers=dz.http_headers, stream=True, timeout=30) + except ConnectionError: + sleep(2) + return stream_track(dz, track, stream, trackAPI, queueItem, interface) + request.raise_for_status() + blowfish_key = str.encode(dz._get_blowfish_key(str(track['id']))) + complete = track['selectedFilesize'] + chunkLength = 0 + percentage = 0 + i = 0 + for chunk in request.iter_content(2048): + if 'cancel' in queueItem: + raise downloadCancelled + if (i % 3) == 0 and len(chunk) == 2048: + chunk = Blowfish.new(blowfish_key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(chunk) + stream.write(chunk) + chunkLength += len(chunk) + if 'SINGLE_TRACK' in trackAPI: + percentage = (chunkLength / complete) * 100 + downloadPercentage = percentage + else: + chunkProgres = (len(chunk) / complete) / trackAPI['SIZE'] * 100 + downloadPercentage += chunkProgres + if round(downloadPercentage) != lastPercentage and round(percentage) % 5 == 0: + lastPercentage = round(downloadPercentage) + if interface: + queueItem['progress'] = lastPercentage + interface.send("updateQueue", {'uuid': queueItem['uuid'], 'progress': lastPercentage}) + i += 1 + def downloadImage(url, path): - if not os.path.isfile(path): - try: - image = get(url, headers={'User-Agent': USER_AGENT_HEADER}, timeout=30) - with open(path, 'wb') as f: - f.write(image.content) - return path - except ConnectionError: - sleep(1) - return downloadImage(url, path) - except HTTPError: - print("Couldn't download Image") - remove(path) - return None - else: - return path + if not os.path.isfile(path): + try: + image = get(url, headers={'User-Agent': USER_AGENT_HEADER}, timeout=30) + with open(path, 'wb') as f: + f.write(image.content) + return path + except ConnectionError: + sleep(1) + return downloadImage(url, 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 + 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) + 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 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 + 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 + 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'] +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 = parseEssentialTrackData(track, trackAPI_gw) + track = {} + track['title'] = trackAPI_gw['SNG_TITLE'] + if 'VERSION' in trackAPI_gw and trackAPI_gw['VERSION']: + track['title'] += " " + trackAPI_gw['VERSION'] - 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 + track = parseEssentialTrackData(track, trackAPI_gw) - 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'] + 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 - 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'] = "" - lastTimestamp = "" - 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"] - lastTimestamp = trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]["lrc_timestamp"] - else: - track['lyrics']['sync'] += lastTimestamp - track['lyrics']['sync'] += trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]["line"] + "\r\n" + 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['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'] + 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'] = "" + lastTimestamp = "" + 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"] + lastTimestamp = trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]["lrc_timestamp"] + else: + track['lyrics']['sync'] += lastTimestamp + track['lyrics']['sync'] += trackAPI_gw["LYRICS"]["LYRICS_SYNC_JSON"][i]["line"] + "\r\n" - 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['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'] - 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'] + 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] + } - 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'] = [] + 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'] - if 'date' in track['album'] and 'date' not in track: - track['date'] = track['album']['date'] + 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 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 'date' in track['album'] and 'date' not in track: + track['date'] = track['album']['date'] - 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'] + 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']) - # 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 + 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'] - # 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() + # 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 - # 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'] += ", " + # 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 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'] + # 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 - return track def downloadTrackObj(dz, trackAPI, settings, bitrate, queueItem, extraTrack=None, interface=None): - result = {} - if 'cancel' in queueItem: - result['cancel'] = True - return result + result = {} + if 'cancel' in queueItem: + result['cancel'] = True + return result - if trackAPI['SNG_ID'] == 0: - result['error'] = { - 'message': "Track not available on Deezer!", - } - if 'SNG_TITLE' in trackAPI: - result['error']['data'] = { - 'id': trackAPI['SNG_ID'], - 'title': trackAPI['SNG_TITLE'], - 'mainArtist': {'name': trackAPI['ART_NAME']} - } - if interface: - queueItem['failed'] += 1 - interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': result['error']['data'], 'error': "Track not available on Deezer!"}) - return 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 - ) - if 'cancel' in queueItem: - result['cancel'] = True - return result - 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, bitrate, queueItem, extraTrack=track, interface=interface) - elif not 'searched' in track and settings['fallbackSearch']: - print("Track not yet encoded, searching for alternative") - searchedId = dz.get_track_from_metadata(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, bitrate, queueItem, extraTrack=track, interface=interface) - else: - print("ERROR: Track not yet encoded and no alternative found!") - result['error'] = { - 'message': "Track not yet encoded and no alternative found!", - 'data': track - } - if interface: - queueItem['failed'] += 1 - interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, 'error': "Track not yet encoded and no alternative found!"}) - return result - else: - print("ERROR: Track not yet encoded!") - result['error'] = { - 'message': "Track not yet encoded!", - 'data': track - } - if interface: - queueItem['failed'] += 1 - interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, 'error': "Track not yet encoded!"}) - return result + if trackAPI['SNG_ID'] == 0: + result['error'] = { + 'message': "Track not available on Deezer!", + } + if 'SNG_TITLE' in trackAPI: + result['error']['data'] = { + 'id': trackAPI['SNG_ID'], + 'title': trackAPI['SNG_TITLE'], + 'mainArtist': {'name': trackAPI['ART_NAME']} + } + if interface: + queueItem['failed'] += 1 + interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': result['error']['data'], + 'error': "Track not available on Deezer!"}) + return 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 + ) + if 'cancel' in queueItem: + result['cancel'] = True + return result + 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, bitrate, queueItem, extraTrack=track, interface=interface) + elif not 'searched' in track and settings['fallbackSearch']: + print("Track not yet encoded, searching for alternative") + searchedId = dz.get_track_from_metadata(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, bitrate, queueItem, extraTrack=track, + interface=interface) + else: + print("ERROR: Track not yet encoded and no alternative found!") + result['error'] = { + 'message': "Track not yet encoded and no alternative found!", + 'data': track + } + if interface: + queueItem['failed'] += 1 + interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, + 'error': "Track not yet encoded and no alternative found!"}) + return result + else: + print("ERROR: Track not yet encoded!") + result['error'] = { + 'message': "Track not yet encoded!", + 'data': track + } + if interface: + queueItem['failed'] += 1 + interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, + 'error': "Track not yet encoded!"}) + return result - # Get the selected bitrate - (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 - } - if interface: - queueItem['failed'] += 1 - interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, 'error': "Track not found at desired bitrate."}) - 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 - } - if interface: - queueItem['failed'] += 1 - interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, 'error': "Track is not available in Reality Audio 360."}) - return result - track['selectedFormat'] = format - track['selectedFilesize'] = filesize - track['dateString'] = formatDate(track['date'], settings['dateFormat']) - if settings['tags']['savePlaylistAsCompilation'] and "_EXTRA_PLAYLIST" in trackAPI: - if 'dzcdn.net'in trackAPI["_EXTRA_PLAYLIST"]['picture_small']: - track['album']['picUrl'] = trackAPI["_EXTRA_PLAYLIST"]['picture_small'][:-24]+"/{}x{}-{}".format(track['album']['pic'], settings['embeddedArtworkSize'], settings['embeddedArtworkSize'], 'none-100-0-0.png' if settings['PNGcovers'] else f'000000-{settings["jpegImageQuality"]}-0-0.jpg') - else: - track['album']['picUrl'] = trackAPI["_EXTRA_PLAYLIST"]['picture_xl'] - track['album']['title'] = trackAPI["_EXTRA_PLAYLIST"]['title'] - track['album']['mainArtist'] = { - 'id': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['id'], - 'name': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['name'], - 'pic': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['picture_small'][trackAPI["_EXTRA_PLAYLIST"]['various_artist']['picture_small'].find('artist/')+7:-24] - } - track['album']['artist'] = {"Main": [trackAPI["_EXTRA_PLAYLIST"]['various_artist']['name'], ]} - track['album']['artists'] = [trackAPI["_EXTRA_PLAYLIST"]['various_artist']['name'], ] - track['trackNumber'] = trackAPI["POSITION"] - track['album']['trackTotal'] = trackAPI["_EXTRA_PLAYLIST"]['nb_tracks'] - track['album']['recordType'] = "Compilation" - track['album']['barcode'] = "" - track['album']['label'] = "" - track['album']['date'] = { - 'day': trackAPI["_EXTRA_PLAYLIST"]["creation_date"][8:10], - 'month': trackAPI["_EXTRA_PLAYLIST"]["creation_date"][5:7], - 'year': trackAPI["_EXTRA_PLAYLIST"]["creation_date"][0:4] - } - track['discNumber'] = "1" - track['album']['discTotal'] = "1" - else: - if 'date' in track['album']: - track['date'] = track['album']['date'] - track['album']['picUrl'] = "https://e-cdns-images.dzcdn.net/images/cover/{}/{}x{}-{}".format(track['album']['pic'], settings['embeddedArtworkSize'], settings['embeddedArtworkSize'], 'none-100-0-0.png' if settings['PNGcovers'] else f'000000-{settings["jpegImageQuality"]}-0-0.jpg') - track['album']['bitrate'] = format - track['album']['dateString'] = formatDate(track['album']['date'], settings['dateFormat']) + # Get the selected bitrate + (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 + } + if interface: + queueItem['failed'] += 1 + interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, + 'error': "Track not found at desired bitrate."}) + 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 + } + if interface: + queueItem['failed'] += 1 + interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, + 'error': "Track is not available in Reality Audio 360."}) + return result + track['selectedFormat'] = format + track['selectedFilesize'] = filesize + track['dateString'] = formatDate(track['date'], settings['dateFormat']) + if settings['tags']['savePlaylistAsCompilation'] and "_EXTRA_PLAYLIST" in trackAPI: + if 'dzcdn.net' in trackAPI["_EXTRA_PLAYLIST"]['picture_small']: + track['album']['picUrl'] = trackAPI["_EXTRA_PLAYLIST"]['picture_small'][:-24] + "/{}x{}-{}".format( + track['album']['pic'], settings['embeddedArtworkSize'], settings['embeddedArtworkSize'], + 'none-100-0-0.png' if settings['PNGcovers'] else f'000000-{settings["jpegImageQuality"]}-0-0.jpg') + else: + track['album']['picUrl'] = trackAPI["_EXTRA_PLAYLIST"]['picture_xl'] + track['album']['title'] = trackAPI["_EXTRA_PLAYLIST"]['title'] + track['album']['mainArtist'] = { + 'id': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['id'], + 'name': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['name'], + 'pic': trackAPI["_EXTRA_PLAYLIST"]['various_artist']['picture_small'][ + trackAPI["_EXTRA_PLAYLIST"]['various_artist']['picture_small'].find('artist/') + 7:-24] + } + track['album']['artist'] = {"Main": [trackAPI["_EXTRA_PLAYLIST"]['various_artist']['name'], ]} + track['album']['artists'] = [trackAPI["_EXTRA_PLAYLIST"]['various_artist']['name'], ] + track['trackNumber'] = trackAPI["POSITION"] + track['album']['trackTotal'] = trackAPI["_EXTRA_PLAYLIST"]['nb_tracks'] + track['album']['recordType'] = "Compilation" + track['album']['barcode'] = "" + track['album']['label'] = "" + track['album']['date'] = { + 'day': trackAPI["_EXTRA_PLAYLIST"]["creation_date"][8:10], + 'month': trackAPI["_EXTRA_PLAYLIST"]["creation_date"][5:7], + 'year': trackAPI["_EXTRA_PLAYLIST"]["creation_date"][0:4] + } + track['discNumber'] = "1" + track['album']['discTotal'] = "1" + else: + if 'date' in track['album']: + track['date'] = track['album']['date'] + track['album']['picUrl'] = "https://e-cdns-images.dzcdn.net/images/cover/{}/{}x{}-{}".format( + track['album']['pic'], settings['embeddedArtworkSize'], settings['embeddedArtworkSize'], + 'none-100-0-0.png' if settings['PNGcovers'] else f'000000-{settings["jpegImageQuality"]}-0-0.jpg') + track['album']['bitrate'] = format + track['album']['dateString'] = formatDate(track['album']['date'], settings['dateFormat']) - # Check if user wants the feat in the title - # 0 => do not change - # 1 => remove from title - # 2 => add to title - if settings['featuredToTitle'] == "1": - track['title'] = track['title_clean'] - elif settings['featuredToTitle'] == "2": - track['title'] = track['title_feat'] + # Check if user wants the feat in the title + # 0 => do not change + # 1 => remove from title + # 2 => add to title + if settings['featuredToTitle'] == "1": + track['title'] = track['title_clean'] + elif settings['featuredToTitle'] == "2": + track['title'] = track['title_feat'] - # 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() + # 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() - # Generate artist tag if needed - if settings['tags']['multitagSeparator'] != "default": - if settings['tags']['multitagSeparator'] == "andFeat": - track['artistsString'] = track['mainArtistsString'] - if 'featArtistsString' in track and settings['featuredToTitle'] != "2": - track['artistsString'] += " "+track['featArtistsString'] - else: - track['artistsString'] = settings['tags']['multitagSeparator'].join(track['artists']) - else: - track['artistsString'] = ", ".join(track['artists']) + # Generate artist tag if needed + if settings['tags']['multitagSeparator'] != "default": + if settings['tags']['multitagSeparator'] == "andFeat": + track['artistsString'] = track['mainArtistsString'] + if 'featArtistsString' in track and settings['featuredToTitle'] != "2": + track['artistsString'] += " " + track['featArtistsString'] + else: + track['artistsString'] = settings['tags']['multitagSeparator'].join(track['artists']) + else: + track['artistsString'] = ", ".join(track['artists']) - # Change Title and Artists casing if needed - if settings['titleCasing'] != "nothing": - track['title'] = changeCase(track['title'], settings['titleCasing']) - if settings['artistCasing'] != "nothing": - track['artistsString'] = changeCase(track['artistsString'], settings['artistCasing']) - for i, artist in enumerate(track['artists']): - track['artists'][i] = changeCase(artist, settings['artistCasing']) + # Change Title and Artists casing if needed + if settings['titleCasing'] != "nothing": + track['title'] = changeCase(track['title'], settings['titleCasing']) + if settings['artistCasing'] != "nothing": + track['artistsString'] = changeCase(track['artistsString'], settings['artistCasing']) + for i, artist in enumerate(track['artists']): + track['artists'][i] = changeCase(artist, settings['artistCasing']) - # Generate filename and filepath from metadata - filename = generateFilename(track, trackAPI, settings) - (filepath, artistPath, coverPath, extrasPath) = generateFilepath(track, trackAPI, settings) + # Generate filename and filepath from metadata + filename = generateFilename(track, trackAPI, settings) + (filepath, artistPath, coverPath, extrasPath) = generateFilepath(track, trackAPI, settings) - if 'cancel' in queueItem: - result['cancel'] = True - return result - # Download and cache coverart - if settings['tags']['savePlaylistAsCompilation'] and "_EXTRA_PLAYLIST" in trackAPI: - track['album']['picPath'] = os.path.join(TEMPDIR, f"pl{trackAPI['_EXTRA_PLAYLIST']['id']}_{settings['embeddedArtworkSize']}.{'png' if settings['PNGcovers'] else 'jpg'}") - else: - 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']) + if 'cancel' in queueItem: + result['cancel'] = True + return result + # Download and cache coverart + if settings['tags']['savePlaylistAsCompilation'] and "_EXTRA_PLAYLIST" in trackAPI: + track['album']['picPath'] = os.path.join(TEMPDIR, + f"pl{trackAPI['_EXTRA_PLAYLIST']['id']}_{settings['embeddedArtworkSize']}.{'png' if settings['PNGcovers'] else 'jpg'}") + else: + 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']) - if os.path.sep in filename: - tempPath = filename[:filename.rfind(os.path.sep)] - filepath = os.path.join(filepath, tempPath) - filename = filename[filename.rfind(os.path.sep)+len(os.path.sep):] - makedirs(filepath, exist_ok=True) - writepath = os.path.join(filepath, filename + extensions[track['selectedFormat']]) + if os.path.sep in filename: + tempPath = filename[:filename.rfind(os.path.sep)] + filepath = os.path.join(filepath, tempPath) + filename = filename[filename.rfind(os.path.sep) + len(os.path.sep):] + 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 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, trackAPI)}.{'png' if settings['PNGcovers'] else 'jpg'}") + # 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, trackAPI)}.{'png' if settings['PNGcovers'] else 'jpg'}") - # Save artist art - if artistPath: - result['artistURL'] = "https://e-cdns-images.dzcdn.net/images/artist/{}/{}x{}-{}".format(track['album']['mainArtist']['pic'], settings['localArtworkSize'], settings['localArtworkSize'], 'none-100-0-0.png' if settings['PNGcovers'] else f'000000-{settings["jpegImageQuality"]}-0-0.jpg') - result['artistPath'] = os.path.join(artistPath, f"{settingsRegexArtist(settings['artistImageTemplate'], track['album']['mainArtist'], settings)}.{'png' if settings['PNGcovers'] else 'jpg'}") + # Save artist art + if artistPath: + result['artistURL'] = "https://e-cdns-images.dzcdn.net/images/artist/{}/{}x{}-{}".format( + track['album']['mainArtist']['pic'], settings['localArtworkSize'], settings['localArtworkSize'], + 'none-100-0-0.png' if settings['PNGcovers'] else f'000000-{settings["jpegImageQuality"]}-0-0.jpg') + result['artistPath'] = os.path.join(artistPath, + f"{settingsRegexArtist(settings['artistImageTemplate'], track['album']['mainArtist'], settings)}.{'png' if settings['PNGcovers'] else 'jpg'}") - # Data for m3u file - if extrasPath: - result['extrasPath'] = extrasPath - result['playlistPosition'] = writepath[len(extrasPath):] + # Data for m3u file + if extrasPath: + result['extrasPath'] = extrasPath + result['playlistPosition'] = writepath[len(extrasPath):] + + track['downloadUrl'] = dz.get_track_stream_url(track['id'], track['MD5'], track['mediaVersion'], + track['selectedFormat']) + try: + with open(writepath, 'wb') as stream: + stream_track(dz, track, stream, trackAPI, queueItem, interface) + except downloadCancelled: + remove(writepath) + result['cancel'] = True + return result + 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, bitrate, queueItem, extraTrack=track, interface=interface) + 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, bitrate, queueItem, extraTrack=track, interface=interface) + elif not 'searched' in track and settings['fallbackSearch']: + print("Track not available, searching for alternative") + searchedId = dz.get_track_from_metadata(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, bitrate, queueItem, extraTrack=track, + interface=interface) + 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 + } + if interface: + queueItem['failed'] += 1 + interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, + 'error': "Track not available on deezer's servers and no alternative found!"}) + return result + else: + print("ERROR: Track not available on deezer's servers!") + result['error'] = { + 'message': "Track not available on deezer's servers!", + 'data': track + } + if interface: + queueItem['failed'] += 1 + interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, + 'error': "Track not available on deezer's servers!"}) + return result + if track['selectedFormat'] in [3, 1, 8]: + tagID3(writepath, track, settings['tags']) + elif track['selectedFormat'] == 9: + tagFLAC(writepath, track, settings['tags']) + if 'searched' in track: + result['searched'] = f'{track["mainArtist"]["name"]} - {track["title"]}' + print("Done!") + if interface: + queueItem['downloaded'] += 1 + interface.send("updateQueue", {'uuid': queueItem['uuid'], 'downloaded': True}) + return result - track['downloadUrl'] = dz.get_track_stream_url(track['id'], track['MD5'], track['mediaVersion'], track['selectedFormat']) - try: - with open(writepath, 'wb') as stream: - stream_track(dz, track, stream, trackAPI, queueItem, interface) - except downloadCancelled: - remove(writepath) - result['cancel'] = True - return result - 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, bitrate, queueItem, extraTrack=track, interface=interface) - 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, bitrate, queueItem, extraTrack=track, interface=interface) - elif not 'searched' in track and settings['fallbackSearch']: - print("Track not available, searching for alternative") - searchedId = dz.get_track_from_metadata(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, bitrate, queueItem, extraTrack=track, interface=interface) - 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 - } - if interface: - queueItem['failed'] += 1 - interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, 'error': "Track not available on deezer's servers and no alternative found!"}) - return result - else: - print("ERROR: Track not available on deezer's servers!") - result['error'] = { - 'message': "Track not available on deezer's servers!", - 'data': track - } - if interface: - queueItem['failed'] += 1 - interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True, 'data': track, 'error': "Track not available on deezer's servers!"}) - return result - if track['selectedFormat'] in [3, 1, 8]: - tagID3(writepath, track, settings['tags']) - elif track['selectedFormat'] == 9: - tagFLAC(writepath, track, settings['tags']) - if 'searched' in track: - result['searched'] = f'{track["mainArtist"]["name"]} - {track["title"]}' - print("Done!") - if interface: - queueItem['downloaded'] += 1 - interface.send("updateQueue", {'uuid': queueItem['uuid'], 'downloaded': True}) - return result def downloadTrackObj_wrap(dz, track, settings, bitrate, queueItem, interface): - try: - result = downloadTrackObj(dz, track, settings, bitrate, queueItem, interface=interface) - except Exception as e: - traceback.print_exc() - result = {'error': { - 'message': str(e), - 'data': { - 'id': track['SNG_ID'], - 'title': track['SNG_TITLE'] + (" "+track['VERSION'] if 'VERSION' in track and track['VERSION'] else ""), - 'mainArtist': {'name': track['ART_NAME']} - } - } - } - if interface: - queueItem['failed'] += 1 - interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True}) - return result + try: + result = downloadTrackObj(dz, track, settings, bitrate, queueItem, interface=interface) + except Exception as e: + traceback.print_exc() + result = {'error': { + 'message': str(e), + 'data': { + 'id': track['SNG_ID'], + 'title': track['SNG_TITLE'] + ( + " " + track['VERSION'] if 'VERSION' in track and track['VERSION'] else ""), + 'mainArtist': {'name': track['ART_NAME']} + } + } + } + if interface: + queueItem['failed'] += 1 + interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True}) + return result + def download(dz, queueItem, interface=None): - global downloadPercentage, lastPercentage - settings = queueItem['settings'] - bitrate = queueItem['bitrate'] - downloadPercentage = 0 - lastPercentage = 0 - if 'single' in queueItem: - try: - result = downloadTrackObj(dz, queueItem['single'], settings, bitrate, queueItem, interface=interface) - except Exception as e: - result = {'error': { - 'message': str(e), - 'data': { - 'id': queueItem['single']['SNG_ID'], - 'title': queueItem['single']['SNG_TITLE'] + (" "+queueItem['single']['VERSION'] if 'VERSION' in queueItem['single'] and queueItem['single']['VERSION'] else ""), - 'mainArtist': {'name': queueItem['single']['ART_NAME']} - } - } - } - if interface: - queueItem['failed'] += 1 - interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True}) - download_path = after_download_single(result, settings, queueItem) - elif 'collection' in queueItem: - print("Downloading collection") - playlist = [None] * len(queueItem['collection']) - with ThreadPoolExecutor(settings['queueConcurrency']) as executor: - for pos, track in enumerate(queueItem['collection'], start=0): - playlist[pos] = executor.submit(downloadTrackObj_wrap, dz, track, settings, bitrate, queueItem, interface=interface) - download_path = after_download(playlist, settings, queueItem) - if interface: - if 'cancel' in queueItem: - interface.send('toast', {'msg': "Current item cancelled.", 'icon':'done', 'dismiss': True, 'id':'cancelling_'+queueItem['uuid']}) - interface.send("removedFromQueue", queueItem['uuid']) - else: - interface.send("finishDownload", queueItem['uuid']) - return { - 'dz': dz, - 'interface': interface, - 'download_path': download_path - } + global downloadPercentage, lastPercentage + settings = queueItem['settings'] + bitrate = queueItem['bitrate'] + downloadPercentage = 0 + lastPercentage = 0 + if 'single' in queueItem: + try: + result = downloadTrackObj(dz, queueItem['single'], settings, bitrate, queueItem, interface=interface) + except Exception as e: + result = {'error': { + 'message': str(e), + 'data': { + 'id': queueItem['single']['SNG_ID'], + 'title': queueItem['single']['SNG_TITLE'] + ( + " " + queueItem['single']['VERSION'] if 'VERSION' in queueItem['single'] and + queueItem['single']['VERSION'] else ""), + 'mainArtist': {'name': queueItem['single']['ART_NAME']} + } + } + } + if interface: + queueItem['failed'] += 1 + interface.send("updateQueue", {'uuid': queueItem['uuid'], 'failed': True}) + download_path = after_download_single(result, settings, queueItem) + elif 'collection' in queueItem: + print("Downloading collection") + playlist = [None] * len(queueItem['collection']) + with ThreadPoolExecutor(settings['queueConcurrency']) as executor: + for pos, track in enumerate(queueItem['collection'], start=0): + playlist[pos] = executor.submit(downloadTrackObj_wrap, dz, track, settings, bitrate, queueItem, + interface=interface) + download_path = after_download(playlist, settings, queueItem) + if interface: + if 'cancel' in queueItem: + interface.send('toast', {'msg': "Current item cancelled.", 'icon': 'done', 'dismiss': True, + 'id': 'cancelling_' + queueItem['uuid']}) + interface.send("removedFromQueue", queueItem['uuid']) + else: + interface.send("finishDownload", queueItem['uuid']) + return { + 'dz': dz, + 'interface': interface, + 'download_path': download_path + } + def after_download(tracks, settings, queueItem): - extrasPath = None - playlist = [None] * len(tracks) - errors = "" - searched = "" - for index in range(len(tracks)): - result = tracks[index].result() - if 'cancel' in result: - return None - if 'error' in result: - if not 'data' in result['error']: - result['error']['data'] = {'id': 0, 'title': 'Unknown', 'mainArtist': {'name': 'Unknown'}} - 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 'albumPath' in result: - downloadImage(result['albumURL'], result['albumPath']) - if settings['saveArtworkArtist'] and 'artistPath' in result: - downloadImage(result['artistURL'], result['artistPath']) - if 'playlistPosition' in result: - playlist[index] = result['playlistPosition'] - else: - playlist[index] = "" - if not extrasPath: - extrasPath = settings['downloadLocation'] - if settings['logErrors'] and errors != "": - with open(os.path.join(extrasPath, 'errors.txt'), 'w') as f: - f.write(errors) - if settings['logSearched'] and searched != "": - with open(os.path.join(extrasPath, 'searched.txt'), 'w') as f: - f.write(searched) - if settings['createM3U8File']: - with open(os.path.join(extrasPath, 'playlist.m3u8'), 'w') as f: - for line in playlist: - f.write(line+"\n") - if settings['executeCommand'] != "": - execute(settings['executeCommand'].replace("%folder%", extrasPath)) - return extrasPath + extrasPath = None + playlist = [None] * len(tracks) + errors = "" + searched = "" + for index in range(len(tracks)): + result = tracks[index].result() + if 'cancel' in result: + return None + if 'error' in result: + if not 'data' in result['error']: + result['error']['data'] = {'id': 0, 'title': 'Unknown', 'mainArtist': {'name': 'Unknown'}} + 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 'albumPath' in result: + downloadImage(result['albumURL'], result['albumPath']) + if settings['saveArtworkArtist'] and 'artistPath' in result: + downloadImage(result['artistURL'], result['artistPath']) + if 'playlistPosition' in result: + playlist[index] = result['playlistPosition'] + else: + playlist[index] = "" + if not extrasPath: + extrasPath = settings['downloadLocation'] + if settings['logErrors'] and errors != "": + with open(os.path.join(extrasPath, 'errors.txt'), 'w') as f: + f.write(errors) + if settings['logSearched'] and searched != "": + with open(os.path.join(extrasPath, 'searched.txt'), 'w') as f: + f.write(searched) + if settings['createM3U8File']: + with open(os.path.join(extrasPath, 'playlist.m3u8'), 'w') as f: + for line in playlist: + f.write(line + "\n") + if settings['executeCommand'] != "": + execute(settings['executeCommand'].replace("%folder%", extrasPath)) + return extrasPath + def after_download_single(track, settings, queueItem): - if 'cancel' in track: - return None - if 'extrasPath' not in track: - track['extrasPath'] = settings['downloadLocation'] - if settings['logSearched'] 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 settings['executeCommand'] != "": - execute(settings['executeCommand'].replace("%folder%", track['extrasPath'])) - return track['extrasPath'] + if 'cancel' in track: + return None + if 'extrasPath' not in track: + track['extrasPath'] = settings['downloadLocation'] + if settings['logSearched'] 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 settings['executeCommand'] != "": + execute(settings['executeCommand'].replace("%folder%", track['extrasPath'])) + return track['extrasPath'] + class downloadCancelled(Exception): - """Base class for exceptions in this module.""" - pass + """Base class for exceptions in this module.""" + pass diff --git a/deemix/app/queuemanager.py b/deemix/app/queuemanager.py index 1c4ee27..5794b6b 100644 --- a/deemix/app/queuemanager.py +++ b/deemix/app/queuemanager.py @@ -1,6 +1,5 @@ -from deemix.utils.misc import getIDFromLink, getTypeFromLink, getBitrateInt -from concurrent.futures import ProcessPoolExecutor from deemix.app.downloader import download +from deemix.utils.misc import getIDFromLink, getTypeFromLink, getBitrateInt queue = [] queueList = {} @@ -26,244 +25,264 @@ if its an album/playlist collection """ + 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 = {} - if type == None or id == None: - print("URL not recognized") - result['error'] = "URL not recognized" - elif type == "track": - trackAPI = dz.get_track_gw(id) - if albumAPI: - trackAPI['_EXTRA_ALBUM'] = albumAPI - trackAPI['FILENAME_TEMPLATE'] = settings['tracknameTemplate'] - trackAPI['SINGLE_TRACK'] = True + forcedBitrate = getBitrateInt(bitrate) + bitrate = forcedBitrate if forcedBitrate else settings['maxBitrate'] + type = getTypeFromLink(url) + id = getIDFromLink(url, type) + result = {} + if type == None or id == None: + print("URL not recognized") + result['error'] = "URL not recognized" + elif type == "track": + trackAPI = dz.get_track_gw(id) + if albumAPI: + trackAPI['_EXTRA_ALBUM'] = albumAPI + 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['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 + 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['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": - 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: - 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) + elif type == "album": + 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: + 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'] - result['artist'] = albumAPI['artist']['name'] - result['cover'] = albumAPI['cover_small'][:-24]+'/75x75-000000-80-0-0.jpg' - result['size'] = albumAPI['nb_tracks'] - result['downloaded'] = 0 - result['failed'] = 0 - 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) + result['title'] = albumAPI['title'] + result['artist'] = albumAPI['artist']['name'] + result['cover'] = albumAPI['cover_small'][:-24] + '/75x75-000000-80-0-0.jpg' + result['size'] = albumAPI['nb_tracks'] + result['downloaded'] = 0 + result['failed'] = 0 + 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": - playlistAPI = dz.get_playlist(id) - playlistTracksAPI = dz.get_playlist_tracks_gw(id) - playlistAPI['various_artist'] = dz.get_artist(5080) + elif type == "playlist": + playlistAPI = dz.get_playlist(id) + 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['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): - trackAPI['_EXTRA_PLAYLIST'] = playlistAPI - trackAPI['POSITION'] = pos - trackAPI['SIZE'] = totalSize - trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate'] - result['collection'].append(trackAPI) + 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['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): + trackAPI['_EXTRA_PLAYLIST'] = playlistAPI + trackAPI['POSITION'] = pos + trackAPI['SIZE'] = totalSize + trackAPI['FILENAME_TEMPLATE'] = settings['playlistTracknameTemplate'] + result['collection'].append(trackAPI) + + elif type == "artist": + artistAPI = dz.get_artist(id) + if interface: + interface.send("toast", + {'msg': f"Adding {artistAPI['name']} albums to queue", 'icon': 'loading', 'dismiss': False, + 'id': 'artist_' + str(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("toast", + {'msg': f"Added {artistAPI['name']} albums to queue", 'icon': 'done', 'dismiss': True, + 'id': 'artist_' + str(artistAPI['id'])}) + return albumList + elif type == "spotifytrack": + result = {} + if not sp.spotifyEnabled: + print("Spotify Features is not setted up correctly.") + result['error'] = "Spotify Features is not setted up correctly." + 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": + result = {} + if not sp.spotifyEnabled: + print("Spotify Features is not setted up correctly.") + result['error'] = "Spotify Features is not setted up correctly." + 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)}) + playlist = sp.convert_spotify_playlist(dz, id, settings) + playlist['bitrate'] = bitrate + playlist['uuid'] = f"{playlist['type']}_{id}_{bitrate}" + result = playlist + if interface: + interface.send("toast", {'msg': f"Spotify playlist converted", 'icon': 'done', 'dismiss': True, + 'id': 'spotifyplaylist_' + str(id)}) + else: + print("URL not supported yet") + result['error'] = "URL not supported yet" + return result - elif type == "artist": - artistAPI = dz.get_artist(id) - if interface: - interface.send("toast", {'msg': f"Adding {artistAPI['name']} albums to queue", 'icon': 'loading', 'dismiss': False, 'id': 'artist_'+str(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("toast", {'msg': f"Added {artistAPI['name']} albums to queue", 'icon': 'done', 'dismiss': True, 'id': 'artist_'+str(artistAPI['id'])}) - return albumList - elif type == "spotifytrack": - result = {} - if not sp.spotifyEnabled: - print("Spotify Features is not setted up correctly.") - result['error'] = "Spotify Features is not setted up correctly." - 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": - result = {} - if not sp.spotifyEnabled: - print("Spotify Features is not setted up correctly.") - result['error'] = "Spotify Features is not setted up correctly." - 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)}) - playlist = sp.convert_spotify_playlist(dz, id, settings) - playlist['bitrate'] = bitrate - playlist['uuid'] = f"{playlist['type']}_{id}_{bitrate}" - result = playlist - if interface: - interface.send("toast", {'msg': f"Spotify playlist converted", 'icon': 'done', 'dismiss': True, 'id': 'spotifyplaylist_'+str(id)}) - else: - print("URL not supported yet") - result['error'] = "URL not supported yet" - 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" - queueItem = generateQueueItem(dz, sp, url, settings, bitrate, interface=interface) - if type(queueItem) is list: - for x in queueItem: - if 'error' in x: - continue - if x['uuid'] in list(queueList.keys()): - print("Already in queue!") - continue - if interface: - interface.send("addedToQueue", x) - queue.append(x['uuid']) - queueList[x['uuid']] = x - else: - if 'error' in queueItem: - if interface: - interface.send("toast", {'msg': queueItem['error'], 'icon': 'error'}) - return False - if queueItem['uuid'] in list(queueList.keys()): - print("Already in queue!") - if interface: - interface.send("toast", {'msg': f"{queueItem['title']} is already in queue!", 'icon': 'playlist_add_check'}) - return False - if interface: - interface.send("addedToQueue", queueItem) - interface.send("toast", {'msg': f"{queueItem['title']} added to queue", 'icon': 'playlist_add'}) - queue.append(queueItem['uuid']) - queueList[queueItem['uuid']] = queueItem - nextItem(dz, interface) - return True + global currentItem, queueList, queue + if not dz.logged_in: + return "Not logged in" + queueItem = generateQueueItem(dz, sp, url, settings, bitrate, interface=interface) + if type(queueItem) is list: + for x in queueItem: + if 'error' in x: + continue + if x['uuid'] in list(queueList.keys()): + print("Already in queue!") + continue + if interface: + interface.send("addedToQueue", x) + queue.append(x['uuid']) + queueList[x['uuid']] = x + else: + if 'error' in queueItem: + if interface: + interface.send("toast", {'msg': queueItem['error'], 'icon': 'error'}) + return False + if queueItem['uuid'] in list(queueList.keys()): + print("Already in queue!") + if interface: + interface.send("toast", + {'msg': f"{queueItem['title']} is already in queue!", 'icon': 'playlist_add_check'}) + return False + if interface: + interface.send("addedToQueue", queueItem) + interface.send("toast", {'msg': f"{queueItem['title']} added to queue", 'icon': 'playlist_add'}) + queue.append(queueItem['uuid']) + queueList[queueItem['uuid']] = queueItem + nextItem(dz, interface) + return True + def nextItem(dz, 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) - result = download(dz, queueList[currentItem], interface) - callbackQueueDone(result) + 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) + result = download(dz, queueList[currentItem], interface) + callbackQueueDone(result) + def callbackQueueDone(result): - global currentItem, queueList, queueComplete - if 'cancel' in queueList[currentItem]: - del queueList[currentItem] - else: - queueComplete.append(currentItem) - currentItem = "" - nextItem(result['dz'], result['interface']) + global currentItem, queueList, queueComplete + if 'cancel' in queueList[currentItem]: + del queueList[currentItem] + else: + queueComplete.append(currentItem) + currentItem = "" + nextItem(result['dz'], result['interface']) + def getQueue(): - global currentItem, queueList, queue, queueComplete - return (queue, queueComplete, queueList, currentItem) + global currentItem, queueList, queue, queueComplete + return (queue, queueComplete, queueList, currentItem) + def removeFromQueue(uuid, interface=None): - global currentItem, queueList, queue, queueComplete - if uuid == currentItem: - if interface: - interface.send('toast', {'msg': "Cancelling current item.", 'icon':'loading', 'dismiss': False, 'id':'cancelling_'+uuid}) - 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) + global currentItem, queueList, queue, queueComplete + if uuid == currentItem: + if interface: + interface.send('toast', {'msg': "Cancelling current item.", 'icon': 'loading', 'dismiss': False, + 'id': 'cancelling_' + uuid}) + 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('toast', {'msg': "Cancelling current item.", 'icon':'loading', 'dismiss': False, 'id':'cancelling_'+currentItem}) - queueList[currentItem]['cancel'] = True - for uuid in list(queueList.keys()): - if uuid != currentItem: - del queueList[uuid] - if interface: - interface.send("removedAllDownloads", currentItem) + global currentItem, queueList, queue, queueComplete + queue = [] + queueComplete = [] + if currentItem != "": + if interface: + interface.send('toast', {'msg': "Cancelling current item.", 'icon': 'loading', 'dismiss': False, + 'id': 'cancelling_' + currentItem}) + queueList[currentItem]['cancel'] = True + for uuid in list(queueList.keys()): + if uuid != currentItem: + del queueList[uuid] + if interface: + interface.send("removedAllDownloads", currentItem) + def removeFinishedDownloads(interface=None): - global queueList, queueComplete - for uuid in queueComplete: - del queueList[uuid] - queueComplete = [] - if interface: - interface.send("removedFinishedDownloads") + global queueList, queueComplete + for uuid in queueComplete: + del queueList[uuid] + queueComplete = [] + if interface: + interface.send("removedFinishedDownloads") diff --git a/deemix/app/settings.py b/deemix/app/settings.py index ff8815d..6a80419 100644 --- a/deemix/app/settings.py +++ b/deemix/app/settings.py @@ -1,57 +1,61 @@ #!/usr/bin/env python3 -import os.path as path -from os import mkdir, rmdir import json +import os.path as path +from os import mkdir import deemix.utils.localpaths as localpaths settings = {} defaultSettings = {} + def initSettings(): - global settings - global defaultSettings - currentFolder = path.abspath(path.dirname(__file__)) - configFolder = localpaths.getConfigFolder() - if not path.isdir(configFolder): - mkdir(configFolder) - with open(path.join(currentFolder, 'default.json'), 'r') as d: - defaultSettings = json.load(d) - if not path.isfile(path.join(configFolder, 'config.json')): - with open(path.join(configFolder, 'config.json'), 'w') as f: - json.dump(defaultSettings, f, indent=2) - with open(path.join(configFolder, 'config.json'), 'r') as configFile: - settings = json.load(configFile) - settingsCheck() - if settings['downloadLocation'] == "": - settings['downloadLocation'] = path.join(localpaths.getHomeFolder(), 'deemix Music') - saveSettings(settings) - if not path.isdir(settings['downloadLocation']): - mkdir(settings['downloadLocation']) - return settings + global settings + global defaultSettings + currentFolder = path.abspath(path.dirname(__file__)) + configFolder = localpaths.getConfigFolder() + if not path.isdir(configFolder): + mkdir(configFolder) + with open(path.join(currentFolder, 'default.json'), 'r') as d: + defaultSettings = json.load(d) + if not path.isfile(path.join(configFolder, 'config.json')): + with open(path.join(configFolder, 'config.json'), 'w') as f: + json.dump(defaultSettings, f, indent=2) + with open(path.join(configFolder, 'config.json'), 'r') as configFile: + settings = json.load(configFile) + settingsCheck() + if settings['downloadLocation'] == "": + settings['downloadLocation'] = path.join(localpaths.getHomeFolder(), 'deemix Music') + saveSettings(settings) + if not path.isdir(settings['downloadLocation']): + mkdir(settings['downloadLocation']) + return settings + def getSettings(): - global settings - return settings + global settings + return settings + def saveSettings(newSettings): - global settings - settings = newSettings - with open(path.join(localpaths.getConfigFolder(), 'config.json'), 'w') as configFile: - json.dump(settings, configFile, indent=2) - return True + global settings + settings = newSettings + with open(path.join(localpaths.getConfigFolder(), 'config.json'), 'w') as configFile: + json.dump(settings, configFile, indent=2) + return True + def settingsCheck(): - global settings - global defaultSettings - changes = 0 - for x in defaultSettings: - if not x in settings or type(settings[x]) != type(defaultSettings[x]): - settings[x] = defaultSettings[x] - changes+=1 - for x in defaultSettings['tags']: - if not x in settings['tags'] or type(settings['tags'][x]) != type(defaultSettings['tags'][x]): - settings['tags'][x] = defaultSettings['tags'][x] - changes+=1 - if changes > 0: - saveSettings(settings) + global settings + global defaultSettings + changes = 0 + for x in defaultSettings: + if not x in settings or type(settings[x]) != type(defaultSettings[x]): + settings[x] = defaultSettings[x] + changes += 1 + for x in defaultSettings['tags']: + if not x in settings['tags'] or type(settings['tags'][x]) != type(defaultSettings['tags'][x]): + settings['tags'][x] = defaultSettings['tags'][x] + changes += 1 + if changes > 0: + saveSettings(settings) diff --git a/deemix/app/spotify.py b/deemix/app/spotify.py index 6eb41a3..9ee466b 100644 --- a/deemix/app/spotify.py +++ b/deemix/app/spotify.py @@ -1,172 +1,179 @@ #!/usr/bin/env python3 -import os.path as path -from os import mkdir, rmdir import json - -import deemix.utils.localpaths as localpaths +import os.path as path +from os import mkdir import spotipy from spotipy.oauth2 import SpotifyClientCredentials +import deemix.utils.localpaths as localpaths + + class SpotifyHelper: - def __init__(self): - self.credentials = {} - self.spotifyEnabled = False - self.sp = None - self.initCredentials() + 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 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 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 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 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 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': "????-00-00", - '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 _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': "????-00-00", + '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_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 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, 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) + playlistAPI['various_artist'] = dz.get_artist(5080) + 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 - def convert_spotify_playlist(self, 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) - playlistAPI['various_artist'] = dz.get_artist(5080) - 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 + pass diff --git a/deemix/utils/localpaths.py b/deemix/utils/localpaths.py index f2fbbe8..5b71e6a 100644 --- a/deemix/utils/localpaths.py +++ b/deemix/utils/localpaths.py @@ -1,21 +1,24 @@ #!/usr/bin/env python3 -import sys import os.path as path +import sys from os import getenv userdata = "" homedata = path.expanduser("~") if getenv("APPDATA"): - userdata = getenv("APPDATA") + path.sep + "deemix" + path.sep + userdata = getenv("APPDATA") + path.sep + "deemix" + path.sep elif sys.platform.startswith('darwin'): - userdata = homedata + '/Library/Application Support/deemix/' + userdata = homedata + '/Library/Application Support/deemix/' elif getenv("XDG_CONFIG_HOME"): - userdata = getenv("XDG_CONFIG_HOME") + '/deemix/'; + userdata = getenv("XDG_CONFIG_HOME") + '/deemix/'; else: - userdata = homedata + '/.config/deemix/'; + userdata = homedata + '/.config/deemix/'; + def getHomeFolder(): - return homedata + return homedata + + def getConfigFolder(): - return userdata + return userdata diff --git a/deemix/utils/misc.py b/deemix/utils/misc.py index 31213f1..a41028a 100644 --- a/deemix/utils/misc.py +++ b/deemix/utils/misc.py @@ -1,94 +1,98 @@ #!/usr/bin/env python3 import re + def getBitrateInt(txt): - txt = str(txt) - if txt in ['flac', 'lossless', '9']: - return 9 - elif txt in ['mp3', '320', '3']: - return 3 - elif txt in ['128', '1']: - return 1 - elif txt in ['360', '360_hq', '15']: - return 15 - elif txt in ['360_mq', '14']: - return 14 - elif txt in ['360_lq', '13']: - return 13 - else: - return None + txt = str(txt) + if txt in ['flac', 'lossless', '9']: + return 9 + elif txt in ['mp3', '320', '3']: + return 3 + elif txt in ['128', '1']: + return 1 + elif txt in ['360', '360_hq', '15']: + return 15 + elif txt in ['360_mq', '14']: + return 14 + elif txt in ['360_lq', '13']: + return 13 + else: + return None + def changeCase(string, type): - if type == "lower": - return string.lower() - elif type == "upper": - return string.upper() - elif type == "start": - string = string.split(" ") - res = [] - for index, value in enumerate(string): - res.append(value[0].upper() + value[0:].lower()) - res = " ".join(res) - return res - elif type == "sentence": - res = string[0].upper() + string[0:].lower() - return res - else: - return string + if type == "lower": + return string.lower() + elif type == "upper": + return string.upper() + elif type == "start": + string = string.split(" ") + res = [] + for index, value in enumerate(string): + res.append(value[0].upper() + value[0:].lower()) + res = " ".join(res) + return res + elif type == "sentence": + res = string[0].upper() + string[0:].lower() + return res + else: + return string + def getIDFromLink(link, type): - if '?' in link: - link = link[:link.find('?')] - if link.endswith("/"): - link = link[:-1] + if '?' in link: + link = link[:link.find('?')] + if link.endswith("/"): + link = link[:-1] - if link.startswith("http") and 'open.spotify.com/' in link: - if type == "spotifyplaylist": - return link[link.find("/playlist/") + 10:] - if type == "spotifytrack": - return link[link.find("/track/") + 7:] - if type == "spotifyalbum": - return link[link.find("/album/") + 7:] - elif link.startswith("spotify:"): - if type == "spotifyplaylist": - return link[link.find("playlist:") + 9:] - if type == "spotifytrack": - return link[link.find("track:") + 6:] - if type == "spotifyalbum": - return link[link.find("album:") + 6:] - elif type == "artisttop": - return re.search(r"\/artist\/(\d+)\/top_track", link)[1] - else: - return link[link.rfind("/") + 1:] + if link.startswith("http") and 'open.spotify.com/' in link: + if type == "spotifyplaylist": + return link[link.find("/playlist/") + 10:] + if type == "spotifytrack": + return link[link.find("/track/") + 7:] + if type == "spotifyalbum": + return link[link.find("/album/") + 7:] + elif link.startswith("spotify:"): + if type == "spotifyplaylist": + return link[link.find("playlist:") + 9:] + if type == "spotifytrack": + return link[link.find("track:") + 6:] + if type == "spotifyalbum": + return link[link.find("album:") + 6:] + elif type == "artisttop": + return re.search(r"\/artist\/(\d+)\/top_track", link)[1] + else: + return link[link.rfind("/") + 1:] def getTypeFromLink(link): - type = '' - if 'spotify' in link: - type = 'spotify' - if 'playlist' in link: - type += 'playlist' - elif 'track' in link: - type += 'track' - elif 'album' in link: - type += 'album' - elif 'deezer' in link: - if '/track' in link: - type = 'track' - elif '/playlist' in link: - type = 'playlist' - elif '/album' in link: - type = 'album' - elif re.search("\/artist\/(\d+)\/top_track", link): - type = 'artisttop' - elif '/artist' in link: - type = 'artist' - return type + type = '' + if 'spotify' in link: + type = 'spotify' + if 'playlist' in link: + type += 'playlist' + elif 'track' in link: + type += 'track' + elif 'album' in link: + type += 'album' + elif 'deezer' in link: + if '/track' in link: + type = 'track' + elif '/playlist' in link: + type = 'playlist' + elif '/album' in link: + type = 'album' + elif re.search("\/artist\/(\d+)\/top_track", link): + type = 'artisttop' + elif '/artist' in link: + type = 'artist' + return type + def isValidLink(text): - if text.lower().startswith("http"): - if "deezer.com" in text.lower() or "open.spotify.com" in text.lower(): - return True - elif text.lower().startswith("spotify:"): - return True - return False + if text.lower().startswith("http"): + if "deezer.com" in text.lower() or "open.spotify.com" in text.lower(): + return True + elif text.lower().startswith("spotify:"): + return True + return False diff --git a/deemix/utils/pathtemplates.py b/deemix/utils/pathtemplates.py index 1eaa2c2..fbc174a 100644 --- a/deemix/utils/pathtemplates.py +++ b/deemix/utils/pathtemplates.py @@ -3,166 +3,194 @@ import re from os.path import sep as pathSep bitrateLabels = { - 15: "360 HQ", - 14: "360 MQ", - 13: "360 LQ", - 9: "FLAC", - 3: "320", - 1: "128", - 8: "128" + 15: "360 HQ", + 14: "360 MQ", + 13: "360 LQ", + 9: "FLAC", + 3: "320", + 1: "128", + 8: "128" } + def fixName(txt, char='_'): - txt = str(txt) - txt = re.sub(r'[\0\/\\:*?"<>|]', char, txt) - return txt + txt = str(txt) + txt = re.sub(r'[\0\/\\:*?"<>|]', char, txt) + return txt + def fixLongName(name): - if pathSep in name: - name2 = name.split(pathSep) - name = "" - for txt in name2: - txt = txt[:200] - name += txt+pathSep - name = name[:-1] - else: - name = name[:200] - return name + if pathSep in name: + name2 = name.split(pathSep) + name = "" + for txt in name2: + txt = txt[:200] + name += txt + pathSep + name = name[:-1] + else: + name = name[:200] + return name + def antiDot(string): - while string[-1:] == "." or string[-1:] == " " or string[-1:] == "\n": - string = string[:-1] - if len(string) < 1: - string = "dot" - return string + while string[-1:] == "." or string[-1:] == " " or string[-1:] == "\n": + string = string[:-1] + if len(string) < 1: + string = "dot" + return string + def pad(num, max, dopad=True): - paddingsize = len(str(max)) - if dopad: - return str(num).zfill(paddingsize) - else: - return str(num) + paddingsize = len(str(max)) + if dopad: + return str(num).zfill(paddingsize) + else: + return str(num) + def generateFilename(track, trackAPI, settings): - if trackAPI['FILENAME_TEMPLATE'] == "": - filename = "%artist% - %title%" - else: - filename = trackAPI['FILENAME_TEMPLATE'] - return settingsRegex(filename, track, settings, trackAPI['_EXTRA_PLAYLIST'] if '_EXTRA_PLAYLIST' in trackAPI else None) + if trackAPI['FILENAME_TEMPLATE'] == "": + filename = "%artist% - %title%" + else: + filename = trackAPI['FILENAME_TEMPLATE'] + return settingsRegex(filename, track, settings, + trackAPI['_EXTRA_PLAYLIST'] if '_EXTRA_PLAYLIST' in trackAPI else None) + def generateFilepath(track, trackAPI, settings): - filepath = settings['downloadLocation'] - if filepath[-1:] != pathSep: - filepath += pathSep - artistPath = None - coverPath = None - extrasPath = None + filepath = settings['downloadLocation'] + if filepath[-1:] != pathSep: + filepath += pathSep + artistPath = None + coverPath = None + extrasPath = None - if settings['createPlaylistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']: - filepath += antiDot(settingsRegexPlaylist(settings['playlistNameTemplate'], trackAPI['_EXTRA_PLAYLIST'], settings)) + pathSep + if settings['createPlaylistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and not settings['tags'][ + 'savePlaylistAsCompilation']: + filepath += antiDot( + settingsRegexPlaylist(settings['playlistNameTemplate'], trackAPI['_EXTRA_PLAYLIST'], settings)) + pathSep - if '_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']: - extrasPath = filepath + if '_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']: + extrasPath = filepath - if ( - settings['createArtistFolder'] and not '_EXTRA_PLAYLIST' in trackAPI or - (settings['createArtistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']) or - (settings['createArtistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist']) - ): - if (int(track['id'])<0 and not 'mainArtist' in track['album']): - track['album']['mainArtist'] = track['mainArtist'] - filepath += antiDot(settingsRegexArtist(settings['artistNameTemplate'], track['album']['mainArtist'], settings)) + pathSep - artistPath = filepath + if ( + settings['createArtistFolder'] and not '_EXTRA_PLAYLIST' in trackAPI or + (settings['createArtistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and settings['tags'][ + 'savePlaylistAsCompilation']) or + (settings['createArtistFolder'] and '_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist']) + ): + if (int(track['id']) < 0 and not 'mainArtist' in track['album']): + track['album']['mainArtist'] = track['mainArtist'] + filepath += antiDot( + settingsRegexArtist(settings['artistNameTemplate'], track['album']['mainArtist'], settings)) + pathSep + artistPath = filepath - if (settings['createAlbumFolder'] and - (not 'SINGLE_TRACK' in trackAPI or ('SINGLE_TRACK' in trackAPI and settings['createSingleFolder'])) and - (not '_EXTRA_PLAYLIST' in trackAPI or ('_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']) or ('_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist'])) - ): - filepath += antiDot(settingsRegexAlbum(settings['albumNameTemplate'], track['album'], settings, trackAPI)) + pathSep - coverPath = filepath + if (settings['createAlbumFolder'] and + (not 'SINGLE_TRACK' in trackAPI or ('SINGLE_TRACK' in trackAPI and settings['createSingleFolder'])) and + (not '_EXTRA_PLAYLIST' in trackAPI or ( + '_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']) or ( + '_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist'])) + ): + filepath += antiDot( + settingsRegexAlbum(settings['albumNameTemplate'], track['album'], settings, trackAPI)) + pathSep + coverPath = filepath - if not ('_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']): - extrasPath = filepath + if not ('_EXTRA_PLAYLIST' in trackAPI and not settings['tags']['savePlaylistAsCompilation']): + extrasPath = filepath - if ( - int(track['album']['discTotal']) > 1 and ( - (settings['createAlbumFolder'] and settings['createCDFolder']) and - (not 'SINGLE_TRACK' in trackAPI or ('SINGLE_TRACK' in trackAPI and settings['createSingleFolder'])) and - (not '_EXTRA_PLAYLIST' in trackAPI or ('_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']) or ('_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist'])) - )): - filepath += 'CD' + str(track['discNumber']) + pathSep + if ( + int(track['album']['discTotal']) > 1 and ( + (settings['createAlbumFolder'] and settings['createCDFolder']) and + (not 'SINGLE_TRACK' in trackAPI or ('SINGLE_TRACK' in trackAPI and settings['createSingleFolder'])) and + (not '_EXTRA_PLAYLIST' in trackAPI or ( + '_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']) or ( + '_EXTRA_PLAYLIST' in trackAPI and settings['createStructurePlaylist'])) + )): + filepath += 'CD' + str(track['discNumber']) + pathSep + + return (filepath, artistPath, coverPath, extrasPath) - return (filepath, artistPath, coverPath, extrasPath) 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['mainArtistsString'], settings['illegalCharacterReplacer'])) - filename = filename.replace("%album%", fixName(track['album']['title'], settings['illegalCharacterReplacer'])) - filename = filename.replace("%albumartist%", fixName(track['album']['mainArtist']['name'], settings['illegalCharacterReplacer'])) - filename = filename.replace("%tracknumber%", pad(track['trackNumber'], track['album']['trackTotal'] if int(settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize'])-1), settings['padTracks'])) - filename = filename.replace("%tracktotal%", str(track['album']['trackTotal'])) - filename = filename.replace("%discnumber%", str(track['discNumber'])) - filename = filename.replace("%disctotal%", str(track['album']['discTotal'])) - if len(track['album']['genre'])>0: - filename = filename.replace("%genre%", fixName(track['album']['genre'][0], settings['illegalCharacterReplacer'])) - else: - filename = filename.replace("%genre%", "Unknown") - filename = filename.replace("%year%", str(track['date']['year'])) - filename = filename.replace("%date%", track['dateString']) - filename = filename.replace("%bpm%", str(track['bpm'])) - filename = filename.replace("%label%", fixName(track['album']['label'], settings['illegalCharacterReplacer'])) - filename = filename.replace("%isrc%", track['ISRC']) - filename = filename.replace("%upc%", track['album']['barcode']) - filename = filename.replace("%explicit%", "(Explicit)" if track['explicit'] else "") + 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['mainArtistsString'], settings['illegalCharacterReplacer'])) + filename = filename.replace("%album%", fixName(track['album']['title'], settings['illegalCharacterReplacer'])) + filename = filename.replace("%albumartist%", + fixName(track['album']['mainArtist']['name'], settings['illegalCharacterReplacer'])) + filename = filename.replace("%tracknumber%", pad(track['trackNumber'], track['album']['trackTotal'] if int( + settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize']) - 1), settings['padTracks'])) + filename = filename.replace("%tracktotal%", str(track['album']['trackTotal'])) + filename = filename.replace("%discnumber%", str(track['discNumber'])) + filename = filename.replace("%disctotal%", str(track['album']['discTotal'])) + if len(track['album']['genre']) > 0: + filename = filename.replace("%genre%", + fixName(track['album']['genre'][0], settings['illegalCharacterReplacer'])) + else: + filename = filename.replace("%genre%", "Unknown") + filename = filename.replace("%year%", str(track['date']['year'])) + filename = filename.replace("%date%", track['dateString']) + filename = filename.replace("%bpm%", str(track['bpm'])) + filename = filename.replace("%label%", fixName(track['album']['label'], settings['illegalCharacterReplacer'])) + filename = filename.replace("%isrc%", track['ISRC']) + filename = filename.replace("%upc%", track['album']['barcode']) + filename = filename.replace("%explicit%", "(Explicit)" if track['explicit'] else "") + + filename = filename.replace("%track_id%", str(track['id'])) + filename = filename.replace("%album_id%", str(track['album']['id'])) + filename = filename.replace("%artist_id%", str(track['mainArtist']['id'])) + if playlist: + filename = filename.replace("%playlist_id%", str(playlist['id'])) + filename = filename.replace("%position%", pad(track['position'], playlist['nb_tracks'] if int( + settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize']) - 1), settings['padTracks'])) + else: + filename = filename.replace("%position%", pad(track['trackNumber'], track['album']['trackTotal'] if int( + settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize']) - 1), settings['padTracks'])) + filename = filename.replace('\\', pathSep).replace('/', pathSep) + return antiDot(fixLongName(filename)) - filename = filename.replace("%track_id%", str(track['id'])) - filename = filename.replace("%album_id%", str(track['album']['id'])) - filename = filename.replace("%artist_id%", str(track['mainArtist']['id'])) - if playlist: - filename = filename.replace("%playlist_id%", str(playlist['id'])) - filename = filename.replace("%position%", pad(track['position'], playlist['nb_tracks'] if int(settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize'])-1), settings['padTracks'])) - else: - filename = filename.replace("%position%", pad(track['trackNumber'], track['album']['trackTotal'] if int(settings['paddingSize']) == 0 else 10 ** (int(settings['paddingSize'])-1), settings['padTracks'])) - filename = filename.replace('\\', pathSep).replace('/', pathSep) - return antiDot(fixLongName(filename)) def settingsRegexAlbum(foldername, album, settings, trackAPI): - if trackAPI and '_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']: - foldername = foldername.replace("%album_id%", "pl_"+str(trackAPI['_EXTRA_PLAYLIST']['id'])) - else: - foldername = foldername.replace("%album_id%", str(album['id'])) - foldername = foldername.replace("%album%", fixName(album['title'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%artist%", fixName(album['mainArtist']['name'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%artist_id%", str(album['mainArtist']['id'])) - foldername = foldername.replace("%tracktotal%", str(album['trackTotal'])) - foldername = foldername.replace("%disctotal%", str(album['discTotal'])) - foldername = foldername.replace("%type%", fixName(album['recordType'][0].upper()+album['recordType'][1:].lower(), settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%upc%", album['barcode']) - foldername = foldername.replace("%label%", fixName(album['label'], settings['illegalCharacterReplacer'])) - if len(album['genre']) > 0: - foldername = foldername.replace("%genre%", fixName(album['genre'][0], settings['illegalCharacterReplacer'])) - else: - foldername = foldername.replace("%genre%", "Unknown") - foldername = foldername.replace("%year%", str(album['date']['year'])) - foldername = foldername.replace("%date%", album['dateString']) - foldername = foldername.replace("%bitrate%", bitrateLabels[int(album['bitrate'])]) + if trackAPI and '_EXTRA_PLAYLIST' in trackAPI and settings['tags']['savePlaylistAsCompilation']: + foldername = foldername.replace("%album_id%", "pl_" + str(trackAPI['_EXTRA_PLAYLIST']['id'])) + else: + foldername = foldername.replace("%album_id%", str(album['id'])) + foldername = foldername.replace("%album%", fixName(album['title'], settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%artist%", + fixName(album['mainArtist']['name'], settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%artist_id%", str(album['mainArtist']['id'])) + foldername = foldername.replace("%tracktotal%", str(album['trackTotal'])) + foldername = foldername.replace("%disctotal%", str(album['discTotal'])) + foldername = foldername.replace("%type%", fixName(album['recordType'][0].upper() + album['recordType'][1:].lower(), + settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%upc%", album['barcode']) + foldername = foldername.replace("%label%", fixName(album['label'], settings['illegalCharacterReplacer'])) + if len(album['genre']) > 0: + foldername = foldername.replace("%genre%", fixName(album['genre'][0], settings['illegalCharacterReplacer'])) + else: + foldername = foldername.replace("%genre%", "Unknown") + foldername = foldername.replace("%year%", str(album['date']['year'])) + foldername = foldername.replace("%date%", album['dateString']) + foldername = foldername.replace("%bitrate%", bitrateLabels[int(album['bitrate'])]) + + foldername = foldername.replace('\\', pathSep).replace('/', pathSep) + return antiDot(fixLongName(foldername)) - foldername = foldername.replace('\\', pathSep).replace('/', pathSep) - return antiDot(fixLongName(foldername)) def settingsRegexArtist(foldername, artist, settings): - foldername = foldername.replace("%artist%", fixName(artist['name'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%artist_id%", str(artist['id'])) - foldername = foldername.replace('\\', pathSep).replace('/', pathSep) - return antiDot(fixLongName(foldername)) + foldername = foldername.replace("%artist%", fixName(artist['name'], settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%artist_id%", str(artist['id'])) + foldername = foldername.replace('\\', pathSep).replace('/', pathSep) + return antiDot(fixLongName(foldername)) + def settingsRegexPlaylist(foldername, playlist, settings): - foldername = foldername.replace("%playlist%", fixName(playlist['title'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%playlist_id%", fixName(playlist['id'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%owner%", fixName(playlist['creator']['name'], settings['illegalCharacterReplacer'])) - foldername = foldername.replace("%owner_id%", str(playlist['creator']['id'])) - foldername = foldername.replace("%year%", str(playlist['creation_date'][:4])) - foldername = foldername.replace("%date%", str(playlist['creation_date'][:10])) - foldername = foldername.replace('\\', pathSep).replace('/', pathSep) - return antiDot(fixLongName(foldername)) + foldername = foldername.replace("%playlist%", fixName(playlist['title'], settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%playlist_id%", fixName(playlist['id'], settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%owner%", + fixName(playlist['creator']['name'], settings['illegalCharacterReplacer'])) + foldername = foldername.replace("%owner_id%", str(playlist['creator']['id'])) + foldername = foldername.replace("%year%", str(playlist['creation_date'][:4])) + foldername = foldername.replace("%date%", str(playlist['creation_date'][:10])) + foldername = foldername.replace('\\', pathSep).replace('/', pathSep) + return antiDot(fixLongName(foldername)) diff --git a/deemix/utils/taggers.py b/deemix/utils/taggers.py index d9a61db..41c6d2d 100644 --- a/deemix/utils/taggers.py +++ b/deemix/utils/taggers.py @@ -1,135 +1,139 @@ #!/usr/bin/env python3 from mutagen.flac import FLAC, Picture from mutagen.id3 import ID3, ID3NoHeaderError, TXXX, TIT2, TPE1, TALB, TPE2, TRCK, TPOS, TCON, TYER, TDAT, TLEN, TBPM, \ - TPUB, TSRC, USLT, APIC, IPLS, TCOM, TCOP, TCMP + TPUB, TSRC, USLT, APIC, IPLS, TCOM, TCOP, TCMP def tagID3(stream, track, save): - try: - tag = ID3(stream) - except ID3NoHeaderError: - tag = ID3() + try: + tag = ID3(stream) + except ID3NoHeaderError: + tag = ID3() - if save['title']: - tag.add(TIT2(text=track['title'])) - if save['artist']: - if save['multitagSeparator'] != "default": - tag.add(TPE1(text=track['artistsString'])) - tag.add(TXXX(desc="ARTISTS", text=track['artists'])) - else: - tag.add(TPE1(text=track['artists'])) - if save['album']: - tag.add(TALB(text=track['album']['title'])) - if save['albumArtist']: - tag.add(TPE2(text=track['album']['artists'])) - if save['trackNumber']: - tag.add(TRCK(text=str(track['trackNumber'])+("/"+str(track['album']['trackTotal']) if save['trackTotal'] else ""))) - if save['discNumber']: - tag.add(TPOS(text=str(track['discNumber'])+("/"+str(track['album']['discTotal']) if save['discTotal'] else ""))) - if save['genre']: - tag.add(TCON(text=track['album']['genre'])) - if save['year']: - tag.add(TYER(text=str(track['date']['year']))) - if save['date']: - tag.add(TDAT(text=str(track['date']['month']) + str(track['date']['day']))) - if save['length']: - tag.add(TLEN(text=str(track['duration']))) - if save['bpm']: - tag.add(TBPM(text=str(track['bpm']))) - if save['label']: - tag.add(TPUB(text=track['album']['label'])) - if save['isrc']: - tag.add(TSRC(text=track['ISRC'])) - if save['barcode']: - tag.add(TXXX(desc="BARCODE", text=track['album']['barcode'])) - if save['explicit']: - tag.add(TXXX(desc="ITUNESADVISORY", text="1" if track['explicit'] else "0")) - if save['replayGain']: - tag.add(TXXX(desc="REPLAYGAIN_TRACK_GAIN", text=track['replayGain'])) - if 'unsync' in track['lyrics'] and save['lyrics']: - tag.add(USLT(text=track['lyrics']['unsync'])) - involved_people = [] - for role in track['contributors']: - if role in ['author', 'engineer', 'mixer', 'producer', 'writer']: - for person in track['contributors'][role]: - involved_people.append([role, person]) - elif role == 'composer' and save['composer']: - tag.add(TCOM(text=track['contributors']['composer'])) - if len(involved_people) > 0 and save['involvedPeople']: - tag.add(IPLS(people=involved_people)) - if save['copyright']: - tag.add(TCOP(text=track['copyright'])) - if save['savePlaylistAsCompilation']: - tag.add(TCMP(text="1")) - if save['cover'] and track['album']['picPath']: - with open(track['album']['picPath'], 'rb') as f: - tag.add(APIC(3, 'image/jpeg' if track['album']['picPath'].endswith('jpg') else 'image/png', 3, data=f.read())) + if save['title']: + tag.add(TIT2(text=track['title'])) + if save['artist']: + if save['multitagSeparator'] != "default": + tag.add(TPE1(text=track['artistsString'])) + tag.add(TXXX(desc="ARTISTS", text=track['artists'])) + else: + tag.add(TPE1(text=track['artists'])) + if save['album']: + tag.add(TALB(text=track['album']['title'])) + if save['albumArtist']: + tag.add(TPE2(text=track['album']['artists'])) + if save['trackNumber']: + tag.add(TRCK( + text=str(track['trackNumber']) + ("/" + str(track['album']['trackTotal']) if save['trackTotal'] else ""))) + if save['discNumber']: + tag.add( + TPOS(text=str(track['discNumber']) + ("/" + str(track['album']['discTotal']) if save['discTotal'] else ""))) + if save['genre']: + tag.add(TCON(text=track['album']['genre'])) + if save['year']: + tag.add(TYER(text=str(track['date']['year']))) + if save['date']: + tag.add(TDAT(text=str(track['date']['month']) + str(track['date']['day']))) + if save['length']: + tag.add(TLEN(text=str(track['duration']))) + if save['bpm']: + tag.add(TBPM(text=str(track['bpm']))) + if save['label']: + tag.add(TPUB(text=track['album']['label'])) + if save['isrc']: + tag.add(TSRC(text=track['ISRC'])) + if save['barcode']: + tag.add(TXXX(desc="BARCODE", text=track['album']['barcode'])) + if save['explicit']: + tag.add(TXXX(desc="ITUNESADVISORY", text="1" if track['explicit'] else "0")) + if save['replayGain']: + tag.add(TXXX(desc="REPLAYGAIN_TRACK_GAIN", text=track['replayGain'])) + if 'unsync' in track['lyrics'] and save['lyrics']: + tag.add(USLT(text=track['lyrics']['unsync'])) + involved_people = [] + for role in track['contributors']: + if role in ['author', 'engineer', 'mixer', 'producer', 'writer']: + for person in track['contributors'][role]: + involved_people.append([role, person]) + elif role == 'composer' and save['composer']: + tag.add(TCOM(text=track['contributors']['composer'])) + if len(involved_people) > 0 and save['involvedPeople']: + tag.add(IPLS(people=involved_people)) + if save['copyright']: + tag.add(TCOP(text=track['copyright'])) + if save['savePlaylistAsCompilation']: + tag.add(TCMP(text="1")) + if save['cover'] and track['album']['picPath']: + with open(track['album']['picPath'], 'rb') as f: + tag.add( + APIC(3, 'image/jpeg' if track['album']['picPath'].endswith('jpg') else 'image/png', 3, data=f.read())) - tag.save(stream, v1=2 if save['saveID3v1'] else 0, v2_version=3, v23_sep=None if save['useNullSeparator'] else ' / ') + tag.save(stream, v1=2 if save['saveID3v1'] else 0, v2_version=3, + v23_sep=None if save['useNullSeparator'] else ' / ') def tagFLAC(stream, track, save): - tag = FLAC(stream) + tag = FLAC(stream) - if save['title']: - tag["TITLE"] = track['title'] - if save['artist']: - if save['multitagSeparator'] != "default": - tag["ARTIST"] = track['artistsString'] - tag["ARTISTS"] = track['artists'] - else: - tag["ARTIST"] = track['artists'] - if save['album']: - tag["ALBUM"] = track['album']['title'] - if save['albumArtist']: - tag["ALBUMARTIST"] = track['album']['artists'] - if save['trackNumber']: - tag["TRACKNUMBER"] = str(track['trackNumber']) - if save['trackTotal']: - tag["TRACKTOTAL"] = str(track['album']['trackTotal']) - if save['discNumber']: - tag["DISCNUMBER"] = str(track['discNumber']) - if save['discTotal']: - tag["DISCTOTAL"] = str(track['album']['discTotal']) - if save['genre']: - tag["GENRE"] = track['album']['genre'] - if save['year']: - tag["YEAR"] = str(track['date']['year']) - if save['date']: - tag["DATE"] = track['dateString'] - if save['length']: - tag["LENGTH"] = str(track['duration']) - if save['bpm']: - tag["BPM"] = str(track['bpm']) - if save['label']: - tag["PUBLISHER"] = track['album']['label'] - if save['isrc']: - tag["ISRC"] = track['ISRC'] - if save['barcode']: - tag["BARCODE"] = track['album']['barcode'] - if save['explicit']: - tag["ITUNESADVISORY"] = "1" if track['explicit'] else "0" - if save['replayGain']: - tag["REPLAYGAIN_TRACK_GAIN"] = track['replayGain'] - if 'unsync' in track['lyrics'] and save['lyrics']: - tag["LYRICS"] = track['lyrics']['unsync'] - for role in track['contributors']: - if role in ['author', 'engineer', 'mixer', 'producer', 'writer', 'composer']: - if save['involvedPeople'] and role != 'composer' or role == 'composer' and save['composer']: - tag[role] = track['contributors'][role] - elif role == 'musicpublisher' and save['involvedPeople']: - tag["ORGANIZATION"] = track['contributors']['musicpublisher'] - if save['copyright']: - tag["COPYRIGHT"] = track['copyright'] - if save['savePlaylistAsCompilation']: - tag["COMPILATION"] = "1" + if save['title']: + tag["TITLE"] = track['title'] + if save['artist']: + if save['multitagSeparator'] != "default": + tag["ARTIST"] = track['artistsString'] + tag["ARTISTS"] = track['artists'] + else: + tag["ARTIST"] = track['artists'] + if save['album']: + tag["ALBUM"] = track['album']['title'] + if save['albumArtist']: + tag["ALBUMARTIST"] = track['album']['artists'] + if save['trackNumber']: + tag["TRACKNUMBER"] = str(track['trackNumber']) + if save['trackTotal']: + tag["TRACKTOTAL"] = str(track['album']['trackTotal']) + if save['discNumber']: + tag["DISCNUMBER"] = str(track['discNumber']) + if save['discTotal']: + tag["DISCTOTAL"] = str(track['album']['discTotal']) + if save['genre']: + tag["GENRE"] = track['album']['genre'] + if save['year']: + tag["YEAR"] = str(track['date']['year']) + if save['date']: + tag["DATE"] = track['dateString'] + if save['length']: + tag["LENGTH"] = str(track['duration']) + if save['bpm']: + tag["BPM"] = str(track['bpm']) + if save['label']: + tag["PUBLISHER"] = track['album']['label'] + if save['isrc']: + tag["ISRC"] = track['ISRC'] + if save['barcode']: + tag["BARCODE"] = track['album']['barcode'] + if save['explicit']: + tag["ITUNESADVISORY"] = "1" if track['explicit'] else "0" + if save['replayGain']: + tag["REPLAYGAIN_TRACK_GAIN"] = track['replayGain'] + if 'unsync' in track['lyrics'] and save['lyrics']: + tag["LYRICS"] = track['lyrics']['unsync'] + for role in track['contributors']: + if role in ['author', 'engineer', 'mixer', 'producer', 'writer', 'composer']: + if save['involvedPeople'] and role != 'composer' or role == 'composer' and save['composer']: + tag[role] = track['contributors'][role] + elif role == 'musicpublisher' and save['involvedPeople']: + tag["ORGANIZATION"] = track['contributors']['musicpublisher'] + if save['copyright']: + tag["COPYRIGHT"] = track['copyright'] + if save['savePlaylistAsCompilation']: + tag["COMPILATION"] = "1" - if save['cover'] and track['album']['picPath']: - image = Picture() - image.type = 3 - image.mime = 'image/jpeg' if track['album']['picPath'].endswith('jpg') else 'image/png' - with open(track['album']['picPath'], 'rb') as f: - image.data = f.read() - tag.add_picture(image) + if save['cover'] and track['album']['picPath']: + image = Picture() + image.type = 3 + image.mime = 'image/jpeg' if track['album']['picPath'].endswith('jpg') else 'image/png' + with open(track['album']['picPath'], 'rb') as f: + image.data = f.read() + tag.add_picture(image) - tag.save(deleteid3=True) + tag.save(deleteid3=True)