Files
musicdl-catalog-sync-suite/catalog-sync/musicdl/modules/utils/deezerutils.py
T

64 lines
3.4 KiB
Python

'''
Function:
Implementation of DeezerMusicClient Utils
Author:
Zhenchao Jin
WeChat Official Account (微信公众号):
Charles的皮卡丘
'''
import re
import base64
import hashlib
import binascii
import functools
from Cryptodome.Cipher import AES, Blowfish
'''DeezerMusicClientUtils'''
class DeezerMusicClientUtils():
BLOWFISH_SECRET = "g4el58wc0zvf9na1"
MUSIC_QUALITIES = ('FLAC', 'MP3_320', 'MP3_128')
IS_ENCRYPTED_RPATTERN = re.compile("/m(?:obile|edia)/")
SHARED_TOKENS = ['ZjI4N2JkNzRjM2Q1NGY5YmJmOTc5OTdjNzhkZWJkMzdiMTU4NjRjZDdhM2MwZjk0MjUxNWNjOWIwNGE1MWM1N2RhYmZiOTQ4YWYyNjM0MDFhOTRkZTUxOGI3MjRlZDdmNDBmMjcyMmNlZGMwMTgxZTEwYmZmNDk5MmVjNzc4NzU3MmU1MDUzZjk0Nzc1NjFiZjhkMjcwNDc0NzRiNzMxMTcxNjUyZWQxNzg0YzlmNTdhMTUxZDMxOTk2NmVjY2Ex']
token_decrypt_func = lambda t: base64.b64decode(str(t).encode('utf-8')).decode('utf-8')
'''decryptchunk'''
@staticmethod
def decryptchunk(key, data):
return Blowfish.new(key, Blowfish.MODE_CBC, b"\x00\x01\x02\x03\x04\x05\x06\x07").decrypt(data)
'''generateblowfishkey'''
@staticmethod
def generateblowfishkey(track_id: str) -> bytes:
md5_hash = hashlib.md5(str(track_id).encode()).hexdigest()
return "".join(chr(functools.reduce(lambda x, y: x ^ y, map(ord, t))) for t in zip(md5_hash[:16], md5_hash[16:], DeezerMusicClientUtils.BLOWFISH_SECRET)).encode()
'''getencryptedfileurl'''
@staticmethod
def getencryptedfileurl(meta_id: str, track_hash: str, media_version: str, format_number: int = 1):
url_bytes = b"\xa4".join((track_hash.encode(), str(format_number).encode(), str(meta_id).encode(), str(media_version).encode()))
info_bytes = bytearray(hashlib.md5(url_bytes).hexdigest().encode())
info_bytes.extend(b"\xa4"); info_bytes.extend(url_bytes); info_bytes.extend(b"\xa4")
padding_len = 16 - (len(info_bytes) % 16); info_bytes.extend(b"." * padding_len)
path = binascii.hexlify(AES.new(b"jo6aey6haid2Teih", AES.MODE_ECB).encrypt(info_bytes)).decode("utf-8")
return f"https://e-cdns-proxy-{track_hash[0]}.dzcdn.net/mobile/1/{path}"
'''getcoverurl'''
@staticmethod
def getcoverurl(pic_id: str):
if not pic_id: return None
return f"https://e-cdns-images.dzcdn.net/images/cover/{pic_id}/1200x1200.jpg"
'''covert2lrclyrics'''
@staticmethod
def covert2lrclyrics(lyrics_node: dict):
lrc_lines = []; lyrics_node.get("writers") and lrc_lines.append(f"[ar:{lyrics_node['writers']}]")
if (sync_lines := lyrics_node.get("synchronizedLines")):
for item in sync_lines: lrc_lines.append(f"{item.get('lrcTimestamp', '')}{item.get('line', '')}") if item.get("lrcTimestamp", "") else (lrc_lines.append(f"[{int(item['milliseconds']) // 60000:02d}:{(int(item['milliseconds']) % 60000) / 1000:05.2f}]{item.get('line', '')}") if "milliseconds" in item else None)
return "\n".join(lrc_lines)
else:
return lyrics_node.get("text")
'''decryptdownloadedaudiofile'''
@staticmethod
def decryptdownloadedaudiofile(src_path: str, dst_path: str, blowfish_key: str):
encrypt_chunk_size = 3 * 2048
with open(src_path, "rb") as src, open(dst_path, "wb") as dst:
while True:
if not (data := src.read(encrypt_chunk_size)): break
decrypted_chunk = DeezerMusicClientUtils.decryptchunk(blowfish_key, data[:2048]) + data[2048:] if len(data) >= 2048 else data
dst.write(decrypted_chunk)