-
Notifications
You must be signed in to change notification settings - Fork 26
/
ncmTranslator.py
150 lines (140 loc) · 5.5 KB
/
ncmTranslator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/python3
# Modifier: Liang Lixin
# Folder dump version by LiangLixin
import binascii
import struct
import base64
import json
import os
import logging
import urllib
import requests
import time
from Crypto.Cipher import AES
music_suffix_list = ['mp3', 'wav', 'ape', 'flac', 'MP3', 'WAV', 'APE', 'FLAC']
def dump(file_path, file_name_no_suffix):
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(file_path,'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:]
meta_data = json.loads(meta_data)
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]
image_data = f.read(image_size)
file_name = file_name_no_suffix + '.' + meta_data['format']
## delete
# try:
# os.remove(os.path.join(os.path.split(file_path)[0], meta_data['musicName'] + '.' + meta_data['format']))
# print('删除文件 :' + os.path.join(os.path.split(file_path)[0], meta_data['musicName'] + '.' + meta_data['format']))
# ## delete
# except Exception as e:
# print('删除失败', e)
m = open(os.path.join(os.path.split(file_path)[0],file_name),'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()
try:
urllib.request.urlretrieve(meta_data['albumPic'], os.path.join(os.path.split(file_path)[0], file_name_no_suffix) + '.jpg');
except Exception as e:
print('下载专辑图片出错', e)
def file_extension(path):
return os.path.splitext(path)[1]
def file_no_extension(path):
return os.path.splitext(path)[0]
def file_exist(file_name, file_list, file_list_path):
for file in file_list:
if (os.path.isdir(os.path.join(file_list_path, file))):
# print('######### 文件夹, 跳过')
continue
for suffix in music_suffix_list:
if (file_no_extension(file_name) + "." + suffix) == file:
return True;
return False;
def recursion(file_name, root_dir, file_list):
# print('root_dir: ' + root_dir)
# print('file_name: ' + file_name)
full_file = os.path.join(root_dir, file_name)
if os.path.isfile(full_file):
print('>>>>>>>>>>>>>>>> 当前文件: ' + full_file)
if file_extension(full_file) == ".ncm":
# 校验文件存在同名
if file_exist(file_name, file_list, root_dir):
print('>>>>>>>>>>>>>>> 同名文件跳过: ' + full_file)
return
try:
print('>>>>>>>>>>>>>>> 开始转码文件: ' + full_file)
dump(full_file, file_no_extension(file_name));
print('>>>>>>>>>>>>>>> 转码文件成功: ' + full_file)
except Exception as err:
print('转码文件失败: ' + full_file + ' error: ' + err)
else:
print('>>>>>>>>>>>>>>> 非ncm文件, 跳过')
elif os.path.isdir(full_file):
print('>>>>>>>>>>>>>>> 处理当前文件夹内容: ' + full_file)
list = os.listdir(full_file)
for i in range(0, len(list)):
recursion(list[i], full_file, list)
if __name__ == '__main__':
import sys
if len(sys.argv) > 1:
rootdir = sys.argv[1]; #如果有输入路径参数,取当前路径参数
else:
# 没写路径默认走当前文件夹
rootdir = os.path.split(os.path.realpath(__file__))[0];
print('>>>>>>>>>>>>>>> 初始路径层级: ' + rootdir)
list = os.listdir(rootdir) # Get all files in folder.is
for i in range(0,len(list)):
try:
recursion(list[i], rootdir, list)
except Exception as e:
print('递归处理出现错误');
logging.exception(e)
finally:
pass
print('全部文件处理完成 ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()));