分享一个golang 版本的ncm 转MP3 的代码

这个是在本站看到有人用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
	})
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值