Jade Dungeon

音频应用

CD抓轨

abcde -1 -o flac

参数说明:

  • -1表示将整张cd抓为一个文件否则一个音轨一个文件,
  • -o flac中的-o用于指定输出格式,
  • -P指定使用管道而不是临时的wav文件,
  • 其他常见的参数还有-b它大概是说平衡各个音轨的音量。

具体信息可以使用abcde --help查看。

输出位置说明:

它不提供指定输出位置的功能(至少2.5.4还不提供),默认将文件输出到当前目录下。如果使用了临时的wav文件,那么它会创建一个临时文件夹“abcde.xxxxxx”其中xxxxx表示一个随机数。对于最终输出,它会以“艺术家-专辑名”创建一个文件夹,然后在其中以“专辑名”/“音轨号-曲目名”保存各个文件

音频文件分轨

linux下使用.cueflac/ape分轨与转换

LinuxUbuntuF# 需要的工具有flac,mac,shntool,ubuntu下安装mac(monkeys audio codec)要添加ppa源:

sudo add-apt-repository ppa:robert-tari/main
sudo apt-get update
sudo apt-get install flac mac shntool

对cue索引的flac分轨:

shntool split example.flac -f example.cue -t "%n.%p-%t" -o flac -d output
shntool split example.ape  -f example.cue -t "%n.%p-%t" -o flac -d output
  • -f:指定分轨的cue文件。
  • -t:指定输出文件的文件名格式,%n是音轨号,%p是演奏者/艺术家, %t标题
  • -o:分出来的音乐格式,一般用开源的无损格式flac
  • -d:指定分轨后的单曲文件的输出目录,不指定的话在当前目录生成

音频格式转换

ape与flac

将ape转换成flac:

shnconv -i ape -o flac example.ape

或者

mac example.ape temp.wav -d
flac temp.wav example.flac

网易云音乐格式

转换目录下的网易云音乐加密文件*.ncm,并下载图片:

# encoding=utf-8
import binascii
import struct
import base64
import json
import os
import re
from Crypto.Cipher import AES

import httplib2

from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB


def dump(baseDir, fileName, filePostFix):
	core_key = binascii.a2b_hex("687A4852416D736F356B496E62617857")
	meta_key = binascii.a2b_hex("2331346C6A6B5F215C5D2630553C2728")
	unpad = lambda s : s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))]
	f = open(os.path.join(baseDir, fileName),'rb')
	header = f.read(8)
	assert binascii.b2a_hex(header) == b'4354454e4644414d'
	f.seek(2, 1)
	key_length = f.read(4)
	key_length = struct.unpack('<I', bytes(key_length))[0]
	key_data = f.read(key_length)
	key_data_array = bytearray(key_data)
	for i in range (0,len(key_data_array)): key_data_array[i] ^= 0x64
	key_data = bytes(key_data_array)
	cryptor = AES.new(core_key, AES.MODE_ECB)
	key_data = unpad(cryptor.decrypt(key_data))[17:]
	key_length = len(key_data)
	key_data = bytearray(key_data)
	key_box = bytearray(range(256))
	c = 0
	last_byte = 0
	key_offset = 0
	for i in range(256):
		swap = key_box[i]
		c = (swap + last_byte + key_data[key_offset]) & 0xff
		key_offset += 1
		if key_offset >= key_length: key_offset = 0
		key_box[i] = key_box[c]
		key_box[c] = swap
		last_byte = c
	meta_length = f.read(4)
	meta_length = struct.unpack('<I', bytes(meta_length))[0]
	meta_data = f.read(meta_length)
	meta_data_array = bytearray(meta_data)
	for i in range(0,len(meta_data_array)): meta_data_array[i] ^= 0x63
	meta_data = bytes(meta_data_array)
	meta_data = base64.b64decode(meta_data[22:])
	cryptor = AES.new(meta_key, AES.MODE_ECB)
	meta_data = unpad(cryptor.decrypt(meta_data)).decode('utf-8')[6:]
	# print(meta_data)
	meta_data = json.loads(meta_data)
	# meta data
	albumName = meta_data['album']
	musicName = re.sub(os.path.sep," ", meta_data['musicName'])
	artist    = re.sub(os.path.sep," ", meta_data['artist'][0][0])
	audioFileFormat = meta_data['format']
	crc32 = f.read(4)
	crc32 = struct.unpack('<I', bytes(crc32))[0]
	f.seek(5, 1)
	image_size = f.read(4)
	image_size = struct.unpack('<I', bytes(image_size))[0]
	imageData = f.read(image_size)
	audioFileName = artist + ' - ' + musicName + '.' + audioFileFormat
	musicFileNamePath = os.path.join(baseDir, audioFileName)
 	print('----------------------------------------')
	print('  file: ' + musicFileNamePath)
	print(' album: ' + albumName)
	print(' title: ' + musicName)
	print('artist: ' + artist)
 	print('----------------------------------------')
	m = open(musicFileNamePath,'wb')
	chunk = bytearray()
	while True:
		chunk = bytearray(f.read(0x8000))
		chunk_length = len(chunk)
		if not chunk:
			break
		for i in range(1,chunk_length+1):
			j = i & 0xff;
			chunk[i-1] ^= key_box[(key_box[j] + key_box[(key_box[j] + j) & 0xff]) & 0xff]
		m.write(chunk)
	m.close()
	f.close()
	# down album cover image
	# downImage(meta_data['albumPic'], '/home/jade/tmp/music', audioFileName)
	#
	f = open(musicFileNamePath + '.jpg','wb')
	f.write(imageData)
	f.close()
	#
	# set meta data id3
	# setMp3Info(musicFileNamePath, albumName, musicName, artist, imageData)




