Skip to content

Commit

Permalink
Finish deezer downloads, remove soundcloud parsing delay
Browse files Browse the repository at this point in the history
  • Loading branch information
justin025 committed Nov 7, 2024
1 parent 85326e2 commit df425a8
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 108 deletions.
112 changes: 47 additions & 65 deletions src/onthespot/api/deezer.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def deezer_get_track_metadata(token, item_id):

return info

def get_song_infos_from_deezer_website(id):
def get_song_info_from_deezer_website(id):
url = f"https://www.deezer.com/us/track/{id}"
session = account_pool[config.get('parsing_acc_sn')]['login']['session']
resp = session.get(url)
Expand Down Expand Up @@ -151,18 +151,19 @@ def blowfishDecrypt(data, key):
c = Blowfish.new(key.encode(), Blowfish.MODE_CBC, iv)
return c.decrypt(data)

def decryptfile(fh, key, fo):
def decryptfile(data_chunks, key, fo):
"""
Decrypt data from file <fh>, and write to file <fo>.
decrypt using blowfish with <key>.
Decrypt data from bytes <data_chunks>, and write to file <fo>.
Decrypt using blowfish with <key>.
Only every third 2048 byte block is encrypted.
"""
blockSize = 2048
i = 0
blockSize = 2048
i = 0
total_length = len(data_chunks)

for data in fh.iter_content(blockSize):
if not data:
break
for start in range(0, total_length, blockSize):
end = min(start + blockSize, total_length)
data = data_chunks[start:end]

isEncrypted = ((i % 3) == 0)
isWholeBlock = len(data) == blockSize
Expand All @@ -185,61 +186,41 @@ def genurlkey(songid, md5origin, mediaver=4, fmt=1):
return hexaescrypt(data, "jo6aey6haid2Teih")

def deezer_login_user(account):
uuid = account['uuid']
arl = account['login']['arl']
headers = {
'Pragma': 'no-cache',
'Origin': 'https://www.deezer.com',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-US,en;q=0.9',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': '*/*',
'Cache-Control': 'no-cache',
'X-Requested-With': 'XMLHttpRequest',
'Connection': 'keep-alive',
'Referer': 'https://www.deezer.com/login',
'DNT': '1',
}
session = requests.Session()
session.headers.update(headers)
session.cookies.update({'arl': arl, 'comeback': '1'})

api_token = None

# Prepare to call the API
method = 'deezer.getUserData'
args = {}
params = {}

# Main API call logic

p = {
'api_version': "1.0",
'api_token': 'null',
'input': '3',
'method': method
}
p.update(params)


resp = session.post(
"http://www.deezer.com/ajax/gw-light.php",
params=p,
timeout=30,
json=args,
headers=headers
)

user_data = resp.json()

bitrate = '128k'
if user_data["results"]["USER"]["OPTIONS"]["web_lossless"]:
bitrate = '1411k'
elif user_data["results"]["USER"]["OPTIONS"]["web_hq"]:
bitrate = '320k'

try:
uuid = account['uuid']
arl = account['login']['arl']
headers = {
'Origin': 'https://www.deezer.com',
'Accept-Encoding': 'utf-8',
'Referer': 'https://www.deezer.com/login',
}
session = requests.Session()
session.headers.update(headers)
session.cookies.update({'arl': arl, 'comeback': '1'})

api_token = None

# Prepare to call the API
method = 'deezer.getUserData'
params = {
'api_version': "1.0",
'api_token': 'null',
'input': '3',
'method': 'deezer.getUserData'
}

user_data = session.post(
"http://www.deezer.com/ajax/gw-light.php",
params=params,
headers=headers
).json()

bitrate = '128k'
if user_data["results"]["USER"]["OPTIONS"]["web_lossless"]:
bitrate = '1411k'
elif user_data["results"]["USER"]["OPTIONS"]["web_hq"]:
bitrate = '320k'

