Browse Source

more options in app #3

pull/4/head
Nzix 6 years ago
parent
commit
6663c2c2c6
  1. 29
      README.md
  2. 100
      app.py
  3. 18
      ncmdump.py

29
README.md

@ -2,20 +2,37 @@
## 简介
大佬的 [anonymous5l/ncmdump ](https://github.com/anonymous5l/ncmdump)项目,因好奇加密算法就用Python移植了下。自测发现转换出来的媒体文件都已包含媒体信息(包括专辑封面),~~故未再实现原repo中的写tag操作~~,应issue要求还是补上了写tag操作。Python实现比C++慢不少,实用性不大,仅供学习交流,请勿传播扩散。
大佬的 [anonymous5l/ncmdump ](https://github.com/anonymous5l/ncmdump)项目,因好奇加密算法就用Python移植了下。自测发现转换出来的媒体文件都已包含媒体信息(包括专辑封面),~~故未再实现原repo中的写tag操作~~,应issue要求还是补上了写tag操作。Python实现比C++慢不少,实用性不大,仅供学习交流,请勿传播扩散。
## 依赖
```
pip(3) install pycryptodome mutagen
$ pip install pycryptodome mutagen
```
## 使用
指定ncm文件
- 指定文件
```
python(3) ncmdump.py [files ...]
$ python ncmdump.py [files ...]
```
工作目录下所有ncm文件
- 遍历工作目录
```
python(3) ncmdump.py
$ python ncmdump.py
```
- 更多选项
```
$ python app.py -h
usage: ncmdump [-h] [-f format] [-o output] [-d] [input [input ...]]
positional arguments:
input ncm file or folder path
optional arguments:
-h, --help show this help message and exit
-f format customize naming format
-o output customize saving folder
-d delete source after conversion
```
> 自定义命名参数: %artist%, %title%, %album%

100
app.py

@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
"""
Created on Fri Sep 28 13:32:51 2018
@author: Nzix
"""
import argparse, os, sys, re
import ncmdump
parser = argparse.ArgumentParser(
prog = 'ncmdump'
)
parser.add_argument(
'input', metavar = 'input', nargs = '*', default = ['.'],
help = 'ncm file or folder path'
)
parser.add_argument(
'-f', metavar = 'format', dest = 'format', default = '',
help = 'customize naming format'
)
parser.add_argument(
'-o', metavar = 'output', dest = 'output',
help = 'customize saving folder'
)
parser.add_argument(
'-d', dest = 'delete', action='store_true',
help = 'delete source after conversion'
)
args = parser.parse_args()
def validate_name(file_name):
pattern = {u'\\': u'', u'/': u'', u':': u'', u'*': u'', u'?': u'', u'"': u'', u'<': u'', u'>': u'', u'|': u''}
for character in pattern:
file_name = file_name.replace(character, pattern[character])
return file_name
def validate_collision(file_path):
index = 1
while os.path.exists(file_path):
file_path = '({})'.format(index).join(os.path.splitext(file_path))
index += 1
return file_path
def name_format(path, meta):
information = {
'artist': ','.join([artist[0] for artist in meta['artist']]),
'title': meta['musicName'],
'album': meta['album']
}
def substitute(matched):
key = matched.group(1)
if key in information:
return information[key]
else:
return key
name = re.sub(r'%(.+?)%', substitute, args.format)
name = os.path.splitext(os.path.split(path)[1])[0] if not name else name
name = validate_name(name)
name += '.' + meta['format']
folder = args.output if args.output else os.path.dirname(path)
save = os.path.join(folder, name)
save = validate_collision(save)
return save
if args.output:
args.output = os.path.abspath(args.output)
if not os.path.exists(args.output):
print('output does not exist')
exit()
if not os.path.isdir(args.output):
print('output is not a folder')
exit()
files = []
for item in args.input:
item = os.path.abspath(item)
if not os.path.exists(item):
continue
if os.path.isdir(item):
files += [os.path.join(item, _file) for _file in os.listdir(item) if os.path.splitext(_file)[-1] == '.ncm']
else:
flles += [item]
if sys.version[0] == '2':
files = [file_name.decode(sys.stdin.encoding) for file_name in files]
if not files:
print('empty input')
exit()
for _file in files:
try:
_save = ncmdump.dump(_file, name_format)
print(os.path.split(_save)[-1])
if args.delete: os.remove(_file)
except KeyboardInterrupt:
exit()

18
ncmdump.py

@ -13,13 +13,14 @@ import os
from Crypto.Cipher import AES
from mutagen import mp3, flac, id3
def dump(file_path):
def dump(input_path, output_path = None):
output_path = (lambda path, meta : os.path.splitext(path)[0] + '.' + meta['format']) if not output_path else output_path
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')
f = open(input_path,'rb')
# magic header
header = f.read(8)
@ -69,8 +70,8 @@ def dump(file_path):
image_data = f.read(image_size)
# media data
music_path = os.path.splitext(file_path)[0] + '.' + meta_data['format']
m = open(music_path,'wb')
output_path = output_path(input_path, meta_data)
m = open(output_path,'wb')
while True:
chunk = bytearray(f.read(0x8000))
@ -89,7 +90,7 @@ def dump(file_path):
# media tag
if meta_data['format'] == 'flac':
audio = flac.FLAC(music_path)
audio = flac.FLAC(output_path)
# audio.delete()
image = flac.Picture()
image.type = 3
@ -98,7 +99,7 @@ def dump(file_path):
audio.clear_pictures()
audio.add_picture(image)
elif meta_data['format'] == 'mp3':
audio = mp3.MP3(music_path)
audio = mp3.MP3(output_path)
# audio.delete()
image = id3.APIC()
image.type = 3
@ -106,13 +107,15 @@ def dump(file_path):
image.data = image_data
audio.tags.add(image)
audio.save()
audio = mp3.EasyMP3(music_path)
audio = mp3.EasyMP3(output_path)
audio['title'] = meta_data['musicName']
audio['album'] = meta_data['album']
audio['artist'] = '/'.join([artist[0] for artist in meta_data['artist']])
audio.save()
return output_path
if __name__ == '__main__':
import sys
if len(sys.argv) > 1:
@ -132,4 +135,3 @@ if __name__ == '__main__':
except Exception as e:
print(e)
pass

Loading…
Cancel
Save