Initial import: Music_Server, MusicFree, catalog-sync
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
'''
|
||||
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)
|
||||
Reference in New Issue
Block a user