account_pool.append({
"uuid": uuid,
"username": arl,
Expand All @@ -254,10 +235,11 @@ def deezer_login_user(account):
}
})
return True
except ConnectionRefusedError:
except Exception as e:
logger.error(f"Unknown Exception: {str(e)}")
account_pool.append({
"uuid": uuid,
"username": username,
"username": arl,
"service": "deezer",
"status": "error",
"account_type": "N/A",
Expand Down
34 changes: 18 additions & 16 deletions src/onthespot/api/spotify.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,24 @@ def spotify_new_session():


def spotify_login_user(account):
# I'd prefer to use 'Session.Builder().stored(credentials).create but
# it seems to be broken, loading from credentials file instead
uuid = account['uuid']
username = account['login']['username']

session_dir = os.path.join(cache_dir(), "onthespot", "sessions")
os.makedirs(session_dir, exist_ok=True)
session_json_path = os.path.join(session_dir, f"ots_login_{uuid}.json")
print(session_json_path)
try:
with open(session_json_path, 'w') as file:
json.dump(account['login'], file)
print(f"Login information for '{username[:4]}*******' written to {session_json_path}")
except IOError as e:
print(f"Error writing to file {session_json_path}: {e}")
# I'd prefer to use 'Session.Builder().stored(credentials).create but
# it seems to be broken, loading from credentials file instead
uuid = account['uuid']
username = account['login']['username']

session_dir = os.path.join(cache_dir(), "onthespot", "sessions")
os.makedirs(session_dir, exist_ok=True)
session_json_path = os.path.join(session_dir, f"ots_login_{uuid}.json")
print(session_json_path)
try:
with open(session_json_path, 'w') as file:
json.dump(account['login'], file)
print(f"Login information for '{username[:4]}*******' written to {session_json_path}")
except IOError as e:
print(f"Error writing to file {session_json_path}: {e}")


try:
config = Session.Configuration.Builder().set_stored_credential_file(session_json_path).build()
# For some reason initialising session as None prevents premature application exit
session = None
Expand All @@ -108,7 +109,8 @@ def spotify_login_user(account):
}
})
return True
except ConnectionRefusedError:
except Exception as e:
logger.error(f"Unknown Exception: {str(e)}")
account_pool.append({
"uuid": uuid,
"username": username,
Expand Down
51 changes: 27 additions & 24 deletions src/onthespot/downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from .post_download import convert_audio_format, set_music_thumbnail
from .api.spotify import spotify_get_token, spotify_get_track_metadata, spotify_get_episode_metadata, spotify_get_lyrics
from .api.soundcloud import soundcloud_get_token, soundcloud_get_track_metadata
from .api.deezer import deezer_get_track_metadata, get_song_infos_from_deezer_website, genurlkey, calcbfkey, decryptfile
from .api.deezer import deezer_get_track_metadata, get_song_info_from_deezer_website, genurlkey, calcbfkey, decryptfile
from .accounts import get_account_token
from .utils import sanitize_data, format_track_path

Expand Down Expand Up @@ -207,7 +207,7 @@ def run(self):
bitrate = "128k"

elif item_service == 'deezer':
song = get_song_infos_from_deezer_website(item['item_id'])
song = get_song_info_from_deezer_website(item['item_id'])

song_quality = 1
song_format = 'MP3_128'
Expand All @@ -228,18 +228,9 @@ def run(self):
bitrate = "256k"

headers = {
'Pragma': 'no-cache',
'Origin': 'https://www.deezer.com',
'Accept-Encoding': 'utf-8',
'Accept-Language': 'en-US,en;q=0.9',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': '*/*',
'Cache-Control': 'no-cache',
'X-Requested-With': 'XMLHttpRequest',
'Connection': 'keep-alive',
'Referer': 'https://www.deezer.com/login',
'DNT': '1',
}

track_data = token.post(
Expand All @@ -249,7 +240,7 @@ def run(self):
'media': [{
'type': "FULL",
'formats': [
{ 'cipher': "BF_CBC_STRIPE", 'format': 'FLAC' }
{ 'cipher': "BF_CBC_STRIPE", 'format': song_format }
]
}],
'track_tokens': [song["TRACK_TOKEN"]]
Expand All @@ -259,30 +250,42 @@ def run(self):

try:
url = track_data['data'][0]['media'][0]['sources'][0]['url']
fh = requests.get(url)
urlkey = genurlkey(song["SNG_ID"], song["MD5_ORIGIN"], song["MEDIA_VERSION"], song_quality)
except KeyError:
# User is likely using a free account
# Fallback to lowest quality
song_quality = 1
song_format = 'MP3_128'
bitrate = "128k"
default_format = ".mp3"
urlkey = genurlkey(song["SNG_ID"], song["MD5_ORIGIN"], song["MEDIA_VERSION"], song_quality)
url = "https://e-cdns-proxy-%s.dzcdn.net/mobile/1/%s" % (song["MD5_ORIGIN"][0], urlkey.decode())
fh = requests.get(url)

if fh.status_code != 200:
logger.info(f"Deezer download attempts failed: {fh.status_code}")
file = requests.get(url, stream=True)

if file.status_code == 200:
total_size = int(file.headers.get('content-length', 0))
downloaded = 0
data_chunks = b'' # empty bytes object

for data in file.iter_content(chunk_size=config.get("chunk_size")):
downloaded += len(data)
data_chunks += data

if self.gui and downloaded != total_size:
self.progress.emit(item, self.tr("Downloading"), int((downloaded / total_size) * 100))

key = calcbfkey(song["SNG_ID"])

if self.gui:
self.progress.emit(item, self.tr("Decrypting"), 99)
with open(temp_file_path, "wb") as fo:
decryptfile(data_chunks, key, fo)

else:
logger.info(f"Deezer download attempts failed: {file.status_code}")
item['item_status'] = "Failed"
if self.gui:
self.progress.emit(item, self.tr("Failed"), 0)
self.readd_item_to_download_queue(item)
continue

key = calcbfkey(song["SNG_ID"])
with open(temp_file_path, "w+b") as fo:
decryptfile(fh, key, fo)

except (RuntimeError):
# Likely Ratelimit
logger.info("Download failed: {item}")
Expand Down
9 changes: 7 additions & 2 deletions src/onthespot/gui/mainui.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ def set_login_fields(self):
self.btn_login_add.clicked.connect(lambda:
(self.__splash_dialog.run(self.tr("Account added, please restart the app.")) or True) and
deezer_add_account(self.inp_login_password.text()) and
self.lb_login_password.clear()
self.inp_login_password.clear()
)

# Soundcloud
Expand All @@ -581,11 +581,16 @@ def set_login_fields(self):
self.lb_login_username.setText(self.tr("Client ID"))
self.inp_login_username.show()
self.lb_login_password.show()
self.lb_login_password.setText(self.tr("Token"))
self.lb_login_password.setText(self.tr("App Version"))
self.inp_login_password.show()
self.btn_login_add.clicked.disconnect()
self.btn_login_add.show()
self.btn_login_add.setText(self.tr("Add Account"))
self.btn_login_add.clicked.connect(lambda:
(self.__splash_dialog.run(self.tr("Currently unsupported, if you have a GO+ account please consider lending it to the dev team.")) or True) and
self.inp_login_username.clear() and
self.inp_login_password.clear()
)

# Spotify
if self.inp_login_service.currentIndex() == 2:
Expand Down
1 change: 0 additions & 1 deletion src/onthespot/parse_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ def parsingworker():
'item_id': item_id,
'parent_category': 'track'
}
time.sleep(4)
continue

if current_type in ["album", "playlist"]:
Expand Down

0 comments on commit df425a8

Please sign in to comment.