1. 程式人生 > >網易雲音樂快取檔案

網易雲音樂快取檔案

1.Ubuntu版的網易雲音樂已經懶得加密了嗎…名字裡甚至都已經帶了md5碼的加密方法…

╰─$ ls ~/.cache/netease-cloud-music/CachedSongs 
567602-128-55881971d77b3ec1a5a134e2670a72e7.mp3

2.如果是windows或Android,需要藉助Python指令碼解密加密了的快取檔案

使用方法:

將快取檔案放到指令碼檔案所在目錄下,在python3中執行該指令碼。解密結果會輸出到”cached_網易雲音樂“目錄下。

以下為解密用指令碼:(注:因為Python2.7對UTF8編碼的支援問題,需要Python3或以上版本)

#coding : utf-8
import os
import sys
import glob
import requests

DESDIR = './cached_網易雲音樂'
LRCDIR = os.path.join(DESDIR, 'lyric')
MSCDIR = os.path.join(DESDIR, 'music')

API = 'https://api.imjad.cn/cloudmusic/?'
# two args: id  type
# type=song, lyric, comments, detail, artist, album, search
# eg  API = 'https://api.imjad.cn/cloudmusic/?type=song&id=1234132'    download music

hasModu = False
try:
    from mutagen.easyid3 import EasyID3
    from mutagen.mp3 import MP3
    hasModu = True
except:
    pass

def safeprint(s):
    '''deal with invalid encoded filename'''
    try:
        print(s)
    except:
        print(repr(s)[1:-1])

class netease_music:
    def __init__(self, path=''):
        '''path is the direcoty that contains Music files(cached)'''
        if path == '':
            path = input('input the path of cached netease_music')
        self.path = path
        safeprint('[+] Current Path: ' + path)
        os.chdir(path)
        self.files = glob.glob('*.uc') + glob.glob('*.uc!')
        self.id_mp = {}
        for i in self.files:
            self.id_mp[self.getId(i)] = i
        if not os.path.exists(DESDIR):
            os.mkdir(DESDIR)
        if not os.path.exists(LRCDIR):
            os.mkdir(LRCDIR)
        if not os.path.exists(MSCDIR):
            os.mkdir(MSCDIR)
        # import re
        # self.nameXpath ='//div[@class="tit"]/em[@class="f-ff2"]/text()'
        # self.lrcSentencePt=re.compile(r'\[\d+:\d+\.\d+\](.*?)\\n')         # wrong  (r'\[\d+,\d+\](\(\d+,\d+\)(\w))+\n')

    def getId(self, name):
        return name[:name.find('-')]

    def getInfoFromWeb(self, musicId):
        dic = {}
        url = API+'type=detail&id=' + musicId
        info = requests.get(url).json()['songs'][0]
        dic['artist'] = [info['ar'][0]['name']]
        dic['title'] = [info['name']]
        dic['cover'] = [info['al']['picUrl']]
        return dic

    def getInfoFromFile(self, path):
        if not os.path.exists(path):
            safeprint('Can not find file ' + path)
            return {}
        elif hasModu:
            return dict(MP3(path, ID3=EasyID3))
        else:
            print('[Error] You can use pip3 to install mutagen or connet to the Internet')
            raise Exception('Failed to get info of ' + path)

    def getPath(self, dic,musicId):
        title = dic['title'][0]
        artist = dic['artist'][0]
        if artist in title:
            title = title.replace(artist, '').strip()
        name = (title + '--' + artist)
        for i in '>?*/\:"|<':
            name = name.replace(i,'-') # form valid file name
        self.id_mp[musicId] = name
        #print('''{{title: "{title}",artist: "{artist}",mp3: "http://ounix1xcw.bkt.clouddn.com/{name}.mp3",cover: "{cover}",}},'''\
               #.format(title = title,name = name,artist=artist,cover=dic['cover'][0]))
        return os.path.join(MSCDIR, name + '.mp3')
    
    def decrypt(self, cachePath):
        musicId = self.getId(cachePath)
        idpath = os.path.join(MSCDIR, musicId + '.mp3')
        try:  # from web
            dic = self.getInfoFromWeb(musicId)
            path = self.getPath(dic,musicId)
            if os.path.exists(path): return 
            with open(path,'wb') as f:
                f.write(bytes(self._decrypt(cachePath)))
        except Exception as e:  # from file
            print(e)
            if not os.path.exists(idpath):
                with open(idpath,'wb') as f:
                    f.write(bytes(self._decrypt(cachePath)))
            dic = self.getInfoFromFile(idpath)
            path = getPath(dic,musicId)
            if os.path.exists(path):
                os.remove(idpath)
                return 
            os.rename(idpath, path)
            
    def _decrypt(self,cachePath):
        with open(cachePath, 'rb') as f:
            btay = bytearray(f.read())
        for i, j in enumerate(btay):
            btay[i] = j ^ 0xa3
        return btay
    
    def getLyric(self, musicId):
        name = self.id_mp[musicId]
        url = API + 'type=lyric&id=' + musicId
        url2 = 'https://music.163.com/api/song/lyric?id='+ musicId +'&lv=1&kv=1&tv=-1'
        try:
            lrc = requests.get(url).json()['lrc']['lyric']
            if lrc=='':
                lrc = requests.get(url2).json()['lrc']['lyric']
            if lrc=='':
                raise Exception('')
            file = os.path.join(LRCDIR, name + '.lrc')
            if not os.path.exists(file):
                with open(file, 'w', encoding='utf8') as f:
                    f.write(str(lrc))
        except Exception as e:
            print(e,end='')
            safeprint(': Failed to get lyric of music '+name)
    def getMusic(self):
        for ct, cachePath in enumerate(self.files):
            self.decrypt(cachePath)
            musicId = self.getId(cachePath)
            print('[{}]'.format(ct+1).ljust(5)+self.id_mp[musicId])
            self.getLyric(musicId)


if __name__ == '__main__':
    if len(sys.argv) > 1:
        path = sys.argv[1].strip()
    else:
        path = os.path.join(os.getcwd(), '.')
    handler = netease_music(path)
    handler.getMusic()