# def setMp3Info(fileNamePath, albumName, musicName, artist, imageData):
# 	print('------------------1---------------------')
# 	songFile = ID3(fileNamePath)
# 	print('------------------2---------------------')
# 	songFile['APIC'] = APIC(  # 插入封面
# 		encoding=3,
# 		mime='image/jpeg',
# 		type=3,
# 		desc=u'Cover',
# 		data=imageData
# 	)
# 	print('------------------3---------------------')
# 	songFile['TIT2'] = TIT2(  # 插入歌名
# 		encoding=3,
# 		text=musicName
# 	)
# 	print('------------------4---------------------')
# 	songFile['TPE1'] = TPE1(  # 插入第一演奏家、歌手、等
# 		encoding=3,
# 		text=artist
# 	)
# 	print('------------------5---------------------')
# 	songFile['TALB'] = TALB(  # 插入专辑名
# 		encoding=3,
# 		text=albumName
# 	)
# 	print('------------------6---------------------')
# 	songFile.save()
# 	print('------------------7---------------------')


# if __name__ == '__main__':
#     picPath = '2.jpg'
#     with open(picPath, 'rb') as f:
#         picData = f.read()
#     info = {'picData': picData, 'title': '你的酒馆对我打了烊',
#             'artist': '陈雪凝', 'album': '绿色'}
#     songPath = '你的酒馆对我打了烊.mp3'
#     SetMp3Info(songPath, info)


# def downImage(imgUrl, baseDir, musicName):
# 	print('-------------------- 1 --------------------------')
# 	print(imgUrl)
# 	img_ext = re.findall(r'\.[^.\\/:*?"<>|\r\n]+$', imgUrl)[0]
# 	fileName = baseDir + '/' + musicName + img_ext
# 	print(fileName)
# 	resp, content = httplib2.Http().request(imgUrl)
# 	print('-------------------- 2 --------------------------')
# 	print(resp)
# 	if resp['status'] == '200':
# 		f = open(fileName, 'wb')
# 		f.write(content)
# 		f.close()



if __name__ == '__main__':
	# baseDir = input("input path like : /user/music\n")
	baseDir = os.path.abspath(os.curdir)
	list = os.listdir(baseDir)
	for i in range(0,len(list)):
		fileName = list[i]
		filePathName = os.path.join(baseDir, fileName)
		filePostFix = os.path.splitext(filePathName)[1]
		if os.path.isfile(filePathName):
			if filePostFix == ".ncm":
				try:
					dump(baseDir, fileName, filePostFix)
				except:
					pass