Skip to content

Commit

Permalink
Using new api and make it more powerful
Browse files Browse the repository at this point in the history
  • Loading branch information
heqin-zhu committed Jun 16, 2018
1 parent 5f854c9 commit 3febc2c
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 318 deletions.
File renamed without changes.
38 changes: 18 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,45 @@
![warning](http://ounix1xcw.bkt.clouddn.com/warning.png)

(学习使用异或加密 :see_no_evil:
(学习使用异或加密 :see_no_evil: )

## 介绍
用于获取网易云音乐缓存文件的mp3文件

缓存文件 在手机上的在 `netease/cloudmusic/Cache/`里的`Music1`, 歌词在`Lyric`里,(电脑上的路径可以在设置里找到还可以自己设置)
缓存文件 在手机上的在 `netease/cloudmusic/Cache/`里的`Music1`
思路就是利用缓存文件,解密得到MP3文件, 并通过其metadata,命名文件,顺便从api或者网页抓取歌词,详细介绍可以看[这里](https://mbinary.coding.me/decrypt-netease-music.html)

## 需求
* python3
* python 模块
- mutagen
- lxml
- requests
- json
- mutagen (optional)

可以pip install 安装,常见问题可以自行搜索解决
可以pip3 install 安装

## 使用

### 找到缓存文件
找到缓存文件的路径, 形如`.../netease/cloudmusic/Cache/Music1`,如果想单独复制过来,请复制 `Music1``Lyric`文件夹在同一目录下,再得到路径,
找到缓存文件的路径,`netease/cloudmusic/Cache/Music1`,复制到电脑上, 在电脑上的路径记为 `PATH`

### 运行
* 在命令行模式下
`python3 decrypt.py  *PATH`

如果不在 netease-music.py所在目录下,可以切换到,或者用它的绝对路径,
path是上一步得到的路径
`python3 decrypt.py  PATH`
这里的 `PATH` 就是缓存文件的位置

* 代码中
可以打开代码文件然后修改path,然后运行即可
也可以将这脚本复制到缓存文件目录的父目录中, 直接运行脚本即可

## 结果
你就可以到Music1所在目录下的`cached_网易云音乐`看到lyric, music了 :smiley:
你就可以到 `Music1` 的父目录下 看到 `cached_网易云音乐`, 以及其中的 `lyric`, `music` :smiley:

![](src/lrc.png)
![](src/result.jpg)

![](src/lyric.jpg)

![](src/music.png)
![](src/music.jpg)

## 说明
这个脚本还有很多地方不完善,而且还可以加入一些功能,欢迎fork&PR
## 贡献
欢迎fork&PR

## Licence
[MIT](LICENCE-MIT.txt)

[MIT](LICENCE)
216 changes: 81 additions & 135 deletions decrypt.py
Original file line number Diff line number Diff line change
@@ -1,164 +1,110 @@
#coding : utf-8
from mutagen.easyid3 import EasyID3
from mutagen.mp3 import MP3
from itertools import zip_longest
from collections import Iterable
from lxml import etree
from operator import or_
import os
import sys
import requests
import json
import glob
import re
import os
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

class netEaseMusic:

class netease_music:
def __init__(self,path=''):
if path == '':path = input('input the path of cached netease_music')
'''path is the direcoty that contains Music files(cached)'''
if path == '': path = input('input the path of cached netease_music')
self.path = path
os.chdir(path)
self.names=glob.glob('*.uc!')
self.files=glob.glob('*.uc!')
self.id_mp = {}
for i in self.names:self.id_mp[self.getId(i)] = i
self.absPaths=[os.path.abspath(i) for i in self.names]
self.prep()
self.headers={
'Referer':'http://music.163.com/',
'Host':'music.163.com',
'Connection':'keep-alive',
'User-Agent': 'ozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
}
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 prep(self):
self.prt= os.path.dirname(os.getcwd())
self.cd('cached_网易云音乐')
self.cd('cached_网易云音乐/lyric')
self.cd('cached_网易云音乐/music')

def cd(self,s):
'''cd to the dir path+s, (create it first if not exists)'''
try:
os.chdir(self.prt)
os.chdir(s)
except:
os.mkdir(s)
os.chdir(s)

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):
if name[-1] not in '0987654321':
name = os.path.basename(name)
return name[:name.find('-')]
def getIdFromIdx(self,idxFileName):
with open(idxFileName,'r') as f:
try:
info = json.load(f) # r'\"musicId\":(\d+)'
except:
raise Exception('file {} is breaken'.format(idxFileName))
return info['musicId']
def getInfo(self,musicId):
try:return self.getInfoFromMp3(musicId)
except:
with open(musicId,'rb') as f:
s = f.read().decode('utf8',errors ='ignore')
dic = {}
mch = re.findall(r'\[ar:(.*?)\]',s)
# to keep consistent with the id3 tag : namely the value is a list
dic['artist']=mch[0] if mch !=[] else ['']
mch = re.findall(r'\[ti:(.*?)\]',s)
dic['title']=mch[0] if mch !=[] else ['']
return dic

def _genFileName(self,dic,musicId):
name = musicId
if 'title' in dic:
name = dic['title'][0]
if 'artist' in dic: name+='--'+dic['artist'][0]
else:
try:name = self.crawlName(musicId)
except Exception as e:
print(repr(e))
return name
def getInfoFromWeb(self,musicId):
dic = {}
url = API+'type=detail&id=' + str(musicId)
info = requests.get(url).json()['songs'][0]
dic['artist']= [ info['ar'][0]['name'] ]
dic['title']= [ info['al']['name'] ]
dic['cover'] = [ info['al']['picUrl'] ]
return dic

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

def genName(self,dic):
title = dic['title'][0]
artist = dic['artist'][0]
if artist in title: title = title.replace(artist,'').strip()
return title + '--' + artist

def decrypt(self,fileName):
with open (fileName,'rb') as f:
btay = bytearray(f.read())
musicId = self.getId(fileName)
self.cd('cached_网易云音乐/music')
with open(str(musicId),'wb') as out:
for i,j in enumerate(btay):
btay[i] = j ^ 0xa3
out.write(bytes(btay))
dic = self.getInfo(musicId)
newName = self.id_mp[musicId] = self._genFileName(dic,musicId)
if newName == '':newName = musicId
try:os.rename(musicId,newName+'.mp3')
except:pass
idpath = os.path.join(MSCDIR,musicId)
if not os.path.exists(idpath):
with open(idpath,'wb') as out:
for i,j in enumerate(btay):
btay[i] = j ^ 0xa3
out.write(bytes(btay))
dic = {}
try:
dic = self.getInfoFromWeb(musicId)
except Exception as e:
dic = self.getInfoFromFile(os.path.join(MSCDIR,musicId))
name = self.genName(dic)
self.id_mp [musicId] = name
path = os.path.join(MSCDIR,name+'.mp3')
if os.path.exists(idpath) and not os.path.exists(path):
os.rename(idpath,path)
return musicId

def crawlName(self,musicId):
url = 'https://music.163.com/#/song?id='+str(musicId)
r= requests.get(url,headers = self.headers)
if r.status_code !=200:
print(r.status_code)
raise Exception('crawl Name Failed! Bad Responde from '+url)
sl = etree.HTML(r.text)
try:
li = sl.xpath(self.nameXpath)[0]
return '' if li ==[] else li[0]
except:
raise Exception('not find music name of id : '+str(musicId))
def crawlLrc(self,musicId):
url = ('http://music.163.com/api/song/lyric?id=' + str(musicId) + '&lv=1&kv=1&tv=-1')
try:
return requests.get(url).text
except:
raise Exception('crawl lyric Failed! Bad Responde from '+url)
def lrcFromFile(self,musicId):
self.cd('Lyric')
### 直接字符串操作路径可能出错
try:
with open(str(musicId),'r') as f:
return f.read()
except:
try:
with open(str(musicId),'rb') as f:
return f.read().decode('utf8',errors = 'ignore') # key point
except:return self.crawlLrc(musicId)
def getLyric(self,musicId):
lrc = self.lrcFromFile(musicId)
name = self.id_mp[musicId]
lrc_lst = self.lrcSentencePt.findall(lrc)
if lrc_lst==[]:
try:
lrc = self.crawlLrc(musicId)
lrc_lst = self.lrcSentencePt.findall(lrc)
except:
print('fail to get lyric of music '+name)
return
self.cd('cached_网易云音乐/lyric')
with open(name +'.txt','w',encoding ='utf8') as f:
f.write(name+'\n\n')
f.write('\n'.join(lrc_lst))
return lrc_lst


def getInfoFromMp3(self,musicPath):
tag = MP3(musicPath,ID3 = EasyID3)
return dict(tag)
# 'http://music.163.com/api/song/lyric?id='
url = API+'type=lyric&id=' + str(musicId)
try:
lrc = requests.get(url).json()
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,' Failed to get lyric of music '+name)

def getMusic(self):
for i in self.absPaths:
for ct,i in enumerate(self.files):
musicId = self.decrypt(i)
print(self.id_mp[musicId])
print('[Music {}]'.format(ct+1).ljust(12)+self.id_mp[musicId])
self.getLyric(musicId)


if __name__=='__main__':
path = sys.argv[1:][0].strip()
#path = 'C:\\Users\\mbinary\\Desktop\\source\\\\myscripts\\Music1'
hd = netEaseMusic(path)
hd.getMusic()
if len(sys.argv) > 1: path = sys.argv[1].strip()
else : path = os.path.join(os.getcwd(),'Music1')
handler= netease_music(path)
handler.getMusic()

Binary file removed src/lrc.png
Binary file not shown.
Binary file added src/lyric.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/music.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/music.png
Binary file not shown.
Loading

0 comments on commit 3febc2c

Please sign in to comment.