From a85396600ae4f0c77d7bb99e2f7a9f0b7db1e518 Mon Sep 17 00:00:00 2001 From: uh_wot Date: Fri, 24 Jul 2020 16:21:24 +0200 Subject: [PATCH] rewrote getPreferredBitrate function using mobile API workaround should be more stable than constantly requesting the CDN --- deemix/api/deezer.py | 32 ++++++++++++++++++++--- deemix/app/downloader.py | 55 ++++++++++++++++++++++++---------------- 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/deemix/api/deezer.py b/deemix/api/deezer.py index d0ded8f..111d129 100755 --- a/deemix/api/deezer.py +++ b/deemix/api/deezer.py @@ -29,12 +29,37 @@ class Deezer: self.selectedAccount = 0 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') + self.session.post("https://www.deezer.com/", headers=self.http_headers) + self.guest_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_track_filesizes(self, sng_id): + try: + site = requests.post( + "https://api.deezer.com/1.0/gateway.php", + params={ + 'api_key': "4VCYIJUCDLOUELGD1V8WBVYBNVDYOXEWSLLZDONGBBDFVXTZJRXPR29JRLQFO6ZE", + 'sid': self.guest_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_filesizes(sng_id) + response = site.json()["results"] + filesizes = {} + for key, value in response.items(): + if key.startswith("FILESIZE_"): + filesizes[key] = value + return filesizes def gw_api_call(self, method, args=None): if args is None: @@ -129,13 +154,14 @@ class Deezer: def login_via_arl(self, arl, child=0): cookie_obj = requests.cookies.create_cookie( - domain='deezer.com', + domain='.deezer.com', name='arl', value=arl, path="/", rest={'HttpOnly': True} ) self.session.cookies.set_cookie(cookie_obj) + self.session.cookies.clear(".deezer.com", "/", "sid") user_data = self.gw_api_call("deezer.getUserData") if user_data["results"]["USER"]["USER_ID"] == 0: self.logged_in = False diff --git a/deemix/app/downloader.py b/deemix/app/downloader.py index ca07d8c..f6bd58f 100644 --- a/deemix/app/downloader.py +++ b/deemix/app/downloader.py @@ -56,7 +56,7 @@ def stream_track(dz, track, stream, trackAPI, queueItem, interface=None): for chunk in request.iter_content(2048): if 'cancel' in queueItem: raise downloadCancelled - if (i % 3) == 0 and len(chunk) == 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) chunkLength += len(chunk) @@ -130,31 +130,42 @@ def formatDate(date, template): def getPreferredBitrate(dz, track, bitrate, fallback=True): if 'localTrack' in track: return 0 - formatsnon360 = [9, 3, 1] # flac, mp3_320, mp3_128 - formats360 = [15, 14, 13] # 360_hq, 360_mq, 360_lq - if not fallback: - errorNum = -100 - formats = formats360 - formats.extend(formatsnon360) - elif int(bitrate) in formats360: - errorNum = -200 - formats = formats360 - else: - errorNum = 8 - formats = formatsnon360 + + formats_non_360 = { + 9: "FLAC", + 3: "MP3_320", + 1: "MP3_128", + } + formats_360 = { + 15: "MP4_RA3", + 14: "MP4_RA2", + 13: "MP4_RA1", + } - for formatNum in formats: - if formatNum <= int(bitrate): - request = get(dz.get_track_stream_url(track['id'], track['MD5'], track['mediaVersion'], formatNum), stream=True) - try: - request.raise_for_status() - except HTTPError: # if the format is not available, Deezer returns a 403 error + if not fallback: + error_num = -100 + formats = formats360 + formats.extend(formats_non_360) + elif int(bitrate) in formats_360: + error_num = -200 + formats = formats_360 + else: + error_num = 8 + formats = formats_non_360 + + filesizes = dz.get_track_filesizes(track["id"]) + + for format_num, format in formats.items(): + if format_num <= int(bitrate): + if f"FILESIZE_{format}" in filesizes and int(filesizes[f"FILESIZE_{format}"]) != 0: + return format_num + else: if fallback: continue else: - return errorNum - return formatNum - return errorNum # fallback is enabled and loop went through all formats + return error_num + + return error_num # fallback is enabled and loop went through all formats def parseEssentialTrackData(track, trackAPI):