python实现ncm转mp3

您所在的位置:网站首页 网易云变成mp3文件 python实现ncm转mp3

python实现ncm转mp3

2023-11-21 06:06| 来源: 网络整理| 查看: 265

昨天,我想将网易云上下载的歌曲拷到MP3里面,方便以后跑5公里的时候听,结果,突然发现不少歌都是ncm格式,不禁产生了好奇。

NCM格式分析

音频知识简介

特意读了一下《音视频开发进阶指南》,总结如下:

我们平常说的mp3格式、wav格式的音乐其实是说的压缩编码格式。

一首歌是怎么从歌手的喉咙里发出后变成一个文件的呢?

需要经过采样、量化和编码三个步骤。

采样

声音是连续的模拟信号,通过采样,将之转变为离散的数字信号,其中要遵循的是奈奎斯特定理:只要采样频率不低于声音信号最高频率的两倍,采样得到的数字信号就能保真地记录、还原声音。

人耳能够听到的范围是20Hz到20kHz,所以采样频率一般为44.1kHz,这样就可以保证采样声音达到20kHz也能被数字化,从而使得经过数字化处理之后,人耳听到的声音质量不会被降低。而所谓的44.1kHz就是代表1秒会采样44100次

量化

量化是指在幅度轴上对信号进行数字化,就是用多少位的数据来记录一个采样。比如用16比特的二进制信号来表示声音的一个采样,而16比特(一个short)所表示的范围是[-32768,32767],共有65536个可能取值,因此最终模拟的音频信号在幅度上也分为了65536层

编码

编码就是我们按一定的格式对采样和量化后的数字数据进行记录。直接存储的话,文件可能过大,像CD那样直接存储下来的没什么问题,但如果要在网络中在线传播,就必须进行压缩。

压缩的原理是压缩掉冗余信号,包括人耳感知不到的信号以及人耳掩蔽效应(指人耳只对最明显的声音反应敏感)掩蔽掉的信号。同时压缩算法包括有损压缩和无损压缩。无损压缩是指解压后的数据可以完全复原。有损压缩是指解压后的数据不能完全复原,会丢失一部分信息。

两种可能

第一种可能是网易独立进行了压缩编码算法的研究,创造出来的新的格式。

第二种是在现有格式的基础上,增加了一些冗余信息,相当于将一首MP3格式的歌放入密码箱中,付费者可开启。

不管是哪种,都必须了解格式的构成。

GitHub项目

我自知学艺不精,所以去万能的GitHub上寻求答案。

果然有先驱者,貌似是anonymous5l提供了最初的ncmdump版本,然后再由其他几位大佬进行重构和功能完善

格式分析

总体结构

首先,我从

由此可得知,NCM 实际上不是音频格式是容器格式,封装了对应格式的 Meta 以及封面等信息

密钥问题

另外,NCM使用了NCM使用了AES加密,但每个NCM加密的密钥是一样的,因此只要获取了AES的密钥KEY,就可以根据格式解开对应的资源。

AES我知道,一种对称加密算法嘛,这学期刚好学了网络密码。

AES是一种迭代型分组加密算法,分组长度为128bit,密钥长度为128、192或256bit,不同的密钥长度对应的迭代轮数不同,对应关系如下:

密钥长度

轮数

128

10

192

12

256

14

我最好奇的是AES的密钥是怎么搞到的。出于“不可能只有我一个人好奇”的信念,看了好几个项目的README.md以及issues

结果只有一个人在yoki123的项目中issues了这个问题,

大佬表示,他的密钥也是从annoymous51处获得的,但他推测是通过反编译播放器客户端得到的。

并给出了三条原因:

播放器也需要读取ncm格式,客户端就包含有解密逻辑

解密算法是AES,是对称加密

恰巧所有的文件都使用了相同的AES key,那么key在客户端播放器中就是一个常量

而作为第一个搞到密钥的大佬annoymous51,他的项目中竟然没有一个人问这个问题,我自己问了一下,看大佬会不会回复

代码分析

密钥的问题暂时不纠结了,接下来对照lianglixin的代码来钻研,

从提交说明来看,folder_dump.py实现的是批量的转换,虽说Python文件操作的部分不难,但是有人做了这个工作也省得我自己动手了。

在她的README.md中说明了需要安装依赖库pycrypto,使用pip install pycrypto安装,但如果使用了Anaconda,就不需要装了

代码地址为:

main函数

main函数中用来进行文件操作,根据输入的参数中的文件夹,在此文件夹中的全部文件中进行筛选,找到.ncm格式的文件,执行dump函数

这个程序按理来说,运行的方法是在命令行中cd到此文件所在路径,然后输入python folder_dump.py ncm保存文件夹路径

但这种方式挺麻烦的,而且程序中竟然还有变量都没有定义,比如rootdir,因此无法运行成功,

于是我对她这一部分再次进行了修改,我将main函数改成如下所示的内容:

if __name__ == '__main__':

file_path = input("请输入文件所在路径(例如:E:\\ncm_music)\n")

list = os.listdir(file_path) # Get all files in folder.

for i in range(0,len(list)):

# path = os.path.join("E:\\ncm_music",list[i])

path = os.path.join(file_path, list[i])

print(path)

if os.path.isfile(path):

if os.path.isfile(path):

if file_extension(path) == ".ncm":

try:

dump(path)

except:

pass

效果如下:

导入模块

然后看看导入的模块

import binascii

import struct

import base64

import json

import os

from Crypto.Cipher import AES

binascii的主要作用是实现进制和字符串之间的转换。

Python提供了struct模块,它是一个类似C或C++的struct结构,配合其模块提供的方法可以将二进制数据与Python的数据结构互相转换。

Base64 是网络上最常见的用于传输 8Bit 字节码的编码方式之一,Base64 就是一种基于 64 个可打印字符来表示二进制数据的方法。可查看 RFC2045 ~ RFC2049,上面有 MIME 的详细规范。Base64 编码是从二进制到字符的过程,可用于在 HTTP 环境下传递较长的标识信息。比如使二进制数据可以作为电子邮件的内容正确地发送,用作 URL 的一部分,或者作为 HTTP POST 请求的一部分。

json模块提供了对JSON的支持,它既包含了将JSON字符串恢复成Python对象的函数,也提供了将Python对象转换成JSON字符串的函数。

os模块提供了多数操作系统的功能接口函数。当os模块被导入后,它会自适应于不同的操作系统平台,根据不同的平台进行相应的操作,在python编程时,经常和文件、目录打交道,所以离不开os模块。

Crypto是一个加密算法模块,Cipher是该模块下的对称加密算法对象。

dump函数

最后看看dump函数,这个才是重点

1. def dump(file_path):

2. core_key = binascii.a2b_hex("687A4852416D736F356B496E62617857")

3. meta_key = binascii.a2b_hex("2331346C6A6B5F215C5D2630553C2728")

4. unpad = lambda s : s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))]

5. f = open(file_path,'rb')

6. header = f.read(8)

7. assert binascii.b2a_hex(header) == b'4354454e4644414d'

8. f.seek(2, 1)

9. key_length = f.read(4)

10. key_length = struct.unpack('

11. key_data = f.read(key_length)

12. key_data_array = bytearray(key_data)

13. for i in range (0,len(key_data_array)): key_data_array[i] ^= 0x64

14. key_data = bytes(key_data_array)

15. cryptor = AES.new(core_key, AES.MODE_ECB)

16. key_data = unpad(cryptor.decrypt(key_data))[17:]

17. key_length = len(key



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3