Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FLAC support #111

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 79 additions & 9 deletions deezer_downloader/deezer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
import html.parser
import requests
from binascii import a2b_hex, b2a_hex

import random
from time import sleep

# BEGIN TYPES
TYPE_TRACK = "track"
Expand All @@ -22,6 +23,8 @@
# END TYPES

session = None
license_token = None
web_sound_quality = {}


def init_deezer_session(proxy_server):
Expand All @@ -46,6 +49,7 @@ def init_deezer_session(proxy_server):
if len(proxy_server.strip()) > 0:
print(f"Using proxy {proxy_server}")
session.proxies.update({"https": proxy_server})
get_user_data()


class Deezer404Exception(Exception):
Expand Down Expand Up @@ -330,14 +334,20 @@ def download_song(song, output_file):
assert type(song) == dict, "song must be a dict"
assert type(output_file) == str, "output_file must be a str"

song_quality = 3 if song.get("FILESIZE_MP3_320") and song.get("FILESIZE_MP3_320") != '0' else \
if license_token and web_sound_quality.get('lossless'):
song, url, extension = get_song_url(song)
else:
song_quality = 3 if song.get("FILESIZE_MP3_320") and song.get("FILESIZE_MP3_320") != '0' else \
5 if song.get("FILESIZE_MP3_256") and song.get("FILESIZE_MP3_256") != '0' else \
1

urlkey = genurlkey(song["SNG_ID"], song["MD5_ORIGIN"], song["MEDIA_VERSION"], song_quality)
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())
extension = 'mp3'
key = calcbfkey(song["SNG_ID"])
if not url:
print("ERROR: Can not download this song. Failed to get url.")
return output_file
try:
url = "https://e-cdns-proxy-%s.dzcdn.net/mobile/1/%s" % (song["MD5_ORIGIN"][0], urlkey.decode())
fh = session.get(url)
if fh.status_code != 200:
# I don't why this happens. to reproduce:
Expand All @@ -347,18 +357,19 @@ def download_song(song, output_file):
# this will give you a 404!?
# but you can play the song in the browser
print("ERROR: Can not download this song. Got a {}".format(fh.status_code))
return

with open(output_file, "w+b") as fo:
return output_file
file_name = output_file.replace('.mp3', f'.{extension.lower()}')
with open(file_name, "w+b") as fo:
# add songcover and DL first 30 sec's that are unencrypted
writeid3v2(fo, song)
decryptfile(fh, key, fo)
writeid3v1_1(fo, song)
return file_name

except Exception as e:
raise
else:
print("Dowload finished: {}".format(output_file))
print("Download finished: {}".format(output_file))


def get_song_infos_from_deezer_website(search_type, id):
Expand Down Expand Up @@ -515,6 +526,60 @@ def get_deezer_favorites(user_id: str) -> Optional[Sequence[int]]:
return songs


def get_user_data():
global license_token
global web_sound_quality
try:
user_data = session.get('https://www.deezer.com/ajax/gw-light.php?method=deezer.getUserData&input=3&api_version=1.0&api_token=')
user_data_json = user_data.json()['results']
options = user_data_json['USER']['OPTIONS']
web_sound_quality = options['web_sound_quality']
license_token = options.get('license_token')
return user_data_json

except (Deezer403Exception, Deezer404Exception) as msg:
print(msg)
print("user data is not working anymore.")
return False

def get_song_url(song, i=0):
if i > 2:
raise ValueError("get song url attempts exceeded")

if i == 1 and song.get('FALLBACK'):
song = song['FALLBACK']
try:
track_token = song['TRACK_TOKEN']
body = {
"license_token": license_token,
"media":[
{
"type":"FULL",
"formats":[
{"cipher":"BF_CBC_STRIPE","format":"FLAC"},
{"cipher":"BF_CBC_STRIPE","format":"MP3_320"},
{"cipher":"BF_CBC_STRIPE","format":"MP3_128"},
{"cipher":"BF_CBC_STRIPE","format":"MP3_64"},
{"cipher":"BF_CBC_STRIPE","format":"MP3_MISC"}
]
}
],
"track_tokens":[track_token]
}
r = session.post("https://media.deezer.com/v1/get_url", data=json.dumps(body))
data = r.json()
if not data['data'][0].get('media'):
print(data)
sleep((random.random()+2**i))
return get_song_url(song, i+1)

result = data['data'][0]['media'][0]
return (song, result['sources'][0]['url'], result['format'].split('_')[0])
except (Deezer403Exception, Deezer404Exception) as msg:
print(msg)
print("get song url is not working anymore.")
return song, False, 'mp3'

def test_deezer_login():
print("Let's check if the deezer login is still working")
try:
Expand All @@ -535,3 +600,8 @@ def test_deezer_login():
if __name__ == '__main__':
if len(sys.argv) > 1 and sys.argv[1] == "check-login":
test_deezer_login()
if len(sys.argv) > 2 and sys.argv[1] == "dl-playlist":
init_deezer_session('')
pl = parse_deezer_playlist(sys.argv[2])
for song in pl[1]:
download_song(song, f'{pl[0]}/{song["ART_NAME"]} - {song["SNG_TITLE"]}.flac')
4 changes: 3 additions & 1 deletion deezer_downloader/web/music_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,11 @@ def download_song_and_get_absolute_filename(search_type, song, playlist_name=Non

if os.path.exists(absolute_filename):
print("Skipping song '{}'. Already exists.".format(absolute_filename))
if os.path.exists(absolute_filename.replace('.mp3', '.flac')):
print("Skipping song '{}'. Already exists.".format(absolute_filename.replace('.mp3', '.flac')))
else:
print("Downloading '{}'".format(song_filename))
download_song(song, absolute_filename)
absolute_filename = download_song(song, absolute_filename)
return absolute_filename


Expand Down