这个是在本站看到有人用python 代码写了一个网易云 ncm 转MP3的工具后,拉下来使用之后,发现转换实在太慢了,然后我改写为golang版本。这个使用简单而且无需其他的外部依赖。
替换下面的ncm路径和转换后的路径即刻
package main
import (
"crypto/aes"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
func unpad(data []byte) []byte {
length := len(data)
if length == 0 {
return data
}
pad := int(data[length-1])
return data[:(length - pad)]
}
func decryptAES(data, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
decrypted := make([]byte, len(data))
size := aes.BlockSize
for i := 0; i < len(data); i += size {
block.Decrypt(decrypted[i:i+size], data[i:i+size])
}
return unpad(decrypted), nil
}
func decryptNCM(filePath string, coreKey, metaKey []byte) (string, error) {
file, err := os.Open(filePath)
if err != nil {
return "", err
}
defer file.Close()
// Read and check header
header := make([]byte, 8)
if _, err = io.ReadFull(file, header); err != nil || hex.EncodeToString(header) != "4354454e4644414d" {
return "", fmt.Errorf("invalid header")
}
// Skip 2 bytes, read key length and key data
file.Seek(2, io.SeekCurrent)
keyLengthData := make([]byte, 4)
if _, err = io.ReadFull(file, keyLengthData); err != nil {
return "", err
}
keyLength := binary.LittleEndian.Uint32(keyLengthData)
keyData := make([]byte, keyLength)
if _, err = io.ReadFull(file, keyData); err != nil {
return "", err
}
for i := range keyData {
keyData[i] ^= 0x64
}
// Decrypt key data with coreKey
decryptedKeyData, err := decryptAES(keyData, coreKey)
if err != nil {
return "", err
}
keyData = decryptedKeyData[17:]
// Generate keyBox
keyBox := make([]byte, 256)
for i := range keyBox {
keyBox[i] = byte(i)
}
c, lastByte, keyOffset := 0, 0, 0
for i := 0; i < 256; i++ {
swap := keyBox[i]
c = (int(swap) + lastByte + int(keyData[keyOffset])) & 0xff
keyBox[i], keyBox[c] = keyBox[c], keyBox[i]
lastByte, keyOffset = c, (keyOffset+1)%len(keyData)
}
// Read meta length and meta data
metaLengthData := make([]byte, 4)
if _, err = io.ReadFull(file, metaLengthData); err != nil {
return "", err
}
metaLength := binary.LittleEndian.Uint32(metaLengthData)
metaData := make([]byte, metaLength)
if _, err = io.ReadFull(file, metaData); err != nil {
return "", err
}
for i := range metaData {
metaData[i] ^= 0x63
}
decodedMetaData, err := base64.StdEncoding.DecodeString(string(metaData[22:]))
if err != nil {
return "", err
}
decryptedMetaData, err := decryptAES(decodedMetaData, metaKey)
if err != nil {
return "", err
}
var meta map[string]interface{}
if err := json.Unmarshal(decryptedMetaData[6:], &meta); err != nil {
return "", err
}
format := meta["format"].(string)
fmt.Printf("format: %v\n", format)
// Skip crc32 and extra bytes
file.Seek(9, io.SeekCurrent)
// Read image data size and image data
imageSizeData := make([]byte, 4)
if _, err = io.ReadFull(file, imageSizeData); err != nil {
return "", err
}
imageSize := binary.LittleEndian.Uint32(imageSizeData)
file.Seek(int64(imageSize), io.SeekCurrent)
oldPath := strings.Split(filepath.Base(filePath), ".")
// Decode audio data
outputFilePath := filepath.Join(filepath.Dir(filePath), oldPath[0]+"."+format)
outputFile, err := os.Create(outputFilePath)
if err != nil {
return "", err
}
defer outputFile.Close()
chunk := make([]byte, 0x8000)
for {
n, err := file.Read(chunk)
if n > 0 {
for i := 1; i <= n; i++ {
j := i & 0xff
chunk[i-1] ^= keyBox[(keyBox[j]+keyBox[(keyBox[j]+byte(j))&0xff])&0xff]
}
outputFile.Write(chunk[:n])
}
if err == io.EOF {
break
} else if err != nil {
return "", err
}
}
return outputFilePath, nil
}
func main() {
ncmDir := "F:\\CloudMusic\\VipSongsDownload"
mp3Dir := "F:\\CloudMusic\\VipSongsDownload\\mp3"
os.MkdirAll(mp3Dir, os.ModePerm)
coreKey, _ := hex.DecodeString("687A4852416D736F356B496E62617857")
metaKey, _ := hex.DecodeString("2331346C6A6B5F215C5D2630553C2728")
filepath.Walk(ncmDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if filepath.Ext(path) == ".ncm" {
fmt.Printf("%s is being converted to mp3 format\n", filepath.Base(path))
mp3Path, err := decryptNCM(path, coreKey, metaKey)
if err != nil {
fmt.Printf("Failed to convert %s: %v\n", filepath.Base(path), err)
} else {
os.Rename(mp3Path, filepath.Join(mp3Dir, filepath.Base(mp3Path)))
}
}
return nil
})
}